Files
gplan-2/game/tests/unit/presentation/standee_fx_sweep_test.gd
T
DKChan 0529e1691c fix(iter37①): T2 呈现/组合根缺陷修——蓄力档分档·庆祝句柄·互斥四口·模态 fail-fast·站桩按 id 解析
- d2 蓄力档 `_intent_charge` 布尔分档(颜色相等判据退役,深浅金同值不再误判);d4 庆祝浮动 tween 句柄 kill 纪律(close 必杀)
- d5/d6/d7/d8 互斥四口:调试钮判据并入 `_sync_sim_button` 单点、组合根两守卫补模拟态/冒险态、`is_running()` 运行态查询面
- d9 ModalStack open/close fail-fast+遮挡账合并非覆盖(重复 open 不再吞旧账);d12 target_id 产发(damage_controller 四处)+站桩两口 id 先名兜底;d17 await 后实例守卫;d18 参数类型注解
- 断言:standee_fx_sweep_test(新增,18 项)/modal_flow_test V6-5(5 项 wd9,V6-1~4 原样同绿)/damage_controller_test target_id/pacing_queue_test 排播 target_id/system_test wd6+wd7
- 代拍板:d18 卡片字面 `lumas_out: Array[Variant] = null` 在 Godot 4.7 解析报错(typed array 不可默认 null),改用 `Variant = null`——唯一合法注法,零行为变化,随代拍板追认单留痕
2026-10-25 03:49:05 +08:00

134 lines
6.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 = []
var calls := 0
func run_level(_db, _seed: int, _level: int, _atk: int, _hp: int, _other: bool, _logger):
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._celebrate_tw
Kit.check("首开胜利画面创建庆祝句柄〔wd4〕", first != null and first.is_valid())
ov.open_victory("判定行", "继续", Rect2(0, 0, 100, 100))
Kit.check("重开胜利画面:旧句柄已 kill、新句柄在位(至多一个活跃)〔wd4〕",
first != null and not first.is_valid() and ov._celebrate_tw != null and ov._celebrate_tw.is_valid())
ov.close()
Kit.check("close 必杀庆祝句柄(句柄清空)〔wd4〕", ov._celebrate_tw == 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"))