From b85b3b45e38f710342f62cf7a7d9781824d0cbd4 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 05:40:31 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A338=E5=8D=A1=E2=91=A0?= =?UTF-8?q?=20levelmap=E6=89=B9):=20level=5Fmap=5Fscreen=20=E4=B8=89?= =?UTF-8?q?=E6=AE=B5=E5=AE=A1=E8=AE=A1=E6=8C=87=E8=AE=A4=E8=90=BD=E5=9C=B0?= =?UTF-8?q?=E2=80=94=E2=80=94l1=20=E8=A7=86=E5=9B=BE=E7=9B=B4=E8=AF=BB?= =?UTF-8?q?=E6=8B=BC=E6=96=87=E6=A1=88=E8=BF=81=E8=A3=85=E9=85=8D=E5=B1=82?= =?UTF-8?q?=EF=BC=88main=5Fadventure=5Fflow=20=E6=96=B0=E5=A2=9E=20level?= =?UTF-8?q?=5Fmap=5Fstatus=5Fpayload=20=E4=B9=9D=E9=94=AE=E6=88=90?= =?UTF-8?q?=E5=93=81=EF=BC=8C=E8=A7=86=E5=9B=BE=20=5Frefresh=5Fstatus=20?= =?UTF-8?q?=E6=94=B9=20apply=5Fstatus=20=E7=BA=AF=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=EF=BC=8C=E5=96=82=E5=85=A5=E7=82=B9=3D=E7=BB=84=E5=90=88?= =?UTF-8?q?=E6=A0=B9=E4=B8=A4=E5=A4=84=20open=5Fchoices=20=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E5=88=B7=E6=96=B0=E6=97=B6=E7=82=B9=E4=B8=8D=E5=8F=98?= =?UTF-8?q?=EF=BC=89=EF=BC=8Bl2=20=5Flayout=20BFS=20=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E7=BC=93=E5=AD=98=EF=BC=88=E9=94=AE=3Dnodes=20=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E7=AD=89=E4=BB=B7=EF=BC=8Bcanvas.size=EF=BC=8C=5Fcont?= =?UTF-8?q?ent=5Fsize=20=E7=BB=8F=20=5Ffit=5Fcanvas=20=E6=B4=BE=E7=94=9F?= =?UTF-8?q?=E9=93=BE=E9=97=B4=E6=8E=A5=E8=A6=86=E7=9B=96=EF=BC=8C=E5=9B=9B?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E7=82=B9=E9=9B=B6=E6=94=B9=E5=8A=A8=EF=BC=89?= =?UTF-8?q?=EF=BC=8Bl3=20=E7=BB=98=E5=88=B6=E5=AD=97=E4=BD=93=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E9=A6=96=E5=8D=95=E5=8F=96=E4=B8=89=E5=A4=84=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=EF=BC=9Bl4=20=E6=94=B9=E5=89=8D=20golden=20=E5=9F=BA?= =?UTF-8?q?=E7=BA=BF=E6=96=B0=E5=BB=BA=EF=BC=88level=5Fmap.png=EF=BC=8BREA?= =?UTF-8?q?DME=20=E7=99=BB=E8=AE=B0=EF=BC=8C=E8=B7=A8=E8=B7=91=E6=AC=A1?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E5=AE=9E=E6=B5=8B=200.00%=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E9=AA=8C=E6=94=B6=3Dwr1=20=E6=94=B9=E5=90=8E=E5=AF=B9?= =?UTF-8?q?=E6=8B=8D=20changed=3D0.00%=EF=BC=8Bwr2=20=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=91=BD=E4=B8=AD/=E6=8D=A2=E4=BB=A3=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E5=8F=8C=E7=BB=BF=EF=BC=88is=5Fsame=20=E5=90=8C=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E5=88=A4=E5=88=AB=EF=BC=8C=E9=9B=B6=E7=94=9F=E4=BA=A7?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8=EF=BC=89=EF=BC=9B=E6=92=A4=E9=94=80?= =?UTF-8?q?=E8=AF=81=E6=98=8E=3D=E5=9B=9E=E9=80=80=E7=BC=93=E5=AD=98=20hun?= =?UTF-8?q?k=E2=86=92V1/V2=20=E7=BA=A2=E2=86=92=E8=BF=98=E5=8E=9F=E5=A4=8D?= =?UTF-8?q?=E7=BB=BF=EF=BC=9B=E5=91=88=E7=8E=B0=E9=80=90=E5=83=8F=E7=B4=A0?= =?UTF-8?q?=E4=B8=8D=E5=8F=98=EF=BC=88=E5=A5=91=E7=BA=A6=201=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E8=A7=86=E5=9B=BE=E4=BE=A7=E6=8B=BC=E8=A3=85=20grep?= =?UTF-8?q?=20=E6=B8=85=E9=9B=B6=EF=BC=88:278=20tint=20=E5=B1=82=E5=8F=B7?= =?UTF-8?q?=E8=AF=BB=E9=9D=9E=E6=8B=BC=E8=A3=85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/main_adventure_flow.gd | 32 +++++++++++ game/presentation/views/level_map_screen.gd | 53 +++++++----------- game/tests/golden/README.md | 10 +++- game/tests/golden/level_map.png | Bin 0 -> 257013 bytes .../presentation/level_map_info_fog_test.gd | 36 ++++++++++++ 5 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 game/tests/golden/level_map.png diff --git a/game/presentation/main_adventure_flow.gd b/game/presentation/main_adventure_flow.gd index e6aa68b9..e71d797f 100644 --- a/game/presentation/main_adventure_flow.gd +++ b/game/presentation/main_adventure_flow.gd @@ -120,6 +120,7 @@ static func _on_battle_end_continue(root: Variant) -> void: _enter_level_screen(root) root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) root.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner) + root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) root.run_scope.pending_candidates = [] root._refresh_presentations() return @@ -171,6 +172,7 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void: _enter_level_screen(root) root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held()))) root.level_screen.open_choices(candidates, root.run_scope.runner) + root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner)) root._refresh_presentations() static func _on_route_chosen(root: Variant, node_id: int) -> void: @@ -198,6 +200,36 @@ static func _exit_level_screen(root: Variant) -> void: root._close_modal(root.level_screen) root._sync_status_entry() # 同上 + +# 选路屏状态栏装配(视图零核心态拼装——architecture §1.3:读核心态→拼展示文案集中于装配器)。 +# 打开本屏/兑付确认回执后由组合根取成品喂入 apply_status(刷新时点与原视图内自刷一致)。 +static func level_map_status_payload(runner: LevelRunner) -> Dictionary: + var atk_bonus := int(runner.run_ledger.bonus.get("atk", 0)) # 加算区住 RunLedger + var ac_bonus := int(runner.run_ledger.bonus.get("armor_class", 0)) + var hp_bonus := int(runner.run_ledger.bonus.get("max_hp", 0)) + var atk_txt := "%d" % runner.effective_atk() + if atk_bonus > 0: + atk_txt += " +%d" % atk_bonus # 加成角标(ASCII 加号——防像素字体缺 ⁺ 字形出豆腐块) + var ac_txt := "%d" % runner.effective_ac() + if ac_bonus > 0: + ac_txt += " +%d" % ac_bonus + var parts: Array[String] = [] + if atk_bonus > 0: + parts.append("攻 +%d" % atk_bonus) + if hp_bonus > 0: + parts.append("上限 +%d" % hp_bonus) + if ac_bonus > 0: + parts.append("AC +%d" % ac_bonus) + return { + "level_label": String(runner.level_row.get("display_name", "")), + "gp_text": "◈ %d" % int(runner.gp), + "hp_text": "HP %d/%d" % [int(runner.hero_hp), int(runner.effective_max_hp())], + "hp_bar_redraw": true, + "attr_text": "攻击 %s · 护甲 AC %s" % [atk_txt, ac_txt], + "bonus_text": " · ".join(parts), + "bonus_row_visible": not parts.is_empty(), + } + static func _on_abandon_requested(root: Variant) -> void: # 放弃入口(冒险结构.md 已放开「MVP 无界面入口」后置条款; # 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型, diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index bd95e641..65a53761 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -52,6 +52,7 @@ var _drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记 var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) var _tint_level := 0 # 当前已应用 tint 的层号(0 = 未应用) +var _layout_cache := {"nodes": [], "canvas_size": Vector2.ZERO, "pos": {}} # 布局缓存(_layout 纯函数于 (nodes, canvas)——键=nodes 内容等价+画布尺寸,图态换代或画布尺寸变即自然失效) func _init() -> void: @@ -274,7 +275,6 @@ func open_choices(cands: Array, runner: LevelRunner) -> void: for cand in cands: _cand_ids.append(_id_of(cand)) _fit_canvas(_runner.nodes()) - _refresh_status() _apply_level_tint(int(_runner.level_row.get("level_index", 0))) visible = true if not was_visible: @@ -322,35 +322,17 @@ func _apply_level_tint(level_index: int) -> void: Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持 -# 状态栏刷新(命令式纪律——组合根打开本屏/兑付确认回执后重查;零推送信号订阅) -func _refresh_status() -> void: - if _runner == null: - return - _level_label.text = String(_runner.level_row.get("display_name", "")) - _gp_value.text = "◈ %d" % int(_runner.gp) - var max_hp: int = _runner.effective_max_hp() - _hp_value.text = "HP %d/%d" % [int(_runner.hero_hp), int(max_hp)] - _hp_bar.queue_redraw() - var atk_bonus := int(_runner.run_ledger.bonus.get("atk", 0)) # 加算区住 RunLedger - var ac_bonus := int(_runner.run_ledger.bonus.get("armor_class", 0)) - var atk_txt := "%d" % _runner.effective_atk() - if atk_bonus > 0: - atk_txt += " +%d" % atk_bonus # 加成角标(ASCII 加号——防像素字体缺 ⁺ 字形出豆腐块) - var ac_txt := "%d" % _runner.effective_ac() - if ac_bonus > 0: - ac_txt += " +%d" % ac_bonus - _attr_value.text = "攻击 %s · 护甲 AC %s" % [atk_txt, ac_txt] - var hp_bonus := int(_runner.run_ledger.bonus.get("max_hp", 0)) - var parts: Array[String] = [] - if atk_bonus > 0: - parts.append("攻 +%d" % atk_bonus) - if hp_bonus > 0: - parts.append("上限 +%d" % hp_bonus) - if ac_bonus > 0: - parts.append("AC +%d" % ac_bonus) - _bonus_value.text = " · ".join(parts) +# 状态栏投喂(命令式纪律——组合根经 level_map_status_payload 取成品后喂入;零推送信号订阅、零核心态拼装) +func apply_status(payload: Dictionary) -> void: + _level_label.text = String(payload["level_label"]) + _gp_value.text = String(payload["gp_text"]) + _hp_value.text = String(payload["hp_text"]) + if bool(payload["hp_bar_redraw"]): + _hp_bar.queue_redraw() + _attr_value.text = String(payload["attr_text"]) + _bonus_value.text = String(payload["bonus_text"]) # 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings) - _bonus_row.visible = not parts.is_empty() or _feat_value.text != "" + _bonus_row.visible = bool(payload["bonus_row_visible"]) or _feat_value.text != "" # ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ---- @@ -444,6 +426,7 @@ func _draw_graph(canvas: Control) -> void: _drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面) var nodes: Array[Variant] = _runner.nodes() var pos := _layout(nodes, canvas) + var font := get_theme_default_font() # 绘制路径字体单次取用(三处 draw_string 共享——零语义差) var current_id: int = _runner.current_node_id() # 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析 var visited: Array[Variant] = _runner.visited_ids() @@ -499,12 +482,11 @@ func _draw_graph(canvas: Control) -> void: canvas.draw_texture_rect(icon, Rect2(p - Vector2(NODE_ICON_PX, NODE_ICON_PX) / 2.0, Vector2(NODE_ICON_PX, NODE_ICON_PX)), false, tint) else: - var font := get_theme_default_font() var label := String(NODE_LABELS.get(t, "?")) canvas.draw_string(font, p + Vector2(-6, 2), label, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, UiKit.INK_ROUTE) # 编号(右上角——与图标占位区零重叠;双色墨随态) var num_ink := UiKit.INK_ROUTE if col == COL_ROOM_CURRENT else UiKit.WARM - canvas.draw_string(get_theme_default_font(), Vector2(rect.end.x - 16.0, rect.position.y + 8.0), + canvas.draw_string(font, Vector2(rect.end.x - 16.0, rect.position.y + 8.0), str(nid), HORIZONTAL_ALIGNMENT_RIGHT, 14, 10, num_ink) # 类型文字标签(demo ④:节点下方 11px;当前位=「当前位置」金、候选=类型名+「 · 可选」烛光黄、 # 其余=类型名暖米弱化;文字+图标+形状三通道) @@ -512,12 +494,16 @@ func _draw_graph(canvas: Control) -> void: rec["draw_label"] = true rec["label_text"] = String(disp["label_text"]) var type_ink := COL_ROOM_CURRENT if nid == current_id else (UiKit.CANDLE if cand_ids.has(nid) else UiKit.WARM_FAINT) - canvas.draw_string(get_theme_default_font(), Vector2(p.x - 44.0, rect.end.y + 15.0), + canvas.draw_string(font, Vector2(p.x - 44.0, rect.end.y + 15.0), String(disp["label_text"]), HORIZONTAL_ALIGNMENT_CENTER, 88, 11, type_ink) _drawn_disp[nid] = rec func _layout(nodes: Array, canvas: Control) -> Dictionary: + # 缓存命中键 = nodes 内容等价(GDScript Array== 逐元素比较)+画布尺寸——布局是二者与 + # _content_size(经 _fit_canvas 派生于 nodes)的纯函数,键间接覆盖派生链;换代即自然失效。 + if _layout_cache.nodes == nodes and _layout_cache.canvas_size == canvas.size: + return _layout_cache.pos var layer := {} var slot := {} _assign_layers(nodes, layer, slot) @@ -526,6 +512,9 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary: for node in nodes: pos[node] = origin + Vector2(LAYER_BASE_X + int(layer[node]) * LAYER_STEP_X, SLOT_BASE_Y + int(slot[node]) * SLOT_STEP_Y) + _layout_cache.nodes = nodes + _layout_cache.canvas_size = canvas.size + _layout_cache.pos = pos return pos diff --git a/game/tests/golden/README.md b/game/tests/golden/README.md index d32fd5d1..658d3a9d 100644 --- a/game/tests/golden/README.md +++ b/game/tests/golden/README.md @@ -1,7 +1,8 @@ # 视觉 golden 基线(渲染闸的比对基准) -四张基线:棋盘三图(`board_L1_dungeon` / `board_L2_underdark` / `board_L3_deep_underdark`, -裁剪区 900×760)+ 启动态菜单(`menu_boot`,全屏)。比对判据与工具见 `tools/golden_compare.py` +五张基线:棋盘三图(`board_L1_dungeon` / `board_L2_underdark` / `board_L3_deep_underdark`, +裁剪区 900×760)+ 启动态菜单(`menu_boot`,全屏)+ 关卡画面(`level_map`,全屏)。 +比对判据与工具见 `tools/golden_compare.py` (逐像素灰度差 >16 的像素占比 ≤2%)。 ## 时效判定纪律(迭代 29 T7 立) @@ -27,3 +28,8 @@ 字形——晚于 2026-10-08 建基提交,与本批零因果。呈报后用户裁「重制三张基线」, 经 `tools/golden_baseline_refresh.sh` 换代;复闸 changed = 0.00% / 0.00% / 0.00%, 启动态菜单 0.00%(未重制)。 +- 2026-10-26(迭代 38 卡① l4):**新增基线 `level_map`**(改前建基——时点铁序)。 + 来源 = shot_probe `_snap("level")` 帧(入口停点出图后第 4 帧,淡入完成态), + 跨跑次逐像素一致性实测 changed=0.00% 后落盘。承载迭代 38 卡① l1–l3 结构重构的 + 逐像素对拍(改后对拍 changed=0.00% 为验收主锚);对拍经 `tools/golden_compare.py` + 直跑(render_gate 现行只对拍棋盘三图+menu_boot,闸件不在本批改动面)。 diff --git a/game/tests/golden/level_map.png b/game/tests/golden/level_map.png new file mode 100644 index 0000000000000000000000000000000000000000..01135c41fb719e500cc8acf8cf134488ff4af1fe GIT binary patch literal 257013 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

j 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 void: _test_details_never_shown() _test_label_three_states() await _test_big_graph_fit(db) + await _test_layout_cache(db) quit(Kit.finish("level_map_info_fog_test")) @@ -189,3 +190,38 @@ func _test_big_graph_fit(db) -> void: and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"])) Kit.check("大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕", not bool(disp[32]["draw_icon"])) screen.queue_free() + + +# ---- 布局缓存命中与换代失效〔levelmap批 W2〕---- +# 真实入口 = open_choices 后的真实绘制路径(_draw_graph→_layout,与 hover→_candidate_at 链同一缓存层); +# 命中读数 = is_same 同实例判别(重算必产新字典、命中返回缓存原实例)——零生产计数器。 + +func _test_layout_cache(db) -> void: + var nodes := _big_graph_nodes() + var runner = _runner(db, nodes, [nodes[1]]) + var screen = ScreenScript.new() + root.add_child(screen) + screen.open_choices([nodes[1]], runner) + await process_frame + await process_frame + var p1: Dictionary = screen.layout_probe(nodes) + var p2: Dictionary = screen.layout_probe(nodes) + Kit.check("布局缓存:同 nodes 两次查询返回同一实例(第二次命中缓存)〔levelmap批 V1〕", + is_same(p1, p2)) + # 换代失效:节点集内容变 → 缓存键(内容等价)击穿 → 重算换写。 + # 判别式 = 同一性:若缓存未随换代失效,新图查询将命中旧缓存返回同实例;重算必产新字典。 + # (不回查旧图——换代后 _content_size 已随 open_choices 重拟合到新图,旧图混查属生产外用法。) + var chain: Array = [ + {"id": 0, "type": "battle", "depth": 0, "next": [1]}, + {"id": 1, "type": "boss", "depth": 1, "next": []}, + ] + runner.level_row["nodes"] = chain + screen.open_choices([chain[0]], runner) + await process_frame + await process_frame # 换代图真实绘制路径重算(缓存随内容变失效重写) + var pc1: Dictionary = screen.layout_probe(chain) + var pc2: Dictionary = screen.layout_probe(chain) + Kit.check("布局缓存换代:新图查询返回新实例非旧缓存(失效重算生效)〔levelmap批 V2〕", + not is_same(p1, pc1) and is_same(pc1, pc2) + and (pc1[chain[0]] as Vector2) != (p1[nodes[0]] as Vector2)) + screen.queue_free() From a15370c524c43493974be45740d19bfca26ecf38 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 05:52:49 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A338=E5=8D=A1=E2=91=A2?= =?UTF-8?q?=20=E6=95=88=E6=9E=9C=E6=96=87=E6=A1=88=E6=89=B9):=20effect=5Fe?= =?UTF-8?q?ngine=20=E6=A0=A1=E9=AA=8C=E6=96=87=E6=A1=88=E8=AE=A1=E6=95=B0?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E2=80=94=E2=80=94b1=20fact=20=E5=87=BA?= =?UTF-8?q?=E5=9F=9F=E6=8A=A5=E9=94=99=E6=96=87=E6=A1=88=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=8F=96=20FACTS.size()=EF=BC=88=E7=94=A8=E6=88=B7=E8=A3=81=20?= =?UTF-8?q?Q=20=E7=94=B2=EF=BC=9A=E4=B8=A4=E6=AC=A1=E6=BC=82=E7=A7=BB?= =?UTF-8?q?=E5=AE=9E=E8=AF=81=EF=BC=8C=E7=A6=81=E7=95=99=E6=AD=BB=E6=95=B0?= =?UTF-8?q?=EF=BC=89=EF=BC=8Bb2=20EFFECT=5FKINDS=20=E5=A4=B4=E6=B3=A8?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E8=A1=A8=E8=BF=B0=E6=B2=BB=E7=90=86=EF=BC=88?= =?UTF-8?q?=E5=8E=BB=E5=86=99=E6=AD=BB=E3=80=8C=E5=AE=9A=E7=A8=BF=2019=20?= =?UTF-8?q?=E8=A1=8C=EF=BC=9B=E7=B1=BB=E5=9E=8B=E5=AE=9E=E6=95=B0=2024?= =?UTF-8?q?=E3=80=8D=E2=86=92=E5=AE=9E=E6=95=B0=E4=BB=A5=E5=B8=B8=E9=87=8F?= =?UTF-8?q?=E4=B8=BA=E5=87=86=EF=BC=8C=E5=8E=86=E5=8F=B2=E7=AC=94=E8=AF=AF?= =?UTF-8?q?=E5=8F=99=E4=BA=8B=E5=8F=A5=E6=8C=89=E7=A6=81=E5=86=99=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E8=AE=B0=E5=BD=95=E7=BA=A2=E7=BA=BF=E4=BF=9D=E7=95=99?= =?UTF-8?q?=EF=BC=89=EF=BC=9Bwr1=20=E6=96=AD=E8=A8=80=E4=B8=80=E6=9D=A1?= =?UTF-8?q?=EF=BC=88message=20=E5=90=AB=E8=AF=8D=E8=A1=A8=E5=AE=9E?= =?UTF-8?q?=E6=95=B0=2019=E3=80=81=E6=97=A0=E3=80=8C9=20=E9=A1=B9=E3=80=8D?= =?UTF-8?q?=E5=86=99=E6=AD=BB=EF=BC=8Ceffect=5Fwords=5Ft1=20=E2=91=A2-b=20?= =?UTF-8?q?=E6=AE=B5=EF=BC=89=EF=BC=9B=E6=92=A4=E9=94=80=E8=AF=81=E6=98=8E?= =?UTF-8?q?=3D=E5=9B=9E=E9=80=80=20b1=20hunk=E2=86=92=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E7=BA=A2=E2=86=92=E8=BF=98=E5=8E=9F=E5=A4=8D=E7=BB=BF=EF=BC=88?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E7=BA=A2=E7=BB=BF=E5=A4=8D=E7=8E=B0=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E5=90=88=E6=B3=95=E6=95=B0=E6=8D=AE=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E9=9B=B6=E5=8F=98=E5=8C=96=EF=BC=88=E5=A5=91=E7=BA=A6=202?= =?UTF-8?q?=EF=BC=8Cerrs=20=E7=BB=93=E6=9E=84=E9=9B=B6=E8=A7=A6=E7=A2=B0?= =?UTF-8?q?=EF=BC=89=EF=BC=9BT0=20=E5=A4=8D=E6=A0=B8=20FACTS=2019/EFFECT?= =?UTF-8?q?=5FKINDS=2032=20=E5=8F=8C=E8=AF=8D=E8=A1=A8=E5=AE=9E=E6=95=B0?= =?UTF-8?q?=E5=9C=A8=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/effect_engine.gd | 4 ++-- game/tests/unit/core/effect_words_t1_test.gd | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/game/core/effect_engine.gd b/game/core/effect_engine.gd index e425518e..ebff39b7 100644 --- a/game/core/effect_engine.gd +++ b/game/core/effect_engine.gd @@ -18,7 +18,7 @@ const EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "attack_commi ## 原 MINE_HOOKS 的同义迁移:挖掘/封印/轮末结算面事件) const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_settle", "round_end", "reward_roll"] -## 效果类型词汇(解释器 switch 闭集——需求卡片 §3.3 定稿 19 行;类型实数 24,含共享行拆型: +## 效果类型词汇(解释器 switch 闭集——词表实数以 EFFECT_KINDS 常量为准,含共享行拆型: ## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、 ## bias_rewards/preview。需求卡片标题计 23 系笔误,落码按行内容全收——commit 留痕) const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket", @@ -357,7 +357,7 @@ static func validate_trigger_row(rid: String, row: Dictionary, triggers: Array, errs.append({"category": "zero_flat", "message": "%s when 键出白名单: %s" % [rid, String(k)]}) var fact := String(c.get("fact", "")) if not FACTS.has(fact): - errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 9 项): %s" % [rid, fact]}) + errs.append({"category": "enum", "message": "%s fact 出域(事实词汇封闭 %d 项): %s" % [rid, FACTS.size(), fact]}) if not OPS.has(String(c.get("op", "eq"))): errs.append({"category": "enum", "message": "%s when 算子出域: %s" % [rid, String(c.get("op", ""))]}) var do_d: Dictionary[Variant, Variant] = tr.get("do", {}) as Dictionary diff --git a/game/tests/unit/core/effect_words_t1_test.gd b/game/tests/unit/core/effect_words_t1_test.gd index 0979ef5d..3f5d44f1 100644 --- a/game/tests/unit/core/effect_words_t1_test.gd +++ b/game/tests/unit/core/effect_words_t1_test.gd @@ -45,6 +45,10 @@ func _init() -> void: EffectEngineScript.validate_trigger_row("出域op", {}, [{"on": "dig_stop", "when": [{"fact": "ap_left", "op": "mod_odd", "value": 2}], "do": {"kind": "heal", "value": 1}}], errs) EffectEngineScript.validate_trigger_row("出域kind", {}, [{"on": "dig_stop", "do": {"kind": "nope_kind", "value": 1}}], errs) Kit.check_quiet(errs.size() == 4, "出域 on/fact/op/kind 各一行全部被拒(4 违例)〔专长批 V1〕") + # ③-b fact 出域文案随词表实数(动态取 FACTS.size()——禁写死计数,词表增删文案自动随)〔效果文案批 V1〕 + Kit.check_quiet(String(errs[1].get("message", "")).contains(str(EffectEngineScript.FACTS.size())) + and not String(errs[1].get("message", "")).contains(" 9 项"), + "fact 出域文案含词表实数 %d、无写死计数〔效果文案批 V1〕" % EffectEngineScript.FACTS.size()) # ④ 运行期命中序(合法新事件;dispatch 出域 fail-fast=debug assert;引擎无状态——计数器经 ctx) var fx = EffectEngineScript.new(null) From 00ed0236597c036532319bc54b5de84bc7747048 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 07:08:32 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A338=E5=8D=A1=E2=91=A4?= =?UTF-8?q?=20=E5=A5=96=E5=8A=B1=E8=8A=82=E7=82=B9=E6=B8=85=E6=89=AB?= =?UTF-8?q?=E6=89=B9):=20=E3=80=8C=E5=A5=96=E5=8A=B1=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E9=9B=B6=E5=85=91=E4=BB=98=E7=9B=B4=E9=80=9A=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E3=80=8D=E8=BF=87=E6=97=B6=E8=A1=A8=E8=BF=B0=E6=97=8F=E5=88=A4?= =?UTF-8?q?=E6=8D=AE=E9=9D=A2=E6=B8=85=E6=89=AB=E2=80=94=E2=80=94d1=20leve?= =?UTF-8?q?l=5Frunner=20=E4=B8=A4=E6=B3=A8=E9=87=8A=E6=8D=A2=E4=BB=A3?= =?UTF-8?q?=EF=BC=88reward=3D=E4=B8=89=E9=80=89=E4=B8=80=E5=81=9C=E7=82=B9?= =?UTF-8?q?=E7=8E=B0=E7=8A=B6=E5=87=86=E7=A1=AE=E8=A1=A8=E8=BF=B0=EF=BC=89?= =?UTF-8?q?=EF=BC=8Bd2=20autoplay=20:484=20=E7=90=86=E7=94=B1=E5=8F=A5?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3=EF=BC=88sim=20=E7=AD=96=E7=95=A5=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E8=AF=AD=E4=B9=89=EF=BC=8C=E8=A1=8C=E4=B8=BA=E9=9B=B6?= =?UTF-8?q?=E8=A7=A6=E7=A2=B0=EF=BC=89=EF=BC=8Bd3=20battle=5Fnarrator=20?= =?UTF-8?q?=E6=88=98=E6=8A=A5=E6=96=87=E6=A1=88=E3=80=8C=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=BE=85=E5=AE=9A=E2=80=94=E2=80=94=E7=9B=B4?= =?UTF-8?q?=E9=80=9A=E3=80=8D=E2=86=92=E3=80=8C=E4=B8=89=E9=80=89=E4=B8=80?= =?UTF-8?q?=E2=80=94=E2=80=94=E9=80=89=E6=AF=95=E6=8E=A8=E8=BF=9B=E3=80=8D?= =?UTF-8?q?=EF=BC=88=E5=94=AF=E4=B8=80=E7=8E=A9=E5=AE=B6=E5=8F=AF=E8=A7=81?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E6=94=B9=E5=8A=A8=EF=BC=8Cwr2=20=E4=B8=93?= =?UTF-8?q?=E8=A1=8C=EF=BC=89=EF=BC=9Bd4=20wa4=20=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E8=BD=AC=E6=AD=A3=E2=80=94=E2=80=94=E7=97=85?= =?UTF-8?q?=E6=A0=B9=3D=E5=8E=9F=E6=8D=95=E8=8E=B7=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E6=9F=A5=20=5Fcurio=5Fpending=EF=BC=88=E9=80=9A=E5=85=B3?= =?UTF-8?q?=E4=B8=89=E9=80=89=E4=B8=80=E5=81=9C=E7=82=B9=E6=80=81=EF=BC=89?= =?UTF-8?q?=E4=B8=8E=E5=A5=96=E5=8A=B1=E5=A4=B4=E9=83=A8=E3=80=8C=E5=85=B3?= =?UTF-8?q?=E5=8D=A1=E5=A5=96=E5=8A=B1=E7=82=B9=E3=80=8D=E4=BA=92=E6=96=A5?= =?UTF-8?q?=E3=80=81=E6=81=92=E4=B8=8D=E5=8F=AF=E5=91=BD=E4=B8=AD=EF=BC=88?= =?UTF-8?q?=E6=8B=A6=E6=88=AA=E8=83=BD=E5=8A=9B=E6=9C=AA=E8=AF=81=E5=AE=9E?= =?UTF-8?q?=E5=86=B5=EF=BC=89=EF=BC=8C=E9=9A=8F=E4=BF=AE=E6=8C=82=20=5Fnod?= =?UTF-8?q?e=5Fcurio=5Fpending=EF=BC=88=E5=A5=96=E5=8A=B1=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=81=9C=E7=82=B9=E6=80=81=EF=BC=8C=E5=A4=B4=E9=83=A8?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=B8=B2=E9=9B=B6=E8=A7=A6=E7=A2=B0=EF=BC=89?= =?UTF-8?q?=EF=BC=8B=E5=AE=9A=E5=90=91=E9=80=89=E8=B7=AF=E5=B0=8F=E6=94=B9?= =?UTF-8?q?=EF=BC=88=E5=80=99=E9=80=89=E5=90=AB=E5=A5=96=E5=8A=B1=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E4=BC=98=E5=85=88=EF=BC=8Cpress=5Fcandidate=20?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E5=91=BD=E4=B8=AD=E8=B7=AF=E5=BE=84=E9=9B=B6?= =?UTF-8?q?=E5=8F=98=E5=8C=96=EF=BC=89=EF=BC=8B=E5=BF=85=E8=BE=BE=E5=88=A4?= =?UTF-8?q?=E6=8D=AE=EF=BC=88=E5=85=A8=E7=A8=8B=E6=9C=AA=E5=88=B0=E5=81=9C?= =?UTF-8?q?=E7=82=B9=E5=8D=B3=E7=BA=A2=EF=BC=89=EF=BC=9Bwr2=20=E6=88=98?= =?UTF-8?q?=E6=8A=A5=E6=96=87=E6=A1=88=E6=96=AD=E8=A8=80=EF=BC=88=E5=90=8C?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E8=B7=AF=E5=BE=84=E6=89=BF=E8=BD=BD=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E6=92=A4=E9=94=80=E8=AF=81=E6=98=8E=E4=B8=A4=E6=9D=A1?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=EF=BC=9A=E5=9B=9E=E9=80=80=20d3=E2=86=92wr2?= =?UTF-8?q?=20=E5=8D=95=E7=BA=A2=E3=80=81=E5=9B=9E=E9=80=80=20d4=20?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E2=86=92wa4=20=E5=BF=85=E8=BE=BE=E7=BA=A2?= =?UTF-8?q?=E3=80=81=E8=BF=98=E5=8E=9F=E5=A4=8D=E7=BB=BF=EF=BC=88=E9=97=AE?= =?UTF-8?q?=E9=A2=98=3D0=EF=BC=89=EF=BC=9Bwr1=20=E4=B8=A4=E4=B8=B2=20grep?= =?UTF-8?q?=20=E5=88=A4=E6=8D=AE=E9=9B=B6=E8=BE=93=E5=87=BA=EF=BC=9B?= =?UTF-8?q?=E6=8E=A2=E9=92=88=E4=B8=8D=E8=BF=9B=E9=AA=8C=E8=AF=81=E9=9B=86?= =?UTF-8?q?=E6=B8=85=E5=8D=95=E6=8F=90=E5=8F=96=E9=9D=A2=EF=BC=88extract?= =?UTF-8?q?=5Fchecks=20=E6=89=AB=20tests/unit|integration|system=5Ftest?= =?UTF-8?q?=E2=80=94=E2=80=94=E6=97=A0=E5=90=8C=E6=AD=A5=E4=B9=89=E5=8A=A1?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/level_runner.gd | 6 +++--- game/presentation/battle_narrator.gd | 2 +- game/probe/ui_click_probe.gd | 29 ++++++++++++++++++++++++++-- game/sim/level_autoplay.gd | 2 +- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 8d88a30c..ca2c3116 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -7,7 +7,7 @@ class_name LevelRunner extends RefCounted -signal node_entered(node: Dictionary, info: Dictionary) ## info 按 type:battle/boss={} · reward={}(内容待定) · rest={healed,hp,max_hp} · shop={gp} +signal node_entered(node: Dictionary, info: Dictionary) ## info 按 type:battle/boss={} · reward={}(三选一停点空载荷——内容经 present_node_curio 拉取) · rest={healed,hp,max_hp} · shop={gp} signal choices_presented(candidates: Array) ## 选路口(兑付确认后——候选节点引用数组,零自动推进) signal step_row(row: Dictionary) ## 转发当前 战斗结算 结算行 signal round_started(round_index: int) @@ -474,8 +474,8 @@ func _enter_node(node: Dictionary) -> bool: "battle", "boss": return _start_battle(node) "reward": - # 奖励点内容未定(可选类道具不在池、功能道具渠道未定—— - # 节点保留占位、零兑付直通);推进 = 兑付确认后选路(零自动推进) + # 奖励节点 = 三选一停点(与通关三选一同池同权重同上限);呈现编排归 + # curio_interact_controller.present_node_curio;推进 = 兑付确认后选路(零自动推进) node_entered.emit(node, {}) return true "rest": diff --git a/game/presentation/battle_narrator.gd b/game/presentation/battle_narrator.gd index d66cf267..8cfcb194 100644 --- a/game/presentation/battle_narrator.gd +++ b/game/presentation/battle_narrator.gd @@ -12,7 +12,7 @@ static func node_banner(runner: LevelRunner, node: Dictionary, t: String) -> Str static func reward_line() -> String: - return "[color=#%s]关卡奖励点(节点内容待定——直通)[/color]" % UiKit.HEX_ROUTE_NOTE + return "[color=#%s]关卡奖励点(三选一——选毕推进)[/color]" % UiKit.HEX_ROUTE_NOTE static func rest_line(healed: int, hp: int, max_hp: int) -> String: diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index b57d332c..39d84aa2 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -415,16 +415,27 @@ func _ready() -> void: # 未适配则走到即卡死)。两态分派:result 态(兑付反馈——点继续推进)/ # 待选态(点首件+确认)。真实信号路径全线。〔无替换子态——装配不可变契约。〕 # 三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。 - if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty() and String(main.curio_overlay._header).contains("关卡奖励点"): + if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure._node_curio_pending.is_empty() and String(main.curio_overlay._header).contains("关卡奖励点"): + # 停点态判据 = _node_curio_pending(奖励节点三选一停点住此——原条件查 + # _curio_pending〔通关三选一态〕与奖励头部互斥、恒不可命中=拦截能力未证病根, + # 卡⑤ d4 转正随修;头部校验串本体零触碰) _tested_curio_head = true var ch := String(main.curio_overlay._header) if not ch.contains("冒险牌库") or not ch.contains("属性值提升与补给") or ch.contains("同权重") or ch.contains("同上限") or ch.contains("提升池"): _problems.append("三选一头部旧话残留或新话缺失(wa4——%s)" % ch.replace(" ", "|")) + # 奖励节点战报文案换代断言(卡⑤ wr2——reward_line 同屏打印于停点拉起前, + # 同一驱动路径承载;旧「内容待定」残留或新话缺失即红) + var logtext: String = main.log_view.parsed_text() + if not logtext.contains("关卡奖励点(三选一——选毕推进)") or logtext.contains("内容待定"): + _problems.append("奖励节点战报文案未换代(卡⑤ wr2——旧话残留或新话缺失)") if not main._input_locked(): _problems.append("三选一停点打开时输入未锁定") if main.curio_overlay._continue_btn.visible: main.curio_overlay.press_continue() + elif main.run_scope.adventure != null and not main.run_scope.adventure._node_curio_pending.is_empty(): + main.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与通关停点同形) + main.curio_overlay.press_confirm() elif main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty(): main.curio_overlay.press_card(0) main.curio_overlay.press_confirm() @@ -470,7 +481,16 @@ func _ready() -> void: cap_found = true if not cap_found: _problems.append("地图屏加成帽未换代为「属性值提升」(wa2)") - main.level_screen.press_candidate(_route_clicks % 2) + # 选路定向(卡⑤ d4——wa4 转正的必达前提):候选含奖励节点优先选(真实 + # press_candidate 命中路径零变化,只改索引策略);无奖励候选回退交替(岔路使用率观测面保留) + var pick := _route_clicks % 2 + if main.run_scope.runner != null: + for nd in main.run_scope.runner.nodes(): + var nd_id: int = main.run_scope.runner.node_id_of(nd) + if String(nd.get("type", "")) == "reward" and main.level_screen._cand_ids.has(nd_id): + pick = main.level_screen._cand_ids.find(nd_id) + break + main.level_screen.press_candidate(pick) _route_clicks += 1 _seals_placed.clear() continue @@ -695,6 +715,11 @@ func _ready() -> void: else: print("[click_probe] 开场行②③判定:续玩重建开场行带后缀(bonus.atk=5、pct=0)— wait=%d 帧" % ol_r_wait) + # wa4 必达判据(卡⑤ d4 转正——条件位退役):定向选路保证到达奖励节点三选一停点, + # 全程未命中即红(拦截能力必证——test-conventions §6 条件断言转正口径) + if not _tested_curio_head: + _problems.append("wa4 转正失守:探针全程未到达奖励节点三选一停点(定向选路失效或奖励节点不可达)〔卡⑤〕") + print("[click_probe] 循环退出(done=%s budget=%s)问题=%d" % [ str(_done), str(action_budget), _problems.size()]) for p in _problems: diff --git a/game/sim/level_autoplay.gd b/game/sim/level_autoplay.gd index a719f0d1..7d020364 100644 --- a/game/sim/level_autoplay.gd +++ b/game/sim/level_autoplay.gd @@ -481,7 +481,7 @@ static func _on_node_entered(node: Dictionary, info: Dictionary, runner, st: Dic st.boss_depth = int(node.get("depth", -1)) st.hp_at_boss = runner.hero_hp elif t == "reward": - runner.continue_after_node() # 奖励点 = 内容未定直通节点 + runner.continue_after_node() # autoplay 直通不解三选一——sim 策略选择(测量期不进兑付面,非机制缺位) elif t == "rest": st.rest_healed = int(info.get("healed", 0)) st.hp_before_rest = int(info.get("hp", 0)) - st.rest_healed From ce49a8eec28aa043103da02a254298caf42028e2 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 07:37:34 +0800 Subject: [PATCH 4/9] =?UTF-8?q?fix(=E8=BF=AD=E4=BB=A338=E5=8D=A1=E2=91=A5?= =?UTF-8?q?=20=E7=BB=93=E7=AE=97=E8=A1=8C=E7=BC=93=E5=86=B2=E6=89=B9):=20?= =?UTF-8?q?=5Fround=5Frows=20=E7=B1=BB=E5=9E=8B=E5=A3=B0=E6=98=8E=E4=B8=8E?= =?UTF-8?q?=E6=89=BF=E8=BD=BD=E6=95=B0=E6=8D=AE=E5=AF=B9=E9=BD=90=EF=BC=88?= =?UTF-8?q?Array[Dictionary]=E2=86=92Array[String]=E2=80=94=E2=80=94append?= =?UTF-8?q?=20=E7=9A=84=E6=98=AF=20=5Fformat=5Frow=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E4=BA=A7=E7=89=A9=20String=EF=BC=8CTypedArray=20?= =?UTF-8?q?=E5=BC=82=E5=9E=8B=20push=5Fback=20=E9=9D=99=E9=BB=98=E4=B8=A2?= =?UTF-8?q?=E5=BC=83=E8=87=B4=E7=BC=93=E5=86=B2=E6=81=92=E7=A9=BA=E3=80=81?= =?UTF-8?q?=E3=80=8C=E4=B8=8A=E8=BD=AE=E6=91=98=E8=A6=81=E3=80=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E8=87=AA=E5=BC=95=E5=85=A5=E7=B1=BB=E5=9E=8B=E9=94=99?= =?UTF-8?q?=E4=BB=A5=E6=9D=A5=E4=BB=8E=E6=9C=AA=E6=98=BE=E7=A4=BA=EF=BC=89?= =?UTF-8?q?=EF=BC=9Bwr1-a=3D=E6=8E=A2=E9=92=88=E6=97=A5=E5=BF=97=E8=AF=A5?= =?UTF-8?q?=20push=5Fback=20=E9=94=99=E8=AF=AF=E8=A1=8C=E5=BD=92=E9=9B=B6?= =?UTF-8?q?=EF=BC=88=E6=94=B9=E5=89=8D=E5=9F=BA=E7=BA=BF=20148=20=E5=A4=84?= =?UTF-8?q?=E2=86=92=E6=94=B9=E5=90=8E=200=EF=BC=89=EF=BC=9Bwr1-b=3D?= =?UTF-8?q?=E4=B8=8A=E8=BD=AE=E6=91=98=E8=A6=81=E6=81=A2=E5=A4=8D=E6=AD=A3?= =?UTF-8?q?=E5=90=91=E6=96=AD=E8=A8=80=EF=BC=88=E6=88=98=E4=B8=AD=E5=9C=BA?= =?UTF-8?q?=E8=A7=82=E6=B5=8B=E7=82=B9=20round=E2=89=A52=20=E4=B8=94?= =?UTF-8?q?=E6=8E=92=E6=92=AD=E9=98=9F=E5=88=97=E5=B7=B2=E7=A9=BA=3Dfinish?= =?UTF-8?q?=5Fsettlement=5Fround=20=E5=B7=B2=E5=88=B7=EF=BC=8C=5Fsummary?= =?UTF-8?q?=20=E9=9D=9E=E7=A9=BA=E2=80=94=E2=80=94=E4=B8=8D=E6=8C=82?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E5=87=BA=E5=8F=A3=E5=9B=A0=E6=94=B6=E5=B0=BE?= =?UTF-8?q?=E7=BB=AD=E7=8E=A9=E9=87=8D=E5=BB=BA=E6=AE=B5=E4=BC=9A=E5=86=8D?= =?UTF-8?q?=E8=B5=B0=20log=5Fview.clear()=EF=BC=89=EF=BC=9B=E6=92=A4?= =?UTF-8?q?=E9=94=80=E8=AF=81=E6=98=8E=E5=AE=9E=E6=B5=8B=3D=E5=9B=9E?= =?UTF-8?q?=E9=80=80=20g1=E2=86=92148=20=E9=94=99=E8=AF=AF=E8=A1=8C?= =?UTF-8?q?=E5=A4=8D=E7=8E=B0=EF=BC=8Bwr1-b=20=E7=BA=A2=EF=BC=8C=E8=BF=98?= =?UTF-8?q?=E5=8E=9F=E5=A4=8D=E7=BB=BF=EF=BC=88click10=20=E9=97=AE?= =?UTF-8?q?=E9=A2=98=3D0=20=E5=90=8C=E6=BA=90=E6=80=81=EF=BC=89=EF=BC=9B?= =?UTF-8?q?=E9=99=A4=E7=94=B3=E6=8A=A5=E7=9A=84=E6=91=98=E8=A6=81=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=A4=96=E5=91=88=E7=8E=B0=E9=9B=B6=E5=8F=98=E5=8C=96?= =?UTF-8?q?=EF=BC=88append/=E6=B6=88=E8=B4=B9/=E6=B8=85=E7=A9=BA=E6=97=B6?= =?UTF-8?q?=E5=BA=8F=E9=9B=B6=E8=A7=A6=E7=A2=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/views/battle_log_view.gd | 2 +- game/probe/ui_click_probe.gd | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/game/presentation/views/battle_log_view.gd b/game/presentation/views/battle_log_view.gd index 905e7cf4..91f66465 100644 --- a/game/presentation/views/battle_log_view.gd +++ b/game/presentation/views/battle_log_view.gd @@ -14,7 +14,7 @@ var _log: RichTextLabel var _summary: Label var _hint: Label var _mode := "dig" # dig = 收起摘要 / settle = 实时流(模式归组合根驱动) -var _round_rows: Array[Dictionary] = [] # 本结算轮行缓冲(上轮摘要构建源) +var _round_rows: Array[String] = [] # 本结算轮行缓冲(上轮摘要构建源——append 的是 _format_row 格式化产物 String) var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count}) diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index 39d84aa2..4f83b4f7 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -28,6 +28,7 @@ var _tested_refresh_chain := false var _tested_tooltip := false var _tested_copy := false # 迭代36 卡A——C 区两段头真实入口断言(每 run 一次) var _tested_curio_head := false # 迭代36 卡A——三选一头部旧话清零断言(奖励点停点捕获) +var _tested_round_summary := false # 卡⑥ wr1-b——上轮摘要恢复断言(战中场一次性判据) var _tested_map_cap := false # 迭代36 卡A——地图屏加成帽换代断言(每 run 一次) var _tested_status := false # 状态面板角标开合/守卫断言(每 run 一次) var _tested_map_status := false # 地图屏角标一次性 case(选路态零扰动) @@ -556,8 +557,14 @@ func _ready() -> void: "phase": main.run_scope.settlement.action_economy.phase, "ap": main.run_scope.settlement.action_economy.ap, "settling": diag.settling, } - if JSON.stringify(st) == JSON.stringify(_last_state): - # 排播演播期豁免(硬锁 v3——合法停顿态):演播期棋盘锁生效、probe 点击被吞、 + # 上轮摘要恢复断言(卡⑥ wr1-b——改前 _round_rows 类型错 append 全数失败、缓冲恒空、 + # 摘要自引入类型错以来从未显示):战中场观测点=round≥2 且排播队列已排空(轮首分隔行 + # 已消费=finish_settlement_round 已刷);不挂循环出口——收尾的续玩重建段会再走 log_view.clear() + if not _tested_round_summary and int(st["round"]) >= 2 and main._row_queue.is_empty(): + _tested_round_summary = true + if main.log_view._summary.text == "": + _problems.append("上轮摘要恒空(卡⑥ wr1-b——结算行缓冲仍失真)") + if JSON.stringify(st) == JSON.stringify(_last_state): # 排播演播期豁免(硬锁 v3——合法停顿态):演播期棋盘锁生效、probe 点击被吞、 # 逻辑态恒定属预期。判据=纯队列(与 main 锁同源)。Waiting 收尾等待期**不豁免**—— # 锁若把收尾链卡死,此处 stall 照报(探测面)。 # T1 D1 处置=保留豁免+注记:冻结/缓存/重放语义不再靠豁免绕行,由 _locktest_tick From d5a03db1a8f85365f49b7f4caed73ce5bfcb9dcb Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 08:52:14 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A338=E5=8D=A1=E2=91=A3?= =?UTF-8?q?=20=E6=98=8E=E7=BB=86=E6=94=B6=E6=95=9B=E6=89=B9):=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E8=A1=8C=E5=9E=8B=E6=94=B6=E6=95=9B=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=9B=9B=E4=B8=AA=E4=B8=93=E9=95=BF=E5=AE=9E=E5=90=8D=E8=A1=8C?= =?UTF-8?q?=E5=9E=8B=EF=BC=88fx=5Freveal/mine=5Fneutralized/insurance=5Fus?= =?UTF-8?q?ed/fx=5Fpreview=5Farea=EF=BC=89=E6=94=B6=E6=95=9B=E4=B8=BA?= =?UTF-8?q?=E5=8D=95=E4=B8=80=20extended=20=E8=A1=8C=E5=9E=8B=EF=BC=8Beffe?= =?UTF-8?q?ct=5Fid=20=E8=BD=BD=E8=8D=B7=EF=BC=88=E5=85=AD=E5=8F=91?= =?UTF-8?q?=E5=B0=84=E7=82=B9=EF=BC=9Aorchestrator=20=E4=B8=89=E5=A4=84?= =?UTF-8?q?=EF=BC=8Bgrant=5Fsegment=20=E4=B8=89=E5=A4=84=EF=BC=8Cdisplay?= =?UTF-8?q?=5Fname/value/highlight=20=E9=80=90=E5=AD=97=E4=B8=8D=E5=8A=A8?= =?UTF-8?q?=EF=BC=89=EF=BC=9Ba2=20=5Femit=5Frow=20=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E8=A1=8C=E5=BF=85=E8=BD=BD=20effect=5Fid=20=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=EF=BC=88emit=20=E4=B9=8B=E5=89=8D=EF=BC=8C=E8=BF=9D=E7=BA=A6?= =?UTF-8?q?=20assert=20=E7=82=B8=E9=9B=B6=E5=8F=91=E5=B0=84=EF=BC=89?= =?UTF-8?q?=EF=BC=9Ba3=20fx=5Fpreview=5Farea=20=E6=9D=A5=E6=BA=90=E7=B1=BB?= =?UTF-8?q?=E5=88=AB=E7=A9=BA=E4=B8=B2=E8=BF=9D=E7=BA=A6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=EF=BC=88=E2=86=92fx=5Fwarning=5Fbeacon=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=20fx=5Fseal=5Fsmith=20=E5=85=88=E4=BE=8B=EF=BC=89?= =?UTF-8?q?=EF=BC=9Ba4=20=E5=A4=B4=E6=B3=A8=E6=8D=A2=E4=BB=A3=E4=B8=A4?= =?UTF-8?q?=E5=B1=82=E8=AF=8D=E8=A1=A8=EF=BC=88=E5=9F=BA=E7=A1=80=209=20?= =?UTF-8?q?=E7=B1=BB=EF=BC=8B=E6=89=A9=E5=B1=95=E8=A1=8C=201=20=E7=B1=BB?= =?UTF-8?q?=EF=BC=8B=E5=9B=9B=E6=95=88=E6=9E=9C=E8=BA=AB=E4=BB=BD=E5=8F=91?= =?UTF-8?q?=E5=B0=84=E7=82=B9=E6=8C=87=E5=BC=95=EF=BC=89=EF=BC=9Ba5=20?= =?UTF-8?q?=E5=91=88=E7=8E=B0=E9=9B=B6=E6=94=B9=E5=8A=A8=E6=A0=B8=E9=AA=8C?= =?UTF-8?q?=EF=BC=88=5Fformat=5Frow=20fallback=20=E6=89=BF=E8=BD=BD?= =?UTF-8?q?=E3=80=81AGG=5FTYPES=20=E4=B8=8D=E5=90=AB=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E8=A1=8C=EF=BC=89=EF=BC=9Ba6=20=E8=A3=81=E7=82=B9=E7=94=B2?= =?UTF-8?q?=E9=87=8D=E9=94=9A=EF=BC=88parity=5Fanchor=20freeze=20=E6=94=B9?= =?UTF-8?q?=E5=90=8E=E8=A1=8C=E4=B8=BA=E7=AB=8B=E6=96=B0=E9=94=9A=EF=BC=8B?= =?UTF-8?q?=E5=A4=8D=E9=97=B8=E4=B8=89=E5=8F=98=E4=BD=93=E5=8F=8C=E9=9D=A2?= =?UTF-8?q?=E5=85=A8=E5=90=8C=EF=BC=9B=E6=94=B9=E5=89=8D=E5=AF=B9=E6=8B=8D?= =?UTF-8?q?=E7=BB=BF=E5=9F=BA=E7=BA=BF=E5=9C=A8=E6=A1=88=E2=80=94=E2=80=94?= =?UTF-8?q?stash=20=E5=B9=B2=E5=87=80=E8=B7=91=E6=89=B9=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E9=AA=8C=E6=94=B6=3Dwr2=20=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E4=BA=94=E6=9D=A1=EF=BC=88insurance=5Fused=E3=80=94V6=20?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E3=80=95=EF=BC=8Bfx=5Freveal=20=E5=85=91?= =?UTF-8?q?=E4=BB=98/=E8=90=BD=E7=A9=BA=E3=80=94=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=8F=AD=E7=A4=BA=E7=AE=A1=E7=BA=BF=E3=80=95=EF=BC=8Bmine=5Fne?= =?UTF-8?q?utralized/fx=5Fpreview=5Farea=E3=80=94treasure=20=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=E9=A6=96=E6=AC=A1=20feats=20=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E3=80=95=EF=BC=89=EF=BC=8Bwr2-np=20=E8=B4=9F=E9=9D=A2=E5=B0=8F?= =?UTF-8?q?=E5=A5=97=E4=BB=B6=EF=BC=88extended=5Frow=5Fviolation=5Ftest?= =?UTF-8?q?=EF=BC=8Cexpect=3D1=20negative=EF=BC=8Cmanifest=20=E7=99=BB?= =?UTF-8?q?=E8=AE=B0=E8=A1=8C=EF=BC=89=EF=BC=8Bwr3=20=E5=B9=B6=E2=91=A4?= =?UTF-8?q?=E6=96=AD=E8=A8=80=EF=BC=9B=E6=92=A4=E9=94=80=E8=AF=81=E6=98=8E?= =?UTF-8?q?=E4=B8=89=E6=9D=A1=E5=AE=9E=E6=B5=8B=E7=BA=A2=E7=BB=BF=E5=A4=8D?= =?UTF-8?q?=E7=8E=B0=EF=BC=9BJSONL=20=E8=AF=BB=E6=95=B0=E5=AF=B9=E6=8B=8D?= =?UTF-8?q?=20PASS=EF=BC=88type=20=E8=BF=81=E7=A7=BB=E8=AE=A1=E6=95=B0?= =?UTF-8?q?=E4=B8=8E=20T0=20=E5=9F=BA=E7=BA=BF=E5=AF=B9=E8=B4=A6=20264/304?= =?UTF-8?q?/0/0=EF=BC=8C=E5=AD=97=E6=AE=B5=E5=8F=A3=E5=BE=84=E5=A4=96?= =?UTF-8?q?=E9=9B=B6=E5=B7=AE=E5=BC=82=E2=80=94=E2=80=94type/effect=5Fid/s?= =?UTF-8?q?ource=5Fclass/source=20=E5=9B=9B=E5=AD=97=E6=AE=B5=EF=BC=8Csour?= =?UTF-8?q?ce=3Dlogger:133=20=E5=90=8C=E5=80=BC=E6=94=B9=E5=90=8D=E5=89=AF?= =?UTF-8?q?=E6=9C=AC=EF=BC=89=EF=BC=9B=E6=94=B9=E5=8A=A8=E9=9D=A2=E5=B0=81?= =?UTF-8?q?=E9=97=AD=EF=BC=88=E5=9F=BA=E7=A1=80=209=20=E7=B1=BB=E9=9B=B6?= =?UTF-8?q?=E8=A7=A6=E7=A2=B0=E3=80=81=E8=B1=81=E5=85=8D=E9=9B=86=E5=90=88?= =?UTF-8?q?=E9=9B=B6=E5=8F=98=E5=8C=96=E3=80=81=E6=95=B0=E5=80=BC=E9=9A=8F?= =?UTF-8?q?=E6=9C=BA=E9=9B=B6=E5=8F=98=E5=8C=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/battle_orchestrator.gd | 22 +++++-- game/core/settlement/grant_segment.gd | 8 +-- game/tests/evidence/parity/anchor.json | 6 +- game/tests/run_all.gd | 1 + .../extended_row_violation_test.gd | 38 +++++++++++++ .../round_flow_skill_pool_test.gd | 57 +++++++++++++++++++ game/tests/unit/core/treasure_effects_test.gd | 54 ++++++++++++++++++ 7 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 game/tests/unit/battle_settlement/extended_row_violation_test.gd diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 263a7a2f..45227ab3 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -12,11 +12,18 @@ signal round_started(round_index: int) signal board_changed() signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态快照(playtest_logger 消费——JSONL 数值分析) -# 明细流水行型协议(战斗结算 Core Rule;9 类; +# 明细流水行型协议(战斗结算 Core Rule;两层词表——基础 9 类封闭+扩展行 1 类; # 清单改动须同步 battle_log_view 的 _format_row 与本文档头注): -# 效果行 effect · 伤害行 damage · 治疗行 heal · +# 基础 9 类:效果行 effect · 伤害行 damage · 治疗行 heal · # 奖励未生效行 reward_ineffective · 封印未中行 seal_miss · 施放行 cast · 衰减行 decay · # 判定行 verdict · 高光行 highlight +# 扩展行:行型值=extended(单一扩展行型),效果身份由 effect_id 载荷携带(_emit_row 强制必载, +# 缺载即断言失败零发射)。现役效果身份与发射点: +# fx_reveal(专长揭示掷格——battle_orchestrator _flush_pending_reveals 三发射点:兑付行+落空行×2) +# mine_neutralized(封印匠·危险格中立移除——grant_segment 封印命中 remove_mines 面) +# fx_preview_area(示警烟火·周围资源类型——grant_segment 封印命中 preview_area 面) +# insurance_used(完美保险·标错豁免——grant_segment 标错 forgive_seal_miss 面) +# 新增专长效果只新增 effect_id 身份不再新增行型;行型词表改动须同步 owner 文档「扩展行」节。 # 行来源断言豁免集合(行型层)——判定段 / 衰减段本就无棋盘来源,勿一刀切误炸 const SOURCE_EXEMPT_TYPES := ["decay", "verdict"] # 行来源断言豁免集合(来源类别层)——事实来源是单位 / 清图而非棋盘格; @@ -146,7 +153,7 @@ func _flush_pending_reveals(ri: int) -> void: if not minesweeper.is_revealed(c) and not board.is_mine(c): cands.append(c) if cands.is_empty(): - _emit_row("fx_reveal", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(无未揭示安全格)", 0, {}, "") + _emit_row("extended", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(无未揭示安全格)", 0, {"effect_id": "fx_reveal"}, "") continue var rg: RandomNumberGenerator = rng.open_stream("fx_reveal") var cell: Vector2i = cands[rg.randi_range(0, cands.size() - 1)] @@ -154,12 +161,12 @@ func _flush_pending_reveals(ri: int) -> void: var ok: bool = minesweeper.fx_reveal(cell) _fx_revealing = false if not ok: - _emit_row("fx_reveal", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {}, "") + _emit_row("extended", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {"effect_id": "fx_reveal"}, "") continue var snap = _reward_economy.query_full_snapshot(cell) if snap is Dictionary: _grant_routed(snap, "positive", "fx_reveal", cell, ri, "fx_reveal") - _emit_row("fx_reveal", "fx_reveal", cell, ri, "专长揭示", 0, {}, "fx_reveal") + _emit_row("extended", "fx_reveal", cell, ri, "专长揭示", 0, {"effect_id": "fx_reveal"}, "fx_reveal") # 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数) @@ -1260,6 +1267,11 @@ func _reset_round_facts() -> void: # ---- 行协议(Core Rule:公共字段 + 行类型特有字段)---- func _emit_row(type: String, source_class: String, origin_cell: Vector2i, ri: int, display: String, value: int, extra: Dictionary, highlight: String = "") -> void: + # 扩展行必载效果身份(行协议 fail-fast——违约行先炸后不发,零发射;开发期违约即炸, + # 与 _assert_row_source 同族 assert 形态) + if type == "extended" and String(extra.get("effect_id", "")) == "": + assert(false, "[战斗结算] 扩展行缺 effect_id 载荷——行协议违约") + return _assert_row_source(type, source_class, origin_cell) var row := { "type": type, diff --git a/game/core/settlement/grant_segment.gd b/game/core/settlement/grant_segment.gd index 39356838..a024443a 100644 --- a/game/core/settlement/grant_segment.gd +++ b/game/core/settlement/grant_segment.gd @@ -268,7 +268,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void: "remove_mines": var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell]) if not removed.is_empty(): - oc._emit_row("mine_neutralized", "fx_seal_smith", cell, ri, "封印匠·危险格中立移除", 0, {}, "") + oc._emit_row("extended", "fx_seal_smith", cell, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"}, "") "preview_area": var board_p: BoardData = oc.minesweeper.current_board() var types: Array[Variant] = [] @@ -278,7 +278,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void: types.append(String((n_snap as Dictionary).get("cell_type", ""))) if oc.treasure_inventory != null and not types.is_empty(): oc.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell)) - oc._emit_row("fx_preview_area", "", cell, ri, "示警烟火·周围资源类型", 0, {"types": types}, "") + oc._emit_row("extended", "fx_warning_beacon", cell, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types}, "") # 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格; # 复用 grant 单点,额外兑付走同一 _grant_routed 正向路由) for h in fh_hits: @@ -306,8 +306,8 @@ func _judge_seal(evt: Dictionary, ri: int) -> void: # _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3) oc._perfect_forgiven += 1 _apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中) - oc._emit_row("insurance_used", "seal_miss", cell, ri, "完美保险·标错豁免", 0, - {"forgiven": oc._perfect_forgiven}, "") + oc._emit_row("extended", "seal_miss", cell, ri, "完美保险·标错豁免", 0, + {"effect_id": "insurance_used", "forgiven": oc._perfect_forgiven}, "") var hl := "perfect_broken" if oc._perfect_hit_false - oc._perfect_forgiven == 1 else "" oc._emit_row("seal_miss", "seal_miss", cell, ri, "封印未中", 0, {}, hl) var snap = oc._reward_economy.grant(cell) diff --git a/game/tests/evidence/parity/anchor.json b/game/tests/evidence/parity/anchor.json index 5786fdfb..c47e19cf 100644 --- a/game/tests/evidence/parity/anchor.json +++ b/game/tests/evidence/parity/anchor.json @@ -49,7 +49,7 @@ "rows": 9216 }, "perfect": { - "sha256": "a37ba2ca47f8619f22aa3dd8a761fffc71916d6c3172f5ff0aeb2b0557aba7e5", + "sha256": "962b6f68440bcf95f2b21ac4cc07a8523655f044ae6355aa674d0db27bdecc1d", "rows": 60774 } } @@ -103,7 +103,7 @@ "rows": 9670 }, "perfect": { - "sha256": "f6bb9fd5ba5fc183fd29adbb00ae526a5c6852e7f52f9f597a0b4d853baf05d6", + "sha256": "06cb4dcc26c56343c61bc8d972238be81b88e3132d7e9b274399f4cfd04cb56e", "rows": 62765 } } @@ -157,7 +157,7 @@ "rows": 7124 }, "perfect": { - "sha256": "7d16b4efb2bb48355a7919011b265de83ff907bd9c83818dc66e69108967d5a3", + "sha256": "c3d0d37c5f40d208fb35d323b4c7a11c4f7ebe77f9f0eaba0fc3445f00971277", "rows": 59435 } } diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 90ef459f..37997467 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -70,6 +70,7 @@ func _suites() -> Array: {"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/variant_placement_test.gd", "expect": 0}, # 变异布置宿主(战内布置段门控与数量公式/全局变异兜底回落与资源面偏斜——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动) + {"path": "res://tests/unit/battle_settlement/extended_row_violation_test.gd", "expect": 1, "negative": true}, # 卡④ wr2-np 扩展行必载 effect_id 负路径(行协议运行期违约面首个负面套件——独立进程豁免错误扫描) {"path": "res://tests/unit/contracts/system_contracts_test.gd", "expect": 0}, {"path": "res://tests/unit/contracts/source_guard_test.gd", "expect": 0}, # 源码契约宿主(project.godot 警告配置与类型闸/产品码卫生死代码·影子账·层序单口——批 B 自 smoke_all 迁入;被测对象=仓库自身源文件,裁决五新设) {"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0}, diff --git a/game/tests/unit/battle_settlement/extended_row_violation_test.gd b/game/tests/unit/battle_settlement/extended_row_violation_test.gd new file mode 100644 index 00000000..c2f31718 --- /dev/null +++ b/game/tests/unit/battle_settlement/extended_row_violation_test.gd @@ -0,0 +1,38 @@ +# 致命类用例(独立进程,期望退出码 1)——迭代 38 卡④ wr2-np:扩展行必载 effect_id 负路径断言 +# 违约直调 _emit_row(type=extended 无 effect_id 载荷)→ a2 行协议校验 assert 炸(debug 实况, +# 输出中的 SCRIPT ERROR 为其测试目标——负面套件全文折叠)→ 零发射; +# 回归态(校验段被移除)= 违约行照发 → 退出码 2 ≠ 期望 1(闸红)。 +# 独立进程理由:GDScript assert 失败必产 SCRIPT ERROR 且不可捕获,非负面套件会被 run_all 错误 +# 扫描判红(迭代 38 卡④ grilling 第二轮同款结论——负面豁免机制为此存在,counter_prefix 先例)。 +# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/extended_row_violation_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + var db = load("res://core/config_db.gd").new() + if not db.load_tables("res://data/tables", false): + push_error("[extended_row_violation_test] 表加载失败") + quit(2) + return + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(74100) + var oc = load("res://core/battle_orchestrator.gd").new(db, rng) + var hits: Array = [] + oc.step_row.connect(func(row): hits.append(row)) + # 违约直调(缺 effect_id 载荷——行协议违约):debug 下 a2 校验 assert 炸中断 _emit_row 本体, + # 控制流回到本函数(headless assert 语义=中断当前函数不中止运行);release 下 return 零发射同效 + oc._emit_row("extended", "fx_reveal", Vector2i.ZERO, 1, "负路径", 0, {}, "") + if not hits.is_empty(): + push_error("[extended_row_violation_test] 违约行被发射——a2 校验失守(回归)") + quit(2) + return + # 对照组:合法扩展行(带 effect_id)照常发射——校验只拦违约形态 + oc._emit_row("extended", "fx_reveal", Vector2i.ZERO, 1, "合法对照", 0, {"effect_id": "fx_reveal"}, "") + if hits.size() != 1 or String(hits[0].get("type", "")) != "extended": + push_error("[extended_row_violation_test] 合法扩展行未发射——校验误拦(回归)") + quit(2) + return + print("[extended_row_violation_test] 违约零发射+合法照发(a2 校验在位)") + quit(1) diff --git a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd index 066a3ad0..ee4ae52e 100644 --- a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd +++ b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd @@ -38,6 +38,7 @@ func _init() -> void: _smoke_settlement_segments() _smoke_skill_pool() _smoke_counter_buckets() + _smoke_extended_rows() quit(Kit.finish("round_flow_skill_pool_test")) @@ -994,6 +995,8 @@ func _smoke_counter_buckets() -> void: "node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()} var oA = RigS.new(_db, _fresh_rng(779401)) oA.begin_battle(encA) + var v6_rows: Array = [] + oA.step_row.connect(func(row): v6_rows.append(row)) # 卡④ wr2①——标错行捕获(真实 step_row 发射面) var safe_a: Variant = null for ca in oA.minesweeper.current_board().all_cells(): if not oA.minesweeper.current_board().is_mine(ca): @@ -1001,8 +1004,62 @@ func _smoke_counter_buckets() -> void: break oA._grant_seg._judge_seal({"cell": safe_a, "hit": false}, 1) Kit.check("V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕", EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1) + # 卡④ wr2①——完美保险标错行=单一扩展行型(原 insurance_used 实名行型收敛) + var v6_ins := {} + for r in v6_rows: + if String(r.get("effect_id", "")) == "insurance_used": + v6_ins = r + Kit.check("卡④ wr2①:完美保险标错行 type=extended 且 effect_id=insurance_used(effect_id 必载)〔明细收敛 V2〕", + not v6_ins.is_empty() and String(v6_ins.get("type", "")) == "extended" + and String(v6_ins.get("source_class", "")) == "seal_miss" and bool(v6_ins.get("forgiven", 0))) var encB = encA.duplicate(true) encB["battle_index"] = 2 var oB = RigS.new(_db, _fresh_rng(779402)) oB.begin_battle(encB) Kit.check("V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕", EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0) + + +# ---- 扩展行收敛(迭代 38 卡④)——fx_reveal 兑付行与落空行经真实揭示管线发射 ---- +# 驱动形态:装配揭示类专长(geologist)的编排器上走生产同一入队/兑付管线 +# (_fx_enqueue_reveal → _flush_pending_reveals——grant/damage 揭示订阅面的共用出口)。 +# V6 形态先例同源:直入真实管线缝(_judge_seal 直调同款口径)。 + +func _smoke_extended_rows() -> void: + print("--- 扩展行收敛:fx_reveal 兑付/落空 ---") + var RigS = load("res://core/battle_orchestrator.gd") # 局部装配别名(_smoke_counter_buckets 同款——跨函数不共享局部量) + var enc_fx = {"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": ["geologist"], "tree_rules": [], "battle_index": 1, + "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0, + "node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()} + # ② 兑付行:有未揭示安全格 → 掷格揭示成功 → 扩展行带真实格与 fx_reveal 身份 + var o_fx = RigS.new(_db, _fresh_rng(779501)) + o_fx.begin_battle(enc_fx) + var fx_rows: Array = [] + o_fx.step_row.connect(func(row): fx_rows.append(row)) + o_fx._fx_enqueue_reveal() + o_fx._flush_pending_reveals(1) + var paid := {} + for r in fx_rows: + if String(r.get("effect_id", "")) == "fx_reveal": + paid = r + Kit.check("卡④ wr2②:专长揭示兑付行 type=extended 且 effect_id=fx_reveal 且来源格真实〔明细收敛 V2〕", + not paid.is_empty() and String(paid.get("type", "")) == "extended" + and Vector2i(paid["origin_cell"][0], paid["origin_cell"][1]).x >= 0) + # ③ 落空行:无未揭示安全格态(全安全格先揭)→ 候选空 → 落空归因行(origin_cell 越界哨值) + var o_void = RigS.new(_db, _fresh_rng(779502)) + o_void.begin_battle(enc_fx) + var void_rows: Array = [] + o_void.step_row.connect(func(row): void_rows.append(row)) + for cv in o_void.minesweeper.current_board().all_cells(): + if not o_void.minesweeper.current_board().is_mine(cv): + o_void.minesweeper.fx_reveal(cv) + o_void._fx_enqueue_reveal() + o_void._flush_pending_reveals(1) + var missed := {} + for r in void_rows: + if String(r.get("effect_id", "")) == "fx_reveal" and String(r.get("display_name", "")).contains("落空"): + missed = r + Kit.check("卡④ wr2③:揭示落空行 type=extended 且 effect_id=fx_reveal 且 origin_cell=越界哨值〔明细收敛 V2〕", + not missed.is_empty() and String(missed.get("type", "")) == "extended" + and Vector2i(missed["origin_cell"][0], missed["origin_cell"][1]).x == -1) diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 95fbd150..5b7d33ed 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -300,6 +300,60 @@ func _init() -> void: and (adr14.runner()._attunement as Array) == snap14, "装配不可变:战斗运行态装配集与冒险装配集逐位同源(运行零写口——快照对比)") + # ⑮ 扩展行收敛(迭代 38 卡④ wr2④⑤——套件首次 feats 注入,V6 形态自 round_flow_skill_pool_test 移植: + # enc 带 feats 键+ledger,专长触发经 _judge_seal 真实封印判定管线) + var enc_feats = {"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": ["seal_smith"], "tree_rules": [], "battle_index": 1, + "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0, + "node_type": "battle", "ledger": load("res://core/run_ledger.gd").new()} + var rng15 = load("res://core/rng_service.gd").new(); rng15.set_master_seed(341) + var ob15 = orch_s.new(db, rng15) + ob15.begin_battle(enc_feats) + var rows15: Array = [] + ob15.step_row.connect(func(row): rows15.append(row)) + # ④ 封印匠(每第 3 次封印命中→危险格中立移除):三枚不同雷格各封一次 + #(_judge_seal 每格去重——_seal_consumed 以危险格实例为键,同格重复命中不计数) + var mines15: Array = [] + for cv in ob15.minesweeper.current_board().all_cells(): + if ob15.minesweeper.current_board().is_mine(cv): + mines15.append(cv) + if mines15.size() >= 3: + break + Kit.check_quiet(mines15.size() == 3, "封印匠:板上存在三枚危险格(断言前提)") + if mines15.size() == 3: + for mv in mines15: + ob15._grant_seg._judge_seal({"cell": mv, "hit": true}, 1) + var smith_row := {} + for r in rows15: + if String(r.get("effect_id", "")) == "mine_neutralized": + smith_row = r + Kit.check_quiet(not smith_row.is_empty() and String(smith_row.get("type", "")) == "extended" + and String(smith_row.get("source_class", "")) == "fx_seal_smith", + "封印匠第三次命中:危险格中立移除行=扩展行型(type=extended+effect_id=mine_neutralized)〔明细收敛 V2〕") + # ⑤ 示警烟火(封印命中→周围资源类型预告):fx_preview_area 扩展行+来源类别修复断言(wr3) + var enc_beacon = enc_feats.duplicate(true) + enc_beacon["feats"] = ["warning_beacon"] + var rng15b = load("res://core/rng_service.gd").new(); rng15b.set_master_seed(342) + var ob15b = orch_s.new(db, rng15b) + ob15b.begin_battle(enc_beacon) + var rows15b: Array = [] + ob15b.step_row.connect(func(row): rows15b.append(row)) + var cell15b: Variant = null + for cv in ob15b.minesweeper.current_board().all_cells(): + if not ob15b.minesweeper.current_board().is_mine(cv): + cell15b = cv + break + if cell15b != null: + ob15b._grant_seg._judge_seal({"cell": cell15b, "hit": true}, 1) + var beacon_row := {} + for r in rows15b: + if String(r.get("effect_id", "")) == "fx_preview_area": + beacon_row = r + Kit.check_quiet(not beacon_row.is_empty() and String(beacon_row.get("type", "")) == "extended" + and String(beacon_row.get("source_class", "")) == "fx_warning_beacon", + "示警烟火命中行:type=extended+effect_id=fx_preview_area+来源类别=fx_warning_beacon(空串违约修复)〔明细收敛 V2〕") + quit(Kit.finish("treasure_effects_test", "PASS — 新目录 12 件逐件行为断言全绿〔秘宝批 V7 附属表〕")) From 5b5214f40f206498422e1bcd603a21a8393bf560 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 08:59:11 +0800 Subject: [PATCH 6/9] =?UTF-8?q?feat(=E8=BF=AD=E4=BB=A338=E5=8D=A1=E2=91=A6?= =?UTF-8?q?=20=E8=B0=83=E8=AF=95=E5=85=A5=E5=8F=A3=E6=89=B9):=20=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E5=85=A5=E5=8F=A3=E5=86=99=E6=AD=BB=E8=A1=8C=E5=88=A0?= =?UTF-8?q?=E9=99=A4=EF=BC=88main.gd:199=20=E5=8F=91=E5=B8=83=E5=89=8D?= =?UTF-8?q?=E6=B8=85=E7=90=86=E4=B9=89=E5=8A=A1=E5=B1=A5=E8=A1=8C=EF=BC=89?= =?UTF-8?q?=E2=80=94=E2=80=94=5Fdebug=5Fentry=20=E6=81=92=E7=BC=BA?= =?UTF-8?q?=E7=9C=81=20false=3D=E5=8F=91=E5=B8=83=E6=80=81=EF=BC=88?= =?UTF-8?q?=E8=A7=92=E6=A0=87=E6=81=92=E9=9A=90=E3=80=81=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E6=97=A0=20UI=20=E5=BC=80=E5=90=AF=E5=8F=A3=EF=BC=9B:73/:200?= =?UTF-8?q?=20=E4=B8=A4=E5=A4=84=E5=BC=95=E7=94=A8=E5=A5=91=E7=BA=A6=201?= =?UTF-8?q?=20=E6=A0=B8=E9=AA=8C=E6=81=B0=E4=BA=8C=EF=BC=89=EF=BC=9Bc2=20:?= =?UTF-8?q?73=20=E6=B3=A8=E9=87=8A=E7=8E=B0=E7=8A=B6=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E6=8D=A2=E4=BB=A3=EF=BC=88=E6=B2=BF=E9=9D=A9=E5=BD=92=20git=20?= =?UTF-8?q?log=EF=BC=89=EF=BC=9Bc3/c4/c5=20=E4=B8=89=E5=A4=84=E3=80=8C?= =?UTF-8?q?=E6=81=92=E5=BC=80=E4=BB=A4=E3=80=8D=E6=96=AD=E8=A8=80=E9=9A=8F?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=80=81=E5=8F=8D=E8=BD=AC=EF=BC=88system=5F?= =?UTF-8?q?test=20V2f=EF=BC=8Fclick=20=E8=AE=BE=E7=BD=AE=E9=9D=A2=EF=BC=8F?= =?UTF-8?q?tune=20=E5=AF=B9=E5=B1=80=E6=AE=B5=E2=80=94=E2=80=94c5=20?= =?UTF-8?q?=E5=AF=B9=E5=B1=80=E4=B8=AD=E5=BC=80=E6=B5=AE=E7=AA=97=E5=9B=9E?= =?UTF-8?q?=E5=BD=92=E6=94=B9=E7=BB=8F=20=5Fshow=5Fdebug=5Foverlay=20?= =?UTF-8?q?=E7=9B=B4=E8=B0=83=E6=89=BF=E8=BD=BD=E3=80=94:61=20=E5=85=88?= =?UTF-8?q?=E4=BE=8B=E3=80=95=EF=BC=8C=E7=BD=AE=E7=81=B0=E5=AD=90=E6=96=AD?= =?UTF-8?q?=E8=A8=80=E9=9A=8F=E6=81=92=E9=9A=90=E8=AF=AD=E4=B9=89=E9=80=80?= =?UTF-8?q?=E5=BD=B9=E7=94=B3=E6=8A=A5=20=C2=A78.2=EF=BC=89=EF=BC=9Bc6=20?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E9=9B=86=E6=B8=85=E5=8D=95=E6=94=B9=E5=88=A4?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=EF=BC=8B=E6=9C=AC=E6=89=B9=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E9=9A=8F=E6=B8=85=E5=8D=95=E9=87=8D=E7=94=9F?= =?UTF-8?q?=E6=88=90=EF=BC=881588=20=E6=9D=A1=E5=AF=B9=E8=B4=A6=E9=9B=B6?= =?UTF-8?q?=E5=B7=AE=E5=BC=82=EF=BC=89=EF=BC=9Bc7=20README=20=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E8=AF=B4=E6=98=8E=E5=8F=91=E5=B8=83=E6=80=81=E6=8D=A2?= =?UTF-8?q?=E4=BB=A3=EF=BC=9Bc8=20menu=5Fboot=20=E5=9F=BA=E7=BA=BF?= =?UTF-8?q?=E6=98=BE=E5=BC=8F=E9=87=8D=E5=88=B6=EF=BC=88=E6=94=B9=E5=90=8E?= =?UTF-8?q?=200.15%=20=E5=B9=BD=E7=81=B5=E8=A7=92=E6=A0=87=E6=B6=88?= =?UTF-8?q?=E5=A4=B1=E2=86=92=E5=A4=8D=E9=97=B8=200.00%=EF=BC=8CREADME=20?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=E8=AE=B0=E5=BD=95=E7=99=BB=E8=AE=B0=EF=BC=89?= =?UTF-8?q?=EF=BC=9B=E6=92=A4=E9=94=80=E8=AF=81=E6=98=8E=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?=3D=E6=81=A2=E5=A4=8D=20:199=20=E5=8D=95=E8=A1=8C=E2=86=92?= =?UTF-8?q?=E4=B8=89=E5=A4=84=E5=8F=8D=E5=90=91=E6=96=AD=E8=A8=80=E9=BD=90?= =?UTF-8?q?=E7=BA=A2=E2=86=92=E8=BF=98=E5=8E=9F=E5=A4=8D=E7=BB=BF=EF=BC=9B?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=90=8E=E6=97=A0=E7=AC=AC=E4=B8=89=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E3=80=81=E6=97=A0=E7=94=9F=E4=BA=A7=E5=BC=80=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=EF=BC=88C2=20=E5=8D=95=E7=82=B9=E8=AE=BA=E6=96=AD?= =?UTF-8?q?=E5=A4=8D=E6=A0=B8=E5=9C=A8=E6=A1=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/README.md | 2 +- game/presentation/main.gd | 5 +- game/probe/ui_click_probe.gd | 8 +- game/probe/ui_tune_probe.gd | 8 +- game/tests/golden/README.md | 3 + game/tests/golden/menu_boot.png | Bin 319749 -> 318257 bytes game/tests/system_test.gd | 4 +- game/tests/验证集清单.md | 3325 ++++++++++++++++--------------- 8 files changed, 1685 insertions(+), 1670 deletions(-) diff --git a/game/README.md b/game/README.md index a997e1e4..e149236f 100644 --- a/game/README.md +++ b/game/README.md @@ -13,7 +13,7 @@ 1. 用 Godot 4.7.2 打开本目录(含 `project.godot` 的文件夹) 2. 按 F5 运行(主场景 `presentation/main.tscn`) 3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关通关发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。唯一试玩入口 = 完整冒险——单关测试直入口(L2/L3)已退役(2026-09-23 用户拍板) -4. `调试` 角标浮窗(恒开——2026-10-16 用户令 DS_DEBUG 写死,原环境变量门控退役;2026-10-04 迭代 14.5 调试整合批:原数值调整板/随机种子框/两层调试三散件收一):攻击/生命百分比加成(模拟天赋树解锁)+ 批量模拟(输出通关率/承伤期望/败北节点分布)+ 两层调试入口 +4. 调试浮窗(功能面:攻击/生命百分比加成〔模拟天赋树解锁〕+ 批量模拟〔输出通关率/承伤期望/败北节点分布〕+ 两层调试入口;2026-10-04 迭代 14.5 调试整合批三散件收一)——发布态入口恒隐(2026-10-25 删写死行批:调试角标不显示、浮窗无 UI 开启口;开发期临时启用改 `_debug_entry` 缺省本地不提交),浮窗体系整体退役属发布工程批 headless 冒烟(752 项断言〔计数随批增长,现值以最新批末闸读数为准〕,含关卡线性推进/满配通关/裸装败北/回放确定性;2026-10-02 性能报告实测 60.55 秒,报告在 `性能实测报告(已退役,git 历史可查)`): diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 04700243..4aca8a09 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -70,7 +70,7 @@ var meta: MetaProgressionCore: get: return get_node(AUTOLOAD_PATH_META) var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件) -var _debug_entry := false # 调试会话门控(写死=true——恒开:启动即开浮窗+两屏角标点亮;_ready 赋值) +var _debug_entry := false # 调试会话门控(缺省 false=发布态:角标恒隐、浮窗无 UI 开启口;沿革查 git log) var _seed_edit: LineEdit var confirm_dialog: ConfirmDialog # 通用确认弹窗(消费方编排——现行消费方=选档删档确认;新冒险互斥确认已随卡②主菜单撤出,归卡④大门) var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清) @@ -196,8 +196,7 @@ func _ready() -> void: _build_ui() _begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序) _start_overlay.visible = true - _debug_entry = true # 调试入口门控写死=true(不读 DS_DEBUG 环境变量;发布前清) - debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui) + debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐 # boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住); # 调试入口=UI 最顶层全局浮层角标(LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡) diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index 4f83b4f7..d0cbbb87 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -321,10 +321,10 @@ func _ready() -> void: _problems.append("音量滑杆未实时生效(master bus db %f ≠ %f)" % [AudioServer.get_bus_volume_db(master_idx), expect_db]) if absf(iso_s.volume_linear - 0.3) > 0.001: _problems.append("音量未入 store(%f ≠ 0.3)" % iso_s.volume_linear) - # 调试入口断言(全局浮层角标=UI 最顶层、任何 UI 不遮挡——本段设置面 - # 开中正是遮挡场景:浮层角标此时仍应可见;点击开浮窗路径归 tune_probe) - if main.debug_badge == null or not main.debug_badge.visible: - _problems.append("设置面开中调试浮层角标不可见(最顶层浮层失守——被遮挡)") + # 调试入口断言(卡⑦ c4 发布态换代——角标恒隐;原「最顶层浮层不被遮挡」 + # 回归语义随调试浮层发布态退役,申报见迭代 38 实现报告 §8.2) + if main.debug_badge == null or main.debug_badge.visible: + _problems.append("设置面开中调试角标可见(发布态恒隐失守)") var settings_close: Button = ProbeUiUtil.find_button(main.settings_overlay, "返回", "prefix") if settings_close == null: _problems.append("设置面返回按钮缺失") diff --git a/game/probe/ui_tune_probe.gd b/game/probe/ui_tune_probe.gd index 481cddc7..43feeb84 100644 --- a/game/probe/ui_tune_probe.gd +++ b/game/probe/ui_tune_probe.gd @@ -114,12 +114,10 @@ func _ready() -> void: # 棋盘输入不锁、状态角标不置灰(「默认开×遮挡复活」下必锁死的破约回归面); # 对局中批量模拟/两层调试两钮禁用;当局数据六项与只读访问器逐项一致;关闭钮真实路径收窗恢复 var dbg_badge: Button = main.debug_badge - if dbg_badge == null or not dbg_badge.visible: - problems.append("对局中调试浮层角标未点亮(全局浮层最顶层失守)") + if dbg_badge == null or dbg_badge.visible: + problems.append("对局中调试浮层角标非恒隐(发布态语义失守——卡⑦ 删写死行批)") else: - if dbg_badge.disabled: - problems.append("战斗屏调试角标被置灰(T2 排除式判据失守)") - dbg_badge.pressed.emit() # 真实按钮信号路径 → 组合根 _on_debug_badge_pressed → C6 单口 + main._show_debug_overlay() # 对局中开浮窗回归经直调承载(:61 既有直调先例——发布态角标恒隐,按钮信号路径退役) if not main.debug_overlay.visible: problems.append("对局中角标开浮窗失败(P2 甲对局中可达失守)") if main._input_locked(): diff --git a/game/tests/golden/README.md b/game/tests/golden/README.md index 658d3a9d..9bb64977 100644 --- a/game/tests/golden/README.md +++ b/game/tests/golden/README.md @@ -33,3 +33,6 @@ 跨跑次逐像素一致性实测 changed=0.00% 后落盘。承载迭代 38 卡① l1–l3 结构重构的 逐像素对拍(改后对拍 changed=0.00% 为验收主锚);对拍经 `tools/golden_compare.py` 直跑(render_gate 现行只对拍棋盘三图+menu_boot,闸件不在本批改动面)。 +- 2026-10-26(迭代 38 卡⑦ c8):**menu_boot 显式重制**(预期面视觉变化——调试角标 + 发布态恒隐,基线右下角幽灵角标像素消失)。改后实测 changed=0.15%(≈卡面预估 0.14%, + 容差内但按 render_gate「预期面视觉变化=显式重制基线」口径重制);复闸 changed=0.00%。 diff --git a/game/tests/golden/menu_boot.png b/game/tests/golden/menu_boot.png index a0fded5241c71902ceab1ba86ac3edcca30e229c..87cb4aad25f59ff2f367008ea47c3b8d313d8d08 100644 GIT binary patch delta 59036 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�eK2V=oTcz z`csYX>wrhuEZ_z3%mFuY_zEE%A)Hgsmd~!|7VN>|0|AnMDciGQ3EWxs!ehWqBUJL1}fs0Dk; zTi;U_)l~D~Op+f^WRo&B9klK-?#a5Me>`p#Wk7@pFXfF&Sad<0;sli6H@duZi(5}w zZslxJ%Q0kz%s1c8$72H3!OweK7VbHk-qn^sru9ilg@*H~$(oeo(Q=FbcVh(UNHhxv znH8#Rf+$g#*R_+t7MK?$#;|;a2XjC_BDX7@QQ2vvdhFVPrB5R{zF8||$$}%nn;19` zg>aLZcb~)}9vm1#L|`zL{VT`G{bAEh|Ax$CRxi9%tVGwJD>j48ymTzDjJ|8+uz&mY zu-Z>N_lJA&d3*_3!^k|=q45`RY5o>=us5B)hc30?Wfi`?vU_HxB7u`J7^ufaS8)Oi zoC-GZ>(zh6omrge0+MqK6*tE1`)zq==(Yx2e&s{@5|RpfZFQ3UYxpW#V7eVUUhIBs zpQ4iE$IwEDsF1jJ&jYl0yP<-5#8`IdtRz10#^9A39K>pZ$-_HSBh*%?I*#)VOv*C) zw*3C62?|FMr>|evBg$9u)VBi^`IA7%QaOoU1kh!?=SpF1z=j8| zak-iR-X^geXJ_HXJc-fOHg(9&tqW4bdGQ*{>^oM?ZmBb1{ep>aZnD+mAv|3 zi>Ek|4;u5Tw=Rks{FE9xuxo1vVGzF+E%cGvBdC_d%aV!I+cab|yQg%sL9%_2=-IQW zlvCZ%w=R&4)eihbQ`2lm2`t)N!QA_XWtpI1YU%y}&JW5&h^8d@+Y**McRj5H=$<4S zR=B%Z{a&qtich2l=wLlj`b`7G$n(yRENMVFp7Vwiz+i`T-%q&uW9yzTuH-?e=qc_A z2maWZwfr{&Lwd3>Rl}kwNE63=_$|HWzHH?61A3kR{3ER&yAL9t{(d3rrMG~3yS8%2 z`|PYA1JkC`7*zXL#+V>c%C~H;rQvP&!E)E;Qj%E9c1PFb%Qk{kb^uFD!ABuCO_^JXPLh1ANJl?l! zn5Ppz*WTO~Zt6dn@ULp6GHa#qqH@QfbN) zJdU7lh(PN60=<<4#(>41&dOrQhbbsy{K$x-roD-A!a`12VFs88?D3K2?+k!<<{`_? zNRc1JSX~>RylRFI(A*T@C6<%FbKkvUTkSDLHnqL%9w6eckxH+a0Fm%-#2dNl@PMrNbL*fy0E$lePv|E_v+JLTz}=&8azDb?c#VJTz~n2 zS=J^0aQBQSbxiY3A$?T=%(H%0WaA3*KjqwBHD4dNhukMp>!!^Y(H;q#Zc!Ls8-cI~ z)3PI%0QYHWZuskQ8R2gI?$E7~bi-ddP+mZTc1}ZusG9zrITt!ttOs=+vU2;Z4|P&S zTa7kpSpO}VHu*c3K;Ie(MJ3bUByO!1GvTkK#gF&w(Z=aJ$bB0Bb+we4LiW!E{!1#_ z*;AryQy*SU$F<(W%ihy-@OW3;5&?B7A;wR2jqEFK(g3t|JMbSZrAkKobgsPI!`8XI z>qCX9tq;Cr_G?EqAm87kPK{zL6+NkOd9OYukTjyNd1r8*1+Tu|=9@6wmX#HuF!>ju zo2h$^a?H$2!sv?wfzZrua=cUj6TJGU&}Z_m70*Wu+dUl+Z5mBimB6J;L**v!A2u_l z@^w&?iyH5A5*$@ZuO%+>gh&BkJ_YoyA1;I4cYB#S%Y=RxCd{s>KO^{3PMC- z;)%!bWt_v=aIJ6f6g5PqKSy*wr~v^=iaw7waX&FHrBOB76$6Pnzn$??HU<_eVx#x4ryg0c%<+@#Mt)RvH<%W8x@M z8?9%^(_0LOPE_U?_BzQ0}^O42IH59BluB0aoW5&p^6oTkg{1DagR0>qh82C|I5- zdtJ+T5jI!CE`HmZf(u~&lIA^2iFAC|CeN)+yb|1$rZ`@$pc&WyDhOBw+?g2FcT-_9 z*~AC7L%`?NhrbBhEOsddk;SVXiNchsuWTmwvj2wE5vWnL$7djJ``!*W@JZSePnrIq zyINqh$$8+A-61R9NF9bU^|bij3Cp4H^>6u-AhM{(V^Eq_*;alD)2?CCyFKQ2_JAiN zUc|mav2^{t!-+Q=Z4K|>o8<0GD;Nyr?=LWaLek=W~HXMSE5QY*>{`4LE8vs>fc z5mM6r^f`ltRz~dVpGB@me!toJMZ%64X&3k3mUNdQXRJf4xo?Q_W3KWgWJBtsP2-st zCfJ5?<{KeP7|*J0r=gF9v%jy_3LIkh(A6zmMxk8}c! zL_+(bb4p|Sz^7FS5AE^jF&Adygf4$^x_xu`O>7)GxX%Ta$Q#v7BlDwbXsMgq;Aez? z)86N}uhf8b0kc5`%WCl89XP1z3&&4{L53^D%Xn@{pV?xeuNxJmaZp>Px?X^v(+44- zPfeG?`sz3PZ+8mm#BR~(j9c#w*_0jbShS_kI?S|I!Yu*e+LdmBGv}}*>JgW>-!Bll`G`>r?^!OzC$&?bslk}~A;XY4SI7GOH zAKjS;CjW^nm@8c8h@=hTlaamWYv&50md0Pju+I5Ni|!%P@GlG1_Ee`dgMLOmPK{U3 zu{LC-VbB)#>ZKmmoij%otO@=|(Ep-IJkO&9>}*tnHwA&(_s{*uz@E(2_i!VJ`5>lB zmn!b9zewKE#J`&QW61B__ezRJ$mii1V#YfuGxb-@)<^zEdqC!IKa5BG1vjVyFFZaW z*oYB1o&O@X|AD&Jy*GdnkIp2ZZ-c8NmkDmwnkx6h4_=^dr5oWM936@#~WJR1249+S`Bis^#nivO59 zb@t44Z*~WYVT9JM^<5YRixL?cWuJ|2Ma@BUH||9 delta 60513 zcmXt;RalhI`-hkAF6k2K5CjCiu%wgnD#!cxK# zl1nYSJAZ!%|ARSs4`#0Sx@P8?=l5Maebq9B=M0U zJ@e+dCl>_$b-9y)E$ZaEBfas?n=v#V|{2AOlP&e7vgROb^l#N?V-wS=MSO&#Dupm2oG_`(cr>MVuYSW*b1>|@uEpGYZVUxeG z7eZ(=)dLOeo{x0TU51YN`K2qhuZZsf0w}Js7uw3a#K3{dwEdZ~gv$I^G>pCu~Sh;H2U!cbPr6%;ZdsVbtF`I=3Pn6{J zhfpJr=@kYJ`7J!_WDdHje%skZuS{qw*F)cNCH7!1yaLp>oM@$sx3^J zSKB_|@`=>Lt;(n9vzw}K2K#+CbrXh@M;{%%xY<}+Jpl1{5%pW--ytsFe{$;I-P+UO zzHKH>jjk+|DTo958LD0@znG7FoGWk-RD9%im1czA9lTFfAhg&_TziGa=1%S$jJR;$ zewdo+Q6E+4UqlNaapUbnmJBTyN1dxtZN=YxEr)+(kLsI~8vR5U?;E^iO_7fZ2;;;^ zyZtf_qK00&7<-p+)2Rvzr}Vszf0js ziooAmSnRb>YsWnhA2O(Ao!?SyRU#C|LX-9>h`X{V;PkL*RMlnVxN${=6vKO-aUWX9 zCfvh5pnCEI)_l^%xnx+!6lb0JuH4!frfs){P%U|e9Fc#aM5qu*uLL`8_Oa&pmF?-S zs4{%Z#z);~XejSma?VWxP#!U`ps%v~X(whUU?`UyokK3mviq?B`7_tt;ugAyFD~($ zMXgPOeV}-@V>cT7);vtx>UIwsLPNLAgiglWJNsV!*8zBVYIhv`ma}GDC(Z^N^|i*y z%JqXk#eMwQ)X;uF*qfusLR6#C=E2EUJW@fwK5XNnzKSr4ICk#5f6M2;-L;Sp13)Bk ztMBW`z+U%VU=s3V#kaum>e3Glx6L={uhdodzYqZ8@3NdmP7!g`*p7Q{!P2nF{iCWr=PU0VPci+tkEHX zhU=F8ZFKXx3oJT#{Jj^HTnO%5eM4m=WCYQMC^XRG#b=maLOVWw_^6L2+pdkS zehWW-fn-Q&ar{4Zk6e}FalTC&u5S;Ud5!`9iljCDCA64{*P$S8x&ygi@T-fwBd}!a z1qRSF+aD-w8WwsSWoDT6rc$+dpW?D}*5Z;BLl?beN8S9wklH3v+c)UBjVXGV|- zU}@4T+hg0d6a9IUY;-67$`>EA#`wW4P>!|Z4D6r5{Vx!mVZ?Njum_#P(Xn?Ikbt8=N4_Ms{G+@ls(e_ zqAugE$AsX*u=2H>A+MYNDTqM7^9D`WvWVvih{Lr%EKi3;QZw)l{9T>DYTZr|_|u0A zq#qleGSu<#3P0FH3GWAba@y`3zbdb)2umDoKRCQ>PK!4oJPt8kxt6neGU8H=aYOW_HvdlQi z^DH+kdDXDK-;Lb)>zyM2MXnmcuu>-;Y5JQ4mwb znC;ILC1G8vl?T85JYBUR+6Ap0RW6vqlp{l!Ab@Bei z0ZYG1XN`b&>L9mQd$0X|&^Zvj0RL4l?qe%Y#*!7>Hvn{k<*j9KL_8xZVGb>FsRf!c zH($!1w!=fXKZOdL&3=*PJio(^6q})Bc>ZvIwZ7s7QRCV-EHB|CH$E|^Y`9VGv%Ka2 z$#lct29>XM<0J+@*~9f-yqi#6PErfnF1%&>^E9_$-}aA=$pfEK0`(6}LEIWmipgDS zoF2{$Fnwc4(s1gW7itR>R)2>T@K%`O8O|HyOF?AI2<-8_c&c^6k`03&vO725tEtsg}+ z1&aJ*LXw5^4a!9EzXW_P;jce7vpF5!;#p8cL?_{)Xqnl)_erELX`8g#GnMk^**eOL z@vAGtfvnl%miUL77rX0Pl#Ew2P&#Hw+Z;jl8u;GGhtq$)S)2i*P^H@AzN9lA04h|_ zFN;H$`lh3H7iqQ7^4UR@bpeq6ss_s)gVUj;F+haMoAHS?nncXQur68^fh{RnC=BqcyT zE?^m+IQ|EIrDCSs$#idN;CB4IUFWlgT5v9v>|=QTXE{C^P)%zb7y~uNM|f2WIR}20!6&`skPOpT>rOHhxQR zD`6_-Oe!{eOtD{<3WEsV2xFDePT_*sHiOTBW7nc>Wj3IL^=GKpV;fyGuPDz6!$~k- zry8l0rG;L86&wcaGxhYJxe4XS~b7~|~LE-Wmg^gZ>M$k>KT#XKM z{vGPRA#SQb%+;v_%wY{N6CF?V(9bNMY+bU)54J&F$q4y0DmTR#O%*Ttn?b49(OW(` z|CSgYztK1&CCZL-QNQ`SmW%_L2MQDi;>BSMn#lwTC{h9yxoNkLdrdzk5QV86Tre(d zaCs?7N7b_@G#!D4^3qH-B=Tin7@v<}C^#wzWTPCB0eS?8k5c5j>3sYLGkaIGi|twh znMH_W!tDySjy{WJynKioL=T%bt@N&#v<+huwrjwf?Y-N!VLhy$r+slJtLrs6Z)J)c z!A5k8X(5@lqMmd7wKAYBdPfQi7%y|&F~J=<+d1|T#$6HQC3U-$8!A3OU}lANiZu{} z>v>Y6Y$xe6;toH~ICB55Z}3>_OD@>tStkBt)+?HnMXB(!fVX(5CruWK-fw!s^uliS zajvLhkS_=mP*8qzWgegd5CwMl5+S9tmQ#yIY6Iv;n!_bg96@5^-%Fww1Oz#0c2`7= z(7%mN@o&(1h?750^ssz0Tj~OoaI=0%x%8Ak4#xm{NS@10LU`+>C=YNW4=U!b*$gEU z$SIt`P3mfqU;mOTXSd^yi;m$}F9oc7)U0~&gBtiDoIRfeWIeol#622vRy z^R)FEB=#AHkoa&y8*6+Br_VY@nE$>3=3|KIF}5bK5u^R0se{?!d6hhs6rgnlV8S#Q zA8Z_D!72}_NsYE_&dq2tF)jv4n0wmP%id#R?SH2P`U9PVzbfh{5Q!g?YeVuA29Siw z$fO{r;T`UBYQ2f^)`qd$<>!JUb-;EE)WM^TP%+mg!_CqLHd|qO0I&5lh%F-e!3ryL z;A(w(yq$#j{IR6VJWBqijGlIt9ibIN!zj(Idh18Z!5`kvH8IE7>rkuuyi#wfoUd>%_k()o?bdGV|&!$zFW1rv(oU$qF~4CWLF zZgMc-Z#>{~#RQgzi>9E-OhByyc;@ zUj5jU3%h7`?DvFjU1K&=w>1W8~~v zAL)K7eKE~C7T@$m3+03N>oZCxfz{QUDyTxCJypn!5RY8b)F2Zz4drS5bwed+VveGYVM8{QgyEt3v)bzB1O~X1Fm+ z_rd2hzdL+qh~JfC46xrMN)RsZ+vd@KR+F$KP+bUr_U0CQW$&KSpL37WH8>Hq#6?;b z2+j~T?FbqAaw_P{iBo_TP!Wvru@cBsxqZcx9o~+>^({|Z_P)Ix%ri@1E1qy24_Y!m zqSN1yb3b?Z;C5JfgEZ2`<$DA(5$}!Gq(}YwYI?D(ppF{7Ew;oS=InXGs z{f(T?*Sr_~m=c~fwqNC2?Ca$%Y`8BcTrBj>s4Fn` z)cen+%_Evzwp&kH9FOH8JA(6El+~dR!BlKUdsL0q=9jE-b?QFir`DmKnVx^q(kb;L z-zz=^=MJ$XBZk>3%dMl~sf0G9F8`Ikc~xE7=A$AeJQpiq=s4R#*+tP%`(6|Jwb2Sq zuCwEMn(WUhvTYytT<=x8QOG@+?e^f>b4|rfXe6$`1@>E@>Xc_2;?0Um2PdMy-MXwF zoZn`Z`wN5d!SuB?Og4*wYUSH|{&+kl@a;+CJFJ|99UIZyC6Yb7L6+}sGC$Sd-44GY zth%-eMwg(}((bP9ehm#lDD|(_XHDPen@oc=H!@faMqAudz+Lc^^v#ZHVRJ?F9u|R0 zFEuTn(OIqGGKHQo-+RbQj=gs92~7r@CpWe(@zr3pa==IPC=t1iC z7$cIevk6PC=<8uI6PBj|T^qZC6L6RAwyml=SI~Gmgj(ChR$z^~K#&V2{}_s=N?A`J z2!C!_h&qGYv6C~te9;3+d2sP}EstjAZ@&`0>%2VSx1|8R^ZmB8d;t;Yrv%6ru4z)) zsH+=~!-+z|QqjW1uw#ylXOGLfgpIAmAK3Dx?1Qz4ZmAMtt59CTa}jGxlRzH0Ll57@i`FsVI_?tZEcT9?au)98v~bOxh3tR; z!tgk!J}$;?&8$%3JyK96WL@<_yjc1Cl?pYiBeDe*)n&3i=A69e_1N`#uT#W}@ql@4 z-cGzsOkzy!`CyIrUpToCSJdMuau|i=!!|?G%-MbKKS!_1Q^M7QNVTsiGbjZbKo0|= zucrKRsYvLzV60a0_(D`PUL>l9 zh@nqT?`r;h(#wRe3;mG1FL#-O!aG>WiUoSGlEQ6@KF`3PWez7v*)aH=HP@>^KUx+WrCfcB zu$4Wp-#uV5J7il~$_Ms(OegmKU#FKf%nHghJkTdAPn8+reSOxlsC2nSQ)c)eBz76g zyCUx(B31L|g$9&TD(02q`(mUje`Y@T9p=r$CzwgL31k476aMjNC&U z2U(IrUv3LD1{+=(WvK+wFZq+@Lt`K}kzayh_4L&j8&jJP1V8~>iA|H!sMtVME~bO| z&83E=J^oYnmmgDmJVIoVvKLTi_V@}feR2jOZ_({LU&HY#^!>9l?Q484o_7|19hMW2 zS4v98s>doJ7%t%bV8x3@lBnXMIsp#Gl1YrogghZ&o(>anRA}Q?{-jXwh0Fp$5p*_kLV1w6*9;F(dQucSH|QC_3P>9Od8Bj^wa zN`0vrG@!qv6T;Q2_W0ehehfRbdw7%5wFU-Jy9hR`oA%3^Q;bW6+X;UEtK}Ekb-n*s z;9VL8UrmkGLp@vqYai@%F6$RYZ8*`CD1w>yPE~RM%g-3;2(gM37Jifuj1G@FBCQP~ z-hS@!lZy9gpUlzMc^%PHO3Paud0jb@-zk1Gb-wCNal9-rA@#|xLcUi`R9`qd3d*efg!nN#XrPoptFyA1Gl%Xk%w^Z zAP8q?cD|V7c%i08q2yh1e=^&CbOkN>TAm=B*se2re%$1=*H#7MfUdCEy4;XVRVqdz z6LeyP9*$e5X|RCrXN0=-q?qo@G;K&aLi9!(W7ll%d4glC{oW9ewv4&wuQh%>F(=D* z6><|N1-A^FvQM~zY>m6pfm3z{^<8f~QX@J1-jt4@dfnZ!&cegd(R=S}CjL0gXT^h4 zf3Xv7$H|Vr(lxE76>>-)_k^$?1h=sHBw6zEO=@b%fTG2Tyu|;}+Iq!eg9TAeocJGv zONaewg-awVfIKC0=xM>V7Bq5+e5y5k|8w}Wmu@r)9w^&LyzreM1H9@^2fwIw2qwlMKXm)E2075Noo}Ac#{KWS;aQ|?ql_pL>{0M?i zgQyo2B5HSmsRs0CyCjIJMEjEW68=AquH}J4j7OPVsKKsb0nwH1vd#yI>iQZG(Kpq0 zE2-n6-PKZ4LR4s_b&~w7tVZ^C8%!+69RO}zCH`rUTiywRkNlN^v7CS23RJ>Li$_I@ zUO-ZoZ?B&i-EZUG`MoHU%?72+j{~Dlz*}GV=^NT~NuLAz4o%0YMUOH&c{}j$6AR{% z)bjeiU-w7D{nwS$vM6Z5&OwW6$J`Wr}ks9ZaqU1le zPP15TCt}FR?;vN;Q;!suEhzXN;60Zp(^YeQz;iOZ?;$B*7L9NU}e?H;T_G(TYB&t+$^2WjflXj2svs+k-T0|8QUt4({|*OF5yCRd{quAH^*&>DVqy%G4n30*YA!zr7rq8C6ErRunj^8G^iEN=n^OaVbj>(u8J zW_^;;aKE|Sc>q&?=);%=ru&?)kf9;#paMV&aJ%*r&s#t5o@e6|9iLok$do{z?*2)v zvauN?1f~4%Jk3*oBHkey{jm`2T21U4MG29goZV5z z>}8W`svnWiSi?5)J0t2N{G%t6w+m6$v01ege$Yiyci@4Z}aII?3WMt3Ztaj+b@j1x#nc>gj5V)|gs{OK)BL8O-?%d|3*Lr?HK?}^6a zi~Rn!@uZkJf5}k~n!($rdQ;SZ4)IW^6pP@Ks7kFX`Yg1du4c`eCQeU!L8^6gmxHYXB`XphW-=GmYA zm{!VD|CElmawq3(lid;GK)`HHe)aXZ9~u=x+q4bFli=4TFA-i1oml^7Gej!n$^E$V z!=a-L!(B98BI{4v`r+5)O7@*VmHmZ_J~KW?o}P>0^fANUt#lqo9-bhzZKK3fWzdL6 zT6Ob}QL-EF3KtSnFm^m#!0!NedYPGY72@%ubZow=JI+_&&q+4^9O*k!4vM&cA|j=h zqc>mcD;WRDh(RM3q!?nZxx~`rpxe0+AUEN8Hvf*=5g?P4+!+Wf!^e2}HC8|s>GAy! zC(iYoejH@%;R4U64~6fI-kbK@+Sg+vjEQ9{_^)baP8f5wQ9Sjk84}??1aJSb?PN}iYM2_LU?lS$7w+?201Pq zBYM8jJ-H=@P&0J6DdtwIC9<*24^=O1`sDNTNL{`}@dPrZ4AW#-Ltrw3s+j=4aruZ2C_M=3?*YHW+$yWp84+_4U=QuzA54Q7xg+5cGAc-UR z7XTN1WH3>j*C9M;@!dEyLB+25hdS=%M%Tnbxk|)h!xstZZL>x*XPm70aE8ewZ{~p$ z*~AW4XO0FWU&x8k5(VJMbOW5Rjy$^HMj;M9=$%5Ve$hAnr-h{DF%ZiTB_h-srx=Ju z2?_er@Jz(MkhCWF;maDg1H?*&ZO}*E0FE7{ExoN(DAK0j*F`+|3-Y#Js}qM<4Y_(2jzEu9w%<*z`Xz6wlvk$hNx#fOer0e&Bpx)O66 zz&;6h<{BAsq{fflQ~v{ujvu+kI#KlkXCUqWrC?dxIw1RDBuPpmcIuiJZ4)>+n7I6K z51+;sznuPkN2nI8JCnRNSFiJ-C-`BL0;I!M3h>=8ng>Cj<9U+~c0M0k2W-2L+S4|o zG_kR=>VCx|p)+tf@eWf#eg-yojAv@h9NF%b4=d(zf^nlec=)AHm->x@)0{5$do2@b$Mq6&V%Zsq@5*RxpjCw+8^MtC$R6CBp`i87YvIZR%5 z*O2p(q2J(=>yH*x)L?y827T&qYyR%w>`Yl%RMca!I5jH1uCe0dsv0iL`f^F-4Mfua zb!=+=n(V%eyXHjC)C8xsJgUBKVS*&gc?jo@8^$ zTKpC6bM{wv1yb|4J60i6b6Rl15>NF9N*>Za<+bQ;B@t+Lv!Fie9-6*y*FqND1FFOB zWo|_R_!FwRg-q+;u)}1pAKcXcQo0N74sNbZjT%9&XLDw`#;8V@%F|jAkU^k^`i2r5 zgQuVy*9E7%9B)BQ-%kAR0R33(fi8#4?FO+@A^?^B$YXiX=Ppa$$=zSH0jh+3_SpMp zt_1D_n|?^~b=iz`6?T>?Bn}qhar4>LuSeun-=ecLFP9U>aH(jc{UIC;%69CNK}=`j zRnn62yU!d~m2Y_QIUK*dG=Ml3%=VEjQhkN$dz6``pqKDu?7LzMJ^BaDWyk zU1A4QU(DP@7(eg0AO?A_WNc;#r)q(nlvspa-@IS`e_fV?d4bLKU<%=A*>@V02PnQ3 z3eLVV6g4M+8rc>piugeN)mq(WVuDk>gU5$m;JWf3)!r|S;I}6(u-pdvQL`<`W&fss z-Wv+frarp$KHbDZx|nS{sIYy6B+2vl3B&mUzKE&wo&fm}FbX3S!a&h{N<>MZ#YTD% zEV!|7Fn3n309}L$)93fVFFdbJyp(;MD7GHUdpVPz_uhU*m*B*074$XH+y*Yq)nWQ? zrM>Tw--IER^KWW>dKxNTdG&Y0?=pNHvKI;MIP}9KXnp?7V3EC0)A|Ik{qK@uUF-{4 z5h5GbB%e&1srXHN7A1Ylyj;YIWVa^JXP&=Mxjcnlf|ZbyFlUi;v3>CVEB0GTTHHE5 zsQ4!q@4Pr7dC&sQPX+sv`8DVXo$HQ~TuV}0>d|W{u-u#cYTl9o)BA4Xvcyi1wtPub z15qTKqe{r6Q1}qpt>eA> zlU^5Av90cWVUc?MS$rfP@!T^HRHZ5d?2tAo8wocXP5B^9ct{mw5miE4dq$3A*F!g8g!F28;l6on_Abk>UXiNgT9RYjoXU<Xo|4wn=SdJy3A zbech3+ia!>WyI!g^HY6H>==4WF2!*3`jVeO}Ew`p_@Tz{jm(s*Rt5NJIH+F0QsITDO- z)&sRLDD?5(v&4EVnd>_Iu0&c7#Xs=&TASQFyq3H_FofypEi#k#m*lhGa*q&0TgL~W z5jwYhDfRNJ?TZcRJ8#L>*+af=Mv@c0cf%U%__&a6KN!6BnZMwthIk35UjS1FNo&1L znXae4+%h@ewHWRcNhN%07(D}3xm#5{iQQWpLS7Ps#* zrN4X0$k24sGaI|uhwTfO?a7Zk!#SjLd0F1hW+tYc9<{`i`oFwgNAGtMR+Qg$*wOJx z<2IyngT-32n^+TMZO@0=Ua@hcm%)b9QPbGwCzRo_L}R5Mks&mh4ey0;er7GcgSug? zrQlT?5gB2ccuvt~E*>^>Hh7xB-o~!FF zTUNP396<8q;vXi6TBvLf?6Z-s1S?#+x4RNN2TB%VFW&xnyUMJ=J%-Et}rM;9AHN^vg?n zp!ohpP?GsIj^NSK9d%pf!8^cVT4A(D!7uCm9z0)T+Uma-#paWtoR67^6T)quUP)Uj zz5S|G(z|@*MjL$FjLd8t$!HbVeE3kKa(1VF)>8zQ49xj%e+P(G5TIYS{r11pfdMS$ z;_i6icv@eP)AymRw@9MY4M=x1kmv)I<--F;&X>T8P;X|10|l2m({O*}#anM)o8;Wx zHlu^m!9}zDd1x5RX!x+TEAdDc(EW0GiFsu0s1bZ3Ie7F_NPQLl0B99}d9}Ckc>~YR z!FrGAs=b6^#LHCMh`eLOFQ62dgZH3n52_)gj8Gpr8Z>wD#gpQoM%z=9C~@fsBA#_z$mEbdGOrexeT=F+4X1p@d?VU%^hK{5Mc$Ax3Ube@Oo#L|nIaWbtmRG#5d*3%NYK_imi5zfUVw4-_5p z-w#&ABNfbM>!Y@-BC6cCRT^(xCE@@f9eJBr>Gn~i2q5U6I1Q#H0?Cd7pGb3A=@c-H zB@-&uIW2`b(=5u;BVZ3zwo6GILcRC#!$BnHqn7Fm(J!#)#9$SjR`wdBaK*qifry!7 zo8VPOZ)rl{-PpfS3c5hlSJLJMJ-PT7hPqozFb!Qo6`^emQ;AMVp=x`GnsfPZGgW~L zhD9iM#93;OiR!v$u~vLo^0QvTxqmvrbH{^VPyMiH;C9i+{PU)px+eojq&1zyW> z@DbW}(C;CX_l*U1;lk7%D4F>hnylE+fp*LVbMUcE3=3B|^Y`|WAPof_022O~ zzmwlMJe=O$eV@Br9}AHm@$N~Fja*UqInrL-${e_JUeis^F9rLwoK+!#@7B1%`RxR< zlCxjlXnh1X&mJFiY%Baj?Kq3}%E|HIU;CON2(fula{L!e_v-CRwCG%dqmGv~5zYgXkHg{!QDnI@8hFqIf9QJN?+pgkG|;DM~O5wh`xJ7%j5`mCcwq$;jTfV54YNX=@Sn*%a|1 zIHl(0l{R(AhpE$LGcz$KsH-P~^MFvJ)}g&JNka!M5gSTx(qEPfsqZ_X0|etXFRza* zX-wW%#Ho&5fpA$NbZDDnw$;~~}zs~tSLx!xm&;nrJ}rYeTwOmoExY{_TGDVrA0_YzI^Z<{1!w$H`72H{vuCrx!+tD2VOGFvfo&#`u8!_3)5H9jGMPrnQWcepeXzER-0{Df1 zIE7sp31;0H=>BUCJE$)ED6V86(Q=t(x`Evl>mG7a3Q9@Gd#4-M$zIs5JOfyW|2 z1qwj>*Oce9Tjn72W$Z{Z(6yJ`xLc*wfcaE_B})UU>Aze;@{%!N$J#1lYXh!c3gUri zCI*$OmxXjzmn)zG@Ov|zxBkt|g7-ZV+?m4f__gMy8;Q+^-s-8F(&}n?$wc^dR+0Pi zWieVfa<+w3ExoA7^S_k*W&%cR{?IMR{P87`L-Q`=)0UEWZsRO)>;wB)Iu-nha=7)^ z(<9Lg?X|7N;XZmo`kvuNf!R3zm-@=%7IcGhKlUy(i&?twok+>>+~r`H?U+1DRiSTt ze8R&uS||=vzi(^HruH#Jcz0_;mH|_%x#Tfx&rB?l;7?EUqz$@1t?a!pXjDcGsRv7s z{Z7~6YwOJ>*YoU|$Y3g``(EH8udf`}yPe1=l5L0F3a$F;X6Thx2G^FZOX#I?%-H-9 zGg71p-HqUV$U^+wMkfu#FHlV`uC8RM&5OnM3=S`y(rjS4@R&N_Z5n~Ub zc~v-Sxfr`~}75Q?;}S$$*>v4yc?DSSoyJ!ED9tj3_Msp&Xy-&X<~2^beYY5z1= zTclO8cLc)PbF?UF&%{LtbsIkH{vbJ1b#@1WpBD64C(71UtnVM^41x7~)6I{V$vByg zUyU2QJo&l*1TPK!d_MiHf|T|KDIO{N?PuTYQU%!{Av*o_-zNS;#0)!E7e9cLIRnA8 zkegSC^XDg=PjZYpxN){^mi^E(9#NksLDI-0k1f2p39Mk0PE!`{QH;+RfNr_jqYsV| z6sf-eT*+GTjSFkf7#K~##Y|@+-liNutoRr~U^`9@y|s<5LUFskr=XXy0mX+MvYi3yZ>8ZtKRGS!@J2<_L*x)A5 z_|LUGKl7DGH&|F@1alYTLtG8yBvLi=sOshO8SbI9n2rM4|I0CuioBoNsS^})U7iN^ z-^#aW=v#(4{U+Hl=X;4R&!}rd(~sy)X^*R&2wCIO<8r*H3WE0LWB3aZ%p}&V%?vI- zCs5Lf)QolVxplLe)tbCN=kBVb=Frz+g{n9Io^DDMAj%~fvJ2$a3GEkOTL%chskr ze!ZF;TFR8$u)fUsb>wawckUot(X$Qv<2gCYzMh_Ek1!C= zIN-drZGDHHef7=O{3Bl^8NnA|zsg~V`1sHF1$#*cf_&9L!LG=agj_?trwVf~6UF2B z^PV+v6=#+Q&CBPIuK=0Hj$Hh|*c!5!9Gl^MiIP@m-+>x{2^byrGot2_jHvzNQw9c3 z5%b#9g%=)&3ZH=zdAkRv#A?mJE9Rt^`MKXO!&z`t@IzQWDsDPeB?3wO9BUNz)?fo9 z;pnsvn-A30s|+NMg46o`BjP{_QJ%rye}ao(8*y0WD()X$!X!BO`XFTNw?p;o^uYzt zApIPITD@N|rSuV`cmBol;;5R9`5$Z8y+7u?i-&aH;F!@^@TVWZw?L`j1AI}u!6kA? zU%y_<+%-dGtUt_}P0(t@TZr%k7~%tvbe+<#C9q5>p!Ii27V8X4zS%6XRr-Om#tC-s zD@4!mKGYAbFl~nP};ua$4xez;I36hHB*f z6SK1ibPNpk8pLKyFz*MyJ&-!=ffxw_=>)b^rouR_`0@gSAhqn$N+OoC^og*)3Z;&> zyOxU+VY>plozY6S1J@+YIIkPH)APj=@GfQ96WwQS+FN6W$e)luz_#uP3cV(2mb$-u zs^Iv9xDckdqf+4+OaH+dW8Y_s?(ZgHMAD(-HFguPxBXFx;Rvbrx*YYTeCKkfHUWrg zggYHAe4@O^m-3%Vfy{+g6`a*wVSNb&arGB-Yx#*hGN`%^dx%eyZ*?)1z1~X@chgBc z{Pxn)c1~eD1S*Nk;dN73+seU;+25B})XIiU8;~e9l99lKbx$d39~QS9jY3OrCO2PJ zv<9??Y<~u)q8^-26IjDpSo?!%u|^Y$UYO@9gV4*uh^&53ZtLrl4oz1osznF8WHe{) z9hi`lEm8LC*nfWd>U<$;fZ2L zEK`)8U^zY`wmj1DThTQKCE%ZB@)v7LuE0P)cK=^qs4L-Ohn`|u?##n`d82OYr{zKi zE$*u7wIB+oYeE`uA(xk$%iy6-)31#u`#5y(QmNPJ`Es!p!BhO~1j#;#a3VTTSp>^% zWVELj`O782x8-fAi8~absOLYzClGXNPBi8l98Eae9Wb>36=w9{C+*r6pkr#n1tG!H zzbj%yUk{A=yh4quXGDJzrY6IlLT>v?oCm%WYzD`Kw)-(nW}pO`!4JsfPy|=hv{Ti{Q=(m43o=IVH_VCb2CwBJ;2ejtHD187kd3V zM4Ge@syT#zM(Q6gQ4J1q`*s2==RcMxXpAIc64IwQ1b75qtB;riin{T1q&*@Z@w2$r zZc3m_f!rTfH88E7&Jn^99j}RK61sR=KVzHI#yELiOCEXLFLIGkyDGJ{$JFI+Z4>|* zGpy1klF3j+W9}rNy%s%n;mZTqjO_p{Y~g_&Y?)i%zHd3)>QF2td3-lFhb4$d25q@c z(VMk4IcV|BKXU-0TG_>(>n$RSxX56AIdo*OACHpmZL^;0Q75t%kKYg|i7_m~eN9`b zey2qO>x^)}nmgI=n|wWLf?AAsXt8v8;t;@ihn((cvr+rTz{dU>@k}RHdA;KTiAs#Y zzTxM)8HU=p$~R3)ybSf^{FdCrV;?%1vnDe+$}&EVK}SH9ozFg?2oqimZd$6%S}zOv zEXn@K>YW#tXk$mcQg?o?jq&2f8}WSEm?g=1c?0;_$(^`ieYjtaWC2_M?-P4hln2k^ zW2s=T?}htsnQm9+9s|qm#Dw{EG+aF|A_g^zMc9;FUW(v!SUs+IHKB{EBgow_7h0Y( zBMSa@4iOO88GPs=ApSrllG=0XVhp=#k6MP6p5<%<@9VYYw)(3GNiH7IzwsnJ5ev2B za8d?C%vE`}{}4xCpG01bP(tozqWNPF420y%+Xa=Y;N(Hw8MGrMNb_Uc>=fnt37yyY zpW^M`8z8oPZZutTaSkHZfknbgaqxk<^gqyP5rM|XYYJvR@r&+P2ZS$eGt7TDFezf7 z6>!k}5uh~SjUcq0H)EkH{^itJQV-RP^~pCt3Xv6!-p8 zUuB1UYx6{AOSBpw{0o40l3SePd*7V(s2*EPsTE-)>?+l%$SPNGIiRPIkWf zM`b3in4HGh6IaDXAc!8wIzKvuzD{`_PHQ0afM=2E-*BmxcB+3_{d|LUTwx?dwvDkd z&6B~p*rpN6Q$>wSzflz!B`%LgOr;u(2fmx z`+=vRUINkRIkgjSl%sPMPeWaC+AKjO@m9`kX#1OYhShTo7M8Wk;RQ57Pg<)qu?~S( z8i5Qkx704mtCt+;z=#hf!0S$gJn-c0Y^tEbx}a*Nvhup?P`le}zHgc%J4K0hA+@}deD z;2;FjbbW3eLc!NJ~F4lYT5&Fo|6}-Ht1$8PB#J&@DURjDhC%%EV4hM z3wODmxCerbM^sM{&p#aT@ku@u%c}t|rI=c9m4U%`4t;*FGY*Z#d`_gVJ_Oc0L4t9n z1|rCumooEUsj~%tKKvIX<%ta?Z)HE9*N=G_M_kT2bPgfX-BahA|HXGRb2T$- z*4pR1=j{FLXG;q(L;EZggM`(0 zKK>AN>h)8`DW#MNOFPvs6Z|SK<(-FU|9Kx5XVphZ|KhjJONVosx&;ReCB4Z)`GPJxl)xX;_B z5%!<-0gKm2izgBdu6Goll2KccLRYbBr9I@zZD^k65puQ#u)!Q5{aR^GdRI4cvnSa zj)OLdDP#HE0{I3CkURK@!~T4~_lt7tw2LC@tV%(HhhSD+BS(!$uwv3xsKum4 z4e`qQUDSnN+=I%1MH)JO6|bp23Y_)S53;F>oA#>2DhH9!s859KublRD#(#8+UfyNm zJUM{>K@N+Svvf;Q0)A|Kpzb5h1@5mitj**Y-f62etmQF~^omKF&#w?0e(5zE$aKjYs{Ul(Q zp+}ZJBQBFflhdUj4bYwf@xav61L~D8N04h$3)k4@R7e_aV523h}d{JkRChBV_Ogmi0fL63|e@4Qxgv+!$W->^d<@29@y~SEp6S zE!>FsF9P+M9^ajrysEtwtnil=h0YaNg65H6~W_3;{T z7yqogakgoLPYt>m3w?12JWw)wYQmjO{GYkghZ_Dsho#+6!1iF<Z=c^T=$ldN}&D*gjU5^&A=baMwTU^=)z zsVV-4GIaBy`c0*y@dqVVp#lc@xT)75OvCXXGrFNB!J&;xR zOtdr2ltw|ckMN`D+qyEFr-GyA2<58QVIf7Ch(tc=yw)3pdgwP_*=F|+h+WE!%d{C| zbu-RBDqMOxK69j-q*g7{|Gmzog9~J5m*RJj*K70)f?T&exor3$f7AWGt_Zy=F3!QfZHK17x_p}!Wf%c(62|C^Q zlionnBC-1BAtXvEHYc7ppiFyoDG18Se$<5&V3pd_H2mFJ54|a{T{w96d5px}&+~l^ zAFedLBrP}!q__R^7{}Sdyra05Kkm0mDVv*z0#_V2vQ@qxLZR3kMuG*z^FAm~f*vk_ zoCKbvw+`&-{U#+vU){c=R+iCWb$WIP9!zk-~u5s^*9t;D;iQ3oVMK-BFKFDQ|z7NT=O94nhVHux8UP99?&H?{ntFqbvS}{ z9V}f(rTjvZFugx}f6H*5Kr;MN_b{+<5oR=f<2vfb8N%S440hX43ag($@`LSPn zh2U(P)w45R6LX&VtVt8M1P~M9_dz1TV)V05=MbKS!TMH2-G2Ny38weS{@OWow(t2$ zD=4xbY=X2_&%?!0{!$q`yZCDiy_z7_bqJdzr??gJT7ekniiO^_{F*q3Xn(0w2?riQSF}^RKTVw5scqj((;e5^4OtS|HCx|W%m(L`6 zltjW3=<(p>^j^8PYX0sy(tR2!O!A5_dU_~*1sqW0&d&c*+^cEIhI{8b_ko2rPpcT% zhc{nHi()P(L5%6%W?vfk3(@u|##e?Fz2TrfgZ|huO$PaIt(GqgS zr1(lwl#|hhrTI}=Y4Q7hay@JboY|+{7IpCWn(u;RCNU=t*W5%pqAADET2oa8cQL<> zOD)r~3l$H!q#;>Ug$AW~|Jp|6u;2>ClOp<7k){*(;onF{+*y0SkfC~pOg)0>9-3&9 zK=Gl&yDp`>_qQ=wZ%)=F)@5v#Sr0CnKAE$c7mcSMm{@C)M5)i5+H=u$p<9Up4qm|3 zvEr~X5P6^na6BioH*tIzz>kdvh}!1)wqHRI2lmQVv-z=pDC>1fIz;aAH;bQGXOp98b`Y{7Qvb1hNd@d$`FmtY-Ka&j-v+_+(GJ zuOGmrn*28ADn4yAAaMP#xId=iMVDn-2jb1_XIF?&9pQr0RR< z2R%oWcI;o;4Uq1u=#$Q=k__l@VqD(LSz~-;Jwd8RJAS$zx;-KWxbZ=OTb6&9?DyaS z!YBYy1wn#~t&RXrEjl!U{A?|7N-df;)ta_UcD%ZaXueiUT?2I1;h(Mb5M$ojNQpHP zz@TCxtMynMca_+V=>3wjj68h!BBQnzgjEGm(riI5ugP74tX%{9c2(Vy75*o;;dp{_J1o!!F*qo<;eto2#jpTm5JT6o z+D*#Nl_v*_8xnI6P{lD5p8rf+Em@ID8y1|JYdn89fAR%WvF2>q_UXvz><11CyNcM# zus^0igU6ZPi_Z|t2h@%;j;V>rybpgF22|#IrLlrRP{XqrnO-I==-GlgV~U*zs&&tKw_C)sx>#6&4sOJqb zVW3+DQl()TLGNr9`+SRqse8Ft=+|gzGQE@db3ViR?^%4@KlUn=5%2jTy5ZEUv%^z% z?n#cTLTFBs9n@Y}IaBLT^o%f)5)q@uNkaYY-}HYYjXtA{vt7iJg?NY(3BPu@k_)pe zX1n7Ld705t__capThHhRHXeo%P=Q4H`FJ|*JrvpPSwuBX0|QMUYuuK-?p2?(Bo#QH z_ibokIqpAtclD>NYay%9g>+EI(mvhuG0+U~;6&oE3d8{sd~p2p^vt&;%JbPq&pLRh zqJtI3Ao9hq8R@ZtPcM)+VRvaE7#5KTAY;<#x!oz|d$oT*nZ)Jfc!wOV&5xgxe9*us zv17?bze6Q+}h02l+ms7V4uqqIp==S&85PCp(^9Jkcgz( z+!9{_uYUuhRmIsJ2Aw&Km=u>erH5%l=pu#lcZ%FLKs&e-Aj{X3b<2Pb4GlnlrtzVJ zNi{Ycd_QsqUtJO7VrKHcyVr?0eDkuCP{-fR%rI4z5x-|`N>+lY2VCZ=tDB)I8-MAAGKN*WV?_=;+|DX9>JcNA)hZ{L8 z6<;ngkq_>{%J%~@Sec|J9G0KJ!$Z?3@|}T1zb%?y`wq}F2Olvu7B)bDg{1dxgdZG(jDh!TBMS!)K_mv>&Jm4j(#>jR zc9-LQG@_Z3h-QIUS&x~sE*xGWis8=Z+%#zyY&3hQ@o=6Q0Gr=G=B69g=2s-}Y|4xP z2-at8es7D~#ro%YR-Y|h3-I_pWOG>2Jp78GWy7ZBA==fPNxWp--RGaqa~==+CJZfI z|D~^LvbY8@KSa4H5rTv3hOI;0#fcvQT^ zCj0a9s^n(N-7iL(Bj(G|A2DJv|Ng$^P}CRaRd8W1Hy!cma!*tFoz{5pn#jRhhQ6K2 z#s9I;Ed7A4k4z=Yy$Q7Eags~Mr&qofw_03%8*=pC(n0Em$+$_C)^;i$-kFB98aM-Y zNpTMFd%woxSxr7#XT85NsvXItG+!`g^b^sjB0q3CT9k=H$Z|UD3g*E#$Nn*7XH@rn zs+dc@ciG^qQ1a(DX~3Oexu4D?NFU)rtYH<|gCfjUzp~p6&;WF;Q>DHDGy=d;0bx5T}i|Bzlh1@E3VPyk@|yMu}MT&P!bG9 zxHfyIca{DFb|w?uKf5fSgUoQ{hDYEd4lyq}fH(NDlGuFwxmT}de0;`62dC`4kzewT z+&hYu0q_NVSc>E^bXG}1a;TQP|2TTb^94?@@rB?;5Q33Te&;msO6Z6><7^ig&-v1e znp;1e*+-RJB9*QnlKj65%W~29pQKUQ2YwCF()_BPtYHP6X%scsYjBb4&i&Dij?hl9 zgGh6x^0@&D{@Wz<>5S(Qpnv&zW&_$ZBm9%n?AU_*81u`@VR*J8kn5MSb?=pAMA#g^ zOIrJmM!~AFht1ge*9F)pyX=M1$+@fct4zQL4z!A`H2TBz}{7ZA>u*D=j3Kj(E%0iKPs+r&ktwVwT( zWtPqIAX75y>y7*QMIR7EzbdfJo6yPCcUpH7AB^^kG2mP(T=>S}7?KHwV%OrTVNJpQ zx9%NH&2$XN**(%i9k0t2-N3i=xL?I?vbzWgWd3+5gA(Tq94FBCo!-iB{)KK;8@9Pr zd~M}VhG{%SA~JAw@u$QDJRQVJi{TE6G zK9GHGMJMPwAtP;rKm+g&Iz1o&TX38m?C@na3dkj6Xx9Ybzev2kht=#jXk5hS4nDu< zAzHAyypaBGJ;-rm5ifqEKLE8}=a$#%6d;at40h!wPyxmW{771_C%Ks#d zwq@X}|*R51} zWpV0hToQ4GN0kbu`fz`z-freMx$Ig`PQX_CBM7GQH*Nwuv6yjsqd~`yp6)PDdMi?E zoQv&xu-*2?!gGz0dadW6bXI17aK2s&EBF_Pe3kDF*7@wHN_h&ng0@=aqOpKw1wL7$}kFdrZhb= z?|bKe-YC|5{!?m`^o(cleZE)d`bZF7 zSzc0g83fZZc{%Sn69U4SB{`IBI0Q8?iv6-Zi=Z{s7{CCF!vngL z_U88}L=V`UP1***GvJF~;{j{E$T^ThHM|`0UDc`J8zatig};<&F}uU5VJ1nzdqABo zrmTo+zIgj0b@BvYkvL%t1onVjJNhC3LPLNZQvfbAoMqWCByJxwAkuX~#xR1m15*7L z#2>yo{Bry_tnLxFza4zm(}E+H-K?g1kFXG?qqA%gd63%cTHohW&zYSf!4XLIf&7!= zVP~MqHuaVg4`IiqOH8^xamh%fcfe@W*TRe$Sf+-X^dQK5m~;Eyo_1TS-Hhjb931~$ zmo9f@)V^T528$Xbpy(BoO1-UTdWWoq_byRAC8UejLItH`i&Uo^tx8?xdxAO%d0?4O z{gdWm=VkLM!K7G1bjr!wYjrb8|DKDW#~Vi;>(QQ7^{&Z@D_^T zi}0VgdUtiO1AAJ~@BK5|tq(Y}hVCrAUC~?uud1oPX2DbLp3n_#^?|nKT`_hiLgl zn^Z$Ugw4A>0~IcGf75=gYy=C~9;XLBhZ}?-mQX}HKFi{S34Ps_JBzzlGVPelMImuzFdEg$YAc)IR@-SdoNXm~b5urbg$sHRB>S?^h`L(7wZhQVNCxaJhyLlKT4 zr9^~0$oO@?*+z5R`xpmZ!a;230hq>zTfvATg$D$7AdEJQT+x~xk%yCH_J$P@Ky3$i z5!pnrUP^a!rer@?K8dkjt1O+9NVwhe7EG!Rs+RnXiychdgc3<{J);ojcr{rj{c!CVTu6|}OlCUc5w^N@3{O@`5@ z37FUH1nreQbQn;4>9_r*lfBkZPGH#5n;ZxS%va_l9q;CAAVg0r8h-Cn8}vjeAGHY2?T&d<@YE%pwIc3il&!Blz%-8qR>j+ragVO4ow14z`f@F>+QR}==Z&H?`^}65DG6v zN)~zSt`IZvIs?N7cCBr-u{ZGKS4n0@KhESJ>J%Kb0n*UY3F|iBx3kRdx@}6hjj7{( z47iD@V7+D(^_ePAyb@Ab@|-yt_UMRtZon}C*G-RAh0|WDZ&;qfhmIhm_HQcOTFAS7 z64O(`BZ@Ct#~ESJ4`0f=+%I=!vN0KV+bzYD!IJMX2#Nc{d%yEPXJU^_L`R7i@1OKu zClx6YHPb4l_n7UYxKV}r+%0j7o^5cJK@FKzl7tfk%j`Z*BwZI0xV$tmMXZCSZa8 zKW}S!3ySZGInwK}fS_&OzwMgU3)?2vM{xuqCFO;wZbb1c zw%^|c*^)+G59sq9*w-Y-sR}}E1U${gy&A9s(mh`_#_GU48f9L_ln-M?`oS?9A=jAI zSm-Mm=s1u#Z-(9)f&tyB5el3^h??%yi!GMlFUVA+1SmJ^_k-mT+O#j`l@#U{TDt~b zc%BkiR7`%rHc5Ww?-kV~e7s$|_W)9nsrFD}Y^Rq#bT5A=_(Lcm;*m0Ra7;7KO^v8n z-4w2Va3tMIfHh?lCgjJ2t=5SMiVuC7oQsQ;R<_u=dug}W8y#Q{8<^Iw5*F9hAPcB9Dyv2mW0f z7)Y-=hCj}S^Pr)dm(!m+NpKZ=K=0y2J3w~Z9wesXx~y&C-2w8~IZj52+z=j92K<7z zYRjIo-pw)3Jtg4;11cHyFMOeU;rf~Z2&lvD#Tq}C?!<$>=KO(@*Xk>I#_IABEm-?Z ztY8#jnvv3Z3T+fz?K_OTdXE%)@?L7)_xRn-U-a~Unv9s@DZKkA)g+i+cQk`Vs zS%rh)o;mslx@CH38@JSQ@uQ{Wzsce=sNuh|xlygLgV_-Wo}CLvlVWol4Y^n41tXK6 zUXV14G~qb%K15+h$Q<9D9)+~z_b3#L+=rEn;@f1=Nve(oVI(7?B^s-ezZK34!Z?c< z&R5jvvmAb#ZJEqdTZ96&p-79Ki7b`ATVkRAqH(3El>Vo2jV!SGob!+E1S`XQ(C&`D zE}Oz@)FkCZtT50sDFq_{*g zYhKYM;KXmE^QvG+bcfSF8vEiH)C>_4dD8LUsmn{4_$nG$DC}syHmnD9`E)DY(k51Hpby>{BAhyrKx})zL)E3s);SPu|?R5l*Cvo0M!JNt|}`A zVPpHx&cn~BTrR<$DDitSk-3>4zcLCdzz)uMTOYXrI%SurZvTPLK#s@MZ9Kq-_n4!^ z&@H`TjS{s7keR z0ZsPVV_b%t%5EdhGvk6DX}6M)3E|-qX;R{!n2i`Bw-VB2bE#6o$jj#ANpJFu7H6Ym zeo`KlkI5Zd3R519QPHgL7|6BJp?0*AyPK|0wscN*@QmYEl`nHvrOz|>2q#C9W$mlY z$9hlK3+|9S82-x~*n3ymo2`;n!uo-+Y_MNX1xKKk*Zlp`fuNoy9w`;HOK8HG-VK_y ztkKOVw@ixcj5243^v@4G=usobB<)n{3<4wm6zwg=xKR1$D4nQ_AucRIyeebS(n^Rhu{Gh`Wj;p(5apbP^Va^OV#P2n;9c7O}nGd7=bm#MT z2CCAr774G2`FVGL5V%OHloUiVlD>ATAE7&<38%CtnW-c~@r=$f?O0ONc(U(8 zMpGOqQd!5^X@m$8v3axgAMNi59)~`iAMf_m#4WF?UhUUHP@dH?(k>V4cB zPL%%H-;Y|?MQ+!x`Nx681%B|QwBYBQ3=soQ_w+wE4nJ;a>)?6f*deJp8ZooYTcpPH?Sn=D@y7*RtTey% ztwU?`xcAlL4>?edJ>K&4-1M)`O`bf=ufrh7uAtYz!`nXW5Qjp{fV}+-i({2=!RJec zDE(^hc>bcop1R^Z!rQ$NVz!|GoTSIEAR#X1k{eI$Y`E{B7D==>%22IU0w3rnW z=G~!2dBr|d$oZ*z0zEPMqxVGqo&;vx)}}{o52B=bQUY?!Myn;%%R%fpbp7h!9BIHb zWA2qFq|`^uh$f?$S-LN>6BKUSV^@p;MWO3u+yPC0u)o?}LzlUiy&rF7bOCEd#B~m_ z|F+3H=92?Le*SK`n2~CMN^v(77a?ONs$6;jx~e?~lyegG^%`nj8&%3az4mpHg0aVN zvYIPrd|vMi{k8w<%vphHc%xICx$x%b)#o#v1WVRywr-Z?Io+~n&^1}w5F-uks>?D2 zWzfm&Z`s_~RdU5C=>_szJplq%dmnGExwXA)6|)M9trk;f#EGejvJ52xR;RQ0ceYM? zvd#ZMGns$BoF7JhbOjg?{?xwL7sq4XSoCa*Tbx|3GGTNk;;GDDQT%dXCfO75~&gcZpT*o|$e6SAX9emki-s=Vw==@Xd zM?zHKKm)Q?qp`**s&>Cy2Fol@rHMR49&rTL+H2Sy@;5;kS}L~M%$U4>&rVp#kQM1EXGf&M`ovtN~Sp@&TeiO!SQPqkK*A!kkm6 zwAZJ8Ug*8c6+|y zBQ=n-FW&+u`ufx7UyE54`=J(_%)A&fLav=Vb)>=A4Dn0Ack-nVib&D&kMcHW?v%+R zzK%m)a574!#n$c~s$$m8Sq~!LW-g!xU{gJ-tTIzF+?KK`j|uOh3a(#^N^M>ldopzc zw?P0R#jlLwJmqXDr-A1a2Sq7IJ5fU%1RV$1=Hcn-WXX@w&uT!r+$M+_Jw#mL@j5IY z3YeU0ULmEHtP3hfj4qLesbp8bbFZTj_2An8NiNlU&s-L~KLgqD%%J%E;cbh$ zUjr634|dwCG!P2897SPTHg>8+A}OMU)nZgCHmYTEhg~JVjZe4TD9?08DY42=7P-_% znFPH>pQwBY{j;}Nn@X1$Nm8P?O6S;Q&-C;vWl&eB;tkzXv8EX%Q>z!2|E^0%1qGa~ z+ILaSBK_xI4bOha|9}iq*fWRbCq!g(z?Vv~@7tvw{AqTKg1ya?%=TPXt}_Oz7&!!~9n95GH7pyiH7&O)JV-1F5<#R*gcI7< zIB*)nox3!iAh5S{OOp@Od2T=>{xPT1A1L8*#DPz`d(C4U&DS?nUc;$CMW}5vH}*$Y zr0xl5(zrUH=~5+t)LE`+td|`lEadf{6#Lp>;cc2Ju@9^?*C)6*7-~G7E;_eH0E z(uW`Mv2X8IZpMQ@#e3er*ncJTK9#A1uT0NzB62bn=CG0`6V8`WvaE27;7*H7x%r*i z)MBVNMV<8b7rN=17a4JK{>olO60knphpUWe9A3Y8&^!f!lKIfi^SMkFE%B=4-!b>m z(EOg4n;sUQZbL9_k_RGsfw8;K0?))o;>M?%Lui5gJIC+HdQ0N6lW)d*P~Wno6Wb5c z+e!REVYt6LJCwstDT8^tTD9N~4ng;yUG?#KwJxN7d9V!Bp8v_s!P>3G!yI|b2{or4 zF7E{|LBH5WCko9dd{RWat%c4(dXEP;+V3HteWcZ)W=*k>1DAo{5OfI33v4@f$^xq> zxLv9ECU`T~oa3n6^08GFm+BOAp3&!6yh`>oop=fbPVP@v&8_)d5+ga53CrUUYCtJ? zYOTw`k+b#P0pcBO5$qhsWv z(PxG4`?9E$hojy+I`jT)cdEGsm%?)t?9;m*sT=8SB+Vt1NA%uu%LIH}KGcEy&3n+; zl4IQWt%SU(8SD1?c1gmi{k@2uw~Ftbqo@w)db7IUR&0*wW!&CtGbBz)eIdJ&LVlv~ZBh_o(Uk@TqUd@k$o9M3BE&{aVE{ z=KOTB#ze}K$jOO`XE&z-FYwg3g-}ZQu56d7DGKM3w~vKd4(svP$?kO4 z9p52t`h_dY!#L{^A6L<|LqIn{H)!GLFrQmKr`!j$Rl30HrI6Aaq2S6zd!wI(96JmT zQ5(uwt=Vso=_YapIw0mU5zx`F{+XJ$gub7vUW>X+ShME7onj6^rlu zIt^N~f3`3F!uTdLG^#;>?mQYZ(rK5$rmP&p|3F-k*+2%rz{fMzBvwn_nqSzQgQl-* z?s|rXJ5GL2?TIShJZ4%R-DUD$owhwL_lhn?erZi6YOV{VR$*AvXCH=IbY_Ky_ zQ-)&RY@fu`oRm?%ylrw&F$!1Pxl{g*BD!PF8P@i6`&3n>aEwM8bA#Q5+;Z+;Tz_#K zQ7~PW-duOcod4GC+rh_LXCFg%KNdN6s=L2fD2hNhcI%;W^xI1lqvO{)z7(f)u5$BU zFy3w*sD}0aS7J+X&2U!NlV{NGA^NUn`s@mnp?NsJ%de>M1T5xDabZQ;rI-jN7t#%~ zZcJH3i*+aLUcRP(6_IhLhGpWXt9m^1_TMk6`I>ba3V*c4$zGZp6z^191#>r;C3Q_7 z*mE$Xe7FI~aIob-Hl1l5-bbhg-zO?U#ynxgM<$e~%$;|4=5lz*f~K}lfJb2ue)s}} zbKQ*1mAQk=i6ixvl$tVd;ChI2UHMF+qqX;{wtRFJj!Jrk6Kr$ z6XuMnz6}O+7tNGT(lalM87lW!Ythdj_s$FVYx-0Hr4^S_Nqe8zgQf_wNf?Vvzq~zML;5oCzAWn-r_|r)uPaH0>=Qri z6=0GE4#u{oJz&IF$oIMIvc1qEh8{Pf0mL+0h$9Y~#CWnm%=I{*@PFZ00tc|QQAh+Q zl&`RDczmc`vqm0L@4lEW1wHyPzez;+medg$ka{qRqIJklD;{x_%1asB9+`tayOV7rg=Qd6l`Yp)$N>UKhu7DN<88J&cfsp+ zjV^%9#&J`Mn%NHhS3B+bd#P{_y5y)|R4-8Z)H|!?1d9e0=ZJIUo z0l(@&e8Al8&$6LPg(Zk1|r>a(Y!S zs_pnQYoIAaL8z@j&_qM1yp8_dW>ot6#`>ADU^l4xFSNF9IC^nF#)MZ-b z+JkhO8a^U#6*tY+38yW1@z1SaiIJ=!xOcl~Yf`{#Ur&)!-uO`{qZ2aKTsT$unFaX` zBB*Rxm}17~wW3MYjml=~^tCGY?d6b2r>Iu3@dh$dHFy8sfp1MA@Z%IZ^mbcaswB8wjjm;S3P0cx&fVe{3@%m zOav$8br_oc)8-&jjyrfeBe{`)My6aMJqEGDMukiP%vYBEuOGLPGHV5h+%9&tEM)m$bCJR1N% zYSNCi?f_AORS%-Oa}J^d5>JviS=KwQGjd$st#4}gO0g8qk{#5oGfv=zT52x{hTB1; z`{cEG?_kYc=seXW(N#Fb6*2MIN*?ZKwhaR^3YaSRk+}z*n+m+`4P9`6dZl-pqvNJK zdr@sF<-BAS>+VBy58c`=uJf3SYyt`9zj;i*t3@n?LmYQM!Ae6yv2$xlZVq&tO+1v? zi+@{(X|_yRCMs`lc9}o{k+}`AMn;MMeXO`<85`>{wr!+!aFcg}97cccETj4tIi1Eb zuKpPb%i@@htW%0giRmm<*%2ztUSHIo8ebLae0!=MLIM-7G!e0QY8b3;=3>Af@O0+l zw>x7>A>FNs;ZBK41A7$dr_AjredR-s?czIJ9~*-rh`iYv$=vZ1>e65jglH&}CeSbS z<>9tNscHqTbOdraw;c=htT^2LbJQuty;O2X2lc1cM0?I2MesG>^}K@r`SM8G$eD2> zrv!@ht4raxeH3v$LR!&OP5ME!ow3@$yAEJyA06-<$DeHc1blhf_2Hh+Im>RA>2;jt zE9VTZV@-hIkUq|G+Tgx0>KJ+~%wiEa9@RdYfV&>txre!Ye>NwPFn*ub>|A|e zO(JF=vt>fj#7ioZU3Ky?V6u$BAX$_+(HGY`d93e-vrNkS5>2*w`)&ck=9CQAF!b5J zl;{zUmB3v=sSfMW%uOyCeD7ai*eXGVhD%6m;i@0UDd7459gpN|?{s^+>0bff%jsdH zFwePOk@U|2`WMr{7IdWx&`FwLY2mH&Do)B;DP)Pv&D)^IWX!Uef3s{XkhH1j zd*VOO%@O->#-m@^(2~?rko6+FFy9Q+!5;DejB)shctwC`p~X1Qs7a0f-h3j_fTW7# z(qp(;`MzbtpN@z4MeyR$M_rk<$#|c`^Y)2Jcbm^w0eEIz^r?Tcr zYdh(MbWQqbaRsbYGL?7~p#B_-xSow|c)a{r{J;2{uWIR7G%Y?$nT~U70@r}pZN{W1 ztpw1aU*;Rd{*+B;EVHusqc!xON!WctJb&314t=!OW^L=aeb3L@h;9=WMr$ZwGjTP1 z#dUU29`|A0F2R6N%#@A#E(Mvo};x4t-pzb=C6ZP^xoA`C9T?Q=WMUJ{iezkcWPT&MXNF>_U zR?$qjZX|{Rb7*-<;EQem1hBRW95*5c96>0{uo2Mi3WzQvp%pTkgSx{t_|f>~5*)pF zS%ySy;9suHF&khOk_;aW&4ThIf?olF-DcOH`ClvdeSYcYzlrORr+e8D;>~4ZR4Yr^ zxTjE?(=FGwt8jB&SoHAOyM^EZDZVE1mkGPqjj;TB{t+?Knp_GbC88=8);giq2Uv55 zVyF52W@NTv_EAsACfnxWgDF9(igE4>cq<88W!tJp;ddS%L%OZ)(Nc)R;8PRT6LpV( z0MySZ1=%|jTF+>k2@+q%ybLdMjyd~6<pE#jsk##&hOZ4PD?@x z^+I}5;iz3Z85AdsMz1}GOo{rz4J_H4Bl$P#!yr4#@43gu(cNq@b+#P=29ugzewEi8 zQUP6;!1=_qfAE{++6WP%n4^X3pLxPQF=5%RwpQdWy`F}AC{dn<)Q!){UB${T?t1J#N>~!oQB)|^wL|HMDOHh`M@&}w<)H>AF z@cGGJ@6#FGzlikPDfw_K_n6ZQCk^+l3fa~cXRiTMXQ^HV0X*;FfvrfLxBjZkWOfA6 zmPS$4kZrtMvI79jM>ux&iFZQmDH-=xNju0=t zLg)3Uz)Zap9777?eR5{OHfn|cqL!h?<1U7ett=c4i zqR%1Y)SMxDcB(|D8ch?M#TyFwPQ>?cmXU2)Wyew||NfkRBeO4vQ3o!d8EjOKxcj?I zL$4}<<{CRBO){nzjUQ)nb;ZEBJV+ak^*oBkKjkk|GO+Tk5J0!y5BzAEkwSkx zy*K5Vg{w*U#CYM^KO;(lNcruOW{Zcjw#JvQv(PTazn_Iz&g?}}E>Jf0JFI^`Ipn|G zJbtA9p?NR8hLbA#cLft+(dvg`guz5^JAjx0*YKVB<5%T4RC!sDj3$FEB4IA z&Mpkz&uM0|jdENT8-|D+hsx!1>_&Ww5OEPzd4%I1mPxU1mNi2q=|z^AT}h)OvKBqI z>ZrVURH*ez1{Ov9LlERPgz)lC3$#i}+``MtL!+|H2G-iF=sUt4ttVz(o>fFGD+4{S z8G1%Hazd)r*If4$+OG_dqS7JNqwwSniMzFB)(E9p<^YxjE$H2PUrU=SRk?(z8tg?> zwGy9=KaEM&cM!Yvud-_AN8KF1ZbvxT<3MLp%p6~;cGq}PEk>|B(v!$w+2dMv9aY4r zWf%FN+#y8an-sL*@bl*y;n%g!6xq=XClRFv?!qg$3rghXW{&gec2-PM{Rx6y;KLwO zur#ZeK;0?#5d=>*A11=;$AjWW&ki#MRGOKJvcWiSW=g@h$x_d0ayM9Ig4FF?_umu?noNnX*5D921`UD{=V`I84(;?rnxM zzIxyC_F1(5-O|zUwdLo+boU8(jGq>+Lw%H=P;+OHp(D`c6}fr%M~u+#LsU~o55j3_b3pyYRbw*~JPeZ&!UYo472KaM;Al?* zuHTz0n!$SC3@5e7cDDAzOPrR+j}dd`a|PRwVW!<$fLv`--xKfR0CCpGGMqy+_Vb1< z&`_5D8=b%3%S`&9E`_<6w^(vBk^p6<-#nSerrMxn$QCm~4GP|&Jyrm23r3>RcQ`I0 z9ySBCirp)Bpk(^B`!5}{50Ali!(6(^HMEVL$gTIK2`;XnZQanez80p!Z;rN!% z(#-^Ep6Z{+;6lXK2!3h(FXIm!KSSpVpYEczEm{R;b}jcGjQUa>x8A^;E4nSnh~!nC znh|{4xv^p#`6Y!73tia}3>1=$O#}YGhM2z%o}P7rIoOQtA*KXyw73s#`f^+|*Ici0 zI!D&|J70laM#B(pRQo|^EcmUxW(k%MRU^vSQ?1J^Zj7=omKkE*X(PjRj>Cl|wglN(ft#yXi-o2=S1zLyK@NX?q=w21{j8!InR85&-%ZdSF`4Cs`9TT9=i|!Z^dr0L6-S4=b#FO!YPf0oC*&@vEbQK!h-~V?|nG}EYdgk*a zjE)Iz8Xcm$1%?%Ulf6h>g^YyXr+lE3E7u3usGWv^uNzqz!43o=e)DOY?70@uS?9bTD9FZ)9MBc3D<+^0nCW;FBYw=4(47ptS)FO*_d#b1LQ;f2+iDixP)C@KS42Q~@h zjtXdip<%-C#7r1#DsPh`2QMaOtj*hv9G(=8xTJvCSqJ=;-YdsjOMF@E7P}nDj;>8iJ9|_w zW|;<5zYA0){8y%R3DAWVdj2HZJr`D!V)R+Ox&yLHQ++3?2}@9<;kD_bY>;K#47^sl z6=$W5tb4xsV*t#-Xn%3tD48}^Gc-xnXjeM|COvh*1D2`MDR|Z-=xwzfsM_Fbz9Nbl zjYE+&fuLRMQVr<> ztzUHgf?ktNg6-mCU@4mT#rCRgJ?O=x`ZNTS=y7(KVQ^R?W}7Jf1|NiNM-kf1g2@yQ z9tvPAa{!w(N%4x5z0wBzPe#u3m#Pdrxs~9Yw#(K>PGEQJD*=ln#@g%I_V>^OLd?1) zBII}na`PGz4%~oPBQ;6{Mbn021=;?43oKs;ZQDVkvu+wK3)&{HG1rzDeVlf7RBugWMe4T>MFkhQFkQ#9L_E_jD>O56a_+Sf6TV?j`*$m1I4Y zTYU%MZwb4KO7#8yrN+fU1pcr-u+&?e>zLYI%f;n|^}k4bNV{hrR>ixR&M4RN?UcJ_ z@_9a>C8-u`a_`nP;eJ`nC8aQj!EiFU;@p8ArFd`7>onf`lc&;GwkTR)(NZtF)GF9sEW^^k}+?26IWW ztbW$ zhx)SzkBM-_lMPOdPt_vJm(GF6cV@xE`7F7U*7$sMjPvd)&dHznI9rF#!G~rd#hPCA#vHSBte|DSDfBO$fhqXl@X=k>bxo39$>U?U6 zZ<6&l;xZJs0O*U(UfKQJMF=_C#{9uu`ZxuX(?)ST8zO1za zQ=ADj^g`$j6Lr3qN44B$ww?48^0uP@e`bX>mn6uL)*;S*k^2Ow`V9z27cypl>j6mH zww5JlzIl)AjjHHDWNQT5+a@_k^fPBP9$K`1%k54eBB^IDyu+(jAXQCR7lN_Wiw`fVbjIQIgp9j@%e^{folWpnHd*evuST4B(Qk>H2+z<3Mnx71FI2JQYj6Rgxzr(_+C-s0Q$m$m;`x$@ttI8vFrBI z|3PLEp`b<;We#me2c6F=gX0iETiXYD2%xftesP0i8uTYIvh=tpqei?nj!&vk|5Mv> z`Jny_E%zTfQ4sRp<7DX+5-_ZWi3#wRU&kVV{i(OS<`abikE1|sAh=(m7JQ1jC_ElE ztg6$HGQlc|N04rbUC^z53GGSIy9%4OH5dii=K)e2o>8U|5?l+?&N|%3zu$rRKd{aL z;;buytMaTLq0egTXt>8{5SE@bV-}%+GsgwB5ndWUT#0}i-+#;s$0tlF(XEwAA5^d*kxN*xl$ zL5KA>7BSzm=l>Kw%hb%_<0gHF>UWnq$?>&3phfimp0rAycz$4f=r4?`?5;w3VB!)y zncLNydm-`2d~m>BhcP5J_{Cq@`#qA@dkt@F1kh*$)4CderEMYpARnR+#ZIn-;zTG| zkdG)HKOsO_;(+pu5mKcptnpQin#ko%6D8g<>fr3fPY<7ZP{*7dO1yL><~XPJUbXmh zU%$g034f_DhBvMssck>za`0+zfDMxeS{qFd1-4nM-~HtnsP__W-SMb0lE71-b`&?B zYk7YdSv8VXNfcAN!;j33106e?%I4}c(}`0ktN8)(pIY^s(4o7ye@DOf?SIyq3zYv1_(69YASSqq zl}Tbvkqj7E7+5ZzX}eaEl-ckYofUs{dYwJ8eo9LfbXV)hJ~giOVY9E43-W#$JpD+V zg$wZg_&_(_$|ezffWRM?b)ti0n$QS?%N36}2cw;jpuJ72nsC53zfKv6EWJ5QYh*r! zxGxoyFni7ag^S?QFK^n8H}c0haRt2G+J5;hFf|5giT?6r>BEm_Ccm?HYelbhZXGK* zo@oXVHF*8L73^4p=56tUX_8iYyX;#44RC91oNV=LUp_2n{U-|9AxNcvat98SPY%>^ z7vdAAqRUaE#-4)4e+}cnpUPkTRQSk(7q;bHsoaoiu@dc z-92D?J4K^J*%DyMet&+pkv7x_~`3 z930?M1iX1#p#{BV1}=bhDgeEj)#V+uHTe3ZmEe)gajChgaenbJ-e&94wojeUyO}qI z3=J<-N!m9bQ%iQ1QfSL>Mgl<1pE!hkL##NU*FvY9t++kN` z*3KWsO3;zyynx^sYjt`C2EVvB({OCVY_To$S!3@8ozm;c6Ve7U4B&PtDV^mQi1#4^ z)l;V69($u!eV+L`Fu*8=xQC)y4Iv-5J3!S1q?!r$LWiC`9}5e$&FGdbJ|m);9c(Q@)VDhF=n}sgZI;H-(qjDw|B~nn%00M z@WFlgUXBzBF;&TuP@9*1i!Kbvf4TQtlP~*fWuN$yd;*NWW-Lj;*p7TggSEZwn|iwG z$A>kK45T0WcR+t)%CWS^juC&OP)zG2cu^VWS6swG=!5`Yfm{nfo<-^fWbvm@4N8P0 z=pOR*E%7fm(?dOmfHF`H4hi@~8+H1LR7L&kaGPT-6S-eX;}07htE+?u?u4V9OQ&Fy zu#f*DP^F`df5R_#yC+Im*@SbNDs~7G45^Iqzpk&G^21jM>jQYRvg4KOyrF#v+abRfa zb4}|E%b0I5PT9H7s_>w7X3Hs8nj>M^V1JAuQm9-A?*wu3O3-WSt7!bb+THg}K_V|e z=TAPfkxio4GoHMJ++M{5295VF&V4^$^W1y0x4giYD2Zh+g~nhcRjj4%wGV6K(AQ*a z1I9AO(QOUxJ00w{My-8fkJOy}N7)rWSd=zCt2&d-sb<8~|D{qvS~+NF@6SXIr{=;U`!TWsCo}FKl?(Txf@cY_An3Ho3I6wCGb3$b4;Z?D7 zT~Yi(;e9zYVS_X{hp7nRUuHyca<_qI8@eD1?v?mU8zeD&GCz@asjEQ2pvTm~44^r(a@ zCrX`D_bH5AKdRUe?MQJS4fXl)qw9LbP|TSB2CPItlhUF<$WOQ4y2wtOHWpUHTi`Ad zh~&K@^gsguT5A_Fz@dmmVN9g%+9^0)L7ydHY-VRg;pW7P!l z_v~A~cwM5NR`TV{uPzts5}r=@t*fVWh>`Mu8y$$*8deCgJGwqWOIVgVnD<;`rm|4h z2>7JPT7A&1_u2mzA;rMWC_WBm9D;#%q@A?_z*V!hn+yx?ai{O8_JfdGm!E27b{&rr zw&c%Dyb?wt59L1A(|NBtS~EL*dz!x$5Lp7I8FfAS_~HrZs@YD1K^Z ze7%wOgqjmQ{}~drao{M*dR4GH^-J2h;!YLZuEuSmX*501ZMO{b3R3PFf-db*q2Fo; z7$Pdr@OfkHPdDLo+8KU>S(A}pJ&m)mOFkDzORaYZ>E3%AoS=wUr;U8Hh9zAoOV!8 z-SjXDeSPodq%V6Lpnoyxc{;2Fa!r`udj)F5u74`a>@M%nlve$Xg=Z5t+!z9I(E9GJ zs}#bHy(NZv%n6nZ{KjPM_1L%DHJEMO4{ZrIR7vlxQBDM<|AB(=h4=FL;3CCe?xYEN z&nLKLK|@?7n8}OaL=CklRdDobE*F%33SN|tjw_-qLOXif$TC&H>W%*Lb}J>Xi~9bI z++kn);Ln1Rx4>mYXB$ohpg-%>fOQKvTJIW@4_@%WVnV(GkrpI=8uzj?s~VZZeYq== z^yVvNucJ&!uGrNd?eU*y)xz&sY~BnK6AdTEKG3qbCrY$n5CEQ;7Z zQWPs<;W~g0KteFWIPJ4-JH`L|3`NU9{gEpy4Poga*!&F_CC*A~RMa8zf(AvdI(gyO z@;U!!&J~dobj7c((m-@b9^%kNAV0<%ykXkJ9DDu!@wC;XJpMuxnVRQPerVchwwtNZ zjby5*;y9x>B3m}-#iy6OQ?FR{VLOE*G%xQSqz9HR=@K=Bm!Cs)Uq-PjOoCivFqhb- zU3A)|+X0aD?q7{$FHPQ}M@tu&mu1ojPk%9bEH{yub1=1M$(C7RqW1BUUjUq}6rLfZ z_I;#8in)vqkjE9N)9qPztUT2E>)%i)CiCngygcZ*Zp|8H`QyHJwl6b3e(Hmi+)(<1;)Aqa|JJ^L=l}LIh5&FhTRe&xO946BjusVt3zuOxJJ1MAWKuB&6tAsq z0NTuHSQIbWtX-|Hgr^u(sfswCr*`r2ykAI_#rpBHfHCC@KmMygvlX?>J(YC1l^iv? z>!+oiw)B~iq#mMj@7dEO3*UXVsh1`KS3-v!x~{iZ|7GhhJr;1nihI&+*!Jk=G5!pi zZ%mHXM8wFGdauu!getmtZMX6t^_VciX+c;Y+|$=awY~qU`&fXCuT@fox~nEsrx&Mx zljH(=`N-^UTN^Y$?MI8KJ?b;aIMN<4l*8F+T*RDy@^FfotP@jiR1blR?l_$L>-R$IWpCvx98vN$&)EktG!SX=U zzgoTUdzA5>GoS;(($(}k4Cu3^pzG6W5;pr=5R z0@YAt*3smbZ+d)hf+Ddus2{2Gpj{o%Z4NMZKpJ3xlPzm& zbx;um+^m|u0}9!UM`fY+6!d+sd!MneWQ~o#wekKrGkFio7IOl%b!8?%rr&5{UZ8Q* z#u4*K>z=0?b_)Gg_fp;#V(q;g0z)3bx25oSJ^py06)4yPJFr(8dcCn?+O7Q_8PaBcJGJBS)-9`g6aP_9j6ee{v+qC5IOTI64S- zxBIuK44Cqs1B-cI%~a^kMQK!NgHD|9t7ErQkG^>Bk^hCNppoB&c#vRqJtgyrmv6_n z1QUylkiL8-kBN=jmq%bhjB&=>oqlKUTL8ks9)+KE!L$C$qXQXk@w0}h7&=JJy2XH} z9}WKdH#`PyZd|GcJ=wGu`L%#c?BkI_++EgSQFGQBT8qR`(X*yTU<|P#n@<~(1v{q+ zi_x=OPbmI4YLY%sJBRIw;2rSeeEhl?n4*h?J$?(?GqPf?RC!>MMGQ(se2R(Q1#VPU#;!7%eU1Lmosr@K;&gcS;XXZquI!1 z-O*gd;ubeM{u0QuDf9~*E!Er0ZZSN|sy-a|ZLSrN%Wkqe53B|MShiBFD#U;ogf{AN z&@M5^5`=)Fa8aAuXqI3k4K)q|DT4WQ-3p+~0V)VQ6lRx&u4TUw!`b?}Y6K62#AuZ|}kc#O7!f{s{l|!!+@h239HV?@aco)RWetBKigStzM@~9S zhT!n~C6pas{+$LCH7aHM&~?2Qu%8)o!f1V(EF%L$=roC^A zY-sCS6BEA{xZqGO%96dr-2JvFljT_Vs{&L#-1VwWM&daClouvaxKxx6pVd%k$($y1 zkygUDHIBf=(v==OpU)5u8Gx6eV6OHP%uW`S>jV3{?-ZNS!O295ie%VfHkt5c6$6&g zwN{1opFvf{wJH^aLIosQCYAqv%wbdjnH;>Yu2Jy^X~NFxzLW^%?GNWWFN?I<~RB_?hN#Amzw}~QLOkZ5lbe; zva@)mtogf7`srD&dNA5YONAJ<3NWp*56H88ZEmiph6Dfv=}i61i_o^{Yl@+G`zpz2 z+uO`X1!$`FYR6{WI>XaY!XYI+zlYp#ts(Y9aL9;_O^}7~COnYulV9m z7`KbBk*^XM4j~RwY{a#ei{aW+4GSPa)h`YGvZT7X1)rI+@7Tm7Bje2~I9pQ;AOK@o zug*<9d3g_O3Wlq&ejp+e`j@oiO(($=L*q1B%UXHs?-otp;RSdx9%<-vr_^-HV_`BvF_d->No0{`a!O8@PSUJG^;WKkexNK47<$WkwVhk zjeU0ZiKyZI@*lXx9j#|rqJ2F4YrRRcyKrxiS0Vc4o{#IyoQ`ZqauK_}=@p@6-8wn} zx&ig7XzCxmcnmQa8_7?y+J};to90M+F5G3sh7DR>A7-;;2&b`XsC(TT7qy+M6dCdJ zcuW`x!wt{D6BXPB7L;4RDEq9uFlb;Pc;j3&vF`$t1O)`d?KYD^f$~oyg7W(r_m}2{ z5L0b`;G=rE@w-N*m3E&jkBvPxVOZgO))AJd72$fjkNeRV>><*J2o%iR+WmdG390}@ z8X4S35>z>-DYb4slnL@4Wyx2Eb0ZpU0^KE_Kg&liw~pP73ZeO`o&5csaqC z8ERjP&EMtd^00gE>8oLj!Cu5h%?$OuV>a?lz5WByxdHEd(tT+61ZlioJI7IKC^xSg zb~`e4$#qh~HX0h7flG;MElhj&bwP;b+AGJnp@@M^82dWCD{j!Gef3+bkaLRnqCT*WwcVFrg%yb!1j8OiY{m-9 z;=2c_Q!+hVeWIM}d31Qi{R6M@g9n}qabOeu>MH@w)!>XhPdhJ|m(x&{>46#nV7*%I z`ESHepGKSmcO~+f86zVhmDGb8d*n%inxjdow|&_)zDJqVk_PM;&r#=t3!%L?3te!F zpUhYBNdU3i_%WbY*ZT4BdZc2{Ul7Q+Wd3)Y)yA?I67j$`qry2=lu5+;da`+CmGN=;X-hnb14 zIme&qq;KR7f7lx`@TBjsds6ebm?C)6@)DQ=B)A5mbo^XR zlmE#GsU=MEBH;vP<-u`~B@I=@R4uw6>(8i7m0mdZ&7iQ9Gdp_!j@ zpRS6PPAX&ZC8%CA9v=3ksqxuLg1GNbLRWra0+az>P1D<$u7!qpd?()OQ@hG;^+8wD zRiJWGNP6rrhB-IhGqoE~73!5!z`t8}v*{I;aqnkqEnK~^SL)Hnoe@|42z{K(J`?Y? zvcXRK!+UAX^LZ}{oWF5`8af!~#Y$%z&Dg|mr(B4tvg0urfnKs6PBTH;CZci-Ygf1Z zlb5bDHX&;WZ?G~N8a=!up!tDCuJh}VZ0L#)h1TB#}2sCsZ(-bM;=9f25?Tp+fYY1!LGM~8-t4f-JE5oa3fEm90>NW?VQ!- z>Xr}j#Kokc%X_%(4Mxv9PMS;QsrcOxg7=xE-$`GlXU$J?MHTxUtMu-j8Izi3c!-Mx z44{Tue=F>xM1OqL?2WqIzX@g3e)#z9M=yM*yBnk4M8KTIn^2t?%4k)m&X=M3MFpvq z5?#W%4o*K};zK@?V-x8&cSztn5nsefdp%byNs{8&Lvb92>(e}WtS$#1lKhc&7DIj0 ziNSa-SEN-o8gYxs)@$zjeNmPJiM#kqmh#U9j|Xrhy~5?3&W(Oo<#a?2^2-|9hhy@& zRTm$5&j32G$Df~#Q}m0VL^leKp6>+^eSalqM}S!64DdKr#E<5qVmjC|xUW1vPX zgG|Ec6G;)ijIeVVbiycsI28U@(@IkXxWkZX)eah>exu6q-&=JFH$g3wY|sLrdu#fL z)eCHYCvi_REUz|^#2-Tf@AYYrMC*=cg+$}~{>331CObmgYOoGA3DN}X&&Rr+17nN8 z&`7f#JY`n0JxG$Bu14zp1`vM{e!UZ4k^Q`6#`F5ASE*VgR8;Tl9KNkp<$k;`f{5(YVl0%q}py-H@2 z!jF%Q#a>Hd;Fj?BNE38mcJ24h6Rz-1hMR_D<&!;AnM(^&^LaOOnGJuUKHo_a?IZsN ztI!|s{9{iXigWTm2_MgJwBA`09jlmphn07`F#J`I422N z2ZFt5sk?zn`e~2v%uclAl_CxN?>yc{Yp*f;S^GgTT3c9GtniL}!A0cOpPXn&gCq(Y zzhZmoU+6+6?ARVdruI$F@90k>iVCIuzBe#;1AERsQ1Gs9x>GHk2cSP3dw*&g=c6oi zyI2vpSctWrOu5V5ELnY7ESv3KbJC2?@w+)Q$^hxT|J{8Llp&_0@L(YZZOP2bzcEzv z3R_Y(u$vh1#X0ez>%EeeeA;hkOjhj_Ny)_di)=w%yVFHcL0`|`Qs9ZwDdyI0|02tu zv9s$VWEQ_x$?CN(1U`;4)LMFz<&|2^daC^!0UzOqfkGGLZ8&BaSWRXmrq=mfFtUn| zOzSPaXaI))+mu4!qlpf=8R+PWLQwsYKOKw4_lnpH@SFgwS67N*pvf8F*Kf>*OUqB; zw#el?#Q+nSgl!@wyrwkgNz4?UeG>QIv8Z`DTAnbFh0(;x3nT76bmTwKP5IC0 z1^OegWGahuwXDJgjY?G4W`p130;dV)Y}v}mKXKpW8v;`>T*V=N#&ng|d&PF%_ioE! zWv2Tx1Ttfmy>C#^Znjm9$!V>Fd4LXdS7-~U0P#U$v}L`wASgyiEGu7(Qx+PmWK^F| zw82UOFgJE>&tZ15c!j-t1nQN;>6D)N2$IUisRG8X{NC&R@`Kb5XCLZ5Hs1$UIhysX z)kfM=3Ck}(T(hnp6Grt2^n@#ro|RIK|BX;ydVRxQpyPsT3p~kt<3&H+VEJRR>PT+v z+3N`W)38t28BT_We7KI?qJogx@HS_>FjIg9piJr5=q`+>;4dt2Bw8~U37d}&`fyry zM_A_zH@8ZQG%p!pviUp7(?r0t61!wJXRv%@6nh;R2orEq4K9!4c`&ly|E7)L-ppaf z)SQIX*|vk_fY2+L@)?CJ9V+mx$7g8ELG4WXylKecht&tc`E!K%vD7R9ujy4lEUWAZ zuz+jDw=stgGN^G}?!lOUGV$k@mqix7ZqVZ!WezqE*gbW;zAL$e6Xxq;FBKA@CnglJ z*soqblS&f(u2g;_@9q%T?lq zvtqe+{W(|x$)m3Pq-%EF3Bo&Xfiv#|OuvQHZM3kTO)XmsJ#yZXqh=w(`S8+5uy{Ia zD{m@bzHXTrUxlO6c$8a3p75rx0IeQx{059db^)I1=SA@61SC$@3-<`~W{JJr# zR~96pF6L!YF6D%8wKf=h&sq0?zqsq^Cdou9QWFnp9+Gni6|N_^8EiZRMKo+Q>|^a- zlE(C!>|LpVrE}VPx0;tjNoGQ6NJR-ADg)SPy!~$Qf%q4sRNMOT9$v~~cu-+@{v-fn zMiN6D={*vpxkWB=)54s$q80!Q6>7B2A`$Zx>vYIkYJ_?B)VWFt9|?YUoxW`M`F6ZgMSt9*SAW${tbx++7jAaQ%x)>Be z!UFitYkk>0jxFw?7G~_P_=9|xQLbxFyW3zj(SR8U!`lE=CI?agd)3@GJn9u(+T+LX ziFPuv9k(E~0JHPXdvk~e{0^Ue_WM>s_IFo+#*TI!><7ZzT$I)aK_9Z>$Eof$@TS0$ z@QY^@^-CO`$${7ESSx&7HL-|#XqcyQ0FT}yHy?th5ZR3*9>mJO=l0;6SMC>Tqm!u} zwOj=wmRyIwMIrKl!Pj4FU;fWbAVD;6rb(G?R<3X)XR6F>_MYV3hNi5iM^P6v;5 z@#xnmUP_83;cWTls)e7m%l%}4B&zjFvxok(}a}!!y zbXSoEkEu$G-Xl!nI>g(cKPeU>_+}^XAK0UG*?J4dY4q z1kj7pEwF8BF3shw8_vzfI6zP&0NESMYF4qPU_xcMvqeNUtb;5!kTEcUsuP@#xkraA zPyX)YS6(qgplYbZ11Kyu3Am_QW4tXYtq`mldB*RG=$iZebgT`x*w|?K)MG*N%hT`7)8I=JEMk+VuE;ycll|!Lf z0Y;ix23PDJrRFz^@2{Xl=bmQh7hmfw=dqnFxnOM&f(->?AJ*;4B?SA@B*mLy5Ll81 z{fN3E$W{*pl+TmVAZP4606Yf;(Ab{F1m~P9@Kfq7y$mWpso>>4M-i$J4U`=LaiGhN zFN&Dtyx0-v&Z#WxM#i(>q4+-I+QvA22~E zaCOp3wR@@IovG?3>DjVEYlgGoXR~L0SQR{8gZqjGJTnr8SAw<_tY7~qq*@$+T$VSicCO16jHn?r-A-8kG0`CtK(Vch0JDDD%L79T4; zL||aa9QT&#G(|4Jg3sfdAE2&3SpM(xsHJa)i(4Z-27)NgpR33X%^6sRf+`+<9W8KCpZVy9XL4Tbd^rF$Ts;hQ!QAgJlR(0Hvz9Qo zn{S&CZvkUE`K1WO?zifVP4}>WXA?|HNo=pR8HtjDh=->mh1+*f!_UKP>;Y+@=9nhtZ{I)NCpt6 zH4-5vJ=k#K!b$Zlk2{{!93hZ5&F5ZE-rDXBCQq2x@fNWl6$MCJdqoc#+XYl|{X!a(r4NmOG};neO!_CsM5NK|2$7S_yB zf~J{DWae@A!T{Q0qFltxI*qbC(KVsSwFBW3xt7JuGb6)bdD>tSpAvFKe_-)PWQGvI z!YS1HLY~+`)YqH_Gi>LI!-R%?KTSi~oey^Gt7V!}sU-9Coh*-BE&G-oB@kzgTIXW* z5FogsOkibY<+vYkOeuz1+-=_vroRD+h=w>=mUqoJ0ld7(7U)Ux&zmmo$i2PS_DIY{ z`JLcUG{ZqXUH)x#JtqjfzUElH-)lA>H1Uq)JdAzz{*NfAaKr_409@L5Ch}D83~Uzq zT$gkE<_R6{p>pKg*mA~+YSd_ZFm}GQ+rU^r!slhG8&$t$j@cT3h8yuWxzGCK*k@KQ z$Z$qA{dT>3@TMrTS1S8Wmo=4O7!lm5QJ)0aRXc`|DV@`iwLKu#7ZKL#0B7F?SN^1e zS%E@rotQIoKa*R0iG%2~I5hNc{Sr30fn{TuuC#N4`L&P-?>z_YXni7~PmrM4V9o8F zlz(x8JlJr~-X-w~^8=X=noX=_cw}#rnIpU&tzMPhBZ$oY#FO_X8i#gM>TWvoIuo|6 zH@Bwte1qSI3m9$Xp2RJbmxDtBFY_dPj|47LYCG6t4Y%28C0SOm z({0#ivXY5oy2iGA$NxoF-EyOyOl7tk5ySt~xY8(6fM*Y-yFMXKo|v&tw${D9RenhZ zjY6(oXM@um&hs=^EsJ+dp9AfRqfbIpf}}2u+0z&`$dsL1>V6-kX4!|jyM~lW#GUNpOQa*xN)+#~ANoHTbJlK+b|#l*Zs*_g>+z0rSaMb;%f0D3s1zK>)OXM z($FtpZB1v@Lnr^1ubMl?m)KI|R$WUhY82pEX`TQTUL?jsUtpwJ*2qr;ak9Pk6M80U z_ZaSM>HPsYk(QfK+i~uEkVR$faWuhN_<<*lM;ZJDkFwVFt38klggxK3l6$p33otoA zZ+~t<+CXo95I30Q<>BYVk(jjEQdy7z;tbyuOeVbQ0im%NtJq$k=7CF=pGZSapqg|C zC!&gFojfNznFzQ(LCIhy!F7=pWjJ{#^wv8Fm3NJ$zH%%5X!D|XqEcZ}k=#j01j?1H zm?@!!=Lb?+ZV<wr=lShymi6W0rgmJ|SF*01M=z3r2Yb#3B@QgK;IyuhINqE($}L-$+CY$PP5ioDPoyf$FhK|xei zSKO0I&M4q0a7zzN5W!M+5p5FklP0vd@r%LbE0HatCZ6R)Gi~!7`#3w1*f^b0a`#Ax zrE;3gr(}8c(3cNgpn6bPha=od?D0yp7guNf=XMdsvUlRID5}R zfv2^xU;Mg^GfVmAL+~fqU{t1G ze0{p7;QkhM^_C@mFNllC)!U^DMU;cn>+l(TYW8mr_%uZl%fayraR_-iz5ZSXT&&b% zU%$tThSe>5PljHL)SvPar1|JLewf#34wna4BzvqP<)|ordhh2YERR666 zn2o^@rCm5lvPEYv;`5xP&$Yz09|Nc)a2f$_o>eS}DE?hB8if4fqWsv(D^1zKd}J2J z%RgpgGLI8-X??xFxYDg=iWQEa;qUwJD3BWs3exJ^z8nO-l^uhe7hOg$*a4;&19uegGlsJO6Qqo)%|%7Y zcjrad>GcUa_e2nj-5CTWex?5#ne{y&1dew@u1A>;rTa>xtioZxOS->-y}93dxiyX; zkOz}ghXl8-qe+-qfY|+Edpuc{e+_6C%mND(k#6$31dR@fKgY*}o!tqeAit@B?T$T` zs$P|>d7dWV_YdspKL+-Ie|F(IB);L8#`AhVzZu6Tuuo=l&tyIz+eJsZio6b1sx0F$6 zVqN!mzvbk`Al_J#&&5~XB+^;aba>M-*)SqO1D%@QxTHcL{fIqgdSWJDx#Xb`uP=97 z7MFw}qzE_`q1U?CCTpN__h*o2BQvhsv2gV_EnQ*v7DXCl4>ZD8K{ZM8SFXvY9f_aG z1n=ibwF)8ZS4ZcPHx8rT!3JgiLa7Sh$#1Lv0f z0Y$b1Wf>#PZL$WX+^3e-Zd`MG@Ve*4aF+y%O1Rg@dkqlkVY?a3QfMptvdINzkv*fw zob-WIdVg9{N~e^U+Gefd$6-ilMI1kg3&JX>+0{%iIF*zwG*JkB8#V2B_ycp)oQ97M z*QP_dVt-5qjcCiDAD2^SUDB(HP&ANV8Vk*BMymq+477zPC>e%yJ=($;XZo)LMS<7R z_yc&!*Rz1hY1%{W=ggjq-tzK(^Pcpz;SP)W`+q0%hc(hREUvWC4T#5pAT`GVMOOek6nE5EH@ zfnQvi7lnRhpx@GHg|qiC#<6(L1ku2)>rasPsKa-DgR>n78dx>o49~z_^m8BUrRW3f zfM@9zpS=3gE20_+>;wVdZ37q~AHQ=2_>|ymBd`PTCNwCGbpLxBVcF6mwTBK;`aqITB3-1vs#5|%^R`;GRZjN6P$9& z9tE&I%H`Y#iF!aIuKi0DU~*FePL=HLg{(hIG<95k&U}t&x+sVSjMabKVi`AE@{`M- zP}7Sp_>vA*X{YRu*(m=g*qsuIZJHuu#$P_mwh_yH#6~LkJMXW*%WgRz`17u)TG;B7 z@3>gXp<^OQnaNIWyEtQ=I>@wJEx#~oNk%Z?6g+1F@!8rBqu}Wp zB3&}(z^=Om)$sYVk-Q!o4bb=s`Wx!)`v-`4Uqdg%%g0>x{TW7^fLY&ynht44Ju|J* z31?}$Pcj_^(^DnTKP?wwm}WYo!)jp3xfS*Kl);J*$}E}{rPwoR(OJo1_4UI)F4V$e zhIQM!pz;I2uZEwPnP;u<_)Z}@PO2*dsxCzUXMvqHmdVu@?N-l?3+I)!ONM2Dd`ayw z$#I3!fUq4K6RzI;TJ2LV3@yBHX2IW>d`0W5f1trI2iO8HEy-PH6CwDQSlVv+3OSs| zGKHI59sw}G4Hqlrp>u!h`hg_YfeYgzNn6Spq8kx7pb84S*(&%QmQV{ZA(H3-pp%R{ z2y4_sa?NC$j;h!LpzG-ePga5!KhD8zq%27v+@pC!!E+C?8-=FF9%}XVA_m$9=uB*A zR!|g^uMV=u6#c)3&cmOoKMv#eNL-R1Wn81IvX$*-l$1zB=B;FuO~^b(va*#CT|#7C zBV=APLS{DE*{*S|>)!kQ-SZcm*Xx|~d7ba`JfHXDQ(qEvaHBEe#K1ytH6s@pb{nZXibQ}%AgH*4=s>9DN7gwL0>@Y7WbXJ2y92#Afk4kiX|D4gU(Le88}a5Ycn zI)?D?q4HA#p4yeDSzcPzxMfQhPi5r%BXjUbEex`6)=FPX5^uO0OzX&a?n6oOv6l8K zmF%SUu9e!2jZnFN2VdU(G?gT2J`x2G*`hV}nfO1wU>F^Ub}n%L!fgBhcRX6*dDx>>sL)7Y&UnbHTXn#~162HC__G8DjyYSHgI+f#@ zgO<3pm7i6-XAn^hcHEQWHbCBspaW79_ME*M*oOq_epPou43;}tb{sL+IQ%%>M$|8Ja;=!c-r)@eDc~c?HQ^=iU|Ly>K z^5mdO^X8Z?5+d+r4!(&d$o^OAUg!s7xRFv)Jz_`rX7ubUx0vK7n--lz&3{gl6ZbOI z>~Bf32r$tc%5pq+wT-$2Vub$7LRekpAPcB6^Po4q@ZH6~Sb?*|D-=sb+HULN&A#g> z>bYe#A@+nXB!jnAY#R-J#as3$U$u(62We54PvN`scOjKHm6r67I5B7F^VGz)kLOt1 z^>yD8gz2bQ;!6`TKEvERU-5L9tM1SDxsKbLko;Bp>UX|&gp(`~|2JXQ*;@k_N zn^8-gCkRD~a8j}DdA?Ji?q>Higp7i}fraO52atqWn%4YA;tu&jukHX_rF*m4(;DqM z?ll*>m!X$iM&a+=5G|Jz81Qfg67uHM=FnUYlH&xE1QM~fMSu9LUM!iSu8~c&IiEwQ z|Gl6-ejmflkojbKiKF#toPFF+gN@&l3}giT3y}hfH1t=@T=GC+~G$zgAO~OyEst&+p|nL6rV+Wu59l*=T_qS%-HAI zKB5DvoG>?XW@54~Wu~JCm}cj{m5ktIv%T1*K~Y5D5f z8mmc(F9<C~29BRGRxX*w+3WHz~8@2XQ79?zV`)GfCGE)pyc?4)y)6&yO4_tivit za!z|`<$@lXW*;Cdc1?YHzfT-0TinfxfVk3R9(H?lv=5b8*=gk$lO!I1cNAi^utR&n#@3g&)b*_HGWpK~uwb0Ju- z!OE?YRiiyj_u<+ub$E&PuPo}8HB5xPT}XPXg_h6{%A}7iAZc%C<5*Fi7YP%6Z^b)T z*L&TiElTwVKc>5L(Md{!q;-g~E1P3857UVu`GmQozUSlE=EK?ehfqu|GaZQTBOUh= zh^fSUN$U;cj)qAA9PX3IwNrQ`G`CTi-l{`?hJO?t&rdm3J#{4d6S@!K?XoeAFOE=y z>-@mr1ii}2bBZB+wK2i3y3AvR6ZonuQQB|6h$`o%5J`p*2KivJROXvgSoTZ_bTTM3dx96V;=wAC5@ZbnW6Pr+hydR5ZY4H zCbf`QaQ|CMhC3N}W;eg7vM1X~HfN(}%|Y%!p14XV>3G2L4BKwQiwE0-JAL6U>Ykl? z?s-in27byFXJ0?tUrcwJL@|S2pH)Y1meFAe+D|?A<<=&AM!~)7;@I!aL_-j9GC+Ip zw)fU7Hm0w59X?L*3uuHnb?XSUPxu6j((o#8&Z||CpnomQ>$bWk$HA?Y6kM~1extAQ z;+^0xJpX2;9|s~71NPE%-tUWBQ}hk_ge-h?NsaB;xVxBW#@g@9zV-nZeqy<&J8E*$ z#6mZ`*Lusov~<{oD(?OFcxq*X=X|W?t8J|L!Ujz$U4wN(3U8e>^diHhC1heVDEX-y ztaO(3a-l9&)gE+gr}|-}8%EgR;-q;*$Xy6#;N}qDqpx8H(`MFrBY3XlHZtj^_m|E% zso34DcdNwEbAALE-FLe?sXzqxh{$Lb3?IBfgPGSEOS}B}%8kP3?~r+^^s$`|e-7Kb zND|$YnisGI<+g90jG2q=OW_KPc0N;m(wFQChC?#*v-2l)8aAwsRwK4nRDt*AX+P0z zxq3fjRIIW3r?&NVwa?8byRV#%&yj?FuudgTUPBE_wxgD2&|wZ0GFuSa2RZx5%;d8W zY|%8o9207NMH@~lyrOZ))w+&RB!<-AJu3`ifdR$PJ&to&TgmD`i#vDy`ck`bm0oN( zsJiAQVea$QxI&q*sO$*o2k7uP$5SNipDiiE<;^gm9?ugz$&{~yKj6FviR(1M@R+X{ zI_fFr*3Xu)mxA!D$UVp}(a3B(t*61sdwYawL;pDJ_E=DnMtZ&Q(s^Ro=moDH22`fw zS-h6?_&+TtV2s*Tl%Jtto;h-56&Q!WiYq)h`(XyVMf)LQe4pDOAx3MF!spdyvykds z#wK_i#xDCfioy2<_VDLKl{PFoM{a18qL+>NHefc+ly>D^R_}+|;n}x#NW&Iu> zt1^7DR@I$j%vxY~KL&g&>WAJ2=oS?_&9a&bP%z)X8FWeX%=#|`tg{@ML1D~W1jK{# z7>Y(##+xE+9C%S5bV2Zps*b94JLcxO^ofKE&kn`)CcE<9TD)A4o%l|jCKI(qNIWvk zY%E?JFD2tm0{VOznD!|>4lp*y>sqCwLTm?bTHK13Zyg=X&X#rWp&@acMIJE zb=d6VX(;!act6_;U1%}=*TOjo`Via;E1aunhMOqEru8*FI5bi=qPt@%t~G~GR84@eRL zt+b=I$qf=(cVv|SjJU|^ohJ{7rkJK%NKE$$NR^BSBPU&{yhn{rx2h;vWf17ei6OVEpyOI$8veV6V5& z6gVqph0MT#`QtIcb17X|!gMw*`XMen@Iip(JMbZWgI1*vKebk?NMOv;&!X15`U||s zy=o&f78%CK%6XH3EPc8qG*9X4TIE_e%7GmMlar^MvqE) zy>o^~)v6lXU7rZM;9{1!x_C6n3Vf5INKjM?@EjR(GL|6b4jQd~vkQ$Z@gIde7i*SCTWPe^Qi}uWgK*m(}1MjhSW$ zq#=VHqa0idbj4DvWeD;;uEE}TMTvt2QKd`D;rD%VO%?MWOXE{RNDx_*s3Lb5zG0ay z@o8VL72nngLcE-=Q5#&smq}f%T?lXX12En|T3_$5Yb~ohY@sd{uS}&~7Wq70+2NXF zH_$26Z?CfFhrg|zqsum>+Tc2k(`CssRN-w5aqB7R6Nu>TO|pqe5L;Qb=~tqLQ9WnS zec@KW(Wn#U)0OW$DDrl$OwSw=b9`=f?(uMuw$s8<5R9nJMli}}74h(Y1H4WomL!}` zFcv|v%El8(g2Qrh7XN;@#+Mz=ndPgkI5j)fbPJMnRPPW`svS_=8xI`)c%4k^PC?!S zO}B*?r5A+-ZszQUizc*^e2@J6@wv!<^pWx*8~Qt`sPaakmDRaLWoO75-US>F_fF)h zs@Yz~iexfm^!V3q#k$gr+-#~X@$jZR+ZfG9fZ@`U^v)JjB z%U{(daXW{TBC5lwbq=<_U@j50BpWQlpS{j6uGSh-hwWqX56Pbq4OdXctA()sIUlc z+a?V_qX$vsQgmzuiS!gb#|vk%T}XMAiG*!oirKckVAJQ8H;@nodqA^p3r zY0g;4pCpTz!k9MTu%7(Ya(2Vt?=Ct}7Wz>6L+f+I5A`6qUzCbtG-(nYlRUgiMYqtL zowLVA((k%veZnN+)Cpgfm?LPpk4FanyJVu9oMxB*p|ySS^cf;(pNZCJ+VbNUfn)FE zGZ)b=&+1E6ZHk9j+o6)aqReu+Y#;uiroe}q5vG^IuW6^OS+Bg|cr&4oSH*8jl(R$s z4Y)rK{ekiigA#?c-_Q}!dfFB`1cpu1)wv>5t?cu&6VwX??f?WgP2@~hPJ#Yq+KNrA z3o#2D)-I*5<(&FhnZPcBM`9C!&kG`}YF7y3q7W&+6|*YEBMVM%RUH_Jdf+8`HPK#s z$vFkDi770LMYpc_-;unhpr8Mk+|S0;K)lSfo^*3}e4 zLgSrTe5gT)uE5cBC%optI{ z6wYlob{@p-!woML?*TRd*%8FPETl}7<2{*W_oc>1R9V~&1@=Vjo|p-tH&^5D+xlZX zVt0HAl~KHq!$C8Ey0gUc{@Pac=o-9#p=NOxiOkRK(&tQ;8B`1(jVZ5<4 z^t>-pYwMv94wQptesX5A;*zSME=h$Lb1>`u+TLWrr)Atq6QB-%#X;*qIi95UQW9zp zc8WnPf^F-D2b{mPYKGq4jj7BKN=qUgvlx61;5~v*#)dVuA`t6aKf)v8Goz??(=(}J zw&A)A%vMjgK;3J|a%!N7IIaTjoS=SL`D*na22)G$eq!~MA=Gm8=#&#sfQTVsf4lT$ z7YZAH2_Qz_vv}O6HDgj2;P{Ay@_(3o5HDJ$-N#@UrB&NMEBIBSeM2lGpgP#8T>PtcVWwL~dA(hIQWvW-yqQf*G$LtW5b4G}-5?UTl508v5MiGyHiC z@A>k6&H&d;T}d=N1avDriuyKK|DS0g8%IiPN9{3=8)x7VPl^g9xmJjOqEC9Mav&nh z_rsx;F$TKjr*7MviQRI0Pd{+60Gf@jlRa&q2nr3zrN^T`c=tBR<^tE2LZtynxMHqJ z`SiumWrEK@-vmWbjOMWDX>l9#0_1njL$}|hDQ6+%*i5O~HpZf~!i{=&cWJLrj3?Z|@ z%N=|7Nz?1&mtAPJ|Bk$j7;#6?De?92U;RngP-Bm7*RbE1=AlgbRvRZ=9rDq34`RU8 z*3D&|&h2s??gQ`9-l`lnUoWd=5;aL_6Y8+$x;Nawsj`^s?)>riI2c0V+>f+~u8YC9 z5DO<-@@!XEkx}Bs$x-?~xd^C6(}w}230BM9n_EFtH&66pfb<&t2&2q!sO*L)*K$N& zVmDk(5xQ3I{tVckVd->VBA`dy@LOxpKSJGaAaeO<7XhgdOn>VS69J(?8KG4-)?J0U zo&y>%Ax8Lo#2+wwsxSgXRZgdF2B7n9Rfzo{nnoPWJtqJYa)_DF*C+jsh3Xgifjuza zAIHkruv9EPvRTlyUwb`9=Pk#7D&Z%=eXcHimbIZo_|H|}0R~eD!=2C3hVdkE9R8?3 z7*NShQ*8#R=Y?d1AM(?bf0VnuI#yo3H`+^p?axeX-ZyiYmJoc(X3iO-sT=s~HZuO5 zje*P8_nM-da(OfG*MG3>6FnygLn^Q0Coe>3VwFJ3$^Bk}tHE;YC8XLb#qlc>fuHM- zHAK0P&%UC@?`dyA?{+IAhk(A`vfC}Grqqii-Ybivc(bIyZ_5*4$>aK_m(;b4@ z%t^Ap)v-`r@v%WOHY+%iMtk%C9ZW;ylzaz*$#Uaa!${4xaf}0BW*iBVa~|iQDR3ce zJ5wUI>f0yH_K|yz62?Q+EIGS-GLzNuTZFS~ZwBSRWS)a`jJLY(yK*DZ{nEq(yi9W; z0v&TF#Kk{f@FxJY4(rI_h1^URB@A7M!d`q(eXmYX6dCdgM*d9(y8qT}G;0mozwm zJmhIe3xsL-5~w@!vCnJ4aI_{$S%UA@Rm9_iff!as^T-e26RCiJ`4Q{o`Uk6_2=pYW zfoanb4muLGCD=Dm?PE@U$A__;xl-dG@TidIQDOjHIslE@nd8%efz8A?HOu{D z%=FHkyW}cTG!MSH9ThnRS*nIWy;zx7+K*E8mWPH`{WLxI@5ez$nGzF}SXnckDXT=QqW6l~gPrg!!8dHXOJx-IYe4?#~tZ|)ro zFSlT(^)b_SL&2Q~TF)$hi$-{2YPl?;3BR_jm78vJwA_mZ7C+(Q&2_v?1TW&1C|TV=2DILGj7`925%v0UX{1qbtB}AM>P*$R;EV zw*W*XQHAM8%jomF&}f?)p*VA*Dp4Z`G?8rjF%;ckgFY0yCDq4dh>#$5H4hNTF>1RX z^u*1LA3T2cQb%~e*N^#3LMyk#(kWIf4X&M7L)y(9>eM9Sl#6wAu>V?NfUvQLf72Sb8O8!K|TP}dO(W<*In!T`x91k0(|@ZowJ`H4aE`_ z0`FP0mUfYG1FoNXfvdm&XEo6ahAlrbRGQd4X(vh7^bm+*l@6T!;}mNX9-4hj1rUzV zlVjgzaXM5xMF$pvu5C;dAwo;dCH>t2Y<#=wz;$YyHs1G~j{}2YaHRLnHlQ~Ru5MIj zTmJwPc6}`$@HYJ1FAWWpl2RG;p8!U1*!t{uOvq|k*_U|{%cb-XhUoj>dnAj9e>yIH zO|E-3|9AiFeL_&Bd(`j^3QKCcjGXQ7zw0+ybi#9gp40-W+88S)*2;vvGRu1P-|BJ2 z3pg$Im*~J3i(QP_OuT({NEMdw*HaBL&9mY$iP|(goN{mU1xt3eFlnPI5caTrxl^1TPov z5~&>?eE`CjXU=NMBvIPz$;Ud3KMofc}wI&>LV#y?Rg`v5#@}-(bVGZ=NVQn|tM^8ID5WoZJpJ-U~4O+6#6ZO!d` zKgwL9EoXXH)9spq)$JwYpGa_Sl{qAP9vK5YkU{7YmjNS7xpnp9%O|@T( zaovNsUOW&aF_H|{$&ndic_>*1TLO$a^X(Cgskc+}PlUFBQJkm8Dar*b&+YBJv7UVpGPC#W)=$v1D3u8%0JQ&jvn2m6 z;!+pAb!Q0#pfCAvN%jhuSNA=&m*eQhfeOJsD)A5$WaA-aLa$cEQ5nl;@?8``mw+~N z!_(rU*uq@{{)!fRw|~a-^T-Y?l9~k%Be3U(^1qzVF0L*liR7Kp+%|m8e5$unPfJh4 zz^i(LTNldmA_9Wsc7E>+?#GF(!CBBt{;+>gQrGTnUW&9md47W}K*^t4xCDZnJ$$(~(ALoUO{_{4fWbiYozpuB9C`OH(nA!+yVxCo!DF6Zk7rgnecr zkG`F$N_X)PmUX8X@>ID52BAw+-fvhs`3sFMN?*y8K5P6v*;O*s-(+-vp-HGJqv<(g z3H~=_v|w^tz(&*wQcPTAdmVYsYsvul*vPRjJ$-qW02&D=@2Nw>dj<<>COw1=M*^Z6b~fB-lK& zKS^yZaJ;Lx5z}&Hz;RJ{?QIVFkSmz1mW2KF4l0cl^`NZ9p&P%CAF`YU@2ABP8TNNC zkmlm+Qo$v#JrV-eejPJ@BFq)*$ePxMWdmLFaf4^W?|9T_T-p4bpfP~e{G#@amq-n$ zXYWzVrU)B@7_FyV!)El|5F7_6i3%*8e^2k{RPC67Pkk`YOOugKCpy)hJ`P$Wdjg<; zs!gx`LZ2ca#D9=@qZwyLU;%%oV-_5vFbrR;P*A>y?I5FtuUgQTF|ftn*E zplB#*C0!hXIMd#fq5>p7w{2S;{0y6a^9=mbrue%l)C*OE->A0*`{)p1+!hW$BBUtfO&qU^ zbsmbhXfGE8ZPxo;n020x?3j`l8Tr0)TKO6l_zV9@p7!Uc)1Qdo8G0wp z7XkmguH;dZe2$88qWvpmgxd^yDD>9_jAe&VjMm>a9Lm0g4rnF7hN-r5Wu==hs2q;7 zW4)lq&%x~XVX#lz{hm!;M~=z2EWC9eezkahDU~`Gb`!| z6>Q4h$r&pbstf_U^{AYM|AyV zT?LkO5%(@Ou=JNUva=l?ADCNB0FpRBM%S2Ol8#rfy9O6;B2p?vnikADG5>;^io;$k zoxh#V1M*mSy3U=a+PwHZo|s4fFwdXAmqNLWZ!JB|A$5bX6?QNG7l96(=znihQi6z5 z+E;K0W+G>N86K@U`0%2$KfT+$cfQWc0@j>o^4X$YJ2=-y^(A#~>R3X-zYNHg@Ou|& zb~=EG0=Z_d=}B+Egn51AR#q@4_Co7=h+~H?Qr&l89i;&>M5GF~8R}~XJXBe|g%InM zRqYVP7o!*OB}DN*2@*R1)sawF2LP+bwWC09fZ+e8zJRK7X@iYe;7oOj#o8#QQ>Z-u zGmT8yMYDLVfhp_0m}I_eR5EGq7c@>Uay~nnwO5T8FX|yomTi2d$tmbjR|BF8w;_l5 ze<}=e5o2c)r!0MVdN@ZAHAq>;ls0VwBb&;V@K|E)#l_Mh5)7KYrTNn+7bLEWmr>FX zeZ(*KRug9W&Z8R{`mj5HtL6&MR`%}H?}e5Y z%uT!rvx*woWIh`>)%XYW?whII$gE5*6#aei>=$o)9Oyf)+EI|vUK1r(_Xc(c4r1il z(B^-H#j~`&&O?_bDzR<;4QqTf%FmlB+zME3@m|z#`jY4~k##xyqnyoEDc;XnQIaXM zMclY#QAMOdcd)_`PSVd$$&+UBtz2Wid+6ywRnq088%p24Z;UG>7YFfLj>nw-cSrH$ zLGtbA&NHW){9P1$0m|~U^uv!FY@?I*em7I?iSUdB)MmYdPK0eMb`oRyi`nz{lu5DC z`9F1Ulr`XgRmVCUMk65IdXxr6&G4J9rxp){{p)QPIdbISvx7@hdbf~Z@t<1a?9EkC5lr3}y2G67O`?xEK3&4hy6>vJq22?~tgT~*;da}_J{6#efs?ZYT4zMQ$I zW))VdY_lxU#xJpe@O9L94EYaad}ydLMeE`+#H)CFXRa0DmVRKJC1zFNY)fFTV-saW zyPUt*0oI@XI@RX8ky+nF{ceQoaiUSKVe<0=Q3psdApP5pimsS8kEwd%nsu!HvhpzS z!_fzd;jsh3fy9U3Ze&UxXV*PXM+~&Ea#&nvNl3jt{$~2-<6+%SwJ+lSHrX$$P-Yqd z7GMp&fFfHfP`&?!vcne5NHHFieaH2@%8GH&=EmJ9#E6}|l(L3u1+#?)uLl$(Va?K> z(1DE1o@k-&bC|;ww`jTwt-mJLZry^>*HC@D;8<6Tv~clieRL$@xV7`e7+~TVlTSb= z-+2nN&5CwtehJf?YQCAjL((&?| zZFD161QOt zW5jhfsRDSs`f|{z2dd98FVrbd(KT(=Kxg&S8mLZs$P-K void: # 入口=开始屏右下角「调试」角标+对局中两屏角标) check(not main.debug_overlay.visible, "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕") - check(main.debug_badge != null and main.debug_badge.visible, - "V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕") + check(main.debug_badge != null and not main.debug_badge.visible, + "V2f 调试浮层角标发布态恒隐(2026-10-25 删写死行批)") main.queue_free() await process_frame for p in ["user://smoke_boot_chain.json", "user://smoke_boot_chain.json.tmp"]: diff --git a/game/tests/验证集清单.md b/game/tests/验证集清单.md index 260ab97e..b9313ede 100644 --- a/game/tests/验证集清单.md +++ b/game/tests/验证集清单.md @@ -1,8 +1,8 @@ # 验证集清单(断言快照·冻结) -> 生成日期:2026-10-26 · 基点 commit:`7d2fbe158fa38d98b917762aa8812a80c8d70aea` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd +> 生成日期:2026-10-26 · 基点 commit:`d5a03db1a8f85365f49b7f4caed73ce5bfcb9dcb` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、extended_row_violation_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd > 提取口径:静态 `check(` 调用点,括号配平归集(可跨行);缺省源=三宿主遍历 -> (unit/**+integration/**+system_test.gd)。本基点实测 1583 个静态调用点;行级 +> (unit/**+integration/**+system_test.gd)。本基点实测 1588 个静态调用点;行级 > `grep -c "check("` 是另一口径(含 `func check(` 定义行与注释行),两口径各自浮动, > 禁互相换算(tests/README「断言计数双口径」)。运行时计数大于静态计数=循环展开条目。 > 生成命令:`python tools/extract_checks.py --extract`(可复现;重新生成须 --force; @@ -136,7 +136,7 @@ | 71 | 197 | "V2d 渲染面可见按钮计数==3(旧钮接回即红——渲染面锚,句柄缝盲区闭合)〔卡② mn1/mn2〕" | _count_visible_buttons(main._start_overlay) == 3 | (待填) | | | | 72 | 200 | "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() | (待填) | | | | 73 | 204 | "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕" | not main.debug_overlay.visible | (待填) | | | -| 74 | 206 | "V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕" | main.debug_badge != null and main.debug_badge.visible | (待填) | | | +| 74 | 206 | "V2f 调试浮层角标发布态恒隐(2026-10-25 删写死行批)" | main.debug_badge != null and not main.debug_badge.visible | (待填) | | | ## S11 _smoke_run_lifecycle(行 231–263 · 11 条 · 源 game/tests/system_test.gd · 去向:待分类回填) @@ -386,2256 +386,2271 @@ | 196 | 452 | "非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" ↩ % ticks.size() | ticks.size() >= 2 | (待填) | | | | 197 | 460 | "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)" | all_full | (待填) | | | -## S36 _smoke_full_battle(行 50–78 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S36 _smoke_full_battle(行 51–79 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 198 | 50 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | (待填) | | | -| 199 | 58 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | (待填) | | | -| 200 | 59 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | (待填) | | | -| 201 | 60 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | (待填) | | | -| 202 | 74 | "%s 步骤流含判定行" ↩ % label | has_verdict | (待填) | | | -| 203 | 77 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | (待填) | | | -| 204 | 78 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | (待填) | | | +| 198 | 51 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | (待填) | | | +| 199 | 59 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | (待填) | | | +| 200 | 60 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | (待填) | | | +| 201 | 61 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | (待填) | | | +| 202 | 75 | "%s 步骤流含判定行" ↩ % label | has_verdict | (待填) | | | +| 203 | 78 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | (待填) | | | +| 204 | 79 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | (待填) | | | -## S37 _run_battle(行 110–110 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S37 _run_battle(行 111–111 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 205 | 110 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | (待填) | | | +| 205 | 111 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | (待填) | | | -## S38 _smoke_active_refresh(行 159–214 · 17 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S38 _smoke_active_refresh(行 160–215 · 17 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 206 | 159 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 207 | 165 | "三轮停手攒 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 | (待填) | | | -| 208 | 166 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | (待填) | | | -| 209 | 176 | "轮内第 1 次刷新受理" | ok1 | (待填) | | | -| 210 | 177 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | (待填) | | | -| 211 | 178 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | (待填) | | | -| 212 | 179 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | (待填) | | | -| 213 | 185 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | (待填) | | | -| 214 | 191 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | (待填) | | | -| 215 | 193 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | (待填) | | | -| 216 | 194 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | (待填) | | | -| 217 | 206 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | (待填) | | | -| 218 | 207 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | (待填) | | | -| 219 | 208 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | (待填) | | | -| 220 | 209 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | (待填) | | | -| 221 | 213 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | (待填) | | | -| 222 | 214 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | (待填) | | | +| 206 | 160 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 207 | 166 | "三轮停手攒 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 | (待填) | | | +| 208 | 167 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | (待填) | | | +| 209 | 177 | "轮内第 1 次刷新受理" | ok1 | (待填) | | | +| 210 | 178 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | (待填) | | | +| 211 | 179 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | (待填) | | | +| 212 | 180 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | (待填) | | | +| 213 | 186 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | (待填) | | | +| 214 | 192 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | (待填) | | | +| 215 | 194 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | (待填) | | | +| 216 | 195 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | (待填) | | | +| 217 | 207 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | (待填) | | | +| 218 | 208 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | (待填) | | | +| 219 | 209 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | (待填) | | | +| 220 | 210 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | (待填) | | | +| 221 | 214 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | (待填) | | | +| 222 | 215 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | (待填) | | | -## S39 _smoke_encounter_roll(行 253–311 · 8 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S39 _smoke_encounter_roll(行 254–312 · 8 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 223 | 253 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | -| 224 | 256 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | (待填) | | | -| 225 | 262 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | (待填) | | | -| 226 | 266 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | (待填) | | | -| 227 | 271 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | -| 228 | 273 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | (待填) | | | -| 229 | 286 | "熊地精首遇带(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") | (待填) | | | -| 230 | 311 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | (待填) | | | +| 223 | 254 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 224 | 257 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | (待填) | | | +| 225 | 263 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | (待填) | | | +| 226 | 267 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | (待填) | | | +| 227 | 272 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 228 | 274 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | (待填) | | | +| 229 | 287 | "熊地精首遇带(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") | (待填) | | | +| 230 | 312 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | (待填) | | | -## S40 _smoke_settlement_segments(行 379–444 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S40 _smoke_settlement_segments(行 380–445 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 231 | 379 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | (待填) | | | -| 232 | 390 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | (待填) | | | -| 233 | 417 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | (待填) | | | -| 234 | 443 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | (待填) | | | -| 235 | 444 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | (待填) | | | +| 231 | 380 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | (待填) | | | +| 232 | 391 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | (待填) | | | +| 233 | 418 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | (待填) | | | +| 234 | 444 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | (待填) | | | +| 235 | 445 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | (待填) | | | -## S41 _smoke_skill_pool_data(行 495–670 · 27 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S41 _smoke_skill_pool_data(行 496–671 · 27 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 236 | 495 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | (待填) | | | -| 237 | 497 | "普攻行:倍率恒 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)) | (待填) | | | -| 238 | 501 | "突刺行(伤害 ×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 | (待填) | | | -| 239 | 503 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | (待填) | | | -| 240 | 505 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | (待填) | | | -| 241 | 507 | "毒爆行(伤害 ×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) | (待填) | | | -| 242 | 515 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | (待填) | | | -| 243 | 518 | "蛛网束缚行(妨害型 · 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 | (待填) | | | -| 244 | 526 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | (待填) | | | -| 245 | 528 | "失衡状态行(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" | (待填) | | | -| 246 | 531 | "毒吐行(伤害 ×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") | (待填) | | | -| 247 | 535 | "盾击行(伤害 ×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") | (待填) | | | -| 248 | 549 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | (待填) | | | -| 249 | 550 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | (待填) | | | -| 250 | 558 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | (待填) | | | -| 251 | 604 | "二十八行九种池模板 = 建议池行值((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 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)" | pools_ok | (待填) | | | -| 252 | 614 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | (待填) | | | -| 253 | 631 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | (待填) | | | -| 254 | 636 | "前置:卓尔 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"}) | (待填) | | | -| 255 | 639 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | (待填) | | | -| 256 | 645 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | (待填) | | | -| 257 | 647 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | (待填) | | | -| 258 | 648 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | (待填) | | | -| 259 | 662 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | (待填) | | | -| 260 | 666 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | (待填) | | | -| 261 | 667 | "硬化状态行 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 | (待填) | | | -| 262 | 668 | "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" | (待填) | | | +| 236 | 496 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | (待填) | | | +| 237 | 498 | "普攻行:倍率恒 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)) | (待填) | | | +| 238 | 502 | "突刺行(伤害 ×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 | (待填) | | | +| 239 | 504 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | (待填) | | | +| 240 | 506 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | (待填) | | | +| 241 | 508 | "毒爆行(伤害 ×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) | (待填) | | | +| 242 | 516 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | (待填) | | | +| 243 | 519 | "蛛网束缚行(妨害型 · 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 | (待填) | | | +| 244 | 527 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | (待填) | | | +| 245 | 529 | "失衡状态行(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" | (待填) | | | +| 246 | 532 | "毒吐行(伤害 ×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") | (待填) | | | +| 247 | 536 | "盾击行(伤害 ×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") | (待填) | | | +| 248 | 550 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | (待填) | | | +| 249 | 551 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | (待填) | | | +| 250 | 559 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | (待填) | | | +| 251 | 605 | "二十八行九种池模板 = 建议池行值((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 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)" | pools_ok | (待填) | | | +| 252 | 615 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | (待填) | | | +| 253 | 632 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | (待填) | | | +| 254 | 637 | "前置:卓尔 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"}) | (待填) | | | +| 255 | 640 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | (待填) | | | +| 256 | 646 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | (待填) | | | +| 257 | 648 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | (待填) | | | +| 258 | 649 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | (待填) | | | +| 259 | 663 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | (待填) | | | +| 260 | 667 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | (待填) | | | +| 261 | 668 | "硬化状态行 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 | (待填) | | | +| 262 | 669 | "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" | (待填) | | | -## S42 _smoke_skill_pool_replay_independence(行 703–709 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S42 _smoke_skill_pool_replay_independence(行 704–710 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 263 | 703 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | (待填) | | | -| 264 | 709 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | (待填) | | | +| 263 | 704 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | (待填) | | | +| 264 | 710 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | (待填) | | | -## S43 _smoke_skill_pool_ev_anchor(行 791–791 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S43 _smoke_skill_pool_ev_anchor(行 792–792 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 265 | 791 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | (待填) | | | +| 265 | 792 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | (待填) | | | -## S44 _smoke_skill_pool_death_void(行 811–830 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S44 _smoke_skill_pool_death_void(行 812–831 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 266 | 811 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | (待填) | | | -| 267 | 817 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | (待填) | | | -| 268 | 820 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | (待填) | | | -| 269 | 826 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | (待填) | | | -| 270 | 830 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | (待填) | | | +| 266 | 812 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | (待填) | | | +| 267 | 818 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | (待填) | | | +| 268 | 821 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | (待填) | | | +| 269 | 827 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | (待填) | | | +| 270 | 831 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | (待填) | | | -## S45 _smoke_skill_pool_validation_reject(行 841–877 · 13 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S45 _smoke_skill_pool_validation_reject(行 842–878 · 13 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 271 | 841 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | (待填) | | | -| 272 | 843 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | -| 273 | 848 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | -| 274 | 850 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | -| 275 | 852 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | -| 276 | 854 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | (待填) | | | -| 277 | 857 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | (待填) | | | -| 278 | 859 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | (待填) | | | -| 279 | 865 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | -| 280 | 867 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | (待填) | | | -| 281 | 869 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | (待填) | | | -| 282 | 871 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | (待填) | | | -| 283 | 877 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | (待填) | | | +| 271 | 842 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | (待填) | | | +| 272 | 844 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 273 | 849 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 274 | 851 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | +| 275 | 853 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | +| 276 | 855 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | (待填) | | | +| 277 | 858 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | (待填) | | | +| 278 | 860 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | (待填) | | | +| 279 | 866 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 280 | 868 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | (待填) | | | +| 281 | 870 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | (待填) | | | +| 282 | 872 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | (待填) | | | +| 283 | 878 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | (待填) | | | -## S46 _smoke_debuff_rider(行 955–957 · 3 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S46 _smoke_debuff_rider(行 956–958 · 3 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 284 | 955 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | (待填) | | | -| 285 | 956 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | (待填) | | | -| 286 | 957 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | (待填) | | | +| 284 | 956 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | (待填) | | | +| 285 | 957 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | (待填) | | | +| 286 | 958 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | (待填) | | | -## S47 _smoke_counter_buckets(行 980–1008 · 6 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S47 _smoke_counter_buckets(行 981–1019 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 287 | 980 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | (待填) | | | -| 288 | 981 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | (待填) | | | -| 289 | 986 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | (待填) | | | -| 290 | 988 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | (待填) | | | -| 291 | 1003 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | (待填) | | | -| 292 | 1008 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | (待填) | | | +| 287 | 981 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | (待填) | | | +| 288 | 982 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | (待填) | | | +| 289 | 987 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | (待填) | | | +| 290 | 989 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | (待填) | | | +| 291 | 1006 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | (待填) | | | +| 292 | 1012 | "卡④ wr2①:完美保险标错行 type=extended 且 effect_id=insurance_used(effect_id 必载)〔明细收敛 V2〕" | not v6_ins.is_empty() and String(v6_ins.get("type", "")) == "extended" and String(v6_ins.get("source_class", "")) == "seal_miss" and bool(v6_ins.get("forgiven", 0)) | (待填) | | | +| 293 | 1019 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | (待填) | | | -## S48 _smoke_seal_ap_exhausted(行 54–80 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S48 _smoke_extended_rows(行 1046–1065 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 293 | 54 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 294 | 68 | "%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 | (待填) | | | -| 295 | 73 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | (待填) | | | -| 296 | 79 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | (待填) | | | -| 297 | 80 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | (待填) | | | +| 294 | 1046 | "卡④ wr2②:专长揭示兑付行 type=extended 且 effect_id=fx_reveal 且来源格真实〔明细收敛 V2〕" | not paid.is_empty() and String(paid.get("type", "")) == "extended" and Vector2i(paid["origin_cell"][0], paid["origin_cell"][1]).x >= 0 | (待填) | | | +| 295 | 1063 | "卡④ wr2③:揭示落空行 type=extended 且 effect_id=fx_reveal 且 origin_cell=越界哨值〔明细收敛 V2〕" | not missed.is_empty() and String(missed.get("type", "")) == "extended" and Vector2i(missed["origin_cell"][0], missed["origin_cell"][1]).x == -1 | (待填) | | | -## S49 _smoke_clear_reward_chain(行 329–341 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S49 _smoke_seal_ap_exhausted(行 54–80 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 298 | 329 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | -| 299 | 336 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | (待填) | | | -| 300 | 338 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | (待填) | | | -| 301 | 339 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | (待填) | | | -| 302 | 341 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | (待填) | | | +| 296 | 54 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 297 | 68 | "%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 | (待填) | | | +| 298 | 73 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | (待填) | | | +| 299 | 79 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | (待填) | | | +| 300 | 80 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | (待填) | | | -## S50 _smoke_clear_reward_zero_paths(行 359–399 · 11 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S50 _smoke_clear_reward_chain(行 329–341 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 303 | 359 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | -| 304 | 363 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | (待填) | | | -| 305 | 366 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | (待填) | | | -| 306 | 367 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | -| 307 | 374 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | -| 308 | 381 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | -| 309 | 382 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | (待填) | | | -| 310 | 389 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | -| 311 | 396 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | (待填) | | | -| 312 | 397 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | -| 313 | 399 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | (待填) | | | +| 301 | 329 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | +| 302 | 336 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | (待填) | | | +| 303 | 338 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | (待填) | | | +| 304 | 339 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | (待填) | | | +| 305 | 341 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | (待填) | | | -## S51 _smoke_refresh_provenance_guard(行 410–421 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S51 _smoke_clear_reward_zero_paths(行 359–399 · 11 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 314 | 410 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | (待填) | | | -| 315 | 411 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | -| 316 | 419 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | (待填) | | | -| 317 | 420 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | (待填) | | | -| 318 | 421 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | (待填) | | | +| 306 | 359 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 307 | 363 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | (待填) | | | +| 308 | 366 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | (待填) | | | +| 309 | 367 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 310 | 374 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 311 | 381 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 312 | 382 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | (待填) | | | +| 313 | 389 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 314 | 396 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | (待填) | | | +| 315 | 397 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 316 | 399 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | (待填) | | | -## S52 _smoke_polarity_routing(行 442–549 · 12 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S52 _smoke_refresh_provenance_guard(行 410–421 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 319 | 442 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | (待填) | | | -| 320 | 443 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | (待填) | | | -| 321 | 444 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | (待填) | | | -| 322 | 445 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | (待填) | | | -| 323 | 462 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | (待填) | | | -| 324 | 479 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | (待填) | | | -| 325 | 492 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典;action 型按行值符号辨极性)" | hit_ok | (待填) | | | -| 326 | 498 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | (待填) | | | -| 327 | 526 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | (待填) | | | -| 328 | 532 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | (待填) | | | -| 329 | 543 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | (待填) | | | -| 330 | 549 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | (待填) | | | +| 317 | 410 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | (待填) | | | +| 318 | 411 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | +| 319 | 419 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | (待填) | | | +| 320 | 420 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | (待填) | | | +| 321 | 421 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | (待填) | | | -## S53 _smoke_seal_irreversible_quadrants(行 571–663 · 21 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S53 _smoke_polarity_routing(行 442–549 · 12 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 331 | 571 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 332 | 574 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | (待填) | | | -| 333 | 578 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | (待填) | | | -| 334 | 580 | "四情况 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" | (待填) | | | -| 335 | 582 | "四情况 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" | (待填) | | | -| 336 | 584 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | (待填) | | | -| 337 | 585 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | (待填) | | | -| 338 | 600 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | (待填) | | | -| 339 | 601 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | (待填) | | | -| 340 | 602 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | (待填) | | | -| 341 | 609 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 342 | 612 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | (待填) | | | -| 343 | 616 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | (待填) | | | -| 344 | 618 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | (待填) | | | -| 345 | 619 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | (待填) | | | -| 346 | 631 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | (待填) | | | -| 347 | 632 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | (待填) | | | -| 348 | 639 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 349 | 643 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | (待填) | | | -| 350 | 662 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | (待填) | | | -| 351 | 663 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | (待填) | | | +| 322 | 442 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | (待填) | | | +| 323 | 443 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | (待填) | | | +| 324 | 444 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | (待填) | | | +| 325 | 445 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | (待填) | | | +| 326 | 462 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | (待填) | | | +| 327 | 479 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | (待填) | | | +| 328 | 492 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典;action 型按行值符号辨极性)" | hit_ok | (待填) | | | +| 329 | 498 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | (待填) | | | +| 330 | 526 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | (待填) | | | +| 331 | 532 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | (待填) | | | +| 332 | 543 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | (待填) | | | +| 333 | 549 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | (待填) | | | -## S54 _smoke_negative_dictionary(行 675–755 · 8 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S54 _smoke_seal_irreversible_quadrants(行 571–663 · 21 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 352 | 675 | "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" | (待填) | | | -| 353 | 679 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | (待填) | | | -| 354 | 716 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | (待填) | | | -| 355 | 726 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | (待填) | | | -| 356 | 738 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | (待填) | | | -| 357 | 748 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | (待填) | | | -| 358 | 753 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | (待填) | | | -| 359 | 755 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | (待填) | | | +| 334 | 571 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 335 | 574 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | (待填) | | | +| 336 | 578 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | (待填) | | | +| 337 | 580 | "四情况 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" | (待填) | | | +| 338 | 582 | "四情况 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" | (待填) | | | +| 339 | 584 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | (待填) | | | +| 340 | 585 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | (待填) | | | +| 341 | 600 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | (待填) | | | +| 342 | 601 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | (待填) | | | +| 343 | 602 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | (待填) | | | +| 344 | 609 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 345 | 612 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | (待填) | | | +| 346 | 616 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | (待填) | | | +| 347 | 618 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | (待填) | | | +| 348 | 619 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | (待填) | | | +| 349 | 631 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | (待填) | | | +| 350 | 632 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | (待填) | | | +| 351 | 639 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 352 | 643 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | (待填) | | | +| 353 | 662 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | (待填) | | | +| 354 | 663 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | (待填) | | | -## S55 _smoke_resource_surface(行 785–804 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S55 _smoke_negative_dictionary(行 675–755 · 8 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 360 | 785 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | (待填) | | | -| 361 | 786 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | (待填) | | | -| 362 | 787 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | (待填) | | | -| 363 | 803 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | (待填) | | | -| 364 | 804 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | (待填) | | | +| 355 | 675 | "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" | (待填) | | | +| 356 | 679 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | (待填) | | | +| 357 | 716 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | (待填) | | | +| 358 | 726 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | (待填) | | | +| 359 | 738 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | (待填) | | | +| 360 | 748 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | (待填) | | | +| 361 | 753 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | (待填) | | | +| 362 | 755 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | (待填) | | | -## S56 _init(行 18–18 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S56 _smoke_resource_surface(行 785–804 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 365 | 18 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 363 | 785 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | (待填) | | | +| 364 | 786 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | (待填) | | | +| 365 | 787 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | (待填) | | | +| 366 | 803 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | (待填) | | | +| 367 | 804 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | (待填) | | | -## S57 _boss(行 30–30 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S57 _init(行 18–18 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 366 | 30 | "前置:spawn (%d,%d) 受理" ↩ % [L, D] | ok | (待填) | | | +| 368 | 18 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S58 _test_gate_predicate(行 49–69 · 9 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S58 _boss(行 30–30 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 367 | 49 | "前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)" | mhp == 130 | (待填) | | | -| 368 | 52 | "门谓词:hp=66 > 阈值 65 → 零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 369 | 53 | "双面过滤:未解锁 venom_curb 不入可摇集合" | not _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -| 370 | 54 | "双面过滤:未解锁 venom_curb 不入保底特殊技集合" | not _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | -| 371 | 57 | "前置:补一刀至阈值 65 受理" | hit65 and int(u.hp) == 65 | (待填) | | | -| 372 | 59 | "门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次" | unlocked.size() == 1 and String(unlocked[0]) == "venom_curb" | (待填) | | | -| 373 | 61 | "解锁即可摇:venom_curb 入可摇集合(冷却初值 0)" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -| 374 | 62 | "解锁即可摇:venom_curb 入保底特殊技集合" | _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | -| 375 | 69 | "保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)" | specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb") | (待填) | | | +| 369 | 30 | "前置:spawn (%d,%d) 受理" ↩ % [L, D] | ok | (待填) | | | -## S59 _test_latch_irreversible(行 77–80 · 3 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S59 _test_gate_predicate(行 49–69 · 9 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 376 | 77 | "前置:半血触发解锁" | ec.check_skill_unlocks(u).size() == 1 | (待填) | | | -| 377 | 79 | "闩不可逆:满血复查零新解锁(单向闩不解锁回退)" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 378 | 80 | "闩不可逆:可摇集合仍含 venom_curb" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 370 | 49 | "前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)" | mhp == 130 | (待填) | | | +| 371 | 52 | "门谓词:hp=66 > 阈值 65 → 零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 372 | 53 | "双面过滤:未解锁 venom_curb 不入可摇集合" | not _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 373 | 54 | "双面过滤:未解锁 venom_curb 不入保底特殊技集合" | not _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | +| 374 | 57 | "前置:补一刀至阈值 65 受理" | hit65 and int(u.hp) == 65 | (待填) | | | +| 375 | 59 | "门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次" | unlocked.size() == 1 and String(unlocked[0]) == "venom_curb" | (待填) | | | +| 376 | 61 | "解锁即可摇:venom_curb 入可摇集合(冷却初值 0)" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 377 | 62 | "解锁即可摇:venom_curb 入保底特殊技集合" | _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | +| 378 | 69 | "保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)" | specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb") | (待填) | | | -## S60 _test_plain_rows_zero_drift(行 91–103 · 4 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S60 _test_latch_irreversible(行 77–80 · 3 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 379 | 91 | "零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)" | not any_unlock_key | (待填) | | | -| 380 | 92 | "零扰动:满血复查零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 381 | 95 | "零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)" | rollable.size() == pool_count | (待填) | | | -| 382 | 103 | "确定性:两实例同输入可摇集合逐位同(容器零随机纪律)" | a == b | (待填) | | | +| 379 | 77 | "前置:半血触发解锁" | ec.check_skill_unlocks(u).size() == 1 | (待填) | | | +| 380 | 79 | "闩不可逆:满血复查零新解锁(单向闩不解锁回退)" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 381 | 80 | "闩不可逆:可摇集合仍含 venom_curb" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -## S61 _smoke_variant_placement(行 60–132 · 9 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) +## S61 _test_plain_rows_zero_drift(行 91–103 · 4 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 383 | 60 | "L%d 布置段战斗启动失败" ↩ % L | false | (待填) | | | -| 384 | 63 | "L%d 布置段板非空" ↩ % L | b != null | (待填) | | | -| 385 | 84 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | (待填) | | | -| 386 | 85 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | (待填) | | | -| 387 | 93 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | (待填) | | | -| 388 | 100 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | (待填) | | | -| 389 | 103 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | (待填) | | | -| 390 | 125 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | (待填) | | | -| 391 | 132 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | (待填) | | | +| 382 | 91 | "零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)" | not any_unlock_key | (待填) | | | +| 383 | 92 | "零扰动:满血复查零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 384 | 95 | "零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)" | rollable.size() == pool_count | (待填) | | | +| 385 | 103 | "确定性:两实例同输入可摇集合逐位同(容器零随机纪律)" | a == b | (待填) | | | -## S62 _smoke_global_variant(行 154–255 · 30 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) +## S62 _smoke_variant_placement(行 60–132 · 9 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 392 | 154 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 393 | 158 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 394 | 162 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 395 | 166 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 396 | 175 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | (待填) | | | -| 397 | 180 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | (待填) | | | -| 398 | 184 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | (待填) | | | -| 399 | 185 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | (待填) | | | -| 400 | 186 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | (待填) | | | -| 401 | 187 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | (待填) | | | -| 402 | 188 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | (待填) | | | -| 403 | 189 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | (待填) | | | -| 404 | 190 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | (待填) | | | -| 405 | 196 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | (待填) | | | -| 406 | 202 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | (待填) | | | -| 407 | 207 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | (待填) | | | -| 408 | 208 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | (待填) | | | -| 409 | 209 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | (待填) | | | -| 410 | 211 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | (待填) | | | -| 411 | 221 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | (待填) | | | -| 412 | 226 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | (待填) | | | -| 413 | 229 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | (待填) | | | -| 414 | 232 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | (待填) | | | -| 415 | 234 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | (待填) | | | -| 416 | 235 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | (待填) | | | -| 417 | 236 | "进攻族上浮: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 | (待填) | | | -| 418 | 240 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | (待填) | | | -| 419 | 241 | "合成序构造例: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 | (待填) | | | -| 420 | 244 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | (待填) | | | -| 421 | 255 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | (待填) | | | +| 386 | 60 | "L%d 布置段战斗启动失败" ↩ % L | false | (待填) | | | +| 387 | 63 | "L%d 布置段板非空" ↩ % L | b != null | (待填) | | | +| 388 | 84 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | (待填) | | | +| 389 | 85 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | (待填) | | | +| 390 | 93 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | (待填) | | | +| 391 | 100 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | (待填) | | | +| 392 | 103 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | (待填) | | | +| 393 | 125 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | (待填) | | | +| 394 | 132 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | (待填) | | | -## S63 _smoke_type_gate(行 25–29 · 2 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) +## S63 _smoke_global_variant(行 154–255 · 30 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 422 | 25 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | (待填) | | | -| 423 | 28 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | (待填) | | | +| 395 | 154 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 396 | 158 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 397 | 162 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 398 | 166 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 399 | 175 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | (待填) | | | +| 400 | 180 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | (待填) | | | +| 401 | 184 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | (待填) | | | +| 402 | 185 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | (待填) | | | +| 403 | 186 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | (待填) | | | +| 404 | 187 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | (待填) | | | +| 405 | 188 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | (待填) | | | +| 406 | 189 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | (待填) | | | +| 407 | 190 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | (待填) | | | +| 408 | 196 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | (待填) | | | +| 409 | 202 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | (待填) | | | +| 410 | 207 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | (待填) | | | +| 411 | 208 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | (待填) | | | +| 412 | 209 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | (待填) | | | +| 413 | 211 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | (待填) | | | +| 414 | 221 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | (待填) | | | +| 415 | 226 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | (待填) | | | +| 416 | 229 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | (待填) | | | +| 417 | 232 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | (待填) | | | +| 418 | 234 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | (待填) | | | +| 419 | 235 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | (待填) | | | +| 420 | 236 | "进攻族上浮: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 | (待填) | | | +| 421 | 240 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | (待填) | | | +| 422 | 241 | "合成序构造例: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 | (待填) | | | +| 423 | 244 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | (待填) | | | +| 424 | 255 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | (待填) | | | -## S64 _smoke_hygiene_gates(行 56–77 · 6 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) +## S64 _smoke_type_gate(行 25–29 · 2 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 424 | 56 | "V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | (待填) | | | -| 425 | 58 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | (待填) | | | -| 426 | 61 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | (待填) | | | -| 427 | 64 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | (待填) | | | -| 428 | 67 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | (待填) | | | -| 429 | 76 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | (待填) | | | +| 425 | 25 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | (待填) | | | +| 426 | 28 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | (待填) | | | -## S65 _init(行 22–22 · 1 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S65 _smoke_hygiene_gates(行 56–77 · 6 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 430 | 22 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 427 | 56 | "V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | (待填) | | | +| 428 | 58 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | (待填) | | | +| 429 | 61 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | (待填) | | | +| 430 | 64 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | (待填) | | | +| 431 | 67 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | (待填) | | | +| 432 | 76 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | (待填) | | | -## S66 _test_damage_controller_vocabularies(行 43–57 · 4 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S66 _init(行 22–22 · 1 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 431 | 43 | "五配方常量非空(开单面唯一分派键)" | nonempty | (待填) | | | -| 432 | 47 | "五配方常量互异(词表封闭无别名)" | unique_kinds.size() == kinds.size() | (待填) | | | -| 433 | 55 | "三目标规则常量互异(结账时刻现场解析词表)" | unique_targets.size() == targets.size() | (待填) | | | -| 434 | 56 | "双结账政策常量互异(A 即结 / B 攒批回切位)" | String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT) | (待填) | | | +| 433 | 22 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S67 _test_rng_tag_closure(行 64–67 · 2 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S67 _test_damage_controller_vocabularies(行 43–57 · 4 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 435 | 64 | "settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)" | RngServiceScript.PURPOSE_TAGS.has(stream_label) | (待填) | | | -| 436 | 66 | "RngService 用途标签词表非空且互异(封闭语义由流改标签)" | RngServiceScript.PURPOSE_TAGS.size() > 0 | (待填) | | | +| 434 | 43 | "五配方常量非空(开单面唯一分派键)" | nonempty | (待填) | | | +| 435 | 47 | "五配方常量互异(词表封闭无别名)" | unique_kinds.size() == kinds.size() | (待填) | | | +| 436 | 55 | "三目标规则常量互异(结账时刻现场解析词表)" | unique_targets.size() == targets.size() | (待填) | | | +| 437 | 56 | "双结账政策常量互异(A 即结 / B 攒批回切位)" | String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT) | (待填) | | | -## S68 _test_ticket_nine_columns(行 83–85 · 3 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S68 _test_rng_tag_closure(行 64–67 · 2 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 437 | 83 | "单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)" | all_present | (待填) | | | -| 438 | 84 | "缺省无类型轴(attack_type 空 = 恒不查克制矩阵)" | String(t.attack_type) == "" | (待填) | | | -| 439 | 85 | "缺省无前置依赖(requires = -1)" | int(t.requires) == -1 | (待填) | | | - -## S69 _test_status_boolean_acceptance(行 107–121 · 5 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 440 | 107 | "前置:状态表非空" | not any_row.is_empty() | (待填) | | | -| 441 | 112 | "域闭授予拒绝(EC1——战斗域外 apply_effect = false)" | effects.apply_effect(probe_id, src) == false | (待填) | | | -| 442 | 113 | "未知 effect_id 拒绝(EC2)" | effects.apply_effect("no_such_effect_id__", src) == false | (待填) | | | -| 443 | 115 | "开域后受理已知 id(布尔受理口径 true 通道)" | effects.apply_effect(probe_id, src) == true | (待填) | | | -| 444 | 121 | "受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)" | projected | (待填) | | | - -## S70 _smoke_adventure_api(行 61–82 · 11 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 445 | 61 | "冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))" | adr.start_adventure(42) | (待填) | | | -| 446 | 62 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | (待填) | | | -| 447 | 63 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | (待填) | | | -| 448 | 64 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | (待填) | | | -| 449 | 67 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | (待填) | | | -| 450 | 68 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | (待填) | | | -| 451 | 69 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | (待填) | | | -| 452 | 78 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | (待填) | | | -| 453 | 79 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | (待填) | | | -| 454 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | (待填) | | | -| 455 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | (待填) | | | - -## S71 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 456 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | | -| 457 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | | -| 458 | 135 | "满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | | -| 459 | 136 | "通关几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | | -| 460 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | | -| 461 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | | -| 462 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | | -| 463 | 154 | "通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | | -| 464 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | | -| 465 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | | -| 466 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | | -| 467 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | | -| 468 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | | -| 469 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | | -| 470 | 245 | "通关部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | | -| 471 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | | -| 472 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | | -| 473 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | | -| 474 | 261 | "同种子两局逐位可复现(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"] | (待填) | | | -| 475 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | | -| 476 | 274 | "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 | (待填) | | | -| 477 | 278 | "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"] | (待填) | | | -| 478 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | | -| 479 | 290 | "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 | (待填) | | | -| 480 | 295 | "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"] | (待填) | | | -| 481 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | | -| 482 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | | -| 483 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | | -| 484 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | | -| 485 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | | -| 486 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | | -| 487 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | | -| 488 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | | -| 489 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | | -| 490 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | | -| 491 | 355 | "L2 通关三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | (待填) | | | -| 492 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | | -| 493 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | | -| 494 | 362 | "三选一属性件与商店购买同账累加(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 | (待填) | | | -| 495 | 364 | "通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | (待填) | | | -| 496 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | | -| 497 | 368 | "恢复点数学: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 | (待填) | | | -| 498 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | | -| 499 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | | -| 500 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | | -| 501 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | | -| 502 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | | -| 503 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | -| 504 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | | -| 505 | 399 | "池耗后候选恒 ≥2(稀有档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | | -| 506 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | | -| 507 | 417 | "满血通关回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | | -| 508 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | -| 509 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | | -| 510 | 435 | "奖励节点 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")) | (待填) | | | -| 511 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | (待填) | | | -| 512 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | | - -## S72 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 513 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | | -| 514 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | | -| 515 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | | -| 516 | 460 | "专长带入池: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 | (待填) | | | -| 517 | 462 | "旧 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() | (待填) | | | -| 518 | 463 | "迁出收尾: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() | (待填) | | | -| 519 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | | -| 520 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | | -| 521 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | | -| 522 | 484 | "数值纪律:陷阱爆发倍率>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 | (待填) | | | -| 523 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | | -| 524 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | | -| 525 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | (待填) | | | -| 526 | 522 | "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | | -| 527 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | | -| 528 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | (待填) | | | -| 529 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | | -| 530 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | (待填) | | | -| 531 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | | -| 532 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | | -| 533 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | (待填) | | | -| 534 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | (待填) | | | -| 535 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | (待填) | | | -| 536 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | | -| 537 | 561 | "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 | (待填) | | | -| 538 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | | -| 539 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | | -| 540 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | | -| 541 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | | -| 542 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | | -| 543 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | | -| 544 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | | -| 545 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | | -| 546 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | | -| 547 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | | -| 548 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | -| 549 | 625 | "边陲矿工:边缘格产出 +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() | (待填) | | | -| 550 | 633 | "封印回响:标中计数≥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" | (待填) | | | -| 551 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | | -| 552 | 644 | "封印桩:标中本轮 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" | (待填) | | | -| 553 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | | -| 554 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | | -| 555 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | | -| 556 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | | -| 557 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | | -| 558 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | | -| 559 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | | -| 560 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(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 | (待填) | | | -| 561 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | | -| 562 | 692 | "石肤:受伤 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 | (待填) | | | -| 563 | 696 | "背水:生命≤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() | (待填) | | | -| 564 | 701 | "破封一击:挖开带封印危险格 +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() | (待填) | | | -| 565 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | | -| 566 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | | -| 567 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | | -| 568 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | | -| 569 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | | -| 570 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | -| 571 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | | -| 572 | 730 | "处刑台:敌血量≤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() | (待填) | | | -| 573 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | | -| 574 | 737 | "血税:双向同局账本(踩陷阱−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" | (待填) | | | -| 575 | 742 | "淬毒刃纹(迁入):克制正面(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() | (待填) | | | -| 576 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | | -| 577 | 750 | "零持有订阅=零命中(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() | (待填) | | | -| 578 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | | -| 579 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | | -| 580 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | | - -## S73 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 581 | 65 | "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 | (待填) | | | -| 582 | 67 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | (待填) | | | - -## S74 _smoke_reward_identity_unit(行 83–116 · 10 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 583 | 83 | "roll_board 受理" | reward_economy.roll_board(eligible) | (待填) | | | -| 584 | 84 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | (待填) | | | -| 585 | 95 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | (待填) | | | -| 586 | 97 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | (待填) | | | -| 587 | 101 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | (待填) | | | -| 588 | 102 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | (待填) | | | -| 589 | 106 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | (待填) | | | -| 590 | 111 | "清图奖励 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]) | (待填) | | | -| 591 | 113 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | (待填) | | | -| 592 | 116 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | (待填) | | | +| 438 | 64 | "settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)" | RngServiceScript.PURPOSE_TAGS.has(stream_label) | (待填) | | | +| 439 | 66 | "RngService 用途标签词表非空且互异(封闭语义由流改标签)" | RngServiceScript.PURPOSE_TAGS.size() > 0 | (待填) | | | -## S75 _smoke_board_generator(行 135–210 · 24 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) +## S69 _test_ticket_nine_columns(行 83–85 · 3 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 593 | 135 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | (待填) | | | -| 594 | 141 | "BoardGen 精确命中 (2,3)(覆盖批键域补齐)" | int(exact_row.get("node_depth", -1)) == 3 | (待填) | | | -| 595 | 151 | "R1 ceil 能力不失(夹具摘除 (2,3)→向上取整到 4·偏难口径)" | int(ceil_row.get("node_depth", -1)) == 4 | (待填) | | | -| 596 | 153 | "夹具表原样还原(行数与解析回到覆盖批现值)" | _db.board_gen.rows.size() == rows_before and int(_db.resolve_board_gen(2, 3).get("node_depth", -1)) == 3 | (待填) | | | -| 597 | 156 | "L%d 生成产物非空" ↩ % L | b1 != null | (待填) | | | -| 598 | 159 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | (待填) | | | -| 599 | 160 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row) | (待填) | | | -| 600 | 167 | "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" | (待填) | | | -| 601 | 169 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | (待填) | | | -| 602 | 170 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | (待填) | | | -| 603 | 171 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | (待填) | | | -| 604 | 173 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | (待填) | | | -| 605 | 175 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | (待填) | | | -| 606 | 177 | "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) | (待填) | | | -| 607 | 179 | "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) | (待填) | | | -| 608 | 181 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | (待填) | | | -| 609 | 185 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | (待填) | | | -| 610 | 187 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | (待填) | | | -| 611 | 193 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | (待填) | | | -| 612 | 195 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | (待填) | | | -| 613 | 197 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | (待填) | | | -| 614 | 199 | "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 | (待填) | | | -| 615 | 207 | "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) | (待填) | | | -| 616 | 209 | "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", "")) != "" | (待填) | | | +| 440 | 83 | "单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)" | all_present | (待填) | | | +| 441 | 84 | "缺省无类型轴(attack_type 空 = 恒不查克制矩阵)" | String(t.attack_type) == "" | (待填) | | | +| 442 | 85 | "缺省无前置依赖(requires = -1)" | int(t.requires) == -1 | (待填) | | | + +## S70 _test_status_boolean_acceptance(行 107–121 · 5 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 443 | 107 | "前置:状态表非空" | not any_row.is_empty() | (待填) | | | +| 444 | 112 | "域闭授予拒绝(EC1——战斗域外 apply_effect = false)" | effects.apply_effect(probe_id, src) == false | (待填) | | | +| 445 | 113 | "未知 effect_id 拒绝(EC2)" | effects.apply_effect("no_such_effect_id__", src) == false | (待填) | | | +| 446 | 115 | "开域后受理已知 id(布尔受理口径 true 通道)" | effects.apply_effect(probe_id, src) == true | (待填) | | | +| 447 | 121 | "受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)" | projected | (待填) | | | + +## S71 _smoke_adventure_api(行 61–82 · 11 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 448 | 61 | "冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))" | adr.start_adventure(42) | (待填) | | | +| 449 | 62 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | (待填) | | | +| 450 | 63 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | (待填) | | | +| 451 | 64 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | (待填) | | | +| 452 | 67 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | (待填) | | | +| 453 | 68 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | (待填) | | | +| 454 | 69 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | (待填) | | | +| 455 | 78 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | (待填) | | | +| 456 | 79 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | (待填) | | | +| 457 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | (待填) | | | +| 458 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | (待填) | | | + +## S72 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 459 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | | +| 460 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | | +| 461 | 135 | "满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | | +| 462 | 136 | "通关几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | | +| 463 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | | +| 464 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | | +| 465 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | | +| 466 | 154 | "通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | | +| 467 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | | +| 468 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | | +| 469 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | | +| 470 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | | +| 471 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | | +| 472 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | | +| 473 | 245 | "通关部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | | +| 474 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | | +| 475 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | | +| 476 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | | +| 477 | 261 | "同种子两局逐位可复现(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"] | (待填) | | | +| 478 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | | +| 479 | 274 | "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 | (待填) | | | +| 480 | 278 | "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"] | (待填) | | | +| 481 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | | +| 482 | 290 | "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 | (待填) | | | +| 483 | 295 | "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"] | (待填) | | | +| 484 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | | +| 485 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | | +| 486 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | | +| 487 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | | +| 488 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | | +| 489 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | | +| 490 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | | +| 491 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | | +| 492 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | | +| 493 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | | +| 494 | 355 | "L2 通关三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | (待填) | | | +| 495 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | | +| 496 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | | +| 497 | 362 | "三选一属性件与商店购买同账累加(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 | (待填) | | | +| 498 | 364 | "通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | (待填) | | | +| 499 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | | +| 500 | 368 | "恢复点数学: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 | (待填) | | | +| 501 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | | +| 502 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | | +| 503 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | | +| 504 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | | +| 505 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | | +| 506 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | +| 507 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | | +| 508 | 399 | "池耗后候选恒 ≥2(稀有档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | | +| 509 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | | +| 510 | 417 | "满血通关回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | | +| 511 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | +| 512 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | | +| 513 | 435 | "奖励节点 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")) | (待填) | | | +| 514 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | (待填) | | | +| 515 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | | + +## S73 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 516 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | | +| 517 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | | +| 518 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | | +| 519 | 460 | "专长带入池: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 | (待填) | | | +| 520 | 462 | "旧 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() | (待填) | | | +| 521 | 463 | "迁出收尾: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() | (待填) | | | +| 522 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | | +| 523 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | | +| 524 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | | +| 525 | 484 | "数值纪律:陷阱爆发倍率>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 | (待填) | | | +| 526 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | | +| 527 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | | +| 528 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | (待填) | | | +| 529 | 522 | "通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | | +| 530 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | | +| 531 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | (待填) | | | +| 532 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | | +| 533 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | (待填) | | | +| 534 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | | +| 535 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | | +| 536 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | (待填) | | | +| 537 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | (待填) | | | +| 538 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | (待填) | | | +| 539 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | | +| 540 | 561 | "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 | (待填) | | | +| 541 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | | +| 542 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | | +| 543 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | | +| 544 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | | +| 545 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | | +| 546 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | | +| 547 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | | +| 548 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | | +| 549 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | | +| 550 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | | +| 551 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | +| 552 | 625 | "边陲矿工:边缘格产出 +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() | (待填) | | | +| 553 | 633 | "封印回响:标中计数≥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" | (待填) | | | +| 554 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | | +| 555 | 644 | "封印桩:标中本轮 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" | (待填) | | | +| 556 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | | +| 557 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | | +| 558 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | | +| 559 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | | +| 560 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | | +| 561 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | | +| 562 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | | +| 563 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(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 | (待填) | | | +| 564 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | | +| 565 | 692 | "石肤:受伤 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 | (待填) | | | +| 566 | 696 | "背水:生命≤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() | (待填) | | | +| 567 | 701 | "破封一击:挖开带封印危险格 +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() | (待填) | | | +| 568 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | | +| 569 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | | +| 570 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | | +| 571 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | | +| 572 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | | +| 573 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | +| 574 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | | +| 575 | 730 | "处刑台:敌血量≤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() | (待填) | | | +| 576 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | | +| 577 | 737 | "血税:双向同局账本(踩陷阱−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" | (待填) | | | +| 578 | 742 | "淬毒刃纹(迁入):克制正面(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() | (待填) | | | +| 579 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | | +| 580 | 750 | "零持有订阅=零命中(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() | (待填) | | | +| 581 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | | +| 582 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | | +| 583 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | | + +## S74 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 584 | 65 | "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 | (待填) | | | +| 585 | 67 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | (待填) | | | + +## S75 _smoke_reward_identity_unit(行 83–116 · 10 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 586 | 83 | "roll_board 受理" | reward_economy.roll_board(eligible) | (待填) | | | +| 587 | 84 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | (待填) | | | +| 588 | 95 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | (待填) | | | +| 589 | 97 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | (待填) | | | +| 590 | 101 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | (待填) | | | +| 591 | 102 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | (待填) | | | +| 592 | 106 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | (待填) | | | +| 593 | 111 | "清图奖励 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]) | (待填) | | | +| 594 | 113 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | (待填) | | | +| 595 | 116 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | (待填) | | | -## S76 _smoke_power_interval(行 240–298 · 5 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) +## S76 _smoke_board_generator(行 135–210 · 24 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 617 | 240 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | (待填) | | | -| 618 | 282 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | (待填) | | | -| 619 | 283 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | (待填) | | | -| 620 | 297 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | (待填) | | | -| 621 | 298 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | (待填) | | | +| 596 | 135 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | (待填) | | | +| 597 | 141 | "BoardGen 精确命中 (2,3)(覆盖批键域补齐)" | int(exact_row.get("node_depth", -1)) == 3 | (待填) | | | +| 598 | 151 | "R1 ceil 能力不失(夹具摘除 (2,3)→向上取整到 4·偏难口径)" | int(ceil_row.get("node_depth", -1)) == 4 | (待填) | | | +| 599 | 153 | "夹具表原样还原(行数与解析回到覆盖批现值)" | _db.board_gen.rows.size() == rows_before and int(_db.resolve_board_gen(2, 3).get("node_depth", -1)) == 3 | (待填) | | | +| 600 | 156 | "L%d 生成产物非空" ↩ % L | b1 != null | (待填) | | | +| 601 | 159 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | (待填) | | | +| 602 | 160 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row) | (待填) | | | +| 603 | 167 | "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" | (待填) | | | +| 604 | 169 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | (待填) | | | +| 605 | 170 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | (待填) | | | +| 606 | 171 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | (待填) | | | +| 607 | 173 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | (待填) | | | +| 608 | 175 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | (待填) | | | +| 609 | 177 | "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) | (待填) | | | +| 610 | 179 | "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) | (待填) | | | +| 611 | 181 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | (待填) | | | +| 612 | 185 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | (待填) | | | +| 613 | 187 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | (待填) | | | +| 614 | 193 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | (待填) | | | +| 615 | 195 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | (待填) | | | +| 616 | 197 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | (待填) | | | +| 617 | 199 | "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 | (待填) | | | +| 618 | 207 | "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) | (待填) | | | +| 619 | 209 | "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", "")) != "" | (待填) | | | -## S77 _init(行 17–17 · 1 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S77 _smoke_power_interval(行 240–298 · 5 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 622 | 17 | "数值表加载" | false | (待填) | | | +| 620 | 240 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | (待填) | | | +| 621 | 282 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | (待填) | | | +| 622 | 283 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | (待填) | | | +| 623 | 297 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | (待填) | | | +| 624 | 298 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | (待填) | | | -## S78 _test_generate_shape_and_mines(行 42–55 · 5 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S78 _init(行 17–17 · 1 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 623 | 42 | "生成返回棋盘" | false | (待填) | | | -| 624 | 47 | "棋盘尺寸与 board_gen 表一致(%dx%d)" ↩ % [want_w, want_h] | b.width == want_w and b.height == want_h | (待填) | | | -| 625 | 49 | "雷数非零且不超过总格数" | b.mines.size() > 0 and b.mines.size() < b.width * b.height | (待填) | | | -| 626 | 54 | "全部危险格在界内" | all_in_bounds | (待填) | | | -| 627 | 55 | "生成期指标集已落盘(metrics 非空)" | not b.metrics.is_empty() | (待填) | | | +| 625 | 17 | "数值表加载" | false | (待填) | | | -## S79 _test_target_mine_count_single_source(行 75–83 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S79 _test_generate_shape_and_mines(行 42–55 · 5 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 628 | 75 | "target_mine_count 与原式同参同值(%d 档对拍全等)〔wd14〕" ↩ % checked | checked > 0 and bad == 0 | (待填) | | | -| 629 | 78 | "生成返回棋盘(雷数单点段)" | false | (待填) | | | -| 630 | 81 | "产物断言读数 = target_mine_count 同源(metrics.target_mines)〔wd14〕" | int(b.metrics.get("target_mines", -1)) == BoardGeneratorScript.target_mine_count({ "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density)}) | (待填) | | | +| 626 | 42 | "生成返回棋盘" | false | (待填) | | | +| 627 | 47 | "棋盘尺寸与 board_gen 表一致(%dx%d)" ↩ % [want_w, want_h] | b.width == want_w and b.height == want_h | (待填) | | | +| 628 | 49 | "雷数非零且不超过总格数" | b.mines.size() > 0 and b.mines.size() < b.width * b.height | (待填) | | | +| 629 | 54 | "全部危险格在界内" | all_in_bounds | (待填) | | | +| 630 | 55 | "生成期指标集已落盘(metrics 非空)" | not b.metrics.is_empty() | (待填) | | | -## S80 _test_digits_equal_neighbor_mine_count(行 89–103 · 2 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S80 _test_target_mine_count_single_source(行 75–83 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 631 | 89 | "生成返回棋盘(数字段)" | false | (待填) | | | -| 632 | 103 | "每个安全格的数字恒等于八邻雷数(全盘逐格复算,错 %d 格)" ↩ % bad | bad == 0 | (待填) | | | +| 631 | 75 | "target_mine_count 与原式同参同值(%d 档对拍全等)〔wd14〕" ↩ % checked | checked > 0 and bad == 0 | (待填) | | | +| 632 | 78 | "生成返回棋盘(雷数单点段)" | false | (待填) | | | +| 633 | 81 | "产物断言读数 = target_mine_count 同源(metrics.target_mines)〔wd14〕" | int(b.metrics.get("target_mines", -1)) == BoardGeneratorScript.target_mine_count({ "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density)}) | (待填) | | | -## S81 _test_same_seed_bit_identical(行 110–113 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S81 _test_digits_equal_neighbor_mine_count(行 89–103 · 2 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 633 | 110 | "生成返回棋盘(确定性段)" | false | (待填) | | | -| 634 | 112 | "同种子生成的雷集逐位一致" | str(a.mines) == str(b.mines) | (待填) | | | -| 635 | 113 | "同种子生成的数字集逐位一致" | str(a.digits) == str(b.digits) | (待填) | | | +| 634 | 89 | "生成返回棋盘(数字段)" | false | (待填) | | | +| 635 | 103 | "每个安全格的数字恒等于八邻雷数(全盘逐格复算,错 %d 格)" ↩ % bad | bad == 0 | (待填) | | | -## S82 _test_clearable_and_inferability_selfconsistent(行 119–126 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S82 _test_same_seed_bit_identical(行 110–113 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 636 | 119 | "生成返回棋盘(可推理段)" | false | (待填) | | | -| 637 | 122 | "可推理性度量返回非空指标集" | not m.is_empty() | (待填) | | | -| 638 | 125 | "清图判定与安全格总数同在(安全格 %d 个,可清=%s)" ↩ % [safe_total, str(clearable)] | safe_total > 0 | (待填) | | | +| 636 | 110 | "生成返回棋盘(确定性段)" | false | (待填) | | | +| 637 | 112 | "同种子生成的雷集逐位一致" | str(a.mines) == str(b.mines) | (待填) | | | +| 638 | 113 | "同种子生成的数字集逐位一致" | str(a.digits) == str(b.digits) | (待填) | | | -## S83 _test_all_links_valid(行 59–59 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S83 _test_clearable_and_inferability_selfconsistent(行 119–126 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 639 | 59 | "全对齿的技能池零错(正向面——实际报错 %s)" ↩ % str(_cats(errs)) | errs.is_empty() | (待填) | | | +| 639 | 119 | "生成返回棋盘(可推理段)" | false | (待填) | | | +| 640 | 122 | "可推理性度量返回非空指标集" | not m.is_empty() | (待填) | | | +| 641 | 125 | "清图判定与安全格总数同在(安全格 %d 个,可清=%s)" ↩ % [safe_total, str(clearable)] | safe_total > 0 | (待填) | | | -## S84 _test_dangling_skill_id(行 70–72 · 2 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S84 _test_all_links_valid(行 59–59 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 640 | 70 | "池内悬空 skill_id 被报(跨表护栏不静默放过)" | not errs.is_empty() | (待填) | | | -| 641 | 71 | "悬空项的错误类别可定位(category 非空)" | not errs.is_empty() and String((errs[0] as Dictionary).get("category", "")) != "" | (待填) | | | +| 642 | 59 | "全对齿的技能池零错(正向面——实际报错 %s)" ↩ % str(_cats(errs)) | errs.is_empty() | (待填) | | | -## S85 _test_cast_side_mismatch(行 84–84 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S85 _test_dangling_skill_id(行 70–72 · 2 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 642 | 84 | "buff 挂主角侧状态被报(施放类 side 守卫)" | _cats(errs).has("cast_side") | (待填) | | | +| 643 | 70 | "池内悬空 skill_id 被报(跨表护栏不静默放过)" | not errs.is_empty() | (待填) | | | +| 644 | 71 | "悬空项的错误类别可定位(category 非空)" | not errs.is_empty() and String((errs[0] as Dictionary).get("category", "")) != "" | (待填) | | | -## S86 _test_band_hit(行 58–62 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S86 _test_cast_side_mismatch(行 84–84 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 643 | 58 | "带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕" | hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -| 644 | 61 | "精确落带:请求深度 5 命中带 5〔fv1〕" | exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 645 | 84 | "buff 挂主角侧状态被报(施放类 side 守卫)" | _cats(errs).has("cast_side") | (待填) | | | -## S87 _test_rowset_order_preserved(行 67–72 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S87 _test_band_hit(行 58–62 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 645 | 67 | "行集返回同带全部行(2 行)〔fv2〕" | rows.size() == 2 | (待填) | | | -| 646 | 71 | "行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕" | ids == ["goblin", "bugbear"] | (待填) | | | +| 646 | 58 | "带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕" | hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 647 | 61 | "精确落带:请求深度 5 命中带 5〔fv1〕" | exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -## S88 _test_top_band_clamp(行 78–87 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S88 _test_rowset_order_preserved(行 67–72 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 647 | 78 | "越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕" | rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -| 648 | 84 | "越顶带发 warning resolve_depth_clamp〔fv3〕" | clamp_hit | (待填) | | | -| 649 | 87 | "同键重复请求警告去重(四元组注册表)〔fv3〕" | _diags.size() == warn_before | (待填) | | | +| 648 | 67 | "行集返回同带全部行(2 行)〔fv2〕" | rows.size() == 2 | (待填) | | | +| 649 | 71 | "行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕" | ids == ["goblin", "bugbear"] | (待填) | | | -## S89 _test_row_kind_routing(行 93–95 · 1 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S89 _test_top_band_clamp(行 78–87 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 650 | 93 | "行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕" | battle_rows.size() == 2 and boss_rows.size() == 1 and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief" | (待填) | | | +| 650 | 78 | "越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕" | rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 651 | 84 | "越顶带发 warning resolve_depth_clamp〔fv3〕" | clamp_hit | (待填) | | | +| 652 | 87 | "同键重复请求警告去重(四元组注册表)〔fv3〕" | _diags.size() == warn_before | (待填) | | | -## S90 _test_request_oob(行 101–107 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S90 _test_row_kind_routing(行 93–95 · 1 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 651 | 101 | "关卡序号 <1 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -| 652 | 104 | "节点深度 <0 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -| 653 | 107 | "row_kind 非法 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 653 | 93 | "行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕" | battle_rows.size() == 2 and boss_rows.size() == 1 and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief" | (待填) | | | -## S91 _test_difficulty_clamp(行 119–122 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S91 _test_request_oob(行 101–107 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 654 | 119 | "难度乘数越域被 clamp:hp 99 → 上限 10" | absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001 | (待填) | | | -| 655 | 120 | "难度乘数越域被 clamp:atk 0.01 → 下限 0.1" | absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001 | (待填) | | | -| 656 | 121 | "未登记坐标返回空修饰(零值不伪装成 ×1)" | _resolver.resolve_difficulty_modifiers(3, 3).is_empty() | (待填) | | | +| 654 | 101 | "关卡序号 <1 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 655 | 104 | "节点深度 <0 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 656 | 107 | "row_kind 非法 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -## S92 _test_field_in_table(行 106–114 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S92 _test_difficulty_clamp(行 119–122 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 657 | 106 | "N 落表:level.tres 读数 2(实际 %d)〔fv6〕" ↩ % n_in_table | n_in_table == 2 | (待填) | | | -| 658 | 110 | "N=1 合法:validate 零错〔fv6〕" | t.validate().is_empty() | (待填) | | | -| 659 | 112 | "N 越域 0:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | -| 660 | 114 | "N 越域 -1:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | +| 657 | 119 | "难度乘数越域被 clamp:hp 99 → 上限 10" | absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001 | (待填) | | | +| 658 | 120 | "难度乘数越域被 clamp:atk 0.01 → 下限 0.1" | absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001 | (待填) | | | +| 659 | 121 | "未登记坐标返回空修饰(零值不伪装成 ×1)" | _resolver.resolve_difficulty_modifiers(3, 3).is_empty() | (待填) | | | -## S93 _test_layer_distance(行 131–133 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S93 _test_field_in_table(行 106–114 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 661 | 131 | "层距 N 内可见:锚层 1 取节点 4(层距 2)〔fv1〕" | vis.has(4) | (待填) | | | -| 662 | 132 | "层距 N+1 不可见:锚层 1 排节点 5(层距 3)〔fv1〕" | not vis.has(5) | (待填) | | | -| 663 | 133 | "层距 N+2 不可见:锚层 1 排节点 6(层距 4)〔fv1〕" | not vis.has(6) | (待填) | | | +| 660 | 106 | "N 落表:level.tres 读数 2(实际 %d)〔fv6〕" ↩ % n_in_table | n_in_table == 2 | (待填) | | | +| 661 | 110 | "N=1 合法:validate 零错〔fv6〕" | t.validate().is_empty() | (待填) | | | +| 662 | 112 | "N 越域 0:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | +| 663 | 114 | "N 越域 -1:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | -## S94 _test_candidate_exempt(行 142–143 · 2 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S94 _test_layer_distance(行 131–133 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 664 | 142 | "候选恒显:跨层跳边候选(层距 N+1)仍在可见集〔fv4〕" | vis.has(3) | (待填) | | | -| 665 | 143 | "候选豁免不改层规则:同锚点非候选节点 2(层距 4)不可见〔fv4〕" | not vis.has(2) | (待填) | | | +| 664 | 131 | "层距 N 内可见:锚层 1 取节点 4(层距 2)〔fv1〕" | vis.has(4) | (待填) | | | +| 665 | 132 | "层距 N+1 不可见:锚层 1 排节点 5(层距 3)〔fv1〕" | not vis.has(5) | (待填) | | | +| 666 | 133 | "层距 N+2 不可见:锚层 1 排节点 6(层距 4)〔fv1〕" | not vis.has(6) | (待填) | | | -## S95 _test_boss_exempt(行 152–155 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S95 _test_candidate_exempt(行 142–143 · 2 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 666 | 152 | "入口停点锚层 0:层距 N 内(节点 2)可见〔fv1〕" | vis.has(2) | (待填) | | | -| 667 | 153 | "入口停点锚层 0:层距 N+2(节点 4)不可见〔fv1〕" | not vis.has(4) | (待填) | | | -| 668 | 154 | "Boss 恒显:层距 6 的末位 Boss 仍在可见集〔fv3〕" | vis.has(7) | (待填) | | | -| 669 | 155 | "Boss 豁免不扩散:同层距的非 Boss 节点 6 不在可见集〔fv3〕" | not vis.has(6) | (待填) | | | +| 667 | 142 | "候选恒显:跨层跳边候选(层距 N+1)仍在可见集〔fv4〕" | vis.has(3) | (待填) | | | +| 668 | 143 | "候选豁免不改层规则:同锚点非候选节点 2(层距 4)不可见〔fv4〕" | not vis.has(2) | (待填) | | | -## S96 _test_single_circle(行 165–167 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S96 _test_boss_exempt(行 152–155 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 670 | 165 | "单圈回雾:已过远端节点 0(层距 5)不因 visited 豁免〔fv7〕" | not vis.has(0) | (待填) | | | -| 671 | 166 | "单圈回雾:已过中距节点 2(层距 4)不可见〔fv7〕" | not vis.has(2) | (待填) | | | -| 672 | 167 | "单圈只按锚层取窗:已过近端节点 4(层距 2)仍可见〔fv7〕" | vis.has(4) | (待填) | | | +| 669 | 152 | "入口停点锚层 0:层距 N 内(节点 2)可见〔fv1〕" | vis.has(2) | (待填) | | | +| 670 | 153 | "入口停点锚层 0:层距 N+2(节点 4)不可见〔fv1〕" | not vis.has(4) | (待填) | | | +| 671 | 154 | "Boss 恒显:层距 6 的末位 Boss 仍在可见集〔fv3〕" | vis.has(7) | (待填) | | | +| 672 | 155 | "Boss 豁免不扩散:同层距的非 Boss 节点 6 不在可见集〔fv3〕" | not vis.has(6) | (待填) | | | -## S97 _test_single_chain_entry(行 176–179 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S97 _test_single_circle(行 165–167 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 673 | 176 | "单链退化形态:入口锚层 0,句柄 2 可见〔fv1〕" | vis.has(2) | (待填) | | | -| 674 | 177 | "单链退化形态:层距 N+1(句柄 3)不可见〔fv1〕" | not vis.has(3) | (待填) | | | -| 675 | 178 | "单链编号空间 = 图句柄 id:Boss 以句柄 6 入集(链上层距 4 的战斗节点 4 入雾)〔fv1〕" | vis.has(6) and not vis.has(4) | (待填) | | | +| 673 | 165 | "单圈回雾:已过远端节点 0(层距 5)不因 visited 豁免〔fv7〕" | not vis.has(0) | (待填) | | | +| 674 | 166 | "单圈回雾:已过中距节点 2(层距 4)不可见〔fv7〕" | not vis.has(2) | (待填) | | | +| 675 | 167 | "单圈只按锚层取窗:已过近端节点 4(层距 2)仍可见〔fv7〕" | vis.has(4) | (待填) | | | -## S98 _test_n_from_table(行 188–196 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S98 _test_single_chain_entry(行 176–179 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 676 | 188 | "N 读表:N=1 时节点 4(层距 2)出窗〔fv6〕" | not r.type_visible_ids().has(4) | (待填) | | | -| 677 | 190 | "N 读表:N=3 时节点 5(层距 3)入窗〔fv6〕" | r.type_visible_ids().has(5) | (待填) | | | -| 678 | 196 | "N 读表:N ≥ 全图层高 → 全部非豁免节点可见〔fv6〕" | all_in | (待填) | | | +| 676 | 176 | "单链退化形态:入口锚层 0,句柄 2 可见〔fv1〕" | vis.has(2) | (待填) | | | +| 677 | 177 | "单链退化形态:层距 N+1(句柄 3)不可见〔fv1〕" | not vis.has(3) | (待填) | | | +| 678 | 178 | "单链编号空间 = 图句柄 id:Boss 以句柄 6 入集(链上层距 4 的战斗节点 4 入雾)〔fv1〕" | vis.has(6) and not vis.has(4) | (待填) | | | -## S99 _test_resume_rebuild(行 234–235 · 1 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S99 _test_n_from_table(行 188–196 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 679 | 234 | "续玩重建同值:resume 路径与同停点直入 type_visible_ids() 逐位一致〔fv7〕" | resumed.type_visible_ids() == direct.type_visible_ids() | (待填) | | | +| 679 | 188 | "N 读表:N=1 时节点 4(层距 2)出窗〔fv6〕" | not r.type_visible_ids().has(4) | (待填) | | | +| 680 | 190 | "N 读表:N=3 时节点 5(层距 3)入窗〔fv6〕" | r.type_visible_ids().has(5) | (待填) | | | +| 681 | 196 | "N 读表:N ≥ 全图层高 → 全部非豁免节点可见〔fv6〕" | all_in | (待填) | | | -## S100 _smoke_table_migration(行 262–269 · 4 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S100 _test_resume_rebuild(行 234–235 · 1 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 680 | 262 | "shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕" | _db.shop.relic_base_price == 15.0 | (待填) | | | -| 681 | 263 | "shop.spend_discount 表值 == 迁移前字面量(0.8)〔wd11〕" | _db.shop.spend_discount == 0.8 | (待填) | | | -| 682 | 264 | "hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕" | _db.hero.display_name == "探险者" | (待填) | | | -| 683 | 268 | "spawn 后英雄单位 display_name 读表同值(探险者)〔wd16〕" | spawned and String(units.hero_unit().get("display_name", "")) == String(_db.hero.display_name) | (待填) | | | +| 682 | 234 | "续玩重建同值:resume 路径与同停点直入 type_visible_ids() 逐位一致〔fv7〕" | resumed.type_visible_ids() == direct.type_visible_ids() | (待填) | | | -## S101 _smoke_generated_coverage(行 283–320 · 6 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S101 _smoke_table_migration(行 262–269 · 4 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 684 | 283 | "L%d 参数档缺行(覆盖断言无源可扫)〔v11〕" ↩ % L | false | (待填) | | | -| 685 | 290 | "L%d 种子 %d 生成图为空(覆盖扫描面空转)〔v11〕" ↩ % [L, seed_val] | false | (待填) | | | -| 686 | 317 | "生成图覆盖扫描面非空(三关参数档×种子 %d–%d 共 %d 个节点读数)〔v11〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, scanned] | scanned > 0 | (待填) | | | -| 687 | 318 | "生成图每个 battle/boss 节点深度的敌侧解析落带 ≥ 请求深度(band_of 非 -1·顶 clamp 消解禁)〔v11〕" | enemy_ok | (待填) | | | -| 688 | 319 | "生成图每个节点深度在 board_gen 精确命中(非空且 node_depth 等值)〔v11〕" | board_ok | (待填) | | | -| 689 | 320 | "多终点 Boss 各终点解析带键两两互异(L2/L3×20 种子)〔v11〕" | boss_band_ok | (待填) | | | +| 683 | 262 | "shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕" | _db.shop.relic_base_price == 15.0 | (待填) | | | +| 684 | 263 | "shop.spend_discount 表值 == 迁移前字面量(0.8)〔wd11〕" | _db.shop.spend_discount == 0.8 | (待填) | | | +| 685 | 264 | "hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕" | _db.hero.display_name == "探险者" | (待填) | | | +| 686 | 268 | "spawn 后英雄单位 display_name 读表同值(探险者)〔wd16〕" | spawned and String(units.hero_unit().get("display_name", "")) == String(_db.hero.display_name) | (待填) | | | -## S102 _smoke_level(行 331–387 · 17 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S102 _smoke_generated_coverage(行 283–320 · 6 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 690 | 331 | "L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes1.size()] | not nodes1.is_empty() | (待填) | | | -| 691 | 334 | "关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | (待填) | | | -| 692 | 342 | "L1 通关路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)" | _path_in_graph(nodes1, win.visited_ids) | (待填) | | | -| 693 | 343 | "L1 通关路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" ↩ % [win.visited_ids.size(), path_battles, int(win.battles)] | path_battles == int(win.battles) and path_battles >= 2 | (待填) | | | -| 694 | 353 | "L1 终点 Boss 深度 = 参数档层高 %d 且通关路径末节点落该深度(实测终点深度 %d·末句柄 %d)" ↩ % [int(params1.get("layers", -1)), boss_depth1, last_id] | boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1 | (待填) | | | -| 695 | 355 | "L1 终点恰一个(大岔口自 L2 起的图侧投影)" | _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1 | (待填) | | | -| 696 | 356 | "L1 生成图含岔口(出度 ≥2 节点 %d 处)" ↩ % _fork_ids(nodes1).size() | _fork_ids(nodes1).size() >= 1 | (待填) | | | -| 697 | 357 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | (待填) | | | -| 698 | 363 | "恢复点公式无源(现行图入口后一位无可直达的恢复候选)" | false | (待填) | | | -| 699 | 366 | "恢复点公式·缺口充分档:回复 = floor(生效max_hp×%d%%) = %d(生效 max %d,实测 %d)" ↩ % [pct1, expect_pct, int(rest_wide["max"]), int(rest_wide["healed"])] | int(rest_wide["healed"]) == expect_pct and expect_pct > 0 | (待填) | | | -| 700 | 370 | "恢复点公式缺口封顶档无源(同图二次驱动未达恢复候选)" | false | (待填) | | | -| 701 | 372 | "恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" ↩ % [int(rest_narrow["healed"]), expect_pct] | int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2 | (待填) | | | -| 702 | 376 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | (待填) | | | -| 703 | 377 | "败北节点记录在域内(节点 %d,图节点数 %d)" ↩ % [lose.defeat_node_index, nodes1.size()] | lose.defeat_node_index >= 0 and lose.defeat_node_index < nodes1.size() | (待填) | | | -| 704 | 382 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | (待填) | | | -| 705 | 383 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | (待填) | | | -| 706 | 385 | "模拟后 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) | (待填) | | | +| 687 | 283 | "L%d 参数档缺行(覆盖断言无源可扫)〔v11〕" ↩ % L | false | (待填) | | | +| 688 | 290 | "L%d 种子 %d 生成图为空(覆盖扫描面空转)〔v11〕" ↩ % [L, seed_val] | false | (待填) | | | +| 689 | 317 | "生成图覆盖扫描面非空(三关参数档×种子 %d–%d 共 %d 个节点读数)〔v11〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, scanned] | scanned > 0 | (待填) | | | +| 690 | 318 | "生成图每个 battle/boss 节点深度的敌侧解析落带 ≥ 请求深度(band_of 非 -1·顶 clamp 消解禁)〔v11〕" | enemy_ok | (待填) | | | +| 691 | 319 | "生成图每个节点深度在 board_gen 精确命中(非空且 node_depth 等值)〔v11〕" | board_ok | (待填) | | | +| 692 | 320 | "多终点 Boss 各终点解析带键两两互异(L2/L3×20 种子)〔v11〕" | boss_band_ok | (待填) | | | -## S103 _smoke_level_graph(行 442–762 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S103 _smoke_level(行 331–387 · 17 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 707 | 442 | "LevelTable 行级校验零错误(图合法性移交 validate_graph)" | _db.level.validate().is_empty() | (待填) | | | -| 708 | 446 | "L2 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes2.size()] | not nodes2.is_empty() | (待填) | | | -| 709 | 449 | "L2 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes2.size(), int(params2.get("layers", -1)), _nodes_by_id(nodes2).size()] | nodes2.size() >= int(params2.get("layers", -1)) + 1 and _nodes_by_id(nodes2).size() == nodes2.size() | (待填) | | | -| 710 | 451 | "L2 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount["battle"], tcount["boss"], tcount["reward"], tcount["rest"], tcount["shop"], nodes2.size()] | int(tcount["battle"]) + int(tcount["boss"]) + int(tcount["reward"]) + int(tcount["rest"]) + int(tcount["shop"]) == nodes2.size() | (待填) | | | -| 711 | 453 | "L2 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount["battle"], int(params2.get("battle_floor", -1)), tcount["boss"], int(params2.get("exit_count", -1))] | int(tcount["battle"]) >= int(params2.get("battle_floor", -1)) and int(tcount["boss"]) == int(params2.get("exit_count", -1)) | (待填) | | | -| 712 | 455 | "L2 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount["rest"], tcount["reward"], tcount["shop"]] | int(tcount["rest"]) >= 1 and int(tcount["reward"]) >= 1 and int(tcount["shop"]) >= 1 | (待填) | | | -| 713 | 457 | "L2 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch | branch >= 1 | (待填) | | | -| 714 | 467 | "图校验正例:两岔合流小图零错误〔v1〕" | LT.validate_graph(good).is_empty() | (待填) | | | -| 715 | 470 | "图校验拒绝:句柄重复(图内唯一句柄)〔v1〕" | _has_err_category(LT.validate_graph(dup), "dup_key") | (待填) | | | -| 716 | 473 | "图校验正例:多入口合法(入口唯一判据随模板池退役)〔v1〕" | LT.validate_graph(dual).is_empty() | (待填) | | | -| 717 | 476 | "图校验拒绝:Boss 带出边(终点不可外出)〔v1〕" | _has_err_category(LT.validate_graph(boss_out), "structural") | (待填) | | | -| 718 | 479 | "图校验拒绝:回边 1→0(目标深度须严格大于源)〔v1〕" | _has_err_category(LT.validate_graph(cyc), "structural") | (待填) | | | -| 719 | 482 | "图校验拒绝:出边指向不存在句柄(悬空致命)〔v1〕" | _has_err_category(LT.validate_graph(dang), "dangling_next") | (待填) | | | -| 720 | 488 | "图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕" | _has_err_category(LT.validate_graph(orphan), "structural") | (待填) | | | -| 721 | 491 | "L2·满配 min 路径通关(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | (待填) | | | -| 722 | 496 | "L2 min 路径逐跳为图出边·末节点为 Boss(实测路径 %s·末句柄型别 %s)" ↩ % [str(win_min.visited_ids), n2_last_type] | _path_in_graph(nodes2, win_min.visited_ids) and n2_last_type == "boss" | (待填) | | | -| 723 | 498 | "L2 min 路径终点深度 = 参数档层高 %d(行进读数 %d——终点落最长路径步数)" ↩ % [int(params2.get("layers", -1)), int(win_min.boss_depth)] | int(win_min.boss_depth) == int(params2.get("layers", -1)) | (待填) | | | -| 724 | 500 | "L2 min 路径穿商店恰 1 家(路径句柄 %s 型别计数 = 1——必经恰一店的行进面)" ↩ % str(win_min.visited_ids) | _path_shop_count(nodes2, win_min.visited_ids) == 1 | (待填) | | | -| 725 | 502 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | (待填) | | | -| 726 | 504 | "L2·满配 max 路径(候选取最大句柄)通关(实测:%s·路径 %s)" ↩ % [win_max.result, str(win_max.visited_ids)] | win_max.result == "victory" | (待填) | | | -| 727 | 509 | "L2 max 路径逐跳为图出边·末节点为 Boss·穿商店恰 1 家" | _path_in_graph(nodes2, win_max.visited_ids) and n2_last_max_type == "boss" and _path_shop_count(nodes2, win_max.visited_ids) == 1 | (待填) | | | -| 728 | 512 | "L2 max 与 min 路径分岔(方向轴生效——两路终点句柄 %d / %d 不同)" ↩ % [n2_last, n2_last_max] | n2_last != n2_last_max and win_max.visited_ids != win_min.visited_ids | (待填) | | | -| 729 | 519 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;图 = 参数档 × 种子 %d)" ↩ % ACTIVE_GRAPH_SEED | runner.start_level(2, ACTIVE_GRAPH_SEED) | (待填) | | | -| 730 | 526 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | (待填) | | | -| 731 | 529 | "runner 消费面图 = 生成面图(节点数 %d/%d 且句柄集逐位相同)" ↩ % [rnodes.size(), nodes2.size()] | rnodes.size() == nodes2.size() and _ids_of_type(rnodes, "battle").size() + _ids_of_type(rnodes, "boss").size() + _ids_of_type(rnodes, "reward").size() \\ + _ids_of_type(rnodes, "rest").size() + _ids_of_type(rnodes, "shop").size() == nodes2.size() and _nodes_by_id(rnodes) == _nodes_by_id(nodes2) | (待填) | | | -| 732 | 537 | "入口选路口候选非空且全在图内(候选 %s·类型 %s)" ↩ % [str(cand), str(cand_types)] | not cand.is_empty() and not cand_types.has("<不在图>") | (待填) | | | -| 733 | 538 | "候选下一跳类型两两互异(岔口去路类型不重复契约——实测 %s)" ↩ % str(cand_types) | cand_types.size() == _uniq(cand_types).size() | (待填) | | | -| 734 | 545 | "候选外句柄 %d 拒绝且仍待选(拒绝语义)" ↩ % outside_id | outside_id >= 0 and not runner.pick_next_node(outside_id) and runner.awaiting_choice() | (待填) | | | -| 735 | 555 | "入口岔口含奖励位与战斗位(句柄 %d/%d——候选型别可定位)" ↩ % [reward_cand, battle_cand] | reward_cand >= 0 and battle_cand >= 0 | (待填) | | | -| 736 | 556 | "选奖励候选 %d 受理" ↩ % reward_cand | runner.pick_next_node(reward_cand) | (待填) | | | -| 737 | 557 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | (待填) | | | -| 738 | 559 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = 该点图出边 %s" ↩ % str(reward_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(reward_outs) | (待填) | | | -| 739 | 566 | "选下一跳战斗候选 %d 开战" ↩ % next_battle | next_battle >= 0 and runner.pick_next_node(next_battle) and runner.current_battle() != null | (待填) | | | -| 740 | 591 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | (待填) | | | -| 741 | 592 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | (待填) | | | -| 742 | 593 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | (待填) | | | -| 743 | 597 | "跨表覆盖扫描面非空:L2 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov2["nodes"]) | int(cov2["nodes"]) >= 1 | (待填) | | | -| 744 | 598 | "L2 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov2["enemy_bad"]) | int(cov2["enemy_bad"]) == 0 | (待填) | | | -| 745 | 599 | "L2 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov2["board_bad"]) | int(cov2["board_bad"]) == 0 | (待填) | | | -| 746 | 607 | "巨蜘蛛首遇带(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") | (待填) | | | -| 747 | 617 | "L2 全部终点深度 %s 的 Boss 行集均 = {drider} 恰一行且首行魔法甲(护甲类型重配批:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % str(l2_terms) | l2_boss_ok | (待填) | | | -| 748 | 624 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | (待填) | | | -| 749 | 628 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | (待填) | | | -| 750 | 629 | "首批混编新行 (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 | (待填) | | | -| 751 | 636 | "L3 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes3.size()] | not nodes3.is_empty() | (待填) | | | -| 752 | 639 | "L3 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes3.size(), int(params3.get("layers", -1)), _nodes_by_id(nodes3).size()] | nodes3.size() >= int(params3.get("layers", -1)) + 1 and _nodes_by_id(nodes3).size() == nodes3.size() | (待填) | | | -| 753 | 641 | "L3 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount3["battle"], tcount3["boss"], tcount3["reward"], tcount3["rest"], tcount3["shop"], nodes3.size()] | int(tcount3["battle"]) + int(tcount3["boss"]) + int(tcount3["reward"]) + int(tcount3["rest"]) + int(tcount3["shop"]) == nodes3.size() | (待填) | | | -| 754 | 643 | "L3 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount3["battle"], int(params3.get("battle_floor", -1)), tcount3["boss"], int(params3.get("exit_count", -1))] | int(tcount3["battle"]) >= int(params3.get("battle_floor", -1)) and int(tcount3["boss"]) == int(params3.get("exit_count", -1)) | (待填) | | | -| 755 | 645 | "L3 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount3["rest"], tcount3["reward"], tcount3["shop"]] | int(tcount3["rest"]) >= 1 and int(tcount3["reward"]) >= 1 and int(tcount3["shop"]) >= 1 | (待填) | | | -| 756 | 647 | "L3 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch3 | branch3 >= 1 | (待填) | | | -| 757 | 648 | "L3 入口数 %d = 参数档 entry_count · 终点数 %d = exit_count ≥2(多终点大岔口自 L2 起)" ↩ % [_entry_ids(nodes3).size(), _terminal_ids(nodes3).size()] | _entry_ids(nodes3).size() == int(params3.get("entry_count", -1)) and _terminal_ids(nodes3).size() == int(params3.get("exit_count", -1)) and _terminal_ids(nodes3).size() >= 2 | (待填) | | | -| 758 | 652 | "跨表覆盖扫描面非空:L3 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov3["nodes"]) | int(cov3["nodes"]) >= 1 | (待填) | | | -| 759 | 653 | "L3 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov3["enemy_bad"]) | int(cov3["enemy_bad"]) == 0 | (待填) | | | -| 760 | 654 | "L3 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov3["board_bad"]) | int(cov3["board_bad"]) == 0 | (待填) | | | -| 761 | 661 | "灰矮人首遇带(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") | (待填) | | | -| 762 | 672 | "L3 全部终点深度 %s 的 Boss 行集均 = {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % str(l3_terms) | l3_boss_ok | (待填) | | | -| 763 | 695 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | (待填) | | | -| 764 | 696 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | (待填) | | | -| 765 | 706 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | (待填) | | | -| 766 | 708 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | (待填) | | | -| 767 | 709 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,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" | (待填) | | | -| 768 | 710 | "混编各行行值独立投影(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 | (待填) | | | -| 769 | 714 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | (待填) | | | -| 770 | 719 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | | -| 771 | 726 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | | -| 772 | 732 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | | -| 773 | 737 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | | -| 774 | 739 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | | -| 775 | 761 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | | -| 776 | 762 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | | - -## S104 _smoke_gp_wallet(行 799–835 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 777 | 799 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | | -| 778 | 809 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | | -| 779 | 812 | "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 | (待填) | | | -| 780 | 817 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | | -| 781 | 819 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | | -| 782 | 820 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | | -| 783 | 827 | "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)}) | (待填) | | | -| 784 | 832 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | | -| 785 | 835 | "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 | (待填) | | | - -## S105 _smoke_gp_escrow(行 873–937 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 786 | 873 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | | -| 787 | 886 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | | -| 788 | 891 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | | -| 789 | 892 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | | -| 790 | 894 | "格面兑付计入 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 | (待填) | | | -| 791 | 900 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | | -| 792 | 904 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | | -| 793 | 909 | "破财压制 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 | (待填) | | | -| 794 | 919 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | | -| 795 | 931 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | | -| 796 | 936 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | | -| 797 | 937 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | | - -## S106 _smoke_shop_room(行 1003–1089 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 798 | 1003 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | | -| 799 | 1016 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | | -| 800 | 1020 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | | -| 801 | 1022 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | | -| 802 | 1024 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | | -| 803 | 1026 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | | -| 804 | 1029 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | | -| 805 | 1031 | "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 | (待填) | | | -| 806 | 1034 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | | -| 807 | 1035 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | | -| 808 | 1037 | "统一冒险加算区 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 | (待填) | | | -| 809 | 1039 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | | -| 810 | 1046 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | | -| 811 | 1047 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | | -| 812 | 1048 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | | -| 813 | 1049 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | | -| 814 | 1051 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | | -| 815 | 1052 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | | -| 816 | 1067 | "余额不足购买被拒(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 | (待填) | | | -| 817 | 1069 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | | -| 818 | 1072 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | | -| 819 | 1073 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | | -| 820 | 1077 | "战斗开始参数 = 基础表值 + 加算区(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 | (待填) | | | -| 821 | 1088 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | | -| 822 | 1089 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | | - -## S107 _smoke_shop_graph(行 1111–1168 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 823 | 1111 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | | -| 824 | 1116 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | | -| 825 | 1142 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | | -| 826 | 1144 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | | -| 827 | 1145 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | | -| 828 | 1146 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | | -| 829 | 1152 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | | -| 830 | 1164 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | | -| 831 | 1167 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | | - -## S108 _smoke_multi_terminal_settlement(行 1176–1223 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 832 | 1176 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | | -| 833 | 1188 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | | -| 834 | 1192 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | | -| 835 | 1199 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | | -| 836 | 1221 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | | - -## S109 _smoke_shop_pool(行 1258–1392 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 837 | 1258 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | | -| 838 | 1266 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | | -| 839 | 1272 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | | -| 840 | 1273 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | | -| 841 | 1275 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | | -| 842 | 1276 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | | -| 843 | 1277 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | | -| 844 | 1285 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | | -| 845 | 1295 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | | -| 846 | 1301 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | | -| 847 | 1308 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | | -| 848 | 1315 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | | -| 849 | 1323 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | | -| 850 | 1324 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | | -| 851 | 1325 | "加算区 +令牌档位件值(池件=奖励件本体——%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)) | (待填) | | | -| 852 | 1326 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | (待填) | | | -| 853 | 1338 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | | -| 854 | 1339 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | | -| 855 | 1341 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | | -| 856 | 1349 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | | -| 857 | 1360 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | | -| 858 | 1362 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | (待填) | | | -| 859 | 1365 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | (待填) | | | -| 860 | 1371 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | | -| 861 | 1376 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | | -| 862 | 1378 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | | -| 863 | 1379 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | | -| 864 | 1384 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | | -| 865 | 1392 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | | - -## S110 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 866 | 46 | "四检正例:多入口多终点图零错误〔v1〕" | _errs(multi).is_empty() | (待填) | | | -| 867 | 53 | "四检正例:含岔口单终点图零错误〔v1〕" | _errs(forked).is_empty() | (待填) | | | -| 868 | 56 | "四检正例:单链退化图零错误(不设必有岔口)〔v1〕" | _errs(chain).is_empty() | (待填) | | | - -## S111 _test_negative(行 63–89 · 11 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 869 | 63 | "负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕" | _has(_errs(non_boss_end), "structural", "终点 1 非 Boss") | (待填) | | | -| 870 | 66 | "负例:Boss 带出边被拒〔v1〕" | _has(_errs(boss_out), "structural", "Boss 节点 1 带出边") | (待填) | | | -| 871 | 68 | "负例:回边(目标深度不严格大于源)命中前进方向判据〔v1〕" | _has(_errs(back_edge), "structural", "违反前进方向") | (待填) | | | -| 872 | 71 | "负例:环(2→1 回边)被前进方向拦下〔v1〕" | _has(_errs(cycle), "structural", "违反前进方向") | (待填) | | | -| 873 | 77 | "判据②正证:两独立链(双入口双终点)不误伤·全员可达〔v1〕" | _errs(two_components).is_empty() | (待填) | | | -| 874 | 79 | "判据②兜底面:自环先被前进方向拦下(④ 与 ② 互斥的实证)〔v1〕" | _has(_errs(self_loop), "structural", "违反前进方向") | (待填) | | | -| 875 | 82 | "负例:悬空出边报 dangling_next〔v1〕" | _has(_errs(dangling), "dangling_next") | (待填) | | | -| 876 | 84 | "负例:句柄重复报 dup_key〔v1〕" | _has(_errs(dup), "dup_key") | (待填) | | | -| 877 | 86 | "负例:battle 节点缺深度被良构基底拒〔v1〕" | _has(_errs(no_depth), "structural", "缺深度") | (待填) | | | -| 878 | 87 | "负例:空节点集被拒〔v1〕" | _has(_errs([]), "structural", "图节点集为空") | (待填) | | | -| 879 | 89 | "负例:类型出域报 enum〔v1〕" | _has(_errs(bad_type), "enum") | (待填) | | | - -## S112 _test_scope_not_extended(行 97–102 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 880 | 97 | "范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕" | _errs(no_shop).is_empty() | (待填) | | | -| 881 | 99 | "范围反证:双入口合法(入口唯一判据已随模板池退役,个数归产出侧)〔v1〕" | _errs(two_entries).is_empty() | (待填) | | | -| 882 | 102 | "范围反证:节点缺 next 字段仍被良构基底拒〔v1〕" | _has(_errs(missing_key), "structural", "缺字段 next") | (待填) | | | - -## S113 _meta_pricing_and_buy(行 138–151 · 4 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 883 | 138 | "树购买受理(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 | (待填) | | | -| 884 | 141 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | (待填) | | | -| 885 | 148 | "余额不足 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 | (待填) | | | -| 886 | 151 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | (待填) | | | - -## S114 _meta_settle_and_workshop(行 168–341 · 42 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 887 | 168 | "新档首通 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 | (待填) | | | -| 888 | 171 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | (待填) | | | -| 889 | 173 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | (待填) | | | -| 890 | 181 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | (待填) | | | -| 891 | 187 | "重复杀第三轮恒定不递减(验收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)) | (待填) | | | -| 892 | 195 | "两关通关逐关归因: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)) | (待填) | | | -| 893 | 203 | "败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | (待填) | | | -| 894 | 210 | "条件件误购拒绝(购买件专属——契约 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") | (待填) | | | -| 895 | 213 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | (待填) | | | -| 896 | 215 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | (待填) | | | -| 897 | 218 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | (待填) | | | -| 898 | 220 | "条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | (待填) | | | -| 899 | 223 | "远程镐=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() | (待填) | | | -| 900 | 227 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | (待填) | | | -| 901 | 230 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | (待填) | | | -| 902 | 233 | "天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | (待填) | | | -| 903 | 236 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | (待填) | | | -| 904 | 241 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | (待填) | | | -| 905 | 244 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收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 | (待填) | | | -| 906 | 253 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | (待填) | | | -| 907 | 258 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | (待填) | | | -| 908 | 260 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | (待填) | | | -| 909 | 265 | "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 | (待填) | | | -| 910 | 270 | "通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | (待填) | | | -| 911 | 273 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | (待填) | | | -| 912 | 275 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | (待填) | | | -| 913 | 277 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | (待填) | | | -| 914 | 279 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | (待填) | | | -| 915 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | (待填) | | | -| 916 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | (待填) | | | -| 917 | 290 | "充能账本按装备 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 | (待填) | | | -| 918 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | (待填) | | | -| 919 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | (待填) | | | -| 920 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | (待填) | | | -| 921 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | (待填) | | | -| 922 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | (待填) | | | -| 923 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | (待填) | | | -| 924 | 321 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | (待填) | | | -| 925 | 323 | "主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入" | bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool" | (待填) | | | -| 926 | 327 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (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 | (待填) | | | -| 927 | 338 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | (待填) | | | -| 928 | 340 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | (待填) | | | - -## S115 _meta_injection(行 353–383 · 10 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 929 | 353 | "生命淬炼 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)) | (待填) | | | -| 930 | 355 | "攻/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 | (待填) | | | -| 931 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | (待填) | | | -| 932 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | (待填) | | | -| 933 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | (待填) | | | -| 934 | 366 | "首战 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)) | (待填) | | | -| 935 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | (待填) | | | -| 936 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | (待填) | | | -| 937 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(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()) | (待填) | | | -| 938 | 382 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | (待填) | | | - -## S116 _smoke_forge_axes(行 392–540 · 33 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 939 | 392 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | (待填) | | | -| 940 | 395 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | (待填) | | | -| 941 | 399 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 942 | 400 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | (待填) | | | -| 943 | 410 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | (待填) | | | -| 944 | 414 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | (待填) | | | -| 945 | 417 | "长剑二级购:扣 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 | (待填) | | | -| 946 | 424 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | (待填) | | | -| 947 | 429 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | (待填) | | | -| 948 | 432 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 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") | (待填) | | | -| 949 | 435 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | (待填) | | | -| 950 | 442 | "通道二:魔法护甲一次性解锁价 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") | (待填) | | | -| 951 | 447 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | (待填) | | | -| 952 | 452 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 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") | (待填) | | | -| 953 | 457 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | (待填) | | | -| 954 | 462 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | (待填) | | | -| 955 | 465 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | (待填) | | | -| 956 | 468 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | (待填) | | | -| 957 | 471 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | (待填) | | | -| 958 | 480 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | (待填) | | | -| 959 | 484 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | (待填) | | | -| 960 | 487 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | (待填) | | | -| 961 | 491 | "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") | (待填) | | | -| 962 | 497 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | (待填) | | | -| 963 | 499 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | (待填) | | | -| 964 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | (待填) | | | -| 965 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | (待填) | | | -| 966 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | (待填) | | | -| 967 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | (待填) | | | -| 968 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | | -| 969 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | (待填) | | | -| 970 | 529 | "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" | (待填) | | | -| 971 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | (待填) | | | - -## S117 _smoke_tree(行 548–621 · 25 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 972 | 548 | "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 | (待填) | | | -| 973 | 550 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | (待填) | | | -| 974 | 558 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | (待填) | | | -| 975 | 566 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | (待填) | | | -| 976 | 571 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | (待填) | | | -| 977 | 576 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | (待填) | | | -| 978 | 581 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | (待填) | | | -| 979 | 586 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | (待填) | | | -| 980 | 588 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | (待填) | | | -| 981 | 590 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | (待填) | | | -| 982 | 592 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | (待填) | | | -| 983 | 593 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | (待填) | | | -| 984 | 594 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | (待填) | | | -| 985 | 598 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | (待填) | | | -| 986 | 600 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | (待填) | | | -| 987 | 601 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | (待填) | | | -| 988 | 602 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | (待填) | | | -| 989 | 604 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | (待填) | | | -| 990 | 606 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | (待填) | | | -| 991 | 607 | "规则键投影=激活态效果键(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") | (待填) | | | -| 992 | 609 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | (待填) | | | -| 993 | 612 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | (待填) | | | -| 994 | 613 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | (待填) | | | -| 995 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | (待填) | | | -| 996 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | (待填) | | | +| 693 | 331 | "L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes1.size()] | not nodes1.is_empty() | (待填) | | | +| 694 | 334 | "关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | (待填) | | | +| 695 | 342 | "L1 通关路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)" | _path_in_graph(nodes1, win.visited_ids) | (待填) | | | +| 696 | 343 | "L1 通关路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" ↩ % [win.visited_ids.size(), path_battles, int(win.battles)] | path_battles == int(win.battles) and path_battles >= 2 | (待填) | | | +| 697 | 353 | "L1 终点 Boss 深度 = 参数档层高 %d 且通关路径末节点落该深度(实测终点深度 %d·末句柄 %d)" ↩ % [int(params1.get("layers", -1)), boss_depth1, last_id] | boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1 | (待填) | | | +| 698 | 355 | "L1 终点恰一个(大岔口自 L2 起的图侧投影)" | _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1 | (待填) | | | +| 699 | 356 | "L1 生成图含岔口(出度 ≥2 节点 %d 处)" ↩ % _fork_ids(nodes1).size() | _fork_ids(nodes1).size() >= 1 | (待填) | | | +| 700 | 357 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | (待填) | | | +| 701 | 363 | "恢复点公式无源(现行图入口后一位无可直达的恢复候选)" | false | (待填) | | | +| 702 | 366 | "恢复点公式·缺口充分档:回复 = floor(生效max_hp×%d%%) = %d(生效 max %d,实测 %d)" ↩ % [pct1, expect_pct, int(rest_wide["max"]), int(rest_wide["healed"])] | int(rest_wide["healed"]) == expect_pct and expect_pct > 0 | (待填) | | | +| 703 | 370 | "恢复点公式缺口封顶档无源(同图二次驱动未达恢复候选)" | false | (待填) | | | +| 704 | 372 | "恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" ↩ % [int(rest_narrow["healed"]), expect_pct] | int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2 | (待填) | | | +| 705 | 376 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | (待填) | | | +| 706 | 377 | "败北节点记录在域内(节点 %d,图节点数 %d)" ↩ % [lose.defeat_node_index, nodes1.size()] | lose.defeat_node_index >= 0 and lose.defeat_node_index < nodes1.size() | (待填) | | | +| 707 | 382 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | (待填) | | | +| 708 | 383 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | (待填) | | | +| 709 | 385 | "模拟后 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) | (待填) | | | -## S118 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) +## S104 _smoke_level_graph(行 442–762 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 997 | 633 | "先手压制:每关首战首轮窗命中(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 | (待填) | | | -| 998 | 635 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (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() | (待填) | | | -| 999 | 638 | "稳健拓荒:首轮窗内受击出盾、窗外不出(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() | (待填) | | | -| 1000 | 654 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | (待填) | | | -| 1001 | 657 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | -| 1002 | 663 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | -| 1003 | 685 | "锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕" | nc_hit != "" | (待填) | | | -| 1004 | 687 | "惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕" | nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷") | (待填) | | | -| 1005 | 691 | "守侧 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"}) | (待填) | | | -| 1006 | 703 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | (待填) | | | -| 1007 | 704 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | (待填) | | | -| 1008 | 708 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | -| 1009 | 713 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | -| 1010 | 717 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | -| 1011 | 721 | "守⑤ 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"}) | (待填) | | | -| 1012 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | (待填) | | | -| 1013 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | (待填) | | | -| 1014 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | | -| 1015 | 739 | "聚 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"}) | (待填) | | | -| 1016 | 748 | "聚 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"}) | (待填) | | | -| 1017 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | | +| 710 | 442 | "LevelTable 行级校验零错误(图合法性移交 validate_graph)" | _db.level.validate().is_empty() | (待填) | | | +| 711 | 446 | "L2 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes2.size()] | not nodes2.is_empty() | (待填) | | | +| 712 | 449 | "L2 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes2.size(), int(params2.get("layers", -1)), _nodes_by_id(nodes2).size()] | nodes2.size() >= int(params2.get("layers", -1)) + 1 and _nodes_by_id(nodes2).size() == nodes2.size() | (待填) | | | +| 713 | 451 | "L2 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount["battle"], tcount["boss"], tcount["reward"], tcount["rest"], tcount["shop"], nodes2.size()] | int(tcount["battle"]) + int(tcount["boss"]) + int(tcount["reward"]) + int(tcount["rest"]) + int(tcount["shop"]) == nodes2.size() | (待填) | | | +| 714 | 453 | "L2 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount["battle"], int(params2.get("battle_floor", -1)), tcount["boss"], int(params2.get("exit_count", -1))] | int(tcount["battle"]) >= int(params2.get("battle_floor", -1)) and int(tcount["boss"]) == int(params2.get("exit_count", -1)) | (待填) | | | +| 715 | 455 | "L2 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount["rest"], tcount["reward"], tcount["shop"]] | int(tcount["rest"]) >= 1 and int(tcount["reward"]) >= 1 and int(tcount["shop"]) >= 1 | (待填) | | | +| 716 | 457 | "L2 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch | branch >= 1 | (待填) | | | +| 717 | 467 | "图校验正例:两岔合流小图零错误〔v1〕" | LT.validate_graph(good).is_empty() | (待填) | | | +| 718 | 470 | "图校验拒绝:句柄重复(图内唯一句柄)〔v1〕" | _has_err_category(LT.validate_graph(dup), "dup_key") | (待填) | | | +| 719 | 473 | "图校验正例:多入口合法(入口唯一判据随模板池退役)〔v1〕" | LT.validate_graph(dual).is_empty() | (待填) | | | +| 720 | 476 | "图校验拒绝:Boss 带出边(终点不可外出)〔v1〕" | _has_err_category(LT.validate_graph(boss_out), "structural") | (待填) | | | +| 721 | 479 | "图校验拒绝:回边 1→0(目标深度须严格大于源)〔v1〕" | _has_err_category(LT.validate_graph(cyc), "structural") | (待填) | | | +| 722 | 482 | "图校验拒绝:出边指向不存在句柄(悬空致命)〔v1〕" | _has_err_category(LT.validate_graph(dang), "dangling_next") | (待填) | | | +| 723 | 488 | "图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕" | _has_err_category(LT.validate_graph(orphan), "structural") | (待填) | | | +| 724 | 491 | "L2·满配 min 路径通关(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | (待填) | | | +| 725 | 496 | "L2 min 路径逐跳为图出边·末节点为 Boss(实测路径 %s·末句柄型别 %s)" ↩ % [str(win_min.visited_ids), n2_last_type] | _path_in_graph(nodes2, win_min.visited_ids) and n2_last_type == "boss" | (待填) | | | +| 726 | 498 | "L2 min 路径终点深度 = 参数档层高 %d(行进读数 %d——终点落最长路径步数)" ↩ % [int(params2.get("layers", -1)), int(win_min.boss_depth)] | int(win_min.boss_depth) == int(params2.get("layers", -1)) | (待填) | | | +| 727 | 500 | "L2 min 路径穿商店恰 1 家(路径句柄 %s 型别计数 = 1——必经恰一店的行进面)" ↩ % str(win_min.visited_ids) | _path_shop_count(nodes2, win_min.visited_ids) == 1 | (待填) | | | +| 728 | 502 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | (待填) | | | +| 729 | 504 | "L2·满配 max 路径(候选取最大句柄)通关(实测:%s·路径 %s)" ↩ % [win_max.result, str(win_max.visited_ids)] | win_max.result == "victory" | (待填) | | | +| 730 | 509 | "L2 max 路径逐跳为图出边·末节点为 Boss·穿商店恰 1 家" | _path_in_graph(nodes2, win_max.visited_ids) and n2_last_max_type == "boss" and _path_shop_count(nodes2, win_max.visited_ids) == 1 | (待填) | | | +| 731 | 512 | "L2 max 与 min 路径分岔(方向轴生效——两路终点句柄 %d / %d 不同)" ↩ % [n2_last, n2_last_max] | n2_last != n2_last_max and win_max.visited_ids != win_min.visited_ids | (待填) | | | +| 732 | 519 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;图 = 参数档 × 种子 %d)" ↩ % ACTIVE_GRAPH_SEED | runner.start_level(2, ACTIVE_GRAPH_SEED) | (待填) | | | +| 733 | 526 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | (待填) | | | +| 734 | 529 | "runner 消费面图 = 生成面图(节点数 %d/%d 且句柄集逐位相同)" ↩ % [rnodes.size(), nodes2.size()] | rnodes.size() == nodes2.size() and _ids_of_type(rnodes, "battle").size() + _ids_of_type(rnodes, "boss").size() + _ids_of_type(rnodes, "reward").size() \\ + _ids_of_type(rnodes, "rest").size() + _ids_of_type(rnodes, "shop").size() == nodes2.size() and _nodes_by_id(rnodes) == _nodes_by_id(nodes2) | (待填) | | | +| 735 | 537 | "入口选路口候选非空且全在图内(候选 %s·类型 %s)" ↩ % [str(cand), str(cand_types)] | not cand.is_empty() and not cand_types.has("<不在图>") | (待填) | | | +| 736 | 538 | "候选下一跳类型两两互异(岔口去路类型不重复契约——实测 %s)" ↩ % str(cand_types) | cand_types.size() == _uniq(cand_types).size() | (待填) | | | +| 737 | 545 | "候选外句柄 %d 拒绝且仍待选(拒绝语义)" ↩ % outside_id | outside_id >= 0 and not runner.pick_next_node(outside_id) and runner.awaiting_choice() | (待填) | | | +| 738 | 555 | "入口岔口含奖励位与战斗位(句柄 %d/%d——候选型别可定位)" ↩ % [reward_cand, battle_cand] | reward_cand >= 0 and battle_cand >= 0 | (待填) | | | +| 739 | 556 | "选奖励候选 %d 受理" ↩ % reward_cand | runner.pick_next_node(reward_cand) | (待填) | | | +| 740 | 557 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | (待填) | | | +| 741 | 559 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = 该点图出边 %s" ↩ % str(reward_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(reward_outs) | (待填) | | | +| 742 | 566 | "选下一跳战斗候选 %d 开战" ↩ % next_battle | next_battle >= 0 and runner.pick_next_node(next_battle) and runner.current_battle() != null | (待填) | | | +| 743 | 591 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | (待填) | | | +| 744 | 592 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | (待填) | | | +| 745 | 593 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | (待填) | | | +| 746 | 597 | "跨表覆盖扫描面非空:L2 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov2["nodes"]) | int(cov2["nodes"]) >= 1 | (待填) | | | +| 747 | 598 | "L2 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov2["enemy_bad"]) | int(cov2["enemy_bad"]) == 0 | (待填) | | | +| 748 | 599 | "L2 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov2["board_bad"]) | int(cov2["board_bad"]) == 0 | (待填) | | | +| 749 | 607 | "巨蜘蛛首遇带(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") | (待填) | | | +| 750 | 617 | "L2 全部终点深度 %s 的 Boss 行集均 = {drider} 恰一行且首行魔法甲(护甲类型重配批:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % str(l2_terms) | l2_boss_ok | (待填) | | | +| 751 | 624 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | (待填) | | | +| 752 | 628 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | (待填) | | | +| 753 | 629 | "首批混编新行 (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 | (待填) | | | +| 754 | 636 | "L3 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes3.size()] | not nodes3.is_empty() | (待填) | | | +| 755 | 639 | "L3 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes3.size(), int(params3.get("layers", -1)), _nodes_by_id(nodes3).size()] | nodes3.size() >= int(params3.get("layers", -1)) + 1 and _nodes_by_id(nodes3).size() == nodes3.size() | (待填) | | | +| 756 | 641 | "L3 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount3["battle"], tcount3["boss"], tcount3["reward"], tcount3["rest"], tcount3["shop"], nodes3.size()] | int(tcount3["battle"]) + int(tcount3["boss"]) + int(tcount3["reward"]) + int(tcount3["rest"]) + int(tcount3["shop"]) == nodes3.size() | (待填) | | | +| 757 | 643 | "L3 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount3["battle"], int(params3.get("battle_floor", -1)), tcount3["boss"], int(params3.get("exit_count", -1))] | int(tcount3["battle"]) >= int(params3.get("battle_floor", -1)) and int(tcount3["boss"]) == int(params3.get("exit_count", -1)) | (待填) | | | +| 758 | 645 | "L3 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount3["rest"], tcount3["reward"], tcount3["shop"]] | int(tcount3["rest"]) >= 1 and int(tcount3["reward"]) >= 1 and int(tcount3["shop"]) >= 1 | (待填) | | | +| 759 | 647 | "L3 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch3 | branch3 >= 1 | (待填) | | | +| 760 | 648 | "L3 入口数 %d = 参数档 entry_count · 终点数 %d = exit_count ≥2(多终点大岔口自 L2 起)" ↩ % [_entry_ids(nodes3).size(), _terminal_ids(nodes3).size()] | _entry_ids(nodes3).size() == int(params3.get("entry_count", -1)) and _terminal_ids(nodes3).size() == int(params3.get("exit_count", -1)) and _terminal_ids(nodes3).size() >= 2 | (待填) | | | +| 761 | 652 | "跨表覆盖扫描面非空:L3 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov3["nodes"]) | int(cov3["nodes"]) >= 1 | (待填) | | | +| 762 | 653 | "L3 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov3["enemy_bad"]) | int(cov3["enemy_bad"]) == 0 | (待填) | | | +| 763 | 654 | "L3 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov3["board_bad"]) | int(cov3["board_bad"]) == 0 | (待填) | | | +| 764 | 661 | "灰矮人首遇带(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") | (待填) | | | +| 765 | 672 | "L3 全部终点深度 %s 的 Boss 行集均 = {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % str(l3_terms) | l3_boss_ok | (待填) | | | +| 766 | 695 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | (待填) | | | +| 767 | 696 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | (待填) | | | +| 768 | 706 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | (待填) | | | +| 769 | 708 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | (待填) | | | +| 770 | 709 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,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" | (待填) | | | +| 771 | 710 | "混编各行行值独立投影(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 | (待填) | | | +| 772 | 714 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | (待填) | | | +| 773 | 719 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | | +| 774 | 726 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | | +| 775 | 732 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | | +| 776 | 737 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | | +| 777 | 739 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | | +| 778 | 761 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | | +| 779 | 762 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | | + +## S105 _smoke_gp_wallet(行 799–835 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 780 | 799 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | | +| 781 | 809 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | | +| 782 | 812 | "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 | (待填) | | | +| 783 | 817 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | | +| 784 | 819 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | | +| 785 | 820 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | | +| 786 | 827 | "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)}) | (待填) | | | +| 787 | 832 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | | +| 788 | 835 | "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 | (待填) | | | + +## S106 _smoke_gp_escrow(行 873–937 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 789 | 873 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | | +| 790 | 886 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | | +| 791 | 891 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | | +| 792 | 892 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | | +| 793 | 894 | "格面兑付计入 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 | (待填) | | | +| 794 | 900 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | | +| 795 | 904 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | | +| 796 | 909 | "破财压制 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 | (待填) | | | +| 797 | 919 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | | +| 798 | 931 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | | +| 799 | 936 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | | +| 800 | 937 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | | + +## S107 _smoke_shop_room(行 1003–1089 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 801 | 1003 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | | +| 802 | 1016 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | | +| 803 | 1020 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | | +| 804 | 1022 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | | +| 805 | 1024 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | | +| 806 | 1026 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | | +| 807 | 1029 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | | +| 808 | 1031 | "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 | (待填) | | | +| 809 | 1034 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | | +| 810 | 1035 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | | +| 811 | 1037 | "统一冒险加算区 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 | (待填) | | | +| 812 | 1039 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | | +| 813 | 1046 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | | +| 814 | 1047 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | | +| 815 | 1048 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | | +| 816 | 1049 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | | +| 817 | 1051 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | | +| 818 | 1052 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | | +| 819 | 1067 | "余额不足购买被拒(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 | (待填) | | | +| 820 | 1069 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | | +| 821 | 1072 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | | +| 822 | 1073 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | | +| 823 | 1077 | "战斗开始参数 = 基础表值 + 加算区(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 | (待填) | | | +| 824 | 1088 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | | +| 825 | 1089 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | | + +## S108 _smoke_shop_graph(行 1111–1168 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 826 | 1111 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | | +| 827 | 1116 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | | +| 828 | 1142 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | | +| 829 | 1144 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | | +| 830 | 1145 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | | +| 831 | 1146 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | | +| 832 | 1152 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | | +| 833 | 1164 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | | +| 834 | 1167 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | | + +## S109 _smoke_multi_terminal_settlement(行 1176–1223 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 835 | 1176 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | | +| 836 | 1188 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | | +| 837 | 1192 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | | +| 838 | 1199 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | | +| 839 | 1221 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | | + +## S110 _smoke_shop_pool(行 1258–1392 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 840 | 1258 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | | +| 841 | 1266 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | | +| 842 | 1272 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | | +| 843 | 1273 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | | +| 844 | 1275 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | | +| 845 | 1276 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | | +| 846 | 1277 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | | +| 847 | 1285 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | | +| 848 | 1295 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | | +| 849 | 1301 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | | +| 850 | 1308 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | | +| 851 | 1315 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | | +| 852 | 1323 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | | +| 853 | 1324 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | | +| 854 | 1325 | "加算区 +令牌档位件值(池件=奖励件本体——%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)) | (待填) | | | +| 855 | 1326 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | (待填) | | | +| 856 | 1338 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | | +| 857 | 1339 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | | +| 858 | 1341 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | | +| 859 | 1349 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | | +| 860 | 1360 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | | +| 861 | 1362 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | (待填) | | | +| 862 | 1365 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | (待填) | | | +| 863 | 1371 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | | +| 864 | 1376 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | | +| 865 | 1378 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | | +| 866 | 1379 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | | +| 867 | 1384 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | | +| 868 | 1392 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | | + +## S111 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 869 | 46 | "四检正例:多入口多终点图零错误〔v1〕" | _errs(multi).is_empty() | (待填) | | | +| 870 | 53 | "四检正例:含岔口单终点图零错误〔v1〕" | _errs(forked).is_empty() | (待填) | | | +| 871 | 56 | "四检正例:单链退化图零错误(不设必有岔口)〔v1〕" | _errs(chain).is_empty() | (待填) | | | + +## S112 _test_negative(行 63–89 · 11 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 872 | 63 | "负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕" | _has(_errs(non_boss_end), "structural", "终点 1 非 Boss") | (待填) | | | +| 873 | 66 | "负例:Boss 带出边被拒〔v1〕" | _has(_errs(boss_out), "structural", "Boss 节点 1 带出边") | (待填) | | | +| 874 | 68 | "负例:回边(目标深度不严格大于源)命中前进方向判据〔v1〕" | _has(_errs(back_edge), "structural", "违反前进方向") | (待填) | | | +| 875 | 71 | "负例:环(2→1 回边)被前进方向拦下〔v1〕" | _has(_errs(cycle), "structural", "违反前进方向") | (待填) | | | +| 876 | 77 | "判据②正证:两独立链(双入口双终点)不误伤·全员可达〔v1〕" | _errs(two_components).is_empty() | (待填) | | | +| 877 | 79 | "判据②兜底面:自环先被前进方向拦下(④ 与 ② 互斥的实证)〔v1〕" | _has(_errs(self_loop), "structural", "违反前进方向") | (待填) | | | +| 878 | 82 | "负例:悬空出边报 dangling_next〔v1〕" | _has(_errs(dangling), "dangling_next") | (待填) | | | +| 879 | 84 | "负例:句柄重复报 dup_key〔v1〕" | _has(_errs(dup), "dup_key") | (待填) | | | +| 880 | 86 | "负例:battle 节点缺深度被良构基底拒〔v1〕" | _has(_errs(no_depth), "structural", "缺深度") | (待填) | | | +| 881 | 87 | "负例:空节点集被拒〔v1〕" | _has(_errs([]), "structural", "图节点集为空") | (待填) | | | +| 882 | 89 | "负例:类型出域报 enum〔v1〕" | _has(_errs(bad_type), "enum") | (待填) | | | + +## S113 _test_scope_not_extended(行 97–102 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 883 | 97 | "范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕" | _errs(no_shop).is_empty() | (待填) | | | +| 884 | 99 | "范围反证:双入口合法(入口唯一判据已随模板池退役,个数归产出侧)〔v1〕" | _errs(two_entries).is_empty() | (待填) | | | +| 885 | 102 | "范围反证:节点缺 next 字段仍被良构基底拒〔v1〕" | _has(_errs(missing_key), "structural", "缺字段 next") | (待填) | | | + +## S114 _meta_pricing_and_buy(行 138–151 · 4 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 886 | 138 | "树购买受理(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 | (待填) | | | +| 887 | 141 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | (待填) | | | +| 888 | 148 | "余额不足 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 | (待填) | | | +| 889 | 151 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | (待填) | | | + +## S115 _meta_settle_and_workshop(行 168–341 · 42 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 890 | 168 | "新档首通 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 | (待填) | | | +| 891 | 171 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | (待填) | | | +| 892 | 173 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | (待填) | | | +| 893 | 181 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | (待填) | | | +| 894 | 187 | "重复杀第三轮恒定不递减(验收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)) | (待填) | | | +| 895 | 195 | "两关通关逐关归因: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)) | (待填) | | | +| 896 | 203 | "败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | (待填) | | | +| 897 | 210 | "条件件误购拒绝(购买件专属——契约 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") | (待填) | | | +| 898 | 213 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | (待填) | | | +| 899 | 215 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | (待填) | | | +| 900 | 218 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | (待填) | | | +| 901 | 220 | "条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | (待填) | | | +| 902 | 223 | "远程镐=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() | (待填) | | | +| 903 | 227 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | (待填) | | | +| 904 | 230 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | (待填) | | | +| 905 | 233 | "天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | (待填) | | | +| 906 | 236 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | (待填) | | | +| 907 | 241 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | (待填) | | | +| 908 | 244 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收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 | (待填) | | | +| 909 | 253 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | (待填) | | | +| 910 | 258 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | (待填) | | | +| 911 | 260 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | (待填) | | | +| 912 | 265 | "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 | (待填) | | | +| 913 | 270 | "通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | (待填) | | | +| 914 | 273 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | (待填) | | | +| 915 | 275 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | (待填) | | | +| 916 | 277 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | (待填) | | | +| 917 | 279 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | (待填) | | | +| 918 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | (待填) | | | +| 919 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | (待填) | | | +| 920 | 290 | "充能账本按装备 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 | (待填) | | | +| 921 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | (待填) | | | +| 922 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | (待填) | | | +| 923 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | (待填) | | | +| 924 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | (待填) | | | +| 925 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | (待填) | | | +| 926 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | (待填) | | | +| 927 | 321 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | (待填) | | | +| 928 | 323 | "主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入" | bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool" | (待填) | | | +| 929 | 327 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (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 | (待填) | | | +| 930 | 338 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | (待填) | | | +| 931 | 340 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | (待填) | | | + +## S116 _meta_injection(行 353–383 · 10 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 932 | 353 | "生命淬炼 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)) | (待填) | | | +| 933 | 355 | "攻/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 | (待填) | | | +| 934 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | (待填) | | | +| 935 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | (待填) | | | +| 936 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | (待填) | | | +| 937 | 366 | "首战 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)) | (待填) | | | +| 938 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | (待填) | | | +| 939 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | (待填) | | | +| 940 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(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()) | (待填) | | | +| 941 | 382 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | (待填) | | | + +## S117 _smoke_forge_axes(行 392–540 · 33 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 942 | 392 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | (待填) | | | +| 943 | 395 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | (待填) | | | +| 944 | 399 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 945 | 400 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | (待填) | | | +| 946 | 410 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | (待填) | | | +| 947 | 414 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | (待填) | | | +| 948 | 417 | "长剑二级购:扣 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 | (待填) | | | +| 949 | 424 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | (待填) | | | +| 950 | 429 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | (待填) | | | +| 951 | 432 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 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") | (待填) | | | +| 952 | 435 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | (待填) | | | +| 953 | 442 | "通道二:魔法护甲一次性解锁价 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") | (待填) | | | +| 954 | 447 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | (待填) | | | +| 955 | 452 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 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") | (待填) | | | +| 956 | 457 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | (待填) | | | +| 957 | 462 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | (待填) | | | +| 958 | 465 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | (待填) | | | +| 959 | 468 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | (待填) | | | +| 960 | 471 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | (待填) | | | +| 961 | 480 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | (待填) | | | +| 962 | 484 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | (待填) | | | +| 963 | 487 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | (待填) | | | +| 964 | 491 | "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") | (待填) | | | +| 965 | 497 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | (待填) | | | +| 966 | 499 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | (待填) | | | +| 967 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | (待填) | | | +| 968 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | (待填) | | | +| 969 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | (待填) | | | +| 970 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | (待填) | | | +| 971 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | | +| 972 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | (待填) | | | +| 973 | 529 | "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" | (待填) | | | +| 974 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | (待填) | | | + +## S118 _smoke_tree(行 548–621 · 25 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 975 | 548 | "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 | (待填) | | | +| 976 | 550 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | (待填) | | | +| 977 | 558 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | (待填) | | | +| 978 | 566 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | (待填) | | | +| 979 | 571 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | (待填) | | | +| 980 | 576 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | (待填) | | | +| 981 | 581 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | (待填) | | | +| 982 | 586 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | (待填) | | | +| 983 | 588 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | (待填) | | | +| 984 | 590 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | (待填) | | | +| 985 | 592 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | (待填) | | | +| 986 | 593 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | (待填) | | | +| 987 | 594 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | (待填) | | | +| 988 | 598 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | (待填) | | | +| 989 | 600 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | (待填) | | | +| 990 | 601 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | (待填) | | | +| 991 | 602 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | (待填) | | | +| 992 | 604 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | (待填) | | | +| 993 | 606 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | (待填) | | | +| 994 | 607 | "规则键投影=激活态效果键(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") | (待填) | | | +| 995 | 609 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | (待填) | | | +| 996 | 612 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | (待填) | | | +| 997 | 613 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | (待填) | | | +| 998 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | (待填) | | | +| 999 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | (待填) | | | -## S119 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S119 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1018 | 22 | "数值表加载" | false | (待填) | | | -| 1019 | 30 | "生成返回棋盘(前置)" | false | (待填) | | | +| 1000 | 633 | "先手压制:每关首战首轮窗命中(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 | (待填) | | | +| 1001 | 635 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (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() | (待填) | | | +| 1002 | 638 | "稳健拓荒:首轮窗内受击出盾、窗外不出(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() | (待填) | | | +| 1003 | 654 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | (待填) | | | +| 1004 | 657 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | +| 1005 | 663 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | +| 1006 | 685 | "锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕" | nc_hit != "" | (待填) | | | +| 1007 | 687 | "惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕" | nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷") | (待填) | | | +| 1008 | 691 | "守侧 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"}) | (待填) | | | +| 1009 | 703 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | (待填) | | | +| 1010 | 704 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | (待填) | | | +| 1011 | 708 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1012 | 713 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1013 | 717 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1014 | 721 | "守⑤ 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"}) | (待填) | | | +| 1015 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | (待填) | | | +| 1016 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | (待填) | | | +| 1017 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | | +| 1018 | 739 | "聚 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"}) | (待填) | | | +| 1019 | 748 | "聚 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"}) | (待填) | | | +| 1020 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | | -## S120 _test_install_invariants(行 48–56 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S120 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1020 | 48 | "装板后 current_board 即所装棋盘" | _rules.current_board() == _board | (待填) | | | -| 1021 | 52 | "装板带初始扩张揭示(%d 格,非零且未铺满安全格 %d)" ↩ % [opened, safe_total] | opened > 0 and opened < safe_total | (待填) | | | -| 1022 | 54 | "安全格余量 = 安全格总数 − 已揭示数(%d − %d)" ↩ % [safe_total, opened] | _rules.safe_remaining() == safe_total - opened | (待填) | | | -| 1023 | 56 | "装板后仍有未揭示格" | _rules.has_unrevealed() | (待填) | | | +| 1021 | 22 | "数值表加载" | false | (待填) | | | +| 1022 | 30 | "生成返回棋盘(前置)" | false | (待填) | | | -## S121 _test_dig_face_rules(行 73–80 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S121 _test_install_invariants(行 48–56 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1024 | 73 | "挖已揭示格被拒(board_face=revealed,禁重复挖)——载荷 %s" ↩ % str(o.get("sub_reason", "")) | String(o.get("outcome", "")) == "rejected" and String(o.get("sub_reason", "")) == "target_revealed" | (待填) | | | -| 1025 | 79 | "挖非相邻格被拒(邻接规则)——载荷 %s" ↩ % str(o2.get("sub_reason", "")) | String(o2.get("outcome", "")) == "rejected" | (待填) | | | +| 1023 | 48 | "装板后 current_board 即所装棋盘" | _rules.current_board() == _board | (待填) | | | +| 1024 | 52 | "装板带初始扩张揭示(%d 格,非零且未铺满安全格 %d)" ↩ % [opened, safe_total] | opened > 0 and opened < safe_total | (待填) | | | +| 1025 | 54 | "安全格余量 = 安全格总数 − 已揭示数(%d − %d)" ↩ % [safe_total, opened] | _rules.safe_remaining() == safe_total - opened | (待填) | | | +| 1026 | 56 | "装板后仍有未揭示格" | _rules.has_unrevealed() | (待填) | | | -## S122 _test_reveal_advances(行 86–93 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S122 _test_dig_face_rules(行 73–80 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1026 | 86 | "存在未揭示安全格(揭示推进前置)" | false | (待填) | | | -| 1027 | 90 | "专长揭示受理(fx_reveal 不受邻接约束)" | ok | (待填) | | | -| 1028 | 91 | "fx_reveal 后该格已揭示" | _rules.is_revealed(c) | (待填) | | | -| 1029 | 92 | "fx_reveal 后安全格余量递减(%d → %d)" ↩ % [before, _rules.safe_remaining()] | _rules.safe_remaining() < before | (待填) | | | +| 1027 | 73 | "挖已揭示格被拒(board_face=revealed,禁重复挖)——载荷 %s" ↩ % str(o.get("sub_reason", "")) | String(o.get("outcome", "")) == "rejected" and String(o.get("sub_reason", "")) == "target_revealed" | (待填) | | | +| 1028 | 79 | "挖非相邻格被拒(邻接规则)——载荷 %s" ↩ % str(o2.get("sub_reason", "")) | String(o2.get("outcome", "")) == "rejected" | (待填) | | | -## S123 _test_seal_place(行 99–105 · 3 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S123 _test_reveal_advances(行 86–93 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1030 | 99 | "存在未揭示安全格(封印前置)" | false | (待填) | | | -| 1031 | 104 | "封印后该格在封印态" | _rules.has_seal(c) | (待填) | | | -| 1032 | 105 | "封印数计数 = 1" | _rules.seal_count() == 1 | (待填) | | | +| 1029 | 86 | "存在未揭示安全格(揭示推进前置)" | false | (待填) | | | +| 1030 | 90 | "专长揭示受理(fx_reveal 不受邻接约束)" | ok | (待填) | | | +| 1031 | 91 | "fx_reveal 后该格已揭示" | _rules.is_revealed(c) | (待填) | | | +| 1032 | 92 | "fx_reveal 后安全格余量递减(%d → %d)" ↩ % [before, _rules.safe_remaining()] | _rules.safe_remaining() < before | (待填) | | | -## S124 _test_reject_out_of_catalog(行 111–112 · 1 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S124 _test_seal_place(行 99–105 · 3 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1033 | 111 | "目录外意图被拒(不静默吞掉)——实际载荷 %s" ↩ % str(o).substr(0, 80) | out.size() == 1 and String(o.get("outcome", o.get("result", ""))) == "rejected" | (待填) | | | +| 1033 | 99 | "存在未揭示安全格(封印前置)" | false | (待填) | | | +| 1034 | 104 | "封印后该格在封印态" | _rules.has_seal(c) | (待填) | | | +| 1035 | 105 | "封印数计数 = 1" | _rules.seal_count() == 1 | (待填) | | | -## S125 _test_output_ok(行 88–89 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S125 _test_reject_out_of_catalog(行 111–112 · 1 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1034 | 88 | "三档×20 种子产出图过产出约束五条+结构铁律(自证零错误)〔fv1〕" | all_ok | (待填) | | | -| 1035 | 89 | "入口数/终点数 = 参数档 entry_count/exit_count〔fv1〕" | entry_exit_ok | (待填) | | | +| 1036 | 111 | "目录外意图被拒(不静默吞掉)——实际载荷 %s" ↩ % str(o).substr(0, 80) | out.size() == 1 and String(o.get("outcome", o.get("result", ""))) == "rejected" | (待填) | | | -## S126 _test_reproducible(行 99–119 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S126 _test_output_ok(行 88–89 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1036 | 99 | "同(种子,参数档)两次产图逐位同图〔fv3〕" | same | (待填) | | | -| 1037 | 109 | "counter 无关(不同号位态同种子同图)〔fv3〕" | _sig(gen_a.generate(_params(2), 42)) == _sig(gen_b.generate(_params(2), 42)) and _sig(gen_a.generate(_params(3), 7)) == _sig(gen_b.generate(_params(3), 7)) | (待填) | | | -| 1038 | 119 | "同参数档不同种子产出异图(10 种子去重数 ≥ 8)〔fv3〕" | distinct >= 8 | (待填) | | | +| 1037 | 88 | "三档×20 种子产出图过产出约束五条+结构铁律(自证零错误)〔fv1〕" | all_ok | (待填) | | | +| 1038 | 89 | "入口数/终点数 = 参数档 entry_count/exit_count〔fv1〕" | entry_exit_ok | (待填) | | | -## S127 _test_fork_rules(行 163–164 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S127 _test_reproducible(行 99–119 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1039 | 163 | "任意岔口各去路下一节点类型互不相同〔fv5〕" | next_hop_ok | (待填) | | | -| 1040 | 164 | "任意岔口各支路类型构成互异且各有独有类型〔fv5〕" | comp_ok | (待填) | | | +| 1039 | 99 | "同(种子,参数档)两次产图逐位同图〔fv3〕" | same | (待填) | | | +| 1040 | 109 | "counter 无关(不同号位态同种子同图)〔fv3〕" | _sig(gen_a.generate(_params(2), 42)) == _sig(gen_b.generate(_params(2), 42)) and _sig(gen_a.generate(_params(3), 7)) == _sig(gen_b.generate(_params(3), 7)) | (待填) | | | +| 1041 | 119 | "同参数档不同种子产出异图(10 种子去重数 ≥ 8)〔fv3〕" | distinct >= 8 | (待填) | | | -## S128 _test_shop_and_attribution(行 228–230 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S128 _test_fork_rules(行 163–164 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1041 | 228 | "任意入口→终点路径恰过一家店〔fv6〕" | paths_ok | (待填) | | | -| 1042 | 229 | "每终点前一位恰一店+商店总数=终点数(归属锁定)〔fv6〕" | lock_ok | (待填) | | | -| 1043 | 230 | "多终点落在互异节点深度(L2/L3)〔fv6〕" | depth_ok | (待填) | | | +| 1042 | 163 | "任意岔口各去路下一节点类型互不相同〔fv5〕" | next_hop_ok | (待填) | | | +| 1043 | 164 | "任意岔口各支路类型构成互异且各有独有类型〔fv5〕" | comp_ok | (待填) | | | -## S129 _test_battle_floor(行 257–257 · 1 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S129 _test_shop_and_attribution(行 228–230 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1044 | 257 | "任意合法产出战斗节点数 ≥ battle_floor〔fv7〕" | ok | (待填) | | | +| 1044 | 228 | "任意入口→终点路径恰过一家店〔fv6〕" | paths_ok | (待填) | | | +| 1045 | 229 | "每终点前一位恰一店+商店总数=终点数(归属锁定)〔fv6〕" | lock_ok | (待填) | | | +| 1046 | 230 | "多终点落在互异节点深度(L2/L3)〔fv6〕" | depth_ok | (待填) | | | -## S130 _test_exit_structure(行 285–287 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S130 _test_battle_floor(行 257–257 · 1 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1045 | 285 | "三关参数档生成图各含 ≥1 岔口节点(第一关即含)〔fv9〕" | fork_all | (待填) | | | -| 1046 | 286 | "L1 生成图终点恰 1(出口恒 1)〔fv9〕" | l1_single | (待填) | | | -| 1047 | 287 | "L2/L3 含分区域大岔口(一节点可达 ≥2 终点且去路数=出口数)〔fv9〕" | big_fork_ok | (待填) | | | +| 1047 | 257 | "任意合法产出战斗节点数 ≥ battle_floor〔fv7〕" | ok | (待填) | | | -## S131 _test_rng_single_point(行 313–318 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S131 _test_exit_structure(行 285–287 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1048 | 313 | "pure_stream 不推全局计数器(产图前后 counter 同值)〔fv10〕" | svc.counter() == before | (待填) | | | -| 1049 | 316 | "pure_stream 同 (seed,scope) 同流〔fv10〕" | a.randf() == b.randf() and a.randi() == b.randi() | (待填) | | | -| 1050 | 317 | "pure_stream 不推计数器且产图非空〔fv10〕" | svc.counter() == before and (g["nodes"] as Array).size() > 0 | (待填) | | | +| 1048 | 285 | "三关参数档生成图各含 ≥1 岔口节点(第一关即含)〔fv9〕" | fork_all | (待填) | | | +| 1049 | 286 | "L1 生成图终点恰 1(出口恒 1)〔fv9〕" | l1_single | (待填) | | | +| 1050 | 287 | "L2/L3 含分区域大岔口(一节点可达 ≥2 终点且去路数=出口数)〔fv9〕" | big_fork_ok | (待填) | | | -## S132 _test_negative_fixtures(行 328–412 · 13 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S132 _test_rng_single_point(行 313–318 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1051 | 328 | "句柄重复夹具报 dup_key〔fv1〕" | _has_cat(_gen.validate_output(dup), "dup_key") | (待填) | | | -| 1052 | 332 | "悬空出边夹具报 dangling_next〔fv1〕" | _has_cat(_gen.validate_output(dang), "dangling_next") | (待填) | | | -| 1053 | 336 | "类型出域夹具报 enum〔fv1〕" | _has_cat(_gen.validate_output(badty), "enum") | (待填) | | | -| 1054 | 343 | "战斗节点低于 battle_floor 的合法形状夹具命中「低于保底」判据〔fv7〕" | _has_msg(_gen.validate_output(thin_battle, {"battle_floor": 4}), "低于保底") | (待填) | | | -| 1055 | 345 | "同夹具在保底 1 下自证零错误(判据不误伤)〔fv7〕" | _gen.validate_output(thin_battle, {"battle_floor": 1}).is_empty() | (待填) | | | -| 1056 | 350 | "非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕" | _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss") | (待填) | | | -| 1057 | 361 | "合法合流图(第二入口直插主链,深度取最长路 3)自证零错误〔fv1〕" | _gen.validate_output(conf_legal).is_empty() | (待填) | | | -| 1058 | 370 | "声明深度高于最长路径步数被拒(深度=自入口最长路径步数)〔fv1〕" | _has_msg(_gen.validate_output(conf_high), "自入口最长路径步数") | (待填) | | | -| 1059 | 377 | "回边夹具报前进方向违例(structural)〔fv1〕" | _has_cat(_gen.validate_output(loop), "structural") | (待填) | | | -| 1060 | 387 | "多终点同深度夹具报 structural〔fv6〕" | _has_cat(_gen.validate_output(twin), "structural") | (待填) | | | -| 1061 | 397 | "双终点异深+区域类型互异合法夹具自证零错误〔fv6〕" | _gen.validate_output(twin_ok).is_empty() | (待填) | | | -| 1062 | 404 | "终点前驱非店夹具报 structural(必经恰一店/商店数)〔fv6〕" | _has_cat(cats, "structural") | (待填) | | | -| 1063 | 412 | "岔口同类型下一跳夹具报 structural〔fv5〕" | _has_cat(_gen.validate_output(sameHop), "structural") | (待填) | | | +| 1051 | 313 | "pure_stream 不推全局计数器(产图前后 counter 同值)〔fv10〕" | svc.counter() == before | (待填) | | | +| 1052 | 316 | "pure_stream 同 (seed,scope) 同流〔fv10〕" | a.randf() == b.randf() and a.randi() == b.randi() | (待填) | | | +| 1053 | 317 | "pure_stream 不推计数器且产图非空〔fv10〕" | svc.counter() == before and (g["nodes"] as Array).size() > 0 | (待填) | | | -## S133 _test_growth(行 57–58 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S133 _test_negative_fixtures(行 328–412 · 13 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1064 | 57 | "生长递增:三档最长路径步数逐关变大〔fv8〕" | steps_ok | (待填) | | | -| 1065 | 58 | "生长递增:节点总数逐关变大且增幅变大〔fv8〕" | count_ok | (待填) | | | +| 1054 | 328 | "句柄重复夹具报 dup_key〔fv1〕" | _has_cat(_gen.validate_output(dup), "dup_key") | (待填) | | | +| 1055 | 332 | "悬空出边夹具报 dangling_next〔fv1〕" | _has_cat(_gen.validate_output(dang), "dangling_next") | (待填) | | | +| 1056 | 336 | "类型出域夹具报 enum〔fv1〕" | _has_cat(_gen.validate_output(badty), "enum") | (待填) | | | +| 1057 | 343 | "战斗节点低于 battle_floor 的合法形状夹具命中「低于保底」判据〔fv7〕" | _has_msg(_gen.validate_output(thin_battle, {"battle_floor": 4}), "低于保底") | (待填) | | | +| 1058 | 345 | "同夹具在保底 1 下自证零错误(判据不误伤)〔fv7〕" | _gen.validate_output(thin_battle, {"battle_floor": 1}).is_empty() | (待填) | | | +| 1059 | 350 | "非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕" | _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss") | (待填) | | | +| 1060 | 361 | "合法合流图(第二入口直插主链,深度取最长路 3)自证零错误〔fv1〕" | _gen.validate_output(conf_legal).is_empty() | (待填) | | | +| 1061 | 370 | "声明深度高于最长路径步数被拒(深度=自入口最长路径步数)〔fv1〕" | _has_msg(_gen.validate_output(conf_high), "自入口最长路径步数") | (待填) | | | +| 1062 | 377 | "回边夹具报前进方向违例(structural)〔fv1〕" | _has_cat(_gen.validate_output(loop), "structural") | (待填) | | | +| 1063 | 387 | "多终点同深度夹具报 structural〔fv6〕" | _has_cat(_gen.validate_output(twin), "structural") | (待填) | | | +| 1064 | 397 | "双终点异深+区域类型互异合法夹具自证零错误〔fv6〕" | _gen.validate_output(twin_ok).is_empty() | (待填) | | | +| 1065 | 404 | "终点前驱非店夹具报 structural(必经恰一店/商店数)〔fv6〕" | _has_cat(cats, "structural") | (待填) | | | +| 1066 | 412 | "岔口同类型下一跳夹具报 structural〔fv5〕" | _has_cat(_gen.validate_output(sameHop), "structural") | (待填) | | | -## S134 _test_fail_fast_static(行 64–74 · 3 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S134 _test_growth(行 57–58 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1066 | 64 | "生产三档参数档可行性判定零错误〔fv4〕" | NodeMapGenerator.check_params(db.level_gen_params_row(1)).is_empty() and NodeMapGenerator.check_params(db.level_gen_params_row(3)).is_empty() | (待填) | | | -| 1067 | 70 | "层深 6 放三出口无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(unsolvable).is_empty() | (待填) | | | -| 1068 | 74 | "战斗保底超可放数无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(overfloor).is_empty() | (待填) | | | +| 1067 | 57 | "生长递增:三档最长路径步数逐关变大〔fv8〕" | steps_ok | (待填) | | | +| 1068 | 58 | "生长递增:节点总数逐关变大且增幅变大〔fv8〕" | count_ok | (待填) | | | -## S135 _test_fatal_subprocess(行 82–91 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S135 _test_fail_fast_static(行 64–74 · 3 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1069 | 82 | "未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕" | false | (待填) | | | -| 1070 | 91 | "无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕" | code == 1 | (待填) | | | +| 1069 | 64 | "生产三档参数档可行性判定零错误〔fv4〕" | NodeMapGenerator.check_params(db.level_gen_params_row(1)).is_empty() and NodeMapGenerator.check_params(db.level_gen_params_row(3)).is_empty() | (待填) | | | +| 1070 | 70 | "层深 6 放三出口无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(unsolvable).is_empty() | (待填) | | | +| 1071 | 74 | "战斗保底超可放数无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(overfloor).is_empty() | (待填) | | | -## S136 _smoke_save(行 81–329 · 60 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) +## S136 _test_fatal_subprocess(行 82–91 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1071 | 81 | "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 | (待填) | | | -| 1072 | 83 | "首杀标记随通关关卡置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | (待填) | | | -| 1073 | 85 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | (待填) | | | -| 1074 | 87 | "写盘成功" | w.write_to_disk() | (待填) | | | -| 1075 | 89 | "读回成功" | r.load_from_disk() | (待填) | | | -| 1076 | 90 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | (待填) | | | -| 1077 | 91 | "树域往返相等(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" | (待填) | | | -| 1078 | 93 | "锻造工坊域往返相等(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" | (待填) | | | -| 1079 | 97 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | (待填) | | | -| 1080 | 98 | "通关次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | (待填) | | | -| 1081 | 99 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | (待填) | | | -| 1082 | 101 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | (待填) | | | -| 1083 | 103 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | (待填) | | | -| 1084 | 108 | "新档合法态(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() | (待填) | | | -| 1085 | 111 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | (待填) | | | -| 1086 | 117 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | (待填) | | | -| 1087 | 118 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | (待填) | | | -| 1088 | 126 | "高版本拒读(load false)" | not f5.load_from_disk() | (待填) | | | -| 1089 | 127 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | -| 1090 | 128 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | -| 1091 | 137 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | (待填) | | | -| 1092 | 139 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | (待填) | | | -| 1093 | 140 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | (待填) | | | -| 1094 | 151 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | (待填) | | | -| 1095 | 153 | "迁移链 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 | (待填) | | | -| 1096 | 165 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | (待填) | | | -| 1097 | 173 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | (待填) | | | -| 1098 | 174 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | (待填) | | | -| 1099 | 182 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | (待填) | | | -| 1100 | 193 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | (待填) | | | -| 1101 | 195 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | (待填) | | | -| 1102 | 197 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | (待填) | | | -| 1103 | 201 | "断点七字段往返:属性加成区" | 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 | (待填) | | | -| 1104 | 204 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | (待填) | | | -| 1105 | 206 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | (待填) | | | -| 1106 | 208 | "断点七字段往返:生成图代差标记键" | int(bp.get("graph_gen_v2", 0)) == 1 | (待填) | | | -| 1107 | 209 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | (待填) | | | -| 1108 | 213 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | (待填) | | | -| 1109 | 216 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | (待填) | | | -| 1110 | 219 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | (待填) | | | -| 1111 | 222 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | (待填) | | | -| 1112 | 224 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | (待填) | | | -| 1113 | 229 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | (待填) | | | -| 1114 | 231 | "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 | (待填) | | | -| 1115 | 243 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | (待填) | | | -| 1116 | 245 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | (待填) | | | -| 1117 | 248 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | (待填) | | | -| 1118 | 257 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | (待填) | | | -| 1119 | 260 | "升档幂等: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 | (待填) | | | -| 1120 | 268 | "收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | (待填) | | | -| 1121 | 272 | "收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | (待填) | | | -| 1122 | 283 | "换代前结构存档:树域废弃重置(旧节点 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() | (待填) | | | -| 1123 | 288 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | (待填) | | | -| 1124 | 297 | "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 | (待填) | | | -| 1125 | 300 | "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" | (待填) | | | -| 1126 | 307 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | (待填) | | | -| 1127 | 311 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | -| 1128 | 316 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | (待填) | | | -| 1129 | 324 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、通关局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | (待填) | | | -| 1130 | 328 | "收束链自增·通关局(三关全通:总局数+1 且通关局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | (待填) | | | - -## S137 _smoke_save_slots(行 359–499 · 31 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1131 | 359 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | (待填) | | | -| 1132 | 362 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | (待填) | | | -| 1133 | 366 | "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) | (待填) | | | -| 1134 | 372 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | (待填) | | | -| 1135 | 373 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | (待填) | | | -| 1136 | 377 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | (待填) | | | -| 1137 | 378 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | (待填) | | | -| 1138 | 379 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | (待填) | | | -| 1139 | 381 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | (待填) | | | -| 1140 | 385 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | (待填) | | | -| 1141 | 386 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | (待填) | | | -| 1142 | 388 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | (待填) | | | -| 1143 | 397 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | (待填) | | | -| 1144 | 398 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | (待填) | | | -| 1145 | 402 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | (待填) | | | -| 1146 | 411 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | (待填) | | | -| 1147 | 414 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | (待填) | | | -| 1148 | 415 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | (待填) | | | -| 1149 | 418 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | (待填) | | | -| 1150 | 429 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | (待填) | | | -| 1151 | 444 | "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 | (待填) | | | -| 1152 | 447 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | (待填) | | | -| 1153 | 449 | "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 | (待填) | | | -| 1154 | 458 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | (待填) | | | -| 1155 | 459 | "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") | (待填) | | | -| 1156 | 469 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | (待填) | | | -| 1157 | 470 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | (待填) | | | -| 1158 | 483 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | (待填) | | | -| 1159 | 485 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | (待填) | | | -| 1160 | 494 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | (待填) | | | -| 1161 | 497 | "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 | (待填) | | | - -## S138 _init(行 14–15 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1162 | 14 | "全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -| 1163 | 15 | "is_ready 通道随加载置位" | db.is_ready | (待填) | | | - -## S139 _test_progress_resolution(行 55–83 · 7 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +| 1072 | 82 | "未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕" | false | (待填) | | | +| 1073 | 91 | "无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕" | code == 1 | (待填) | | | + +## S137 _smoke_save(行 81–329 · 60 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1074 | 81 | "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 | (待填) | | | +| 1075 | 83 | "首杀标记随通关关卡置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | (待填) | | | +| 1076 | 85 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | (待填) | | | +| 1077 | 87 | "写盘成功" | w.write_to_disk() | (待填) | | | +| 1078 | 89 | "读回成功" | r.load_from_disk() | (待填) | | | +| 1079 | 90 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | (待填) | | | +| 1080 | 91 | "树域往返相等(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" | (待填) | | | +| 1081 | 93 | "锻造工坊域往返相等(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" | (待填) | | | +| 1082 | 97 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | (待填) | | | +| 1083 | 98 | "通关次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | (待填) | | | +| 1084 | 99 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | (待填) | | | +| 1085 | 101 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | (待填) | | | +| 1086 | 103 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | (待填) | | | +| 1087 | 108 | "新档合法态(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() | (待填) | | | +| 1088 | 111 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | (待填) | | | +| 1089 | 117 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | (待填) | | | +| 1090 | 118 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | (待填) | | | +| 1091 | 126 | "高版本拒读(load false)" | not f5.load_from_disk() | (待填) | | | +| 1092 | 127 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | +| 1093 | 128 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | +| 1094 | 137 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | (待填) | | | +| 1095 | 139 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | (待填) | | | +| 1096 | 140 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | (待填) | | | +| 1097 | 151 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | (待填) | | | +| 1098 | 153 | "迁移链 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 | (待填) | | | +| 1099 | 165 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | (待填) | | | +| 1100 | 173 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | (待填) | | | +| 1101 | 174 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | (待填) | | | +| 1102 | 182 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | (待填) | | | +| 1103 | 193 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | (待填) | | | +| 1104 | 195 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | (待填) | | | +| 1105 | 197 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | (待填) | | | +| 1106 | 201 | "断点七字段往返:属性加成区" | 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 | (待填) | | | +| 1107 | 204 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | (待填) | | | +| 1108 | 206 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | (待填) | | | +| 1109 | 208 | "断点七字段往返:生成图代差标记键" | int(bp.get("graph_gen_v2", 0)) == 1 | (待填) | | | +| 1110 | 209 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | (待填) | | | +| 1111 | 213 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | (待填) | | | +| 1112 | 216 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | (待填) | | | +| 1113 | 219 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | (待填) | | | +| 1114 | 222 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | (待填) | | | +| 1115 | 224 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | (待填) | | | +| 1116 | 229 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | (待填) | | | +| 1117 | 231 | "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 | (待填) | | | +| 1118 | 243 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | (待填) | | | +| 1119 | 245 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | (待填) | | | +| 1120 | 248 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | (待填) | | | +| 1121 | 257 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | (待填) | | | +| 1122 | 260 | "升档幂等: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 | (待填) | | | +| 1123 | 268 | "收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1124 | 272 | "收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1125 | 283 | "换代前结构存档:树域废弃重置(旧节点 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() | (待填) | | | +| 1126 | 288 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | (待填) | | | +| 1127 | 297 | "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 | (待填) | | | +| 1128 | 300 | "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" | (待填) | | | +| 1129 | 307 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | (待填) | | | +| 1130 | 311 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | +| 1131 | 316 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | (待填) | | | +| 1132 | 324 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、通关局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1133 | 328 | "收束链自增·通关局(三关全通:总局数+1 且通关局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | (待填) | | | + +## S138 _smoke_save_slots(行 359–499 · 31 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1134 | 359 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | (待填) | | | +| 1135 | 362 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | (待填) | | | +| 1136 | 366 | "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) | (待填) | | | +| 1137 | 372 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | (待填) | | | +| 1138 | 373 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | (待填) | | | +| 1139 | 377 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | (待填) | | | +| 1140 | 378 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | (待填) | | | +| 1141 | 379 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | (待填) | | | +| 1142 | 381 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | (待填) | | | +| 1143 | 385 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | (待填) | | | +| 1144 | 386 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | (待填) | | | +| 1145 | 388 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | (待填) | | | +| 1146 | 397 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | (待填) | | | +| 1147 | 398 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | (待填) | | | +| 1148 | 402 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | (待填) | | | +| 1149 | 411 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | (待填) | | | +| 1150 | 414 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | (待填) | | | +| 1151 | 415 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | (待填) | | | +| 1152 | 418 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | (待填) | | | +| 1153 | 429 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | (待填) | | | +| 1154 | 444 | "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 | (待填) | | | +| 1155 | 447 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | (待填) | | | +| 1156 | 449 | "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 | (待填) | | | +| 1157 | 458 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | (待填) | | | +| 1158 | 459 | "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") | (待填) | | | +| 1159 | 469 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | (待填) | | | +| 1160 | 470 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | (待填) | | | +| 1161 | 483 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | (待填) | | | +| 1162 | 485 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | (待填) | | | +| 1163 | 494 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | (待填) | | | +| 1164 | 497 | "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 | (待填) | | | + +## S139 _init(行 14–15 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1165 | 14 | "全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1166 | 15 | "is_ready 通道随加载置位" | db.is_ready | (待填) | | | + +## S140 _test_progress_resolution(行 55–83 · 7 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1167 | 55 | "进度解析精确命中返回原行(board_gen 首行)" | int(got.get("level_index", -99)) == int(probe_row.level_index) and int(got.get("node_depth", -99)) == int(probe_row.node_depth) and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")) | (待填) | | | +| 1168 | 63 | "enemy 行集同坐标解析非空(双表精确行契约——battle 行类)" | not en_got.is_empty() | (待填) | | | +| 1169 | 68 | "深度越最大行 clamp 取该关最大深度行" | int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]) | (待填) | | | +| 1170 | 70 | "clamp 解析发警告诊断(resolve_depth_clamp)" | db.diagnostics_count("warning") >= 1 | (待填) | | | +| 1171 | 75 | "警告类别 = resolve_depth_clamp" | found_clamp | (待填) | | | +| 1172 | 78 | "同键重复请求警告去重(四元组注册表)" | db.diagnostics_count("warning") == warn_before | (待填) | | | +| 1173 | 83 | "关卡越最大行 clamp 取最大关卡行" | int(high.get("level_index", -99)) == max_l | (待填) | | | + +## S141 _test_variant_pool_gate(行 108–118 · 6 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1164 | 55 | "进度解析精确命中返回原行(board_gen 首行)" | int(got.get("level_index", -99)) == int(probe_row.level_index) and int(got.get("node_depth", -99)) == int(probe_row.node_depth) and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")) | (待填) | | | -| 1165 | 63 | "enemy 行集同坐标解析非空(双表精确行契约——battle 行类)" | not en_got.is_empty() | (待填) | | | -| 1166 | 68 | "深度越最大行 clamp 取该关最大深度行" | int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]) | (待填) | | | -| 1167 | 70 | "clamp 解析发警告诊断(resolve_depth_clamp)" | db.diagnostics_count("warning") >= 1 | (待填) | | | -| 1168 | 75 | "警告类别 = resolve_depth_clamp" | found_clamp | (待填) | | | -| 1169 | 78 | "同键重复请求警告去重(四元组注册表)" | db.diagnostics_count("warning") == warn_before | (待填) | | | -| 1170 | 83 | "关卡越最大行 clamp 取最大关卡行" | int(high.get("level_index", -99)) == max_l | (待填) | | | +| 1174 | 108 | "变异池 unlock 前不含未解锁行(level=unlock−1)" | not _rows_contain(before, gated_row) | (待填) | | | +| 1175 | 109 | "变异池 unlock 当级含该行" | _rows_contain(after, gated_row) | (待填) | | | +| 1176 | 113 | "∅ board_types 行通配(任一 board_type 命中)" | _rows_contain(any_pool, wildcard_row) | (待填) | | | +| 1177 | 116 | "global 行 boss 节点入池(board_types ∅ 通配)" | _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row) | (待填) | | | +| 1178 | 117 | "global 行缺省双参调用跳过(既有调用口径零扰动)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row) | (待填) | | | +| 1179 | 118 | "global 行 battle 节点静默跳过(门控兜底零报错)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row) | (待填) | | | -## S140 _test_variant_pool_gate(行 108–118 · 6 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S142 _test_shop_accessors(行 133–152 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1171 | 108 | "变异池 unlock 前不含未解锁行(level=unlock−1)" | not _rows_contain(before, gated_row) | (待填) | | | -| 1172 | 109 | "变异池 unlock 当级含该行" | _rows_contain(after, gated_row) | (待填) | | | -| 1173 | 113 | "∅ board_types 行通配(任一 board_type 命中)" | _rows_contain(any_pool, wildcard_row) | (待填) | | | -| 1174 | 116 | "global 行 boss 节点入池(board_types ∅ 通配)" | _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row) | (待填) | | | -| 1175 | 117 | "global 行缺省双参调用跳过(既有调用口径零扰动)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row) | (待填) | | | -| 1176 | 118 | "global 行 battle 节点静默跳过(门控兜底零报错)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row) | (待填) | | | +| 1180 | 133 | "ShopTable products 行族非空(表已已入库前提)" | false | (待填) | | | +| 1181 | 135 | "ShopTable products 行族非空" | true | (待填) | | | +| 1182 | 138 | "shop_product_row 按 product_id 精确命中首行" | got == first | (待填) | | | +| 1183 | 139 | "shop_product_row 未知 id 返回空字典(容错通道)" | db.shop_product_row("no_such_product_id__").is_empty() | (待填) | | | +| 1184 | 143 | "shop_gp_drop_row 按 tier 命中" | not db.shop_gp_drop_row(tier0).is_empty() | (待填) | | | +| 1185 | 147 | "shop_level_gp_bonus 按关卡序号命中行值" | db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus) | (待填) | | | +| 1186 | 148 | "shop_level_gp_bonus 未登记关卡返回 0(兜底通道)" | db.shop_level_gp_bonus(-1) == 0 | (待填) | | | +| 1187 | 152 | "shop_price_level_bonus 按关卡序号命中行值" | db.shop_price_level_bonus(plv) == int(price_rows[0].bonus) | (待填) | | | -## S141 _test_shop_accessors(行 133–152 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S143 _test_meta_accessors(行 162–166 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1177 | 133 | "ShopTable products 行族非空(表已已入库前提)" | false | (待填) | | | -| 1178 | 135 | "ShopTable products 行族非空" | true | (待填) | | | -| 1179 | 138 | "shop_product_row 按 product_id 精确命中首行" | got == first | (待填) | | | -| 1180 | 139 | "shop_product_row 未知 id 返回空字典(容错通道)" | db.shop_product_row("no_such_product_id__").is_empty() | (待填) | | | -| 1181 | 143 | "shop_gp_drop_row 按 tier 命中" | not db.shop_gp_drop_row(tier0).is_empty() | (待填) | | | -| 1182 | 147 | "shop_level_gp_bonus 按关卡序号命中行值" | db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus) | (待填) | | | -| 1183 | 148 | "shop_level_gp_bonus 未登记关卡返回 0(兜底通道)" | db.shop_level_gp_bonus(-1) == 0 | (待填) | | | -| 1184 | 152 | "shop_price_level_bonus 按关卡序号命中行值" | db.shop_price_level_bonus(plv) == int(price_rows[0].bonus) | (待填) | | | +| 1188 | 162 | "meta_boss_keystone_row 按关卡序号命中" | db.meta_boss_keystone_row(klv) == key_rows[0] | (待填) | | | +| 1189 | 166 | "meta_conversion_row 按 attr 命中" | db.meta_conversion_row(cattr) == conv_rows[0] | (待填) | | | -## S142 _test_meta_accessors(行 162–166 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S144 _test_manifest_dir_sync(行 188–188 · 1 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1185 | 162 | "meta_boss_keystone_row 按关卡序号命中" | db.meta_boss_keystone_row(klv) == key_rows[0] | (待填) | | | -| 1186 | 166 | "meta_conversion_row 按 attr 命中" | db.meta_conversion_row(cattr) == conv_rows[0] | (待填) | | | +| 1190 | 188 | "TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)" | synced | (待填) | | | -## S143 _test_manifest_dir_sync(行 188–188 · 1 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S145 _test_rowset_discipline(行 208–273 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1187 | 188 | "TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)" | synced | (待填) | | | +| 1191 | 208 | "混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)" | errs.is_empty() | (待填) | | | +| 1192 | 214 | "Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)" | errs.is_empty() | (待填) | | | +| 1193 | 225 | "行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕" | gap_hit | (待填) | | | +| 1194 | 235 | "同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕" | dup_hit | (待填) | | | +| 1195 | 246 | "单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)" | dom_hit | (待填) | | | +| 1196 | 256 | "非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕" | kind_hit | (待填) | | | +| 1197 | 265 | "缺 (battle, band 0) 锚行报 anchor〔fv10〕" | no_anchor_hit | (待填) | | | +| 1198 | 273 | "缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕" | no_boss_hit | (待填) | | | -## S144 _test_rowset_discipline(行 208–273 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S146 _init(行 17–17 · 1 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1188 | 208 | "混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)" | errs.is_empty() | (待填) | | | -| 1189 | 214 | "Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)" | errs.is_empty() | (待填) | | | -| 1190 | 225 | "行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕" | gap_hit | (待填) | | | -| 1191 | 235 | "同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕" | dup_hit | (待填) | | | -| 1192 | 246 | "单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)" | dom_hit | (待填) | | | -| 1193 | 256 | "非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕" | kind_hit | (待填) | | | -| 1194 | 265 | "缺 (battle, band 0) 锚行报 anchor〔fv10〕" | no_anchor_hit | (待填) | | | -| 1195 | 273 | "缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕" | no_boss_hit | (待填) | | | +| 1199 | 17 | "生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S145 _init(行 17–17 · 1 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S147 _test_production_rows(行 30–81 · 13 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1196 | 17 | "生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1200 | 30 | "六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕" | rows.size() == 6 | (待填) | | | +| 1201 | 35 | "路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕" | ids == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | +| 1202 | 46 | "六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕" | keys_closed | (待填) | | | +| 1203 | 53 | "初始持有恰 sword+light〔锻造工坊批 V1〕" | initial == ["light", "sword"] | (待填) | | | +| 1204 | 55 | "默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_attack_type() == "normal" | (待填) | | | +| 1205 | 56 | "默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_armor_type() == "light" | (待填) | | | +| 1206 | 58 | "长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("spear").get("prereq_route", "")) == "sword" and String(db.forge_route("staff").get("prereq_route", "")) == "sword" | (待填) | | | +| 1207 | 61 | "重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("heavy").get("prereq_route", "")) == "light" and String(db.forge_route("ward").get("prereq_route", "")) == "light" | (待填) | | | +| 1208 | 64 | "四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕" | int(db.forge_route("spear").get("unlock_price", 0)) == 200 and int(db.forge_route("staff").get("unlock_price", 0)) == 200 and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 and int(db.forge_route("ward").get("unlock_price", 0)) == 200 | (待填) | | | +| 1209 | 70 | "甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕" | int(db.forge_route("light").get("base_ac", 0)) == 10 and int(db.forge_route("heavy").get("base_ac", 0)) == 12 and int(db.forge_route("ward").get("base_ac", 0)) == 9 | (待填) | | | +| 1210 | 75 | "forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕" | String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward" | (待填) | | | +| 1211 | 77 | "forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕" | String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear" | (待填) | | | +| 1212 | 80 | "未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕" | db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty() | (待填) | | | -## S146 _test_production_rows(行 30–81 · 13 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S148 _test_price_ladder(行 91–94 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1197 | 30 | "六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕" | rows.size() == 6 | (待填) | | | -| 1198 | 35 | "路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕" | ids == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | -| 1199 | 46 | "六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕" | keys_closed | (待填) | | | -| 1200 | 53 | "初始持有恰 sword+light〔锻造工坊批 V1〕" | initial == ["light", "sword"] | (待填) | | | -| 1201 | 55 | "默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_attack_type() == "normal" | (待填) | | | -| 1202 | 56 | "默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_armor_type() == "light" | (待填) | | | -| 1203 | 58 | "长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("spear").get("prereq_route", "")) == "sword" and String(db.forge_route("staff").get("prereq_route", "")) == "sword" | (待填) | | | -| 1204 | 61 | "重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("heavy").get("prereq_route", "")) == "light" and String(db.forge_route("ward").get("prereq_route", "")) == "light" | (待填) | | | -| 1205 | 64 | "四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕" | int(db.forge_route("spear").get("unlock_price", 0)) == 200 and int(db.forge_route("staff").get("unlock_price", 0)) == 200 and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 and int(db.forge_route("ward").get("unlock_price", 0)) == 200 | (待填) | | | -| 1206 | 70 | "甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕" | int(db.forge_route("light").get("base_ac", 0)) == 10 and int(db.forge_route("heavy").get("base_ac", 0)) == 12 and int(db.forge_route("ward").get("base_ac", 0)) == 9 | (待填) | | | -| 1207 | 75 | "forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕" | String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward" | (待填) | | | -| 1208 | 77 | "forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕" | String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear" | (待填) | | | -| 1209 | 80 | "未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕" | db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty() | (待填) | | | +| 1213 | 91 | "长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕" | prices == [20, 30, 45, 67, 101] | (待填) | | | +| 1214 | 93 | "已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕" | ForgeTableScript.level_price(row, 5) == 151 | (待填) | | | -## S147 _test_price_ladder(行 91–94 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S149 _test_negative_cases(行 133–189 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1210 | 91 | "长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕" | prices == [20, 30, 45, 67, 101] | (待填) | | | -| 1211 | 93 | "已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕" | ForgeTableScript.level_price(row, 5) == 151 | (待填) | | | +| 1215 | 133 | "负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕" | t_ok.validate().is_empty() | (待填) | | | +| 1216 | 189 | "锻造 schema 负向:%s〔锻造工坊批 V1〕" ↩ % label | _has_cat(errs, expect_cat) | (待填) | | | -## S148 _test_negative_cases(行 133–189 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S150 _test_effect_kinds_closed(行 197–200 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1212 | 133 | "负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕" | t_ok.validate().is_empty() | (待填) | | | -| 1213 | 189 | "锻造 schema 负向:%s〔锻造工坊批 V1〕" ↩ % label | _has_cat(errs, expect_cat) | (待填) | | | +| 1217 | 197 | "效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕" | EffectEngineScript.EFFECT_KINDS.size() == 32 | (待填) | | | +| 1218 | 199 | "效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕" | EffectEngineScript.EVENTS.size() == 20 | (待填) | | | -## S149 _test_effect_kinds_closed(行 197–200 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S151 _init(行 16–17 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1214 | 197 | "效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕" | EffectEngineScript.EFFECT_KINDS.size() == 32 | (待填) | | | -| 1215 | 199 | "效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕" | EffectEngineScript.EVENTS.size() == 20 | (待填) | | | +| 1219 | 16 | "生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S150 _init(行 16–17 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S152 _test_production(行 47–55 · 4 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1216 | 16 | "生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1220 | 47 | "访问器读回 L1 行五旋钮齐备〔fv2〕" | int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1 | (待填) | | | +| 1221 | 50 | "访问器读回 L2/L3 行层深 9/13 递增〔fv2〕" | int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13 | (待填) | | | +| 1222 | 52 | "三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕" | int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8 | (待填) | | | +| 1223 | 55 | "生产表 validate 零错误〔fv2〕" | db.level_gen_params.validate().is_empty() | (待填) | | | -## S151 _test_production(行 47–55 · 4 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S153 _test_accessor(行 62–62 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1217 | 47 | "访问器读回 L1 行五旋钮齐备〔fv2〕" | int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1 | (待填) | | | -| 1218 | 50 | "访问器读回 L2/L3 行层深 9/13 递增〔fv2〕" | int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13 | (待填) | | | -| 1219 | 52 | "三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕" | int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8 | (待填) | | | -| 1220 | 55 | "生产表 validate 零错误〔fv2〕" | db.level_gen_params.validate().is_empty() | (待填) | | | +| 1224 | 62 | "row_by_level_index 缺行返回空字典〔fv2〕" | t.row_by_level_index(9).is_empty() | (待填) | | | -## S152 _test_accessor(行 62–62 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S154 _test_negative(行 72–105 · 11 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1221 | 62 | "row_by_level_index 缺行返回空字典〔fv2〕" | t.row_by_level_index(9).is_empty() | (待填) | | | +| 1225 | 72 | "合法三行域校验零错误(夹具自证)〔fv2〕" | _table(base).validate().is_empty() | (待填) | | | +| 1226 | 75 | "L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕" | not _table(bad_l1_exit).validate().is_empty() | (待填) | | | +| 1227 | 78 | "配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕" | not _table(bad_ratio).validate().is_empty() | (待填) | | | +| 1228 | 81 | "层深 5<6 被拒(层深下限已锁)〔fv2〕" | not _table(bad_layers).validate().is_empty() | (待填) | | | +| 1229 | 85 | "层深增幅倒挂被拒(递增且增幅变大)〔fv2〕" | not _table(bad_growth).validate().is_empty() | (待填) | | | +| 1230 | 88 | "出口 4 越域 [1,3] 被拒〔fv2〕" | not _table(bad_exit).validate().is_empty() | (待填) | | | +| 1231 | 92 | "岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕" | not _table([bad_fork_row, base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1232 | 97 | "字段键集非 ROW_KEYS 闭集被拒〔fv2〕" | not _table([bad_keys_row, base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1233 | 100 | "键重复行被拒〔fv2〕" | not _table([base[0], base[0]]).validate().is_empty() | (待填) | | | +| 1234 | 102 | "战斗保底 -1 被拒(≥0)〔fv2〕" | not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1235 | 105 | "空表域校验零错误(合法态)〔fv2〕" | _table([]).validate().is_empty() | (待填) | | | -## S153 _test_negative(行 72–105 · 11 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S155 _test_derive_determinism(行 36–40 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1222 | 72 | "合法三行域校验零错误(夹具自证)〔fv2〕" | _table(base).validate().is_empty() | (待填) | | | -| 1223 | 75 | "L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕" | not _table(bad_l1_exit).validate().is_empty() | (待填) | | | -| 1224 | 78 | "配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕" | not _table(bad_ratio).validate().is_empty() | (待填) | | | -| 1225 | 81 | "层深 5<6 被拒(层深下限已锁)〔fv2〕" | not _table(bad_layers).validate().is_empty() | (待填) | | | -| 1226 | 85 | "层深增幅倒挂被拒(递增且增幅变大)〔fv2〕" | not _table(bad_growth).validate().is_empty() | (待填) | | | -| 1227 | 88 | "出口 4 越域 [1,3] 被拒〔fv2〕" | not _table(bad_exit).validate().is_empty() | (待填) | | | -| 1228 | 92 | "岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕" | not _table([bad_fork_row, base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1229 | 97 | "字段键集非 ROW_KEYS 闭集被拒〔fv2〕" | not _table([bad_keys_row, base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1230 | 100 | "键重复行被拒〔fv2〕" | not _table([base[0], base[0]]).validate().is_empty() | (待填) | | | -| 1231 | 102 | "战斗保底 -1 被拒(≥0)〔fv2〕" | not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1232 | 105 | "空表域校验零错误(合法态)〔fv2〕" | _table([]).validate().is_empty() | (待填) | | | +| 1236 | 36 | "同种子两实例 derive_at(0..7) 逐位一致" | same | (待填) | | | +| 1237 | 40 | "derive_at 为纯函数不推计数器" | a2.counter() == before | (待填) | | | -## S154 _test_derive_determinism(行 36–40 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S156 _test_counter_ledger(行 51–57 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1233 | 36 | "同种子两实例 derive_at(0..7) 逐位一致" | same | (待填) | | | -| 1234 | 40 | "derive_at 为纯函数不推计数器" | a2.counter() == before | (待填) | | | +| 1238 | 51 | "open_stream 逐次推进全局计数器(+1/次)" | c1 == c0 + 1 and c2 == c0 + 2 | (待填) | | | +| 1239 | 57 | "同 scope 异用途标签派生种子一致(标签不参与派生)" | s1 == s2 | (待填) | | | -## S155 _test_counter_ledger(行 51–57 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S157 _test_stream_draw_determinism(行 71–71 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1235 | 51 | "open_stream 逐次推进全局计数器(+1/次)" | c1 == c0 + 1 and c2 == c0 + 2 | (待填) | | | -| 1236 | 57 | "同 scope 异用途标签派生种子一致(标签不参与派生)" | s1 == s2 | (待填) | | | +| 1240 | 71 | "同种子两实例同标签流取数 12 次逐位一致" | same | (待填) | | | -## S156 _test_stream_draw_determinism(行 71–71 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S158 _test_negative_seed_normalization(行 79–79 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1237 | 71 | "同种子两实例同标签流取数 12 次逐位一致" | same | (待填) | | | +| 1241 | 79 | "负种子 abs 归一化后派生与正种子一致" | pos.derive_at(0) == neg.derive_at(0) | (待填) | | | -## S157 _test_negative_seed_normalization(行 79–79 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S159 _smoke_initialize(行 28–53 · 13 条 · 源 game/tests/unit/data_config/rng_stream_boot_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1238 | 79 | "负种子 abs 归一化后派生与正种子一致" | pos.derive_at(0) == neg.derive_at(0) | (待填) | | | +| 1242 | 28 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | (待填) | | | +| 1243 | 37 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | (待填) | | | +| 1244 | 38 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | (待填) | | | +| 1245 | 39 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | (待填) | | | +| 1246 | 40 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | (待填) | | | +| 1247 | 41 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | (待填) | | | +| 1248 | 42 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | (待填) | | | +| 1249 | 43 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | (待填) | | | +| 1250 | 44 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | (待填) | | | +| 1251 | 45 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | (待填) | | | +| 1252 | 49 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | (待填) | | | +| 1253 | 50 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | (待填) | | | +| 1254 | 53 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | (待填) | | | -## S158 _smoke_initialize(行 28–53 · 13 条 · 源 game/tests/unit/data_config/rng_stream_boot_test.gd · 去向:待分类回填) +## S160 _smoke_adventure_table(行 92–203 · 18 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1239 | 28 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | (待填) | | | -| 1240 | 37 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | (待填) | | | -| 1241 | 38 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | (待填) | | | -| 1242 | 39 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | (待填) | | | -| 1243 | 40 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | (待填) | | | -| 1244 | 41 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | (待填) | | | -| 1245 | 42 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | (待填) | | | -| 1246 | 43 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | (待填) | | | -| 1247 | 44 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | (待填) | | | -| 1248 | 45 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | (待填) | | | -| 1249 | 49 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | (待填) | | | -| 1250 | 50 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | (待填) | | | -| 1251 | 53 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | (待填) | | | +| 1255 | 92 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | (待填) | | | +| 1256 | 95 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | (待填) | | | +| 1257 | 96 | "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 | (待填) | | | +| 1258 | 99 | "档位配额结构锚(四级 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]) | (待填) | | | +| 1259 | 101 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | (待填) | | | +| 1260 | 102 | "数值对(四级换代承袭):小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 | (待填) | | | +| 1261 | 107 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | (待填) | | | +| 1262 | 109 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | (待填) | | | +| 1263 | 111 | "同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, null) == 0 | (待填) | | | +| 1264 | 113 | "同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, _TreeSaveMock.new()) == 1 | (待填) | | | +| 1265 | 120 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | (待填) | | | +| 1266 | 131 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | (待填) | | | +| 1267 | 159 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | (待填) | | | +| 1268 | 162 | "装配→战斗贯通: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") | (待填) | | | +| 1269 | 166 | "空装配基线:不带 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") | (待填) | | | +| 1270 | 181 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | (待填) | | | +| 1271 | 188 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | (待填) | | | +| 1272 | 202 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | (待填) | | | -## S159 _smoke_adventure_table(行 92–203 · 18 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S161 _smoke_enemy_bands(行 229–261 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1252 | 92 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | (待填) | | | -| 1253 | 95 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | (待填) | | | -| 1254 | 96 | "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 | (待填) | | | -| 1255 | 99 | "档位配额结构锚(四级 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]) | (待填) | | | -| 1256 | 101 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | (待填) | | | -| 1257 | 102 | "数值对(四级换代承袭):小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 | (待填) | | | -| 1258 | 107 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | (待填) | | | -| 1259 | 109 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | (待填) | | | -| 1260 | 111 | "同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, null) == 0 | (待填) | | | -| 1261 | 113 | "同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, _TreeSaveMock.new()) == 1 | (待填) | | | -| 1262 | 120 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | (待填) | | | -| 1263 | 131 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | (待填) | | | -| 1264 | 159 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | (待填) | | | -| 1265 | 162 | "装配→战斗贯通: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") | (待填) | | | -| 1266 | 166 | "空装配基线:不带 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") | (待填) | | | -| 1267 | 181 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | (待填) | | | -| 1268 | 188 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | (待填) | | | -| 1269 | 202 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | (待填) | | | +| 1273 | 229 | "敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 28 and got_keys == expect_keys | (待填) | | | +| 1274 | 236 | "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 | (待填) | | | +| 1275 | 239 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | (待填) | | | +| 1276 | 260 | "三关 Boss 行集带键 = 覆盖表(模板终点 4/9/9 在册+生成图终点 6/8/9/11/12/13 扩带)%s〔fv13〕" ↩ % boss_detail | boss_ok | (待填) | | | -## S160 _smoke_enemy_bands(行 229–261 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S162 _smoke_ac_swap_guard(行 272–380 · 13 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1270 | 229 | "敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 28 and got_keys == expect_keys | (待填) | | | -| 1271 | 236 | "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 | (待填) | | | -| 1272 | 239 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | (待填) | | | -| 1273 | 260 | "三关 Boss 行集带键 = 覆盖表(模板终点 4/9/9 在册+生成图终点 6/8/9/11/12/13 扩带)%s〔fv13〕" ↩ % boss_detail | boss_ok | (待填) | | | +| 1277 | 272 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | (待填) | | | +| 1278 | 274 | "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" | (待填) | | | +| 1279 | 282 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | (待填) | | | +| 1280 | 284 | "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") | (待填) | | | +| 1281 | 299 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | (待填) | | | +| 1282 | 305 | "hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)" | hd_maps == 0 | (待填) | | | +| 1283 | 307 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | (待填) | | | +| 1284 | 324 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | (待填) | | | +| 1285 | 332 | "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)) | (待填) | | | +| 1286 | 348 | "熊地精首遇带(1,battle,%d)降档锚定(hp 22 · ac 2 · 池 attack6/charge2/heavy2 零改动——迭代 24 N1;首遇带数据驱动定位)〔fv21〕" ↩ % l12_band | l12_band >= 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} | (待填) | | | +| 1287 | 370 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | (待填) | | | +| 1288 | 375 | "掷点参数拍板值在位(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" | (待填) | | | +| 1289 | 379 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | (待填) | | | -## S161 _smoke_ac_swap_guard(行 272–380 · 13 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S163 _smoke_variation_framework(行 387–415 · 9 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1274 | 272 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | (待填) | | | -| 1275 | 274 | "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" | (待填) | | | -| 1276 | 282 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | (待填) | | | -| 1277 | 284 | "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") | (待填) | | | -| 1278 | 299 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | (待填) | | | -| 1279 | 305 | "hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)" | hd_maps == 0 | (待填) | | | -| 1280 | 307 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | (待填) | | | -| 1281 | 324 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | (待填) | | | -| 1282 | 332 | "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)) | (待填) | | | -| 1283 | 348 | "熊地精首遇带(1,battle,%d)降档锚定(hp 22 · ac 2 · 池 attack6/charge2/heavy2 零改动——迭代 24 N1;首遇带数据驱动定位)〔fv21〕" ↩ % l12_band | l12_band >= 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} | (待填) | | | -| 1284 | 370 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | (待填) | | | -| 1285 | 375 | "掷点参数拍板值在位(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" | (待填) | | | -| 1286 | 379 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | (待填) | | | +| 1290 | 387 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | (待填) | | | +| 1291 | 389 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | (待填) | | | +| 1292 | 394 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | (待填) | | | +| 1293 | 398 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | (待填) | | | +| 1294 | 399 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | (待填) | | | +| 1295 | 403 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | (待填) | | | +| 1296 | 405 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | (待填) | | | +| 1297 | 411 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 1298 | 415 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | (待填) | | | -## S162 _smoke_variation_framework(行 387–415 · 9 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S164 _smoke_difficulty_table(行 422–444 · 8 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1287 | 387 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | (待填) | | | -| 1288 | 389 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | (待填) | | | -| 1289 | 394 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | (待填) | | | -| 1290 | 398 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | (待填) | | | -| 1291 | 399 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | (待填) | | | -| 1292 | 403 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | (待填) | | | -| 1293 | 405 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | (待填) | | | -| 1294 | 411 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 1295 | 415 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 1299 | 422 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | (待填) | | | +| 1300 | 425 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1301 | 428 | "空表零修饰(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) | (待填) | | | +| 1302 | 433 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1303 | 435 | "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) | (待填) | | | +| 1304 | 437 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | (待填) | | | +| 1305 | 442 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1306 | 443 | "越域乘数 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 | (待填) | | | -## S163 _smoke_difficulty_table(行 422–444 · 8 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S165 _smoke_nine_types_gp_stream(行 452–494 · 6 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1296 | 422 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | (待填) | | | -| 1297 | 425 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 1298 | 428 | "空表零修饰(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) | (待填) | | | -| 1299 | 433 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 1300 | 435 | "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) | (待填) | | | -| 1301 | 437 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | (待填) | | | -| 1302 | 442 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 1303 | 443 | "越域乘数 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 | (待填) | | | +| 1307 | 452 | "格面资源十型封闭(第九型 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") | (待填) | | | +| 1308 | 467 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/通关〕;清图槽独立批尾不计)" | cell_rows == 18 | (待填) | | | +| 1309 | 474 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | (待填) | | | +| 1310 | 487 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | (待填) | | | +| 1311 | 489 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | (待填) | | | +| 1312 | 493 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | (待填) | | | -## S164 _smoke_nine_types_gp_stream(行 452–494 · 6 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S166 _smoke_meta(行 502–515 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1304 | 452 | "格面资源十型封闭(第九型 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") | (待填) | | | -| 1305 | 467 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/通关〕;清图槽独立批尾不计)" | cell_rows == 18 | (待填) | | | -| 1306 | 474 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | (待填) | | | -| 1307 | 487 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | (待填) | | | -| 1308 | 489 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | (待填) | | | -| 1309 | 493 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | (待填) | | | +| 1313 | 502 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | (待填) | | | +| 1314 | 504 | "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 | (待填) | | | +| 1315 | 510 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | (待填) | | | +| 1316 | 515 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | (待填) | | | -## S165 _smoke_meta(行 502–515 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S167 _init(行 13–14 · 2 条 · 源 game/tests/unit/example_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1310 | 502 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | (待填) | | | -| 1311 | 504 | "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 | (待填) | | | -| 1312 | 510 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | (待填) | | | -| 1313 | 515 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | (待填) | | | +| 1317 | 13 | "headless SceneTree 协议可用(quit(code) 通道)" | _test_headless_protocol() | (待填) | | | +| 1318 | 14 | "命名规范可执行:test_[scenario]_[expected]" | _test_naming_convention_followed() | (待填) | | | -## S166 _init(行 13–14 · 2 条 · 源 game/tests/unit/example_test.gd · 去向:待分类回填) +## S168 _test_numeric_lines_carry_values(行 30–35 · 3 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1314 | 13 | "headless SceneTree 协议可用(quit(code) 通道)" | _test_headless_protocol() | (待填) | | | -| 1315 | 14 | "命名规范可执行:test_[scenario]_[expected]" | _test_naming_convention_followed() | (待填) | | | +| 1319 | 30 | "休整叙述带入回复量与当前血量(实际「%s」)" ↩ % rest | rest.contains("12") and rest.contains("30") and rest.contains("40") | (待填) | | | +| 1320 | 33 | "商店叙述带入 GP 数(实际「%s」)" ↩ % shop | shop.contains("77") | (待填) | | | +| 1321 | 34 | "奖励行与休整行互异(不是同一句套所有场景)" | NarratorScript.reward_line() != rest | (待填) | | | -## S167 _test_numeric_lines_carry_values(行 30–35 · 3 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S169 _test_end_text_differs_by_result(行 41–47 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1316 | 30 | "休整叙述带入回复量与当前血量(实际「%s」)" ↩ % rest | rest.contains("12") and rest.contains("30") and rest.contains("40") | (待填) | | | -| 1317 | 33 | "商店叙述带入 GP 数(实际「%s」)" ↩ % shop | shop.contains("77") | (待填) | | | -| 1318 | 34 | "奖励行与休整行互异(不是同一句套所有场景)" | NarratorScript.reward_line() != rest | (待填) | | | +| 1322 | 41 | "关卡收束态恒为「关卡通关!」(实际「%s」)" ↩ % level | level == "关卡通关!" | (待填) | | | +| 1323 | 44 | "单场胜负文案互异(胜「%s」/负「%s」)" ↩ % [win, lose] | win != lose | (待填) | | | +| 1324 | 45 | "胜利文案非空(实际「%s」)" ↩ % win | win != "" | (待填) | | | +| 1325 | 47 | "未知结果有兜底文案不吐空串(实际「%s」)" ↩ % unknown | unknown != "" | (待填) | | | -## S168 _test_end_text_differs_by_result(行 41–47 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S170 _test_phase_text_nonempty(行 52–54 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1319 | 41 | "关卡收束态恒为「关卡通关!」(实际「%s」)" ↩ % level | level == "关卡通关!" | (待填) | | | -| 1320 | 44 | "单场胜负文案互异(胜「%s」/负「%s」)" ↩ % [win, lose] | win != lose | (待填) | | | -| 1321 | 45 | "胜利文案非空(实际「%s」)" ↩ % win | win != "" | (待填) | | | -| 1322 | 47 | "未知结果有兜底文案不吐空串(实际「%s」)" ↩ % unknown | unknown != "" | (待填) | | | +| 1326 | 52 | "相位叙述非空(实际「%s」)" ↩ % t | t != "" | (待填) | | | +| 1327 | 54 | "Boss 在场时叙述带入 Boss 名(实际「%s」)" ↩ % boss | boss.contains("灰矮人石君王") | (待填) | | | -## S169 _test_phase_text_nonempty(行 52–54 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S171 _test_boss_name_uses_boss_rowset(行 81–82 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1323 | 52 | "相位叙述非空(实际「%s」)" ↩ % t | t != "" | (待填) | | | -| 1324 | 54 | "Boss 在场时叙述带入 Boss 名(实际「%s」)" ↩ % boss | boss.contains("灰矮人石君王") | (待填) | | | +| 1328 | 81 | "Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" ↩ % [boss_expect, got] | got == boss_expect | (待填) | | | +| 1329 | 82 | "Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" ↩ % [boss_expect, battle_name] | boss_expect != battle_name | (待填) | | | -## S170 _test_boss_name_uses_boss_rowset(行 81–82 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S172 _run(行 27–102 · 13 条 · 源 game/tests/unit/presentation/forge_workbench_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1325 | 81 | "Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" ↩ % [boss_expect, got] | got == boss_expect | (待填) | | | -| 1326 | 82 | "Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" ↩ % [boss_expect, battle_name] | boss_expect != battle_name | (待填) | | | +| 1330 | 27 | "ConfigDb 加载(工作间测试夹具前提)" | false | (待填) | | | +| 1331 | 44 | "锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕" | not ov._forge_workbench.visible and ov._forge_lock_l.visible | (待填) | | | +| 1332 | 46 | "锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0 | (待填) | | | +| 1333 | 52 | "解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕" | ov._forge_workbench.visible | (待填) | | | +| 1334 | 53 | "两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3 | (待填) | | | +| 1335 | 56 | "六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕" | rows.has("sword") and rows.has("spear") and rows.has("staff") and rows.has("light") and rows.has("heavy") and rows.has("ward") | (待填) | | | +| 1336 | 63 | "长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕" | rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled | (待填) | | | +| 1337 | 66 | "轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕" | rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled | (待填) | | | +| 1338 | 73 | "长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕" | rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled and rsp.get("buy_btn") == null and rsp.get("select_btn") == null | (待填) | | | +| 1339 | 76 | "魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕" | rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") and String(rwa.status_l.text).contains("200") | (待填) | | | +| 1340 | 84 | "余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕" | rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled | (待填) | | | +| 1341 | 92 | "长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕" | rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null | (待填) | | | +| 1342 | 101 | "三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕" | buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"] | (待填) | | | -## S171 _run(行 27–102 · 13 条 · 源 game/tests/unit/presentation/forge_workbench_test.gd · 去向:待分类回填) +## S173 _test_accepted_text(行 20–20 · 1 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1327 | 27 | "ConfigDb 加载(工作间测试夹具前提)" | false | (待填) | | | -| 1328 | 44 | "锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕" | not ov._forge_workbench.visible and ov._forge_lock_l.visible | (待填) | | | -| 1329 | 46 | "锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0 | (待填) | | | -| 1330 | 52 | "解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕" | ov._forge_workbench.visible | (待填) | | | -| 1331 | 53 | "两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3 | (待填) | | | -| 1332 | 56 | "六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕" | rows.has("sword") and rows.has("spear") and rows.has("staff") and rows.has("light") and rows.has("heavy") and rows.has("ward") | (待填) | | | -| 1333 | 63 | "长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕" | rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled | (待填) | | | -| 1334 | 66 | "轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕" | rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled | (待填) | | | -| 1335 | 73 | "长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕" | rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled and rsp.get("buy_btn") == null and rsp.get("select_btn") == null | (待填) | | | -| 1336 | 76 | "魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕" | rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") and String(rwa.status_l.text).contains("200") | (待填) | | | -| 1337 | 84 | "余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕" | rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled | (待填) | | | -| 1338 | 92 | "长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕" | rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null | (待填) | | | -| 1339 | 101 | "三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕" | buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"] | (待填) | | | +| 1343 | 20 | "受理态文案固定为「执行完成。」(实际「%s」)" ↩ % t | t == "执行完成。" | (待填) | | | -## S172 _test_accepted_text(行 20–20 · 1 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S174 _test_reject_text_by_reason(行 36–38 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1340 | 20 | "受理态文案固定为「执行完成。」(实际「%s」)" ↩ % t | t == "执行完成。" | (待填) | | | +| 1344 | 36 | "棋盘面各拒绝原因文案逐类正确(错 %d 项 %s)" ↩ % [bad.size(), str(bad)] | bad.is_empty() | (待填) | | | +| 1345 | 38 | "资源面拒绝文案为「AP 不足。」(实际「%s」)" ↩ % ap | ap == "AP 不足。" | (待填) | | | -## S173 _test_reject_text_by_reason(行 36–38 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S175 _test_unknown_reason_fallback(行 43–47 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1341 | 36 | "棋盘面各拒绝原因文案逐类正确(错 %d 项 %s)" ↩ % [bad.size(), str(bad)] | bad.is_empty() | (待填) | | | -| 1342 | 38 | "资源面拒绝文案为「AP 不足。」(实际「%s」)" ↩ % ap | ap == "AP 不足。" | (待填) | | | +| 1346 | 43 | "未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" ↩ % t | t == "动作被拒。" | (待填) | | | +| 1347 | 47 | "棋盘面未知子因兜底带前缀可诊断(实际「%s」)" ↩ % sub | sub.begins_with("棋盘面拒绝: ") | (待填) | | | -## S174 _test_unknown_reason_fallback(行 43–47 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S176 _test_fog_hides_type(行 51–56 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1343 | 43 | "未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" ↩ % t | t == "动作被拒。" | (待填) | | | -| 1344 | 47 | "棋盘面未知子因兜底带前缀可诊断(实际「%s」)" ↩ % sub | sub.begins_with("棋盘面拒绝: ") | (待填) | | | +| 1348 | 51 | "可见集内节点:图标/类型文字均绘制〔fv2〕" | bool(shown["draw_icon"]) and bool(shown["draw_label"]) | (待填) | | | +| 1349 | 54 | "雾下节点:图标与类型文字均不绘制(显隐语义,非灰化)〔fv2〕" | not bool(fogged["draw_icon"]) and not bool(fogged["draw_label"]) | (待填) | | | +| 1350 | 56 | "雾下非 Boss 节点无 Boss 描边〔fv2〕" | not bool(fogged["boss_border"]) | (待填) | | | -## S175 _test_fog_hides_type(行 50–55 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S177 _test_boss_exempt_and_multi_boss(行 65–73 · 4 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1345 | 50 | "可见集内节点:图标/类型文字均绘制〔fv2〕" | bool(shown["draw_icon"]) and bool(shown["draw_label"]) | (待填) | | | -| 1346 | 53 | "雾下节点:图标与类型文字均不绘制(显隐语义,非灰化)〔fv2〕" | not bool(fogged["draw_icon"]) and not bool(fogged["draw_label"]) | (待填) | | | -| 1347 | 55 | "雾下非 Boss 节点无 Boss 描边〔fv2〕" | not bool(fogged["boss_border"]) | (待填) | | | +| 1351 | 65 | "多终点:两个 Boss 节点均带 Boss 描边〔fv3〕" | bool(boss_a["boss_border"]) and bool(boss_b["boss_border"]) | (待填) | | | +| 1352 | 67 | "Boss 不入雾:可见集与图标/文字三通道均在〔fv3〕" | bool(boss_a["draw_icon"]) and bool(boss_a["draw_label"]) and bool(boss_b["draw_icon"]) | (待填) | | | +| 1353 | 70 | "Boss 豁免独立于可见集:不在 type_vis 的 Boss 仍出示类型〔fv3〕" | bool(far_boss["draw_icon"]) and bool(far_boss["draw_label"]) | (待填) | | | +| 1354 | 73 | "Boss 豁免不扩散:雾下非 Boss 仍隐类型〔fv3〕" | not bool(fogged_battle["draw_icon"]) | (待填) | | | -## S176 _test_boss_exempt_and_multi_boss(行 64–72 · 4 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S178 _test_details_never_shown(行 87–89 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1348 | 64 | "多终点:两个 Boss 节点均带 Boss 描边〔fv3〕" | bool(boss_a["boss_border"]) and bool(boss_b["boss_border"]) | (待填) | | | -| 1349 | 66 | "Boss 不入雾:可见集与图标/文字三通道均在〔fv3〕" | bool(boss_a["draw_icon"]) and bool(boss_a["draw_label"]) and bool(boss_b["draw_icon"]) | (待填) | | | -| 1350 | 69 | "Boss 豁免独立于可见集:不在 type_vis 的 Boss 仍出示类型〔fv3〕" | bool(far_boss["draw_icon"]) and bool(far_boss["draw_label"]) | (待填) | | | -| 1351 | 72 | "Boss 豁免不扩散:雾下非 Boss 仍隐类型〔fv3〕" | not bool(fogged_battle["draw_icon"]) | (待填) | | | +| 1355 | 87 | "决策面不消费内容键:携带 enemy_id/reward_id 与不带者输出逐键相等〔fv5〕" | same | (待填) | | | +| 1356 | 88 | "label_text 不含内容值(实际「%s」)〔fv5〕" ↩ % String(b["label_text"]) | not String(b["label_text"]).contains("goblin_secret") and not String(b["label_text"]).contains("reward_secret") | (待填) | | | -## S177 _test_details_never_shown(行 86–88 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S179 _test_label_three_states(行 96–101 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1352 | 86 | "决策面不消费内容键:携带 enemy_id/reward_id 与不带者输出逐键相等〔fv5〕" | same | (待填) | | | -| 1353 | 87 | "label_text 不含内容值(实际「%s」)〔fv5〕" ↩ % String(b["label_text"]) | not String(b["label_text"]).contains("goblin_secret") and not String(b["label_text"]).contains("reward_secret") | (待填) | | | +| 1357 | 96 | "当前位文案=「当前位置」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置" | (待填) | | | +| 1358 | 98 | "候选文案=「战斗 · 可选」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, true)["label_text"]) == "战斗 · 可选" | (待填) | | | +| 1359 | 100 | "其余文案=类型名「战斗」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, false)["label_text"]) == "战斗" | (待填) | | | -## S178 _test_label_three_states(行 95–100 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S180 _test_big_graph_fit(行 165–191 · 6 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1354 | 95 | "当前位文案=「当前位置」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置" | (待填) | | | -| 1355 | 97 | "候选文案=「战斗 · 可选」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, true)["label_text"]) == "战斗 · 可选" | (待填) | | | -| 1356 | 99 | "其余文案=类型名「战斗」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, false)["label_text"]) == "战斗" | (待填) | | | +| 1360 | 165 | "大图夹具规模:≥11 层 × ≥6 槽(实测最大层 %d/最大槽 %d)〔fv8〕" ↩ % [maxl, maxslot] | maxl >= 10 and maxslot >= 5 | (待填) | | | +| 1361 | 171 | "大图拟合:画布 min.x 覆盖最深节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.x, need_w] | min_size.x >= need_w - 0.01 | (待填) | | | +| 1362 | 173 | "大图拟合:画布 min.y 覆盖最宽层底节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.y, need_h] | min_size.y >= need_h - 0.01 | (待填) | | | +| 1363 | 181 | "大图布局:全节点方块落在拟合画布内(origin=(%.1f,%.1f) size=(%.1f,%.1f))〔fv8〕" ↩ % [origin.x, origin.y, min_size.x, min_size.y] | all_in | (待填) | | | +| 1364 | 188 | "大图呈现:两个终点 Boss 均有描边标记〔fv8〕" | disp.has(boss_ids[0]) and disp.has(boss_ids[1]) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]) | (待填) | | | +| 1365 | 191 | "大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕" | not bool(disp[32]["draw_icon"]) | (待填) | | | -## S179 _test_big_graph_fit(行 164–190 · 6 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S181 _test_layout_cache(行 209–226 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1357 | 164 | "大图夹具规模:≥11 层 × ≥6 槽(实测最大层 %d/最大槽 %d)〔fv8〕" ↩ % [maxl, maxslot] | maxl >= 10 and maxslot >= 5 | (待填) | | | -| 1358 | 170 | "大图拟合:画布 min.x 覆盖最深节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.x, need_w] | min_size.x >= need_w - 0.01 | (待填) | | | -| 1359 | 172 | "大图拟合:画布 min.y 覆盖最宽层底节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.y, need_h] | min_size.y >= need_h - 0.01 | (待填) | | | -| 1360 | 180 | "大图布局:全节点方块落在拟合画布内(origin=(%.1f,%.1f) size=(%.1f,%.1f))〔fv8〕" ↩ % [origin.x, origin.y, min_size.x, min_size.y] | all_in | (待填) | | | -| 1361 | 187 | "大图呈现:两个终点 Boss 均有描边标记〔fv8〕" | disp.has(boss_ids[0]) and disp.has(boss_ids[1]) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]) | (待填) | | | -| 1362 | 190 | "大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕" | not bool(disp[32]["draw_icon"]) | (待填) | | | +| 1366 | 209 | "布局缓存:同 nodes 两次查询返回同一实例(第二次命中缓存)〔levelmap批 V1〕" | is_same(p1, p2) | (待填) | | | +| 1367 | 224 | "布局缓存换代:新图查询返回新实例非旧缓存(失效重算生效)〔levelmap批 V2〕" | not is_same(p1, pc1) and is_same(pc1, pc2) and (pc1[chain[0]] as Vector2) != (p1[nodes[0]] as Vector2) | (待填) | | | -## S180 _init(行 105–140 · 9 条 · 源 game/tests/unit/presentation/pacing_queue_test.gd · 去向:待分类回填) +## S182 _init(行 105–140 · 9 条 · 源 game/tests/unit/presentation/pacing_queue_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1363 | 105 | "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)" | snap_before == snap_after | (待填) | | | -| 1364 | 107 | "结算行已产生(断言前提)" | rows_seen.size() > 0 | (待填) | | | -| 1365 | 108 | "排空后队列恒空(flush 完整性)" | main._row_queue.is_empty() | (待填) | | | -| 1366 | 117 | "末信号行原文在明细带(排空不吞行:『%s』)" ↩ % last_text | last_text != "" and main.log_view.parsed_text().contains(last_text) | (待填) | | | -| 1367 | 123 | "常态段间延迟 >0 且 ≥ 段内(%f/%f)" ↩ % [d_on, d_same] | d_on > 0.0 and d_on >= d_same | (待填) | | | -| 1368 | 125 | "减动效段间延迟归零(行不跳过——同帧直落快路)" | main.pace_delay_for("damage", "heal") == 0.0 | (待填) | | | -| 1369 | 135 | "结算标出按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "哥布林"] | (待填) | | | -| 1370 | 138 | "跨区弹道按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 2 and fstage.calls[1] == ["goblin_b", "哥布林"] | (待填) | | | -| 1371 | 140 | "两路解析成功后演出层各接一拍(记参面非空)〔wd12〕" | ffx.calls.size() == 2 | (待填) | | | +| 1368 | 105 | "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)" | snap_before == snap_after | (待填) | | | +| 1369 | 107 | "结算行已产生(断言前提)" | rows_seen.size() > 0 | (待填) | | | +| 1370 | 108 | "排空后队列恒空(flush 完整性)" | main._row_queue.is_empty() | (待填) | | | +| 1371 | 117 | "末信号行原文在明细带(排空不吞行:『%s』)" ↩ % last_text | last_text != "" and main.log_view.parsed_text().contains(last_text) | (待填) | | | +| 1372 | 123 | "常态段间延迟 >0 且 ≥ 段内(%f/%f)" ↩ % [d_on, d_same] | d_on > 0.0 and d_on >= d_same | (待填) | | | +| 1373 | 125 | "减动效段间延迟归零(行不跳过——同帧直落快路)" | main.pace_delay_for("damage", "heal") == 0.0 | (待填) | | | +| 1374 | 135 | "结算标出按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "哥布林"] | (待填) | | | +| 1375 | 138 | "跨区弹道按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 2 and fstage.calls[1] == ["goblin_b", "哥布林"] | (待填) | | | +| 1376 | 140 | "两路解析成功后演出层各接一拍(记参面非空)〔wd12〕" | ffx.calls.size() == 2 | (待填) | | | -## S181 _sweep_hero_panel(行 47–50 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S183 _sweep_hero_panel(行 47–50 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1372 | 47 | "C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕" | feat_head != null and feat_head.text == "专长" | (待填) | | | -| 1373 | 49 | "C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕" | curio_head != null and curio_head.text == "属性值提升" | (待填) | | | +| 1377 | 47 | "C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕" | feat_head != null and feat_head.text == "专长" | (待填) | | | +| 1378 | 49 | "C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕" | curio_head != null and curio_head.text == "属性值提升" | (待填) | | | -## S182 _sweep_narrator(行 57–64 · 7 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S184 _sweep_narrator(行 57–64 · 7 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1374 | 57 | "奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕" | head.contains("冒险牌库") | (待填) | | | -| 1375 | 58 | "奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕" | head.contains("属性值提升与补给") | (待填) | | | -| 1376 | 59 | "奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕" | head.contains("同库分流") | (待填) | | | -| 1377 | 60 | "奖励点头部零「同权重」旧话(u3 清零)〔wa4〕" | not head.contains("同权重") | (待填) | | | -| 1378 | 61 | "奖励点头部零「同上限」旧话(u3 清零)〔wa4〕" | not head.contains("同上限") | (待填) | | | -| 1379 | 62 | "奖励点头部零「提升池」旧话(u3 清零)〔wa4〕" | not head.contains("提升池") | (待填) | | | -| 1380 | 63 | "专长卡兜底==「专长\\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕" | String(narrator.feat_card_text({})) == "专长\\n???" | (待填) | | | +| 1379 | 57 | "奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕" | head.contains("冒险牌库") | (待填) | | | +| 1380 | 58 | "奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕" | head.contains("属性值提升与补给") | (待填) | | | +| 1381 | 59 | "奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕" | head.contains("同库分流") | (待填) | | | +| 1382 | 60 | "奖励点头部零「同权重」旧话(u3 清零)〔wa4〕" | not head.contains("同权重") | (待填) | | | +| 1383 | 61 | "奖励点头部零「同上限」旧话(u3 清零)〔wa4〕" | not head.contains("同上限") | (待填) | | | +| 1384 | 62 | "奖励点头部零「提升池」旧话(u3 清零)〔wa4〕" | not head.contains("提升池") | (待填) | | | +| 1385 | 63 | "专长卡兜底==「专长\\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕" | String(narrator.feat_card_text({})) == "专长\\n???" | (待填) | | | -## S183 _sweep_legend(行 75–79 · 5 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S185 _sweep_legend(行 75–79 · 5 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1381 | 75 | "图例封印行存在(wa5 夹具前提)〔wa5〕" | not seal_row.is_empty() | (待填) | | | -| 1382 | 76 | "图例封印行含「标空」(u4——与「标中」成对)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("标空") | (待填) | | | -| 1383 | 77 | "图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚") | (待填) | | | -| 1384 | 78 | "图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错") | (待填) | | | -| 1385 | 79 | "图例封印行零「挖雷」(u4 雷字清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷") | (待填) | | | +| 1386 | 75 | "图例封印行存在(wa5 夹具前提)〔wa5〕" | not seal_row.is_empty() | (待填) | | | +| 1387 | 76 | "图例封印行含「标空」(u4——与「标中」成对)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("标空") | (待填) | | | +| 1388 | 77 | "图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚") | (待填) | | | +| 1389 | 78 | "图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错") | (待填) | | | +| 1390 | 79 | "图例封印行零「挖雷」(u4 雷字清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷") | (待填) | | | -## S184 _sweep_source_names(行 92–104 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S186 _sweep_source_names(行 92–104 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1386 | 92 | "状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕" | slot != null | (待填) | | | -| 1387 | 95 | "状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕" | row_view._tip_label.text.contains("来源:危险格") | (待填) | | | -| 1388 | 98 | "结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕" | String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格" | (待填) | | | -| 1389 | 103 | "战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕" | row_txt.contains("(危险格)") | (待填) | | | +| 1391 | 92 | "状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕" | slot != null | (待填) | | | +| 1392 | 95 | "状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕" | row_view._tip_label.text.contains("来源:危险格") | (待填) | | | +| 1393 | 98 | "结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕" | String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格" | (待填) | | | +| 1394 | 103 | "战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕" | row_txt.contains("(危险格)") | (待填) | | | -## S185 _sweep_board_glyph(行 133–142 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S187 _sweep_board_glyph(行 133–142 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1390 | 133 | "棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" ↩ % glow_steps | mine_cell.x >= 0 | (待填) | | | -| 1391 | 139 | "已开危险格 glyph==「危」(u7 换代)〔wa8〕" | u.glyph == "危" | (待填) | | | -| 1392 | 140 | "已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕" | u.plate == true | (待填) | | | -| 1393 | 141 | "已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕" | u.glyph_color == UiKit.RED_MINE | (待填) | | | +| 1395 | 133 | "棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" ↩ % glow_steps | mine_cell.x >= 0 | (待填) | | | +| 1396 | 139 | "已开危险格 glyph==「危」(u7 换代)〔wa8〕" | u.glyph == "危" | (待填) | | | +| 1397 | 140 | "已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕" | u.plate == true | (待填) | | | +| 1398 | 141 | "已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕" | u.glyph_color == UiKit.RED_MINE | (待填) | | | -## S186 _sweep_token_scans(行 148–158 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S188 _sweep_token_scans(行 148–158 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1394 | 148 | "board_view.gd 源零 \\"雷\\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕" | board_src.count("\\"雷\\"") == 0 | (待填) | | | -| 1395 | 157 | "battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕" | orch_hits == 0 | (待填) | | | +| 1399 | 148 | "board_view.gd 源零 \\"雷\\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕" | board_src.count("\\"雷\\"") == 0 | (待填) | | | +| 1400 | 157 | "battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕" | orch_hits == 0 | (待填) | | | -## S187 _init(行 75–132 · 18 条 · 源 game/tests/unit/presentation/standee_fx_sweep_test.gd · 去向:待分类回填) +## S189 _init(行 75–132 · 18 条 · 源 game/tests/unit/presentation/standee_fx_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1396 | 75 | "站桩 enemy_id 访问器读装载值〔wd12〕" | s1.enemy_id() == "goblin_a" and s2.enemy_id() == "goblin_b" | (待填) | | | -| 1397 | 76 | "同名敌按 id 精确解析(不做名子串撞车)〔wd12〕" | stage._card_by_id("goblin_b") == s2 and stage._card_by_id("goblin_a") == s1 | (待填) | | | -| 1398 | 78 | "空 id / 未登记 id 恒不命中(名兜底归调用点)〔wd12〕" | stage._card_by_id("") == null and stage._card_by_id("no_such") == null | (待填) | | | -| 1399 | 83 | "伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕" | s2._flash_count == 1 and s1._flash_count == 0 | (待填) | | | -| 1400 | 88 | "弹道落点按 target_id 取站桩中心〔wd12〕" | c_b != null and c_b == c_b_expect | (待填) | | | -| 1401 | 91 | "无 target_id 时按名兜底同旧行为(首命中)〔wd12〕" | c_fallback != null and c_fallback == c_fallback_expect | (待填) | | | -| 1402 | 95 | "蓄力技能(heavy)置 _intent_charge=true〔wd2〕" | s1._intent_charge | (待填) | | | -| 1403 | 97 | "非蓄力攻击技(goblin_slash)零置位〔wd2〕" | not s1._intent_charge | (待填) | | | -| 1404 | 99 | "增益系零置位〔wd2〕" | not s1._intent_charge | (待填) | | | -| 1405 | 101 | "普攻零表示路径复位 _intent_charge〔wd2〕" | not s1._intent_charge | (待填) | | | -| 1406 | 108 | "首开胜利画面创建庆祝句柄〔wd4〕" | first != null and first.is_valid() | (待填) | | | -| 1407 | 111 | "重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕" | first != null and not first.is_valid() and second != null and second.is_valid() | (待填) | | | -| 1408 | 114 | "close 必杀庆祝句柄(句柄清空)〔wd4〕" | ov.get_meta(&"celebrate_tw", null) == null | (待填) | | | -| 1409 | 119 | "模拟运行期调试入口钮同置灰〔wd5〕" | dbg._dbg_btn.disabled | (待填) | | | -| 1410 | 121 | "模拟收尾调试入口钮恢复(未锁定)〔wd5〕" | not dbg._dbg_btn.disabled | (待填) | | | -| 1411 | 130 | "控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕" | drv.seen == [true, true] | (待填) | | | -| 1412 | 131 | "控制器收尾 is_running()=false〔wd8〕" | not ctrl.is_running() and not panel.running | (待填) | | | -| 1413 | 132 | "两局报告全文注入面板(run 收尾链路完整)〔wd8〕" | String(panel.report) != "" | (待填) | | | +| 1401 | 75 | "站桩 enemy_id 访问器读装载值〔wd12〕" | s1.enemy_id() == "goblin_a" and s2.enemy_id() == "goblin_b" | (待填) | | | +| 1402 | 76 | "同名敌按 id 精确解析(不做名子串撞车)〔wd12〕" | stage._card_by_id("goblin_b") == s2 and stage._card_by_id("goblin_a") == s1 | (待填) | | | +| 1403 | 78 | "空 id / 未登记 id 恒不命中(名兜底归调用点)〔wd12〕" | stage._card_by_id("") == null and stage._card_by_id("no_such") == null | (待填) | | | +| 1404 | 83 | "伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕" | s2._flash_count == 1 and s1._flash_count == 0 | (待填) | | | +| 1405 | 88 | "弹道落点按 target_id 取站桩中心〔wd12〕" | c_b != null and c_b == c_b_expect | (待填) | | | +| 1406 | 91 | "无 target_id 时按名兜底同旧行为(首命中)〔wd12〕" | c_fallback != null and c_fallback == c_fallback_expect | (待填) | | | +| 1407 | 95 | "蓄力技能(heavy)置 _intent_charge=true〔wd2〕" | s1._intent_charge | (待填) | | | +| 1408 | 97 | "非蓄力攻击技(goblin_slash)零置位〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1409 | 99 | "增益系零置位〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1410 | 101 | "普攻零表示路径复位 _intent_charge〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1411 | 108 | "首开胜利画面创建庆祝句柄〔wd4〕" | first != null and first.is_valid() | (待填) | | | +| 1412 | 111 | "重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕" | first != null and not first.is_valid() and second != null and second.is_valid() | (待填) | | | +| 1413 | 114 | "close 必杀庆祝句柄(句柄清空)〔wd4〕" | ov.get_meta(&"celebrate_tw", null) == null | (待填) | | | +| 1414 | 119 | "模拟运行期调试入口钮同置灰〔wd5〕" | dbg._dbg_btn.disabled | (待填) | | | +| 1415 | 121 | "模拟收尾调试入口钮恢复(未锁定)〔wd5〕" | not dbg._dbg_btn.disabled | (待填) | | | +| 1416 | 130 | "控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕" | drv.seen == [true, true] | (待填) | | | +| 1417 | 131 | "控制器收尾 is_running()=false〔wd8〕" | not ctrl.is_running() and not panel.running | (待填) | | | +| 1418 | 132 | "两局报告全文注入面板(run 收尾链路完整)〔wd8〕" | String(panel.report) != "" | (待填) | | | -## S188 _init(行 24–34 · 2 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S190 _init(行 24–34 · 2 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1414 | 24 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -| 1415 | 34 | "前置:board_gen 词表非空(%d 型)" ↩ % types.size() | types.size() > 0 | (待填) | | | +| 1419 | 24 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1420 | 34 | "前置:board_gen 词表非空(%d 型)" ↩ % types.size() | types.size() > 0 | (待填) | | | -## S189 _test_set_completeness(行 56–56 · 1 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S191 _test_set_completeness(行 56–56 · 1 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1416 | 56 | "图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" ↩ % [String(bt), variants, str(missing)] | missing.is_empty() | (待填) | | | +| 1421 | 56 | "图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" ↩ % [String(bt), variants, str(missing)] | missing.is_empty() | (待填) | | | -## S190 _test_hue_domains(行 72–82 · 4 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S192 _test_hue_domains(行 72–82 · 4 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1417 | 72 | "图型 %s 抽检样本可读" ↩ % String(bt) | false | (待填) | | | -| 1418 | 77 | "图型 %s 墙顶均值色相在带 %s(实测 %.3f)" ↩ % [String(bt), str(band), float(wall["h"])] | float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]) | (待填) | | | -| 1419 | 79 | "图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" ↩ % [String(bt), wall_v[bt], floor_v[bt]] | wall_v[bt] > floor_v[bt] | (待填) | | | -| 1420 | 82 | "deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)" | float(wall_v["deep_underdark"]) < float(wall_v["underdark"]) | (待填) | | | +| 1422 | 72 | "图型 %s 抽检样本可读" ↩ % String(bt) | false | (待填) | | | +| 1423 | 77 | "图型 %s 墙顶均值色相在带 %s(实测 %.3f)" ↩ % [String(bt), str(band), float(wall["h"])] | float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]) | (待填) | | | +| 1424 | 79 | "图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" ↩ % [String(bt), wall_v[bt], floor_v[bt]] | wall_v[bt] > floor_v[bt] | (待填) | | | +| 1425 | 82 | "deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)" | float(wall_v["deep_underdark"]) < float(wall_v["underdark"]) | (待填) | | | -## S191 _test_digit_ink_modes(行 137–159 · 5 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S193 _test_digit_ink_modes(行 137–159 · 5 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1421 | 137 | "图型 %s 数字墨族断言样本可读" ↩ % String(bt) | false | (待填) | | | -| 1422 | 144 | "图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" ↩ % [ String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])] | float(mode["cr_mean_dark"]) < float(TilePaletteScript.DIGIT_INK_CR_MEAN_DARK) | (待填) | | | -| 1423 | 151 | "图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, float(TilePaletteScript.DIGIT_INK_CR_P90), cr_p90] | cr_p90 >= float(TilePaletteScript.DIGIT_INK_CR_P90) | (待填) | | | -| 1424 | 155 | "图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, mean_gate, cr_mean] | cr_mean >= mean_gate | (待填) | | | -| 1425 | 158 | "图型 %s 呈现模式期望=%s(实测=%s)" ↩ % [String(bt), "色牌" if bool(expect_plate[bt]) else "直画", mode_name] | plate == bool(expect_plate[bt]) | (待填) | | | +| 1426 | 137 | "图型 %s 数字墨族断言样本可读" ↩ % String(bt) | false | (待填) | | | +| 1427 | 144 | "图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" ↩ % [ String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])] | float(mode["cr_mean_dark"]) < float(TilePaletteScript.DIGIT_INK_CR_MEAN_DARK) | (待填) | | | +| 1428 | 151 | "图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, float(TilePaletteScript.DIGIT_INK_CR_P90), cr_p90] | cr_p90 >= float(TilePaletteScript.DIGIT_INK_CR_P90) | (待填) | | | +| 1429 | 155 | "图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, mean_gate, cr_mean] | cr_mean >= mean_gate | (待填) | | | +| 1430 | 158 | "图型 %s 呈现模式期望=%s(实测=%s)" ↩ % [String(bt), "色牌" if bool(expect_plate[bt]) else "直画", mode_name] | plate == bool(expect_plate[bt]) | (待填) | | | -## S192 _smoke_type_glow(行 214–374 · 24 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S194 _smoke_type_glow(行 214–374 · 24 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1426 | 214 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | (待填) | | | -| 1427 | 216 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | -| 1428 | 219 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | -| 1429 | 221 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | (待填) | | | -| 1430 | 231 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | (待填) | | | -| 1431 | 248 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | (待填) | | | -| 1432 | 257 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | (待填) | | | -| 1433 | 271 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | (待填) | | | -| 1434 | 272 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | (待填) | | | -| 1435 | 274 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | -| 1436 | 287 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | (待填) | | | -| 1437 | 289 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | (待填) | | | -| 1438 | 291 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | -| 1439 | 297 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | -| 1440 | 303 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | (待填) | | | -| 1441 | 304 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | -| 1442 | 307 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | (待填) | | | -| 1443 | 308 | "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", {})) | (待填) | | | -| 1444 | 311 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | (待填) | | | -| 1445 | 315 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | (待填) | | | -| 1446 | 328 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | (待填) | | | -| 1447 | 330 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | (待填) | | | -| 1448 | 371 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | (待填) | | | -| 1449 | 374 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | (待填) | | | +| 1431 | 214 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | (待填) | | | +| 1432 | 216 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | +| 1433 | 219 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | +| 1434 | 221 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | (待填) | | | +| 1435 | 231 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | (待填) | | | +| 1436 | 248 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | (待填) | | | +| 1437 | 257 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | (待填) | | | +| 1438 | 271 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | (待填) | | | +| 1439 | 272 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | (待填) | | | +| 1440 | 274 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | +| 1441 | 287 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | (待填) | | | +| 1442 | 289 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | (待填) | | | +| 1443 | 291 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | +| 1444 | 297 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | +| 1445 | 303 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | (待填) | | | +| 1446 | 304 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | +| 1447 | 307 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | (待填) | | | +| 1448 | 308 | "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", {})) | (待填) | | | +| 1449 | 311 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | (待填) | | | +| 1450 | 315 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | (待填) | | | +| 1451 | 328 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | (待填) | | | +| 1452 | 330 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | (待填) | | | +| 1453 | 371 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | (待填) | | | +| 1454 | 374 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | (待填) | | | -## S193 _smoke_battle_log_view(行 391–422 · 6 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S195 _smoke_battle_log_view(行 391–422 · 6 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1450 | 391 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | (待填) | | | -| 1451 | 393 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | (待填) | | | -| 1452 | 400 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | (待填) | | | -| 1453 | 402 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | (待填) | | | -| 1454 | 408 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | (待填) | | | -| 1455 | 421 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | (待填) | | | +| 1455 | 391 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | (待填) | | | +| 1456 | 393 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | (待填) | | | +| 1457 | 400 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | (待填) | | | +| 1458 | 402 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | (待填) | | | +| 1459 | 408 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | (待填) | | | +| 1460 | 421 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | (待填) | | | -## S194 _smoke_level_open_line(行 429–435 · 3 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S196 _smoke_level_open_line(行 429–435 · 3 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1456 | 429 | "开场行 boosted=true 尾接「(含面板加成)」〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)") | (待填) | | | -| 1457 | 431 | "开场行 boosted=false 零后缀(白板局不带)〔wc1〕" | not String(NarratorScript.level_open_line("第一层", 42, 5, 30, false)).contains("(含面板加成)") | (待填) | | | -| 1458 | 433 | "开场行种子/两值带入不受后缀位影响(数值带入回归)〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("种子 42") and String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("攻击 5 / 生命 30") | (待填) | | | +| 1461 | 429 | "开场行 boosted=true 尾接「(含面板加成)」〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)") | (待填) | | | +| 1462 | 431 | "开场行 boosted=false 零后缀(白板局不带)〔wc1〕" | not String(NarratorScript.level_open_line("第一层", 42, 5, 30, false)).contains("(含面板加成)") | (待填) | | | +| 1463 | 433 | "开场行种子/两值带入不受后缀位影响(数值带入回归)〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("种子 42") and String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("攻击 5 / 生命 30") | (待填) | | | -## S195 _smoke_global_theme(行 444–539 · 22 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S197 _smoke_global_theme(行 444–539 · 22 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1459 | 444 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | (待填) | | | -| 1460 | 453 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | (待填) | | | -| 1461 | 455 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | (待填) | | | -| 1462 | 463 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | -| 1463 | 467 | "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") | (待填) | | | -| 1464 | 470 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | (待填) | | | -| 1465 | 473 | "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") | (待填) | | | -| 1466 | 479 | "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") | (待填) | | | -| 1467 | 483 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | (待填) | | | -| 1468 | 486 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | (待填) | | | -| 1469 | 490 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | (待填) | | | -| 1470 | 491 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | (待填) | | | -| 1471 | 499 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | (待填) | | | -| 1472 | 502 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | (待填) | | | -| 1473 | 504 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | (待填) | | | -| 1474 | 507 | "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 | (待填) | | | -| 1475 | 511 | "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 | (待填) | | | -| 1476 | 515 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | (待填) | | | -| 1477 | 519 | "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") | (待填) | | | -| 1478 | 533 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | (待填) | | | -| 1479 | 536 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | (待填) | | | -| 1480 | 539 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | (待填) | | | +| 1464 | 444 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | (待填) | | | +| 1465 | 453 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | (待填) | | | +| 1466 | 455 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | (待填) | | | +| 1467 | 463 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | +| 1468 | 467 | "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") | (待填) | | | +| 1469 | 470 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | (待填) | | | +| 1470 | 473 | "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") | (待填) | | | +| 1471 | 479 | "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") | (待填) | | | +| 1472 | 483 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | (待填) | | | +| 1473 | 486 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | (待填) | | | +| 1474 | 490 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | (待填) | | | +| 1475 | 491 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | (待填) | | | +| 1476 | 499 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | (待填) | | | +| 1477 | 502 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | (待填) | | | +| 1478 | 504 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | (待填) | | | +| 1479 | 507 | "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 | (待填) | | | +| 1480 | 511 | "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 | (待填) | | | +| 1481 | 515 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | (待填) | | | +| 1482 | 519 | "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") | (待填) | | | +| 1483 | 533 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | (待填) | | | +| 1484 | 536 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | (待填) | | | +| 1485 | 539 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | (待填) | | | -## S196 _smoke_button_five_states(行 552–624 · 23 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S198 _smoke_button_five_states(行 552–624 · 23 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1481 | 552 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | (待填) | | | -| 1482 | 558 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | (待填) | | | -| 1483 | 562 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | (待填) | | | -| 1484 | 566 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | (待填) | | | -| 1485 | 569 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | _sb_content_differs(d_sb, p_sb) | (待填) | | | -| 1486 | 571 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | (待填) | | | -| 1487 | 573 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | (待填) | | | -| 1488 | 574 | "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) | (待填) | | | -| 1489 | 577 | "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) | (待填) | | | -| 1490 | 580 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | (待填) | | | -| 1491 | 585 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | (待填) | | | -| 1492 | 589 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | (待填) | | | -| 1493 | 591 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | (待填) | | | -| 1494 | 593 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | -| 1495 | 595 | "V1 主钮 disabled≠pressed" | _sb_content_differs(ppn, pdd) | (待填) | | | -| 1496 | 596 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | (待填) | | | -| 1497 | 602 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | (待填) | | | -| 1498 | 605 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | (待填) | | | -| 1499 | 607 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | (待填) | | | -| 1500 | 609 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | -| 1501 | 611 | "V1 危险钮 disabled≠pressed" | _sb_content_differs(dpr, dds) | (待填) | | | -| 1502 | 616 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | (待填) | | | -| 1503 | 623 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | +| 1486 | 552 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | (待填) | | | +| 1487 | 558 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | (待填) | | | +| 1488 | 562 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | (待填) | | | +| 1489 | 566 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | (待填) | | | +| 1490 | 569 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | _sb_content_differs(d_sb, p_sb) | (待填) | | | +| 1491 | 571 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | (待填) | | | +| 1492 | 573 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | (待填) | | | +| 1493 | 574 | "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) | (待填) | | | +| 1494 | 577 | "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) | (待填) | | | +| 1495 | 580 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | (待填) | | | +| 1496 | 585 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | (待填) | | | +| 1497 | 589 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | (待填) | | | +| 1498 | 591 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | (待填) | | | +| 1499 | 593 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | +| 1500 | 595 | "V1 主钮 disabled≠pressed" | _sb_content_differs(ppn, pdd) | (待填) | | | +| 1501 | 596 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | (待填) | | | +| 1502 | 602 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | (待填) | | | +| 1503 | 605 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | (待填) | | | +| 1504 | 607 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | (待填) | | | +| 1505 | 609 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | +| 1506 | 611 | "V1 危险钮 disabled≠pressed" | _sb_content_differs(dpr, dds) | (待填) | | | +| 1507 | 616 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | (待填) | | | +| 1508 | 623 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | -## S197 _smoke_texture_filter(行 631–673 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1504 | 631 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | (待填) | | | -| 1505 | 635 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | -| 1506 | 639 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | -| 1507 | 657 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | (待填) | | | -| 1508 | 659 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | (待填) | | | -| 1509 | 660 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | (待填) | | | -| 1510 | 664 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | -| 1511 | 669 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | -| 1512 | 672 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | +## S199 _smoke_texture_filter(行 631–673 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1509 | 631 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | (待填) | | | +| 1510 | 635 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1511 | 639 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1512 | 657 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | (待填) | | | +| 1513 | 659 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | (待填) | | | +| 1514 | 660 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | (待填) | | | +| 1515 | 664 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1516 | 669 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | +| 1517 | 672 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | -## S198 _smoke_contrast_inks(行 682–698 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S200 _smoke_contrast_inks(行 682–698 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1513 | 682 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | (待填) | | | -| 1514 | 684 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | (待填) | | | -| 1515 | 686 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | (待填) | | | -| 1516 | 689 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | (待填) | | | -| 1517 | 691 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | (待填) | | | -| 1518 | 692 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | (待填) | | | -| 1519 | 693 | "V4 意图蓄力档判据=分档布尔(standee_body:179 CHARGE_SKILL_IDS 判定;旧 GOLD_DEEP 恒不可达判据清零)〔wd2〕" | sb_src.contains("_intent_charge = pid in CHARGE_SKILL_IDS") and not sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | (待填) | | | -| 1520 | 696 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | (待填) | | | -| 1521 | 698 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | (待填) | | | +| 1518 | 682 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | (待填) | | | +| 1519 | 684 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | (待填) | | | +| 1520 | 686 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | (待填) | | | +| 1521 | 689 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | (待填) | | | +| 1522 | 691 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | (待填) | | | +| 1523 | 692 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | (待填) | | | +| 1524 | 693 | "V4 意图蓄力档判据=分档布尔(standee_body:179 CHARGE_SKILL_IDS 判定;旧 GOLD_DEEP 恒不可达判据清零)〔wd2〕" | sb_src.contains("_intent_charge = pid in CHARGE_SKILL_IDS") and not sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | (待填) | | | +| 1525 | 696 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | (待填) | | | +| 1526 | 698 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | (待填) | | | -## S199 _smoke_element_details(行 707–726 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S201 _smoke_element_details(行 707–726 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1522 | 707 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | (待填) | | | -| 1523 | 710 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | (待填) | | | -| 1524 | 712 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | (待填) | | | -| 1525 | 715 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | (待填) | | | -| 1526 | 719 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | (待填) | | | -| 1527 | 723 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | (待填) | | | -| 1528 | 725 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | (待填) | | | -| 1529 | 726 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | (待填) | | | +| 1527 | 707 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | (待填) | | | +| 1528 | 710 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | (待填) | | | +| 1529 | 712 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | (待填) | | | +| 1530 | 715 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | (待填) | | | +| 1531 | 719 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | (待填) | | | +| 1532 | 723 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | (待填) | | | +| 1533 | 725 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | (待填) | | | +| 1534 | 726 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | (待填) | | | -## S200 _smoke_diag_button_retired(行 733–746 · 7 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S202 _smoke_diag_button_retired(行 733–746 · 7 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1530 | 733 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | (待填) | | | -| 1531 | 735 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | (待填) | | | -| 1532 | 737 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | (待填) | | | -| 1533 | 740 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | (待填) | | | -| 1534 | 743 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | (待填) | | | -| 1535 | 744 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | (待填) | | | -| 1536 | 745 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | (待填) | | | +| 1535 | 733 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | (待填) | | | +| 1536 | 735 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | (待填) | | | +| 1537 | 737 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | (待填) | | | +| 1538 | 740 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | (待填) | | | +| 1539 | 743 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | (待填) | | | +| 1540 | 744 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | (待填) | | | +| 1541 | 745 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | (待填) | | | -## S201 _status_panel_projection(行 762–792 · 10 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S203 _status_panel_projection(行 762–792 · 10 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1537 | 762 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | (待填) | | | -| 1538 | 763 | "余额徽投影 = 快照 balance 读数(sv4 回归锚)〔sv4〕" | String(po._balance_badge.value_label.text) == "钥石 77" | (待填) | | | -| 1539 | 765 | "属性行全值投影 = 契约4 钉值格式逐字(生命/上限/攻击/AC/GP 五现值)〔sv1〕" | String(po._attrs_label.text) == "生命 13/29 · 攻击 7 · 护甲 AC 11 · GP 41" | (待填) | | | -| 1540 | 767 | "秘宝行投影 = 受理序 join+已用件「(已用)」尾缀〔sv2〕" | String(po._curios_label.text) == "青苔密卷 · 锈蚀怀表(已用)" | (待填) | | | -| 1541 | 769 | "专长行投影 = 行串 join〔sv3〕" | String(po._feats_label.text) == "疼痛记忆 ×2 · 双管镐 ×1" | (待填) | | | -| 1542 | 776 | "面板子树零「道具工坊/天赋树」文案(跨关概览退役零残留)〔sv5〕" | stale == "" | (待填) | | | -| 1543 | 777 | "旧段成员退役(_workshop_holder/_tree_label 零残留——契约2 数据/成员层面清零)〔sv5〕" | po.get("_workshop_holder") == null and po.get("_tree_label") == null | (待填) | | | -| 1544 | 784 | "空秘宝态句=「未携带秘宝」〔sv2〕" | String(po2._curios_label.text) == "未携带秘宝" | (待填) | | | -| 1545 | 785 | "空专长态句=「未获专长」〔sv3〕" | String(po2._feats_label.text) == "未获专长" | (待填) | | | -| 1546 | 791 | "提示行=「查看本局冒险进度(只读)」(新定位换代)〔sv8〕" | hint_txt == "查看本局冒险进度(只读)" | (待填) | | | +| 1542 | 762 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | (待填) | | | +| 1543 | 763 | "余额徽投影 = 快照 balance 读数(sv4 回归锚)〔sv4〕" | String(po._balance_badge.value_label.text) == "钥石 77" | (待填) | | | +| 1544 | 765 | "属性行全值投影 = 契约4 钉值格式逐字(生命/上限/攻击/AC/GP 五现值)〔sv1〕" | String(po._attrs_label.text) == "生命 13/29 · 攻击 7 · 护甲 AC 11 · GP 41" | (待填) | | | +| 1545 | 767 | "秘宝行投影 = 受理序 join+已用件「(已用)」尾缀〔sv2〕" | String(po._curios_label.text) == "青苔密卷 · 锈蚀怀表(已用)" | (待填) | | | +| 1546 | 769 | "专长行投影 = 行串 join〔sv3〕" | String(po._feats_label.text) == "疼痛记忆 ×2 · 双管镐 ×1" | (待填) | | | +| 1547 | 776 | "面板子树零「道具工坊/天赋树」文案(跨关概览退役零残留)〔sv5〕" | stale == "" | (待填) | | | +| 1548 | 777 | "旧段成员退役(_workshop_holder/_tree_label 零残留——契约2 数据/成员层面清零)〔sv5〕" | po.get("_workshop_holder") == null and po.get("_tree_label") == null | (待填) | | | +| 1549 | 784 | "空秘宝态句=「未携带秘宝」〔sv2〕" | String(po2._curios_label.text) == "未携带秘宝" | (待填) | | | +| 1550 | 785 | "空专长态句=「未获专长」〔sv3〕" | String(po2._feats_label.text) == "未获专长" | (待填) | | | +| 1551 | 791 | "提示行=「查看本局冒险进度(只读)」(新定位换代)〔sv8〕" | hint_txt == "查看本局冒险进度(只读)" | (待填) | | | -## S202 _career_overlay_projection(行 837–872 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S204 _career_overlay_projection(行 837–872 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1547 | 837 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | (待填) | | | -| 1548 | 838 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | (待填) | | | -| 1549 | 840 | "通关局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | (待填) | | | -| 1550 | 845 | "各关通关次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | (待填) | | | -| 1551 | 847 | "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 次" | (待填) | | | -| 1552 | 849 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | (待填) | | | -| 1553 | 858 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | (待填) | | | -| 1554 | 869 | "旧档载入后生涯页正常显示(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" | (待填) | | | +| 1552 | 837 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | (待填) | | | +| 1553 | 838 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | (待填) | | | +| 1554 | 840 | "通关局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | (待填) | | | +| 1555 | 845 | "各关通关次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | (待填) | | | +| 1556 | 847 | "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 次" | (待填) | | | +| 1557 | 849 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | (待填) | | | +| 1558 | 858 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | (待填) | | | +| 1559 | 869 | "旧档载入后生涯页正常显示(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" | (待填) | | | -## S203 _test_grouping_closed(行 61–79 · 7 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S205 _test_grouping_closed(行 61–79 · 7 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1555 | 61 | "九型封闭:%s → 组%d(期望组%d)" ↩ % [t, got, int(TYPE_EXPECT[t])] | got == int(TYPE_EXPECT[t]) | (待填) | | | -| 1556 | 65 | "清单外值「%s」→ GLOW_NONE(禁静默空光)" ↩ % bad | UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE | (待填) | | | -| 1557 | 70 | "两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)" | band_blast == UiKitScript.TG_MAGIC | (待填) | | | -| 1558 | 71 | "两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)" | UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE | (待填) | | | -| 1559 | 74 | "glow_color_for_group 群伤组 = TG_GLOW_AOE" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE | (待填) | | | -| 1560 | 76 | "glow_color_for_group 无组(GLOW_NONE)→ 全透明" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0) | (待填) | | | -| 1561 | 78 | "glow_color_for_group 越界组号 → 全透明" | UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0) | (待填) | | | +| 1560 | 61 | "九型封闭:%s → 组%d(期望组%d)" ↩ % [t, got, int(TYPE_EXPECT[t])] | got == int(TYPE_EXPECT[t]) | (待填) | | | +| 1561 | 65 | "清单外值「%s」→ GLOW_NONE(禁静默空光)" ↩ % bad | UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE | (待填) | | | +| 1562 | 70 | "两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)" | band_blast == UiKitScript.TG_MAGIC | (待填) | | | +| 1563 | 71 | "两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)" | UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE | (待填) | | | +| 1564 | 74 | "glow_color_for_group 群伤组 = TG_GLOW_AOE" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE | (待填) | | | +| 1565 | 76 | "glow_color_for_group 无组(GLOW_NONE)→ 全透明" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0) | (待填) | | | +| 1566 | 78 | "glow_color_for_group 越界组号 → 全透明" | UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0) | (待填) | | | -## S204 _test_frozen_hue_face(行 88–102 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S206 _test_frozen_hue_face(行 88–102 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1562 | 88 | "实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" ↩ % [CONST_NAME[g], int(m.r8), int(m.g8), int(m.b8)] | impl_const == m | (待填) | | | -| 1563 | 93 | "冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" ↩ % [g, dh * 360.0, ds] | dh <= 0.2 / 360.0 and ds <= 0.01 | (待填) | | | -| 1564 | 98 | "群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" ↩ % (absf(aoe_m.h - aoe_f.h) * 360.0) | absf(aoe_m.h - aoe_f.h) < 0.001 | (待填) | | | -| 1565 | 100 | "群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" ↩ % absf(aoe_m.s - aoe_f.s) | absf(aoe_m.s - aoe_f.s) <= 0.01 | (待填) | | | -| 1566 | 102 | "群伤组专项:V=0.65 明度档( provisional 域内)" | absf(aoe_m.v - 166.0 / 255.0) < 0.002 | (待填) | | | +| 1567 | 88 | "实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" ↩ % [CONST_NAME[g], int(m.r8), int(m.g8), int(m.b8)] | impl_const == m | (待填) | | | +| 1568 | 93 | "冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" ↩ % [g, dh * 360.0, ds] | dh <= 0.2 / 360.0 and ds <= 0.01 | (待填) | | | +| 1569 | 98 | "群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" ↩ % (absf(aoe_m.h - aoe_f.h) * 360.0) | absf(aoe_m.h - aoe_f.h) < 0.001 | (待填) | | | +| 1570 | 100 | "群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" ↩ % absf(aoe_m.s - aoe_f.s) | absf(aoe_m.s - aoe_f.s) <= 0.01 | (待填) | | | +| 1571 | 102 | "群伤组专项:V=0.65 明度档( provisional 域内)" | absf(aoe_m.v - 166.0 / 255.0) < 0.002 | (待填) | | | -## S205 _test_render_params(行 107–113 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S207 _test_render_params(行 107–113 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1567 | 107 | "泛光强度 α = 170/255(v2 呈现参数冻结)" | is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0) | (待填) | | | -| 1568 | 109 | "带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)" | is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125) | (待填) | | | -| 1569 | 111 | "组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)" | UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5 | (待填) | | | +| 1572 | 107 | "泛光强度 α = 170/255(v2 呈现参数冻结)" | is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0) | (待填) | | | +| 1573 | 109 | "带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)" | is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125) | (待填) | | | +| 1574 | 111 | "组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)" | UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5 | (待填) | | | -## S206 _test_gamma_ladder(行 128–139 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S208 _test_gamma_ladder(行 128–139 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1570 | 128 | "阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" ↩ % [g, lum, prev_lum] | lum < prev_lum | (待填) | | | -| 1571 | 134 | "相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" ↩ % [order[i], order[i + 1], cr] | cr >= 1.20 | (待填) | | | -| 1572 | 138 | "群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" ↩ % [aoe_lin, magic_lin] | aoe_lin < magic_lin | (待填) | | | +| 1575 | 128 | "阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" ↩ % [g, lum, prev_lum] | lum < prev_lum | (待填) | | | +| 1576 | 134 | "相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" ↩ % [order[i], order[i + 1], cr] | cr >= 1.20 | (待填) | | | +| 1577 | 138 | "群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" ↩ % [aoe_lin, magic_lin] | aoe_lin < magic_lin | (待填) | | | -## S207 _test_probe_api_shape(行 146–153 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S209 _test_probe_api_shape(行 146–153 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1573 | 146 | "probe 键集 = {enabled, cells, errors}" | snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3 | (待填) | | | -| 1574 | 148 | "probe 默认态:enabled=true(Q4 丙案生产默认开)" | bool(snap.get("enabled", false)) | (待填) | | | -| 1575 | 149 | "probe 默认态:作用集空(无板未派生)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | -| 1576 | 150 | "probe 默认态:清单外登记空" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | -| 1577 | 152 | "set_type_glow_enabled(false) → probe enabled=false(无板不崩)" | not bool(view.type_glow_probe().get("enabled", true)) | (待填) | | | +| 1578 | 146 | "probe 键集 = {enabled, cells, errors}" | snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3 | (待填) | | | +| 1579 | 148 | "probe 默认态:enabled=true(Q4 丙案生产默认开)" | bool(snap.get("enabled", false)) | (待填) | | | +| 1580 | 149 | "probe 默认态:作用集空(无板未派生)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | +| 1581 | 150 | "probe 默认态:清单外登记空" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | +| 1582 | 152 | "set_type_glow_enabled(false) → probe enabled=false(无板不崩)" | not bool(view.type_glow_probe().get("enabled", true)) | (待填) | | | -## S208 _init(行 16–28 · 6 条 · 源 game/tests/unit/presentation/ui_font_asset_test.gd · 去向:待分类回填) +## S210 _init(行 16–28 · 6 条 · 源 game/tests/unit/presentation/ui_font_asset_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1578 | 16 | "随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)" | f != null | (待填) | | | -| 1579 | 18 | "中文字形在集(攻/雷/旗/封)" | f.has_char("攻".unicode_at(0)) and f.has_char("雷".unicode_at(0)) \\ and f.has_char("旗".unicode_at(0)) and f.has_char("封".unicode_at(0)) | (待填) | | | -| 1580 | 20 | "中文字形在集(危——已开危险格字形换代前置,迭代36 u12/wa9)" | f.has_char("危".unicode_at(0)) | (待填) | | | -| 1581 | 21 | "ASCII/数字字形在集(A/0/%)" | f.has_char("A".unicode_at(0)) and f.has_char("0".unicode_at(0)) and f.has_char("%".unicode_at(0)) | (待填) | | | -| 1582 | 25 | "中文采样文本宽度 > 0(C6 headless 判据)" | w_cn > 0.0 and w_label > 0.0 | (待填) | | | -| 1583 | 28 | "字体缺失时后续断言跳过(缺位=回归,此行仅占位计数)" | false | (待填) | | | +| 1583 | 16 | "随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)" | f != null | (待填) | | | +| 1584 | 18 | "中文字形在集(攻/雷/旗/封)" | f.has_char("攻".unicode_at(0)) and f.has_char("雷".unicode_at(0)) \\ and f.has_char("旗".unicode_at(0)) and f.has_char("封".unicode_at(0)) | (待填) | | | +| 1585 | 20 | "中文字形在集(危——已开危险格字形换代前置,迭代36 u12/wa9)" | f.has_char("危".unicode_at(0)) | (待填) | | | +| 1586 | 21 | "ASCII/数字字形在集(A/0/%)" | f.has_char("A".unicode_at(0)) and f.has_char("0".unicode_at(0)) and f.has_char("%".unicode_at(0)) | (待填) | | | +| 1587 | 25 | "中文采样文本宽度 > 0(C6 headless 判据)" | w_cn > 0.0 and w_label > 0.0 | (待填) | | | +| 1588 | 28 | "字体缺失时后续断言跳过(缺位=回归,此行仅占位计数)" | false | (待填) | | | --- -全源共 208 章、1583 条(静态调用点口径)。 +全源共 210 章、1588 条(静态调用点口径)。 From 0cd86469d5d4908f4f857765ba95070442f5629a Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 16:00:28 +0800 Subject: [PATCH 7/9] =?UTF-8?q?fix(=E8=BF=AD=E4=BB=A338=C2=B7=E9=97=B8?= =?UTF-8?q?=E7=BA=A2=E5=BF=85=E4=BF=AE):=20S11=20=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E8=AF=8D=E5=9B=9B=E5=A4=84=E6=8D=A2=E4=BB=A3?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=B9=B6=E5=85=A5=20master=2063763565?= =?UTF-8?q?=EF=BC=88=E9=80=9A=E5=85=B3=E5=8F=A3=E5=BE=84=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=EF=BC=89=E5=90=8E=E6=9C=AC=E6=89=B9=E5=9B=9B=E5=A4=84=E6=96=B0?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=B8=A9=E9=80=80=E5=BD=B9=E8=AF=8D=EF=BC=9A?= =?UTF-8?q?level=5Frunner=E3=80=8C=E9=80=9A=E5=85=B3=E4=B8=89=E9=80=89?= =?UTF-8?q?=E4=B8=80=E3=80=8D=E2=86=92=E3=80=8C=E5=85=B3=E5=8D=A1=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E4=B8=89=E9=80=89=E4=B8=80=E3=80=8D=EF=BC=8Bui=5Fclic?= =?UTF-8?q?k=5Fprobe=20=E4=B8=A4=E5=A4=84=E3=80=8C=E9=80=9A=E5=85=B3?= =?UTF-8?q?=E4=B8=89=E9=80=89=E4=B8=80=E6=80=81/=E9=80=9A=E5=85=B3?= =?UTF-8?q?=E5=81=9C=E7=82=B9=E3=80=8D=E2=86=92=E3=80=8C=E5=85=B3=E5=8D=A1?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E3=80=8D=E6=97=8F=EF=BC=8Btreasure=5Feffects?= =?UTF-8?q?=5Ftest=E3=80=8C=E9=9B=B7=E6=A0=BC=E3=80=8D=E2=86=92=E3=80=8C?= =?UTF-8?q?=E5=8D=B1=E9=99=A9=E6=A0=BC=E3=80=8D=EF=BC=9B=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E9=9B=B6=E5=8F=98=E5=8C=96=EF=BC=8C=E5=AF=B9=E9=BD=90=20master?= =?UTF-8?q?=20=E7=BB=9F=E4=B8=80=E5=8F=A3=E5=BE=84=EF=BC=88=E5=8D=95?= =?UTF-8?q?=E5=85=B3=3D=E5=85=B3=E5=8D=A1=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/level_runner.gd | 2 +- game/probe/ui_click_probe.gd | 4 ++-- game/tests/unit/core/treasure_effects_test.gd | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index a8d9733c..b01e7b08 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -474,7 +474,7 @@ func _enter_node(node: Dictionary) -> bool: "battle", "boss": return _start_battle(node) "reward": - # 奖励节点 = 三选一停点(与通关三选一同池同权重同上限);呈现编排归 + # 奖励节点 = 三选一停点(与关卡完成三选一同池同权重同上限);呈现编排归 # curio_interact_controller.present_node_curio;推进 = 兑付确认后选路(零自动推进) node_entered.emit(node, {}) return true diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index ebba33bf..05f632e4 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -418,7 +418,7 @@ func _ready() -> void: # 三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。 if not _tested_curio_head and main.run_scope.adventure != null and not main.run_scope.adventure._node_curio_pending.is_empty() and String(main.curio_overlay._header).contains("关卡奖励点"): # 停点态判据 = _node_curio_pending(奖励节点三选一停点住此——原条件查 - # _curio_pending〔通关三选一态〕与奖励头部互斥、恒不可命中=拦截能力未证病根, + # _curio_pending〔关卡完成三选一态〕与奖励头部互斥、恒不可命中=拦截能力未证病根, # 卡⑤ d4 转正随修;头部校验串本体零触碰) _tested_curio_head = true var ch := String(main.curio_overlay._header) @@ -435,7 +435,7 @@ func _ready() -> void: if main.curio_overlay._continue_btn.visible: main.curio_overlay.press_continue() elif main.run_scope.adventure != null and not main.run_scope.adventure._node_curio_pending.is_empty(): - main.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与通关停点同形) + main.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与关卡完成停点同形) main.curio_overlay.press_confirm() elif main.run_scope.adventure != null and not main.run_scope.adventure._curio_pending.is_empty(): main.curio_overlay.press_card(0) diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index 5b7d33ed..57fec602 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -312,7 +312,7 @@ func _init() -> void: ob15.begin_battle(enc_feats) var rows15: Array = [] ob15.step_row.connect(func(row): rows15.append(row)) - # ④ 封印匠(每第 3 次封印命中→危险格中立移除):三枚不同雷格各封一次 + # ④ 封印匠(每第 3 次封印命中→危险格中立移除):三枚不同危险格各封一次 #(_judge_seal 每格去重——_seal_consumed 以危险格实例为键,同格重复命中不计数) var mines15: Array = [] for cv in ob15.minesweeper.current_board().all_cells(): From 549b006ed5c4d78887c7b612706c7bdff1e618d7 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 16:57:50 +0800 Subject: [PATCH 8/9] =?UTF-8?q?chore(=E8=BF=AD=E4=BB=A338=C2=B7=E6=9A=82?= =?UTF-8?q?=E5=81=9C=E6=94=B6=E5=8F=A3):=20=E7=94=A8=E6=88=B7=E8=A3=81?= =?UTF-8?q?=E6=9A=82=E5=81=9C=E2=80=94=E2=80=94=E5=88=86=E6=94=AF=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=AE=8C=E6=95=B4=E7=95=99=E5=AD=98=E2=80=94=E2=80=94?= =?UTF-8?q?=E2=91=A0=E9=AA=8C=E8=AF=81=E9=9B=86=E6=B8=85=E5=8D=95=E8=A1=A5?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=AD=A3=E7=A1=AE=E9=87=8D=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=89=88=EF=BC=881596=20=E6=9D=A1=EF=BC=8C=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=A7=A3=E5=86=B2=E7=AA=81=E6=97=B6=20add=20=E5=85=88=E4=BA=8E?= =?UTF-8?q?=E9=87=8D=E7=94=9F=E6=88=90=E8=87=B4=E6=8F=90=E4=BA=A4=E7=89=88?= =?UTF-8?q?=E6=BB=9E=E7=95=99=20master=20=E4=BE=A7=E6=97=A7=201591=20?= =?UTF-8?q?=E6=9D=A1=E2=80=94=E2=80=94=E5=AE=A1=E8=AE=A1=E9=98=BB=E6=96=AD?= =?UTF-8?q?=E2=91=A1=E6=B8=85=E5=81=BF=EF=BC=8Cextract=5Fchecks=20--diff?= =?UTF-8?q?=20PASS=EF=BC=89=EF=BC=9B=E2=91=A1W2=20=E6=AE=B5=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E5=B0=BE=E7=BC=80=E7=AC=94=E8=AF=AF=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=94levelmap=E6=89=B9=20V1/V2=E3=80=95=EF=BC=88=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E8=BD=BB=E5=BE=AE=E9=A1=B9=EF=BC=89=EF=BC=9B=E2=91=A2?= =?UTF-8?q?=E5=9B=9B=E4=BB=BD=E4=BA=A7=E5=87=BA=E7=89=A9=E8=90=BD=E4=BD=8D?= =?UTF-8?q?=EF=BC=88=E5=AE=9E=E7=8E=B0=E6=8A=A5=E5=91=8A=E3=80=94=E5=90=AB?= =?UTF-8?q?=E9=97=B8=E7=BA=A2=E5=AE=9E=E5=86=B5=E6=8A=AB=E9=9C=B2=E4=B8=8E?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E7=BB=93=E8=AE=BA=E5=9B=9E=E5=A1=AB=E3=80=95?= =?UTF-8?q?/=E4=BB=A3=E6=8B=8D=E6=9D=BF=E8=BF=BD=E8=AE=A4=E5=8D=95/?= =?UTF-8?q?=E5=8F=8D=E5=93=BA=E7=82=B9=E6=B8=85=E5=8D=95/=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E5=AE=A1=E8=AE=A1=E6=8A=A5=E5=91=8A=EF=BC=89=E3=80=82?= =?UTF-8?q?=E5=88=86=E6=94=AF=E5=B0=96=E9=97=B8=E9=9D=A2=E6=9C=AA=E9=97=AD?= =?UTF-8?q?=E5=90=88=E9=A1=B9=EF=BC=9DS13=20gdstyle=20=E6=81=B6=E5=8C=96?= =?UTF-8?q?=202=20=E6=9D=A1=E5=BE=85=E7=94=A8=E6=88=B7=E5=90=8E=E7=BB=AD?= =?UTF-8?q?=E8=A3=81=EF=BC=88=E8=BF=98=E5=80=BA=E4=B8=8D=E5=8F=AF=E8=A1=8C?= =?UTF-8?q?=E5=B7=B2=E5=91=88=E6=8A=A5=EF=BC=8C=E9=87=8D=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E5=BE=85=E6=89=B9=EF=BC=89=EF=BC=9B=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E6=94=B6=E5=8F=A3=E8=B7=AF=E5=BE=84=EF=BC=9D=E8=A3=81?= =?UTF-8?q?=E5=90=8E=E9=87=8D=E8=B7=91=E5=85=A8=E9=87=8F=E5=9F=9F=E9=97=B8?= =?UTF-8?q?=20=E2=86=92=20=E7=BB=BF=E5=9B=9E=E6=89=A7=20=E2=86=92=20?= =?UTF-8?q?=E5=90=88=E5=B9=B6=20master=20=E2=86=92=20=E4=B8=BB=E5=8C=BA?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=90=8E=E9=9B=B6=E5=A4=8D=E8=B7=91=20?= =?UTF-8?q?=E2=86=92=20worktree=20=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/level_map_info_fog_test.gd | 2 +- game/tests/验证集清单.md | 3343 +++++++++-------- reports/iter38-代拍板-2026-10-26.md | 42 + reports/iter38-反哺点-2026-10-26.md | 40 + reports/iter38-实现报告-2026-10-26.md | 117 + reports/iter38-验收审计-2026-10-26.md | 120 + 6 files changed, 1999 insertions(+), 1665 deletions(-) create mode 100644 reports/iter38-代拍板-2026-10-26.md create mode 100644 reports/iter38-反哺点-2026-10-26.md create mode 100644 reports/iter38-实现报告-2026-10-26.md create mode 100644 reports/iter38-验收审计-2026-10-26.md diff --git a/game/tests/unit/presentation/level_map_info_fog_test.gd b/game/tests/unit/presentation/level_map_info_fog_test.gd index 7fd70b0c..c0cce8bf 100644 --- a/game/tests/unit/presentation/level_map_info_fog_test.gd +++ b/game/tests/unit/presentation/level_map_info_fog_test.gd @@ -192,7 +192,7 @@ func _test_big_graph_fit(db) -> void: screen.queue_free() -# ---- 布局缓存命中与换代失效〔levelmap批 W2〕---- +# ---- 布局缓存命中与换代失效〔levelmap批 V1/V2〕---- # 真实入口 = open_choices 后的真实绘制路径(_draw_graph→_layout,与 hover→_candidate_at 链同一缓存层); # 命中读数 = is_same 同实例判别(重算必产新字典、命中返回缓存原实例)——零生产计数器。 diff --git a/game/tests/验证集清单.md b/game/tests/验证集清单.md index 1b7653f1..31872f35 100644 --- a/game/tests/验证集清单.md +++ b/game/tests/验证集清单.md @@ -1,8 +1,8 @@ # 验证集清单(断言快照·冻结) -> 生成日期:2026-10-26 · 基点 commit:`54395b8b434c2662924947e886c65c7393d88de4` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd +> 生成日期:2026-10-26 · 基点 commit:`5b5214f40f206498422e1bcd603a21a8393bf560` · 源:cross_system_flow_test.gd、node_map_determinism_test.gd、save_rng_roundtrip_test.gd、system_test.gd、damage_controller_test.gd、damage_math_test.gd、extended_row_violation_test.gd、roll_multiplier_test.gd、round_flow_skill_pool_test.gd、seal_polarity_test.gd、skill_unlock_test.gd、variant_placement_test.gd、source_guard_test.gd、system_contracts_test.gd、adventure_feats_test.gd、board_gen_reward_test.gd、board_generator_test.gd、breakpoint_resume_test.gd、config_db_cross_validator_test.gd、config_db_resolver_test.gd、curio_refresh_paid_test.gd、effect_words_t1_test.gd、feat_vouchers_test.gd、level_map_type_vis_test.gd、level_shop_economy_test.gd、level_table_validate_test.gd、meta_progression_forge_test.gd、minesweeper_rules_test.gd、node_map_generator_test.gd、node_map_growth_test.gd、pool_case_d_test.gd、save_boot_no_activate_test.gd、save_manager_domain_test.gd、save_slot_isolation_test.gd、save_slot_summary_test.gd、settle_segments_test.gd、treasure_effects_test.gd、treasure_items_test.gd、tree_requires_level_test.gd、tree_rules_v11_test.gd、config_db_fatal_oob_test.gd、config_db_test.gd、counter_prefix_fatal_test.gd、forge_table_test.gd、level_gen_params_test.gd、reward_dangling_treasure_test.gd、rng_service_test.gd、rng_stream_boot_test.gd、table_structure_guard_test.gd、treasure_target_violation_test.gd、example_test.gd、battle_narrator_test.gd、forge_workbench_test.gd、hud_presenter_test.gd、level_map_info_fog_test.gd、modal_flow_test.gd、pacing_queue_test.gd、player_copy_sweep_test.gd、reward_overlay_test.gd、standee_fx_sweep_test.gd、terrain_tile_sets_test.gd、theme_projection_test.gd、type_glow_test.gd、ui_font_asset_test.gd、test_kit.gd > 提取口径:静态 `check(` 调用点,括号配平归集(可跨行);缺省源=三宿主遍历 -> (unit/**+integration/**+system_test.gd)。本基点实测 1591 个静态调用点;行级 +> (unit/**+integration/**+system_test.gd)。本基点实测 1596 个静态调用点;行级 > `grep -c "check("` 是另一口径(含 `func check(` 定义行与注释行),两口径各自浮动, > 禁互相换算(tests/README「断言计数双口径」)。运行时计数大于静态计数=循环展开条目。 > 生成命令:`python tools/extract_checks.py --extract`(可复现;重新生成须 --force; @@ -136,7 +136,7 @@ | 71 | 197 | "V2d 渲染面可见按钮计数==3(旧钮接回即红——渲染面锚,句柄缝盲区闭合)〔卡② mn1/mn2〕" | _count_visible_buttons(main._start_overlay) == 3 | (待填) | | | | 72 | 200 | "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() | (待填) | | | | 73 | 204 | "V2f 调试浮窗 boot 不自动开(菜单优先)〔2026-10-16 用户令〕" | not main.debug_overlay.visible | (待填) | | | -| 74 | 206 | "V2f 调试浮层角标 boot 可见(全局浮层入口在座)〔2026-10-16 用户令〕" | main.debug_badge != null and main.debug_badge.visible | (待填) | | | +| 74 | 206 | "V2f 调试浮层角标发布态恒隐(2026-10-25 删写死行批)" | main.debug_badge != null and not main.debug_badge.visible | (待填) | | | ## S11 _smoke_run_lifecycle(行 231–263 · 11 条 · 源 game/tests/system_test.gd · 去向:待分类回填) @@ -386,2274 +386,2289 @@ | 196 | 452 | "非施放攻击拍采样充足(%d 拍——增益施放行零兑付为设计语义不计)" ↩ % ticks.size() | ticks.size() >= 2 | (待填) | | | | 197 | 460 | "fresh 池非蓄力拍 d_tick = max(1, 掷点档位基线)(Boss 攻击链健康——Bug#2 回归断言)" | all_full | (待填) | | | -## S36 _smoke_full_battle(行 50–78 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S36 _smoke_full_battle(行 51–79 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 198 | 50 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | (待填) | | | -| 199 | 58 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | (待填) | | | -| 200 | 59 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | (待填) | | | -| 201 | 60 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | (待填) | | | -| 202 | 74 | "%s 步骤流含判定行" ↩ % label | has_verdict | (待填) | | | -| 203 | 77 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | (待填) | | | -| 204 | 78 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | (待填) | | | +| 198 | 51 | "%s 战斗在轮次上限内终局(%d 轮,%s)" ↩ % [label, run1.rounds, run1.result] | run1.terminated | (待填) | | | +| 199 | 59 | "%s 回放断言:结算步骤流逐行相同" ↩ % label | JSON.stringify(run1.stream) == JSON.stringify(run2.stream) | (待填) | | | +| 200 | 60 | "%s 回放断言:事件流逐位相同" ↩ % label | JSON.stringify(run1.events) == JSON.stringify(run2.events) | (待填) | | | +| 201 | 61 | "%s 结算步骤流非空(%d 行)" ↩ % [label, run1.stream.size()] | run1.stream.size() > 0 | (待填) | | | +| 202 | 75 | "%s 步骤流含判定行" ↩ % label | has_verdict | (待填) | | | +| 203 | 78 | "%s 主角先手攻击行必存在(2026-09-11 拍板① + 案C 地图账殿后)×%d" ↩ % [label, hero_attacks] | hero_attacks >= 1 | (待填) | | | +| 204 | 79 | "%s 主角攻击行携带类型键与掷点读数(单分量——充能退役批)" ↩ % label | hero_breakdown_ok | (待填) | | | -## S37 _run_battle(行 110–110 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S37 _run_battle(行 111–111 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 205 | 110 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | (待填) | | | +| 205 | 111 | "战斗收束后行动经济相位回 Idle(notify_combat_end 生效,实测=%s)" ↩ % String(action_economy.phase) | action_economy.phase == "Idle" | (待填) | | | -## S38 _smoke_active_refresh(行 159–214 · 17 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S38 _smoke_active_refresh(行 160–215 · 17 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 206 | 159 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 207 | 165 | "三轮停手攒 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 | (待填) | | | -| 208 | 166 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | (待填) | | | -| 209 | 176 | "轮内第 1 次刷新受理" | ok1 | (待填) | | | -| 210 | 177 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | (待填) | | | -| 211 | 178 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | (待填) | | | -| 212 | 179 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | (待填) | | | -| 213 | 185 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | (待填) | | | -| 214 | 191 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | (待填) | | | -| 215 | 193 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | (待填) | | | -| 216 | 194 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | (待填) | | | -| 217 | 206 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | (待填) | | | -| 218 | 207 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | (待填) | | | -| 219 | 208 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | (待填) | | | -| 220 | 209 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | (待填) | | | -| 221 | 213 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | (待填) | | | -| 222 | 214 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | (待填) | | | +| 206 | 160 | "轮内刷新战斗启动(Boss 行 150HP——承载多轮样本)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 207 | 166 | "三轮停手攒 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 | (待填) | | | +| 208 | 167 | "第 4 轮 Active 相位" | settlement.action_economy.phase == "Active" | (待填) | | | +| 209 | 177 | "轮内第 1 次刷新受理" | ok1 | (待填) | | | +| 210 | 178 | "相位保持 Active、AP 保留不重置(ap=%d)" ↩ % settlement.action_economy.ap | settlement.action_economy.phase == "Active" and settlement.action_economy.ap == ap_before | (待填) | | | +| 211 | 179 | "RAP -刷新价(%d)" ↩ % settlement.action_economy.rap | settlement.action_economy.rap == rap_banked - int(_db.action_economy.rap_refresh_cost) | (待填) | | | +| 212 | 180 | "当场换新图(board_changed +1)" | int(ct["board_swaps"]) == swaps_before + 1 and minesweeper.current_board() != board_before | (待填) | | | +| 213 | 186 | "轮内第 2 次刷新受理(RAP=%d)" ↩ % settlement.action_economy.rap | ok2 | (待填) | | | +| 214 | 192 | "第 3 次刷新被拒(每轮兜底上限 2——RAP %d 足额仍拒)" ↩ % settlement.action_economy.rap | not ok3 | (待填) | | | +| 215 | 194 | "V5 快照刷新计数=2(受理单源——main 副本已删)〔迭代27 T3〕" | int(settlement.presentation_snapshot().get("refresh_used", -1)) == 2 | (待填) | | | +| 216 | 195 | "V5 上限达门禁一致(can_refresh 与快照计数同源)〔迭代27 T3〕" | not settlement.can_refresh() | (待填) | | | +| 217 | 207 | "轮 4 挖至 AP 耗尽自动结算(round=%d result='%s')" ↩ % [settlement.round_index, settlement.battle_result] | settlement.round_index == 5 and settlement.battle_result == "" | (待填) | | | +| 218 | 208 | "跨图 facts 完整兑现(safe_cell 结算行 %d == safe 事件 %d——快照批次隔离)" ↩ % [int(ct["safe_rows"]), int(ct["safe_events"])] | int(ct["safe_rows"]) == int(ct["safe_events"]) | (待填) | | | +| 219 | 209 | "跨图挖掘 safe 事件 ≥3(%d——断言非空转)" ↩ % int(ct["safe_events"]) | int(ct["safe_events"]) >= 3 | (待填) | | | +| 220 | 210 | "结算段内(Waiting 相位)刷新被拒(等待期换图通道已移除)" | bool(ct["waiting_refresh_rejected"]) and bool(ct["waiting_probe_hit"]) | (待填) | | | +| 221 | 214 | "轮内刷新回放断言:同种子两遍步骤流逐行相同" | JSON.stringify(rep1) == JSON.stringify(rep2) | (待填) | | | +| 222 | 215 | "轮内刷新步骤流非空(%d 行)" ↩ % rep1.size() | rep1.size() > 0 | (待填) | | | -## S39 _smoke_encounter_roll(行 253–311 · 8 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S39 _smoke_encounter_roll(行 254–312 · 8 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 223 | 253 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | -| 224 | 256 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | (待填) | | | -| 225 | 262 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | (待填) | | | -| 226 | 266 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | (待填) | | | -| 227 | 271 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | -| 228 | 273 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | (待填) | | | -| 229 | 286 | "熊地精首遇带(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") | (待填) | | | -| 230 | 311 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | (待填) | | | +| 223 | 254 | "遭遇扫描战斗启动(quiet 计数档——计数入总账,成败由后续 spawn 断言归因)" | settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 224 | 257 | "D0 区间 [1,2] 两值均出现(分布 %s)" ↩ % str(counts) | counts.has(1) and counts.has(2) | (待填) | | | +| 225 | 263 | "全部计数在域内 [1,2](分布 %s)" ↩ % str(counts) | all_in_domain | (待填) | | | +| 226 | 267 | "同种子遭遇骰可回放(%s == %s)" ↩ % [str(r1), str(r2)] | r1 == r2 | (待填) | | | +| 227 | 272 | "D1 战斗启动" | settlement_d1.begin_battle({"level_index": 1, "node_depth": 1, "hero_hp": int(_db.hero.max_hp)}) | (待填) | | | +| 228 | 274 | "D1 区间 [2,3] 域内(%d 只)" ↩ % n1 | n1 >= 2 and n1 <= 3 | (待填) | | | +| 229 | 287 | "熊地精首遇带(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") | (待填) | | | +| 230 | 312 | "全深度 × 12 种子 spawn 全域内(%d 场,∈[unit_count, unit_count_max] 且 ≥1)——空遭遇不可达(Bug#1 回归)" ↩ % scan_total | scan_bad == 0 | (待填) | | | -## S40 _smoke_settlement_segments(行 379–444 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S40 _smoke_settlement_segments(行 380–445 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 231 | 379 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | (待填) | | | -| 232 | 390 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | (待填) | | | -| 233 | 417 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | (待填) | | | -| 234 | 443 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | (待填) | | | -| 235 | 444 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | (待填) | | | +| 231 | 380 | "segment_order 五段冻结在位(表值 = 拍板序恒等)" | Array(_db.settlement.segment_order) == SettlementTable.FROZEN_SEGMENT_ORDER and SettlementTable.FROZEN_SEGMENT_ORDER == ["grant", "buff_debuff", "combat", "decay", "verdict"] | (待填) | | | +| 232 | 391 | "行型封闭 10 类(删五类零残留:item/consumed_ineffective/use_rejected/window_open/window_skip)" | legacy_total == 0 | (待填) | | | +| 233 | 418 | "每轮行相位单调(授予→增益/减益→战斗→衰减→判定)" | order_ok | (待填) | | | +| 234 | 444 | "案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)" | mapdmg_ok | (待填) | | | +| 235 | 445 | "衰减/判定段每结算恰一段(先兑付后衰减——含末轮)" | decay_verdict_ok | (待填) | | | -## S41 _smoke_skill_pool_data(行 495–670 · 27 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S41 _smoke_skill_pool_data(行 496–671 · 27 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 236 | 495 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | (待填) | | | -| 237 | 497 | "普攻行:倍率恒 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)) | (待填) | | | -| 238 | 501 | "突刺行(伤害 ×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 | (待填) | | | -| 239 | 503 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | (待填) | | | -| 240 | 505 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | (待填) | | | -| 241 | 507 | "毒爆行(伤害 ×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) | (待填) | | | -| 242 | 515 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | (待填) | | | -| 243 | 518 | "蛛网束缚行(妨害型 · 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 | (待填) | | | -| 244 | 526 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | (待填) | | | -| 245 | 528 | "失衡状态行(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" | (待填) | | | -| 246 | 531 | "毒吐行(伤害 ×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") | (待填) | | | -| 247 | 535 | "盾击行(伤害 ×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") | (待填) | | | -| 248 | 549 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | (待填) | | | -| 249 | 550 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | (待填) | | | -| 250 | 558 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | (待填) | | | -| 251 | 604 | "二十八行九种池模板 = 建议池行值((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 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)" | pools_ok | (待填) | | | -| 252 | 614 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | (待填) | | | -| 253 | 631 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | (待填) | | | -| 254 | 636 | "前置:卓尔 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"}) | (待填) | | | -| 255 | 639 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | (待填) | | | -| 256 | 645 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | (待填) | | | -| 257 | 647 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | (待填) | | | -| 258 | 648 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | (待填) | | | -| 259 | 662 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | (待填) | | | -| 260 | 666 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | (待填) | | | -| 261 | 667 | "硬化状态行 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 | (待填) | | | -| 262 | 668 | "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" | (待填) | | | +| 236 | 496 | "技能表十四行(普攻/突刺/蓄力/重击/硬化 + 撕咬/毒爆/蛛网束缚 + 大地脉动 + 震地怒击/遏毒/岩崩 + 毒吐/盾击——前三批 + Boss 半血解锁技三行〔迭代14④ T5〕+ 普通敌错峰两行〔迭代 24 N2〕)" | _db.skill.rows.size() == 14 | (待填) | | | +| 237 | 498 | "普攻行:倍率恒 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)) | (待填) | | | +| 238 | 502 | "突刺行(伤害 ×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 | (待填) | | | +| 239 | 504 | "蓄力行 = 纯攻击增益技(0 伤害、buff_ref → empower——与重击无组合语义)" | not charge_row.is_empty() and String(charge_row.buff_ref) == "empower" | (待填) | | | +| 240 | 506 | "撕咬行(伤害 ×1.5 · 冷却 1——巨蜘蛛池载体,与突刺同倍率错开冷却轴)" | not bite.is_empty() and is_equal_approx(float(bite.damage_mult), 1.5) and int(bite.cooldown_beats) == 1 | (待填) | | | +| 241 | 508 | "毒爆行(伤害 ×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) | (待填) | | | +| 242 | 516 | "毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)" | venom_sole_ok | (待填) | | | +| 243 | 519 | "蛛网束缚行(妨害型 · 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 | (待填) | | | +| 244 | 527 | "蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)" | snare_sole_ok | (待填) | | | +| 245 | 529 | "失衡状态行(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" | (待填) | | | +| 246 | 532 | "毒吐行(伤害 ×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") | (待填) | | | +| 247 | 536 | "盾击行(伤害 ×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") | (待填) | | | +| 248 | 550 | "毒吐投放面(仅 depth_band > 巨蜘蛛首遇带(%d)的巨蜘蛛族行——首遇带池不动,迭代 24 N2/Q6)〔fv24〕" ↩ % spider_lead_band | spit_sole_ok | (待填) | | | +| 249 | 551 | "盾击投放面(仅 (3,·) 灰矮人族——钩爪恐魔零触碰,迭代 24 N2)" | bash_sole_ok | (待填) | | | +| 250 | 559 | "巨蜘蛛首遇带行 pool 恒 {attack:6, bite:2, charge:1}(教学位池零新招——毒吐首投在更深的带)〔fv24〕" | spider_lead_pool == {"attack": 6, "bite": 2, "charge": 1} | (待填) | | | +| 251 | 605 | "二十八行九种池模板 = 建议池行值((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 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)" | pools_ok | (待填) | | | +| 252 | 615 | "每池恰一行普攻(空池不可能的加载校验同款)" | basic_count_ok | (待填) | | | +| 253 | 632 | "逐池增益 ≤1 · 妨害 ≤1(池构成纪律——迭代 24 N2 毒吐/盾击投放后复查)" | comp_ok | (待填) | | | +| 254 | 637 | "前置:卓尔 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"}) | (待填) | | | +| 255 | 640 | "前置:Boss 压到阈值 65(=round(130×0.5))" | int(boss_u.hp) == 65 | (待填) | | | +| 256 | 646 | "解锁归因行恰一次(×%d——闩去重,二次检查零行)" ↩ % unlock_rows.size() | unlock_rows.size() == 1 | (待填) | | | +| 257 | 648 | "解锁行文本 =「蛛化卓尔精灵 解锁了 遏毒」(高光键 skill_unlock——呈现层一次性技能名 toast 路由键)" | String(unlock_rows[0].get("display_name", "")) == "蛛化卓尔精灵 解锁了 遏毒" | (待填) | | | +| 258 | 649 | "解锁行型 = highlight 既有行型(零行型新增——行协议封闭)" | String(unlock_rows[0].get("type", "")) == "highlight" | (待填) | | | +| 259 | 663 | "全深度实例 spawn 后可摇集合恒非空(普攻恒冷却 0——容器级断言,L1/L2 双关卡)" | rollable_ok | (待填) | | | +| 260 | 667 | "蓄力状态行 empower(enemy +100 percent——敌方输出 ×2 走既有 enemy_output 池)" | not empower.is_empty() and String(empower.target_side) == "enemy" and int(empower.magnitude) == 100 | (待填) | | | +| 261 | 668 | "硬化状态行 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 | (待填) | | | +| 262 | 669 | "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" | (待填) | | | -## S42 _smoke_skill_pool_replay_independence(行 703–709 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S42 _smoke_skill_pool_replay_independence(行 704–710 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 263 | 703 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | (待填) | | | -| 264 | 709 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | (待填) | | | +| 263 | 704 | "多实例种子可达(seed=%d——D3 遭遇 ≥2 实例)" ↩ % seed_val | seed_val > 0 | (待填) | | | +| 264 | 710 | "同种子回放:各实例摇定动作序列逐位相同(×%d 轮 × %d 实例——子流独立复放面随并流退役)" ↩ % [rounds, int(run1.count)] | not run1.is_empty() and JSON.stringify(run1.seqs) == JSON.stringify(run2.seqs) | (待填) | | | -## S43 _smoke_skill_pool_ev_anchor(行 791–791 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S43 _smoke_skill_pool_ev_anchor(行 792–792 · 1 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 265 | 791 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | (待填) | | | +| 265 | 792 | "EV 抽样对锚 %s(实测 %.4f ∈ [%.2f, %.2f] 含闸长期速率,×%d 拍)" ↩ % [eid, measured, float(band[0]), float(band[1]), n_beats] | in_band | (待填) | | | -## S44 _smoke_skill_pool_death_void(行 811–830 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S44 _smoke_skill_pool_death_void(行 812–831 · 5 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 266 | 811 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | (待填) | | | -| 267 | 817 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | (待填) | | | -| 268 | 820 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | (待填) | | | -| 269 | 826 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | (待填) | | | -| 270 | 830 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | (待填) | | | +| 266 | 812 | "死亡打断种子可达(seed=%d——D2 首拍摇中特殊技)" ↩ % void_seed | void_seed > 0 | (待填) | | | +| 267 | 818 | "死亡打断战斗启动(hero_atk 999——先手必杀 40HP 熊地精)" | settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}) | (待填) | | | +| 268 | 821 | "开轮已摇定本拍动作(待发 = %s)" ↩ % pending | pending != "" | (待填) | | | +| 269 | 827 | "先手击杀 → 摇定技能作废(第 1 轮零敌方行动行——死亡即不动手)" | enemy_rows == 0 | (待填) | | | +| 270 | 831 | "作废不进账(全池冷却 0 · 保底计数 0——执行上报未发生)" | cd_sum == 0 and int(u.basic_streak) == 0 | (待填) | | | -## S45 _smoke_skill_pool_validation_reject(行 841–877 · 13 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S45 _smoke_skill_pool_validation_reject(行 842–878 · 13 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 271 | 841 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | (待填) | | | -| 272 | 843 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | -| 273 | 848 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | -| 274 | 850 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | -| 275 | 852 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | -| 276 | 854 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | (待填) | | | -| 277 | 857 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | (待填) | | | -| 278 | 859 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | (待填) | | | -| 279 | 865 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | -| 280 | 867 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | (待填) | | | -| 281 | 869 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | (待填) | | | -| 282 | 871 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | (待填) | | | -| 283 | 877 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | (待填) | | | +| 271 | 842 | "加载校验拒绝:池条目 skill_id 悬空致命" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "dangling_skill") | (待填) | | | +| 272 | 844 | "加载校验拒绝:池普攻行数 ≠ 1(空池不可能前提)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 273 | 849 | "加载校验拒绝:池增益技 > 1(每池增益 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 274 | 851 | "加载校验拒绝:普攻配倍率 ≠ 1(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | +| 275 | 853 | "加载校验拒绝:普攻配冷却 ≠ 0(域校验锁死)" | not skill_table.validate().is_empty() | (待填) | | | +| 276 | 855 | "加载校验拒绝:damage_mult 越域 [1.1,16]" | not skill_table.validate().is_empty() | (待填) | | | +| 277 | 858 | "加载校验拒绝:池权重 ≤ 0" | not et2.validate().is_empty() | (待填) | | | +| 278 | 860 | "加载校验拒绝:空池(禁)" | not et2.validate().is_empty() | (待填) | | | +| 279 | 866 | "加载校验拒绝:池妨害技 > 1(每池妨害 ≤1 纪律)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table), "pool_composition") | (待填) | | | +| 280 | 868 | "加载校验拒绝:妨害技缺 buff_ref(效果引用)" | not skill_table.validate().is_empty() | (待填) | | | +| 281 | 870 | "加载校验拒绝:rider_mult 越域 (0,1]" | not skill_table.validate().is_empty() | (待填) | | | +| 282 | 872 | "加载校验拒绝:非伤害技带必中附加分量" | not skill_table.validate().is_empty() | (待填) | | | +| 283 | 878 | "加载校验拒绝:妨害技引用敌方侧状态(施放类 side 守卫)" | _has_err_category(_db.validate_skill_pool_links(skill_table, enemy_table, _db.status_effects), "cast_side") | (待填) | | | -## S46 _smoke_debuff_rider(行 955–957 · 3 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S46 _smoke_debuff_rider(行 956–958 · 3 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 284 | 955 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | (待填) | | | -| 285 | 956 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | (待填) | | | -| 286 | 957 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | (待填) | | | +| 284 | 956 | "毒爆必中附加分量种子可达(seed=%d,毒爆伤害行 ×%d)" ↩ % [venom_seed, venom_rows_seen] | venom_seed > 0 and venom_rows_seen >= 1 | (待填) | | | +| 285 | 957 | "毒爆伤害行逐拍精确复算:终值 = max(1, 掷点档位主分量) + 必中附加(rider 只随 rider_mult 行出现)" | venom_check_ok | (待填) | | | +| 286 | 958 | "蛛网束缚落主角:施放后掷点阈值 = 基准 AC + 失衡幅度(off_guard ac 轴端到端,seed=%d)" ↩ % snare_seed | snare_seed > 0 | (待填) | | | -## S47 _smoke_counter_buckets(行 980–1008 · 6 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) +## S47 _smoke_counter_buckets(行 981–1019 · 7 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 287 | 980 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | (待填) | | | -| 288 | 981 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | (待填) | | | -| 289 | 986 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | (待填) | | | -| 290 | 988 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | (待填) | | | -| 291 | 1003 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | (待填) | | | -| 292 | 1008 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | (待填) | | | +| 287 | 981 | "V1 第1战真实 request_dig 受理(begin_battle 后 ACTIVE 相位)〔无状态化 V1〕" | dig1 | (待填) | | | +| 288 | 982 | "V1 第1战付费挖格经 ctx 就地写 adventure 桶=1(无 export 搬运时点)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 1 | (待填) | | | +| 289 | 987 | "V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕" | EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1 | (待填) | | | +| 290 | 989 | "V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕" | int(led.adventure_counters.get("adv_twin_digs", 0)) == 2 | (待填) | | | +| 291 | 1006 | "V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕" | EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1 | (待填) | | | +| 292 | 1012 | "卡④ wr2①:完美保险标错行 type=extended 且 effect_id=insurance_used(effect_id 必载)〔明细收敛 V2〕" | not v6_ins.is_empty() and String(v6_ins.get("type", "")) == "extended" and String(v6_ins.get("source_class", "")) == "seal_miss" and bool(v6_ins.get("forgiven", 0)) | (待填) | | | +| 293 | 1019 | "V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕" | EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0 | (待填) | | | -## S48 _smoke_seal_ap_exhausted(行 54–80 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S48 _smoke_extended_rows(行 1046–1065 · 2 条 · 源 game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 293 | 54 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 294 | 68 | "%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 | (待填) | | | -| 295 | 73 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | (待填) | | | -| 296 | 79 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | (待填) | | | -| 297 | 80 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | (待填) | | | +| 294 | 1046 | "卡④ wr2②:专长揭示兑付行 type=extended 且 effect_id=fx_reveal 且来源格真实〔明细收敛 V2〕" | not paid.is_empty() and String(paid.get("type", "")) == "extended" and Vector2i(paid["origin_cell"][0], paid["origin_cell"][1]).x >= 0 | (待填) | | | +| 295 | 1063 | "卡④ wr2③:揭示落空行 type=extended 且 effect_id=fx_reveal 且 origin_cell=越界哨值〔明细收敛 V2〕" | not missed.is_empty() and String(missed.get("type", "")) == "extended" and Vector2i(missed["origin_cell"][0], missed["origin_cell"][1]).x == -1 | (待填) | | | -## S49 _smoke_clear_reward_chain(行 329–341 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S49 _smoke_seal_ap_exhausted(行 54–80 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 298 | 329 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | -| 299 | 336 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | (待填) | | | -| 300 | 338 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | (待填) | | | -| 301 | 339 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | (待填) | | | -| 302 | 341 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | (待填) | | | +| 296 | 54 | "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)" | settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 297 | 68 | "%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 | (待填) | | | +| 298 | 73 | "仍存在可挖目标(封印目标来源)" | not plan2.is_empty() | (待填) | | | +| 299 | 79 | "末点 AP 封印被受理" | not out.is_empty() and bool(out[0].accepted) | (待填) | | | +| 300 | 80 | "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" ↩ % str(rounds) | rounds.size() >= 2 | (待填) | | | -## S50 _smoke_clear_reward_zero_paths(行 359–399 · 11 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S50 _smoke_clear_reward_chain(行 329–341 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 303 | 359 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | -| 304 | 363 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | (待填) | | | -| 305 | 366 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | (待填) | | | -| 306 | 367 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | -| 307 | 374 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | -| 308 | 381 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | -| 309 | 382 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | (待填) | | | -| 310 | 389 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | -| 311 | 396 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | (待填) | | | -| 312 | 397 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | -| 313 | 399 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | (待填) | | | +| 301 | 329 | "① 完美封印清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | +| 302 | 336 | "① 清图奖励双份真实兑付(clear_reward 伤害行 ×%d——clear_blast 身份两份各打一发)" ↩ % dmg.size() | dmg.size() == 2 | (待填) | | | +| 303 | 338 | "① 第二份按身份类型走真实兑付(首份『清图奖励』/ 第二份『完美第二份』)" | String(dmg[0].display_name).contains("清图奖励") and String(dmg[1].display_name).contains("完美第二份") | (待填) | | | +| 304 | 339 | "④ 高光行 value = 实际有效份数 2(不记名义值;行数 %d)" ↩ % hl.size() | hl.size() == 1 and int(hl[0].value) == 2 | (待填) | | | +| 305 | 341 | "高光标记 = perfect_sweep" | String(hl[0].get("highlight", "")) == "perfect_sweep" | (待填) | | | -## S51 _smoke_refresh_provenance_guard(行 410–421 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S51 _smoke_clear_reward_zero_paths(行 359–399 · 11 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 314 | 410 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | (待填) | | | -| 315 | 411 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | -| 316 | 419 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | (待填) | | | -| 317 | 420 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | (待填) | | | -| 318 | 421 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | (待填) | | | +| 306 | 359 | "零兑付构造战斗启动" | s.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 307 | 363 | "清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)" | snap != null and String(snap.cell_type) == "blast" | (待填) | | | +| 308 | 366 | "② 即时类首份零兑付(无存活敌)→ first_ok=false" | not first_ok | (待填) | | | +| 309 | 367 | "② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)" | _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 310 | 374 | "高光守卫构造战斗启动" | s_b.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 311 | 381 | "② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案" | _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 312 | 382 | "② 首份零兑付 → 高光行不发(不记名义值 2)" | _rows_of_type(rows_b, "highlight").is_empty() | (待填) | | | +| 313 | 389 | "第二份被拒构造战斗启动" | s2.begin_battle({"level_index": 1, "node_depth": CLEAR_SMOKE_DEPTH, "hero_hp": 80, "hero_max_hp": 80, "node_type": "boss"}) | (待填) | | | +| 314 | 396 | "③ 首份 aoe 真实兑付(击杀唯一存活敌)" | _has_source_row(rows2, "damage", "clear_reward") | (待填) | | | +| 315 | 397 | "③ 第二份被拒产出 reward_ineffective 行(零兑付)" | _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast") | (待填) | | | +| 316 | 399 | "④ 高光行 value = 实际有效份数 1(不记名义值 2)" | hl2.size() == 1 and int(hl2[0].value) == 1 | (待填) | | | -## S52 _smoke_polarity_routing(行 442–549 · 12 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S52 _smoke_refresh_provenance_guard(行 410–421 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 319 | 442 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | (待填) | | | -| 320 | 443 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | (待填) | | | -| 321 | 444 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | (待填) | | | -| 322 | 445 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | (待填) | | | -| 323 | 462 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | (待填) | | | -| 324 | 479 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | (待填) | | | -| 325 | 492 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典;action 型按行值符号辨极性)" | hit_ok | (待填) | | | -| 326 | 498 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | (待填) | | | -| 327 | 526 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | (待填) | | | -| 328 | 532 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | (待填) | | | -| 329 | 543 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | (待填) | | | -| 330 | 549 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | (待填) | | | +| 317 | 410 | "轮内刷新受理(三轮停手攒 RAP 后 Active 相位发起)" | bool(run.refreshed) | (待填) | | | +| 318 | 411 | "刷新图清图达成(%d 轮)" ↩ % int(run.rounds) | bool(run.cleared) | (待填) | | | +| 319 | 419 | "刷新图清图 → 无 clear_reward 行(守卫在位——实测 %d 行)" ↩ % cr | cr == 0 | (待填) | | | +| 320 | 420 | "刷新图清图 → 无高光行(守卫在位——实测 %d 行)" ↩ % hl | hl == 0 | (待填) | | | +| 321 | 421 | "对照基线:常规换图清图有 clear_reward 行(×%d——守卫非全局静默)" ↩ % _regular_clear_reward_rows | _regular_clear_reward_rows > 0 | (待填) | | | -## S53 _smoke_seal_irreversible_quadrants(行 571–663 · 21 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S53 _smoke_polarity_routing(行 442–549 · 12 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 331 | 571 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 332 | 574 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | (待填) | | | -| 333 | 578 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | (待填) | | | -| 334 | 580 | "四情况 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" | (待填) | | | -| 335 | 582 | "四情况 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" | (待填) | | | -| 336 | 584 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | (待填) | | | -| 337 | 585 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | (待填) | | | -| 338 | 600 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | (待填) | | | -| 339 | 601 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | (待填) | | | -| 340 | 602 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | (待填) | | | -| 341 | 609 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 342 | 612 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | (待填) | | | -| 343 | 616 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | (待填) | | | -| 344 | 618 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | (待填) | | | -| 345 | 619 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | (待填) | | | -| 346 | 631 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | (待填) | | | -| 347 | 632 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | (待填) | | | -| 348 | 639 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | -| 349 | 643 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | (待填) | | | -| 350 | 662 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | (待填) | | | -| 351 | 663 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | (待填) | | | +| 322 | 442 | "极性路由构造种子可达(seed=%d——并流后行为面重锚)" ↩ % polarity_seed | polarity_seed > 0 | (待填) | | | +| 323 | 443 | "构造触达:标中真雷 + 标空安全格均入队" | bool(probe["seal_hit_done"]) and bool(probe["seal_miss_done"]) | (待填) | | | +| 324 | 444 | "构造触达:挖到危险格 ≥1(%d)" ↩ % int(probe["mine_events"]) | int(probe["mine_events"]) >= 1 | (待填) | | | +| 325 | 445 | "构造触达:挖到安全格 ≥1(%d)" ↩ % int(probe["safe_events"]) | int(probe["safe_events"]) >= 1 | (待填) | | | +| 326 | 462 | "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["safe_cells"].size() | safe_all_pos | (待填) | | | +| 327 | 479 | "挖危险格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" ↩ % probe["mine_cells"].size() | mine_all_neg | (待填) | | | +| 328 | 492 | "封印命中 → 该危险格资源的正向兑付行(origin 指回危险格,效果 ∈ 正向字典;action 型按行值符号辨极性)" | hit_ok | (待填) | | | +| 329 | 498 | "封印溢价配置在位(seal_hit_premium=125 provisional——形态 a 兑付溢价)" | prem_pct == 125 | (待填) | | | +| 330 | 526 | "标中兑付溢价(×%d 行逐型=同型正向 ×%d/100——等号压线注记:δ=1.25 时条件二 E[标中]=1.25·E_dig 恰等成立)" ↩ % [prem_rows, prem_pct] | prem_rows >= 1 and prem_ok | (待填) | | | +| 331 | 532 | "标空惩罚零溢价(负向直伤行=固定表值原值——溢价只挂标中正向)" | miss_flat_ok | (待填) | | | +| 332 | 543 | "标空封印 → 封印未中归因行 + 该安全格资源负向兑付(同格同批)" | not miss_rows.is_empty() and miss_neg_ok | (待填) | | | +| 333 | 549 | "封印判定兑付仅封印判定批两封印在案(撤封印抵消过滤零泄漏——%d 格)" ↩ % seal_srcs.size() | int(seal_srcs.size()) <= 2 | (待填) | | | -## S54 _smoke_negative_dictionary(行 675–755 · 8 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S54 _smoke_seal_irreversible_quadrants(行 571–663 · 21 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 352 | 675 | "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" | (待填) | | | -| 353 | 679 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | (待填) | | | -| 354 | 716 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | (待填) | | | -| 355 | 726 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | (待填) | | | -| 356 | 738 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | (待填) | | | -| 357 | 748 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | (待填) | | | -| 358 | 753 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | (待填) | | | -| 359 | 755 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | (待填) | | | +| 334 | 571 | "四情况 A:开局受理" | settle_a.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 335 | 574 | "四情况 A:找到安全格封印目标" | not t_a.is_empty() | (待填) | | | +| 336 | 578 | "四情况 A:封印受理" | ms_a.settle_batch([{"action": "seal_place", "cell": t_a.seal_cell}])[0].accepted | (待填) | | | +| 337 | 580 | "四情况 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" | (待填) | | | +| 338 | 582 | "四情况 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" | (待填) | | | +| 339 | 584 | "四情况 A:封印格直接挖开被受理(不再须先撤封印)" | not dig_a.is_empty() and bool(dig_a[0].get("accepted", false)) | (待填) | | | +| 340 | 585 | "四情况 A:封印随挖掘隐式退役(has_seal = false)" | not ms_a.has_seal(t_a.seal_cell) | (待填) | | | +| 341 | 600 | "四情况 A:标空判定零兑付(×%d——封印已擦除不入轮末基线)" ↩ % miss_a | miss_a == 0 | (待填) | | | +| 342 | 601 | "四情况 A:安全格正向兑付作废(×%d——零奖励)" ↩ % grant_a | grant_a == 0 | (待填) | | | +| 343 | 602 | "四情况 A:兑付作废归因行恰一次(×%d——零惩罚零奖励留痕)" ↩ % void_a | void_a == 1 | (待填) | | | +| 344 | 609 | "四情况 B:开局受理" | settle_b.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 345 | 612 | "四情况 B:找到危险格封印目标" | not t_b.is_empty() | (待填) | | | +| 346 | 616 | "四情况 B:封印受理" | ms_b.settle_batch([{"action": "seal_place", "cell": t_b.seal_cell}])[0].accepted | (待填) | | | +| 347 | 618 | "四情况 B:封印危险格直接挖开被受理" | not dig_b.is_empty() and bool(dig_b[0].get("accepted", false)) | (待填) | | | +| 348 | 619 | "四情况 B:危险格已揭示(挖雷本体照常)" | ms_b.is_revealed(t_b.seal_cell) and ms_b.current_board().is_mine(t_b.seal_cell) | (待填) | | | +| 349 | 631 | "四情况 B:挖雷惩罚照常兑付(×%d——只受挖雷一层惩罚)" ↩ % mine_hit_b | mine_hit_b >= 1 | (待填) | | | +| 350 | 632 | "四情况 B:标中不发(×%d——封印已擦除不判)" ↩ % hit_b | hit_b == 0 | (待填) | | | +| 351 | 639 | "四情况 C:开局受理" | settle_c.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}) | (待填) | | | +| 352 | 643 | "四情况 C:安全格+危险格封印目标齐备" | not t_safe.is_empty() and not t_mine.is_empty() | (待填) | | | +| 353 | 662 | "四情况 C:标空惩罚照常(归因×%d 负向兑付×%d)" ↩ % [miss_c, miss_neg_c] | miss_c >= 1 and miss_neg_c >= 1 | (待填) | | | +| 354 | 663 | "四情况 C:标中奖励照常(×%d)" ↩ % hit_c | hit_c >= 1 | (待填) | | | -## S55 _smoke_resource_surface(行 785–804 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) +## S55 _smoke_negative_dictionary(行 675–755 · 8 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 360 | 785 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | (待填) | | | -| 361 | 786 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | (待填) | | | -| 362 | 787 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | (待填) | | | -| 363 | 803 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | (待填) | | | -| 364 | 804 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | (待填) | | | +| 355 | 675 | "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" | (待填) | | | +| 356 | 679 | "effect_pool_map 收编 sapped → hero_offense(attack_up 镜像池)" | String(_db.settlement.effect_pool_map.get("sapped", "")) == "hero_offense" | (待填) | | | +| 357 | 716 | "扫描 6 板内出现五型齐备板(%s)" ↩ % str(by_type.keys()) | by_type.size() == 5 | (待填) | | | +| 358 | 726 | "%s 型负向 → %s 效果行(mine_hit 来源指回该格)" ↩ % [ctype, eff] | _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff) | (待填) | | | +| 359 | 738 | "heal 型负向 → 主角直伤 %d(%d → %d,不经增益状态)" ↩ % [backlash_power, hp_before, hp_after] | hp_after == hp_before - backlash_power | (待填) | | | +| 360 | 748 | "blast 型负向 → 警觉(敌方护甲等级 +1 状态在位)" | alert_ok | (待填) | | | +| 361 | 753 | "action 型负向 → 下轮行动点 −1(排队)" | settlement.action_economy.pending_ap_delta() == d0 - 1 | (待填) | | | +| 362 | 755 | "action 型正负抵消 → 排队净值归零" | settlement.action_economy.pending_ap_delta() == d0 | (待填) | | | -## S56 _init(行 18–18 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S56 _smoke_resource_surface(行 785–804 · 5 条 · 源 game/tests/unit/battle_settlement/seal_polarity_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 365 | 18 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 363 | 785 | "chain/dig 揭示事件 resource 恒 true(雷安同池——违例 %d)" ↩ % digchain_bad | digchain_bad == 0 | (待填) | | | +| 364 | 786 | "initial 揭示事件(封锁格)resource 恒 false(×%d 事件)" ↩ % initial_count | initial_bad == 0 and initial_count >= 1 | (待填) | | | +| 365 | 787 | "危险格揭示携带资源(mine ×resource=true %d——危险格不再恒无)" ↩ % mine_with_resource | mine_with_resource >= 1 | (待填) | | | +| 366 | 803 | "封锁格(R₀)身份查询 = null(不入摇号清单)" | settlement.query_reward_identity(r0_first) == null | (待填) | | | +| 367 | 804 | "未开危险格身份恒可查({reward_id, cell_type, tier}——秘宝 桥接前提)" | mine_unrevealed != null and settlement.query_reward_identity(mine_unrevealed) != null | (待填) | | | -## S57 _boss(行 30–30 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S57 _init(行 18–18 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 366 | 30 | "前置:spawn (%d,%d) 受理" ↩ % [L, D] | ok | (待填) | | | +| 368 | 18 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S58 _test_gate_predicate(行 49–69 · 9 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S58 _boss(行 30–30 · 1 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 367 | 49 | "前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)" | mhp == 130 | (待填) | | | -| 368 | 52 | "门谓词:hp=66 > 阈值 65 → 零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 369 | 53 | "双面过滤:未解锁 venom_curb 不入可摇集合" | not _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -| 370 | 54 | "双面过滤:未解锁 venom_curb 不入保底特殊技集合" | not _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | -| 371 | 57 | "前置:补一刀至阈值 65 受理" | hit65 and int(u.hp) == 65 | (待填) | | | -| 372 | 59 | "门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次" | unlocked.size() == 1 and String(unlocked[0]) == "venom_curb" | (待填) | | | -| 373 | 61 | "解锁即可摇:venom_curb 入可摇集合(冷却初值 0)" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -| 374 | 62 | "解锁即可摇:venom_curb 入保底特殊技集合" | _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | -| 375 | 69 | "保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)" | specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb") | (待填) | | | +| 369 | 30 | "前置:spawn (%d,%d) 受理" ↩ % [L, D] | ok | (待填) | | | -## S59 _test_latch_irreversible(行 77–80 · 3 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S59 _test_gate_predicate(行 49–69 · 9 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 376 | 77 | "前置:半血触发解锁" | ec.check_skill_unlocks(u).size() == 1 | (待填) | | | -| 377 | 79 | "闩不可逆:满血复查零新解锁(单向闩不解锁回退)" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 378 | 80 | "闩不可逆:可摇集合仍含 venom_curb" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 370 | 49 | "前置:卓尔 max_hp=130(表值——难度乘数空表恒 1.0)" | mhp == 130 | (待填) | | | +| 371 | 52 | "门谓词:hp=66 > 阈值 65 → 零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 372 | 53 | "双面过滤:未解锁 venom_curb 不入可摇集合" | not _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 373 | 54 | "双面过滤:未解锁 venom_curb 不入保底特殊技集合" | not _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | +| 374 | 57 | "前置:补一刀至阈值 65 受理" | hit65 and int(u.hp) == 65 | (待填) | | | +| 375 | 59 | "门谓词:hp=65 ≤ 阈值 65 → 恰解锁 venom_curb 一次" | unlocked.size() == 1 and String(unlocked[0]) == "venom_curb" | (待填) | | | +| 376 | 61 | "解锁即可摇:venom_curb 入可摇集合(冷却初值 0)" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | +| 377 | 62 | "解锁即可摇:venom_curb 入保底特殊技集合" | _has_sid(ec.special_skills(u), "venom_curb") | (待填) | | | +| 378 | 69 | "保底不逼锁定:锁定期保底集合 = heavy+venom_burst+web_snare(venom_curb 过滤不出)" | specials.size() == 3 and _has_sid(specials, "heavy") and _has_sid(specials, "web_snare") and _has_sid(specials, "venom_burst") and not _has_sid(specials, "venom_curb") | (待填) | | | -## S60 _test_plain_rows_zero_drift(行 91–103 · 4 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) +## S60 _test_latch_irreversible(行 77–80 · 3 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 379 | 91 | "零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)" | not any_unlock_key | (待填) | | | -| 380 | 92 | "零扰动:满血复查零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | -| 381 | 95 | "零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)" | rollable.size() == pool_count | (待填) | | | -| 382 | 103 | "确定性:两实例同输入可摇集合逐位同(容器零随机纪律)" | a == b | (待填) | | | +| 379 | 77 | "前置:半血触发解锁" | ec.check_skill_unlocks(u).size() == 1 | (待填) | | | +| 380 | 79 | "闩不可逆:满血复查零新解锁(单向闩不解锁回退)" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 381 | 80 | "闩不可逆:可摇集合仍含 venom_curb" | _has_sid(ec.rollable_skills(u), "venom_curb") | (待填) | | | -## S61 _smoke_variant_placement(行 60–132 · 9 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) +## S61 _test_plain_rows_zero_drift(行 91–103 · 4 条 · 源 game/tests/unit/battle_settlement/skill_unlock_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 383 | 60 | "L%d 布置段战斗启动失败" ↩ % L | false | (待填) | | | -| 384 | 63 | "L%d 布置段板非空" ↩ % L | b != null | (待填) | | | -| 385 | 84 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | (待填) | | | -| 386 | 85 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | (待填) | | | -| 387 | 93 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | (待填) | | | -| 388 | 100 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | (待填) | | | -| 389 | 103 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | (待填) | | | -| 390 | 125 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | (待填) | | | -| 391 | 132 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | (待填) | | | +| 382 | 91 | "零扰动:无字段行池快照零 unlock 键(16/19 行逐位同前)" | not any_unlock_key | (待填) | | | +| 383 | 92 | "零扰动:满血复查零解锁" | ec.check_skill_unlocks(u).is_empty() | (待填) | | | +| 384 | 95 | "零扰动:可摇集合 = 全池(普攻冷却恒 0,过滤恒空转)" | rollable.size() == pool_count | (待填) | | | +| 385 | 103 | "确定性:两实例同输入可摇集合逐位同(容器零随机纪律)" | a == b | (待填) | | | -## S62 _smoke_global_variant(行 154–255 · 30 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) +## S62 _smoke_variant_placement(行 60–132 · 9 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 392 | 154 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 393 | 158 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 394 | 162 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 395 | 166 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 396 | 175 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | (待填) | | | -| 397 | 180 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | (待填) | | | -| 398 | 184 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | (待填) | | | -| 399 | 185 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | (待填) | | | -| 400 | 186 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | (待填) | | | -| 401 | 187 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | (待填) | | | -| 402 | 188 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | (待填) | | | -| 403 | 189 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | (待填) | | | -| 404 | 190 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | (待填) | | | -| 405 | 196 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | (待填) | | | -| 406 | 202 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | (待填) | | | -| 407 | 207 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | (待填) | | | -| 408 | 208 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | (待填) | | | -| 409 | 209 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | (待填) | | | -| 410 | 211 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | (待填) | | | -| 411 | 221 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | (待填) | | | -| 412 | 226 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | (待填) | | | -| 413 | 229 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | (待填) | | | -| 414 | 232 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | (待填) | | | -| 415 | 234 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | (待填) | | | -| 416 | 235 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | (待填) | | | -| 417 | 236 | "进攻族上浮: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 | (待填) | | | -| 418 | 240 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | (待填) | | | -| 419 | 241 | "合成序构造例: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 | (待填) | | | -| 420 | 244 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | (待填) | | | -| 421 | 255 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | (待填) | | | +| 386 | 60 | "L%d 布置段战斗启动失败" ↩ % L | false | (待填) | | | +| 387 | 63 | "L%d 布置段板非空" ↩ % L | b != null | (待填) | | | +| 388 | 84 | "L%d 变异 id 全在候选池(门控一致性:%s)" ↩ % [L, str(pool_ids)] | not bad_id | (待填) | | | +| 389 | 85 | "L%d 每图至多一种变异类型(单格单 id + 全图同 id)" ↩ % L | single_type | (待填) | | | +| 390 | 93 | "L%d 变异格全在初始展开区外且不吃危险格(布置域结构保障——closed_only)" ↩ % L | domain_ok | (待填) | | | +| 391 | 100 | "L%d 变异格数 %d ≤ 公式上限 %d(可挖池百分比 + 关卡递增)" ↩ % [L, vcells.size(), expect_cap] | vcells.size() <= expect_cap | (待填) | | | +| 392 | 103 | "L%d 公式量>0 时非零布置(极小池 round 至 0 = 合法零布置——2026-09-21 基数修正批)" ↩ % L | expect_cap == 0 or vcells.size() > 0 | (待填) | | | +| 393 | 125 | "L%d 全部连通片 ≤ %d 格(全连通生长、2×2/1×4/1×3+1 等形)" ↩ % [L, pack_max] | pack_ok | (待填) | | | +| 394 | 132 | "L%d 同种子变异布置逐格相同(variant_place 流确定性)" ↩ % L | b2 != null and _variant_dump(b2) == _variant_dump(b) | (待填) | | | -## S63 _smoke_type_gate(行 25–29 · 2 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) +## S63 _smoke_global_variant(行 154–255 · 30 条 · 源 game/tests/unit/battle_settlement/variant_placement_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 422 | 25 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | (待填) | | | -| 423 | 28 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | (待填) | | | +| 395 | 154 | "互锁负样本③:resource_face + scope=region 被拒(resource_face ⇒ global)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 396 | 158 | "互锁负样本④:scope=global + effect=hide_number 被拒(global ⇒ resource_face)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 397 | 162 | "互锁负样本⑤:scope=global + trigger=on_open 被拒(global ⇒ always 整图恒在)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 398 | 166 | "互锁负样本⑥:scope=global + placement=closed_only 被拒(global ⇒ any 无格子布置域)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 399 | 175 | "互锁负样本⑦:resource_face effect_value=51 越域 [0,50] 被拒(不适用 flat_damage [1,9] 加法核)" | domain_hit | (待填) | | | +| 400 | 180 | "门控三维:L2 underdark boss 池 = region 两行(贫矿脉 2026-10-16 屏蔽删行后无 global 行)" | pool_boss_ids == ["magma", "ash"] | (待填) | | | +| 401 | 184 | "门控三维:battle 节点静默跳过 global 行(普通图零报错零警告)" | pool_battle_ids == ["magma", "ash"] | (待填) | | | +| 402 | 185 | "门控三维:缺省双参调用 = global 行跳过(既有调用口径零扰动)" | _db.variant_pool(2, "underdark").is_empty() == false and _db.variant_pool(2, "underdark").size() == 2 | (待填) | | | +| 403 | 186 | "L1 Boss 免疫:unlock_level=2 门控在 boss 节点同样生效(教学保护带 B)" | _db.variant_pool(1, "underdark", "boss").is_empty() | (待填) | | | +| 404 | 187 | "全局决议:battle 节点恒无全局型" | _db.global_variant_row(2, "underdark", "battle").is_empty() | (待填) | | | +| 405 | 188 | "全局决议:L2 boss 全局池空 → 空行(贫矿脉屏蔽后兜底回落承载)" | _db.global_variant_row(2, "underdark", "boss").is_empty() | (待填) | | | +| 406 | 189 | "全局决议:L1 boss 全局池空 → 空行(兜底回落承载)" | _db.global_variant_row(1, "underdark", "boss").is_empty() and _db.global_variant_row(1, "dungeon", "boss").is_empty() | (待填) | | | +| 407 | 190 | "elite 前向词位:boss 同池恒空(贫矿脉屏蔽后——词表位不造行)" | _db.global_variant_row(2, "underdark", "elite").is_empty() | (待填) | | | +| 408 | 196 | "L2 Boss 战斗启动(node_depth=9 Boss 终点——兜底回落断言载体)" | boss_ok | (待填) | | | +| 409 | 202 | "屏蔽后回落:Boss 图无整图全局型(board_variants 空——全局池空合法态)" | bb.board_variant_ids().is_empty() | (待填) | | | +| 410 | 207 | "屏蔽后回落:Boss 图 region 变异恰一行且在候选池内(实测 %s)" ↩ % str(boss_vids.keys()) | boss_vids.size() == 1 and (boss_vids.has("magma") or boss_vids.has("ash")) | (待填) | | | +| 411 | 208 | "屏蔽后回落:Boss 图恢复 region 变异格布置(原替换制跳过 region 的逻辑随全局池空失效)" | not bb.variants.is_empty() | (待填) | | | +| 412 | 209 | "屏蔽后回落:variant_place 恢复取号(原全局取用零取号态消失)" | crng.variant_calls > 0 | (待填) | | | +| 413 | 211 | "快照投影:board_variants 空态一致(横幅/角标唯一读取面——无全局型即零预告)" | _arr_str_eq(snap.get("board_variants", []), bb.board_variant_ids()) | (待填) | | | +| 414 | 221 | "确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)" | _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls | (待填) | | | +| 415 | 226 | "偏斜基线:正向池现行合计 77(挖掘风险批口径)" | base_sum == 77 | (待填) | | | +| 416 | 229 | "off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)" | off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows) | (待填) | | | +| 417 | 232 | "合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)" | eco._pool_weight(on_rows) == 77 | (待填) | | | +| 418 | 234 | "白名单豁免:weaken/gp/action 三行偏斜后逐位不动" | wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2 | (待填) | | | +| 419 | 235 | "生存族下调:heal 4→3 / defense 11→7(v=30 构造例)" | wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7 | (待填) | | | +| 420 | 236 | "进攻族上浮: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 | (待填) | | | +| 421 | 240 | "合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8" | eco._pool_weight(combo_rows) == 77 + 4 * 2 | (待填) | | | +| 422 | 241 | "合成序构造例: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 | (待填) | | | +| 423 | 244 | "域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)" | eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0 | (待填) | | | +| 424 | 255 | "确定性:同种子普通图布置/摇号两遍逐位相同(off 全链路)" | n1 == _board_resource_dump(normal2) and not n1.is_empty() | (待填) | | | -## S64 _smoke_hygiene_gates(行 56–77 · 6 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) +## S64 _smoke_type_gate(行 25–29 · 2 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 424 | 56 | "V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | (待填) | | | -| 425 | 58 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | (待填) | | | -| 426 | 61 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | (待填) | | | -| 427 | 64 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | (待填) | | | -| 428 | 67 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | (待填) | | | -| 429 | 76 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | (待填) | | | +| 425 | 25 | "V7 类型闸 warning 配置在位(untyped_declaration=1 显形)〔迭代27 T5〕" | pj.contains("gdscript/warnings/untyped_declaration=1") | (待填) | | | +| 426 | 28 | "V7 类型审计器闸机在位(static_scan S9 → tools/audit_type_hints.py)〔迭代27 T5〕" | scan_src.contains("audit_type_hints.py") | (待填) | | | -## S65 _init(行 22–22 · 1 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S65 _smoke_hygiene_gates(行 56–77 · 6 条 · 源 game/tests/unit/contracts/source_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 430 | 22 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 427 | 56 | "V8 字符串 Callable 产品码零命中(实测应 0——方法引用换装闸)〔迭代27 T6〕" | callables == 0 | (待填) | | | +| 428 | 58 | "V8 FeatsEngine 整文件退役(class_name 注销=文件删除)〔迭代27 T6〕" | not FileAccess.file_exists("res://core/feats_engine.gd") | (待填) | | | +| 429 | 61 | "V8 死代码抽查·_attune_row 零残留〔迭代27 T6〕" | not bo.contains("func _attune_row") | (待填) | | | +| 430 | 64 | "V8 死代码抽查·attunement_counters 零残留〔迭代27 T6〕" | not sm.contains("func attunement_counters") | (待填) | | | +| 431 | 67 | "V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕" | not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger") | (待填) | | | +| 432 | 76 | "V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" ↩ % outside_calls | outside_calls == 0 | (待填) | | | -## S66 _test_damage_controller_vocabularies(行 43–57 · 4 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S66 _init(行 22–22 · 1 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 431 | 43 | "五配方常量非空(开单面唯一分派键)" | nonempty | (待填) | | | -| 432 | 47 | "五配方常量互异(词表封闭无别名)" | unique_kinds.size() == kinds.size() | (待填) | | | -| 433 | 55 | "三目标规则常量互异(结账时刻现场解析词表)" | unique_targets.size() == targets.size() | (待填) | | | -| 434 | 56 | "双结账政策常量互异(A 即结 / B 攒批回切位)" | String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT) | (待填) | | | +| 433 | 22 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S67 _test_rng_tag_closure(行 64–67 · 2 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S67 _test_damage_controller_vocabularies(行 43–57 · 4 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 435 | 64 | "settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)" | RngServiceScript.PURPOSE_TAGS.has(stream_label) | (待填) | | | -| 436 | 66 | "RngService 用途标签词表非空且互异(封闭语义由流改标签)" | RngServiceScript.PURPOSE_TAGS.size() > 0 | (待填) | | | +| 434 | 43 | "五配方常量非空(开单面唯一分派键)" | nonempty | (待填) | | | +| 435 | 47 | "五配方常量互异(词表封闭无别名)" | unique_kinds.size() == kinds.size() | (待填) | | | +| 436 | 55 | "三目标规则常量互异(结账时刻现场解析词表)" | unique_targets.size() == targets.size() | (待填) | | | +| 437 | 56 | "双结账政策常量互异(A 即结 / B 攒批回切位)" | String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT) | (待填) | | | -## S68 _test_ticket_nine_columns(行 83–85 · 3 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) +## S68 _test_rng_tag_closure(行 64–67 · 2 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 437 | 83 | "单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)" | all_present | (待填) | | | -| 438 | 84 | "缺省无类型轴(attack_type 空 = 恒不查克制矩阵)" | String(t.attack_type) == "" | (待填) | | | -| 439 | 85 | "缺省无前置依赖(requires = -1)" | int(t.requires) == -1 | (待填) | | | - -## S69 _test_status_boolean_acceptance(行 107–121 · 5 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 440 | 107 | "前置:状态表非空" | not any_row.is_empty() | (待填) | | | -| 441 | 112 | "域闭授予拒绝(EC1——战斗域外 apply_effect = false)" | effects.apply_effect(probe_id, src) == false | (待填) | | | -| 442 | 113 | "未知 effect_id 拒绝(EC2)" | effects.apply_effect("no_such_effect_id__", src) == false | (待填) | | | -| 443 | 115 | "开域后受理已知 id(布尔受理口径 true 通道)" | effects.apply_effect(probe_id, src) == true | (待填) | | | -| 444 | 121 | "受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)" | projected | (待填) | | | - -## S70 _smoke_adventure_api(行 61–82 · 11 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 445 | 61 | "冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))" | adr.start_adventure(42) | (待填) | | | -| 446 | 62 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | (待填) | | | -| 447 | 63 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | (待填) | | | -| 448 | 64 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | (待填) | | | -| 449 | 67 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | (待填) | | | -| 450 | 68 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | (待填) | | | -| 451 | 69 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | (待填) | | | -| 452 | 78 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | (待填) | | | -| 453 | 79 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | (待填) | | | -| 454 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | (待填) | | | -| 455 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | (待填) | | | - -## S71 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 456 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | | -| 457 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | | -| 458 | 135 | "满配冒险成功(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | | -| 459 | 136 | "完成几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | | -| 460 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | | -| 461 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | | -| 462 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | | -| 463 | 154 | "关卡完成三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | | -| 464 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | | -| 465 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | | -| 466 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | | -| 467 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | | -| 468 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | | -| 469 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | | -| 470 | 245 | "关卡完成部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | | -| 471 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | | -| 472 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | | -| 473 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | | -| 474 | 261 | "同种子两局逐位可复现(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"] | (待填) | | | -| 475 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | | -| 476 | 274 | "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 | (待填) | | | -| 477 | 278 | "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"] | (待填) | | | -| 478 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | | -| 479 | 290 | "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 | (待填) | | | -| 480 | 295 | "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"] | (待填) | | | -| 481 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | | -| 482 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | | -| 483 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | | -| 484 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | | -| 485 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | | -| 486 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | | -| 487 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | | -| 488 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | | -| 489 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | | -| 490 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | | -| 491 | 355 | "L2 关卡完成三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | (待填) | | | -| 492 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | | -| 493 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | | -| 494 | 362 | "三选一属性件与商店购买同账累加(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 | (待填) | | | -| 495 | 364 | "关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | (待填) | | | -| 496 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | | -| 497 | 368 | "恢复点数学: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 | (待填) | | | -| 498 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | | -| 499 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | | -| 500 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | | -| 501 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | | -| 502 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | | -| 503 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | -| 504 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | | -| 505 | 399 | "池耗后候选恒 ≥2(稀有档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | | -| 506 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | | -| 507 | 417 | "满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | | -| 508 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | -| 509 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | | -| 510 | 435 | "奖励节点 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")) | (待填) | | | -| 511 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | (待填) | | | -| 512 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | | - -## S72 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 513 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | | -| 514 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | | -| 515 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | | -| 516 | 460 | "专长带入池: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 | (待填) | | | -| 517 | 462 | "旧 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() | (待填) | | | -| 518 | 463 | "迁出收尾: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() | (待填) | | | -| 519 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | | -| 520 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | | -| 521 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | | -| 522 | 484 | "数值纪律:陷阱爆发倍率>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 | (待填) | | | -| 523 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | | -| 524 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | | -| 525 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | (待填) | | | -| 526 | 522 | "关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | | -| 527 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | | -| 528 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | (待填) | | | -| 529 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | | -| 530 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | (待填) | | | -| 531 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | | -| 532 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | | -| 533 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | (待填) | | | -| 534 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | (待填) | | | -| 535 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | (待填) | | | -| 536 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | | -| 537 | 561 | "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 | (待填) | | | -| 538 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | | -| 539 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | | -| 540 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | | -| 541 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | | -| 542 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | | -| 543 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | | -| 544 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | | -| 545 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | | -| 546 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | | -| 547 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | | -| 548 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | -| 549 | 625 | "边陲矿工:边缘格产出 +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() | (待填) | | | -| 550 | 633 | "封印回响:标中计数≥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" | (待填) | | | -| 551 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | | -| 552 | 644 | "封印桩:标中本轮 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" | (待填) | | | -| 553 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | | -| 554 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | | -| 555 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | | -| 556 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | | -| 557 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | | -| 558 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | | -| 559 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | | -| 560 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(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 | (待填) | | | -| 561 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | | -| 562 | 692 | "石肤:受伤 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 | (待填) | | | -| 563 | 696 | "背水:生命≤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() | (待填) | | | -| 564 | 701 | "破封一击:挖开带封印危险格 +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() | (待填) | | | -| 565 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | | -| 566 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | | -| 567 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | | -| 568 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | | -| 569 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | | -| 570 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | -| 571 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | | -| 572 | 730 | "处刑台:敌血量≤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() | (待填) | | | -| 573 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | | -| 574 | 737 | "血税:双向同局账本(踩陷阱−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" | (待填) | | | -| 575 | 742 | "淬毒刃纹(迁入):克制正面(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() | (待填) | | | -| 576 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | | -| 577 | 750 | "零持有订阅=零命中(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() | (待填) | | | -| 578 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | | -| 579 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | | -| 580 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | | - -## S73 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 581 | 65 | "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 | (待填) | | | -| 582 | 67 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | (待填) | | | - -## S74 _smoke_reward_identity_unit(行 83–116 · 10 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 583 | 83 | "roll_board 受理" | reward_economy.roll_board(eligible) | (待填) | | | -| 584 | 84 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | (待填) | | | -| 585 | 95 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | (待填) | | | -| 586 | 97 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | (待填) | | | -| 587 | 101 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | (待填) | | | -| 588 | 102 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | (待填) | | | -| 589 | 106 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | (待填) | | | -| 590 | 111 | "清图奖励 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]) | (待填) | | | -| 591 | 113 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | (待填) | | | -| 592 | 116 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | (待填) | | | +| 438 | 64 | "settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)" | RngServiceScript.PURPOSE_TAGS.has(stream_label) | (待填) | | | +| 439 | 66 | "RngService 用途标签词表非空且互异(封闭语义由流改标签)" | RngServiceScript.PURPOSE_TAGS.size() > 0 | (待填) | | | -## S75 _smoke_board_generator(行 135–210 · 24 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) +## S69 _test_ticket_nine_columns(行 83–85 · 3 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 593 | 135 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | (待填) | | | -| 594 | 141 | "BoardGen 精确命中 (2,3)(覆盖批键域补齐)" | int(exact_row.get("node_depth", -1)) == 3 | (待填) | | | -| 595 | 151 | "R1 ceil 能力不失(夹具摘除 (2,3)→向上取整到 4·偏难口径)" | int(ceil_row.get("node_depth", -1)) == 4 | (待填) | | | -| 596 | 153 | "夹具表原样还原(行数与解析回到覆盖批现值)" | _db.board_gen.rows.size() == rows_before and int(_db.resolve_board_gen(2, 3).get("node_depth", -1)) == 3 | (待填) | | | -| 597 | 156 | "L%d 生成产物非空" ↩ % L | b1 != null | (待填) | | | -| 598 | 159 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | (待填) | | | -| 599 | 160 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row) | (待填) | | | -| 600 | 167 | "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" | (待填) | | | -| 601 | 169 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | (待填) | | | -| 602 | 170 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | (待填) | | | -| 603 | 171 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | (待填) | | | -| 604 | 173 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | (待填) | | | -| 605 | 175 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | (待填) | | | -| 606 | 177 | "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) | (待填) | | | -| 607 | 179 | "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) | (待填) | | | -| 608 | 181 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | (待填) | | | -| 609 | 185 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | (待填) | | | -| 610 | 187 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | (待填) | | | -| 611 | 193 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | (待填) | | | -| 612 | 195 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | (待填) | | | -| 613 | 197 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | (待填) | | | -| 614 | 199 | "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 | (待填) | | | -| 615 | 207 | "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) | (待填) | | | -| 616 | 209 | "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", "")) != "" | (待填) | | | +| 440 | 83 | "单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)" | all_present | (待填) | | | +| 441 | 84 | "缺省无类型轴(attack_type 空 = 恒不查克制矩阵)" | String(t.attack_type) == "" | (待填) | | | +| 442 | 85 | "缺省无前置依赖(requires = -1)" | int(t.requires) == -1 | (待填) | | | + +## S70 _test_status_boolean_acceptance(行 107–121 · 5 条 · 源 game/tests/unit/contracts/system_contracts_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 443 | 107 | "前置:状态表非空" | not any_row.is_empty() | (待填) | | | +| 444 | 112 | "域闭授予拒绝(EC1——战斗域外 apply_effect = false)" | effects.apply_effect(probe_id, src) == false | (待填) | | | +| 445 | 113 | "未知 effect_id 拒绝(EC2)" | effects.apply_effect("no_such_effect_id__", src) == false | (待填) | | | +| 446 | 115 | "开域后受理已知 id(布尔受理口径 true 通道)" | effects.apply_effect(probe_id, src) == true | (待填) | | | +| 447 | 121 | "受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)" | projected | (待填) | | | + +## S71 _smoke_adventure_api(行 61–82 · 11 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 448 | 61 | "冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))" | adr.start_adventure(42) | (待填) | | | +| 449 | 62 | "进行中重复 start_adventure 拒绝(重开须待结算完毕)" | not adr.start_adventure(43) | (待填) | | | +| 450 | 63 | "停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)" | sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented" | (待填) | | | +| 451 | 64 | "入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)" | adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \\ and adr.runner().current_battle() == null | (待填) | | | +| 452 | 67 | "pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)" | adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])) | (待填) | | | +| 453 | 68 | "pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)" | sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null | (待填) | | | +| 454 | 69 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not adr.abandon() | (待填) | | | +| 455 | 78 | "首战胜后选路态(非战斗期——current_battle 置空)" | adr.runner().current_battle() == null and adr.runner().awaiting_choice() | (待填) | | | +| 456 | 79 | "非战斗期 abandon 受理(选路态——同失败型结算)" | adr.abandon() | (待填) | | | +| 457 | 81 | "放弃结算:result=abandoned · cleared=false(已获面不追回——钥石累计 %d)" ↩ % int(sum.get("keystone_total", -1)) | String(sum.get("result", "")) == "abandoned" and not bool(sum.get("cleared", true)) | (待填) | | | +| 458 | 82 | "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)" | not adr.abandon() | (待填) | | | + +## S72 _smoke_adventure(行 113–438 · 57 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 459 | 113 | "序列关数 = 行集关数(三关正式序列生效)" | int(_db.level.rows.size()) == 3 | (待填) | | | +| 460 | 130 | "生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" ↩ % [cov_scan, cov_miss] | cov_scan > 0 and cov_miss == 0 | (待填) | | | +| 461 | 135 | "满配冒险成功(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)" | String(st["result"]) == "completed" and bool(sum.get("cleared", false)) | (待填) | | | +| 462 | 136 | "完成几关 = 3(levels_cleared)" | int(sum.get("levels_cleared", -1)) == 3 | (待填) | | | +| 463 | 140 | "钥石合计 = ΣBoss 首杀行值(%d——逐杀掉落新口径;断点未注入恒首杀)" ↩ % int(sum.get("keystone_total", -1)) | int(sum.get("keystone_total", -1)) == ks_exp | (待填) | | | +| 464 | 142 | "逐杀掉落明细恰 3 笔(L1/L2/L3——additive 摘要键 keystone_drops)" | drops.size() == 3 | (待填) | | | +| 465 | 143 | "摘要携带主种子(42)" | int(sum.get("master_seed", -1)) == 42 | (待填) | | | +| 466 | 154 | "关卡完成三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)" | picks.size() == 2 and resolves.size() == 2 + node_picks | (待填) | | | +| 467 | 165 | "三关生成图各含 ≥1 奖励节点(节点三选一候选位在图)〔v4〕" | reward_levels == 3 | (待填) | | | +| 468 | 169 | "奖励分支点选→三选一候选可取且停点在途(候选 %s 件)" ↩ % str((rw.get("cands", []) as Array).size()) | bool(rw.get("pending", false)) and (rw.get("cands", []) as Array).size() >= 1 | (待填) | | | +| 469 | 182 | "奖励三选一点选兑付受理(件=%s)" ↩ % String(rw_pc.get("kind", "")) | rw_pick_ok | (待填) | | | +| 470 | 188 | "进战载荷两样=关卡序号+节点深度(实测 depth=%s 句柄=%s)〔v6〕" ↩ % [str(enc_probe.get("node_depth", -1)), str(node_probe.get("id", -1))] | not enc_probe.is_empty() and int(enc_probe.get("node_depth", -1)) == int(node_probe.get("depth", -1)) and int(enc_probe.get("level_index", -1)) == 1 and int(node_probe.get("id", -1)) != int(node_probe.get("depth", -1)) | (待填) | | | +| 471 | 197 | "奖励节点兑付回执与经停逐笔相等(context=reward_node——同账计数 %d/%d)" ↩ % [node_resolves, node_picks] | node_resolves == node_picks | (待填) | | | +| 472 | 244 | "三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)" | cand_ok | (待填) | | | +| 473 | 245 | "关卡完成部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)" | heal_ok | (待填) | | | +| 474 | 246 | "钥石逐杀入账(首杀档位 + 累计逐位)" | ks_gain_ok | (待填) | | | +| 475 | 247 | "点选恰兑付选中一件(值=令牌档位表值;未选项零生效)" | resolve_ok | (待填) | | | +| 476 | 258 | "各战上限入参 ∈ [基础, 基础+3×件值+购血余量](覆盖值通道——双渠道共享上限 3;模拟档贪心购血合法叠加〔数值修复批 v3.4〕;首战=基础 %d)" ↩ % exp_base | seq_ok | (待填) | | | +| 477 | 261 | "同种子两局逐位可复现(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"] | (待填) | | | +| 478 | 271 | "零配 L1 败北种子可达(seed=%s)" ↩ % str(lose.get("s", -1)) | not lose.is_empty() | (待填) | | | +| 479 | 274 | "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 | (待填) | | | +| 480 | 278 | "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"] | (待填) | | | +| 481 | 287 | "半配(atk+50)L2 败北种子可达(seed=%s)" ↩ % str(half.get("s", -1)) | not half.is_empty() | (待填) | | | +| 482 | 290 | "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 | (待填) | | | +| 483 | 295 | "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"] | (待填) | | | +| 484 | 301 | "重复拒绝场景:冒险启动(seed 7——生成图确定同上)" | adr6.start_adventure(7) | (待填) | | | +| 485 | 309 | "重复拒绝场景就位:选路态放弃 → 已结算" | adr6.abandon() | (待填) | | | +| 486 | 312 | "重复关卡结束宣布拒绝(账本与摘要零变化——契约 7)" | adr6.summary == sum6_before | (待填) | | | +| 487 | 322 | "每战完成入账恰一次(gain %d = battles %d——GP 袋件入账单列)" ↩ % [gains, int(st["battles"])] | gains == int(st["battles"]) | (待填) | | | +| 488 | 323 | "钱包 clear 恰一次且为末事件(关卡收束不清——GP 冒险收束清零归冒险结构)" | clears == 1 and wallet.size() > 0 and String(wallet[wallet.size() - 1]["event"]) == "clear" | (待填) | | | +| 489 | 325 | "三关商店房各到达一次(每关入口→终点路径恰穿一店的驱动投影)且首达余额 > 0(%d——GP 跨关延续)" ↩ % (shop_seq[0] if shop_seq.size() > 0 else -1) | shop_seq.size() == 3 and int(shop_seq[0]) > 0 | (待填) | | | +| 490 | 336 | "同账场景种子可达(seed=%s——L2 商店房就位)" ↩ % str(same.get("s", -1)) | not same.is_empty() | (待填) | | | +| 491 | 349 | "进店余额 ≥ hp_up L2 首价(%d = 基价 %d + 关卡加成 %d——配置推导)" ↩ % [hp_price + l2_price_bonus, hp_price, l2_price_bonus] | gp0 >= hp_price + l2_price_bonus | (待填) | | | +| 492 | 350 | "购买 hp_up 受理(出账 + 加算区)" | r8.buy_product("hp_up") | (待填) | | | +| 493 | 352 | "统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" ↩ % acc_shop | acc_shop >= 3 | (待填) | | | +| 494 | 355 | "L2 关卡完成三选一停点在案(直调兑付前——零自动推进)" | not a8._curio_pending.is_empty() | (待填) | | | +| 495 | 356 | "未兑付推进拒绝(选择停点零自动推进契约)" | not a8.continue_to_next_level() | (待填) | | | +| 496 | 359 | "三选一点选兑付受理(候选取首件)" | a8.pick_curio(String(l2_pick.get("kind", "")), String(l2_pick.get("tier", "")), String(l2_pick.get("feat_id", ""))) | (待填) | | | +| 497 | 362 | "三选一属性件与商店购买同账累加(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 | (待填) | | | +| 498 | 364 | "关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" ↩ % [int(a8.ledger["hero_hp"]), max_eff8] | int(a8.ledger["hero_hp"]) <= max_eff8 | (待填) | | | +| 499 | 365 | "兑付后推进受理(下一关开始)" | a8.continue_to_next_level() | (待填) | | | +| 500 | 368 | "恢复点数学: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 | (待填) | | | +| 501 | 372 | "同一公式两个落点逐位一致(LevelRunner.rest_heal_amount 内置默认)" | int(lr9.rest_heal_amount(30, 70, 100)) == 30 and int(lr9.rest_heal_amount(30, 95, 100)) == 5 | (待填) | | | +| 502 | 376 | "放弃场景:冒险启动(seed 15——生成图确定同上)" | a10.start_adventure(15) | (待填) | | | +| 503 | 377 | "放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)" | a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])) | (待填) | | | +| 504 | 378 | "战斗中 abandon 拒绝(战斗域单事务在飞)" | not a10.abandon() | (待填) | | | +| 505 | 385 | "非战斗期(选路态)abandon 受理" | a10.runner().current_battle() == null and a10.abandon() | (待填) | | | +| 506 | 389 | "剔牌库/替换场景:冒险启动(seed 11——道具解锁集只含 wide_scout_band)" | a11.start_adventure(11, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | +| 507 | 398 | "池耗即枯竭(attack/ap 令牌耗尽不出候选——%s)" ↩ % str(draw) | not draw_kinds.has("attack") and not draw_kinds.has("ap") | (待填) | | | +| 508 | 399 | "池耗后候选恒 ≥2(稀有档余量——防「三选项全不可选」死角)" | (draw as Array).size() >= 2 | (待填) | | | +| 509 | 408 | "池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)" | not treasure_seen | (待填) | | | +| 510 | 417 | "满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)" | int(cap["payload"].get("heal_gained", -1)) == 0 | (待填) | | | +| 511 | 420 | "节点共享池场景:冒险启动(seed 13)" | a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}) | (待填) | | | +| 512 | 431 | "奖励节点共享池:令牌耗尽同样不出候选(同库同池——%s)" ↩ % str(ncands13) | not has_attack13 | (待填) | | | +| 513 | 435 | "奖励节点 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")) | (待填) | | | +| 514 | 436 | "节点停点态兑付后清零(恰一次语义同构)" | a13._node_curio_pending.is_empty() | (待填) | | | +| 515 | 438 | "放弃摘要:abandoned · 未打穿(已获大奖励不追回——同失败型结算)" | String(s10.get("result", "")) == "abandoned" and not bool(s10.get("cleared", true)) | (待填) | | | + +## S73 _smoke_feats(行 449–761 · 68 条 · 源 game/tests/unit/core/adventure_feats_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 516 | 449 | "feat_table 已加载(30 件=基础 27+迁入 2+卡② 完美保险 1——迭代 31 卡② 换代)" | _db.feats != null and (_db.feats.rows as Array).size() == 30 | (待填) | | | +| 517 | 450 | "feat_table.validate 零违例(四级 rarity/cap 域/trigger 词表/行键白名单全过)" | _db.feats.validate().is_empty() | (待填) | | | +| 518 | 452 | "专长带场景:冒险启动(seed 71)" | a_r0.start_adventure(71) | (待填) | | | +| 519 | 460 | "专长带入池: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 | (待填) | | | +| 520 | 462 | "旧 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() | (待填) | | | +| 521 | 463 | "迁出收尾: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() | (待填) | | | +| 522 | 470 | "层级纪律:牌库 kind 全集=CURIO_KINDS+relic(秘宝 item_id 零入池)〔专长批 V18〕" | kinds_ok | (待填) | | | +| 523 | 476 | "棋盘事实不可变:新 30 行零棋盘改写类订阅(rewrite_board 已除名消费面)〔专长批 V19〕" | rewrite_rows == 0 | (待填) | | | +| 524 | 483 | "对外命名纪律:30 行 display_name/desc 零「雷/旗」字面(封印/陷阱风味系)〔专长批 V21 初筛〕" | bad_words == 0 | (待填) | | | +| 525 | 484 | "数值纪律:陷阱爆发倍率>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 | (待填) | | | +| 526 | 502 | "新 schema 构造表 validate 零违例(四级 rarity/cap 字段/词表全过——流派标签已退役零 archetype)" | rt.validate().is_empty() | (待填) | | | +| 527 | 506 | "专长带场景:冒险启动(seed 72——注入 5 行临时表)" | a_r.start_adventure(72) | (待填) | | | +| 528 | 511 | "专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107" | deck_relic == 5 and a_r._curio_deck.size() == 107 | (待填) | | | +| 529 | 522 | "关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)" | feat_seen.size() >= 2 | (待填) | | | +| 530 | 523 | "节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)" | not node_relic_seen | (待填) | | | +| 531 | 531 | "A5 专长凭证兑付受理(节点渠道——拿到即开)" | a_t.apply_node_curio("feat_ticket", "excellent") | (待填) | | | +| 532 | 537 | "A5 拿到即开——停点候选已替换为专长三选一(全 relic)" | tkt_all_relic | (待填) | | | +| 533 | 538 | "A5 零摘牌——专长凭证兑付不动令牌库(不进持有集)" | a_t.pool_stock("attack") == int(stock_before["attack"]) and a_t.pool_stock("gp") == int(stock_before["gp"]) | (待填) | | | +| 534 | 539 | "A5 无专长凭证残留持有集" | not (a_t.feats_held() as Array).has("feat_ticket") | (待填) | | | +| 535 | 542 | "A5 专长面兑付受理(专长凭证开出候选可正常入持有集)" | (a_t.feats_held() as Array).size() == feats_n + 1 | (待填) | | | +| 536 | 547 | "A6 保底触发(pity=5 → 第 6 次展示必出——概率无关)" | a_t._feat_ticket_consider(rg_t) | (待填) | | | +| 537 | 548 | "A6 触发后清零" | a_t._feat_ticket_pity == 0 | (待填) | | | +| 538 | 551 | "A6 限购满不再触发(保底冻结)" | not a_t._feat_ticket_consider(rg_t) | (待填) | | | +| 539 | 559 | "A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)" | offered | (待填) | | | +| 540 | 561 | "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 | (待填) | | | +| 541 | 585 | "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" ↩ % (i + 1) | refund | (待填) | | | +| 542 | 587 | "双管镐反例:第 %d 次不返(未满 2 次)" ↩ % (i + 1) | not refund | (待填) | | | +| 543 | 591 | "地质学家:首开新类型集 1 点〔专长批 V3〕" | orch._hits_of(geo_hits, "counter_add").size() == 1 | (待填) | | | +| 544 | 595 | "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕" | orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0 | (待填) | | | +| 545 | 597 | "地质学家反例:同类型重复首开不叠点〔专长批 V3〕" | orch._hits_of(geo_hits3, "counter_add").is_empty() | (待填) | | | +| 546 | 601 | "安全帽:开安全格攒层 ×2〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2 | (待填) | | | +| 547 | 607 | "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕" | hh_reduce == 2 | (待填) | | | +| 548 | 611 | "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕" | orch._hits_of(sk_zero, "counter_add").size() == 1 | (待填) | | | +| 549 | 615 | "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕" | orch._hits_of(sk_full, "reveal_tiles").size() == 1 | (待填) | | | +| 550 | 616 | "跳格采掘反例:普通开格不计点〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty() | (待填) | | | +| 551 | 623 | "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | +| 552 | 625 | "边陲矿工:边缘格产出 +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() | (待填) | | | +| 553 | 633 | "封印回响:标中计数≥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" | (待填) | | | +| 554 | 642 | "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕" | sm_remove == 1 | (待填) | | | +| 555 | 644 | "封印桩:标中本轮 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" | (待填) | | | +| 556 | 648 | "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1 | (待填) | | | +| 557 | 657 | "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕" | pm_bonus == 2 | (待填) | | | +| 558 | 664 | "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty() | (待填) | | | +| 559 | 668 | "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕" | fw_shield == 0 | (待填) | | | +| 560 | 670 | "见血开窍:踩雷揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1 | (待填) | | | +| 561 | 677 | "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕" | ti_val == 2 | (待填) | | | +| 562 | 682 | "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕" | EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2 | (待填) | | | +| 563 | 685 | "陷阱爆发:全体敌雷伤×倍率直伤(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 | (待填) | | | +| 564 | 689 | "血蜜:踩雷回复 N 生命〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1 | (待填) | | | +| 565 | 692 | "石肤:受伤 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 | (待填) | | | +| 566 | 696 | "背水:生命≤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() | (待填) | | | +| 567 | 701 | "破封一击:挖开带封印危险格 +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() | (待填) | | | +| 568 | 709 | "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕" | orch._hits_of(me_hits, "counter_add").size() == 1 | (待填) | | | +| 569 | 713 | "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕" | me_atk == 4 | (待填) | | | +| 570 | 714 | "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty() | (待填) | | | +| 571 | 717 | "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕" | orch._hits_of(ld_hits, "grant_ap").size() == 1 | (待填) | | | +| 572 | 718 | "一滴不剩反例:上轮有余不发〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty() | (待填) | | | +| 573 | 724 | "收盘:本轮开格≥8→+1 GP〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1 | (待填) | | | +| 574 | 727 | "斩首学者:击杀揭示 1 格〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1 | (待填) | | | +| 575 | 730 | "处刑台:敌血量≤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() | (待填) | | | +| 576 | 734 | "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1 | (待填) | | | +| 577 | 737 | "血税:双向同局账本(踩陷阱−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" | (待填) | | | +| 578 | 742 | "淬毒刃纹(迁入):克制正面(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() | (待填) | | | +| 579 | 746 | "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕" | orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1 | (待填) | | | +| 580 | 750 | "零持有订阅=零命中(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() | (待填) | | | +| 581 | 758 | "特性段空态隐藏(视图空态纪律同特性段)" | not panel._feat_section.visible | (待填) | | | +| 582 | 760 | "特性段持有呈现(逐件行常驻可读——「局内奖励选了什么看不到」缺口承载)" | panel._feat_section.visible and panel._feat_list.get_child_count() == 2 | (待填) | | | +| 583 | 761 | "特性卡文案表值化:desc 行字段直读(零硬编码映射)" | String(load("res://presentation/battle_narrator.gd").feat_card_text({"feat_id": "pain_memory", "display_name": "疼痛记忆", "desc": "触陷阱攒 1 层"})).contains("触陷阱攒 1 层") | (待填) | | | + +## S74 _smoke_layout_cell_formula(行 65–68 · 2 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 584 | 65 | "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 | (待填) | | | +| 585 | 67 | "L%d 格边在 26–46 档内(%dpx)" ↩ % [L, int(cell)] | cell >= 26.0 and cell <= 46.0 | (待填) | | | + +## S75 _smoke_reward_identity_unit(行 83–116 · 10 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 586 | 83 | "roll_board 受理" | reward_economy.roll_board(eligible) | (待填) | | | +| 587 | 84 | "重复 roll_board 被拒(恰一次——double-roll 守卫)" | not reward_economy.roll_board(eligible) | (待填) | | | +| 588 | 95 | "query_identity 投影 = {reward_id, cell_type, tier} 且五型封闭(样本 %s)" ↩ % str(types_seen.keys()) | types_ok | (待填) | | | +| 589 | 97 | "清单外坐标查询 = null(封锁格无身份同口径)" | reward_economy.query_identity(Vector2i(99, 99)) == null | (待填) | | | +| 590 | 101 | "同格重复 grant 幂等(同一快照——受理恰一次)" | JSON.stringify(snap1) == JSON.stringify(snap2) | (待填) | | | +| 591 | 102 | "清单外 grant 拒绝" | reward_economy.grant(Vector2i(99, 99)) == null | (待填) | | | +| 592 | 106 | "清图批尾身份 = clear_blast(clear_default 唯一活行)" | String(cs1.cell_type) == "blast" and String(cs1.reward_id) == "clear_blast" | (待填) | | | +| 593 | 111 | "清图奖励 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]) | (待填) | | | +| 594 | 113 | "grant_clear 同批次重调幂等" | JSON.stringify(cs1) == JSON.stringify(cs2) | (待填) | | | +| 595 | 116 | "映射退役后 grant 拒绝(生命周期闭合)" | reward_economy.grant(eligible[0].cell) == null | (待填) | | | -## S76 _smoke_power_interval(行 240–298 · 5 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) +## S76 _smoke_board_generator(行 135–210 · 24 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 617 | 240 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | (待填) | | | -| 618 | 282 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | (待填) | | | -| 619 | 283 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | (待填) | | | -| 620 | 297 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | (待填) | | | -| 621 | 298 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | (待填) | | | +| 596 | 135 | "BoardGen 行解析 (%d,0)" ↩ % L | not row.is_empty() | (待填) | | | +| 597 | 141 | "BoardGen 精确命中 (2,3)(覆盖批键域补齐)" | int(exact_row.get("node_depth", -1)) == 3 | (待填) | | | +| 598 | 151 | "R1 ceil 能力不失(夹具摘除 (2,3)→向上取整到 4·偏难口径)" | int(ceil_row.get("node_depth", -1)) == 4 | (待填) | | | +| 599 | 153 | "夹具表原样还原(行数与解析回到覆盖批现值)" | _db.board_gen.rows.size() == rows_before and int(_db.resolve_board_gen(2, 3).get("node_depth", -1)) == 3 | (待填) | | | +| 600 | 156 | "L%d 生成产物非空" ↩ % L | b1 != null | (待填) | | | +| 601 | 159 | "L%d clearable(安全格 8 邻连通)" ↩ % L | board_generator.is_clearable(b1) | (待填) | | | +| 602 | 160 | "L%d legality 全过" ↩ % L | board_generator.legality(b1, row) | (待填) | | | +| 603 | 167 | "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" | (待填) | | | +| 604 | 169 | "L%d 生成器产物零变体格(变异布置归战斗初始化第 4 步——生成器管线不布置,2026-09-19 用户拍板棋盘设置顺序)" ↩ % L | b1.variants.is_empty() | (待填) | | | +| 605 | 170 | "L%d 指标集输出降级标记与目标雷数(三分出口表)" ↩ % L | b1.metrics.has("degraded_product") and b1.metrics.has("target_mines") | (待填) | | | +| 606 | 171 | "L%d 降级产物标记 = (accept_mode==accept_best)(%s→%s)" ↩ % [L, mode, str(b1.degraded_product)] | b1.degraded_product == (mode == "accept_best") | (待填) | | | +| 607 | 173 | "L%d 非法产物标记 = (accept_mode==accept_worst)(%s→%s)" ↩ % [L, mode, str(b1.illegal_product)] | b1.illegal_product == (mode == "accept_worst") | (待填) | | | +| 608 | 175 | "L%d 降级/非法产物标记互斥(两出口不可同真)" ↩ % L | not b1.degraded_product or not b1.illegal_product | (待填) | | | +| 609 | 177 | "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) | (待填) | | | +| 610 | 179 | "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) | (待填) | | | +| 611 | 181 | "L%d 非法产物标记 = false" ↩ % L | not b1.illegal_product | (待填) | | | +| 612 | 185 | "L%d 同种子两次生成逐格相同" ↩ % L | _board_dump(b2) == _board_dump(b3) | (待填) | | | +| 613 | 187 | "L%d 异种子产物不同(退化护栏)" ↩ % L | _board_dump(b2) != _board_dump(b4) | (待填) | | | +| 614 | 193 | "L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" ↩ % [L, cap_val] | cap_val >= BoardData.SPAWN_AREA_CELLS | (待填) | | | +| 615 | 195 | "L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" ↩ % [L, int(b1.metrics.r0_count), cap_val] | int(b1.metrics.r0_count) <= cap_val | (待填) | | | +| 616 | 197 | "L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" ↩ % [L, int(b1.metrics.get("r0_cap_rejected", -1))] | int(b1.metrics.get("r0_cap_rejected", -1)) >= 0 | (待填) | | | +| 617 | 199 | "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 | (待填) | | | +| 618 | 207 | "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) | (待填) | | | +| 619 | 209 | "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", "")) != "" | (待填) | | | -## S77 _init(行 17–17 · 1 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S77 _smoke_power_interval(行 240–298 · 5 条 · 源 game/tests/unit/core/board_gen_reward_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 622 | 17 | "数值表加载" | false | (待填) | | | +| 620 | 240 | "区间行配对齐全且 power = floor 中点(配置推导 %d 行:%s)" ↩ % [interval_rows.size(), str(interval_rows.keys())] | anchors_ok and interval_rows.size() >= 1 | (待填) | | | +| 621 | 282 | "24 板全区间行快照 power ∈ [power_min, power_max](含清图槽)" | in_domain | (待填) | | | +| 622 | 283 | "定值行快照 power 恒等表值——区间批零外溢" | fixed_ok | (待填) | | | +| 623 | 297 | "区间行上下阶实际流动(≥2 样本的区间行须 ≥2 个不同 roll 值)" | variance_ok | (待填) | | | +| 624 | 298 | "定值行(lo=hi)roll 恒等表值" | fixed_rows_ok | (待填) | | | -## S78 _test_generate_shape_and_mines(行 42–55 · 5 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S78 _init(行 17–17 · 1 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 623 | 42 | "生成返回棋盘" | false | (待填) | | | -| 624 | 47 | "棋盘尺寸与 board_gen 表一致(%dx%d)" ↩ % [want_w, want_h] | b.width == want_w and b.height == want_h | (待填) | | | -| 625 | 49 | "雷数非零且不超过总格数" | b.mines.size() > 0 and b.mines.size() < b.width * b.height | (待填) | | | -| 626 | 54 | "全部危险格在界内" | all_in_bounds | (待填) | | | -| 627 | 55 | "生成期指标集已落盘(metrics 非空)" | not b.metrics.is_empty() | (待填) | | | +| 625 | 17 | "数值表加载" | false | (待填) | | | -## S79 _test_target_mine_count_single_source(行 75–83 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S79 _test_generate_shape_and_mines(行 42–55 · 5 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 628 | 75 | "target_mine_count 与原式同参同值(%d 档对拍全等)〔wd14〕" ↩ % checked | checked > 0 and bad == 0 | (待填) | | | -| 629 | 78 | "生成返回棋盘(雷数单点段)" | false | (待填) | | | -| 630 | 81 | "产物断言读数 = target_mine_count 同源(metrics.target_mines)〔wd14〕" | int(b.metrics.get("target_mines", -1)) == BoardGeneratorScript.target_mine_count({ "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density)}) | (待填) | | | +| 626 | 42 | "生成返回棋盘" | false | (待填) | | | +| 627 | 47 | "棋盘尺寸与 board_gen 表一致(%dx%d)" ↩ % [want_w, want_h] | b.width == want_w and b.height == want_h | (待填) | | | +| 628 | 49 | "雷数非零且不超过总格数" | b.mines.size() > 0 and b.mines.size() < b.width * b.height | (待填) | | | +| 629 | 54 | "全部危险格在界内" | all_in_bounds | (待填) | | | +| 630 | 55 | "生成期指标集已落盘(metrics 非空)" | not b.metrics.is_empty() | (待填) | | | -## S80 _test_digits_equal_neighbor_mine_count(行 89–103 · 2 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S80 _test_target_mine_count_single_source(行 75–83 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 631 | 89 | "生成返回棋盘(数字段)" | false | (待填) | | | -| 632 | 103 | "每个安全格的数字恒等于八邻雷数(全盘逐格复算,错 %d 格)" ↩ % bad | bad == 0 | (待填) | | | +| 631 | 75 | "target_mine_count 与原式同参同值(%d 档对拍全等)〔wd14〕" ↩ % checked | checked > 0 and bad == 0 | (待填) | | | +| 632 | 78 | "生成返回棋盘(雷数单点段)" | false | (待填) | | | +| 633 | 81 | "产物断言读数 = target_mine_count 同源(metrics.target_mines)〔wd14〕" | int(b.metrics.get("target_mines", -1)) == BoardGeneratorScript.target_mine_count({ "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density)}) | (待填) | | | -## S81 _test_same_seed_bit_identical(行 110–113 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S81 _test_digits_equal_neighbor_mine_count(行 89–103 · 2 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 633 | 110 | "生成返回棋盘(确定性段)" | false | (待填) | | | -| 634 | 112 | "同种子生成的雷集逐位一致" | str(a.mines) == str(b.mines) | (待填) | | | -| 635 | 113 | "同种子生成的数字集逐位一致" | str(a.digits) == str(b.digits) | (待填) | | | +| 634 | 89 | "生成返回棋盘(数字段)" | false | (待填) | | | +| 635 | 103 | "每个安全格的数字恒等于八邻雷数(全盘逐格复算,错 %d 格)" ↩ % bad | bad == 0 | (待填) | | | -## S82 _test_clearable_and_inferability_selfconsistent(行 119–126 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) +## S82 _test_same_seed_bit_identical(行 110–113 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 636 | 119 | "生成返回棋盘(可推理段)" | false | (待填) | | | -| 637 | 122 | "可推理性度量返回非空指标集" | not m.is_empty() | (待填) | | | -| 638 | 125 | "清图判定与安全格总数同在(安全格 %d 个,可清=%s)" ↩ % [safe_total, str(clearable)] | safe_total > 0 | (待填) | | | +| 636 | 110 | "生成返回棋盘(确定性段)" | false | (待填) | | | +| 637 | 112 | "同种子生成的雷集逐位一致" | str(a.mines) == str(b.mines) | (待填) | | | +| 638 | 113 | "同种子生成的数字集逐位一致" | str(a.digits) == str(b.digits) | (待填) | | | -## S83 _test_all_links_valid(行 59–59 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S83 _test_clearable_and_inferability_selfconsistent(行 119–126 · 3 条 · 源 game/tests/unit/core/board_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 639 | 59 | "全对齿的技能池零错(正向面——实际报错 %s)" ↩ % str(_cats(errs)) | errs.is_empty() | (待填) | | | +| 639 | 119 | "生成返回棋盘(可推理段)" | false | (待填) | | | +| 640 | 122 | "可推理性度量返回非空指标集" | not m.is_empty() | (待填) | | | +| 641 | 125 | "清图判定与安全格总数同在(安全格 %d 个,可清=%s)" ↩ % [safe_total, str(clearable)] | safe_total > 0 | (待填) | | | -## S84 _test_dangling_skill_id(行 70–72 · 2 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S84 _test_all_links_valid(行 59–59 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 640 | 70 | "池内悬空 skill_id 被报(跨表护栏不静默放过)" | not errs.is_empty() | (待填) | | | -| 641 | 71 | "悬空项的错误类别可定位(category 非空)" | not errs.is_empty() and String((errs[0] as Dictionary).get("category", "")) != "" | (待填) | | | +| 642 | 59 | "全对齿的技能池零错(正向面——实际报错 %s)" ↩ % str(_cats(errs)) | errs.is_empty() | (待填) | | | -## S85 _test_cast_side_mismatch(行 84–84 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) +## S85 _test_dangling_skill_id(行 70–72 · 2 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 642 | 84 | "buff 挂主角侧状态被报(施放类 side 守卫)" | _cats(errs).has("cast_side") | (待填) | | | +| 643 | 70 | "池内悬空 skill_id 被报(跨表护栏不静默放过)" | not errs.is_empty() | (待填) | | | +| 644 | 71 | "悬空项的错误类别可定位(category 非空)" | not errs.is_empty() and String((errs[0] as Dictionary).get("category", "")) != "" | (待填) | | | -## S86 _test_band_hit(行 58–62 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S86 _test_cast_side_mismatch(行 84–84 · 1 条 · 源 game/tests/unit/core/config_db_cross_validator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 643 | 58 | "带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕" | hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -| 644 | 61 | "精确落带:请求深度 5 命中带 5〔fv1〕" | exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 645 | 84 | "buff 挂主角侧状态被报(施放类 side 守卫)" | _cats(errs).has("cast_side") | (待填) | | | -## S87 _test_rowset_order_preserved(行 67–72 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S87 _test_band_hit(行 58–62 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 645 | 67 | "行集返回同带全部行(2 行)〔fv2〕" | rows.size() == 2 | (待填) | | | -| 646 | 71 | "行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕" | ids == ["goblin", "bugbear"] | (待填) | | | +| 646 | 58 | "带命中取偏难一侧:请求深度 4 命中带 5(逐字不回带 0)〔fv1〕" | hit.size() == 1 and String((hit[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 647 | 61 | "精确落带:请求深度 5 命中带 5〔fv1〕" | exact.size() == 1 and String((exact[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -## S88 _test_top_band_clamp(行 78–87 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S88 _test_rowset_order_preserved(行 67–72 · 2 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 647 | 78 | "越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕" | rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | -| 648 | 84 | "越顶带发 warning resolve_depth_clamp〔fv3〕" | clamp_hit | (待填) | | | -| 649 | 87 | "同键重复请求警告去重(四元组注册表)〔fv3〕" | _diags.size() == warn_before | (待填) | | | +| 648 | 67 | "行集返回同带全部行(2 行)〔fv2〕" | rows.size() == 2 | (待填) | | | +| 649 | 71 | "行集按表文件行序返回(goblin 先于 bugbear——行序即契约)〔fv2〕" | ids == ["goblin", "bugbear"] | (待填) | | | -## S89 _test_row_kind_routing(行 93–95 · 1 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S89 _test_top_band_clamp(行 78–87 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 650 | 93 | "行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕" | battle_rows.size() == 2 and boss_rows.size() == 1 and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief" | (待填) | | | +| 650 | 78 | "越顶带 clamp 取顶带行集(battle 顶带 5)〔fv3〕" | rows.size() == 1 and String((rows[0] as Dictionary).get("enemy_id", "")) == "hobgoblin" | (待填) | | | +| 651 | 84 | "越顶带发 warning resolve_depth_clamp〔fv3〕" | clamp_hit | (待填) | | | +| 652 | 87 | "同键重复请求警告去重(四元组注册表)〔fv3〕" | _diags.size() == warn_before | (待填) | | | -## S90 _test_request_oob(行 101–107 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S90 _test_row_kind_routing(行 93–95 · 1 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 651 | 101 | "关卡序号 <1 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -| 652 | 104 | "节点深度 <0 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -| 653 | 107 | "row_kind 非法 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 653 | 93 | "行类路由:同请求深度 battle 与 boss 命中不同键域〔fv4〕" | battle_rows.size() == 2 and boss_rows.size() == 1 and String((boss_rows[0] as Dictionary).get("enemy_id", "")) == "ogre_chief" | (待填) | | | -## S91 _test_difficulty_clamp(行 119–122 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) +## S91 _test_request_oob(行 101–107 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 654 | 119 | "难度乘数越域被 clamp:hp 99 → 上限 10" | absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001 | (待填) | | | -| 655 | 120 | "难度乘数越域被 clamp:atk 0.01 → 下限 0.1" | absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001 | (待填) | | | -| 656 | 121 | "未登记坐标返回空修饰(零值不伪装成 ×1)" | _resolver.resolve_difficulty_modifiers(3, 3).is_empty() | (待填) | | | +| 654 | 101 | "关卡序号 <1 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 655 | 104 | "节点深度 <0 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | +| 656 | 107 | "row_kind 非法 → fatal request_oob〔fv5〕" | _has_fatal_oob() | (待填) | | | -## S92 _test_field_in_table(行 106–114 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S92 _test_difficulty_clamp(行 119–122 · 3 条 · 源 game/tests/unit/core/config_db_resolver_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 657 | 106 | "N 落表:level.tres 读数 2(实际 %d)〔fv6〕" ↩ % n_in_table | n_in_table == 2 | (待填) | | | -| 658 | 110 | "N=1 合法:validate 零错〔fv6〕" | t.validate().is_empty() | (待填) | | | -| 659 | 112 | "N 越域 0:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | -| 660 | 114 | "N 越域 -1:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | +| 657 | 119 | "难度乘数越域被 clamp:hp 99 → 上限 10" | absf(float(m.get("hp_mult", 0.0)) - 10.0) < 0.001 | (待填) | | | +| 658 | 120 | "难度乘数越域被 clamp:atk 0.01 → 下限 0.1" | absf(float(m.get("atk_mult", 0.0)) - 0.1) < 0.001 | (待填) | | | +| 659 | 121 | "未登记坐标返回空修饰(零值不伪装成 ×1)" | _resolver.resolve_difficulty_modifiers(3, 3).is_empty() | (待填) | | | -## S93 _test_layer_distance(行 131–133 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S93 _test_field_in_table(行 106–114 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 661 | 131 | "层距 N 内可见:锚层 1 取节点 4(层距 2)〔fv1〕" | vis.has(4) | (待填) | | | -| 662 | 132 | "层距 N+1 不可见:锚层 1 排节点 5(层距 3)〔fv1〕" | not vis.has(5) | (待填) | | | -| 663 | 133 | "层距 N+2 不可见:锚层 1 排节点 6(层距 4)〔fv1〕" | not vis.has(6) | (待填) | | | +| 660 | 106 | "N 落表:level.tres 读数 2(实际 %d)〔fv6〕" ↩ % n_in_table | n_in_table == 2 | (待填) | | | +| 661 | 110 | "N=1 合法:validate 零错〔fv6〕" | t.validate().is_empty() | (待填) | | | +| 662 | 112 | "N 越域 0:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | +| 663 | 114 | "N 越域 -1:validate 判 domain 错〔fv6〕" | _has_n_domain_err(t.validate()) | (待填) | | | -## S94 _test_candidate_exempt(行 142–143 · 2 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S94 _test_layer_distance(行 131–133 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 664 | 142 | "候选恒显:跨层跳边候选(层距 N+1)仍在可见集〔fv4〕" | vis.has(3) | (待填) | | | -| 665 | 143 | "候选豁免不改层规则:同锚点非候选节点 2(层距 4)不可见〔fv4〕" | not vis.has(2) | (待填) | | | +| 664 | 131 | "层距 N 内可见:锚层 1 取节点 4(层距 2)〔fv1〕" | vis.has(4) | (待填) | | | +| 665 | 132 | "层距 N+1 不可见:锚层 1 排节点 5(层距 3)〔fv1〕" | not vis.has(5) | (待填) | | | +| 666 | 133 | "层距 N+2 不可见:锚层 1 排节点 6(层距 4)〔fv1〕" | not vis.has(6) | (待填) | | | -## S95 _test_boss_exempt(行 152–155 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S95 _test_candidate_exempt(行 142–143 · 2 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 666 | 152 | "入口停点锚层 0:层距 N 内(节点 2)可见〔fv1〕" | vis.has(2) | (待填) | | | -| 667 | 153 | "入口停点锚层 0:层距 N+2(节点 4)不可见〔fv1〕" | not vis.has(4) | (待填) | | | -| 668 | 154 | "Boss 恒显:层距 6 的末位 Boss 仍在可见集〔fv3〕" | vis.has(7) | (待填) | | | -| 669 | 155 | "Boss 豁免不扩散:同层距的非 Boss 节点 6 不在可见集〔fv3〕" | not vis.has(6) | (待填) | | | +| 667 | 142 | "候选恒显:跨层跳边候选(层距 N+1)仍在可见集〔fv4〕" | vis.has(3) | (待填) | | | +| 668 | 143 | "候选豁免不改层规则:同锚点非候选节点 2(层距 4)不可见〔fv4〕" | not vis.has(2) | (待填) | | | -## S96 _test_single_circle(行 165–167 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S96 _test_boss_exempt(行 152–155 · 4 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 670 | 165 | "单圈回雾:已过远端节点 0(层距 5)不因 visited 豁免〔fv7〕" | not vis.has(0) | (待填) | | | -| 671 | 166 | "单圈回雾:已过中距节点 2(层距 4)不可见〔fv7〕" | not vis.has(2) | (待填) | | | -| 672 | 167 | "单圈只按锚层取窗:已过近端节点 4(层距 2)仍可见〔fv7〕" | vis.has(4) | (待填) | | | +| 669 | 152 | "入口停点锚层 0:层距 N 内(节点 2)可见〔fv1〕" | vis.has(2) | (待填) | | | +| 670 | 153 | "入口停点锚层 0:层距 N+2(节点 4)不可见〔fv1〕" | not vis.has(4) | (待填) | | | +| 671 | 154 | "Boss 恒显:层距 6 的末位 Boss 仍在可见集〔fv3〕" | vis.has(7) | (待填) | | | +| 672 | 155 | "Boss 豁免不扩散:同层距的非 Boss 节点 6 不在可见集〔fv3〕" | not vis.has(6) | (待填) | | | -## S97 _test_single_chain_entry(行 176–179 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S97 _test_single_circle(行 165–167 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 673 | 176 | "单链退化形态:入口锚层 0,句柄 2 可见〔fv1〕" | vis.has(2) | (待填) | | | -| 674 | 177 | "单链退化形态:层距 N+1(句柄 3)不可见〔fv1〕" | not vis.has(3) | (待填) | | | -| 675 | 178 | "单链编号空间 = 图句柄 id:Boss 以句柄 6 入集(链上层距 4 的战斗节点 4 入雾)〔fv1〕" | vis.has(6) and not vis.has(4) | (待填) | | | +| 673 | 165 | "单圈回雾:已过远端节点 0(层距 5)不因 visited 豁免〔fv7〕" | not vis.has(0) | (待填) | | | +| 674 | 166 | "单圈回雾:已过中距节点 2(层距 4)不可见〔fv7〕" | not vis.has(2) | (待填) | | | +| 675 | 167 | "单圈只按锚层取窗:已过近端节点 4(层距 2)仍可见〔fv7〕" | vis.has(4) | (待填) | | | -## S98 _test_n_from_table(行 188–196 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S98 _test_single_chain_entry(行 176–179 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 676 | 188 | "N 读表:N=1 时节点 4(层距 2)出窗〔fv6〕" | not r.type_visible_ids().has(4) | (待填) | | | -| 677 | 190 | "N 读表:N=3 时节点 5(层距 3)入窗〔fv6〕" | r.type_visible_ids().has(5) | (待填) | | | -| 678 | 196 | "N 读表:N ≥ 全图层高 → 全部非豁免节点可见〔fv6〕" | all_in | (待填) | | | +| 676 | 176 | "单链退化形态:入口锚层 0,句柄 2 可见〔fv1〕" | vis.has(2) | (待填) | | | +| 677 | 177 | "单链退化形态:层距 N+1(句柄 3)不可见〔fv1〕" | not vis.has(3) | (待填) | | | +| 678 | 178 | "单链编号空间 = 图句柄 id:Boss 以句柄 6 入集(链上层距 4 的战斗节点 4 入雾)〔fv1〕" | vis.has(6) and not vis.has(4) | (待填) | | | -## S99 _test_resume_rebuild(行 234–235 · 1 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) +## S99 _test_n_from_table(行 188–196 · 3 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 679 | 234 | "续玩重建同值:resume 路径与同停点直入 type_visible_ids() 逐位一致〔fv7〕" | resumed.type_visible_ids() == direct.type_visible_ids() | (待填) | | | +| 679 | 188 | "N 读表:N=1 时节点 4(层距 2)出窗〔fv6〕" | not r.type_visible_ids().has(4) | (待填) | | | +| 680 | 190 | "N 读表:N=3 时节点 5(层距 3)入窗〔fv6〕" | r.type_visible_ids().has(5) | (待填) | | | +| 681 | 196 | "N 读表:N ≥ 全图层高 → 全部非豁免节点可见〔fv6〕" | all_in | (待填) | | | -## S100 _smoke_table_migration(行 262–269 · 4 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S100 _test_resume_rebuild(行 234–235 · 1 条 · 源 game/tests/unit/core/level_map_type_vis_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 680 | 262 | "shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕" | _db.shop.relic_base_price == 15.0 | (待填) | | | -| 681 | 263 | "shop.spend_discount 表值 == 迁移前字面量(0.8)〔wd11〕" | _db.shop.spend_discount == 0.8 | (待填) | | | -| 682 | 264 | "hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕" | _db.hero.display_name == "探险者" | (待填) | | | -| 683 | 268 | "spawn 后英雄单位 display_name 读表同值(探险者)〔wd16〕" | spawned and String(units.hero_unit().get("display_name", "")) == String(_db.hero.display_name) | (待填) | | | +| 682 | 234 | "续玩重建同值:resume 路径与同停点直入 type_visible_ids() 逐位一致〔fv7〕" | resumed.type_visible_ids() == direct.type_visible_ids() | (待填) | | | -## S101 _smoke_generated_coverage(行 283–320 · 6 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S101 _smoke_table_migration(行 262–269 · 4 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 684 | 283 | "L%d 参数档缺行(覆盖断言无源可扫)〔v11〕" ↩ % L | false | (待填) | | | -| 685 | 290 | "L%d 种子 %d 生成图为空(覆盖扫描面空转)〔v11〕" ↩ % [L, seed_val] | false | (待填) | | | -| 686 | 317 | "生成图覆盖扫描面非空(三关参数档×种子 %d–%d 共 %d 个节点读数)〔v11〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, scanned] | scanned > 0 | (待填) | | | -| 687 | 318 | "生成图每个 battle/boss 节点深度的敌侧解析落带 ≥ 请求深度(band_of 非 -1·顶 clamp 消解禁)〔v11〕" | enemy_ok | (待填) | | | -| 688 | 319 | "生成图每个节点深度在 board_gen 精确命中(非空且 node_depth 等值)〔v11〕" | board_ok | (待填) | | | -| 689 | 320 | "多终点 Boss 各终点解析带键两两互异(L2/L3×20 种子)〔v11〕" | boss_band_ok | (待填) | | | +| 683 | 262 | "shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕" | _db.shop.relic_base_price == 15.0 | (待填) | | | +| 684 | 263 | "shop.spend_discount 表值 == 迁移前字面量(0.8)〔wd11〕" | _db.shop.spend_discount == 0.8 | (待填) | | | +| 685 | 264 | "hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕" | _db.hero.display_name == "探险者" | (待填) | | | +| 686 | 268 | "spawn 后英雄单位 display_name 读表同值(探险者)〔wd16〕" | spawned and String(units.hero_unit().get("display_name", "")) == String(_db.hero.display_name) | (待填) | | | -## S102 _smoke_level(行 331–387 · 17 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S102 _smoke_generated_coverage(行 283–320 · 6 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 690 | 331 | "L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes1.size()] | not nodes1.is_empty() | (待填) | | | -| 691 | 334 | "关卡·满配(atk+200/hp+100)完成(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | (待填) | | | -| 692 | 342 | "L1 关卡完成路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)" | _path_in_graph(nodes1, win.visited_ids) | (待填) | | | -| 693 | 343 | "L1 关卡完成路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" ↩ % [win.visited_ids.size(), path_battles, int(win.battles)] | path_battles == int(win.battles) and path_battles >= 2 | (待填) | | | -| 694 | 353 | "L1 终点 Boss 深度 = 参数档层高 %d 且关卡完成路径末节点落该深度(实测终点深度 %d·末句柄 %d)" ↩ % [int(params1.get("layers", -1)), boss_depth1, last_id] | boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1 | (待填) | | | -| 695 | 355 | "L1 终点恰一个(大岔口自 L2 起的图侧投影)" | _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1 | (待填) | | | -| 696 | 356 | "L1 生成图含岔口(出度 ≥2 节点 %d 处)" ↩ % _fork_ids(nodes1).size() | _fork_ids(nodes1).size() >= 1 | (待填) | | | -| 697 | 357 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | (待填) | | | -| 698 | 363 | "恢复点公式无源(现行图入口后一位无可直达的恢复候选)" | false | (待填) | | | -| 699 | 366 | "恢复点公式·缺口充分档:回复 = floor(生效max_hp×%d%%) = %d(生效 max %d,实测 %d)" ↩ % [pct1, expect_pct, int(rest_wide["max"]), int(rest_wide["healed"])] | int(rest_wide["healed"]) == expect_pct and expect_pct > 0 | (待填) | | | -| 700 | 370 | "恢复点公式缺口封顶档无源(同图二次驱动未达恢复候选)" | false | (待填) | | | -| 701 | 372 | "恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" ↩ % [int(rest_narrow["healed"]), expect_pct] | int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2 | (待填) | | | -| 702 | 376 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | (待填) | | | -| 703 | 377 | "败北节点记录在域内(节点 %d,图节点数 %d)" ↩ % [lose.defeat_node_index, nodes1.size()] | lose.defeat_node_index >= 0 and lose.defeat_node_index < nodes1.size() | (待填) | | | -| 704 | 382 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | (待填) | | | -| 705 | 383 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | (待填) | | | -| 706 | 385 | "模拟后 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) | (待填) | | | +| 687 | 283 | "L%d 参数档缺行(覆盖断言无源可扫)〔v11〕" ↩ % L | false | (待填) | | | +| 688 | 290 | "L%d 种子 %d 生成图为空(覆盖扫描面空转)〔v11〕" ↩ % [L, seed_val] | false | (待填) | | | +| 689 | 317 | "生成图覆盖扫描面非空(三关参数档×种子 %d–%d 共 %d 个节点读数)〔v11〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, scanned] | scanned > 0 | (待填) | | | +| 690 | 318 | "生成图每个 battle/boss 节点深度的敌侧解析落带 ≥ 请求深度(band_of 非 -1·顶 clamp 消解禁)〔v11〕" | enemy_ok | (待填) | | | +| 691 | 319 | "生成图每个节点深度在 board_gen 精确命中(非空且 node_depth 等值)〔v11〕" | board_ok | (待填) | | | +| 692 | 320 | "多终点 Boss 各终点解析带键两两互异(L2/L3×20 种子)〔v11〕" | boss_band_ok | (待填) | | | -## S103 _smoke_level_graph(行 442–762 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) +## S103 _smoke_level(行 331–387 · 17 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 707 | 442 | "LevelTable 行级校验零错误(图合法性移交 validate_graph)" | _db.level.validate().is_empty() | (待填) | | | -| 708 | 446 | "L2 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes2.size()] | not nodes2.is_empty() | (待填) | | | -| 709 | 449 | "L2 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes2.size(), int(params2.get("layers", -1)), _nodes_by_id(nodes2).size()] | nodes2.size() >= int(params2.get("layers", -1)) + 1 and _nodes_by_id(nodes2).size() == nodes2.size() | (待填) | | | -| 710 | 451 | "L2 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount["battle"], tcount["boss"], tcount["reward"], tcount["rest"], tcount["shop"], nodes2.size()] | int(tcount["battle"]) + int(tcount["boss"]) + int(tcount["reward"]) + int(tcount["rest"]) + int(tcount["shop"]) == nodes2.size() | (待填) | | | -| 711 | 453 | "L2 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount["battle"], int(params2.get("battle_floor", -1)), tcount["boss"], int(params2.get("exit_count", -1))] | int(tcount["battle"]) >= int(params2.get("battle_floor", -1)) and int(tcount["boss"]) == int(params2.get("exit_count", -1)) | (待填) | | | -| 712 | 455 | "L2 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount["rest"], tcount["reward"], tcount["shop"]] | int(tcount["rest"]) >= 1 and int(tcount["reward"]) >= 1 and int(tcount["shop"]) >= 1 | (待填) | | | -| 713 | 457 | "L2 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch | branch >= 1 | (待填) | | | -| 714 | 467 | "图校验正例:两岔合流小图零错误〔v1〕" | LT.validate_graph(good).is_empty() | (待填) | | | -| 715 | 470 | "图校验拒绝:句柄重复(图内唯一句柄)〔v1〕" | _has_err_category(LT.validate_graph(dup), "dup_key") | (待填) | | | -| 716 | 473 | "图校验正例:多入口合法(入口唯一判据随模板池退役)〔v1〕" | LT.validate_graph(dual).is_empty() | (待填) | | | -| 717 | 476 | "图校验拒绝:Boss 带出边(终点不可外出)〔v1〕" | _has_err_category(LT.validate_graph(boss_out), "structural") | (待填) | | | -| 718 | 479 | "图校验拒绝:回边 1→0(目标深度须严格大于源)〔v1〕" | _has_err_category(LT.validate_graph(cyc), "structural") | (待填) | | | -| 719 | 482 | "图校验拒绝:出边指向不存在句柄(悬空致命)〔v1〕" | _has_err_category(LT.validate_graph(dang), "dangling_next") | (待填) | | | -| 720 | 488 | "图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕" | _has_err_category(LT.validate_graph(orphan), "structural") | (待填) | | | -| 721 | 491 | "L2·满配 min 路径完成(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | (待填) | | | -| 722 | 496 | "L2 min 路径逐跳为图出边·末节点为 Boss(实测路径 %s·末句柄型别 %s)" ↩ % [str(win_min.visited_ids), n2_last_type] | _path_in_graph(nodes2, win_min.visited_ids) and n2_last_type == "boss" | (待填) | | | -| 723 | 498 | "L2 min 路径终点深度 = 参数档层高 %d(行进读数 %d——终点落最长路径步数)" ↩ % [int(params2.get("layers", -1)), int(win_min.boss_depth)] | int(win_min.boss_depth) == int(params2.get("layers", -1)) | (待填) | | | -| 724 | 500 | "L2 min 路径穿商店恰 1 家(路径句柄 %s 型别计数 = 1——必经恰一店的行进面)" ↩ % str(win_min.visited_ids) | _path_shop_count(nodes2, win_min.visited_ids) == 1 | (待填) | | | -| 725 | 502 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | (待填) | | | -| 726 | 504 | "L2·满配 max 路径(候选取最大句柄)完成(实测:%s·路径 %s)" ↩ % [win_max.result, str(win_max.visited_ids)] | win_max.result == "victory" | (待填) | | | -| 727 | 509 | "L2 max 路径逐跳为图出边·末节点为 Boss·穿商店恰 1 家" | _path_in_graph(nodes2, win_max.visited_ids) and n2_last_max_type == "boss" and _path_shop_count(nodes2, win_max.visited_ids) == 1 | (待填) | | | -| 728 | 512 | "L2 max 与 min 路径分岔(方向轴生效——两路终点句柄 %d / %d 不同)" ↩ % [n2_last, n2_last_max] | n2_last != n2_last_max and win_max.visited_ids != win_min.visited_ids | (待填) | | | -| 729 | 519 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;图 = 参数档 × 种子 %d)" ↩ % ACTIVE_GRAPH_SEED | runner.start_level(2, ACTIVE_GRAPH_SEED) | (待填) | | | -| 730 | 526 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | (待填) | | | -| 731 | 529 | "runner 消费面图 = 生成面图(节点数 %d/%d 且句柄集逐位相同)" ↩ % [rnodes.size(), nodes2.size()] | rnodes.size() == nodes2.size() and _ids_of_type(rnodes, "battle").size() + _ids_of_type(rnodes, "boss").size() + _ids_of_type(rnodes, "reward").size() \\ + _ids_of_type(rnodes, "rest").size() + _ids_of_type(rnodes, "shop").size() == nodes2.size() and _nodes_by_id(rnodes) == _nodes_by_id(nodes2) | (待填) | | | -| 732 | 537 | "入口选路口候选非空且全在图内(候选 %s·类型 %s)" ↩ % [str(cand), str(cand_types)] | not cand.is_empty() and not cand_types.has("<不在图>") | (待填) | | | -| 733 | 538 | "候选下一跳类型两两互异(岔口去路类型不重复契约——实测 %s)" ↩ % str(cand_types) | cand_types.size() == _uniq(cand_types).size() | (待填) | | | -| 734 | 545 | "候选外句柄 %d 拒绝且仍待选(拒绝语义)" ↩ % outside_id | outside_id >= 0 and not runner.pick_next_node(outside_id) and runner.awaiting_choice() | (待填) | | | -| 735 | 555 | "入口岔口含奖励位与战斗位(句柄 %d/%d——候选型别可定位)" ↩ % [reward_cand, battle_cand] | reward_cand >= 0 and battle_cand >= 0 | (待填) | | | -| 736 | 556 | "选奖励候选 %d 受理" ↩ % reward_cand | runner.pick_next_node(reward_cand) | (待填) | | | -| 737 | 557 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | (待填) | | | -| 738 | 559 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = 该点图出边 %s" ↩ % str(reward_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(reward_outs) | (待填) | | | -| 739 | 566 | "选下一跳战斗候选 %d 开战" ↩ % next_battle | next_battle >= 0 and runner.pick_next_node(next_battle) and runner.current_battle() != null | (待填) | | | -| 740 | 591 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | (待填) | | | -| 741 | 592 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | (待填) | | | -| 742 | 593 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | (待填) | | | -| 743 | 597 | "跨表覆盖扫描面非空:L2 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov2["nodes"]) | int(cov2["nodes"]) >= 1 | (待填) | | | -| 744 | 598 | "L2 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov2["enemy_bad"]) | int(cov2["enemy_bad"]) == 0 | (待填) | | | -| 745 | 599 | "L2 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov2["board_bad"]) | int(cov2["board_bad"]) == 0 | (待填) | | | -| 746 | 607 | "巨蜘蛛首遇带(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") | (待填) | | | -| 747 | 617 | "L2 全部终点深度 %s 的 Boss 行集均 = {drider} 恰一行且首行魔法甲(护甲类型重配批:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % str(l2_terms) | l2_boss_ok | (待填) | | | -| 748 | 624 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | (待填) | | | -| 749 | 628 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | (待填) | | | -| 750 | 629 | "首批混编新行 (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 | (待填) | | | -| 751 | 636 | "L3 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes3.size()] | not nodes3.is_empty() | (待填) | | | -| 752 | 639 | "L3 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes3.size(), int(params3.get("layers", -1)), _nodes_by_id(nodes3).size()] | nodes3.size() >= int(params3.get("layers", -1)) + 1 and _nodes_by_id(nodes3).size() == nodes3.size() | (待填) | | | -| 753 | 641 | "L3 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount3["battle"], tcount3["boss"], tcount3["reward"], tcount3["rest"], tcount3["shop"], nodes3.size()] | int(tcount3["battle"]) + int(tcount3["boss"]) + int(tcount3["reward"]) + int(tcount3["rest"]) + int(tcount3["shop"]) == nodes3.size() | (待填) | | | -| 754 | 643 | "L3 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount3["battle"], int(params3.get("battle_floor", -1)), tcount3["boss"], int(params3.get("exit_count", -1))] | int(tcount3["battle"]) >= int(params3.get("battle_floor", -1)) and int(tcount3["boss"]) == int(params3.get("exit_count", -1)) | (待填) | | | -| 755 | 645 | "L3 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount3["rest"], tcount3["reward"], tcount3["shop"]] | int(tcount3["rest"]) >= 1 and int(tcount3["reward"]) >= 1 and int(tcount3["shop"]) >= 1 | (待填) | | | -| 756 | 647 | "L3 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch3 | branch3 >= 1 | (待填) | | | -| 757 | 648 | "L3 入口数 %d = 参数档 entry_count · 终点数 %d = exit_count ≥2(多终点大岔口自 L2 起)" ↩ % [_entry_ids(nodes3).size(), _terminal_ids(nodes3).size()] | _entry_ids(nodes3).size() == int(params3.get("entry_count", -1)) and _terminal_ids(nodes3).size() == int(params3.get("exit_count", -1)) and _terminal_ids(nodes3).size() >= 2 | (待填) | | | -| 758 | 652 | "跨表覆盖扫描面非空:L3 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov3["nodes"]) | int(cov3["nodes"]) >= 1 | (待填) | | | -| 759 | 653 | "L3 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov3["enemy_bad"]) | int(cov3["enemy_bad"]) == 0 | (待填) | | | -| 760 | 654 | "L3 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov3["board_bad"]) | int(cov3["board_bad"]) == 0 | (待填) | | | -| 761 | 661 | "灰矮人首遇带(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") | (待填) | | | -| 762 | 672 | "L3 全部终点深度 %s 的 Boss 行集均 = {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % str(l3_terms) | l3_boss_ok | (待填) | | | -| 763 | 695 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | (待填) | | | -| 764 | 696 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | (待填) | | | -| 765 | 706 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | (待填) | | | -| 766 | 708 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | (待填) | | | -| 767 | 709 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,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" | (待填) | | | -| 768 | 710 | "混编各行行值独立投影(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 | (待填) | | | -| 769 | 714 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | (待填) | | | -| 770 | 719 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | | -| 771 | 726 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | | -| 772 | 732 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | | -| 773 | 737 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | | -| 774 | 739 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | | -| 775 | 761 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | | -| 776 | 762 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | | - -## S104 _smoke_gp_wallet(行 799–835 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 777 | 799 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | | -| 778 | 809 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | | -| 779 | 812 | "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 | (待填) | | | -| 780 | 817 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | | -| 781 | 819 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | | -| 782 | 820 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | | -| 783 | 827 | "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)}) | (待填) | | | -| 784 | 832 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | | -| 785 | 835 | "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 | (待填) | | | - -## S105 _smoke_gp_escrow(行 873–937 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 786 | 873 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | | -| 787 | 886 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | | -| 788 | 891 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | | -| 789 | 892 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | | -| 790 | 894 | "格面兑付计入 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 | (待填) | | | -| 791 | 900 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | | -| 792 | 904 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | | -| 793 | 909 | "破财压制 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 | (待填) | | | -| 794 | 919 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | | -| 795 | 931 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | | -| 796 | 936 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | | -| 797 | 937 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | | - -## S106 _smoke_shop_room(行 1003–1089 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 798 | 1003 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | | -| 799 | 1016 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | | -| 800 | 1020 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | | -| 801 | 1022 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | | -| 802 | 1024 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | | -| 803 | 1026 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | | -| 804 | 1029 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | | -| 805 | 1031 | "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 | (待填) | | | -| 806 | 1034 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | | -| 807 | 1035 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | | -| 808 | 1037 | "统一冒险加算区 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 | (待填) | | | -| 809 | 1039 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | | -| 810 | 1046 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | | -| 811 | 1047 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | | -| 812 | 1048 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | | -| 813 | 1049 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | | -| 814 | 1051 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | | -| 815 | 1052 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | | -| 816 | 1067 | "余额不足购买被拒(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 | (待填) | | | -| 817 | 1069 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | | -| 818 | 1072 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | | -| 819 | 1073 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | | -| 820 | 1077 | "战斗开始参数 = 基础表值 + 加算区(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 | (待填) | | | -| 821 | 1088 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | | -| 822 | 1089 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | | - -## S107 _smoke_shop_graph(行 1111–1168 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 823 | 1111 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | | -| 824 | 1116 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | | -| 825 | 1142 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | | -| 826 | 1144 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | | -| 827 | 1145 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | | -| 828 | 1146 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | | -| 829 | 1152 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | | -| 830 | 1164 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | | -| 831 | 1167 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | | - -## S108 _smoke_multi_terminal_settlement(行 1176–1223 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 832 | 1176 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | | -| 833 | 1188 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | | -| 834 | 1192 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | | -| 835 | 1199 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | | -| 836 | 1221 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | | - -## S109 _smoke_shop_pool(行 1258–1392 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 837 | 1258 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | | -| 838 | 1266 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | | -| 839 | 1272 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | | -| 840 | 1273 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | | -| 841 | 1275 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | | -| 842 | 1276 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | | -| 843 | 1277 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | | -| 844 | 1285 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | | -| 845 | 1295 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | | -| 846 | 1301 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | | -| 847 | 1308 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | | -| 848 | 1315 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | | -| 849 | 1323 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | | -| 850 | 1324 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | | -| 851 | 1325 | "加算区 +令牌档位件值(池件=奖励件本体——%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)) | (待填) | | | -| 852 | 1326 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | (待填) | | | -| 853 | 1338 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | | -| 854 | 1339 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | | -| 855 | 1341 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | | -| 856 | 1349 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | | -| 857 | 1360 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | | -| 858 | 1362 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | (待填) | | | -| 859 | 1365 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | (待填) | | | -| 860 | 1371 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | | -| 861 | 1376 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | | -| 862 | 1378 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | | -| 863 | 1379 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | | -| 864 | 1384 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | | -| 865 | 1392 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | | - -## S110 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 866 | 46 | "四检正例:多入口多终点图零错误〔v1〕" | _errs(multi).is_empty() | (待填) | | | -| 867 | 53 | "四检正例:含岔口单终点图零错误〔v1〕" | _errs(forked).is_empty() | (待填) | | | -| 868 | 56 | "四检正例:单链退化图零错误(不设必有岔口)〔v1〕" | _errs(chain).is_empty() | (待填) | | | - -## S111 _test_negative(行 63–89 · 11 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 869 | 63 | "负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕" | _has(_errs(non_boss_end), "structural", "终点 1 非 Boss") | (待填) | | | -| 870 | 66 | "负例:Boss 带出边被拒〔v1〕" | _has(_errs(boss_out), "structural", "Boss 节点 1 带出边") | (待填) | | | -| 871 | 68 | "负例:回边(目标深度不严格大于源)命中前进方向判据〔v1〕" | _has(_errs(back_edge), "structural", "违反前进方向") | (待填) | | | -| 872 | 71 | "负例:环(2→1 回边)被前进方向拦下〔v1〕" | _has(_errs(cycle), "structural", "违反前进方向") | (待填) | | | -| 873 | 77 | "判据②正证:两独立链(双入口双终点)不误伤·全员可达〔v1〕" | _errs(two_components).is_empty() | (待填) | | | -| 874 | 79 | "判据②兜底面:自环先被前进方向拦下(④ 与 ② 互斥的实证)〔v1〕" | _has(_errs(self_loop), "structural", "违反前进方向") | (待填) | | | -| 875 | 82 | "负例:悬空出边报 dangling_next〔v1〕" | _has(_errs(dangling), "dangling_next") | (待填) | | | -| 876 | 84 | "负例:句柄重复报 dup_key〔v1〕" | _has(_errs(dup), "dup_key") | (待填) | | | -| 877 | 86 | "负例:battle 节点缺深度被良构基底拒〔v1〕" | _has(_errs(no_depth), "structural", "缺深度") | (待填) | | | -| 878 | 87 | "负例:空节点集被拒〔v1〕" | _has(_errs([]), "structural", "图节点集为空") | (待填) | | | -| 879 | 89 | "负例:类型出域报 enum〔v1〕" | _has(_errs(bad_type), "enum") | (待填) | | | - -## S112 _test_scope_not_extended(行 97–102 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 880 | 97 | "范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕" | _errs(no_shop).is_empty() | (待填) | | | -| 881 | 99 | "范围反证:双入口合法(入口唯一判据已随模板池退役,个数归产出侧)〔v1〕" | _errs(two_entries).is_empty() | (待填) | | | -| 882 | 102 | "范围反证:节点缺 next 字段仍被良构基底拒〔v1〕" | _has(_errs(missing_key), "structural", "缺字段 next") | (待填) | | | - -## S113 _meta_pricing_and_buy(行 138–151 · 4 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 883 | 138 | "树购买受理(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 | (待填) | | | -| 884 | 141 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | (待填) | | | -| 885 | 148 | "余额不足 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 | (待填) | | | -| 886 | 151 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | (待填) | | | - -## S114 _meta_settle_and_workshop(行 168–341 · 42 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 887 | 168 | "新档首通 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 | (待填) | | | -| 888 | 171 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | (待填) | | | -| 889 | 173 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | (待填) | | | -| 890 | 181 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | (待填) | | | -| 891 | 187 | "重复杀第三轮恒定不递减(验收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)) | (待填) | | | -| 892 | 195 | "两关完成逐关归因: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)) | (待填) | | | -| 893 | 203 | "败北零关卡完成摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | (待填) | | | -| 894 | 210 | "条件件误购拒绝(购买件专属——契约 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") | (待填) | | | -| 895 | 213 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | (待填) | | | -| 896 | 215 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | (待填) | | | -| 897 | 218 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | (待填) | | | -| 898 | 220 | "条件未达不解锁(远程镐=冒险成功 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | (待填) | | | -| 899 | 223 | "远程镐=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() | (待填) | | | -| 900 | 227 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | (待填) | | | -| 901 | 230 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | (待填) | | | -| 902 | 233 | "天眼双条件 AND(冒险成功≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | (待填) | | | -| 903 | 236 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | (待填) | | | -| 904 | 241 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | (待填) | | | -| 905 | 244 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收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 | (待填) | | | -| 906 | 253 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | (待填) | | | -| 907 | 258 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | (待填) | | | -| 908 | 260 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | (待填) | | | -| 909 | 265 | "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 | (待填) | | | -| 910 | 270 | "完成 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | (待填) | | | -| 911 | 273 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | (待填) | | | -| 912 | 275 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | (待填) | | | -| 913 | 277 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | (待填) | | | -| 914 | 279 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | (待填) | | | -| 915 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | (待填) | | | -| 916 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | (待填) | | | -| 917 | 290 | "充能账本按装备 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 | (待填) | | | -| 918 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | (待填) | | | -| 919 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | (待填) | | | -| 920 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | (待填) | | | -| 921 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | (待填) | | | -| 922 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | (待填) | | | -| 923 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | (待填) | | | -| 924 | 321 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | (待填) | | | -| 925 | 323 | "主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入" | bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool" | (待填) | | | -| 926 | 327 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (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 | (待填) | | | -| 927 | 338 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | (待填) | | | -| 928 | 340 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | (待填) | | | - -## S115 _meta_injection(行 353–383 · 10 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 929 | 353 | "生命淬炼 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)) | (待填) | | | -| 930 | 355 | "攻/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 | (待填) | | | -| 931 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | (待填) | | | -| 932 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | (待填) | | | -| 933 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | (待填) | | | -| 934 | 366 | "首战 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)) | (待填) | | | -| 935 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | (待填) | | | -| 936 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | (待填) | | | -| 937 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(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()) | (待填) | | | -| 938 | 382 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | (待填) | | | - -## S116 _smoke_forge_axes(行 392–540 · 33 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 939 | 392 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | (待填) | | | -| 940 | 395 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | (待填) | | | -| 941 | 399 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 942 | 400 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | (待填) | | | -| 943 | 410 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | (待填) | | | -| 944 | 414 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | (待填) | | | -| 945 | 417 | "长剑二级购:扣 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 | (待填) | | | -| 946 | 424 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | (待填) | | | -| 947 | 429 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | (待填) | | | -| 948 | 432 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 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") | (待填) | | | -| 949 | 435 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | (待填) | | | -| 950 | 442 | "通道二:魔法护甲一次性解锁价 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") | (待填) | | | -| 951 | 447 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | (待填) | | | -| 952 | 452 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 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") | (待填) | | | -| 953 | 457 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | (待填) | | | -| 954 | 462 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | (待填) | | | -| 955 | 465 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | (待填) | | | -| 956 | 468 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | (待填) | | | -| 957 | 471 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | (待填) | | | -| 958 | 480 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | (待填) | | | -| 959 | 484 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | (待填) | | | -| 960 | 487 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | (待填) | | | -| 961 | 491 | "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") | (待填) | | | -| 962 | 497 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | (待填) | | | -| 963 | 499 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | (待填) | | | -| 964 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | (待填) | | | -| 965 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | (待填) | | | -| 966 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | (待填) | | | -| 967 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | (待填) | | | -| 968 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | | -| 969 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | (待填) | | | -| 970 | 529 | "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" | (待填) | | | -| 971 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | (待填) | | | - -## S117 _smoke_tree(行 548–621 · 25 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 972 | 548 | "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 | (待填) | | | -| 973 | 550 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | (待填) | | | -| 974 | 558 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | (待填) | | | -| 975 | 566 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | (待填) | | | -| 976 | 571 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | (待填) | | | -| 977 | 576 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | (待填) | | | -| 978 | 581 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | (待填) | | | -| 979 | 586 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | (待填) | | | -| 980 | 588 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | (待填) | | | -| 981 | 590 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | (待填) | | | -| 982 | 592 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | (待填) | | | -| 983 | 593 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | (待填) | | | -| 984 | 594 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | (待填) | | | -| 985 | 598 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | (待填) | | | -| 986 | 600 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | (待填) | | | -| 987 | 601 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | (待填) | | | -| 988 | 602 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | (待填) | | | -| 989 | 604 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | (待填) | | | -| 990 | 606 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | (待填) | | | -| 991 | 607 | "规则键投影=激活态效果键(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") | (待填) | | | -| 992 | 609 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | (待填) | | | -| 993 | 612 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | (待填) | | | -| 994 | 613 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | (待填) | | | -| 995 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | (待填) | | | -| 996 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | (待填) | | | +| 693 | 331 | "L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes1.size()] | not nodes1.is_empty() | (待填) | | | +| 694 | 334 | "关卡·满配(atk+200/hp+100)完成(实测:%s @节点 %d)" ↩ % [win.result, win.defeat_node_index] | win.result == "victory" | (待填) | | | +| 695 | 342 | "L1 关卡完成路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)" | _path_in_graph(nodes1, win.visited_ids) | (待填) | | | +| 696 | 343 | "L1 关卡完成路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" ↩ % [win.visited_ids.size(), path_battles, int(win.battles)] | path_battles == int(win.battles) and path_battles >= 2 | (待填) | | | +| 697 | 353 | "L1 终点 Boss 深度 = 参数档层高 %d 且关卡完成路径末节点落该深度(实测终点深度 %d·末句柄 %d)" ↩ % [int(params1.get("layers", -1)), boss_depth1, last_id] | boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1 | (待填) | | | +| 698 | 355 | "L1 终点恰一个(大岔口自 L2 起的图侧投影)" | _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1 | (待填) | | | +| 699 | 356 | "L1 生成图含岔口(出度 ≥2 节点 %d 处)" ↩ % _fork_ids(nodes1).size() | _fork_ids(nodes1).size() >= 1 | (待填) | | | +| 700 | 357 | "进 Boss HP 携带在域内(%d)" ↩ % win.hp_at_boss | win.hp_at_boss >= 1 and win.hp_at_boss <= int(_db.hero.max_hp) * 3 | (待填) | | | +| 701 | 363 | "恢复点公式无源(现行图入口后一位无可直达的恢复候选)" | false | (待填) | | | +| 702 | 366 | "恢复点公式·缺口充分档:回复 = floor(生效max_hp×%d%%) = %d(生效 max %d,实测 %d)" ↩ % [pct1, expect_pct, int(rest_wide["max"]), int(rest_wide["healed"])] | int(rest_wide["healed"]) == expect_pct and expect_pct > 0 | (待填) | | | +| 703 | 370 | "恢复点公式缺口封顶档无源(同图二次驱动未达恢复候选)" | false | (待填) | | | +| 704 | 372 | "恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" ↩ % [int(rest_narrow["healed"]), expect_pct] | int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2 | (待填) | | | +| 705 | 376 | "关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" ↩ % lose.result | lose.result == "defeat" | (待填) | | | +| 706 | 377 | "败北节点记录在域内(节点 %d,图节点数 %d)" ↩ % [lose.defeat_node_index, nodes1.size()] | lose.defeat_node_index >= 0 and lose.defeat_node_index < nodes1.size() | (待填) | | | +| 707 | 382 | "关卡回放断言:同种子两遍结算步骤流逐行相同" | JSON.stringify(rep1.rows) == JSON.stringify(rep2.rows) | (待填) | | | +| 708 | 383 | "关卡步骤流非空(%d 行)" ↩ % rep1.rows.size() | rep1.rows.size() > 0 | (待填) | | | +| 709 | 385 | "模拟后 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) | (待填) | | | -## S118 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) +## S104 _smoke_level_graph(行 442–762 · 70 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 997 | 633 | "先手压制:每关首战首轮窗命中(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 | (待填) | | | -| 998 | 635 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (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() | (待填) | | | -| 999 | 638 | "稳健拓荒:首轮窗内受击出盾、窗外不出(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() | (待填) | | | -| 1000 | 654 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | (待填) | | | -| 1001 | 657 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | -| 1002 | 663 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | -| 1003 | 685 | "锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕" | nc_hit != "" | (待填) | | | -| 1004 | 687 | "惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕" | nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷") | (待填) | | | -| 1005 | 691 | "守侧 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"}) | (待填) | | | -| 1006 | 703 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | (待填) | | | -| 1007 | 704 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | (待填) | | | -| 1008 | 708 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | -| 1009 | 713 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | -| 1010 | 717 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | -| 1011 | 721 | "守⑤ 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"}) | (待填) | | | -| 1012 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | (待填) | | | -| 1013 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | (待填) | | | -| 1014 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | | -| 1015 | 739 | "聚 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"}) | (待填) | | | -| 1016 | 748 | "聚 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"}) | (待填) | | | -| 1017 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | | +| 710 | 442 | "LevelTable 行级校验零错误(图合法性移交 validate_graph)" | _db.level.validate().is_empty() | (待填) | | | +| 711 | 446 | "L2 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes2.size()] | not nodes2.is_empty() | (待填) | | | +| 712 | 449 | "L2 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes2.size(), int(params2.get("layers", -1)), _nodes_by_id(nodes2).size()] | nodes2.size() >= int(params2.get("layers", -1)) + 1 and _nodes_by_id(nodes2).size() == nodes2.size() | (待填) | | | +| 713 | 451 | "L2 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount["battle"], tcount["boss"], tcount["reward"], tcount["rest"], tcount["shop"], nodes2.size()] | int(tcount["battle"]) + int(tcount["boss"]) + int(tcount["reward"]) + int(tcount["rest"]) + int(tcount["shop"]) == nodes2.size() | (待填) | | | +| 714 | 453 | "L2 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount["battle"], int(params2.get("battle_floor", -1)), tcount["boss"], int(params2.get("exit_count", -1))] | int(tcount["battle"]) >= int(params2.get("battle_floor", -1)) and int(tcount["boss"]) == int(params2.get("exit_count", -1)) | (待填) | | | +| 715 | 455 | "L2 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount["rest"], tcount["reward"], tcount["shop"]] | int(tcount["rest"]) >= 1 and int(tcount["reward"]) >= 1 and int(tcount["shop"]) >= 1 | (待填) | | | +| 716 | 457 | "L2 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch | branch >= 1 | (待填) | | | +| 717 | 467 | "图校验正例:两岔合流小图零错误〔v1〕" | LT.validate_graph(good).is_empty() | (待填) | | | +| 718 | 470 | "图校验拒绝:句柄重复(图内唯一句柄)〔v1〕" | _has_err_category(LT.validate_graph(dup), "dup_key") | (待填) | | | +| 719 | 473 | "图校验正例:多入口合法(入口唯一判据随模板池退役)〔v1〕" | LT.validate_graph(dual).is_empty() | (待填) | | | +| 720 | 476 | "图校验拒绝:Boss 带出边(终点不可外出)〔v1〕" | _has_err_category(LT.validate_graph(boss_out), "structural") | (待填) | | | +| 721 | 479 | "图校验拒绝:回边 1→0(目标深度须严格大于源)〔v1〕" | _has_err_category(LT.validate_graph(cyc), "structural") | (待填) | | | +| 722 | 482 | "图校验拒绝:出边指向不存在句柄(悬空致命)〔v1〕" | _has_err_category(LT.validate_graph(dang), "dangling_next") | (待填) | | | +| 723 | 488 | "图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕" | _has_err_category(LT.validate_graph(orphan), "structural") | (待填) | | | +| 724 | 491 | "L2·满配 min 路径完成(实测:%s)" ↩ % win_min.result | win_min.result == "victory" | (待填) | | | +| 725 | 496 | "L2 min 路径逐跳为图出边·末节点为 Boss(实测路径 %s·末句柄型别 %s)" ↩ % [str(win_min.visited_ids), n2_last_type] | _path_in_graph(nodes2, win_min.visited_ids) and n2_last_type == "boss" | (待填) | | | +| 726 | 498 | "L2 min 路径终点深度 = 参数档层高 %d(行进读数 %d——终点落最长路径步数)" ↩ % [int(params2.get("layers", -1)), int(win_min.boss_depth)] | int(win_min.boss_depth) == int(params2.get("layers", -1)) | (待填) | | | +| 727 | 500 | "L2 min 路径穿商店恰 1 家(路径句柄 %s 型别计数 = 1——必经恰一店的行进面)" ↩ % str(win_min.visited_ids) | _path_shop_count(nodes2, win_min.visited_ids) == 1 | (待填) | | | +| 728 | 502 | "选路口发生 %d 次(每节点兑付后——零自动推进)" ↩ % win_min.choices | win_min.choices >= 3 | (待填) | | | +| 729 | 504 | "L2·满配 max 路径(候选取最大句柄)完成(实测:%s·路径 %s)" ↩ % [win_max.result, str(win_max.visited_ids)] | win_max.result == "victory" | (待填) | | | +| 730 | 509 | "L2 max 路径逐跳为图出边·末节点为 Boss·穿商店恰 1 家" | _path_in_graph(nodes2, win_max.visited_ids) and n2_last_max_type == "boss" and _path_shop_count(nodes2, win_max.visited_ids) == 1 | (待填) | | | +| 731 | 512 | "L2 max 与 min 路径分岔(方向轴生效——两路终点句柄 %d / %d 不同)" ↩ % [n2_last, n2_last_max] | n2_last != n2_last_max and win_max.visited_ids != win_min.visited_ids | (待填) | | | +| 732 | 519 | "手动驱动:L2 启动(入口战斗直接开打——入口无前置兑付;图 = 参数档 × 种子 %d)" ↩ % ACTIVE_GRAPH_SEED | runner.start_level(2, ACTIVE_GRAPH_SEED) | (待填) | | | +| 733 | 526 | "首战胜利后:settlement 释放 + 进选路态(战斗胜利不再自动推进——接口漂移清偿)" | String(res_box.get("result", "")) == "victory" and runner.current_battle() == null and runner.awaiting_choice() | (待填) | | | +| 734 | 529 | "runner 消费面图 = 生成面图(节点数 %d/%d 且句柄集逐位相同)" ↩ % [rnodes.size(), nodes2.size()] | rnodes.size() == nodes2.size() and _ids_of_type(rnodes, "battle").size() + _ids_of_type(rnodes, "boss").size() + _ids_of_type(rnodes, "reward").size() \\ + _ids_of_type(rnodes, "rest").size() + _ids_of_type(rnodes, "shop").size() == nodes2.size() and _nodes_by_id(rnodes) == _nodes_by_id(nodes2) | (待填) | | | +| 735 | 537 | "入口选路口候选非空且全在图内(候选 %s·类型 %s)" ↩ % [str(cand), str(cand_types)] | not cand.is_empty() and not cand_types.has("<不在图>") | (待填) | | | +| 736 | 538 | "候选下一跳类型两两互异(岔口去路类型不重复契约——实测 %s)" ↩ % str(cand_types) | cand_types.size() == _uniq(cand_types).size() | (待填) | | | +| 737 | 545 | "候选外句柄 %d 拒绝且仍待选(拒绝语义)" ↩ % outside_id | outside_id >= 0 and not runner.pick_next_node(outside_id) and runner.awaiting_choice() | (待填) | | | +| 738 | 555 | "入口岔口含奖励位与战斗位(句柄 %d/%d——候选型别可定位)" ↩ % [reward_cand, battle_cand] | reward_cand >= 0 and battle_cand >= 0 | (待填) | | | +| 739 | 556 | "选奖励候选 %d 受理" ↩ % reward_cand | runner.pick_next_node(reward_cand) | (待填) | | | +| 740 | 557 | "到达奖励点:直通兑付(确认前不出选路口)" | String(runner.current_node().get("type", "")) == "reward" and not runner.awaiting_choice() | (待填) | | | +| 741 | 559 | "兑付确认后才出现选路口(continue_after_node 泛化语义)候选 = 该点图出边 %s" ↩ % str(reward_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(reward_outs) | (待填) | | | +| 742 | 566 | "选下一跳战斗候选 %d 开战" ↩ % next_battle | next_battle >= 0 and runner.pick_next_node(next_battle) and runner.current_battle() != null | (待填) | | | +| 743 | 591 | "首遇位零踩雷:完美档 12 种子(节点 0 熊地精)恒零踩雷" | green_ok | (待填) | | | +| 744 | 592 | "首遇战胜局 ≥7/12(实际 %d——阶段拔高后锚行退出绿档,多数胜口径)" ↩ % green_wins | green_wins >= 7 | (待填) | | | +| 745 | 593 | "首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green_min_hp | green_min_hp > 0 | (待填) | | | +| 746 | 597 | "跨表覆盖扫描面非空:L2 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov2["nodes"]) | int(cov2["nodes"]) >= 1 | (待填) | | | +| 747 | 598 | "L2 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov2["enemy_bad"]) | int(cov2["enemy_bad"]) == 0 | (待填) | | | +| 748 | 599 | "L2 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov2["board_bad"]) | int(cov2["board_bad"]) == 0 | (待填) | | | +| 749 | 607 | "巨蜘蛛首遇带(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") | (待填) | | | +| 750 | 617 | "L2 全部终点深度 %s 的 Boss 行集均 = {drider} 恰一行且首行魔法甲(护甲类型重配批:关卡 2 唯一非轻甲;P4 乙 Boss 行集多行合法——零内容期仍单行)〔fv24〕" ↩ % str(l2_terms) | l2_boss_ok | (待填) | | | +| 751 | 624 | "首批混编行集 (2,battle,5) = 双行且行序 [bugbear 重击手 → giant_spider 巢群](R6 行序契约——文件序 = 声明序 = 生成序,首行默认目标 = 前排坦克;迭代 18 卡⑤投放)〔fv18〕" | mix_shape_ok | (待填) | | | +| 752 | 628 | "首批混编行集 (2,battle,5) 静态最坏骰和 = Σ(unit_count_max,固定行缺省=unit_count) = %d ≤ 4(行集实例总数守卫同口径——重击手固定 1 + 巢群 [2,3])〔fv19〕" ↩ % mix_sum_max | mix_sum_max <= 4 | (待填) | | | +| 753 | 629 | "首批混编新行 (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 | (待填) | | | +| 754 | 636 | "L3 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" ↩ % [ACTIVE_GRAPH_SEED, nodes3.size()] | not nodes3.is_empty() | (待填) | | | +| 755 | 639 | "L3 节点数 %d ≥ 层高 %d+1 且 = 句柄去重读数 %d(全员句柄唯一)" ↩ % [nodes3.size(), int(params3.get("layers", -1)), _nodes_by_id(nodes3).size()] | nodes3.size() >= int(params3.get("layers", -1)) + 1 and _nodes_by_id(nodes3).size() == nodes3.size() | (待填) | | | +| 756 | 641 | "L3 五型计数合计 = 节点数(battle %d + boss %d + reward %d + rest %d + shop %d = %d)" ↩ % [tcount3["battle"], tcount3["boss"], tcount3["reward"], tcount3["rest"], tcount3["shop"], nodes3.size()] | int(tcount3["battle"]) + int(tcount3["boss"]) + int(tcount3["reward"]) + int(tcount3["rest"]) + int(tcount3["shop"]) == nodes3.size() | (待填) | | | +| 757 | 643 | "L3 战斗节点数 %d ≥ 参数档战斗保底 %d 且 Boss 节点数 %d = 参数档出口数 %d" ↩ % [tcount3["battle"], int(params3.get("battle_floor", -1)), tcount3["boss"], int(params3.get("exit_count", -1))] | int(tcount3["battle"]) >= int(params3.get("battle_floor", -1)) and int(tcount3["boss"]) == int(params3.get("exit_count", -1)) | (待填) | | | +| 758 | 645 | "L3 恢复/奖励/商店节点各 ≥1(实测 rest %d · reward %d · shop %d——计数由逐节点扫描同一读数)" ↩ % [tcount3["rest"], tcount3["reward"], tcount3["shop"]] | int(tcount3["rest"]) >= 1 and int(tcount3["reward"]) >= 1 and int(tcount3["shop"]) >= 1 | (待填) | | | +| 759 | 647 | "L3 岔口(出度 ≥2 的节点)= %d 处(≥1)" ↩ % branch3 | branch3 >= 1 | (待填) | | | +| 760 | 648 | "L3 入口数 %d = 参数档 entry_count · 终点数 %d = exit_count ≥2(多终点大岔口自 L2 起)" ↩ % [_entry_ids(nodes3).size(), _terminal_ids(nodes3).size()] | _entry_ids(nodes3).size() == int(params3.get("entry_count", -1)) and _terminal_ids(nodes3).size() == int(params3.get("exit_count", -1)) and _terminal_ids(nodes3).size() >= 2 | (待填) | | | +| 761 | 652 | "跨表覆盖扫描面非空:L3 现行图战斗/Boss 节点 %d 颗" ↩ % int(cov3["nodes"]) | int(cov3["nodes"]) >= 1 | (待填) | | | +| 762 | 653 | "L3 现行图每战斗/Boss 节点深度敌侧解析落带 ≥ 请求深度(违例 %d 处)" ↩ % int(cov3["enemy_bad"]) | int(cov3["enemy_bad"]) == 0 | (待填) | | | +| 763 | 654 | "L3 现行图每节点深度 board_gen 精确命中(非空且 node_depth 等值·违例 %d 处)" ↩ % int(cov3["board_bad"]) | int(cov3["board_bad"]) == 0 | (待填) | | | +| 764 | 661 | "灰矮人首遇带(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") | (待填) | | | +| 765 | 672 | "L3 全部终点深度 %s 的 Boss 行集均 = {duergar_king} 恰一行且首行重甲(重甲收束「轻甲→魔法甲→重甲」教学梯度)〔fv24〕" ↩ % str(l3_terms) | l3_boss_ok | (待填) | | | +| 766 | 695 | "L3 首遇位绿档:成长代理档口径(atk_pct=200/hp_pct=100)完美档 12 种子首遇战(节点 0 灰矮人)全胜且零踩雷" | green3_ok | (待填) | | | +| 767 | 696 | "L3 首遇战胜后 HP 下探读数 %d(>0——绿档记录)" ↩ % green3_min_hp | green3_min_hp > 0 | (待填) | | | +| 768 | 706 | "混编行集 spawn 受理((1,battle,5) 双行 地精1+熊地精2——定值无区间路径,行集构成纪律放开面正向)" | mix_sys.spawn(1, 5, int(_db.hero.max_hp)) | (待填) | | | +| 769 | 708 | "混编实例总数 = 各行数量求和(1+2=3——行集实例总数 ≤4 守卫内)" | mix_units.size() == 3 | (待填) | | | +| 770 | 709 | "混编生成序 = 文件行序(行序即契约——地精在前熊地精在后,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" | (待填) | | | +| 771 | 710 | "混编各行行值独立投影(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 | (待填) | | | +| 772 | 714 | "行集实例总数守卫:两行各定值 3(总 6 > 4)spawn 被拒(行集构成纪律③——R3 fail-fast 运行面)" | not over_sys.spawn(1, 6, int(_db.hero.max_hp)) | (待填) | | | +| 773 | 719 | "混编断言段注入行全量还原(表回 28 行——迭代 35 生成图深度扩面覆盖批后的基线行数;首批混编投放基线 20 行+覆盖 8 行)" | int(_db.enemy.rows.size()) == 28 | (待填) | | | +| 774 | 726 | "可见性冒烟:L2 入口停点就位(defer_entry——无当前节点,锚层 0)" | vis_ok and vis_runner.awaiting_choice() and vis_runner.current_node_id() == -1 | (待填) | | | +| 775 | 732 | "可见性冒烟:入口停点候选集 ⊆ 类型可见集(下一跳恒可见)〔fv4〕" | vis_cand_in | (待填) | | | +| 776 | 737 | "可见性冒烟:L2 全部终点 Boss 句柄 %s 恒在可见集(终点不入雾)〔fv3〕" ↩ % str(_ids_of_type(nodes2, "boss")) | vis_boss_ok | (待填) | | | +| 777 | 739 | "可见性冒烟:N 读数据表 = %d(provisional 2——代码零硬编码)〔fv6〕" ↩ % vis_n | vis_n == 2 | (待填) | | | +| 778 | 761 | "可见性冒烟:入口锚层 0 时层距 ≤ %d 的节点全在可见集(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, near_count] | near_count >= 1 and near_ok | (待填) | | | +| 779 | 762 | "可见性冒烟:入口锚层 0 时层距 > %d 的非终点节点全入雾(扫描面 %d 颗·违例 0)〔fv1〕" ↩ % [vis_n, fog_count] | fog_count >= 1 and fog_ok | (待填) | | | + +## S105 _smoke_gp_wallet(行 799–835 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 780 | 799 | "GP 到账:L1 启动(普通战档)" | runner.start_level(1, 42) | (待填) | | | +| 781 | 809 | "L1 首战胜利(battle_finished gp_dropped 载荷在案)" | fin.has("p") and String(fin["p"].get("result", "")) == "victory" | (待填) | | | +| 782 | 812 | "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 | (待填) | | | +| 783 | 817 | "钱包恰入账一次(gain 事件 ×%d)" ↩ % gains.size() | gains.size() == 1 | (待填) | | | +| 784 | 819 | "入账金额 = gp_dropped 载荷(%d)" ↩ % gp_dropped | int(gains[0]["amount"]) == gp_dropped | (待填) | | | +| 785 | 820 | "钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" ↩ % runner.gp | runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]) | (待填) | | | +| 786 | 827 | "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)}) | (待填) | | | +| 787 | 832 | "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" ↩ % String(boss.battle_result) | fin_b.has("p") | (待填) | | | +| 788 | 835 | "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 | (待填) | | | + +## S106 _smoke_gp_escrow(行 873–937 · 12 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 789 | 873 | "GP 袋(gp 型安全格)可挖掘局种子可达(seed=%s)" ↩ % str(scan.get("seed", -1)) | not scan.is_empty() | (待填) | | | +| 790 | 886 | "GP局战斗终局(先手 999 收束)" | settlement2.battle_result == "victory" | (待填) | | | +| 791 | 891 | "GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)" | gain_ok | (待填) | | | +| 792 | 892 | "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" ↩ % int(settlement2.escrow) | int(settlement2.escrow) >= 3 | (待填) | | | +| 793 | 894 | "格面兑付计入 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 | (待填) | | | +| 794 | 900 | "破财直验战斗启动" | settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}) | (待填) | | | +| 795 | 904 | "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)" | int(settle_l.escrow) == -6 | (待填) | | | +| 796 | 909 | "破财压制 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 | (待填) | | | +| 797 | 919 | "clamp 场景:L1 启动(冒险钱包开局 0)" | runner.start_level(1, 4242) | (待填) | | | +| 798 | 931 | "破财压制入账载荷为负(gp_dropped=%d)" ↩ % gp3 | gp3 < 0 | (待填) | | | +| 799 | 936 | "破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" ↩ % gp3 | gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0 | (待填) | | | +| 800 | 937 | "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" ↩ % [runner.gp, int(ev_box["min_gp"])] | runner.gp == 0 and int(ev_box["min_gp"]) >= 0 | (待填) | | | + +## S107 _smoke_shop_room(行 1003–1089 · 25 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 801 | 1003 | "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" ↩ % str(shop_run.get("seed", -1)) | not shop_run.is_empty() | (待填) | | | +| 802 | 1016 | "L2 行进到商店房:当前节点型别 = shop 且出边恰一条指终点 Boss(句柄 %d·出边 %s)" ↩ % [shop_id, str(shop_outs)] | String(runner.current_node().get("type", "")) == "shop" and shop_outs.size() == 1 \\ and String(s_byid.get(int(shop_outs[0]), {}).get("type", "")) == "boss" \\ and (s_byid.get(int(shop_outs[0]), {}).get("next", []) as Array).is_empty() | (待填) | | | +| 803 | 1020 | "L2 到店路径穿商店恰 1 家(已到句柄 %s 型别计数——必经恰一店的行进面)" ↩ % str(runner.visited_ids()) | _path_shop_count(s_nodes, runner.visited_ids()) == 1 | (待填) | | | +| 804 | 1022 | "L2 商店节点(句柄 %d)入边恰一条(店位归终点的直接前驱——行进面复核)" ↩ % shop_id | shop_preds == 1 | (待填) | | | +| 805 | 1024 | "到店商店节点无出边(夹具停摆——去终点方向无法从图解析)" | false | (待填) | | | +| 806 | 1026 | "到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)" | not runner.awaiting_choice() and runner.next_candidate_ids().is_empty() | (待填) | | | +| 807 | 1029 | "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)" | prods.size() == 3 | (待填) | | | +| 808 | 1031 | "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 | (待填) | | | +| 809 | 1034 | "购买 atk_up 受理(现价 6)" | runner.buy_product("atk_up") | (待填) | | | +| 810 | 1035 | "钱包扣现价 6(%d → %d)" ↩ % [gp0, runner.gp] | runner.gp == gp0 - 6 | (待填) | | | +| 811 | 1037 | "统一冒险加算区 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 | (待填) | | | +| 812 | 1039 | "atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)" | int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1 | (待填) | | | +| 813 | 1046 | "购买 hp_up 受理(现价 4)" | runner.buy_product("hp_up") | (待填) | | | +| 814 | 1047 | "钱包扣 4(%d)" ↩ % runner.gp | runner.gp == gp0 - 6 - 4 | (待填) | | | +| 815 | 1048 | "生命上限 +3(%d → %d——加算区 max_hp 轴)" ↩ % [mhp_before, runner.effective_max_hp()] | runner.effective_max_hp() == mhp_before + 3 | (待填) | | | +| 816 | 1049 | "当前生命同步 +3 封顶新上限(%d → %d)" ↩ % [hp_before, runner.hero_hp] | runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()) | (待填) | | | +| 817 | 1051 | "购买 armor_up 受理(现价 6)" | runner.buy_product("armor_up") | (待填) | | | +| 818 | 1052 | "AC = 10+1(armor_class 加算区轴,无百分比通道)" | runner.effective_ac() == int(_db.hero.armor_class) + 1 | (待填) | | | +| 819 | 1067 | "余额不足购买被拒(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 | (待填) | | | +| 820 | 1069 | "离开商店 → 回选路口(候选 = 店出边 %s——零自动推进收口)" ↩ % str(shop_outs) | runner.continue_after_node() and runner.awaiting_choice() and _sorted_ids(runner.next_candidate_ids()) == _sorted_ids(shop_outs) | (待填) | | | +| 821 | 1072 | "主角攻击生效值 = 2+1(基础表值 + 加算区)" | runner.effective_atk() == int(_db.hero.atk) + 1 | (待填) | | | +| 822 | 1073 | "选终点 Boss(句柄 %d)开战" ↩ % int(shop_outs[0]) | runner.pick_next_node(int(shop_outs[0])) and runner.current_battle() != null | (待填) | | | +| 823 | 1077 | "战斗开始参数 = 基础表值 + 加算区(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 | (待填) | | | +| 824 | 1088 | "冒险收束(Boss 战终局:%s)" ↩ % String(fin_end.get("p", {}).get("result", "")) | fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"] | (待填) | | | +| 825 | 1089 | "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)" | runner.gp == 0 | (待填) | | | + +## S108 _smoke_shop_graph(行 1111–1168 · 9 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 826 | 1111 | "L%d 参数档缺行(商店编排断言无源可扫)" ↩ % L | false | (待填) | | | +| 827 | 1116 | "L%d 种子 %d 生成图为空(编排断言扫描面空转)" ↩ % [L, seed_val] | false | (待填) | | | +| 828 | 1142 | "编排断言扫描面非空(三关参数档 × 种子 %d–%d=图 %d 张·终点 %d·路径 %d)〔v5〕" ↩ % [SWEEP_SEED_FROM, SWEEP_SEED_TO - 1, graph_total, term_total, path_total] | graph_total == 3 * (SWEEP_SEED_TO - SWEEP_SEED_FROM) and term_total >= 3 and path_total >= 3 | (待填) | | | +| 829 | 1144 | "性质① 每个终点的直接前驱恰一家商店(入边一条且源型别=shop·违例 %d/%d 终点)〔v5〕" ↩ % [pred_bad, term_total] | pred_bad == 0 | (待填) | | | +| 830 | 1145 | "性质② 每图商店节点数 = 终点数(违例 %d/%d 图)〔v5〕" ↩ % [count_bad, graph_total] | count_bad == 0 | (待填) | | | +| 831 | 1146 | "性质③ 每条入口→终点路径恰穿一家商店(违例 %d/%d 条路径)〔v5〕" ↩ % [path_bad, path_total] | path_bad == 0 | (待填) | | | +| 832 | 1152 | "L%d 现行图终点 %d 个 = 商店 %d 个·入口→终点路径 %d 条全穿恰一店" ↩ % [L, cur_terms.size(), cur_shops.size(), _all_entry_terminal_paths(cur_nodes).size()] | cur_terms.size() == cur_shops.size() and not cur_terms.is_empty() and _all_entry_terminal_paths(cur_nodes).size() >= cur_terms.size() and _paths_all_one_shop(cur_nodes) | (待填) | | | +| 833 | 1164 | "L%d 现行图终点深度 %s 逐点 Boss 行集解析命中且落带 ≥ 终点深度" ↩ % [L, str(tdepths)] | hit_ok | (待填) | | | +| 834 | 1167 | "L1 现行图跨表覆盖(战斗/Boss 节点 %d 颗非空·敌侧违例 %d·板侧违例 %d)" ↩ % [int(l1_cov["nodes"]), int(l1_cov["enemy_bad"]), int(l1_cov["board_bad"])] | int(l1_cov["nodes"]) >= 1 and int(l1_cov["enemy_bad"]) == 0 and int(l1_cov["board_bad"]) == 0 | (待填) | | | + +## S109 _smoke_multi_terminal_settlement(行 1176–1223 · 5 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 835 | 1176 | "L2 现行图为多终点(Boss 终点 %s 两颗以上——大岔口承载面)" ↩ % str(boss_ids) | boss_ids.size() >= 2 | (待填) | | | +| 836 | 1188 | "L2 min/max 两路各打进各自终点 Boss(末句柄 %d / %d 互异且都在图 Boss 集 %s 内;两路 result=%s/%s)" ↩ % [min_last, max_last, str(boss_ids), String(run_min.get("result", "")), String(run_max.get("result", ""))] | String(run_min.get("result", "")) == "victory" and String(run_max.get("result", "")) == "victory" and boss_ids.has(min_last) and boss_ids.has(max_last) and min_last != max_last | (待填) | | | +| 837 | 1192 | "min 路未到 max 终点 %d、max 路未到 min 终点 %d(区域隔离——走 A 区不误兑付 B 区 Boss)" ↩ % [max_last, min_last] | not min_vis.has(max_last) and not max_vis.has(min_last) | (待填) | | | +| 838 | 1199 | "L2 全程驱动启动受理" | runner.start_level(2, 42) | (待填) | | | +| 839 | 1221 | "任一 Boss 胜即本关收束恰一次(count=%d·载荷 result=%s·node_id=%s 在图 Boss 集 %s 内)" ↩ % [int(fin["count"]), String(payload.get("result", "")), str(payload.get("node_id", -1)), str(boss_ids)] | int(fin["count"]) == 1 and String(payload.get("result", "")) == "victory" and boss_ids.has(int(payload.get("node_id", -1))) | (待填) | | | + +## S110 _smoke_shop_pool(行 1258–1392 · 29 条 · 源 game/tests/unit/core/level_shop_economy_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 840 | 1258 | "池模式商店房场景可达(seed=%s)" ↩ % str(scen.get("s", -1)) | not scen.is_empty() | (待填) | | | +| 841 | 1266 | "进店抽 3(货架 %d 卡——同构交互)" ↩ % first_shelf_a.size() | first_shelf_a.size() == 3 | (待填) | | | +| 842 | 1272 | "刷新首价=基价(%d)" ↩ % cost0 | cost0 == int(_db.adventure.shop_refresh_cost_base) | (待填) | | | +| 843 | 1273 | "基础免费 0 次(Q2——天赋增益位后续添加)" | r8.shop_refresh_free_left() == 0 | (待填) | | | +| 844 | 1275 | "GP 刷新受理(gp=%d cost=%d)" ↩ % [gp0, cost0] | r8.shop_refresh() | (待填) | | | +| 845 | 1276 | "GP 刷新扣费(%d→%d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - cost0 | (待填) | | | +| 846 | 1277 | "刷新爬价 ×curve(%d)" ↩ % int(r8.shop_refresh_cost()) | int(r8.shop_refresh_cost()) == int(ceil(float(cost0) * float(_db.adventure.shop_refresh_cost_curve))) | (待填) | | | +| 847 | 1285 | "同种子同货架逐位(确定性重放承载——断点货架续玩语义)" | same_shelf | (待填) | | | +| 848 | 1295 | "relic 现价 = ceil(15.0×b^(关序−1))(读表与迁移前字面量逐位一致,%d)〔wd11〕" ↩ % relic_price_now | relic_price_now == relic_price_pre | (待填) | | | +| 849 | 1301 | "货架 kind 在封闭词表或保底 relic 卡(%s)" ↩ % ckind | is_relic or shelf_kinds.has(ckind) | (待填) | | | +| 850 | 1308 | "首购现价 = ceil(基价×b^(关序−1))(%s: %d = %d 推导)" ↩ % [ckind, int(c.get("price", -1)), expect_p] | int(c.get("price", -1)) == expect_p | (待填) | | | +| 851 | 1315 | "可购卡在架(GP %d——种子扫描保证首购空间)" ↩ % int(r8.gp) | not buy_c.is_empty() | (待填) | | | +| 852 | 1323 | "购买受理(pool 口——%s\|%s)" ↩ % [kind, tier] | r8.buy_pool_item(kind) | (待填) | | | +| 853 | 1324 | "钱包扣现价(%d → %d)" ↩ % [gp0, int(r8.gp)] | int(r8.gp) == gp0 - int(buy_c.get("price", 0)) | (待填) | | | +| 854 | 1325 | "加算区 +令牌档位件值(池件=奖励件本体——%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)) | (待填) | | | +| 855 | 1326 | "共享池账本 -1(跨渠道单点——taken %d → %d)" ↩ % [taken0, int(a8._curio_taken.get(kind, 0))] | int(a8._curio_taken.get(kind, 0)) == taken0 + 1 | (待填) | | | +| 856 | 1338 | "A 链爬价 = ceil(base×b^(关序−1)×A^1)(进店第 2 购——%d)" ↩ % expect_p2 | int(second.get("price", -1)) == expect_p2 | (待填) | | | +| 857 | 1339 | "同 visit 第二购受理(A 链软闸在价不在限)" | r8.buy_pool_item(String(second.get("kind_id", ""))) | (待填) | | | +| 858 | 1341 | "A 链爬价场景未构造(抬 GP 后货架仍无可购第二卡)——场景绑定失败" | false | (待填) | | | +| 859 | 1349 | "切边件零入店(40 次重抽零 gp/aux——负交易切边+三轴 doctrine)" | not cut_seen | (待填) | | | +| 860 | 1360 | "库存尽剔牌库(attack 令牌耗尽——池消耗即上限跨渠道共库 reward→shop 向)" | not attack_seen | (待填) | | | +| 861 | 1362 | "池空消费拒绝(handler 零副作用——买光即枯竭硬闸)" | not a8._consume_pool_purchase("attack", "common") and a8.pool_stock("attack") == stock_before | (待填) | | | +| 862 | 1365 | "商店消费口受理(_apply_curio_pick 同口——真单点库)" | a8._consume_pool_purchase("attack", "common") | (待填) | | | +| 863 | 1371 | "商店购买进奖励牌库摘牌(买一件=奖励面少一件——shop→reward 向共库)" | not reward_attack_seen | (待填) | | | +| 864 | 1376 | "离开商店 → 回选路口(货架态清零落档——零自动推进收口)" | r8.continue_after_node() and r8.awaiting_choice() | (待填) | | | +| 865 | 1378 | "断点扩键 shop_shelf 在案(离开店=空架合法态——P2 甲扩列)" | bp.has("shop_shelf") and (bp["shop_shelf"] as Array).is_empty() | (待填) | | | +| 866 | 1379 | "断点扩键 shop_visit_buys 在案(离开店清零——A 链输入不入档语义)" | bp.has("shop_visit_buys") and int(bp["shop_visit_buys"]) == 0 | (待填) | | | +| 867 | 1384 | "终局池读数六键出讫(run_adventure pool 面——四池种+T5 专长凭证+T6 heal 行,读数只锚一次载体)" | ps.size() == 6 | (待填) | | | +| 868 | 1392 | "终局池余量在域 [0,配额](%s)" ↩ % str(ps) | ps_ok | (待填) | | | + +## S111 _test_positive(行 46–56 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 869 | 46 | "四检正例:多入口多终点图零错误〔v1〕" | _errs(multi).is_empty() | (待填) | | | +| 870 | 53 | "四检正例:含岔口单终点图零错误〔v1〕" | _errs(forked).is_empty() | (待填) | | | +| 871 | 56 | "四检正例:单链退化图零错误(不设必有岔口)〔v1〕" | _errs(chain).is_empty() | (待填) | | | + +## S112 _test_negative(行 63–89 · 11 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 872 | 63 | "负例:终点非 Boss 命中「终点恒为 Boss」〔v1〕" | _has(_errs(non_boss_end), "structural", "终点 1 非 Boss") | (待填) | | | +| 873 | 66 | "负例:Boss 带出边被拒〔v1〕" | _has(_errs(boss_out), "structural", "Boss 节点 1 带出边") | (待填) | | | +| 874 | 68 | "负例:回边(目标深度不严格大于源)命中前进方向判据〔v1〕" | _has(_errs(back_edge), "structural", "违反前进方向") | (待填) | | | +| 875 | 71 | "负例:环(2→1 回边)被前进方向拦下〔v1〕" | _has(_errs(cycle), "structural", "违反前进方向") | (待填) | | | +| 876 | 77 | "判据②正证:两独立链(双入口双终点)不误伤·全员可达〔v1〕" | _errs(two_components).is_empty() | (待填) | | | +| 877 | 79 | "判据②兜底面:自环先被前进方向拦下(④ 与 ② 互斥的实证)〔v1〕" | _has(_errs(self_loop), "structural", "违反前进方向") | (待填) | | | +| 878 | 82 | "负例:悬空出边报 dangling_next〔v1〕" | _has(_errs(dangling), "dangling_next") | (待填) | | | +| 879 | 84 | "负例:句柄重复报 dup_key〔v1〕" | _has(_errs(dup), "dup_key") | (待填) | | | +| 880 | 86 | "负例:battle 节点缺深度被良构基底拒〔v1〕" | _has(_errs(no_depth), "structural", "缺深度") | (待填) | | | +| 881 | 87 | "负例:空节点集被拒〔v1〕" | _has(_errs([]), "structural", "图节点集为空") | (待填) | | | +| 882 | 89 | "负例:类型出域报 enum〔v1〕" | _has(_errs(bad_type), "enum") | (待填) | | | + +## S113 _test_scope_not_extended(行 97–102 · 3 条 · 源 game/tests/unit/core/level_table_validate_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 883 | 97 | "范围反证:无商店图不在加载校验内报错(商店位由冒烟断言把关)〔v1〕" | _errs(no_shop).is_empty() | (待填) | | | +| 884 | 99 | "范围反证:双入口合法(入口唯一判据已随模板池退役,个数归产出侧)〔v1〕" | _errs(two_entries).is_empty() | (待填) | | | +| 885 | 102 | "范围反证:节点缺 next 字段仍被良构基底拒〔v1〕" | _has(_errs(missing_key), "structural", "缺字段 next") | (待填) | | | + +## S114 _meta_pricing_and_buy(行 138–151 · 4 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 886 | 138 | "树购买受理(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 | (待填) | | | +| 887 | 141 | "购买持久(reload 后级与余额不变)" | sv_r.tree_level("sp_hp") == 1 and sv_r.keystone_balance() == bal0 - price0 | (待填) | | | +| 888 | 148 | "余额不足 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 | (待填) | | | +| 889 | 151 | "未知节点 false 零副作用(词表外出域)" | not mp_dry.tree_buy("no_such_node") | (待填) | | | + +## S115 _meta_settle_and_workshop(行 168–341 · 42 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 890 | 168 | "新档首通 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 | (待填) | | | +| 891 | 171 | "明细 total = keystone_total(逐杀掉落全源——无二次派生加账)" | int(d1.get("total", 0)) == int(row1.get("first_kill", 0)) | (待填) | | | +| 892 | 173 | "余额 = 累计入账(逐杀掉落合计——无重复加账)" | sv.keystone_balance() == int(row1.get("first_kill", 0)) | (待填) | | | +| 893 | 181 | "再通 L1:{first:false, amount=repeat_kill}(首杀标记置位后转小额)" | not bool(drop2.get("first", true)) and int(drop2.get("amount", 0)) == int(row1.get("repeat_kill", -1)) | (待填) | | | +| 894 | 187 | "重复杀第三轮恒定不递减(验收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)) | (待填) | | | +| 895 | 195 | "两关完成逐关归因: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)) | (待填) | | | +| 896 | 203 | "败北零关卡完成摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)" | dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before | (待填) | | | +| 897 | 210 | "条件件误购拒绝(购买件专属——契约 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") | (待填) | | | +| 898 | 213 | "余额不足购买拒绝且零副作用(余额 10 < 价 25)〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("twin_loader") and sv_u.keystone_balance() == 10 | (待填) | | | +| 899 | 215 | "钥石购买解锁:余额足→解锁态落档+余额扣减正确(价 10)〔秘宝批 V3〕" | mp_u.buy_treasure_unlock("morning_order") and sv_u.item_unlocked("morning_order") and sv_u.keystone_balance() == 0 | (待填) | | | +| 900 | 218 | "已解锁重复购买拒绝〔秘宝批 V3〕" | not mp_u.buy_treasure_unlock("morning_order") | (待填) | | | +| 901 | 220 | "条件未达不解锁(远程镐=冒险成功 1 次)〔秘宝批 V4〕" | mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick") | (待填) | | | +| 902 | 223 | "远程镐=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() | (待填) | | | +| 903 | 227 | "刷新执照=boss_kill_count≥3(阈值读 unlock_requires 行值不硬编)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("refresh_license") | (待填) | | | +| 904 | 230 | "天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕" | not sv_u.item_unlocked("sky_eye") | (待填) | | | +| 905 | 233 | "天眼双条件 AND(冒险成功≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("sky_eye") | (待填) | | | +| 906 | 236 | "信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕" | (mp_u.refresh_treasure_unlocks() as Array).has("beacon_network") | (待填) | | | +| 907 | 241 | "解锁态持久投影一致(reload 面同源读数;5 件=购买 1+条件 4)〔秘宝批 V4〕" | r_n == 5 | (待填) | | | +| 908 | 244 | "摘要最新在前 + 元素含掉落明细副本(复盘可归因——验收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 | (待填) | | | +| 909 | 253 | "摘要 cap 10 且最新在前(12 连 settle 截断后 seed 11 居首)" | sv_c.recent_summaries().size() == 10 and int(sv_c.recent_summaries()[0].get("master_seed", -1)) == 11 | (待填) | | | +| 910 | 258 | "白板档同调位求值 = 0(0 起步裁决)且 Y=0(未购无刷新能力——P1 甲初始 0 同构)" | Attunement.slots(_db, sv_a) == 0 and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 0 | (待填) | | | +| 911 | 260 | "满位禁选(Slots 0 时装配拒绝;兼未解锁拒绝前置——直置解锁后下方装配)〔秘宝批 V5〕" | mp_a.toggle_attunement("morning_order") == false | (待填) | | | +| 912 | 265 | "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 | (待填) | | | +| 913 | 270 | "完成 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕" | Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1 | (待填) | | | +| 914 | 273 | "树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)" | Attunement.slots(_db, sv_a) == 1 | (待填) | | | +| 915 | 275 | "稀有件入普通格拒绝(件档 rank 3 > 装入位序 1——契约 2 档位格校验)〔秘宝批 V5〕" | not mp_a.toggle_attunement("sky_eye") | (待填) | | | +| 916 | 277 | "装配持久写入 + 满位禁选(Slots 1 装第二件拒绝)〔秘宝批 V5 换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("twin_loader") == false | (待填) | | | +| 917 | 279 | "卸下恒可再装配(自卸自装——营地换装=冒险开始前合法面,装配不可变只锁运行中)〔秘宝批换主语〕" | mp_a.toggle_attunement("morning_order") and mp_a.toggle_attunement("morning_order") | (待填) | | | +| 918 | 284 | "装配关系读回持久(reload 后投影一致——写新件 id)〔秘宝批 V15〕" | (mp_a2.attune_camp_status()["equipped"] as Array).has("morning_order") | (待填) | | | +| 919 | 288 | "充能重置场景:装配冒险启动(seed 12)〔秘宝批 V8 换主语〕" | a12.start_adventure(12, {"attunement": ["incense_stick", "sky_eye"]}) | (待填) | | | +| 920 | 290 | "充能账本按装备 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 | (待填) | | | +| 921 | 296 | "刷新双计数器——X 优先消耗(x1y2 → x0y2)" | a12.refresh_curio() and a12._attune_refresh_x == 0 and a12._attune_refresh_y == 2 | (待填) | | | +| 922 | 298 | "刷新双计数器——X 耗尽转 Y 池、池竭拒绝(x0y2 → y0 → 第四次 false 零副作用)" | a12.refresh_curio() and a12.refresh_curio() and a12._attune_refresh_y == 0 and a12.refresh_curio() == false | (待填) | | | +| 923 | 302 | "刷新 X 每碰三选一重置(不结转;新目录无 bonus_refresh 行源——X 机制保留恒 0 空转,存量加仓走 _refresh_stock=刷新练习 V8)〔秘宝批换代〕" | a12._attune_refresh_x == 0 and a12.attune_refresh_left() == 0 | (待填) | | | +| 924 | 313 | "开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)" | rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2 | (待填) | | | +| 925 | 317 | "主菜单开新冒险参数面:同调装配 = 存档装配集(营地装了什么开局就带什么)" | (p_new.get("attunement", []) as Array) == (sv_e.attunement_equipped() as Array) | (待填) | | | +| 926 | 319 | "主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)" | int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() | (待填) | | | +| 927 | 321 | "主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)" | (p_new.get("tree_rules", []) as Array) == rules_e | (待填) | | | +| 928 | 323 | "主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入" | bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool" | (待填) | | | +| 929 | 327 | "主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)" | (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 | (待填) | | | +| 930 | 338 | "继续上局受理面:resume_adventure 受理三键参数面(尾裁微批b 受理闭环——受理不再吃白板)" | adr_res.resume_adventure(bp_min, p_resume) | (待填) | | | +| 931 | 340 | "继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)" | (adr_res._tree_rules as Array) == rules_e | (待填) | | | + +## S116 _meta_injection(行 353–383 · 10 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 932 | 353 | "生命淬炼 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)) | (待填) | | | +| 933 | 355 | "攻/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 | (待填) | | | +| 934 | 357 | "命中线在账未注入(契约 6——override 无 hit 字段〔天赋树批 V13〕)" | not mp.hero_override().has("hit") | (待填) | | | +| 935 | 360 | "注入参数冒险启动(start_adventure params 通道)" | adr.start_adventure(920251, {"hero_override": mp.hero_override()}) | (待填) | | | +| 936 | 364 | "首战 hero_atk = 表值 + 永久强化 flat(begin_battle 同表达式——下一局能用到强化)" | int(r.effective_atk()) == int(_db.hero.atk) + int(ov.get("atk", 0)) | (待填) | | | +| 937 | 366 | "首战 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)) | (待填) | | | +| 938 | 371 | "白板冒险启动(无 params 实参——B1 空参数路径)" | adr_w.start_adventure(920251) | (待填) | | | +| 939 | 374 | "零强化参数冒险启动(新档 hero_override 全零)" | adr_z.start_adventure(920251, {"hero_override": load("res://core/meta_progression.gd").new(_db, sv_white).hero_override()}) | (待填) | | | +| 940 | 378 | "白板成立:空参数 = 零强化参数 = 纯表值(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()) | (待填) | | | +| 941 | 382 | "ac 轴:白板恒表值、未换强化时注入零位移" | int(rw.effective_ac()) == int(_db.hero.armor_class) and int(r.effective_ac()) == int(_db.hero.armor_class) | (待填) | | | + +## S117 _smoke_forge_axes(行 392–540 · 33 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 942 | 392 | "hero.tres 无 attack_type/armor_type 字段(契约 2——静态表两轴删除)〔锻造工坊批 V5〕" | not ("attack_type" in _db.hero) and not ("armor_type" in _db.hero) | (待填) | | | +| 943 | 395 | "锻造配置默认值 attack_type=normal / armor_type=light(常驻持有)〔锻造工坊批 V5〕" | _db.forge_default_attack_type() == "normal" and _db.forge_default_armor_type() == "light" | (待填) | | | +| 944 | 399 | "spawn 受理(三参形态——两轴缺省回退面)" | uc.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 945 | 400 | "主角单位快照两轴 = 锻造配置默认值 normal/light(零注入回退——行为连续)〔锻造工坊批 V5〕" | String(uc.hero_unit().get("attack_type", "")) == "normal" and String(uc.hero_unit().get("armor_type", "")) == "light" | (待填) | | | +| 946 | 410 | "长剑锻造首购:扣 20 钥石、等级 0→1(Q1——第一级=获得装备=首个 +1)〔锻造工坊批 V2〕" | mp2.forge_buy_level("sword") and sv2.forge_route_level("sword") == 1 and sv2.keystone_balance() == 80 | (待填) | | | +| 947 | 414 | "购买持久(reload 后等级与余额不变——即时写盘)〔锻造工坊批 V2〕" | sv2r.forge_route_level("sword") == 1 and sv2r.keystone_balance() == 80 | (待填) | | | +| 948 | 417 | "长剑二级购:扣 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 | (待填) | | | +| 949 | 424 | "余额不足购买拒绝且零副作用(0 余额——三域零变化)〔锻造工坊批 V2〕" | not mp2d.forge_buy_level("sword") and sv2d.keystone_balance() == 0 and sv2d.forge_routes_snapshot() == fr_dry | (待填) | | | +| 950 | 429 | "已满级拒购/未解锁拒购(cap 闸+解锁闸——零副作用)〔锻造工坊批 V2〕" | not mp2.forge_buy_level("sword") and not mp2d.forge_buy_level("spear") | (待填) | | | +| 951 | 432 | "通道一:长剑练满→长枪/法杖派生解锁;轻甲未练满→重甲/魔法护甲仍锁〔锻造工坊批 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") | (待填) | | | +| 952 | 435 | "派生解锁后长枪可购(首购 20——余额 50−20)〔锻造工坊批 V3〕" | mp2.forge_buy_level("spear") and sv2.forge_route_level("spear") == 1 and sv2.keystone_balance() == 30 | (待填) | | | +| 953 | 442 | "通道二:魔法护甲一次性解锁价 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") | (待填) | | | +| 954 | 447 | "直购后练满不重复收(已解锁=拒绝零收费——两通道互不影响)〔锻造工坊批 V3〕" | mp3.forge_route_unlocked("ward") and not mp3.forge_buy_unlock("ward") and sv3.keystone_balance() == 300 | (待填) | | | +| 955 | 452 | "不设串联门槛(法杖直购开——长枪零状态不阻)〔锻造工坊批 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") | (待填) | | | +| 956 | 457 | "两币分工:购买只扣钥石(余额差=当级价 20——GP 零触碰、无材料计数)〔锻造工坊批 V4〕" | mp3.forge_buy_level("ward") and sv3.keystone_balance() == ks4 - 20 and sv3.forge_route_level("ward") == 1 | (待填) | | | +| 957 | 462 | "选中切换免费(余额零动——写存档域即盘)〔锻造工坊批 V6〕" | mp3.forge_select_armor("ward") and sv3.forge_armor() == "ward" and sv3.keystone_balance() == ks6 | (待填) | | | +| 958 | 465 | "切换后旧路线练度保留(轻甲 5 级不丢——回家待命只不生效)〔锻造工坊批 V6〕" | sv3.forge_route_level("light") == 5 | (待填) | | | +| 959 | 468 | "未解锁路线拒选零副作用〔锻造工坊批 V6〕" | not mp2d.forge_select_weapon("spear") and sv2d.forge_weapon() == "sword" | (待填) | | | +| 960 | 471 | "类型轴当前生效值随选中走(ward;未解锁档回退默认 normal/light)〔锻造工坊批 V5/V6〕" | mp3.hero_armor_type() == "ward" and mp2d.hero_attack_type() == "normal" and mp2d.hero_armor_type() == "light" | (待填) | | | +| 961 | 480 | "build_adventure_params 注入默认值 normal/light(树钥匙未点亮——新档初始持有态)〔锻造工坊批 V5〕" | String(bp_params.get("attack_type", "")) == "normal" and String(bp_params.get("armor_type", "")) == "light" | (待填) | | | +| 962 | 484 | "长枪直购解锁+首购+选中(注入面夹具——200+20 钥石)" | mp4.forge_buy_unlock("spear") and mp4.forge_buy_level("spear") and mp4.forge_select_weapon("spear") | (待填) | | | +| 963 | 487 | "build_adventure_params 读存档选中覆盖(选中长枪→pierce)〔锻造工坊批 V5〕" | String(bp_sel.get("attack_type", "")) == "pierce" and String(bp_sel.get("armor_type", "")) == "light" | (待填) | | | +| 964 | 491 | "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") | (待填) | | | +| 965 | 497 | "换当前武器→攻击加值随切换(长剑 3 级待命不生效——只认选中)〔锻造工坊批 V7〕" | int(mp4.hero_override().get("atk", -1)) == 1 | (待填) | | | +| 966 | 499 | "切回长剑→加值=3 级量(旧路线练度保留且生效)〔锻造工坊批 V7〕" | mp4.forge_select_weapon("sword") and int(mp4.hero_override().get("atk", -1)) == 3 | (待填) | | | +| 967 | 505 | "重甲直购解锁+首购+选中(三层 AC 夹具)" | mp5.forge_buy_unlock("heavy") and mp5.forge_buy_level("heavy") and mp5.forge_select_armor("heavy") | (待填) | | | +| 968 | 509 | "注入参数冒险启动(两轴+override 同面)" | adr5.start_adventure(77, p5) | (待填) | | | +| 969 | 511 | "入战 AC 三层:甲型基础 12(重甲)+路线加值 1+局内 0〔锻造工坊批 V7〕" | int(r5.effective_ac()) == 12 + 1 | (待填) | | | +| 970 | 513 | "参数面快照入 runner(attack_type=normal/armor_type=heavy)〔锻造工坊批 V6〕" | adr5.hero_attack_type == "normal" and adr5.hero_armor_type == "heavy" | (待填) | | | +| 971 | 516 | "冒险开始后锁死:改存档选中不影响参数面快照与生效值(契约 8)〔锻造工坊批 V6〕" | mp5.forge_select_armor("light") and adr5.hero_armor_type == "heavy" and int(r5.effective_ac()) == 12 + 1 | (待填) | | | +| 972 | 525 | "enc 两轴直达 spawn 英雄快照(hero_attack 行携带 pierce 键——注入链末端)〔锻造工坊批 V5〕" | pierce_rows >= 1 | (待填) | | | +| 973 | 529 | "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" | (待填) | | | +| 974 | 539 | "树零攻防来源复核(12 行 effects 键集无 atk/armor_class——契约 1)〔锻造工坊批 V10〕" | not tree_atk_ac | (待填) | | | + +## S118 _smoke_tree(行 548–621 · 25 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 975 | 548 | "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 | (待填) | | | +| 976 | 550 | "MetaTreeTable.validate 零违例(脉/类型/币词表出域即拒/Fork 三组每组恰一且持 a/b 双效果键/彩蛋 ≤3/effects 键封闭/零 archetype)" | _db.meta_tree.validate().is_empty() | (待填) | | | +| 977 | 558 | "easter_ley effects 键集 ⊆ {max_hp, hit}(攻/护分量已摘除——契约 9)〔天赋树批 V8 落点〕" | ley_ok | (待填) | | | +| 978 | 566 | "schema 反例:vein 出域被拒" | not t2.validate().is_empty() | (待填) | | | +| 979 | 571 | "schema 反例:fork_group 出域(fg_mastery 已退役组)被拒" | not t2.validate().is_empty() | (待填) | | | +| 980 | 576 | "schema 反例:effects.archetype 加载致命(🔴 层级纪律护栏面)〔天赋树批 V4〕" | not t2.validate().is_empty() | (待填) | | | +| 981 | 581 | "schema 反例:requires 悬空被拒" | not t2.validate().is_empty() | (待填) | | | +| 982 | 586 | "树购买受理(脊柱生命 L1——树全钥石单币)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 1 | (待填) | | | +| 983 | 588 | "行门槛:第 2 级不可购(总等级 1 < 4——一线一节点主语,买第 N 级需全树总投入 ≥4×(N−1))〔天赋树批 V2〕" | not mp.tree_can_buy("sp_hp") | (待填) | | | +| 984 | 590 | "命中 L1/锻造钥匙(free 无前置=初始零购零币可点亮)/同调位 1(根节点无前置)受理(总等级 1→4)" | mp.tree_buy("sp_hit") and mp.tree_buy("forge_workshop") and mp.tree_buy("att_socket1") | (待填) | | | +| 985 | 592 | "锻造工坊钥匙零币(free)" | int(_db.tree_node("forge_workshop").get("price", -1)) == 0 | (待填) | | | +| 986 | 593 | "行门槛恰达开放(总等级 4 ≥ 4×1)" | mp.tree_can_buy("sp_hp") | (待填) | | | +| 987 | 594 | "第 2 级购买受理(门槛恰达当刻开放)" | mp.tree_buy("sp_hp") and sv.tree_level("sp_hp") == 2 | (待填) | | | +| 988 | 598 | "Fork 购买=解锁+默认 A 态(一节点双态——单行承载)" | mp.tree_buy_fork("fork_posture") and sv.tree_level("fork_posture") == 1 and sv.fork_side("fg_posture") == "a" | (待填) | | | +| 989 | 600 | "已解锁 Fork 重复购买拒绝(cap 1)" | not mp.tree_can_buy("fork_posture") | (待填) | | | +| 990 | 601 | "同态重复切换零操作(false)" | not mp.tree_toggle_fork("fg_posture", "a") | (待填) | | | +| 991 | 602 | "未解锁 Fork 切换拒绝(解锁唯一入口=购买面)" | not mp.tree_toggle_fork("fg_sense", "b") | (待填) | | | +| 992 | 604 | "零币切态 b(购买解锁后 A/B 可切)" | mp.tree_toggle_fork("fg_posture", "b") and sv.fork_side("fg_posture") == "b" | (待填) | | | +| 993 | 606 | "切态零消费(钥石余额差分不动)" | int(sv.keystone_balance()) == bal_pre_toggle | (待填) | | | +| 994 | 607 | "规则键投影=激活态效果键(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") | (待填) | | | +| 995 | 609 | "切回 A 态键集随换(六键词表在册——V11 校验面归单元段)" | mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike") | (待填) | | | +| 996 | 612 | "彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)" | not mp.tree_can_buy("easter_ley") | (待填) | | | +| 997 | 613 | "觉醒前置未满足不亮(两线未拉满)" | sv.tree_level("easter_ley") == 0 | (待填) | | | +| 998 | 618 | "两线拉满+同调位全解锁 → 地脉共鸣自动点亮(零币特化判定)" | sv.tree_level("easter_ley") == 1 | (待填) | | | +| 999 | 621 | "觉醒零消费(钥石余额不动)" | int(sv.keystone_balance()) == bal_now | (待填) | | | -## S119 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S119 _smoke_tree_rules(行 633–757 · 21 条 · 源 game/tests/unit/core/meta_progression_forge_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1018 | 22 | "数值表加载" | false | (待填) | | | -| 1019 | 30 | "生成返回棋盘(前置)" | false | (待填) | | | +| 1000 | 633 | "先手压制:每关首战首轮窗命中(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 | (待填) | | | +| 1001 | 635 | "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)" | (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() | (待填) | | | +| 1002 | 638 | "稳健拓荒:首轮窗内受击出盾、窗外不出(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() | (待填) | | | +| 1003 | 654 | "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)" | got_mult and got_add | (待填) | | | +| 1004 | 657 | "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕" | (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | +| 1005 | 663 | "锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕" | (fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty() | (待填) | | | +| 1006 | 685 | "锋:惊巢反制 mine_hit 减半分支受理(wa12 前提——halve_pct=50 效果行落 step_row)〔wa12〕" | nc_hit != "" | (待填) | | | +| 1007 | 687 | "惊巢反制效果行==「惊巢反制——危险格挖掘惩罚减半(6→3)」零「挖雷」(u13 换代)〔wa12〕" | nc_hit == "惊巢反制——危险格挖掘惩罚减半(6→3)" and not nc_hit.contains("挖雷") | (待填) | | | +| 1008 | 691 | "守侧 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"}) | (待填) | | | +| 1009 | 703 | "守侧 rig:安全格定位(非危险格=标错负样本)" | safe.x >= 0 | (待填) | | | +| 1010 | 704 | "守侧 rig:首轮后 queue 干净(开局 posture queue 已兑现)" | int(orch.action_economy.pending_ap_delta()) == 0 | (待填) | | | +| 1011 | 708 | "守①:当回合标错且封印在场 → 下轮 AP+1(queue 排队——本轮 AP 逐位零变化)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1012 | 713 | "守③④:旧封印重判零触发+同格最多一次(_seal_miss_seen 去重闸=「当回合标错动作」判别器)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1013 | 717 | "守②负样本:标中/被挖封印零触发(标错事件只在 miss 分支发射——发射面过滤承载)" | int(orch.action_economy.pending_ap_delta()) == 1 | (待填) | | | +| 1014 | 721 | "守⑤ 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"}) | (待填) | | | +| 1015 | 727 | "守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)" | int(orch2.action_economy.pending_ap_delta()) == 0 | (待填) | | | +| 1016 | 733 | "散未激活:商品标价原价(无 economy_spend 键)" | int(lr._spend_discounted(100)) == 100 | (待填) | | | +| 1017 | 735 | "散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)" | int(lr._spend_discounted(100)) == 80 and int(lr._spend_discounted(1)) == 1 | (待填) | | | +| 1018 | 739 | "聚 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"}) | (待填) | | | +| 1019 | 748 | "聚 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"}) | (待填) | | | +| 1020 | 756 | "聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)" | gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base | (待填) | | | -## S120 _test_install_invariants(行 48–56 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S120 _init(行 22–30 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1020 | 48 | "装板后 current_board 即所装棋盘" | _rules.current_board() == _board | (待填) | | | -| 1021 | 52 | "装板带初始扩张揭示(%d 格,非零且未铺满安全格 %d)" ↩ % [opened, safe_total] | opened > 0 and opened < safe_total | (待填) | | | -| 1022 | 54 | "安全格余量 = 安全格总数 − 已揭示数(%d − %d)" ↩ % [safe_total, opened] | _rules.safe_remaining() == safe_total - opened | (待填) | | | -| 1023 | 56 | "装板后仍有未揭示格" | _rules.has_unrevealed() | (待填) | | | +| 1021 | 22 | "数值表加载" | false | (待填) | | | +| 1022 | 30 | "生成返回棋盘(前置)" | false | (待填) | | | -## S121 _test_dig_face_rules(行 73–80 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S121 _test_install_invariants(行 48–56 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1024 | 73 | "挖已揭示格被拒(board_face=revealed,禁重复挖)——载荷 %s" ↩ % str(o.get("sub_reason", "")) | String(o.get("outcome", "")) == "rejected" and String(o.get("sub_reason", "")) == "target_revealed" | (待填) | | | -| 1025 | 79 | "挖非相邻格被拒(邻接规则)——载荷 %s" ↩ % str(o2.get("sub_reason", "")) | String(o2.get("outcome", "")) == "rejected" | (待填) | | | +| 1023 | 48 | "装板后 current_board 即所装棋盘" | _rules.current_board() == _board | (待填) | | | +| 1024 | 52 | "装板带初始扩张揭示(%d 格,非零且未铺满安全格 %d)" ↩ % [opened, safe_total] | opened > 0 and opened < safe_total | (待填) | | | +| 1025 | 54 | "安全格余量 = 安全格总数 − 已揭示数(%d − %d)" ↩ % [safe_total, opened] | _rules.safe_remaining() == safe_total - opened | (待填) | | | +| 1026 | 56 | "装板后仍有未揭示格" | _rules.has_unrevealed() | (待填) | | | -## S122 _test_reveal_advances(行 86–93 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S122 _test_dig_face_rules(行 73–80 · 2 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1026 | 86 | "存在未揭示安全格(揭示推进前置)" | false | (待填) | | | -| 1027 | 90 | "专长揭示受理(fx_reveal 不受邻接约束)" | ok | (待填) | | | -| 1028 | 91 | "fx_reveal 后该格已揭示" | _rules.is_revealed(c) | (待填) | | | -| 1029 | 92 | "fx_reveal 后安全格余量递减(%d → %d)" ↩ % [before, _rules.safe_remaining()] | _rules.safe_remaining() < before | (待填) | | | +| 1027 | 73 | "挖已揭示格被拒(board_face=revealed,禁重复挖)——载荷 %s" ↩ % str(o.get("sub_reason", "")) | String(o.get("outcome", "")) == "rejected" and String(o.get("sub_reason", "")) == "target_revealed" | (待填) | | | +| 1028 | 79 | "挖非相邻格被拒(邻接规则)——载荷 %s" ↩ % str(o2.get("sub_reason", "")) | String(o2.get("outcome", "")) == "rejected" | (待填) | | | -## S123 _test_seal_place(行 99–105 · 3 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S123 _test_reveal_advances(行 86–93 · 4 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1030 | 99 | "存在未揭示安全格(封印前置)" | false | (待填) | | | -| 1031 | 104 | "封印后该格在封印态" | _rules.has_seal(c) | (待填) | | | -| 1032 | 105 | "封印数计数 = 1" | _rules.seal_count() == 1 | (待填) | | | +| 1029 | 86 | "存在未揭示安全格(揭示推进前置)" | false | (待填) | | | +| 1030 | 90 | "专长揭示受理(fx_reveal 不受邻接约束)" | ok | (待填) | | | +| 1031 | 91 | "fx_reveal 后该格已揭示" | _rules.is_revealed(c) | (待填) | | | +| 1032 | 92 | "fx_reveal 后安全格余量递减(%d → %d)" ↩ % [before, _rules.safe_remaining()] | _rules.safe_remaining() < before | (待填) | | | -## S124 _test_reject_out_of_catalog(行 111–112 · 1 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) +## S124 _test_seal_place(行 99–105 · 3 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1033 | 111 | "目录外意图被拒(不静默吞掉)——实际载荷 %s" ↩ % str(o).substr(0, 80) | out.size() == 1 and String(o.get("outcome", o.get("result", ""))) == "rejected" | (待填) | | | +| 1033 | 99 | "存在未揭示安全格(封印前置)" | false | (待填) | | | +| 1034 | 104 | "封印后该格在封印态" | _rules.has_seal(c) | (待填) | | | +| 1035 | 105 | "封印数计数 = 1" | _rules.seal_count() == 1 | (待填) | | | -## S125 _test_output_ok(行 88–89 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S125 _test_reject_out_of_catalog(行 111–112 · 1 条 · 源 game/tests/unit/core/minesweeper_rules_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1034 | 88 | "三档×20 种子产出图过产出约束五条+结构铁律(自证零错误)〔fv1〕" | all_ok | (待填) | | | -| 1035 | 89 | "入口数/终点数 = 参数档 entry_count/exit_count〔fv1〕" | entry_exit_ok | (待填) | | | +| 1036 | 111 | "目录外意图被拒(不静默吞掉)——实际载荷 %s" ↩ % str(o).substr(0, 80) | out.size() == 1 and String(o.get("outcome", o.get("result", ""))) == "rejected" | (待填) | | | -## S126 _test_reproducible(行 99–119 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S126 _test_output_ok(行 88–89 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1036 | 99 | "同(种子,参数档)两次产图逐位同图〔fv3〕" | same | (待填) | | | -| 1037 | 109 | "counter 无关(不同号位态同种子同图)〔fv3〕" | _sig(gen_a.generate(_params(2), 42)) == _sig(gen_b.generate(_params(2), 42)) and _sig(gen_a.generate(_params(3), 7)) == _sig(gen_b.generate(_params(3), 7)) | (待填) | | | -| 1038 | 119 | "同参数档不同种子产出异图(10 种子去重数 ≥ 8)〔fv3〕" | distinct >= 8 | (待填) | | | +| 1037 | 88 | "三档×20 种子产出图过产出约束五条+结构铁律(自证零错误)〔fv1〕" | all_ok | (待填) | | | +| 1038 | 89 | "入口数/终点数 = 参数档 entry_count/exit_count〔fv1〕" | entry_exit_ok | (待填) | | | -## S127 _test_fork_rules(行 163–164 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S127 _test_reproducible(行 99–119 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1039 | 163 | "任意岔口各去路下一节点类型互不相同〔fv5〕" | next_hop_ok | (待填) | | | -| 1040 | 164 | "任意岔口各支路类型构成互异且各有独有类型〔fv5〕" | comp_ok | (待填) | | | +| 1039 | 99 | "同(种子,参数档)两次产图逐位同图〔fv3〕" | same | (待填) | | | +| 1040 | 109 | "counter 无关(不同号位态同种子同图)〔fv3〕" | _sig(gen_a.generate(_params(2), 42)) == _sig(gen_b.generate(_params(2), 42)) and _sig(gen_a.generate(_params(3), 7)) == _sig(gen_b.generate(_params(3), 7)) | (待填) | | | +| 1041 | 119 | "同参数档不同种子产出异图(10 种子去重数 ≥ 8)〔fv3〕" | distinct >= 8 | (待填) | | | -## S128 _test_shop_and_attribution(行 228–230 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S128 _test_fork_rules(行 163–164 · 2 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1041 | 228 | "任意入口→终点路径恰过一家店〔fv6〕" | paths_ok | (待填) | | | -| 1042 | 229 | "每终点前一位恰一店+商店总数=终点数(归属锁定)〔fv6〕" | lock_ok | (待填) | | | -| 1043 | 230 | "多终点落在互异节点深度(L2/L3)〔fv6〕" | depth_ok | (待填) | | | +| 1042 | 163 | "任意岔口各去路下一节点类型互不相同〔fv5〕" | next_hop_ok | (待填) | | | +| 1043 | 164 | "任意岔口各支路类型构成互异且各有独有类型〔fv5〕" | comp_ok | (待填) | | | -## S129 _test_battle_floor(行 257–257 · 1 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S129 _test_shop_and_attribution(行 228–230 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1044 | 257 | "任意合法产出战斗节点数 ≥ battle_floor〔fv7〕" | ok | (待填) | | | +| 1044 | 228 | "任意入口→终点路径恰过一家店〔fv6〕" | paths_ok | (待填) | | | +| 1045 | 229 | "每终点前一位恰一店+商店总数=终点数(归属锁定)〔fv6〕" | lock_ok | (待填) | | | +| 1046 | 230 | "多终点落在互异节点深度(L2/L3)〔fv6〕" | depth_ok | (待填) | | | -## S130 _test_exit_structure(行 285–287 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S130 _test_battle_floor(行 257–257 · 1 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1045 | 285 | "三关参数档生成图各含 ≥1 岔口节点(第一关即含)〔fv9〕" | fork_all | (待填) | | | -| 1046 | 286 | "L1 生成图终点恰 1(出口恒 1)〔fv9〕" | l1_single | (待填) | | | -| 1047 | 287 | "L2/L3 含分区域大岔口(一节点可达 ≥2 终点且去路数=出口数)〔fv9〕" | big_fork_ok | (待填) | | | +| 1047 | 257 | "任意合法产出战斗节点数 ≥ battle_floor〔fv7〕" | ok | (待填) | | | -## S131 _test_rng_single_point(行 313–318 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S131 _test_exit_structure(行 285–287 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1048 | 313 | "pure_stream 不推全局计数器(产图前后 counter 同值)〔fv10〕" | svc.counter() == before | (待填) | | | -| 1049 | 316 | "pure_stream 同 (seed,scope) 同流〔fv10〕" | a.randf() == b.randf() and a.randi() == b.randi() | (待填) | | | -| 1050 | 317 | "pure_stream 不推计数器且产图非空〔fv10〕" | svc.counter() == before and (g["nodes"] as Array).size() > 0 | (待填) | | | +| 1048 | 285 | "三关参数档生成图各含 ≥1 岔口节点(第一关即含)〔fv9〕" | fork_all | (待填) | | | +| 1049 | 286 | "L1 生成图终点恰 1(出口恒 1)〔fv9〕" | l1_single | (待填) | | | +| 1050 | 287 | "L2/L3 含分区域大岔口(一节点可达 ≥2 终点且去路数=出口数)〔fv9〕" | big_fork_ok | (待填) | | | -## S132 _test_negative_fixtures(行 328–412 · 13 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) +## S132 _test_rng_single_point(行 313–318 · 3 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1051 | 328 | "句柄重复夹具报 dup_key〔fv1〕" | _has_cat(_gen.validate_output(dup), "dup_key") | (待填) | | | -| 1052 | 332 | "悬空出边夹具报 dangling_next〔fv1〕" | _has_cat(_gen.validate_output(dang), "dangling_next") | (待填) | | | -| 1053 | 336 | "类型出域夹具报 enum〔fv1〕" | _has_cat(_gen.validate_output(badty), "enum") | (待填) | | | -| 1054 | 343 | "战斗节点低于 battle_floor 的合法形状夹具命中「低于保底」判据〔fv7〕" | _has_msg(_gen.validate_output(thin_battle, {"battle_floor": 4}), "低于保底") | (待填) | | | -| 1055 | 345 | "同夹具在保底 1 下自证零错误(判据不误伤)〔fv7〕" | _gen.validate_output(thin_battle, {"battle_floor": 1}).is_empty() | (待填) | | | -| 1056 | 350 | "非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕" | _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss") | (待填) | | | -| 1057 | 361 | "合法合流图(第二入口直插主链,深度取最长路 3)自证零错误〔fv1〕" | _gen.validate_output(conf_legal).is_empty() | (待填) | | | -| 1058 | 370 | "声明深度高于最长路径步数被拒(深度=自入口最长路径步数)〔fv1〕" | _has_msg(_gen.validate_output(conf_high), "自入口最长路径步数") | (待填) | | | -| 1059 | 377 | "回边夹具报前进方向违例(structural)〔fv1〕" | _has_cat(_gen.validate_output(loop), "structural") | (待填) | | | -| 1060 | 387 | "多终点同深度夹具报 structural〔fv6〕" | _has_cat(_gen.validate_output(twin), "structural") | (待填) | | | -| 1061 | 397 | "双终点异深+区域类型互异合法夹具自证零错误〔fv6〕" | _gen.validate_output(twin_ok).is_empty() | (待填) | | | -| 1062 | 404 | "终点前驱非店夹具报 structural(必经恰一店/商店数)〔fv6〕" | _has_cat(cats, "structural") | (待填) | | | -| 1063 | 412 | "岔口同类型下一跳夹具报 structural〔fv5〕" | _has_cat(_gen.validate_output(sameHop), "structural") | (待填) | | | +| 1051 | 313 | "pure_stream 不推全局计数器(产图前后 counter 同值)〔fv10〕" | svc.counter() == before | (待填) | | | +| 1052 | 316 | "pure_stream 同 (seed,scope) 同流〔fv10〕" | a.randf() == b.randf() and a.randi() == b.randi() | (待填) | | | +| 1053 | 317 | "pure_stream 不推计数器且产图非空〔fv10〕" | svc.counter() == before and (g["nodes"] as Array).size() > 0 | (待填) | | | -## S133 _test_growth(行 57–58 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S133 _test_negative_fixtures(行 328–412 · 13 条 · 源 game/tests/unit/core/node_map_generator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1064 | 57 | "生长递增:三档最长路径步数逐关变大〔fv8〕" | steps_ok | (待填) | | | -| 1065 | 58 | "生长递增:节点总数逐关变大且增幅变大〔fv8〕" | count_ok | (待填) | | | +| 1054 | 328 | "句柄重复夹具报 dup_key〔fv1〕" | _has_cat(_gen.validate_output(dup), "dup_key") | (待填) | | | +| 1055 | 332 | "悬空出边夹具报 dangling_next〔fv1〕" | _has_cat(_gen.validate_output(dang), "dangling_next") | (待填) | | | +| 1056 | 336 | "类型出域夹具报 enum〔fv1〕" | _has_cat(_gen.validate_output(badty), "enum") | (待填) | | | +| 1057 | 343 | "战斗节点低于 battle_floor 的合法形状夹具命中「低于保底」判据〔fv7〕" | _has_msg(_gen.validate_output(thin_battle, {"battle_floor": 4}), "低于保底") | (待填) | | | +| 1058 | 345 | "同夹具在保底 1 下自证零错误(判据不误伤)〔fv7〕" | _gen.validate_output(thin_battle, {"battle_floor": 1}).is_empty() | (待填) | | | +| 1059 | 350 | "非 Boss 终点夹具命中「终点(无出边)非 Boss」判据〔fv1〕" | _has_msg(_gen.validate_output(noBoss), "终点(无出边)非 Boss") | (待填) | | | +| 1060 | 361 | "合法合流图(第二入口直插主链,深度取最长路 3)自证零错误〔fv1〕" | _gen.validate_output(conf_legal).is_empty() | (待填) | | | +| 1061 | 370 | "声明深度高于最长路径步数被拒(深度=自入口最长路径步数)〔fv1〕" | _has_msg(_gen.validate_output(conf_high), "自入口最长路径步数") | (待填) | | | +| 1062 | 377 | "回边夹具报前进方向违例(structural)〔fv1〕" | _has_cat(_gen.validate_output(loop), "structural") | (待填) | | | +| 1063 | 387 | "多终点同深度夹具报 structural〔fv6〕" | _has_cat(_gen.validate_output(twin), "structural") | (待填) | | | +| 1064 | 397 | "双终点异深+区域类型互异合法夹具自证零错误〔fv6〕" | _gen.validate_output(twin_ok).is_empty() | (待填) | | | +| 1065 | 404 | "终点前驱非店夹具报 structural(必经恰一店/商店数)〔fv6〕" | _has_cat(cats, "structural") | (待填) | | | +| 1066 | 412 | "岔口同类型下一跳夹具报 structural〔fv5〕" | _has_cat(_gen.validate_output(sameHop), "structural") | (待填) | | | -## S134 _test_fail_fast_static(行 64–74 · 3 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S134 _test_growth(行 57–58 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1066 | 64 | "生产三档参数档可行性判定零错误〔fv4〕" | NodeMapGenerator.check_params(db.level_gen_params_row(1)).is_empty() and NodeMapGenerator.check_params(db.level_gen_params_row(3)).is_empty() | (待填) | | | -| 1067 | 70 | "层深 6 放三出口无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(unsolvable).is_empty() | (待填) | | | -| 1068 | 74 | "战斗保底超可放数无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(overfloor).is_empty() | (待填) | | | +| 1067 | 57 | "生长递增:三档最长路径步数逐关变大〔fv8〕" | steps_ok | (待填) | | | +| 1068 | 58 | "生长递增:节点总数逐关变大且增幅变大〔fv8〕" | count_ok | (待填) | | | -## S135 _test_fatal_subprocess(行 82–91 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) +## S135 _test_fail_fast_static(行 64–74 · 3 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1069 | 82 | "未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕" | false | (待填) | | | -| 1070 | 91 | "无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕" | code == 1 | (待填) | | | +| 1069 | 64 | "生产三档参数档可行性判定零错误〔fv4〕" | NodeMapGenerator.check_params(db.level_gen_params_row(1)).is_empty() and NodeMapGenerator.check_params(db.level_gen_params_row(3)).is_empty() | (待填) | | | +| 1070 | 70 | "层深 6 放三出口无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(unsolvable).is_empty() | (待填) | | | +| 1071 | 74 | "战斗保底超可放数无解 → check_params 拦截〔fv4〕" | not NodeMapGenerator.check_params(overfloor).is_empty() | (待填) | | | -## S136 _smoke_save(行 81–329 · 60 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) +## S136 _test_fatal_subprocess(行 82–91 · 2 条 · 源 game/tests/unit/core/node_map_growth_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1071 | 81 | "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 | (待填) | | | -| 1072 | 83 | "首杀标记随关卡完成置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | (待填) | | | -| 1073 | 85 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、冒险成功局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | (待填) | | | -| 1074 | 87 | "写盘成功" | w.write_to_disk() | (待填) | | | -| 1075 | 89 | "读回成功" | r.load_from_disk() | (待填) | | | -| 1076 | 90 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | (待填) | | | -| 1077 | 91 | "树域往返相等(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" | (待填) | | | -| 1078 | 93 | "锻造工坊域往返相等(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" | (待填) | | | -| 1079 | 97 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | (待填) | | | -| 1080 | 98 | "关卡完成次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | (待填) | | | -| 1081 | 99 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | (待填) | | | -| 1082 | 101 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | (待填) | | | -| 1083 | 103 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | (待填) | | | -| 1084 | 108 | "新档合法态(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() | (待填) | | | -| 1085 | 111 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | (待填) | | | -| 1086 | 117 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | (待填) | | | -| 1087 | 118 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | (待填) | | | -| 1088 | 126 | "高版本拒读(load false)" | not f5.load_from_disk() | (待填) | | | -| 1089 | 127 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | -| 1090 | 128 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | -| 1091 | 137 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | (待填) | | | -| 1092 | 139 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | (待填) | | | -| 1093 | 140 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | (待填) | | | -| 1094 | 151 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | (待填) | | | -| 1095 | 153 | "迁移链 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 | (待填) | | | -| 1096 | 165 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | (待填) | | | -| 1097 | 173 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | (待填) | | | -| 1098 | 174 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | (待填) | | | -| 1099 | 182 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | (待填) | | | -| 1100 | 193 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | (待填) | | | -| 1101 | 195 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | (待填) | | | -| 1102 | 197 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | (待填) | | | -| 1103 | 201 | "断点七字段往返:属性加成区" | 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 | (待填) | | | -| 1104 | 204 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | (待填) | | | -| 1105 | 206 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | (待填) | | | -| 1106 | 208 | "断点七字段往返:生成图代差标记键" | int(bp.get("graph_gen_v2", 0)) == 1 | (待填) | | | -| 1107 | 209 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | (待填) | | | -| 1108 | 213 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | (待填) | | | -| 1109 | 216 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | (待填) | | | -| 1110 | 219 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | (待填) | | | -| 1111 | 222 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | (待填) | | | -| 1112 | 224 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | (待填) | | | -| 1113 | 229 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | (待填) | | | -| 1114 | 231 | "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 | (待填) | | | -| 1115 | 243 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | (待填) | | | -| 1116 | 245 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | (待填) | | | -| 1117 | 248 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | (待填) | | | -| 1118 | 257 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | (待填) | | | -| 1119 | 260 | "升档幂等: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 | (待填) | | | -| 1120 | 268 | "收束自增恰一·冒险成功局(三关全通 cleared=true:总局数+1 且冒险成功局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | (待填) | | | -| 1121 | 272 | "收束自增恰一·中止局(放弃局:仅总局数+1,冒险成功局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | (待填) | | | -| 1122 | 283 | "换代前结构存档:树域废弃重置(旧节点 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() | (待填) | | | -| 1123 | 288 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | (待填) | | | -| 1124 | 297 | "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 | (待填) | | | -| 1125 | 300 | "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" | (待填) | | | -| 1126 | 307 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | (待填) | | | -| 1127 | 311 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | -| 1128 | 316 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | (待填) | | | -| 1129 | 324 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、冒险成功局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | (待填) | | | -| 1130 | 328 | "收束链自增·冒险成功局(三关全通:总局数+1 且冒险成功局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | (待填) | | | - -## S137 _smoke_save_slots(行 359–499 · 31 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1131 | 359 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | (待填) | | | -| 1132 | 362 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | (待填) | | | -| 1133 | 366 | "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) | (待填) | | | -| 1134 | 372 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | (待填) | | | -| 1135 | 373 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | (待填) | | | -| 1136 | 377 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | (待填) | | | -| 1137 | 378 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | (待填) | | | -| 1138 | 379 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | (待填) | | | -| 1139 | 381 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | (待填) | | | -| 1140 | 385 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | (待填) | | | -| 1141 | 386 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | (待填) | | | -| 1142 | 388 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | (待填) | | | -| 1143 | 397 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | (待填) | | | -| 1144 | 398 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | (待填) | | | -| 1145 | 402 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | (待填) | | | -| 1146 | 411 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | (待填) | | | -| 1147 | 414 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | (待填) | | | -| 1148 | 415 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | (待填) | | | -| 1149 | 418 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | (待填) | | | -| 1150 | 429 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | (待填) | | | -| 1151 | 444 | "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 | (待填) | | | -| 1152 | 447 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | (待填) | | | -| 1153 | 449 | "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 | (待填) | | | -| 1154 | 458 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | (待填) | | | -| 1155 | 459 | "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") | (待填) | | | -| 1156 | 469 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | (待填) | | | -| 1157 | 470 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | (待填) | | | -| 1158 | 483 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | (待填) | | | -| 1159 | 485 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | (待填) | | | -| 1160 | 494 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | (待填) | | | -| 1161 | 497 | "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 | (待填) | | | - -## S138 _init(行 14–15 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | -|---|---|---|---|---|---|---| -| 1162 | 14 | "全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -| 1163 | 15 | "is_ready 通道随加载置位" | db.is_ready | (待填) | | | - -## S139 _test_progress_resolution(行 55–83 · 7 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +| 1072 | 82 | "未找到引擎二进制,致命子进程通道未跑(如实标注)〔fv4〕" | false | (待填) | | | +| 1073 | 91 | "无解参数档置致命封印并由组合根停机(退出码恰 1,无残图交付)〔fv4〕" | code == 1 | (待填) | | | + +## S137 _smoke_save(行 81–329 · 60 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1074 | 81 | "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 | (待填) | | | +| 1075 | 83 | "首杀标记随关卡完成置位(1/2 有、3 无)" | w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3) | (待填) | | | +| 1076 | 85 | "生涯终身计数随 apply 自增恰一(中止局:总局数 1、冒险成功局数 0——迭代 18 卡④)" | w.adventure_total() == 1 and w.adventure_cleared() == 0 | (待填) | | | +| 1077 | 87 | "写盘成功" | w.write_to_disk() | (待填) | | | +| 1078 | 89 | "读回成功" | r.load_from_disk() | (待填) | | | +| 1079 | 90 | "钥石余额往返相等(10)" | r.keystone_balance() == 10 | (待填) | | | +| 1080 | 91 | "树域往返相等(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" | (待填) | | | +| 1081 | 93 | "锻造工坊域往返相等(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" | (待填) | | | +| 1082 | 97 | "道具解锁往返相等" | r.item_unlocked("probe_compass") and not r.item_unlocked("rope") | (待填) | | | +| 1083 | 98 | "关卡完成次数表往返相等" | r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0 | (待填) | | | +| 1084 | 99 | "首杀标记往返相等" | r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3) | (待填) | | | +| 1085 | 101 | "生涯终身计数往返相等(1/0)" | r.adventure_total() == 1 and r.adventure_cleared() == 0 | (待填) | | | +| 1086 | 103 | "冒险摘要往返相等(1 条,seed 99)" | r.recent_summaries().size() == 1 and int(s0.get("master_seed", -1)) == 99 and int(s0.get("keystone_total", -1)) == 3 | (待填) | | | +| 1087 | 108 | "新档合法态(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() | (待填) | | | +| 1088 | 111 | "新档生涯终身计数缺省全零(迭代 18 卡④两新域——白板存档完整可玩)" | f2.adventure_total() == 0 and f2.adventure_cleared() == 0 | (待填) | | | +| 1089 | 117 | "余额 5 花 8 拒绝且余额不动" | not f3.spend_keystone(8) and f3.keystone_balance() == 5 | (待填) | | | +| 1090 | 118 | "余额内消费成功(5→2)" | f3.spend_keystone(3) and f3.keystone_balance() == 2 | (待填) | | | +| 1091 | 126 | "高版本拒读(load false)" | not f5.load_from_disk() | (待填) | | | +| 1092 | 127 | "拒读后原文件字节不动" | FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | +| 1093 | 128 | "拒读态写盘拒绝且文件仍字节不动" | not f5.write_to_disk() and FileAccess.open(paths[3], FileAccess.READ).get_as_text() == hv_text | (待填) | | | +| 1094 | 137 | "损坏档兜底(默认数据继续)" | f6.load_from_disk() and f6.keystone_balance() == 0 and f6.recent_summaries().is_empty() | (待填) | | | +| 1095 | 139 | "坏档备份 .corrupt.bak 存在" | FileAccess.file_exists(paths[4] + ".corrupt.bak") | (待填) | | | +| 1096 | 140 | "备份内容 = 原坏档字节" | FileAccess.open(paths[4] + ".corrupt.bak", FileAccess.READ).get_as_text() == junk | (待填) | | | +| 1097 | 151 | "v0 旧档经迁移链入账(keystone_old 12 → 余额 12)" | f7.load_from_disk() and f7.keystone_balance() == 12 | (待填) | | | +| 1098 | 153 | "迁移链 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 | (待填) | | | +| 1099 | 165 | "域界干净——meta 域无局内资源键(发现 %s)" ↩ % str(leaked) | leaked.is_empty() | (待填) | | | +| 1100 | 173 | "摘要 cap 10(12 份 → 10 条)" | arr.size() == 10 | (待填) | | | +| 1101 | 174 | "最新在前(头 seed 11 / 尾 seed 2)" | int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2 | (待填) | | | +| 1102 | 182 | "断点:新档无断点(合法空态——「继续上局」隐藏)" | not fbp.has_breakpoint() | (待填) | | | +| 1103 | 193 | "断点:写入+写盘成功" | fbp.has_breakpoint() and fbp.write_to_disk() | (待填) | | | +| 1104 | 195 | "断点:读回在档" | rb.load_from_disk() and rb.has_breakpoint() | (待填) | | | +| 1105 | 197 | "断点七字段往返:关卡序号/节点位/生命" | int(bp.get("level_index", -1)) == 2 and int(bp.get("node_id", -1)) == 5 and int(bp.get("hero_hp", -1)) == 13 | (待填) | | | +| 1106 | 201 | "断点七字段往返:属性加成区" | 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 | (待填) | | | +| 1107 | 204 | "断点七字段往返:GP 钱包" | int(bp.get("gp", -1)) == 17 | (待填) | | | +| 1108 | 206 | "断点七字段往返:商店已购档" | int(bp_shop.get("hp_small", -1)) == 2 and int(bp_shop.get("atk_small", -1)) == 1 | (待填) | | | +| 1109 | 208 | "断点七字段往返:生成图代差标记键" | int(bp.get("graph_gen_v2", 0)) == 1 | (待填) | | | +| 1110 | 209 | "断点附随件往返:主种子/计数器/钥石累计" | int(bp.get("master_seed", -1)) == 424242 and int(bp.get("rng_counter", -1)) == 38 and int(bp.get("keystone", -1)) == 6 | (待填) | | | +| 1111 | 213 | "断点附随件往返:已到节点序" | bp_visited.size() == 3 and int(bp_visited[0]) == 0 and int(bp_visited[2]) == 5 | (待填) | | | +| 1112 | 216 | "断点附随件往返:道具库存" | bp_treasure.size() == 1 and String(bp_treasure[0].get("item_id", "")) == "aux_scout" | (待填) | | | +| 1113 | 219 | "断点读面值拷贝(改读面不渗档)" | int((rb.breakpoint_snapshot() as Dictionary).get("hero_hp", -1)) == 13 | (待填) | | | +| 1114 | 222 | "断点:清除+写盘(三结局收束口径)" | not rb.has_breakpoint() and rb.write_to_disk() | (待填) | | | +| 1115 | 224 | "断点:清除后读回无断点" | rc.load_from_disk() and not rc.has_breakpoint() | (待填) | | | +| 1116 | 229 | "v1 旧档迁移 v7:断点域补空(合法空态,无断点域不炸)" | fo.load_from_disk() and fo.keystone_balance() == 4 and not fo.has_breakpoint() | (待填) | | | +| 1117 | 231 | "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 | (待填) | | | +| 1118 | 243 | "v2 旧档迁移 v3:断点域与 meta 域原样保留(缺新附随件=合法空态)" | fv2.load_from_disk() and fv2.has_breakpoint() and fv2.keystone_balance() == 7 | (待填) | | | +| 1119 | 245 | "v2 旧断点字段零丢失(向后兼容——breakpoint 内容不因 schema 升版重写)" | int((fv2.breakpoint_snapshot() as Dictionary).get("master_seed", 0)) == 777 | (待填) | | | +| 1120 | 248 | "v2 旧断点续玩受理(缺 bonus 附随件走常规重建路径——g 裁 semver 口径)" | adr_v2.resume_adventure(fv2.breakpoint_snapshot(), {"hero_override": {}}) | (待填) | | | +| 1121 | 257 | "v5 旧档升 v7:计数域缺省补零且既有余额原样(迁移只增字段不改值——v5→v6→v7 链式)" | f13.load_from_disk() and f13.adventure_total() == 0 and f13.adventure_cleared() == 0 and f13.keystone_balance() == 9 | (待填) | | | +| 1122 | 260 | "升档幂等: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 | (待填) | | | +| 1123 | 268 | "收束自增恰一·冒险成功局(三关全通 cleared=true:总局数+1 且冒险成功局数+1)" | f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1124 | 272 | "收束自增恰一·中止局(放弃局:仅总局数+1,冒险成功局数不动)" | f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1125 | 283 | "换代前结构存档:树域废弃重置(旧节点 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() | (待填) | | | +| 1126 | 288 | "废弃重置后后续购买正常受理(无残留脏节点)" | mp16.tree_buy("sp_hp") and f15.tree_level("sp_hp") == 1 | (待填) | | | +| 1127 | 297 | "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 | (待填) | | | +| 1128 | 300 | "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" | (待填) | | | +| 1129 | 307 | "forge_routes 域键集恰四项(无特效域——满级特效本期不做,契约 7)〔锻造工坊批 V8/V11〕" | fr_keys == ["armor", "levels", "unlocked", "weapon"] | (待填) | | | +| 1130 | 311 | "levels 六路线键集恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V8〕" | lv_keys == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | +| 1131 | 316 | "forge_routes_snapshot 值拷贝出户(改快照不渗档)〔锻造工坊批 V8〕" | f16.forge_route_level("sword") == 0 | (待填) | | | +| 1132 | 324 | "收束链自增·中止局(settle_adventure 真实挂点:总局数+1、冒险成功局数不动)" | f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1 | (待填) | | | +| 1133 | 328 | "收束链自增·冒险成功局(三关全通:总局数+1 且冒险成功局数+1——全链恰一)" | f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2 | (待填) | | | + +## S138 _smoke_save_slots(行 359–499 · 31 条 · 源 game/tests/unit/core/save_manager_domain_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1134 | 359 | "sv5 进档受理·槽 %d(激活+读盘)〔卡① sv5〕" ↩ % i | t.activate_slot(i) | (待填) | | | +| 1135 | 362 | "sv3 激活态写盘受理·槽 %d〔卡① sv1〕" ↩ % i | t.write_to_disk() | (待填) | | | +| 1136 | 366 | "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) | (待填) | | | +| 1137 | 372 | "sv3 写盘受理·槽 1 加值后〔卡① sv1〕" | t.write_to_disk() | (待填) | | | +| 1138 | 373 | "sv1 位间隔离:改槽 1 槽 2 字节不动〔卡① sv1〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_before | (待填) | | | +| 1139 | 377 | "sv5 切激活至槽 3(删激活槽夹具前置)〔卡① sv5〕" | t.activate_slot(3) | (待填) | | | +| 1140 | 378 | "sv6 删档受理·激活槽 3〔卡① sv6〕" | t.delete_slot(3) | (待填) | | | +| 1141 | 379 | "sv6 删后文件不在盘+无 .tmp 残留〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 3) and not FileAccess.file_exists(slot_tpl % 3 + ".tmp") | (待填) | | | +| 1142 | 381 | "sv6 删激活槽激活指针连清除(该槽不再可写)〔卡① sv6〕" | t.active_slot() == 0 and not t.has_write_target() | (待填) | | | +| 1143 | 385 | "sv6 删非激活槽受理·槽 2〔卡① sv6〕" | t.delete_slot(2) | (待填) | | | +| 1144 | 386 | "sv6 删非激活槽:文件不在盘+激活指针保持〔卡① sv6〕" | not FileAccess.file_exists(slot_tpl % 2) and t.active_slot() == 1 | (待填) | | | +| 1145 | 388 | "sv6 删槽 2 槽 1 字节不动(位间互隔离)〔卡① sv6〕" | FileAccess.get_file_as_bytes(slot_tpl % 1) == slot1_before | (待填) | | | +| 1146 | 397 | "sv4 新档受理·槽 3(创建即激活)〔卡① sv4〕" | t.create_slot(3) | (待填) | | | +| 1147 | 398 | "sv4 新档=全零可玩合法白板态(旧内容 999/5 零带入——钥石/总局数/断点全零)〔卡① sv4〕" | t.active_slot() == 3 and t.keystone_balance() == 0 and not t.has_breakpoint() and t.adventure_total() == 0 | (待填) | | | +| 1148 | 402 | "sv4 新档落盘=白板内容(盘面核验非 999 旧值)〔卡① sv4〕" | int((created.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 0 | (待填) | | | +| 1149 | 411 | "sv5 进档槽 1 读回(107)〔卡① sv10〕" | hv.activate_slot(1) and hv.keystone_balance() == 107 | (待填) | | | +| 1150 | 414 | "sv9 高版本槽进档拒绝(拒读禁写链按槽)〔卡① sv9〕" | not hv.activate_slot(2) | (待填) | | | +| 1151 | 415 | "sv10 激活失败恢复切换前三态(路径/激活号/数据归槽 1)〔卡① sv10〕" | hv.active_slot() == 1 and hv.keystone_balance() == 107 | (待填) | | | +| 1152 | 418 | "sv10 恢复后写盘落激活槽 1(高版本槽 2 零误写)〔卡① sv10〕" | hv.write_to_disk() and FileAccess.get_file_as_bytes(slot_tpl % 2) == slot2_hv | (待填) | | | +| 1153 | 429 | "sv9 迁移链按槽成立·槽 %d(v5→v7 各槽独立+写盘 version=7)〔卡① sv9〕" ↩ % i | mi.activate_slot(i) and mi.keystone_balance() == i * 11 and mi.write_to_disk() | (待填) | | | +| 1154 | 444 | "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 | (待填) | | | +| 1155 | 447 | "sv6 删槽 3 备空槽窥视〔卡① sv6〕" | pv.delete_slot(3) | (待填) | | | +| 1156 | 449 | "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 | (待填) | | | +| 1157 | 458 | "sv8 坏槽窥视=显式损坏标记〔卡① sv8〕" | not bool(pb.get("readable", true)) | (待填) | | | +| 1158 | 459 | "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") | (待填) | | | +| 1159 | 469 | "sv8 高版本槽窥视=不可读标记〔卡① sv8〕" | not bool(ph.get("readable", true)) | (待填) | | | +| 1160 | 470 | "sv8 高版本窥视零副作用(字节+mtime 不变+零改名)〔卡① sv8〕" | FileAccess.get_file_as_bytes(slot_tpl % 2) == hv_bytes and FileAccess.get_modified_time(slot_tpl % 2) == hv_mtime | (待填) | | | +| 1161 | 483 | "sv12 旧档字节迁入槽 1(逐域相等——余额 1234)〔卡① sv12〕" | int((migrated.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 | (待填) | | | +| 1162 | 485 | "sv12 迁移后源改名留痕(.migrated.bak 在盘、原路径不在)〔卡① sv12〕" | not FileAccess.file_exists(legacy_src) and FileAccess.file_exists(legacy_src + ".migrated.bak") | (待填) | | | +| 1163 | 494 | "sv12 幂等:槽 1 已存在不重迁(预置 777 零覆盖、源保留)〔卡① sv12〕" | int((slot1_now.get("meta", {}) as Dictionary).get("keystone_balance", -1)) == 1234 and FileAccess.file_exists(legacy_src) | (待填) | | | +| 1164 | 497 | "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 | (待填) | | | + +## S139 _init(行 14–15 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1165 | 14 | "全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1166 | 15 | "is_ready 通道随加载置位" | db.is_ready | (待填) | | | + +## S140 _test_progress_resolution(行 55–83 · 7 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +|---|---|---|---|---|---|---| +| 1167 | 55 | "进度解析精确命中返回原行(board_gen 首行)" | int(got.get("level_index", -99)) == int(probe_row.level_index) and int(got.get("node_depth", -99)) == int(probe_row.node_depth) and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")) | (待填) | | | +| 1168 | 63 | "enemy 行集同坐标解析非空(双表精确行契约——battle 行类)" | not en_got.is_empty() | (待填) | | | +| 1169 | 68 | "深度越最大行 clamp 取该关最大深度行" | int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]) | (待填) | | | +| 1170 | 70 | "clamp 解析发警告诊断(resolve_depth_clamp)" | db.diagnostics_count("warning") >= 1 | (待填) | | | +| 1171 | 75 | "警告类别 = resolve_depth_clamp" | found_clamp | (待填) | | | +| 1172 | 78 | "同键重复请求警告去重(四元组注册表)" | db.diagnostics_count("warning") == warn_before | (待填) | | | +| 1173 | 83 | "关卡越最大行 clamp 取最大关卡行" | int(high.get("level_index", -99)) == max_l | (待填) | | | + +## S141 _test_variant_pool_gate(行 108–118 · 6 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1164 | 55 | "进度解析精确命中返回原行(board_gen 首行)" | int(got.get("level_index", -99)) == int(probe_row.level_index) and int(got.get("node_depth", -99)) == int(probe_row.node_depth) and String(got.get("board_type", "")) == String(probe_row.get("board_type", "")) | (待填) | | | -| 1165 | 63 | "enemy 行集同坐标解析非空(双表精确行契约——battle 行类)" | not en_got.is_empty() | (待填) | | | -| 1166 | 68 | "深度越最大行 clamp 取该关最大深度行" | int(deep.get("level_index", -99)) == min_l and int(deep.get("node_depth", -99)) == int(min_depths[-1]) | (待填) | | | -| 1167 | 70 | "clamp 解析发警告诊断(resolve_depth_clamp)" | db.diagnostics_count("warning") >= 1 | (待填) | | | -| 1168 | 75 | "警告类别 = resolve_depth_clamp" | found_clamp | (待填) | | | -| 1169 | 78 | "同键重复请求警告去重(四元组注册表)" | db.diagnostics_count("warning") == warn_before | (待填) | | | -| 1170 | 83 | "关卡越最大行 clamp 取最大关卡行" | int(high.get("level_index", -99)) == max_l | (待填) | | | +| 1174 | 108 | "变异池 unlock 前不含未解锁行(level=unlock−1)" | not _rows_contain(before, gated_row) | (待填) | | | +| 1175 | 109 | "变异池 unlock 当级含该行" | _rows_contain(after, gated_row) | (待填) | | | +| 1176 | 113 | "∅ board_types 行通配(任一 board_type 命中)" | _rows_contain(any_pool, wildcard_row) | (待填) | | | +| 1177 | 116 | "global 行 boss 节点入池(board_types ∅ 通配)" | _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row) | (待填) | | | +| 1178 | 117 | "global 行缺省双参调用跳过(既有调用口径零扰动)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row) | (待填) | | | +| 1179 | 118 | "global 行 battle 节点静默跳过(门控兜底零报错)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row) | (待填) | | | -## S140 _test_variant_pool_gate(行 108–118 · 6 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S142 _test_shop_accessors(行 133–152 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1171 | 108 | "变异池 unlock 前不含未解锁行(level=unlock−1)" | not _rows_contain(before, gated_row) | (待填) | | | -| 1172 | 109 | "变异池 unlock 当级含该行" | _rows_contain(after, gated_row) | (待填) | | | -| 1173 | 113 | "∅ board_types 行通配(任一 board_type 命中)" | _rows_contain(any_pool, wildcard_row) | (待填) | | | -| 1174 | 116 | "global 行 boss 节点入池(board_types ∅ 通配)" | _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "boss"), global_row) | (待填) | | | -| 1175 | 117 | "global 行缺省双参调用跳过(既有调用口径零扰动)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag"), global_row) | (待填) | | | -| 1176 | 118 | "global 行 battle 节点静默跳过(门控兜底零报错)" | not _rows_contain(db.variant_pool(u3, "nonexistent_board_type_tag", "battle"), global_row) | (待填) | | | +| 1180 | 133 | "ShopTable products 行族非空(表已已入库前提)" | false | (待填) | | | +| 1181 | 135 | "ShopTable products 行族非空" | true | (待填) | | | +| 1182 | 138 | "shop_product_row 按 product_id 精确命中首行" | got == first | (待填) | | | +| 1183 | 139 | "shop_product_row 未知 id 返回空字典(容错通道)" | db.shop_product_row("no_such_product_id__").is_empty() | (待填) | | | +| 1184 | 143 | "shop_gp_drop_row 按 tier 命中" | not db.shop_gp_drop_row(tier0).is_empty() | (待填) | | | +| 1185 | 147 | "shop_level_gp_bonus 按关卡序号命中行值" | db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus) | (待填) | | | +| 1186 | 148 | "shop_level_gp_bonus 未登记关卡返回 0(兜底通道)" | db.shop_level_gp_bonus(-1) == 0 | (待填) | | | +| 1187 | 152 | "shop_price_level_bonus 按关卡序号命中行值" | db.shop_price_level_bonus(plv) == int(price_rows[0].bonus) | (待填) | | | -## S141 _test_shop_accessors(行 133–152 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S143 _test_meta_accessors(行 162–166 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1177 | 133 | "ShopTable products 行族非空(表已已入库前提)" | false | (待填) | | | -| 1178 | 135 | "ShopTable products 行族非空" | true | (待填) | | | -| 1179 | 138 | "shop_product_row 按 product_id 精确命中首行" | got == first | (待填) | | | -| 1180 | 139 | "shop_product_row 未知 id 返回空字典(容错通道)" | db.shop_product_row("no_such_product_id__").is_empty() | (待填) | | | -| 1181 | 143 | "shop_gp_drop_row 按 tier 命中" | not db.shop_gp_drop_row(tier0).is_empty() | (待填) | | | -| 1182 | 147 | "shop_level_gp_bonus 按关卡序号命中行值" | db.shop_level_gp_bonus(blv) == int(bonus_rows[0].bonus) | (待填) | | | -| 1183 | 148 | "shop_level_gp_bonus 未登记关卡返回 0(兜底通道)" | db.shop_level_gp_bonus(-1) == 0 | (待填) | | | -| 1184 | 152 | "shop_price_level_bonus 按关卡序号命中行值" | db.shop_price_level_bonus(plv) == int(price_rows[0].bonus) | (待填) | | | +| 1188 | 162 | "meta_boss_keystone_row 按关卡序号命中" | db.meta_boss_keystone_row(klv) == key_rows[0] | (待填) | | | +| 1189 | 166 | "meta_conversion_row 按 attr 命中" | db.meta_conversion_row(cattr) == conv_rows[0] | (待填) | | | -## S142 _test_meta_accessors(行 162–166 · 2 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S144 _test_manifest_dir_sync(行 188–188 · 1 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1185 | 162 | "meta_boss_keystone_row 按关卡序号命中" | db.meta_boss_keystone_row(klv) == key_rows[0] | (待填) | | | -| 1186 | 166 | "meta_conversion_row 按 attr 命中" | db.meta_conversion_row(cattr) == conv_rows[0] | (待填) | | | +| 1190 | 188 | "TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)" | synced | (待填) | | | -## S143 _test_manifest_dir_sync(行 188–188 · 1 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S145 _test_rowset_discipline(行 208–273 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1187 | 188 | "TABLE_MANIFEST 与 data/tables 目录逐文件同步(无缺登/无悬登)" | synced | (待填) | | | +| 1191 | 208 | "混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)" | errs.is_empty() | (待填) | | | +| 1192 | 214 | "Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)" | errs.is_empty() | (待填) | | | +| 1193 | 225 | "行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕" | gap_hit | (待填) | | | +| 1194 | 235 | "同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕" | dup_hit | (待填) | | | +| 1195 | 246 | "单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)" | dom_hit | (待填) | | | +| 1196 | 256 | "非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕" | kind_hit | (待填) | | | +| 1197 | 265 | "缺 (battle, band 0) 锚行报 anchor〔fv10〕" | no_anchor_hit | (待填) | | | +| 1198 | 273 | "缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕" | no_boss_hit | (待填) | | | -## S144 _test_rowset_discipline(行 208–273 · 8 条 · 源 game/tests/unit/data_config/config_db_test.gd · 去向:待分类回填) +## S146 _init(行 17–17 · 1 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1188 | 208 | "混编行集加载通过(同带键两行不同敌种相邻——行集恰一敌种校验退役的正向面)" | errs.is_empty() | (待填) | | | -| 1189 | 214 | "Boss 带混编行集加载通过(P4 乙——Boss 多行合法,无硬闸)" | errs.is_empty() | (待填) | | | -| 1190 | 225 | "行序相邻校验:同带键行被隔断报 rowset_rule 且定位带键〔fv9〕〔fv20〕" | gap_hit | (待填) | | | -| 1191 | 235 | "同带键同敌种二行被 dup_key 新键拦(纪律①——enemy_id 唯一)〔fv8〕" | dup_hit | (待填) | | | -| 1192 | 246 | "单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)" | dom_hit | (待填) | | | -| 1193 | 256 | "非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕" | kind_hit | (待填) | | | -| 1194 | 265 | "缺 (battle, band 0) 锚行报 anchor〔fv10〕" | no_anchor_hit | (待填) | | | -| 1195 | 273 | "缺 Boss 行集报 anchor(每关 Boss 行集 ≥1)〔fv10〕" | no_boss_hit | (待填) | | | +| 1199 | 17 | "生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S145 _init(行 17–17 · 1 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S147 _test_production_rows(行 30–81 · 13 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1196 | 17 | "生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1200 | 30 | "六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕" | rows.size() == 6 | (待填) | | | +| 1201 | 35 | "路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕" | ids == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | +| 1202 | 46 | "六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕" | keys_closed | (待填) | | | +| 1203 | 53 | "初始持有恰 sword+light〔锻造工坊批 V1〕" | initial == ["light", "sword"] | (待填) | | | +| 1204 | 55 | "默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_attack_type() == "normal" | (待填) | | | +| 1205 | 56 | "默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_armor_type() == "light" | (待填) | | | +| 1206 | 58 | "长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("spear").get("prereq_route", "")) == "sword" and String(db.forge_route("staff").get("prereq_route", "")) == "sword" | (待填) | | | +| 1207 | 61 | "重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("heavy").get("prereq_route", "")) == "light" and String(db.forge_route("ward").get("prereq_route", "")) == "light" | (待填) | | | +| 1208 | 64 | "四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕" | int(db.forge_route("spear").get("unlock_price", 0)) == 200 and int(db.forge_route("staff").get("unlock_price", 0)) == 200 and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 and int(db.forge_route("ward").get("unlock_price", 0)) == 200 | (待填) | | | +| 1209 | 70 | "甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕" | int(db.forge_route("light").get("base_ac", 0)) == 10 and int(db.forge_route("heavy").get("base_ac", 0)) == 12 and int(db.forge_route("ward").get("base_ac", 0)) == 9 | (待填) | | | +| 1210 | 75 | "forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕" | String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward" | (待填) | | | +| 1211 | 77 | "forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕" | String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear" | (待填) | | | +| 1212 | 80 | "未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕" | db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty() | (待填) | | | -## S146 _test_production_rows(行 30–81 · 13 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S148 _test_price_ladder(行 91–94 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1197 | 30 | "六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕" | rows.size() == 6 | (待填) | | | -| 1198 | 35 | "路线 id 全集 = {sword, spear, staff, light, heavy, ward}〔锻造工坊批 V1〕" | ids == ["heavy", "light", "spear", "staff", "sword", "ward"] | (待填) | | | -| 1199 | 46 | "六行字段键集恰 = ROW_KEYS 闭集(无特效参数位)〔锻造工坊批 V11〕" | keys_closed | (待填) | | | -| 1200 | 53 | "初始持有恰 sword+light〔锻造工坊批 V1〕" | initial == ["light", "sword"] | (待填) | | | -| 1201 | 55 | "默认攻击类型轴 = normal(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_attack_type() == "normal" | (待填) | | | -| 1202 | 56 | "默认甲型轴 = light(常驻持有)〔锻造工坊批 V1〕" | db.forge_default_armor_type() == "light" | (待填) | | | -| 1203 | 58 | "长枪/法杖 ← 长剑练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("spear").get("prereq_route", "")) == "sword" and String(db.forge_route("staff").get("prereq_route", "")) == "sword" | (待填) | | | -| 1204 | 61 | "重甲/魔法护甲 ← 轻甲练满(并联双通道①)〔锻造工坊批 V1〕" | String(db.forge_route("heavy").get("prereq_route", "")) == "light" and String(db.forge_route("ward").get("prereq_route", "")) == "light" | (待填) | | | -| 1205 | 64 | "四待解锁路线一次性解锁价 = 200〔锻造工坊批 V1〕" | int(db.forge_route("spear").get("unlock_price", 0)) == 200 and int(db.forge_route("staff").get("unlock_price", 0)) == 200 and int(db.forge_route("heavy").get("unlock_price", 0)) == 200 and int(db.forge_route("ward").get("unlock_price", 0)) == 200 | (待填) | | | -| 1206 | 70 | "甲型基础 AC = 轻 10 / 重 12 / 魔法护甲 9〔锻造工坊批 V1〕" | int(db.forge_route("light").get("base_ac", 0)) == 10 and int(db.forge_route("heavy").get("base_ac", 0)) == 12 and int(db.forge_route("ward").get("base_ac", 0)) == 9 | (待填) | | | -| 1207 | 75 | "forge_armor_route_by_type(ward) = 魔法护甲锻造〔锻造工坊批 V1〕" | String(db.forge_armor_route_by_type("ward").get("route_id", "")) == "ward" | (待填) | | | -| 1208 | 77 | "forge_weapon_route_by_type(pierce) = 长枪锻造〔锻造工坊批 V1〕" | String(db.forge_weapon_route_by_type("pierce").get("route_id", "")) == "spear" | (待填) | | | -| 1209 | 80 | "未知 route_id / 未知类型轴 = 空字典〔锻造工坊批 V1〕" | db.forge_route("blade").is_empty() and db.forge_armor_route_by_type("plate").is_empty() | (待填) | | | +| 1213 | 91 | "长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕" | prices == [20, 30, 45, 67, 101] | (待填) | | | +| 1214 | 93 | "已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕" | ForgeTableScript.level_price(row, 5) == 151 | (待填) | | | -## S147 _test_price_ladder(行 91–94 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S149 _test_negative_cases(行 133–189 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1210 | 91 | "长剑各级价格 = 20/30/45/67/101(基础 20×150%^已购级数)〔锻造工坊批 V2〕" | prices == [20, 30, 45, 67, 101] | (待填) | | | -| 1211 | 93 | "已满级(owned=5)再购价照算 151(cap 闸归写口,价格函数纯几何)〔锻造工坊批 V2〕" | ForgeTableScript.level_price(row, 5) == 151 | (待填) | | | +| 1215 | 133 | "负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕" | t_ok.validate().is_empty() | (待填) | | | +| 1216 | 189 | "锻造 schema 负向:%s〔锻造工坊批 V1〕" ↩ % label | _has_cat(errs, expect_cat) | (待填) | | | -## S148 _test_negative_cases(行 133–189 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S150 _test_effect_kinds_closed(行 197–200 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1212 | 133 | "负向夹具(合法六行)validate 零违例〔锻造工坊批 V1〕" | t_ok.validate().is_empty() | (待填) | | | -| 1213 | 189 | "锻造 schema 负向:%s〔锻造工坊批 V1〕" ↩ % label | _has_cat(errs, expect_cat) | (待填) | | | +| 1217 | 197 | "效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕" | EffectEngineScript.EFFECT_KINDS.size() == 32 | (待填) | | | +| 1218 | 199 | "效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕" | EffectEngineScript.EVENTS.size() == 20 | (待填) | | | -## S149 _test_effect_kinds_closed(行 197–200 · 2 条 · 源 game/tests/unit/data_config/forge_table_test.gd · 去向:待分类回填) +## S151 _init(行 16–17 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1214 | 197 | "效果引擎效果类型闭集 32 种(锻造工坊批零新增口径随迭代 31 卡② 扩签 forgive_seal_miss 换代)〔锻造工坊批 V11〕" | EffectEngineScript.EFFECT_KINDS.size() == 32 | (待填) | | | -| 1215 | 199 | "效果引擎事件词表在册 20 种(本批零扩签参照面)〔锻造工坊批 V11〕" | EffectEngineScript.EVENTS.size() == 20 | (待填) | | | +| 1219 | 16 | "生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -## S150 _init(行 16–17 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S152 _test_production(行 47–55 · 4 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1216 | 16 | "生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1220 | 47 | "访问器读回 L1 行五旋钮齐备〔fv2〕" | int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1 | (待填) | | | +| 1221 | 50 | "访问器读回 L2/L3 行层深 9/13 递增〔fv2〕" | int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13 | (待填) | | | +| 1222 | 52 | "三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕" | int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8 | (待填) | | | +| 1223 | 55 | "生产表 validate 零错误〔fv2〕" | db.level_gen_params.validate().is_empty() | (待填) | | | -## S151 _test_production(行 47–55 · 4 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S153 _test_accessor(行 62–62 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1217 | 47 | "访问器读回 L1 行五旋钮齐备〔fv2〕" | int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1 | (待填) | | | -| 1218 | 50 | "访问器读回 L2/L3 行层深 9/13 递增〔fv2〕" | int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13 | (待填) | | | -| 1219 | 52 | "三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕" | int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8 | (待填) | | | -| 1220 | 55 | "生产表 validate 零错误〔fv2〕" | db.level_gen_params.validate().is_empty() | (待填) | | | +| 1224 | 62 | "row_by_level_index 缺行返回空字典〔fv2〕" | t.row_by_level_index(9).is_empty() | (待填) | | | -## S152 _test_accessor(行 62–62 · 1 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S154 _test_negative(行 72–105 · 11 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1221 | 62 | "row_by_level_index 缺行返回空字典〔fv2〕" | t.row_by_level_index(9).is_empty() | (待填) | | | +| 1225 | 72 | "合法三行域校验零错误(夹具自证)〔fv2〕" | _table(base).validate().is_empty() | (待填) | | | +| 1226 | 75 | "L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕" | not _table(bad_l1_exit).validate().is_empty() | (待填) | | | +| 1227 | 78 | "配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕" | not _table(bad_ratio).validate().is_empty() | (待填) | | | +| 1228 | 81 | "层深 5<6 被拒(层深下限已锁)〔fv2〕" | not _table(bad_layers).validate().is_empty() | (待填) | | | +| 1229 | 85 | "层深增幅倒挂被拒(递增且增幅变大)〔fv2〕" | not _table(bad_growth).validate().is_empty() | (待填) | | | +| 1230 | 88 | "出口 4 越域 [1,3] 被拒〔fv2〕" | not _table(bad_exit).validate().is_empty() | (待填) | | | +| 1231 | 92 | "岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕" | not _table([bad_fork_row, base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1232 | 97 | "字段键集非 ROW_KEYS 闭集被拒〔fv2〕" | not _table([bad_keys_row, base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1233 | 100 | "键重复行被拒〔fv2〕" | not _table([base[0], base[0]]).validate().is_empty() | (待填) | | | +| 1234 | 102 | "战斗保底 -1 被拒(≥0)〔fv2〕" | not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty() | (待填) | | | +| 1235 | 105 | "空表域校验零错误(合法态)〔fv2〕" | _table([]).validate().is_empty() | (待填) | | | -## S153 _test_negative(行 72–105 · 11 条 · 源 game/tests/unit/data_config/level_gen_params_test.gd · 去向:待分类回填) +## S155 _test_derive_determinism(行 36–40 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1222 | 72 | "合法三行域校验零错误(夹具自证)〔fv2〕" | _table(base).validate().is_empty() | (待填) | | | -| 1223 | 75 | "L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕" | not _table(bad_l1_exit).validate().is_empty() | (待填) | | | -| 1224 | 78 | "配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕" | not _table(bad_ratio).validate().is_empty() | (待填) | | | -| 1225 | 81 | "层深 5<6 被拒(层深下限已锁)〔fv2〕" | not _table(bad_layers).validate().is_empty() | (待填) | | | -| 1226 | 85 | "层深增幅倒挂被拒(递增且增幅变大)〔fv2〕" | not _table(bad_growth).validate().is_empty() | (待填) | | | -| 1227 | 88 | "出口 4 越域 [1,3] 被拒〔fv2〕" | not _table(bad_exit).validate().is_empty() | (待填) | | | -| 1228 | 92 | "岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕" | not _table([bad_fork_row, base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1229 | 97 | "字段键集非 ROW_KEYS 闭集被拒〔fv2〕" | not _table([bad_keys_row, base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1230 | 100 | "键重复行被拒〔fv2〕" | not _table([base[0], base[0]]).validate().is_empty() | (待填) | | | -| 1231 | 102 | "战斗保底 -1 被拒(≥0)〔fv2〕" | not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty() | (待填) | | | -| 1232 | 105 | "空表域校验零错误(合法态)〔fv2〕" | _table([]).validate().is_empty() | (待填) | | | +| 1236 | 36 | "同种子两实例 derive_at(0..7) 逐位一致" | same | (待填) | | | +| 1237 | 40 | "derive_at 为纯函数不推计数器" | a2.counter() == before | (待填) | | | -## S154 _test_derive_determinism(行 36–40 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S156 _test_counter_ledger(行 51–57 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1233 | 36 | "同种子两实例 derive_at(0..7) 逐位一致" | same | (待填) | | | -| 1234 | 40 | "derive_at 为纯函数不推计数器" | a2.counter() == before | (待填) | | | +| 1238 | 51 | "open_stream 逐次推进全局计数器(+1/次)" | c1 == c0 + 1 and c2 == c0 + 2 | (待填) | | | +| 1239 | 57 | "同 scope 异用途标签派生种子一致(标签不参与派生)" | s1 == s2 | (待填) | | | -## S155 _test_counter_ledger(行 51–57 · 2 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S157 _test_stream_draw_determinism(行 71–71 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1235 | 51 | "open_stream 逐次推进全局计数器(+1/次)" | c1 == c0 + 1 and c2 == c0 + 2 | (待填) | | | -| 1236 | 57 | "同 scope 异用途标签派生种子一致(标签不参与派生)" | s1 == s2 | (待填) | | | +| 1240 | 71 | "同种子两实例同标签流取数 12 次逐位一致" | same | (待填) | | | -## S156 _test_stream_draw_determinism(行 71–71 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S158 _test_negative_seed_normalization(行 79–79 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1237 | 71 | "同种子两实例同标签流取数 12 次逐位一致" | same | (待填) | | | +| 1241 | 79 | "负种子 abs 归一化后派生与正种子一致" | pos.derive_at(0) == neg.derive_at(0) | (待填) | | | -## S157 _test_negative_seed_normalization(行 79–79 · 1 条 · 源 game/tests/unit/data_config/rng_service_test.gd · 去向:待分类回填) +## S159 _smoke_initialize(行 28–53 · 13 条 · 源 game/tests/unit/data_config/rng_stream_boot_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1238 | 79 | "负种子 abs 归一化后派生与正种子一致" | pos.derive_at(0) == neg.derive_at(0) | (待填) | | | +| 1242 | 28 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | (待填) | | | +| 1243 | 37 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | (待填) | | | +| 1244 | 38 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | (待填) | | | +| 1245 | 39 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | (待填) | | | +| 1246 | 40 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | (待填) | | | +| 1247 | 41 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | (待填) | | | +| 1248 | 42 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | (待填) | | | +| 1249 | 43 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | (待填) | | | +| 1250 | 44 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | (待填) | | | +| 1251 | 45 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | (待填) | | | +| 1252 | 49 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | (待填) | | | +| 1253 | 50 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | (待填) | | | +| 1254 | 53 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | (待填) | | | -## S158 _smoke_initialize(行 28–53 · 13 条 · 源 game/tests/unit/data_config/rng_stream_boot_test.gd · 去向:待分类回填) +## S160 _smoke_adventure_table(行 92–203 · 18 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1239 | 28 | "全部表加载 + 校验通过(含跨表护栏)" | load_ok | (待填) | | | -| 1240 | 37 | "统一流向量 m=42 scope=0(=旧 board_gen scope0 连续性锚)" | rng.derive_at(0) == 8672514112661456564 | (待填) | | | -| 1241 | 38 | "统一流向量 m=42 scope=1" | rng.derive_at(1) == 8834291164406037215 | (待填) | | | -| 1242 | 39 | "统一流向量 m=42 scope=2" | rng.derive_at(2) == 7714373262647006282 | (待填) | | | -| 1243 | 40 | "统一流向量 m=42 scope=3" | rng.derive_at(3) == 1280102845971471347 | (待填) | | | -| 1244 | 41 | "统一流向量 m=42 scope=4" | rng.derive_at(4) == 82585501446482399 | (待填) | | | -| 1245 | 42 | "统一流向量 m=42 scope=5" | rng.derive_at(5) == 8078924421474470922 | (待填) | | | -| 1246 | 43 | "统一流向量 m=42 scope=6" | rng.derive_at(6) == 6638052740430622424 | (待填) | | | -| 1247 | 44 | "统一流向量 m=42 scope=7" | rng.derive_at(7) == 8264196488417721294 | (待填) | | | -| 1248 | 45 | "derive_at 纯函数恒等" | rng.derive_at(0) == rng.derive_at(0) | (待填) | | | -| 1249 | 49 | "open_stream 全局计数器单调 +1 ×2" | rng.counter() == 2 | (待填) | | | -| 1250 | 50 | "首个取号 = scope 0(顺序确定)" | s0.seed == rng.derive_at(0) and s1.seed == rng.derive_at(1) | (待填) | | | -| 1251 | 53 | "new_adventure 重置全局计数器" | rng.counter() == 1 and s2.seed == rng.derive_at(0) | (待填) | | | +| 1255 | 92 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | (待填) | | | +| 1256 | 95 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | (待填) | | | +| 1257 | 96 | "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 | (待填) | | | +| 1258 | 99 | "档位配额结构锚(四级 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]) | (待填) | | | +| 1259 | 101 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | (待填) | | | +| 1260 | 102 | "数值对(四级换代承袭):小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 | (待填) | | | +| 1261 | 107 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | (待填) | | | +| 1262 | 109 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | (待填) | | | +| 1263 | 111 | "同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, null) == 0 | (待填) | | | +| 1264 | 113 | "同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, _TreeSaveMock.new()) == 1 | (待填) | | | +| 1265 | 120 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | (待填) | | | +| 1266 | 131 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | (待填) | | | +| 1267 | 159 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | (待填) | | | +| 1268 | 162 | "装配→战斗贯通: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") | (待填) | | | +| 1269 | 166 | "空装配基线:不带 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") | (待填) | | | +| 1270 | 181 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | (待填) | | | +| 1271 | 188 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | (待填) | | | +| 1272 | 202 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | (待填) | | | -## S159 _smoke_adventure_table(行 92–203 · 18 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S161 _smoke_enemy_bands(行 229–261 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1252 | 92 | "db.adventure 已加载(adventure.tres 入 manifest)" | _db.adventure != null | (待填) | | | -| 1253 | 95 | "AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)" | _db.adventure.validate().is_empty() | (待填) | | | -| 1254 | 96 | "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 | (待填) | | | -| 1255 | 99 | "档位配额结构锚(四级 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]) | (待填) | | | -| 1256 | 101 | "令牌池总量 = 102 张(四级 普通 70/优秀 25/稀有 7/传说 0——迭代 31 卡② 净删两件投放 104→102)〔凭证裁削批 V5〕" | int(_db.adventure.deck_total()) == 102 | (待填) | | | -| 1257 | 102 | "数值对(四级换代承袭):小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 | (待填) | | | -| 1258 | 107 | "恢复件 +6 当即回血(优秀档承袭)· AP +1(稀有档承袭)〔专长批 V14 换代〕" | int(_db.adventure.curio_value("heal", "excellent")) == 6 and int(_db.adventure.curio_value("ap", "rare")) == 1 | (待填) | | | -| 1259 | 109 | "cap 占位在域(案②提前退役——99=实质无上限,实数归调参批)〔专长批 V8 形态面〕" | int(_db.adventure.curio_cap("gp")) == 99 and int(_db.adventure.curio_cap("attack")) == 99 | (待填) | | | -| 1260 | 111 | "同调位读数 null 档早退分支求值 0(迭代 21 T4 槽位收编——谓词表已退役)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, null) == 0 | (待填) | | | -| 1261 | 113 | "同调位读数源=树同调位节点点亮数(非空档——socket1 点亮 1 件计 1)〔天赋树批 V14·核代码增补〕" | Attunement.slots(_db, _TreeSaveMock.new()) == 1 | (待填) | | | -| 1262 | 120 | "新目录 12 件全量落表(旧 12 行删除;装配词表=活行 12 件零重复)〔秘宝批 V1〕" | trows.size() == 12 and tid_set.size() == 12 | (待填) | | | -| 1263 | 131 | "命名锁定:12 件 item_id 与 display_name 逐件=正式名(C2 代号即名)〔秘宝批 V2 初筛〕" | names_ok | (待填) | | | -| 1264 | 159 | "逐行解锁方式与契约 8 分配一致(钥石 8 件带价格、条件 4 件带谓词行互斥)+三档 7/3/2〔秘宝批 V1〕" | alloc_ok and tier_map["common"] == 7 and tier_map["excellent"] == 3 and tier_map["rare"] == 2 | (待填) | | | -| 1265 | 162 | "装配→战斗贯通: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") | (待填) | | | -| 1266 | 166 | "空装配基线:不带 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") | (待填) | | | -| 1267 | 181 | "新 12 行效果词零棋盘改写类(危险格/安全格分布同池冻结——真视/开格动作外无写面)〔秘宝批 V9〕" | v9_ok | (待填) | | | -| 1268 | 188 | "效果三方向覆盖在册(信息/行动资源/操作能力=plan·抉择刷新——12 件目录结构对齐设计权威)〔秘宝批 V10 覆盖半〕" | tag_set.has("info") and tag_set.has("action") and tag_set.has("plan") | (待填) | | | -| 1269 | 202 | "装配面空目录合法态:白板档 12 件全未解锁且 mode 分派正确(空目录=合法可玩态)〔秘宝批 V18〕" | v18_ok and locked_n == 12 | (待填) | | | +| 1273 | 229 | "敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 28 and got_keys == expect_keys | (待填) | | | +| 1274 | 236 | "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 | (待填) | | | +| 1275 | 239 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | (待填) | | | +| 1276 | 260 | "三关 Boss 行集带键 = 覆盖表(模板终点 4/9/9 在册+生成图终点 6/8/9/11/12/13 扩带)%s〔fv13〕" ↩ % boss_detail | boss_ok | (待填) | | | -## S160 _smoke_enemy_bands(行 229–261 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S162 _smoke_ac_swap_guard(行 272–380 · 13 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1270 | 229 | "敌人表 28 行且键序 = 覆盖表序((L,row_kind,depth_band,enemy_id) 逐行比对——文件行序不变)〔fv11〕" | _db.enemy.rows.size() == 28 and got_keys == expect_keys | (待填) | | | -| 1271 | 236 | "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 | (待填) | | | -| 1272 | 239 | "hobgoblin 缺口登记随行(表文件注释在位——缺口不静默)〔fv25〕" | FileAccess.get_file_as_string("res://data/tables/enemy.tres").contains("hobgoblin 行为缺口行") | (待填) | | | -| 1273 | 260 | "三关 Boss 行集带键 = 覆盖表(模板终点 4/9/9 在册+生成图终点 6/8/9/11/12/13 扩带)%s〔fv13〕" ↩ % boss_detail | boss_ok | (待填) | | | +| 1277 | 272 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | (待填) | | | +| 1278 | 274 | "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" | (待填) | | | +| 1279 | 282 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | (待填) | | | +| 1280 | 284 | "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") | (待填) | | | +| 1281 | 299 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | (待填) | | | +| 1282 | 305 | "hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)" | hd_maps == 0 | (待填) | | | +| 1283 | 307 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | (待填) | | | +| 1284 | 324 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | (待填) | | | +| 1285 | 332 | "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)) | (待填) | | | +| 1286 | 348 | "熊地精首遇带(1,battle,%d)降档锚定(hp 22 · ac 2 · 池 attack6/charge2/heavy2 零改动——迭代 24 N1;首遇带数据驱动定位)〔fv21〕" ↩ % l12_band | l12_band >= 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} | (待填) | | | +| 1287 | 370 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | (待填) | | | +| 1288 | 375 | "掷点参数拍板值在位(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" | (待填) | | | +| 1289 | 379 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | (待填) | | | -## S161 _smoke_ac_swap_guard(行 272–380 · 13 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S163 _smoke_variation_framework(行 387–415 · 9 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1274 | 272 | "状态表 defense_up 行已退役(无双轨)" | _db.status_effect_row("defense_up").is_empty() | (待填) | | | -| 1275 | 274 | "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" | (待填) | | | -| 1276 | 282 | "奖励表 defense 正向行 effect_id = ac_up(显示名盾牌加护,权重/tier 不变)" | String(ac_reward.get("effect_id", "")) == "ac_up" and String(ac_reward.get("display_name", "")) == "盾牌加护" | (待填) | | | -| 1277 | 284 | "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") | (待填) | | | -| 1278 | 299 | "hero_defense 池 percent 行叠满不可触池底(墙最深%s > clamp_lo %d——单效果归零结构性不可达)" ↩ % [ wall_detail if wall_detail != "" else "(池内现无负向行)", int(_db.settlement.pool_clamp_lo)] | wall_ok | (待填) | | | -| 1279 | 305 | "hero_defense 池映射数==0(绊线钉——红=已加映射,去核上方护栏是否已同步激活)" | hd_maps == 0 | (待填) | | | -| 1280 | 307 | "主角 armor_class 在域内(%d——暂定基准随表走)" ↩ % int(_db.hero.armor_class) | int(_db.hero.armor_class) >= 0 and int(_db.hero.armor_class) <= 30 | (待填) | | | -| 1281 | 324 | "敌方 AC 行值在已裁带宽内(杂鱼 0~5·重击手/重装手 8~9·Boss 8~12——迭代 5 返工批 v1 重投,越带行%s)" ↩ % [ ac_detail if ac_detail != "" else "无"] | ac_band | (待填) | | | -| 1282 | 332 | "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)) | (待填) | | | -| 1283 | 348 | "熊地精首遇带(1,battle,%d)降档锚定(hp 22 · ac 2 · 池 attack6/charge2/heavy2 零改动——迭代 24 N1;首遇带数据驱动定位)〔fv21〕" ↩ % l12_band | l12_band >= 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} | (待填) | | | -| 1284 | 370 | "第一关相邻深度带总量比 ≤ 2.5(12/10/22/35/72——迭代 24 N1 曲线护栏,带键升序;越带%s)" ↩ % [ curve_detail if curve_detail != "" else "无"] | curve_ok | (待填) | | | -| 1285 | 375 | "掷点参数拍板值在位(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" | (待填) | | | -| 1286 | 379 | "档位表拍板值在位(缺口 1-3 → ×0.75 · 4-7 → ×0.5 · 8+ → ×0.25——无零档)" | bounds == [3, 7] and discounts == [75, 50, 25] | (待填) | | | +| 1290 | 387 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | (待填) | | | +| 1291 | 389 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | (待填) | | | +| 1292 | 394 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | (待填) | | | +| 1293 | 398 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | (待填) | | | +| 1294 | 399 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | (待填) | | | +| 1295 | 403 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | (待填) | | | +| 1296 | 405 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | (待填) | | | +| 1297 | 411 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 1298 | 415 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | (待填) | | | -## S162 _smoke_variation_framework(行 387–415 · 9 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S164 _smoke_difficulty_table(行 422–444 · 8 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1287 | 387 | "变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)" | _db.variation.rows.size() == 6 | (待填) | | | -| 1288 | 389 | "变异表校验零错误(6 行全过域与跨字段互锁——含 scope=global 三互锁负样本)" | _db.variation.validate().is_empty() | (待填) | | | -| 1289 | 394 | "L1 dungeon 候选池 = 荆棘丛+藤蔓缠绕(unlock + board_types 双闸)" | pool1_ids == ["thorn", "vine"] | (待填) | | | -| 1290 | 398 | "L2 underdark 候选池 = 岩浆+烬覆(第二关解锁)" | pool2_ids == ["magma", "ash"] | (待填) | | | -| 1291 | 399 | "unlock 门控:L1 不进 underdark 行(岩浆/烬覆第二关解锁)" | _db.variant_pool(1, "underdark").is_empty() | (待填) | | | -| 1292 | 403 | "board_types 门控:L2 dungeon 板池 = 荆棘丛+藤蔓缠绕(解锁累积;岩浆/烬覆被 board_types ∅ 交集排除)" | pool2d_ids == ["thorn", "vine"] | (待填) | | | -| 1293 | 405 | "清单外 variant_id 返回空(读取通道在位)" | _db.variation.row_by_id("nonexistent").is_empty() | (待填) | | | -| 1294 | 411 | "跨字段互锁负样本①:flat_damage + placement=any 被拒(只出现在未打开的格子)" | _errs_has_cross_field(vt.validate()) | (待填) | | | -| 1295 | 415 | "跨字段互锁负样本②:hide_number + trigger≠on_number_shown 被拒" | _errs_has_cross_field(vt.validate()) | (待填) | | | +| 1299 | 422 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | (待填) | | | +| 1300 | 425 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1301 | 428 | "空表零修饰(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) | (待填) | | | +| 1302 | 433 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1303 | 435 | "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) | (待填) | | | +| 1304 | 437 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | (待填) | | | +| 1305 | 442 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | +| 1306 | 443 | "越域乘数 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 | (待填) | | | -## S163 _smoke_difficulty_table(行 422–444 · 8 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S165 _smoke_nine_types_gp_stream(行 452–494 · 6 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1296 | 422 | "DifficultyTable MVP 空表加载(结构位)" | _db.difficulty != null and _db.difficulty.rows.is_empty() | (待填) | | | -| 1297 | 425 | "spawn 受理" | enemy_system.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 1298 | 428 | "空表零修饰(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) | (待填) | | | -| 1299 | 433 | "修饰后 spawn 受理" | enemy_system_b.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 1300 | 435 | "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) | (待填) | | | -| 1301 | 437 | "atk 修饰 ×2.0(%d→%d)" ↩ % [int(d0.atk), int(g2.atk)] | int(g2.atk) == int(d0.atk) * 2 | (待填) | | | -| 1302 | 442 | "越域修饰 spawn 受理" | enemy_system_c.spawn(1, 0, int(_db.hero.max_hp)) | (待填) | | | -| 1303 | 443 | "越域乘数 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 | (待填) | | | +| 1307 | 452 | "格面资源十型封闭(第九型 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") | (待填) | | | +| 1308 | 467 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/关卡完成〕;清图槽独立批尾不计)" | cell_rows == 18 | (待填) | | | +| 1309 | 474 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | (待填) | | | +| 1310 | 487 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | (待填) | | | +| 1311 | 489 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | (待填) | | | +| 1312 | 493 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | (待填) | | | -## S164 _smoke_nine_types_gp_stream(行 452–494 · 6 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S166 _smoke_meta(行 502–515 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1304 | 452 | "格面资源十型封闭(第九型 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") | (待填) | | | -| 1305 | 467 | "奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/关卡完成〕;清图槽独立批尾不计)" | cell_rows == 18 | (待填) | | | -| 1306 | 474 | "危险格负向直伤 %s power=%d(卡⑧投值)" ↩ % [rid, int(mine_power[rid])] | int(row.get("power", -1)) == int(mine_power[rid]) | (待填) | | | -| 1307 | 487 | "正向 cell 行权重 ≥1 全在池 / 负向行权重恒 0 不入池(正向合计 %d——配置推导)" ↩ % pos_weight | pos_all_in_pool and neg_all_zero | (待填) | | | -| 1308 | 489 | "gp 型两行(GP 袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)" | gp_pos == 2 and gp_neg == 0 | (待填) | | | -| 1309 | 493 | "derive_at 纯函数不推计数器(统一流全局计数器恒 0——并流后纯函数语义不变,gp_drop 专用向量随分盐作废、统一向量中央断言)" | rng.counter() == 0 | (待填) | | | +| 1313 | 502 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | (待填) | | | +| 1314 | 504 | "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 | (待填) | | | +| 1315 | 510 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | (待填) | | | +| 1316 | 515 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | (待填) | | | -## S165 _smoke_meta(行 502–515 · 4 条 · 源 game/tests/unit/data_config/table_structure_guard_test.gd · 去向:待分类回填) +## S167 _init(行 13–14 · 2 条 · 源 game/tests/unit/example_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1310 | 502 | "MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)" | meta_tbl != null and meta_tbl.validate().is_empty() | (待填) | | | -| 1311 | 504 | "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 | (待填) | | | -| 1312 | 510 | "conversion attr 词表封闭恰全(三属性轴——meta_* 访问器可达)" | attrs_ok | (待填) | | | -| 1313 | 515 | "boss_keystone L1–L3 行连续可达且 first_kill ≥ repeat_kill 全行(首杀全量/重复小额)" | boss_ok | (待填) | | | +| 1317 | 13 | "headless SceneTree 协议可用(quit(code) 通道)" | _test_headless_protocol() | (待填) | | | +| 1318 | 14 | "命名规范可执行:test_[scenario]_[expected]" | _test_naming_convention_followed() | (待填) | | | -## S166 _init(行 13–14 · 2 条 · 源 game/tests/unit/example_test.gd · 去向:待分类回填) +## S168 _test_numeric_lines_carry_values(行 31–36 · 3 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1314 | 13 | "headless SceneTree 协议可用(quit(code) 通道)" | _test_headless_protocol() | (待填) | | | -| 1315 | 14 | "命名规范可执行:test_[scenario]_[expected]" | _test_naming_convention_followed() | (待填) | | | +| 1319 | 31 | "休整叙述带入回复量与当前血量(实际「%s」)" ↩ % rest | rest.contains("12") and rest.contains("30") and rest.contains("40") | (待填) | | | +| 1320 | 34 | "商店叙述带入 GP 数(实际「%s」)" ↩ % shop | shop.contains("77") | (待填) | | | +| 1321 | 35 | "奖励行与休整行互异(不是同一句套所有场景)" | NarratorScript.reward_line() != rest | (待填) | | | -## S167 _test_numeric_lines_carry_values(行 31–36 · 3 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S169 _test_end_text_differs_by_result(行 42–48 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1316 | 31 | "休整叙述带入回复量与当前血量(实际「%s」)" ↩ % rest | rest.contains("12") and rest.contains("30") and rest.contains("40") | (待填) | | | -| 1317 | 34 | "商店叙述带入 GP 数(实际「%s」)" ↩ % shop | shop.contains("77") | (待填) | | | -| 1318 | 35 | "奖励行与休整行互异(不是同一句套所有场景)" | NarratorScript.reward_line() != rest | (待填) | | | +| 1322 | 42 | "关卡收束态恒为「关卡完成!」(实际「%s」)" ↩ % level | level == "关卡完成!" | (待填) | | | +| 1323 | 45 | "单场胜负文案互异(胜「%s」/负「%s」)" ↩ % [win, lose] | win != lose | (待填) | | | +| 1324 | 46 | "胜利文案非空(实际「%s」)" ↩ % win | win != "" | (待填) | | | +| 1325 | 48 | "未知结果有兜底文案不吐空串(实际「%s」)" ↩ % unknown | unknown != "" | (待填) | | | -## S168 _test_end_text_differs_by_result(行 42–48 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S170 _test_phase_text_nonempty(行 53–55 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1319 | 42 | "关卡收束态恒为「关卡完成!」(实际「%s」)" ↩ % level | level == "关卡完成!" | (待填) | | | -| 1320 | 45 | "单场胜负文案互异(胜「%s」/负「%s」)" ↩ % [win, lose] | win != lose | (待填) | | | -| 1321 | 46 | "胜利文案非空(实际「%s」)" ↩ % win | win != "" | (待填) | | | -| 1322 | 48 | "未知结果有兜底文案不吐空串(实际「%s」)" ↩ % unknown | unknown != "" | (待填) | | | +| 1326 | 53 | "相位叙述非空(实际「%s」)" ↩ % t | t != "" | (待填) | | | +| 1327 | 55 | "Boss 在场时叙述带入 Boss 名(实际「%s」)" ↩ % boss | boss.contains("灰矮人石君王") | (待填) | | | -## S169 _test_phase_text_nonempty(行 53–55 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S171 _test_boss_name_uses_boss_rowset(行 82–83 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1323 | 53 | "相位叙述非空(实际「%s」)" ↩ % t | t != "" | (待填) | | | -| 1324 | 55 | "Boss 在场时叙述带入 Boss 名(实际「%s」)" ↩ % boss | boss.contains("灰矮人石君王") | (待填) | | | +| 1328 | 82 | "Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" ↩ % [boss_expect, got] | got == boss_expect | (待填) | | | +| 1329 | 83 | "Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" ↩ % [boss_expect, battle_name] | boss_expect != battle_name | (待填) | | | -## S170 _test_boss_name_uses_boss_rowset(行 82–83 · 2 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S172 _test_level_clear_wording(行 90–101 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1325 | 82 | "Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" ↩ % [boss_expect, got] | got == boss_expect | (待填) | | | -| 1326 | 83 | "Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" ↩ % [boss_expect, battle_name] | boss_expect != battle_name | (待填) | | | +| 1330 | 90 | "关卡完成回血行带入回血量与生命区间(实际「%s」)〔口径换代 V1〕" ↩ % healed | healed.contains("关卡完成回血 +5(生命 10 → 15)") | (待填) | | | +| 1331 | 93 | "满血零回血合法态文案(实际「%s」)〔口径换代 V1〕" ↩ % full_hp | full_hp.contains("生命已满——关卡完成回血 0(合法态)") | (待填) | | | +| 1332 | 96 | "奖励节点头部行渠道句点明关卡完成三选一(实际「%s」)〔口径换代 V2〕" ↩ % node_header | node_header.contains("与关卡完成专长三选一同库分流") | (待填) | | | +| 1333 | 100 | "冒险摘要完成关数行(实际「%s」)〔口径换代 V3〕" ↩ % summary | summary.contains("完成 1 关 · 止步第 2 层") and not summary.contains("通关") | (待填) | | | -## S171 _test_level_clear_wording(行 90–101 · 4 条 · 源 game/tests/unit/presentation/battle_narrator_test.gd · 去向:待分类回填) +## S173 _run(行 27–102 · 13 条 · 源 game/tests/unit/presentation/forge_workbench_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1327 | 90 | "关卡完成回血行带入回血量与生命区间(实际「%s」)〔口径换代 V1〕" ↩ % healed | healed.contains("关卡完成回血 +5(生命 10 → 15)") | (待填) | | | -| 1328 | 93 | "满血零回血合法态文案(实际「%s」)〔口径换代 V1〕" ↩ % full_hp | full_hp.contains("生命已满——关卡完成回血 0(合法态)") | (待填) | | | -| 1329 | 96 | "奖励节点头部行渠道句点明关卡完成三选一(实际「%s」)〔口径换代 V2〕" ↩ % node_header | node_header.contains("与关卡完成专长三选一同库分流") | (待填) | | | -| 1330 | 100 | "冒险摘要完成关数行(实际「%s」)〔口径换代 V3〕" ↩ % summary | summary.contains("完成 1 关 · 止步第 2 层") and not summary.contains("通关") | (待填) | | | +| 1334 | 27 | "ConfigDb 加载(工作间测试夹具前提)" | false | (待填) | | | +| 1335 | 44 | "锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕" | not ov._forge_workbench.visible and ov._forge_lock_l.visible | (待填) | | | +| 1336 | 46 | "锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0 | (待填) | | | +| 1337 | 52 | "解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕" | ov._forge_workbench.visible | (待填) | | | +| 1338 | 53 | "两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3 | (待填) | | | +| 1339 | 56 | "六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕" | rows.has("sword") and rows.has("spear") and rows.has("staff") and rows.has("light") and rows.has("heavy") and rows.has("ward") | (待填) | | | +| 1340 | 63 | "长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕" | rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled | (待填) | | | +| 1341 | 66 | "轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕" | rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled | (待填) | | | +| 1342 | 73 | "长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕" | rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled and rsp.get("buy_btn") == null and rsp.get("select_btn") == null | (待填) | | | +| 1343 | 76 | "魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕" | rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") and String(rwa.status_l.text).contains("200") | (待填) | | | +| 1344 | 84 | "余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕" | rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled | (待填) | | | +| 1345 | 92 | "长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕" | rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null | (待填) | | | +| 1346 | 101 | "三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕" | buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"] | (待填) | | | -## S172 _run(行 27–102 · 13 条 · 源 game/tests/unit/presentation/forge_workbench_test.gd · 去向:待分类回填) +## S174 _test_accepted_text(行 20–20 · 1 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1331 | 27 | "ConfigDb 加载(工作间测试夹具前提)" | false | (待填) | | | -| 1332 | 44 | "锁定态:工作间不可见(forge_workshop 钥匙未点亮)〔锻造工坊批 V13〕" | not ov._forge_workbench.visible and ov._forge_lock_l.visible | (待填) | | | -| 1333 | 46 | "锁定态:两区零行板(占位纪律——零购买路径)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 0 and ov._forge_zone_armor.get_child_count() == 0 | (待填) | | | -| 1334 | 52 | "解锁态:工作间可见(钥匙点亮展开)〔锻造工坊批 V13〕" | ov._forge_workbench.visible | (待填) | | | -| 1335 | 53 | "两区各三路线(武器锻造区 3 + 护甲锻造区 3)〔锻造工坊批 V13〕" | ov._forge_zone_weapon.get_child_count() == 3 and ov._forge_zone_armor.get_child_count() == 3 | (待填) | | | -| 1336 | 56 | "六路线行册恰全(sword/spear/staff/light/heavy/ward)〔锻造工坊批 V13〕" | rows.has("sword") and rows.has("spear") and rows.has("staff") and rows.has("light") and rows.has("heavy") and rows.has("ward") | (待填) | | | -| 1337 | 63 | "长剑行:购买钮「锻造 20」(0 级位几何价)+选中钮「当前」禁用〔锻造工坊批 V13〕" | rsw.get("buy_btn") != null and String(rsw.buy_btn.text) == "锻造 20" and not rsw.buy_btn.disabled and rsw.get("select_btn") != null and String(rsw.select_btn.text) == "当前" and rsw.select_btn.disabled | (待填) | | | -| 1338 | 66 | "轻甲行:购买钮「锻造 20」+选中钮「当前」禁用〔锻造工坊批 V13〕" | rli.get("buy_btn") != null and String(rli.buy_btn.text) == "锻造 20" and String(rli.select_btn.text) == "当前" and rli.select_btn.disabled | (待填) | | | -| 1339 | 73 | "长枪行:解锁钮「解锁 200」+无购买钮+无选中钮〔锻造工坊批 V13〕" | rsp.get("unlock_btn") != null and String(rsp.unlock_btn.text) == "解锁 200" and not rsp.unlock_btn.disabled and rsp.get("buy_btn") == null and rsp.get("select_btn") == null | (待填) | | | -| 1340 | 76 | "魔法护甲行:解锁钮「解锁 200」+状态含并联双通道文案(练满轻甲锻造 或 200 钥石直购)〔锻造工坊批 V13〕" | rwa.get("unlock_btn") != null and String(rwa.status_l.text).contains("练满轻甲锻造") and String(rwa.status_l.text).contains("200") | (待填) | | | -| 1341 | 84 | "余额 0:长剑购买钮禁用+长枪解锁钮禁用(余额联动常态防线)〔锻造工坊批 V13〕" | rows.sword.buy_btn.disabled and rows.spear.unlock_btn.disabled | (待填) | | | -| 1342 | 92 | "长剑满级:无购买钮+状态「已满级」+长枪派生解锁出解锁钮消失(练满通道)〔锻造工坊批 V13〕" | rows.sword.get("buy_btn") == null and String(rows.sword.status_l.text) == "已满级" and rows.spear.get("unlock_btn") == null and rows.spear.get("buy_btn") != null | (待填) | | | -| 1343 | 101 | "三意图信号外发(buy=spear/unlock=heavy/select=spear——UI 只发意图)〔锻造工坊批 V13〕" | buys == ["spear"] and unlocks == ["heavy"] and selects == ["spear"] | (待填) | | | +| 1347 | 20 | "受理态文案固定为「执行完成。」(实际「%s」)" ↩ % t | t == "执行完成。" | (待填) | | | -## S173 _test_accepted_text(行 20–20 · 1 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S175 _test_reject_text_by_reason(行 36–38 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1344 | 20 | "受理态文案固定为「执行完成。」(实际「%s」)" ↩ % t | t == "执行完成。" | (待填) | | | +| 1348 | 36 | "棋盘面各拒绝原因文案逐类正确(错 %d 项 %s)" ↩ % [bad.size(), str(bad)] | bad.is_empty() | (待填) | | | +| 1349 | 38 | "资源面拒绝文案为「AP 不足。」(实际「%s」)" ↩ % ap | ap == "AP 不足。" | (待填) | | | -## S174 _test_reject_text_by_reason(行 36–38 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S176 _test_unknown_reason_fallback(行 43–47 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1345 | 36 | "棋盘面各拒绝原因文案逐类正确(错 %d 项 %s)" ↩ % [bad.size(), str(bad)] | bad.is_empty() | (待填) | | | -| 1346 | 38 | "资源面拒绝文案为「AP 不足。」(实际「%s」)" ↩ % ap | ap == "AP 不足。" | (待填) | | | +| 1350 | 43 | "未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" ↩ % t | t == "动作被拒。" | (待填) | | | +| 1351 | 47 | "棋盘面未知子因兜底带前缀可诊断(实际「%s」)" ↩ % sub | sub.begins_with("棋盘面拒绝: ") | (待填) | | | -## S175 _test_unknown_reason_fallback(行 43–47 · 2 条 · 源 game/tests/unit/presentation/hud_presenter_test.gd · 去向:待分类回填) +## S177 _test_fog_hides_type(行 51–56 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1347 | 43 | "未知拒绝原因兜底为「动作被拒。」而非空串(实际「%s」)" ↩ % t | t == "动作被拒。" | (待填) | | | -| 1348 | 47 | "棋盘面未知子因兜底带前缀可诊断(实际「%s」)" ↩ % sub | sub.begins_with("棋盘面拒绝: ") | (待填) | | | +| 1352 | 51 | "可见集内节点:图标/类型文字均绘制〔fv2〕" | bool(shown["draw_icon"]) and bool(shown["draw_label"]) | (待填) | | | +| 1353 | 54 | "雾下节点:图标与类型文字均不绘制(显隐语义,非灰化)〔fv2〕" | not bool(fogged["draw_icon"]) and not bool(fogged["draw_label"]) | (待填) | | | +| 1354 | 56 | "雾下非 Boss 节点无 Boss 描边〔fv2〕" | not bool(fogged["boss_border"]) | (待填) | | | -## S176 _test_fog_hides_type(行 50–55 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S178 _test_boss_exempt_and_multi_boss(行 65–73 · 4 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1349 | 50 | "可见集内节点:图标/类型文字均绘制〔fv2〕" | bool(shown["draw_icon"]) and bool(shown["draw_label"]) | (待填) | | | -| 1350 | 53 | "雾下节点:图标与类型文字均不绘制(显隐语义,非灰化)〔fv2〕" | not bool(fogged["draw_icon"]) and not bool(fogged["draw_label"]) | (待填) | | | -| 1351 | 55 | "雾下非 Boss 节点无 Boss 描边〔fv2〕" | not bool(fogged["boss_border"]) | (待填) | | | +| 1355 | 65 | "多终点:两个 Boss 节点均带 Boss 描边〔fv3〕" | bool(boss_a["boss_border"]) and bool(boss_b["boss_border"]) | (待填) | | | +| 1356 | 67 | "Boss 不入雾:可见集与图标/文字三通道均在〔fv3〕" | bool(boss_a["draw_icon"]) and bool(boss_a["draw_label"]) and bool(boss_b["draw_icon"]) | (待填) | | | +| 1357 | 70 | "Boss 豁免独立于可见集:不在 type_vis 的 Boss 仍出示类型〔fv3〕" | bool(far_boss["draw_icon"]) and bool(far_boss["draw_label"]) | (待填) | | | +| 1358 | 73 | "Boss 豁免不扩散:雾下非 Boss 仍隐类型〔fv3〕" | not bool(fogged_battle["draw_icon"]) | (待填) | | | -## S177 _test_boss_exempt_and_multi_boss(行 64–72 · 4 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S179 _test_details_never_shown(行 87–89 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1352 | 64 | "多终点:两个 Boss 节点均带 Boss 描边〔fv3〕" | bool(boss_a["boss_border"]) and bool(boss_b["boss_border"]) | (待填) | | | -| 1353 | 66 | "Boss 不入雾:可见集与图标/文字三通道均在〔fv3〕" | bool(boss_a["draw_icon"]) and bool(boss_a["draw_label"]) and bool(boss_b["draw_icon"]) | (待填) | | | -| 1354 | 69 | "Boss 豁免独立于可见集:不在 type_vis 的 Boss 仍出示类型〔fv3〕" | bool(far_boss["draw_icon"]) and bool(far_boss["draw_label"]) | (待填) | | | -| 1355 | 72 | "Boss 豁免不扩散:雾下非 Boss 仍隐类型〔fv3〕" | not bool(fogged_battle["draw_icon"]) | (待填) | | | +| 1359 | 87 | "决策面不消费内容键:携带 enemy_id/reward_id 与不带者输出逐键相等〔fv5〕" | same | (待填) | | | +| 1360 | 88 | "label_text 不含内容值(实际「%s」)〔fv5〕" ↩ % String(b["label_text"]) | not String(b["label_text"]).contains("goblin_secret") and not String(b["label_text"]).contains("reward_secret") | (待填) | | | -## S178 _test_details_never_shown(行 86–88 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S180 _test_label_three_states(行 96–101 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1356 | 86 | "决策面不消费内容键:携带 enemy_id/reward_id 与不带者输出逐键相等〔fv5〕" | same | (待填) | | | -| 1357 | 87 | "label_text 不含内容值(实际「%s」)〔fv5〕" ↩ % String(b["label_text"]) | not String(b["label_text"]).contains("goblin_secret") and not String(b["label_text"]).contains("reward_secret") | (待填) | | | +| 1361 | 96 | "当前位文案=「当前位置」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置" | (待填) | | | +| 1362 | 98 | "候选文案=「战斗 · 可选」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, true)["label_text"]) == "战斗 · 可选" | (待填) | | | +| 1363 | 100 | "其余文案=类型名「战斗」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, false)["label_text"]) == "战斗" | (待填) | | | -## S179 _test_label_three_states(行 95–100 · 3 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S181 _test_big_graph_fit(行 165–191 · 6 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1358 | 95 | "当前位文案=「当前位置」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], true, false)["label_text"]) == "当前位置" | (待填) | | | -| 1359 | 97 | "候选文案=「战斗 · 可选」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, true)["label_text"]) == "战斗 · 可选" | (待填) | | | -| 1360 | 99 | "其余文案=类型名「战斗」〔fv2〕" | String(ScreenScript.node_display(node, 3, [3], false, false)["label_text"]) == "战斗" | (待填) | | | +| 1364 | 165 | "大图夹具规模:≥11 层 × ≥6 槽(实测最大层 %d/最大槽 %d)〔fv8〕" ↩ % [maxl, maxslot] | maxl >= 10 and maxslot >= 5 | (待填) | | | +| 1365 | 171 | "大图拟合:画布 min.x 覆盖最深节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.x, need_w] | min_size.x >= need_w - 0.01 | (待填) | | | +| 1366 | 173 | "大图拟合:画布 min.y 覆盖最宽层底节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.y, need_h] | min_size.y >= need_h - 0.01 | (待填) | | | +| 1367 | 181 | "大图布局:全节点方块落在拟合画布内(origin=(%.1f,%.1f) size=(%.1f,%.1f))〔fv8〕" ↩ % [origin.x, origin.y, min_size.x, min_size.y] | all_in | (待填) | | | +| 1368 | 188 | "大图呈现:两个终点 Boss 均有描边标记〔fv8〕" | disp.has(boss_ids[0]) and disp.has(boss_ids[1]) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]) | (待填) | | | +| 1369 | 191 | "大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕" | not bool(disp[32]["draw_icon"]) | (待填) | | | -## S180 _test_big_graph_fit(行 164–190 · 6 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) +## S182 _test_layout_cache(行 209–226 · 2 条 · 源 game/tests/unit/presentation/level_map_info_fog_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1361 | 164 | "大图夹具规模:≥11 层 × ≥6 槽(实测最大层 %d/最大槽 %d)〔fv8〕" ↩ % [maxl, maxslot] | maxl >= 10 and maxslot >= 5 | (待填) | | | -| 1362 | 170 | "大图拟合:画布 min.x 覆盖最深节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.x, need_w] | min_size.x >= need_w - 0.01 | (待填) | | | -| 1363 | 172 | "大图拟合:画布 min.y 覆盖最宽层底节点(%.1f ≥ %.1f)〔fv8〕" ↩ % [min_size.y, need_h] | min_size.y >= need_h - 0.01 | (待填) | | | -| 1364 | 180 | "大图布局:全节点方块落在拟合画布内(origin=(%.1f,%.1f) size=(%.1f,%.1f))〔fv8〕" ↩ % [origin.x, origin.y, min_size.x, min_size.y] | all_in | (待填) | | | -| 1365 | 187 | "大图呈现:两个终点 Boss 均有描边标记〔fv8〕" | disp.has(boss_ids[0]) and disp.has(boss_ids[1]) and bool(disp[boss_ids[0]]["boss_border"]) and bool(disp[boss_ids[1]]["boss_border"]) | (待填) | | | -| 1366 | 190 | "大图呈现:入口停点远处战斗节点(句柄 32,层 6)类型入雾〔fv8〕" | not bool(disp[32]["draw_icon"]) | (待填) | | | +| 1370 | 209 | "布局缓存:同 nodes 两次查询返回同一实例(第二次命中缓存)〔levelmap批 V1〕" | is_same(p1, p2) | (待填) | | | +| 1371 | 224 | "布局缓存换代:新图查询返回新实例非旧缓存(失效重算生效)〔levelmap批 V2〕" | not is_same(p1, pc1) and is_same(pc1, pc2) and (pc1[chain[0]] as Vector2) != (p1[nodes[0]] as Vector2) | (待填) | | | -## S181 _init(行 105–140 · 9 条 · 源 game/tests/unit/presentation/pacing_queue_test.gd · 去向:待分类回填) +## S183 _init(行 105–140 · 9 条 · 源 game/tests/unit/presentation/pacing_queue_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1367 | 105 | "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)" | snap_before == snap_after | (待填) | | | -| 1368 | 107 | "结算行已产生(断言前提)" | rows_seen.size() > 0 | (待填) | | | -| 1369 | 108 | "排空后队列恒空(flush 完整性)" | main._row_queue.is_empty() | (待填) | | | -| 1370 | 117 | "末信号行原文在明细带(排空不吞行:『%s』)" ↩ % last_text | last_text != "" and main.log_view.parsed_text().contains(last_text) | (待填) | | | -| 1371 | 123 | "常态段间延迟 >0 且 ≥ 段内(%f/%f)" ↩ % [d_on, d_same] | d_on > 0.0 and d_on >= d_same | (待填) | | | -| 1372 | 125 | "减动效段间延迟归零(行不跳过——同帧直落快路)" | main.pace_delay_for("damage", "heal") == 0.0 | (待填) | | | -| 1373 | 135 | "结算标出按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "哥布林"] | (待填) | | | -| 1374 | 138 | "跨区弹道按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 2 and fstage.calls[1] == ["goblin_b", "哥布林"] | (待填) | | | -| 1375 | 140 | "两路解析成功后演出层各接一拍(记参面非空)〔wd12〕" | ffx.calls.size() == 2 | (待填) | | | +| 1372 | 105 | "排空前后核心诊断快照逐位一致(AC⑤ 演出关停零分叉)" | snap_before == snap_after | (待填) | | | +| 1373 | 107 | "结算行已产生(断言前提)" | rows_seen.size() > 0 | (待填) | | | +| 1374 | 108 | "排空后队列恒空(flush 完整性)" | main._row_queue.is_empty() | (待填) | | | +| 1375 | 117 | "末信号行原文在明细带(排空不吞行:『%s』)" ↩ % last_text | last_text != "" and main.log_view.parsed_text().contains(last_text) | (待填) | | | +| 1376 | 123 | "常态段间延迟 >0 且 ≥ 段内(%f/%f)" ↩ % [d_on, d_same] | d_on > 0.0 and d_on >= d_same | (待填) | | | +| 1377 | 125 | "减动效段间延迟归零(行不跳过——同帧直落快路)" | main.pace_delay_for("damage", "heal") == 0.0 | (待填) | | | +| 1378 | 135 | "结算标出按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "哥布林"] | (待填) | | | +| 1379 | 138 | "跨区弹道按行 target_id 解析站桩(id 与名同传——d12)〔wd12〕" | fstage.calls.size() == 2 and fstage.calls[1] == ["goblin_b", "哥布林"] | (待填) | | | +| 1380 | 140 | "两路解析成功后演出层各接一拍(记参面非空)〔wd12〕" | ffx.calls.size() == 2 | (待填) | | | -## S182 _sweep_hero_panel(行 47–50 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S184 _sweep_hero_panel(行 47–50 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1376 | 47 | "C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕" | feat_head != null and feat_head.text == "专长" | (待填) | | | -| 1377 | 49 | "C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕" | curio_head != null and curio_head.text == "属性值提升" | (待填) | | | +| 1381 | 47 | "C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕" | feat_head != null and feat_head.text == "专长" | (待填) | | | +| 1382 | 49 | "C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕" | curio_head != null and curio_head.text == "属性值提升" | (待填) | | | -## S183 _sweep_narrator(行 57–64 · 7 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S185 _sweep_narrator(行 57–64 · 7 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1378 | 57 | "奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕" | head.contains("冒险牌库") | (待填) | | | -| 1379 | 58 | "奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕" | head.contains("属性值提升与补给") | (待填) | | | -| 1380 | 59 | "奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕" | head.contains("同库分流") | (待填) | | | -| 1381 | 60 | "奖励点头部零「同权重」旧话(u3 清零)〔wa4〕" | not head.contains("同权重") | (待填) | | | -| 1382 | 61 | "奖励点头部零「同上限」旧话(u3 清零)〔wa4〕" | not head.contains("同上限") | (待填) | | | -| 1383 | 62 | "奖励点头部零「提升池」旧话(u3 清零)〔wa4〕" | not head.contains("提升池") | (待填) | | | -| 1384 | 63 | "专长卡兜底==「专长\\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕" | String(narrator.feat_card_text({})) == "专长\\n???" | (待填) | | | +| 1383 | 57 | "奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕" | head.contains("冒险牌库") | (待填) | | | +| 1384 | 58 | "奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕" | head.contains("属性值提升与补给") | (待填) | | | +| 1385 | 59 | "奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕" | head.contains("同库分流") | (待填) | | | +| 1386 | 60 | "奖励点头部零「同权重」旧话(u3 清零)〔wa4〕" | not head.contains("同权重") | (待填) | | | +| 1387 | 61 | "奖励点头部零「同上限」旧话(u3 清零)〔wa4〕" | not head.contains("同上限") | (待填) | | | +| 1388 | 62 | "奖励点头部零「提升池」旧话(u3 清零)〔wa4〕" | not head.contains("提升池") | (待填) | | | +| 1389 | 63 | "专长卡兜底==「专长\\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕" | String(narrator.feat_card_text({})) == "专长\\n???" | (待填) | | | -## S184 _sweep_legend(行 75–79 · 5 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S186 _sweep_legend(行 75–79 · 5 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1385 | 75 | "图例封印行存在(wa5 夹具前提)〔wa5〕" | not seal_row.is_empty() | (待填) | | | -| 1386 | 76 | "图例封印行含「标空」(u4——与「标中」成对)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("标空") | (待填) | | | -| 1387 | 77 | "图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚") | (待填) | | | -| 1388 | 78 | "图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错") | (待填) | | | -| 1389 | 79 | "图例封印行零「挖雷」(u4 雷字清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷") | (待填) | | | +| 1390 | 75 | "图例封印行存在(wa5 夹具前提)〔wa5〕" | not seal_row.is_empty() | (待填) | | | +| 1391 | 76 | "图例封印行含「标空」(u4——与「标中」成对)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("标空") | (待填) | | | +| 1392 | 77 | "图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕" | String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚") | (待填) | | | +| 1393 | 78 | "图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错") | (待填) | | | +| 1394 | 79 | "图例封印行零「挖雷」(u4 雷字清零)〔wa5〕" | not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷") | (待填) | | | -## S185 _sweep_source_names(行 92–104 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S187 _sweep_source_names(行 92–104 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1390 | 92 | "状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕" | slot != null | (待填) | | | -| 1391 | 95 | "状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕" | row_view._tip_label.text.contains("来源:危险格") | (待填) | | | -| 1392 | 98 | "结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕" | String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格" | (待填) | | | -| 1393 | 103 | "战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕" | row_txt.contains("(危险格)") | (待填) | | | +| 1395 | 92 | "状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕" | slot != null | (待填) | | | +| 1396 | 95 | "状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕" | row_view._tip_label.text.contains("来源:危险格") | (待填) | | | +| 1397 | 98 | "结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕" | String(_db.settlement.source_label_map.get("mine_hit", "")) == "危险格" | (待填) | | | +| 1398 | 103 | "战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕" | row_txt.contains("(危险格)") | (待填) | | | -## S186 _sweep_board_glyph(行 133–142 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S188 _sweep_board_glyph(行 133–142 · 4 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1394 | 133 | "棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" ↩ % glow_steps | mine_cell.x >= 0 | (待填) | | | -| 1395 | 139 | "已开危险格 glyph==「危」(u7 换代)〔wa8〕" | u.glyph == "危" | (待填) | | | -| 1396 | 140 | "已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕" | u.plate == true | (待填) | | | -| 1397 | 141 | "已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕" | u.glyph_color == UiKit.RED_MINE | (待填) | | | +| 1399 | 133 | "棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" ↩ % glow_steps | mine_cell.x >= 0 | (待填) | | | +| 1400 | 139 | "已开危险格 glyph==「危」(u7 换代)〔wa8〕" | u.glyph == "危" | (待填) | | | +| 1401 | 140 | "已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕" | u.plate == true | (待填) | | | +| 1402 | 141 | "已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕" | u.glyph_color == UiKit.RED_MINE | (待填) | | | -## S187 _sweep_token_scans(行 148–158 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) +## S189 _sweep_token_scans(行 148–158 · 2 条 · 源 game/tests/unit/presentation/player_copy_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1398 | 148 | "board_view.gd 源零 \\"雷\\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕" | board_src.count("\\"雷\\"") == 0 | (待填) | | | -| 1399 | 157 | "battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕" | orch_hits == 0 | (待填) | | | +| 1403 | 148 | "board_view.gd 源零 \\"雷\\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕" | board_src.count("\\"雷\\"") == 0 | (待填) | | | +| 1404 | 157 | "battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕" | orch_hits == 0 | (待填) | | | -## S188 _init(行 75–132 · 18 条 · 源 game/tests/unit/presentation/standee_fx_sweep_test.gd · 去向:待分类回填) +## S190 _init(行 75–132 · 18 条 · 源 game/tests/unit/presentation/standee_fx_sweep_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1400 | 75 | "站桩 enemy_id 访问器读装载值〔wd12〕" | s1.enemy_id() == "goblin_a" and s2.enemy_id() == "goblin_b" | (待填) | | | -| 1401 | 76 | "同名敌按 id 精确解析(不做名子串撞车)〔wd12〕" | stage._card_by_id("goblin_b") == s2 and stage._card_by_id("goblin_a") == s1 | (待填) | | | -| 1402 | 78 | "空 id / 未登记 id 恒不命中(名兜底归调用点)〔wd12〕" | stage._card_by_id("") == null and stage._card_by_id("no_such") == null | (待填) | | | -| 1403 | 83 | "伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕" | s2._flash_count == 1 and s1._flash_count == 0 | (待填) | | | -| 1404 | 88 | "弹道落点按 target_id 取站桩中心〔wd12〕" | c_b != null and c_b == c_b_expect | (待填) | | | -| 1405 | 91 | "无 target_id 时按名兜底同旧行为(首命中)〔wd12〕" | c_fallback != null and c_fallback == c_fallback_expect | (待填) | | | -| 1406 | 95 | "蓄力技能(heavy)置 _intent_charge=true〔wd2〕" | s1._intent_charge | (待填) | | | -| 1407 | 97 | "非蓄力攻击技(goblin_slash)零置位〔wd2〕" | not s1._intent_charge | (待填) | | | -| 1408 | 99 | "增益系零置位〔wd2〕" | not s1._intent_charge | (待填) | | | -| 1409 | 101 | "普攻零表示路径复位 _intent_charge〔wd2〕" | not s1._intent_charge | (待填) | | | -| 1410 | 108 | "首开胜利画面创建庆祝句柄〔wd4〕" | first != null and first.is_valid() | (待填) | | | -| 1411 | 111 | "重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕" | first != null and not first.is_valid() and second != null and second.is_valid() | (待填) | | | -| 1412 | 114 | "close 必杀庆祝句柄(句柄清空)〔wd4〕" | ov.get_meta(&"celebrate_tw", null) == null | (待填) | | | -| 1413 | 119 | "模拟运行期调试入口钮同置灰〔wd5〕" | dbg._dbg_btn.disabled | (待填) | | | -| 1414 | 121 | "模拟收尾调试入口钮恢复(未锁定)〔wd5〕" | not dbg._dbg_btn.disabled | (待填) | | | -| 1415 | 130 | "控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕" | drv.seen == [true, true] | (待填) | | | -| 1416 | 131 | "控制器收尾 is_running()=false〔wd8〕" | not ctrl.is_running() and not panel.running | (待填) | | | -| 1417 | 132 | "两局报告全文注入面板(run 收尾链路完整)〔wd8〕" | String(panel.report) != "" | (待填) | | | +| 1405 | 75 | "站桩 enemy_id 访问器读装载值〔wd12〕" | s1.enemy_id() == "goblin_a" and s2.enemy_id() == "goblin_b" | (待填) | | | +| 1406 | 76 | "同名敌按 id 精确解析(不做名子串撞车)〔wd12〕" | stage._card_by_id("goblin_b") == s2 and stage._card_by_id("goblin_a") == s1 | (待填) | | | +| 1407 | 78 | "空 id / 未登记 id 恒不命中(名兜底归调用点)〔wd12〕" | stage._card_by_id("") == null and stage._card_by_id("no_such") == null | (待填) | | | +| 1408 | 83 | "伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕" | s2._flash_count == 1 and s1._flash_count == 0 | (待填) | | | +| 1409 | 88 | "弹道落点按 target_id 取站桩中心〔wd12〕" | c_b != null and c_b == c_b_expect | (待填) | | | +| 1410 | 91 | "无 target_id 时按名兜底同旧行为(首命中)〔wd12〕" | c_fallback != null and c_fallback == c_fallback_expect | (待填) | | | +| 1411 | 95 | "蓄力技能(heavy)置 _intent_charge=true〔wd2〕" | s1._intent_charge | (待填) | | | +| 1412 | 97 | "非蓄力攻击技(goblin_slash)零置位〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1413 | 99 | "增益系零置位〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1414 | 101 | "普攻零表示路径复位 _intent_charge〔wd2〕" | not s1._intent_charge | (待填) | | | +| 1415 | 108 | "首开胜利画面创建庆祝句柄〔wd4〕" | first != null and first.is_valid() | (待填) | | | +| 1416 | 111 | "重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕" | first != null and not first.is_valid() and second != null and second.is_valid() | (待填) | | | +| 1417 | 114 | "close 必杀庆祝句柄(句柄清空)〔wd4〕" | ov.get_meta(&"celebrate_tw", null) == null | (待填) | | | +| 1418 | 119 | "模拟运行期调试入口钮同置灰〔wd5〕" | dbg._dbg_btn.disabled | (待填) | | | +| 1419 | 121 | "模拟收尾调试入口钮恢复(未锁定)〔wd5〕" | not dbg._dbg_btn.disabled | (待填) | | | +| 1420 | 130 | "控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕" | drv.seen == [true, true] | (待填) | | | +| 1421 | 131 | "控制器收尾 is_running()=false〔wd8〕" | not ctrl.is_running() and not panel.running | (待填) | | | +| 1422 | 132 | "两局报告全文注入面板(run 收尾链路完整)〔wd8〕" | String(panel.report) != "" | (待填) | | | -## S189 _init(行 24–34 · 2 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S191 _init(行 24–34 · 2 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1418 | 24 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | -| 1419 | 34 | "前置:board_gen 词表非空(%d 型)" ↩ % types.size() | types.size() > 0 | (待填) | | | +| 1423 | 24 | "前置:表加载零 error" | load_ok and db.diagnostics_count("error") == 0 | (待填) | | | +| 1424 | 34 | "前置:board_gen 词表非空(%d 型)" ↩ % types.size() | types.size() > 0 | (待填) | | | -## S190 _test_set_completeness(行 56–56 · 1 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S192 _test_set_completeness(行 56–56 · 1 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1420 | 56 | "图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" ↩ % [String(bt), variants, str(missing)] | missing.is_empty() | (待填) | | | +| 1425 | 56 | "图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" ↩ % [String(bt), variants, str(missing)] | missing.is_empty() | (待填) | | | -## S191 _test_hue_domains(行 72–82 · 4 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S193 _test_hue_domains(行 72–82 · 4 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1421 | 72 | "图型 %s 抽检样本可读" ↩ % String(bt) | false | (待填) | | | -| 1422 | 77 | "图型 %s 墙顶均值色相在带 %s(实测 %.3f)" ↩ % [String(bt), str(band), float(wall["h"])] | float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]) | (待填) | | | -| 1423 | 79 | "图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" ↩ % [String(bt), wall_v[bt], floor_v[bt]] | wall_v[bt] > floor_v[bt] | (待填) | | | -| 1424 | 82 | "deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)" | float(wall_v["deep_underdark"]) < float(wall_v["underdark"]) | (待填) | | | +| 1426 | 72 | "图型 %s 抽检样本可读" ↩ % String(bt) | false | (待填) | | | +| 1427 | 77 | "图型 %s 墙顶均值色相在带 %s(实测 %.3f)" ↩ % [String(bt), str(band), float(wall["h"])] | float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]) | (待填) | | | +| 1428 | 79 | "图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" ↩ % [String(bt), wall_v[bt], floor_v[bt]] | wall_v[bt] > floor_v[bt] | (待填) | | | +| 1429 | 82 | "deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)" | float(wall_v["deep_underdark"]) < float(wall_v["underdark"]) | (待填) | | | -## S192 _test_digit_ink_modes(行 137–159 · 5 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) +## S194 _test_digit_ink_modes(行 137–159 · 5 条 · 源 game/tests/unit/presentation/terrain_tile_sets_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1425 | 137 | "图型 %s 数字墨族断言样本可读" ↩ % String(bt) | false | (待填) | | | -| 1426 | 144 | "图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" ↩ % [ String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])] | float(mode["cr_mean_dark"]) < float(TilePaletteScript.DIGIT_INK_CR_MEAN_DARK) | (待填) | | | -| 1427 | 151 | "图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, float(TilePaletteScript.DIGIT_INK_CR_P90), cr_p90] | cr_p90 >= float(TilePaletteScript.DIGIT_INK_CR_P90) | (待填) | | | -| 1428 | 155 | "图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, mean_gate, cr_mean] | cr_mean >= mean_gate | (待填) | | | -| 1429 | 158 | "图型 %s 呈现模式期望=%s(实测=%s)" ↩ % [String(bt), "色牌" if bool(expect_plate[bt]) else "直画", mode_name] | plate == bool(expect_plate[bt]) | (待填) | | | +| 1430 | 137 | "图型 %s 数字墨族断言样本可读" ↩ % String(bt) | false | (待填) | | | +| 1431 | 144 | "图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" ↩ % [ String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]), float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])] | float(mode["cr_mean_dark"]) < float(TilePaletteScript.DIGIT_INK_CR_MEAN_DARK) | (待填) | | | +| 1432 | 151 | "图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, float(TilePaletteScript.DIGIT_INK_CR_P90), cr_p90] | cr_p90 >= float(TilePaletteScript.DIGIT_INK_CR_P90) | (待填) | | | +| 1433 | 155 | "图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" ↩ % [String(bt), mode_name, mean_gate, cr_mean] | cr_mean >= mean_gate | (待填) | | | +| 1434 | 158 | "图型 %s 呈现模式期望=%s(实测=%s)" ↩ % [String(bt), "色牌" if bool(expect_plate[bt]) else "直画", mode_name] | plate == bool(expect_plate[bt]) | (待填) | | | -## S193 _smoke_type_glow(行 215–375 · 24 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S195 _smoke_type_glow(行 215–375 · 24 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1430 | 215 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | (待填) | | | -| 1431 | 217 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | -| 1432 | 220 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | -| 1433 | 222 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | (待填) | | | -| 1434 | 232 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | (待填) | | | -| 1435 | 249 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | (待填) | | | -| 1436 | 258 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | (待填) | | | -| 1437 | 272 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | (待填) | | | -| 1438 | 273 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | (待填) | | | -| 1439 | 275 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | -| 1440 | 288 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | (待填) | | | -| 1441 | 290 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | (待填) | | | -| 1442 | 292 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | -| 1443 | 298 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | -| 1444 | 304 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | (待填) | | | -| 1445 | 305 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | -| 1446 | 308 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | (待填) | | | -| 1447 | 309 | "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", {})) | (待填) | | | -| 1448 | 312 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | (待填) | | | -| 1449 | 316 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | (待填) | | | -| 1450 | 329 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | (待填) | | | -| 1451 | 331 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | (待填) | | | -| 1452 | 372 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | (待填) | | | -| 1453 | 375 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | (待填) | | | +| 1435 | 215 | "V1 微光默认开(Q4 丙案——呈现态布尔缺省 true,生产写入面零显式开)" | bool(snap.get("enabled", false)) | (待填) | | | +| 1436 | 217 | "V1 夹具全集合法九型 ⇒ 清单外登记为空(禁静默空光通道零误报)" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | +| 1437 | 220 | "V1 作用集恒等:微光格集合 == _diggable ∩ 身份可查(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | +| 1438 | 222 | "V1 夹具前提:英雄未开邻格 ≥5(实测 %d——novice %d 步)" ↩ % [expect.size(), steps] | expect.size() >= 5 | (待填) | | | +| 1439 | 232 | "V1 已开格/r0 出生展开区零微光(S1)%s" ↩ % ("" if opened_leak == "" else "违例:" + opened_leak) | opened_leak == "" | (待填) | | | +| 1440 | 249 | "V2 夹具前提:未开邻格派定 ≥5 型(实测 %d)" ↩ % mapped | mapped >= 5 | (待填) | | | +| 1441 | 258 | "V2 九型→五组逐格映射(attack/defense/action/gp→增益/heal→治疗/pierce→穿刺/blast→群伤/magic→魔法)%s" ↩ % ("" if map_bad == "" else "违例:" + map_bad) | map_bad == "" | (待填) | | | +| 1442 | 272 | "V2 清单外 cell_type 显式登记(errors=%s)" ↩ % str(errs) | errs.has("keystone") | (待填) | | | +| 1443 | 273 | "V2 清单外格零微光(无静默空光——%s 不在作用集)" ↩ % str(bogus_cell) | not (snap.get("cells", {}) as Dictionary).has(bogus_cell) | (待填) | | | +| 1444 | 275 | "V2 清单外格剔除后作用集 = 派定数 −1(%d→%d)" ↩ % [mapped, (snap.get("cells", {}) as Dictionary).size()] | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | +| 1445 | 288 | "V1 封锁格零微光(query null ⇒ 天然惰性——%s)" ↩ % str(blocked_cell) | not (snap.get("cells", {}) as Dictionary).has(blocked_cell) | (待填) | | | +| 1446 | 290 | "V1 封锁格零登记(errors 仍仅清单外一条——%s)" ↩ % str(snap.get("errors", [])) | (snap.get("errors", []) as Array).size() == 1 | (待填) | | | +| 1447 | 292 | "V1 封锁格剔除后作用集 = 派定数 −1(实测 %d)" ↩ % (snap.get("cells", {}) as Dictionary).size() | (snap.get("cells", {}) as Dictionary).size() == mapped - 1 | (待填) | | | +| 1448 | 298 | "V1 封锁解除后作用集复位恒等(期望 %d,实测 %d)" ↩ % [expect.size(), (snap.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap.get("cells", {}), expect) | (待填) | | | +| 1449 | 304 | "V7 off 态开关读数=false" | not bool(snap.get("enabled", true)) | (待填) | | | +| 1450 | 305 | "V7 off 态作用集为空(基线保护面)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | +| 1451 | 308 | "V7 on 态开关读数=true(复现)" | bool(snap_on.get("enabled", false)) | (待填) | | | +| 1452 | 309 | "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", {})) | (待填) | | | +| 1453 | 312 | "V7 off→on 作用集与独立重算期望集恒等(%d 格)" ↩ % (snap_on.get("cells", {}) as Dictionary).size() | _glow_key_set_equal(snap_on.get("cells", {}), expect) | (待填) | | | +| 1454 | 316 | "V7 微光开关零玩法写入(已开集/英雄位/封印数三面对照逐位一致)" | revealed_before == revealed_after and hero_before == ms.hero_pos() and seals_before == ms.seal_count() | (待填) | | | +| 1455 | 329 | "V3 换图 rebuild 后作用集按新板重算(期望 %d,实测 %d——零旧板残留)" ↩ % [expect2.size(), (snap2.get("cells", {}) as Dictionary).size()] | _glow_key_set_equal(snap2.get("cells", {}), expect2) | (待填) | | | +| 1456 | 331 | "V3 换图清清单外登记(rebuild 清零)" | (snap2.get("errors", []) as Array).is_empty() | (待填) | | | +| 1457 | 372 | "契约 1 同板同序列 on/off 两态结算逐位一致(%d 步对照——A 开态/B 关态)%s" ↩ % [seq_steps, diverge] | diverge == "" | (待填) | | | +| 1458 | 375 | "契约 1 对照前提:同序列 ≥5 步(实测 %d)" ↩ % seq_steps | seq_steps >= 5 | (待填) | | | -## S194 _smoke_battle_log_view(行 392–423 · 6 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S196 _smoke_battle_log_view(行 392–423 · 6 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1454 | 392 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | (待填) | | | -| 1455 | 394 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | (待填) | | | -| 1456 | 401 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | (待填) | | | -| 1457 | 403 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | (待填) | | | -| 1458 | 409 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | (待填) | | | -| 1459 | 422 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | (待填) | | | +| 1459 | 392 | "敌方伤害行格式化(baseline 行不读退役 attach)" | enemy_txt.contains("撕咬") and enemy_txt.contains("〔掷 15 vs 护甲 12") | (待填) | | | +| 1460 | 394 | "退役伏击/基线拆行文案不再出现" | not enemy_txt.contains("伏击") and not enemy_txt.contains("基线") | (待填) | | | +| 1461 | 401 | "主角穿刺型暴击行金框(单分量 payload 不触发三分量拆行)" | hero_txt.contains("暴击×") and hero_txt.contains("⚔击杀") and hero_txt.contains("■") | (待填) | | | +| 1462 | 403 | "退役三分量拆行文案不再出现" | not hero_txt.contains(" + 穿刺 ") | (待填) | | | +| 1463 | 409 | "未生效行聚合计数 ×2 呈现" | agg_txt.contains("⚠") and agg_txt.contains("×2") | (待填) | | | +| 1464 | 422 | "行型 %s 格式化落地(解析文本增长)" ↩ % String(row_type) | view.parsed_text().length() > before | (待填) | | | -## S195 _smoke_level_open_line(行 430–436 · 3 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S197 _smoke_level_open_line(行 430–436 · 3 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1460 | 430 | "开场行 boosted=true 尾接「(含面板加成)」〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)") | (待填) | | | -| 1461 | 432 | "开场行 boosted=false 零后缀(白板局不带)〔wc1〕" | not String(NarratorScript.level_open_line("第一层", 42, 5, 30, false)).contains("(含面板加成)") | (待填) | | | -| 1462 | 434 | "开场行种子/两值带入不受后缀位影响(数值带入回归)〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("种子 42") and String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("攻击 5 / 生命 30") | (待填) | | | +| 1465 | 430 | "开场行 boosted=true 尾接「(含面板加成)」〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("(含面板加成)") | (待填) | | | +| 1466 | 432 | "开场行 boosted=false 零后缀(白板局不带)〔wc1〕" | not String(NarratorScript.level_open_line("第一层", 42, 5, 30, false)).contains("(含面板加成)") | (待填) | | | +| 1467 | 434 | "开场行种子/两值带入不受后缀位影响(数值带入回归)〔wc1〕" | String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("种子 42") and String(NarratorScript.level_open_line("第一层", 42, 5, 30, true)).contains("攻击 5 / 生命 30") | (待填) | | | -## S196 _smoke_global_theme(行 445–540 · 22 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S198 _smoke_global_theme(行 445–540 · 22 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1463 | 445 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | (待填) | | | -| 1464 | 454 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | (待填) | | | -| 1465 | 456 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | (待填) | | | -| 1466 | 464 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | -| 1467 | 468 | "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") | (待填) | | | -| 1468 | 471 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | (待填) | | | -| 1469 | 474 | "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") | (待填) | | | -| 1470 | 480 | "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") | (待填) | | | -| 1471 | 484 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | (待填) | | | -| 1472 | 487 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | (待填) | | | -| 1473 | 491 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | (待填) | | | -| 1474 | 492 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | (待填) | | | -| 1475 | 500 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | (待填) | | | -| 1476 | 503 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | (待填) | | | -| 1477 | 505 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | (待填) | | | -| 1478 | 508 | "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 | (待填) | | | -| 1479 | 512 | "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 | (待填) | | | -| 1480 | 516 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | (待填) | | | -| 1481 | 520 | "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") | (待填) | | | -| 1482 | 534 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | (待填) | | | -| 1483 | 537 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | (待填) | | | -| 1484 | 540 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | (待填) | | | +| 1468 | 445 | "V1 项目主题挂载(project.godot gui/theme/custom → main_theme.tres)" | th != null | (待填) | | | +| 1469 | 454 | "V1 主题类型覆盖 %s" ↩ % t | type_set.has(t) | (待填) | | | +| 1470 | 456 | "V1 红线 Button 类型词条零接触" | not type_set.has("Button") | (待填) | | | +| 1471 | 464 | "V1 红线 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | +| 1472 | 468 | "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") | (待填) | | | +| 1473 | 471 | "V1 滚动条 scroll_focus 未定义(红线)" | not th.has_stylebox("scroll_focus", "VScrollBar") and not th.has_stylebox("scroll_focus", "HScrollBar") | (待填) | | | +| 1474 | 474 | "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") | (待填) | | | +| 1475 | 480 | "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") | (待填) | | | +| 1476 | 484 | "V1 SpinBox 箭头词条在位(updown 合并槽)" | sp.has_theme_icon("updown") | (待填) | | | +| 1477 | 487 | "V1 分隔线词条在位" | hsep.has_theme_stylebox("separator") and vsep.has_theme_stylebox("separator") | (待填) | | | +| 1478 | 491 | "V1 TooltipPanel 词条在位" | tpanel.has_theme_stylebox("panel") | (待填) | | | +| 1479 | 492 | "V1 TooltipLabel 词条在位" | tlabel.has_theme_color("font_color") and tlabel.has_theme_font_size("font_size") | (待填) | | | +| 1480 | 500 | "V2 冻结值锚定(已填充段=BG_ROOT #3A2E22,槽=暖石 #B8A582)" | fill_c.is_equal_approx(Color8(58, 46, 34)) and track_c.is_equal_approx(Color8(184, 165, 130)) | (待填) | | | +| 1481 | 503 | "V2 滑轨两段对比 %.2f:1 ≥4.5:1(拖到中点一眼可辨)" ↩ % ratio | ratio >= 4.5 | (待填) | | | +| 1482 | 505 | "V2 滑轨 stylebox 类型均为 StyleBoxFlat" | false | (待填) | | | +| 1483 | 508 | "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 | (待填) | | | +| 1484 | 512 | "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 | (待填) | | | +| 1485 | 516 | "V3 PopupMenu 词条存在(弹层预盖)" | pm.has_theme_stylebox("panel") and pm.has_theme_stylebox("hover") and pm.has_theme_font_size("font_size") | (待填) | | | +| 1486 | 520 | "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") | (待填) | | | +| 1487 | 534 | "V4 退役实现零残留(%s)%s" ↩ % [path, "" if bad == "" else "违例:" + bad] | bad == "" | (待填) | | | +| 1488 | 537 | "V6 主题 default_font 非空(挂主题顶掉 custom_font 也不丢中文字形)" | th.default_font != null | (待填) | | | +| 1489 | 540 | "V6 Label 实取字体非空(中文字形防御实证)" | lbl.get_theme_font("font") != null | (待填) | | | -## S197 _smoke_button_five_states(行 553–625 · 23 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S199 _smoke_button_five_states(行 553–625 · 23 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1485 | 553 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | (待填) | | | -| 1486 | 559 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | (待填) | | | -| 1487 | 563 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | (待填) | | | -| 1488 | 567 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | (待填) | | | -| 1489 | 570 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | _sb_content_differs(d_sb, p_sb) | (待填) | | | -| 1490 | 572 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | (待填) | | | -| 1491 | 574 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | (待填) | | | -| 1492 | 575 | "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) | (待填) | | | -| 1493 | 578 | "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) | (待填) | | | -| 1494 | 581 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | (待填) | | | -| 1495 | 586 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | (待填) | | | -| 1496 | 590 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | (待填) | | | -| 1497 | 592 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | (待填) | | | -| 1498 | 594 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | -| 1499 | 596 | "V1 主钮 disabled≠pressed" | _sb_content_differs(ppn, pdd) | (待填) | | | -| 1500 | 597 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | (待填) | | | -| 1501 | 603 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | (待填) | | | -| 1502 | 606 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | (待填) | | | -| 1503 | 608 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | (待填) | | | -| 1504 | 610 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | -| 1505 | 612 | "V1 危险钮 disabled≠pressed" | _sb_content_differs(dpr, dds) | (待填) | | | -| 1506 | 617 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | (待填) | | | -| 1507 | 624 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | +| 1490 | 553 | "V1 普通钮 %s 样式盒非空" ↩ % st | b.has_theme_stylebox_override(st) | (待填) | | | +| 1491 | 559 | "V1 普通钮 normal/hover/disabled 均为石纹九宫格贴图样式盒" | n_sb is StyleBoxTexture and h_sb is StyleBoxTexture and d_sb is StyleBoxTexture | (待填) | | | +| 1492 | 563 | "V1 普通钮 hover=modulate 提亮(乙案,异于常态 WHITE)" | h_mod != Color.WHITE and h_mod.r > 1.0 | (待填) | | | +| 1493 | 567 | "V1 普通钮 disabled=本族底 α0.45 幽灵化(甲案)" | is_equal_approx(d_mod.a, 0.45) and d_mod.r == 1.0 and d_mod.g == 1.0 | (待填) | | | +| 1494 | 570 | "V1 普通钮 disabled≠pressed(旧错配已修正)" | _sb_content_differs(d_sb, p_sb) | (待填) | | | +| 1495 | 572 | "V1 普通钮 focus 样式盒为扁平环(StyleBoxFlat)" | f_sb is StyleBoxFlat | (待填) | | | +| 1496 | 574 | "V1 焦点环色=INK(与 font_focus_color 同源)" | fsb.border_color == uikit_s.INK | (待填) | | | +| 1497 | 575 | "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) | (待填) | | | +| 1498 | 578 | "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) | (待填) | | | +| 1499 | 581 | "V1 焦点环无底(叠加态不遮钮底)" | not fsb.draw_center and fsb.bg_color.a < 0.001 | (待填) | | | +| 1500 | 586 | "V1 主钮 %s 样式盒非空" ↩ % st | pb.has_theme_stylebox_override(st) | (待填) | | | +| 1501 | 590 | "V1 主钮 pressed/disabled=本族平涂(非石纹)" | ppn is StyleBoxFlat and pdd is StyleBoxFlat | (待填) | | | +| 1502 | 592 | "V1 主钮 pressed=烛光黄深档(现行值不动)" | (ppn as StyleBoxFlat).bg_color == Color8(214, 172, 88) | (待填) | | | +| 1503 | 594 | "V1 主钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((pdd as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | +| 1504 | 596 | "V1 主钮 disabled≠pressed" | _sb_content_differs(ppn, pdd) | (待填) | | | +| 1505 | 597 | "V1 主钮 hover=提亮档(承现行不动)" | (phv as StyleBoxFlat).bg_color == uikit_s.CANDLE.lightened(0.06) | (待填) | | | +| 1506 | 603 | "V1 危险钮 %s 样式盒非空" ↩ % st | db.has_theme_stylebox_override(st) | (待填) | | | +| 1507 | 606 | "V1 危险钮 pressed/disabled=本族平涂(非石纹,旧错配已修正)" | dpr is StyleBoxFlat and dds is StyleBoxFlat | (待填) | | | +| 1508 | 608 | "V1 危险钮 pressed=深红深档(暗于常态)" | (dpr as StyleBoxFlat).bg_color.r < 180.0 / 255.0 | (待填) | | | +| 1509 | 610 | "V1 危险钮 disabled=本族底 α0.45 幽灵化" | is_equal_approx((dds as StyleBoxFlat).bg_color.a, 0.45) | (待填) | | | +| 1510 | 612 | "V1 危险钮 disabled≠pressed" | _sb_content_differs(dpr, dds) | (待填) | | | +| 1511 | 617 | "V2 红线维持 Button 类型词条零接触" | not th.get_type_list().has("Button") | (待填) | | | +| 1512 | 624 | "V2 红线维持 focus 族词条零定义%s" ↩ % ("" if focus_hits == "" else "(违例:" + focus_hits + ")") | focus_hits == "" | (待填) | | | -## S198 _smoke_texture_filter(行 632–674 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) - -| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | +## S200 _smoke_texture_filter(行 632–674 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) + +| 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1508 | 632 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | (待填) | | | -| 1509 | 636 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | -| 1510 | 640 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | -| 1511 | 658 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | (待填) | | | -| 1512 | 660 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | (待填) | | | -| 1513 | 661 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | (待填) | | | -| 1514 | 665 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | -| 1515 | 670 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | -| 1516 | 673 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | +| 1513 | 632 | "V3 工程设置 default_texture_filter=0(Nearest,翻转面=立绘/瓦片/九宫格 crisp)" | v != null and int(v) == 0 | (待填) | | | +| 1514 | 636 | "V3 白名单在位:暗角 vignette 显式 LINEAR" | vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1515 | 640 | "V3 白名单在位:光柱 beams 显式 LINEAR" | bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1516 | 658 | "T4 光柱带心峰值 α≈0.05(8bit 截断量化下限 12/255,实测 %.5f)" ↩ % max_a | max_a >= 12.0 / 255.0 and max_a <= 0.05 | (待填) | | | +| 1517 | 660 | "T4 光柱带内连续 alpha 过渡像素 %d 个(旧零过渡硬边已修)" ↩ % mid_a | mid_a > 100 | (待填) | | | +| 1518 | 661 | "T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" ↩ % [top_sum, bot_sum] | top_sum > bot_sum * 2.0 | (待填) | | | +| 1519 | 665 | "V3 白名单在位:地图渐变 gradient_base 显式 LINEAR" | gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR | (待填) | | | +| 1520 | 670 | "V3 脚影显式 LINEAR 不丢(board_world.gd)" | bw_src.contains("shadow.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | +| 1521 | 673 | "V3 状态图标显式 LINEAR 不丢(status_slot_row.gd)" | ss_src.contains("icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR") | (待填) | | | -## S199 _smoke_contrast_inks(行 683–699 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S201 _smoke_contrast_inks(行 683–699 · 9 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1517 | 683 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | (待填) | | | -| 1518 | 685 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | (待填) | | | -| 1519 | 687 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | (待填) | | | -| 1520 | 690 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | (待填) | | | -| 1521 | 692 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | (待填) | | | -| 1522 | 693 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | (待填) | | | -| 1523 | 694 | "V4 意图蓄力档判据=分档布尔(standee_body:179 CHARGE_SKILL_IDS 判定;旧 GOLD_DEEP 恒不可达判据清零)〔wd2〕" | sb_src.contains("_intent_charge = pid in CHARGE_SKILL_IDS") and not sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | (待填) | | | -| 1524 | 697 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | (待填) | | | -| 1525 | 699 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | (待填) | | | +| 1522 | 683 | "V4 顺位徽标/重甲徽标 GOLD_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_order | r_order >= 4.5 | (待填) | | | +| 1523 | 685 | "V4 魔法护甲徽标 HERO_BLUE_BRIGHT 对舞台底 %.2f:1 ≥4.5:1" ↩ % r_ward | r_ward >= 4.5 | (待填) | | | +| 1524 | 687 | "V4 RAP 反馈行 STONE_DEEP 对数据板 %.2f:1 ≥4.5:1" ↩ % r_rap | r_rap >= 4.5 | (待填) | | | +| 1525 | 690 | "V4 顺位徽标①②③ 墨=GOLD_BRIGHT(13px 行原位)" | sb_src.contains("HORIZONTAL_ALIGNMENT_LEFT, -1, 13, UiKit.GOLD_BRIGHT") | (待填) | | | +| 1526 | 692 | "V4 魔法护甲徽标墨=HERO_BLUE_BRIGHT" | sb_src.contains("col = UiKit.HERO_BLUE_BRIGHT") | (待填) | | | +| 1527 | 693 | "V4 重甲徽标墨=GOLD_BRIGHT" | sb_src.contains("col = UiKit.GOLD_BRIGHT") | (待填) | | | +| 1528 | 694 | "V4 意图蓄力档判据=分档布尔(standee_body:179 CHARGE_SKILL_IDS 判定;旧 GOLD_DEEP 恒不可达判据清零)〔wd2〕" | sb_src.contains("_intent_charge = pid in CHARGE_SKILL_IDS") and not sb_src.contains("if _intent_col == UiKit.GOLD_DEEP:") | (待填) | | | +| 1529 | 697 | "V4 RAP 反馈行墨=STONE_DEEP(原位行锚定)" | ap_src.contains('UiKit.label("",UiKit.FS_CAPTION, false, UiKit.STONE_DEEP)') | (待填) | | | +| 1530 | 699 | "V4 RAP 反馈行旧墨 STONE_FAINT 清零(含注记面)" | not ap_src.contains("STONE_FAINT") | (待填) | | | -## S200 _smoke_element_details(行 708–727 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S202 _smoke_element_details(行 708–727 · 8 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1526 | 708 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | (待填) | | | -| 1527 | 711 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | (待填) | | | -| 1528 | 713 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | (待填) | | | -| 1529 | 716 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | (待填) | | | -| 1530 | 720 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | (待填) | | | -| 1531 | 724 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | (待填) | | | -| 1532 | 726 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | (待填) | | | -| 1533 | 727 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | (待填) | | | +| 1531 | 708 | "T5② BCard 图标槽白图标对比 %.2f:1 ≥3:1(硬约束)" ↩ % r_white | r_white >= 3.0 | (待填) | | | +| 1532 | 711 | "T5② 明度差降档生效(槽对卡面 %.2f:1 < 旧 %.2f:1)" ↩ % [r_new, r_old] | r_new < r_old | (待填) | | | +| 1533 | 713 | "T5② BCard 图标槽落柔和档(SLOT_BACKDROP_SOFT)" | uk_src.contains("UiKit.panel_flat(SLOT_BACKDROP_SOFT, PANEL_BORDER, 0, 1)") | (待填) | | | +| 1534 | 716 | "T5② B 区状态图标槽仍用深档(SLOT_BACKDROP,未误伤)" | ss_src.contains("UiKit.SLOT_BACKDROP") | (待填) | | | +| 1535 | 720 | "T5③ 商店卡片区垂直居中(ALIGNMENT_CENTER)" | shop_src.contains("_cards_box.alignment = BoxContainer.ALIGNMENT_CENTER") | (待填) | | | +| 1536 | 724 | "T5③ 营地三 holder 统一 min 高 336(实测 %d 处)" ↩ % camp_min | camp_min == 3 | (待填) | | | +| 1537 | 726 | "T5③ 营地四栏面板纵向 FILL 等高(实测 %d 处)" ↩ % camp_fill | camp_fill == 4 | (待填) | | | +| 1538 | 727 | "T5③ 三处锁文垂直居中在位" | camp_src.count("SIZE_SHRINK_CENTER") >= 3 | (待填) | | | -## S201 _smoke_diag_button_retired(行 734–747 · 7 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S203 _smoke_diag_button_retired(行 734–747 · 7 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1534 | 734 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | (待填) | | | -| 1535 | 736 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | (待填) | | | -| 1536 | 738 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | (待填) | | | -| 1537 | 741 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | (待填) | | | -| 1538 | 744 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | (待填) | | | -| 1539 | 745 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | (待填) | | | -| 1540 | 746 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | (待填) | | | +| 1539 | 734 | "V6 主区无 build_diag_button 调用残留(旧实现同批清理)" | not main_src.contains("build_diag_button") | (待填) | | | +| 1540 | 736 | "V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)" | main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)") | (待填) | | | +| 1541 | 738 | "V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)" | main_src.contains("func _on_diagnose_pressed()") | (待填) | | | +| 1542 | 741 | "V6 build_diag_button 工厂已退役(ui_assembler 零残留)" | not asm_src.contains("build_diag_button") | (待填) | | | +| 1543 | 744 | "V6 调试浮窗 diagnose_requested 信号在位" | dbg_src.contains("signal diagnose_requested") | (待填) | | | +| 1544 | 745 | "V6 浮窗转储钮外发方法在位(纯视图边界)" | dbg_src.contains("_emit_diagnose_requested") | (待填) | | | +| 1545 | 746 | "V6 浮窗诊断转储钮在位(玩家画面角落钮清零后唯一入口)" | dbg_src.contains('UiKit.button("转储")') | (待填) | | | -## S202 _status_panel_projection(行 763–793 · 10 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S204 _status_panel_projection(行 763–793 · 10 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1541 | 763 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | (待填) | | | -| 1542 | 764 | "余额徽投影 = 快照 balance 读数(sv4 回归锚)〔sv4〕" | String(po._balance_badge.value_label.text) == "钥石 77" | (待填) | | | -| 1543 | 766 | "属性行全值投影 = 契约4 钉值格式逐字(生命/上限/攻击/AC/GP 五现值)〔sv1〕" | String(po._attrs_label.text) == "生命 13/29 · 攻击 7 · 护甲 AC 11 · GP 41" | (待填) | | | -| 1544 | 768 | "秘宝行投影 = 受理序 join+已用件「(已用)」尾缀〔sv2〕" | String(po._curios_label.text) == "青苔密卷 · 锈蚀怀表(已用)" | (待填) | | | -| 1545 | 770 | "专长行投影 = 行串 join〔sv3〕" | String(po._feats_label.text) == "疼痛记忆 ×2 · 双管镐 ×1" | (待填) | | | -| 1546 | 777 | "面板子树零「道具工坊/天赋树」文案(跨关概览退役零残留)〔sv5〕" | stale == "" | (待填) | | | -| 1547 | 778 | "旧段成员退役(_workshop_holder/_tree_label 零残留——契约2 数据/成员层面清零)〔sv5〕" | po.get("_workshop_holder") == null and po.get("_tree_label") == null | (待填) | | | -| 1548 | 785 | "空秘宝态句=「未携带秘宝」〔sv2〕" | String(po2._curios_label.text) == "未携带秘宝" | (待填) | | | -| 1549 | 786 | "空专长态句=「未获专长」〔sv3〕" | String(po2._feats_label.text) == "未获专长" | (待填) | | | -| 1550 | 792 | "提示行=「查看本局冒险进度(只读)」(新定位换代)〔sv8〕" | hint_txt == "查看本局冒险进度(只读)" | (待填) | | | +| 1546 | 763 | "状态面板开层可见(open_status 直开——P4 甲零动效路径)" | po.visible | (待填) | | | +| 1547 | 764 | "余额徽投影 = 快照 balance 读数(sv4 回归锚)〔sv4〕" | String(po._balance_badge.value_label.text) == "钥石 77" | (待填) | | | +| 1548 | 766 | "属性行全值投影 = 契约4 钉值格式逐字(生命/上限/攻击/AC/GP 五现值)〔sv1〕" | String(po._attrs_label.text) == "生命 13/29 · 攻击 7 · 护甲 AC 11 · GP 41" | (待填) | | | +| 1549 | 768 | "秘宝行投影 = 受理序 join+已用件「(已用)」尾缀〔sv2〕" | String(po._curios_label.text) == "青苔密卷 · 锈蚀怀表(已用)" | (待填) | | | +| 1550 | 770 | "专长行投影 = 行串 join〔sv3〕" | String(po._feats_label.text) == "疼痛记忆 ×2 · 双管镐 ×1" | (待填) | | | +| 1551 | 777 | "面板子树零「道具工坊/天赋树」文案(跨关概览退役零残留)〔sv5〕" | stale == "" | (待填) | | | +| 1552 | 778 | "旧段成员退役(_workshop_holder/_tree_label 零残留——契约2 数据/成员层面清零)〔sv5〕" | po.get("_workshop_holder") == null and po.get("_tree_label") == null | (待填) | | | +| 1553 | 785 | "空秘宝态句=「未携带秘宝」〔sv2〕" | String(po2._curios_label.text) == "未携带秘宝" | (待填) | | | +| 1554 | 786 | "空专长态句=「未获专长」〔sv3〕" | String(po2._feats_label.text) == "未获专长" | (待填) | | | +| 1555 | 792 | "提示行=「查看本局冒险进度(只读)」(新定位换代)〔sv8〕" | hint_txt == "查看本局冒险进度(只读)" | (待填) | | | -## S203 _camp_recent_projection(行 818–819 · 1 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S205 _camp_recent_projection(行 818–819 · 1 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1551 | 818 | "营地最近冒险行完成关数措辞(实际「%s」)〔口径换代 V6〕" ↩ % recent_text | recent_text.contains("完成 2 关") and not recent_text.contains("通关") | (待填) | | | +| 1556 | 818 | "营地最近冒险行完成关数措辞(实际「%s」)〔口径换代 V6〕" ↩ % recent_text | recent_text.contains("完成 2 关") and not recent_text.contains("通关") | (待填) | | | -## S204 _career_overlay_projection(行 852–897 · 11 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) +## S206 _career_overlay_projection(行 852–897 · 11 条 · 源 game/tests/unit/presentation/theme_projection_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1552 | 852 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | (待填) | | | -| 1553 | 853 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | (待填) | | | -| 1554 | 855 | "冒险成功局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | (待填) | | | -| 1555 | 860 | "各关完成次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | (待填) | | | -| 1556 | 862 | "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 次" | (待填) | | | -| 1557 | 864 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | (待填) | | | -| 1558 | 871 | "生涯页行标签换代(冒险成功局数/各关完成次数)〔口径换代 V4〕" | career_labels_joined.contains("冒险成功局数") and career_labels_joined.contains("各关完成次数") | (待填) | | | -| 1559 | 873 | "生涯页 label 全集零「通关」残留〔口径换代 V4〕" | not career_labels_joined.contains("通关") | (待填) | | | -| 1560 | 874 | "秘宝解锁条件标签冒险成功口径(UiKit 单点常量)〔口径换代 V5〕" | UiKit.TREASURE_REQ_LABELS["adventure_cleared"] == "冒险成功" | (待填) | | | -| 1561 | 883 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | (待填) | | | -| 1562 | 894 | "旧档载入后生涯页正常显示(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" | (待填) | | | +| 1557 | 852 | "生涯页开层可见(open_career 直开——零动效路径)" | co.visible | (待填) | | | +| 1558 | 853 | "总局数投影 = adventure_total() 读数(%s)" ↩ % co._total_label.text | String(co._total_label.text) == "2 局" | (待填) | | | +| 1559 | 855 | "冒险成功局数投影 = adventure_cleared() 读数(%s)" ↩ % co._cleared_label.text | String(co._cleared_label.text) == "1 局(三关全通)" | (待填) | | | +| 1560 | 860 | "各关完成次数投影 = clear_count() 逐关读数(%s)" ↩ % co._clears_label.text | String(co._clears_label.text) == " · ".join(expect_clears) | (待填) | | | +| 1561 | 862 | "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 次" | (待填) | | | +| 1562 | 864 | "钥石余额徽投影 = keystone_balance() 读数(%s)" ↩ % co._keystone_badge.value_label.text | String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance() | (待填) | | | +| 1563 | 871 | "生涯页行标签换代(冒险成功局数/各关完成次数)〔口径换代 V4〕" | career_labels_joined.contains("冒险成功局数") and career_labels_joined.contains("各关完成次数") | (待填) | | | +| 1564 | 873 | "生涯页 label 全集零「通关」残留〔口径换代 V4〕" | not career_labels_joined.contains("通关") | (待填) | | | +| 1565 | 874 | "秘宝解锁条件标签冒险成功口径(UiKit 单点常量)〔口径换代 V5〕" | UiKit.TREASURE_REQ_LABELS["adventure_cleared"] == "冒险成功" | (待填) | | | +| 1566 | 883 | "v5 现役档升 v6 载入成功(计数域缺省补零)" | v5.load_from_disk() and v5.adventure_total() == 0 and v5.adventure_cleared() == 0 | (待填) | | | +| 1567 | 894 | "旧档载入后生涯页正常显示(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" | (待填) | | | -## S205 _test_grouping_closed(行 61–79 · 7 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S207 _test_grouping_closed(行 61–79 · 7 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1563 | 61 | "九型封闭:%s → 组%d(期望组%d)" ↩ % [t, got, int(TYPE_EXPECT[t])] | got == int(TYPE_EXPECT[t]) | (待填) | | | -| 1564 | 65 | "清单外值「%s」→ GLOW_NONE(禁静默空光)" ↩ % bad | UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE | (待填) | | | -| 1565 | 70 | "两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)" | band_blast == UiKitScript.TG_MAGIC | (待填) | | | -| 1566 | 71 | "两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)" | UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE | (待填) | | | -| 1567 | 74 | "glow_color_for_group 群伤组 = TG_GLOW_AOE" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE | (待填) | | | -| 1568 | 76 | "glow_color_for_group 无组(GLOW_NONE)→ 全透明" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0) | (待填) | | | -| 1569 | 78 | "glow_color_for_group 越界组号 → 全透明" | UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0) | (待填) | | | +| 1568 | 61 | "九型封闭:%s → 组%d(期望组%d)" ↩ % [t, got, int(TYPE_EXPECT[t])] | got == int(TYPE_EXPECT[t]) | (待填) | | | +| 1569 | 65 | "清单外值「%s」→ GLOW_NONE(禁静默空光)" ↩ % bad | UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE | (待填) | | | +| 1570 | 70 | "两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)" | band_blast == UiKitScript.TG_MAGIC | (待填) | | | +| 1571 | 71 | "两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)" | UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE | (待填) | | | +| 1572 | 74 | "glow_color_for_group 群伤组 = TG_GLOW_AOE" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE | (待填) | | | +| 1573 | 76 | "glow_color_for_group 无组(GLOW_NONE)→ 全透明" | UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0) | (待填) | | | +| 1574 | 78 | "glow_color_for_group 越界组号 → 全透明" | UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0) | (待填) | | | -## S206 _test_frozen_hue_face(行 88–102 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S208 _test_frozen_hue_face(行 88–102 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1570 | 88 | "实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" ↩ % [CONST_NAME[g], int(m.r8), int(m.g8), int(m.b8)] | impl_const == m | (待填) | | | -| 1571 | 93 | "冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" ↩ % [g, dh * 360.0, ds] | dh <= 0.2 / 360.0 and ds <= 0.01 | (待填) | | | -| 1572 | 98 | "群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" ↩ % (absf(aoe_m.h - aoe_f.h) * 360.0) | absf(aoe_m.h - aoe_f.h) < 0.001 | (待填) | | | -| 1573 | 100 | "群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" ↩ % absf(aoe_m.s - aoe_f.s) | absf(aoe_m.s - aoe_f.s) <= 0.01 | (待填) | | | -| 1574 | 102 | "群伤组专项:V=0.65 明度档( provisional 域内)" | absf(aoe_m.v - 166.0 / 255.0) < 0.002 | (待填) | | | +| 1575 | 88 | "实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" ↩ % [CONST_NAME[g], int(m.r8), int(m.g8), int(m.b8)] | impl_const == m | (待填) | | | +| 1576 | 93 | "冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" ↩ % [g, dh * 360.0, ds] | dh <= 0.2 / 360.0 and ds <= 0.01 | (待填) | | | +| 1577 | 98 | "群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" ↩ % (absf(aoe_m.h - aoe_f.h) * 360.0) | absf(aoe_m.h - aoe_f.h) < 0.001 | (待填) | | | +| 1578 | 100 | "群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" ↩ % absf(aoe_m.s - aoe_f.s) | absf(aoe_m.s - aoe_f.s) <= 0.01 | (待填) | | | +| 1579 | 102 | "群伤组专项:V=0.65 明度档( provisional 域内)" | absf(aoe_m.v - 166.0 / 255.0) < 0.002 | (待填) | | | -## S207 _test_render_params(行 107–113 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S209 _test_render_params(行 107–113 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1575 | 107 | "泛光强度 α = 170/255(v2 呈现参数冻结)" | is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0) | (待填) | | | -| 1576 | 109 | "带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)" | is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125) | (待填) | | | -| 1577 | 111 | "组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)" | UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5 | (待填) | | | +| 1580 | 107 | "泛光强度 α = 170/255(v2 呈现参数冻结)" | is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0) | (待填) | | | +| 1581 | 109 | "带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)" | is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125) | (待填) | | | +| 1582 | 111 | "组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)" | UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2 and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5 | (待填) | | | -## S208 _test_gamma_ladder(行 128–139 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S210 _test_gamma_ladder(行 128–139 · 3 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1578 | 128 | "阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" ↩ % [g, lum, prev_lum] | lum < prev_lum | (待填) | | | -| 1579 | 134 | "相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" ↩ % [order[i], order[i + 1], cr] | cr >= 1.20 | (待填) | | | -| 1580 | 138 | "群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" ↩ % [aoe_lin, magic_lin] | aoe_lin < magic_lin | (待填) | | | +| 1583 | 128 | "阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" ↩ % [g, lum, prev_lum] | lum < prev_lum | (待填) | | | +| 1584 | 134 | "相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" ↩ % [order[i], order[i + 1], cr] | cr >= 1.20 | (待填) | | | +| 1585 | 138 | "群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" ↩ % [aoe_lin, magic_lin] | aoe_lin < magic_lin | (待填) | | | -## S209 _test_probe_api_shape(行 146–153 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) +## S211 _test_probe_api_shape(行 146–153 · 5 条 · 源 game/tests/unit/presentation/type_glow_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1581 | 146 | "probe 键集 = {enabled, cells, errors}" | snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3 | (待填) | | | -| 1582 | 148 | "probe 默认态:enabled=true(Q4 丙案生产默认开)" | bool(snap.get("enabled", false)) | (待填) | | | -| 1583 | 149 | "probe 默认态:作用集空(无板未派生)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | -| 1584 | 150 | "probe 默认态:清单外登记空" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | -| 1585 | 152 | "set_type_glow_enabled(false) → probe enabled=false(无板不崩)" | not bool(view.type_glow_probe().get("enabled", true)) | (待填) | | | +| 1586 | 146 | "probe 键集 = {enabled, cells, errors}" | snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3 | (待填) | | | +| 1587 | 148 | "probe 默认态:enabled=true(Q4 丙案生产默认开)" | bool(snap.get("enabled", false)) | (待填) | | | +| 1588 | 149 | "probe 默认态:作用集空(无板未派生)" | (snap.get("cells", {}) as Dictionary).is_empty() | (待填) | | | +| 1589 | 150 | "probe 默认态:清单外登记空" | (snap.get("errors", []) as Array).is_empty() | (待填) | | | +| 1590 | 152 | "set_type_glow_enabled(false) → probe enabled=false(无板不崩)" | not bool(view.type_glow_probe().get("enabled", true)) | (待填) | | | -## S210 _init(行 16–28 · 6 条 · 源 game/tests/unit/presentation/ui_font_asset_test.gd · 去向:待分类回填) +## S212 _init(行 16–28 · 6 条 · 源 game/tests/unit/presentation/ui_font_asset_test.gd · 去向:待分类回填) | 序号 | 行号 | 标签原文 | cond 摘要 | 白话释义 | 处置 | quiet | |---|---|---|---|---|---|---| -| 1586 | 16 | "随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)" | f != null | (待填) | | | -| 1587 | 18 | "中文字形在集(攻/雷/旗/封)" | f.has_char("攻".unicode_at(0)) and f.has_char("雷".unicode_at(0)) \\ and f.has_char("旗".unicode_at(0)) and f.has_char("封".unicode_at(0)) | (待填) | | | -| 1588 | 20 | "中文字形在集(危——已开危险格字形换代前置,迭代36 u12/wa9)" | f.has_char("危".unicode_at(0)) | (待填) | | | -| 1589 | 21 | "ASCII/数字字形在集(A/0/%)" | f.has_char("A".unicode_at(0)) and f.has_char("0".unicode_at(0)) and f.has_char("%".unicode_at(0)) | (待填) | | | -| 1590 | 25 | "中文采样文本宽度 > 0(C6 headless 判据)" | w_cn > 0.0 and w_label > 0.0 | (待填) | | | -| 1591 | 28 | "字体缺失时后续断言跳过(缺位=回归,此行仅占位计数)" | false | (待填) | | | +| 1591 | 16 | "随包字体守卫装载非空(res://assets/fonts/ui_font_fusion_pixel_zh_hans.ttf)" | f != null | (待填) | | | +| 1592 | 18 | "中文字形在集(攻/雷/旗/封)" | f.has_char("攻".unicode_at(0)) and f.has_char("雷".unicode_at(0)) \\ and f.has_char("旗".unicode_at(0)) and f.has_char("封".unicode_at(0)) | (待填) | | | +| 1593 | 20 | "中文字形在集(危——已开危险格字形换代前置,迭代36 u12/wa9)" | f.has_char("危".unicode_at(0)) | (待填) | | | +| 1594 | 21 | "ASCII/数字字形在集(A/0/%)" | f.has_char("A".unicode_at(0)) and f.has_char("0".unicode_at(0)) and f.has_char("%".unicode_at(0)) | (待填) | | | +| 1595 | 25 | "中文采样文本宽度 > 0(C6 headless 判据)" | w_cn > 0.0 and w_label > 0.0 | (待填) | | | +| 1596 | 28 | "字体缺失时后续断言跳过(缺位=回归,此行仅占位计数)" | false | (待填) | | | --- -全源共 210 章、1591 条(静态调用点口径)。 +全源共 212 章、1596 条(静态调用点口径)。 diff --git a/reports/iter38-代拍板-2026-10-26.md b/reports/iter38-代拍板-2026-10-26.md new file mode 100644 index 00000000..de336bee --- /dev/null +++ b/reports/iter38-代拍板-2026-10-26.md @@ -0,0 +1,42 @@ +# 迭代 38 代拍板追认单(2026-10-26) + +> 各卡 §10 代拍板白名单内裁定的执行记录——授权发生在卡片审问阶段(白名单即预授权面),本单为批后备案确认;超白名单裁定零(如有会已停机呈报)。 + +## 卡①(levelmap 批) + +1. payload 键名与 apply_status 形态:九键定值 level_label/gp_text/hp_text/hp_bar_redraw/attr_text/bonus_text/bonus_row_visible(hp_bar_redraw 恒 true——原逻辑无条件 queue_redraw 保真);视图方法名 apply_status。 +2. 缓存命中读数实现形态:is_same 同实例判别(重算必产新字典、命中返回缓存原实例)——零生产计数器;换代失效判别改「新图查询非旧缓存实例+坐标不等」(原案「旧图回查」不可行:换代后 _content_size 已随 open_choices 重拟合,旧图混查属生产外用法——L2 行号漂移同级的实现事实修正,随卡记录)。 +3. wr 断言挂点与消息文本:level_map_info_fog_test 新增 _test_layout_cache 段,尾缀〔levelmap批 V1/V2〕。 +4. 提交切分与分支:单 worktree iter38、逐卡一笔提交。 + +## 卡③(效果文案批) + +5. b2 头注表述:「词表实数以 EFFECT_KINDS 常量为准」;历史笔误叙事句按禁写变更记录红线保留。 +6. 断言挂点与消息:effect_words_t1_test ③ 段后追加 ③-b,断言 message 含 str(FACTS.size()) 且不含「9 项」,尾缀〔效果文案批 V1〕。 + +## 卡④(明细收敛批) + +7. 扩展行型串值:`extended`(推荐案);effect_id 取值:四效果沿用原行型串(推荐案)。 +8. a3 来源类别:`fx_warning_beacon`(推荐案,对齐 fx_seal_smith 先例)。 +9. a5 裸键呈现差异处置:申报差异(推荐案)——示警烟火行带 fx_warning_beacon 裸键,随目检清单覆盖。 +10. 断言形态补充(L1/L2 白名单内两笔):①封印匠夹具改三枚不同雷格各封一次——_judge_seal 每格去重(_seal_consumed 以格实例为键),同格三封只计一次;②JSONL 对拍字段口径并入 source(playtest_logger.gd:133 对 source_class 的同值改名副本,随行平铺非本批新增)+剔除 session/diagnosis 墙钟行(parity_anchor 既有口径)。 +11. 改前基线取证流程:改前 parity check 在 stash 干净态跑批(第一次跑批被并行改码污染、作废重取);T2 撤销证明按对照表三条全数实测(原案「任选其一」从全执行)。 + +## 卡⑤(奖励节点清扫批) + +12. d1/d2/d3 措辞:「三选一停点」现状语义族;d3 定值「关卡奖励点(三选一——选毕推进)」。 +13. wr2 断言:探针战报文本含新串/不含「内容待定」,挂 _tested_curio_head 捕获块(同一驱动路径承载)。 +14. d4 必达形态:定向选路(候选含奖励节点优先)+捕获条件随修挂 `_node_curio_pending`(病根=原条件查通关停点态 `_curio_pending` 与奖励头部互斥恒不可命中——「拦截能力未证」实况病根,头部校验串零触碰;属白名单第 3 条「必达形态的具体实现」内)。 +15. 中途故障两笔(异常分级处置留痕):①首轮探针 35 分钟无输出=wr2 断言行 `var logtext :=` 类型推断失败(Variant 返回)→ 解析错误探针未加载进程空转——显式 `: String` 修复(L1);②插桩诊断一轮(DBG 候选类型打印)取证后移除。 + +## 卡⑥(结算行缓冲批) + +16. wr1-b 观测点落位:战中场(round≥2 且排播队列空)一次性判据——循环出口断言被收尾续玩重建段 log_view.clear() 合法冲掉(实测发现;卡面「T0 定落点」授权内)。 +17. 错误行判据按实际标记执行:`Attempted to push_back … TypedArray`(卡面引述「SCRIPT ERROR 行」,实测该错误走 ERROR 通道非 SCRIPT ERROR——同一证据对象,判据更精确非放宽)。 + +## 卡⑦(调试入口批) + +18. c2/c7 措辞:发布态恒隐语义族;c7 含「开发期临时启用改 `_debug_entry` 缺省本地不提交」运行者指引。 +19. c5 直调落点:else 分支首行 `main._show_debug_overlay()`,后续断言原序保持;「置灰子断言」随恒隐语义退役(§8.2 申报随实现报告)。 +20. c8 基线处置:显式重制(推荐案),改后 0.15% → 复闸 0.00%。 +21. c6 清单同步范围:本批新增单测断言(卡①③④)随清单 --force 重生成一并同步(1588 条对账零差异)——清单维护锚「断言新增必须同步清单」的全量履行,超出卡面 c6 单行范围属对账工具全量口径(extract --diff 对全部新断言报新增,只改判一行无法过对账)。 diff --git a/reports/iter38-反哺点-2026-10-26.md b/reports/iter38-反哺点-2026-10-26.md new file mode 100644 index 00000000..8b0684bb --- /dev/null +++ b/reports/iter38-反哺点-2026-10-26.md @@ -0,0 +1,40 @@ +# 迭代 38 反哺点清单(2026-10-26) + +> 各卡 §6 关联文档同步面汇总——实现批禁写 `design/` 与根规则文件,以下逐条待用户裁决后由文档批落文。裁决方式:逐条「落文/不落/改写」。 + +## 来自卡①(levelmap 批) + +1. **`待办清单.md`**——「结构审查未立项小件候审」条目拆注核对:件②归本卡(已收口 b85b3b45)、其余五件归卡②(**已停卡**,状态待回退,见实现报告 §三)。属状态核对注记。 +2. **`design/engineering/architecture/architecture.md` §1.3**——「唯一越界者」清偿后该条纪律实况注记。**预期零改动**(纪律文本不变,T0 已核):l1 落地后视图内核心态拼装清零,纪律由「要求」变为「已达成」,无需改文;如文档批认为值得在纪律条目加「2026-10-26 清偿」凭据注,措辞自定。 + +## 来自卡③(效果文案批) + +3. **`待办清单.md`**——「效果引擎校验文案计数错误」来源条目状态推进(已收口 a15370c5,完成即删)。 + +## 来自卡④(明细收敛批) + +4. **`design/gdd/systems/战斗循环/战斗结算.md`「扩展行」节**——过渡句改写。原句:「现行实现的扩展行型值以专长实名串承载,效果身份清单与发射点以实现侧 `battle_orchestrator.gd` 文件头注为准;收敛为单一扩展行型+效果载荷的条目在待办清单在册」。落地后「实名串承载」半句与「条目在册」句均过时——建议改写为:「实现已收敛为单一扩展行型(type=extended)+effect_id 载荷;效果身份清单与发射点以实现侧 `battle_orchestrator.gd` 文件头注为准」(头注指引句保留,实现侧头注已换代两层词表+四效果身份发射点指引)。 +5. **`roadmap.md`「当前位·下一步」句**——「结算明细日志行序重锚(2026-10-21 授权发起、时点另定)」指路句:重锚已随卡④裁点甲执行完毕(parity 锚换代+复闸绿),该句改写为已出账口径或摘除。 +6. **`待办清单.md`**——「明细行扩展总型收敛批」来源条目状态推进(完成即删);「结算明细日志行序重锚批」条目同批出账(裁点甲已执行)。 + +## 来自卡⑤(奖励节点清扫批) + +7. **`design/gdd/08-Alpha版本标准.md:113`**——「未结半=奖励节点现为零兑付直通占位,登记面=待办清单『奖励节点三选一实装』条」:注销后登记面死指+现状句与实现相悖——随注销改写(三选一已实装:奖励节点=三选一停点,呈现归 present_node_curio)。 +8. **`roadmap.md`「当前位·下一步」待编池**——「奖励节点三选一实装(2026-10-25 新登记)」短句摘除(注销承接收口,卡⑤已落地)。 +9. **`待办清单.md`**——「奖励节点过时表述清扫批」来源条目状态推进(完成即删)。 + +## 来自卡⑥(结算行缓冲批) + +10. **`待办清单.md`**——「闸与探针存量噪声两件」条目拆注与状态推进:件①(产品码类型错)已收口 ce49a8ee;**件②(闸壳 per-suite 挂死与进程树回收,tools/probe_gate.sh+run_all.gd)留工程直改通道未实施**——条目不应整条删除,拆注保留件②。 + +## 来自卡⑦(调试入口批) + +11. **`design/gdd/ux/battle.md:159/229`**——调试浮窗入口/DS_DEBUG 门控表述:删行后「入口在座」类陈述落空(含 2026-10-16 恒开批存量失真一并显形)——换代为发布态恒隐口径(角标恒隐、浮窗无 UI 开启口;开发期临时启用改 `_debug_entry` 缺省本地不提交)。 +12. **`design/gdd/systems/跨局循环/生涯.md:18`**——调试入口描述随删行失真(T0 未逐句核,落文时按现行文本定位具体句),同口径换代。 +13. **`design/engineering/test-conventions.md:49`**——「调试注入两层」节调试入口表述(DS_DEBUG/DebugOverlay 人工体验面):入口恒隐后该节「人工体验」通道描述需加发布态注记(调试注入两层的能力测试面不受影响——自动化测试不经 UI 直调核心写口,口径不变)。 +14. **`design/engineering/architecture/code-architecture.json:227`**——「DS_DEBUG 门控」节点副标:写死行已删、发布态恒隐,副标换代。 +15. **`待办清单.md`**——「调试入口写死行必须在发布前删除」来源条目状态推进(完成即删)。 + +## 来自卡②(停卡——随呈报非反哺) + +16. 卡②待办条目状态回退与缺口补记拟稿见实现报告 §三(属呈报移交件,落笔归写卡段/用户,不属文档批反哺落文)。 diff --git a/reports/iter38-实现报告-2026-10-26.md b/reports/iter38-实现报告-2026-10-26.md new file mode 100644 index 00000000..383545e9 --- /dev/null +++ b/reports/iter38-实现报告-2026-10-26.md @@ -0,0 +1,117 @@ +# 迭代 38 实现报告(2026-10-26) + +- 分支:`iter38`(worktree `D:\code\g-plan2-iter38`);基点 b23fa50d(编排提交),收口合并点 416ca84a(并入 master 2465a73b)。 +- 编入七卡,实际收口六卡;卡②(结构小件定点修批)施工中途停卡呈报退上游(§卡②),改动已还原、零提交。 +- 全量域闸:`tools/run_all_gates.sh --code` 批末一次(回执见 `game/tests/.gates/receipt.json`,结论见 §闸)。 + +## 一、各卡状态 + +| 卡 | 状态 | 收口提交 | 核心读数 | +|---|---|---|---| +| ① levelmap 装配越界与 BFS 缓存 | 已收口 | b85b3b45 | wr1 对拍 changed=0.00%;wr2 缓存命中/换代双绿 | +| ② 结构小件定点修(p1–p6) | **停卡呈报退上游** | (无提交) | wr5/wr6 验收载体与闸机错误扫描结构性互斥(§卡②) | +| ③ 效果引擎校验文案计数修正 | 已收口 | a15370c5 | wr1 断言绿;撤销红绿复现 | +| ④ 明细行扩展总型收敛 | 已收口 | d5a03db1 | 五断言+负面套件绿;重锚复闸三变体全同;JSONL 对拍 PASS | +| ⑤ 奖励节点过时表述清扫 | 已收口 | 00ed0236 | wr1 grep 零命中;wa4 必达真执行;撤销两条红绿复现 | +| ⑥ 结算行缓冲类型错修复 | 已收口 | ce49a8ee | 错误行 148→0;摘要恢复断言绿;撤销红绿复现 | +| ⑦ 调试入口写死行删除 | 已收口 | 5b5214f4 | 三断言反转绿;清单对账零差异;基线重制复闸 0.00% | + +## 二、实际对照表(改动—验收,逐卡精要) + +尾缀 grep 通道:〔levelmap批 V1/V2〕〔效果文案批 V1〕〔明细收敛 V2〕〔卡⑤〕〔卡⑥ wr1-b〕按各断言消息文本 grep 可互查。 + +### 卡① +| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 | +|---|---|---|---|---|---|---| +| wr1 呈现零变化 | l1 迁装+l2 缓存+l3 字体(level_map_screen.gd+main_adventure_flow.gd) | golden 对拍 changed=0.00%(level_map.png 改前基线)+shot_probe 问题=0 | 渲染+探针 | shot_probe 真实窗口跑批 | 等价重构申报(对拍差异非零即红) | 绿 | +| wr2 缓存生效与失效 | l2 缓存层 | 〔levelmap批 V1〕命中(is_same 同实例)+〔levelmap批 V2〕换代失效 | 单元 | level_map_info_fog_test 经 open_choices+layout_probe 真实路径 | 回退缓存 hunk → V1/V2 红(实测) | 绿 | + +- l4 基线建立:改前 shot_probe level 帧落盘 `game/tests/golden/level_map.png`(跨跑次一致性 0.00% 实测在案);时点铁序遵守(基线先于一切码改)。 +- T0 契约 3 论据链核验:`_fit_canvas`(open_choices :276)先于四调用点首触缓存——成立;`nodes()` 返回 level_row 内部数组,同关内内容稳定。 +- 喂入通道:生产调用点恰两处(main_adventure_flow :122/:173),两处后接 `apply_status(level_map_status_payload(...))`;shot_probe GP 徽章断言经真实生产路径兼容验证。 + +### 卡③ +| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 | +|---|---|---|---|---|---|---| +| wr1 文案与实数对齐 | effect_engine.gd :360 动态实数+:21-23 头注 | 〔效果文案批 V1〕message 含 FACTS.size() 且无「9 项」 | 单元 | validate_trigger_row static(与加载期同函数) | 回退 b1 hunk → 断言红(实测)→ 还原复绿 | 绿 | + +- T0 复核:FACTS 19 项/EFFECT_KINDS 32 项(脚本计数在案);全文件 errs.append 10 处仅 :360 含计数——无同类残留。 + +### 卡④ +| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 | +|---|---|---|---|---|---|---| +| wr1 行型收敛 | 六发射点 type→extended+effect_id 载荷 | 断言①–⑤+grep 判据+JSONL 读数对拍 | 单元+接口 | feats 装配驱动真实结算管线 | 回退被断言钉住的发射点 → 对应断言红(实测①) | 绿 | +| wr2 必载 effect_id | _emit_row 校验段(emit 之前) | 负面套件 extended_row_violation_test(expect=1, negative)+manifest 登记行 | 单元(负面套件) | _emit_row 真实直调(独立进程) | 回退校验段 → 违约行照发 exit 2(实测②) | 绿 | +| wr3 空来源修复 | grant :281 source_class→fx_warning_beacon | 断言⑤含 source_class==fx_warning_beacon | 单元 | warning_beacon 装配+封印命中 | 回退 a3 → wr3 红(实测③) | 绿 | +| wr4 parity 重锚(裁点甲) | anchor.json 重制 | 改前 check 绿基线+改后 freeze+复闸全同 | 集成 | parity_anchor 三变体真实跑批 | 重锚前后读数留档(§读数) | 绿 | + +- JSONL 读数对拍(wr1 附属):改前/改后各 6 文件(71000/72000/73000×normal/perfect)逐行比对——type 迁移 fx_reveal→extended 77+120+67=264、fx_preview_area→extended 136+168=304、insurance_used/mine_neutralized 0→0,与 T0 基线计数逐一对账;**字段口径外零差异**(口径=type/effect_id/source_class/source 四字段+剔除 session/diagnosis 墙钟行——source 为 playtest_logger.gd:133 对 source_class 的同值改名副本,随行平铺;ts 属墙钟已按锚口径剔除)。 +- a5 核验结论(零改动依据):battle_log_view `_format_row` 对四实名串本无 match 分支(全落 :210 fallback 按 display_name 呈现)、AGG_TYPES=["reward_ineffective"] 不含扩展行——收敛后扩展串同样落 fallback,a1 面呈现输出逐字节不变。**a3 面呈现差异如实申报**:示警烟火行从无来源标注变为带 `fx_warning_beacon` 裸键渲染(与 mine_neutralized 行 `fx_seal_smith` 裸键先例一致,来源归因可见性归位;§10 白名单第 4 条推荐案已执行)。 +- 改动面封闭核验:基础 9 类发射零触碰;SOURCE_EXEMPT_TYPES/CLASSES 逐字不动(fx_reveal 豁免按来源类别层继续生效);随机流九名零触碰;grep 判据=六发射点第一参全 extended+A8① 行型值类残留零命中(②–⑦保留类各现位抽查在案)。 + +### 卡⑤ +| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 | +|---|---|---|---|---|---|---| +| wr1 注释清扫 | level_runner :477-478/:10+autoplay :484 | grep 两串(零兑付直通/内容待定+内容未定)零命中 | 静态 | grep 真实跑批 | 恢复原句 → 命中即红 | 绿 | +| wr2 玩家文案换代 | battle_narrator :15 | 探针战报断言(含新串/不含旧串)〔卡⑤〕 | 探针 | 奖励节点真实战报路径 | 回退 d3 → wr2 单红(实测) | 绿 | +| wr3 wa4 转正 | ui_click_probe 捕获条件+必达判据+定向选路 | 直跑绿+真执行+未到停点即红 | 探针 | 三选一停点真实路径(press_candidate/press_card 真实按钮) | 回退 d4 条件 → wa4 必达红(实测) | 绿 | + +- **wa4 转正病根修正(d4 实现形态,§10 白名单第 3 条)**:原捕获条件查 `_curio_pending`(通关三选一停点态)而奖励节点停点住 `_node_curio_pending`——两态互斥使原条件带「关卡奖励点」头部串恒不可命中,即「拦截能力未证」的实况病根;转正随修挂正确停点态,头部校验串本体零触碰。定向选路=候选含奖励节点优先(press_candidate 真实命中路径零变化)。 +- wa4 真执行证据:直跑日志 `_tested_curio_head` 捕获成功、问题=0(/tmp/iter38_click6→c7_click 各跑次一致)。 +- extract_checks 提取面核查:探针断言不进清单(工具扫 tests/unit|integration|system_test)——无清单同步义务。 + +### 卡⑥ +| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 | +|---|---|---|---|---|---|---| +| wr1-a 错误行消失 | battle_log_view :17 类型一行 | 探针日志该 push_back 错误行归零(改前 148 处基线) | 探针+日志 | ui_click_probe 真实冒险路径 | 回退 g1 → 148 错误行复现(实测) | 绿 | +| wr1-b 摘要恢复 | 同上 | 正向断言(round≥2 且队列空时 _summary 非空)〔卡⑥ wr1-b〕 | 探针 | finish_settlement_round 真实轮界 | 回退 g1 → 断言红(实测同跑次) | 绿 | + +- wr1-b 观测点落位说明(L1 白名单内):挂主循环战中场(round≥2 且排播队列已空=轮首分隔行已消费),不挂循环出口——收尾续玩重建段会再走 `log_view.clear()`,出口断言会被合法清空冲掉(实测发现并修正)。 +- 错误行形态实证:`ERROR: Attempted to push_back a variable of type 'String' into a TypedArray of type 'Dictionary'`(非 SCRIPT ERROR——与 G2「断言仍绿」及闸机扫描不误报自洽;卡面引述的「SCRIPT ERROR 行」按实际标记执行,判据对象同一直接证据)。 + +### 卡⑦ +| 需求点 | 改动 | 验收测试 | 层级 | 真实入口 | 撤销证明 | 结果 | +|---|---|---|---|---|---|---| +| wr1 发布态恒隐 | main.gd :199 删除+三断言反转+c6/c8 随动 | system_test V2f+click 设置面+tune 对局段+清单对账+新基线 | 系统+探针+渲染 | 三宿主真实跑批 | 恢复 :199 单行 → 三断言齐红(实测) | 绿 | +| wr2 对局中开窗回归保载 | c5 直调承载 | 浮窗可见/输入锁不锁/六项读数断言保持绿 | 探针 | _show_debug_overlay 真实单口直调 | 回退=c5 整段回原形(契约 4 口径) | 绿 | + +- 契约 1 核验:`grep -c _debug_entry main.gd` 恰 2(:73 声明/:200 读取)。 +- c8 基线重制(§10 落定值=显式重制):改后实测 changed=0.15%(幽灵角标消失,≈卡面预估 0.14%)→ 重制后复闸 0.00%;README 判定记录登记在案。 +- 清单对账:`extract_checks.py --diff` 零差异(本批新增断言随清单重生成,1588 条——含卡①③④新增单测断言的全量同步)。 + +## 三、卡②停卡呈报(实现段零裁断——上游缺陷移交) + +**停卡时点**:T1 实装中(p1–p6 六件全部实装完成、wr4 断言载体核查时发现结构性冲突)。改动已还原(WIP 补丁留存 /tmp/iter38_card2_wip.patch,172 行),零提交、不阻塞其余卡。 + +**缺陷**:wr5/wr6 验收载体与闸机错误扫描结构性互斥。卡片处方(§7 接口块)=在现役**非负面**单测套件内直调 `board_generator._fatal()`/`open_stream("bogus_purpose")` 触发 assert 取证断言;但 headless 实测(探针脚本在案):GDScript assert 失败必产 `SCRIPT ERROR` 行且**不可捕获、只中断当前函数**——run_all.gd:133-136 对非负面套件输出含该标记即判 FAIL。即:断言跑到的同跑次闸 02 必红,「wr5/wr6 绿+全套闸全绿」不可同时成立。此与卡④ grilling 第二轮发现的同款机制缺陷完全同型(卡④因此立了独立负面小套件),卡②两轮复核均未捕获。 + +**两种读法**:①按卡面处方实装 → wr5/wr6「绿」但全套闸红(完成定义第 1 条自相矛盾);②改载体为独立负面小套件 → 卡面「零新增文件、wr5 落 round_flow_skill_pool_test 增断言」等节全部失准,属卡片设计变更非实现细节。 + +**建议案**:退回写卡段,比照卡④先例改处方——wr5/wr6 断言落独立负面小套件(各一或合并一件,manifest `expect=1, negative=true`),撤销证明口径同步改「退出码 0 ≠ expect 1」;文件结构图「零新增文件」句、估时(+0.1d 量级)、§8.1 对照表落点随更。另:卡② §9 L3 已点名的 master_seed<0 分支同病根第二处(:104-106 assert 后无 return)仍在册待用户裁另立或并批——若并批,建议与 wr6 同套件承载(同文件同族负路径)。 + +**待办清单对应条目状态回退拟稿**:「结构审查未立项小件候审」条目状态「已写卡+已编迭代 38」→ 回退「已写卡(v1.2,退回补验收载体处方)」;缺口补记=wr5/wr6 载体改负面小套件+master_seed 分支处置待裁。落笔归写卡段/用户(本批禁写根规则文件)。 + +## 四、闸与读数 + +- 全量域闸:`tools/run_all_gates.sh --code` 批末一次(416ca84a 时点,exit=1 未全绿);其后闸红必修一笔(0cd86469 S11 四处禁用词换代——并 master 文档批「通关口径统一」63763565 退役词新增所致)+W2 尾缀笔误随暂停收尾修正。**分支尖闸面现状(未闭合,用户 2026-10-26 裁「暂停收口」)**:09 静态扫描 S13 gdstyle 恶化 2 条——`battle_orchestrator.gd` max-file-length 1314(基线 1302,卡④ a4 头注必载内容所致)、`level_map_screen.gd` max-class-variables 22(基线 21,卡①契约 3 缓存成员所致);压缩还债经计算不可行(极限 1305 > 1302 且削减卡片要求内容),闸壳「经用户批准随批重生成基线」通道待用户后续裁决。其余闸段(00/01/02/03/04/05/10)全绿;S11/S12 修复后单件复跑 ok 在案。 +- MC 对拍:卡④触结算行协议面(零数值改动)——按裁点甲以 parity_anchor 三变体对拍承载:改前 check 全同(stash 干净跑批实测)→ 改后 freeze 立新锚 → 复闸 RUNSUM+JSONL 双面逐位一致;**审计独立复跑三变体对拍一致**(审计报告在案)。锚已随卡④提交换代。 +- 卡④ T0/T2 读数:改前行型计数 fx_reveal 264/fx_preview_area 304/insurance_used 0/mine_neutralized 0;改后迁移计数逐一相抵;基线 JSONL 留存 /tmp/iter38_pre_jsonl(临时区,对拍读数已录本报告)。 + +## 五、实机目检总清单(登记不阻塞) + +1. 选路屏状态栏/悬停高亮(卡①——l1 重构后目检一项)。 +2. 选档删除确认流(卡②——未实施,随停卡不登记)。 +3. 奖励节点战报行新文案「关卡奖励点(三选一——选毕推进)」(卡⑤)。 +4. 结算明细带轮摘要显示(卡⑥——「上轮摘要」恢复后的观感)。 +5. 结算明细屏四效果行目检+示警烟火行来源标注(卡④——扩展行 fallback 呈现与裸键标注)。 +6. 菜单/设置面/对局中调试角标恒隐(卡⑦三项)。 + +## 六、遗留读数义务与在飞注记 + +- 卡②WIP 补丁:/tmp/iter38_card2_wip.patch(临时区——若写卡段改卡后重启施工,以此为底稿重做而非直接复用,行号须按合并后 master 复核)。 +- 卡④ a3 面呈现差异(示警烟火行 `fx_warning_beacon` 裸键)随 §五.5 目检覆盖;settlement.tres source_label_map 补键属 §10 备选未裁不实装。 +- parity 锚换代完成:「结算明细日志行序重锚批」待办条目出账依据随卡④提交在案(文档批落文见反哺点清单②)。 + +## 七、独立验收审计结论 + +审计报告:`reports/iter38-验收审计-2026-10-26.md`——结论**不通过**(合并前置未满足,与用户「暂停收口」裁决一致)。六卡实质面全部核实通过(逐卡对照表、撤销证明抽查、削弱核查零未申报、卡④ parity 重锚审计独立复跑一致);两项阻断均为闸面/账面而非码面:①S13 恶化 2 条(=§四已披露、用户已裁暂停待后续处置的事项);②验证集清单提交态失同步(合并解冲突时 git add 先于清单重生成——提交版为 master 侧旧 1591 条,正确的 1596 条重生成版滞留工作区;暂停收尾已补提交,`extract_checks --diff` PASS 在案)。轻微两项(W2 尾缀笔误、产出物未跟踪)已随暂停收尾修正/提交。 diff --git a/reports/iter38-验收审计-2026-10-26.md b/reports/iter38-验收审计-2026-10-26.md new file mode 100644 index 00000000..ddc10006 --- /dev/null +++ b/reports/iter38-验收审计-2026-10-26.md @@ -0,0 +1,120 @@ +# 迭代 38 独立验收审计报告(2026-10-26) + +- 审计人:独立验收审计会话(未参与实现);审计基点=iter38 分支尖 `0cd86469`(worktree `D:\code\g-plan2-iter38`,全程只读+最窄复跑,零文件改动——卡③卡①撤销复现的临时改动均当场 `git checkout --` 还原)。 +- 审计对象:卡①③④⑤⑥⑦ 六卡收口提交链(b85b3b45→a15370c5→00ed0236→ce49a8ee→d5a03db1→5b5214f4→416ca84a 并 master→0cd86469 S11 闸红必修);卡②停卡零提交不在范围(停卡留痕 /tmp/iter38_card2_wip.patch 10215 字节在案,worktree 工作区无残码)。 +- 输入合同:七张需求卡片 §8.1/§8.2/§8.3+实现报告 §二 对照表+代拍板追认单 21 条。 + +## 结论行 + +**不通过——六卡代码面、断言面、撤销证明、削弱申报全部核实通过,卡④ parity 重锚经审计独立复跑逐位一致;但分支尖存在两项闸面未闭合:①09 静态扫描仍红(gdstyle 两条指标恶化未还债未随批重生成基线,批末闸回执 all_green=false 且实现报告未披露);②合并把验证集清单退回 master 旧版(本批新增断言在提交版清单零命中),正确的再生成件躺在 worktree 未提交。两件清偿+闸复跑绿回执后方可合并。** + +## 一、逐卡逐行核对表(实现报告 §二 vs 实码) + +### 卡① levelmap 批(b85b3b45) + +| 报告行 | 核对项 | 结果 | 依据 | +|---|---|---|---| +| wr1 | golden 基线+对拍 0.00% | 通过(读数采信,复跑受限) | level_map.png 与码改同提交(git log --diff-filter=A 唯一 b85b3b45);golden/README 登记行在案(改前建基、跨跑次 0.00% 后落盘);审计窗口化 shot_probe 复跑在本环境 180 秒挂起无输出(exit 124,引擎 OpenGL 启动后停滞——环境性问题非码问题),像素级未独立复跑;收口时点闸 05 渲染闸绿(shot_probe 七屏 A-D 断言)佐证渲染面健康 | +| wr2 | 〔levelmap批 V1/V2〕 | 通过 | level_map_info_fog_test.gd:209/:224 两断言在位;经 open_choices+layout_probe(视图 :524 既有件、ui_input_probe 同款)真实绘制路径;缓存层在 `_layout` 内、四调用点零改动(diff 核实) | +| l1 装配迁移 | 喂入通道 | 通过 | main_adventure_flow :122-123/:174-175 两生产调用点均 open_choices 后接 apply_status(level_map_status_payload(...));payload 七键与代拍板 #1 一致;视图侧拼装 grep 残留仅 :204 注释/:207 血条绘制读 max_hp(绘制面非拼文案)/:278 tint 层号——清零判据成立 | +| l3 字体提取 | 函数首单取 | 通过 | :429 `var font := get_theme_default_font()`,三处 draw_string 共享(diff 核实) | + +### 卡③ 效果文案批(a15370c5) + +| 报告行 | 核对项 | 结果 | 依据 | +|---|---|---|---| +| wr1 | 〔效果文案批 V1〕 | 通过 | effect_words_t1_test.gd:48-51 断言在位:message 含 str(FACTS.size()) 且不含「9 项」;validate_trigger_row static 与加载期同函数(卡面处方,非绕过) | +| b1/b2 | 动态实数+头注治理 | 通过 | effect_engine.gd:360 `%d 項 % FACTS.size()`;:21-23 头注去写死计数,历史笔误句按代拍板 #5 保留;errs.append 全文件仅 :360 含计数(grep 复核);FACTS 19/EFFECT_KINDS 32 脚本计数复核一致 | +| 改动面封闭 | 契约 1 | 通过 | 提交 diff 恰两文件共 6 行(+4/-2),零越界 | + +### 卡④ 明细收敛批(d5a03db1) + +| 报告行 | 核对项 | 结果 | 依据 | +|---|---|---|---| +| wr1 行型收敛 | 六发射点 | 通过 | orchestrator :156/:164/:169+grant_segment :271/:281/:309 第一参全 `extended`,display_name/value/highlight 逐字不动(diff 逐行核);grep 判据=四实名串作第一参零残留 | +| wr2 必载 effect_id | a2 校验段 | 通过 | _emit_row 体内 `step_row.emit` 之前(先于 _assert_row_source),缺载 assert+return 零发射;负面套件 extended_row_violation_test 直调 _emit_row 为卡面 §7 明文处方,非绕过 | +| wr2-np | 负面套件+manifest | 通过 | 套件在位(违约直调→零发射→合法对照行照发→quit(1));run_all.gd:73 登记 `expect=1, negative=true`;审计直跑实测 exit=1、输出「违约零发射+合法照发(a2 校验在位)」 | +| wr2 断言①–⑤ | 两现役套件 | 通过 | round_flow :1012/:1046/:1063(feats 装配驱动真实 step_row 信号捕获+_judge_seal/_flush_pending_reveals 真实管线)+treasure :333/:355(套件首次 feats 注入,V6 形态移植;三枚不同雷格各封一次=代拍板 #10①);其中两条用 Kit.check_quiet——不进验证集清单提取面(工具只提 `check(`),清单算术 1591+5=1596 与此吻合 | +| wr3 空来源 | a3 修复 | 通过 | grant :281 `""`→`fx_warning_beacon`;断言⑤含 source_class 判据 | +| wr4 parity 重锚 | 锚换代+复闸 | 通过(审计独立复跑) | anchor.json 在 d5a03db1 换代:恰 3/6 条 sha256 变更(三变体 perfect 面)、rows 字段零变化——与读数「迁移集中于部分文件、RUNSUM 数值面零变化」自洽;审计复跑 `python tools/parity_anchor.py check` exit=0,三变体 RUNSUM+JSONL 双面逐位一致,实算 JSONL 哈希(962b6f68…/06cb4dcc…/c3d0d37c…)与新锚逐位相同 | +| JSONL 读数对拍 | 264/304/0/0 | 自洽(原始件未独立复算) | 对拍原始 JSONL 留存 /tmp/iter38_pre_jsonl 临时区不可复取;锚 diff 形态(3/6 哈希变、行数零变)与读数口径一致;T0 计数随报告留档 | +| a5 呈现核验 | battle_log_view 零改动 | 通过 | d5a03db1 未触该文件 | +| 改动面封闭 | 契约 1/3 | 通过 | SOURCE_EXEMPT_TYPES/CLASSES 两常量在 diff 中仅作上下文出现、现值逐字未动;`open_stream("fx_reveal")` 等随机流零触碰;基础 9 类发射点零触碰(全 diff 仅六发射点+校验段+头注) | + +### 卡⑤ 奖励节点清扫批(00ed0236) + +| 报告行 | 核对项 | 结果 | 依据 | +|---|---|---|---| +| wr1 | 两串 grep 零命中 | 通过(复跑) | 审计复跑两条 grep 命令均零输出;E2 豁免面未被顺手改:autoplay :220/:381 行为性准确表述保留、ui_click_probe :697「卡片前提缺陷记录」历史注记保留 | +| wr2 | 战报文案断言〔卡⑤ wr2〕 | 通过(审读替代复现) | ui_click_probe :428-432:log_view 含「关卡奖励点(三选一——选毕推进)」且不含「内容待定」;battle_narrator :15 新文案在位;断言挂奖励节点停点捕获块,同一驱动路径 | +| wr3 wa4 转正 | 必达判据〔卡⑤〕+定向选路 | 通过(审读替代复现) | 捕获条件 `_curio_pending`→`_node_curio_pending`(病根修正,头部校验串本体零触碰);:725-728 全程未到停点即红;定向选路只改 press_candidate 索引策略、真实按钮命中路径零变化;press_card/press_confirm 真实按钮路径。撤销证明按审计指令以代码审读替代(探针复现 >10 分钟);报告声明回退 d3→wr2 单红、回退 d4→必达红两条实测在案 | + +### 卡⑥ 结算行缓冲批(ce49a8ee) + +| 报告行 | 核对项 | 结果 | 依据 | +|---|---|---|---| +| wr1-a | 错误行 148→0 | 通过(审读替代复现) | 改动恰 :17 一行 Array[Dictionary]→Array[String];148 基线读数与归零不可廉价复跑(探针全程约分钟级×改前改后),按审计指令以代码审读替代:错误机理(TypedArray 异型 push_back)与修复一行因果闭合 | +| wr1-b | 〔卡⑥ wr1-b〕断言 | 通过(审读替代复现) | ui_click_probe :31/:560-566:观测点在主循环战中场(round≥2 且 _row_queue 空=轮首分隔行已消费),一次性判据,非循环出口——与代拍板 #16 落位说明一致;判定读 `_summary.text` 非空 | +| 无断言删除 | 削弱面 | 通过 | 提交 diff 零断言删除,探针仅 +9/-2(新增状态位+断言块) | + +### 卡⑦ 调试入口批(5b5214f4) + +| 报告行 | 核对项 | 结果 | 依据 | +|---|---|---|---| +| wr1 发布态恒隐 | 删行+三断言反转 | 通过 | main.gd :199 写死行已删(diff 核实,:73/:200 保留);grep -c `_debug_entry` main.gd 恰 2(审计实测);system_test V2f、click :324-327、tune :117-118 三处反转形态在位 | +| wr2 直调承载 | c5 | 通过 | tune else 分支 `main._show_debug_overlay()` 直调(:61 先例),后续浮窗可见/输入锁/六项读数断言原序保持;:44-49 程序性段零触碰(diff 核实) | +| 撤销证明 | 恢复 :199 三断言齐红 | 通过(审读替代复现) | 三处反向断言判据直接钉 `_debug_entry` 恒 false 语义,恢复写死行必红——机制审读成立;报告声明实测在案。置灰子断言删除有代拍板 #19 记录 | +| c6/c7/c8 随动 | 清单/README/基线 | 通过(c6 见问题②例外) | game/README 发布态表述换代;menu_boot.png 重制(Bin 变更)+golden/README 判定记录在案;c6 于 5b5214f4 时点对账零差异属实(提交版基点 d5a03db1/1588),合并后回归见问题清单② | + +## 二、撤销证明抽查记录 + +| 卡 | 方式 | 命令与输出摘要 | +|---|---|---| +| ③ | 复现红/绿实测 | 绿基线:`--script res://tests/unit/core/effect_words_t1_test.gd`→`all green (18 checks)`。改红:sed :360 `FACTS.size()`→`9`→`FAIL — fact 出域文案含词表实数 19、无写死计数〔效果文案批 V1〕`。还原 `git checkout --`→复绿 `all green (18 checks)` | +| ① | 复现红/绿实测 | 改红::505 缓存命中条件前置 `if false and ...`→`[FAIL] 〔levelmap批 V1〕`+`[FAIL] 〔levelmap批 V2〕` 双红。还原→复绿:`[PASS] V1`+`[PASS] V2`,`all green (21 checks)` | +| ④ | 直跑实测 | `--script res://tests/unit/battle_settlement/extended_row_violation_test.gd`→exit=1、`[extended_row_violation_test] 违约零发射+合法照发(a2 校验在位)`;manifest 登记行 run_all.gd:73 在位 | +| ⑤ | 代码审读替代(探针复现 >10 分钟) | wr2 断言与 wa4 必达判据代码在位(:428-432/:725-728),判据钉死新文案与停点态,机制上回退即红;报告两条实测记录在案 | +| ⑥ | 代码审读替代(探针复现代价高,按审计指令) | wr1-b 断言在位、观测点落位与代拍板 #16 一致;一行类型修复因果闭合 | +| ⑦ | 代码审读替代(探针复现代价高) | 三处反向断言判据直接依赖「无写死行」前提,恢复 :199 必红;报告实测记录在案 | + +## 三、测试削弱核查(七卡 §8.2+代拍板单 vs 实际 diff) + +逐提交核对 b23fa50d→0cd86469 区间(含合并带入的 master 通关换代批——该批有独立审计 57447386 在案,不计本批): + +1. **卡④**:SOURCE_EXEMPT_TYPES/CLASSES 两常量零变化(diff 中仅上下文行)——申报吻合。基础 9 类行型发射零触碰:全区间 `_emit_row` 第一参改动恰六发射点+校验段,六点外无第一参改动(grep+diff 双核)。 +2. **卡⑦**:三处断言反转=换代非削弱,申报在卡面 §8.2;置灰子断言删除有代拍板 #19 记录;ui_click_probe 设置面原「浮层不被遮挡」语义退役有卡面 §8.2 申报。既有断言零删除(三处全原位反转)。 +3. **卡⑤**:wa4 条件转必达=判定面增强;头部校验串、autoplay 直通行为、:220/:381 豁免表述、:697 历史注记全部原样(审计逐处复核)。 +4. **卡⑥**:零断言删除。**卡③/卡①**:既有断言零改动(effect_words +4/-0、level_map_info_fog +36/-0 纯增)。 +5. **验证集清单**:断言改判(V2f 行 74)与新增同步在 5b5214f4 履行(1588 条对账零差异,卡内申报一致);合并回归单独列问题②。 + +**削弱核查结论:零未申报削弱。** + +## 四、附加核对结果 + +1. **合并 416ca84a 代码面零触碰——成立(比指令口径更严的核法)**:区间 diff `5b5214f4..416ca84a` 含 master 侧通关换代批的全部码改(该批为 master 上已审计的独立批次,非合并引入);合并解的真实触碰面用 `git diff-tree --cc` 定位=7 文件(6 码件+README),6 个码件逐一以 patch-id 对比「合并结果 vs master 侧」恰等于「iter38 侧自身改动」——全部 MECHANICAL(机械合成),即合并解对手只落在 README.md 与验证集清单.md 两文档(与提交声明一致;清单侧取 master 整版引发问题②)。 +2. **卡① golden 时序**:level_map.png 新增提交=b85b3b45=码改同一笔。git 历史无法证明提交内「先建基线后改码」时序——**该限制如实注明**,采信依据=golden/README 登记记载(「改前建基——时点铁序,跨跑次 0.00% 实测后落盘」)与卡面 T1 前置步处方。 +3. **卡④ parity 锚**:d5a03db1 恰 3/6 哈希换代+rows 零变化,与 264/304/0/0 读数自洽;审计独立复跑 check 全同(见对照表 wr4 行)。 +4. **卡⑦**:`grep -c "_debug_entry" main.gd`=2(实测)。 +5. **卡⑤清单义务豁免**:探针不在 extract_checks 提取面(工具扫 tests/unit|integration|system_test)——「无清单同步义务」申述属实。 +6. **闸回执**:`game/tests/.gates/receipt.json` commit=416ca84a(15:38:28)、domain=code、**all_green=false**——09 静态扫描 exit=1;其后 0cd86469(16:00:28)只修 S11 四处,**无修复后的复跑回执**。审计复跑静态扫描:S11 已转绿(238 文件 23 词零命中),S13 仍红(详见问题①)。 + +## 五、问题清单 + +**阻断(合回前必须清偿)** + +1. **09 静态扫描在分支尖仍红——gdstyle 两条指标恶化未处置,实现报告未披露**。审计复跑实测:`battle_orchestrator.gd|quality/max-file-length = 1314(基线登记 1302)`(卡④ 校验段+头注加行所致)、`level_map_screen.gd|quality/max-class-variables = 22(基线登记 21)`(卡① _layout_cache 所致);master 侧分别为 1301/21,恶化归属本批无疑。批末闸回执(416ca84a)即因 S11+此两条判红,0cd86469 只修了 S11;七笔提交零触碰 tools/gdstyle_baseline.txt。按闸机口径须还债或经用户批准随批重生成基线,二者均未发生;完成定义「全套闸全绿」未满足,报告 §一「已收口」与 §四未提闸红,属呈报缺漏。 +2. **验证集清单在提交态与码失同步**。合并 416ca84a 对清单冲突取 master 侧整版(基点 54395b8b/1591 条):本批新增断言(levelmap批 V1/V2、效果文案批 V1、明细收敛 V2×3、卡⑥ wr1-b)在 HEAD 提交版清单 **零命中**,V2f 行 74 回退旧 cond「boot 可见」与已反转的码面断言相抵。正确再生成件(1596 条、含两侧断言、`extract_checks --diff` PASS)已存在于 worktree 但**未提交**。c6「清单没跟上视同未完成」在提交态不成立。清偿=提交该再生成件(算术复核 1591+5=1596,check_quiet 与探针不在提取面,无遗漏)。 + +**轻微(不阻断,随批或批后顺手)** + +3. level_map_info_fog_test.gd:195 段头注释尾缀误写〔levelmap批 W2〕——断言本体 V1/V2 正确,grep 通道不受影响,纯注释笔误。 +4. 三份产出物(实现报告/代拍板/反哺点)在主区与 worktree 均为未跟踪态,随批后收尾提交即可(现状与「实现批产出物只写 reports/」不冲突,登记在案防遗漏)。 + +**限制声明(采信依据已注明,非缺陷)** + +5. 卡① wr1 像素级对拍(changed=0.00%)与卡⑥ 148→0 基线读数未独立复跑:前者窗口化 shot_probe 在审计环境挂起(gate 05 渲染闸收口时点绿佐证),后者按审计指令以代码审读替代;两者均有实现报告读数与机制闭合支撑。 +6. 卡④ JSONL 原始对拍件留 /tmp 临时区不可复取,读数采信以锚 diff 形态+check 复跑佐证。 + +## 六、审计操作性记录 + +撤销复现的临时改动(effect_engine.gd :360、level_map_screen.gd :505)均以 `git checkout -- ` 当场还原,还原后工作区与审计起点一致(仅存审计前的验证集清单未提交再生成件与未跟踪产出物,零新增改动);worktree 主区未触碰。parity check 复跑约 13 分钟(base 288s/attune 286s/shop 234s),exit 0。 From c4c69701014d84d0022473d5263e0610759e3466 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Mon, 26 Oct 2026 18:08:22 +0800 Subject: [PATCH 9/9] =?UTF-8?q?chore(=E5=9F=BA=E7=BA=BF):=2009=20=E9=9D=99?= =?UTF-8?q?=E6=80=81=E6=89=AB=E6=8F=8F=E8=AE=B0=E5=AD=98=E9=87=8F=E7=BA=A2?= =?UTF-8?q?=EF=BC=88=E7=94=A8=E6=88=B7=E7=89=B9=E6=89=B9=E5=B8=A6=E7=BA=A2?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E2=80=94=E2=80=94=E8=BF=AD=E4=BB=A3=2038=20?= =?UTF-8?q?=E6=94=B6=E5=8F=A3=20gdstyle=20=E6=81=B6=E5=8C=96=202=20?= =?UTF-8?q?=E6=9D=A1=EF=BC=9Abattle=5Forchestrator=20=E8=A1=8C=E6=95=B0=20?= =?UTF-8?q?1314>1302=EF=BC=9D=E5=8D=A1=E2=91=A3=E5=A4=B4=E6=B3=A8=E5=BF=85?= =?UTF-8?q?=E8=BD=BD=E5=86=85=E5=AE=B9=E3=80=81level=5Fmap=5Fscreen=20?= =?UTF-8?q?=E7=B1=BB=E5=8F=98=E9=87=8F=2022>21=EF=BC=9D=E5=8D=A1=E2=91=A0?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=88=90=E5=91=98=EF=BC=9B=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E4=B9=89=E5=8A=A1=E7=9A=84=E8=AE=B0=E8=B4=A6=E6=BC=82=E7=A7=BB?= =?UTF-8?q?=E9=9D=9E=E7=BC=BA=E9=99=B7=EF=BC=8C=E7=94=A8=E6=88=B7=E8=A3=81?= =?UTF-8?q?=E4=B8=8D=E7=AE=A1=E6=81=B6=E5=8C=96=E7=9B=B4=E6=8E=A5=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E5=B9=B6=E7=89=B9=E8=AE=B8=E5=85=8D=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=87=8D=E8=B7=91=EF=BC=9B=E5=90=8E=E7=BB=AD=E6=89=B9=E6=AC=A1?= =?UTF-8?q?=E8=A7=A6=E4=B8=A4=E6=96=87=E4=BB=B6=E5=8F=AF=E9=A1=BA=E5=B8=A6?= =?UTF-8?q?=E8=BF=98=E5=80=BA=E6=88=96=E7=BB=8F=E6=89=B9=E5=87=86=E9=87=8D?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=BD=92=E7=BB=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/.gates/baseline.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/game/tests/.gates/baseline.json b/game/tests/.gates/baseline.json index 4fe9b1a7..6ec1962d 100644 --- a/game/tests/.gates/baseline.json +++ b/game/tests/.gates/baseline.json @@ -1,5 +1,5 @@ { - "note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。2026-10-18 新增闸件 08c 流水线闸(tools/pipeline_gate.py 注册上电),手工加入绿记录(roadmap 压缩批实测 --docs 域 exit=0),其余条目原样保留。2026-10-22 测试术语与层级重构批 A 改键:01 冒烟全量→01 系统测试(同一断言面 smoke_all.gd,运行器换 tools/run_system_test.py,过渡期判定口径不变)、02 单元套件→02 单元与集成测试(同一 run_all.gd 入口,名从实)、新增 00 冒烟测试(tools/run_smoke.py+game/tests/smoke_boot.tscn,重写后单跑实测 17 项 0 失败 PASS);09 静态扫描基线 exit=1(fp=4354b2aa72a7)系滞后红,本批单跑实测 exit=0(与 2026-10-22 16:20 回执一致),基线改回绿;其余条目原样保留。;2026-10-23 主会话记 07 doc_link 为存量红(用户令忽略:锻造工坊.md 死链的修复在另一会话未合入,合回闸豁免;外部修复合入后应重生成基线归绿)", + "note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成;2026-10-17 人工确认重生成 06 条目:af94ce97 把 gdd_lint 记为存量红(01 篇 R5),该红已由 fcb8c3f8 补流程图消除,实测 --docs 域 06 exit=0,故基线改回绿。2026-10-18 新增闸件 08c 流水线闸(tools/pipeline_gate.py 注册上电),手工加入绿记录(roadmap 压缩批实测 --docs 域 exit=0),其余条目原样保留。2026-10-22 测试术语与层级重构批 A 改键:01 冒烟全量→01 系统测试(同一断言面 smoke_all.gd,运行器换 tools/run_system_test.py,过渡期判定口径不变)、02 单元套件→02 单元与集成测试(同一 run_all.gd 入口,名从实)、新增 00 冒烟测试(tools/run_smoke.py+game/tests/smoke_boot.tscn,重写后单跑实测 17 项 0 失败 PASS);09 静态扫描基线 exit=1(fp=4354b2aa72a7)系滞后红,本批单跑实测 exit=0(与 2026-10-22 16:20 回执一致),基线改回绿;其余条目原样保留。;2026-10-23 主会话记 07 doc_link 为存量红(用户令忽略:锻造工坊.md 死链的修复在另一会话未合入,合回闸豁免;外部修复合入后应重生成基线归绿);2026-10-26 用户特批带红合并(迭代 38 收口):09 静态扫描记存量红——gdstyle 恶化 2 条(battle_orchestrator.gd max-file-length 1314>1302=卡④头注必载内容、level_map_screen.gd max-class-variables 22>21=卡①缓存成员,均为卡片义务的记账漂移非缺陷,压缩还债经计算不可行),用户裁「不管引入的恶化直接提交、特许免测试重跑」;后续批次若触及两文件可顺带还债或经批准重生成基线归绿", "gates": { "00 冒烟测试": { "exit": "0", @@ -46,8 +46,8 @@ "fp": "green" }, "09 静态扫描": { - "exit": "0", - "fp": "green" + "exit": "1", + "fp": "user-approved-iter38" }, "10 熵增闸": { "exit": "0",