# VERTICAL SLICE - NOT FOR PRODUCTION # 呈现域缺陷扫掠断言(行为缺陷定点修批卡① T2 五族): # ① 意图蓄力档可达(d2——_intent_charge 分档,旧 GOLD_DEEP 判据恒不可达); # ② 庆祝 tween 句柄纪律(d4——重开不叠加、close 必杀); # ③ 互斥四口(d5 调试钮同判据 / d8 控制器运行态可查); # ⑤ 站桩按敌 id 解析(d12 两口:on_step_row_fx 闪白落点 + enemy_standee_center 弹道落点)。 # d6/d7 两守卫断言宿主 = system_test;d3/d17/d18 属非行为面(实现报告等价申报)。 # 运行:godot --headless --path game --script res://tests/unit/presentation/standee_fx_sweep_test.gd(expect 0) extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const StandeeBodyScript := preload("res://presentation/views/standee_body.gd") const BattleEndScript := preload("res://presentation/views/battle_end_overlay.gd") const DebugOverlayScript := preload("res://presentation/views/debug_overlay.gd") const TuneSimScript := preload("res://presentation/tune_sim_controller.gd") const StageScript := preload("res://presentation/views/battle_stage_view.gd") # 假调试面板(TuneSimController 所需 setter 面 + 树内节点供给 get_tree) class FakePanel extends Node: var running := false var progress := "" var report := "" var atk := 0 var hp := 0 func atk_pct() -> int: return atk func hp_pct() -> int: return hp func set_sim_running(r: bool) -> void: running = r func set_sim_progress(t: String) -> void: progress = t func set_sim_report(t: String) -> void: report = t # 假自动策略(run_level 签名同 level_autoplay——回调内读控制器运行态) class FakeDriver extends RefCounted: var ctrl var seen: Array[bool] = [] var calls := 0 func run_level(_db: Variant, _seed: int, _level: int, _atk: int, _hp: int, _other: bool, _logger: Variant) -> Dictionary: calls += 1 seen.append(ctrl.is_running()) if calls == 1: return {"hero_dmg": 12.0, "rounds": 4, "result": "victory", "hp_left": 3.0, "hp_at_boss": 5.0} return {"hero_dmg": 6.0, "rounds": 2, "result": "defeat", "defeat_node_index": 3, "defeat_node_type": "battle", "hp_left": 0.0, "hp_at_boss": 0.0} func _init() -> void: UiKit.motion_scale = 1.0 # ---- ⑤ 站桩按 id 解析(两口共用同名字敌载体)---- var stage = StageScript.new(null) root.add_child(stage) var s1 = StandeeBodyScript.new("enemy") var s2 = StandeeBodyScript.new("enemy") stage.world.add_child(s1) stage.world.add_child(s2) s1.set_data({"display_name": "地精", "enemy_id": "goblin_a", "hp": 3, "max_hp": 3, "order": 1}) s2.set_data({"display_name": "地精", "enemy_id": "goblin_b", "hp": 3, "max_hp": 3, "order": 2}) s1.position = Vector2(10, 20) s2.position = Vector2(200, 20) stage._cards.clear() stage._cards.append(s1) stage._cards.append(s2) Kit.check("站桩 enemy_id 访问器读装载值〔wd12〕", s1.enemy_id() == "goblin_a" and s2.enemy_id() == "goblin_b") Kit.check("同名敌按 id 精确解析(不做名子串撞车)〔wd12〕", stage._card_by_id("goblin_b") == s2 and stage._card_by_id("goblin_a") == s1) Kit.check("空 id / 未登记 id 恒不命中(名兜底归调用点)〔wd12〕", stage._card_by_id("") == null and stage._card_by_id("no_such") == null) # 口一:on_step_row_fx —— 伤害行携 target_id,闪白落点须在 id 指向的站桩 stage.on_step_row_fx({"type": "damage", "target": "地精", "target_id": "goblin_b"}) await create_timer(0.6).timeout # 弹道到达拍 0.33s + 余量 Kit.check("伤害行按 target_id 命中正确站桩(闪白落点)〔wd12〕", s2._flash_count == 1 and s1._flash_count == 0) # 口二:enemy_standee_center —— id 先、display_name 兜底 var c_b = stage.enemy_standee_center("goblin_b", "地精") var c_b_expect: Vector2 = stage.world_to_shell(s2.position + s2.box * 0.5) Kit.check("弹道落点按 target_id 取站桩中心〔wd12〕", c_b != null and c_b == c_b_expect) var c_fallback = stage.enemy_standee_center("", "地精") var c_fallback_expect: Vector2 = stage.world_to_shell(s1.position + s1.box * 0.5) Kit.check("无 target_id 时按名兜底同旧行为(首命中)〔wd12〕", c_fallback != null and c_fallback == c_fallback_expect) # ---- ① 意图蓄力档可达(d2)---- s1.set_pending("damage", "heavy") Kit.check("蓄力技能(heavy)置 _intent_charge=true〔wd2〕", s1._intent_charge) s1.set_pending("damage", "goblin_slash") Kit.check("非蓄力攻击技(goblin_slash)零置位〔wd2〕", not s1._intent_charge) s1.set_pending("buff", "goblin_rage") Kit.check("增益系零置位〔wd2〕", not s1._intent_charge) s1.set_pending("damage", "basic") Kit.check("普攻零表示路径复位 _intent_charge〔wd2〕", not s1._intent_charge) # ---- ② 庆祝 tween 句柄纪律(d4)---- var ov = BattleEndScript.new() root.add_child(ov) ov.open_victory("判定行", "继续", Rect2(0, 0, 100, 100)) var first: Tween = ov.get_meta(&"celebrate_tw", null) Kit.check("首开胜利画面创建庆祝句柄〔wd4〕", first != null and first.is_valid()) ov.open_victory("判定行", "继续", Rect2(0, 0, 100, 100)) var second: Tween = ov.get_meta(&"celebrate_tw", null) Kit.check("重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕", first != null and not first.is_valid() and second != null and second.is_valid()) ov.close() Kit.check("close 必杀庆祝句柄(句柄清空)〔wd4〕", ov.get_meta(&"celebrate_tw", null) == null) # ---- ③ 互斥四口(d5/d8)---- var dbg = DebugOverlayScript.new(null, 0, 0) dbg.set_sim_running(true) Kit.check("模拟运行期调试入口钮同置灰〔wd5〕", dbg._dbg_btn.disabled) dbg.set_sim_running(false) Kit.check("模拟收尾调试入口钮恢复(未锁定)〔wd5〕", not dbg._dbg_btn.disabled) dbg.free() var panel := FakePanel.new() root.add_child(panel) var drv := FakeDriver.new() var ctrl = TuneSimScript.new({"action_economy": {"ap_per_round": 2}}, panel, 100, drv) drv.ctrl = ctrl await ctrl.run(2) # 假 driver 回调内读 is_running()(两局各一读) Kit.check("控制器运行中 is_running()=true(驱动回调内读数)〔wd8〕", drv.seen == [true, true]) Kit.check("控制器收尾 is_running()=false〔wd8〕", not ctrl.is_running() and not panel.running) Kit.check("两局报告全文注入面板(run 收尾链路完整)〔wd8〕", String(panel.report) != "") quit(Kit.finish("standee_fx_sweep"))