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`——唯一合法注法,零行为变化,随代拍板追认单留痕
This commit is contained in:
1 parent
1b9736f065
commit
0529e1691c
19 files changed
+348
-21
No files matched your search
@@ -87,6 +87,7 @@ func _init() -> void:
|
||||
# V6-4 演播后排窗挂起+边沿派发(T17 语义)
|
||||
var end_ov := Control.new()
|
||||
end_ov.visible = false
|
||||
flow.register(end_ov) # d9 fail-fast 后:未登记层不可进入编排(夹具按 register 先行契约登记)
|
||||
rows["empty"] = false
|
||||
var sync_before1: int = calls["open_sync"]
|
||||
flow.open_after_playback(end_ov)
|
||||
@@ -105,9 +106,34 @@ func _init() -> void:
|
||||
flow.open_after_playback(end_ov)
|
||||
rows["empty"] = true
|
||||
var other := Control.new()
|
||||
flow.register(other)
|
||||
var sync_before: int = calls["open_sync"]
|
||||
flow.open(other)
|
||||
Kit.check_quiet(end_ov.visible and flow._deferred == null and calls["open_sync"] == sync_before + 2,
|
||||
"V6 挂起兜底:开新层先结挂起(T17 防永不兑现——挂起层先开+新层后开两拍)〔迭代27 T4〕")
|
||||
|
||||
# V6-5 遮挡失败快+遮挡账合而非覆盖(d9)
|
||||
var ms = ModalStackScript.new()
|
||||
var ma := Control.new()
|
||||
var mb := Control.new()
|
||||
var stranger := Control.new()
|
||||
stranger.visible = false # Control 默认 visible=true——先隐,才能断言 fail-fast 未把它照上屏
|
||||
ms.register(ma)
|
||||
ms.register(mb)
|
||||
ms.open(ma)
|
||||
ms.open(stranger) # 未登记层:push_error + 早退(零副作用)
|
||||
Kit.check_quiet(not stranger.visible and ma.visible,
|
||||
"V6 未登记层 open fail-fast(该层不可见、已开层不变)〔wd9〕")
|
||||
ms.close(stranger)
|
||||
Kit.check_quiet(not stranger.visible and ma.visible,
|
||||
"V6 未登记层 close fail-fast(已开层不受影响)〔wd9〕")
|
||||
ms.open(mb)
|
||||
ms.open(ma) # 开低层隐其上方可见层
|
||||
Kit.check_quiet(not mb.visible, "V6 开低层隐其上方层(重复 open 前态)〔wd9〕")
|
||||
ms.open(ma) # 同层重复 open:上方已隐,hidden 空表——旧账须保留
|
||||
Kit.check_quiet(not mb.visible, "V6 重复 open 保持遮挡(V6 在役语义不变)〔wd9〕")
|
||||
ms.close(ma)
|
||||
Kit.check_quiet(mb.visible, "V6 重复 open 不吞遮挡账(close 低层恢复 mb——合并非覆盖)〔wd9〕")
|
||||
ms.close(mb)
|
||||
|
||||
quit(Kit.finish("modal_flow"))
|
||||
@@ -7,9 +7,35 @@
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/pacing_queue_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
const RowPacerScript := preload("res://presentation/row_pacer.gd")
|
||||
|
||||
var _problems: Array = []
|
||||
|
||||
|
||||
# 记参假站桩(d12 排播半):继承真类以过类型面,只记解析入参
|
||||
class FakeStage extends BattleStageView:
|
||||
var calls: Array = []
|
||||
|
||||
func enemy_standee_center(target_id: String, display_name: String) -> Variant:
|
||||
calls.append([target_id, display_name])
|
||||
return Vector2(10, 10)
|
||||
|
||||
|
||||
# 记参假演出层(同上——play_* 全部只记不演)
|
||||
class FakeFx extends SettlementFxView:
|
||||
var calls: Array = []
|
||||
|
||||
func play_projectile(_from: Vector2, _to: Vector2, _glyph: String, _col: Color, _crit: bool = false) -> void:
|
||||
calls.append(["projectile", _to])
|
||||
|
||||
func play_damage_number(_pos: Vector2, _text: String, _col: Color, _crit: bool = false) -> void:
|
||||
calls.append(["mark", _pos])
|
||||
|
||||
func play_status_pulse(_pos: Vector2, _col: Color) -> void:
|
||||
calls.append(["pulse", _pos])
|
||||
|
||||
|
||||
|
||||
func check(cond: bool, msg: String) -> void:
|
||||
if not cond:
|
||||
_problems.append(msg)
|
||||
@@ -98,6 +124,21 @@ func _init() -> void:
|
||||
UiKit.motion_scale = 0.5
|
||||
check(main.pace_delay_for("damage", "heal") == 0.0, "减动效段间延迟归零(行不跳过——同帧直落快路)")
|
||||
UiKit.motion_scale = 1.0
|
||||
# ---- 断言④:排播两处取用按行 target_id 解析站桩(d12 排播半——假 stage 记参)----
|
||||
var rp = RowPacerScript.new()
|
||||
var fstage := FakeStage.new(null)
|
||||
var ffx := FakeFx.new()
|
||||
rp.stage = fstage
|
||||
rp.board_view = main.board_view
|
||||
rp.settlement_fx = ffx
|
||||
rp._play_settlement_mark({"type": "damage", "target": "哥布林", "target_id": "goblin_b", "value": 3})
|
||||
check(fstage.calls.size() == 1 and fstage.calls[0] == ["goblin_b", "哥布林"],
|
||||
"结算标出按行 target_id 解析站桩(id 与名同传——d12)")
|
||||
rp._play_cross_zone_fx({"type": "damage", "target": "哥布林", "target_id": "goblin_b", "value": 3})
|
||||
check(fstage.calls.size() == 2 and fstage.calls[1] == ["goblin_b", "哥布林"],
|
||||
"跨区弹道按行 target_id 解析站桩(id 与名同传——d12)")
|
||||
check(ffx.calls.size() == 2, "两路解析成功后演出层各接一拍(记参面非空)")
|
||||
rp.free()
|
||||
_finish()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 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"))
|
||||
@@ -0,0 +1 @@
|
||||
uid://b3vqjio51r2nf
|
||||
Reference in new issue
Block a user