diff --git a/CLAUDE.md b/CLAUDE.md index 5fae15db..f00cbd70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -92,7 +92,7 @@ ## 硬性红线(勿违反) - **worktree 铁律**:主工作区禁止直接改动与提交(细则见上节;并行会话各占 worktree 互不踩踏) -- **测试执行两入口铁规(2026-10-02 用户立规)**:测试类执行——冒烟测试、单元测试(tests/run_all)、模拟验证(MC/verifier/调参批量工具)、场景探针等一切跑游戏的验证调用——只有两个触发入口:①**迭代 loop 开发**(冒烟挂迭代开发完成后的出口/落籍闸,loop 内过程性验证属开发工作本身);②**用户主动点名**。除此之外(问答/调研/文档/审查/零散小改等短小会话)一律零测试,不得擅自调用任何测试;确需测试支撑结论时先请示,以静态阅读/推理作答并注明未跑测。**MC/模拟验证触发细则(2026-10-02 用户确认,2026-10-03 补落文——出处:迭代 9 出口批 81000 锚误跑质询)**:MC/verifier 仅在**数值验证场景**触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**——结构位移类「存档锚」须先向用户请示定性再跑;任务书预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。**分级周期口径(2026-09-26 用户裁定,2026-10-03 自 06-待办清单收编)**:轻闸(run_smoke 全量+ui_input_probe;触碰 presentation 加跑 ui_click_probe+ui_layout_probe)可随变更执行;重闸(B 档视口截图像素断言〔game/smoke/shot_probe.gd〕、C 档黄金图对比〔tools/golden_compare.py〕、真人实机目检、MC 大扫描)属**周期执行**——统一在表现批收口或固定周检跑,不进每次变更 +- **测试执行两入口铁规(2026-10-02 用户立规)**:测试类执行——冒烟测试、单元测试(tests/run_all)、模拟验证(MC/verifier/调参批量工具)、场景探针等一切跑游戏的验证调用——只有两个触发入口:①**迭代 loop 开发**(冒烟挂迭代开发完成后的出口/落籍闸,loop 内过程性验证属开发工作本身);②**用户主动点名**。除此之外(问答/调研/文档/审查/零散小改等短小会话)一律零测试,不得擅自调用任何测试;确需测试支撑结论时先请示,以静态阅读/推理作答并注明未跑测。**MC/模拟验证触发细则(2026-10-02 用户确认,2026-10-03 补落文——出处:迭代 9 出口批 81000 锚误跑质询)**:MC/verifier 仅在**数值验证场景**触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**——结构位移类「存档锚」须先向用户请示定性再跑;任务书预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。**分级周期口径(2026-09-26 用户裁定,2026-10-03 自 06-待办清单收编;重闸定义随迭代 19 卡①任务书 Q1 裁决扩容 2026-10-08)**:轻闸(run_smoke 全量+ui_input_probe;触碰 presentation 加跑 ui_click_probe+ui_layout_probe)可随变更执行;**重闸自此批末=轻闸+probe_gate --click+probe_gate --full(ui_probe/ui_tune_probe/t17_defer_probe/fx_probe 四探针)+render_gate(窗口化渲染闸:shot_probe 七屏样张 A-D 断言+fx_shot/board_shot 取证+棋盘 golden 逐像素)+run_all 单元全套+doc 三闸(gdd_lint/doc_link_audit/doc_term_audit)+熵增闸(repo_structure_audit)**,属**周期执行**——统一在批次收口或固定周检跑,不进每次变更;真人实机目检仍归用户不进自动闸(07⑮ 目检半不销号);MC 大扫描仍按上方 MC 触发细则(无数值改动零跑) - **MC 采集档位细则(2026-10-03 用户四档立规,承接 MC 触发细则)**:MC 局数按用途四档硬闸(表见 `sim-data/README.md`「MC 采集档位表」):**一档·调研**=总量 >40(100+ 为常用锚量级;60/档=结构回归、100=95% CI ±9.8pp、170=±7.5pp=15pp 带半宽、400 满档=±4.9pp 贴边裁决;normal 独立满档 ~8 分钟同属一档)**只能由用户发起**——任务书/计划书/agent 均无权发起,非用户发起一律不构成一档授权;**二档·数值修改验证**=总量 ≤40、每档 ≤20(仅 ≥~40pp 结构位移可判,同种子配对下 ~30pp);**三档·小调**=总量 ≤20、每档 ≤10(只测不判);**四档·微量**=总量 ≤5(sanity 门,零统计判读)。四档仅承接 MC 触发细则的数值验证场景,两入口铁规不变;超档=停机呈报,禁在二/三/四档下带值结论(窗口不足如实挂起)。改前/改后锚同 seed0 配对、改前锚留盘禁补跑;对拍断言零新跑(复验用锚内子集 ≤20);读数缺陷(零 gameplay/RNG 触碰)禁整套重跑、走事件流重构补算;≥60 局采集先 N=5 pilot 校验字段;二/三/四档会话累计 ≤20 分钟;零数值改动=零 MC - **中间产物吸收即死(2026-10-01 立规)**:中间文档(批任务书/机检报告/调研审计报告/模拟读数/迭代计划书/目检清单)寿命=迭代寿命——结论先吸收进正主文档、未决项先迁 `design/gdd/06-待办清单.md` 或 roadmap,然后文件即删,且永不提交 git;活文档=唯一事实源,git=变更记录。细则见 `design/dev/doc-conventions.md` 第 11 节 - **用语红线(2026-10-04 用户立规,黑话审计后落文)**:面向用户的一切输出——文档、工作日志(active.md)、报告、清单、对话——禁止自造黑话与代号式命名;判据 = 业内读者没读过项目历史也能一遍看懂,需要作者解释才能懂的词必须展开成完整白话句(三种固定形态、本仓反例全表与正确写法见 `design/dev/doc-conventions.md` 第 4 节) diff --git a/active.md b/active.md index cb8dd756..7b36eabb 100644 --- a/active.md +++ b/active.md @@ -907,6 +907,8 @@ 402. **迭代 19 卡③CI 预接线批(2026-10-08,裁决依据=任务书 v1.2 全裁〔裁点①远端仓库=暂不建维持待命/裁点② ui_layout 入 CI 候卡①后裁〕;worktree g-plan2-iter19-ciwire 分支 iter19-ciwire-20261008 基点 9160942;B6 出处=自动化验收补齐批 §一明示不收件,随本批闭环销账)**:T0-T3 全履行——tests.yml 单文件改写四件:①原 :41-45 整块注释探针段(示例 ui_probe/ui_click_probe 选型滞后)解封=正式轻闸三探针段(input/struct/contrast=probe_gate 默认段同款;每探针 timeout 120s 外壳+退出码 124/137 超时判+grep PASS 串判定〔T0 本机实跑三探针取 PASS 行样本钉死判据〕,三探针全跑不短路汇总定红绿,probe_gate 判定逻辑 Linux 独立镜像——probe_gate.sh 本体系 Windows 专用〔PowerShell+taskkill〕CI 禁调);②冷检出导入步 `--import` 300s 前置冒烟之前(job 级基础设施步同覆盖冒烟与探针 .godot 依赖,Q9 甲);③触发分支 push/pull_request 两处 [main]→[master] 修死角(实际分支 master、main 不存在);④头注释保留「尚无远端」句+追加「远端仓库建好后无需改本文件即自动生效」;探针日志落 logs/probe_ci/ 顺现有 upload-artifact 段零改并入。验收全静态(本地无远端端到端不可验=红线边界)五判据全过:pyyaml 解析 OK+CI 段 res:// 引用逐一实存+timeout 外壳清单齐(300s 导入/120s 冒烟/120s×3 探针)+头注释自动生效句在位+diff 面仅 tests.yml+roadmap 账务行(游戏代码/探针件/probe_gate.sh/CLAUDE.md 零 diff=契约 1);轻段选型锁死三件、窗口化四件与重闸五件零入(契约 2);MC/verifier 零跑。**S1 呈报项(该段冻结未代修,处置待用户裁)=tests.yml setup-godot 步 `nikeee/godot-actions/setup-godot@v2` 指向不存在仓库**(GitHub API 404+nikeee 159 公共仓 grep godot 零命中+marketplace 同名=Vitorgus v1 Godot3 旧件+对照 actions/checkout 查询正常=API 可信;候选替代 chickensoft-games/setup-godot v2.4.3 待裁甲换 action/乙手动下载/丙挂起)——远端建好首跑前必须先裁否则 smoke job 必红;S2 未触发(兄弟卡未先行合并,master 停 9160942);roadmap 卡③行内登记按 Q13 由主控合并时串行重放。**移交**——首次 push 后人工盯首跑清单(任务书 §七 三条+本批增补 setup-godot 前置门一条)随远端立项之日激活。出口报告=design/qa/iter19-ciwire-exit-report-2026-10-08.md;提交三笔 3799e0c(T1 tests.yml)/7397603(T3 roadmap)/本笔(出口报告+台账记账)。 403. **迭代 19 卡④提灯载体道具批(2026-10-08,裁决依据=任务书 v1.2 全裁〔裁点①载体=案A 秘宝·同调位新行/裁点②缺陷另卡先行=卡⓪已落籍=本卡 T1 前置门 S1 天然满足〔基点 568101b 链上含卡⓪ merge 9160942〕/裁点③随案C 落选消解;行字段=用户 2026-10-08「按推荐」一次秒答全收〕;worktree g-plan2-iter19-lantern 分支 iter19-lantern-20261008 基点 568101b;B4 出处=自动化验收补齐批 §一明示不收件,随本批闭环销账)**:T1-T5 全履行——T1=treasure_items.tres 表尾追加 fog_lantern「魔法提灯」行(行字段裁值:display_name 魔法提灯/desc 灯光区半径+2/id fog_lantern/slot_cost 1/稀有度不设字段〔KEY_WHITELIST 封闭十七键〕/unlock_requires 键省略=已解锁;机械字段 form=passive+**triggers 显式空数组**〔活行空 effect_kinds 校验豁免以 row.has("triggers") 为条件——treasure_item_table.gd:111,漏写=加载红,本批头号易错点已钉〕+effect_kinds=[]/target=none/magnitude=0/use_phase=active_dig/dimension_tags=[info];表尾追加保 MC 自动档装配画像逐位不变);T2=接线生产路径贯通(完成度 0→1):battle_orchestrator.gd:128-134 装配集公共只读出口 attune_has(_attune_has 契约化出口,save v6 先例)+main.gd:852-860/:875-886 呈现桥 _sync_fog_lantern 挂 _on_round_started ri==1 每战首轮装配链**显式重算含否**(🔴 契约 7 Q8.1 复原语义=禁战斗结束隐式复原;时序依据=round_started 首轮发射位在 begin_battle 尾 attunement 注入后——board_changed 在 _install_new_board 内先发装配集后注入,同帧直挂 _on_board_changed 会读上一战旧集故不挂);T3=测试三形态(契约 9):case⑮ 内载体级段不开新 case(装配→build_adventure_params 参数面+settlement.attunement 注入→呈现桥→lantern=2 且 dist==R+1 转可见+卸装显式重算复原;case⑮ 既有 17 断言面零删减)+smoke 现有套件 _smoke_adventure_table 加断言 +4(行字段逐键/同调目录投影 display_name 九件→十件/装配贯通 attune_has=true/空装配基线 false)+存档回归 breakpoint_resume_test Part 6(装配→停点→续玩受理→回局首战 attune_has 仍 true)+新单测 fog_lantern_wiring_test.gd(run_all manifest 17→18 套);T4=改道指路句实查 6 处全换代(board_view.gd :139/:145+art-bible.md:387〔常量拟名 LIGHT_RADIUS_LANTERN_BONUS 顺手对齐落码实名 FOG_LANTERN_BONUS〕+HUD框架.md :168/:170 同段 2 处整段换代+落籍注记;残留「Alpha 信息件」字样三处均=销账声明/改道留痕叙事非活性指路;qa 历史成文零触碰)+06-待办清单 菜单开局同调传参缺陷条回写落籍态(卡⓪ merge 9160942+resume tree_rules 受理缺口披露归用户裁)+秘宝.md 目录九件→十件+v1.1 行(速览 aux_items.tres→treasure_items.tres 过时文件名顺手订正)+roadmap 迭代 19 行卡④段落籍登记;T5=批末重闸三件全绿=冒烟 840→**844 项 0 失败**三闸过(+4 全系新增断言)+probe_gate 含 click 全绿(input PASS/struct PASS/contrast 15 对 PASS〔本分支基点为卡②合并前旧态,主控合并时按 master 38 对现役重放〕+click 段退出码 0)+run_all **18 套件**全绿+残留 Godot=0 双点(probe_gate 批末闸+批后 powershell 复点);MC 零跑静态论证在案(纯呈现层:行 triggers 空=效果引擎零订阅结算流逐位不受扰+装配画像表尾不可达 slots≤3+off 态 _fog_lantern 写 0 数值恒等——2026-10-07 用户裁 Q3 静态替代)。**消歧**——greedy_lantern(feats.tres:291 贪婪矿灯 dig_reveal yield_mult)零关联零触碰,行文全称「魔法提灯」。**移交**——提灯点亮视觉挂账不动(资产升档重产批+06 挂起项,S2 未触发);全局变异案E 解封条件「提灯接线落位」随本批推进、解封重议归全局变异批 Own;显影尘/地穴全图卷封存重定仍留 Alpha 信息件重定批零触碰;roadmap 行冲突重放归主控。出口报告=design/qa/iter19-lantern-exit-report-2026-10-08.md;提交五笔 90a39f1(T1)/eef343c(T2)/f0f2009(T3)/0b5834d(T4)/本笔(T5 重闸+出口报告+台账记账)。 +399. **迭代 19 卡①自动化验收补齐批——T0-T2 落地+S1 停机点命中(2026-10-08,裁决依据=卡①任务书 v1.1 待裁点 0 项〔P1-P5+拷打轮一 Q1-Q8 全裁〕;worktree g-plan2-iter19-verify 分支 iter19-verify-20261008 基点 9160942;本批零 MC 零 verifier 符合迭代口径)**:**T0 基线复读全绿定档**(最终环境实测:冒烟 840 项 0 失败+run_all 17 套件全绿+probe_gate 默认段/--click 全绿+四探针 ui_probe/ui_tune_probe/t17_defer_probe/fx_probe 全 PASS;主区在飞脏文件 15+ 只标旗未碰;**worktree 拷导入缓存教训一条在案——`*.import` 边车全局 gitignore,拷主区素材须含 game/assets 全树 .import 含 ui/icons,缺则状态图标纹理 ResourceLoader 失载 ui_probe 三断言环境性假红**)。**T1 渲染闸落码**=tools/render_gate.sh(骨架复用 probe_gate:timeout 守卫/孤儿预清后清/退出码闸/残留批末闸;段=①shot_probe 七屏→shot_checker A-D 自动判定②fx_shot+board_shot 取证段失败=闸红〔Q7〕③golden 逐像素④1280 单档 1920 注记〔Q4〕;过程截图 mktemp 即弃禁入 git〔Q2〕)+shot_probe 扩屏四屏→七屏(设置面/生涯页/暂停层真实信号路径;商店房考证=真实路径必经 L1 打穿进 L2 商店节点选路、低代价确定性到达不成立、合成投喂破坏真实路径口径——注记留 B 批,代拍板白名单③)+board_shot_probe 取证三件套(headless 护栏/空纹理 fail/board_meta.json 取样坐标落盘)+golden_board_crop.py/golden_baseline_refresh.sh 两辅助件+**隔离档即时 apply 修正**(main 装配按真实设置恢复窗口态——本机真实档 fullscreen=true 时视口纹理 2304x1440≠1280 基准档 checker 全盘失真;探针侧 apply 隔离档出厂默认恢复窗口化设计前提,零运行时 diff)。**T2 黄金图基线入库**=game/tests/golden/ 三张(L1/L2/L3 素材齐盘态 2026-10-08 窗口化抓帧 900x760 裁剪帧——非字形回退态 S3 前提自证;换代触发原因提交说明强制记载;容差 provisional 差≤16/changed≤2%)+golden_compare 接线进 render_gate,首对比 0.00%/0.00%/0.89% 全过(L3 0.89% 系动画相位墙钟差,容差内,S2 观察项在出口报告注记)。**S1 停机点命中**=render_gate 首跑存量红三件全为 shot_checker B 类对比度断言取样锚漂移(①菜单副标题锚「改造批」→现行「扫雷的因 · 逐行兑现的果」②菜单帮助文锚「玩法:」载体=玩法速览牌 2026-10-06 用户令退役 ③战斗明细提示锚「结算明细」→现行「上轮摘要(回看归「生涯」)」),A 骨架七屏/C 明度三档/D 纹理+hero 面板 B 类全过实证在案;三件已**登记 06 条目级**(各附建议案、处置权归用户),render_gate 裁前保持红=闸语义正确;**T3-T6 未执行**(收编/--full/重闸扩容/退役件/ui_layout 两档/tests README 漂移/integration 删壳/07⑮ 注记/roadmap 换代全在册待用户裁后续跑)。出口报告=design/qa/iter19-verify-exit-report-2026-10-08.md;提交三笔 8246790(T1)/3249697(T2)/87f749d(S1 登记 06)+本笔(台账记账)。 +400. **迭代 19 卡①自动化验收补齐批——锚清障续接完工+T3-T6 全落+S2 停机点命中与根因闭环(2026-10-08,裁决依据=S1 三件用户裁甲清障〔06 在册〕+卡①任务书 v1.1 T3-T6;接续前手 T0-T2 段〔台账 399〕,同 worktree 同分支)**:**锚清障三件续接落籍**(前手中断遗作两文件审验全数成立零补刀——①菜单副标题锚改认现行「扫雷的因 · 逐行兑现的果」逐字子串全工程唯一命中②战斗明细带提示锚改认现行「上轮摘要(回看归「生涯」)」唯一命中零碰撞③菜单帮助文腿三处同撤不重锚;提交 a623ec3)。**S2 停机点命中与闭环**=清障后 render_gate 首跑 golden L2_underdark 2.53%>2% 越限,停机诊断实证波动性越限(L2 四跑 0.00/2.53/2.29/1.17%)根因=board_view rebuild 入场涟漪动画(最远格 ≈0.56s)未完即抓帧(原制 6 process_frame≈0.1s 恒捕中途态、逐跑帧时序抖动);修复=抓帧前 create_timer(1.2)s 时间制锚定完结态(探针侧零运行时 diff)+golden 三张基线换代(S3 素材守卫/Q2 临时区全随、触发原因提交说明强制记载),换代后三连跑全 0.00%+render_gate 全绿 EXIT=0,**容差 2%/16 用户冻结口径未动**(提交 3317076)。**T3 探针收编**=probe_gate --full 段四探针(ui_probe/ui_tune_probe/t17_defer_probe/fx_probe,timeout 120=2-7×余量)+头注「渲染闸归真人目检」纪律句删除改写(2026-10-07 用户授权)+默认段零删减;ui_layout_probe 补 1280/1920 两档最小布局断言(三区互斥不越界/board_view 含于 D 区/1920 档钉分辨率案甲构图恒等;首跑 PASS;闸归属归卡③裁点②候裁)(提交 b523f7d)。**T3/T4 退役与标注**=config_fatal_probe 删除(与 run_all config_db_fatal_oob_test 断言面重复还账)+数据配置.md:205 指路句连带改指(v0.4 版本史行历史记载不动)+damage_probe「诊断工具·非验收件」+skill_rhythm/board_stat「分析工具·非验收件」+引用零残留(提交 b3404e8)。**T4 账务**=tests/README 十五→18 套(卡④ fog_lantern_wiring_test 注名,本分支基点早于卡④合并实测 17 套全绿)+「752+」改双口径(运行时 840 项〔master 合并卡②④后 846〕/静态 check( 764 处)+integration 空壳删章程并入(提交 fcb91ce)。**T5 纪律落文**=CLAUDE.md 分级周期口径重闸扩容(自此批末重闸=轻闸+--click+--full+render_gate+run_all+doc 三闸/熵增;真人目检仍归用户;MC 仍按触发细则;落点=硬性红线内该句=任务书所称 loop §5)(提交 57a7919)。**T6 收口**=07⑮ 机器补跑半核销注记(真人目检半不销号)+熵增闸图像白名单收编 game/tests/golden/(T2 落库漏改致红,同批修订 commit 留痕)+批末重闸全跑全绿(冒烟 840 项 0 失败/run_all 17 套全绿/probe_gate 默认+--click+--full 三形态全绿/render_gate 全绿;doc_link_audit/doc_term_audit 绿)+roadmap 行换代归主控(分支落后多合并不追)。**存量红呈报不代修**:熵增闸 design/diagrams 越界(基点 00712d5 既有,脚本自文须用户拍板——甲入白名单/乙迁出)+gdd_lint 06/07 两文件 R3/R7 违例(基点既有归因在案,本批零新增)。出口报告完工终版=design/qa/iter19-verify-exit-report-2026-10-08.md(S1/S2 两停机点全案+闸数字改前改后全列);提交七笔 a623ec3/3317076/b523f7d/b3404e8/fcb91ce/57a7919/本笔前后两笔(07⑮ 注记+白名单收编/出口报告+台账);MC/verifier 零跑。 401. **迭代 19 卡②旧界面呈现断言回补批(2026-10-08,任务书=design/research/任务书-旧界面呈现断言回补批-2026-10-07.md v1.1 待裁点 0 项;worktree g-plan2-iter19-uibackfill 分支 iter19-uibackfill-20261008 基点 9160942)**:三件全落——①T1+T2 ui_contrast 测点对 15→38 对(+23:battle_end 3/shop 5/camp 9/settings 2/curio 2/迷雾记忆墨合成对 1,全 UiKit 常量实时求值+行尾注理由,石皮纹面板按缺素材回退牌色 PANEL_DATA 计);②T3 ui_struct 补采样 legend+node 两根(13→15 根全真路径开合——legend=board_zone.legend_toggled 信号开→closed 信号关,node=_open_modal 登记册+show_rest 合成喂入→continue_pressed 信号关;既有 13 根/五族断言/pause-career 契约族/resume 专项零删减);③T4 文档面三件(出口报告+06 settlement_review 遗留件退役处置登记落位〔随生涯实装批 P5 甲复活承接或正式删除二选一归用户裁〕+roadmap 卡②段换代)。**🔴 停机点 S1 命中:新对首跑暴露存量对比度违例 7 项**——结算 GP 账行 GOLD_DEEP/结算板 2.67:1、商店 legacy 行卡名 INK_LABEL/暗底行卡 1.12:1(重度不可读档)、货架价签 GOLD/货架板 1.46:1、营地铭牌副题 STONE/铭牌板 3.59:1、行价格 STONE/行板 4.02:1、树分组头 GOLD_DEEP/柜台板 2.67:1、工坊状态 GOLD_DEEP/行板 3.00:1(V1-V7 全量清单+修复建议案〔全现役常量换墨可落〕在出口报告 §3);按任务书 §十 S1 停机呈报,处置权归用户(甲=另立修复批〔违例对在册=闸红登记存量形态,修后自然转绿〕/乙=回拔 7 对留纸面登记),批内零修(红线:旧屏呈现层代码零 diff,git diff 可核=仅两探针+文档面)。S2 未命中:legend/node 结构首跑全绿、WL 零新增(仍空表)。闸数字改前/改后——冒烟 840 项 0 失败持平+run_all 17 套件持平+残留 Godot=0;ui_contrast FAIL 7 项越带(S1 呈报物非实现回归,存量红零新增);ui_struct PASS 15 根。**时长闸披露**——probe_gate 默认段整闸基线窗口 186.7s→改后窗口 306.3s 系兄弟卡①③并发噪声主导(同窗口交错 A/B 三轮:同一探针本体波动 5.1→20.6s,改前 B2 亦跑出 15.7s);本体同窗口配对差中位 contrast +0.52s/struct -1.64s 无系统性增量、技术下限毫秒级(23 对静态求值+两屏采样≈0.5s)——按本体配对口径验收闸 5(≤2s)通过,整闸空窗复测归主控合并窗口。白名单行使披露——三不收位(bcard 描述/脚注/✓章/primary 钮字四处内联 Color8 无常量形态+page_shell 顶条板同;TG_* 五族带=非文字装饰件不适用 4.5 文字判据);对数 38 超预期带 25-32 系违例位全量入表(登记存量须哨兵在册)。迷雾对注——floor avg #453020 系素材侧值无常量形态取 art-bible §389 迭代 16 T0 在册复算值(合成底 #3E3731 与在册一致、6.7:1 逐位吻合手算);三关逐关 avg 无在册记录且素材不入 git 不可实查〔主区只读约束〕,最坏情况判据扩对归移交。**移交六件**——①S1 七项处置(最高优先);②迷雾三关扩对(素材入库/授权后脚本算 avg);③字面量墨收测前置=ui_kit 立常量;④TG 装饰带非文字判据另立探针段;⑤settlement_review 去向二选一;⑥probe_gate 整闸空窗复测。出口报告=design/qa/iter19-uibackfill-exit-report-2026-10-08.md;提交四笔 92c0598(T1+T2)/74d2f73(T3)/6339b63(T4 文档面)/本笔(台账记账)。 404. **迭代 19 尾裁微批三件(2026-10-08,裁决依据=用户 2026-10-08 六裁授权落地批〔裁甲=对比度存量 7 项另立修复批随尾裁批回绿/卡⓪resume tree_rules 受理闭环/卡③S1 换 setup-godot action〕;worktree g-plan2-iter19-followup 分支 iter19-followup-20261008 基点 0ba3ddf)**:三件独立提交全落——**a 对比度存量违例 7 项全修**(V1 结算 GP 账行 battle_end_overlay.gd:86-87 GOLD_DEEP→GOLD_INK 5.21:1/V2 商店行卡名 shop_overlay_view.gd:127 bold 缺省 INK_LABEL→显式传 WARM_SOFT 9.53:1/V3 bcard 价签 ui_kit.gd:677 GOLD→GOLD_INK 5.21:1〔三选一价签恒空串隐藏零波及实证:curio_pick_overlay.gd:109 传空串 price_l.visible=false〕/V4 营地铭牌副题 camp_overlay_view.gd:74 STONE→STONE_DEEP 5.98:1/V5 行价格+行进度 :342+:369 STONE→STONE_DEEP 6.70:1/V6 树分组头 :191 GOLD_DEEP→GOLD_INK 5.21:1/V7 工坊已解锁 :275+:278 GOLD_DEEP→GOLD_INK 5.84:1;改前 2.67/1.12/1.46/3.59/4.02/2.67/3.00 全越 4.5 带;只换文字墨零布局零文案,同位非文字共用甄别不动〔battle_end_overlay.gd:48 GOLD_DEEP 面板边框用途在册不动〕;ui_contrast_probe 测点表 7 对随代码同步改指新常量+行尾注新比率)——probe_gate 默认段 ui_contrast 15 对存量红清零→38 对全绿 PASS;**b 卡⓪resume tree_rules 受理闭环**(adventure_runner.gd resume_adventure :215 按 start_adventure :135 同式补一行类型防御三元受理,消费面 :938 set_tree_rules 下传现成零改;main.gd build_adventure_params「tree_rules 暂不消费」在册披露句退役改写〔呈现层注释允许〕;冒烟断言段 :4689-4698 追加续玩受理两查=resume_adventure 直呼三键参数面受理+runner._tree_rules 与传入一致〔v2 旧断点夹具同式最小断点〕)——冒烟 840→842/0+run_all 17 套全绿+probe_gate --click 全绿;**c tests.yml 换 action**(:37 nikeee/godot-actions/setup-godot@v2→chickensoft-games/setup-godot@v2〔卡③轻 CI 首跑 404 实证〕,既有裁注注释改写保留裁甲依据+404 实证,version 4.7.2 参数语义不变;纯静态核验=pyyaml 解析+uses 换装唯一+version 保留+缩进同构)。台账查撞:master 停 402 无 404 撞〔403=卡④预配在飞,本条按预配 404 记〕。出口报告=design/qa/iter19-followup-exit-report-2026-10-08.md;提交四笔 5891b5b(a)/768e366(b)/c489d16(c)/本笔(台账)。 diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index 3615c96b..cd142a93 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -2,6 +2,18 @@ 本清单是全项目待办事项的**唯一登记处**:各系统文档不再自建「待办」条目节,只留一行指路存根;新增待办事项一律直接登记本清单。条目按四个循环分组,组内按系统分节;每条事项一个四级标题,其下用粗体属性名成段——**现状**(现状与失义原因,自各系统文档原样搬运、措辞不改;跨文档重复条目合并为一条,登记在建议归属主责方名下,其余来源在现状中注明出处;相关但不同面的条目各自保留,现状末尾互相指认;搬运时仅将原文中的「本文 / 本系统」替换为对应系统名,并把相对链接改写为本文件位置可用的路径)、**归属**、**状态**。事项解决后**从本清单移除**(2026-09-18 用户拍板改口径,原"改状态留痕"废止):去向与依据登记当批台账与 active.md 快照,git 历史即留痕。 +#### render_gate 首跑 S1 存量红三件——shot_checker 断言锚漂移,处置权归用户(2026-10-08 登记,S1 停机点) + +**现状**:迭代 19 卡① 渲染闸(tools/render_gate.sh)首跑,tools/shot_checker.py 对现行构建首次全量跑——七屏骨架(A 类)/战斗明度三档带(C 类)/C 区面板对比度与纹理(B/D 类)**全过**(窗口态修正后实测:WCAG=6.14、纹理幅度=0.0、三档带 205>88>55);存量红全部集中在 B 类对比度断言的**取样锚漂移**(shot_probe.gd 的 meta 锚文本与现行文案失配,呈现层非缺陷、是断言锚失效),逐条: + +1. **菜单·副标题锚「改造批」失配**——现行副标题文案已是「扫雷的因 · 逐行兑现的果」(game/presentation/ui_assembler.gd:132),锚文本系迭代 9 建闸时旧文案。建议案:锚改指现行副标题(shot_probe.gd meta 行一词改动),断言保留。 +2. **菜单·帮助文锚「玩法:」载体已退役**——玩法速览牌 2026-10-06 用户令退役(「界面禁玩法文字墙」,ui_assembler.gd:100-101 注记在案),现行菜单面无任何「玩法:」标签,**断言载体系用户裁决有意移除**。建议案:本断言腿退役(shot_probe meta 键+shot_checker 菜单帮助文段同撤),不重锚——除非用户另指菜单面新文字锚。 +3. **战斗·明细带提示锚「结算明细」失配**——现行战斗屏明细带头提示=「上轮摘要(回看归「生涯」)」(battle_log_view.gd:47),旧「结算明细」文案标签不存在。建议案:锚改指「上轮摘要」(shot_probe.gd meta 行一词改动),断言保留。 + +**归属**:迭代 19 卡① S1 停机点(处置权归用户);裁后执行=shot_checker 断言面微批(一词锚改/一腿退役,卡① 续跑时随 T3 前置清障或另批) + +**状态**:待用户逐条裁(三案均零呈现层代码改动——只动探针锚/checker 断言腿);render_gate 在裁前保持红(checker 三条 FAIL 闸红语义正确),卡① T3-T6 未执行 + #### 旧行为 MC 行为锚全数作废——重锚归数值批(2026-10-06 登记) **现状**:案C 地图伤害账殿后批(git 7ef18ac,台账 371)改变每轮结算行为序——授予段不再兑付任何伤害(伤害形态单据暂存至战斗段末,段内序=主角先手→逐敌还手→地图账),主角死亡短路语义随之换代(「授予段伤害致死短路先手」路径消失)。既有全部 MC 行为锚与对拍锚所锚行为已不存在,**全数作废**;新段内序断言已入冒烟闸。按 sim-data MC 缓存层规则(无数值调整禁重跑),重锚不在改动批执行。 diff --git a/design/gdd/07-体验验证清单.md b/design/gdd/07-体验验证清单.md index 86cfbdb3..bcf6e148 100644 --- a/design/gdd/07-体验验证清单.md +++ b/design/gdd/07-体验验证清单.md @@ -149,7 +149,7 @@ ### ⑮ 秘宝词族换词全 UI 面目检 + 渲染像素闸补跑(2026-10-05 秘宝代码词族对齐批随批登记——迭代15③号卡) - **实机目检(「秘宝」字样全 UI 面,约 3 分钟)**:①C 区槽位标题应显示「秘宝」(原「魔法道具」,hero_info_panel_view 槽位组);②空槽悬停提示应显示「秘宝槽(空)」;③战斗总结三选一叙述行应显示「秘宝 / …」或「秘宝 / 随机获得一件」(原「魔法道具」开头);④换关 toast 应显示「上关剩余 N 件秘宝已随关卡结束清空」。**红灯信号**:任何界面残留「魔法道具」字样。 -- **渲染像素类闸补跑挂账**:本批屏蔽五件(golden_compare / shot_probe / fx_probe / ui_layout_probe / board_shot——组 A 文案变更使像素断言合法失效,按 goal 令弃断);**补跑时点=迭代 14 全落籍后的表现批周期闸一次性补跑**,与本批无独立补跑义务;届时若 golden 基线因文案换代失效,按换代流程重锚而非当缺陷修。**分工注记(2026-10-05 ui-struct-probe 批)**:结构类断言(互压/等宽/出屏)已由 `ui_struct_probe` 入轻闸默认必跑段(tools/probe_gate.sh),与像素五闸互补不替代——五闸补跑义务不变。 +- **渲染像素类闸补跑挂账**:本批屏蔽五件(golden_compare / shot_probe / fx_probe / ui_layout_probe / board_shot——组 A 文案变更使像素断言合法失效,按 goal 令弃断);**补跑时点=迭代 14 全落籍后的表现批周期闸一次性补跑**,与本批无独立补跑义务;届时若 golden 基线因文案换代失效,按换代流程重锚而非当缺陷修。**分工注记(2026-10-05 ui-struct-probe 批)**:结构类断言(互压/等宽/出屏)已由 `ui_struct_probe` 入轻闸默认必跑段(tools/probe_gate.sh),与像素五闸互补不替代——五闸补跑义务不变。**核销注记(2026-10-08 迭代 19 卡①自动化验收补齐批——机器补跑半随批核销)**:五件机器半全部归宿落籍并实测绿——shot_probe(扩屏七屏+shot_checker A-D 断言)/board_shot(L1-L3 取证+golden 逐像素 0.00%×3)/golden_compare 三件入 render_gate(窗口化自动闸),fx_probe 入 probe_gate --full(headless 逻辑层断言),ui_layout_probe 补 1280/1920 两档最小断言(首跑 PASS);五闸归宿对账见任务书-自动化验收补齐批 §一。**真人目检半(上方「秘宝」字样全 UI 面 3 分钟)仍归用户,不销号**。 ### ⑯ UI 结构批复验(2026-10-05 ui-struct-probe mini-loop 随批登记——标题屏双列修复+按钮宽度纪律,约 2 分钟) diff --git a/design/gdd/systems/表现与支撑/数据配置.md b/design/gdd/systems/表现与支撑/数据配置.md index 7736d807..b0e0c259 100644 --- a/design/gdd/systems/表现与支撑/数据配置.md +++ b/design/gdd/systems/表现与支撑/数据配置.md @@ -202,7 +202,7 @@ - 黄金向量:随机派生纯函数对登记向量逐个复算相等;同输入恒同输出;同序号重复调用恒等。 - 计数器纪律:连续取号跨模拟关卡切换,计数器单调不重置;热重载成败都不改变主种子与计数器。 - 向上取整解析:请求无精确行时返回**不低于**请求坐标的最近行并留一条警告;请求超出最大已有行时封顶取最大行并留警告;同类警告去重;精确命中零警告。 -- 越域与未播种:坐标低于合法域(关卡序号 <1、节点深度 <0)在进入解析前致命中止(Fatal 级诊断 + 进程退出码 1,切片探针 `smoke/config_fatal_probe.gd` 验证);高于最大已有行走封顶取最大行(警告留痕,不致命);未设主种子就取随机数致命中止,无默认种子兜底。 +- 越域与未播种:坐标低于合法域(关卡序号 <1、节点深度 <0)在进入解析前致命中止(Fatal 级诊断 + 进程退出码 1,致命类单测 `tests/unit/data_config/config_db_fatal_oob_test.gd` 验证——迭代 19 卡① T4 探针退役随批改指,原切片探针断言面与 run_all 致命类单测重复故退役);高于最大已有行走封顶取最大行(警告留痕,不致命);未设主种子就取随机数致命中止,无默认种子兜底。 - 热重载门:战斗中重载被拒绝且读数不变、不发通知;门未置位时新值可读且通知恰一次;校验失败保留旧表停留就绪态。 - 版本常量在位:表结构版本常量存在且全部表结构携带版本字段(高版本拒读判定归存档序列化)。 - 零字面量、零第二路径:静态扫描加人工复核确认玩法代码无数值字面量、无读取口之外的表加载、无全局随机函数调用。 diff --git a/design/qa/iter19-verify-exit-report-2026-10-08.md b/design/qa/iter19-verify-exit-report-2026-10-08.md new file mode 100644 index 00000000..640bcdcf --- /dev/null +++ b/design/qa/iter19-verify-exit-report-2026-10-08.md @@ -0,0 +1,99 @@ +# 迭代 19 卡①「自动化验收补齐批」出口报告——**完工终版**(2026-10-08;前版=S1 停机点版,同日续接完工全量改写) + +- worktree:`D:\code\g-plan2-iter19-verify`(分支 `iter19-verify-20261008`,基点 9160942 = master 顶部含卡⓪落籍;批末顶端见 §5 提交清单) +- 台账号:**399**(T0-T2 段)+ **400**(T3-T6 段;记账前以 `git -C D:/code/g-plan2 show master:active.md` 复查撞号——master 台账至 404,399/400 空缺未撞未顺延) +- MC/verifier:零跑(本迭代零跑+静态论证口径,全程未触) +- **停机点命中两次,均闭环或呈报**:①S1(render_gate 首跑存量 FAIL 三件锚漂移)——用户裁甲清障,本批续接落籍转绿;②S2(golden L2_underdark 越限 2.53%>2%)——停机诊断定位根因为探针抓帧时序缺陷(非视觉回归非平台差异),根因修复+基线换代后三连跑 0.00% 全绿,**容差 2%/16 用户冻结口径未动**(全案见 §2) + +--- + +## 1. S1 停机点全案(前版立案,本批闭环) + +首跑判定存量红 3 条,全部为 shot_checker B 类对比度断言取样锚漂移(呈现层无缺陷)。**用户 2026-10-08 裁甲清障,三件已全部落籍(提交 a623ec3)**: + +| # | 锚漂移 | 裁后落法 | 转绿实证 | +|---|---|---|---| +| 1 | 菜单·副标题「改造批」失配 | 锚改认现行文案「扫雷的因 · 逐行兑现的果」(ui_assembler.gd:132)的逐字子串「扫雷的因」——全工程唯一命中(contains 语义) | WCAG=8.44 纹理幅度=3.9 C-OK | +| 2 | 菜单·帮助文「玩法:」载体退役 | 断言腿整个退役:shot_probe meta 键+shot_checker 断言段+头注断言清单三处同撤不重锚(06 在册口径=同撤) | 腿不存在,checker 全 PASS | +| 3 | 战斗·明细带提示「结算明细」失配 | 锚改认现行「上轮摘要(回看归「生涯」)」(battle_log_view.gd:47)子串「上轮摘要」——唯一命中(摘要行文案系「上轮: …」不含「摘要」零碰撞;_log RichTextLabel 不在 Label 匹配面) | WCAG=11.94 纹理幅度=8.6 C-OK | + +清障后 render_gate 首段全绿:A 类骨架七屏全过(最低 career std=21.8 colors=794)、C 类明度三档带 205>87>55、B/D 类 hero 面板 WCAG=6.14 纹理 0.0。 + +## 2. S2 停机点全案(本批新增——golden 越限:诊断→根因→修复→换代) + +**命中**:S1 清障后 render_gate 首跑,golden L2_underdark changed=**2.53%**>2% 容差(基线入库时首对比 0.00%)。 + +**诊断**(停机呈报前取证):波动性越限,非确定性回归——L2 四跑 0.00/2.53/2.29/1.17%、L3 三跑 0.89/1.78/1.59%、L1 恒 0.00%;差异像素聚集在棋盘单一紧凑区块(x159-348/y159-444),目验=战争迷雾揭示程度差异。 + +**根因**:board_view.rebuild 有入场动画——全板淡入 motion(0.2)+R0 涟漪逐格延迟显形(motion(0.04×离板心距离)+motion(0.12),最远格 ≈0.56s);board_shot_probe 原制只等 6 个 process_frame(热机后 ≈0.1s)=**恒捕在涟漪中途态**,逐跑帧时序抖动使揭示 alpha 漂移。基线生成跑恰逢冷机慢帧捕到完结态,故入库时全过——基线锚在「动画完结态」这个事实上正确、但原探针不可复现的相位上。 + +**修复**(提交 3317076):抓帧前改时间制 `create_timer(1.2)s`(2×余量)+process_frame+frame_post_draw——锚定动画完结态。探针侧零运行时 diff(契约红线①内)。 + +**换代与稳定化**:golden_baseline_refresh.sh 换代三张基线(S3 素材齐盘守卫/Q2 临时区纪律全随)后**三连跑稳定性实证全 0.00%/0.00%/0.00%**,render_gate 复跑全绿(EXIT=0)。**容差 2%/16 未动**——S2 的用户裁决域是容差值,本次走根因修复未触;若未来跨平台/字体渲染推高 changed,仍按 S2 呈报定值。 + +## 3. 逐件产物(file 级——前版 T0-T2 + 本批 T3-T6) + +| 任务卡 | 产物 | 状态 | +|---|---|---| +| T0 | 基线复读记档+归属核对+环境修正两课(§5 前版) | ✅ | +| T1 | `tools/render_gate.sh`+shot_probe 扩屏七屏+board_shot_probe 取证三件套+两辅助件 | ✅(前版) | +| T2 | `game/tests/golden/` 三张基线+golden_board_crop.py+golden_baseline_refresh.sh+golden_compare 接线 | ✅(前版;**本批 S2 换代**,触发原因提交说明强制记载) | +| S1 | 06-待办清单条目级登记→用户裁甲→三件清障落籍(shot_probe.gd+shot_checker.py,提交 a623ec3) | ✅ 闭环 | +| S2 | board_shot_probe 时制等待+golden 三张换代(提交 3317076) | ✅ 闭环 | +| T3 | `tools/probe_gate.sh` --full 段=ui_probe/ui_tune_probe/t17_defer_probe/fx_probe 四探针收编(timeout 120=实测 17.4/49.6/63.0/21.9s 的 2-7×余量;PASS+退出码+残留三闸随骨架);头注「shot_probe 禁入本闸……归真人目检」纪律句删除改写(2026-10-07 用户授权);轻闸默认段零删减零改动(提交 b523f7d) | ✅ | +| T3 | `game/smoke/ui_layout_probe.gd` 零断言诊断件补最小布局断言转断言件:1280/1920 两档(基座 1280x800/顶层三区互斥不越界/board_view 含于 D 区/1920 档钉迭代 9 分辨率案甲契约——stretch 下逻辑视口与四区矩形恒定);首跑 PASS 退出码 0;闸归属归卡③裁点②候裁本批不入闸(同提交) | ✅ | +| T3/T4 | config_fatal_probe 退役(.gd+.uid——与 run_all config_db_fatal_oob_test 断言面重复,2026-10-02 呈报退役还账)+数据配置.md:205 指路句连带改指+v0.4 版本史行不动(历史记载);damage_probe 头注「诊断工具·非验收件」出一切闸;skill_rhythm_probe/board_stat_probe 头注「分析工具·非验收件」;全仓引用清查零残留(提交 b3404e8) | ✅ | +| T4 | tests/README.md 订正:套件数十五→**18**(第 18 套=卡④ `unit/core/fog_lantern_wiring_test` 注名;本分支基点早于卡④合并实测 17 套全绿,合并后 18)+断言数「752+」改**双口径**(运行时=run_smoke 打印〔分支基点 840 项 0 失败;master 合并卡②④后 846〕/静态=smoke 源码 `check(` 764 处——两口径不同各自浮动禁写死)+「十场景探针」去数化;**integration 空壳目录删除**(Q8),章程句并入 tests/README(提交 fcb91ce+57a7919 前笔) | ✅ | +| T5 | CLAUDE.md 分级周期口径**重闸定义正式扩容**:自此批末重闸=轻闸+--click+--full+render_gate+run_all+doc 三闸/熵增闸;真人目检仍归用户;MC 仍按触发细则。落点=硬性红线·分级周期口径句(重闸定义在 CLAUDE.md 唯一在册处=任务书所称 loop §5)(提交 57a7919) | ✅ | +| T6 | 07-体验验证清单 ⑮ **机器补跑半核销注记**(五闸归宿落籍实测绿;真人目检半仍归用户不销号);熵增闸图像白名单收编 game/tests/golden/(T2 落库漏改致红——同批修订 commit 留痕);批末重闸全跑(§4);出口报告本篇;台账 400 | ✅ | +| T6 | roadmap 迭代 19 行换代:**未动,归主控**——本分支 roadmap 落后 master 多个合并(卡②④已改该行),合并时行冲突重放归主控(goal 既有分工) | ⏭ 移交 | + +## 4. 闸数字(改前/改后全列) + +| 闸 | T0 基线(基点 9160942) | 批末现状(完工态) | +|---|---|---| +| 冒烟 run_smoke | 840 项 0 失败三闸过 | **840 项 0 失败三闸过 EXIT=0**(批末实跑;master 合并卡②④后=846,README 双口径已注) | +| 单元 run_all | 17 套件全绿 | **17 套件全绿 EXIT=0**(批末实跑;卡④第 18 套合并后到位) | +| probe_gate 默认段 | 全绿 | **全绿 EXIT=0**(默认段零删减实证) | +| probe_gate --click | 全绿 | **全绿**(click PASS 问题=0) | +| probe_gate --full | —(不存在) | **新增全绿**:ui_probe/ui_tune_probe/t17_defer_probe/fx_probe 四 PASS 残留 0 | +| render_gate | —(S1 版首跑 shot 段红) | **全绿 EXIT=0**:shot_checker A-D 全 PASS(新锚 WCAG 8.44/11.94)+fx/board 取证绿+golden 0.00%×3+残留 0 | +| ui_layout_probe(新断言) | 零断言 | **PASS 退出码 0**(1280/1920 两档首跑) | +| doc_link_audit / doc_term_audit | 未跑 | **绿 EXIT=0**(输出内有存量挂账条目:奇物.md:130 两死链/06:419 archive 死链/数据配置.md:223 历史行 backtick 指已退役探针——审计口径=报告不 FAIL) | +| gdd_lint | 未跑(基点即红,本批归因:06/07 两文件 R3/R7 违例**基点既有**——base 已有 382 字括号/12 处删除线,非本批引入) | **EXIT=1(存量红)**——本批零新增违例;存量处置呈报不代修(§6) | +| 熵增闸 repo_structure_audit | 基点即红(golden 三张 T2 落库漏改白名单+design/diagrams 基点既有) | golden 件**已修**(白名单收编);**design/diagrams 越界=存量红 EXIT=1**——脚本自文「新分类先过用户拍板」,呈报不代裁(§6) | +| 熵增/doc 三闸/MC | 未跑 | 如上;MC=迭代零跑口径 | + +## 5. 提交清单(本批续接段;前版五笔 8246790/3249697/87f749d/fe124a4/f397b80 在先) + +| hash | message 摘要 | +|---|---| +| a623ec3 | 卡①锚清障(续)——S1 裁甲三件落籍+render_gate 清障后首跑 shot_checker 全 PASS 实证 | +| 3317076 | 卡①S2 观察项根因修复——board_shot_probe 抓帧时序+golden 三张基线换代(触发原因强制记载) | +| b523f7d | 卡①T3——probe_gate --full 四探针收编+头注改写+ui_layout_probe 1280/1920 两档断言 | +| b3404e8 | 卡①T3/T4——config_fatal_probe 退役+数据配置.md:205 改指+三件头注标注 | +| fcb91ce | 卡①T4账务——tests/README 18 套双口径+integration 删壳章程并入 | +| 57a7919 | 卡①T5——CLAUDE.md 分级周期口径重闸定义扩容 | +| (台账笔) | 卡①T6——07⑮ 机器补跑半核销注记 | +| (本笔) | 卡①T6——熵增闸白名单收编 golden+README 840 订正+出口报告完工终版+台账 400 | + +(过程披露:中途一次 reset 分批重整——git rm 暂存先行混入 T3 提交,soft reset 重新分五批干净落籍;reflog 在案,零改动丢失。) + +## 6. 未决移交项 + +1. **design/diagrams 越界(熵增闸存量红,基点 00712d5 既有)**——脚本自文「新分类先过用户拍板再改两处白名单」。可修案:甲=用户拍板 `design/diagrams` 入 DESIGN_SUBDIRS 白名单(archify 总图批产物归位);乙=迁出 design/ 归位合法区。**合并前须用户裁**,本批不代裁。 +2. **gdd_lint 存量红两文件(06-待办清单/07-体验验证清单,基点既有)**——R3 删除线残留/R7 长括号注记。本批零新增。处置建议:随下批文档批清理(06 条目历史形态与 R3 规则的口径取舍建议用户裁:历史登记条目的删除线是否豁免)。 +3. **数据配置.md:223(v0.4 版本史行)backtick 指已退役探针**——doc_link_audit 报告项(不 FAIL)。历史行不改写原则下留观;如需清零可下批把该行 backtick 去格式化(prose 化)+补 v0.5 退役行。 +4. **主区合并+合并复跑**归主控:本分支 13 笔提交待并;合并时 roadmap 迭代 19 行冲突重放归主控(§3 T6 行);合并复跑含 render_gate(任务书 T6 口径)。 +5. **卡③裁点②(ui_layout 入 CI/闸归属)**:断言面已就绪(PASS/FAIL/退出码形态),候主控裁。 +6. 1920 档:render_gate 单档 1280(Q4)+ui_layout 两档断言钉案甲构图恒等,注记在两处头注。 +7. 商店房截图纳屏:留 B 批(考证结论在 shot_probe 头注)。 + +## 7. 契约自检(完工态) + +- 运行时代码零 diff:本批续接段改动面=game/smoke/ 探针四件+tools/ 三件+game/tests/(golden 基线+README+integration 删壳)+文档四件——红线内 ✅(`git diff 9160942..HEAD --stat` 复核无 core/presentation/data 触碰;board_view 动画是既有呈现行为,本批只改探针等待时序) +- 轻闸默认段零删减:probe_gate 默认段三探针+残留闸逐字未动,--full 只增不改;--click 语义原样 ✅(FORM1/FORM2 两形态对照实证) +- golden 基线边界:只入 game/tests/golden/、素材齐盘态、换代触发原因提交说明强制记载 ✅ +- 过程产物即弃:全部截图/诊断拼图落 mktemp 与 /tmp,用后即弃,git 状态零 PNG 残留 ✅ +- 在飞避让:主区零触碰(台账/roadmap 只读查撞)✅ +- 改纪律落文:probe_gate 头注与 CLAUDE.md 同批改写完成,新旧两口径并存态已消除 ✅ diff --git a/game/smoke/board_shot_probe.gd b/game/smoke/board_shot_probe.gd index b340052a..3369a1db 100644 --- a/game/smoke/board_shot_probe.gd +++ b/game/smoke/board_shot_probe.gd @@ -4,12 +4,22 @@ # Godot_console.exe --path . res://smoke/board_shot_probe.tscn -- <输出目录> # 流程:L1/L2/L3 各起一板(真实 LevelRunner+settle_batch 驱动 ~18 步 novice——自然揭示数字/旗), # BoardView 真渲染,frame_post_draw 同步后抓视口 PNG。零存档触碰(无 save_manager 引用)。 +# 迭代 19 卡① T1/T2(2026-10-08):①headless fail-fast 护栏(shot_probe 同款); +# ②空纹理取证 fail(Q7——取证段失败=闸红,零字节/空纹理不允许静默落盘); +# ③board_meta.json 取样坐标落盘(棋盘视图全局矩形——golden 前置裁剪坐标源,禁硬编码像素)。 extends Node const AutoPlayer := preload("res://smoke/auto_player.gd") +var _meta := {} # 棋盘视图实测几何(全局 rect——tools/golden_board_crop.py 取样坐标源) + func _ready() -> void: + # 运行前提=窗口化真渲染面(shot_probe 同款护栏——headless 下 frame_post_draw 永不触发+视口纹理恒空) + if DisplayServer.get_name() == "headless": + print("[board_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空);正主=tools/render_gate.sh 取证段") + get_tree().quit(1) + return var args := OS.get_cmdline_user_args() var out := args[0] if args.size() >= 1 and args[0] != "" else "user://board-shots" DirAccess.make_dir_recursive_absolute(out) @@ -49,14 +59,28 @@ func _ready() -> void: view.set_world_font(ui_font) add_child(view) view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper) - for i in range(6): - await get_tree().process_frame + # 抓帧前等入场动画完结(迭代 19 卡① S2 观察项根因修复 2026-10-08):rebuild 有全板淡入 + # motion(0.2)+R0 涟漪逐格延迟显形(motion(0.04×离板心距离)+motion(0.12),最远格 ≈0.56s), + # 原制 6 个 process_frame 热机后 ≈0.1s=捕在涟漪中途——逐跑帧时序抖动使揭示 alpha 漂移, + # golden L2/L3 跨 2% 容差线波动(实测 0.00-2.53%)。改时间制 1.2s(2×余量)=锚定动画完结态。 + await get_tree().create_timer(1.2).timeout + await get_tree().process_frame await RenderingServer.frame_post_draw var img := get_viewport().get_texture().get_image() + if img == null or img.is_empty(): + push_error("[board_shot] FAIL — 抓帧空纹理: %s(Q7 取证语义=闸红)" % tag) + get_tree().quit(1) + return var path := "%s/board_%s.png" % [out, tag] img.save_png(path) + _meta[tag] = {"x": view.global_position.x, "y": view.global_position.y, + "w": view.size.x, "h": view.size.y} print("[board_shot] saved %s (%dx%d)" % [path, img.get_width(), img.get_height()]) view.queue_free() await get_tree().process_frame + var mf := FileAccess.open(out.path_join("board_meta.json"), FileAccess.WRITE) + if mf != null: + mf.store_string(JSON.stringify(_meta)) + mf.close() print("[board_shot] done") get_tree().quit(0) diff --git a/game/smoke/board_stat_probe.gd b/game/smoke/board_stat_probe.gd index 7f75e7de..0374f856 100644 --- a/game/smoke/board_stat_probe.gd +++ b/game/smoke/board_stat_probe.gd @@ -1,4 +1,5 @@ # VERTICAL SLICE - NOT FOR PRODUCTION +# 定位:分析工具·非验收件(2026-10-08 迭代 19 卡① T3 收编排标注)——零断言取数工具,不出入任何闸。 # Date: 2026-09-19 # 棋盘板形统计探针(清图支配性对账重跑批取数工具——判式"实测板形"口径的数据源): # 对 board_gen.tres 每行 (L, D) 生成 N 张板,统计幸存雷数 |M| 与安全格数 |S| diff --git a/game/smoke/config_fatal_probe.gd b/game/smoke/config_fatal_probe.gd deleted file mode 100644 index de7ad1d8..00000000 --- a/game/smoke/config_fatal_probe.gd +++ /dev/null @@ -1,22 +0,0 @@ -# VERTICAL SLICE - NOT FOR PRODUCTION -# 验证问题:运行期数据致命通道是否成立(2026-09-18 用户拍板——进度坐标越域记 Fatal 级日志后进程退出,退出码 1) -# 运行:Godot_console.exe --headless --path . --script res://smoke/config_fatal_probe.gd -# 预期:进程以退出码 1 终止;打印 FAIL 行或退出码 0 即致命通道未生效。 -extends SceneTree - -var _frames := 0 - -func _initialize() -> void: - var db = load("res://core/config_db.gd").new() - root.add_child(db) # 入树——autoload 同款生命周期 - db.load_tables("res://data/tables", false) - db.resolve_enemy_rowset(0, 0) # 越域:关卡序号 <1 → Fatal,quit(1) 排队帧末退出 - print("FATAL-PROBE — Fatal 已记,等待帧末退出(退出码应为 1)") - -func _process(_delta: float) -> bool: - _frames += 1 - if _frames > 5: - print("FATAL-PROBE FAIL — 5 帧内未退出,致命通道未生效") - quit(0) - return true - return false diff --git a/game/smoke/config_fatal_probe.gd.uid b/game/smoke/config_fatal_probe.gd.uid deleted file mode 100644 index cd47d27d..00000000 --- a/game/smoke/config_fatal_probe.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cg2dgdnbkd7op diff --git a/game/smoke/damage_probe.gd b/game/smoke/damage_probe.gd index 56b2e4df..4f84ef9f 100644 --- a/game/smoke/damage_probe.gd +++ b/game/smoke/damage_probe.gd @@ -1,4 +1,7 @@ # VERTICAL SLICE - NOT FOR PRODUCTION +# 定位:诊断工具·非验收件(2026-10-08 迭代 19 卡① T3 收编排标注)——A1 红灯=改前既有诊断面 +# (design/qa/m4iter14-mineq-exit-report §四.7),不出入任何闸(probe_gate/render_gate/run_smoke 全不涉); +# roadmap M4 数值批引用其修复义务不受本标注影响。 # Date: 2026-09-13(playtest-2026-09-12 报告 Top1——零承伤根因分割探针) # 2026-09-16 改掷点口径(护甲等级掷点批):defense_up 墙已随 AC 换装退役—— # H1 旧 defense_up 叠满墙失义(hero_defense 池现仅 off_guard 正向行,×0 墙结构面消失) diff --git a/game/smoke/fx_shot_probe.gd b/game/smoke/fx_shot_probe.gd index 4c346b91..c29fddd4 100644 --- a/game/smoke/fx_shot_probe.gd +++ b/game/smoke/fx_shot_probe.gd @@ -29,6 +29,8 @@ func _ready() -> void: add_child(main) await get_tree().process_frame ProbeUiUtil.isolate_save(main, "fx_shot_probe", false, true, false) + # 隔离档即时应用(迭代 19 卡① T1——真实档 fullscreen 态会污染抓帧尺寸〔shot_probe 同款修正〕) + main.settings_store.apply() for i in 8: await get_tree().process_frame # UI 装配完成等足帧(canvas_item 空引用窗口) var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains") diff --git a/game/smoke/shot_probe.gd b/game/smoke/shot_probe.gd index f2d04471..83a646c4 100644 --- a/game/smoke/shot_probe.gd +++ b/game/smoke/shot_probe.gd @@ -1,12 +1,17 @@ # VERTICAL SLICE - NOT FOR PRODUCTION -# 整屏截图探针(迭代 9 卡⑰a 2026-09-30——呈现层测试分级 B 档基建:四屏视口截图程序化产出)。 +# 整屏截图探针(迭代 9 卡⑰a 2026-09-30——呈现层测试分级 B 档基建:视口截图程序化产出; +# 迭代 19 卡① T1 2026-10-08 扩屏四屏→七屏——新纳 设置面/生涯页/暂停层,正主=tools/render_gate.sh)。 # 必须**窗口化**运行(真渲染设备——headless 无渲染面,get_image 得到空纹理): # Godot_console.exe --path . res://smoke/shot_probe.tscn -- <输出目录> # 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档, -# 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 营地(campbtn)→ -# 关卡画面(lvbtn→停点出图)→ 战斗(level_screen.press_candidate(0) 真实选路提交)。 +# 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 设置面(settingsbtn +# 真实信号路径)→ 生涯页(careerbtn 同款)→ 营地(campbtn)→ 关卡画面(lvbtn→停点出图)→ +# 战斗(level_screen.press_candidate(0) 真实选路提交)→ 暂停层(地图屏暂停角标真实信号路径)。 # 每屏双帧 + frame_post_draw 同步后抓 get_viewport().get_texture(),PNG 落输出目录; -# 最小自检 = 四件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。 +# 最小自检 = 七件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。 +# 商店房注记(迭代 19 卡① T1 考证结论,代拍板白名单授权):真实路径必经 L1 打穿→L2 商店节点 +# 选路(ui_click_probe 实证商店层在 L2 Boss 前必经位)——探针现行驱动只进首战节点,低代价确定性 +# 到达不成立;合成投喂(ui_struct_probe 同款)破坏本探针真实路径口径 → 不纳屏,注记留 B 批。 extends Node const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") # T6 收口:树寻址/隔离样板单点 @@ -32,12 +37,57 @@ func _ready() -> void: await get_tree().process_frame # ── 存档/元进度/设置三面隔离(ui_click_probe 同款——真档零污染;历史语义 = contains 寻址) ── ProbeUiUtil.isolate_save(main, "smoke_shot_probe", false, true, false) + # 隔离档即时应用(迭代 19 卡① T1——main 装配已按真实设置恢复过窗口态〔本机实测:真实档 + # fullscreen=true 时窗口进全屏,视口纹理 2304x1440≠1280 基准档,checker 尺寸/取样全盘失真〕; + # apply 隔离档出厂默认=窗口化 1280x800,恢复本探针设计前提。探针侧环境修正,零运行时改动) + main.settings_store.apply() await get_tree().process_frame - # ① 菜单(启动态) - _meta["menu_sub"] = ProbeUiUtil.label_rect(main._start_overlay, "改造批") - _meta["menu_help"] = ProbeUiUtil.label_rect(main._start_overlay, "玩法:") + # ① 菜单(启动态)——meta 锚经 S1 裁后换代(2026-10-08 用户裁:副标题锚改认现行文案; + # 帮助文腿整个退役——载体玩法速览牌 2026-10-06 用户令退役,checker 对应断言段同撤) + _meta["menu_sub"] = ProbeUiUtil.label_rect(main._start_overlay, "扫雷的因") _meta["menu_title"] = ProbeUiUtil.label_rect(main._start_overlay, "Dungeon Sweeper") await _snap("menu") + # ①b 设置面(迭代 19 卡① T1 扩屏——settingsbtn 真实信号路径,开合同款真实钮) + var setbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "设置", "prefix") + if setbtn == null: + _problems.append("设置入口缺失(开始层 settingsbtn)") + else: + setbtn.pressed.emit() + for i in 3: + await get_tree().process_frame + if not main.settings_overlay.visible: + _problems.append("设置面未打开(settingsbtn 路径)") + else: + _meta["settings_title"] = ProbeUiUtil.label_rect(main.settings_overlay, "设置") + await _snap("settings") + var set_close: Button = ProbeUiUtil.find_button(main.settings_overlay, "返回", "prefix") + if set_close == null: + _problems.append("设置面返回钮缺失") + else: + set_close.pressed.emit() + await get_tree().process_frame + # ①c 生涯页(迭代 19 卡① T1 扩屏——careerbtn 真实信号路径,ui_struct_probe 同款入口链) + var careerbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "生涯", "prefix") + if careerbtn == null: + _problems.append("生涯入口缺失(开始层 careerbtn)") + else: + careerbtn.pressed.emit() + for i in 3: + await get_tree().process_frame + if not main.career_overlay.visible or main._start_overlay.visible: + _problems.append("生涯页未打开或开始层未隐藏(careerbtn 路径)") + else: + _meta["career_title"] = ProbeUiUtil.label_rect(main.career_overlay, "生 涯") + await _snap("career") + var career_close: Button = ProbeUiUtil.find_button(main.career_overlay, "返回", "prefix") + if career_close == null: + _problems.append("生涯页返回钮缺失") + else: + career_close.pressed.emit() + for i in 3: + await get_tree().process_frame + if main.career_overlay.visible or not main._start_overlay.visible: + _problems.append("生涯页关闭后页未隐藏或开始层未恢复") # ② 营地 var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "contains") if campbtn == null: @@ -73,8 +123,8 @@ func _ready() -> void: for i in 10: await get_tree().process_frame await _snap("battle") - # 战斗屏文字框/明度带锚点(全局 rect——checker 取样坐标源) - _meta["battle_log_hint"] = ProbeUiUtil.label_rect(main.stage, "结算明细") + # 战斗屏文字框/明度带锚点(全局 rect——checker 取样坐标源;明细带锚 S1 裁后改认现行文案) + _meta["battle_log_hint"] = ProbeUiUtil.label_rect(main.stage, "上轮摘要") _meta["hero_panel"] = _node_rect(ProbeUiUtil.find_by_script(main, "hero_info_panel_view")) _meta["stage_view"] = _node_rect(main.stage) _meta["board_zone"] = _node_rect(main.board_zone) @@ -84,6 +134,27 @@ func _ready() -> void: var dc := int(Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME)) _meta["battle_draw_calls"] = dc print("[shot_probe] battle draw_calls=%d(基线 270/目标 <200)" % dc) + # ⑤ 暂停层(迭代 19 卡① T1 扩屏——战斗屏暂停角标真实信号路径;层在战斗屏之上开合) + var pb: Button = main.board_zone.pause_badge + if pb == null or not pb.visible or pb.disabled: + _problems.append("暂停入口角标缺失/置灰(战斗屏 board_zone)") + else: + pb.pressed.emit() + for i in 3: + await get_tree().process_frame + if not main.pause_overlay.visible: + _problems.append("暂停层未打开(角标路径)") + else: + _meta["pause_title"] = ProbeUiUtil.label_rect(main.pause_overlay, "暂停") + await _snap("pause") + var pause_close: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if pause_close == null: + _problems.append("暂停层继续钮缺失") + else: + pause_close.pressed.emit() + await get_tree().process_frame + if main.pause_overlay.visible: + _problems.append("暂停层关闭后未隐藏(继续钮路径)") else: _problems.append("关卡画面未出现(lvbtn 路径)") # ── 几何元数据落盘(shot_checker 取样坐标源) ── @@ -91,8 +162,8 @@ func _ready() -> void: if mf != null: mf.store_string(JSON.stringify(_meta)) mf.close() - # ── 最小自检:四件存在且非零字节 ── - for s in ["menu", "camp", "level", "battle"]: + # ── 最小自检:七件存在且非零字节(迭代 19 卡① T1 扩屏后口径) ── + for s in ["menu", "settings", "career", "camp", "level", "battle", "pause"]: var p := out_dir.path_join("shot_%s.png" % s) if not FileAccess.file_exists(p): _problems.append("截图缺失: %s" % p) diff --git a/game/smoke/skill_rhythm_probe.gd b/game/smoke/skill_rhythm_probe.gd index e0378012..b1beeab9 100644 --- a/game/smoke/skill_rhythm_probe.gd +++ b/game/smoke/skill_rhythm_probe.gd @@ -1,4 +1,5 @@ # VERTICAL SLICE - NOT FOR PRODUCTION +# 定位:分析工具·非验收件(2026-10-08 迭代 19 卡① T3 收编排标注)——零断言取数工具,不出入任何闸。 # Date: 2026-09-19 # 技能池节奏探针:地精一伙 (1,1) 750 出招零突刺异常的定位—— # ① 表侧:resolve_enemy_rowset(1,1) 的池快照内容; diff --git a/game/smoke/ui_layout_probe.gd b/game/smoke/ui_layout_probe.gd index 582f4d6d..459a729b 100644 --- a/game/smoke/ui_layout_probe.gd +++ b/game/smoke/ui_layout_probe.gd @@ -1,6 +1,11 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # Date: 2026-09-13(四区布局诊断探针——"内容未满幅/棋盘底裁切"回归定位:打印窗口/视口/四区实际矩形 # 与最小尺寸、展开旗标,对照 battle.md §Layout 1280×800 基座规格) +# 迭代 19 卡① T5(2026-10-08):零断言诊断件补最小布局断言转断言件——1280/1920 两档: +# ①基座 1280×800(battle.md §Layout)②顶层三区(B=stage/C=info_panel/D=board_zone)互不重叠不越视口 +# ③board_view(=board_zone 子节点别名)不越视口且含于 D 区 ④1920 档钉分辨率案甲契约(迭代 9 案甲—— +# stretch=canvas_items/aspect=keep 下逻辑视口与四区矩形恒定=构图与基准档一致);退出码/PASS-FAIL 语义 +# 具备闸可接形态(闸归属归卡③裁点②候裁,本批不入闸)。原诊断打印面全保留。 # 运行:Godot_console.exe --headless --path . res://smoke/ui_layout_probe.tscn extends Node @@ -43,7 +48,61 @@ func _ready() -> void: root_box.anchor_left, root_box.anchor_top, root_box.anchor_right, root_box.anchor_bottom, root_box.offset_left, root_box.offset_top, root_box.offset_right, root_box.offset_bottom, root_box.size]) print("board_view cell_size=%s board=%s" % [main.board_view.get("cell_size"), main.board_view.board()]) - get_tree().quit(0) + + # ── 最小布局断言(迭代 19 卡① T5——1280/1920 两档;语义见头注) ── + var problems: PackedStringArray = [] + var vr: Rect2 = get_viewport().get_visible_rect() + if vr.size != Vector2(1280, 800): + problems.append("基座视口 %s ≠ 1280x800(battle.md §Layout)" % vr.size) + var azones := {"stage": main.stage, "info_panel": main.info_panel, "board_zone": main.board_zone} + var arects := {} + for k in azones: + var actl: Control = azones[k] + if actl == null: + problems.append("%s=null" % k) + continue + var ar := Rect2(actl.global_position, actl.size) + arects[k] = ar + if ar.size.x <= 0.0 or ar.size.y <= 0.0: + problems.append("%s 零尺寸 %s" % [k, ar.size]) + elif not vr.encloses(ar): + problems.append("%s 越视口 %s ⊄ %s" % [k, ar, vr]) + var bvc: Control = main.board_view + if bvc == null: + problems.append("board_view=null") + else: + var br := Rect2(bvc.global_position, bvc.size) + if not vr.encloses(br): + problems.append("board_view 越视口 %s ⊄ %s" % [br, vr]) + if arects.has("board_zone") and not arects["board_zone"].encloses(br): + problems.append("board_view 越出 board_zone(%s ⊄ %s)" % [br, arects["board_zone"]]) + var akeys := arects.keys() + for i in range(akeys.size()): + for j in range(i + 1, akeys.size()): + if arects[akeys[i]].intersects(arects[akeys[j]]): + problems.append("顶层区重叠 %s×%s(%s ∩ %s)" % [akeys[i], akeys[j], arects[akeys[i]], arects[akeys[j]]]) + # 1920 档(Q4 注记对应面:单档 1280 闸+本两档断言钉 1920 构图恒等——分辨率维度不重复建闸) + get_window().size = Vector2i(1920, 1080) + for i in range(4): + await get_tree().process_frame + var vr2: Rect2 = get_viewport().get_visible_rect() + if vr2 != vr: + problems.append("1920 档逻辑视口漂移 %s ≠ %s(案甲:stretch 基座恒定)" % [vr2, vr]) + for k in azones: + var actl2: Control = azones[k] + if actl2 == null: + continue + var ar2 := Rect2(actl2.global_position, actl2.size) + if arects.has(k) and ar2 != arects[k]: + problems.append("1920 档 %s 矩形漂移 %s ≠ %s(案甲:构图与基准档一致)" % [k, ar2, arects[k]]) + if problems.is_empty(): + print("[layout_probe] PASS — 1280/1920 两档全过(基座1280x800/顶层三区互斥不越界/board_view含于D区/案甲构图恒定)") + get_tree().quit(0) + else: + for p in problems: + print("[layout_probe] FAIL — " + p) + print("[layout_probe] FAIL — %d 项断言未过" % problems.size()) + get_tree().quit(1) func _dump_ctrl(c: Control, depth: int) -> void: diff --git a/game/tests/README.md b/game/tests/README.md index ed9a53b2..6148a12c 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -4,20 +4,28 @@ **Test Framework**: 双载体(ADR-0004)——`--headless --script` 逻辑冒烟 + headless 场景探针;gdUnit4 **deferred**(D2 OQ1 关闭形态,正式 CI 期再议) **CI**: `.github/workflows/tests.yml`(首次 push 到 GitHub 后自动生效;仓库已初始化 git、尚未配置远端) **Setup date**: 2026-09-12(2026-09-27 `7f24b640` 三层收敛:根 tests/ 并入 `game/tests/`、docs 并入 `design/dev/`、production 解散) -**参考实现**: `game/smoke/`(冒烟 752+ 断言 + 十场景探针——正式工程的活样板) +**参考实现**: `game/smoke/`(冒烟断言 + 场景探针族——正式工程的活样板;断言计数双口径见下节) ## Directory Layout ``` tests/ unit/ # 隔离单元测试(公式/状态机/纯逻辑)——extends SceneTree + quit(code) - integration/ # 跨系统测试(Core-Core 集成、回放确定性、存档往返) smoke/ # critical-paths.md——人工 gate 清单(/smoke-check 读取) evidence/ # 截图与人工签核记录 run_all.gd # CI 冒烟统一入口(套件 manifest 协议,D2 AC-2b) static_scan.sh # ADR-0004 静态扫描域 S1–S8 可执行子集 ``` +> integration/ 目录已删空壳(迭代 19 卡① T4/Q8 2026-10-08——彼时仅有章程 README 无实体)。章程句保留在此:将来跨系统测试(Core-Core 集成、回放确定性〔同种子步骤流逐行相同——B1 AC-33 形态〕、存档/RNG 快照往返、A1×A2 双包含端到端)落地时随批建 `tests/integration/` 迁入;各 GDD 登记的 deferral 集成用例以对侧登记为生效条件。 + +## 断言计数双口径(2026-10-08 迭代 19 卡① T4 订正) + +历史 README 写死「752+ 断言/十五套件」必然漂移,改为两种口径并注明语义: + +- **运行时口径**:`python tools/run_smoke.py` 打印的通过项数(迭代 19 卡①分支基点实测 **840 项 0 失败**,2026-10-08;master 合并卡②④后为 846——随断言面增减浮动,以当次跑测打印为准)——循环内断言按执行次数计。 +- **静态口径**:`game/smoke/` 源码 `check(` 调用点计数(2026-10-08 本分支实测 **764 处**)——按源码位置计,一处在循环内即多次执行,故两口径数值不同且各自浮动,禁互相换算或写死。 + ## Running Tests ```bash @@ -32,7 +40,7 @@ godot --headless --path . res://smoke/.tscn timeout 120s godot --headless --script tests/run_all.gd ``` -> 单元/契约套件状态(单元测试与契约层批 2026-09-28 立基,后随迭代 7/8 增补):`_suites()` manifest 已登记实跑十五套件——data_config(ConfigDB 加载/进度坐标 ceil 解析/fatal 退出码/统一随机流)、core(秘宝结构冻结/断点续玩)、battle_settlement(DamageMath 纯函数/DamageController 伤害单据五配方)、contracts(系统间接口契约断言)、presentation(节奏队列/奖励浮层/字体资产/地形瓦片集);致命类独立进程 `expect=1`。期望值一律由表读值推导(零玩法数值字面量)、随机只经 RngService 统一流。 +> 单元/契约套件状态(单元测试与契约层批 2026-09-28 立基,后随迭代 7/8 增补):`_suites()` manifest 已登记实跑 **18 套件**(第 18 套 = 迭代 19 卡④提灯载体批新增 `unit/core/fog_lantern_wiring_test` 提灯装配链〔行加载/schema/只读出口双态/enc 注入面〕——本分支基点早于卡④合并时实测 17 套全绿,合并后 18)——data_config(ConfigDB 加载/进度坐标 ceil 解析/fatal 退出码/统一随机流)、core(秘宝结构冻结/断点续玩/提灯装配链)、battle_settlement(DamageMath 纯函数/DamageController 伤害单据五配方)、contracts(系统间接口契约断言)、presentation(节奏队列/奖励浮层/字体资产/地形瓦片集);致命类独立进程 `expect=1`。期望值一律由表读值推导(零玩法数值字面量)、随机只经 RngService 统一流。 **协议要点**(D2 冒烟协议 + ADR-0004): - 冒烟脚本 `extends SceneTree` 以获得 `quit(code)`;成功 `quit(0)`、失败 `quit(1)` @@ -52,7 +60,7 @@ timeout 120s godot --headless --script tests/run_all.gd | Story Type | Required Evidence | Location | |---|---|---| | Logic | Automated unit test — must pass | `tests/unit/[system]/` | -| Integration | Integration test OR playtest doc | `tests/integration/[system]/` | +| Integration | Integration test OR playtest doc | 落地时随批建 `tests/integration/[system]/`(目录已删空壳,章程见上) | | Visual/Feel | Screenshot + lead sign-off | `tests/evidence/` | | UI | Manual walkthrough OR interaction test(场景探针为首选) | `tests/evidence/` | | Config/Data | Smoke check pass | `design/qa/` | diff --git a/game/tests/golden/board_L1_dungeon.png b/game/tests/golden/board_L1_dungeon.png new file mode 100644 index 00000000..795e7b93 Binary files /dev/null and b/game/tests/golden/board_L1_dungeon.png differ diff --git a/game/tests/golden/board_L2_underdark.png b/game/tests/golden/board_L2_underdark.png new file mode 100644 index 00000000..93e1ea64 Binary files /dev/null and b/game/tests/golden/board_L2_underdark.png differ diff --git a/game/tests/golden/board_L3_deep_underdark.png b/game/tests/golden/board_L3_deep_underdark.png new file mode 100644 index 00000000..f4b25ace Binary files /dev/null and b/game/tests/golden/board_L3_deep_underdark.png differ diff --git a/game/tests/integration/README.md b/game/tests/integration/README.md deleted file mode 100644 index 376628c6..00000000 --- a/game/tests/integration/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# tests/integration/ — 跨系统测试 - -承载:Core-Core 集成(真 A3 + 真 B2/B3/B4 容器实例)、回放确定性(同种子步骤流逐行相同——B1 AC-33 形态)、存档/RNG 快照往返(D3 落地后)、A1×A2 双包含端到端。 - -各 GDD 登记的 deferral 集成用例(以对侧登记为生效条件)随对应系统实现迁入本目录。 diff --git a/tools/golden_baseline_refresh.sh b/tools/golden_baseline_refresh.sh new file mode 100644 index 00000000..0d61f280 --- /dev/null +++ b/tools/golden_baseline_refresh.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# 棋盘 golden 基线换代辅助(迭代 19 卡① T2 2026-10-08——呈现类改动批专用)。 +# 权限口径(Q6):呈现类改动批内 agent 自行换代+提交说明**强制写明触发原因**;数值/逻辑批禁碰。 +# 前置条件(Q5/S3):素材必须齐盘(字体/棋盘瓦片/英雄/旗纹理——本脚本逐项守卫, +# 缺位即拒跑退出 1=基线禁锚字形回退态;缺位时先拷主区素材+导入边车再跑)。 +# 流程:窗口化 board_shot_probe 抓 L1-L3 整屏帧+board_meta.json → golden_board_crop.py +# 按 meta 取样坐标裁棋盘区(禁硬编码像素)→ 覆盖 game/tests/golden/ 三张基线。 +# 过程截图落 mktemp 临时区即弃(Q2),只有裁剪产物入库。 +# 用法: tools/golden_baseline_refresh.sh [game目录] 触发原因写进当批提交说明(强制)。 +set -u +G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" +REPO="$(cd "$(dirname "$0")/.." && pwd)" +GAME="${1:-$REPO/game}" +GAME_WIN="$(cygpath -m "$GAME")" +TMP="$(mktemp -d /tmp/goldenrefresh_XXXXXX)" +trap 'rm -rf "$TMP"' EXIT + +for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \ + assets/art/board/flag_seal.png \ + assets/art/character/character_hero_idle.png \ + assets/art/board/terrain_wall_n_dungeon.png assets/art/board/terrain_floor_dungeon_1.png \ + assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \ + assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do + if [ ! -f "$GAME/$a" ]; then + echo "[golden_refresh] 拒跑 — 素材缺位: $a(S3 停机点口径:基线禁锚字形回退态,先补素材+导入边车)" + exit 1 + fi +done + +timeout 180 "$G" --path "$GAME_WIN" res://smoke/board_shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1 +st=$? +grep -E "\[board_shot\]" "$TMP/run.log" | tail -6 +if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/board_meta.json" ]; then + echo "[golden_refresh] FAIL — board_shot_probe 退出码=$st(meta/截图缺失见上)" + exit 1 +fi +python tools/golden_board_crop.py "$TMP/shots/board_meta.json" "$TMP/shots" "$TMP/crop" || exit 1 +mkdir -p "$REPO/game/tests/golden" +for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png; do + cp "$TMP/crop/$b" "$REPO/game/tests/golden/$b" || exit 1 +done +echo "[golden_refresh] 完成 — 三张基线已换代入 game/tests/golden/(提交说明须写明触发原因)" diff --git a/tools/golden_board_crop.py b/tools/golden_board_crop.py new file mode 100644 index 00000000..e74e8f5e --- /dev/null +++ b/tools/golden_board_crop.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +r"""golden_board_crop.py — 棋盘 golden 前置裁剪(迭代 19 卡① T2 2026-10-08)。 + +board_shot_probe 截整屏视口(get_viewport().get_texture()),golden 逐像素对比前须裁出棋盘区。 +本工具按 board_meta.json 取样坐标(board_shot_probe 输出的棋盘视图实测全局矩形)裁剪, +**禁硬编码像素**(迭代 15③ 挂账口径——布局改动只须重跑探针刷新 meta,裁剪代码零改动)。 + +用法: python tools/golden_board_crop.py <截图目录> <输出目录> +输出: <输出目录>/board_.png(tag=meta 键,如 L1_dungeon)。 + +容错:矩形越界按图像边界收拢;meta 键缺失/矩形退化(宽高≤0)= 报错退出 1(取证缺失不静默)。 +退出码: 0 = 全部裁出 / 1 = 有失败。 +""" +import json +import sys +from pathlib import Path + +from PIL import Image + + +def main() -> int: + if len(sys.argv) != 4: + print("用法: python tools/golden_board_crop.py <截图目录> <输出目录>") + return 1 + meta_p, src_d, dst_d = Path(sys.argv[1]), Path(sys.argv[2]), Path(sys.argv[3]) + if not meta_p.exists(): + print("[golden_crop] FAIL — board_meta.json 缺失: %s" % meta_p) + return 1 + meta = json.loads(meta_p.read_text(encoding="utf-8")) + if not meta: + print("[golden_crop] FAIL — board_meta.json 空表(取样坐标零在案)") + return 1 + dst_d.mkdir(parents=True, exist_ok=True) + failures = [] + for tag, r in meta.items(): + src = src_d / ("board_%s.png" % tag) + if not src.exists(): + failures.append("%s: 截图缺失 %s" % (tag, src)) + continue + img = Image.open(src).convert("RGB") + try: + x, y, w, h = int(r["x"]), int(r["y"]), int(r["w"]), int(r["h"]) + except (KeyError, TypeError, ValueError): + failures.append("%s: meta 矩形字段缺失/非法 %s" % (tag, r)) + continue + # 越界收拢到图像边界(全局 rect 可能触视口边) + box = (max(0, x), max(0, y), min(img.size[0], x + w), min(img.size[1], y + h)) + if box[2] - box[0] <= 0 or box[3] - box[1] <= 0: + failures.append("%s: 矩形退化 %s(img %s)" % (tag, box, img.size)) + continue + img.crop(box).save(dst_d / ("board_%s.png" % tag)) + print("[golden_crop] %s — %s -> %s(%dx%d)" % (tag, box, dst_d / ("board_%s.png" % tag), + box[2] - box[0], box[3] - box[1])) + if failures: + print("[golden_crop] FAIL——") + for f in failures: + print(" - %s" % f) + return 1 + print("[golden_crop] PASS(%d 张棋盘区裁剪全出)" % len(meta)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/probe_gate.sh b/tools/probe_gate.sh index 5b32d97c..228d49eb 100644 --- a/tools/probe_gate.sh +++ b/tools/probe_gate.sh @@ -5,17 +5,21 @@ # 段:ui_input_probe=默认必跑;ui_struct_probe=默认必跑(ui-struct-probe mini-loop 2026-10-05 注册—— # 全 UI 屏结构断言:按钮互压/等宽/出屏/零尺寸/标题屏双列);ui_contrast_probe=默认必跑(UI 设计对齐批 # T2 2026-10-06 注册——UI 文字对比度机检 ≥4.5:1 无区域例外,测点表随批维护);--click=加跑 ui_click_probe 行为走查 -# (自退出+退出码闸;超时多半=探针未适配新停点层,归 probe适配批);shot_probe 禁入本闸—— -# 必须窗口化运行(headless 无渲染面,探针内已有 fail-fast 护栏),归真人目检。 -# 用法: tools/probe_gate.sh [game目录] [--click] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) +# (自退出+退出码闸;超时多半=探针未适配新停点层,归 probe适配批)。 +# --full 段(迭代 19 卡① T3 收编 2026-10-08——重闸件):ui_probe/ui_tune_probe/t17_defer_probe/fx_probe +# 四件带断言探针收进(此前游离在一切闸外只能手敲);轻闸默认段断言面零删减零改动(--full 只增不改)。 +# 渲染面归 render_gate.sh 正主(窗口化:shot_probe 七屏+fx_shot/board_shot 取证+棋盘 golden 逐像素)—— +# 旧头注「shot_probe 禁入本闸……归真人目检」纪律句随三闸口径扩容改写退役(2026-10-07 用户授权=历史偷懒标记)。 +# 用法: tools/probe_gate.sh [game目录] [--click] [--full] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) # 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell;Godot 路径可用环境变量 GODOT_BIN 覆盖。 set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" -GAME=""; WITH_CLICK=0 +GAME=""; WITH_CLICK=0; WITH_FULL=0 for a in "$@"; do case "$a" in --click) WITH_CLICK=1 ;; + --full) WITH_FULL=1 ;; *) GAME="$a" ;; esac done @@ -85,6 +89,50 @@ if [ "$WITH_CLICK" -eq 1 ]; then fi fi +# ── --full 收编段(迭代 19 卡① T3 2026-10-08——四件带断言探针入闸;实测时长 ui_probe 17.4s/ +# ui_tune_probe 49.6s/t17_defer_probe 63.0s/fx_probe 21.9s,timeout 120=2-7×余量) ── +if [ "$WITH_FULL" -eq 1 ]; then + echo "======== ui_probe(--full 收编) ========" + timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_probe.tscn >"$TMP/flp.log" 2>&1 + st=$?; kill_orphans + grep -E "\[ui_probe\]" "$TMP/flp.log" | tail -2 + if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[probe_gate] FAIL — ui_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 + elif ! grep -q "PASS" "$TMP/flp.log"; then + echo "[probe_gate] FAIL — ui_probe 未见 PASS(exit=$st)"; _rc=1 + fi + + echo "======== ui_tune_probe(--full 收编) ========" + timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/ui_tune_probe.tscn >"$TMP/flt.log" 2>&1 + st=$?; kill_orphans + grep -E "\[tune_probe\]" "$TMP/flt.log" | tail -2 + if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[probe_gate] FAIL — ui_tune_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 + elif ! grep -q "PASS" "$TMP/flt.log"; then + echo "[probe_gate] FAIL — ui_tune_probe 未见 PASS(exit=$st)"; _rc=1 + fi + + echo "======== t17_defer_probe(--full 收编) ========" + timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/t17_defer_probe.tscn >"$TMP/fld.log" 2>&1 + st=$?; kill_orphans + grep -E "\[t17_defer\]" "$TMP/fld.log" | tail -2 + if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[probe_gate] FAIL — t17_defer_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 + elif ! grep -q "PASS" "$TMP/fld.log"; then + echo "[probe_gate] FAIL — t17_defer_probe 未见 PASS(exit=$st)"; _rc=1 + fi + + echo "======== fx_probe(--full 收编) ========" + timeout 120 "$G" --headless --path "$GAME_WIN" res://smoke/fx_probe.tscn >"$TMP/flx.log" 2>&1 + st=$?; kill_orphans + grep -E "\[fx_probe\]" "$TMP/flx.log" | tail -2 + if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[probe_gate] FAIL — fx_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 + elif ! grep -q "PASS" "$TMP/flx.log"; then + echo "[probe_gate] FAIL — fx_probe 未见 PASS(exit=$st)"; _rc=1 + fi +fi + echo "======== 残留检查(批末闸) ========" left="$(orphans | wc -l | tr -d ' ')" total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)" diff --git a/tools/render_gate.sh b/tools/render_gate.sh new file mode 100644 index 00000000..5810cf17 --- /dev/null +++ b/tools/render_gate.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# 渲染闸正主(迭代 19 卡① 自动化验收补齐批 2026-10-08 落码——T1/T2)——窗口化自动闸。 +# 迭代 15③ 挂账「渲染五闸」机器补跑半随本闸落籍(shot_probe/golden_compare/board_shot 三件入闸; +# fx_probe 逻辑层归 probe_gate --full、ui_layout_probe 归其自身两档断言——归宿对账见卡①任务书 §一)。 +# 骨架复用 probe_gate:①每段 timeout 守卫(挂死=FAIL 不静默)②段前预清+段后清本 game 目录孤儿 +# Godot(按命令行精确匹配本 game 路径,不误杀其他 worktree 会话)③末尾残留清点=批末闸(必须 0)。 +# 段: +# ① shot_probe 七屏窗口化截图(menu/settings/career/camp/level/battle/pause——迭代 19 卡① T1 扩屏) +# → tools/shot_checker.py A-D 语义断言自动判定(A 屏级骨架全屏/B 文字对比度/C 明度三档带/ +# D 文字禁压纹理;E 焦点预算=语义面仍归人闸不机检)。首跑存量 FAIL 按 S1 停机点全量呈报登记 06。 +# ② 取证段(Q7:超时/零字节/文件缺失任一即 FAIL——「渲染真实发生」必须成立,不做像素判定): +# fx_shot_probe 三时刻抓帧(行到达/结算排播/轮末连帧)+ board_shot_probe L1-L3 棋盘抓帧。 +# ③ 棋盘 golden 逐像素(T2):board 整屏帧按 board_meta.json 取样坐标裁剪棋盘区 +# (tools/golden_board_crop.py——禁硬编码像素)→ tools/golden_compare.py 对 +# game/tests/golden/ 三张基线(容差 provisional:差 ≤16/changed ≤2%,S2 停机点口径)。 +# 素材缺位=SKIP 不判红(Q5 运行时口径;基线生成期素材缺位=L3 停机——基线禁锚回退态)。 +# ④ 基准档 1280 单档(Q4)。1920 档注记:16:9 全屏下 stretch=canvas_items/aspect=keep 等比 +# 1.35×+左右黑边各 96px,构图与基准档一致(迭代 9 分辨率案甲)——分辨率维度不重复建闸, +# ui_layout_probe 两档断言(T5)承担 1920 布局机检面。 +# 过程产物纪律(Q2):一切截图 PNG 落 mktemp 临时区,闸毕即弃——任何过程 PNG 禁入 git; +# 例外=game/tests/golden/ 基线(测试资产入库,.gitignore 已核不挡)。 +# 窗口化运行会短暂弹 Godot 窗口(本机可开窗口径,2026-10-07 用户令);禁改 --headless 跑本闸 +# (headless 无渲染面,探针内 fail-fast 护栏会红——那是护栏生效不是闸红)。 +# 用法: tools/render_gate.sh [game目录] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) +# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell + python(PIL);Godot 路径可用环境变量 GODOT_BIN 覆盖。 +set -u +G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" +REPO="$(cd "$(dirname "$0")/.." && pwd)" +GAME="${1:-$REPO/game}" +GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式 +TMP="$(mktemp -d /tmp/rendergate_XXXXXX)" +trap 'rm -rf "$TMP"' EXIT +_rc=0 + +orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(console wrapper+真引擎都算) + printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$GAME_WIN" > "$TMP/k.ps1" + powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$TMP/k.ps1")" 2>/dev/null | tr -d '\r' +} +kill_orphans() { + local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done +} + +# 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付) +if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then + echo "[render_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)" + timeout 300 "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 + kill_orphans +fi + +pre="$(orphans | wc -l | tr -d ' ')" +if [ "$pre" -gt 0 ]; then echo "[render_gate] 预清本闸残留 Godot×$pre"; kill_orphans; fi + +echo "======== shot_probe 七屏截图 → shot_checker A-D ========" +SHOT="$TMP/shots" +timeout 180 "$G" --path "$GAME_WIN" res://smoke/shot_probe.tscn -- "$SHOT" >"$TMP/shot.log" 2>&1 +st=$?; kill_orphans +grep -E "\[shot_probe\]" "$TMP/shot.log" | tail -12 +if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[render_gate] FAIL — shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 +elif [ "$st" -ne 0 ] || ! grep -q "问题=0" "$TMP/shot.log"; then + echo "[render_gate] FAIL — shot_probe 未干净完成(exit=$st,缺失屏/空文件/路径断裂见上)"; _rc=1 +fi +if [ "$_rc" -eq 0 ]; then + python tools/shot_checker.py "$SHOT" shot_menu.png shot_settings.png shot_career.png shot_camp.png shot_level.png shot_battle.png shot_pause.png | sed 's/^/[render_gate] /' + _rc="${PIPESTATUS[0]}" + [ "$_rc" -ne 0 ] && echo "[render_gate] FAIL — shot_checker A-D 断言存在红(S1 停机点:首跑存量 FAIL 全量呈报登记 06)" +fi + +echo "======== fx_shot_probe 三时刻取证(Q7) ========" +FX="$TMP/fxshots" +timeout 180 "$G" --path "$GAME_WIN" res://smoke/fx_shot_probe.tscn -- "$FX" >"$TMP/fx.log" 2>&1 +st=$?; kill_orphans +grep -E "\[fx_shot_probe\]" "$TMP/fx.log" | tail -4 +if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[render_gate] FAIL — fx_shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 +elif [ "$st" -ne 0 ]; then + echo "[render_gate] FAIL — fx_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1 +fi +fx_evidence() { # 取证双检:模式文件存在且非零字节(零字节/缺失任一即 FAIL——Q7) + local pat="$1" min="$2" n=0 f + for f in "$FX"/$pat; do + [ -f "$f" ] || continue + [ -s "$f" ] || { echo "[render_gate] FAIL — 取证零字节: $f"; _rc=1; return; } + n=$((n+1)) + done + if [ "$n" -lt "$min" ]; then echo "[render_gate] FAIL — 取证文件缺失: $pat 仅 $n/<$min"; _rc=1; fi +} +fx_evidence "fx_dig*.png" 1 +fx_evidence "fx_settle*.png" 1 +fx_evidence "fx_round*.png" 6 +fx_evidence "fx_final.png" 1 + +echo "======== board_shot_probe L1-L3 取证(Q7) ========" +BD="$TMP/boardshots" +timeout 180 "$G" --path "$GAME_WIN" res://smoke/board_shot_probe.tscn -- "$BD" >"$TMP/bd.log" 2>&1 +st=$?; kill_orphans +grep -E "\[board_shot\]" "$TMP/bd.log" | tail -6 +if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then + echo "[render_gate] FAIL — board_shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 +elif [ "$st" -ne 0 ]; then + echo "[render_gate] FAIL — board_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1 +fi +for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png board_meta.json; do + if [ ! -s "$BD/$b" ]; then echo "[render_gate] FAIL — 取证缺失/零字节: $b"; _rc=1; fi +done + +echo "======== 棋盘 golden 逐像素(T2——裁剪→对比,容差 provisional) ========" +if [ ! -d "$REPO/game/tests/golden" ]; then + echo "[render_gate] FAIL — game/tests/golden/ 基线目录缺失(基线在库=验收断言②)"; _rc=1 +elif [ ! -s "$BD/board_meta.json" ]; then + echo "[render_gate] SKIP — board_meta.json 缺失(取证段已红),golden 对比不判" +else + # Q5 运行时素材缺位=SKIP:基线锚在素材齐盘态,缺盘态对比必误红——SKIP 不判红,注记在案 + _asset_miss=0 + for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \ + assets/art/board/flag_seal.png \ + assets/art/character/character_hero_idle.png \ + assets/art/board/terrain_wall_n_dungeon.png assets/art/board/terrain_floor_dungeon_1.png \ + assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \ + assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do + [ -f "$GAME/$a" ] || { echo "[render_gate] SKIP — 棋盘素材缺位: $a(Q5——基线禁锚回退态,运行时 SKIP)"; _asset_miss=1; break; } + done + if [ "$_asset_miss" -eq 0 ]; then + CROP="$TMP/crop" + python tools/golden_board_crop.py "$BD/board_meta.json" "$BD" "$CROP" | sed 's/^/[render_gate] /' + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + echo "[render_gate] FAIL — 棋盘区裁剪失败"; _rc=1 + else + python tools/golden_compare.py "$REPO/game/tests/golden" "$CROP" \ + board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png | sed 's/^/[render_gate] /' + if [ "${PIPESTATUS[0]}" -ne 0 ]; then + echo "[render_gate] FAIL — golden 逐像素超容差(S2 停机点:字体/平台误红呈报后定容差)"; _rc=1 + fi + fi + fi +fi + +echo "======== 残留检查(批末闸) ========" +left="$(orphans | wc -l | tr -d ' ')" +total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)" +echo "本闸残留 Godot=$left(必须0) 全局 Godot 进程=$total(其他会话的仅报数不算账)" +if [ "$left" -ne 0 ]; then echo "[render_gate] FAIL — 本闸残留未清零"; kill_orphans; _rc=1; fi +if [ "$_rc" -eq 0 ]; then echo "[render_gate] 全绿"; else echo "[render_gate] 存在 FAIL,见上"; fi +exit "$_rc" diff --git a/tools/repo_structure_audit.py b/tools/repo_structure_audit.py index e5d48042..3bbfee6b 100644 --- a/tools/repo_structure_audit.py +++ b/tools/repo_structure_audit.py @@ -27,9 +27,11 @@ ROOT_FILES = {"CLAUDE.md", "active.md", "roadmap.md", "stage.txt", "review-mode. ".gitignore", ".zcodeignore"} # 3) 图像允许目录(相对路径前缀;archive/ 为归档 zip 不扫) # design/qa/ 收编(迭代 5 返工批 2026-09-29——U2 目检预览图/QA 呈现产物归位,commit 留痕) +# game/tests/golden/ 收编(迭代 19 卡① T2/T6 2026-10-08——棋盘 golden 基线=机检测试资产非美术素材, +# 素材齐盘态生成入库【基线换代权限 Q6】;T1 落库时漏改本白名单致熵增闸红,同批修订 commit 留痕) IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", ".pxo", ".psd", ".bmp"} IMAGE_PREFIXES = ("artwork/", "game/assets/", "design/art/references/", "design/dev/architecture/", - "design/qa/") + "design/qa/", "game/tests/golden/") # 4) 退役路径(目录或文件任一再现即 FAIL) RETIRED = ["production", "docs", "tests", "qa-data", "design/art/candidates", "production/mockups", diff --git a/tools/shot_checker.py b/tools/shot_checker.py index 638b7eea..042d7666 100644 --- a/tools/shot_checker.py +++ b/tools/shot_checker.py @@ -8,7 +8,8 @@ r"""shot_checker.py — 整屏样张闸机检(迭代 9 卡⑤/T8 2026-09-30; 断言(整屏样张闸 §1.4 四项的机检可判面): A 屏级骨架: PNG 存在/尺寸=工程档/灰度 std≥8/唯一色数≥64(渲染真实发生); B 文字对比度: 已知文字框〔主墨/暖白 文字簇 vs 背景簇 p2/p98 极性无关〕WCAG ≥4.5:1 - (菜单副标题+帮助文/战斗 C 区面板/结算明细提示——圣经 v3.5 §7.2 全局文字硬指标机检面); + (菜单副标题/战斗 C 区面板/结算明细提示——圣经 v3.5 §7.2 全局文字硬指标机检面; + 原「菜单·帮助文」腿随载体玩法速览牌退役同撤——迭代 19 卡① S1 裁 2026-10-08); C 明度三档带: 战斗屏 信息面(C 区面板均值) > 世界中(棋盘区均值) > 世界底(舞台无光柱区均值) (§2 明度分层——层间灰度可分); D 文字禁压纹理: 各文字框背景主簇纯度 ≥0.5(±12 灰度带占比——文字下垫平滑容器非 busy 纹理); @@ -131,7 +132,8 @@ def main() -> int: if "shot_menu.png" in imgs: m = imgs["shot_menu.png"] check_text_box(m, box_of(meta, "menu_sub"), "菜单·副标题", failures) - check_text_box(m, box_of(meta, "menu_help"), "菜单·帮助文", failures) + # 菜单·帮助文断言腿退役(迭代 19 卡① S1 裁 2026-10-08——载体玩法速览牌 2026-10-06 用户令 + # 退役「界面禁玩法文字墙」,锚/取样键/断言段三处同撤,不重锚) print("[shot_checker] 焦点预算〔首焦唯一〕= 目检项(T15 段闸人闸清单判定——语义面不机检)") if failures: print("[shot_checker] FAIL——")