From 208e5652db5f32d0fbd1e5f0492813e16d76509e Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 31 Oct 2026 08:06:06 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=AD=E4=BB=A346=E5=8D=A1A-T5(=E4=BA=94?= =?UTF-8?q?=E4=BB=B6=E5=90=8E=E7=BB=AD=E6=95=88=E6=9E=9C):=20=E6=8E=88?= =?UTF-8?q?=E4=BA=88=E6=AE=B5round=5Fend=20heal=20value=5Fcounter=E5=85=91?= =?UTF-8?q?=E4=BB=98(=E5=9D=91=E9=81=93=E5=B0=8F=E6=86=A9=E2=80=94?= =?UTF-8?q?=E2=80=94units.heal=E5=B0=81=E9=A1=B6=C2=B7=E6=BB=A1=E8=A1=80?= =?UTF-8?q?=E6=9C=AA=E7=94=9F=E6=95=88=E8=A1=8C=C2=B7=E5=85=88=E4=BA=8Eset?= =?UTF-8?q?=E6=B8=85=E9=9B=B6=E8=A1=8C=E6=B6=88=E8=B4=B9=E5=B1=82=E5=80=BC?= =?UTF-8?q?)=C2=B7=E7=9F=B3=E5=B1=82=E7=BC=93=E5=86=B2/=E8=93=84=E5=8A=9B?= =?UTF-8?q?=E5=91=A8=E8=BD=AC=E5=85=A8=E8=B5=B0=E6=97=A2=E6=9C=89=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E9=9D=A2(=E7=BB=9F=E4=B8=80damage=5Ftaken=E4=B8=8Eatt?= =?UTF-8?q?ack=5Fbonus=20value=5Fcounter=E9=9B=B6=E6=96=B0=E7=A0=81)=C2=B7?= =?UTF-8?q?feats.tres+3=E8=A1=8C(=E7=9F=B3=E5=B1=82=E7=BC=93=E5=86=B2commo?= =?UTF-8?q?n/=E8=93=84=E5=8A=9B=E5=91=A8=E8=BD=ACexcellent/=E5=9D=91?= =?UTF-8?q?=E9=81=93=E5=B0=8F=E6=86=A9common=C2=B7trigger=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E6=8C=89=E5=8D=A1=C2=A7=E5=8D=81.T6)=C2=B7V6=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=9B=BA=E5=AE=9A=E5=87=8F=E4=BC=A4(pierce8=E2=86=926?= =?UTF-8?q?=E5=8F=8C=E5=88=B7=E4=B8=8D=E5=8F=A0=E5=8A=A0=C2=B7=E6=9C=AA?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E8=BD=AE=E4=B8=8D=E5=87=8F=C2=B7=E5=9C=B0?= =?UTF-8?q?=E5=BD=A2max(=E4=B8=8B=E9=99=90,ev=E2=88=922))=C2=B7V9=E8=BD=AE?= =?UTF-8?q?=E6=9C=AB=E6=81=A2=E5=A4=8D(=E7=99=BD=E7=9B=92=E9=A2=84?= =?UTF-8?q?=E7=BD=AE=E4=B8=8A=E9=99=903=E9=97=B8=C2=B7=E5=9B=9E=E5=A4=8D3?= =?UTF-8?q?=E5=B1=82=E6=B8=850=C2=B7=E6=BB=A1=E8=A1=80=E6=9C=AA=E7=94=9F?= =?UTF-8?q?=E6=95=88=C2=B7=E8=87=AA=E7=84=B6=E7=B4=AF=E8=AE=A12)=C2=B7V8?= =?UTF-8?q?=E8=93=84=E5=8A=9B=E6=B6=88=E8=B4=B9(=E6=94=BB=E5=87=BB?= =?UTF-8?q?=E5=90=8E=E6=B8=850=C2=B7=E6=88=98=E6=96=97=E6=96=B0=E5=BB=BA0)?= =?UTF-8?q?=C2=B7V7=E5=BB=B6=E8=BF=9F=E7=BE=A4=E4=BC=A4(=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E9=9B=B6=E4=BC=A4=E5=AE=B3=E8=A1=8C=C2=B7=E6=88=98=E6=96=97?= =?UTF-8?q?=E6=AE=B5=E6=9C=AB=C3=972)=C2=B7=E7=AA=84=E6=B5=8B=E4=B8=89?= =?UTF-8?q?=E4=BB=B6=E5=85=A8=E7=BB=BF(44/70/134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/settlement/grant_segment.gd | 19 ++ game/data/tables/feats.tres | 144 +++++++++++++ .../battle_settlement/refresh_feats_test.gd | 191 +++++++++++++++++- 3 files changed, 350 insertions(+), 4 deletions(-) diff --git a/game/core/settlement/grant_segment.gd b/game/core/settlement/grant_segment.gd index 3b5f43bb..d2a5d3e5 100644 --- a/game/core/settlement/grant_segment.gd +++ b/game/core/settlement/grant_segment.gd @@ -266,6 +266,7 @@ func _seal_strike_value(fs_fx: Array, fs_hits: int, fs_misses: int) -> Dictionar func _round_end_apply(ri: int) -> void: var re_fx := fx_host.fx_emit("round_end", {"round": ri}) _round_end_blast_tickets(re_fx, ri) + _round_end_heal_value_counter(re_fx, ri) # 迭代 46 卡A T5——坑道小憩恢复兑付(先于清零行消费层值,声明序=heal 行在 counter set 行前) for h in re_fx: if String(h.get("kind", "")) == "counter_add": _apply_counter_hit(h) @@ -285,6 +286,24 @@ func _round_end_apply(ri: int) -> void: oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界) +# round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点, +# units.heal 封顶、满血发未生效行;层清零由同事件 counter_add set 行承载〔声明序在后〕——本面 +# 先于 counter_add 遍历消费层值即保序。行源=成功刷新事实族(无棋盘格——fx_board_refresh 豁免类)) +func _round_end_heal_value_counter(re_fx: Array, ri: int) -> void: + for h in re_fx: + if String(h.get("kind", "")) != "heal" or String(h.get("value_counter", "")) == "": + continue + var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", ""))) + if layers <= 0: + continue + var hu: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit() + if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)): + oc._emit_row({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))}) + continue + var healed: int = oc._enemy_system.heal(hu, layers) + oc._emit_row({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"}) + + # round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名, # per-counter 反打通道保留泛用面;案C 暂存战斗段末兑付) func _round_end_blast_tickets(re_fx: Array, ri: int) -> void: diff --git a/game/data/tables/feats.tres b/game/data/tables/feats.tres index 6667b737..6bb2bf5c 100644 --- a/game/data/tables/feats.tres +++ b/game/data/tables/feats.tres @@ -935,5 +935,149 @@ rows = [ } } ] + }, + { + "feat_id": "stone_mitigation", + "display_name": "石层缓冲", + "rarity": "common", + "cap": 1, + "desc": "当轮减伤:本轮成功刷新后,每次受伤固定 −2(多刷不叠加)", + "triggers": [ + { + "on": "board_refreshed", + "do": { + "kind": "counter_add", + "counter_id": "round_swap_guard", + "value": 1 + } + }, + { + "on": "damage_taken", + "when": [ + { + "fact": "counter", + "id": "round_swap_guard", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "damage_reduce", + "value": 2 + } + } + ] + }, + { + "feat_id": "charge_rotation", + "display_name": "蓄力周转", + "rarity": "excellent", + "cap": 1, + "desc": "刷后蓄力:每次刷新 +1 层(上限 3),下一次攻击消费全部层数", + "triggers": [ + { + "on": "board_refreshed", + "when": [ + { + "fact": "counter", + "id": "swap_charge_layers", + "op": "lt", + "value": 3 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "swap_charge_layers", + "value": 1 + } + }, + { + "on": "attack_commit", + "when": [ + { + "fact": "counter", + "id": "swap_charge_layers", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "attack_bonus", + "value_counter": "swap_charge_layers" + } + }, + { + "on": "attack_commit", + "when": [ + { + "fact": "counter", + "id": "swap_charge_layers", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "swap_charge_layers", + "set": 0 + } + } + ] + }, + { + "feat_id": "tunnel_rest", + "display_name": "坑道小憩", + "rarity": "common", + "cap": 1, + "desc": "当轮恢复:每次刷新累计 1 点(上限 3),本轮轮末合计回复", + "triggers": [ + { + "on": "board_refreshed", + "when": [ + { + "fact": "counter", + "id": "round_swap_rest", + "op": "lt", + "value": 3 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "round_swap_rest", + "value": 1 + } + }, + { + "on": "round_end", + "when": [ + { + "fact": "counter", + "id": "round_swap_rest", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "heal", + "value_counter": "round_swap_rest" + } + }, + { + "on": "round_end", + "when": [ + { + "fact": "counter", + "id": "round_swap_rest", + "op": "gte", + "value": 1 + } + ], + "do": { + "kind": "counter_add", + "counter_id": "round_swap_rest", + "set": 0 + } + } + ] } ] diff --git a/game/tests/unit/battle_settlement/refresh_feats_test.gd b/game/tests/unit/battle_settlement/refresh_feats_test.gd index 6aa55a0e..e601da85 100644 --- a/game/tests/unit/battle_settlement/refresh_feats_test.gd +++ b/game/tests/unit/battle_settlement/refresh_feats_test.gd @@ -34,6 +34,10 @@ func _init() -> void: test_no_feats_baseline() test_board_refresh_fact_only_on_success() test_mine_profit_gp() + test_stone_mitigation_all_damage() + test_rest_rotation_cap_and_clear() + test_charge_layers_consumed_same_round() + test_blast_defers_to_combat_segment() quit(Kit.finish("refresh_feats_test")) @@ -55,10 +59,10 @@ func _orch(seed_v: int): return load("res://core/battle_orchestrator.gd").new(_db, rng) -func _enc(feats: Array) -> Dictionary: - return {"level_index": 1, "node_depth": 4, "hero_hp": 200, "hero_atk": 8, "hero_max_hp": 200, - "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": [], - "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}} +func _enc(feats: Array, voucher_rap: int = 0, armor: int = 10, hero_hp: int = 200) -> Dictionary: + return {"level_index": 1, "node_depth": 4, "hero_hp": hero_hp, "hero_atk": 8, "hero_max_hp": 200, + "hero_armor_class": armor, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": [], + "battle_index": 1, "voucher_rap": voucher_rap, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}} func _probe_count(orch) -> int: @@ -419,3 +423,182 @@ func test_mine_profit_gp() -> void: # 拒绝零变化:RAP 不足 Kit.check("V10 拒绝零变化(escrow=%d)〔换图系批 V10〕" % orch.reward_grant().escrow, not orch.player_request_refresh() and orch.reward_grant().escrow == escrow0 + 2 and int(gp_rows["n"]) == 2) + + +# ---- V6/V8/V9/V7 五件后续效果(迭代 46 卡A T5)---- + +func _counter(orch, counter_id: String) -> int: + return EffectEngineScript.counter(orch.fx_host().counters_ctx, counter_id) + + +# 伤害明细行泛收集(type=damage 全量——按来源类与值断言) +func _collect_damage_rows(orch) -> Array: + var rows: Array = [] + orch.step_row.connect(func(row): + if String(row.get("type", "")) == "damage": + rows.append(row)) + return rows + + +func _damage_rows_by_source(rows: Array, source: String) -> Array: + var out: Array = [] + for r_v in rows: + var r := r_v as Dictionary + if String(r.get("source_class", "")) == source: + out.append(r) + return out + + +# V6 石层缓冲:全局固定减伤(危险格 pierce 8→6 两档钉死:单刷减 2·同轮多刷不叠加·未刷新轮不减; +# 地形路径减后值=max(下限, effect_value−2)——含下限保持;敌方路径照常单次发射) +func test_stone_mitigation_all_damage() -> void: + print("--- V6 石层缓冲:全局固定减伤 ---") + # S1 同轮双刷 → 危险格 pierce 8 减 2=6(叠加即 4——多刷不增钉死) + var orch1 = _orch(46041) + var rows1 := _collect_damage_rows(orch1) + Kit.check("V6 S1 前置:战斗受理", orch1.begin_battle(_enc(["stone_mitigation"]))) + _bank_rap(orch1, 3) + Kit.check("V6 S1 同轮双刷受理", orch1.player_request_refresh() and orch1.player_request_refresh()) + Kit.check("V6 S1 guard 累计 2(同轮双刷)", _counter(orch1, "round_swap_guard") == 2) + var mine1 := _dig_until(orch1, func(evt): + return String(evt.get("kind", "")) == "mine" and _is_direct_type(orch1, evt.cell)) + Kit.check("V6 S1 前置:直接伤型危险格已挖开(%s)" % str(mine1.get("cell", Vector2i(-1, -1))), not mine1.is_empty()) + orch1.minesweeper.settle_batch([{"action": "stop"}]) + var pierce_rows1 := _damage_rows_by_source(rows1, "mine_hit") + Kit.check("V6 S1 危险格减伤恰 2 且多刷不叠加(行值 %s∈[6]——叠加态应为 4)〔换图系批 V6〕" % str(pierce_rows1.map(func(r): return int(r.value))), + pierce_rows1.size() >= 1 and int((pierce_rows1[0] as Dictionary).get("value", -1)) == 6) + # S2 未刷新轮:guard 随 round_start 清空 → 同型危险格不减(pierce/magic 表幂 8——heal 型幂 6 不同锚勿混) + var mine2 := _dig_until(orch1, func(evt): + var snap_v2 = orch1.reward_grant().reward_economy.query_full_snapshot(evt.cell) + return String(evt.get("kind", "")) == "mine" and snap_v2 is Dictionary \ + and ["pierce", "magic"].has(String((snap_v2 as Dictionary).get("cell_type", "")))) + Kit.check("V6 S2 前置:第二直接伤型危险格已挖开(%s)" % str(mine2.get("cell", Vector2i(-1, -1))), not mine2.is_empty()) + orch1.minesweeper.settle_batch([{"action": "stop"}]) + var pierce_rows2 := _damage_rows_by_source(rows1, "mine_hit") + Kit.check("V6 S2 未刷新轮不减伤(新轮行值 %s 含 8——round 桶轮始清空)〔换图系批 V6〕" % str(pierce_rows2.map(func(r): return int(r.value))), + pierce_rows2.size() >= 2 and int((pierce_rows2[pierce_rows2.size() - 1] as Dictionary).get("value", -1)) == 8) + # S3 地形路径:减后值 = max(下限, effect_value−2)——下限保持与定值减一并钉死 + # (种子 46002=V5② 已证含 flat_damage 变异板;刷新换板后变异重布—— digging guard 内可达) + var orch3 = _orch(46002) + var rows3 := _collect_damage_rows(orch3) + Kit.check("V6 S3 前置:战斗受理", orch3.begin_battle(_enc(["stone_mitigation"]))) + _bank_rap(orch3, 2) + Kit.check("V6 S3 单刷受理", orch3.player_request_refresh()) + var terr3 := _dig_until(orch3, func(evt): + for vid_v in (evt.get("variant_ids", []) as Array): + if String(_db.combat.variation.row_by_id(String(vid_v)).get("effect", "")) == "flat_damage": + return true + return false) + Kit.check("V6 S3 前置:伤害型变异格已挖开(%s)" % str(terr3.get("cell", Vector2i(-1, -1))), not terr3.is_empty()) + orch3.minesweeper.settle_batch([{"action": "stop"}]) + var terrain_rows := _damage_rows_by_source(rows3, "地形变异") + var floor_v: int = int(_db.combat.settlement.damage_roll.damage_floor) + var ev_ok := true + for tr_v in terrain_rows: + var tr := tr_v as Dictionary + var vid := String(tr.get("variant_id", "")) + var ev: int = int(_db.combat.variation.row_by_id(vid).get("effect_value", 0)) + if int(tr.get("value", -1)) != maxi(floor_v, ev - 2): + ev_ok = false + Kit.check("V6 S3 地形减伤=max(下限,effect_value−2) 逐单闭合(%d 单)〔换图系批 V6〕" % terrain_rows.size(), + terrain_rows.size() >= 1 and ev_ok) + + +# V9 坑道小憩:轮末合计回复(白盒预置承载上限 3——计数器直改属数据面注入; +# 自然累计 2 与满血未生效两形态分战场钉死) +func test_rest_rotation_cap_and_clear() -> void: + print("--- V9 坑道小憩:轮末恢复 ---") + # R1 白盒预置 3 层 + 刷新闸(when<3 拒第 4 层)→ 停手回复 min(3, 缺血)——护甲降档保缺血 ≥3 + var orch1 = _orch(46051) + var rows1: Array = [] + orch1.step_row.connect(func(row): rows1.append(row)) + Kit.check("V9 R1 前置:战斗受理", orch1.begin_battle(_enc(["tunnel_rest"], 0, 10, 100))) + _bank_rap(orch1, 1) # 开局血差 100(hero_hp 100/max 200)——缺血 ≥3 恒成立,回复面确定 + var missing: int = 200 - int(orch1._enemy_system.hero_unit().get("hp", 0)) + EffectEngineScript.counter_add(orch1.fx_host().counters_ctx, "round_swap_rest", 3) + Kit.check("V9 R1 预置 3 层后刷新被闸(when<3——上限 3)", orch1.player_request_refresh() and _counter(orch1, "round_swap_rest") == 3) + orch1.minesweeper.settle_batch([{"action": "stop"}]) + var heal_rows1: Array = [] + var idle_rows1 := 0 + for r_v in rows1: + var r := r_v as Dictionary + if String(r.get("type", "")) == "heal" and String(r.get("source_class", "")) == "fx_board_refresh": + heal_rows1.append(r) + if String(r.get("type", "")) == "reward_ineffective" and String(r.get("batch_key", "")) == "round_swap_rest": + idle_rows1 += 1 + Kit.check("V9 R1 轮末回复=min(3,缺血%d) 且层清 0(heal 行值 %s)〔换图系批 V9〕" % [missing, str(heal_rows1.map(func(r): return int(r.value)))], + missing >= 3 and heal_rows1.size() == 1 and int((heal_rows1[0] as Dictionary).get("value", -1)) == 3 and _counter(orch1, "round_swap_rest") == 0) + # R2 满血未生效:首轮刷新后停手——授予段 heal 时点主角仍满血(combat 段还手在其后) + var orch2 = _orch(46052) + var rows2: Array = [] + orch2.step_row.connect(func(row): rows2.append(row)) + Kit.check("V9 R2 前置:战斗受理", orch2.begin_battle(_enc(["tunnel_rest"], 8))) + Kit.check("V9 R2 首轮刷新受理(voucher_rap 入池)", orch2.player_request_refresh()) + orch2.minesweeper.settle_batch([{"action": "stop"}]) + var heal_rows2 := 0 + var idle2 := 0 + for r2_v in rows2: + var r2 := r2_v as Dictionary + if String(r2.get("type", "")) == "heal" and String(r2.get("source_class", "")) == "fx_board_refresh": + heal_rows2 += 1 + if String(r2.get("type", "")) == "reward_ineffective" and String(r2.get("batch_key", "")) == "round_swap_rest": + idle2 += 1 + Kit.check("V9 R2 满血未生效行在案且零 heal 行(未生效 %d·heal %d)〔换图系批 V9〕" % [idle2, heal_rows2], + idle2 == 1 and heal_rows2 == 0 and _counter(orch2, "round_swap_rest") == 0) + # R3 自然累计:同轮双刷 → 回复 2(上限 3 的闸面由 R1 白盒钉住,此处证自然路径) + var orch3 = _orch(46053) + var rows3: Array = [] + orch3.step_row.connect(func(row): rows3.append(row)) + Kit.check("V9 R3 前置:战斗受理", orch3.begin_battle(_enc(["tunnel_rest"]))) + _bank_rap(orch3, 2) + Kit.check("V9 R3 同轮双刷受理", orch3.player_request_refresh() and orch3.player_request_refresh()) + Kit.check("V9 R3 自然累计 2 层", _counter(orch3, "round_swap_rest") == 2) + orch3.minesweeper.settle_batch([{"action": "stop"}]) + var heal3 := 0 + for r3_v in rows3: + if String((r3_v as Dictionary).get("type", "")) == "heal" and String((r3_v as Dictionary).get("source_class", "")) == "fx_board_refresh": + heal3 += 1 + Kit.check("V9 R3 轮末 heal 行值 2(自然累计路径)〔换图系批 V9〕", + heal3 == 1 and _counter(orch3, "round_swap_rest") == 0) + + +# V8 蓄力周转:当轮攻击消费全部层数(上限 3 闸=白盒预置;战斗新建自然清空) +func test_charge_layers_consumed_same_round() -> void: + print("--- V8 蓄力周转:攻击消费层 ---") + var orch1 = _orch(46061) + var rows1 := _collect_damage_rows(orch1) + Kit.check("V8 前置:战斗受理", orch1.begin_battle(_enc(["charge_rotation"], 16))) + # 上限 3 闸:白盒预置 3 层后刷新被拒层(when<3) + EffectEngineScript.counter_add(orch1.fx_host().counters_ctx, "swap_charge_layers", 3) + Kit.check("V8 上限 3 闸(预置 3 刷新不加层)", orch1.player_request_refresh() and _counter(orch1, "swap_charge_layers") == 3) + # 当轮攻击消费:停手 → 主角先手攻击 → 层数全消费清零 + orch1.minesweeper.settle_batch([{"action": "stop"}]) + var attack_rows := 0 + for r_v in rows1: + if String((r_v as Dictionary).get("source_class", "")) == "hero_attack": + attack_rows += 1 + Kit.check("V8 当轮攻击消费全部层(攻击行在案 %d·层清 0)〔换图系批 V8〕" % attack_rows, + attack_rows >= 1 and _counter(orch1, "swap_charge_layers") == 0) + # 自然累计:双刷 2 层(voucher_rap 16 入池)→ 停手消费归零 + var orch2 = _orch(46062) + Kit.check("V8 前置:战斗受理", orch2.begin_battle(_enc(["charge_rotation"], 16))) + Kit.check("V8 战斗新建层 0(引擎重建自然清空)", _counter(orch2, "swap_charge_layers") == 0) + Kit.check("V8 双刷累计 2 层", orch2.player_request_refresh() and orch2.player_request_refresh() and _counter(orch2, "swap_charge_layers") == 2) + orch2.minesweeper.settle_batch([{"action": "stop"}]) + Kit.check("V8 攻击后层清 0(消费面闭合)〔换图系批 V8〕", _counter(orch2, "swap_charge_layers") == 0) + + +# V7 崩壁冲击:延迟群伤单(刷新时点零伤害行——战斗段末按存活目标结账) +func test_blast_defers_to_combat_segment() -> void: + print("--- V7 崩壁冲击:延迟群伤 ---") + var orch = _orch(46071) + var rows := _collect_damage_rows(orch) + Kit.check("V7 前置:战斗受理", orch.begin_battle(_enc(["wall_blast"], 8))) + Kit.check("V7 刷新受理(voucher_rap 入池)", orch.player_request_refresh()) + Kit.check("V7 刷新时点零伤害行(授予段不结账——案C 延迟)〔换图系批 V7〕", + _damage_rows_by_source(rows, "fx_board_refresh").is_empty()) + orch.minesweeper.settle_batch([{"action": "stop"}]) + var blast_rows := _damage_rows_by_source(rows, "fx_board_refresh") + Kit.check("V7 战斗段末群伤单恰一单(全体存活敌 ×2——行值 2 count≥1)〔换图系批 V7〕", + blast_rows.size() == 1 and int((blast_rows[0] as Dictionary).get("value", -1)) == 2 + and int((blast_rows[0] as Dictionary).get("count", 0)) >= 1)