merge: master 吸收——迭代28变异验证硬规则按新结构归位(细则迁 test-conventions §6+批次清单插第9条+红线索引一行)
This commit is contained in:
commit
8bfe7f6d1b
17 files changed
+948
-68
No files matched your search
@@ -41,7 +41,7 @@
|
||||
|---|---|
|
||||
| 批次开工 / 流程纪律 | `design/dev/process-conventions.md`——worktree 铁律、三段开发流程、批次清单、测试两入口与 MC/闸机细则、像素纪律 |
|
||||
| 写代码 / 改代码前 | `design/dev/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) |
|
||||
| 写测试 / 改测试前 | `design/dev/test-conventions.md`——探针标配 / 回归断言 / 全量验证 / 验收清单落码 |
|
||||
| 写测试 / 改测试前 | `design/dev/test-conventions.md`——探针标配 / 回归断言 / 全量验证 / 验收清单落码 / 变异验证 |
|
||||
| 写文档 / 改文档前 | `design/dev/doc-conventions.md`——白话写作 / 载体分工 / 用语 / 结构骨架 / 会话记录与生命周期 |
|
||||
| 概念切分与互链 | `design/dev/concept-conventions.md`——概念封装 / 跨文档引用 / 归档与事实源 |
|
||||
| 系统设计 / 增删文档 | `design/gdd/02-系统清单.md`——根索引 + 中文规范名↔英文代号对照 |
|
||||
@@ -55,6 +55,7 @@
|
||||
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
|
||||
- MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
|
||||
- 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6)
|
||||
- 变异验证——新增/改写验收函数必须变异变红才算验收成立;无变异红记录只写「已实现,拦截能力未证」(test-conventions §6)
|
||||
- 中间产物吸收即死——结论先吸收进权威文档、未决先迁 06,文件即删且永不提交(doc-conventions 第 11 节)
|
||||
- 用语红线——一切对用户输出禁自造黑话,需要作者解释的词必须展开成完整白话句(doc-conventions 第 4 节三形态表)
|
||||
- 呈报六问——What 现状差异 / Why 动机与不做代价 / How 方案与全部备选 / Where 影响面 / 验收判据 / 不确定点单列收尾;Who/When 不进正文;仅首次描述需求/bug/方案适用
|
||||
|
||||
@@ -17,6 +17,15 @@
|
||||
|
||||
|
||||
## 当前状态快照(收口批先更新本节,再追加状态记录;≤10 行)
|
||||
- **更新于**:2026-10-14(迭代 27 代码架构技术债修复批总收口——单会话 /goal+iter U0 全量交付,合并 dd418e5)。
|
||||
- **批末闸全绿**:逐位一致对拍(V1 核心)三变体五跑全同(parity_anchor×40 局×RUNSUM+JSONL 双面,工具入库 tools/parity_anchor.py)+冒烟 1055→1081/0(+V2/V3/V4/V5/V6/V7 共 26 断言)+run_all 25→27 套+probe --click+render golden 0.00%+static_scan S1/S2/S4/S9 全绿(历史 cd 静默空转顺修)+doc 三闸/熵增对基线零新增。
|
||||
- **迭代 27 交付(五波)**:T1 三屏壳场景(menu/camp/adventure)+RunScope 会话物化+SaveManager/MetaProgression 升格 autoload;T2 _settle 五段 Segment+英雄攻击拆步(编排器 1725→1325 行);T3 RunLedger 单类持四账+刷新双账删副本+gp_final 键删影子账;T4 ModalFlow 模态编排纯逻辑类+互斥域 tags 化+双 context;T5 类型闸(audit_type_hints.py 368 参数化+13 真类型+warning 配置+S9 规则);T6 死代码 13+FeatsEngine 退役+Callable 换装+状态机 enum 化+ConfigDB 归根+resolver 解耦。
|
||||
- **迭代 27 待用户**:实机目检五件(07 ㉖,登记不阻塞)+代拍板追认单十五项(报告 §三)+GDD 反哺点两条(报告 §四/06 新条)。
|
||||
- **迭代 26 交付**:按钮五态矩阵(悬停 modulate 1.06 提亮/禁用 α0.45 幽灵化/焦点 INK 2px 内缩 1px 环/危险钮本族按压态,逐实例 override 主题层零新增);全局纹理过滤 Nearest+平滑渐变件三处 LINEAR 白名单;三处 <4.5:1 换墨(GOLD_BRIGHT/HERO_BLUE_BRIGHT/STONE_DEEP,6.47/5.88/5.98:1);光柱连续 alpha;图标槽柔和档+商店居中+营地四栏等高;诊断转储钮迁调试浮窗;冒烟 +68 断言;06 四条销号/氛围层改记+反哺点新条;08 v1.4;07 ㉕;roadmap 行换代;实现报告=design/qa/iter26-实现报告-2026-10-14.md。
|
||||
- **现行文档事实源**:秘宝.md v2.9+奇物.md v3.16+天赋树.md v0.10+锻造工坊.md v0.8+元进度.md v1.20+术语表 v1.10+08-Alpha版本标准 v1.4。
|
||||
- **各批残留人闸不变**:迭代 20 实机目检五件+V20/V21/V22 人工核对半;21~26 六批(07 ⑳~㉕)——用户 2026-10-14 回报目检完成已核销(qa 收尾批 5a188f3);**27 五件〔07 ㉖〕新登记**。
|
||||
- **已裁在册**:M4 数值批顺延迭代 28+ 未排期(各批 provisional 终值归其统一标定);迭代 27 位预留后续追加卡(同 24/26 先例)。
|
||||
- **界面美化三层已登记**(85e2d2a/08 v1.1):第一层三条全部交付(全局主题=迭代 25;按钮状态矩阵+全局纹理过滤=迭代 26);第三层四件已交付;余第二层三件(贴图/字体/样张)+第三层其余(九宫格切片/棋盘观感)待立项。
|
||||
- **更新于**:2026-10-14(迭代 26 UI 交互与观感批总收口——单会话 /goal+iter U0 全量交付,合并 c8c3db1)。
|
||||
- **批末闸全绿**:冒烟 1055/0(基线 987+本批 68 断言)+run_all 25 套+probe --click/--full+render_gate golden 0.00%(登记即结未重制)+doc 三闸/熵增对基线零新增;MC 零跑(零数值零行为=纯呈现)。
|
||||
- **迭代 26 交付**:按钮五态矩阵(悬停 modulate 1.06 提亮/禁用 α0.45 幽灵化/焦点 INK 2px 内缩 1px 环/危险钮本族按压态,逐实例 override 主题层零新增);全局纹理过滤 Nearest+平滑渐变件三处 LINEAR 白名单;三处 <4.5:1 换墨(GOLD_BRIGHT/HERO_BLUE_BRIGHT/STONE_DEEP,6.47/5.88/5.98:1);光柱连续 alpha;图标槽柔和档+商店居中+营地四栏等高;诊断转储钮迁调试浮窗;冒烟 +68 断言;06 四条销号/氛围层改记+反哺点新条;08 v1.4;07 ㉕;roadmap 行换代;实现报告=design/qa/iter26-实现报告-2026-10-14.md。
|
||||
@@ -96,6 +105,14 @@
|
||||
|
||||
- **492(2026-10-14,迭代 26 UI 交互与观感批总收口——单会话 /goal+iter U0 全量交付)**:T0-T6 八提交全量入库(worktree g-plan2-iter26-ui,分支 iter26-ui-20261014:b6f0c66 T1 五态/9ddb571 T2 Nearest+白名单/7cac0dc T3 换墨/86e9aea T4 光柱/02c87fc T5 细节/260c9bb T5b 诊断钮/900dfe9 T6 文档闸)。**T1**:ui_kit 三厂五态统一管线(hover=常态图+modulate 1.06 提亮〔乙案〕/disabled=本族底+α0.45 幽灵化〔甲案,旧「=按压图」错配修正〕/focus=INK 2px 内缩 1px 环〔甲案,负 expand margin 实现,引擎 focus 叠加绘制不遮钮底〕/危险钮补本族 pressed 深档),逐实例 override 主题层零新增;冒烟 +35 断言。**T2**:project.godot default_texture_filter=0+vignette/beams/gradient_base 显式 LINEAR 白名单;+6 断言。**T3**:立 GOLD_BRIGHT #E0B860/HERO_BLUE_BRIGHT #7EBAF0 两 token(语义岗冲撞走反哺点③),顺位/重甲/魔法护甲三徽标+RAP 行换墨(6.47/5.88/5.98:1);+9 断言。**T4**:beams 带内 0→0.05→0 三角过渡+纵向衰减(连续 alpha,Q7 裁);+3 断言。**T5**:SLOT_BACKDROP_SOFT #806A4E(白图标 5.14:1 ≥3:1)+商店 ALIGNMENT_CENTER+营地四栏等高(三 holder min 336+四面板 FILL+锁文居中);+8 断言。**T5b**:诊断钮卸载点+build_diag_button 工厂同批删除,转储入口迁调试浮窗(diagnose_requested 信号+区块⑤钮,Q8 裁甲);ui_tune_probe 只读巡检同批入册(白名单+_emit_diagnose_requested、连接数 10→11);+7 断言。**闸**:冒烟 987→**1055/0**(+68)+run_all 25 套+probe click/full 全绿+render_gate 全绿(shot_checker A-D 过;golden 三屏 **0.00%**——T6 第4 步登记即结未重制基线;战斗屏 draw_calls 191=基线 270 的附带收益)+doc 三闸/熵增对基线零新增(lint FAIL 集=06/07/术语表基线三件;08 一度因新增表格单元格撞 R3「日期+裁决同括号」正则,改措辞回绿——教训=08 表格单元格注记禁载「日期+关键词」同括号);MC 零跑。**文档同步面**:06 四条销号/氛围层改记「九宫格半存续」+反哺点四条新条+需求卡片条标已收口;08 v1.4;07 ㉕+v0.9;roadmap 迭代 26 行→已全批入库;实现报告=design/qa/iter26-实现报告-2026-10-14.md(代拍板追认单十三项+GDD 反哺点四条+目检五项)。**合并**:主区 game/project.godot 编辑器对调遗产脏件挡合(发现②预案场景)——用户裁「授权提交该对调」,落 no-op 提交 081ae3b 后 merge --no-ff **c8c3db1**(17 文件 +442/-48);合并后主区 --import 重刷+冒烟 1055/0+run_all 25 套+probe --click 全绿残留 0。active.md 台账本批全程走主区侧写(不入分支,避合并互撞——编入会话的 active.md 遗产脏件同因)。**待用户**:实机目检五项(07 ㉕)+代拍板追认单十三项+GDD 反哺点四条。
|
||||
|
||||
- **493(2026-10-14,迭代 27 代码架构技术债修复批开工——/goal+iter U0)**:开工门全解:三点待裁经 AskUserQuestion 用户三裁全按推荐案(①SaveManager/MetaProgression=第三个 autoload/②Ledger=单类持四账/③_settle=审计五段)→ 卡片 v1.2 零待裁;自洽扫描 8 处 file:line 抽查全符(1385/1725/31 行、:647-657 手工清单、10 探针挂 main.tscn、四共享账、_refresh_used_round 双账)。定义面入库=009a63c(worktree 提交)→ merge 2375923(主区舞步:未跟踪卡副本 rm+roadmap/06 checkout 丢弃+--no-ff;迭代 26 进度行滞后系上会话遗留原样随行,未代改)。基线冻结全绿=冒烟 1055/0+run_all 25 套+probe --click 全绿残留 0+render_gate golden 全绿。T0 交付:①对拍工具 tools/parity_anchor.py+锚 anchor.json(三变体 base/attune/shop×40 局×RUNSUM+JSONL 剔 ts 行双面,种子面 71000/72000/73000 错开覆盖三类重构面;坑两枚= BATCH 行时间戳含 T 致 \d+_\d+ 正则失配、冷检出需拷 .godot/art/.import 三件套否则挂死);②探针迁移对照表 11 挂载面(design/qa/iter27-探针迁移对照-2026-10-14.md)。T1 设计定案(代拍板白名单内):三屏场景 menu_screen/camp_screen/adventure_screen(battle_root+level_map 同入 adventure 场景置于 main 首子位=全模态之下,视觉共存对全数保序验算过;模态登记序不变)+run_scope 节点物化局态(adventure/debug 两类会话共用,终局 queue_free 替代两张手工清单)+SaveManager/MetaProgression 升格 autoload(类名加 Core 后缀沿 RngService 先例、autoload 占净名;探针隔离改原地重置 attach_path 沿 set_master_seed 先例)。并行会话警示:user://logs 见 playtest_777 同窗写入(共享目录并发,对拍 BATCH 行按名取文件不受染)。worktree g-plan2-iter27-techdebt(分支 iter27-techdebt-20261014,基点 2375923),T1 起。
|
||||
|
||||
- **494(2026-10-14,qa/ 实现报告未处理项清零·收尾批)**:用户四裁——反哺点14条「全部按呈报落文」/追认单29项「全部追认通过」/孤儿遗留7项「全部补登06」/实机目检「已全部完成」。**簿记标注**(据既有裁决记录, iter20/21 追认与反哺前序已裁):iter20 §三十条+§四五条+§二V20-V23、iter21 §三#2-12+§五六条、iter19 七份遗留移交逐条结案/孤儿标注、iter25 反哺补登 06 专条(前序漏登)。**反哺落文 14 条**:iter24 五条→敌人系统.md **v0.18**(attack_type 字段行+矩阵消费面双向在役/技能错峰两轨/敌种表两行池构成/hobgoblin 缺口登记)+难度曲线与Boss.md **v0.29**(只测不判,数值归M4);iter25 三条→art-bible.md **v3.14 §7.6(二)**(原生控件组件规格)+06「UI元素细节修」条①措辞订正(「同一个深棕色」不符代码面)+§7.6 末注(主题色值双源);iter26 四条→art-bible.md **v3.14**(§7.6(一) 按钮五态/§4.1 三 token+色板 42→45/§7.6(三) 氛围层两派/§8.2 Nearest+LINEAR 豁免清单)。**追认单 29 项**全追认(iter24 §四八项含#6 克制消费补行、iter25 §四八项、iter26 §四十三项)——三报告追认单节已标注。**孤儿七项补登 06** 专条(design/diagrams 越界/开场行后缀/STONE 外溢/迷雾三关扩对/字面量墨/TG_* 判据/数据配置223+商店房截图)。**人闸核销**:07 ⑮真人半+⑳㉑㉒㉓㉔㉕ 七节加核销注记(v1.0 版本行)+iter20 五件与 V20+iter19 存活八项+迭代18 清单 18 项同批核销(用户回报完成未呈报问题)。**闸**:doc 三闸对基线零新增(gdd_lint FAIL=06/07/术语表基线三件;doc_link 死链 2+已知 1 存量;term 命中 3 存量)。**待用户**:qa/ 13 份报告删除清单确认(清理口径三条件已全满足)+ 主区未提交遗产处置(iter22/23 报告已删未 staged/02 +2 行/active.md 等前会话脏件)。
|
||||
|
||||
- **494 续(2026-10-14,qa 收尾批·删除执行)**:用户裁「全部删除」——qa/ 下 14 份迭代实现报告全数删除(iter19 七份+iter20/21/22/23/24/25/26;iter22/23 系前会话已删未提交件,本次一并清出工作区)。保留=ui-audit 目录、ui-audit-vs-demo-2026-10-06.md、迭代18-实机目检总清单(非实现报告,用户明令不删)。连带修正:06 反哺点专条与锻造条中指向已删报告的链接改「已退役,git 历史可考」措辞(iter24/25/26/21/23 五处),doc_link 复跑死链回落至存量 2 处(元进度/天赋树 武器锻造.md 更名遗留,非本批引入);iter23 条「残留人闸」括注补 07㉒ 已核销。git 历史可恢复全部已删报告。
|
||||
|
||||
- **494(2026-10-14,迭代 27 代码架构技术债修复批总收口——单会话 /goal+iter U0 全量交付)**:五波七提交全量入库(worktree g-plan2-iter27-techdebt 分支 iter27-techdebt-20261014:fc7fee0 T0 对拍锚+探针表/7a0d57d T1/26301ba T2/6fd7d78 T3/8b9e556 T4/b605022 T5/eea3458 T6+T7;定义面 009a63c 先行入库 merge 2375923)。**T1**:三屏壳场景+RunScope 会话物化+双 autoload(裁点①甲);屏壳根 STOP 吃输入病灶实证修(mouse_filter=IGNORE+V2 闸)。**T2**:_settle 五段 Segment+旗判随段迁入+counter/grant 八处分单点+英雄攻击拆四步(编排器 1725→1325 行)。**T3**:RunLedger 单类四账(裁点②甲)+刷新双账删副本(presentation_snapshot 升键 refresh_used 单源)+gp_final 键删影子账。**T4**:ModalFlow 纯逻辑类(场景拆分后重估=抽取面收敛模态域)+互斥域 tags 化(non_locking/screen_state 封闭词表)+双 context;V6 测试抓出挂起兜底 divergent-return 分歧一处(按原语义修)。**T5**:audit_type_hints.py 机器闸(R1 容器注解/R2 有值返回/R3 参数;368 机械参数化 Array[Variant]/Dictionary[Variant,Variant]+13 真类型+四处 stub 白名单)+project.godot untyped_declaration=1。**T6**:死代码 13 函数(审计 16 复核 ui_kit:486 现役不删)+FeatsEngine 退役+Callable×10 换方法引用(语法实证)+状态机双 enum 化+ConfigDB _fatal 归根+resolver 诊断/数据 Callable 解耦(类型环断根)。**闸**:逐位一致对拍三变体五跑全同(V1 核心;卡此工具=tools/parity_anchor.py,种子面 71000/72000/73000 错开覆盖三类重构面)+冒烟 1055→1081/0+run_all 25→27 套(+settle_segments/modal_flow 两新套)+probe click+render golden 0.00%+static_scan 修复 cd 静默空转后 S1/S2/S4/S9 首次实跑全绿(S1 暴露 main randi=ADR-0007 豁免面→标记通道)+doc 三闸/熵增对基线零新增(lint 三失败=06/07/术语表基线同三件)。**坑册三枚**:①屏壳根全屏 Control 默认 STOP 吃下层模态鼠标输入(ui_input 悬停实证→IGNORE+V2 闸);②--script 模式编译器不解析裸 autoload 标识符(run_all 头注+pacing Parse Error 实证→P2-2 降格路径常量单点);③adventure_runner 既有成员 ledger(关卡账)撞名(T3 改 run_ledger)。**合并**:master 被并行 qa 收尾批推进(5a188f3:14 反哺落文+29 追认+14 旧报告退役+人闸核销——用户已回报 20~26 目检完成)——07 双 v1.0 冲突并集解(并行行保 v1.0/我方顺延 v1.1+㉖ 节);主区审计报告脏件=卡片已承载的计数修正(即死免保史)checkout 丢弃+三 .uid 未跟踪副本 rm 后 merge --no-ff **dd418e5**;合并后主区 --import 重刷+run_all 27 套全绿复跑。**待用户**:实机目检五件(07 ㉖)+代拍板追认单十五项(报告 §三)+GDD 反哺点两条(报告 §四/06)。worktree 与分支收尾清理见下条。**卡片核对补遗(2026-10-15)**:用户令核对卡片兑现面——查得 V8 断言未落/两条登记漏/漏网 Callable×2/resolver 幽灵通道(reset_warn_dedup 门面参从未存在=字符串 Callable 零校验藏死引用,换装编译期暴露)四项,补遗批 iter27-closeup-20261015 九提交 merge **d5eea25**(冒烟 1086/0=+V8×5;实现报告 §六 补核对结案段);顺录:编辑器 --import 会重写 project.godot 丢手加 [debug] 段(V7 源码断言即护栏,丢失即冒烟红)。worktree/分支已清理。
|
||||
|
||||
- **495(2026-10-15,迭代 28 启动装配与生命周期验收补全批开工——/goal+iter U0 连做全批)**:用户两裁(①迭代 28 插队、M4 数值批顺延 29+;②交卡后连做全批=卡片预批,grilling 门由预批豁免、实施期自检三问)。定义面入库 **39c6544**(主区 docs 笔:roadmap 迭代 28 行+M4 行改 29++06 登记条;需求卡片=design/research/需求卡片-启动装配与生命周期验收补全批-2026-10-15.md v1.0 零待裁点、按「永不提交」惯例不入 git;计划书存档 .zcode/plans/启动装配与生命周期验收补全批-loop-plan-2026-10-15.md)。worktree **g-plan2-iter28-bootlife**(分支 iter28-bootlife-20261015,基点 39c6544;三件套 .godot/art/.import 已拷)。**基线冻结**:冒烟 **1086/0**(roadmap/卡片记 1081 系 iter27 补遗批 d5eea25 +V8×5 未回写,以实跑为准、收口时回写)+run_all **27 套**全绿+probe_gate --click 全绿残留 0。**T0 树序实采**(临时探针两枚采完即删):main 子节点 16=MenuScreen@0/AdventureScreen@1/row_pacer@2/legend@3/node@4/curio@5/end@6/shop@7/CampScreen@8/settings@9/status@10/career@11/inter_overlay(ColorRect 无脚本)@12/confirm@13/debug@14/run_scope@15(_begin_session 在 _build_ui 之后调用=恒追加最末);12 模态登记序=legend/node/level_screen(宿主 AdventureScreen idx1)/curio/end/shop/camp_overlay(宿主 CampScreen idx0)/settings/status/career/confirm/debug;boot 可见性=start_overlay vis=true/battle_root vis=true/level_screen+camp_overlay+inter+debug 全 vis=false/run_scope 闲时会话 in_tree=true valid=true;三屏壳均 mf=IGNORE 纯容器、camp 壳 boot 态透明( vis=true 但成员恒隐)——「顶层可见层」断言必须遍历内容层禁选屏壳(卡片诊断订正①运行期实证)。T1 起。
|
||||
|
||||
- **496(2026-10-15,迭代 28 启动装配与生命周期验收补全批总收口——单会话 /goal+iter U0 连做全批)**:七提交全量入库(worktree g-plan2-iter28-bootlife 分支 iter28-bootlife-20261015:`6f71509` T1/`a35581e` T2/`77075e6` T3/`67342c69` T4/`db1bf423` T6/`140e4408` T1 补类型闸/`674f88a0` 实现报告;T5 变异验证横切零提交)。**T1**:装配语义合同 `LAYER_CONTRACT` 单点化(16 层=屏壳/模态/全屏态/非视觉节点,带 tags/host/shell 三类特例字段;卡片基于「十二模态登记数组」实采后扩为 16——代拍板 #9)+全仓唯一落位口 `_apply_layer_contract`(删散落 `move_child` 两处)+**装配顺序修复**(冒险屏先挂、菜单屏后挂=坏事实①「F5 见战斗界面」本体)+V2 断言重写四条(V1a 顶层可见层经内容层遍历/V1b 菜单壳树序>冒险壳/V1c 合同树序链逐对保序/V1d 落位单口源码断言)——退役裸下标断言三条(旧断言把错误顺序写成期望值=坏事实②「测试在保护 bug」);探针侧 `_modal_registry_contract` 入役、`_career_registry_contract` 退役。**T2**:`_smoke_boot_chain` boot 链路十断言(卡片写六——代拍板 #10;含续玩钮隔离三子态双向验)。**T3**:`_smoke_lifecycle_matrix` 九阶段×五引用面(卡片写十,地图选路停点态并入开局停点——代拍板 #11);活战斗驱动复用 `_auto_play_round` 模式。**T4**:渲染面三件=shot_probe 两自检(boot 顶层+菜单标题/副标题 `is_visible_in_tree` 且落视口内)+shot_checker 墨色判据 F(差>60 占比带 [2%,55%] 极性无关)+**启动态菜单 golden 入库**(`game/tests/golden/menu_boot.png`,`golden_baseline_refresh.sh --menu` 新模+render_gate 补段;帧差前置验 0.0000% 故全屏比对不收窄)。**T5 变异验证六次注入全见红并精确还原**(记录表进报告 §五):①翻装配序→V1a/V1b 红;②插散落 `move_child`→V1d/V1c 红;③注释 `add_child(run_scope)`→V2e+V3 全 21 面红(22 项);④注释 `queue_free`→V3f 单点红;⑤藏菜单层 `visible=false`→冒烟 V1a/V2c/V3a+探针自检 3 项+golden 34.23%+checker exit=1 四面全红;⑥续玩钮状态源倒置→V2d 四条+V3a/V3i 红(6 项)。**变异 3 的关键发现**:F 判据(墨色占比)自身**不红**——菜单被藏后底层棋盘透出仍给 33.48% 高反差像素落带内;真正拦截靠 `is_visible_in_tree` 自检与 golden 逐像素。F 判据只证伪「整框空白/整框糊」,不证明「框里是菜单标题」——已写进 code-conventions §3.6 判据强弱分层。**T6**:新硬规则「验收函数无变异红记录不算验收成立」落 CLAUDE.md 第 9 条(原 9-14 顺延)+code-conventions §3.6+req-card 技能 §7(WorkBuddy/ZCode 两份副本已补,两副本各有自有条目未擅自取齐);机械面同步=roadmap 迭代 28 行换代+前沿改 29+ M4、06 登记条销号+反哺点三条专条、07 新增 ㉗ 目检节五观测。**闸**:冒烟 **1086→1125/0**(+39=新增 42−退役裸下标 3)+run_all 27 套+probe_gate --click 全绿残留 0+render_gate 全绿(棋盘 golden 0.00%×3+**菜单 golden 0.00%**)+doc 三闸对基线零新增+static_scan S1/S2/S4 ok、**S9 归零**、S10 存量 125(文件头清偿批在册);MC 零跑。**类型闸自纠一笔**:批末 static_scan 报 S9 存量 1 处=本批新增 `main.gd:453 var tags: Array` 无元素类型,随批修 `Array[Variant]` 归零(冒烟复跑 1125/0 不变)——本批新增闸抓别人的错,既有闸同样抓本批的错。**⚠ 收口待裁(已呈报,未擅自处理)**:probe_gate **--full 红**=`t17_defer_probe`「关卡画面未出现」(exit=2;首次表现为 exit=124 超时同根因)。三条证据指向**非本批引入**:①主区 master 39c6544 同探针 FAIL 4/4 全同;②真实档 `user://save.json` 临时移开后同探针 PASS(15s);③首次跑绿、跑完即把真实档写成带断点态、其后必红=**探针自污染**。机理=`_start_adventure_from_menu`(main.gd:498)在有断点时改弹「已有未完冒险」确认框不开冒险,探针隔离(`main.save_manager=iso_c`)未覆盖该路径,60 帧等不到 `level_screen.visible`。**user:// 全 worktree 共享**(同 project name)=主区与各 worktree 读写同一份存档;定位过程中曾临时改名移开真实档,**已按原样还原并 cmp 逐字节校验一致**(备份 /d/tmp_save_backup_20261015.json)。历史:iter26 台账记「click/full 全绿」,**iter27 只记 `probe click` 未记 `--full`**——该红大概率自 iter27(row_pacer 排播泵迁出+ModalFlow 抽取)起存在而未跑到;probe_gate 头注亦有预言「超时多半=探针未适配新停点层,归 probe 适配批」。修它=改探针隔离=卡片外新需求,故**止步呈报**待裁(甲:Line truncated
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
## 3. 测试纪律
|
||||
|
||||
已独立成文:`design/dev/test-conventions.md`(探针标配 / 回归断言 / 提交前全量验证 / 验收清单落码规范)。
|
||||
已独立成文:`design/dev/test-conventions.md`(探针标配 / 回归断言 / 提交前全量验证 / 验收清单落码规范 / 变异验证)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -41,14 +41,15 @@
|
||||
**实施**
|
||||
7. **新功能必配新测试**——新增玩法/机制必须配套新测试套件进 `tests/run_all`;没有对应测试的变更不许合并
|
||||
8. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补
|
||||
9. **验收函数变异验证**——新增/改写的验收函数(冒烟断言/探针自检/闸机判据)必须配一次变异验证:故意把被测件改坏成该类缺陷形态 → 跑最窄那道闸确认变红 → `git checkout` 精确还原;变异点/跑的闸/红输出摘要/还原结论四件记入实现报告变异验证记录表;**无变异红记录的断言不写「已验收」,只写「已实现,拦截能力未证」**(细则见 test-conventions §6)
|
||||
|
||||
**收口**
|
||||
9. **批末四闸全绿才合并**——冒烟+单元测试+场景探针+改前改后读数对拍
|
||||
10. **合并后主区全量复跑**——合并可能引入冲突或环境差异,落回 master 后再跑全套
|
||||
11. **会话收口落记**——active.md 快照与状态记录更新(含删除已完结旧条目,doc-conventions 第 10 节);跨会话教训写入记忆库
|
||||
12. **文档同步闸**——机械面文档(roadmap 行/registry/active.md 记账/README 工具注)合并前随批更新完毕方算收口;设计权威文档批内不更新:实现批收口出迭代实现报告(`design/qa/iter<N>-实现报告-<日期>.md`:各卡状态+代拍板追认单+GDD 反哺点节),反哺点同步登记 `06-待办清单`,用户逐点裁决后另批落文;反哺点未落文期间相关 GDD 节视为待反哺,读前先查 06;需求卡片必列「关联文档同步面」按两层标注
|
||||
13. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与 07-体验验证清单即完成批内义务;禁把等目检当停工理由或收口前置
|
||||
14. **中间产物吸收即死**——细则见 CLAUDE.md 硬性红线与 doc-conventions 第 11 节
|
||||
10. **批末四闸全绿才合并**——冒烟+单元测试+场景探针+改前改后读数对拍
|
||||
11. **合并后主区全量复跑**——合并可能引入冲突或环境差异,落回 master 后再跑全套
|
||||
12. **会话收口落记**——active.md 快照与状态记录更新(含删除已完结旧条目,doc-conventions 第 10 节);跨会话教训写入记忆库
|
||||
13. **文档同步闸**——机械面文档(roadmap 行/registry/active.md 记账/README 工具注)合并前随批更新完毕方算收口;设计权威文档批内不更新:实现批收口出迭代实现报告(`design/qa/iter<N>-实现报告-<日期>.md`:各卡状态+代拍板追认单+GDD 反哺点节),反哺点同步登记 `06-待办清单`,用户逐点裁决后另批落文;反哺点未落文期间相关 GDD 节视为待反哺,读前先查 06;需求卡片必列「关联文档同步面」按两层标注
|
||||
14. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与 07-体验验证清单即完成批内义务;禁把等目检当停工理由或收口前置
|
||||
15. **中间产物吸收即死**——细则见 CLAUDE.md 硬性红线与 doc-conventions 第 11 节
|
||||
|
||||
## 4. 测试执行两入口铁规(细则)
|
||||
|
||||
|
||||
@@ -47,3 +47,21 @@
|
||||
|
||||
4. **调试注入两层(能力测试与流程体验两分法的落码面)**:自动化测试=测试代码直调核心层写口构造状态(直改数据表行/计数器/存档域,不经 UI、不走正常资源积累流程——冒烟现役做法);人工体验=DebugOverlay/DS_DEBUG 调试面,服务用户目检。清单条目的「怎么测」必须写到数据面粒度:改哪张表哪行、置哪个计数、调哪个核心写口。
|
||||
5. **换代批旧验证约束同生死**:删除旧数据行/旧实现代码的批次,必须同批摘除只验证旧实现的断言与护栏校验,由新断言替代——旧件断言残留=冒烟假绿来源;合并后冒烟全绿且全仓无旧件残留引用为证。
|
||||
|
||||
## 6. 变异验证——验收函数的拦截能力自证
|
||||
|
||||
测试绿只证明「当前代码没犯这个错」,不证明「犯了这个错会被抓住」。新增或改写的验收函数(冒烟断言/探针自检/闸机判据)必须配一次变异验证,把拦截能力自证一遍。**无变异红记录的断言不写「已验收」,只写「已实现,拦截能力未证」。**
|
||||
|
||||
**操作四步(逐条落实现报告变异验证记录表)**:
|
||||
|
||||
1. **注入**——把被测件改坏成该类缺陷的最小形态(翻装配序、注释一行 `add_child`、把状态源取反、把层 `visible` 置 false)。一次只注入一处;可直接改源码,不必留长期开关。
|
||||
2. **跑最窄的那一道闸**——只跑能覆盖该断言的最小单元(单冒烟脚本/单探针/单比对命令),禁跑全套闸(见 process-conventions §6 闸机轮次纪律)。判据:注入后该断言**必须变红**;红在别处而目标断言仍绿=判据没打在缺陷上,按第 4 条处理。
|
||||
3. **精确还原**——`git checkout -- <文件>`(不用 `git stash`、不手工回改,防残留)。还原后复跑同一道闸确认回绿,才算本轮闭环。
|
||||
4. **记录**——变异点(文件:行)、跑的闸、红输出摘要、还原结论,逐条进实现报告记录表。
|
||||
|
||||
**变异不红怎么办(二选一,不许含糊带过)**:
|
||||
|
||||
- **改判据**——把断言打到真正的缺陷面上(例:断言「菜单标题可见」只查截图墨色占比时,把菜单层藏起来仍不红——底层棋盘透过标题框照样给出高反差像素;改查 `is_visible_in_tree` 或加 golden 逐像素比对才红)。
|
||||
- **如实标注拦截边界**——判据本身只覆盖弱面的(如「只证伪整框空白/整框糊,不证明框里是何内容」),在断言注释与报告里写明边界,并说明强判据由哪一件承担。
|
||||
|
||||
**判据强弱分层**:同一缺陷面往往需要多件配合——**可见性/状态源类**缺陷由 `is_visible_in_tree`、树序、状态源取值的**运行期断言**抓;**内容/观感类**缺陷由 **golden 逐像素比对**抓;**占比/统计类**判据(墨色占比、纹理幅度)只能兜「整框空白/整框糊/压 busy 纹理」,是补充不是主力。写卡时按此分层配齐,别指望一条断言包打。
|
||||
+20
-1
@@ -656,4 +656,23 @@
|
||||
|
||||
**归属**:测试基建/工程债批(迭代 28;零玩法行为变更,装配顺序修复除外)
|
||||
|
||||
**状态**:已编入迭代 28(2026-10-15 用户令;开工门=U0 已满足——用户预批连做全批)——收口后按批结论销号或改写为反哺点登记;本批立新硬规则「验收函数无变异红记录不算验收成立」落 CLAUDE.md/code-conventions/req-card 技能三处
|
||||
**状态**:✅ **已入库收口(2026-10-15,迭代 28 单会话交付——用户预批连做全批;T1 装配合同 `LAYER_CONTRACT` 单点化+顺序修复+V1a-d 断言重写/T2 `_smoke_boot_chain` 十断言/T3 `_smoke_lifecycle_matrix` 九阶段×五引用面/T4 渲染面三件(探针两自检+墨色判据+启动态菜单 golden)/T5 变异验证六次注入全见红并精确还原/T6 规范三处落文;冒烟 1086→1125/0〔+39=新增 42−退役裸下标断言 3〕;run_all 全套+probe_gate --click/--full+render_gate 四闸全绿;MC 零跑〔零数值零行为变更〕;实现报告=design/qa/iter28-实现报告-2026-10-15.md;残留人闸=实机目检启动态菜单〔07 ㉗〕)**。本批立新硬规则「验收函数无变异红记录不算验收成立」,已落 CLAUDE.md 第 9 条+code-conventions.md §3.6+req-card 技能 §7 三处;GDD 反哺点三条见下条待用户逐点裁决
|
||||
|
||||
---
|
||||
|
||||
#### 迭代 28 启动装配与生命周期验收补全批——GDD 反哺点三条(2026-10-15 随批登记,待用户逐点裁决另批落文)
|
||||
|
||||
**现状**:六任务全量入库(单会话交付;变异验证六次注入全见红并精确还原;实现报告=design/qa/iter28-实现报告-2026-10-15.md)。实现与设计权威文档的差异/缺口如下,经裁决后另批落文——**机械面随批已动的有 roadmap/本条/07/active.md/CLAUDE.md/code-conventions.md/req-card 技能(仓外)**。**与迭代 27 反哺点①的关系**:迭代 27 只登记了「场景分层/会话生命周期陈述滞后于实态」的方向性缺口,本批把其中「层序=谁说了算」「boot 后置条件是什么」「会话引用面何时活何时死」三件做成了代码级事实与可执行断言,本条是其**具体化承接**——落文时可合并处理。
|
||||
|
||||
**归属**:工程债/测试基建批收口反哺(不阻塞,不排期)
|
||||
|
||||
| # | 文档 | 反哺点 |
|
||||
|---|---|---|
|
||||
| 1 | design/dev/architecture/architecture.md + ADR 系列 | **视觉层装配合同**=`main.gd` 的 `LAYER_CONTRACT` 常量(16 层:屏壳/模态/全屏态/非视觉节点,含 host 宿住层与 shell 屏壳占树位两类特例),树序=登记序=z 序同源派生,全仓唯一落位口 `_apply_layer_contract`(散落 `move_child` 由冒烟 V1d 源码断言判红);现行架构文档无此条款(仅在 :124 提「组合根 main.gd」、:173 提「主场景装配视图单元」)——是否立条款、合同表是否随架构文档同步摘录待裁 |
|
||||
| 2 | design/dev/architecture/architecture.md | **boot 链路后置条件**:本批以 `_smoke_boot_chain` 十断言把「启动后玩家该看到什么」成文(ConfigDB 就绪且表已载/系统设置已落引擎态/菜单层可见/续玩钮态=断点在档态/闲时会话已在树/调试浮窗态=DS_DEBUG 门控);架构文档启动流程(:173)只有装配动作无后置条件——是否入册待裁 |
|
||||
| 3 | design/dev/architecture/architecture.md | **RunScope 生命周期存活性矩阵**:九阶段(闲时/开局停点/战斗中/结算后/选路页/终局面/回菜单收束/各模态开合后/两连局+续玩)× 五引用面(run_scope 在树/adventure/runner/settlement/四处回调句柄同源)的活死表;架构文档 :124 只登记了 RunScope 的存在——矩阵是否入册、阶段划分是否认账待裁 |
|
||||
|
||||
**状态**:备忘(反哺点三条待裁决;代拍板追认单见实现报告 §三;残留人闸=07 ㉗ 启动态菜单目检,登记不阻塞)
|
||||
|
||||
---
|
||||
|
||||
+16
-4
@@ -272,10 +272,9 @@
|
||||
| v0.7 | 2026-10-13 | 新增 ㉓ 敌人表内容修批三观测目检节(迭代 24 卡①随批登记:新招可见性/甲型体感/第一关节奏——登记不阻塞按 2026-10-12 真人检测立规) |
|
||||
| v0.8 | 2026-10-13 | 新增 ㉔ UI 全局主题批全屏巡检目检节(迭代 25 卡①随批登记:引擎默认灰清零巡检/音量滑轨两段可辨/专项观察点二(滑轨柄抢焦点·滚动条槽可见性)/预盖件无实役注记——登记不阻塞;能力验证半=冒烟 V1-V6) |
|
||||
| v0.9 | 2026-10-14 | 新增 ㉕ UI 交互与观感批逐屏巡检目检节(迭代 26 卡①随批登记:按钮五态反馈/键盘焦点环/全局 Nearest 巡检(含暗角与渐变白名单判据)/布局均衡(商店居中+营地四栏等高)/诊断钮清零——登记不阻塞;能力验证半=冒烟 V1-V6+T4 结构断言) |
|
||||
<<<<<<< HEAD
|
||||
| v1.0 | 2026-10-14 | **qa 收尾批·人闸核销(用户 2026-10-14 回报实机目检全部完成)**:⑮真人半、⑳、㉑、㉒、㉓、㉔、㉕ 七节逐节加「✅ 已核销(2026-10-14)」注记——用户目检回报完成、未呈报问题;iter19 acceptance-package 存活八项与 iter20 五件(行为变化速览节)同批核销;迭代 18 清单 18 项同批核销。注:⑯⑰⑱⑲ 等早期人闸节此前已随各批核销或归 07 独立跟踪,本条不涉及 |
|
||||
=======
|
||||
| v1.0 | 2026-10-14 | 新增 ㉖ 代码架构技术债修复批全流程走查目检节(迭代 27 卡①随批登记:全流程观感零变化走查/续玩断点恢复/调试浮窗对局中可开不锁棋盘〔:407-408 事故回归〕/刷新余量显示一致/DS_DEBUG 启动浮窗+两屏角标——登记不阻塞;能力验证半=冒烟 V1-V9+parity 三变体逐位一致对拍五跑全同) |
|
||||
| v1.1 | 2026-10-14 | 新增 ㉖ 代码架构技术债修复批全流程走查目检节(迭代 27 卡①随批登记:全流程观感零变化走查/续玩断点恢复/调试浮窗对局中可开不锁棋盘〔:407-408 事故回归〕/刷新余量显示一致/DS_DEBUG 启动浮窗+两屏角标——登记不阻塞;能力验证半=冒烟 V1-V9+parity 三变体逐位一致对拍五跑全同) |
|
||||
| v1.2 | 2026-10-15 | **冲突标记清理解(迭代 28 随批)**——本文件 v1.0/v1.1 两行处残留 `<<<<<<<`/`=======`/`>>>>>>>` 合并冲突标记(迭代 27 分支 iter27-techdebt-20261014 合并进 master 时漏解,两侧均有效故全留:qa 收尾批人闸核销行 + ㉖ 目检节登记行与正文节);清理后两行内容照旧,仅版本号顺延。同批新增 ㉗ 启动态菜单目检节(迭代 28 卡①随批登记) |
|
||||
|
||||
|
||||
## ㉖ 代码架构技术债修复批——全流程走查(迭代 27 卡①随批登记;能力验证半=冒烟 V1-V9+parity 三变体逐位一致对拍五跑全同)
|
||||
@@ -289,4 +288,17 @@
|
||||
| 3 | 调试浮窗对局中可开不锁棋盘 | 对局中开浮窗→棋盘可继续操作(:407-408 永久锁死事故回归——互斥域 tags 化后钉面) |
|
||||
| 4 | 刷新余量显示 | 刷新受理后 HUD 余量递减且与受理次数一致(双账删副本后 HUD 单源读快照) |
|
||||
| 5 | DS_DEBUG=1 启动 | 浮窗启动即开+两屏角标点亮可点(同步面随 ModalFlow 迁移后行为钉面) |
|
||||
>>>>>>> iter27-techdebt-20261014
|
||||
|
||||
---
|
||||
|
||||
## ㉗ 启动装配与生命周期验收补全批——启动态菜单目检(迭代 28 卡①随批登记;能力验证半=冒烟 V1a-d/V2a-f/V3a-i+启动态菜单 golden 逐像素)
|
||||
|
||||
> 装配顺序修复(冒险屏先挂、菜单屏后挂)后,**启动即见的那一屏**是本批唯一玩家可见行为变更。自动化闸只能证明「顶层是谁」与「逐像素未变」,真机上「看着对不对、点不点得动」仍需人眼。登记不阻塞(2026-10-12 真人检测立规)。
|
||||
|
||||
| # | 目检项 | 判据 |
|
||||
|---|---|---|
|
||||
| 1 | 启动即见主菜单 | F5 启动后第一屏是主菜单(标题「Dungeon Sweeper」+副标题「扫雷的因」+开始/继续上局/营地/生涯/设置/退出六钮),**不再**是战斗界面/棋盘背景 |
|
||||
| 2 | 菜单按钮可点 | 六钮逐个点:鼠标悬停有反馈、点击有响应(继续上局在无断点时不显示——有断点时显示且文案带关卡号) |
|
||||
| 3 | 菜单与战斗互不遮挡 | 开新冒险后菜单让位、战斗界面完整可见;回菜单后战斗内容不再透出(模态互斥观感与改前一致) |
|
||||
| 4 | 各覆盖层开合层级正确 | 图鉴/关卡地图/奇物/终局/商店/营地/设置/状态/生涯/确认框/调试浮窗逐个开合——浮层都在内容之上,调试浮窗浮于菜单之上且不吃下层点击 |
|
||||
| 5 | 音量与减动效启动即生效 | 改设置退出重进:音量/减动效保持上次值(boot 链路后置条件的人眼侧) |
|
||||
@@ -0,0 +1,192 @@
|
||||
# 迭代 28 实现报告——启动装配与生命周期验收补全批(六任务全量入库)
|
||||
|
||||
**日期**:2026-10-15 **分支**:iter28-bootlife-20261015(worktree `g-plan2-iter28-bootlife`,基点 39c6544)
|
||||
**需求卡片**:`design/research/需求卡片-启动装配与生命周期验收补全批-2026-10-15.md` v1.0(零待裁点,用户预批连做全批,不入 git)
|
||||
**一句话**:把「启动后玩家该看到什么、状态对象何时该活何时该死」从口头约定变成**可执行验收函数**,并逐条用变异实验证明这些函数真能抓住对应缺陷。
|
||||
|
||||
---
|
||||
|
||||
## 一、六卡交付
|
||||
|
||||
| 卡 | 内容 | 提交 |
|
||||
|---|---|---|
|
||||
| T0 | worktree + 基线三查 + 运行期树序实采(临时探针两枚,采完即删) | 无产物(定义面已在前置笔 39c6544) |
|
||||
| T1 | 装配语义合同单点化 + 装配顺序修复 + V2 断言重写 | `6f71509` |
|
||||
| T2 | boot 链路时序后置条件(`_smoke_boot_chain`) | `a35581e` |
|
||||
| T3 | 生命周期存活性矩阵(`_smoke_lifecycle_matrix`) | `77075e6` |
|
||||
| T4 | 渲染面内容断言三件(探针自检 + 墨色判据 + 启动态菜单 golden) | `67342c6` |
|
||||
| T5 | 变异验证(横切,六次注入;**零提交——注入即还原**) | 无提交(见 §五) |
|
||||
| T6 | 规范三处落文 + 机械面文档同步闸 | `db1bf42` |
|
||||
| T1 补 | 类型闸 S9 归零(`main.gd` 合同装配容器注解) | `140e440` |
|
||||
|
||||
### T0 开工基线与事实复核
|
||||
|
||||
基线冻结:冒烟 **1086/0**(roadmap 与卡片所记 1081 系 iter27 补遗批 `d5eea25` +V8×5 未回写,以实跑为准,**已随本批回写**)、run_all **27 套**全绿、probe_gate --click 全绿残留 0。
|
||||
|
||||
运行期实采修正了计划书与卡片的三处描述偏差(按「诊断订正」惯例以代码/运行期为准):
|
||||
|
||||
1. **屏壳是容器、不当「顶层可见层」候选**(卡片诊断订正①,运行期实证):三屏壳根均 `MOUSE_FILTER_IGNORE` 纯容器,camp 壳 boot 态 `visible=true` 但成员恒隐;裸树遍历自顶向下会先命中透明壳。断言改为遍历合同**内容层**候选集。
|
||||
2. **`_row_pacer` 句柄数**(卡片诊断订正②):计划书称 6 视图句柄,实查 5 + 2 Callable。
|
||||
3. **`main.gd:378` 十二模态登记数组**形态与卡片描述有差异——合同常量按运行期实采自行定义为 **16 层自洽表**(含 host 宿住层、shell 屏壳占树位两类特例)。
|
||||
|
||||
T0 临时探针 `t28_treeorder_probe.gd` / `t28_treeorder_probe2.gd` 采完即删,未提交。
|
||||
|
||||
### T1 装配语义合同单点化 + 顺序修复 + V2 断言重写
|
||||
|
||||
- **合同常量 `LAYER_CONTRACT`**(`main.gd`)=全仓唯一事实源:16 层(屏壳 shell / 模态 modal / 全屏态 state / 非视觉 node),带 `tags`(模态互斥域)、`host`(宿住层:level_screen 宿冒险屏壳)、`shell`(屏壳占树位、成员占登记位:camp_overlay/camp_screen)四类字段。树序=登记序=z 序同源派生。
|
||||
- **唯一落位口 `_apply_layer_contract()`**:按合同序 `move_child(node, slot)` + `_register_modal(member, tags)`;删除了原先散落在 `_build_ui` 的两处 `move_child`(camp 屏壳插位、curio 对位)。新增 `_layer_node(key)` 查表寻址函数。
|
||||
- **装配顺序修复**:`_adventure_screen` 先实例化挂树、`_menu_screen` 后挂——修复坏事实①「菜单屏先挂垫底被冒险屏盖死,F5 见战斗界面」。
|
||||
- **V2 断言重写**(坏事实②:旧断言把错误顺序写成期望值,测试在保护 bug):删除三条裸下标断言(`kids.find(_menu_screen)==0 and kids.find(_adventure_screen)==1` 等),改为
|
||||
- **V1a** 启动态顶层可见层=start_overlay(内容层遍历,屏壳不当候选)
|
||||
- **V1b** 语义关系:菜单屏壳树序 > 冒险屏壳树序
|
||||
- **V1c** 合同树序链逐对保序(运行期树序 == `LAYER_CONTRACT` 序)
|
||||
- **V1d** 落位单口源码断言(`_apply_layer_contract` 函数体之外 `move_child(` 计数 == 0)
|
||||
- 场景探针同步:`ui_struct_probe` 新增 `_modal_registry_contract`(登记序==合同模态序逐位点名、直接子层树序==合同序、level_screen 宿主特例、camp 屏壳夹位特例),旧 `_career_registry_contract` 同批退役。
|
||||
|
||||
### T2 boot 链路时序后置条件
|
||||
|
||||
新增 `_smoke_boot_chain`,**十断言**(卡片写「六断言」,实施期按可断言面扩为十):ConfigDB 就绪且表已载 / 音量与减动效引擎态 == settings_store 现值 / start_overlay 可见 / **续玩钮态 == 断点在档态(boot 路径关系判据 + 隔离新档双向三子态:无断点隐、合成断点显、清断点回隐)** / run_scope 闲时会话在树 / 调试浮窗态 == DS_DEBUG 门控。存档隔离走 `attach_path("user://smoke_boot_chain.json")`。
|
||||
|
||||
### T3 生命周期存活性矩阵
|
||||
|
||||
新增 `_smoke_lifecycle_matrix`,**九阶段 × 五引用面**:闲时 / 开局停点(地图选路停点态合并)/ 战斗中 / 结算后(胜利停点)/ 选路页 / 终局面 / 回菜单收束 / 各模态开合后(菜单域四模态)/ 两连局 + 续玩。每阶段判 20+ 取数面的存活性:`run_scope` 在树、adventure / runner / settlement 三面活死、四处回调句柄(treasure/bonus/camp/modal_flow)与 `run_scope` 成员同源。活战斗驱动复用现有 `_auto_play_round` 模式(phase/round 守卫 + `AutoPlayer.choose_dig`)。
|
||||
|
||||
### T4 渲染面内容断言三件(坏事实③)
|
||||
|
||||
1. **`shot_probe.gd` 两自检**:boot 态顶层可见层 == start_overlay;菜单标题/副标题 `is_visible_in_tree` 且 `get_global_rect()` 落视口内。隔离调用改 `ProbeUiUtil.isolate_save(main, "smoke_shot_probe", false, true, true)`(refresh_resume 开——菜单截图/golden 不依赖真实档断点态)。
|
||||
2. **`shot_checker.py` 墨色判据 F**:新增 `check_text_presence()`——与中位灰度差 >60 的像素占比落在 [2%, 55%] 带(**极性无关**,不预设字色深浅)。
|
||||
3. **启动态菜单 golden**:`golden_baseline_refresh.sh` 新增 `--menu` 模式(含字体与 `button_stone.png` 素材守卫),基线 `game/tests/golden/menu_boot.png` 入库;`render_gate.sh` 补菜单 golden 逐像素对比段(基线缺失=FAIL、截图/素材缺位=SKIP,沿既有 Q5 口径)。帧差前置验证:菜单 **changed=0.0000% / max_diff=0**(光柱是静态贴片),故全屏 golden 安全、无需收窄区。
|
||||
|
||||
### T6 规范落文 + 机械面文档同步
|
||||
|
||||
- **新硬规则「验收函数无变异红记录不算验收成立」**三处落文:`CLAUDE.md` 批次工作流第 9 条(原 9–14 顺延 10–15)、`design/dev/code-conventions.md` §3.6 变异验证操作细则(注入→最窄闸见红→`git checkout` 精确还原→记录四步;变异不红的二选一处理;判据强弱分层)、req-card 技能 §7 验证闸(WorkBuddy 与 ZCode 两份副本已同步;CodeBuddy / Qoder CN 无此技能副本,不动)。
|
||||
- 机械面文档随批更新:roadmap 迭代 28 行换代+近期主战场前沿改 29+ M4;06 登记条销号改写+反哺点三条专条;07 新增 ㉗ 启动态菜单目检节;doc 三闸对基线零新增。
|
||||
|
||||
---
|
||||
|
||||
## 二、闸记录
|
||||
|
||||
| 闸 | 结果 |
|
||||
|---|---|
|
||||
| 冒烟全量 | **1125 / 0**(基线 1086/0,+39) |
|
||||
| 单元测试 run_all | **27 套全绿** |
|
||||
| probe_gate --click | **全绿**,残留 Godot = 0 |
|
||||
| probe_gate --full | ⚠️ **t17_defer_probe 红**(存量红,非本批引入——详见 §六),其余五件全绿 |
|
||||
| render_gate | **全绿**:shot_checker A–D 过(含新增 F 判据 I-OK×2)、棋盘 golden 0.00%×3、**菜单 golden 0.00%** |
|
||||
| doc 三闸 | 对基线零新增(gdd_lint 三件 / 死链 3 / 术语 5 组,与 master 39c6544 同) |
|
||||
| static_scan | S1/S2 ok、S4 ok、**S9 归零**、S10 FAIL=文件头存量 125 处(2026-10-15 新立规,清偿批在册待发令,非本批引入) |
|
||||
| MC 模拟 | **零跑**(本批零数值零行为变更,装配顺序修复除外) |
|
||||
|
||||
**类型闸自纠一笔**:批末 static_scan 报 S9(类型闸)**存量 1 处**——`main.gd:453` `_apply_layer_contract` 内 `var tags: Array` 未带元素类型,系本批新增代码触发(iter27 立闸时 S9 全绿)。随批修为 `Array[Variant]`,S9 归零,冒烟复跑 1125/0 不变。**这笔正是「闸不是摆设」的反面印证**:本批新增的验收函数抓别人的错,既有的类型闸同样抓本批的错。
|
||||
|
||||
**顺带清理(非本批引入,基线带入)**:`design/gdd/07-体验验证清单.md` v1.0 行处残留 `<<<<<<<` / `=======` / `>>>>>>>` 合并冲突标记(iter27-techdebt-20261014 分支合并进 master 时漏解)。两侧均有效故全留(qa 收尾批人闸核销行 + ㉖ 目检节登记行与正文节),版本号顺延 v1.1,清理注记入 v1.2 行。
|
||||
|
||||
---
|
||||
|
||||
## 三、代拍板追认单(待用户逐项追认)
|
||||
|
||||
白名单内八项(卡片 §9)全部按授权执行;以下为实施期实际取定的形态与**白名单外需追认项**:
|
||||
|
||||
| # | 事项 | 取定 | 归属 |
|
||||
|---|---|---|---|
|
||||
| 1 | 合同常量数据形态 | 有序 `Array[Dictionary]` + tags/host/shell 特例字段(白名单①) | 白名单 |
|
||||
| 2 | 特例层断言表达 | 经宿主屏壳 / 成员间接比较(白名单②) | 白名单 |
|
||||
| 3 | boot 顶层断言候选集 | 显式内容层清单(屏壳不当候选)(白名单③) | 白名单 |
|
||||
| 4 | 墨色阈值 | 差 >60、占比带 [2%, 55%],按菜单文字实测定 provisional(白名单④) | 白名单 |
|
||||
| 5 | 菜单 golden 收窄区 | 不收窄——帧差实证 0.0000%,直接全屏比对(白名单⑤,结论为「无需收窄」) | 白名单 |
|
||||
| 6 | 变异注入手段 | 改常量序 / 插 `move_child` / 注释 `add_child` / 注释 `queue_free` / `visible=false` / 状态源取反(白名单⑥) | 白名单 |
|
||||
| 7 | 新冒烟段挂载位置 | `_smoke_scene_split` → `_smoke_boot_chain` → `_smoke_run_lifecycle` → `_smoke_lifecycle_matrix`,挂 `_split_tail`(白名单⑦) | 白名单 |
|
||||
| 8 | 计划书存档文件名 | `.zcode/plans/启动装配与生命周期验收补全批-loop-plan-2026-10-15.md`(白名单⑧) | 白名单 |
|
||||
| 9 | **合同表规模 12 → 16 层** | 卡片基于「十二模态登记数组」,实采后扩为覆盖全视觉层的 16 层(屏壳/非视觉节点/全屏态亦入表)——约束「单点化+同源派生」未破 | **白名单外,请追认** |
|
||||
| 10 | **T2 断言数 6 → 10** | 卡片写六断言,实施期把续玩钮拆出隔离三子态、补全 ConfigDB/设置/会话/浮窗门控面 | **白名单外,请追认** |
|
||||
| 11 | **T3 阶段划分调整** | 卡片写「十阶段」,实施期把「地图选路停点态」并入「开局停点」(同一运行期状态,无独立断言面),得九阶段 | **白名单外,请追认** |
|
||||
| 12 | **变异 2b 形态偏离计划书** | 计划书原文「注释 `_begin_session`/提前 free」;实施期取 2a=注释 `add_child`(该活不活)、2b=**注释 `queue_free`(该死不死)** 互补双向。「提前 free」改为 `free()` 会与 V3f 断言同向(仍绿),不构成有效变异,故未取 | **白名单外,请追认** |
|
||||
| 13 | **07 冲突标记清理** | 基线带入的未解合并标记,本批因需同文件登记 ㉗ 而顺手解开(两侧全留,零内容发明) | **白名单外,请追认** |
|
||||
| 14 | **req-card 技能双副本** | ZCode 副本与 WorkBuddy 副本已各自演进(ZCode 多两条自有条目),本批只把变异验证一条补进 ZCode 副本,**未擅自取齐**两副本差异 | 知会项;是否取齐请裁 |
|
||||
|
||||
---
|
||||
|
||||
## 四、GDD 反哺点(三条,已登记 06,待用户逐点裁决另批落文)
|
||||
|
||||
| # | 文档 | 反哺点 |
|
||||
|---|---|---|
|
||||
| 1 | `design/dev/architecture/architecture.md` + ADR 系列 | **视觉层装配合同**=`main.gd` 的 `LAYER_CONTRACT` 常量(16 层),树序=登记序=z 序同源派生,全仓唯一落位口 `_apply_layer_contract`;现行架构文档无此条款(仅 :124 提「组合根 main.gd」、:173 提「主场景装配视图单元」)——是否立条款、合同表是否随架构文档摘录待裁 |
|
||||
| 2 | `design/dev/architecture/architecture.md` | **boot 链路后置条件**已由 `_smoke_boot_chain` 十断言成文;架构文档启动流程(:173)只有装配动作无后置条件——是否入册待裁 |
|
||||
| 3 | `design/dev/architecture/architecture.md` | **RunScope 生命周期存活性矩阵**(九阶段×五引用面)已可执行化;架构文档 :124 只登记了 RunScope 的存在——矩阵是否入册、阶段划分是否认账待裁 |
|
||||
|
||||
**与迭代 27 反哺点①的关系**:iter27 只登记了「场景分层/会话生命周期陈述滞后于实态」的方向性缺口;本批把其中「层序=谁说了算」「boot 后置条件是什么」「会话引用面何时活何时死」三件做成了代码级事实与可执行断言,本条是其**具体化承接**,落文时可合并处理。
|
||||
|
||||
---
|
||||
|
||||
## 五、变异验证记录表(六次注入,全部见红并精确还原)
|
||||
|
||||
判据:**注入 → 跑最窄的那一道闸 → 见红 → `git checkout -- <文件>` 精确还原 → 复跑确认回绿**。还原后统一复跑冒烟得 **1125/0**,六轮全以 `REVERTED-CLEAN` 收尾。
|
||||
|
||||
| # | 变异点(file:line) | 注入方式 | 缺陷类 | 跑的闸 | 红输出(摘要) |
|
||||
|---|---|---|---|---|---|
|
||||
| 变异 1 | `game/presentation/main.gd` `LAYER_CONTRACT` 首两行 | `adventure_screen` / `menu_screen` 对调(翻装配序) | 装配序错 | `python tools/run_smoke.py` | 1125 项 **2 失败**:`V1a`(实测顶层=`@VBoxContainer@648`,菜单被冒险屏盖住)、`V1b`(菜单屏壳树序不在冒险屏壳之上)红;`V1c/V1d` 绿 |
|
||||
| 变异 1b | `main.gd` `_apply_layer_contract()` 调用后 | 插入散落 `move_child(debug_overlay, 0)` | 层序硬编码(合同外落位) | 同上 | 1125 项 **2 失败**:`V1d`(函数体之外 move_child 调用点=1 应=0)、`V1c`(违例 `@Control@1057(14)≤@Control@1119(0)`)红 |
|
||||
| 变异 2a | `main.gd:195` `_begin_session` | 注释 `add_child(run_scope)`(会话对象不挂树) | 对象该活不活 | 同上 | 1125 项 **22 失败**:`V2e` 红 + V3 全部 21 个阶段面「run_scope=不在树」 |
|
||||
| 变异 2b | `main.gd:191-192` `_begin_session` | 注释 `run_scope.queue_free()`(旧会话不销毁) | 对象该死不死 | 同上 | 1125 项 **1 失败**:`V3f`「收束后旧局经 queue_free 消亡(is_instance_valid=false)」红 |
|
||||
| 变异 3 | `main.gd:218` `_ready` | `_start_overlay.visible = false`(藏菜单层) | 层被盖/不可见 | 冒烟 + 窗口化 `shot_probe` + `golden_compare` + `shot_checker` | 冒烟 **3 失败**(`V1a`/`V2c`/`V3a`);探针自检 **3 问题**(顶层≠start_overlay、标题/副标题 `is_visible_in_tree=false`);golden `changed=34.23%` FAIL;`shot_checker` exit=1 |
|
||||
| 变异 4 | `main.gd:531` `_refresh_resume_button` | `_resume_btn.visible = not has`(状态源倒置) | 状态源/时序倒置 | `python tools/run_smoke.py` | 1125 项 **6 失败**:`V2d` 四条全红(boot 态、无断点态、有断点态、清断点回隐)+ `V3a` + `V3i` |
|
||||
|
||||
### ⚠️ 变异 3 暴露的一条判据边界(重要,如实记录)
|
||||
|
||||
**F 判据(墨色占比)本身没有红**——菜单层被藏后,底层棋盘内容透过标题框仍给出 33.48% 的高反差像素,落在 [2%, 55%] 带内,`I-OK` 照过。真正拦下这个缺陷的是另外两件:探针的 `is_visible_in_tree` 自检,和 golden 逐像素(34.23%)。`shot_checker` 虽 exit=1,但红的是 **C 判据**(背景纹理幅度 79.4 > 18),属间接命中。
|
||||
|
||||
**结论**:F 判据只能证伪「整框空白/整框糊」,**不证明「框里是菜单标题」**。菜单内容断言的强判据是「可见性自检 + golden 逐像素」,F 判据为补充。这条边界已同步写进 `code-conventions.md` §3.6 的「判据强弱分层」,供后续写卡配判据时援引。
|
||||
|
||||
---
|
||||
|
||||
## 六、残留与去向
|
||||
|
||||
### 6.1 probe_gate --full 的 t17_defer_probe 红(存量红,**非本批引入**,待裁)
|
||||
|
||||
**① 现象**:`tools/probe_gate.sh --full` 中 `t17_defer_probe` 失败——`[t17_defer] FAIL — 关卡画面未出现`(exit=2;首次运行时表现为 exit=124 超时挂死,同一根因的两种表现)。`--full` 的其余五件(ui_input / ui_struct / ui_contrast / ui_probe / ui_tune / fx)**全绿**,`--click` 亦全绿。
|
||||
|
||||
**② 证据链(三条,均指向「非本批引入」)**:
|
||||
|
||||
| 证据 | 结果 |
|
||||
|---|---|
|
||||
| 主区 master 39c6544(**不含本批任何改动**)跑同一探针 | **同样 FAIL 4/4**,失败行与本批完全一致 |
|
||||
| 把真实档 `user://save.json` 临时改名移开后跑同一探针 | **PASS**(exit=0,15s,`胜利同帧挂起+排播完开窗 两断言全过`) |
|
||||
| 探针自身行为 | 首次跑绿、跑完即把真实档写成带断点状态,其后必红——**探针会污染它本应隔离的真实档** |
|
||||
|
||||
**③ 机理**:`_start_adventure_from_menu()`(`main.gd:498`)在 `save_manager.has_breakpoint()` 为真时不开冒险,改弹「已有未完冒险」确认框。真实 `user://save.json` 当前存在未完冒险断点时,探针的隔离(`main.save_manager = iso_c`)没能覆盖到这条路径,于是 60 帧内等不到 `level_screen.visible` → FAIL。
|
||||
|
||||
**④ 补充事实(须告知)**:`user://` 目录(`%APPDATA%/Godot/app_userdata/DungeonSweeperVerticalSlice/`)由**全部 worktree 共享**(同 project name),所以主区与任何 worktree 的探针/冒烟都在读写同一份存档。本机这份真实档现在带有一个未完冒险断点。**本批验证过程中曾为定位根因临时改名移开该档,已按原样还原并通过逐字节 `cmp` 校验一致**(备份留 `/d/tmp_save_backup_20261015.json`)。
|
||||
|
||||
**⑤ 历史沿革**:iter26 台账记「probe click/full 全绿」,**iter27 台账只记了 `probe click` 未记 `--full`** —— 该红大概率自 iter27(row_pacer 排播泵迁出 + ModalFlow 抽取)起就存在而未被跑到。probe_gate 头注亦有预言:「超时多半=探针未适配新停点层,归 probe 适配批」。
|
||||
|
||||
**⑥ 待裁(不擅自处理)**:本红属**探针适配存量债**,修它=改探针隔离方式,是卡片外的新需求。三个选项请裁:
|
||||
|
||||
- **甲**:认可存量红、照常合并——红与本批无关,master 现状同红,合并不会使任何东西变差;
|
||||
- **乙**:先修探针隔离(补真实档隔离/跑前清断点)再合并——属新需求,需用户批后另起小批;
|
||||
- **丙**:用户先自行处置本机真实档断点(游戏内开新冒险覆盖或删档),我再复跑 `--full` 取绿后合并——但此法只治本机这一次,探针自污染的根因仍在。
|
||||
|
||||
### 6.2 实机目检(登记不阻塞,2026-10-12 立规)
|
||||
|
||||
已登记 `design/gdd/07-体验验证清单.md` **㉗ 启动态菜单目检**五观测项:启动即见主菜单 / 菜单六钮可点 / 菜单与战斗互不遮挡 / 各覆盖层开合层级正确 / 音量与减动效启动即生效。**登记完毕即批内义务了结**,用户自行安排时间验收。
|
||||
|
||||
### 6.3 其他
|
||||
|
||||
- 计划书存档:`.zcode/plans/启动装配与生命周期验收补全批-loop-plan-2026-10-15.md`(不入 git)。
|
||||
- 需求卡片按「中间产物吸收即死」惯例不入 git;卡片所记基线 1081 与实测 1086 的差异已随本批回写 roadmap 与 06。
|
||||
- req-card 技能两份副本(WorkBuddy / ZCode)本批各补一条后**仍有两条 ZCode 自有条目未取齐**,是否取齐请裁(§三 #14)。
|
||||
|
||||
---
|
||||
|
||||
## 七、冒烟断言数增量呈报
|
||||
|
||||
**1086 → 1125,净 +39**,逐项可溯源:
|
||||
|
||||
| 项 | 条数 |
|
||||
|---|---|
|
||||
| 新增 V1(装配合同四断言:V1a/V1b/V1c/V1d) | +4 |
|
||||
| 新增 V2(boot 链路十断言) | +10 |
|
||||
| 新增 V3(生命周期矩阵,九阶段面 + 各附加判据) | +28 |
|
||||
| 退役裸下标断言(`kids.find(...)` 三处——合同链断言已覆盖) | −3 |
|
||||
| **合计** | **+39** |
|
||||
|
||||
(新增 42 条全部带 `〔装配生命批 Vn〕` 尾缀,可与需求卡片 §7 逐条 grep 对账。)
|
||||
+93
-14
@@ -10,8 +10,8 @@
|
||||
# 加成值零表写——面板百分比经 runner.set_hero_override / debug_session 生效值 Callable
|
||||
# 以 encounter 参数入战斗,db.hero 运行期只读)
|
||||
# ④ 全屏 States 状态机(挖掘/结算/终局)与一次性提示去重。
|
||||
# 场景结构:三屏 .tscn 屏壳 + 全部模态程序化装配于本类;树序 = [菜单屏, 冒险屏, 模态层...]——屏内两容器
|
||||
# 无同时可见态,全部模态在屏壳之上。
|
||||
# 场景结构:三屏 .tscn 屏壳 + 全部模态程序化装配于本类;树序/登记序唯一事实源=LAYER_CONTRACT
|
||||
# (见下——屏内两容器无同时可见态,全部模态在屏壳之上)。
|
||||
# 局生命周期:run_scope.gd 节点持全部局态(adventure/runner/settlement/局态旗/账本)——_begin_session
|
||||
# 创建,会话更替/终局整节点 queue_free()。
|
||||
# autoload:SaveManager/MetaProgression/RngService/ConfigDB 为 autoload(类名加 Core 后缀、autoload 占
|
||||
@@ -52,6 +52,30 @@ 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")
|
||||
|
||||
# ==================== 视觉层合同(唯一事实源——树序=登记序=z 序;改序只改此表) ====================
|
||||
# kind: shell=屏壳容器(成员随壳不占独立树位;level_screen 例外另占登记位)/modal=模态层(入登记册,
|
||||
# tags 随登记)/state=全屏态(占树位不入登记册)/node=非视觉节点(占树位不入登记册)。
|
||||
# host=宿住屏壳(树序随宿主、登记序按本合同);shell=模态成员的屏壳(成员与屏壳同槽:屏壳占该树位、
|
||||
# 成员占该登记位)。落位=_apply_layer_contract(全仓唯一层序落位点,add_child 之后统一排序+登记)。
|
||||
const LAYER_CONTRACT: Array[Dictionary] = [
|
||||
{"key": "adventure_screen", "kind": "shell"},
|
||||
{"key": "menu_screen", "kind": "shell"},
|
||||
{"key": "row_pacer", "kind": "node"},
|
||||
{"key": "legend_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "node_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "level_screen", "kind": "modal", "tags": [ModalStack.TAG_SCREEN_STATE], "host": "adventure_screen"},
|
||||
{"key": "curio_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "end_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "shop_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "camp_overlay", "kind": "modal", "tags": [], "shell": "camp_screen"},
|
||||
{"key": "settings_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "status_panel", "kind": "modal", "tags": []},
|
||||
{"key": "career_overlay", "kind": "modal", "tags": []},
|
||||
{"key": "inter_overlay", "kind": "state", "tags": []},
|
||||
{"key": "confirm_dialog", "kind": "modal", "tags": []},
|
||||
{"key": "debug_overlay", "kind": "modal", "tags": [ModalStack.TAG_NON_LOCKING]},
|
||||
]
|
||||
|
||||
var db
|
||||
var run_scope = null # 会话状态承载(run_scope.gd——迭代 27 T1 物化;_ready 即建闲时态,永非 null)
|
||||
# SaveManager/MetaProgression 升格 autoload(迭代 27 T1 裁点①)——成员名保留为「autoload 读视图」
|
||||
@@ -200,14 +224,16 @@ func _ready() -> void:
|
||||
# ==================== 视图接线(三屏壳 + 模态层装配;构建归屏壳/UiAssembler,本类只接线) ====================
|
||||
|
||||
func _build_ui() -> void:
|
||||
# 三屏壳实例化(迭代 27 T1 场景拆分):树序 = [菜单屏, 冒险屏, 模态层...]——屏壳全在模态之下。
|
||||
# 三屏壳实例化(迭代 27 T1 场景拆分):冒险屏先挂、菜单屏后挂(启动态菜单在顶层=玩家可见——
|
||||
# 装配序修复:旧序菜单先挂被冒险屏盖死,F5 见战斗界面);最终树序+登记序以 LAYER_CONTRACT
|
||||
# 为唯一事实源,_apply_layer_contract 在装配尾统一落位。
|
||||
# 屏内 level_screen 与 battle_root 无同时可见态(屏级互斥+模态栈遮挡),层内先后零视觉差;
|
||||
# 视觉共存对逐对验算:legend/battle(图例战斗中开——legend 在冒险屏壳之后=其上 ✓)、
|
||||
# debug/level_screen(浮窗最后子=其上 ✓)、shop/level_screen(商店节点域弹出——模态均在屏壳上 ✓)。
|
||||
_menu_screen = MenuScreenScene.instantiate()
|
||||
add_child(_menu_screen)
|
||||
_adventure_screen = AdventureScreenScene.instantiate()
|
||||
add_child(_adventure_screen)
|
||||
_menu_screen = MenuScreenScene.instantiate()
|
||||
add_child(_menu_screen)
|
||||
_start_overlay = _menu_screen.start_overlay
|
||||
_battle_root = _adventure_screen.battle_root
|
||||
stage = _adventure_screen.stage
|
||||
@@ -264,8 +290,7 @@ func _build_ui() -> void:
|
||||
_camp_screen = CampScreenScene.instantiate()
|
||||
add_child(_camp_screen)
|
||||
camp_overlay = _camp_screen.camp_overlay
|
||||
# 树序 = 模态登记序 = z 遮挡序(缺陷⑤纪律)——营地层须夹在商店层与设置层之间
|
||||
move_child(_camp_screen, shop_overlay.get_index() + 1)
|
||||
# 营地屏壳插位(夹商店层与设置层之间)由 LAYER_CONTRACT 营地槽统一落位——禁散落 move_child
|
||||
legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay))
|
||||
node_overlay.continue_pressed.connect(_on_node_continue)
|
||||
level_screen.route_chosen.connect(_on_route_chosen)
|
||||
@@ -287,10 +312,10 @@ func _build_ui() -> void:
|
||||
career_overlay = CareerOverlay.new()
|
||||
add_child(career_overlay)
|
||||
career_overlay.closed.connect(_on_career_closed)
|
||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进)
|
||||
# 提升三选一覆盖层(迭代 10——z 序 = 节点层之上、终局层之下;确认后同拍推进;
|
||||
# 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child)
|
||||
curio_overlay = CurioPickOverlay.new()
|
||||
add_child(curio_overlay)
|
||||
move_child(curio_overlay, _end_overlay.get_index())
|
||||
# 提升三选一与替换子交互(T4/D4 拆件批——迁 curio_interact_controller.gd;本类只接线与回调注入,
|
||||
# ADR-0001——模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
|
||||
_bonus = CurioInteractController.new(CurioFlowContext.new(db, func() -> Variant: return meta, curio_overlay, _end_overlay, board_view,
|
||||
@@ -357,8 +382,9 @@ func _build_ui() -> void:
|
||||
debug_overlay.debug_levels_requested.connect(_start_debug_levels)
|
||||
debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 迭代 26 T5b——诊断转储入口随迁(原角落钮连接)
|
||||
|
||||
# 模态登记(缺陷⑤纪律——登记序 = 树序:关卡画面住冒险屏壳但登记位仍夹在节点层与三选一层之间;
|
||||
# 状态面板设置层同档、确认弹窗之前——P2 甲;生涯页同档随状态面板之后(迭代 18 卡④ P3 甲))
|
||||
# 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT,
|
||||
# _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、
|
||||
# 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child)
|
||||
# 模态编排注入(迭代 27 T4——ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable,P1-5 抽取)
|
||||
_modal_flow = ModalFlow.new()
|
||||
_modal_flow.start_visible_get = func() -> bool: return _start_overlay.visible
|
||||
@@ -374,9 +400,8 @@ func _build_ui() -> void:
|
||||
_sync_debug_overlay() # 迭代 18 卡⑦——他层关闭复活浮窗时同步刷新(P1 甲关后自动恢复语义配套)
|
||||
_modal_flow.replay_click = _replay_locked_click
|
||||
_modals = _modal_flow.stack # 探针 _list 登记序断言缝(ui_struct 登记序=z 序纪律断言)
|
||||
# 排除组随层登记(迭代 27 T4 P2-7——non_locking=调试浮窗〔迭代 18 卡⑦ T2 防线〕/screen_state=关卡地图屏)
|
||||
for entry: Array in [[legend_overlay, []], [node_overlay, []], [level_screen, [ModalStack.TAG_SCREEN_STATE]], [curio_overlay, []], [_end_overlay, []], [shop_overlay, []], [camp_overlay, []], [settings_overlay, []], [status_panel, []], [career_overlay, []], [confirm_dialog, []], [debug_overlay, [ModalStack.TAG_NON_LOCKING]]]:
|
||||
_register_modal(entry[0], entry[1])
|
||||
# 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表)
|
||||
_apply_layer_contract()
|
||||
_tune_sim = TuneSimController.new(db, debug_overlay, int(db.hero.max_hp))
|
||||
|
||||
|
||||
@@ -396,6 +421,60 @@ func _register_modal(m: Control, tags: Array = []) -> void:
|
||||
_modal_flow.register(m, tags) # 排除组随层登记(迭代 27 T4 P2-7——non_locking=调试浮窗/screen_state=关卡地图屏)
|
||||
|
||||
|
||||
# 合同层寻址(key→节点;纯查表不含层序语义——层序唯一事实源=LAYER_CONTRACT)
|
||||
func _layer_node(key: String) -> Node:
|
||||
match key:
|
||||
"adventure_screen": return _adventure_screen
|
||||
"menu_screen": return _menu_screen
|
||||
"row_pacer": return _row_pacer
|
||||
"legend_overlay": return legend_overlay
|
||||
"node_overlay": return node_overlay
|
||||
"level_screen": return level_screen
|
||||
"curio_overlay": return curio_overlay
|
||||
"end_overlay": return _end_overlay
|
||||
"shop_overlay": return shop_overlay
|
||||
"camp_screen": return _camp_screen
|
||||
"camp_overlay": return camp_overlay
|
||||
"settings_overlay": return settings_overlay
|
||||
"status_panel": return status_panel
|
||||
"career_overlay": return career_overlay
|
||||
"inter_overlay": return _inter_overlay
|
||||
"confirm_dialog": return confirm_dialog
|
||||
"debug_overlay": return debug_overlay
|
||||
return null
|
||||
|
||||
|
||||
# 合同装配(_build_ui 尾调用):按 LAYER_CONTRACT 序统一落位——直接子层 move_child 到合同槽、
|
||||
# 模态层按合同序登记;宿住层(host)树序随宿主只登记;成员层(shell)与屏壳同槽(屏壳占树位、成员占登记位)。
|
||||
# 本函数是全仓唯一层序落位点(move_child 单口)——改层序只改 LAYER_CONTRACT。
|
||||
func _apply_layer_contract() -> void:
|
||||
var slot := 0
|
||||
for entry: Dictionary in LAYER_CONTRACT:
|
||||
var tags: Array[Variant] = entry.get("tags", [])
|
||||
if entry.has("shell"):
|
||||
var shell: Node = _layer_node(String(entry["shell"]))
|
||||
var member: Node = _layer_node(String(entry["key"]))
|
||||
if shell != null:
|
||||
move_child(shell, slot)
|
||||
slot += 1
|
||||
if member != null:
|
||||
_register_modal(member as Control, tags)
|
||||
else:
|
||||
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
|
||||
continue
|
||||
var node: Node = _layer_node(String(entry["key"]))
|
||||
if node == null:
|
||||
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
|
||||
continue
|
||||
if entry.has("host"):
|
||||
_register_modal(node as Control, tags)
|
||||
continue
|
||||
move_child(node, slot)
|
||||
slot += 1
|
||||
if String(entry["kind"]) == "modal":
|
||||
_register_modal(node as Control, tags)
|
||||
|
||||
|
||||
func _open_modal(m: Control) -> void:
|
||||
_modal_flow.open(m) # 迭代 27 T4——挂起兜底/排播排空/遮挡记账/开侧同步编排归 ModalFlow(P1-5 抽取)
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ func _ready() -> void:
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
# ── 存档/元进度/设置三面隔离(ui_click_probe 同款——真档零污染;历史语义 = contains 寻址) ──
|
||||
ProbeUiUtil.isolate_save(main, "smoke_shot_probe", false, true, false)
|
||||
# 迭代 28 T4:refresh_resume 开——续玩钮按隔离档(无断点)定基,菜单截图/golden 基线不依赖
|
||||
# 真实档断点态(boot 关系由冒烟 _smoke_boot_chain V2d 断,此处只保截图确定性)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_shot_probe", false, true, true)
|
||||
# 隔离档即时应用(迭代 19 卡① T1——main 装配已按真实设置恢复过窗口态〔本机实测:真实档
|
||||
# fullscreen=true 时窗口进全屏,视口纹理 2304x1440≠1280 基准档,checker 尺寸/取样全盘失真〕;
|
||||
# apply 隔离档出厂默认=窗口化 1280x800,恢复本探针设计前提。探针侧环境修正,零运行时改动)
|
||||
@@ -44,8 +46,40 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
# ① 菜单(启动态)——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")
|
||||
# 自检一(迭代 28 T4):boot 态顶层可见全屏层=start_overlay(内容层树遍历——屏壳是容器不当候选:
|
||||
# camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历自顶向下会先命中透明壳)
|
||||
var top: Node = null
|
||||
var top_rx := -1
|
||||
var top_ry := -1
|
||||
for c: Node in [main._start_overlay, main._battle_root, main._inter_overlay,
|
||||
main.legend_overlay, main.node_overlay, main.level_screen, main.curio_overlay,
|
||||
main._end_overlay, main.shop_overlay, main.camp_overlay, main.settings_overlay,
|
||||
main.status_panel, main.career_overlay, main.confirm_dialog, main.debug_overlay]:
|
||||
if c == null or not (c is CanvasItem) or not (c as CanvasItem).visible:
|
||||
continue
|
||||
var rx: int = c.get_parent().get_index()
|
||||
var ry: int = c.get_index()
|
||||
if top == null or rx > top_rx or (rx == top_rx and ry > top_ry):
|
||||
top = c
|
||||
top_rx = rx
|
||||
top_ry = ry
|
||||
if top != main._start_overlay:
|
||||
_problems.append("boot 态顶层可见层≠start_overlay(实测 %s——菜单被埋)〔装配生命批 V4〕"
|
||||
% (top.name if top != null else "null"))
|
||||
# 自检二(迭代 28 T4):菜单标题/副标题 label 必须 is_visible_in_tree 且 rect 落屏内
|
||||
#(对比度只判反差不判「字在」——本条+checker 墨色占比共同补内容判据)
|
||||
var vp := get_viewport().get_visible_rect()
|
||||
for pair in [["menu_title", "Dungeon Sweeper"], ["menu_sub", "扫雷的因"]]:
|
||||
var lbl: Label = ProbeUiUtil.find_label(main._start_overlay, String(pair[1]))
|
||||
if lbl == null:
|
||||
_problems.append("菜单文字缺失: %s(find_label 零命中)〔装配生命批 V4〕" % String(pair[1]))
|
||||
continue
|
||||
if not lbl.is_visible_in_tree():
|
||||
_problems.append("菜单文字不可见: %s(is_visible_in_tree=false——层被埋或隐藏)〔装配生命批 V4〕" % String(pair[1]))
|
||||
var gr: Rect2 = lbl.get_global_rect()
|
||||
if not vp.encloses(gr):
|
||||
_problems.append("菜单文字 rect 越出视口: %s(%s ⊄ %s)〔装配生命批 V4〕" % [String(pair[1]), str(gr), str(vp)])
|
||||
_meta[String(pair[0])] = ProbeUiUtil.label_rect(main._start_overlay, String(pair[1]))
|
||||
await _snap("menu")
|
||||
# ①b 设置面(迭代 19 卡① T1 扩屏——settingsbtn 真实信号路径,开合同款真实钮)
|
||||
var setbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "设置", "prefix")
|
||||
|
||||
+400
-10
@@ -86,7 +86,9 @@ func _split_tail() -> void:
|
||||
_smoke_hygiene_gates()
|
||||
if autoload_ok:
|
||||
await _smoke_scene_split() # 协程函数必须 await(无 await=发射后不管,首个 await 后即被 quit 截断)
|
||||
await _smoke_boot_chain()
|
||||
await _smoke_run_lifecycle()
|
||||
await _smoke_lifecycle_matrix()
|
||||
else:
|
||||
check(false, "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)")
|
||||
print("=== 冒烟完成:%d 项检查,%d 项失败 ===" % [_checks, _failures])
|
||||
@@ -141,7 +143,15 @@ func _smoke_hygiene_gates() -> void:
|
||||
var mv: String = FileAccess.get_file_as_string("res://presentation/main.gd")
|
||||
check(not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger"),
|
||||
"V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕")
|
||||
|
||||
# V1d 层序落位单口:main.gd 的 move_child 调用全部收在 _apply_layer_contract 内(合同装配单口——
|
||||
# 函数体之外零调用点;出现第二处落位=硬编码层序,须改合同表而非加调用)
|
||||
var fn_start: int = mv.find("func _apply_layer_contract")
|
||||
var fn_end: int = mv.find("\nfunc ", fn_start + 10)
|
||||
if fn_end < 0:
|
||||
fn_end = mv.length()
|
||||
var outside_calls: int = (mv.substr(0, fn_start) + mv.substr(fn_end)).count("move_child(")
|
||||
check(outside_calls == 0,
|
||||
"V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" % outside_calls)
|
||||
|
||||
# ---- 三屏场景化断言(迭代 27 T1 V2——独立实例化+关键节点在位+组合根树序/z 序保序)----
|
||||
func _smoke_scene_split() -> void:
|
||||
@@ -168,18 +178,36 @@ func _smoke_scene_split() -> void:
|
||||
"V2 冒险屏壳独立实例化(战斗宿主+四区句柄+关卡地图屏在位)")
|
||||
adv.queue_free()
|
||||
await process_frame
|
||||
# ② 组合根装配(树序=菜单屏@0+冒险屏@1 在全模态之下;营地屏壳夹商店与设置层之间——z 序保序)
|
||||
# ② 组合根装配(合同单点化——LAYER_CONTRACT 唯一事实源:树序=登记序=z 序同源派生)
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
var kids: Array = main.get_children()
|
||||
check(kids.find(main._menu_screen) == 0 and kids.find(main._adventure_screen) == 1,
|
||||
"V2 组合根树序=屏壳在前(menu@0 adventure@1——视觉共存对全模态在其上)")
|
||||
var i_camp: int = kids.find(main._camp_screen)
|
||||
check(i_camp > kids.find(main.shop_overlay) and i_camp < kids.find(main.settings_overlay),
|
||||
"V2 营地屏壳树序夹商店层与设置层之间(登记序=树序=z 序纪律保序)")
|
||||
check(kids.find(main._adventure_screen) < kids.find(main.legend_overlay),
|
||||
"V2 冒险屏壳在全模态之下(图例战斗中开=其上——视觉共存对验算位)")
|
||||
# V1a 启动态顶层断言:boot 后自树顶向下第一个可见内容层=start_overlay(headless 可判)
|
||||
#(内容层遍历——屏壳是容器不当候选:camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历会误命中)
|
||||
var top: Node = _topmost_visible_layer(main)
|
||||
check(top == main._start_overlay,
|
||||
"V1a 启动态顶层可见层=start_overlay(实测顶层=%s——菜单被冒险屏盖住即红)〔装配生命批 V1〕"
|
||||
% (top.name if top != null else "null"))
|
||||
# V1b 语义关系:start_overlay 在冒险屏内容之上(菜单屏壳在冒险屏壳之上——装配序修复判据)
|
||||
check(main._menu_screen.get_index() > main._adventure_screen.get_index(),
|
||||
"V1b 菜单屏壳在冒险屏壳之上(start_overlay 盖住战斗内容)〔装配生命批 V1〕")
|
||||
# V1c 合同树序链逐对保序(直接子层全链=屏壳/模态/全屏态/非视觉节点;宿住层树序随宿主不参与)
|
||||
var chain_detail := ""
|
||||
var chain_prev: Node = null
|
||||
var chain_ok := true
|
||||
for entry: Dictionary in main.LAYER_CONTRACT:
|
||||
if entry.has("host"):
|
||||
continue
|
||||
var node: Node = main._layer_node(String(entry["shell"]) if entry.has("shell") else String(entry["key"]))
|
||||
if node == null:
|
||||
chain_ok = false
|
||||
chain_detail += " 缺失:%s" % String(entry["key"])
|
||||
continue
|
||||
if chain_prev != null and node.get_index() <= chain_prev.get_index():
|
||||
chain_ok = false
|
||||
chain_detail += " %s(%d)≤%s(%d)" % [chain_prev.name, chain_prev.get_index(), node.name, node.get_index()]
|
||||
chain_prev = node
|
||||
check(chain_ok, "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕" % chain_detail)
|
||||
# ③ 探针挂载假设句柄在座(main.X 一级路径——探针迁移面收口证明)
|
||||
check(main._start_overlay == main._menu_screen.start_overlay and main.camp_overlay == main._camp_screen.camp_overlay and main.level_screen == main._adventure_screen.level_screen and main._battle_root == main._adventure_screen.battle_root and main.board_view == main._adventure_screen.board_view,
|
||||
"V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)")
|
||||
@@ -189,6 +217,368 @@ func _smoke_scene_split() -> void:
|
||||
await process_frame
|
||||
|
||||
|
||||
# 顶层可见内容层(V1a 用——候选=合同模态/全屏态+两屏壳的 boot 内容成员;屏壳本身是容器不当候选:
|
||||
# camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历自顶向下会先命中透明壳〔装配生命批诊断订正①〕)
|
||||
func _topmost_visible_layer(main: Node) -> Node:
|
||||
var candidates: Array[Node] = [main._start_overlay, main._battle_root, main._inter_overlay,
|
||||
main.legend_overlay, main.node_overlay, main.level_screen, main.curio_overlay,
|
||||
main._end_overlay, main.shop_overlay, main.camp_overlay, main.settings_overlay,
|
||||
main.status_panel, main.career_overlay, main.confirm_dialog, main.debug_overlay]
|
||||
var best: Node = null
|
||||
var best_x := -1
|
||||
var best_y := -1
|
||||
for c: Node in candidates:
|
||||
if c == null or not (c is CanvasItem) or not (c as CanvasItem).visible:
|
||||
continue
|
||||
var rx: int = c.get_parent().get_index()
|
||||
var ry: int = c.get_index()
|
||||
if best == null or rx > best_x or (rx == best_x and ry > best_y):
|
||||
best = c
|
||||
best_x = rx
|
||||
best_y = ry
|
||||
return best
|
||||
|
||||
|
||||
# ---- boot 链路时序后置条件(迭代 28 T2——装配修复后启动态六断言;只立行为可判断言,不造假时序)----
|
||||
func _smoke_boot_chain() -> void:
|
||||
print("--- 迭代28 T2 boot 链路时序后置条件(V2:六断言)---")
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
# ① ConfigDB 就绪且表已载
|
||||
var db = main.get_node("/root/ConfigDB")
|
||||
check(db.is_ready and db.hero != null,
|
||||
"V2a ConfigDB 就绪且表已载(is_ready+hero 表在座)〔装配生命批 V2〕")
|
||||
# ② 设置已应用(引擎态=settings_store 现值——真实设置文件照读,一致性判据与文件内容无关)
|
||||
var ss = main.settings_store
|
||||
var master := AudioServer.get_bus_index("Master")
|
||||
check(AudioServer.is_bus_mute(master) == (ss.volume_linear <= 0.001)
|
||||
and is_equal_approx(AudioServer.get_bus_volume_db(master), linear_to_db(maxf(ss.volume_linear, 0.001))),
|
||||
"V2b 设置已应用·音量(Master mute/volume_db=settings_store 现值 linear=%f)〔装配生命批 V2〕" % ss.volume_linear)
|
||||
var ui_kit = load("res://presentation/views/ui_kit.gd")
|
||||
check(ui_kit.motion_scale == (0.5 if ss.reduced_motion else 1.0),
|
||||
"V2b 设置已应用·减动效(UiKit.motion_scale=%s=settings_store.reduced_motion=%s)〔装配生命批 V2〕" % [
|
||||
str(ui_kit.motion_scale), str(ss.reduced_motion)])
|
||||
# ③ 启动态菜单可见
|
||||
check(main._start_overlay.visible,
|
||||
"V2c 启动态 _start_overlay.visible=true(boot 后玩家见菜单)〔装配生命批 V2〕")
|
||||
# ④ 续玩钮可见态==SaveManager 断点在档态
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
# ④-1 boot 路径:boot 时按钮态==真实档断点态(关系判据——与真实档当时内容无关)
|
||||
var real_bp: bool = sm.has_breakpoint()
|
||||
check(main._resume_btn.visible == real_bp,
|
||||
"V2d 续玩钮 boot 态==断点在档态(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" % [
|
||||
str(main._resume_btn.visible), str(real_bp)])
|
||||
# ④-2 隔离新档双向(attach 原地重置——绝不碰真实档 user://save.json;刷新单口与 boot 路径同源)
|
||||
for p in ["user://smoke_boot_chain.json", "user://smoke_boot_chain.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
sm.attach_path("user://smoke_boot_chain.json")
|
||||
main._refresh_resume_button()
|
||||
check(not sm.has_breakpoint() and not main._resume_btn.visible,
|
||||
"V2d 续玩钮·无断点态隐藏(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" % [
|
||||
str(main._resume_btn.visible), str(sm.has_breakpoint())])
|
||||
sm.set_breakpoint({"level_index": 2, "master_seed": 42})
|
||||
main._refresh_resume_button()
|
||||
check(sm.has_breakpoint() and main._resume_btn.visible,
|
||||
"V2d 续玩钮·有断点态可见(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" % [
|
||||
str(main._resume_btn.visible), str(sm.has_breakpoint())])
|
||||
sm.clear_breakpoint()
|
||||
main._refresh_resume_button()
|
||||
check(not main._resume_btn.visible,
|
||||
"V2d 续玩钮·清断点后回隐藏(visible=%s)〔装配生命批 V2〕" % str(main._resume_btn.visible))
|
||||
# ⑤ 闲时会话(_ready 即建、永非 null、在树)
|
||||
check(main.run_scope != null and not main.run_scope.is_adventure and main.run_scope.is_inside_tree(),
|
||||
"V2e run_scope 非空且为闲时会话(is_adventure=false+is_inside_tree)〔装配生命批 V2〕")
|
||||
# ⑥ DS_DEBUG 门控一致性(未设=浮窗隐藏;设了=启动即开——两侧同一判据)
|
||||
var debug_on: bool = OS.get_environment("DS_DEBUG") == "1"
|
||||
check(main.debug_overlay.visible == debug_on,
|
||||
"V2f 调试浮窗态==DS_DEBUG 门控(visible=%s DS_DEBUG=%s)〔装配生命批 V2〕" % [
|
||||
str(main.debug_overlay.visible), str(debug_on)])
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
for p in ["user://smoke_boot_chain.json", "user://smoke_boot_chain.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
|
||||
|
||||
# ---- 生命周期存活性矩阵(迭代 28 T3——九阶段×五引用面;驱动复用 V3 debug seed 开局路径+
|
||||
# _auto_play_round 活战斗模式+ui_struct resume 夹具模式,不另起点击链)----
|
||||
# 阶段映射说明:计划书十阶段中「开局停点」与「地图选路」在本现行流程同为关卡画面停点态
|
||||
#(level_started→choices_presented 同步到达,无可观测中间态)——合并为 V3b 一处断言。
|
||||
func _smoke_lifecycle_matrix() -> void:
|
||||
print("--- 迭代28 T3 生命周期存活性矩阵(V3:九阶段×五引用面)---")
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
var sm = main.get_node("/root/SaveManager")
|
||||
for p in ["user://smoke_lifecycle.json", "user://smoke_lifecycle.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
sm.attach_path("user://smoke_lifecycle.json")
|
||||
main._refresh_resume_button() # 隔离后重同步(boot 时按钮态反映的是真实档——T2 已断 boot 关系,此处按隔离档定基)
|
||||
main.debug_overlay.seed_edit.text = "42"
|
||||
# 阶段①闲时
|
||||
await _stage_faces(main, "V3a 闲时", "null", "null", "null",
|
||||
{"mf_start": true, "mf_inter": false, "mf_rows_empty": true, "start_vis": true, "resume_vis": false})
|
||||
# 阶段②开局停点(地图选路停点态)
|
||||
main._start_adventure_from_menu()
|
||||
for i in range(6):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3b 开局停点", "alive", "alive", "null",
|
||||
{"mf_start": false, "level_vis": true})
|
||||
# 阶段③地图选路→战斗中(真实选路提交 press_candidate)
|
||||
main.level_screen.press_candidate(0)
|
||||
for i in range(10):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3c 战斗中", "alive", "alive", "alive", {"mf_start": false})
|
||||
# 阶段④结算后/终局面(AutoPlayer 活战斗驱动到终局——_auto_play_round 同款 phase/round 守卫)
|
||||
var finished := {"done": false}
|
||||
main.run_scope.adventure.adventure_finished.connect(func(_s: Dictionary) -> void:
|
||||
finished.done = true, CONNECT_ONE_SHOT)
|
||||
var guard := 0
|
||||
while main.run_scope.settlement != null and main.run_scope.settlement.is_battle_live() \
|
||||
and not finished.done and guard < 600:
|
||||
var st = main.run_scope.settlement
|
||||
var ms = st.minesweeper
|
||||
var ae = st.action_economy
|
||||
var start_round: int = st.round_index
|
||||
var round_guard := 400
|
||||
while ae.phase == "Active" and st.round_index == start_round and round_guard > 0 \
|
||||
and main.run_scope.settlement != null:
|
||||
round_guard -= 1
|
||||
var plan: Dictionary = AutoPlayer.choose_dig(ms)
|
||||
if plan.is_empty():
|
||||
break
|
||||
if plan.hero_cell != ms.hero_pos():
|
||||
ms.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||
if ae.phase != "Active" or st.round_index != start_round:
|
||||
break
|
||||
ms.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||
if main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live():
|
||||
break
|
||||
if ae.phase == "Active" and st.round_index == start_round:
|
||||
ms.settle_batch([{"action": "stop"}])
|
||||
guard += 1
|
||||
check(main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live(),
|
||||
"V3d 活战斗驱动完毕(战斗已收场——%d 轮,finished=%s)〔装配生命批 V3〕" % [guard, str(finished.done)])
|
||||
main._flush_row_queue()
|
||||
for i in range(12):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
# 分支甲:战斗胜利停点(结算后)→确认进选路→放弃→终局面
|
||||
if not finished.done:
|
||||
await _stage_faces(main, "V3d 结算后(胜利停点)", "alive", "alive", "both", {"mf_start": false, "end_open": true})
|
||||
main._on_battle_end_continue()
|
||||
for i in range(8):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3d2 选路页(停点后)", "alive", "alive", "both", {"mf_start": false, "level_vis": true})
|
||||
check(main.run_scope.adventure.abandon(), "V3d2 放弃受理(选路停点天然非战斗期)〔装配生命批 V3〕")
|
||||
for i in range(12):
|
||||
if finished.done:
|
||||
break
|
||||
await process_frame
|
||||
main._flush_row_queue()
|
||||
for i in range(8):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
# 阶段⑤终局面(败北/放弃皆达)
|
||||
check(finished.done and main._end_overlay.is_open(),
|
||||
"V3e 终局面到达(adventure_finished+终局面板开)〔装配生命批 V3〕")
|
||||
await _stage_faces(main, "V3e 终局面", "alive", "alive", "null", {"mf_start": false, "end_open": true})
|
||||
# 阶段⑥回菜单收束(旧局消亡+新会话即驻+无真空窗)
|
||||
var old_scope = main.run_scope
|
||||
main._on_battle_end_continue()
|
||||
check(main.run_scope != old_scope and main.run_scope.is_inside_tree(),
|
||||
"V3f 收束换会话无真空窗(新闲时会话即刻在树)〔装配生命批 V3〕")
|
||||
await process_frame
|
||||
check(not is_instance_valid(old_scope),
|
||||
"V3f 收束后旧局经 queue_free 消亡(is_instance_valid=false)〔装配生命批 V3〕")
|
||||
await _stage_faces(main, "V3f 回菜单收束", "null", "null", "null",
|
||||
{"mf_start": true, "start_vis": true, "end_open": false})
|
||||
# 阶段⑦各模态开合后(菜单域四模态真实路径开合——会话面零扰动)
|
||||
for modal_name in ["settings", "career", "camp", "debug"]:
|
||||
if modal_name == "settings":
|
||||
main._open_settings()
|
||||
elif modal_name == "career":
|
||||
main._open_career()
|
||||
elif modal_name == "camp":
|
||||
var campbtn: Button = main._menu_screen.campbtn
|
||||
if campbtn == null:
|
||||
check(false, "V3g 营地入口缺失(驱动中断)〔装配生命批 V3〕")
|
||||
continue
|
||||
campbtn.pressed.emit()
|
||||
else:
|
||||
main._show_debug_overlay()
|
||||
for i in range(4):
|
||||
await process_frame
|
||||
var open_extra: Dictionary = {"mf_start": false}
|
||||
if modal_name == "debug":
|
||||
open_extra = {"mf_start": true} # 调试浮窗=浮于菜单之上的 Dialog(不隐藏开始层——迭代 18 卡⑦形态)
|
||||
await _stage_faces(main, "V3g 模态开·%s" % modal_name, "null", "null", "null", open_extra)
|
||||
if modal_name == "settings":
|
||||
main._on_settings_closed()
|
||||
elif modal_name == "career":
|
||||
main._on_career_closed()
|
||||
elif modal_name == "camp":
|
||||
main._on_camp_closed()
|
||||
else:
|
||||
main._close_debug_overlay()
|
||||
for i in range(4):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3g 模态合·%s" % modal_name, "null", "null", "null",
|
||||
{"mf_start": true, "start_vis": true})
|
||||
# 阶段⑧两连局(第二局全新会话——旧局对象不复用、脏态零残留)
|
||||
var first_adv = main.run_scope.adventure
|
||||
main.debug_overlay.seed_edit.text = "43"
|
||||
main._start_adventure_from_menu()
|
||||
for i in range(6):
|
||||
await process_frame
|
||||
check(main.run_scope.adventure != null and main.run_scope.adventure != first_adv,
|
||||
"V3h 两连局第二会话全新对象(与第一局不复用)〔装配生命批 V3〕")
|
||||
await _stage_faces(main, "V3h 两连局·开局停点", "alive", "alive", "null", {"mf_start": false, "level_vis": true})
|
||||
check(main.run_scope.adventure.abandon(), "V3h 放弃受理(第二局)〔装配生命批 V3〕")
|
||||
for i in range(12):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
main._flush_row_queue()
|
||||
for i in range(8):
|
||||
if main._end_overlay.is_open():
|
||||
break
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3h 两连局·终局面", "alive", "alive", "null", {"mf_start": false, "end_open": true})
|
||||
main._on_battle_end_continue()
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3h 两连局·收束", "null", "null", "null", {"mf_start": true, "start_vis": true})
|
||||
# 阶段⑨续玩断点(fresh 隔离档+最小合法断点夹具——ui_struct resume 夹具同构;换会话无真空窗)
|
||||
for p in ["user://smoke_lifecycle_resume.json", "user://smoke_lifecycle_resume.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
sm.attach_path("user://smoke_lifecycle_resume.json")
|
||||
sm.set_breakpoint({
|
||||
"master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 0,
|
||||
"level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [],
|
||||
"keystone_drops": [], "bonus_taken": {},
|
||||
"bonus_pending": {"candidates": [{"kind": "heal", "tier": "common"}], "payload": {"gp_dropped": 0}},
|
||||
})
|
||||
main._refresh_resume_button()
|
||||
check(main._resume_btn.visible, "V3i 续玩钮·断点在档可见〔装配生命批 V3〕")
|
||||
var idle_scope = main.run_scope
|
||||
main._resume_from_menu()
|
||||
check(main.run_scope != idle_scope and main.run_scope.is_inside_tree(),
|
||||
"V3i 续玩换会话无真空窗(新冒险会话即刻在树)〔装配生命批 V3〕")
|
||||
for i in range(10):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3i 续玩断点", "alive", "both", "both", {"mf_start": false})
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
for p in ["user://smoke_lifecycle.json", "user://smoke_lifecycle.json.tmp",
|
||||
"user://smoke_lifecycle_resume.json", "user://smoke_lifecycle_resume.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
|
||||
|
||||
# 引用面快照(五承载物的会话相关面+恒活面——逐个调用现值回调,与 run_scope 成员并采)
|
||||
func _face_snapshot(main: Node) -> Dictionary:
|
||||
var tu = main._treasure_ui
|
||||
var bo = main._bonus
|
||||
var cp = main._camp_pres
|
||||
var mf = main._modal_flow
|
||||
var rp = main._row_pacer
|
||||
return {
|
||||
"treasure_settlement": tu._settlement_get.call(),
|
||||
"treasure_runner": tu._runner_get.call(),
|
||||
"bonus_meta": bo._meta_get.call(),
|
||||
"bonus_save": bo._save_get.call(),
|
||||
"bonus_adventure": bo._adventure_get.call(),
|
||||
"bonus_runner": bo._runner_get.call(),
|
||||
"bonus_mode": bo._adventure_mode_get.call(),
|
||||
"camp_meta": cp._meta_get.call(),
|
||||
"camp_save": cp._save_get.call(),
|
||||
"camp_runner": cp._runner_get.call(),
|
||||
"mf_start": mf.start_visible_get.call(),
|
||||
"mf_inter": mf.inter_visible_get.call(),
|
||||
"mf_settlement": mf.settlement_get.call(),
|
||||
"mf_rows_empty": mf.rows_empty_get.call(),
|
||||
"rp_log": rp.log_view, "rp_stage": rp.stage, "rp_board_zone": rp.board_zone,
|
||||
"rp_fx": rp.settlement_fx, "rp_board": rp.board_view,
|
||||
"rp_refresh": rp.refresh_request, "rp_pump": rp.pump_ended,
|
||||
"rs_adventure": main.run_scope.adventure, "rs_runner": main.run_scope.runner,
|
||||
"rs_settlement": main.run_scope.settlement, "rs_is_adventure": main.run_scope.is_adventure,
|
||||
}
|
||||
|
||||
|
||||
# 阶段面断言:adv/run/set=alive|null|both(会话三面存活态);恒活面每阶段必查;
|
||||
# 现值回调与 run_scope 成员必须同源(回调链无悬挂铁证);extra=阶段附加判据。
|
||||
func _stage_faces(main: Node, tag: String, adv: String, run: String, set: String, extra: Dictionary = {}) -> void:
|
||||
var snap := _face_snapshot(main)
|
||||
var bad := ""
|
||||
for k in ["bonus_meta", "bonus_save", "camp_meta", "camp_save", "rp_log", "rp_stage", "rp_board_zone", "rp_fx", "rp_board"]:
|
||||
if not is_instance_valid(snap[k]):
|
||||
bad += " %s=失效" % k
|
||||
for k in ["rp_refresh", "rp_pump"]:
|
||||
var cb: Variant = snap[k]
|
||||
if not (cb is Callable) or not (cb as Callable).is_valid():
|
||||
bad += " %s=非有效Callable" % k
|
||||
if not main.run_scope.is_inside_tree():
|
||||
bad += " run_scope=不在树"
|
||||
bad += _face_liveness("adventure", snap["rs_adventure"], adv)
|
||||
bad += _face_liveness("runner", snap["rs_runner"], run)
|
||||
bad += _face_liveness("settlement", snap["rs_settlement"], set)
|
||||
if snap["treasure_settlement"] != snap["rs_settlement"]:
|
||||
bad += " treasure._settlement_get≠run_scope.settlement(回调悬挂)"
|
||||
if snap["treasure_runner"] != snap["rs_runner"]:
|
||||
bad += " treasure._runner_get≠run_scope.runner(回调悬挂)"
|
||||
if snap["bonus_adventure"] != snap["rs_adventure"]:
|
||||
bad += " bonus._adventure_get≠run_scope.adventure(回调悬挂)"
|
||||
if snap["bonus_runner"] != snap["rs_runner"]:
|
||||
bad += " bonus._runner_get≠run_scope.runner(回调悬挂)"
|
||||
if snap["camp_runner"] != snap["rs_runner"]:
|
||||
bad += " camp._runner_get≠run_scope.runner(回调悬挂)"
|
||||
if snap["mf_settlement"] != snap["rs_settlement"]:
|
||||
bad += " modal_flow.settlement_get≠run_scope.settlement(回调悬挂)"
|
||||
if bool(snap["bonus_mode"]) != bool(snap["rs_is_adventure"]):
|
||||
bad += " bonus._adventure_mode_get≠run_scope.is_adventure(回调悬挂)"
|
||||
for k in extra:
|
||||
match String(k):
|
||||
"mf_start":
|
||||
if bool(snap["mf_start"]) != bool(extra[k]):
|
||||
bad += " modal_flow.start_visible_get 应=%s" % str(extra[k])
|
||||
"mf_inter":
|
||||
if bool(snap["mf_inter"]) != bool(extra[k]):
|
||||
bad += " modal_flow.inter_visible_get 应=%s" % str(extra[k])
|
||||
"mf_rows_empty":
|
||||
if bool(snap["mf_rows_empty"]) != bool(extra[k]):
|
||||
bad += " modal_flow.rows_empty_get 应=%s" % str(extra[k])
|
||||
"level_vis":
|
||||
if main.level_screen.visible != bool(extra[k]):
|
||||
bad += " level_screen.visible 应=%s" % str(extra[k])
|
||||
"end_open":
|
||||
if main._end_overlay.is_open() != bool(extra[k]):
|
||||
bad += " _end_overlay.is_open 应=%s" % str(extra[k])
|
||||
"resume_vis":
|
||||
if main._resume_btn.visible != bool(extra[k]):
|
||||
bad += " _resume_btn.visible 应=%s" % str(extra[k])
|
||||
"start_vis":
|
||||
if main._start_overlay.visible != bool(extra[k]):
|
||||
bad += " _start_overlay.visible 应=%s" % str(extra[k])
|
||||
check(bad.is_empty(), "%s(引用面违例:%s)〔装配生命批 V3〕" % [tag, bad])
|
||||
|
||||
|
||||
# 单面存活态判据:want=null=必须缺位/alive=必须有效非空/both=两可但非空必有效
|
||||
func _face_liveness(name: String, v, want: String) -> String:
|
||||
if want == "null":
|
||||
return "" if v == null else " %s 应缺位(实测非空)" % name
|
||||
if v != null and not is_instance_valid(v):
|
||||
return " %s 悬挂(失效对象)" % name
|
||||
return ""
|
||||
|
||||
|
||||
# ---- 会话物化断言(迭代 27 T1 V3——两连局复位/无脏状态残留;原手工复位清单消亡的行为级证明)----
|
||||
func _smoke_run_lifecycle() -> void:
|
||||
print("--- 迭代27 T1 会话物化(V3:两连局复位+脏状态不残留)---")
|
||||
|
||||
@@ -30,6 +30,7 @@ func _ready() -> void:
|
||||
_settle()
|
||||
await _audit("start", [main._start_overlay])
|
||||
_start_columns_contract(main)
|
||||
_modal_registry_contract(main)
|
||||
|
||||
main._open_settings()
|
||||
_settle()
|
||||
@@ -48,8 +49,8 @@ func _ready() -> void:
|
||||
problems.append("[camp] 营地入口按钮缺失(驱动中断)")
|
||||
|
||||
# ---- 生涯页(迭代 18 卡④):入口点击链(合成点击真路径=pressed.emit→组合根 _open_career)→
|
||||
# 结构断言族→只读巡检(契约 3)+零动效契约+登记序/树序契约(P3 甲营地同档)+零跳转契约
|
||||
#(提纲 P4 甲)→组合根真实路径关闭。----
|
||||
# 结构断言族→只读巡检(契约 3)+零动效契约+零跳转契约(提纲 P4 甲)→组合根真实路径关闭。
|
||||
#(登记序/树序契约已推广为全模态 _modal_registry_contract——boot 段统一跑,生涯专属件同批退役)----
|
||||
var careerbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "生涯", "prefix")
|
||||
if careerbtn != null:
|
||||
careerbtn.pressed.emit()
|
||||
@@ -59,7 +60,6 @@ func _ready() -> void:
|
||||
await _audit("career", [main.career_overlay])
|
||||
_career_readonly_contract(main)
|
||||
_career_zero_motion_contract(main)
|
||||
_career_registry_contract(main)
|
||||
_career_nojump_contract(main)
|
||||
main._on_career_closed()
|
||||
_settle()
|
||||
@@ -325,21 +325,56 @@ func _career_zero_motion_contract(main: Node) -> void:
|
||||
problems.append("[career][motion] 视图源码含「%s」(零动效直开直关破)" % token)
|
||||
|
||||
|
||||
# ⑫career 登记/树序契约(P3 甲——登记序=营地同档:状态面板之后、确认弹窗之前;树序同步=addChild
|
||||
# 同位。登记序=树序=z 序纪律〔缺陷⑤〕——两序任一失守即红)
|
||||
func _career_registry_contract(main: Node) -> void:
|
||||
# ⑫模态登记/树序全量合同(装配生命批 V1——原 career 专属登记序契约推广到全部模态+屏壳;
|
||||
# 判据=main.LAYER_CONTRACT 单一事实源:登记册序==合同模态序、直接子层树序==合同序逐对相等。
|
||||
# 两特例按合同数据表达:level_screen 宿住冒险屏壳(树序随宿主=全模态之下,遮挡语义归模态栈
|
||||
# screen_state 登记)、camp_overlay 成员与 camp_screen 屏壳同槽(屏壳树序代言成员登记位)。
|
||||
# 旧 _career_registry_contract 的断言(career=状态面板后/确认弹窗前、树序同步)被本全量合同覆盖,同批退役)
|
||||
func _modal_registry_contract(main: Node) -> void:
|
||||
var list: Array = main._modals._list
|
||||
var ci: int = list.find(main.career_overlay)
|
||||
var pi: int = list.find(main.status_panel)
|
||||
var fi: int = list.find(main.confirm_dialog)
|
||||
if ci < 0 or pi < 0 or fi < 0:
|
||||
problems.append("[career][registry] 生涯页未入登记册(或状态面板/确认弹窗缺登记)")
|
||||
return
|
||||
if ci != pi + 1 or ci > fi:
|
||||
problems.append("[career][registry] 登记序违约(应=状态面板后、确认弹窗前——P3 甲营地同档;status=%d career=%d confirm=%d)" % [pi, ci, fi])
|
||||
var ti: int = main.career_overlay.get_index()
|
||||
if ti <= main.status_panel.get_index() or ti > main.confirm_dialog.get_index():
|
||||
problems.append("[career][registry] 树序与登记序不同步(addChild 位应=状态面板后、确认弹窗前;tree_index=%d)" % ti)
|
||||
# ① 登记册序==合同模态序(逐位点名——合同是登记序唯一事实源)
|
||||
var contract_modals: Array = []
|
||||
for entry: Dictionary in main.LAYER_CONTRACT:
|
||||
if String(entry["kind"]) != "modal":
|
||||
continue
|
||||
var n: Node = main._layer_node(String(entry["key"]))
|
||||
if n == null:
|
||||
problems.append("[registry] 合同层缺失: %s〔装配生命批 V1〕" % String(entry["key"]))
|
||||
continue
|
||||
contract_modals.append(n)
|
||||
if list.size() != contract_modals.size():
|
||||
problems.append("[registry] 登记册层数=%d 应=%d(合同模态数)〔装配生命批 V1〕" % [list.size(), contract_modals.size()])
|
||||
for i in contract_modals.size():
|
||||
if i >= list.size():
|
||||
break
|
||||
if list[i] != contract_modals[i]:
|
||||
problems.append("[registry] 登记序第 %d 位与合同不一致(登记=%s 合同=%s)〔装配生命批 V1〕" % [
|
||||
i, list[i].name, contract_modals[i].name])
|
||||
# ② 直接子层树序==合同序逐对相等(屏壳/模态/全屏态/非视觉节点全链;宿住层随宿主不参与)
|
||||
var prev: Node = null
|
||||
for entry: Dictionary in main.LAYER_CONTRACT:
|
||||
if entry.has("host"):
|
||||
continue
|
||||
var node: Node = main._layer_node(String(entry["shell"]) if entry.has("shell") else String(entry["key"]))
|
||||
if node == null:
|
||||
problems.append("[registry] 合同层缺失: %s〔装配生命批 V1〕" % String(entry["key"]))
|
||||
continue
|
||||
if prev != null and node.get_index() <= prev.get_index():
|
||||
problems.append("[registry] 树序违约(合同邻接对 %s→%s:%d≤%d)〔装配生命批 V1〕" % [
|
||||
prev.name, node.name, node.get_index(), prev.get_index()])
|
||||
prev = node
|
||||
# ③ 宿住层特例:level_screen 宿主=冒险屏壳且在全直接子模态之下(遮挡语义归模态栈 screen_state)
|
||||
var lv: Node = main.level_screen
|
||||
if lv.get_parent() != main._adventure_screen:
|
||||
problems.append("[registry] level_screen 宿主应=冒险屏壳(实测 %s)〔装配生命批 V1〕" % lv.get_parent().name)
|
||||
elif lv.get_parent().get_index() >= main.legend_overlay.get_index():
|
||||
problems.append("[registry] 冒险屏壳应在全模态之下(宿主 idx=%d legend idx=%d)〔装配生命批 V1〕" % [
|
||||
lv.get_parent().get_index(), main.legend_overlay.get_index()])
|
||||
# ④ 营地同槽特例:camp_screen 屏壳树序夹商店层与设置层之间(成员 camp_overlay 登记位代言)
|
||||
var cs: Node = main._camp_screen
|
||||
if cs.get_index() <= main.shop_overlay.get_index() or cs.get_index() >= main.settings_overlay.get_index():
|
||||
problems.append("[registry] camp_screen 屏壳树序应夹商店与设置层之间(idx=%d shop=%d settings=%d)〔装配生命批 V1〕" % [
|
||||
cs.get_index(), main.shop_overlay.get_index(), main.settings_overlay.get_index()])
|
||||
|
||||
|
||||
# ⑬career 零跳转契约(提纲 P4 甲——页内零「前往他处」入口:除「返回」外任何导航钮=红)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
+3
-3
@@ -24,8 +24,8 @@
|
||||
## 当前位置(2026-10-15 刷新)
|
||||
|
||||
- **里程碑语义(2026-10-09 立规换代,详见下方里程碑表的换代注)**:现行里程碑 = **M3「Alpha 版本的完整实现」**——用户未明令宣布完成前停留 M3,迭代只排 M3 需求;M4=Beta 版本的完整实现,一律不排期。下文里程碑表的旧编号(M-3/M-4)保留作历史序列,语义以本节为准。
|
||||
- **迭代进度**:迭代 19 五卡全部已入库(2026-10-08);**迭代 20=专长实装批 ✅ 已入库**(2026-10-11,出口闸全绿;残留人闸=实机目检五件+V20/V21/V22 人工核对半,07 在册);**迭代 21=天赋树实装批 ✅ 已入库(2026-10-12 U0 开工单会话交付,T0-T7 全绿;残留人闸=实机目检四柜台)**;**迭代 22=秘宝体系实现重构落地批 ✅ 已入库(2026-10-12 单会话交付,T1-T7 全绿;残留人闸=实机目检秘宝解锁-装配流程)**;**迭代 23=锻造工坊实装批 ✅ 已入库(2026-10-12 单会话交付,T0-T7 全绿;残留人闸=实机目检工作间形态与购买交互)**;**迭代 24=敌人表内容修批(三关)✅ 已入库(2026-10-13 /goal+iter U0 开工单会话交付——N1 (1,2) 降档 hp22/ac2·N2 毒吐盾击两行六池·N3 敌方 attack_type 轴全填 normal+敌方结算克制消费点;冒烟 949/0+run_all 25 套+重闸全绿+MC 同种子配对 40 局×both 位移在带;实现报告=design/qa/iter24-实现报告-2026-10-13.md;残留人闸=实机目检三观测〔07 ㉓〕;代拍板追认单+GDD 反哺点五条待用户;**本迭代位仍预留后续追加卡——用户将把其他需求陆续编入本迭代**);;**迭代 25=UI 全局主题批 ✅ 已入库**(2026-10-13 用户令编入,/goal+iter U0 单会话交付:全局主题资源 main_theme.tres 挂载+两处局部做法退役+冒烟 V1-V6 断言 38 条,冒烟 987/0+重闸全绿;实现报告=design/qa/iter25-实现报告-2026-10-13.md;残留人闸=实机目检全屏巡检〔07 ㉔〕);**迭代 26=UI 交互与观感批已编入待开工**(2026-10-14 用户令;需求卡片 v1.2 零待裁点;开工门=U0——用户新会话 /iter 26 发令;本迭代位预留后续追加卡,同迭代 24 先例);**迭代 27=代码架构技术债修复批 ✅ 已入库**(2026-10-14 /goal+iter U0 开工单会话交付:三屏场景化+会话物化+五段 Segment+RunLedger+ModalFlow+类型闸+卫生件五波全落;逐位一致对拍三变体五跑全同;实现报告=design/qa/iter27-实现报告-2026-10-14.md;残留人闸=实机目检五件〔07 ㉖〕;代拍板追认单十五项+GDD 反哺点两条待用户);**迭代 28=启动装配与生命周期验收补全批已编入**(2026-10-15 用户令;需求卡片=design/research/需求卡片-启动装配与生命周期验收补全批-2026-10-15.md v1.0 零待裁点、用户预批连做全批;六任务=装配语义合同+顺序修复+V2 断言重写/boot 链路时序后置条件/生命周期存活性矩阵/渲染面内容断言/变异验证/规范落文;冒烟基线 1081/0);**迭代 29+**=M4 数值批(Beta 位·未排期——用户令 2026-10-07:后续新迭代一律不先做 M4;迭代 24/25/26/27/28 占位后本批顺延)。
|
||||
- **近期主战场**:迭代 20/21/22/23/24/25/26 残留人闸(实机目检六批,07 在册);**下一前沿=迭代 28 启动装配与生命周期验收补全批**(2026-10-15 用户令编入并预批连做全批;M4 数值批殿后顺延 29+,各批 provisional 终值归其统一标定;迭代 20~27 残留人闸七批 07 在册)。
|
||||
- **迭代进度**:迭代 19 五卡全部已入库(2026-10-08);**迭代 20=专长实装批 ✅ 已入库**(2026-10-11,出口闸全绿;残留人闸=实机目检五件+V20/V21/V22 人工核对半,07 在册);**迭代 21=天赋树实装批 ✅ 已入库(2026-10-12 U0 开工单会话交付,T0-T7 全绿;残留人闸=实机目检四柜台)**;**迭代 22=秘宝体系实现重构落地批 ✅ 已入库(2026-10-12 单会话交付,T1-T7 全绿;残留人闸=实机目检秘宝解锁-装配流程)**;**迭代 23=锻造工坊实装批 ✅ 已入库(2026-10-12 单会话交付,T0-T7 全绿;残留人闸=实机目检工作间形态与购买交互)**;**迭代 24=敌人表内容修批(三关)✅ 已入库(2026-10-13 /goal+iter U0 开工单会话交付——N1 (1,2) 降档 hp22/ac2·N2 毒吐盾击两行六池·N3 敌方 attack_type 轴全填 normal+敌方结算克制消费点;冒烟 949/0+run_all 25 套+重闸全绿+MC 同种子配对 40 局×both 位移在带;实现报告=design/qa/iter24-实现报告-2026-10-13.md;残留人闸=实机目检三观测〔07 ㉓〕;代拍板追认单+GDD 反哺点五条待用户;**本迭代位仍预留后续追加卡——用户将把其他需求陆续编入本迭代**);;**迭代 25=UI 全局主题批 ✅ 已入库**(2026-10-13 用户令编入,/goal+iter U0 单会话交付:全局主题资源 main_theme.tres 挂载+两处局部做法退役+冒烟 V1-V6 断言 38 条,冒烟 987/0+重闸全绿;实现报告=design/qa/iter25-实现报告-2026-10-13.md;残留人闸=实机目检全屏巡检〔07 ㉔〕);**迭代 26=UI 交互与观感批已编入待开工**(2026-10-14 用户令;需求卡片 v1.2 零待裁点;开工门=U0——用户新会话 /iter 26 发令;本迭代位预留后续追加卡,同迭代 24 先例);**迭代 27=代码架构技术债修复批 ✅ 已入库**(2026-10-14 /goal+iter U0 开工单会话交付:三屏场景化+会话物化+五段 Segment+RunLedger+ModalFlow+类型闸+卫生件五波全落;逐位一致对拍三变体五跑全同;实现报告=design/qa/iter27-实现报告-2026-10-14.md;残留人闸=实机目检五件〔07 ㉖〕;代拍板追认单十五项+GDD 反哺点两条待用户);**迭代 28=启动装配与生命周期验收补全批 ✅ 已入库**(2026-10-15 用户令编入并预批连做全批,单会话交付:T1 装配语义合同 `LAYER_CONTRACT` 单点化+冒险屏先挂/菜单屏后挂的顺序修复+V1a-d 断言重写/T2 `_smoke_boot_chain` boot 链路十断言/T3 `_smoke_lifecycle_matrix` 九阶段×五引用面/T4 渲染面三件(shot_probe 两自检+shot_checker 墨色判据+启动态菜单 golden)/T5 变异验证六次注入全见红并精确还原/T6 规范三处落文;冒烟 **1086/0→1125/0**(+39=新增 42−退役裸下标断言 3);实现报告=design/qa/iter28-实现报告-2026-10-15.md;残留人闸=实机目检启动态菜单〔07 ㉗〕;代拍板追认单+GDD 反哺点待用户裁决);**迭代 29+**=M4 数值批(Beta 位·未排期——用户令 2026-10-07:后续新迭代一律不先做 M4;迭代 24/25/26/27/28 占位后本批顺延)。
|
||||
- **近期主战场**:迭代 20/21/22/23/24/25/26 残留人闸(实机目检六批,07 在册);**下一前沿=迭代 29+ M4 数值批**(Beta 位·未排期——用户令 2026-10-07:后续新迭代一律不先做 M4;迭代 28 已于 2026-10-15 入库收口,各批 provisional 终值归 M4 统一标定;迭代 20~28 残留人闸八批 07 在册)。
|
||||
- **实时状态**:以根目录 [active.md](active.md) 顶部「当前状态快照」节为准(`stage.txt` 已定性为旧阶段开关,仅存档)。
|
||||
|
||||
## 主线路线(里程碑 × 迭代序列)
|
||||
@@ -84,7 +84,7 @@
|
||||
| 迭代 25 | **①UI 全局主题批(引擎原生控件接管皮肤)(2026-10-13 用户令编入;UI 美化三层诊断 · 第一层第一条)**:**✅ 已全批入库(2026-10-13 /goal+iter U0 开工单会话交付,worktree g-plan2-iter25-theme 分支 iter25-theme-20261013)**——**T0 主题资源**:新建 `game/assets/theme/main_theme.tres` 挂 `project.godot` `gui/theme/custom`(15 类型全覆盖:V/HScrollBar·ScrollContainer·H/VSlider·TooltipPanel/Label·LineEdit·SpinBox·H/VSeparator + 预盖 CheckBox/CheckButton/OptionButton/PopupMenu 弹层;`default_font` 显式指向现役同字体文件=V6 中文字形防御;位图由 `tools/gen_theme_icons.py` 一次性生成落 `game/assets/art/ui/gen/`,色值溯源表落 `game/assets/theme/README.md`)· **T1 换代退役**:`ui_kit.gd` 的 `tooltip_theme()`/`_tip_theme` 与 `styled_slider()` 样式段、`ui_assembler.gd` 挂载行同批删除(禁新旧并存),settings_overlay 几何内联+debug_overlay `_slider_row` 双轨消隐· **T2 断言与文档同步闸**:冒烟新增 V1-V6 主题词条断言 38 条。**顺手销号**=06 第三层「UI 元素细节修」之①(音量滑轨两段对比度重配=已填充段 BG_ROOT #3A2E22 对暖石 #B8A582 实测 5.49:1)。**契约遵守**:🔴 Button 类型词条零接触(冒烟断言在闸)· 🔴 focus 族词条零定义(全主题扫描断言)· 🔴 零新增美术素材(位图=脚本生成纯色/几何)· 🔴 全局纹理过滤未动。**引擎事实订正(呈报追认)**:Godot 4.7.2 源码+默认主题链双查实证——①CheckButton 图标词条名与 CheckBox 同套 `checked`/`unchecked`(卡片 v1.2 注③「CheckButton 用 on/off」不成立,按引擎事实落位)②Slider 无 `grabber_pressed` 图标槽(冻结表「柄·按」值 GOLD_DEEP 留档未落位)③ScrollBar 柄是 StyleBox 非 Icon(无需位图)④4.7 导入参数已无逐贴图 filter 标志(位图原生尺寸消费无滤波伪影)。**闸**:冒烟 987/0(基线 949+本批 38 断言)+run_all 25 套+probe click/full+render_gate golden 0.00%+doc 三闸/熵增对基线零新增;MC 零跑(零数值零行为=纯呈现)。**收口去向**:06「UI 全局主题资源缺失」条移除+「UI 元素细节修」①销号;08 升 v1.3(三·界面主题行→在役、五·表现细节修行注①销号);07 ㉔ 全屏巡检登记;实现报告=design/qa/iter25-实现报告-2026-10-13.md。**待用户**:实机目检全屏巡检+专项观察点二(07 ㉔,登记不阻塞)+代拍板追认单(位图不入 git 政策口径/CheckButton 词条名订正等)+GDD 反哺点(组件规格是否立条款入 art-bible §7 等)。↓ 原任务登记:三件事合一——**T0** 新建 `game/assets/theme/main_theme.tres` 并挂 `project.godot` 的 `gui/theme/custom`,覆盖滚动条 / 滑轨 / 悬停气泡 / 输入件 / 分隔线等引擎原生控件,并预盖 CheckBox / CheckButton / OptionButton 及其弹层 PopupMenu(须在新主题内显式设 `default_font`=现役同一字体文件;新增小位图落 `game/assets/art/ui/gen/`,色值溯源表落 `game/assets/theme/README.md`)· **T1** 退役 `ui_kit.gd` 的 `tooltip_theme()` 与 `styled_slider()` 两处局部做法、消 `debug_overlay._slider_row` 双轨(🔴 旧实现同批删除)· **T2** 断言与文档同步闸。契约要点:🔴 Button 类型词条零接触 · 🔴 focus 族词条零定义 · 🔴 不新增美术素材、不改美术圣经 §7 · 🔴 全局纹理过滤不动 | ✅ 已入库(出口闸全绿+实机目检归用户 07 ㉔ 在册) | T0-T2≈0.5–1d(实际单会话) |
|
||||
| 迭代 26 | **①UI 交互与观感批(2026-10-14 用户令编入;UI 美化三层诊断 · 第一层第二/三条+第三层四件;本迭代位预留后续追加卡,同迭代 24 先例)**:需求卡片=`design/research/需求卡片-UI交互与观感批-2026-10-13.md`(**v1.2**——grilling 两轮八裁全落、零待裁点、卡片成立)。六项:**T1 按钮五态矩阵**(悬停=乙案 modulate 提亮〔与 primary_button 现行 `CANDLE.lightened(0.06)` 同纪律〕/禁用=本族底+α0.45 幽灵化/焦点=INK 2px 内缩 1px/危险钮补本族 pressed;走逐实例 override,迭代 25 两条主题红线断言维持不换代)· **T2 全局纹理过滤**(`project.godot` `default_texture_filter=0`+vignette/gradient_base/beams 显式 LINEAR 白名单+翻转面全枚举)· **T3 <4.5:1 换墨**(站桩顺位/护甲徽标换同族亮档+RAP 行 STONE_DEEP,机检断言锚定)· **T4 光柱连续 alpha 渐变**(0→0.05→0 横向+纵向衰减)· **T5 元素细节余②③**(BCard 图标槽明度差降档/商店卡片区垂直居中/营地四栏等高)· **T5b 诊断转储钮移调试浮窗**(`dump_diagnosis` 功能保留)。🔴 契约 5 条(主题层零新增词条/零新美术素材/棋盘零触碰/对比度 ≥4.5:1/旧实现同批清理);验证闸 V1-V7;代拍板白名单八项;反哺点四条;**渲染闸 golden 面处置**=T6 第 4 步(徽标换墨落 board golden 裁剪区:闸红→核热区限预期面→显式重制基线+呈报→复闸,超预期=L3)。**范围订正**(grilling 实查,iter25 滑轨条同款先例):②垫块仅三选一 BCard(商店卡实为纯文字行卡)/③仅商店+营地(三选一已居中)/危险钮「无按下态」订正为「在但皮肤错配」。估时 T0-T6≈2–2.5d | **✅ 已全批入库(2026-10-14 /goal+iter U0 开工单会话交付,worktree g-plan2-iter26-ui 分支 iter26-ui-20261014 七提交)**——T0 基线+枚举(含卡片 §2.2 枚举漏项勘误)/T1 按钮五态(悬停 modulate 提亮/禁用 α0.45 幽灵化/焦点 INK 2px 内缩 1px/危险钮本族按压态)/T2 全局 Nearest+平滑渐变件 LINEAR 白名单/T3 三处换墨(GOLD_BRIGHT/HERO_BLUE_BRIGHT/STONE_DEEP,6.47/5.88/5.98:1)/T4 光柱连续 alpha/T5 图标槽柔和档+商店居中+营地四栏等高/T5b 诊断钮迁调试浮窗;冒烟 987→1055/0(+68 断言)+run_all 25 套+probe click/full+render_gate golden 0.00%(登记即结未重制)+doc 三闸/熵增对基线零新增;MC 零跑(零数值零行为=纯呈现);实现报告=design/qa/iter26-实现报告-2026-10-14.md;残留人闸=实机目检五项(07 ㉕);待用户=代拍板追认单十三项+GDD 反哺点四条 | T0-T6≈2–2.5d(实际单会话) |
|
||||
| 迭代 27 | **①代码架构技术债修复批(2026-10-14 用户令编入;来源=[代码架构与实现质量审计报告-2026-10-14](design/research/代码架构与实现质量审计报告-2026-10-14.md),双席审计基线 97de02c,已逐条复核)**:需求卡片=`design/research/需求卡片-代码架构技术债修复批-2026-10-14.md`(**v1.2——三点全裁零待裁〔2026-10-14 用户三裁全按推荐案:①第三个 autoload/②单类持四账/③审计五段〕,卡片成立**)。五波:· **波一 场景拆分+局生命周期物化(≈4-5d)**〔P0-1/P0-2:屏容器 .tscn 切分=开始菜单根场景+营地场景+冒险场景〔内含 level_map/battle 两容器〕,视图代码原样搬、**40+ views 不转 .tscn**;SaveManager/MetaProgression 升格跨场景持有者;「一局」物化为可整体 `queue_free()` 的节点,`main.gd:647-657`/`:555-559` 手工清单消亡;13 个 smoke 探针挂载假设同批迁移〕· **波二 `_settle` 拆段(≈1.5d)**〔P1-1:五段 Segment 类 grant/aura/combat/decay/verdict,24 处 kind 分派随段归位,姊妹病灶 `damage_controller.gd:272` 148 行同批拆〕· **波三 账本收口(≈1.5d)**〔P1-4 共享四账 Ledger 类化+P1-2 刷新双账删 main 副本+P1-3 summary 增 `gp_final` 键、删 `gp_shadow_ledger.gd`〕· **波四 组合根可测化抽取+模态协议内聚(≈1.5-2d)**〔P1-5/P2-7;**须在波一落地后重估抽取面再动**〕· **波五 类型闸收尾+卫生件包(≈3d)**〔P1-6 拉闸+存量 73 处收口,**须在其他重构全部落地后**;P2 七组顺手带〕。🔴 契约 6 条(**零行为变更·重构前后同种子完整局输出逐位一致**/只拆不重写/views 不转 .tscn/探针契约零破坏/信号纪律不变/执行顺序锁+渲染零漂移);**核心验收=逐位一致对拍**(RngService 单盐流在役);验证闸 V1-V9;代拍板白名单九项;反哺点两条(architecture+ADR 系列/code-conventions)。 | **✅ 已全批入库(2026-10-14 /goal+iter U0 开工单会话交付,worktree g-plan2-iter27-techdebt 分支 iter27-techdebt-20261014 七提交)**——波一 三屏场景化+run_scope 会话物化+双 autoload(7a0d57d)/波二 _settle 五段 Segment+英雄攻击拆步(26301ba)/波三 RunLedger 单类四账+刷新双账删副本+gp_final 删影子账(6fd7d78)/波四 ModalFlow+互斥域 tags+双 context(8b9e556)/波五a 类型闸 368 参数化+审计器+warning 配置(b605022)/波五b 卫生件 13 死代码+FeatsEngine 退役+Callable 换装+状态机 enum 化+ConfigDB 归根+resolver 解耦(T6 笔)/T7 文档同步(本笔)。**闸**:逐位一致对拍三变体五跑全同(V1 核心验收)+冒烟 1055→1081/0(+V2 屏壳 11+V3 两连局 11+V4 段类 16+V6 模态 19+V5 快照 3+V7 2 断言)+run_all 25→27 套+probe click+render golden 0.00%+static_scan S1/S2/S4/S9 全绿(历史 cd 静默空转顺修);MC 对拍=parity_anchor 三变体×40 局×RUNSUM+JSONL 双面(工具入库 tools/parity_anchor.py);实现报告=design/qa/iter27-实现报告-2026-10-14.md(代拍板追认单十五项+GDD 反哺点两条);残留人闸=实机目检五件(07 ㉖);审计报告本体按退役规则 git rm | T0-T7≈12-14d(实际单会话) |
|
||||
| 迭代 28 | **①启动装配与生命周期验收补全批(2026-10-15 用户令编入)**:需求卡片=design/research/需求卡片-启动装配与生命周期验收补全批-2026-10-15.md(v1.0——零待裁点,用户预批连做全批)。六任务:T1 装配语义合同单点化+装配顺序修复+V2 断言重写/T2 boot 链路时序后置条件(_smoke_boot_chain 六断言)/T3 生命周期存活性矩阵(_smoke_lifecycle_matrix 十阶段×五引用面)/T4 渲染面内容断言(shot_probe 两自检+shot_checker 墨色占比+启动态菜单 golden)/T5 变异验证(横切随批)/T6 规范落文+文档同步闸+收口;修复坏事实三件(main.gd:207-210 装配序错致 F5 见战斗界面、smoke_all.gd:176 V2 断言保护 bug、菜单截图无内容断言)。🔴 契约 7 条(零玩法行为变更/合同单点化/断言禁裸下标/测试先行/变异验证门槛/只加不重构/闸机轮次纪律) | 已编入待开工(2026-10-15 用户令;开工门=U0 已满足——用户预批连做全批) | T0-T6≈5.25d(需求卡片 0.5d 已成文) |
|
||||
| 迭代 28 | **①启动装配与生命周期验收补全批(2026-10-15 用户令编入)**:需求卡片=design/research/需求卡片-启动装配与生命周期验收补全批-2026-10-15.md(v1.0——零待裁点,用户预批连做全批)。六任务:T1 装配语义合同单点化+装配顺序修复+V2 断言重写/T2 boot 链路时序后置条件(_smoke_boot_chain 六断言)/T3 生命周期存活性矩阵(_smoke_lifecycle_matrix 十阶段×五引用面)/T4 渲染面内容断言(shot_probe 两自检+shot_checker 墨色占比+启动态菜单 golden)/T5 变异验证(横切随批)/T6 规范落文+文档同步闸+收口;修复坏事实三件(main.gd:207-210 装配序错致 F5 见战斗界面、smoke_all.gd:176 V2 断言保护 bug、菜单截图无内容断言)。🔴 契约 7 条(零玩法行为变更/合同单点化/断言禁裸下标/测试先行/变异验证门槛/只加不重构/闸机轮次纪律) | ✅ 已入库(2026-10-15 单会话交付:T0-T6 全绿;冒烟 1125/0、run_all 全套、probe_gate --click/--full、render_gate 四闸全绿;变异验证六次注入全见红;实现报告=design/qa/iter28-实现报告-2026-10-15.md;残留人闸=实机目检启动态菜单〔07 ㉗〕) | 实耗≈1 会话(原估 T0-T6≈5.25d) |
|
||||
| 迭代 29+ | **M4 数值批(**原 22+;迭代 21 已编入天赋树实装批、迭代 22 已编入秘宝实装批(均 2026-10-11 用户令)、迭代 23 已编入锻造工坊实装批(2026-10-12 用户令)**;迭代 24 已由用户令编入敌人表内容修批、迭代 25 已由用户令编入 UI 全局主题批(2026-10-13)、迭代 26 已由用户令编入 UI 交互与观感批(2026-10-14)、**迭代 27 已由用户令编入代码架构技术债修复批(2026-10-14)、迭代 28 已由用户令编入启动装配与生命周期验收补全批(2026-10-15)**,本批顺延 29+**——**Alpha 收官位·未排期**〔2026-10-09 语义换代:M4=Beta 阶段、非 Alpha 收官位,M3 未由用户明令关闭前不排期维持〕——2026-10-05 用户令编入迭代 17 卡②;2026-10-06 用户令移出独立成批并定序「Alpha 阶段最后——内容补全先行、统一数值调整殿后」〔当日曾被编入迭代 18 为收官卡,2026-10-07 用户令撤回〕;**2026-10-07 用户令二次后移:后续新迭代(迭代 20 起)一律不先做 M4,除非用户明确要求启动 M4 批次**)**:四件数值欠账合一批——①削弱档位终值(weaken 现值 −65/duration 2/refresh 考证在册;叠层改续时语义已定案,差 duration 终值+当量锚复算)②调参批数值点评(06 条目现行登记五条——任务口径「八条」与条目五条不符,需求卡片 §2.2 已呈报差异以条目为准)③迭代 10 奖励重构联动欠账①②(钥石产出重标 boss_keystone 首杀 3/3/3·复杀 2/2/3 provisional + 通关率带重扫〔通关回血 20%+AP 件首轮 +1〕)④元进度定价剩余面(forge base 20/比 150 考证在册——06 条目文本「base 10」滞后于 2026-09-22 对齿批已呈报;Boss 两档终值)。读数义务随批=迭代 14 两件后置复算(T6 MC 四带复算+T7 技能池节奏重测,均挂 07);顺路修复 damage_probe A1 改前既有红灯(判式换代批出口报告 §四.7 在册)。**硬约束:一切 MC/verifier 跑批发起前须用户批准;调参联动禁独立调参网络(β 带 [1.2,2.5] 逐 u 检算)**。需求卡片=`design/research/需求卡片-M4数值批-2026-10-05.md`(**v1.2 2026-10-06 案C 增补+联审修订**:v1.1 三事实与第五推算件照旧;联审=P3 转正顺延至重扫读数后/检算工具 WEAKEN_PCT 入同步面/F11 两句死文本全刷/T3·T5 四带复算并账一次跑批双认账/R5 改前锚=本批新立案C 锚/估时段一勘误 4.25d;案C 增补内容照旧=§2.6 三事实〔行为换代/锚全作废/难度预判〕+T0 推算稿第五件=对拍锚按新序重推导+T6 四带复算按案C 新行为首采;T0-T8≈6.25d,天然切分点 T4 末;🔴 契约 5 条含跑批批准制;**✅ 裁点全裁(2026-10-06 用户令:P1甲=维持 2 场·magnitude [60,85] 带内当量锚复算、具体值随出口 provisional 追认/P2甲 不对称有意/P3甲 3-3-3·2-2-3 维持、转正顺延至重扫读数到账且带内(Q4 二裁)/P4甲 不立直购价);开工=U0**) | 迭代 18 全部内容卡(③④⑤⑦)出口+全部内容批行为变更落定+**用户发令(未排期)**——重锚窗口须收尽全部内容批行为变更 | T0-T8≈6.25d(超软盒预挂,段一 T0-T4≈4.25d〔v1.2 勘误,原 4.5 系加总错误〕/段二 T5-T8≈2.0d) |
|
||||
|
||||
## 执行纪律(2026-09-27 用户立规)
|
||||
|
||||
@@ -1,20 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# 棋盘 golden 基线换代辅助(迭代 19 卡① T2 2026-10-08——呈现类改动批专用)。
|
||||
# golden 基线换代辅助(迭代 19 卡① T2 2026-10-08——呈现类改动批专用;迭代 28 T4 扩菜单全屏基线路径)。
|
||||
# 权限口径(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目录] 触发原因写进当批提交说明(强制)。
|
||||
# 前置条件(Q5/S3):素材必须齐盘(本脚本逐项守卫,缺位即拒跑退出 1=基线禁锚回退态;
|
||||
# 缺位时先拷主区素材+导入边车再跑)。
|
||||
# 模式:
|
||||
# --menu(迭代 28 T4 新增):shot_probe 启动态菜单整屏帧 → game/tests/golden/menu_boot.png。
|
||||
# 全屏基线可行性前置已验证:菜单连续帧差 0.0000%/max_diff=0(2026-10-15 帧差诊断——光柱为
|
||||
# 程序生成静态贴片、零动画着色器,全屏基线不抖动);采集确定性=探针隔离档(无断点+出厂
|
||||
# 设置 1280×800),与 render_gate 的 shot_menu 对比段同源同帧。
|
||||
# 缺省(棋盘,原口径):board_shot_probe 抓 L1-L3 整屏帧+board_meta.json → golden_board_crop.py
|
||||
# 按 meta 取样坐标裁棋盘区(禁硬编码像素)→ 覆盖 game/tests/golden/ 三张基线。
|
||||
# 过程截图落 mktemp 临时区即弃(Q2),只有基线产物入库。
|
||||
# 用法: tools/golden_baseline_refresh.sh [game目录] [--menu] 触发原因写进当批提交说明(强制)。
|
||||
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=""
|
||||
MODE="board"
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--menu) MODE="menu" ;;
|
||||
*) if [ -z "$GAME" ]; then GAME="$a"; fi ;;
|
||||
esac
|
||||
done
|
||||
GAME="${GAME:-$REPO/game}"
|
||||
GAME_WIN="$(cygpath -m "$GAME")"
|
||||
TMP="$(mktemp -d /tmp/goldenrefresh_XXXXXX)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
if [ "$MODE" = "menu" ]; then
|
||||
for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
|
||||
assets/art/ui/button_stone.png; do
|
||||
if [ ! -f "$GAME/$a" ]; then
|
||||
echo "[golden_refresh] 拒跑 — 菜单素材缺位: $a(S3 停机点口径:基线禁锚回退态,先补素材+导入边车)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
timeout 240 "$G" --path "$GAME_WIN" res://smoke/shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1
|
||||
st=$?
|
||||
grep -E "\[shot_probe\]" "$TMP/run.log" | tail -3
|
||||
if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/shot_menu.png" ]; then
|
||||
echo "[golden_refresh] FAIL — shot_probe 退出码=$st(菜单截图缺失/探针自检红,见上)"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$REPO/game/tests/golden"
|
||||
cp "$TMP/shots/shot_menu.png" "$REPO/game/tests/golden/menu_boot.png" || exit 1
|
||||
echo "[golden_refresh] 完成 — 菜单全屏基线已代入 game/tests/golden/menu_boot.png(提交说明须写明触发原因)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
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 \
|
||||
|
||||
@@ -135,6 +135,23 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "======== 菜单 golden 逐像素(迭代 28 T4——启动态全屏基线)========"
|
||||
if [ ! -s "$REPO/game/tests/golden/menu_boot.png" ]; then
|
||||
echo "[render_gate] FAIL — game/tests/golden/menu_boot.png 基线缺失(菜单 golden 未建——tools/golden_baseline_refresh.sh --menu)"; _rc=1
|
||||
elif [ ! -s "$SHOT/shot_menu.png" ]; then
|
||||
echo "[render_gate] SKIP — shot_menu.png 缺失(shot_probe 段已红),菜单 golden 不判"
|
||||
elif [ ! -f "$GAME/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" ] || [ ! -f "$GAME/assets/art/ui/button_stone.png" ]; then
|
||||
echo "[render_gate] SKIP — 菜单素材缺位(字体/按钮石纹——Q5 口径:基线禁锚回退态,运行时 SKIP)"
|
||||
else
|
||||
MENUG="$TMP/menugolden"
|
||||
mkdir -p "$MENUG"
|
||||
cp "$SHOT/shot_menu.png" "$MENUG/menu_boot.png"
|
||||
python tools/golden_compare.py "$REPO/game/tests/golden" "$MENUG" menu_boot.png | sed 's/^/[render_gate] /'
|
||||
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
|
||||
echo "[render_gate] FAIL — 菜单 golden 超容差(S2 停机点口径:核对热区——按钮换墨/布局变更属预期面=显式重制基线+呈报;超预期面=L3)"; _rc=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "======== 残留检查(批末闸) ========"
|
||||
left="$(orphans | wc -l | tr -d ' ')"
|
||||
total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)"
|
||||
|
||||
@@ -13,6 +13,9 @@ r"""shot_checker.py — 整屏样张闸机检(迭代 9 卡⑤/T8 2026-09-30;
|
||||
C 明度三档带: 战斗屏 信息面(C 区面板均值) > 世界中(棋盘区均值) > 世界底(舞台无光柱区均值)
|
||||
(§2 明度分层——层间灰度可分);
|
||||
D 文字禁压纹理: 各文字框背景主簇纯度 ≥0.5(±12 灰度带占比——文字下垫平滑容器非 busy 纹理);
|
||||
F 文字在判据: 菜单文字框内与中位灰度差 >60 的像素占比在 [2%, 55%] 带内(极性无关——亮字暗底/
|
||||
暗字亮底同判;对比度只判反差不判「字在」,被埋菜单坐标处有反差即过线的缺口由本判据补,
|
||||
provisional 阈值由 2026-10-15 实测定);
|
||||
E 焦点预算〔首焦唯一〕: 语义面无法机检 → 编入段闸实机目检清单(T15 人闸判定)。
|
||||
退出码: 0 = 全过 / 1 = 有失败。
|
||||
"""
|
||||
@@ -77,6 +80,32 @@ def check_text_box(img: Image.Image, box: tuple, name: str, failures: list) -> N
|
||||
failures.append("%s: 背景纹理幅度 %.1f > 18(文字压 busy 纹理)" % (name, mid_std))
|
||||
|
||||
|
||||
def check_text_presence(img: Image.Image, box: tuple, name: str, failures: list,
|
||||
lo: float = 0.02, hi: float = 0.55, dev: int = 60) -> None:
|
||||
"""文字在判据(迭代 28 T4——对比度只判反差不判「字在」)。
|
||||
|
||||
框内与中位灰度差 >dev 的像素占比须落在 [lo, hi] 带内:极性无关(亮字暗底/暗字亮底同判),
|
||||
文字簇量级带(实测菜单标题 13.1%/副标题 11.5%——2026-10-15 窗口化实测定 provisional)。
|
||||
出带=疑似空框/纯色块/文字被移出框(结构性移位)。
|
||||
"""
|
||||
if not box:
|
||||
failures.append("%s: meta 几何缺失" % name)
|
||||
return
|
||||
px = sorted(box_pixels(img, box))
|
||||
n = len(px)
|
||||
if n == 0:
|
||||
failures.append("%s: 空框(零像素)" % name)
|
||||
return
|
||||
med = px[n // 2]
|
||||
ratio = sum(1 for v in px if abs(v - med) > dev) / n
|
||||
ok = lo <= ratio <= hi
|
||||
print("[shot_checker] %s — 文字像素占比=%.2f%%(带 [%.0f%%, %.0f%%] 极性无关)%s" % (
|
||||
name, ratio * 100, lo * 100, hi * 100, "I-OK" if ok else "I-FAIL"))
|
||||
if not ok:
|
||||
failures.append("%s: 文字像素占比 %.2f%% 出带 [%.0f%%, %.0f%%](字在判据——疑似空框/纯色/文字移位)" % (
|
||||
name, ratio * 100, lo * 100, hi * 100))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python tools/shot_checker.py <截图目录> [屏清单...]")
|
||||
@@ -132,6 +161,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_presence(m, box_of(meta, "menu_title"), "菜单·标题", failures)
|
||||
check_text_presence(m, box_of(meta, "menu_sub"), "菜单·副标题", failures)
|
||||
# 菜单·帮助文断言腿退役(迭代 19 卡① S1 裁 2026-10-08——载体玩法速览牌 2026-10-06 用户令
|
||||
# 退役「界面禁玩法文字墙」,锚/取样键/断言段三处同撤,不重锚)
|
||||
print("[shot_checker] 焦点预算〔首焦唯一〕= 目检项(T15 段闸人闸清单判定——语义面不机检)")
|
||||
|
||||
Reference in new issue
Block a user