Files
gplan-2/game/tests/unit/presentation/standee_fx_sweep_test.gd
T
DKChan 4b174877be 补提交(布局几何单点化批·代码面+工具面+architecture行·跨会话遗留): battle_layout.tres 新表单行五字段(stage_h/info_panel_w/ap_bar_h/log_band_h_dig/log_band_h_settle)经 schema battle_layout_table.gd+域容器 config_domain_presentation.gd 收进 ConfigDb.presentation(ConfigDB 三容器→四容器,manifest 增 battle_layout 键,B区高度裁决 2026-10-30 用户裁=取代码标准 stage_h);views 五件构造签名增 layout 参(BattleStageView/HeroInfoPanelView/BoardZoneView/ApBarView/BattleLogView 常量高度改表值驱动)+ui_assembler 装配传 layout+probes 两件改址+main.gd 结算带注释去数值;tools/layout_config.py 新件+render_sample_sheet 两版读布局表现算(样张工具面);文档面: architecture.md ConfigDB 行换代(四容器成员 7/7/5/1、类变量 10)+HUD框架.md 三区表/装板不等式/调参表去数值化改指布局表+数据配置.md+interaction-patterns.md 明细带句去数值(battle.md 区表与待办清单登记面已随 1cdef6ba 在库);消费漏网 L1 就地补改九处(构造签名换型与 iter45 断点件收编的测试改址漏网,判据零变化): theme_projection_test:386/player_copy_sweep_test:44,98/adventure_feats_test:772/standee_fx_sweep_test:75/feat_vouchers_test:292 六处补 layout 实参,adventure_feats_test:791/level_shop_economy_test:1480/feat_vouchers_test:81 三处断点成员直读改经 a._bp_store(iter45卡②六成员搬移漏网,02 闸合回后首全量暴露);load_tables 超函数长键清偿(容器装配块抽 _assign_loaded 私有协函数——表行按域收进四容器+resolver 注入单一职责,与 _cross_validate 委托先例同型,S13 新增红修至基线口径内);窄验: 四件修复套件单跑全绿+gdstyle 基线外新违规 0
2026-10-31 05:05:08 +08:00

150 lines
6.7 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[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}
# 假配置库(TuneSimController 只读 db.run.action_economy.ap_per_round——最小域对象图)
class FakeActionEconomy:
var ap_per_round := 2
class FakeRun:
var action_economy := FakeActionEconomy.new()
class FakeDB:
var run := FakeRun.new()
func _init() -> void:
UiKit.motion_scale = 1.0
# ---- ⑤ 站桩按 id 解析(两口共用同名字敌载体)----
var stage = StageScript.new(null, load("res://data/tables/battle_layout.tres"))
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.fx_probe().flash_count == 1 and s1.fx_probe().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)----
# 蓄力判据表:[kind, skill_id, 期望 _intent_charge, 个案标签]
var charge_cases := [
["damage", "heavy", true, "蓄力技能(heavy)置 _intent_charge=true"],
["damage", "goblin_slash", false, "非蓄力攻击技(goblin_slash)零置位"],
["buff", "goblin_rage", false, "增益系零置位"],
["damage", "basic", false, "普攻零表示路径复位 _intent_charge"],
]
for c in charge_cases:
s1.set_pending(c[0], c[1])
Kit.check("%s〔wd2〕" % c[3], s1._intent_charge == c[2])
# ---- ② 庆祝 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(FakeDB.new(), 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"))