From 771f8715272070dfd86ba30979ffb76e5d3eca9d Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 22 Oct 2026 23:18:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=BF=81=E7=A7=BB):=20?= =?UTF-8?q?=E6=89=B9=20B=20=E6=B3=A2=200=20=E5=9F=BA=E5=BB=BA=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=96=AD=E8=A8=80=E9=9D=99=E6=80=81=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E5=99=A8=EF=BC=8B=E9=AA=8C=E8=AF=81=E9=9B=86=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E5=86=BB=E7=BB=93=EF=BC=8B=E8=BF=81=E7=A7=BB=E8=B4=A6=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools/extract_checks.py:smoke_all.gd 静态 check( 调用点提取器(括号配平归集可跨行调用, 码位掩码排字符串/注释内字样)——--extract 生成清单(已存在拒写防误覆)、--diff 逐条对账 (消失/新增/成对字面变更三类报告,批 B 出口机检唯一工具)、--list-funcs 编账辅助。 基点实测调用点 1065;行级 grep=1067 系另一口径(多 2 行=func check 定义行+一处注释行), 清单头注已写明。对刚生成清单 --diff 自测零差异;释义/处置列回填后复跑仍零差异。 game/tests/验证集清单.md(冻结参照物·常驻):77 章 1065 条,标签原文与 cond 摘要逐字 保真(cond 跨行折叠单行口径),每条人工白话释义+处置列(去向)已回填;章节含行段/条数/ 去向/施工组。过 static_scan S11(205 文件零命中,唯一退役词字面标签与「去重闸」同行被 既有豁免覆盖,未新增豁免条款)。 reports/批B-断言迁移账本-2026-10-22.md:77 套分类账(系统 6 套 49 条/集成 1 套 14 条/ 单元 70 套 1002 条)+12 施工组+套级变异策略+宿主落位计划(11 个新宿主预定路径与 run_all.gd manifest/system_test.gd 挂载方式,本波零建文件)+歧义套 8 项留主会话裁决 +引用普查附录(合同在案必改点逐条核实+合同外新发现 18 类,含产品码 tune_sim_controller.gd preload smoke 库件一处)。 smoke_all.gd 零改动(git diff 空);三个去向既有测试文件零改动;本波零游戏测试。 --- game/tests/验证集清单.md | 1467 ++++++++++++++++++++++++ reports/批B-断言迁移账本-2026-10-22.md | 214 ++++ tools/extract_checks.py | 494 ++++++++ 3 files changed, 2175 insertions(+) create mode 100644 game/tests/验证集清单.md create mode 100644 reports/批B-断言迁移账本-2026-10-22.md create mode 100644 tools/extract_checks.py diff --git a/game/tests/验证集清单.md b/game/tests/验证集清单.md new file mode 100644 index 00000000..4c241858 --- /dev/null +++ b/game/tests/验证集清单.md @@ -0,0 +1,1467 @@ +# 验证集清单(迁移参照物·冻结) + +> 生成日期:2026-10-22 · 基点 commit:`1aea93972b5ff3bc51de1814081e69a79f74a735` · 源:`smoke_all.gd` +> 提取口径:静态 `check(` 调用点,括号配平归集(可跨行)。基点实测调用点 1065 处; +> 行级 `grep -c "check("` = 1067 是另一口径——多出的 2 行为 `func check(` 定义行与 +> 一处注释行(「禁恒真 check(true)」),非调用点。运行时计数大于静态计数=循环展开条目, +> 两口径各自浮动,禁互相换算(tests/README「断言计数双口径」)。 +> 生成命令:`python tools/extract_checks.py --extract`(可复现;重新生成须 --force)。 +> 冻结纪律:每条的「标签原文」「cond 摘要」「quiet」三列为机器提取逐字保真,禁任何改写; +> cond 摘要=原文跨行折叠单行(连续空白并一空格),单元格内 `|`→`\|`、`\`→`\\` 转义,读回还原。 +> 「白话释义」「处置」两列由维护回填,不属冻结面。 +> 出口对账:`python tools/extract_checks.py --diff game/tests/验证集清单.md` +> ——标签/cond/计数三对齐才算迁移完(合同 §五出口机检)。 +> 维护义务:此后断言新增/删除/改判必须同步本清单(test-conventions §9.3 锚)。 + +## S01 _initialize(行 44–70 · 14 条 · 去向:单元测试 → tests/unit/data_config/rng_stream_boot_test.gd · 施工组 G12) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1 | 44 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | 全部数据表能加载且跨表校验零错误——表损坏或引用悬空在此红。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 2 | 53 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | 主种子 42 下统一流 scope 0 的派生种子必须等于此黄金值(与旧棋盘生成向量逐位连续的锚)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 3 | 54 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | 同上,scope 1 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 4 | 55 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | 同上,scope 2 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 5 | 56 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | 同上,scope 3 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 6 | 57 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | 同上,scope 4 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 7 | 58 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | 同上,scope 5 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 8 | 59 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | 同上,scope 6 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 9 | 60 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | 同上,scope 7 黄金向量。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 10 | 61 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | derive_at 同参数两次调用结果恒等(纯函数、无隐藏状态)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 11 | 65 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | 开两条流后全局取号计数器恰为 2(单调加一,不跳不漏)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 12 | 66 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | 首个与次个 open_stream 依次取全局 scope 0、1 的派生种子(取号顺序确定)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 13 | 69 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | new_adventure 后计数器归 1 且新流首号=scope 0(会话重置语义)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | +| 14 | 70 | "事件日志会话落盘(2026-09-27 统一流裁决)" | FileAccess.file_exists("user://logs/rng_log_4242.jsonl") | RNG 事件日志按会话落盘成文件(2026-09-27 统一流裁决的落地面)。 | 迁单元测试(tests/unit/data_config/rng_stream_boot_test.gd) | | + +## S02 _split_tail(行 92–92 · 1 条 · 去向:系统测试 → tests/system_test.gd · 施工组 G1) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 15 | 92 | "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)" | false | autoload 链(ConfigDB/SaveManager)未就绪时必须显式红,禁静默跳过尾段。 | 迁系统测试(tests/system_test.gd) | | + +## S03 _smoke_type_gate(行 101–105 · 2 条 · 去向:单元测试 → tests/unit/contracts/source_guard_test.gd · 施工组 G12) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 16 | 101 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | project.godot 里 untyped_declaration=1 警告配置在位(类型闸的配置面源码断言)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 17 | 104 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | 静态扫描脚本里类型审计器接线在座(audit_type_hints.py 被引用)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | + +## S04 _smoke_hygiene_gates(行 132–153 · 6 条 · 去向:单元测试 → tests/unit/contracts/source_guard_test.gd · 施工组 G12) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 18 | 132 | "V8 字符串 Callable 产品码零命中(实测 %d——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | core/presentation/data 三目录产品码零字符串形式 Callable(self,…)——方法引用必须换装。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 19 | 134 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | core/feats_engine.gd 文件已删(FeatsEngine 退役=文件消失,不是注释掉)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 20 | 137 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | 战斗编排器源码无 _attune_row 死函数残留。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 21 | 140 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | 存档管理器源码无 attunement_counters 死函数残留。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 22 | 143 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | main 源码无 GP 影子账标识符残留(gp_shadow_ledger/_gp_ledger 双词面零命中)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | +| 23 | 152 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | main 的 move_child 调用全部收在 _apply_layer_contract 一个函数内(层序合同单口,函数外零调用)。 | 迁单元测试(tests/unit/contracts/source_guard_test.gd) | | + +## S05 _smoke_scene_split(行 162–214 · 11 条 · 去向:系统测试 → tests/system_test.gd · 施工组 G1) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 24 | 162 | "V2 菜单屏壳独立实例化(start_overlay+操作组六钮在位)" | menu.start_overlay != null and menu.lvbtn != null and menu.resumebtn != null and menu.campbtn != null and menu.careerbtn != null and menu.settingsbtn != null and menu.quitbtn != null | 菜单屏壳独立实例化后开始浮层与操作组六钮句柄齐备。 | 迁系统测试(tests/system_test.gd) | | +| 25 | 164 | "V2 菜单层默认可见(非模态全屏态)" | menu.start_overlay.visible | 开始层(菜单)默认可见(非模态全屏态)。 | 迁系统测试(tests/system_test.gd) | | +| 26 | 165 | "V2 屏壳根零鼠标截获(IGNORE——全屏 STOP 壳吃输入病灶回归闸)" | menu.mouse_filter == Control.MOUSE_FILTER_IGNORE | 菜单屏壳根不截获鼠标(IGNORE——全屏 STOP 壳吃输入的病灶回归闸)。 | 迁系统测试(tests/system_test.gd) | | +| 27 | 170 | "V2 营地屏壳独立实例化(camp_overlay 在位)" | camp.camp_overlay != null | 营地屏壳独立实例化营地浮层在位。 | 迁系统测试(tests/system_test.gd) | | +| 28 | 171 | "V2 营地层默认隐藏(模态纪律)" | not camp.camp_overlay.visible | 营地浮层默认隐藏(模态纪律)。 | 迁系统测试(tests/system_test.gd) | | +| 29 | 176 | "V2 冒险屏壳独立实例化(战斗宿主+四区句柄+关卡地图屏在位)" | adv.battle_root != null and adv.stage != null and adv.board_zone != null and adv.board_view != null and adv.ap_bar != null and adv.level_screen != null | 冒险屏壳独立实例化:战斗宿主、四区句柄、关卡地图屏在位。 | 迁系统测试(tests/system_test.gd) | | +| 30 | 187 | "V1a 启动态顶层可见层=start_overlay(实测顶层=%s——菜单被冒险屏盖住即红)〔装配生命批 V1〕" ↩ % (top.name if top != null else "null") | top == main._start_overlay | 启动态自树顶向下第一个可见内容层=开始浮层(菜单被冒险屏盖住即红)。 | 迁系统测试(tests/system_test.gd) | | +| 31 | 191 | "V1b 菜单屏壳在冒险屏壳之上(start_overlay 盖住战斗内容)〔装配生命批 V1〕" | main._menu_screen.get_index() > main._adventure_screen.get_index() | 菜单屏壳树序在冒险屏壳之上(开始层盖住战斗内容)。 | 迁系统测试(tests/system_test.gd) | | +| 32 | 209 | "V1c 合同树序链逐对保序(运行期树序==LAYER_CONTRACT 序;违例:%s)〔装配生命批 V1〕" ↩ % chain_detail | chain_ok | 运行期组合根树序与层序合同逐对保序(违例细节进标签)。 | 迁系统测试(tests/system_test.gd) | | +| 33 | 211 | "V2 组合根句柄指向屏壳成员(探针挂载假设 main.X 零迁移)" | 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 | 组合根一级句柄指向屏壳成员(探针挂载假设 main.X 零迁移)。 | 迁系统测试(tests/system_test.gd) | | +| 34 | 213 | "V2 闲时会话自 _ready 即在(永非 null;迭代 27 T3——gp 影子账退役无账本成员)" | main.run_scope != null and not main.run_scope.is_adventure | 闲时会话自 _ready 即在、永非空且非冒险态。 | 迁系统测试(tests/system_test.gd) | | + +## S06 _smoke_boot_chain(行 250–307 · 15 条 · 去向:系统测试 → tests/system_test.gd · 施工组 G1) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 35 | 250 | "V2a ConfigDB 就绪且表已载(is_ready+hero 表在座)〔装配生命批 V2〕" | db.is_ready and db.hero != null | autoload ConfigDB 就绪且主角表已载。 | 迁系统测试(tests/system_test.gd) | | +| 36 | 255 | "V2b 设置已应用·音量(Master mute/volume_db=settings_store 现值 linear=%f)〔装配生命批 V2〕" ↩ % ss.volume_linear | 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))) | 引擎 Master 总线静音/音量与设置存储现值一致(真实设置文件已应用)。 | 迁系统测试(tests/system_test.gd) | | +| 37 | 259 | "V2b 设置已应用·减动效(UiKit.motion_scale=%s=settings_store.reduced_motion=%s)〔装配生命批 V2〕" ↩ % [ str(ui_kit.motion_scale), str(ss.reduced_motion)] | ui_kit.motion_scale == (0.5 if ss.reduced_motion else 1.0) | UiKit 减动效系数随设置 reduced_motion 取 0.5/1.0。 | 迁系统测试(tests/system_test.gd) | | +| 38 | 263 | "V2c 启动态 _start_overlay.visible=true(boot 后玩家见菜单)〔装配生命批 V2〕" | main._start_overlay.visible | 启动后开始层可见(玩家开机见菜单)。 | 迁系统测试(tests/system_test.gd) | | +| 39 | 269 | "V2d 续玩钮 boot 态恒隐(无激活态——启动态不隐式激活;断点在档=%s visible=%s)〔装配生命批 V2·卡① sv2 改写〕" ↩ % [ str(sm.has_breakpoint()), str(main._resume_btn.visible)] | not sm.has_breakpoint() and not main._resume_btn.visible | boot 态无激活档:断点不在档且续玩钮恒隐藏。 | 迁系统测试(tests/system_test.gd) | | +| 40 | 273 | "V2-s1 boot 后无激活态(active_slot=0、写盘目标空)〔卡① sv2〕" | sm.active_slot() == 0 and not sm.has_write_target() | boot 后无激活态:激活槽 0、无写盘目标。 | 迁系统测试(tests/system_test.gd) | | +| 41 | 275 | "V2-s2 boot 内存=默认合法空态(钥石/断点/总局数全零值)〔卡① sv2〕" | sm.keystone_balance() == 0 and not sm.has_breakpoint() and sm.adventure_total() == 0 | boot 内存为合法空态:钥石 0、无断点、总局数 0。 | 迁系统测试(tests/system_test.gd) | | +| 42 | 277 | "V2-s3 boot 无激活写盘拒绝(write_to_disk=false 生产入口)〔卡① sv3〕" | not sm.write_to_disk() | 无激活态调写盘被拒(生产入口返回 false)。 | 迁系统测试(tests/system_test.gd) | | +| 43 | 279 | "V2-s4 旧单档迁移不变式(旧档在则槽 1 必在——Q1 甲一次性迁移已发生)〔卡① sv12〕" | not (FileAccess.file_exists("user://save.json") and not FileAccess.file_exists(sm.slot_path(1))) | 旧单档迁移不变式:旧档存在则槽 1 档必存在(一次性迁移已发生)。 | 迁系统测试(tests/system_test.gd) | | +| 44 | 287 | "V2d 续玩钮·无断点态隐藏(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" ↩ % [ str(main._resume_btn.visible), str(sm.has_breakpoint())] | not sm.has_breakpoint() and not main._resume_btn.visible | 隔离档无断点时续玩钮隐藏(刷新单口与 boot 路径同源)。 | 迁系统测试(tests/system_test.gd) | | +| 45 | 292 | "V2d 续玩钮·有断点态可见(visible=%s has_breakpoint=%s)〔装配生命批 V2〕" ↩ % [ str(main._resume_btn.visible), str(sm.has_breakpoint())] | sm.has_breakpoint() and main._resume_btn.visible | 设断点后续玩钮可见。 | 迁系统测试(tests/system_test.gd) | | +| 46 | 297 | "V2d 续玩钮·清断点后回隐藏(visible=%s)〔装配生命批 V2〕" ↩ % str(main._resume_btn.visible) | not main._resume_btn.visible | 清断点后续玩钮回隐藏。 | 迁系统测试(tests/system_test.gd) | | +| 47 | 300 | "V2e run_scope 非空且为闲时会话(is_adventure=false+is_inside_tree)〔装配生命批 V2〕" | main.run_scope != null and not main.run_scope.is_adventure and main.run_scope.is_inside_tree() | 闲时会话 _ready 即建、在树、非冒险态(永非空)。 | 迁系统测试(tests/system_test.gd) | | +| 48 | 304 | "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕" | not main.debug_overlay.visible | boot 不自动开调试浮窗(菜单优先)。 | 迁系统测试(tests/system_test.gd) | | +| 49 | 306 | "V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕" | main.debug_badge != null and main.debug_badge.visible | 调试浮层入口角标 boot 可见。 | 迁系统测试(tests/system_test.gd) | | + +## S07 _smoke_lifecycle_matrix(行 374–483 · 10 条 · 去向:系统测试 → tests/system_test.gd · 施工组 G1) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 50 | 374 | "V3d 活战斗驱动完毕(战斗已收场——%d 轮,finished=%s)〔装配生命批 V3〕" ↩ % [guard, str(finished.done)] | main.run_scope.settlement == null or not main.run_scope.settlement.is_battle_live() | 活战斗自动驱动能把战斗打到收场(防驱动死循环)。 | 迁系统测试(tests/system_test.gd) | | +| 51 | 388 | "V3d2 放弃受理(选路停点天然非战斗期)〔装配生命批 V3〕" | main.run_scope.adventure.abandon() | 胜利停点后的选路停点放弃受理(天然非战斗期)。 | 迁系统测试(tests/system_test.gd) | | +| 52 | 399 | "V3e 终局面到达(adventure_finished+终局面板开)〔装配生命批 V3〕" | finished.done and main._end_overlay.is_open() | 冒险终局到达:完成信号已发+终局面板已开。 | 迁系统测试(tests/system_test.gd) | | +| 53 | 405 | "V3f 收束换会话无真空窗(新闲时会话即刻在树)〔装配生命批 V3〕" | main.run_scope != old_scope and main.run_scope.is_inside_tree() | 收束回菜单换新闲时会话且即刻在树(无真空窗)。 | 迁系统测试(tests/system_test.gd) | | +| 54 | 408 | "V3f 收束后旧局经 queue_free 消亡(is_instance_valid=false)〔装配生命批 V3〕" | not is_instance_valid(old_scope) | 旧局会话对象经队列释放真正消亡(无僵尸引用)。 | 迁系统测试(tests/system_test.gd) | | +| 55 | 421 | "V3g 营地入口缺失(驱动中断)〔装配生命批 V3〕" | false | 营地入口按钮缺失时驱动显式红(防夹具静默中断)。 | 迁系统测试(tests/system_test.gd) | | +| 56 | 450 | "V3h 两连局第二会话全新对象(与第一局不复用)〔装配生命批 V3〕" | main.run_scope.adventure != null and main.run_scope.adventure != first_adv | 两连局的第二局是全新冒险对象(不复用第一局实例)。 | 迁系统测试(tests/system_test.gd) | | +| 57 | 453 | "V3h 放弃受理(第二局)〔装配生命批 V3〕" | main.run_scope.adventure.abandon() | 第二局放弃受理。 | 迁系统测试(tests/system_test.gd) | | +| 58 | 479 | "V3i 续玩钮·断点在档可见〔装配生命批 V3〕" | main._resume_btn.visible | 断点在档(隔离档夹具)→ 续玩钮可见。 | 迁系统测试(tests/system_test.gd) | | +| 59 | 482 | "V3i 续玩换会话无真空窗(新冒险会话即刻在树)〔装配生命批 V3〕" | main.run_scope != idle_scope and main.run_scope.is_inside_tree() | 续玩换新冒险会话即刻在树(无真空窗)。 | 迁系统测试(tests/system_test.gd) | | + +## S08 _stage_faces(行 579–579 · 1 条 · 去向:系统测试 → tests/system_test.gd · 施工组 G1) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 60 | 579 | "%s(引用面违例:%s)〔装配生命批 V3〕" ↩ % [tag, bad] | bad.is_empty() | 阶段引用面零违例:恒活面对象全有效、现值回调与 run_scope 同源、存活态与附加判据全过。 | 迁系统测试(tests/system_test.gd) | | + +## S09 _smoke_run_lifecycle(行 608–640 · 11 条 · 去向:系统测试 → tests/system_test.gd · 施工组 G1) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 61 | 608 | "V3 开局建会话(is_adventure+adventure 在座)" | main.run_scope != null and main.run_scope.is_adventure and main.run_scope.adventure != null | 开局建冒险会话:冒险态真、冒险对象在座。 | 迁系统测试(tests/system_test.gd) | | +| 62 | 610 | "V3 新会话局态零值" | main.run_scope.hints_shown.is_empty() | 新会话的提示已显示集为空(局态零值起点)。 | 迁系统测试(tests/system_test.gd) | | +| 63 | 619 | "V3 放弃受理(入口停点天然非战斗期)" | main.run_scope.adventure.abandon() | 入口停点放弃受理。 | 迁系统测试(tests/system_test.gd) | | +| 64 | 624 | "V3 冒险终局到达(abandon→adventure_finished 同步链)" | finished.done | 放弃→冒险完成信号同步链到达(终局必达)。 | 迁系统测试(tests/system_test.gd) | | +| 65 | 625 | "V3 终局面板弹起" | main._end_overlay.is_open() | 终局面板弹起。 | 迁系统测试(tests/system_test.gd) | | +| 66 | 628 | "V3 会话收束(新闲时会话零冒险/关卡对象)" | not main.run_scope.is_adventure and main.run_scope.adventure == null and main.run_scope.runner == null | 会话收束后闲时会话零冒险/关卡/战斗对象。 | 迁系统测试(tests/system_test.gd) | | +| 67 | 630 | "V3 局态封印/账本全复位(queue_free 物化——注入脏状态零残留)" | main.run_scope.hints_shown.is_empty() and not main.run_scope.boss_active and main.run_scope.last_gain_amount == 0 | 注入的脏状态零残留:提示集/旗标/账本值全复位。 | 迁系统测试(tests/system_test.gd) | | +| 68 | 632 | "V3 回开始菜单(终局出口路径)" | main._start_overlay.visible | 终局出口路径回到开始菜单。 | 迁系统测试(tests/system_test.gd) | | +| 69 | 634 | "V3 放弃终局清断点(无残留断点——续玩按钮态联动)" | not sm.has_breakpoint() | 放弃终局清断点(续玩联动无残留)。 | 迁系统测试(tests/system_test.gd) | | +| 70 | 638 | "V3 第二连局全新会话(两连局零泄漏)" | main.run_scope.is_adventure and main.run_scope.adventure != null and main.run_scope.hints_shown.is_empty() | 第二连局全新会话且局态零值(两连局零泄漏)。 | 迁系统测试(tests/system_test.gd) | | +| 71 | 640 | "V3 直进入局(菜单隐)" | not main._start_overlay.visible | 直进入局后开始菜单隐藏。 | 迁系统测试(tests/system_test.gd) | | + +## S10 _smoke_node_map_templates(行 719–784 · 14 条 · 去向:集成测试 → tests/integration/node_map_templates_test.gd · 施工组 G12) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 72 | 719 | "node_map 标签入 rng_service 封闭词表(扩签通道=改代码+冒烟守卫)" | (svc.PURPOSE_TAGS as Array).has("node_map") | node_map 用途标签在 RNG 服务封闭词表内(扩签=改代码+此守卫)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 73 | 727 | "L%d 模板池 = 5 张(拍板点④——首版池规模)" ↩ % li | tpls.size() == 5 | 第 1–3 关每关模板池恰 5 张模板。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 74 | 728 | "L%d 模板 0 = 现行固定图(回滚档成员)" ↩ % li | not tpls.is_empty() and String(tpls[0].get("display_name", "")).ends_with("(现行)") | 每关模板 0 是现行固定图(显示名带现行标记)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 75 | 735 | "强制 template_pick=0 起关受理" | runner0.start_level_external(2, {"hero_hp": 20, "gp": 0}, {}, Callable(self, "_smoke_rest_stub"), {}, true, 0) | 强制 template_pick=0 的起关调用受理。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 76 | 737 | "强制取模板零 RNG 消耗(零抽签——首次脚本语义)" | rng0.counter() == c_before | 强制取模板 0 零 RNG 消耗(首次脚本语义=零抽签)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 77 | 742 | "强制 0 的生效节点表 = 模板 0 逐位相等(回滚档成立)" | runner0.level_row.get("nodes", []) == tpl0.get("nodes", []) | 强制 0 的生效节点表与模板 0 节点表逐位相等。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 78 | 753 | "抽签确定性:同种子逐位同图(实测 %s)" ↩ % String(names[0]) | names[0] == names[1] and String(names[0]) != "" | 同种子两次抽签取到同一模板(抽签确定性)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 79 | 759 | "N=1 池表通过图校验(回滚档形态合法)" | lt.validate().is_empty() | 单模板行(N=1 池)通过表图校验(回滚档形态合法)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 80 | 765 | "首次脚本模式:L1 取模板 0(全关零抽签——存档布尔位 false 语义)" | String(adrF.runner().active_template_name).ends_with("(现行)") | 首次脚本模式第 1 关取模板 0(存档布尔位 false 语义)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 81 | 772 | "抽签模式恰多消费 1 scope(node_map 逐关恰一——L1 起关抽签)" | rngT.counter() == cF + 1 | 抽签模式比首次脚本恰多消费 1 个 scope(逐关恰一抽签)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 82 | 773 | "抽签模式 active_template_name 有值(MC 分层出数取数面在位)" | String(adrT.runner().active_template_name) != "" | 抽签模式起关后模板名有值(测量取数面在位)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 83 | 779 | "新档首次冒险布尔位 = false(默认域)" | sv.is_first_adventure_done() == false | 新档首次冒险布尔位=false(默认域)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 84 | 782 | "冒险收束点置位(三结局同写——写入只在收束挂点)" | sv.is_first_adventure_done() == true | 冒险收束点把布尔位置 true(三结局同写、只在收束挂点)。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | +| 85 | 784 | "置位幂等(重复收束不翻转)" | sv.is_first_adventure_done() == true | 置位幂等:重复收束不翻转。 | 迁集成测试(tests/integration/node_map_templates_test.gd) | | + +## S11 _smoke_adventure_table(行 806–915 · 17 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 86 | 806 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | adventure 表已加载进配置宿主。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 87 | 809 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | AdventureTable 自校验零违例(全键域检+档位配额结构锚)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 88 | 810 | "clear_heal_pct ∈ [0,100](%d——通关部分回血比例)" ↩ % int(_db.adventure.clear_heal_pct) | int(_db.adventure.clear_heal_pct) >= 0 and int(_db.adventure.clear_heal_pct) <= 100 | 通关回血比例在 [0,100] 域内。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 89 | 813 | "档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" ↩ % [tier, int(_db.adventure.TIER_QUOTAS[tier])] | int(_db.adventure.tier_total(tier)) == int(_db.adventure.TIER_QUOTAS[tier]) | 四档令牌配额结构=锚值(70/25/7/0)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 90 | 815 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | 令牌池总量恰 102 张(迭代 31 卡② 净删后口径)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 91 | 816 | "数值对(四级换代承袭):小GP 20/大GP 50 · 小增伤 +1/大 +2 · 小生命 +5/大 +10 · 小护甲 +1/大 +2" | int(_db.adventure.curio_value("gp", "common")) == 20 and int(_db.adventure.curio_value("gp", "excellent")) == 50 and int(_db.adventure.curio_value("attack", "common")) == 1 and int(_db.adventure.curio_value("attack", "excellent")) == 2 and int(_db.adventure.curio_value("max_hp", "common")) == 5 and int(_db.adventure.curio_value("max_hp", "excellent")) == 10 and int(_db.adventure.curio_value("armor", "common")) == 1 and int(_db.adventure.curio_value("armor", "excellent")) == 2 | 八类令牌件值对=正式承袭值(GP 20/50、增伤 1/2、生命 5/10、护甲 1/2)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 92 | 821 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | 恢复件优秀档 +6、AP 稀有档 +1(换代承袭值)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 93 | 823 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | GP/攻击 cap 占位 99 在域(实质无上限口径)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 94 | 825 | "同调位读数源=树同调位节点点亮数(迭代 21 T4 槽位收编——null 档求值 0;谓词表已退役)〔天赋树批 V14〕" | Attunement.slots(_db, null) == 0 | 同调位读数=树同调位节点点亮数(空档求值 0,谓词表已退役)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 95 | 832 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | 同调目录 12 件全量落表且 id 零重复。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 96 | 843 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | 12 件 item_id 与显示名逐件=正式定名(C2 代号即名)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 97 | 871 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | 逐行解锁方式与契约分配一致:8 件钥石价、4 件条件谓词互斥,档位 7/3/2。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 98 | 874 | "装配→战斗贯通:enc.attunement=[morning_order] 起战 attune_has=true(注入面+只读出口)〔秘宝批换主语〕" | orch_l.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "attunement": ["morning_order"]}) and orch_l.attune_has("morning_order") | 带晨操令装配起战,战斗侧 attune_has=true(装配→战斗贯通)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 99 | 878 | "空装配基线:不带 attunement 起战 attune_has=false(off 态零扰动)〔秘宝批换主语〕" | orch_e.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) and not orch_e.attune_has("morning_order") | 不带装配起战 attune_has=false(off 态零扰动基线)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 100 | 893 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | 12 行效果词零棋盘改写类(rewrite_board 族零引用——棋盘事实不可变契约)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 101 | 900 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | 效果三方向覆盖标签齐备(信息/行动资源/抉择刷新)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 102 | 914 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | 白板档 12 件全未解锁且解锁模式分派正确(空目录=合法可玩态)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S12 _smoke_adventure_api(行 929–955 · 11 条 · 去向:单元测试 → tests/unit/core/adventure_feats_test.gd · 施工组 G7) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 103 | 929 | "冒险启动受理(主种子注入恰一次——state=running;first_script=固定模板 0——精确路径断言需要现行图,零抽签)" | adr.start_adventure(42, {"first_script": true}) | 冒险启动受理(主种子注入恰一次)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 104 | 930 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | 进行中重复启动被拒(重开须待结算完毕)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 105 | 931 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | 信号次序契约:关卡开始信号先于入口候选呈现。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 106 | 933 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | 入口停点形态:选路待选+单候选+首战未开(呈现零 RNG 消耗)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 107 | 936 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | 选路点选入口候选受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 108 | 938 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | 点选后节点进入信号到达且首战在飞。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 109 | 940 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | 战斗中放弃被拒(战斗域单事务在飞)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 110 | 949 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | 首战胜后回到选路态(战斗对象置空)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 111 | 951 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | 非战斗期放弃受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 112 | 953 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | 放弃结算:结果=abandoned、未打穿(已获面不追回)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 113 | 955 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | 结算后重复放弃被拒(结算只受理一次)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | + +## S13 _smoke_adventure(行 980–1287 · 54 条 · 去向:单元测试 → tests/unit/core/adventure_feats_test.gd · 施工组 G7) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 114 | 980 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | 关卡行集 3 行(三关正式序列生效)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 115 | 996 | "行集内每个战斗/Boss 节点在 board_gen + enemy 有精确行集(缺失 %d——带键 (L,row_kind,depth_band))" ↩ % miss | miss == 0 | 行集内每个战斗/Boss 节点在棋盘生成表与敌人表都有精确行集(覆盖义务)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 116 | 1000 | "满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | 满配通关(调试攻血覆盖)达成 cleared=true。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 117 | 1002 | "通关几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | 通关数=3。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 118 | 1006 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | 钥石合计=各关 Boss 首杀行值之和(逐杀掉落口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 119 | 1009 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | 逐杀掉落明细恰 3 笔(每关一笔)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 120 | 1010 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | 冒险摘要携带主种子 42。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 121 | 1021 | "通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | 通关三选一恰呈现 2 次、恰兑付 2 次(末关不发)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 122 | 1023 | "奖励节点三选一 ≥1 次(L1 链必经 1 个;L2/L3 图可选路经若干——实际经停 %d)" ↩ % node_picks | node_picks >= 1 | 奖励节点三选一至少经停 1 次。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 123 | 1028 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | 奖励节点兑付回执与经停数逐笔相等(同账计数)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 124 | 1078 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | 三选一候选 2–3 枚且零重复(令牌洗牌不放回)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 125 | 1079 | "通关部分回血封顶(floor(上限×比例) 封顶缺口——血回满退役;购血容差窗口)" | heal_ok | 通关回血按生效上限比例封顶(血回满语义退役,含购血容差窗口)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 126 | 1080 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | 钥石逐杀入账:首杀档位+累计前缀逐位一致。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 127 | 1081 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | 点选恰兑付选中一件且值=表值(未选项零生效)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 128 | 1092 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | 各战敌人上限入参落在[基础, 基础+件值+购血余量]窗口(覆盖值通道)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 129 | 1096 | "同种子两局逐位可复现(summary/上限序列/行进序列/三选一载荷——reward_pick 封闭流)" | st2["summary"] == sum and st2["encounter_max_hp_seq"] == seq and st2["visited_ids"] == st["visited_ids"] and st2["reward_events"] == st["reward_events"] | 同种子两局全量逐位可复现(摘要/上限序列/行进/三选一载荷)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 130 | 1106 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | 零配 L1 败北种子可达(败北线存在性)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 131 | 1109 | "L1 败北摘要:未打穿 · 通关 0 关 · 钥石 0 · 止步第 1 层" | not bool(sl["cleared"]) and int(sl["levels_cleared"]) == 0 \\ and int(sl["keystone_total"]) == 0 and int(sl["stopped_at"]) == 1 | L1 败北摘要:未打穿、0 关、0 钥石、止步第 1 层。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 132 | 1113 | "L1 败北败退明细:层号/节点编号/节点类型在案(数值验证取数面 2026-09-22)" | int(dl.get("level_index", -1)) == 1 and int(dl.get("node_id", -1)) >= 0 \\ and String(dl.get("node_type", "")) in ["battle", "boss"] | L1 败北明细:层号/节点编号/节点类型在案(数值验证取数面)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 133 | 1122 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | 半配 L2 败北种子可达。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 134 | 1125 | "L2 败北摘要:L1 已过(钥石=L1 首杀掉落)· 止步第 2 层" | not bool(sh["cleared"]) and int(sh["levels_cleared"]) == 1 \\ and int(sh["keystone_total"]) == int(_db.meta_boss_keystone_row(1).get("first_kill", 0)) \\ and int(sh["stopped_at"]) == 2 | L2 败北摘要:已过 L1(钥石=L1 首杀)且止步第 2 层。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 135 | 1130 | "L2 败北败退明细:层号=2 · 节点在案(数值验证取数面 2026-09-22)" | int(dh.get("level_index", -1)) == 2 and int(dh.get("node_id", -1)) >= 0 \\ and String(dh.get("node_type", "")) in ["battle", "boss"] | L2 败北明细:层号=2、节点在案。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 136 | 1136 | "重复拒绝场景:冒险启动(seed 7——固定模板 0 同上)" | adr6.start_adventure(7, {"first_script": true}) | 重复拒绝场景就位:固定模板启动冒险。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 137 | 1144 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | 选路态放弃→已结算(场景推进到可重复宣布态)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 138 | 1147 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | 重复关卡结束宣布被拒:摘要零变化(契约 7)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 139 | 1157 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | 每战完成 GP 入账恰一次(三选一 GP 袋件入账单列不算)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 140 | 1158 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | 钱包 clear 事件恰一次且为末事件(关卡收束不清账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 141 | 1161 | "L2 商店房到达余额 > 0(%d——L1 积累跨关延续;两关商店房必经各 1)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 2 and int(shop_seq[0]) > 0 | L2 商店房到达时余额>0(L1 积累跨关延续)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 142 | 1173 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | 同账场景种子可达(L2 商店房就位)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 143 | 1186 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | 进店余额≥hp_up 该关首价(基价+关卡加成,配置推导)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 144 | 1188 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | 购买 hp_up 受理(出账+进加算区)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 145 | 1190 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | 统一冒险加算区含商店购得值(同账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 146 | 1193 | "L2 通关三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | L2 通关后三选一停点在案(零自动推进)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 147 | 1194 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | 未兑付时推进被拒(选择停点零自动推进契约)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 148 | 1197 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | 三选一点选兑付受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 149 | 1200 | "三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" ↩ % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2] | int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc | 三选一属性件与商店购买同账累加(同一本账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 150 | 1203 | "通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | 通关回血后血量不越生效上限(封顶口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 151 | 1205 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | 兑付后推进受理(下一关开始)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 152 | 1208 | "恢复点数学:floor(100×30%)=30 · 封顶缺口 5 · 满血回 0 合法" | int(a9.apply_rest(30, 70, 100)) == 30 and int(a9.apply_rest(30, 95, 100)) == 5 \\ and int(a9.apply_rest(30, 100, 100)) == 0 | 恢复点数学三读数:30% 回复/封顶缺口/满血回 0。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 153 | 1212 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | 同一恢复公式在两个落点逐位一致。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 154 | 1217 | "放弃场景:冒险启动(seed 15——固定模板 0 同上)" | a10.start_adventure(15, {"first_script": true}) | 放弃场景冒险启动受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 155 | 1218 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | 入口停点点选进首战。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 156 | 1220 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | 战斗中放弃被拒。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 157 | 1227 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | 非战斗期放弃受理(选路态)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 158 | 1231 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"first_script": true, "treasure_unlocked": {"wide_scout_band": true}}) | 剔牌库场景启动(道具解锁集只含阔视野带)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 159 | 1241 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | attack/ap 令牌耗尽后候选零出现(池耗即枯竭)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 160 | 1243 | "池耗后候选恒 ≥2(稀有/罕见档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | 池耗后候选仍≥2(防三选项全不可选死角)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 161 | 1252 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | 40 次重抽零 aux 候选(停点渠道 aux 卡退役)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 162 | 1261 | "满血通关回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | 满血通关回血 0 合法(封顶口径非负溢出)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 163 | 1265 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"first_script": true, "treasure_unlocked": {"wide_scout_band": true}}) | 节点共享池场景启动。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 164 | 1277 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | 令牌耗尽后奖励节点同样零出候选(同库同池)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 165 | 1282 | "奖励节点 GP 件入账(钱包「奖励入账」收口——+%d)" ↩ % int(_db.adventure.curio_value("gp", "common")) | a13.apply_node_curio("gp", "common") and int(a13.ledger.get("gp", 0)) == gp13 + int(_db.adventure.curio_value("gp", "common")) | 奖励节点 GP 件入账走钱包收口。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 166 | 1284 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | 节点停点态兑付后清零(恰一次语义)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 167 | 1286 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | 放弃摘要:abandoned、未打穿(已获不追回)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | + +## S14 _smoke_feats(行 1295–1637 · 71 条 · 去向:单元测试 → tests/unit/core/adventure_feats_test.gd · 施工组 G7) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 168 | 1295 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | 专长表已加载且恰 30 行(基础 27+迁入 2+完美保险 1)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 169 | 1296 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | 专长表自校验零违例(四级稀有度/上限域/触发词表/行键白名单)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 170 | 1298 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71, {"first_script": true}) | 专长带场景冒险启动受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 171 | 1306 | "专长带入池:30 件四级直映射(普通 12/优秀 9/稀有 9/传说 0)——牌库 102+30=132〔专长批 V2+卡② 完美保险迁入〕" | feat_tokens == 30 and int(tier_counts.get("common", 0)) == 12 and int(tier_counts.get("excellent", 0)) == 9 and int(tier_counts.get("rare", 0)) == 9 and a_r0._curio_deck.size() == 132 | 30 件专长以 relic 令牌入池且档位直映射(12/9/9/0),牌库 102+30=132。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 172 | 1309 | "旧 27 件已退役:arcane_ember/magma_body 表内零残留〔专长批 V2〕" | _db.feats.row_by_id("arcane_ember").is_empty() and _db.feats.row_by_id("magma_body").is_empty() | 旧 27 件代表行(arcane_ember/magma_body)表内零残留(旧代已退役)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 173 | 1311 | "迁出收尾:treasure_items 无 venom_edge/seal_burst 行(同调目录自然收窄)〔专长批 V13〕" | _db.treasure_items.row_by_id("venom_edge").is_empty() and _db.treasure_items.row_by_id("seal_burst").is_empty() | 迁出收尾:秘宝表无 venom_edge/seal_burst 行(同调目录收窄)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 174 | 1319 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | 层级纪律:牌库 kind 全集=表内 CURIO_KINDS+relic(秘宝 id 零入池)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 175 | 1325 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | 30 行触发器零棋盘改写类订阅(rewrite_board 已除名消费面)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 176 | 1332 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | 30 行显示名与描述零「雷/旗」字面(对外命名纪律初筛)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 177 | 1333 | "数值纪律:陷阱爆发倍率>1 · 血蜜回复<典型陷阱伤 · 石肤上限 3 在册(结构锚形态内 provisional)〔专长批 V22 机检半〕" | float((_db.feats.row_by_id("trap_burst").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("mult", 0)) > 1.0 and int((_db.feats.row_by_id("blood_honey").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("value", 99)) < 5 and int((_db.feats.row_by_id("stone_skin").get("triggers", [{}])[0] as Dictionary).get("do", {}).get("cap", 0)) == 3 | 数值纪律锚:陷阱爆发倍率>1、血蜜回复<典型陷阱伤、石肤上限 3。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 178 | 1351 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | 新 schema 手搓 5 行临时表自校验零违例(流派标签已退役零 archetype)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 179 | 1355 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72, {"first_script": true}) | 注入临时表后冒险启动受理。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 180 | 1360 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | 5 行 relic 每件 1 张入池,牌库 102+5=107。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 181 | 1372 | "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | 通关渠道 60 次重抽出≥2 种专长(只出专长)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 182 | 1373 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | 节点渠道 60 次重抽零专长(专长在非 Boss 渠道只以凭证形态出现)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 183 | 1381 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | 专长凭证节点兑付受理(拿到即开)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 184 | 1387 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | 凭证兑付后停点候选已替换为全 relic 专长三选一。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 185 | 1388 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | 凭证兑付零摘牌:令牌库库存不动(凭证不进持有集)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 186 | 1390 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | 凭证本体不在持有集残留。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 187 | 1393 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | 凭证开出候选可正常兑付入持有集(计数+1)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 188 | 1398 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | 保底 pity 满步时下 consider 必触发(概率无关)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 189 | 1399 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | 保底触发后 pity 清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 190 | 1402 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | 限购满后保底冻结不再触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 191 | 1410 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | 概率面存在:20% 概率 200 次展示内必出现(零触发=概率面失守)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 192 | 1412 | "relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" ↩ % [deck_relic, deck_relic - 1] | a_r.pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.pool_stock("relic") == deck_relic - 1 | relic 兑付:入持有集+牌库摘牌(选中踢池)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 193 | 1420 | "断点附随件:relics 持有集入快照(迭代 12 T1 扩列)" | (bp_r.get("relics", []) as Array).has("x3") | 断点快照附随 relics 持有集(续玩还原面)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 194 | 1421 | "断点附随件:adventure 计数器账本键在案(T6/T7 扩列)" | int((bp_r.get("adventure_counters", {}) as Dictionary).size()) >= 0 | 断点快照附随 adventure 计数器账本键。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 195 | 1426 | "断点余库:已兑付 relic 令牌逐枚还原(x3 零残留——续玩不重复获得)" | bp_deck_x3 == 0 | 断点余库逐枚还原:已兑付 relic 令牌零残留(续玩不重复获得)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 196 | 1449 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | 双管镐:每第 2 次付费挖格返 1 AP(mod_eq 预评估口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 197 | 1451 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | 双管镐反例:未满 2 次不返 AP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 198 | 1455 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | 地质学家:首开新资源类型积 1 点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 199 | 1459 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | 地质学家满 4 种:揭示 1 格且计数清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 200 | 1462 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | 地质学家反例:同类型重复首开不叠点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 201 | 1466 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | 安全帽:开安全格攒层 ×2。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 202 | 1472 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | 安全帽:踩雷耗层减伤=层值 2(一次清零)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 203 | 1476 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | 跳格采掘:空白 0 格攒 1 点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 204 | 1480 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | 跳格采掘满 3 点:揭示 1 格。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 205 | 1482 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | 跳格采掘反例:普通开格不计点。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 206 | 1490 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | 爆破会计:本轮挖格花费≥5 轮末+1 GP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 207 | 1492 | "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty() | 边陲矿工:边缘格产出材料、中心格反例零产出。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 208 | 1500 | "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕" | orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue" | 封印回响:标中计数≥2 轮末返 1 AP(queue 模式)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 209 | 1510 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | 封印匠:标中每第 3 次中立移除恰一次(频率闸死)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 210 | 1512 | "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round" | 封印桩:标中本轮 AC 加值(round 域,轮始清零)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 211 | 1516 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | 示警烟火:标中揭示 3×3 资源类型(不揭安危位置)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 212 | 1525 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | 疼痛记忆:攻击附加层值伤害且用后清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 213 | 1532 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | 保险丝反例:单次受伤<5 不攒点不触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 214 | 1537 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | 保险丝首点不出盾(2 点才出,预评估口径)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 215 | 1539 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | 见血开窍:踩雷揭示 1 格。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 216 | 1546 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | 陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 217 | 1551 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | 连环陷阱:同轮第 2 颗起每颗额外 +1 层(与疼痛记忆同账)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 218 | 1554 | "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕" | orch._hits_of(tb_hits, "blast_scaled").size() == 1 and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0 | 陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled 通道)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 219 | 1558 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | 血蜜:踩雷回复 N 生命。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 220 | 1561 | "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕" | orch._hits_of(ss_hits, "ac_bonus").size() == 1 and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3 | 石肤:受伤 AC+1(本场域 cap 3,封顶归累积器)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 221 | 1565 | "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty() | 背水:生命≤50% 减伤触发、过半血反例零减伤。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 222 | 1570 | "破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty() | 破封一击:挖开带封印危险格 +1 层、无封印反例零触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 223 | 1578 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | 弃图兑换:停手剩余 AP≥3 入账。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 224 | 1582 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | 弃图兑换:折为攻击伤害=剩余 AP 全额且用后清零。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 225 | 1583 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | 弃图兑换反例:剩余 AP<3 不触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 226 | 1587 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | 一滴不剩:上轮 AP 耗尽→本轮 +1 AP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 227 | 1588 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | 一滴不剩反例:上轮有余不发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 228 | 1595 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | 收盘:本轮开格≥8 轮末+1 GP。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 229 | 1598 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | 斩首学者:击杀揭示 1 格。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 230 | 1602 | "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕" | orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty() | 处刑台:敌血量≤30% 挂标记加伤、阈值外反例零命中。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 231 | 1606 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | 战地包扎:战斗胜利回复 N 生命(battle_won 挂点)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 232 | 1610 | "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk" | 血税:双向同局账本(踩陷阱减 max_hp/封印命中加 atk,战末汇入)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 233 | 1615 | "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 and orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty() | 淬毒刃纹(迁入):克制正面追加效果、中性面反例零触发。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 234 | 1619 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | 封印爆裂(迁入):标中全体敌群伤。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 235 | 1624 | "零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)" | orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty() and orch0._fx_emit("battle_won", {"round": 1}).is_empty() | 零持有订阅零命中(relic 关着零扰动,伤害管线既有行为不变)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 236 | 1632 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | 特性段空态隐藏(视图空态纪律)。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 237 | 1634 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | 特性段持有呈现:逐件行常驻可读。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | +| 238 | 1636 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | 特性卡文案表值化:desc 字段直读零硬编码映射。 | 迁单元测试(tests/unit/core/adventure_feats_test.gd) | | + +## S15 _smoke_layout_cell_formula(行 1703–1704 · 2 条 · 去向:单元测试 → tests/unit/core/board_gen_reward_test.gd · 施工组 G3) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 239 | 1703 | "L%d %dx%d 板网格高 %d ≤ 可用高 %d − 预留 24(格边 %dpx——下缘不贴边不裁切)" ↩ % [L, b.width, b.height, grid_h, int(avail.y), int(cell)] | grid_h <= int(avail.y) - 24 | 网格实高(含格间距)≤ 可用高−24 预留(下缘不贴边不裁切——真人试玩 bug⑥ 回归)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 240 | 1704 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | 格边长在 26–46px 档内。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | + +## S16 _smoke_type_glow(行 1799–1953 · 24 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 241 | 1799 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | 类型微光默认开启(呈现态布尔缺省 true)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 242 | 1800 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | 夹具全集合法九型时清单外登记为空(禁静默空光零误报)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 243 | 1802 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | 微光格集合与独立重算期望集恒等(可挖∩身份可查)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 244 | 1804 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | 夹具前提:英雄未开邻格≥5(驱动步数在册)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 245 | 1813 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | 已开格与出生展开区零微光。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 246 | 1829 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | 夹具前提:未开邻格派定≥5 型。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 247 | 1838 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | 九型→五组逐格映射正确(增益/治疗/穿刺/群伤/魔法)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 248 | 1851 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | 清单外 cell_type 显式登记进 errors(不静默)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 249 | 1852 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | 清单外格零微光。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 250 | 1854 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | 清单外格剔除后作用集=派定数−1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 251 | 1867 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | 封锁格(查询 null)零微光(天然惰性)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 252 | 1869 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | 封锁格零登记(errors 仍仅清单外一条)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 253 | 1871 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | 封锁格剔除后作用集=派定数−1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 254 | 1877 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | 封锁解除后作用集复位恒等(进出圈复亮走回)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 255 | 1883 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | off 态开关读数=false。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 256 | 1884 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | off 态作用集为空(基线保护面)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 257 | 1887 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | on 态开关读数=true(复现)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 258 | 1888 | "V7 off→on 作用集逐位复现(关前 %d 格/开后 %d 格,组号逐位一致)" ↩ % [ (snap_before_off.get("cells", {}) as Dictionary).size(), (snap_on.get("cells", {}) as Dictionary).size()] | _glow_map_equal(snap_on.get("cells", {}), snap_before_off.get("cells", {})) | off→on 作用集逐位复现(组号一致)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 259 | 1891 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | off→on 作用集与独立重算期望集恒等。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 260 | 1895 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | 微光开关零玩法写入(已开集/英雄位/封印数三面零扰动)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 261 | 1908 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | 换图 rebuild 后作用集按新板重算(零旧板残留)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 262 | 1910 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | 换图清清单外登记(rebuild 清零)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 263 | 1951 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | 同板同序列 on/off 双实例结算逐位一致(微光派生零写板态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 264 | 1953 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | 对照前提:同序列≥5 步(非空转)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S17 _smoke_reward_identity_unit(行 1986–2019 · 10 条 · 去向:单元测试 → tests/unit/core/board_gen_reward_test.gd · 施工组 G3) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 265 | 1986 | "roll_board 受理" | reward_economy.roll_board(eligible) | 资源格摇号 roll_board 受理。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 266 | 1987 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | 重复 roll_board 被拒(恰一次守卫)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 267 | 1998 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | 身份查询投影={reward_id,cell_type,tier} 且五型封闭。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 268 | 2000 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | 清单外坐标身份查询=null(封锁格无身份)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 269 | 2004 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | 同格重复 grant 幂等(同一快照)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 270 | 2005 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | 清单外坐标 grant 拒绝。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 271 | 2009 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | 清图批尾身份=clear_blast(clear_default 唯一活行)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 272 | 2014 | "清图奖励 power ∈ 表区间 [%d, %d](roll 值 %d——配置推导)" ↩ % [cb_range[0], cb_range[1], int(cs1.power)] | int(cs1.power) >= int(cb_range[0]) and int(cs1.power) <= int(cb_range[1]) | 清图奖励 power 落在表区间(配置推导)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 273 | 2016 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | grant_clear 同批次重调幂等。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 274 | 2019 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | 映射退役后 grant 拒绝(生命周期闭合)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | + +## S18 _smoke_seal_ap_exhausted(行 2034–2058 · 5 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 275 | 2034 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 封印回归战斗启动(Boss 行承载)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 276 | 2047 | "%d 次挖掘后恰剩 1 AP(phase=%s ap=%d)" ↩ % [ap_total - 1, str(settlement.action_economy.phase), settlement.action_economy.ap] | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == 1 | 表驱动次数挖掘后恰剩 1 AP。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 277 | 2051 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | 仍存在可挖目标(封印目标来源)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 278 | 2057 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | 末点 AP 封印被受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 279 | 2058 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | 末点封印触发轮末结算进入第 2 轮(playtest 缺陷②回归)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S19 _smoke_level(行 2069–2094 · 11 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 280 | 2069 | "关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | 关卡满配通关达成。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 281 | 2070 | "战斗节点数 = 5(战×4 + Boss)" | win.battles == 5 | 战斗节点数=5(战×4+Boss)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 282 | 2071 | "Boss 节点以 Dmax=4 解析(棋盘生成 第三轴登记约定)" | win.boss_depth == 4 | Boss 节点深度=4(第三轴登记约定)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 283 | 2072 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | 进 Boss 时血量携带在域内。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 284 | 2079 | "恢复点回复 = floor(生效max_hp×30%%) 与缺口的较小值(%d/%d,生效 max %d)" ↩ % [rest.rest_healed, expect, eff_max] | rest.rest_healed == expect | 恢复点回复=floor(生效上限×30%) 与缺口的较小值。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 285 | 2081 | "恢复点未到达(seed 42 hp+100 在恢复点前败北?%s)" ↩ % rest.result | false | 恢复点未到达时显式红(防夹具空转)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 286 | 2084 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | 裸装败北路径可达(Roguelike 基线难度)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 287 | 2085 | "败北节点记录在域内(节点 %d)" ↩ % lose.defeat_node_index | lose.defeat_node_index >= 0 and lose.defeat_node_index < 7 | 败北节点记录在域内。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 288 | 2089 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | 同种子两遍结算步骤流逐行相同(关卡回放确定性)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 289 | 2090 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | 关卡步骤流非空。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 290 | 2092 | "模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" ↩ % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)] | int(_db.hero.atk) == int(hero_base.atk) and int(_db.hero.max_hp) == int(hero_base.hp) \\ and int(_db.hero.armor_class) == int(hero_base.ac) | 模拟后主角表恢复基线(覆盖不泄漏到后续会话)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S20 _smoke_enemy_bands(行 2115–2143 · 4 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 291 | 2115 | "敌人表 20 行且键序 = 迁移表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 20 and got_keys == expect_keys | 敌人表 20 行且键序=迁移表序(行序即契约)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 292 | 2122 | "hobgoblin 行在 (1,battle,3) 留任随迁(缺口行不删不补——补齐归敌种内容批)〔fv12〕〔fv25〕" | int(hob.get("level_index", -1)) == 1 and String(hob.get("row_kind", "")) == "battle" \\ and int(hob.get("depth_band", -1)) == 3 | hobgoblin 缺口行在 (1,battle,3) 留任随迁(不删不补)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 293 | 2125 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | 缺口登记随行:表文件注释在位(缺口不静默)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 294 | 2142 | "三关 Boss 行各恰一行且 row_kind=boss、band=图 Boss 节点深度(%d/%d/%d%s)〔fv13〕" ↩ % [ _boss_depth(1), _boss_depth(2), _boss_depth(3), boss_detail] | boss_ok | 三关 Boss 行各恰一行且深度带=图 Boss 节点深度。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S21 _smoke_enemy_attack_type(行 2161–2205 · 4 条 · 去向:单元测试 → tests/unit/battle_settlement/roll_multiplier_test.gd · 施工组 G4) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 295 | 2161 | "敌方 %d 行 attack_type 全填 normal(迭代 24 N3 轴开列,越轨行%s)" ↩ % [ _db.enemy.rows.size(), at_detail if at_detail != "" else "无"] | at_ok | 敌方全部行 attack_type 全填 normal(轴开列)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 296 | 2201 | "敌方单位快照 attack_type 非空(spawn 注入面——迭代 24 N3)" | snap_ok | 敌方单位快照 attack_type 非空(spawn 注入面)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 297 | 2202 | "克制矩阵命中证词:同种子三甲型实测 heavy %d == light %d − 1 · ward %d == light %d + 1(迭代 24 N3 全局平移 −1/0/+1)" ↩ % [ t_heavy, t_light, t_ward, t_light] | matrix_found and t_light >= 2 | 克制矩阵命中证词:同种子三甲型实测 heavy=light−1、ward=light+1。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 298 | 2205 | "敌方伤害技单据载荷 attack_type 非空(= %s——第 9 参传值面,迭代 24 N3)" ↩ % payload_at | payload_at == "normal" | 敌方伤害单据载荷 attack_type 非空。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | + +## S22 _smoke_ac_swap_guard(行 2218–2320 · 12 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 299 | 2218 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | 状态表 defense_up 行已退役(无双轨)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 300 | 2220 | "ac_up 行在位(hero · +1 ac · 只续时不叠层——2026-09-20 主角增益续时批)" | not ac_row.is_empty() and String(ac_row.get("target_side", "")) == "hero" \\ and String(ac_row.get("unit", "")) == "ac" and int(ac_row.get("magnitude", 0)) == 1 \\ and String(ac_row.get("stack_policy", "")) == "refresh" | ac_up 行在位:主角侧+1AC 只续时不叠层。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 301 | 2228 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | 奖励表 defense 正向行 effect_id=ac_up(盾牌加护)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 302 | 2230 | "effect_pool_map 无 defense_up/ac_up 键(减伤不入池——AC 不经乘区)" | not _db.settlement.effect_pool_map.has("defense_up") and not _db.settlement.effect_pool_map.has("ac_up") | 效果池映射无 defense_up/ac_up 键(AC 不经乘区)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 303 | 2245 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | 免伤墙归零护栏:percent 行叠满不可触池底(BUG-0003 免疫类防回归)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 304 | 2248 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | 主角 AC 在 [0,30] 域内(暂定基准随表走)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 305 | 2265 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | 敌方 AC 行值在裁带宽内(杂鱼 0~5/重击手 8~9/Boss 8~12)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 306 | 2273 | "Boss AC 关卡台阶严格递增(%d < %d < %d)" ↩ % [int(boss_ac.get(1, 0)), int(boss_ac.get(2, 0)), int(boss_ac.get(3, 0))] | int(boss_ac.get(1, 0)) < int(boss_ac.get(2, 0)) and int(boss_ac.get(2, 0)) < int(boss_ac.get(3, 0)) | Boss AC 关卡台阶严格递增(L1= 0 and not l12_row.is_empty() and int(l12_row.get("hp", -1)) == 22 \\ and int(l12_row.get("armor_class", -1)) == 2 \\ and l12_pool == {"attack": 6, "charge": 2, "heavy": 2} | 熊地精首遇带降档锚定:hp22/ac2/池 attack6-charge2-heavy2 零改动。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 308 | 2311 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | 第一关相邻深度带总量比≤2.5(难度曲线护栏)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 309 | 2316 | "掷点参数拍板值在位(d20 / nat20 暴击 ×200%% / 下限 1 / attack_bonus 0 / armor_roll 流)" | int(_db.settlement.roll_faces) == 20 and int(_db.settlement.crit_roll) == 20 \\ and int(_db.settlement.crit_multiplier) == 200 and int(_db.settlement.damage_floor) == 1 \\ and int(_db.settlement.attack_bonus) == 0 and String(_db.settlement.armor_roll_stream) == "armor_roll" | 掷点参数拍板值在位(d20/nat20×2/下限 1/加成 0/专流)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 310 | 2320 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | 折扣档表拍板值在位(3 档边界与折扣无零档)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S23 _smoke_armor_roll(行 2339–2400 · 15 条 · 去向:单元测试 → tests/unit/battle_settlement/roll_multiplier_test.gd · 施工组 G4) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 311 | 2339 | "查档 AC%d 全掷点域分布(100/75/50/25 = %s)" ↩ % [cfg.ac, str([dist[100], dist[75], dist[50], dist[25]])] | tier_ok and dist == cfg.expect | 查档纯函数全掷点域分布逐位正确(AC10/AC12)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 312 | 2343 | "AC10 EV 锚:期望系数合计 %d/2000 = 期望减免 21.25%%(nat20 恒全额含内)" ↩ % ev_sum | ev_sum == 1575 | AC10 期望系数锚=1575/2000(期望减免 21.25%)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 313 | 2353 | "nat20 种子可达(seed=%d——主角先手首掷命中 %d)" ↩ % [crit_seed, int(_db.settlement.crit_roll)] | crit_seed > 0 | nat20 种子可达(主角先手首掷命中 20)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 314 | 2354 | "nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)" | int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll) and bool(crit_rows.get("crit", false)) | nat20 行留档 roll=20+crit 标记。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 315 | 2356 | "nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" ↩ % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)] | int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk) | nat20 普通分量×2(暴击只乘普通分量)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 316 | 2361 | "削弱重复授予 → 层数保持 1、剩余结算数重置 2(续时语义——结构性消饱和)" | int(weaken_probe.get("layers", -1)) == 1 and int(weaken_probe.get("remaining", -1)) == 2 | 削弱重复授予:层数保持 1、剩余结算数重置(续时语义)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 317 | 2363 | "削弱单层池读数 = 行值(enemy_output 池单层贡献——×2 叠满触底 ×0 不可达,免伤轮根治)" | int(weaken_probe.get("pool", -999)) == int(_db.status_effect_row("weaken").magnitude) | 削弱单层池读数=行值。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 318 | 2374 | "AC11 攻击拍逐拍判定(×%d 拍全部落档域 {100,75,50,25})" ↩ % tiers.size() | tiers_valid and tiers.size() >= 3 | AC11 攻击拍逐拍判定全落档域。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 319 | 2375 | "折扣档实际流动(AC11 实测档位 %s——≥2 档可见)" ↩ % str(tier_distinct.keys()) | tier_distinct.size() >= 2 | 折扣档实际流动(≥2 档可见)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 320 | 2392 | "全战斗流零 v=0 伤害行(伤害下限全局生效)" | all_positive | 全战斗流零 0 值伤害行(伤害下限全局生效)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 321 | 2393 | "hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)" | attack_ok | 攻击类伤害行掷点读数+档位随行留档(归因)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 322 | 2394 | "棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)" | board_ok | 棋盘来源伤害行零掷点字段(必中全额)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 323 | 2398 | "棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)" | DamageMath.counter_component(2, -1, df) == 1 | 棋盘克制直伤贴底下限 1。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 324 | 2399 | "克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)" | DamageMath.counter_component(1, -3, df) == 1 | 克制修正触底穿透下限 1。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 325 | 2400 | "空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)" | DamageMath.counter_component(0, 5, df) == 0 | 空桶不吃修正恒 0(边界不受下限影响)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | + +## S24 _smoke_pool_wiring(行 2481–2487 · 4 条 · 去向:单元测试 → tests/unit/battle_settlement/roll_multiplier_test.gd · 施工组 G4) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 326 | 2481 | "单分量基线在位(normal 分量 = 落地值、基线 %d——充能退役批)" ↩ % base_v | int(base_row.get("normal", -1)) == base_v and base_v > 0 | 单分量基线在位(normal 分量=落地值)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 327 | 2482 | "增伤经乘区加成合计(%d → %d = ×1.5——attack_up 直乘旁路退役,覆盖面=攻击合计)" ↩ % [base_v, int(up_row.get("value", -1))] | int(up_row.get("value", -1)) == int(floor(base_v * 1.5)) | 增伤经乘区合计×1.5(直乘旁路退役)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 328 | 2484 | "乏力经乘区削减合计(%d → %d = ×0.75)" ↩ % [base_v, int(sapped_row.get("value", -1))] | int(sapped_row.get("value", -1)) == int(floor(base_v * 0.75)) | 乏力经乘区削减×0.75。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 329 | 2486 | "硬化折半方向(%d → %d = ×0.5 且 < 基线——敌方防御池负值减伤,翻倍 bug 退役)" ↩ % [base_v, int(fortify_row.get("value", -1))] | int(fortify_row.get("value", -1)) == int(floor(base_v * 0.5)) and int(fortify_row.get("value", -1)) < base_v | 硬化折半方向×0.5 且<基线(翻倍 bug 退役)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | + +## S25 _smoke_board_generator(行 2514–2564 · 22 条 · 去向:单元测试 → tests/unit/core/board_gen_reward_test.gd · 施工组 G3) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 330 | 2514 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | 棋盘生成行解析 (L,0) 非空。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 331 | 2518 | "R1 ceil 解析 (2,3)→(2,4)(向上取整偏难)" | int(ceil_row.get("node_depth", -1)) == 4 | ceil 解析 (2,3)→(2,4)(无精确行向上取整偏难)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 332 | 2520 | "L%d 生成产物非空" ↩ % L | b1 != null | 生成产物非空。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 333 | 2523 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | 可清性成立(安全格 8 邻连通)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 334 | 2524 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row, _db) | 合法性全过。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 335 | 2531 | "L%d inferability %.3f %s target %.2f(accept_mode=%s)" ↩ % [L, inf, "≥" if inf >= float(row.inferability_target) else "<", float(row.inferability_target), mode] | inf >= float(row.inferability_target) or mode == "accept_best" | 出图验收:首过≥可推理目标或诚实降级 accept_best。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 336 | 2533 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | 生成器产物零变体格(变异布置归战斗初始化第 4 步)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 337 | 2534 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | 指标集输出降级标记与目标雷数。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 338 | 2535 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | 降级标记=(accept_mode==accept_best)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 339 | 2536 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | 非法产物标记=(accept_mode==accept_worst)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 340 | 2537 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | 降级/非法产物标记互斥。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 341 | 2538 | "L%d 产物断言:实际雷数 %d ≤ 目标雷数 N=%d(先布后删只减不增)" ↩ % [L, int(b1.metrics.actual_mines), int(b1.metrics.target_mines)] | int(b1.metrics.actual_mines) <= int(b1.metrics.target_mines) | 实际雷数≤目标雷数(先布后删只减不增)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 342 | 2539 | "L%d attempts_used ∈ [1,%d]" ↩ % [L, int(row.max_gen_attempts)] | b1.metrics.get("attempts_used", 99) >= 1 and b1.metrics.get("attempts_used", 99) <= int(row.max_gen_attempts) | 尝试次数在 [1,上限] 域内。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 343 | 2540 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | 非法产物标记=false(正常出图)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 344 | 2544 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | 同种子两次生成逐格相同(确定性)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 345 | 2546 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | 异种子产物不同(退化护栏)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 346 | 2552 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | r0 上限正式行投值在位(≥出生保障面 9)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 347 | 2553 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | 结构断言:出生展开数≤cap(重掷闸承载)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 348 | 2554 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | cap 拒绝计数指标在位。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 349 | 2555 | "L%d ring_removed=%d ≤ 剃雷域候选危险格数((3+2r)²−9——全域剃雷退役)" ↩ % [L, int(b1.metrics.get("ring_removed", 99))] | int(b1.metrics.get("ring_removed", 99)) <= (2 + int(row.get("shave_ring_radius", 1))) * (2 + int(row.get("shave_ring_radius", 1))) * 4 - 5 | 剃雷量≤域内候选危险格数(全域剃雷退役)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 350 | 2562 | "L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" ↩ % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS else ">9→illegal_product"] | b9 != null and (int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS or b9.illegal_product) | cap 压线 9:出图 R₀≤9 或诚实降级非法标记(玩家永不卡死)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 351 | 2564 | "L1 cap 压线出图指标集完整(accept_mode=%s / 闸触发 %d 次)" ↩ % [String(b9.metrics.get("accept_mode", "")), int(b9.metrics.get("r0_cap_rejected", 0))] | int(b9.metrics.get("r0_cap_rejected", 0)) >= 0 and String(b9.metrics.get("accept_mode", "")) != "" | cap 压线出图指标集完整(accept_mode+闸触发数)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | + +## S26 _smoke_variation_framework(行 2578–2603 · 9 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 352 | 2578 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | 变异清单恰 6 行(首批四类型+落石/矿尘)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 353 | 2579 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | 变异表自校验零错误(含 global 三互锁负样本)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 354 | 2583 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | L1 地牢候选池=荆棘丛+藤蔓缠绕(解锁+板型双闸)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 355 | 2587 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | L2 幽暗地域池=岩浆+烬覆。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 356 | 2588 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | 解锁门控:L1 不进幽暗地域行。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 357 | 2592 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | 板型门控:L2 地牢池被 board_types 交集排除岩浆/烬覆。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 358 | 2593 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | 清单外 variant_id 返回空(读取通道在位)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 359 | 2599 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | 互锁负样本①:flat_damage+placement=any 被拒。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 360 | 2603 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | 互锁负样本②:hide_number+trigger≠on_number_shown 被拒。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S27 _smoke_global_variant(行 2641–2749 · 30 条 · 去向:单元测试 → tests/unit/battle_settlement/variant_placement_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 361 | 2641 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | 互锁负样本③:resource_face+scope=region 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 362 | 2645 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | 互锁负样本④:global+hide_number 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 363 | 2649 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | 互锁负样本⑤:global+on_open 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 364 | 2653 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | 互锁负样本⑥:global+closed_only 被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 365 | 2662 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | 互锁负样本⑦:resource_face 值 51 越域被拒。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 366 | 2667 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | 门控三维:L2 boss 池=region 两行(贫矿脉屏蔽后无 global 行)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 367 | 2671 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | 门控三维:battle 节点静默跳过 global 行。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 368 | 2672 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | 缺省双参调用=global 行跳过(既有口径零扰动)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 369 | 2674 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | L1 Boss 免疫:解锁门控在 boss 节点同样生效。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 370 | 2675 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | 全局决议:battle 节点恒无全局型。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 371 | 2676 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | 全局决议:L2 boss 全局池空→空行(兜底回落承载)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 372 | 2677 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | 全局决议:L1 boss 全局池空→空行。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 373 | 2679 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | elite 前向词位:boss 同池恒空(词表位不造行)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 374 | 2685 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | L2 Boss 战斗启动受理(兜底回落断言载体)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 375 | 2691 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | 屏蔽后回落:Boss 图无整图全局型(合法空态)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 376 | 2696 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | 屏蔽后回落:Boss 图 region 变异恰一行且在候选池内。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 377 | 2698 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | 屏蔽后回落:Boss 图恢复 region 变异格布置。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 378 | 2699 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | 屏蔽后回落:variant_place 恢复取号。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 379 | 2701 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | 快照投影 board_variants 空态一致(横幅唯一读取面)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 380 | 2711 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | 同种子 Boss 图两遍布置/变异/取号逐位相同(region 布置路径确定性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 381 | 2717 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | 资源偏斜基线:正向池合计 77。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 382 | 2720 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | off 态零扰动:skew=0 行集与基线逐位相同。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 383 | 2723 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | 合计守恒:偏斜后正向池合计仍 77。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 384 | 2725 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | 白名单豁免:weaken/gp/action 三行偏斜后逐位不动。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 385 | 2727 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | 生存族下调构造例(heal 4→3、defense 11→7)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 386 | 2728 | "进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)" | wmap.get("attack_up", -1) == 15 and wmap.get("spark_aoe", -1) == 18 and wmap.get("pierce_strike", -1) == 9 and wmap.get("magic_bolt", -1) == 9 | 进攻族上浮构造例(等比上浮守恒)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 387 | 2733 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | 合成序:图级偏斜先乘、件级加算后加(合计 77+8)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 388 | 2734 | "合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)" | cw.get("heal_minor", -1) == 5 and cw.get("gp_coin", -1) == 4 and cw.get("pierce_strike", -1) == 11 and cw.get("magic_bolt", -1) == 11 | 合成序构造例逐行值正确。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 389 | 2738 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | 域上界 v=50:守恒成立且逐行权重恒正。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 390 | 2749 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | 同种子普通图布置/摇号两遍逐位相同(off 全链路确定性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | + +## S28 _smoke_variant_placement(行 2801–2873 · 9 条 · 去向:单元测试 → tests/unit/battle_settlement/variant_placement_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 391 | 2801 | "L%d 布置段战斗启动失败" ↩ % L | false | 布置段战斗启动失败显式红。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 392 | 2804 | "L%d 布置段板非空" ↩ % L | b != null | 布置段板非空。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 393 | 2825 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | 变异 id 全在候选池(门控一致性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 394 | 2826 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | 每图至多一种变异类型(单格单 id+全图同 id)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 395 | 2834 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | 变异格全在初始展开区外且不吃危险格。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 396 | 2841 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | 变异格数≤公式上限(可挖池百分比+关卡递增)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 397 | 2844 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | 公式量>0 时非零布置(极小池 round 至 0=合法零布置)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 398 | 2866 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | 全部变异连通片≤pack_max 格。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | +| 399 | 2873 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | 同种子变异布置逐格相同(variant_place 流确定性)。 | 迁单元测试(tests/unit/battle_settlement/variant_placement_test.gd) | | + +## S29 _smoke_full_battle(行 2900–2928 · 7 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 400 | 2900 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | 完整战斗在轮次上限内终局。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 401 | 2908 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | 同种子两遍结算步骤流逐行相同(战斗回放断言)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 402 | 2909 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | 同种子两遍事件流逐位相同。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 403 | 2910 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | 结算步骤流非空。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 404 | 2924 | "%s 步骤流含判定行" ↩ % label | has_verdict | 步骤流含判定行。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 405 | 2927 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | 主角先手攻击行必存在(先手必兑+地图账殿后)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 406 | 2928 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | 主角攻击行携带类型键与掷点读数(归因面)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S30 _run_battle(行 2960–2961 · 1 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 407 | 2960 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | 战斗收束后行动经济相位回 Idle(notify_combat_end 生效)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S31 _smoke_power_interval(行 3003–3055 · 5 条 · 去向:单元测试 → tests/unit/core/board_gen_reward_test.gd · 施工组 G3) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 408 | 3003 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | 区间行配对齐全且 power=floor 中点(配置推导)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 409 | 3039 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | 24 板全区间行快照 power 在区间内(含清图槽)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 410 | 3040 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | 定值行快照 power 恒等表值(区间批零外溢)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 411 | 3054 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | 区间行上下阶实际流动(≥2 样本须≥2 不同值)。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | +| 412 | 3055 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | 定值行(lo=hi)roll 恒等表值。 | 迁单元测试(tests/unit/core/board_gen_reward_test.gd) | | + +## S32 _smoke_active_refresh(行 3088–3144 · 17 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 413 | 3088 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 轮内刷新战斗启动(Boss 行多轮样本)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 414 | 3094 | "三轮停手攒 RAP=%d(round=%d rap=%d)" ↩ % [rap_banked, settlement.round_index, settlement.action_economy.rap] | settlement.round_index == 4 and settlement.action_economy.rap == rap_banked | 三轮停手攒 RAP=3×每轮 AP。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 415 | 3095 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | 第 4 轮进入 Active 相位。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 416 | 3105 | "轮内第 1 次刷新受理" | ok1 | 轮内第 1 次刷新受理。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 417 | 3106 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | 刷新后相位保持 Active、AP 保留不重置。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 418 | 3107 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | 刷新扣 RAP 价(表值)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 419 | 3108 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | 当场换新图(board_changed 事件+板对象更换)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 420 | 3114 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | 轮内第 2 次刷新受理。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 421 | 3120 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | 第 3 次刷新被拒(每轮兜底上限 2,RAP 足额仍拒)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 422 | 3122 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | 快照刷新计数=2(受理单源,main 副本已删)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 423 | 3124 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | 上限达门禁与快照计数同源。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 424 | 3136 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | 拒绝后挖至 AP 耗尽自动结算进第 5 轮。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 425 | 3137 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | 跨图 facts 完整兑现(safe_cell 行数==事件数)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 426 | 3138 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | 跨图挖掘 safe 事件≥3(断言非空转)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 427 | 3139 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | 结算段内(Waiting 相位)刷新被拒。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 428 | 3143 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | 轮内刷新回放:同种子两遍步骤流逐行相同。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 429 | 3144 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | 轮内刷新步骤流非空。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S33 _smoke_difficulty_table(行 3173–3192 · 8 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 430 | 3173 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | 难度表 MVP 空表加载(结构位)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 431 | 3176 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | 空表下 spawn 受理。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 432 | 3179 | "空表零修饰(hp=%d atk=%d = 行值 %d/%d)" ↩ % [int(unit.hp), int(unit.atk), int(d0.hp), int(d0.atk)] | int(unit.hp) == int(d0.hp) and int(unit.atk) == int(d0.atk) | 空表零修饰:敌值=行值原样。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 433 | 3183 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | 修饰行注入后 spawn 受理。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 434 | 3185 | "hp 修饰 ×1.5(%d→%d,max_hp 同步)" ↩ % [int(d0.hp), int(g2.hp)] | int(g2.hp) == int(round(float(d0.hp) * 1.5)) and int(g2.max_hp) == int(g2.hp) | hp 修饰 ×1.5 且 max_hp 同步。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 435 | 3186 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | atk 修饰 ×2。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 436 | 3191 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | 越域修饰 spawn 受理。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 437 | 3192 | "越域乘数 clamp 至 10(%d→%d)" ↩ % [int(d0.hp), int(enemy_system_c.all_enemies()[0].hp)] | int(enemy_system_c.all_enemies()[0].hp) == int(d0.hp) * 10 | 越域乘数 clamp 至 10。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S34 _smoke_damage_types(行 3202–3229 · 10 条 · 去向:单元测试 → tests/unit/battle_settlement/roll_multiplier_test.gd · 施工组 G4) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 438 | 3202 | "goblin 护甲 = light(轻甲——教学位)" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) and String(enemy_system.all_enemies()[0].armor_type) == "light" | 地精护甲=轻甲(教学位)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 439 | 3204 | "bugbear 护甲 = light(护甲类型重配批 2026-09-18:关卡 1 全轻甲)" | enemy_system_b.spawn(1, 2, int(_db.hero.max_hp)) and String(enemy_system_b.all_enemies()[0].armor_type) == "light" | 熊地精护甲=轻甲(第 1 关全轻甲)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 440 | 3206 | "ogre_chief 护甲 = light(Boss 行集——护甲类型重配批 2026-09-18:关卡 1 全轻甲)" | enemy_system_c.spawn(1, 4, int(_db.hero.max_hp), [], -1, -1, [], "", "boss") and String(enemy_system_c.all_enemies()[0].armor_type) == "light" | 食人魔首领护甲=轻甲。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 441 | 3209 | "普通行:轻甲 0 / 魔法护甲 +1 / 重甲 −1(加法核)" | int(am.normal.light) == 0 and int(am.normal.ward) == 1 and int(am.normal.heavy) == -1 | 克制矩阵普通行表值在位(0/+1/−1 加法核)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 442 | 3210 | "穿刺行:轻甲 −1 / 魔法护甲 +1 / 重甲 0 中性(用户拍板)" | int(am.pierce.light) == -1 and int(am.pierce.ward) == 1 and int(am.pierce.heavy) == 0 | 克制矩阵穿刺行表值在位。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 443 | 3211 | "魔法行:轻甲 0 / 魔法护甲 −1 / 重甲 +1(加法核)" | int(am.magic.light) == 0 and int(am.magic.ward) == -1 and int(am.magic.heavy) == 1 | 克制矩阵魔法行表值在位。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 444 | 3224 | "标错封印产生 seal_miss 归因行(×%d)" ↩ % miss_rows | miss_rows >= 1 | 标错封印产生 seal_miss 归因行。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 445 | 3225 | "标错封印兑付该安全格资源的负向字典行(×%d——attack/defense/weaken 效果行或 heal 直伤行)" ↩ % negative_rows | negative_rows >= 1 | 标错封印兑付安全格资源负向字典行。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 446 | 3226 | "封印命中兑付该危险格资源的正向字典行(×%d——充能退役后无魔法分量,正向=格面资源)" ↩ % hit_rows | hit_rows >= 1 | 封印命中兑付危险格资源正向字典行。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 447 | 3229 | "标错惩罚轮回放断言:同种子步骤流逐行相同(类型化负向兑付确定性)" | JSON.stringify(run1.rows) == JSON.stringify(run2.rows) | 标错惩罚轮回放:同种子步骤流逐行相同。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | + +## S35 _smoke_seal_irreversible_quadrants(行 3294–3388 · 21 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 448 | 3294 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 四情况场景 A 开局受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 449 | 3297 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | 场景 A 找到安全格封印目标。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 450 | 3301 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | 场景 A 封印受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 451 | 3303 | "四情况 A:seal_pull 出目录拒绝(撤封印退役)" | not pull_out.is_empty() and not bool(pull_out[0].get("accepted", true)) and String(pull_out[0].get("sub_reason", "")) == "out_of_catalog" | 场景 A:撤封印动作出目录拒绝(不可逆)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 452 | 3306 | "四情况 A:重复封印仍被拒(seal_present)" | not dup_place.is_empty() and not bool(dup_place[0].get("accepted", true)) and String(dup_place[0].get("sub_reason", "")) == "seal_present" | 场景 A:重复封印仍被拒(seal_present)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 453 | 3309 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | 场景 A:封印格直接挖开被受理(不须先撤)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 454 | 3310 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | 场景 A:封印随挖掘隐式退役。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 455 | 3325 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | 场景 A:标空判定零兑付(封印已擦除不入轮末基线)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 456 | 3326 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | 场景 A:安全格正向兑付作废(零奖励)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 457 | 3327 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | 场景 A:兑付作废归因行恰一次(零惩罚零奖励留痕)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 458 | 3334 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 四情况场景 B 开局受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 459 | 3337 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | 场景 B 找到危险格封印目标。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 460 | 3341 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | 场景 B 封印受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 461 | 3343 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | 场景 B:封印危险格直接挖开受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 462 | 3344 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | 场景 B:危险格已揭示(挖雷本体照常)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 463 | 3356 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | 场景 B:挖雷惩罚照常(只受一层惩罚)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 464 | 3357 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | 场景 B:标中不发(封印已擦除不判)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 465 | 3364 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 四情况场景 C 开局受理。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 466 | 3368 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | 场景 C:安全格+危险格封印目标齐备。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 467 | 3387 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | 场景 C:标空惩罚照常(归因+负向兑付)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 468 | 3388 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | 场景 C:标中奖励照常。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S36 _smoke_playtest_logger(行 3402–3435 · 8 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 469 | 3402 | "会话文件开启(%s)" ↩ % path | path.contains("playtest_20260912") | 试玩日志会话文件开启(文件名含种子)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 470 | 3403 | "快照战斗启动" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | 快照战斗启动。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 471 | 3407 | "每结算一快照(%d 次结算 → %d 快照)" ↩ % [2, snaps.size()] | snaps.size() == 2 | 每结算恰一快照(2 结算→2 快照)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 472 | 3414 | "快照字段齐备(缺 %s)" ↩ % str(missing) | missing.is_empty() | 快照必需字段齐备(12 键零缺)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 473 | 3415 | "快照含英雄/经济/板序号(板 seq=%d)" ↩ % int(s1.board.seq) | int(s1.hero.max_hp) == int(_db.hero.max_hp) and s1.economy.has("phase") and s1.board.has("seq") | 快照含英雄/经济/板序号。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 474 | 3418 | "快照 spawn_count 在位且 == unit_count(无击杀轮:%d/%d——全灭/空场可判别)" ↩ % [int(s1.get("spawn_count", -1)), int(s1.get("unit_count", -1))] | int(s1.get("spawn_count", -1)) >= 1 and int(s1.get("spawn_count", -1)) == int(s1.get("unit_count", -2)) | 快照 spawn_count 在位且等于单位数(无击杀轮全灭/空场可判别——Bug#1 回归)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 475 | 3422 | "诊断转储文件生成(%s)" ↩ % diag | diag.contains("diag_") | 诊断转储文件生成。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 476 | 3435 | "JSONL 全部行可解析(%d 行 = session + 2 结算)" ↩ % lines | lines >= 3 and parse_ok | JSONL 全部行可解析(会话+2 结算)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S37 _smoke_encounter_roll(行 3448–3506 · 8 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 477 | 3448 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | 遭遇扫描 20 种子战斗启动(quiet 计数档)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | true | +| 478 | 3451 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | D0 区间 [1,2] 两值均出现。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 479 | 3457 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | 全部遭遇计数在域内 [1,2]。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 480 | 3461 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | 同种子遭遇骰可回放。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 481 | 3466 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | D1 战斗启动。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 482 | 3468 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | D1 区间 [2,3] 域内。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 483 | 3481 | "熊地精首遇带(1,battle,%d——扫表取最低带)固定 1 不加骰〔fv23〕" ↩ % bugbear_lead_band | settlement_d2.presentation_snapshot().enemies.size() == 1 \\ and int(bugbear_lead_row.get("unit_count", -1)) == 1 and not bugbear_lead_row.has("unit_count_max") | 熊地精首遇带固定 1 不加骰(教学位)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 484 | 3506 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | 全深度×12 种子 spawn 全域内且≥1(空遭遇不可达——Bug#1 回归)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S38 _smoke_attack_chain_fresh_pool(行 3576–3590 · 3 条 · 去向:单元测试 → tests/unit/battle_settlement/roll_multiplier_test.gd · 施工组 G4) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 485 | 3576 | "fresh 池回归战斗启动(Boss 行)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | fresh 池回归战斗启动(Boss 行)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 486 | 3582 | "非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" ↩ % ticks.size() | ticks.size() >= 2 | 非施放攻击拍采样充足(≥2 拍)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | +| 487 | 3590 | "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)" | all_full | fresh 池攻击拍伤害=掷点档位足额(池空不吞伤——Bug#2 回归)。 | 迁单元测试(tests/unit/battle_settlement/roll_multiplier_test.gd) | | + +## S39 _smoke_clear_reward_chain(行 3616–3629 · 5 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 488 | 3616 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | 完美封印清图达成(真实流)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 489 | 3623 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | 清图奖励双份真实兑付(两发 clear_reward 伤害行)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 490 | 3625 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | 第二份按身份类型走真实兑付(首份/第二份显示名区分)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 491 | 3627 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | 高光行 value=实际有效份数 2(不记名义值)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 492 | 3629 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | 高光标记=perfect_sweep。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S40 _smoke_clear_reward_zero_paths(行 3643–3690 · 11 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 493 | 3643 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | 零兑付构造战斗启动。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 494 | 3648 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | 清图槽身份=blast 型(clear_blast)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 495 | 3652 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | 无存活敌时首份零兑付 first_ok=false。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 496 | 3653 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | 零兑付产出未生效归因行。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 497 | 3661 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | 高光守卫构造战斗启动。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 498 | 3669 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | 守卫路径重走首份→未生效行仍在案。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 499 | 3671 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | 首份零兑付→高光行不发(不记名义值)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 500 | 3678 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | 第二份被拒构造战斗启动。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 501 | 3686 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | 首份 aoe 真实兑付(击杀唯一存活敌)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 502 | 3687 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | 第二份被拒产出未生效行(零兑付)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 503 | 3690 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | 高光行 value=实际有效份数 1。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S41 _smoke_refresh_provenance_guard(行 3698–3709 · 5 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 504 | 3698 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | 轮内刷新受理(三轮攒 RAP 后发起)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 505 | 3699 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | 刷新图清图达成。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 506 | 3707 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | 刷新图清图零 clear_reward 行(来源守卫在位)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 507 | 3708 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | 刷新图清图零高光行。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 508 | 3709 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | 对照基线:常规清图有 clear_reward 行(守卫非全局静默)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S42 _smoke_polarity_routing(行 3750–3853 · 12 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 509 | 3750 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | 极性路由构造种子可达(行为面重锚)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 510 | 3751 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | 构造触达:标中真雷+标空安全格均入队。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 511 | 3752 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | 构造触达:挖到危险格≥1。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 512 | 3753 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | 构造触达:挖到安全格≥1。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 513 | 3770 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | 挖安全格只出正向兑付(效果行∈正向字典+每格≥1 行)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 514 | 3787 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | 挖危险格只出负向兑付(效果行∈负向字典+每格≥1 行)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 515 | 3796 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典)" | hit_ok | 封印命中→该危险格正向兑付行(origin 指回格,效果∈正向字典)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 516 | 3802 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | 封印溢价配置在位(125 provisional)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 517 | 3830 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | 标中兑付逐型溢价=同型正向×1.25。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 518 | 3836 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | 标空惩罚零溢价(负向直伤=固定表值原值)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 519 | 3847 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | 标空封印→归因行+安全格负向兑付(同格同批)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 520 | 3853 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | 封印判定兑付仅两封印在案(撤封印抵消过滤零泄漏)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S43 _smoke_negative_dictionary(行 3916–3998 · 8 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 521 | 3916 | "sapped 行在位(hero · −25 percent · dur 1 · max 16 · stack——weaken 型负向)" | not sapped.is_empty() and String(sapped.get("target_side", "")) == "hero" \\ and int(sapped.get("magnitude", 0)) == -25 and int(sapped.get("duration_settlements", 0)) == 1 \\ and int(sapped.get("max_stack", 0)) == 16 and String(sapped.get("stack_policy", "")) == "stack" | sapped 状态行在位(−25% 持续 1 上限 16 可叠)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 522 | 3920 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | 效果池映射收编 sapped→hero_offense(attack_up 镜像池)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 523 | 3958 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | 6 板扫描内出现五型齐备板。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 524 | 3968 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | attack/defense/weaken 型负向→对应效果行(来源指回该格)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 525 | 3981 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | heal 型负向→主角直伤表值(不经增益状态)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 526 | 3991 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | blast 型负向→警觉状态在位(敌 AC+1)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 527 | 3996 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | action 型负向→下轮行动点 −1(排队)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 528 | 3998 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | action 型正负抵消→排队净值归零。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S44 _smoke_resource_surface(行 4024–4044 · 5 条 · 去向:单元测试 → tests/unit/battle_settlement/seal_polarity_test.gd · 施工组 G5) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 529 | 4024 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | chain/dig 揭示事件恒携带资源标记(雷安同池)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 530 | 4025 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | initial 揭示事件(封锁格)资源标记恒 false。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 531 | 4026 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | 危险格揭示携带资源(×resource=true≥1)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 532 | 4042 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | 封锁格(出生区)身份查询=null。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | +| 533 | 4043 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | 未开危险格身份恒可查(秘宝桥接前提)。 | 迁单元测试(tests/unit/battle_settlement/seal_polarity_test.gd) | | + +## S45 _smoke_settlement_segments(行 4050–4115 · 5 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 534 | 4050 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | 结算五段序冻结在位(表值=拍板序恒等)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 535 | 4061 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | 行型封闭 10 类:已删五类零残留。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 536 | 4088 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | 每轮行相位单调(授予→增益减益→战斗→衰减→判定)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 537 | 4114 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | 案C 段内序:地图伤害账殿后(先手→敌方→地图账)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 538 | 4115 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | 衰减/判定段每结算恰一段。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S46 _smoke_skill_pool_data(行 4233–4406 · 27 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 539 | 4233 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | 技能表恰 14 行(三批+Boss 半血解锁技+普通敌错峰两行)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 540 | 4235 | "普攻行:倍率恒 1 · 冷却恒 0(域校验锁死——池恒非空的结构前提)" | not basic.is_empty() and String(basic.type) == "basic" and int(basic.cooldown_beats) == 0 and (not basic.has("damage_mult") or is_equal_approx(float(basic.damage_mult), 1.0)) | 普攻行:倍率恒 1、冷却恒 0(池恒非空的结构前提)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 541 | 4239 | "突刺行(伤害 ×1.5 · 冷却 2——提案 §2.5 建议池新行)" | not lunge.is_empty() and is_equal_approx(float(lunge.damage_mult), 1.5) and int(lunge.cooldown_beats) == 2 | 突刺行(伤害 ×1.5、冷却 2)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 542 | 4242 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | 蓄力行=纯攻击增益技(buff_ref 指向 empower)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 543 | 4245 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | 撕咬行(伤害 ×1.5、冷却 1)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 544 | 4248 | "毒爆行(伤害 ×2.5 · 冷却 4 · 必中附加 ×0.75——蛛化卓尔精灵专属池独占技,新效应词汇批)" | not venom.is_empty() and is_equal_approx(float(venom.damage_mult), 2.5) and int(venom.cooldown_beats) == 4 and is_equal_approx(float(venom.rider_mult), 0.75) | 毒爆行(伤害 ×2.5、冷却 4、必中附加 ×0.75)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 545 | 4256 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | 毒爆独占性:仅蛛化卓尔精灵池引用。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 546 | 4259 | "蛛网束缚行(妨害型 · buff_ref → off_guard 失衡 · duration 2 = 1 有效拍 · 冷却 4——新效应词汇批首发)" | not snare.is_empty() and String(snare.type) == "debuff" and String(snare.buff_ref) == "off_guard" and int(snare.buff_duration) == 2 and int(snare.cooldown_beats) == 4 | 蛛网束缚行(妨害型、指向失衡、有效 1 拍、冷却 4)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 547 | 4267 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | 蛛网束缚独占性:仅蛛化卓尔精灵池引用。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 548 | 4269 | "失衡状态行(hero · ac 轴——妨害技落点,ConfigDB side 守卫正例)" | not off_guard_row.is_empty() and String(off_guard_row.target_side) == "hero" and String(off_guard_row.unit) == "ac" | 失衡状态行在位(主角侧 ac 轴——妨害技落点)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 549 | 4273 | "毒吐行(伤害 ×1.6 · 冷却 2 · 无 rider——低 atk 下 rider 恒被 damage_floor 兜成 1 白占字段,迭代 24 N2)" | not spit.is_empty() and String(spit.display_name) == "毒吐" and String(spit.type) == "damage" \\ and is_equal_approx(float(spit.damage_mult), 1.6) and int(spit.cooldown_beats) == 2 and not spit.has("rider_mult") | 毒吐行(伤害 ×1.6、冷却 2、无附加分量)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 550 | 4277 | "盾击行(伤害 ×1.7 · 冷却 3 · 无 rider——迭代 24 N2)" | not bash.is_empty() and String(bash.display_name) == "盾击" and String(bash.type) == "damage" \\ and is_equal_approx(float(bash.damage_mult), 1.7) and int(bash.cooldown_beats) == 3 and not bash.has("rider_mult") | 盾击行(伤害 ×1.7、冷却 3、无附加分量)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 551 | 4291 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | 毒吐投放面:仅巨蜘蛛族首遇带以深的行。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 552 | 4292 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | 盾击投放面:仅第 3 关灰矮人族。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 553 | 4300 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | 巨蜘蛛首遇带行池恒 attack6/bite2/charge1(教学位零新招)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 554 | 4337 | "二十行九种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2+unlock_hp_pct 50——迭代14④ T5〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕,其余带键仍恰一行)" | pools_ok | 20 行 9 种池模板逐行=建议池行值(四元键精确比对)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 555 | 4347 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | 每池恰一行普攻(空池不可能的结构前提)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 556 | 4364 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | 逐池增益≤1 且妨害≤1(池构成纪律投放后复查)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 557 | 4369 | "前置:卓尔 Boss 战受理(2,9——解锁技 venom_curb 门槛 50)" | sett_u.begin_battle({"level_index": 2, "node_depth": 9, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | 卓尔 Boss 战受理(半血解锁技载体)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 558 | 4372 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | Boss 血量被压到阈值 65(=半血线)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 559 | 4378 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | 解锁归因行恰一次(闩去重——二次检查零行)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 560 | 4380 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | 解锁行文本格式正确(一次性技能名 toast 路由键)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 561 | 4382 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | 解锁行型=highlight 既有行型(行协议封闭零新增)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 562 | 4396 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | 全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 563 | 4400 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | 蓄力状态行 empower 在位(敌方输出 ×2)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 564 | 4402 | "硬化状态行 fortify(enemy −50 percent——敌方防御池负值 = 主角攻击折半;+100 符号 bug 2026-09-18 纠正,provisional 归 状态层重设计批)" | not fortify.is_empty() and String(fortify.target_side) == "enemy" and int(fortify.magnitude) == -50 | 硬化状态行 fortify 在位(敌方防御 −50%)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 565 | 4404 | "effect_pool_map 收编 empower/fortify(敌方防御池 enemy_defense 入表)" | String(_db.settlement.effect_pool_map.get("empower", "")) == "enemy_output" \\ and String(_db.settlement.effect_pool_map.get("fortify", "")) == "enemy_defense" | 效果池映射收编 empower/fortify(enemy_output/enemy_defense)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S47 _smoke_skill_pool_replay_independence(行 4419–4426 · 2 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 566 | 4419 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | 多实例种子可达(D3 遭遇≥2 实例)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 567 | 4425 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | 同种子回放各实例摇定动作序列逐位相同。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S48 _smoke_skill_pool_ev_anchor(行 4500–4500 · 1 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 568 | 4500 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | 三敌种 EV 抽样实测落在登记带宽内(5000 拍含保底闸长期速率)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S49 _smoke_skill_pool_death_void(行 4517–4538 · 5 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 569 | 4517 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | 死亡打断种子可达(D2 首拍摇中特殊技)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 570 | 4523 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | 死亡打断战斗启动受理(主角一击必杀构造)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 571 | 4527 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | 开轮已摇定本拍动作(待发非空)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 572 | 4533 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | 先手击杀后第 1 轮零敌方行动行(摇定技能作废=死亡不动手)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 573 | 4537 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | 作废不进账:全池冷却 0 且保底计数 0。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S50 _smoke_skill_pool_validation_reject(行 4546–4587 · 13 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 574 | 4546 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | 加载校验拒绝:池条目技能 id 悬空致命。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 575 | 4549 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | 加载校验拒绝:池普攻行数≠1(空池不可能前提)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 576 | 4555 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | 加载校验拒绝:池增益技>1(每池增益≤1)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 577 | 4558 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | 加载校验拒绝:普攻配倍率≠1(域校验锁死)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 578 | 4560 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | 加载校验拒绝:普攻配冷却≠0(域校验锁死)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 579 | 4562 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | 加载校验拒绝:伤害倍率越域 [1.1,16]。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 580 | 4565 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | 加载校验拒绝:池权重≤0。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 581 | 4567 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | 加载校验拒绝:空池(禁)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 582 | 4573 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | 加载校验拒绝:池妨害技>1(每池妨害≤1)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 583 | 4576 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | 加载校验拒绝:妨害技缺 buff_ref。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 584 | 4578 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | 加载校验拒绝:必中附加分量越域 (0,1]。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 585 | 4580 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | 加载校验拒绝:非伤害技带必中附加分量。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 586 | 4586 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | 加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S51 _smoke_debuff_rider(行 4660–4662 · 3 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 587 | 4660 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | 毒爆必中附加分量种子可达(伤害行样本在案)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 588 | 4661 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | 毒爆伤害行逐拍精确复算:终值=掷点主分量+必中附加(rider 键只随授权行出现)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 589 | 4662 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | 蛛网束缚落主角:施放后掷点阈值=基准 AC+失衡幅度(端到端)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S52 _smoke_level_graph(行 4671–4943 · 54 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 590 | 4671 | "LevelTable 图合法性校验:L1 单链 + L2 图同套算法零错误" | _db.level.validate().is_empty() | 关卡表图合法性校验零错误(L1 单链与 L2 图同套算法)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 591 | 4677 | "L2 节点数 = 10(入口 1 + 中间三层 2/3/2 + 商店房 + Boss——商店房批 Boss 重编号 8→9)" | not l2.is_empty() and _level_active_nodes(2).size() == 10 | L2 节点数=10(入口+三层中间+商店房+Boss)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 592 | 4684 | "L2 普通战斗节点 = 6 + Boss 恒一(编号最大恒终点)" | int(tcount.battle) == 6 and int(tcount.boss) == 1 | L2 普通战斗节点=6 且 Boss 恒一。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 593 | 4685 | "L2 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)+ 商店房节点 1(商店房批)" | int(tcount.reward) == 1 and int(tcount.rest) == 1 and int(tcount.shop) == 1 | L2 恢复/奖励/商店房节点各 1。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 594 | 4686 | "L2 真实岔路 = %d 处(≥2)" ↩ % branch | branch >= 2 | L2 真实岔路≥2 处。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 595 | 4692 | "图校验正例:两岔合流小图零错误" | lt.validate().is_empty() | 图校验正例:两岔合流小图零错误。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 596 | 4696 | "图校验拒绝:节点编号重复(关卡内唯一定位地址)" | _has_err_category(lt.validate(), "dup_key") | 图校验拒绝:节点编号重复。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 597 | 4700 | "图校验拒绝:第二无入边节点(入口唯一违例——入口节点数 = 3)" | _has_err_category(lt.validate(), "structural") | 图校验拒绝:第二无入边节点(入口唯一违例)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 598 | 4705 | "图校验拒绝:Boss 带出边(终点不可外出)" | _has_err_category(lt.validate(), "structural") | 图校验拒绝:Boss 带出边(终点不可外出)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 599 | 4709 | "图校验拒绝:回边 1→0(连线只朝前进方向 ⇒ 无环)" | _has_err_category(lt.validate(), "structural") | 图校验拒绝:回边(连线只朝前进方向⇒无环)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 600 | 4713 | "图校验拒绝:出边指向不存在编号(悬空致命)" | _has_err_category(lt.validate(), "dangling_next") | 图校验拒绝:出边指向不存在编号(悬空致命)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 601 | 4720 | "图校验拒绝:孤儿节点(从入口不可达 / 双入口同体违例)" | _has_err_category(orphan_errs, "structural") | 图校验拒绝:孤儿节点(入口不可达)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 602 | 4724 | "L2·满配 min 路径通关(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | L2 满配 min 路径通关。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 603 | 4725 | "L2 Boss 以编号 9 解析(终点编号最大——商店房批重编号 8→9)" | win_min.boss_depth == 9 | L2 Boss 以编号 9 解析(终点编号最大)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 604 | 4726 | "min 路径 = 0→1→3(休整)→7→8(商店零购离开)→9(实测 %s——T17 出度≤2 拍板模板 0 重建)" ↩ % str(win_min.visited_ids) | win_min.visited_ids == [0, 1, 3, 7, 8, 9] | min 路径行进序列逐位正确(存储拍板模板)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 605 | 4727 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | 选路口发生≥3 次(每节点兑付后零自动推进)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 606 | 4735 | "L2·满配 max 路径(max 取最大编号候选)通关 = 0→2(奖励)→6→7→8(商店零购离开)→9(存储种子 %d——T17 出度≤2 拍板模板 0 重建)" ↩ % MAX_PATH_SEED | win_max.result == "victory" and win_max.visited_ids == [0, 2, 6, 7, 8, 9] | max 路径通关且行进序列逐位正确(存储种子)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 607 | 4742 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;template_pick=0 固定现行图——精确选路断言)" | runner.start_level(2, 42, {}, 0) | 手动驱动 L2 启动受理(固定模板 0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 608 | 4749 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | 首战胜利后释放战斗并进选路态(不再自动推进)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 609 | 4751 | "入口岔路候选 = [1, 2](T17 出度≤2 拍板模板 0 宽廊双干——层宽 4 上限随出度约束)" | runner.next_candidate_ids() == [1, 2] | 入口岔路候选=[1,2](出度≤2 拍板模板)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 610 | 4752 | "候选外编号拒绝且仍待选(拒绝语义)" | not runner.pick_next_node(8) and runner.awaiting_choice() | 候选外编号拒绝且仍待选(拒绝语义)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 611 | 4753 | "选编号 2(奖励点)受理" | runner.pick_next_node(2) | 选编号 2(奖励点)受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 612 | 4754 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | 到达奖励点直通兑付(确认前不出选路口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 613 | 4756 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = [5, 6]" | runner.continue_after_node() and runner.awaiting_choice() and runner.next_candidate_ids() == [5, 6] | 兑付确认后才出选路口且候选=[5,6]。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 614 | 4758 | "选编号 6(巨蜘蛛巢群)开战" | runner.pick_next_node(6) and runner.current_battle() != null | 选编号 6 开战。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 615 | 4783 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | L2 首遇位完美档 12 种子恒零踩雷。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 616 | 4784 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | L2 首遇战胜局≥7/12(多数胜口径)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 617 | 4785 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | L2 首遇战胜后 HP 下探读数>0(绿档记录)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 618 | 4801 | "跨表行完整:L2 全部战斗/Boss 节点在 board_gen + enemy 精确行集(7 节点——带键 (2,row_kind,depth_band))" | cross_ok | L2 全部战斗/Boss 节点跨表行集完整。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 619 | 4809 | "巨蜘蛛首遇带(2,battle,%d——扫表取最低带)行集 = {giant_spider} 恰一行且 unit_count=2 无 unit_count_max(固定 2 只——首遇教学位不加骰)〔fv22〕" ↩ % l2_spider_band | l2_spider_band >= 0 and l2_spider_ids.has("giant_spider") and l2_spider_ids.size() == 1 and l2_spider_set.size() == 1 \\ and int(l2_spider_set[0].get("unit_count", -1)) == 2 and not l2_spider_set[0].has("unit_count_max") | 巨蜘蛛首遇带行集=恰一行固定 2 只不加骰。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 620 | 4816 | "蛛化卓尔精灵 Boss 行集(2,boss,%d)= {drider} 恰一行且首行魔法甲(护甲类型重配批 2026-09-18:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % _boss_depth(2) | l2_boss_ids.has("drider") and l2_boss_ids.size() == 1 and String(l2_boss_set[0].get("armor_type", "")) == "ward" | 蛛化卓尔 Boss 行集=恰一行且魔法甲。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 621 | 4824 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | 首批混编行集 (2,battle,5) 双行且行序正确。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 622 | 4829 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | 混编行集静态最坏骰和≤4(实例总数守卫)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 623 | 4831 | "首批混编新行 (2,battle,5,giant_spider) 行值 = 复制巢群行(P4 甲零数值新增——2-3 只/hp 26/atk [1,2]/light/ac 2;既有 19 行现值零变更)" | int(l2_mix_set[1].get("unit_count", 0)) == 2 and int(l2_mix_set[1].get("unit_count_max", 0)) == 3 \\ and int(l2_mix_set[1].get("hp", 0)) == 26 and int(l2_mix_set[1].get("armor_class", -1)) == 2 | 混编新行行值=复制巢群行(零数值新增)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 624 | 4840 | "L3 节点数 = 10(入口 1 + 中间三层 2/3/2 + 商店房 + Boss——L2 同模板 + 商店房批)" | not l3.is_empty() and _level_active_nodes(3).size() == 10 | L3 节点数=10(L2 同模板)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 625 | 4847 | "L3 普通战斗节点 = 6 + Boss 恒一(编号最大恒终点)" | int(tcount3.battle) == 6 and int(tcount3.boss) == 1 | L3 战斗节点=6 且 Boss 恒一。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 626 | 4848 | "L3 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)+ 商店房节点 1(商店房批)" | int(tcount3.reward) == 1 and int(tcount3.rest) == 1 and int(tcount3.shop) == 1 | L3 恢复/奖励/商店房节点各 1。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 627 | 4849 | "L3 真实岔路 = %d 处(0→[1,2,3,4,5] · 2→[6,7] · 5→[6,7]——U2 返工批五岔环廊层宽 5)" ↩ % branch3 | branch3 == 3 | L3 真实岔路=3 处(五岔环廊层宽 5)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 628 | 4859 | "跨表行完整:L3 全部战斗/Boss 节点在 board_gen + enemy 精确行集(7 节点——带键 (3,row_kind,depth_band))" | cross_ok3 | L3 全部战斗/Boss 节点跨表行集完整。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 629 | 4866 | "灰矮人首遇带(3,battle,%d——扫表取最低带)行集 = {duergar} 恰一行、首行重甲、固定 1 只不加骰〔fv23〕" ↩ % l3_lead_band | l3_lead_band >= 0 and l3_lead_ids.has("duergar") and l3_lead_ids.size() == 1 \\ and String(l3_lead_set[0].get("armor_type", "")) == "heavy" \\ and int(l3_lead_set[0].get("unit_count", -1)) == 1 and not l3_lead_set[0].has("unit_count_max") | 灰矮人首遇带行集=恰一行重甲固定 1 只。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 630 | 4874 | "灰矮人石君王 Boss 行集(3,boss,%d)= {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % _boss_depth(3) | l3_king_ids.has("duergar_king") and l3_king_ids.size() == 1 and String(l3_king_set[0].get("armor_type", "")) == "heavy" | 石君王 Boss 行集=恰一行重甲(甲型教学梯度收束)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 631 | 4898 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | L3 首遇位成长代理档完美档 12 种子全胜零踩雷。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 632 | 4899 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | L3 首遇战胜后 HP 下探读数>0。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 633 | 4909 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | 混编行集 spawn 受理(正向)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 634 | 4911 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | 混编实例总数=各行数量求和(守卫内)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 635 | 4912 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,handle_id 跨行连续派发)" | String(mix_units[0].get("enemy_id")) == "goblin" and String(mix_units[1].get("enemy_id")) == "bugbear" and String(mix_units[2].get("enemy_id")) == "bugbear" | 混编生成序=文件行序(行序即契约)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 636 | 4914 | "混编各行行值独立投影(display_name/hp/atk 按各自行值——同场不同种不同值)" | String(mix_units[0].get("display_name")) == "地精" and int(mix_units[1].get("hp")) == 20 and int(mix_units[1].get("atk")) == 3 | 混编各行行值独立投影(同场不同种不同值)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 637 | 4919 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | 两行各定值 3 总 6>4 的 spawn 被拒(行集纪律 fail-fast 运行面)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 638 | 4924 | "混编断言段注入行全量还原(表回 20 行——首批混编投放后基线行数,迭代 18 卡⑤;后续断言面零污染)" | int(_db.enemy.rows.size()) == 20 | 混编注入行全量还原(表回 20 行零污染)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 639 | 4931 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | L2 入口停点就位(defer_entry 无当前节点)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 640 | 4938 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | 入口停点候选集⊆类型可见集(下一跳恒可见)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 641 | 4939 | "可见性冒烟:L2 Boss(编号 %d)恒在可见集(终点不入雾)〔fv3〕" ↩ % _boss_depth(2) | vis_ids.has(_boss_depth(2)) | L2 Boss 恒在可见集(终点不入雾)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 642 | 4941 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | 可见层数 N 读数据表(代码零硬编码)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 643 | 4942 | "可见性冒烟:入口锚层 0 时层距 3/4 的节点 7/8 入雾(非豁免——入口 BFS 层差 > N)〔fv1〕" | not vis_ids.has(7) and not vis_ids.has(8) | 层距超 N 的节点 7/8 入雾(非豁免)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S53 _smoke_gp_wallet(行 4969–5009 · 9 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 644 | 4969 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | GP 到账场景 L1 启动受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 645 | 4979 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | L1 首战胜利(完成信号 gp_dropped 载荷在案)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 646 | 4982 | "L1 普通战 gp_dropped − escrow ∈ [2+0, 4+0](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_dropped - escrow_sum, escrow_sum] | gp_dropped - escrow_sum >= 2 and gp_dropped - escrow_sum <= 4 | L1 普通战 gp_dropped 减 escrow 落在掷定+关级加值窗口。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 647 | 4988 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | 钱包恰入账一次。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 648 | 4990 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | 入账金额=gp_dropped 载荷。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 649 | 4991 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | 钱包读数=入账后投影(破财负值经钳制≥0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 650 | 4999 | "L2 Boss 战启动((2,9)——Boss 档掷定 [6,10] + 关级加值 1)" | boss.begin_battle({"level_index": 2, "node_depth": 9, "node_type": "boss", "hero_hp": int(_db.hero.max_hp)}) | L2 Boss 战启动受理(Boss 档掷定+关级加值)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 651 | 5005 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | L2 Boss 战终局到达(gp_dropped 战斗末掷定上抛)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 652 | 5008 | "L2 Boss 战 gp_dropped − escrow ∈ [6+1, 10+1](掷定+关级加值 %d,escrow %+d)" ↩ % [gp_b - escrow_b, escrow_b] | gp_b - escrow_b >= 7 and gp_b - escrow_b <= 11 | L2 Boss gp_dropped 减 escrow 落在 Boss 档窗口。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S54 _smoke_gp_escrow(行 5041–5108 · 12 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 653 | 5041 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | GP 袋可挖掘局种子可达。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 654 | 5054 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | GP 局战斗终局(先手收束)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 655 | 5059 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | GP 袋兑付行在案(+3 入战斗内缓冲,来源指回格)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 656 | 5060 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | escrow 累计缓冲≥3(战斗域中间态不入钱包)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 657 | 5062 | "格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" ↩ % [gp_d, int(settlement2.escrow)] | gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4 | 格面兑付计入 gp_dropped(战斗末一次到账随上抛即弃)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 658 | 5069 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | 破财直验战斗启动受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 659 | 5074 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | 破财 ×3 → escrow −6(负向缓冲记账零钱包触达)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 660 | 5079 | "破财压制 gp_dropped 负值上抛(%d + 6 = %d ∈ Boss 档掷定 [%d,%d] + 关级加值 %d——掷定不受破财污染)" ↩ % [ gp_l, gp_l + 6, int(boss_drop.get("roll_min", 0)), int(boss_drop.get("roll_max", 0)), boss_bonus1] | gp_l + 6 >= int(boss_drop.get("roll_min", 0)) + boss_bonus1 and gp_l + 6 <= int(boss_drop.get("roll_max", 0)) + boss_bonus1 | 破财压制 gp_dropped 负值上抛(掷定本身不受污染)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 661 | 5089 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | clamp 场景 L1 启动(冒险钱包开局 0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 662 | 5101 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | 破财压制入账载荷为负。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 663 | 5106 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | 破财入账恰一次且投影读数 0(钳制后归零)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 664 | 5108 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | 钱包全程不透支(事件投影最小读数≥0)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S55 _smoke_shop_room(行 5169–5244 · 22 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 665 | 5169 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | 商店房到达种子可达(进店 GP≥16)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 666 | 5173 | "L2 行进到商店房(节点 8,next=[9]——Boss 前必经位)" | String(runner.current_node().get("type", "")) == "shop" and int(runner.current_node_id()) == 8 | L2 行进到商店房(Boss 前必经位)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 667 | 5175 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | 到店开店态(离开前不出选路口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 668 | 5179 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | 三商品全陈列(无刷新无摇号)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 669 | 5181 | "L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)" | int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6 | L2 首购现价=基价+关级加值。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 670 | 5185 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | 购买 atk_up 受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 671 | 5186 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | 钱包扣现价 6。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 672 | 5187 | "统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)" | int(runner.run_ledger.bonus.get("atk", -1)) == 1 and int(runner.run_ledger.bonus.get("max_hp", -1)) == 0 and int(runner.run_ledger.bonus.get("armor_class", -1)) == 0 | 统一冒险加算区 atk +1(RunLedger 账本)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 673 | 5190 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | atk_up 二购现价=步长×已购档(现价现算)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 674 | 5198 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | 购买 hp_up 受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 675 | 5199 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | 钱包扣 4。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 676 | 5200 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | 生命上限 +3(加算区 max_hp 轴)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 677 | 5201 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | 当前生命同步 +3 封顶新上限。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 678 | 5203 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | 购买 armor_up 受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 679 | 5204 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | AC=10+1(armor_class 加算区轴无百分比通道)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 680 | 5219 | "余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" ↩ % [gp_dry, hp_dry] | not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry | 余额不足购买被拒(零扣费零档位变动)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 681 | 5222 | "离开商店 → 回选路口(候选 [9]——零自动推进收口)" | runner.continue_after_node() and runner.awaiting_choice() and runner.next_candidate_ids() == [9] | 离开商店回选路口(候选 [9] 零自动推进收口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 682 | 5225 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | 主角攻击生效值=基础表值+加算区。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 683 | 5226 | "选 Boss(节点 9)开战" | runner.pick_next_node(9) and runner.current_battle() != null | 选 Boss 开战。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 684 | 5230 | "战斗开始参数 = 基础表值 + 加算区(atk %d = 2+1 / max_hp %d = %d+%d×3)" ↩ % [int(snap["hero"].get("atk", -1)), int(snap["hero"].get("max_hp", -1)), int(_db.hero.max_hp), hp_buys] | int(snap["hero"].get("atk", -1)) == int(_db.hero.atk) + 1 and int(snap["hero"].get("max_hp", -1)) == int(_db.hero.max_hp) + 3 * hp_buys | 战斗开始参数=基础表值+加算区(按实际购买笔数推导)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 685 | 5242 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | 冒险收束到达(Boss 战终局)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 686 | 5244 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | 冒险收束钱包清零(局内货币生命周期终点)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S56 _smoke_shop_graph(行 5273–5292 · 6 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 687 | 5273 | "L%d 商店房恰 1 且 node_depth=8(next=[9])" ↩ % L | shops.size() == 1 and int(shops[0]) == 8 | 商店房恰 1 且编号 8(next=[9])。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 688 | 5274 | "L%d Boss = 9 = 全图最大编号(商店房批重编号 8→9)" ↩ % L | boss_id == 9 and boss_id == max_id | Boss=9=全图最大编号。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 689 | 5280 | "L%d 全部 %d 条入口→Boss 路径穿商店房 8(必经位——DFS 枚举)" ↩ % [L, paths.size()] | paths.size() >= 2 and via_shop | 全部入口→Boss 路径穿商店房(必经位 DFS 枚举)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 690 | 5289 | "(%d,boss,9) Boss 行在 board_gen + enemy 精确存在" ↩ % L | bg_keys.has("%d_9" % L) and en_keys.has("%d_boss_9" % L) | (·,boss,9) Boss 行跨表精确存在。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 691 | 5290 | "(%d,battle,8) 商店房无板/战斗行集残留(陈旧行已清理)" ↩ % L | not bg_keys.has("%d_8" % L) and not en_keys.has("%d_battle_8" % L) | (·,battle,8) 商店房无板/战斗行集残留。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 692 | 5291 | "L1 行集零改动(单链 Boss (1,4)——教学期最小修订面)" | bg_keys.has("1_4") and en_keys.has("1_boss_4") and not bg_keys.has("1_8") and not en_keys.has("1_battle_8") | L1 行集零改动(教学期最小修订面)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S57 _smoke_shop_pool(行 5317–5450 · 27 条 · 去向:单元测试 → tests/unit/core/level_shop_economy_test.gd · 施工组 G8) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 693 | 5317 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | 池模式商店房场景种子可达。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 694 | 5325 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | 进店抽 3(货架 3 卡同构交互)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 695 | 5331 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | 刷新首价=基价。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 696 | 5332 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | 基础免费 0 次。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 697 | 5334 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | GP 刷新受理。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 698 | 5335 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | GP 刷新扣费。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 699 | 5336 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | 刷新爬价×curve 现算。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 700 | 5344 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | 同种子同货架逐位(断点货架续玩=确定性重放)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 701 | 5356 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | 货架 kind 在封闭词表或保底 relic 卡。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 702 | 5363 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | 首购现价=ceil(基价×关序曲线)(核心现算呈现零复算)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 703 | 5371 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | 可购卡在架(种子扫描保证首购空间)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 704 | 5379 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | 购买受理(pool 口)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 705 | 5380 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | 钱包扣现价。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 706 | 5381 | "加算区 +令牌档位件值(池件=奖励件本体——%s\|%s %+d)" ↩ % [kind, tier, int(_db.adventure.curio_value(kind, tier))] | int(a8.run_ledger.bonus.get(String(val_map[kind]), -999)) == val0 + int(_db.adventure.curio_value(kind, tier)) | 加算区+令牌档位件值(池件=奖励件本体)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 707 | 5383 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | 共享池账本 −1(跨渠道单点)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 708 | 5393 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | A 链爬价=公式含 A^1(进店第 2 购)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 709 | 5395 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | 同 visit 第二购受理(A 链软闸在价不在限)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 710 | 5403 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | 切边件零入店(40 次重抽零 GP 袋/aux)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 711 | 5414 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | 库存尽剔牌库(池消耗即上限跨渠道共库)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 712 | 5416 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | 池空消费拒绝(handler 零副作用买光即枯竭)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 713 | 5420 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | 商店消费口受理(真单点库)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 714 | 5426 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | 商店购买进奖励牌库摘牌(买一件=奖励面少一件)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 715 | 5432 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | 离开商店回选路口(货架态清零落档)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 716 | 5434 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | 断点扩键 shop_shelf 在案(离开店=空架合法态)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 717 | 5436 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | 断点扩键 shop_visit_buys 在案(离开店清零)。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 718 | 5442 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | 终局池读数六键出讫。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | +| 719 | 5450 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | 终局池余量在 [0,配额] 域内。 | 迁单元测试(tests/unit/core/level_shop_economy_test.gd) | | + +## S58 _smoke_nine_types_gp_stream(行 5486–5524 · 6 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 720 | 5486 | "格面资源十型封闭(第九型 gp 九型批 2026-09-20 / 第十型 treasure_item 秘宝内容批 2026-09-29——词表 v1.2)" | RewardTable.CELL_TYPES.size() == 10 and RewardTable.CELL_TYPES.has("gp") and RewardTable.CELL_TYPES.has("treasure_item") | 格面资源十型封闭(gp 与秘宝件在册)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 721 | 5501 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/通关〕;清图槽独立批尾不计)" | cell_rows == 18 | 奖励字典恰 18 行(九型×正负)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 722 | 5507 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | 危险格负向直伤行 power=投值(三行逐行)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 723 | 5520 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | 正向行权重≥1 全在池、负向行权重恒 0 不入池。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 724 | 5522 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | GP 型两行权重(正向 2 入池/负向 0 不入池)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 725 | 5524 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | derive_at 纯函数不推计数器(全局计数恒 0)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S59 _smoke_meta(行 5532–5545 · 4 条 · 去向:单元测试 → tests/unit/data_config/table_structure_guard_test.gd · 施工组 G2) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 726 | 5532 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | 元进度表非空且自校验零错。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 727 | 5534 | "MetaTable 行族规模 3/3/2(Boss/换算恰三/Y 升级两档——forge_products 迭代 21 退役、workshop_items 迭代 22 退役)" | meta_tbl.boss_keystone.size() == 3 and meta_tbl.conversion.size() == 3 and meta_tbl.attune_refresh_upgrade.size() == 2 | 行族规模 3/3/2(Boss/换算/同调刷新升级)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 728 | 5540 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | 换算 attr 词表封闭恰全(三属性轴)。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | +| 729 | 5545 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | Boss 钥石 L1–L3 行连续且首杀≥重复杀。 | 迁单元测试(tests/unit/data_config/table_structure_guard_test.gd) | | + +## S60 _smoke_forge_axes(行 5559–5707 · 33 条 · 去向:单元测试 → tests/unit/core/meta_progression_forge_test.gd · 施工组 G9) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 730 | 5559 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | 主角静态表无两类型轴字段(轴所有权移交锻造配置)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 731 | 5562 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | 锻造配置默认值 normal/light(常驻持有与解锁解耦)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 732 | 5566 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | spawn 三参缺省形态受理(回退面)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 733 | 5567 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | 主角单位快照两轴=锻造配置默认值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 734 | 5577 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | 长剑首购:扣 20 钥石、等级 0→1。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 735 | 5581 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | 购买持久(重载后等级与余额不变)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 736 | 5584 | "长剑二级购:扣 30、等级→2;轻甲独立零扰动(六路线各练各的)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 2 and sv2.keystone_balance() == 50 and sv2.forge_route_level("light") == 0 | 长剑二级购扣 30;轻甲独立零扰动。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 737 | 5591 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | 余额不足购买拒绝且零副作用(三域零变化)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 738 | 5596 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | 已满级拒购/未解锁拒购(上限闸+解锁闸)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 739 | 5599 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 V3〕" | mp2.forge_route_unlocked("spear") and mp2.forge_route_unlocked("staff") and not mp2.forge_route_unlocked("heavy") and not mp2.forge_route_unlocked("ward") | 通道一:长剑练满派生解锁长枪/法杖;轻甲未满重甲/魔法甲仍锁。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 740 | 5602 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | 派生解锁后长枪可购(首购 20)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 741 | 5609 | "通道二:魔法护甲一次性解锁价 200 直购开(轻甲未练满也可买)〔锻造工坊批 V3〕" | not mp3.forge_route_unlocked("ward") and mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 and mp3.forge_route_unlocked("ward") | 通道二:一次性解锁价 200 直购开(未练满也可买)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 742 | 5614 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | 直购后练满不重复收(已解锁再直购拒绝零收费)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 743 | 5619 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 V3〕" | mp2d.forge_buy_unlock("staff") and mp2d.forge_route_unlocked("staff") and sv2d.forge_route_level("spear") == 0 and not mp2d.forge_route_unlocked("spear") | 不设串联门槛(法杖直购开长枪零状态不阻)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 744 | 5624 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | 两币分工:购买只扣钥石(GP 零触碰)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 745 | 5629 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | 选中切换免费(余额零动写存档即盘)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 746 | 5632 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | 切换后旧路线练度保留(回家待命只不生效)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 747 | 5635 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | 未解锁路线拒选零副作用。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 748 | 5638 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | 类型轴当前生效值随选中走(未解锁档回退默认)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 749 | 5647 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | 开局参数构造注入默认值(树钥匙未点亮新档)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 750 | 5651 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | 长枪直购解锁+首购+选中(夹具)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 751 | 5654 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | 开局参数读存档选中覆盖(选中长枪→穿刺)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 752 | 5658 | "hero_override=锻造路线加值(选中长枪 Lv1→atk +1;甲 0 级→AC +0;树零分量;无 hit 键)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 and int(mp4.hero_override().get("max_hp", -1)) == 0 and int(mp4.hero_override().get("armor_class", -1)) == 0 and not mp4.hero_override().has("hit") | hero_override=锻造路线加值(只认选中、树零分量、无命中键)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 753 | 5664 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | 换当前武器→攻击加值随切换(待命练度不生效)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 754 | 5666 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | 切回长剑→加值=3 级量(旧练度保留且生效)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 755 | 5672 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | 重甲直购解锁+首购+选中(三层 AC 夹具)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 756 | 5676 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | 注入参数冒险启动受理。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 757 | 5678 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | 入战 AC 三层:甲型基础 12+路线加值 1+局内 0。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 758 | 5680 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | 参数面快照入 runner(两轴正确)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 759 | 5683 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | 冒险开始后锁死:改存档选中不影响快照与生效值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 760 | 5692 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | enc 两轴直达 spawn 英雄快照(攻击行携带选中类型键)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 761 | 5696 | "spawn 显式两轴注入(magic/heavy 直入英雄快照)〔锻造工坊批 V5〕" | uc2.spawn(1, 0, int(_db.hero.max_hp), [], -1, -1, [], "magic", "heavy") and String(uc2.hero_unit().get("attack_type", "")) == "magic" and String(uc2.hero_unit().get("armor_type", "")) == "heavy" | spawn 显式两轴注入(直入英雄快照)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 762 | 5706 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | 树零攻防来源复核(12 行效果键无攻/护)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | + +## S61 _status_panel_projection(行 5727–5763 · 4 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 763 | 5727 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | 状态面板开层可见。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 764 | 5728 | "余额徽投影 = keystone_balance() 读数(%s)" ↩ % po._balance_badge.value_label.text | String(po._balance_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | 余额徽投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 765 | 5756 | "秘宝匣 12 行投影 = treasure_shop_status() 逐项(名称/钥石价与条件进度/解锁态——首件解锁夹具在列)〔秘宝批 V19 呈现数据源〕" | ws_ok | 秘宝匣 12 行投影=工坊状态逐项(名称/价与进度/解锁态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 766 | 5762 | "天赋树概览聚合文案与已购节点数一致(零已购=空态句)" | tree_txt == "尚未购买任何节点(天赋树在营地主柜台)" or tree_txt.begins_with("已购 %d 项:" % owned) | 天赋树概览聚合文案与已购节点数一致(零已购=空态句)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S62 _career_overlay_projection(行 5796–5831 · 8 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 767 | 5796 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | 生涯页开层可见。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 768 | 5797 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | 总局数投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 769 | 5799 | "通关局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | 通关局数投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 770 | 5804 | "各关通关次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | 各关通关次数投影=逐关读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 771 | 5806 | "Boss 首杀/总杀投影 = 首杀标记计数 + boss_kill_count()(%s / %s)" ↩ % [co._boss_first_label.text, co._boss_kills_label.text] | String(co._boss_first_label.text) == "3/3 关" and String(co._boss_kills_label.text) == "4 次" | Boss 首杀/总杀投影=标记计数+访问器。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 772 | 5808 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | 钥石余额徽投影=访问器读数。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 773 | 5817 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | v5 现役档升版载入成功(计数域缺省补零)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 774 | 5828 | "旧档载入后生涯页正常显示(v5 升 v6:总局数/通关局数显示 0,既有钥石余额原样呈现)" | co_v5.visible and String(co_v5._total_label.text) == "0 局" \\ and String(co_v5._cleared_label.text) == "0 局(三关全通)" \\ and String(co_v5._keystone_badge.value_label.text) == "钥石 3" | 旧档载入后生涯页正常显示(0 值呈现+余额原样)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S63 _meta_pricing_and_buy(行 5871–5883 · 4 条 · 去向:单元测试 → tests/unit/core/meta_progression_forge_test.gd · 施工组 G9) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 775 | 5871 | "树购买受理(Lv 0→1、余额 %d−%d)" ↩ % [bal0, price0] | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 and sv.keystone_balance() == bal0 - price0 | 树购买受理(Lv0→1、余额扣表价)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 776 | 5874 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | 购买持久(重载后级与余额不变)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 777 | 5880 | "余额不足 false 零副作用(0 余额购买拒绝——三域零变化〔天赋树批 V3〕)" | not mp_dry.tree_buy("sp_hp") and sv_dry.keystone_balance() == 0 and sv_dry.tree_nodes() == tn_before and sv_dry.fork_sides() == fs_before | 余额不足 false 零副作用(三域零变化)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 778 | 5883 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | 未知节点 false 零副作用(词表外出域)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | + +## S64 _meta_injection(行 5894–5923 · 10 条 · 去向:单元测试 → tests/unit/core/meta_progression_forge_test.gd · 施工组 G9) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 779 | 5894 | "生命淬炼 2 级 → hero_override().max_hp == Σ 节点 effects×等级(验收4 换代〔天赋树批 V12〕)" | int(mp.hero_override().get("max_hp", -1)) == 2 * int(_db.tree_node("sp_hp").get("effects", {}).get("max_hp", 0)) | 生命淬炼 2 级 → 永久强化 max_hp=Σ节点效果×等级。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 780 | 5896 | "攻/AC 树零来源(契约 9——树表零 atk/armor_class 行,override 二轴恒 0+预留位 0)" | int(mp.hero_override().get("atk", -1)) == 0 and int(mp.hero_override().get("armor_class", -1)) == 0 | 攻/AC 树零来源(override 二轴恒 0)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 781 | 5898 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | 命中线在账未注入(override 无 hit 字段)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 782 | 5901 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | 注入参数冒险启动受理。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 783 | 5904 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | 首战 hero_atk=表值+永久强化 flat。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 784 | 5906 | "首战 hero_max_hp 与开局生命账本 = 表值 + 注入档" | int(r.effective_max_hp()) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) and int(adr.ledger["hero_hp"]) == int(_db.hero.max_hp) + int(ov.get("max_hp", 0)) | 首战上限与开局生命账本=表值+注入档。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 785 | 5911 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | 白板冒险启动(无参数路径)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 786 | 5914 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | 零强化参数冒险启动(新档全零)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 787 | 5918 | "白板成立:空参数 = 零强化参数 = 纯表值(atk/max_hp/ac 三轴逐位一致——验收 2/3)" | int(rw.effective_atk()) == int(_db.hero.atk) and int(rw.effective_max_hp()) == int(_db.hero.max_hp) and int(rz.effective_atk()) == int(rw.effective_atk()) and int(rz.effective_max_hp()) == int(rw.effective_max_hp()) and int(rz.effective_ac()) == int(rw.effective_ac()) | 白板成立:空参数=零强化参数=纯表值(三轴逐位)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 788 | 5922 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | ac 轴:白板恒表值、注入零位移。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | + +## S65 _meta_settle_and_workshop(行 5941–6109 · 42 条 · 去向:单元测试 → tests/unit/core/meta_progression_forge_test.gd · 施工组 G9) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 789 | 5941 | "新档首通 L1:boss_drops[0] {first:true, amount=first_kill}(验收5——明细随摘要归档)" | bool(drop1.get("first", false)) and int(drop1.get("amount", 0)) == int(row1.get("first_kill", -1)) and int(drop1.get("level", 0)) == 1 | 新档首通 L1:掉落明细 {first:true, 金额=首杀值}。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 790 | 5943 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | 明细 total=keystone_total(无二次派生加账)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 791 | 5944 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | 余额=累计入账(无重复加账)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 792 | 5951 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | 再通 L1:{first:false, 金额=重复杀值}(标记置位后转小额)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 793 | 5957 | "重复杀第三轮恒定不递减(验收5——无衰减曲线)" | not bool(d3["boss_drops"][0].get("first", true)) and int(d3["boss_drops"][0].get("amount", 0)) == int(row1.get("repeat_kill", -1)) | 重复杀第三轮恒定不递减(无衰减曲线)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 794 | 5965 | "两关通关逐关归因:L1 重复档 + L2 首杀档(first_kill 金额)" | not bool(d4["boss_drops"][0].get("first", true)) and bool(d4["boss_drops"][1].get("first", false)) and int(d4["boss_drops"][1].get("amount", 0)) == int(row2.get("first_kill", -1)) | 两关通关逐关归因(重复档+首杀档)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 795 | 5973 | "败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | 败北零通关摘要:零入账零标记、通关次数不变。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 796 | 5980 | "条件件误购拒绝(购买件专属——契约 8 分配面)〔秘宝批 V3 反例〕" | not mp_u.buy_treasure_unlock("sky_eye") and not sv_u.item_unlocked("sky_eye") and not mp_u.buy_treasure_unlock("ranged_pick") | 条件件误购拒绝(购买件专属)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 797 | 5983 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | 余额不足购买拒绝且零副作用。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 798 | 5985 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | 钥石购买解锁:解锁态落档+余额扣减。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 799 | 5988 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | 已解锁重复购买拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 800 | 5990 | "条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | 条件未达不解锁。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 801 | 5993 | "远程镐=adventure_cleared≥1 达标恰解锁一次+重复刷新幂等(C6:通关=打通整局冒险)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("ranged_pick") and sv_u.item_unlocked("ranged_pick") and mp_u.refresh_treasure_unlocks().is_empty() | 达标恰解锁一次+重复刷新幂等。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 802 | 5997 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | 刷新执照=Boss 击杀数达标(阈值读行值不硬编)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 803 | 6000 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | 天眼单条件不足不解锁(AND 语义)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 804 | 6002 | "天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | 天眼双条件 AND 达标解锁。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 805 | 6005 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | 信标网络=真视类工具累计使用达标解锁(行为计数新域)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 806 | 6010 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | 解锁态持久投影一致(5 件=购买 1+条件 4)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 807 | 6013 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收5)" | rs.size() >= 5 and String(rs[0].get("result", "")) != "" and rs[0].has("boss_drops") and (rs[1] as Dictionary).has("keystone_drops") and int(rs[0].get("master_seed", -1)) == 15 | 摘要最新在前+元素含掉落明细副本。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 808 | 6021 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | 摘要上限 10 且最新在前(12 连收束截断)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 809 | 6026 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | 白板档同调位求值 0 且刷新次数 Y=0。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 810 | 6028 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | 满位禁选(Slots 0 装配拒绝)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 811 | 6033 | "Y 升级购买入账(Lv1 → 每冒险 +1 次刷新)" | mp_a.buy_attune_refresh() and sv_a.attune_refresh_level() == 1 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 1 | Y 升级购买入账(Lv1→+1 次刷新)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 812 | 6038 | "通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | 通关 1 关 Slots 恒 0(槽位唯一来源=树点亮)且 Boss 击杀计数=1。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 813 | 6041 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | 树上点亮同调位→Slots=1。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 814 | 6043 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | 稀有件入普通格拒绝(档位格校验)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 815 | 6045 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | 装配持久写入+满位禁选。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 816 | 6047 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | 卸下恒可再装配(自卸自装合法面)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 817 | 6052 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | 装配关系读回持久(重载投影一致)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 818 | 6056 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | 充能重置场景装配冒险启动。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 819 | 6058 | "充能账本按装备 active_charged 件重置(新目录表值:线香 battle 级 1 次/天眼 level 级 1 次——周期重置点归 V8 逐形态面)" | int(a12.run_ledger.attune_charges.get("incense_stick", -1)) == 1 and int(a12.run_ledger.attune_charges.get("sky_eye", -1)) == 1 | 充能账本按装备件重置(表值周期)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 820 | 6064 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | 刷新双计数器 X 优先消耗。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 821 | 6066 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | 刷新双计数器 X 耗尽转 Y 池、池竭拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 822 | 6070 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | 刷新 X 每碰三选一重置(不结转)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 823 | 6081 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | 开局传参夹具前置(Fork 激活+Y 档表值防空转)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 824 | 6085 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | 开新冒险参数面:同调装配=存档装配集。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 825 | 6087 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | 开新冒险参数面:Y 刷新池=当前等级行表值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 826 | 6089 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | 开新冒险参数面:树 rule 键集=Fork 激活侧。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 827 | 6091 | "主菜单开新冒险参数面:入口特有键(first_script/shop_mode)随 extra 原样并入" | bool(p_new.get("first_script", false)) and String(p_new.get("shop_mode", "")) == "pool" | 开新冒险参数面:入口特有键随 extra 并入。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 828 | 6095 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (p_resume.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) and int(p_resume.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() and (p_resume.get("tree_rules", []) as Array) == rules_e | 续玩参数面三键与开新冒险同源同值。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 829 | 6106 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | 续玩受理面:resume_adventure 受理三键参数面。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 830 | 6108 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | tree_rules 随参数面进 runner(消费面下传)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | + +## S66 _smoke_tree(行 6118–6187 · 25 条 · 去向:单元测试 → tests/unit/core/meta_progression_forge_test.gd · 施工组 G9) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 831 | 6118 | "meta_tree 已加载(12 节点=脊柱 2〔生命/命中淬炼一线一节点 cap4〕+锻造 2〔钥匙+彩蛋——彩蛋含在锻造 2 内非第 13 行〕+钥石 2〔秘宝匣/刷新升级〕+同调 6〔socket 3+Fork 3 双态〕——v0.8 终态)〔天赋树批 V1〕" | _db.meta_tree != null and (_db.meta_tree.rows as Array).size() == 12 | 天赋树表已加载恰 12 节点。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 832 | 6120 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | 天赋树表自校验零违例(词表/Fork 结构/彩蛋上限/effects 封闭)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 833 | 6128 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | 彩蛋行效果键集⊆{max_hp,hit}(攻/护分量已摘除)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 834 | 6135 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | schema 反例:脉出域被拒。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 835 | 6140 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | schema 反例:Fork 组出域被拒。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 836 | 6145 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | schema 反例:effects.archetype 加载致命(层级纪律护栏)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 837 | 6149 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | schema 反例:前置引用悬空被拒。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 838 | 6154 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | 树购买受理(脊柱生命 L1 全钥石单币)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 839 | 6155 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | 行门槛:总等级不足时第 2 级不可购。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 840 | 6157 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | 命中/锻造钥匙/同调位 1 三行受理(总等级 1→4)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 841 | 6159 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | 锻造工坊钥匙零币(free)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 842 | 6160 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | 行门槛恰达当刻开放(总等级 4)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 843 | 6161 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | 第 2 级购买受理。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 844 | 6164 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | Fork 购买=解锁+默认 A 态(一节点双态)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 845 | 6166 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | 已解锁 Fork 重复购买拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 846 | 6167 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | 同态重复切换零操作。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 847 | 6168 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | 未解锁 Fork 切换拒绝。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 848 | 6170 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | 零币切态 b(解锁后 A/B 可切)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 849 | 6172 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | 切态零消费(余额不动)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 850 | 6173 | "规则键投影=激活态效果键(B 态稳健拓荒——tree_rule_keys 随切态换键集)" | (mp.tree_rule_keys() as Array).has("posture_bulwark") and not (mp.tree_rule_keys() as Array).has("posture_first_strike") | 规则键投影=激活态效果键(随切态换键集)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 851 | 6175 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | 切回 A 态键集随换。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 852 | 6178 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | 彩蛋不经购买(觉醒唯一入口=特化判定)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 853 | 6179 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | 觉醒前置未满足不亮。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 854 | 6184 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | 两线拉满+同调位全解锁→彩蛋自动点亮(零币特化判定)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 855 | 6187 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | 觉醒零消费(余额不动)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | + +## S67 _smoke_counter_buckets(行 6208–6241 · 6 条 · 去向:单元测试 → tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 施工组 G6) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 856 | 6208 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | 第 1 战真实付费挖格受理(Active 相位)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 857 | 6209 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | 第 1 战付费计数经上下文就地写 adventure 桶=1。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 858 | 6215 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | 第 2 战开战经上下文读到上一战持久值=1。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 859 | 6218 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | 第 2 战挖后 adventure 桶跨战累积=2(副本搬运态在此红)。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 860 | 6234 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | 第 1 战标错落 battle 桶 insurance_used=1。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | +| 861 | 6240 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | battle 桶随宿主销毁:次战新编排器读 0。 | 迁单元测试(tests/unit/battle_settlement/round_flow_skill_pool_test.gd) | | + +## S68 _smoke_tree_rules(行 6250–6346 · 19 条 · 去向:单元测试 → tests/unit/core/meta_progression_forge_test.gd · 施工组 G9) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 862 | 6250 | "先手压制:每关首战首轮窗命中(battle_index=1 且 round=1 → grant_ap queue)〔天赋树批 V10〕" | (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 | 先手压制:每关首战首轮窗命中。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 863 | 6252 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 2}, Callable()) as Array).is_empty() and (fx.dispatch(ctx_p, "round_start", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty() | 先手压制:首轮窗外地不触发(第二轮/第二关首战)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 864 | 6255 | "稳健拓荒:首轮窗内受击出盾、窗外不出(shield damage_taken 同窗条件)" | (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 and (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty() | 稳健拓荒:首轮窗内受击出盾、窗外不出。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 865 | 6271 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | 锋:踩雷减半命中+限次记账(闸开双行同态)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 866 | 6273 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | 锋:限次耗尽全额(闸关)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 867 | 6278 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | 锋:限次跨战持久(共享桶引用)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 868 | 6282 | "守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)" | orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 守侧 rig 战斗启动(携带守侧规则)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 869 | 6294 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | 守侧 rig 安全格定位(非危险格=标错负样本)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 870 | 6295 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | 守侧 rig 首轮后队列干净。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 871 | 6298 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | 守①:当回合标错且封印在场→下轮 AP+1(排队)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 872 | 6303 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | 守③④:旧封印重判零触发+同格最多一次(去重闸判别器)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 873 | 6307 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | 守②负样本:标中/被挖封印零触发(发射面过滤)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 874 | 6310 | "守⑤ rig:次战编排启动(新战新队列实例)" | orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 守⑤ rig:次战编排启动(新战新队列实例)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 875 | 6316 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | 守⑤:末轮判出不返(战亡即弃无跨战泄漏)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 876 | 6322 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | 散未激活:商品标价原价。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 877 | 6324 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | 散激活:商品购买价×80%(floor 偏向玩家下限 1)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 878 | 6328 | "聚 rig:基线编排启动(无增益侧)" | orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": [], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 聚 rig:基线编排启动(无增益侧)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 879 | 6337 | "聚 rig:增益编排启动(tree_rules=economy_hoard)" | orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, "feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) | 聚 rig:增益编排启动。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | +| 880 | 6345 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | 聚:战斗掉落乘算×1.5(同种子配对对拍)。 | 迁单元测试(tests/unit/core/meta_progression_forge_test.gd) | | + +## S69 _smoke_battle_log_view(行 6362–6389 · 6 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 881 | 6362 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | 敌方伤害行格式化(不读退役字段——卡死 bug 回归)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 882 | 6363 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | 退役伏击/基线拆行文案不再出现。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 883 | 6369 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | 主角穿刺暴击行金框(单分量 payload 不触发拆行)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 884 | 6370 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | 退役三分量拆行文案不再出现。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 885 | 6376 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | 未生效行聚合计数×2 呈现(不逐行刷屏)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 886 | 6389 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | 其余行型逐型格式化落地(解析文本增长——脚本错误静默吞断言转可见 FAIL)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S70 _smoke_global_theme(行 6398–6486 · 22 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 887 | 6398 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | 项目主题已挂载(工程设置指向主主题)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 888 | 6407 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | 主题类型覆盖在册(15 类逐类型——含弹层预盖)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 889 | 6409 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | Button 类型词条零接触(五态矩阵归按钮工厂)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 890 | 6417 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | focus 族词条零定义(红线扫描)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 891 | 6420 | "V1 滚动条槽/柄三态词条在位" | vsb.has_theme_stylebox("scroll") and vsb.has_theme_stylebox("grabber") and vsb.has_theme_stylebox("grabber_highlight") and vsb.has_theme_stylebox("grabber_pressed") | 滚动条槽/柄三态词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 892 | 6423 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | 滚动条 scroll_focus 未定义(红线)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 893 | 6426 | "V1 滑轨槽/填充/柄词条在位" | hsl.has_theme_stylebox("slider") and hsl.has_theme_stylebox("grabber_area") and hsl.has_theme_stylebox("grabber_area_highlight") and hsl.has_theme_icon("grabber") and hsl.has_theme_icon("grabber_highlight") and hsl.has_theme_icon("grabber_disabled") | 滑轨槽/填充/柄词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 894 | 6431 | "V1 LineEdit 词条在位(含占位色 font_placeholder_color)" | le.has_theme_stylebox("normal") and le.has_theme_color("font_color") and le.has_theme_color("font_placeholder_color") | 输入框词条在位(含占位色)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 895 | 6434 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | 微调框箭头词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 896 | 6437 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | 分隔线词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 897 | 6440 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | 提示面板词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 898 | 6441 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | 提示文字词条在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 899 | 6448 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | 滑轨冻结值锚定(已填充段与槽色=拍板色值)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 900 | 6451 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | 滑轨两段对比度≥4.5:1(拖到中点一眼可辨)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 901 | 6453 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | 滑轨样式盒类型均为扁平(否则显式红)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 902 | 6456 | "V3 CheckBox 四态图标非空" | cbx.get_theme_icon("checked") != null and cbx.get_theme_icon("unchecked") != null and cbx.get_theme_icon("checked_disabled") != null and cbx.get_theme_icon("unchecked_disabled") != null | 复选框四态图标非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 903 | 6460 | "V3 CheckButton 四态图标非空(与 CheckBox 同套 checked/unchecked 名,4.7 源码实证)" | cbt.get_theme_icon("checked") != null and cbt.get_theme_icon("unchecked") != null and cbt.get_theme_icon("checked_disabled") != null and cbt.get_theme_icon("unchecked_disabled") != null | 开关钮四态图标非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 904 | 6464 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | 弹出菜单词条存在(弹层预盖)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 905 | 6467 | "V3 OptionButton 词条存在(预盖)" | ob.has_theme_stylebox("normal") and ob.has_theme_stylebox("hover") and ob.has_theme_stylebox("pressed") and ob.has_theme_color("font_color") | 选项按钮词条存在(预盖)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 906 | 6481 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | 四文件退役实现零残留(剥注释行扫旧符号)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 907 | 6483 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | 主题默认字体非空(挂主题不丢中文字形)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 908 | 6486 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | Label 实取字体非空(中文字形防御实证)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S71 _smoke_button_five_states(行 6499–6562 · 23 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 909 | 6499 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | 普通钮五态样式盒逐态非空(循环 5 态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 910 | 6505 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | 普通钮三态为石纹九宫格贴图样式盒。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 911 | 6509 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | 普通钮悬停=提亮(乙案,异于常态白)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 912 | 6512 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | 普通钮禁用=本族底 α0.45 幽灵化(甲案)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 913 | 6515 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | d_sb != p_sb | 普通钮禁用≠按压(旧错配已修正)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 914 | 6517 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | 普通钮焦点样式盒为扁平环。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 915 | 6519 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | 焦点环色=INK(与焦点字色同源)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 916 | 6520 | "V1 焦点环宽=2px" | is_equal_approx(fsb.border_width_left, 2.0) and is_equal_approx(fsb.border_width_right, 2.0) and is_equal_approx(fsb.border_width_top, 2.0) and is_equal_approx(fsb.border_width_bottom, 2.0) | 焦点环宽=2px(四边)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 917 | 6523 | "V1 焦点环内缩=1px" | is_equal_approx(fsb.expand_margin_left, -1.0) and is_equal_approx(fsb.expand_margin_top, -1.0) and is_equal_approx(fsb.expand_margin_right, -1.0) and is_equal_approx(fsb.expand_margin_bottom, -1.0) | 焦点环内缩=1px(四边)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 918 | 6526 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | 焦点环无底(叠加态不遮钮底)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 919 | 6531 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | 主钮五态样式盒逐态非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 920 | 6535 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | 主钮按压/禁用=本族平涂(非石纹)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 921 | 6536 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | 主钮按压=烛光黄深档(现行值)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 922 | 6537 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | 主钮禁用=本族底 α0.45 幽灵化。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 923 | 6538 | "V1 主钮 disabled≠pressed" | pdd != ppn | 主钮禁用≠按压。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 924 | 6539 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | 主钮悬停=提亮档(承现行)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 925 | 6544 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | 危险钮五态样式盒逐态非空。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 926 | 6547 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | 危险钮按压/禁用=本族平涂。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 927 | 6548 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | 危险钮按压=深红深档(暗于常态)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 928 | 6549 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | 危险钮禁用=α0.45 幽灵化。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 929 | 6550 | "V1 危险钮 disabled≠pressed" | dds != dpr | 危险钮禁用≠按压。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 930 | 6555 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | 主题红线维持:Button 类型词条零接触。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 931 | 6562 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | 主题红线维持:focus 族词条零定义。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S72 _smoke_texture_filter(行 6569–6606 · 9 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 932 | 6569 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | 工程默认纹理过滤=Nearest(像素 crisp)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 933 | 6572 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | 白名单:暗角显式 LINEAR。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 934 | 6575 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | 白名单:光柱显式 LINEAR。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 935 | 6592 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | 光柱带心峰值 α≈0.05(量化下限内)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 936 | 6594 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | 光柱带内连续过渡像素>100(旧零过渡硬边已修)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 937 | 6595 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | 光柱纵向衰减生效(上亮下暗 2 倍)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 938 | 6598 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | 白名单:地图渐变显式 LINEAR。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 939 | 6602 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | 脚影显式 LINEAR 不丢(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 940 | 6605 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | 状态图标显式 LINEAR 不丢(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S73 _smoke_contrast_inks(行 6615–6631 · 9 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 941 | 6615 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | 顺位徽标亮墨对舞台底对比≥4.5:1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 942 | 6617 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | 魔法护甲徽标亮墨对比≥4.5:1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 943 | 6619 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | RAP 反馈行深墨对数据板对比≥4.5:1。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 944 | 6622 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | 顺位徽标墨=亮金(源码锚定原位)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 945 | 6624 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | 魔法护甲徽标墨=亮蓝。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 946 | 6625 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | 重甲徽标墨=亮金。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 947 | 6626 | "V4 GOLD_DEEP 语义岗不动(standee:199 意图动画判据)" | sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | 深金语义岗不动(意图动画判据)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 948 | 6629 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | RAP 反馈行墨=深石墨(原位行锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 949 | 6631 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | RAP 反馈行旧墨零残留(含注记面)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S74 _smoke_element_details(行 6640–6658 · 8 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 950 | 6640 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | BCard 图标槽白图标对比≥3:1(图形硬约束)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 951 | 6643 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | 图标槽明度差降档生效(新槽<旧槽对比)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 952 | 6645 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | 图标槽落柔和档(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 953 | 6648 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | B 区状态图标槽仍用深档(未误伤)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 954 | 6651 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | 商店卡片区垂直居中(源码锚定)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 955 | 6655 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | 营地三 holder 统一最小高 336。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 956 | 6657 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | 营地四栏面板纵向等高填充。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 957 | 6658 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | 三处锁文垂直居中在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S75 _smoke_diag_button_retired(行 6665–6674 · 7 条 · 去向:单元测试 → tests/unit/presentation/theme_projection_test.gd · 施工组 G11) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 958 | 6665 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | 主区无旧诊断钮工厂调用残留。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 959 | 6666 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | 转储入口连接随迁(组合根接浮窗信号)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 960 | 6668 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | 转储编排承接函数在位(卡死取证保留)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 961 | 6670 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | 旧诊断钮工厂已退役(装配器零残留)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 962 | 6672 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | 调试浮窗转储信号在位。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 963 | 6673 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | 浮窗转储钮外发方法在位(纯视图边界)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | +| 964 | 6674 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | 浮窗诊断转储钮在位(唯一入口)。 | 迁单元测试(tests/unit/presentation/theme_projection_test.gd) | | + +## S76 _smoke_save(行 6713–6977 · 70 条 · 去向:单元测试 → tests/unit/core/save_manager_domain_test.gd · 施工组 G10) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 965 | 6713 | "apply 原语入账(钥石 7+3、通关次数 1/1/0)" | w.keystone_balance() == 10 and w.clear_count(1) == 1 and w.clear_count(2) == 1 and w.clear_count(3) == 0 | 收束原语入账(钥石+通关次数按摘要)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 966 | 6715 | "首杀标记随通关关卡置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | 首杀标记随通关关卡置位。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 967 | 6717 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | 生涯终身计数随收束自增恰一(中止局口径)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 968 | 6719 | "写盘成功" | w.write_to_disk() | 写盘成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 969 | 6721 | "读回成功" | r.load_from_disk() | 读回成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 970 | 6722 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | 钥石余额往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 971 | 6723 | "树域往返相等(tree_nodes/fork_sides——迭代 21 换代承载)" | r.tree_level("sp_hp") == 2 and r.tree_level("sp_hit") == 1 and r.fork_side("fg_posture") == "b" | 树域往返相等(节点/分叉侧)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 972 | 6725 | "锻造工坊域往返相等(levels/unlocked/weapon/armor——迭代 23 T5)〔锻造工坊批 V8〕" | r.forge_route_level("spear") == 3 and r.forge_route_level("sword") == 0 and r.forge_unlocked_purchases() == ["spear"] and r.forge_weapon() == "spear" and r.forge_armor() == "ward" | 锻造域往返相等(等级/直购/选中)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 973 | 6729 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | 道具解锁往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 974 | 6730 | "通关次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | 通关次数表往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 975 | 6731 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | 首杀标记往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 976 | 6733 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | 生涯终身计数往返相等。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 977 | 6735 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | 冒险摘要往返相等(1 条种子 99)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 978 | 6740 | "新档合法态(load true + 全零默认)" | f2.load_from_disk() and f2.keystone_balance() == 0 and f2.tree_level("sp_hp") == 0 and not f2.item_unlocked("anything") and f2.clear_count(1) == 0 and f2.recent_summaries().is_empty() | 新档合法态(读入 true+全零默认)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 979 | 6743 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | 新档生涯计数缺省全零(白板完整可玩)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 980 | 6749 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | 余额不足花钥石拒绝且余额不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 981 | 6750 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | 余额内消费成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 982 | 6758 | "高版本拒读(load false)" | not f5.load_from_disk() | 高版本档拒读(读入 false)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 983 | 6759 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | 拒读后原文件字节不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 984 | 6760 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | 拒读态写盘拒绝且文件仍字节不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 985 | 6769 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | 损坏档兜底(默认数据继续)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 986 | 6771 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | 坏档备份文件存在。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 987 | 6772 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | 备份内容=原坏档字节。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 988 | 6782 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | v0 旧档经迁移链入账。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 989 | 6783 | "迁移链 0→1(测试钩)→7(生产) 写盘 version == 7(v7=锻造工坊域,迭代 23 T5——v6=生涯终身计数域迭代 18 卡④)" | f7.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[5], FileAccess.READ).get_as_text()).get("version", -1)) == 7 | 迁移链写盘版本=7(链式升到现行)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 990 | 6795 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | 域界干净——meta 域无局内资源键。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 991 | 6803 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | 摘要上限 10(12 份截断)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 992 | 6804 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | 摘要最新在前。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 993 | 6815 | "摘要集成:冒险驱动满配通关(seed 42)" | String(st9.get("result", "")) == "completed" | 摘要集成:真实局满配通关。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 994 | 6816 | "集成钥石余额 = ΣBoss 首杀行值(%d——逐杀掉落全源)" ↩ % f9.keystone_balance() | f9.keystone_balance() == ks_run | 集成钥石余额=ΣBoss 首杀行值。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 995 | 6817 | "集成通关次数表全 1" | f9.clear_count(1) == 1 and f9.clear_count(2) == 1 and f9.clear_count(3) == 1 | 集成通关次数表全 1。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 996 | 6818 | "集成三关首杀标记齐" | f9.boss_first_kill_marked(1) and f9.boss_first_kill_marked(2) and f9.boss_first_kill_marked(3) | 集成三关首杀标记齐。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 997 | 6820 | "集成 recent 1 条且 seed 42" | f9.recent_summaries().size() == 1 and int(f9.recent_summaries()[0].get("master_seed", -1)) == 42 | 集成摘要 1 条且种子正确。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 998 | 6824 | "第二局后通关次数全 2" | f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2 | 第二局后通关次数全 2。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 999 | 6825 | "第二局后余额翻倍(2 × Σ首杀——模拟驱动恒首杀口径)" | f9.keystone_balance() == 2 * ks_run | 第二局后余额翻倍。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1000 | 6826 | "集成 recent 长度 2" | f9.recent_summaries().size() == 2 | 集成摘要长度 2。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1001 | 6827 | "集成写盘成功" | f9.write_to_disk() | 集成写盘成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1002 | 6829 | "集成写盘读回全量保持" | r9.load_from_disk() and r9.keystone_balance() == 2 * ks_run and r9.clear_count(3) == 2 and r9.recent_summaries().size() == 2 and r9.boss_first_kill_marked(3) | 集成写盘读回全量保持。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1003 | 6835 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | 断点:新档无断点(合法空态)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1004 | 6846 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | 断点写入+写盘成功。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1005 | 6848 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | 断点读回在档。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1006 | 6850 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | 断点往返:关卡序号/节点位/生命。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1007 | 6853 | "断点七字段往返:属性加成区" | int(bp_bonus.get("atk", -1)) == 2 and int(bp_bonus.get("max_hp", -1)) == 5 and int(bp_bonus.get("armor_class", -1)) == 1 | 断点往返:属性加成区。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1008 | 6855 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | 断点往返:GP 钱包。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1009 | 6857 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | 断点往返:商店已购档。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1010 | 6859 | "断点七字段往返:已抽模板" | int(bp.get("template_pick", -1)) == 3 | 断点往返:已抽模板。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1011 | 6860 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | 断点附随件往返:主种子/计数器/钥石累计。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1012 | 6863 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | 断点附随件往返:已到节点序。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1013 | 6866 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | 断点附随件往返:道具库存。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1014 | 6869 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | 断点读面值拷贝(改读面不渗档)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1015 | 6872 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | 断点清除+写盘。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1016 | 6874 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | 断点清除后读回无断点。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1017 | 6879 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | v1 旧档迁移:断点域补空(合法空态不炸)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1018 | 6881 | "v1 旧档迁移后写盘 version == 7(v1→v7 链式——锻造工坊域 2026-10-12)" | fo.write_to_disk() and int(JSON.parse_string(FileAccess.open(paths[9], FileAccess.READ).get_as_text()).get("version", -1)) == 7 | v1 旧档迁移后写盘版本=7(链式)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1019 | 6893 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | v2 旧档迁移:断点域与 meta 域原样保留。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1020 | 6895 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | v2 旧断点字段零丢失(向后兼容)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1021 | 6898 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | v2 旧断点续玩受理(缺附随件走常规重建)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1022 | 6907 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | v5 旧档升版:计数域缺省补零且既有余额原样(只增不改)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1023 | 6910 | "升档幂等:v7 档写盘再载零漂移(重复迁移零副作用——锻造工坊域同批补齐)" | f13.write_to_disk() and f13.load_from_disk() and int(JSON.parse_string(FileAccess.open(paths[11], FileAccess.READ).get_as_text()).get("version", -1)) == 7 and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 | 升档幂等:写盘再载零漂移。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1024 | 6918 | "收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | 收束自增恰一·通关局(总局数+1 且通关局数+1)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1025 | 6922 | "收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | 收束自增恰一·中止局(仅总局数+1)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1026 | 6933 | "换代前结构存档:树域废弃重置(旧节点 id/fork 侧/forge_levels 旧域残留清退)、钥石余额零折算零变动" | f15.load_from_disk() and f15.keystone_balance() == 33 and f15.tree_nodes().is_empty() and f15.fork_sides().is_empty() | 换代前结构存档:树域废弃重置(旧残留清退、余额零折算)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1027 | 6938 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | 废弃重置后后续购买正常受理(无残留脏节点)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1028 | 6946 | "v6 旧档升 v7:既有域逐位不动(钥石/树/生涯计数零折算——迁移只增不改)〔锻造工坊批 V8〕" | f16.load_from_disk() and f16.keystone_balance() == 21 and f16.tree_level("sp_hp") == 1 and f16.adventure_total() == 4 and f16.adventure_cleared() == 1 | v6 旧档升版:既有域逐位不动(迁移只增不改)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1029 | 6949 | "v6 旧档缺键补齐=初始持有态(六路线 0 级/直购清单空/选中=sword+light)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 and f16.forge_route_level("spear") == 0 and f16.forge_route_level("light") == 0 and f16.forge_route_level("ward") == 0 and f16.forge_unlocked_purchases().is_empty() and f16.forge_weapon() == "sword" and f16.forge_armor() == "light" | v6 旧档缺键补齐=初始持有态(六路线 0 级/选中默认)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1030 | 6956 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | 锻造域键集恰四项(无特效域)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1031 | 6960 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | levels 六路线键集恰全。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1032 | 6965 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | 锻造快照值拷贝出户(改快照不渗档)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1033 | 6972 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、通关局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | 收束链自增·中止局(真实挂点全链恰一)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1034 | 6976 | "收束链自增·通关局(三关全通:总局数+1 且通关局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | 收束链自增·通关局(全链恰一)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | + +## S77 _smoke_save_slots(行 7007–7146 · 31 条 · 去向:单元测试 → tests/unit/core/save_manager_domain_test.gd · 施工组 G10) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1035 | 7007 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | 进档受理(激活+读盘)逐槽。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1036 | 7010 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | 激活态写盘受理逐槽。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1037 | 7014 | "sv1 三槽往返一致·槽 %d(异值按位读回)〔卡① sv1〕" ↩ % i | t2.activate_slot(i) and t2.keystone_balance() == 100 * i and t2.adventure_total() == 1 and t2.adventure_cleared() == (1 if i == 1 else 0) | 三槽往返一致(异值按位读回)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1038 | 7020 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | 槽 1 加值后写盘受理。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1039 | 7021 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | 位间隔离:改槽 1 槽 2 字节不动。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1040 | 7025 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | 切激活至槽 3(删档夹具前置)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1041 | 7026 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | 删激活槽受理。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1042 | 7027 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | 删后文件不在盘+无临时残留。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1043 | 7029 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | 删激活槽激活指针连清除(该槽不再可写)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1044 | 7033 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | 删非激活槽受理。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1045 | 7034 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | 删非激活槽:文件不在盘+激活指针保持。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1046 | 7036 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | 删槽 2 槽 1 字节不动(位间互隔离)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1047 | 7045 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | 新档受理(创建即激活)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1048 | 7046 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | 新档=全零可玩合法白板态(旧内容零带入)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1049 | 7050 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | 新档落盘=白板内容(盘面核验非旧值)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1050 | 7059 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | 进档槽 1 读回(高版本夹具前置)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1051 | 7061 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | 高版本槽进档拒绝(拒读禁写链按槽)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1052 | 7062 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | 激活失败恢复切换前三态(路径/激活号/数据)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1053 | 7065 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | 恢复后写盘落激活槽(高版本槽零误写)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1054 | 7076 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | 迁移链按槽成立(各槽独立+写盘版本=7)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1055 | 7091 | "sv7 窥视三项逐槽正确·槽 %d〔卡① sv7〕" ↩ % i | bool(ps.get("readable", false)) and int(ps.get("keystone", -1)) == 200 * i and int(ps.get("cleared", -1)) == i and int(ps.get("total", -1)) == 5 * i | 窥视三项逐槽正确(余额/通关/总局数)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1056 | 7094 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | 删槽 3 备空槽窥视。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1057 | 7096 | "sv7 空槽窥视=零值合法态〔卡① sv7〕" | bool(pe.get("readable", false)) and int(pe.get("keystone", -1)) == 0 and int(pe.get("cleared", -1)) == 0 and int(pe.get("total", -1)) == 0 | 空槽窥视=零值合法态。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1058 | 7105 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | 坏槽窥视=显式损坏标记。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1059 | 7106 | "sv8 坏槽窥视零副作用(字节+mtime 双比对不变+零 .corrupt.bak 改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == bad_bytes and FileAccess.get_modified_time(slot_tpl % 1) == bad_mtime and not FileAccess.file_exists(slot_tpl % 1 + ".corrupt.bak") | 坏槽窥视零副作用(字节+mtime 双比对+零改名)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1060 | 7116 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | 高版本槽窥视=不可读标记。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1061 | 7117 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | 高版本窥视零副作用(字节+mtime 不变)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1062 | 7130 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | 旧档字节迁入槽 1(逐域相等)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1063 | 7132 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | 迁移后源改名留痕(备份在盘原路径不在)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1064 | 7141 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | 幂等:槽已存在不重迁(零覆盖源保留)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | +| 1065 | 7144 | "sv12 迁移他槽零触碰(槽 2/3 字节不动)〔卡① sv12〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == s2_before and FileAccess.get_file_as_bytes(slot_tpl % 3) == s3_before | 迁移他槽零触碰(槽 2/3 字节不动)。 | 迁单元测试(tests/unit/core/save_manager_domain_test.gd) | | + +--- +全源共 77 章、1065 条(静态调用点口径)。 diff --git a/reports/批B-断言迁移账本-2026-10-22.md b/reports/批B-断言迁移账本-2026-10-22.md new file mode 100644 index 00000000..a0e7747c --- /dev/null +++ b/reports/批B-断言迁移账本-2026-10-22.md @@ -0,0 +1,214 @@ +# 批 B 断言迁移账本(波 0 基建产出) + +> 状态:波 0 基建完成 · 2026-10-22 · 主刀代理(波 1 迁移)的施工输入合同 +> 基点 commit:`1aea93972b5ff3bc51de1814081e69a79f74a735`(smoke_all.gd 在此基点冻结) +> 合同:`reports/提案-测试术语与层级重构方案-2026-10-22.md` §四/§五/§七 +> 冻结参照物:`game/tests/验证集清单.md`(机器生成+逐条白话释义+处置列已回填;出口对账=`python tools/extract_checks.py --diff game/tests/验证集清单.md`) +> 波 0 纪律遵守:`game/smoke/smoke_all.gd` 零改动(git diff 空);三个去向既有测试文件零改动;未跑任何游戏测试 + +## 一、提取对账(先读这一节) + +- **静态调用点 = 1065 处**(`tools/extract_checks.py` 括号配平提取,含跨行调用归集)。 +- **与合同 §一「1067」的差**:1067 是行级 `grep -c "check("` 口径,多出的 2 行=`func check(` 定义行(:14)+一处注释行(:3452「禁恒真 check(true)」),均非调用点。清单头注已写明两口径,**迁移对账一律以 1065 为准**。 +- **--diff 自测**:对刚生成的清单跑 `--diff`=零差异(计数 1065/1065 对齐、标签/cond 逐字对齐);释义与处置列回填后复跑仍零差异(回填不触冻结列)。 +- 清单结构:77 章(套)×1065 条;每条含序号/基点行号/标签原文(逐字)/cond 摘要(跨行折叠单行)/白话释义/处置/quiet(全源仅 1 处 quiet=true,:3448)。 + +## 二、分类账总表(77 套) + +去向判据(tests/README「分层判据」):`--script` 不加载 autoload 能验完=单元;跨系统且走落盘=集成;需完整项目引导与场景树=系统测试。套=smoke_all.gd 的断言函数;行段=基点行号。 + +| 套 | 行段 | 条数 | 去向 | 新宿主 | 守的生产模块(变异改坏点落点) | 组 | +|---|---|---|---|---|---|---| +| _initialize | 44–70 | 14 | 单元 | unit/data_config/rng_stream_boot_test.gd | data/tables 加载链+core/rng_service.gd(统一流黄金向量/取号/事件日志) | G12 | +| _split_tail | 80–94 | 1 | 系统 | tests/system_test.gd | autoload 装配链就绪护栏(main.tscn 引导) | G1 | +| _smoke_type_gate | 99–105 | 2 | 单元 | unit/contracts/source_guard_test.gd | project.godot 警告配置+tools/audit_type_hints.py 闸接线 | G12 | +| _smoke_hygiene_gates | 109–153 | 6 | 单元 | unit/contracts/source_guard_test.gd | 产品码源码卫生(core/presentation/data 三目录死代码/影子账/层序单口) | G12 | +| _smoke_scene_split | 156–216 | 11 | 系统 | tests/system_test.gd | presentation 三屏壳+main.tscn 层序合同(LAYER_CONTRACT) | G1 | +| _smoke_boot_chain | 242–312 | 15 | 系统 | tests/system_test.gd | main.gd boot 链(设置应用/续玩钮/启动态/调试浮窗)+SaveManager 启动态 | G1 | +| _smoke_lifecycle_matrix | 319–492 | 10 | 系统 | tests/system_test.gd | main.gd 九阶段生命周期(引用面/模态开合/两连局/续玩断点) | G1 | +| _stage_faces | 527–579 | 1 | 系统 | tests/system_test.gd | 呈现层引用面快照协议(现值回调与 run_scope 同源) | G1 | +| _smoke_run_lifecycle | 592–645 | 11 | 系统 | tests/system_test.gd | main.gd 会话物化(脏态复位/断点清理/两连局零泄漏) | G1 | +| _smoke_node_map_templates | 716–784 | 14 | 集成 | integration/node_map_templates_test.gd | level_runner+adventure_runner+save_manager+meta_progression(模板池跨系统+首冒险置位落盘) | G12 | +| _smoke_adventure_table | 804–915 | 17 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/adventure.tres+同调目录 12 件+orchestrator 装配贯通 | G2 | +| _smoke_adventure_api | 919–955 | 11 | 单元 | unit/core/adventure_feats_test.gd | core/adventure_runner.gd 状态机受理面(重复拒/停点序/放弃两态) | G7 | +| _smoke_adventure | 974–1287 | 54 | 单元 | unit/core/adventure_feats_test.gd | adventure_runner 冒险结构(三关/大奖励/账本封闭/败北线/恢复数学/共享池) | G7 | +| _smoke_feats | 1292–1637 | 71 | 单元 | unit/core/adventure_feats_test.gd | data/tables/feats.tres+core/effect_engine.gd 逐件订阅+凭证保底 | G7 | +| _smoke_layout_cell_formula | 1691–1705 | 2 | 单元 | unit/core/board_gen_reward_test.gd | presentation/views/board_view.gd 格边长公式(下缘预留回归) | G3 | +| _smoke_type_glow | 1767–1955 | 24 | 单元 | unit/presentation/theme_projection_test.gd | board_view 微光数据面(作用集恒等/九型归组/零玩法写入) | G11 | +| _smoke_reward_identity_unit | 1977–2019 | 10 | 单元 | unit/core/board_gen_reward_test.gd | core/reward_economy.gd 身份面(roll 恰一次/五型封闭/幂等) | G3 | +| _smoke_seal_ap_exhausted | 2027–2058 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | core/action_economy.gd 末点 AP 封印轮末结算(playtest 缺陷②回归) | G5 | +| _smoke_level | 2063–2094 | 11 | 单元 | unit/core/level_shop_economy_test.gd | level_runner 线性推进(经 level_autoplay:满配/败北/回放/恢复公式) | G8 | +| _smoke_enemy_bands | 2099–2143 | 4 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/enemy.tres 带键结构(20 行键序/hobgoblin 留任/Boss 行) | G2 | +| _smoke_enemy_attack_type | 2151–2205 | 4 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | 敌 attack_type 轴+克制矩阵消费(±1 位移证词) | G4 | +| _smoke_ac_swap_guard | 2215–2320 | 12 | 单元 | unit/data_config/table_structure_guard_test.gd | status_effect/reward/settlement 表(AC 单轨/免伤墙护栏/掷点参数/敌 AC 带宽) | G2 | +| _smoke_armor_roll | 2325–2400 | 15 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | core/damage_math.gd 查档+orchestrator 掷点行为(nat20/下限/续时) | G4 | +| _smoke_pool_wiring | 2474–2487 | 4 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | orchestrator 乘区接线(×1.5/×0.75/硬化折半) | G4 | +| _smoke_board_generator | 2509–2564 | 22 | 单元 | unit/core/board_gen_reward_test.gd | core/board_generator.gd(可清性/合法性/出图验收/确定性/r0 闸) | G3 | +| _smoke_variation_framework | 2576–2603 | 9 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/variation.tres+config_db 池门控+跨字段互锁 | G2 | +| _smoke_global_variant | 2634–2749 | 30 | 单元 | unit/battle_settlement/variant_placement_test.gd | orchestrator 全局变异兜底回落+reward_economy 偏斜投影 | G6 | +| _smoke_variant_placement | 2795–2873 | 9 | 单元 | unit/battle_settlement/variant_placement_test.gd | orchestrator 战斗初始化布置段(门控/布置域/数量公式/连通片) | G6 | +| _smoke_full_battle | 2896–2928 | 7 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | orchestrator 全战斗回放(双流逐位/判定行/先手必兑) | G6 | +| _run_battle | 2931–2962 | 1 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | orchestrator 收束相位回 Idle(随 S29 迁) | G6 | +| _smoke_power_interval | 2988–3055 | 5 | 单元 | unit/core/board_gen_reward_test.gd | reward_economy 区间行(中点锚/流动/定值不受扰) | G3 | +| _smoke_active_refresh | 3069–3144 | 17 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | action_economy 轮内刷新(上限/双账/V5 快照单源/回放) | G6 | +| _smoke_difficulty_table | 3171–3193 | 8 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/difficulty.tres+unit_container 修饰(空表/套乘/clamp) | G2 | +| _smoke_damage_types | 3198–3229 | 10 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | unit_container 护甲类型+克制矩阵表值+标错/标中兑付 | G4 | +| _smoke_seal_irreversible_quadrants | 3286–3388 | 21 | 单元 | unit/battle_settlement/seal_polarity_test.gd | minesweeper_rules 封印不可逆四情况+拒绝面 | G5 | +| _smoke_playtest_logger | 3392–3436 | 8 | 单元 | unit/presentation/theme_projection_test.gd | presentation/playtest_logger.gd(快照 JSONL/诊断转储) | G11 | +| _smoke_encounter_roll | 3441–3506 | 8 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | orchestrator 遭遇区间骰(域内/回放/首遇带固定/空遭遇不可达) | G6 | +| _smoke_attack_chain_fresh_pool | 3566–3590 | 3 | 单元 | unit/battle_settlement/roll_multiplier_test.gd | damage_controller fresh 池攻击链(Bug#2 掷点口径) | G4 | +| _smoke_clear_reward_chain | 3611–3631 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | orchestrator 清图奖励双份兑付(真实流完美封印) | G5 | +| _smoke_clear_reward_zero_paths | 3636–3690 | 11 | 单元 | unit/battle_settlement/seal_polarity_test.gd | 清图奖励弱形式分支(零兑付/高光守卫/第二份拒) | G5 | +| _smoke_refresh_provenance_guard | 3694–3709 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | orchestrator 刷新图 provenance 守卫 | G5 | +| _smoke_polarity_routing | 3740–3853 | 12 | 单元 | unit/battle_settlement/seal_polarity_test.gd | orchestrator 极性路由+封印溢价 125% | G5 | +| _smoke_negative_dictionary | 3912–3998 | 8 | 单元 | unit/battle_settlement/seal_polarity_test.gd | 负向字典逐型兑付形态(效果行/直伤/警觉/行动点排队) | G5 | +| _smoke_resource_surface | 4003–4044 | 5 | 单元 | unit/battle_settlement/seal_polarity_test.gd | 揭示事件资源标记+封锁格身份查询面 | G5 | +| _smoke_settlement_segments | 4048–4115 | 5 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | settlement 五段全序+行型封闭+案C 段内序 | G6 | +| _smoke_skill_pool_data | 4232–4406 | 27 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | data/tables/skill.tres+enemy.tres 池模板+解锁技编排集成 | G6 | +| _smoke_skill_pool_replay_independence | 4412–4426 | 2 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | SkillLottery 同种子回放 | G6 | +| _smoke_skill_pool_ev_anchor | 4452–4500 | 1 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | SkillLottery EV 带宽锚(含保底闸速率) | G6 | +| _smoke_skill_pool_death_void | 4504–4538 | 5 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | 编排死亡打断作废(不动手不进账) | G6 | +| _smoke_skill_pool_validation_reject | 4542–4587 | 13 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | skill/enemy 表加载校验拒绝族 | G6 | +| _smoke_debuff_rider | 4600–4662 | 3 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | 妨害技落点+毒爆必中附加逐拍复算 | G6 | +| _smoke_level_graph | 4667–4943 | 54 | 单元 | unit/core/level_shop_economy_test.gd | level.tres 图校验+level_runner 推进+enemy 行集定点+类型可见性 | G8 | +| _smoke_gp_wallet | 4960–5009 | 9 | 单元 | unit/core/level_shop_economy_test.gd | level_runner 钱包到账(掷定窗口/关级加值) | G8 | +| _smoke_gp_escrow | 5023–5108 | 12 | 单元 | unit/core/level_shop_economy_test.gd | orchestrator escrow 缓冲+钱包钳制不透支 | G8 | +| _smoke_shop_room | 5133–5244 | 22 | 单元 | unit/core/level_shop_economy_test.gd | level_runner 商店三收口(现价/加算区/拒购/离开/清零) | G8 | +| _smoke_shop_graph | 5256–5292 | 6 | 单元 | unit/core/level_shop_economy_test.gd | level.tres 商店房图不变式(必经位/跨表行集迁移) | G8 | +| _smoke_shop_pool | 5297–5450 | 27 | 单元 | unit/core/level_shop_economy_test.gd | adventure_runner 商店共享池(抽 3 买 1/乘法价/共库/断点扩键) | G8 | +| _smoke_nine_types_gp_stream | 5484–5524 | 6 | 单元 | unit/data_config/table_structure_guard_test.gd | reward.tres 十型封闭+18 行字典+gp 流权重 | G2 | +| _smoke_meta | 5529–5545 | 4 | 单元 | unit/data_config/table_structure_guard_test.gd | data/tables/meta.tres 结构(行族/词表/first≥repeat) | G2 | +| _smoke_forge_axes | 5556–5707 | 33 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression 锻造域+main.gd build_adventure_params+unit_container 两轴 | G9 | +| _status_panel_projection | 5713–5765 | 4 | 单元 | unit/presentation/theme_projection_test.gd | presentation/views/status_panel.gd 只读投影 | G11 | +| _career_overlay_projection | 5772–5833 | 8 | 单元 | unit/presentation/theme_projection_test.gd | presentation/views/career_overlay.gd 投影+v5 旧档显示 | G11 | +| _meta_pricing_and_buy | 5862–5883 | 4 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression 树购买(受理/持久/拒绝零副作用) | G9 | +| _meta_injection | 5887–5924 | 10 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression hero_override+adventure_runner 注入/白板 | G9 | +| _meta_settle_and_workshop | 5928–6110 | 42 | 单元 | unit/core/meta_progression_forge_test.gd | meta_progression 收束入账/工坊解锁/同调位/开局传参面 | G9 | +| _smoke_tree | 6115–6188 | 25 | 单元 | unit/core/meta_progression_forge_test.gd | meta_tree 表+购买面/Fork 双态/彩蛋觉醒 | G9 | +| _smoke_counter_buckets | 6193–6241 | 6 | 单元 | unit/battle_settlement/round_flow_skill_pool_test.gd | effect_engine 四桶+run_ledger 跨战持久 | G6 | +| _smoke_tree_rules | 6244–6347 | 19 | 单元 | unit/core/meta_progression_forge_test.gd | effect_engine 树六键+level_runner 计价+编排守侧 queue | G9 | +| _smoke_battle_log_view | 6354–6389 | 6 | 单元 | unit/presentation/theme_projection_test.gd | presentation/views/battle_log_view.gd 行协议同步闸 | G11 | +| _smoke_global_theme | 6395–6488 | 22 | 单元 | unit/presentation/theme_projection_test.gd | main_theme.tres+ThemeDB 挂载(词条/滑轨对比度/字体防御) | G11 | +| _smoke_button_five_states | 6492–6562 | 23 | 单元 | unit/presentation/theme_projection_test.gd | ui_kit 按钮四工厂五态矩阵 | G11 | +| _smoke_texture_filter | 6566–6606 | 9 | 单元 | unit/presentation/theme_projection_test.gd | project.godot 纹理过滤+ui_kit 白名单+光柱结构 | G11 | +| _smoke_contrast_inks | 6610–6631 | 9 | 单元 | unit/presentation/theme_projection_test.gd | ui_kit 墨色对比度+standee/ap_bar 源码锚 | G11 | +| _smoke_element_details | 6635–6658 | 8 | 单元 | unit/presentation/theme_projection_test.gd | ui_kit 槽色降档+商店/营地布局源码锚 | G11 | +| _smoke_diag_button_retired | 6662–6674 | 7 | 单元 | unit/presentation/theme_projection_test.gd | main/ui_assembler/debug_overlay 诊断钮退役源码契约 | G11 | +| _smoke_save | 6680–6982 | 70 | 单元 | unit/core/save_manager_domain_test.gd | core/save_manager.gd 读写/迁移链/兜底/域界/摘要/断点域 | G10 | +| _smoke_save_slots | 6988–7151 | 31 | 单元 | unit/core/save_manager_domain_test.gd | save_manager 三槽位(往返/删档/窥视/迁移/旧单档) | G10 | + +计数核对:套 6+1+70=77;条 49(系统)+14(集成)+1002(单元:data_config 74+contracts 8+core 550+battle_settlement 242+presentation 128)=1065。 + +随套迁移的无断言 helper(63 个,与主套同 commit 剪切):`check/_theme_rel_luminance/_theme_contrast/_topmost_visible_layer/_face_snapshot/_face_liveness/_run_core_smoke/_smoke_rest_stub/_smoke_find_token/_api_mark_*/_adv_drive_to_shop/_adv_st/_glow_*/_ms_digest/_first_hero_attack_rows/_weaken_refresh_probe/_ac11_tier_samples/_hero_attack_row_with/_mk_stream/_errs_has_cross_field/_arr_str_eq/_rows_weights_equal/_rows_weight_map/_board_resource_dump/_variant_dump/_board_dump/_auto_play_round/_row_power/_active_refresh_replay/_run_seal_backlash_round/_find_seal_target/_row_kind_of/_boss_depth/_first_encounter_band/_level_active_nodes/_level_battle_nodes/_encounter_replay/_polarity_probe_accepts/_run_polarity_probe/_rows_at/_row_phase/_run_full_clear/_drive_clear/_rows_of_type/_has_source_row/_has_effect_id/_battle_st/_smoke_skill_pool(调度壳)/_record_pool_trajectories/_has_err_category/_fresh_rng/_escrow_sum_of/_find_gp_dig/_shop_products_by_id/_all_entry_boss_paths/_meta_smoke_cleanup/_meta_new_save`。归属:`check` 归各宿主自持(新宿主已有 Kit.check 或宿主 check,迁移时按各宿主协议换通道——**这是唯一的非逐字面**,见 §五注意事项 4);其余 helper 一对一随首用套迁移,共享 helper(`_fresh_rng/_find_seal_target/_auto_play_round/_run_battle` 等)落其首要消费者宿主、他人 preload。 + +## 三、施工组(12 组,供逐组原子提交) + +| 组 | 套 | 条数 | 宿主 | +|---|---|---|---| +| G1 系统·启动链尾段 | _split_tail、_smoke_scene_split、_smoke_boot_chain、_smoke_run_lifecycle、_smoke_lifecycle_matrix、_stage_faces(+_topmost_visible_layer/_face_snapshot/_face_liveness) | 49 | system_test.gd | +| G2 单元·表结构族 | _smoke_adventure_table、_smoke_enemy_bands、_smoke_ac_swap_guard、_smoke_variation_framework、_smoke_nine_types_gp_stream、_smoke_meta、_smoke_difficulty_table | 60 | unit/data_config/table_structure_guard_test.gd | +| G3 单元·棋盘生成与资源 | _smoke_board_generator、_smoke_layout_cell_formula、_smoke_reward_identity_unit、_smoke_power_interval | 39 | unit/core/board_gen_reward_test.gd | +| G4 单元·掷点与乘区 | _smoke_armor_roll、_smoke_pool_wiring、_smoke_enemy_attack_type、_smoke_attack_chain_fresh_pool、_smoke_damage_types | 36 | unit/battle_settlement/roll_multiplier_test.gd | +| G5 单元·封印与极性 | _smoke_seal_ap_exhausted、_smoke_seal_irreversible_quadrants、_smoke_polarity_routing、_smoke_negative_dictionary、_smoke_resource_surface、_smoke_clear_reward_chain、_smoke_clear_reward_zero_paths、_smoke_refresh_provenance_guard | 72 | unit/battle_settlement/seal_polarity_test.gd | +| G6 单元·轮内流与技能池+变异 | _smoke_active_refresh、_smoke_encounter_roll、_smoke_settlement_segments、_smoke_full_battle+_run_battle、_smoke_skill_pool_*(5 件)、_smoke_debuff_rider、_smoke_counter_buckets、_smoke_global_variant、_smoke_variant_placement | 134 | unit/battle_settlement/round_flow_skill_pool_test.gd+variant_placement_test.gd(双宿主) | +| G7 单元·冒险结构与专长 | _smoke_adventure_api、_smoke_adventure、_smoke_feats | 136 | unit/core/adventure_feats_test.gd | +| G8 单元·关卡图与 GP 商店 | _smoke_level、_smoke_level_graph、_smoke_gp_wallet、_smoke_gp_escrow、_smoke_shop_room、_smoke_shop_graph、_smoke_shop_pool | 141 | unit/core/level_shop_economy_test.gd | +| G9 单元·元进度与锻造 | _smoke_tree、_smoke_tree_rules、_meta_pricing_and_buy、_meta_injection、_meta_settle_and_workshop、_smoke_forge_axes | 133 | unit/core/meta_progression_forge_test.gd | +| G10 单元·存档域 | _smoke_save、_smoke_save_slots | 101 | unit/core/save_manager_domain_test.gd | +| G11 单元·呈现投影与主题 | _smoke_type_glow、_status_panel_projection、_career_overlay_projection、_smoke_battle_log_view、_smoke_global_theme、_smoke_button_five_states、_smoke_texture_filter、_smoke_contrast_inks、_smoke_element_details、_smoke_diag_button_retired、_smoke_playtest_logger | 128 | unit/presentation/theme_projection_test.gd | +| G12 单元·源码契约+集成 | _initialize、_smoke_type_gate、_smoke_hygiene_gates、_smoke_node_map_templates | 36 | unit/data_config/rng_stream_boot_test.gd+unit/contracts/source_guard_test.gd+integration/node_map_templates_test.gd | + +组内顺序建议:先小后大热身(G3/G4 → G2/G12 → G5/G6 → G1 → G8/G9 → G7 → G10 → G11);每组一个原子 commit(新宿主落函数+smoke_all.gd 同 commit 删函数)。 + +## 四、变异策略(到套级,按条执行细则照 test-conventions §6) + +标准改坏形态按生产模块族分四类,每套守的模块见 §二末列: + +1. **表结构类(G2/G12 表半/技能池数据面)**:改坏 `game/data/tables/*.tres` 一行值(如改 adventure.tres 档位配额、enemy.tres 一行 hp、meta.tres 首杀值)→ 跑该套断言验红 → `git checkout` 还原。 +2. **公式/纯逻辑类(G3/G4)**:改 core 公式一行(如 damage_math.gd 查档边界、board_generator.gd 尺寸公式、reward_economy.gd 中点取整)→ 验红还原。 +3. **状态机/编排行为类(G5/G6/G7/G8/G9 存档外半)**:改编排器/runner 分支(如删一处拒绝分支、翻转一个标志、改 action_economy 上限常量)→ 验红还原。 +4. **呈现/源码契约类(G11/G12 卫生面)**:改 ui_kit 常量或视图字段验红;源码契约面(hygiene/diag_button)的变异=往被扫产品码临时加一个违例符号(如一行 `Callable(self, "x")` 或 `build_diag_button` 调用)→ 验红还原。 +5. **系统类(G1)**:改 main.gd 装配序/层序合同一行或 settings 默认值 → 验红还原(跑 `run_system_test.py --script res://tests/system_test.gd`)。 + +注意:单元宿主断言通道是 `Kit.check("描述", cond)`(参序与 smoke 的 `check(cond, label)` 相反)——「只剪切禁改写」约束的是**标签字符串与 cond 表达式逐字**,调用通道换成宿主协议属宿主接线,不属改写;出口 `--diff` 比对的正是标签+cond 本身,不受通道影响。 + +## 五、宿主落位计划(本波不建任何宿主文件) + +**为什么本波不建**:空壳测试文件会触发单元运行器「断言数为零即红」(test_kit.gd `finish()` 零断言返回退出码 2),且 system_test.gd 现状零断言跑绿是批 A 有意的过渡态。宿主文件只随波 1 各组迁移 commit 出现。 + +**新宿主文件(11 个,随首组迁移创建)**: +- `game/tests/unit/data_config/rng_stream_boot_test.gd`(G12:_initialize 14 条) +- `game/tests/unit/data_config/table_structure_guard_test.gd`(G2) +- `game/tests/unit/contracts/source_guard_test.gd`(G12:type_gate+hygiene) +- `game/tests/integration/node_map_templates_test.gd`(G12:首个集成新增件) +- `game/tests/unit/core/board_gen_reward_test.gd`(G3) +- `game/tests/unit/battle_settlement/roll_multiplier_test.gd`(G4) +- `game/tests/unit/battle_settlement/seal_polarity_test.gd`(G5) +- `game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd`(G6 主宿主) +- `game/tests/unit/battle_settlement/variant_placement_test.gd`(G6 第二宿主) +- `game/tests/unit/core/adventure_feats_test.gd`(G7);`level_shop_economy_test.gd`(G8);`meta_progression_forge_test.gd`(G9);`save_manager_domain_test.gd`(G10);`unit/presentation/theme_projection_test.gd`(G11)——core 四件与 presentation 一件共 5 个。 + +**挂载方式**: +- **unit/integration**:新文件在 `game/tests/run_all.gd` `_suites()` manifest 追加 `{"path": "res://tests/unit/<域>/<名>_test.gd", "expect": 0}`(集成件同样进此 manifest,README 明示「与单元测试同一入口、同一协议」)。命名照 `[system]_[feature]_test.gd`;协议照 example_test.gd(extends SceneTree+Kit preload+quit(Kit.finish(...)));随组迁移的同批登记,禁出现「文件在 manifest 不在」的空窗(manifest 数即闸 02 计数口径)。 +- **system_test.gd**:断言函数直接追加进既有薄壳(宿主已自持 `check(cond, label)` 通道与 `_failures/_checks` 计数,摘要行 `[system_test] 摘要:N 项检查,M 项失败` 已被 run_system_test.py 三闸识别)。迁移期单跑验证:`python tools/run_system_test.py --script res://tests/system_test.gd`。**闸 01 缺省宿主在迁移期不动**(仍吃 smoke_all.gd),全部迁完的收尾批才把 `tools/run_system_test.py` 缺省 `--script` 改指 `res://tests/system_test.gd`(其头注既定程序)。 +- **夹具前提**:新宿主须自建 `_db/_rng` 等实例成员(smoke_all 的 `var _db/_rng` 由 `_initialize` 赋值)——照单元惯例显式实例化 ConfigDB/RngService 构造注入(rng_stream_boot_test 即 S01 本体的夹具形态);`AutoPlayer`/`level_autoplay` 等 smoke 库件迁移期继续按现路径 `res://smoke/...` preload,目录收尾批改名时同 commit 改 preload 路径。 + +## 六、歧义套(留主会话裁决,波 0 未定夺) + +| 套 | 波 0 倾向 | 拿不准的理由 | +|---|---|---| +| _initialize | 单元 → data_config | 13 条(表加载+RNG 向量/流语义)是标准单元域且现役 rng_service_test.gd 同域;但 :70 事件日志落盘一条带落盘味;且它是入口编排函数,编排壳(_run_core_smoke/_split_tail 调用序)不随迁而随源文件退役——「整套迁 data_config 一个宿主」还是「按域拆两宿主」留裁。 | +| _smoke_feats | 单元 → core | 主体 68 条=效果引擎逐件直测(纯逻辑);但 1420–1426 三条断点往返跨 SaveManager 落盘(集成味)。套级定单元、三条子块拆不拆留裁。 | +| _smoke_save | 单元 → core | 与现役 unit/core/save_*_test 同惯例(临时档隔离);但 6807–6831 摘要集成块驱动真实 run_adventure 全局再入账(adventure×save 两系统落盘)——该子块是否升集成留裁。 | +| _smoke_global_theme | 单元 → presentation | 依赖 ThemeDB 项目主题在座;`--script` 模式下 `ThemeDB.get_project_theme()` 能否取到项目主题未实跑验证(波 0 禁跑游戏测试),若取不到则此套须降级系统测试。主刀迁 G11 时先单跑验证。 | +| _smoke_type_gate / _smoke_hygiene_gates | 单元 → contracts | 断言对象是仓库自身文件(project.godot/static_scan.sh/产品码源码),归「契约」语义最贴;但现役 unit/contracts/ 定位是「系统间接口契约」——新设 source_guard 宿主还是并进别处留裁。 | +| _smoke_node_map_templates | 集成 | 跨四系统且走落盘判据明确;但前半 8 条(RNG 词表/模板池结构/抽签确定性)纯表结构可单元——整套集成还是拆套留裁。 | +| _smoke_playtest_logger | 单元 → presentation | logger 自持临时档;但它挂在编排器真实结算流上(跨系统)——单测/集成两可,倾向单测(被测主体唯一)。 | +| _smoke_type_glow | 单元 → presentation | 现役 unit/presentation/type_glow_test.gd 已覆盖同域微光——新宿主文件与它的分工边界(并列新文件还是后续合并)留主刀代理按「既有文件零改动」红线执行时定。 | + +## 七、附录 A:引用普查清单(全仓 grep `game/smoke`/`res://smoke`/`smoke_all`,reports/ 历史件与本账本自身除外) + +**合同在案必改点核实(§七批 B 第 4 条清单逐条)**: + +| 在案点 | 实况 | 核实 | +|---|---|---| +| run_all_gates.sh PARITY_TOUCH_FACES | :200–210 定义块,`game/smoke/adventure_monte_carlo.gd` 在 :209(合同写 :206——行号已漂 3 行,重核时以符号搜为准) | 在案 ✓ | +| AGENTS.md 在册入口 | :20(run_system_test 过渡宿主+探针路径)、:21(调参批量两件)、:22(verifier_batch 采集端) | 在案 ✓ | +| tools/parity_anchor.py | :55 `res://smoke/adventure_monte_carlo.gd` | 在案 ✓ | +| tools/balance_verifier.py | :54 `SCRIPT = "res://smoke/verifier_batch.gd"` | 在案 ✓ | +| sim-data/analyze.py | **零命中**(仅 :182 注释提「smoke/calibrate_boards.gd」字样)——合同虚列或在案即此注释行,收尾批顺手核 | 在案但弱 ✎ | +| architecture.md | :189(run_system_test 过渡宿主)、:241(三层承载表)、:242(UI 探针路径)、:315(启动后置条件判据事实源指针) | 在案 ✓ | +| design/gdd/术语表.md 豁免行 | :128「被检查的禁字不算使用」节引 smoke_all.gd contains("旗") 例 | 在案 ✓ | +| 08d 在册行号指针 | 08d=doc_ref_audit(run_all_gates.sh:192);其校验的行号指针散在设计文档——实测三处:锻造工坊.md:131(引「smoke_all.gd:1199 断言同步」史录)、数据配置.md:95(黄金向量断言指针)、体验验证清单.md:83(引函数名 `_smoke_flag_irreversible_quadrants`——**已滞后**,现名 `_smoke_seal_irreversible_quadrants`) | 在案 ✓(+1 处滞后名) | + +**合同清单之外的新发现(迁移/收尾批必改或须知)**: + +| 文件:行 | 性质 | +|---|---| +| game/presentation/tune_sim_controller.gd:27 | **产品码** preload `res://smoke/level_autoplay.gd`——smoke 目录改名时必须同批改,否则产品运行红 | +| game/tests/unit/presentation/pacing_queue_test.gd:53 | 现役单测 preload `res://smoke/auto_player.gd`——同上 | +| tools/probe_gate.sh:89–148(10 处) | 探针场景路径 `res://smoke/*.tscn`(合同 §四「批 B 随目录改名改路径」覆盖,列出防漏) | +| tools/render_gate.sh:56/72/96(3 处) | 同上(渲染探针路径) | +| tools/golden_baseline_refresh.sh:39/64(2 处) | **新发现**——golden 基线刷新脚本的探针路径,与 render_gate 同面 | +| tools/t3_lever_scan.py:74 | **新发现**——调参扫描脚本调 `res://smoke/adventure_monte_carlo.gd` | +| sim-data/README.md:36 | **新发现**——MC 采集档位表引 `game/smoke/adventure_monte_carlo.gd` 路径 | +| game/tests/unit/README.md:6 | 参考实现指针「`game/smoke/smoke_all.gd`(752+ 条断言…)」——迁移后改指新宿主并顺带订正滞后读数 | +| game/tests/unit/{rng_service_test:4, level_map_type_vis_test:4, curio_refresh_paid_test:26} | 注释提及 smoke_all(黄金向量锚分工/白盒先例)——收尾批顺手改提法 | +| game/tests/static_scan.sh:100 | S11 去重闸头注提 smoke_all.gd(批 A 加的豁免说明——smoke_all 删除后改提新宿主) | +| game/tests/.gates/baseline.json note | 历史记事文本含 smoke_all 字样(记事性引用,非路径依赖;随闸况重生成自然换代) | +| game/tests/README.md(11 处) | 过渡期口径句(层级表/游离台账/运行示例)——收尾批统一换代 | +| game/README.md:8/21/27/33/41(5 处) | **新发现**——工程 README 的运行命令全是 smoke_all 与 res://smoke 探针路径 | +| 待办清单.md:570/656/658、体验验证清单.md:83 | 流程清单条目与滞后函数名(体验验证清单:83 函数名已滞后,见上) | +| design/engineering/test-conventions.md:3/36 | 约束范围句与执行载体清单提 smoke_all.gd/smoke/ | +| design/gdd/{锻造工坊:131, 数据配置:95} | 设计文档指针(锻造工坊为史录句;数据配置为活性指针须改指) | +| game/smoke/*.gd 自引(各文件头注 res://smoke 路径,约 30 处) | 目录收尾改名时同批改;非闸件的自引属文件自身头注 | +| tools/run_all_gates.sh:166 | 注释「过渡期宿主=game/smoke/smoke_all.gd」——收尾批同步 | + +普查口径:`grep -rnE "game/smoke|res://smoke|smoke_all"` 限 .py/.sh/.gd/.md/.godot/.cfg/.json,排除 reports/、archive/、.git/ 与本账本/清单自身;总命中文件 63 个(上表归并后 18 类)。 + +## 八、其他需主会话注意 + +1. **清单入库面**:`game/tests/验证集清单.md` 已过 static_scan S11(205 文件词表扫描零命中)——唯一含退役词字面的标签(:6304「重判」行)与「去重闸」同行被既有豁免覆盖,**未新增任何豁免条款**。 +2. **工具面**:`tools/extract_checks.py` 为常驻对账工具(非本批中间件);其熵增闸归属=tools/ 现有类别,无新目录。它依赖码位掩码解析,smoke_all.gd 冻结期内源文件变动(不该发生)会直接反映在 diff。 +3. **收尾批提示**:smoke_all.gd 拆空删除时,`game/tests/smoke/` 子目录(critical-paths.md 人工闸清单)与 `game/smoke/` 目录改名是两件事,别漏前者;baseline.json 01 键 note 在收尾批换宿主时随批更新。 +4. **释义列的性质**:白话释义由波 0 逐条人工撰写(非机器生成),是对冻结面的可读注解;后续维护只增改释义不算改清单冻结面(--diff 不比对释义/处置列)。 diff --git a/tools/extract_checks.py b/tools/extract_checks.py new file mode 100644 index 00000000..33db63ca --- /dev/null +++ b/tools/extract_checks.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +r"""extract_checks.py — smoke_all.gd 断言静态提取器(验证集清单生成与出口对账) + +机器提取 game/smoke/smoke_all.gd 的全部静态 check( 调用点(每个调用点记录:所在函数、 +行号、标签字符串字面量原文、cond 表达式原文),提供两种模式: + +用法: + python tools/extract_checks.py --extract [--src <源文件>] [--out <清单文件>] [--force] + 生成验证集清单(已存在时拒写,除非 --force——冻结参照物防误覆) + python tools/extract_checks.py --diff <清单文件> [--src <源文件>] + 对当前源码再提取,与清单逐条比对——批 B 出口对账唯一工具 + python tools/extract_checks.py --list-funcs [--src <源文件>] + 只打印函数清单与各函数条数(编账本辅助,不写文件) + +提取口径: + - 静态 check( 调用点。与行级 grep(grep -c "check(")的差:grep 数含 func check 定义行 + 与一处注释行(「禁恒真 check(true)」),行级 grep=1067、真实调用点=1065(2026-10-22 基点实测)。 + - 调用点可跨行:从 `check(` 起括号配平归集完整实参文本(() [] {} 三种括号都计深度); + 字符串字面量内的括号/逗号/井号、行尾注释、转义序列均不参与结构判定(码位掩码单趟预扫)。 + - cond 摘要=cond 原文跨行折叠为单行(连续空白并一空格);标签原文=第二个实参里的字符串 + 字面量逐字保真(含引号与转义序列原样,禁改写);标签实参若带格式化/拼接后缀 + (`"..." % [...]`),后缀折叠后以「↩」接排在标签列。 + - md 表格单元格内 `|` 转义为 `\|`、`\` 转义为 `\\`(源码串内无这两序列的歧义形态,读回可还原)。 + +--diff 对账规则:只比对「标签+cond+计数」三要素(合同 §五出口机检);行号随迁移漂移、 +序号重排、白话释义/处置列由维护回填,均不参与比对。对不上的条目分三类报告: +消失(清单有现码无)、新增(现码有清单无)、成对字面变更(cond 同标签异=标签变更; +标签同 cond 异=cond 变更——一条「消失」与一条「新增」同因一字之改时按成对呈现)。 +退出码:0=全对齐;1=有差异;2=用法错或文件不可读。 +""" +import argparse +import bisect +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +DEFAULT_SRC = ROOT / "game" / "smoke" / "smoke_all.gd" +DEFAULT_OUT = ROOT / "game" / "tests" / "验证集清单.md" + +CALL_RE = re.compile(r"check\(") +DEF_RE = re.compile(r"^\s*(static\s+)?func\s+check\s*\(") +FUNC_RE = re.compile(r"^(static\s+)?func\s+([A-Za-z_][A-Za-z0-9_]*)") +LITERAL_RE = re.compile(r'"((?:[^"\\]|\\.)*)"') +OPENERS = "([{" +CLOSERS = ")]}" +CLOSER_OF = {")": "(", "]": "[", "}": "{"} + + +def fold(text: str) -> str: + """跨行折叠:连续空白(含换行/制表/回车)并为一空格。对账归一口径。""" + return re.sub(r"\s+", " ", text).strip() + + +def esc_cell(text: str) -> str: + return text.replace("\\", "\\\\").replace("|", "\\|") + + +def unesc_cell(text: str) -> str: + """esc_cell 的逆。源码串内无 `\\|`/`\\\\` 歧义形态(提取口径节),还原无歧义。""" + out = [] + i = 0 + while i < len(text): + if text[i] == "\\" and i + 1 < len(text): + out.append(text[i + 1]) + i += 2 + else: + out.append(text[i]) + i += 1 + return "".join(out) + + +def code_mask(joined: str) -> bytearray: + """单趟预扫:1=码位(非字符串内部、非注释)。字符串与行尾注释位置置 0。""" + mask = bytearray(len(joined)) + in_str = False + i = 0 + n = len(joined) + while i < n: + ch = joined[i] + if in_str: + mask[i] = 0 + if ch == "\\" and i + 1 < n: + mask[i + 1] = 0 + i += 2 + continue + if ch == '"': + in_str = False + i += 1 + continue + if ch == '"': + in_str = True + mask[i] = 0 + i += 1 + continue + if ch == "#": + nl = joined.find("\n", i) + end = n if nl == -1 else nl + for j in range(i, end): + mask[j] = 0 + i = end + continue + mask[i] = 1 + i += 1 + return mask + + +def func_table(lines): + """顶层函数表:[(name, start_line_1based)]。lambda 归宿主函数。""" + funcs = [] + for i, line in enumerate(lines, 1): + m = FUNC_RE.match(line) + if m: + funcs.append((m.group(2), i)) + return funcs + + +def func_at(funcs, lineno): + name = "" + for fn, start in funcs: + if start <= lineno: + name = fn + else: + break + return name + + +def split_args(body): + """按括号配平拆实参(顶层逗号分隔)。body=check( 与最外层 ) 之间的文本(换行已并空格)。""" + args = [] + depth = {"(": 0, "[": 0, "{": 0} + cur = [] + in_str = False + i = 0 + while i < len(body): + ch = body[i] + if in_str: + if ch == "\\" and i + 1 < len(body): + cur.append(body[i]) + cur.append(body[i + 1]) # 转义序列原样入账 + i += 2 + continue + if ch == '"': + in_str = False + cur.append(ch) + elif ch == '"': + in_str = True + cur.append(ch) + elif ch in depth: + depth[ch] += 1 + cur.append(ch) + elif ch in CLOSERS: + depth[CLOSER_OF[ch]] -= 1 + cur.append(ch) + elif ch == "," and depth["("] == 0 and depth["["] == 0 and depth["{"] == 0: + args.append("".join(cur)) + cur = [] + else: + cur.append(ch) + i += 1 + args.append("".join(cur)) + return args + + +def collect_call(joined, start): + """从 check( 的 start 起括号配平归集调用。返回 (body, end_pos);不配平返回 None。""" + i = start + len("check(") + depth = 1 + in_str = False + parts = [] + n = len(joined) + while i < n: + ch = joined[i] + if in_str: + if ch == "\\": + parts.append(joined[i:i + 2]) + i += 2 + continue + if ch == '"': + in_str = False + parts.append(ch) + elif ch == '"': + in_str = True + parts.append(ch) + elif ch in OPENERS: + depth += 1 + parts.append(ch) + elif ch in CLOSERS: + depth -= 1 + if depth == 0: + return "".join(parts), i + parts.append(ch) + elif ch == "#": + nl = joined.find("\n", i) + i = n if nl == -1 else nl # 行尾注释不进 body(并一空格) + parts.append(" ") + continue + else: + parts.append(ch if ch != "\n" else " ") + i += 1 + return None, i + + +def extract(src_path: Path): + """提取全部静态 check( 调用点。返回(条目列表, 函数表),条目保持源码顺序。""" + src = src_path.read_text(encoding="utf-8") + lines = src.split("\n") + funcs = func_table(lines) + offsets = [] + total = 0 + for line in lines: + offsets.append(total) + total += len(line) + 1 + joined = "\n".join(lines) + mask = code_mask(joined) + + def line_of(pos): + i = bisect.bisect_right(offsets, pos) - 1 + return i + 1 + + entries = [] + for m in CALL_RE.finditer(joined): + if not mask[m.start()]: + continue # 字符串字面量内 / 注释里的 check( 字样 + ln = line_of(m.start()) + if DEF_RE.match(lines[ln - 1]): + continue # func check 定义行 + body, end_pos = collect_call(joined, m.start()) + if body is None: + raise SystemExit("[extract_checks] FAIL:行 %d 起的 check( 括号不配平到文件尾——先查解析缺口" % ln) + args = split_args(body) + if len(args) not in (2, 3): + raise SystemExit( + "[extract_checks] FAIL:行 %d 的 check( 拆出 %d 个实参(预期 2/3)——解析缺口,先查再跑" + % (ln, len(args))) + cond = args[0] + label_expr = args[1] + quiet = args[2] if len(args) == 3 else "" + lm = LITERAL_RE.search(label_expr) + if lm is None: + raise SystemExit( + "[extract_checks] FAIL:行 %d 的标签实参不含字符串字面量——解析缺口,先查再跑" % ln) + entries.append({ + "line": ln, + "end_line": line_of(min(end_pos, len(joined) - 1)), + "func": func_at(funcs, ln), + "label_literal": lm.group(0), # 含引号,原文逐字 + "label_suffix": label_expr[lm.end():], # 格式化/拼接后缀(通常为空或 % ...) + "cond": cond, + "quiet": quiet, + }) + return entries, funcs + + +def label_cell(e): + """标签列:字面量 ↩ 后缀(后缀空则只有字面量)。""" + sfx = fold(e["label_suffix"]) + return e["label_literal"] + (" ↩ " + sfx if sfx else "") + + +def entry_key(e): + """对账指纹:cond 折叠 + 标签字面量(逐字)+ 后缀折叠 + quiet。""" + return (fold(e["cond"]), e["label_literal"], fold(e["label_suffix"]), fold(e["quiet"])) + + +def pad3(n): + return "S%02d" % n if n < 100 else "S%03d" % n + + +def base_commit(): + try: + r = subprocess.run(["git", "rev-parse", "HEAD"], cwd=str(ROOT), + capture_output=True, text=True, encoding="utf-8") + if r.returncode == 0: + return r.stdout.strip() + except OSError: + pass + return "" + + +def render_list(entries, funcs, src_rel: str) -> str: + """生成验证集清单骨架。白话释义/处置两列留待回填(回填不动冻结列)。""" + out = [] + out.append("# 验证集清单(迁移参照物·冻结)") + out.append("") + out.append("> 生成日期:2026-10-22 · 基点 commit:`%s` · 源:`%s`" % (base_commit(), src_rel)) + out.append("> 提取口径:静态 `check(` 调用点,括号配平归集(可跨行)。基点实测调用点 1065 处;") + out.append("> 行级 `grep -c \"check(\"` = 1067 是另一口径——多出的 2 行为 `func check(` 定义行与") + out.append("> 一处注释行(「禁恒真 check(true)」),非调用点。运行时计数大于静态计数=循环展开条目,") + out.append("> 两口径各自浮动,禁互相换算(tests/README「断言计数双口径」)。") + out.append("> 生成命令:`python tools/extract_checks.py --extract`(可复现;重新生成须 --force)。") + out.append("> 冻结纪律:每条的「标签原文」「cond 摘要」「quiet」三列为机器提取逐字保真,禁任何改写;") + out.append("> cond 摘要=原文跨行折叠单行(连续空白并一空格),单元格内 `|`→`\\|`、`\\`→`\\\\` 转义,读回还原。") + out.append("> 「白话释义」「处置」两列由维护回填,不属冻结面。") + out.append("> 出口对账:`python tools/extract_checks.py --diff game/tests/验证集清单.md`") + out.append("> ——标签/cond/计数三对齐才算迁移完(合同 §五出口机检)。") + out.append("> 维护义务:此后断言新增/删除/改判必须同步本清单(test-conventions §9.3 锚)。") + out.append("") + seq = 0 + by_func = [] + for e in entries: + if by_func and by_func[-1][0] == e["func"]: + by_func[-1][1].append(e) + else: + by_func.append((e["func"], [e])) + for idx, (fn, es) in enumerate(by_func, 1): + start, end = es[0]["line"], es[-1]["end_line"] + out.append("## %s %s(行 %d–%d · %d 条 · 去向:待分类回填)" % (pad3(idx), fn, start, end, len(es))) + out.append("") + out.append("| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet |") + out.append("|---|---|---|---|---|---|---|") + for e in es: + seq += 1 + out.append("| %d | %d | %s | %s | (待填) | | %s |" % ( + seq, e["line"], esc_cell(label_cell(e)), esc_cell(fold(e["cond"])), esc_cell(fold(e["quiet"])))) + out.append("") + out.append("---") + out.append("全源共 %d 章、%d 条(静态调用点口径)。" % (len(by_func), seq)) + return "\n".join(out) + "\n" + + +CELL_SPLIT_RE = re.compile(r"(? int: + cur, _funcs = extract(src_path) + frozen = parse_list(list_path) + cur_keys = {} + for e in cur: + cur_keys.setdefault(entry_key(e), []).append(e) + frozen_keys = {} + for e in frozen: + frozen_keys.setdefault(entry_key(e), []).append(e) + missing = [k for k in frozen_keys if k not in cur_keys] + added = [k for k in cur_keys if k not in frozen_keys] + # 成对归类:cond 同→标签变更;标签同→cond 变更 + label_changes, cond_changes = [], [] + used_added = set() + rest_missing = [] + for k in missing: + cond, lit, sfx, _q = k + pair = next((ka for ka in added if ka not in used_added and ka[0] == cond), None) + if pair is not None: + used_added.add(pair) + label_changes.append((k, pair)) + continue + pair = next((ka for ka in added if ka not in used_added and ka[1] == lit and ka[2] == sfx), None) + if pair is not None: + used_added.add(pair) + cond_changes.append((k, pair)) + continue + rest_missing.append(k) + rest_added = [k for k in added if k not in used_added] + ok = True + print("[extract_checks] 对账:%s vs 现提取 %s" % (list_path.name, src_path.name)) + print(" 清单条数=%d 现提取条数=%d %s" % ( + len(frozen), len(cur), "(计数对齐)" if len(frozen) == len(cur) else "(计数不齐)")) + + def show(k, tag): + cond, lit, sfx, q = k + s = " %s cond=%s label=%s" % (tag, _short(cond), _short(lit)) + if sfx: + s += " suffix=%s" % _short(fold(sfx)) + if q: + s += " quiet=%s" % fold(q) + print(s) + + if label_changes: + ok = False + print(" 标签字面变更 %d 对(cond 同、标签异):" % len(label_changes)) + for old, new in label_changes: + show(old, "-") + show(new, "+") + if cond_changes: + ok = False + print(" cond 字面变更 %d 对(标签同、cond 异):" % len(cond_changes)) + for old, new in cond_changes: + show(old, "-") + show(new, "+") + if rest_missing: + ok = False + print(" 消失 %d 条(清单有、现码无):" % len(rest_missing)) + for k in rest_missing: + print(" - [%s]" % frozen_keys[k][0].get("section", "?"), end="") + show(k, "") + if rest_added: + ok = False + print(" 新增 %d 条(现码有、清单无):" % len(rest_added)) + for k in rest_added: + es = cur_keys[k][0] + print(" + [func %s @%d]" % (es["func"], es["line"]), end="") + show(k, "") + if ok: + print("[extract_checks] PASS——标签/cond/计数三对齐,零差异") + return 0 + print("[extract_checks] FAIL——见上差异清单") + return 1 + + +def list_funcs(entries, funcs): + by_func = [] + for e in entries: + if by_func and by_func[-1][0] == e["func"]: + by_func[-1][1] = by_func[-1][1] + 1 + else: + by_func.append([e["func"], 1]) + checked = {f for f, _ in by_func} + for fn, n in by_func: + print("%s\t%d" % (fn, n)) + print("--- 无断言函数(helper,随套迁移)---") + for fn, ln in funcs: + if fn not in checked: + print("%s\t@%d" % (fn, ln)) + + +def main() -> int: + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + ap = argparse.ArgumentParser(description="smoke_all.gd 断言静态提取器(清单生成/对账)") + ap.add_argument("--extract", action="store_true", help="生成验证集清单") + ap.add_argument("--diff", metavar="清单文件", help="与清单逐条对账(批 B 出口机检)") + ap.add_argument("--list-funcs", action="store_true", help="打印函数清单与条数(编账本辅助)") + ap.add_argument("--src", default=str(DEFAULT_SRC), help="断言源文件") + ap.add_argument("--out", default=str(DEFAULT_OUT), help="--extract 输出路径") + ap.add_argument("--force", action="store_true", help="输出文件已存在时允许覆盖") + args = ap.parse_args() + if sum(bool(x) for x in (args.extract, args.diff, args.list_funcs)) != 1: + print("[extract_checks] 用法:--extract / --diff <清单文件> / --list-funcs 三选一") + return 2 + src_path = Path(args.src) + if not src_path.is_file(): + print("[extract_checks] FAIL:源文件不可读:%s" % src_path) + return 2 + if args.extract: + out = Path(args.out) + if out.exists() and not args.force: + print("[extract_checks] FAIL:清单已存在(%s)——冻结参照物防误覆,重生成须加 --force" % out) + return 2 + entries, _funcs = extract(src_path) + out.write_text(render_list(entries, _funcs, src_path.name), encoding="utf-8", newline="\n") + print("[extract_checks] 已生成 %s(%d 个调用点)" % (out, len(entries))) + return 0 + if args.diff: + lp = Path(args.diff) + if not lp.is_file(): + print("[extract_checks] FAIL:清单不可读:%s" % lp) + return 2 + return diff_report(lp, src_path) + entries, funcs = extract(src_path) + list_funcs(entries, funcs) + return 0 + + +if __name__ == "__main__": + sys.exit(main())