iter49-卡3:营地五界面真窗口化批(窗壳形态换装):WINDOW_SIZE 720×640单点+modal_shell窗壳骨架+career同壳+screen_flow_probe V1-V8窗壳形态段

This commit is contained in:
DKChan committed 2026-11-02 07:30:34 +08:00
1 parent 240f1a1769
commit 041d509b4d
19 files changed
+524 -132

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@
需求卡片 §8「改动—验收对照表」每一条都要落成可执行的验证;本文定统一写法,卡里定每批的具体内容(测什么/注入哪里)。写卡期查不出执行载体的条目=不可测,当场呈报,禁拖到实现期。
1. **执行载体五选一,逐条声明**:单元测试(`tests/run_all`)/系统测试断言(`tests/system_test.gd`)/场景探针(`probe/` 下 `*_probe`)/MC 同种子逐项比对/人工核对(仅限无法自动化的纪律类需求点,如命名锁定、数值占位)。行为类条目以自动化载体为主。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但每条都要有自己单独可判红的判据(取证口径见 §8.4 唯一口径——禁变异/禁撤销)。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。**工具面局限备案(2026-10-27 迭代 39 验收审计)**:`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言消息不进 `tools/extract_checks.py` 扫描面——验收尾缀 grep 对该通道失效;如需覆盖属提取器改版(工程批)。
2. **粒度与对应关系**:每条清单条目至少一个自动断言,且断言必须从该条目的真实入口进入;多条条目可合用一个测试函数,但每条都要有自己单独可判红的判据(取证口径见 §8.4 唯一口径——禁变异/禁撤销)。断言失败消息末尾带编号尾缀〔卡短名 Vn〕(如〔秘宝批 V3〕)——断言与卡片清单可互相 grep 查找。`Kit.check_quiet` 静默收集形态(计数不入判定即打印、失败消息进 problems 汇总)的断言已入 `tools/extract_checks.py` 扫描面(2026-11-02 提取器改版:三通道同口径提取,静默通道条目 quiet 列记 `channel:check_quiet`),验收尾缀 grep 对该通道有效。
3. **断言词汇按验证对象五选一**:
| 词汇 | 适用场景 |
@@ -1,7 +1,7 @@
# 锻造工坊
> 一句话:营地的武器与护甲成长——武器锻造区三条路线、护甲锻造区三条路线各练各的,钥石消费,一次冒险各选一件,满级特效留位本期不做
> 状态:**v0.10(2026-10-29 实现内容清理:删「现役代码事实」「实装边界」两节与正文代码指证,规则与数值零变化)**——全部数值 provisional,终值归 Alpha 收官 M4 数值批〔未排期〕
> 状态:**v0.11(2026-11-02 补验收标准节:九条结构面判据落文,规则与数值零变化)**——全部数值 provisional,终值归 Alpha 收官 M4 数值批〔未排期〕
## 设计目的
用户原案(playnote 2026-10-06):把现役"营地花钱买攻击 +1"改造成有等级、有成长、有满级特效的**武器升级路线**;新增长枪(穿刺)与法杖(魔法)实现武器间差异化;护甲同理——初始只有轻甲(升级只提 AC),最高等级后解锁重甲/魔法护甲,基础 AC 各异、各带满级特效。
@@ -209,3 +209,17 @@
### 10 · 数值与验证
全部数值 provisional 归 Alpha 收官 M4 数值批;实装批必须带改前 / 改后同随机种子配对读数验证难度影响。
## 验收标准
本节判据全部绑结构与关系、不绑具体数值:价格、增量、等级上限等行值 provisional 归 M4 数值批,改值不推翻判据;判据失效只发生在结构关系本身被改时。
- **柜台解锁与默认值解耦**:天赋树的锻造工坊钥匙节点(见[天赋树](天赋树.md))点亮后本柜台开放、可购买;未点亮时柜台锁定、不存在任何购买途径;未点亮期间武器/甲默认值照常经参数面注入冒险,不存在「未解锁就读不到默认值」的窗口(2026-10-11 树侧钥匙裁)。
- **路线解锁双通道并联**:长枪、法杖、重甲、魔法护甲四条路线的解锁条件全部在本系统内部、与关卡进度零绑定;每条路线同时开两条互不影响的通道——练满前一档、或支付一次性解锁价直购——任一通道成立即解锁(4b/4b-1 裁)。
- **窗口内容**:营地四柜台并排呈现;本柜台分武器、护甲两区,各列三条路线行,行名=件名+锻造;出场行显示当前选中(武器+甲);满级特效无窗口入口、无文案(本期不做)。
- **单选注入与树零攻防**:攻击加值唯一来源=当前选中武器路线、AC 加值唯一来源=当前选中甲路线;0 级=未练合法态、零加值;天赋树淬炼对攻击与 AC 零贡献;开局注入按「账号层基础值+树淬炼 flat+锻造加值+局内加算区」分层可加,锻造加值只取当前选中(2026-10-11 所有权切分裁)。
- **切换语义**:营地内任意武器×甲组合切换免费、即时生效;换装不丢级不生效——旧选中等级保留、加值待命;一次冒险=一个武器类型+一个甲型;冒险开始后锁死、局内不存在任何换装途径(1a/1b/1c 裁)。
- **定价结构**:每级价格随已购级数几何递增(第 N+1 级价格 > 第 N 级价格,基价与公比读表单点);路线解锁价一次性收取;钥石不足时购买被拒、余额与等级零变化;GP 全程不可用于本柜台(5b 裁)。
- **存档闭合**:六条路线等级+当前武器/甲选中标记写入存档,读档恢复与写入前一致;存档域无特效域(6 裁连带)。
- **祝福件负断言**:局内商店购买攻击/护甲祝福后,本柜台路线等级与选中态零变化(8a 裁连带——货架行名带「攻击」「护甲」字样,属性祝福与锻造升级是两条互不相通的通道)。
- **甲型双轴可感**:切换甲型后基础 AC 与挨普通攻击的克制修正(轻甲 0/重甲 −1/魔法护甲 +1)双轴同时变化(3b 裁;现行敌方攻击全部按普通处理,断言条件当下即成立)。
+2 -2
View File
@@ -28,7 +28,7 @@ var damage: DamageController
var emit_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形:head/ri/display/value/extra)
var facts_cb: Callable # 宿主 _fx_host_fact——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实宿主侧单点
var grant_cb: Callable = Callable() # 揭示兑付回调=RewardGrant 授予面(bind_grant 注入;构造序解法——本件对授予面零强引用)
var engine: EffectEngine # 效果引擎(原编排器 _fx——宿主 _init 经此转注 damage/action_economy 两 set_fx;reward_economy 经 luck_bias_for 查询口取回偏运合计)
var engine: EffectEngine # 效果引擎(原编排器 _fx——宿主 _init 经此建效果端口件转注两业务类 bind_port;reward_economy 经 luck_bias_for 查询口取回偏运合计)
var counters_ctx: EffectContext # 四桶引用束(round/battle 两桶本件自有;adventure/level 桶 begin_battle mount_buckets 挂引用)
var ac := {"base": -1, "round": 0, "battle": 0} # ac 束(base=主角 AC 基线快照 enc.hero_armor_class,-1=表值兜底未解析;round=ac_bonus 本轮累积器〔封印桩,round_start 清零〕;battle=本战累积器〔石肤,cap 封顶,begin_battle 清零〕)
var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末宿主 bonus_delta_drain 读后清零)
@@ -144,7 +144,7 @@ func apply_ac_bonus(h: Dictionary) -> void:
# 揭示效果入队(fx_reveal 掷格兑付延后到授予段末——防轮 facts 迭代中追加重入;见血开窍/
# 斩首学者〔经伤害控制器 fx_calls["reveal"] 回调〕/地质学家/跳格采掘共用)
# 斩首学者〔经伤害控制器端口 reveal_effect 路由〕/地质学家/跳格采掘共用)
func enqueue_reveal() -> void:
reveal.queue.append({"round": int(facts_cb.call("round_index"))})
-10
View File
@@ -14,21 +14,11 @@ const AUTOLOAD_PATH_RNG := "/root/RngService"
const AUTOLOAD_PATH_SAVE := "/root/SaveManager"
const AUTOLOAD_PATH_META := "/root/MetaProgression"
const UiAssembler := preload("res://presentation/ui_assembler.gd")
const Narrator := preload("res://presentation/battle_narrator.gd")
const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
const DebugSessionScript := preload("res://presentation/debug_session.gd")
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互
const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影
const BoardResolversScript := preload("res://presentation/board_resolvers.gd") # 棋盘解析回调族
const MainUiBuild := preload("res://presentation/main_ui_build.gd") # 装配分步承接件
const MainAdventureFlow := preload("res://presentation/main_adventure_flow.gd") # 冒险流程承接件
const MainOverlayFlow := preload("res://presentation/main_overlay_flow.gd") # 覆盖层流程承接件
const MainDebugFlow := preload("res://presentation/main_debug_flow.gd") # 调试流程承接件
const MenuScreenScene := preload("res://presentation/scenes/menu_screen.tscn") # 三屏壳场景
const CampScreenScene := preload("res://presentation/scenes/camp_screen.tscn")
const AdventureScreenScene := preload("res://presentation/scenes/adventure_screen.tscn")
var db
var run_scope = null # 会话状态承载(run_scope.gd;_ready 即建闲时态,永非 null)
+14 -18
View File
@@ -1,5 +1,6 @@
## 营地子界面模态壳基类(锻造工坊/秘宝匣/天赋树/奖励格图鉴四子界面公共形态):
## 实底全屏暖亮档+石纹暗角+顶栏(铭牌+钥石徽)+内容列+底栏返回钮。
## 营地子界面功能窗口基类(锻造工坊/秘宝匣/天赋树/奖励格图鉴四子界面公共形态):
## 居中定尺寸窗壳(石板皮窗板+半透明压暗遮罩)+顶栏(铭牌+钥石徽)+内容列+底栏返回钮。
## 窗几何单点=WINDOW_SIZE(camp.md v2.5 起点 720×640);窗内滚动=内容体超高由子类滚动容器承载(窗不随内容涨)。
## 开合可见性归模态栈(登记层开合一律走栈);返回钮只发 closed——关层编排归 presenter(组合根单口)。
## 余额徽随子类 refresh_camp 刷新(钥石读数=投影单口,禁第二账本)。
class_name CampSubViewBase
@@ -7,24 +8,19 @@ extends Control
signal closed
const WINDOW_SIZE := Vector2(720, 640) # 功能窗口起点定值单点(camp.md v2.5——生涯页经本常量引用,禁第二处字面值)
var _balance_label: Label # 钥石徽数值行(探针直读面——ui_probe 余额文案断言改指新界面)
## 壳构建(子类 _init 调用一次):content 由子类先建好传入(列容器/滚动容器皆可)
## 壳构建(子类 _init 调用一次):content 由子类先建好传入(列容器/滚动容器皆可)。
## 窗壳骨架=UiKit.modal_shell(dim 全屏压暗+CenterContainer 居中+石板皮窗板;min_size 参数保持 ZERO
## 使窗板尺寸经 custom_minimum_size 精确可钉 720×640——panel_skinned 边距被 PANEL_SLICE 钳到 16 为既有工厂行为)
func _build_shell(title: String, icon_path: String, content: Control) -> void:
# 半透明压暗遮罩 0.6(camp.md v2.3 定稿——场景根留底可见,窗口之下垫遮罩;交互聚焦由遮罩 STOP 兜底)
var dim := UiKit._modal_dim(self, Color(UiKit.DIM, 0.6))
var margin := MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 16)
margin.add_theme_constant_override("margin_right", 16)
margin.add_theme_constant_override("margin_top", 12)
margin.add_theme_constant_override("margin_bottom", 12)
dim.add_child(margin)
var col := VBoxContainer.new()
col.add_theme_constant_override("separation", 8)
margin.add_child(col)
_build_top_bar(col, title, icon_path)
# 半透明压暗遮罩 0.6(camp.md v2.5——场景根留底可见,窗口之下垫遮罩;交互聚焦由遮罩 STOP 兜底)
var shell := UiKit.modal_shell(self, Color(UiKit.DIM, 0.6), Vector2.ZERO, 8, UiKit.panel_skinned(Vector4(14, 12, 14, 12)))
shell.panel.custom_minimum_size = WINDOW_SIZE
_build_top_bar(shell.box, title, icon_path)
content.size_flags_vertical = Control.SIZE_EXPAND_FILL
# 内容米板身(柜台行板对比底=PanelData——旧柜台面板形态延续,行板墨色对比表不脱锚)
var body := PanelContainer.new()
@@ -32,8 +28,8 @@ func _build_shell(title: String, icon_path: String, content: Control) -> void:
UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(10, 10, 10, 10)))
body.size_flags_vertical = Control.SIZE_EXPAND_FILL
body.add_child(content)
col.add_child(body)
_build_bottom_bar(col)
shell.box.add_child(body)
_build_bottom_bar(shell.box)
visible = false # 登记模态初始隐(开合走栈)
+43 -17
View File
@@ -1,6 +1,6 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 生涯页·只读全屏页:跨局终身计数汇总(不统计天赋树与账号成长态)。
## 入口唯一在开始覆盖层「营地与系统」组、局内不放;页内零跳转零动效(直开直关)。
## 生涯页·只读功能窗口:跨局终身计数汇总(不统计天赋树与账号成长态)。
## 入口=营地场景图帐篷旁笔记本;窗内零跳转零动效(直开直关)。
## 构成件全部经 UiKit 工厂产出——禁新素材/新色值/自建样式。
class_name CareerOverlay
extends Control
@@ -16,28 +16,37 @@ var _keystone_badge: UiKit.KeystoneBadge # 钥石余额(badge 工厂件—
func _init() -> void:
# 全屏页壳(T6/T7 对齐批先例——暖亮底+石纹弱质感+暗角+56px 通栏顶条+内容列)
var page: Dictionary[Variant, Variant] = UiKit.page_shell(self, true)
page.base.color = Color(UiKit.DIM, 0.6) # 功能窗口化(camp.md v2.3 定稿——场景根留底+半透明压暗遮罩 0.6;观感随样张目检校准)
var top: HBoxContainer = page.top_row
top.add_child(UiKit.label("生 涯",UiKit.FS_TITLE, true, UiKit.WARM))
var top_sp := Control.new()
top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(top_sp)
var close_btn := UiKit.button("返回", 90.0) # 紧凑返回钮(设置/状态面板先例形制)
close_btn.tooltip_text = "关闭生涯页,返回主菜单(本页只读,不发生任何购买或修改)"
close_btn.pressed.connect(_emit_closed)
top.add_child(close_btn)
# 功能窗口壳(camp.md v2.5 生涯页与四子界面同壳同形态——居中定尺寸窗板+半透明压暗遮罩 0.6;
# 窗几何单点=CampSubViewBase.WINDOW_SIZE,禁第二处字面值)
var shell := UiKit.modal_shell(self, Color(UiKit.DIM, 0.6), Vector2.ZERO, 8, UiKit.panel_skinned(Vector4(14, 12, 14, 12)))
shell.panel.custom_minimum_size = CampSubViewBase.WINDOW_SIZE
_build_top_row(shell)
var box: VBoxContainer = page.box
# 内容列滚动面(窗内滚动——窗尺寸不随内容增长;横向禁滚同四子界面形态)
var scroll := ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
shell.box.add_child(scroll)
# 内容板(BG_BRIGHT——原页底色随壳迁移,内容板文字 token 零改——契约 9 口径)
var body := PanelContainer.new()
body.add_theme_stylebox_override("panel",
UiKit.panel_flat(UiKit.BG_BRIGHT, UiKit.PANEL_BORDER, UiKit.RADIUS_OBJECT, 1, Vector4(12, 12, 12, 12)))
body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.add_child(body)
var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 8)
body.add_child(box)
var hint := UiKit.label("跨局生涯汇总(只读——购买与装配在营地;历史冒险回看随后续批次)",
UiKit.FS_HINT, false, UiKit.WARM_SOFT)
hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART # 窗内换行——单行 min 宽会把定尺寸窗板撑宽(720 契约)
hint.size_flags_horizontal = Control.SIZE_EXPAND_FILL
box.add_child(hint)
# 组一·冒险战绩(终身计数域——存档 v6:总局数开局即计、冒险成功局数三关全通才计)
# 对比度口径:页底=暖亮档深棕(BG_BRIGHT),文字全用亮墨族 token(WARM 9.0:1 / WARM_SOFT 8.0:1 /
# 对比度口径:内容板底=暖亮档深棕(BG_BRIGHT),文字全用亮墨族 token(WARM 9.0:1 / WARM_SOFT 8.0:1 /
# CANDLE 7.2:1 / INK_L_GOLD 9.1:1 实测对 BG_BRIGHT 全过 §7.2 ≥4.5:1——深墨族〔INK_LABEL〕
# 压深棕底仅 ~1.3:1 属违例,禁用)
# 压深棕底仅 ~1.3:1 属违例,禁用;顶行标题墨压石板皮不在此列——INK_TITLE 深墨族先例)
_section_head(box, "冒险战绩")
_total_label = _row(box, "冒险总局数")
_cleared_label = _row(box, "冒险成功局数")
@@ -58,6 +67,21 @@ func _init() -> void:
visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律)
# 顶行(「生 涯」标题+「返回」钮——文案与连接零改;标题墨随亮底石板皮惯例订正 INK_TITLE——设置/状态面板先例)
func _build_top_row(shell: Dictionary[Variant, Variant]) -> void:
var top := HBoxContainer.new()
top.add_theme_constant_override("separation", 10)
(shell.box as VBoxContainer).add_child(top)
top.add_child(UiKit.label("生 涯",UiKit.FS_TITLE, true, UiKit.INK_TITLE))
var top_sp := Control.new()
top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
top.add_child(top_sp)
var close_btn := UiKit.button("返回", 90.0) # 紧凑返回钮(设置/状态面板先例形制)
close_btn.tooltip_text = "关闭生涯页,返回主菜单(本页只读,不发生任何购买或修改)"
close_btn.pressed.connect(_emit_closed)
top.add_child(close_btn)
# ---- 开层(组合根唯一调用口——快照随开随建,只读同源口径同状态面板 R4)----
func open_career(data: Dictionary) -> void:
@@ -91,5 +115,7 @@ func _row(box: VBoxContainer, name_text: String) -> Label:
name_l.custom_minimum_size = Vector2(150, 0)
line.add_child(name_l)
var val_l := UiKit.label("—",UiKit.FS_BODY, true, UiKit.CANDLE)
val_l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART # 长值窗内换行——定尺寸窗禁单行 min 宽撑宽(同 hint 口径)
val_l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
line.add_child(val_l)
return val_l
+1 -1
View File
@@ -230,7 +230,7 @@ static func motion(dur: float) -> float:
# 的字形/无演出回退路径;禁 preload(素材缺位树上直接断言炸脚本,冒烟安全红线)
const TEX_HERO_IDLE := "res://assets/art/character/character_hero_idle.png"
const TEX_HERO_WALK := "res://assets/art/character/character_hero_walk.png"
const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘·normal 态(48px 档)
const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘·normal 态(素材=原稿分辨率,显示档缩放读 standee_motion PORTRAIT_DISPLAY_H)
const TEX_HERO_ATTACK := "res://assets/art/character/character_hero_attack.png" # B 区站桩立绘·attack 态(三态切换)
const TEX_HERO_HIT := "res://assets/art/character/character_hero_hit.png" # B 区站桩立绘·hit 态(三态切换)
# 敌种立绘(B 区站桩)——文件键 = 数据表主键 enemy_id(圣经 §8.3 命名规范),
+204
View File
@@ -28,6 +28,7 @@ func _ready() -> void:
await _step_menu_to_camp()
await _step_camp_back_to_menu()
await _step_camp_esc_and_hud() # 卡⑤——Esc 四态路由/三出口/设置语境分支/HUD 读数
await _step_camp_window_shell() # 真窗口化批——五窗统一窗壳形态 V1–V8(插 Esc 段之后、选档段之前)
await _step_save_select_open_back() # 迭代 33 卡③ sl10——选档开合+返回路径+ESC 语境不响应
await _step_menu_to_adventure()
await _step_level_to_battle_and_back()
@@ -295,6 +296,209 @@ func _step_camp_esc_and_hud() -> void:
_problems.append("[esc] 非营地语境 Esc 唤起浮窗(契约 9④ 失守)〔ESC批 V1〕")
# ---- ③'' 营地五窗统一窗壳(真窗口化批 V1–V8——真实进档入口+真实信号路由+真实钮/键事件)----
# 判的是呈现形态换壳(近全屏面板→居中定尺寸窗壳 720×640):窗几何/场景根留底/遮罩 0.6/关闭钮/
# 互斥单开/ESC 优先序/窗内滚动/生涯同壳——行为面(开合路由)零改,只钉窗壳可观测提交面。
func _step_camp_window_shell() -> void:
ProbeUiUtil.isolate_save(main, "smoke_screen_flow_winshell", false, false, true)
if not ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_winshell"):
_problems.append("[winshell] 营地入口缺失(选档层新档钮驱动中断)〔窗口化批 V1〕")
return
await _frames()
var views := {
&"forge": main.ui.modals.camp.forge,
&"chest": main.ui.modals.camp.treasure,
&"dummy": main.ui.modals.camp.tree,
&"board": main.ui.modals.camp.codex,
}
var career: Control = main.ui.modals.menu.career_overlay
var five: Array[Control] = [views[&"forge"], views[&"chest"], views[&"dummy"], views[&"board"], career]
var vp: Vector2 = main.get_viewport().get_visible_rect().size
var opened: Array[StringName] = []
for k: StringName in [&"forge", &"chest", &"dummy", &"board"]:
# 每窗先经真实关闭钮关前窗(V4 四窗半边——轮转间钉关闭链)
if not opened.is_empty():
var prev: Control = views[opened.back()]
var prev_back: Button = ProbeUiUtil.find_button(prev, "返回营地")
if prev_back == null:
_problems.append("[winshell] %s 底栏「返回营地」钮缺失〔窗口化批 V4〕" % String(opened.back()))
else:
prev_back.pressed.emit()
await _frames()
if prev.visible or not main.ui.modals.camp.scene.visible:
_problems.append("[winshell] %s 关闭钮按下未隐窗或场景根失守〔窗口化批 V4〕" % String(opened.back()))
main.ui.modals.camp.scene.object_pressed.emit(k)
await _frames()
opened.append(k)
var view: Control = views[k]
var panel: PanelContainer = _window_panel(view)
if panel == null:
_problems.append("[winshell] %s 窗板缺失(CenterContainer 下无 PanelContainer)〔窗口化批 V1〕" % String(k))
continue
var rect: Rect2 = panel.get_global_rect()
# V1 四窗几何:位置 ≈ (280, 80)、尺寸 ≈ (720, 640)(浮点容差 0.01——禁裸 ==)
if absf(rect.position.x - 280.0) > 0.01 or absf(rect.position.y - 80.0) > 0.01 \
or absf(rect.size.x - 720.0) > 0.01 or absf(rect.size.y - 640.0) > 0.01:
_problems.append("[winshell] %s 窗板几何偏移(实测 %s 应≈P(280,80) S(720,640))〔窗口化批 V1〕" % [String(k), str(rect)])
# V2 场景根留底:场景根 visible 且窗板四边距视口边 ≥80px
if not main.ui.modals.camp.scene.visible:
_problems.append("[winshell] %s 开态场景根不可见(栈语义失守)〔窗口化批 V2〕" % String(k))
elif rect.position.y < 80.0 - 0.01 or vp.y - rect.end.y < 80.0 - 0.01 \
or rect.position.x < 80.0 - 0.01 or vp.x - rect.end.x < 80.0 - 0.01:
_problems.append("[winshell] %s 窗板四边距视口边 <80px(场景留底不足)〔窗口化批 V2〕" % String(k))
# V3 遮罩 0.6:视图首个子节点 ColorRect alpha≈0.6 且全屏(容差 0.01)
var dim := view.get_child(0)
if not (dim is ColorRect):
_problems.append("[winshell] %s 首子节点非遮罩件(ColorRect 缺位)〔窗口化批 V3〕" % String(k))
else:
var cr := dim as ColorRect
if absf(cr.color.a - 0.6) > 0.01 or absf(cr.size.x - vp.x) > 0.01 or absf(cr.size.y - vp.y) > 0.01:
_problems.append("[winshell] %s 遮罩 alpha/尺寸偏离(实测 a=%.3f size=%s)〔窗口化批 V3〕" % [String(k), cr.color.a, str(cr.size)])
# V5a 互斥单开:五窗集 visible 计数 == 1
var vis_n := 0
for w in five:
if w.visible:
vis_n += 1
if vis_n != 1:
_problems.append("[winshell] %s 开态五窗 visible 计数=%d 应=1(互斥单开失守)〔窗口化批 V5〕" % [String(k), vis_n])
# 树窗点选遮挡(V5b——树窗开态向锻炉命中区推真实鼠标点:被遮罩挡=零新窗零响应)
main.ui.modals.camp.scene.object_pressed.emit(&"dummy")
await _frames()
var tree_view: Control = views[&"dummy"]
if not tree_view.visible:
_problems.append("[winshell] 树窗未开(V5b 前置中断)〔窗口化批 V5〕")
else:
var hotspot: Rect2 = main.ui.modals.camp.scene.get_hotspot_rect(&"forge")
var c := hotspot.get_center()
_push_click(c)
_push_release(c)
await _frames()
var vis_n2 := 0
for w in five:
if w.visible:
vis_n2 += 1
if vis_n2 != 1 or not tree_view.visible:
_problems.append("[winshell] 窗开态点穿场景(visible=%d 树窗=%s——遮罩未挡)〔窗口化批 V5〕" % [vis_n2, str(tree_view.visible)])
# 清窗经真实钮(真实 UI 中生涯入口只在无窗态可达——遮罩挡场景点选;信号直发的轮转残留
# 随栈 close 恢复语义逐层清账,V6/V8 从干净单窗态起步)
await _close_all_windows(views)
# V6 ESC 先关窗(生涯窗开态 Esc→关生涯窗、浮窗不唤起)
main.ui.modals.camp.scene.object_pressed.emit(&"notebook")
await _frames()
if not career.visible:
_problems.append("[winshell] 生涯窗未开(V6 前置中断)〔窗口化批 V6〕")
else:
_push_esc()
await _frames()
if career.visible:
_problems.append("[winshell] 生涯窗开态 Esc 未关窗(ESC 优先序失守)〔窗口化批 V6〕")
if main.ui.modals.camp.esc_overlay.visible:
_problems.append("[winshell] 关窗同拍唤起浮窗(应无窗才进浮窗)〔窗口化批 V6〕")
# V7 内容超高窗内滚动(树窗+真实投影喂加长行册)
main.ui.modals.camp.scene.object_pressed.emit(&"dummy")
await _frames()
if not tree_view.visible:
_problems.append("[winshell] 树窗未开(V7 前置中断)〔窗口化批 V7〕")
else:
var data: Dictionary = main._camp_data()
var rows: Array = data.get("tree", []) as Array
for i in 30:
rows.append({"node_id": "probe_row_%d" % i, "display_name": "探针加长行 %d" % i,
"vein": "spine", "kind": "stat", "level": 0, "cap": 1, "price": 0, "can_buy": false})
data["tree"] = rows
(tree_view as TreeView).refresh_camp(data)
await _frames(3) # queue_free 摘行与 min size 重算帧末延迟
var panel7: PanelContainer = _window_panel(tree_view)
var rect7: Rect2 = panel7.get_global_rect()
if absf(rect7.position.x - 280.0) > 0.01 or absf(rect7.position.y - 80.0) > 0.01 \
or absf(rect7.size.x - 720.0) > 0.01 or absf(rect7.size.y - 640.0) > 0.01:
_problems.append("[winshell] 加长行册后窗板涨形(实测 %s——窗不随内容涨失守)〔窗口化批 V7〕" % str(rect7))
var holder: VBoxContainer = (tree_view as TreeView)._tree_holder
var scroll := holder.get_parent() as ScrollContainer
if not (holder.size.y > scroll.size.y):
_problems.append("[winshell] 内容实高 %f 未超滚动视口 %f(超高实据不足)〔窗口化批 V7〕" % [holder.size.y, scroll.size.y])
# 清窗经真实钮(同 V6 前置——干净单窗态起步)
await _close_all_windows(views)
# V8 生涯同壳(窗板同几何+返回钮在位+内容列滚动面在)
main.ui.modals.camp.scene.object_pressed.emit(&"notebook")
await _frames()
if not career.visible:
_problems.append("[winshell] 生涯窗未开(V8 前置中断)〔窗口化批 V8〕")
else:
var panel8: PanelContainer = _window_panel(career)
if panel8 == null:
_problems.append("[winshell] 生涯窗板缺失〔窗口化批 V8〕")
else:
var rect8: Rect2 = panel8.get_global_rect()
if absf(rect8.position.x - 280.0) > 0.01 or absf(rect8.position.y - 80.0) > 0.01 \
or absf(rect8.size.x - 720.0) > 0.01 or absf(rect8.size.y - 640.0) > 0.01:
_problems.append("[winshell] 生涯窗板几何偏移(实测 %s)〔窗口化批 V8〕" % str(rect8))
if ProbeUiUtil.find_button(career, "返回", "prefix") == null:
_problems.append("[winshell] 生涯「返回」钮缺失〔窗口化批 V8〕")
var scrolls := career.find_children("*", "ScrollContainer", true, false)
var scroll_ok := false
for s in scrolls:
if (s as ScrollContainer).size_flags_vertical == Control.SIZE_EXPAND_FILL:
scroll_ok = true
if not scroll_ok:
_problems.append("[winshell] 生涯内容列滚动面缺失(ScrollContainer 纵向 EXPAND_FILL)〔窗口化批 V8〕")
# 段尾收闭:生涯窗经真实「返回」钮关闭(on_camp_closed 关闭名单不含生涯窗——防残留泄段)
var career_back: Button = ProbeUiUtil.find_button(career, "返回", "prefix")
if career_back != null:
career_back.pressed.emit()
await _frames()
if career.visible:
_problems.append("[winshell] 生涯「返回」钮未隐窗〔窗口化批 V4〕")
main._on_camp_closed() # 复归菜单语境(后续选档段前提不受扰)
await _frames()
# 清窗经真实钮(逐窗巡检 visible→钮 pressed——栈 close 恢复账随层清空)
func _close_all_windows(views: Dictionary) -> void:
for k: StringName in [&"forge", &"chest", &"dummy", &"board"]:
var w: Control = views[k]
if not w.visible:
continue
var back: Button = ProbeUiUtil.find_button(w, "返回营地")
if back == null:
_problems.append("[winshell] %s 底栏「返回营地」钮缺失〔窗口化批 V4〕" % String(k))
continue
back.pressed.emit()
await _frames()
# 窗板定位(视图子树固定链:首子节点=遮罩 ColorRect → CenterContainer → 窗板 PanelContainer)
func _window_panel(view: Control) -> PanelContainer:
if view.get_child_count() == 0:
return null
var dim := view.get_child(0)
if dim.get_child_count() == 0:
return null
var center := dim.get_child(0)
if center.get_child_count() == 0 or not (center.get_child(0) is PanelContainer):
return null
return center.get_child(0) as PanelContainer
# 真实鼠标合成(press+release 成对——BaseButton pressed 信号默认 release 触发,ui_input_probe :718 先例)
func _push_click(pos: Vector2) -> void:
var ev := InputEventMouseButton.new()
ev.position = pos
ev.global_position = pos
ev.button_index = MOUSE_BUTTON_LEFT
ev.pressed = true
main.get_viewport().push_input(ev, true)
func _push_release(pos: Vector2) -> void:
var ev := InputEventMouseButton.new()
ev.position = pos
ev.global_position = pos
ev.button_index = MOUSE_BUTTON_LEFT
ev.pressed = false
main.get_viewport().push_input(ev, true)
func _push_esc(echo := false) -> void:
var ev := InputEventKey.new()
ev.keycode = KEY_ESCAPE
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

@@ -0,0 +1,75 @@
# 工程改动报告——工程清理批(2026-11-02)
接收方:待办清单 7 条目(本批完成整条删除——palette_check 基线建档/CAMP_HEADER_BAND 评估/ui_kit 立绘注释/迭代 43 四色墨断言/断言提取器 check_quiet/run_all_gates 02 传参〔已完成漏删〕/main.gd 死 preload)。
## 一、批定位
工程改动批(用户发令直改,不写卡)。worktree `eng-cleanup`,基点 7e9e57fd,基点即 master 尖端。七件:六件本批落地+一件核查确认已完成(run_all_gates 02 传参)。
## 二、逐件落地
### 件 1 palette_check 基线建档+双门机检化
- 基线目录 `game/tests/golden/palette_baseline/`(图像白名单在册目录)两张条带 PNG:`ui_tokens.png` 92 色(ui_kit.gd 全量 Color8 常量唯一值快照)+ `art_snapshot.png` 1274 色(现役素材 128 张 PNG 唯一色并集快照);并集 1346 色=双门②「新增色 vs 既有色板」的既有色板机检承载物。
- 新件 `tools/gen_palette_baseline.py`:基线建档与刷新器(token 提取剔除注释行;素材目录零 PNG 拒绝生成空快照)。
- `tools/palette_check.py` 零代码改动——头注补基线构成与维护纪律、退出码描述校正(单张超 32 色是 WARN 非 FAIL)。
- 闸机接线:`tools/run_all_gates.sh` 新增 **05b 色板闸**(code 域;--quick 与影响面选闸 SKIP;素材齐盘预检之后段执行)——扫 `game/assets/art` 全目录对基线判新增色近色红。
- 口径边界(呈报):门①语义检查属设计面人工核对(§4.2 色相语义判域),不入本器执法面;48 token 计数上限不接闸——ui_kit 现役唯一色值 92 与「48 上限」的成员名单口径差归 art-bible §4.1 owner 裁定,本批不自裁。
- 维护纪律:素材批收尾跑 `python tools/gen_palette_baseline.py` 重生成全套并随批提交——新色过门后须入基线,否则下批扫描把已过门的新色再报新增;两张 PNG 缺一即漏判。
### 件 2 extract_checks.py 补 check_quiet 静默通道
- `CALL_RE` → `check(?:_quiet)?\(`、`DEF_RE` 同扩(func check / func check_quiet 定义行双拦)、`collect_call` 按实际匹配长度偏移;check_quiet 通道契约 cond 在前,条目 quiet 列记 `channel:check_quiet`。单趟正则无重叠匹配,结构上不双计。
- 自证三重(当轮实跑):①双计等式——旧两正则匹配数之和 2155 == 新 CALL_RE 2155(全 tests 面);②新旧版条目对拍 1719 → 2133,差 414 条 check_quiet 入账(grep 调用行实数 416,差 2=定义行与注释行不入账,方向自洽);③`--extract` 生成临时清单后 `--diff` 往返对账 PASS 零差异(标签/cond/计数三对齐)。
- 验证集清单已随验证体系整改阶段 2 退役,`--diff` 无存档参照——对账自证以临时清单往返承载,语义等同。
- 随批换代 `design/engineering/test-conventions.md` §5.2 工具面局限备案句(2026-10-27 立的「断言消息不进扫描面——验收尾缀 grep 对该通道失效」已消除,留存会漂移);文档设计面仅此一句现状描述更新,申报。
### 件 3 意图徽标四色墨机检断言
- `tools/render_sample_sheet_v3.py` `run_screen_checks` 增 **S1b**:意图徽标四色墨(攻击/增益/减益/蓄力)对 PANEL_DATA 垫牌(#E9D9B8,与绘制矩形 fill 同源)≥4.5:1 色值对表断言,四墨值即脚本意图色表四值,零新增色。
- 当轮真实读数:攻击 5.62/增益 5.10/减益 4.97/蓄力 5.21——与独立审计复算区间 4.97–5.62 逐位一致。
- 样张机检 31/31 全绿。V10 改前基线对拍图属 artwork(不入 git)环境件,worktree 用 `--save-baseline` 重建(本批零素材零绘制变更,基线等值)。
### 件 4 main.gd 拆件遗留死 preload 清理
- 逐条 grep 复核(登记纪律):死码 **10 条**——登记 8 条,复核多出 2 条(三场景 preload 中 AdventureScreenScene 登记时另有消费面、UiAssembler 登记时在役;现全零)。UiAssembler 唯一非定义出现=组合根接线段注释提及类名(非常量使用)。
- 7 具名(TuneSimController/DebugSessionScript/TreasureInteractController/CurioInteractController/CampShopPresenter/BoardResolversScript 等)消费面已迁 `main_ui_build.gd:9-13`(拆件自持 preload)——main.gd 旧常量=拆件残留重复声明;三场景消费面=system_test.gd `load()` 与 main.tscn,删常量不触。
- 删除 10 条 const preload,保留 Narrator/MainUiBuild/MainAdventureFlow/MainOverlayFlow/MainDebugFlow 五条在役。
### 件 5 过时注释换代
- `game/presentation/views/ui_kit.gd` TEX_HERO_PORTRAIT 行注释「48px 档」→「素材=原稿分辨率,显示档缩放读 standee_motion PORTRAIT_DISPLAY_H」(迭代 48 显示档批现行口径)。
- `tools/path_check.py` 头注删「· 立绘定档批 2026-10-07」日期批注记(变更叙事黑名单类);47 行键集指名核实现行仍在役(standee_asset_check 现行 import STANDEE_DISPLAY_H),不改。
### 件 6 ui_kit CAMP_HEADER_BAND 令牌退役评估
现役消费面 4 处逐一核过:`level_map_screen.gd:107/144`(关卡屏头带与地图条)、`shop_overlay_view.gd:49`(商店头带)、`ui_kit.gd` KeystoneBadge 工厂(钥石徽描边——消费方=营地覆盖层+状态面板钥石余额行)。
**结论**:「退役删除」不成立——4 处在用且全是活面板绘制面;营地域柜台头带语义岗已随迭代 47 四柜台覆盖层退役,令牌现役值 #6E4526 只剩材质棕底色语义(木色头带/徽章描边)。**处置=改名中性化**(脱 CAMP_ 前缀,名随现役语义,如 WOOD_BAND 一族)归 ui_kit 令牌批顺带;改名零值变化零渲染变化,届时逐点核后动。维持原登记口径「退役归 ui_kit 令牌批顺带处置、非独立强删」。
### 件 7 run_all_gates 02 关联档传参(核查确认已完成)
`d32ff342`(工程小件批·件 1)已落两案中的「解析器补逗号切分」案:`run_all.gd` 选跑解析补 `split(",")`,闸脚本逗号串传参与之配套,孤立验证四套件全绿。本批零改动,条目属完成漏删。
## 三、待办清单
删 7 条(整条删除,状态机出口):palette_check 基线建档/CAMP_HEADER_BAND 评估/ui_kit 立绘注释/迭代 43 移交小件(①能力清单补行已随 2026-10-31 写卡批落位、②四色墨断言本批落)/断言提取器改版/run_all_gates 02 传参(d32ff342 已完成)/main.gd 死 preload。流水线闸复验 PASS(33 条目/49 总表行)。
## 四、窄验读数(worktree 内当轮实跑)
| 验证 | 读数 |
|---|---|
| palette_check 带基线扫 art 全目录 | 128 张 0 红 0 新增色 |
| presentation 单元套件(main.gd/ui_kit.gd 编译面) | 17 suites 全绿 |
| 冒烟 run_smoke | 15 项 0 失败,三闸全过 |
| 样张 render_sample_sheet_v3 | 31/31 全绿(含新 S1b) |
| path_check | 0 处问题 |
| extract_checks | 双计等式过/1719→2133/往返对账 PASS |
| pipeline_gate | PASS(33 条目/49 总表行/交叉核 3 链) |
全量回归归合回后本地 CI(post-merge 钩子自动跑,含新 05b 色板闸)。
## 五、边界与移交
- 48 token 计数上限的机检承载与门①语义检查不属本批(口径归 art-bible owner),呈报待裁。
- 05b 色板闸的基线维护义务(素材批收尾刷新)已写进 palette_check.py 头注与 gen_palette_baseline.py 头注,闸机注释同步点名。
-1
View File
@@ -45,7 +45,6 @@ STATUS_VALUES = ("已覆盖", "部分覆盖", "未覆盖")
# 验收标准节豁免:无可写验收面的在册件(逐件附理由;新豁免须经用户裁)
ACCEPT_EXEMPT = {
"锻造工坊.md": "工程化系统,验收面由天赋树与元进度两篇承接,本批迁移实现事实后复核",
"生涯.md": "P6 后置未实装,无现成可观测面;实装批补验收标准节",
}
+24 -19
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
r"""extract_checks.py — 断言静态提取器(验证集清单生成与出口对账)
机器提取宿主脚本的全部静态 check( 调用点(每个调用点记录:所在文件、所在函数、
机器提取宿主脚本的全部静态断言调用点(每个调用点记录:所在文件、所在函数、
行号、标签字符串字面量原文、cond 表达式原文),提供两种模式:
用法:
@@ -19,10 +19,11 @@ r"""extract_checks.py — 断言静态提取器(验证集清单生成与出口
断言,对账时以「新增」出现(非迁移面),出口判定以显式模式为准。
提取口径:
- 静态 check( 调用点,两种通道都认:smoke/系统宿主的 check(cond, label)(cond 在前)
与单元/集成宿主的 Kit.check("label", cond)(点前缀=标签在前);`_check(` 等同名
前缀调用非标准通道,不取。
- 调用点可跨行:从 `check(` 起括号配平归集完整实参文本(() [] {} 三种括号都计深度);
- 静态断言调用点,三通道都认:smoke/系统宿主的 check(cond, label)(cond 在前)、
单元/集成宿主的 Kit.check("label", cond)(点前缀=标签在前)、静默收集通道
Kit.check_quiet(cond, label)(cond 在前,条目 quiet 列记 channel:check_quiet);
`_check(` 等同名前缀调用非标准通道,不取。
- 调用点可跨行:从调用名起括号配平归集完整实参文本(() [] {} 三种括号都计深度);
字符串字面量内的括号/逗号/井号、行尾注释、转义序列均不参与结构判定(码位掩码单趟预扫)。
- cond 摘要=cond 原文跨行折叠为单行(连续空白并一空格);标签原文=标签实参里的字符串
字面量逐字保真(含引号与转义序列原样,禁改写);标签实参若带格式化/拼接后缀
@@ -64,8 +65,8 @@ def default_sources():
files.extend(sorted(p.rglob("*.gd")))
return sorted(files)
CALL_RE = re.compile(r"check\(")
DEF_RE = re.compile(r"^\s*(static\s+)?func\s+check\s*\(")
CALL_RE = re.compile(r"check(?:_quiet)?\(")
DEF_RE = re.compile(r"^\s*(static\s+)?func\s+check(?:_quiet)?\s*\(")
FUNC_RE = re.compile(r"^(static\s+)?func\s+([A-Za-z_][A-Za-z0-9_]*)")
LITERAL_RE = re.compile(r'"((?:[^"\\]|\\.)*)"')
OPENERS = "([{"
@@ -188,9 +189,9 @@ def split_args(body):
return args
def collect_call(joined, start):
"""从 check( 的 start 起括号配平归集调用。返回 (body, end_pos);不配平返回 None。"""
i = start + len("check(")
def collect_call(joined, start, call_len):
"""从调用名起点起括号配平归集调用。返回 (body, end_pos);不配平返回 None。"""
i = start + call_len
depth = 1
in_str = False
parts = []
@@ -228,7 +229,7 @@ def collect_call(joined, start):
def extract(src_path: Path):
"""提取单文件全部静态 check( 调用点。返回(条目列表, 函数表),条目保持源码顺序。"""
"""提取单文件全部静态断言调用点(check/check_quiet)。返回(条目列表, 函数表),条目保持源码顺序。"""
src = src_path.read_text(encoding="utf-8")
lines = src.split("\n")
funcs = func_table(lines)
@@ -247,28 +248,32 @@ def extract(src_path: Path):
entries = []
for m in CALL_RE.finditer(joined):
if not mask[m.start()]:
continue # 字符串字面量内 / 注释里的 check( 字样
continue # 字符串字面量内 / 注释里的调用名字样
ln = line_of(m.start())
if DEF_RE.match(lines[ln - 1]):
continue # func check 定义行
continue # func check / func check_quiet 定义行
prev = joined[m.start() - 1] if m.start() > 0 else ""
if prev and (prev.isalnum() or prev == "_"):
continue # `_check(` 等同名前缀——非标准通道
label_first = prev == "." # Kit.check("label", cond)——点前缀=标签在前
body, end_pos = collect_call(joined, m.start())
is_quiet = m.group(0).startswith("check_quiet")
if is_quiet:
label_first = False # Kit.check_quiet(cond, label)——cond 在前
else:
label_first = prev == "." # Kit.check("label", cond)——点前缀=标签在前
body, end_pos = collect_call(joined, m.start(), len(m.group(0)))
if body is None:
raise SystemExit("[extract_checks] FAIL:%s 行 %d 起的 check( 括号不配平到文件尾——先查解析缺口"
raise SystemExit("[extract_checks] FAIL:%s 行 %d 起的断言调用括号不配平到文件尾——先查解析缺口"
% (src_path.name, ln))
args = split_args(body)
if len(args) not in (2, 3):
raise SystemExit(
"[extract_checks] FAIL:%s 行 %d 的 check( 拆出 %d 个实参(预期 2/3)——解析缺口,先查再跑"
"[extract_checks] FAIL:%s 行 %d 的断言调用拆出 %d 个实参(预期 2/3)——解析缺口,先查再跑"
% (src_path.name, ln, len(args)))
if label_first:
label_expr, cond = args[0], args[1]
else:
cond, label_expr = args[0], args[1]
quiet = args[2] if len(args) == 3 else ""
quiet = "channel:check_quiet" if is_quiet else (args[2] if len(args) == 3 else "")
lm = LITERAL_RE.search(label_expr)
if lm is None:
continue # 标签实参无字面量(wrapper 直传变量——既有套件先例 reward_overlay_test _req)
@@ -336,7 +341,7 @@ def render_list(entries, funcs_by_src, src_names, carry=None) -> str:
out.append("# 验证集清单(断言快照·冻结)")
out.append("")
out.append("> 生成日期:%s · 基点 commit:`%s` · 源:%s" % (date.today().isoformat(), base_commit(), "、".join(src_names)))
out.append("> 提取口径:静态 `check(` 调用点,括号配平归集(可跨行);缺省源=三宿主遍历")
out.append("> 提取口径:静态 `check(`/`check_quiet(` 调用点,括号配平归集(可跨行);缺省源=三宿主遍历")
out.append("> (unit/**+integration/**+system_test.gd)。本基点实测 %d 个静态调用点;行级" % len(entries))
out.append("> `grep -c \"check(\"` 是另一口径(含 `func check(` 定义行与注释行),两口径各自浮动,")
out.append("> 禁互相换算(tests/README「断言计数双口径」)。运行时计数大于静态计数=循环展开条目。")
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""色板基线生成器——palette_check 双门②基线的建档与刷新件。
产出两张 1×N 条带 PNG 落 game/tests/golden/palette_baseline/(图像白名单在册目录):
ui_tokens.png = ui_kit.gd 全量 Color8 常量唯一色值(token 板现役值快照)
art_snapshot.png = 现役素材(game/assets/art 递归)全量唯一色快照(像素板+各屏环境子板)
palette_check --baseline 取目录下全部 PNG 的唯一色并集为「既有色板」;基线缺一张
即漏判——基线刷新必须重生成全套。维护纪律:素材批收尾跑一次本器刷新并随批提交
(新色过门②后须入基线,否则下批扫描把已过门的新色再报新增)。
用法:
python tools/gen_palette_baseline.py [--repo <仓库根>]
退出码: 0 成功 / 2 前置缺位(ui_kit.gd 或素材目录缺 / 素材零 PNG)
"""
import argparse
import os
import re
import sys
from PIL import Image
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from palette_check import palette_of # 排除全透明的唯一色提取——与检查器同口径
def strip_comments(text):
"""剔 GDScript 行注释与字符串字面量,防注释里举的色值被误收进 token 快照。"""
out = []
for line in text.split("\n"):
i = line.find("#")
if i != -1:
line = line[:i]
out.append(line)
return "\n".join(out)
def token_cols(ui_kit_path):
"""ui_kit.gd 全量 Color8 唯一色值集合。"""
text = strip_comments(open(ui_kit_path, encoding="utf-8").read())
vals = re.findall(r"Color8\((\d+),\s*(\d+),\s*(\d+)\)", text)
return sorted(set((int(r), int(g), int(b)) for r, g, b in vals))
def scan_dirs(roots):
"""递归扫 PNG,唯一色并集(含像素位置首见序不保——并集无序)。"""
cols = set()
count = 0
for root in roots:
for dirpath, _, names in os.walk(root):
for fn in sorted(names):
if fn.lower().endswith(".png"):
c, _n = palette_of(os.path.join(dirpath, fn))
cols |= c
count += 1
return cols, count
def write_strip(cols, out_path):
"""1×N 条带 PNG(一色一像素列)。"""
cols = sorted(cols)
im = Image.new("RGB", (len(cols), 1))
for i, c in enumerate(cols):
im.putpixel((i, 0), c)
im.save(out_path)
return len(cols)
def main():
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
ap = argparse.ArgumentParser(description="色板基线生成器(token∪素材快照)")
ap.add_argument("--repo", default=".", help="仓库根(含 game/ 与 tools/)")
args = ap.parse_args()
ui_kit = os.path.join(args.repo, "game", "presentation", "views", "ui_kit.gd")
artdir = os.path.join(args.repo, "game", "assets", "art")
outdir = os.path.join(args.repo, "game", "tests", "golden", "palette_baseline")
if not os.path.isfile(ui_kit):
print("[gen_palette_baseline] FAIL:ui_kit.gd 缺:%s" % ui_kit)
return 2
if not os.path.isdir(artdir):
print("[gen_palette_baseline] FAIL:素材目录缺:%s" % artdir)
return 2
os.makedirs(outdir, exist_ok=True)
tcols = token_cols(ui_kit)
n_t = write_strip(tcols, os.path.join(outdir, "ui_tokens.png"))
acols, npng = scan_dirs([artdir])
if npng == 0:
print("[gen_palette_baseline] FAIL:素材目录零 PNG(素材未同步)——拒绝生成空快照")
return 2
n_a = write_strip(acols, os.path.join(outdir, "art_snapshot.png"))
union = set(tcols) | set(acols)
print("[gen_palette_baseline] 已生成:ui_tokens.png %d 色 / art_snapshot.png %d 色"
"(素材 %d 张 PNG);基线并集 %d 色 → %s"
% (n_t, n_a, npng, len(union), outdir))
return 0
if __name__ == "__main__":
sys.exit(main())
+11 -2
View File
@@ -17,12 +17,21 @@
另:必须排除全透明像素 (0,0,0,0)(同standee_asset_check 的坑)。
基线(双门②「新增色 vs 既有色板」的既有色板机检承载物):
目录 = game/tests/golden/palette_baseline/,两张条带 PNG(1×N,一色一像素列):
ui_tokens.png = ui_kit.gd 全量 Color8 token 快照(token 板现役值)
art_snapshot.png = 现役素材全量唯一色快照(像素板+各屏环境子板)
两张并集=「既有色板」。缺一张即漏判——基线刷新必须重生成全套。
门①语义检查属设计面人工核对(§4.2 色相语义判域),不在本器执法面。
用法:
# 主用法:扫目录,新增色相对 --baseline 判定
# 主用法(闸机 05b 色板闸承载):扫目录,新增色相对 --baseline 判定
python tools/palette_check.py <目录> --baseline <基线目录>
# 无基线:只出读数(单张色数/ 近色对计数),不判红
python tools/palette_check.py <目录>
退出码: 0 通过 / 1 有红灯(仅新增色违规或超单张上限时)
# 基线建档与刷新:重扫现役素材∪token 重生成基线条带(素材批收尾动作,随批提交)
python tools/gen_palette_baseline.py
退出码: 0 通过 / 1 有红灯(仅新增色近色不过门②时)
"""
import os
import sys
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""B3 路径核对器(圣经 §8.3② 挂点实现 · 立绘定档批 2026-10-07)。
"""B3 路径核对器(圣经 §8.3② 挂点实现)。
职责:扫描 asset-path-map.md 声明的全部路径 + 代码侧素材常量,
报「悬空(声明存在但盘上无)/ 退役未清(仍引用已退役命名)」。
+12
View File
@@ -1209,6 +1209,18 @@ def run_screen_checks(scr, zones, board_origin, frames_strip, frames_meta, s6_im
weakest = min(ratios, key=lambda r: r[1])
check("S1", "整屏①全部文字对直接背景 ≥4.5:1(WCAG AA)", all(r[1] >= 4.5 for r in ratios),
"最弱 %s=%.2f;全部 %s" % (weakest[0], weakest[1], ratios))
# S1b 意图徽标四色墨对 PANEL_DATA 垫牌 ≥4.5:1(色值对表——镜像实装同值;垫牌=ui_kit PANEL_DATA #E9D9B8,
# 与 draw_standee 徽标垫矩形 fill 同源;四墨=意图色表 damage/buff/debuff/charge 四值)
panel_data = (233, 217, 184)
intent_inks = [("攻击墨", (150, 44, 36)), ("增益墨", (30, 90, 150)),
("减益墨", (122, 66, 148)), ("蓄力墨", (116, 80, 11))]
iratios = [(n, round((max(wcag_lum(a), wcag_lum(panel_data)) + 0.05)
/ (min(wcag_lum(a), wcag_lum(panel_data)) + 0.05), 2))
for n, a in intent_inks]
iweak = min(iratios, key=lambda r: r[1])
check("S1b", "意图徽标四色墨对 PANEL_DATA 垫牌 ≥4.5:1(WCAG AA·色值对表)",
all(r[1] >= 4.5 for r in iratios),
"最弱 %s=%.2f;全部 %s" % (iweak[0], iweak[1], iratios))
# S2 整屏②明度三档带 + 世界底层两子带(§2;舞台=实测区域读数,格底=实测板面;WCAG 0–1 域)
stage_l = _region_wcag(scr, (300, 190, 600, 220))
bx, by = board_origin
+8
View File
@@ -413,6 +413,14 @@ if [ $QUICK -eq 1 ] || [ $NONBEHAVIOR -eq 1 ]; then
else
gate "05 渲染闸" "render.log" bash "$REPO/tools/render_gate.sh" "$GAME"
fi
# ── 5b. 色板闸(§4.1 新色双门②:素材新增色 vs 基线〔token∪素材快照〕近色判红;
# 门①语义检查属设计面人工核对不入机检;基线刷新=tools/gen_palette_baseline.py,素材批收尾动作)──
if [ $QUICK -eq 1 ] || [ $NONBEHAVIOR -eq 1 ]; then
NAMES+=("05b 色板闸"); EXITS+=("SKIP"); LOGS+=("")
echo ""; echo "[gates] 05b 色板闸 → SKIP(--quick 或 影响面选闸:素材色板面不随测量面改动开闸)"
else
gate "05b 色板闸" "palette.log" "$PY" "$REPO/tools/palette_check.py" "$GAME/assets/art" --baseline "$REPO/game/tests/golden/palette_baseline"
fi
fi
# ══ 文档域(6–8e)══
if in_domain docs; then
+8 -59
View File
@@ -2,22 +2,6 @@
本清单是流水线的唯一登记处:以后才做的事在此登记一条,随流水线段推进改状态,做完整条删除(条目记进度,不是各段的开工前置);各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。
#### palette_check 基线建档与双门机检化(2026-11-02 登记,来源=批次移交)
**实况**:证据=迭代 47 实现报告 §7.2(已按吸收即死退役,git 提交 3443890d 可考)——卡④ V10 素材级读数:15 张素材 palette_check 只读模式 0 红灯 0 新增色;但 48 令牌 baseline 目录在库缺位(机检无对照基线),§4.1 两道门(语义检查+RGB 三通道差)的双门机检化缺承载。归工程改动批:建 baseline 目录+闸件双门判据落地。
**改动面**:工具代码
**状态**:待执行
#### ui_kit CAMP_HEADER_BAND 令牌退役评估(2026-11-02 登记,来源=批次移交)
**实况**:证据=迭代 47 审计记档 2 订正在案(git 提交 3443890d 可考)——营地域柜台头带消费面已随迭代 47 四柜台覆盖层退役注销(ui_contrast 令牌表四行随形态注销),现役消费面仅剩 keystone_badge 工厂与关卡屏/商店两处非营地域用途。处置口径(实现批备案):退役归 ui_kit 令牌批顺带处置、非独立强删;届时逐点核消费面后删令牌或改名。
**改动面**:游戏代码
**状态**:待执行
#### hobgoblin 内容三件套补齐(2026-11-01 登记,来源=审计发现)
**实况**:敌人表 hobgoblin 行(寻址键 (1, battle, 3))数值完整在役、战斗正常运行,但内容三件套未交付——立绘、敌种语义模板、敌人系统.md 敌种表行均缺,呈现面因语义模板未成文无定义可依。登记凭据=[敌人系统](design/gdd/systems/战斗循环/敌人系统.md) v1.1「hobgoblin 缺口登记」节(2026-10-13 迭代 24 反哺④)。
@@ -38,22 +22,6 @@
**状态**:待文档设计
#### ui_kit.gd 立绘注释陈旧顺带换代(2026-11-02 登记,来源=批次移交)
**实况**:证据=迭代 48 实现报告§六与验收审计观察项(已退役,git 提交 4c94eee3 可考)——`ui_kit.gd` TEX_HERO_PORTRAIT 行注释仍写「48px 档」(现行=原稿分辨率+显示档缩放),`path_check.py` 一处注释表述也已改指外;零行为,随下次触碰该两件的批次顺带换代(顺带件,不独立出批)。
**改动面**:游戏代码
**状态**:待执行
#### 迭代 43 批后移交小件——能力清单补行+四色墨断言(2026-10-30 登记,来源=批次移交)
**实况**:证据=迭代 43 反哺点清单 9/13(已按吸收即死退役,git 历史可考)。逐条:①**能力清单补行**——game/tests/README.md 能力清单无战斗一屏样张批 V5–V9 代表用例行(卡片成文早于清单 2026-10-29 改版定稿),归写卡段随下一写卡会话按 test-conventions §8.3 补行(能力=站桩呈现定值与素材绑定;代表用例=`game/tests/unit/presentation/standee_present_values_test.gd:_init`,真实入口=`UiKit.load_tex()`/`StandeeBody.name_line_probe()`),并同步覆盖域表「战斗表现.md」行。②**意图徽标四色墨补机检断言**——样张脚本 S1 文字对比为绘图用色值对表,意图徽标四色墨(对 PANEL_DATA 垫牌)未入 CHECKS;独立审计复算 4.97–5.62 全 ≥4.5 事实成立仅缺机检断言,`tools/render_sample_sheet_v3.py` CHECKS 补一条对表断言(values 全在册,零新增色)。**2026-10-31 写卡会话落位**:①能力清单补行已随「站桩名字行提档与地台色合并批」卡片成文同批入表(能力行+覆盖域表战斗表现.md 行);②四色墨断言仍待执行。
**改动面**:游戏代码+工具代码
**状态**:待执行
#### 架构审查优化讨论——全库架构审计读数的逐条裁决与整改分批(2026-10-29 登记,来源=审计发现)
**实况**:2026-10-29 全库架构静态审计完成,读数见 reports/审计-全库架构审查-2026-10-29.md(七节:总评/健康面/P0/P1/P2/待裁点/取证口径;零测试执行零引擎运行,全部结论带 文件:行号)。健康面已逐条核干净:Core 零 UI 八项判据全零命中、core 内 RNG 单点 100% 成立、字符串式信号连接全库 0 处、core 层零超长函数、19 表 ↔ 19 schema 一一对应、无循环 preload。问题集中在三条「写了没闸守」的纪律被实际破坏:①数值单一事实源被 18 处硬编码击穿,最危险两处是 `presentation/main.gd:787` 与 `:790` 把 `rap_refresh_cost=8` 与 `refresh_cap_per_round=2` 写死进提示文案(紧邻 783 行是正确读表的),表改文案不改、玩家直接受错误数字;另有 4 组代码自标「机制结构常量/产出约束」的字面量(`node_map_generator.gd:12` `MAX_WIDTH`、`action_economy.gd:14` `CHARGE_LAYER_CAP`、`board_data.gd:9` `SPAWN_AREA_CELLS`、`node_map_generator.gd:83-94` 域字面量)是否入表待裁。②`game/sim/` 与 `game/core/` 四组平行实现——扫雷推理引擎双份(`board_generator.gd` `measure_inferability` 族 vs `auto_player.gd` `_deduce` 族,输入集口径不同:前者含 safe_set oracle、后者 no-peek)、伤害掷点期望重算(`damage_probe.gd:196-201`)、档位排序与驱动循环双份(`level_autoplay.gd` vs `treasure_item_table.gd:44` 与 `battle_driver.gd:17`)、FakeDB 伪造表喂生成器——直接威胁「模拟读数 = 游戏真实数值」这一数值验证前提。③RNG 单点纪律在 core 外失守(sim 4 处/probe 1 处/tests 3 处自建 `RandomNumberGenerator`,均显式定种、确定性尚存),而 `static_scan.sh` 的 S1/S2 扫描面只含 `DIR_CORE` + `DIR_PRES`。守卫面另有三缺口:S8 是 9 文件显式清单(新增结算文件忘登记=静默不扫,缺位自检只防删改不防增)、S9 类型闸与 S13 质量闸扫描面止步于 core+data+presentation(sim/probe/tests 三层除 S11/S12 外零闸)、S13 三处豁免常态化到最大户文件(`battle_orchestrator`/`main.gd`/`tune_sim_controller`);另有四个 core 文件零测试点名(`attune_loadout`/`battle_fx_host`/`meta_state_store`/`slot_store`)。P2 级:`core/settlement/` 五段类直读写编排器私有成员 20+ 处(`oc._damage.*`/`oc._enemy_system.*`,GDScript 无编译期检查、改名静默断裂);架构文档三处漂移(`architecture.md:110` 登记的 `core/hero_ledger.gd` 盘上不存在且 `hero_ledger_test.gd:171` 显式断言它必须不存在、`:135` 行数过期、EC8 热重载门后 `ConfigDb.reload()` 零调用方且走 `load()` 拿不到磁盘新值);class_name 级双向依赖(`core/config_db` → `data/schemas/board_gen_table` → `core/board_data.SPAWN_AREA_CELLS`)与 `NODE_TYPES` 词表双份;`MetaProgression` 是唯一用 `get_node("/root/...")` 取依赖、唯一跨 Autoload 直调的单例。
@@ -87,29 +55,18 @@
**状态**:文档设计已闭合
#### 断言提取器改版——覆盖 Kit.check_quiet 静默通道(2026-10-29 登记,来源=会话提及)
**实况**:`tools/extract_checks.py:67` 的 `CALL_RE = check\(` 只认即时通道,静默收集通道 `Kit.check_quiet(cond, desc)` 静态站点实测 407 个在提取口径外(证据=`grep -rn "check_quiet(" game/tests --include="*.gd"` 实数;test-conventions §5.2 已备案该工具局限)。能力清单以 `文件:函数` 为锚,不受影响;但要把断言面全账纳入机器对账(重建账本或全量断言面审计),须先改版提取器:识别 `check_quiet(` 并按反参序(cond 在前)提取标签与 cond。改版后须以全量套件跑一遍 `--diff` 自证提取面覆盖、且 `Kit.check(` 与 `check_quiet(` 不双计。
**改动面**:工具代码
**状态**:待执行
#### 能力清单覆盖缺口补齐——六类无断言面或设计未定项(2026-10-29 登记,来源=会话提及)
**实况**:证据=`game/tests/README.md` 覆盖域表 25 篇对照行(2026-10-29 立)。以下部分覆盖/未覆盖项作为缺口指针,逐项:①增益状态——只验契约词表与布尔受理,叠层/衰减/清除结算链无断言;②设置与本地化——减动效开关本体仅系统测试与冒烟覆盖、无单元/集成断言,多语言切换面未建;③音频——MVP 无音频,P1 后置无断言面;④战斗表现——死亡灰化与减动效演出档只有探针与人工目检;⑤难度曲线与Boss——难度曲线整链与 Boss 出场门控无专项断言;⑥锻造工坊——价格梯仅长剑路线逐级验,GDD 无验收标准节(`capability_audit` 验收标准豁免在册)。教学引导与生涯两篇的缺口归各自在册条目(「教学形态未定」「「生涯」界面——冒险中数据回看的唯一承接处」),不在本条。补齐时同批刷新覆盖域表与能力行,新增断言按 test-conventions §8.4 绑真实入口。
**实况**:实际情况=2026-11-02 设计讨论闭合——④除名(用户裁:死亡灰化/碎裂属纯表现锦上添花,探针+人工目检已是合规终态,呈现与 battle.md 承诺行零触碰);范围收窄:④除名后余五类,其中②多语言半边(P1 功能缺口,归 owner 文档待设计问题清单)、③音频(P1 后置)、⑤数值落带半边(归 M4 数值批)三块延期不在本条,可写卡面=①增益状态结算链、②减动效开关、⑤机制门控、⑥锻造工坊结构四处。关联文档:
- `game/tests/README.md` 覆盖域表 25 篇对照行(2026-10-29 立,缺口证据)
- `design/gdd/systems/跨局循环/锻造工坊.md` v0.11(2026-11-02,验收标准节九条判据落文——⑥写卡输入合同)
- `design/gdd/systems/战斗循环/增益状态.md` v1.1(2026-10-26,①结算链规则定稿)
- `design/gdd/systems/冒险循环/难度曲线与Boss.md` v1.2(⑤机制门控规则定稿;数值带 provisional 归 M4)
- `design/gdd/systems/表现与支撑/设置与本地化.md` v1.1(②减动效开关与呈现承诺现行口径)
**改动面**:游戏代码
**改动面**:游戏代码+工具代码
**状态**:待执行
#### run_all_gates 02 关联档多套件词传参修复(2026-10-31 登记,来源=批次移交)
**实况**:证据=iter45 实现报告 §5 遗留观察(已按吸收即死退役,git 历史可考)——`tools/run_all_gates.sh` 02 关联档以逗号连接多套件词传单个 `--only=a,b` 参数,而 `game/tests/run_all.gd` 选跑解析器不切逗号(多词整串做子串匹配→零命中 quit(2));iter45 施工时以多 `--only=` 旗标形态绕过。合回后 CI 跑全量档不经此路径不受影响,只挡开发期关联档窄跑。修法两案待裁:闸脚本改传多旗标,或解析器补逗号切分。
**改动面**:工具代码
**状态**:待执行
**状态**:文档设计已闭合+待写卡
#### 战斗域结构深拆批——效果发射端口+掷格/受理分件(2026-10-31 重立,来源=批次移交)
@@ -126,14 +83,6 @@
**状态**:文档设计已闭合+已写卡+已编迭代 49
#### main.gd 拆件遗留死 preload 清理(2026-10-31 登记,来源=批次移交)
**实况**:证据=iter44 反哺点 11(已按吸收即死退役,git 历史可考;实现报告实现决定记录同载)——迭代 44 卡① 组合根注册面批拆件后,main.gd 遗留零使用 preload 常量 8 条(UiAssembler/TuneSimController/DebugSessionScript/TreasureInteractController/CurioInteractController/CampShopPresenter/BoardResolversScript/三场景 preload 中 main 零使用者三件);卡① 施工时仅清理与迁移直接相关的 ModalStack/RowPacer 两条,其余属存量债不扩范围,归工程改动直改批顺路清理。清理前须逐条 grep 复核零使用(布局批与 iter45 合回后消费面或有变化)。
**改动面**:游戏代码
**状态**:待执行
#### 数值标定窗口总挂账——原九条数值类条目合并(2026-10-28 登记,来源=会话提及)
**实况**:2026-10-28 用户令把九条数值类挂账合并为一条:削弱档位的最终数值/迭代 10 奖励重构联动欠账/调参批数值点评登记/三方向各自的解锁清单与定价/GP 经济换算失衡/天赋树实装批登记欠账余两件/商店独立刷新机制两参数/基线通关率越带加难杠杆扫描/成长系数节奏——原九条同步删除,内容全量迁入汇总文档,git 历史可考(本提交)。共同背景:M4 数值批卡已随 2026-10-16 删卡令退役;2026-10-18 用户裁=各条留待办清单、未设计未启动、不另行归属,未来数值标定窗口重开时重走设计段再写卡——本条沿用该裁定,重启条件=用户发令开窗口。合并动机=九条共享同一重启条件与同一执行形态(窗口内一次 MC 批量统一覆盖各条对比需求:全冒险期望+奖励期望),分散登记徒增维护面。逐条挂账明细、裁定史与相关文档见汇总文档。