Files
gplan-2/game/smoke/fx_probe.gd
T
DKChan 6578b284b1 feat(t5): 主角受击+死亡演出——拆两处早退,演出密度与敌方对等
①battle_stage_view.on_step_row_fx hero 早退拆除:受击态+弹道到达拍(0.33s)闪白+打击火花+击杀塌陷(killed 行)——与敌方路径同构同密度;树外守卫即时执行
②main._play_cross_zone_fx hero 早退拆除:敌→主角伤害行 ⚔ 危险红弹道飞向主角站桩;crit 只加重字号不加金(金=收益/胜利语义位,不用于敌方输出——代拍板待追认)
③standee_body flash_hit 增 _flash_count 探针计数;fx_probe 扩第六段=主角受击路由(闪白计数增量+火花)与击杀塌陷断言(BattleStageView 实体级路由测试)
轻闸=冒烟 576/0+三探针 PASS
2026-09-29 04:16:40 +08:00

139 lines
6.6 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
# 演出密度探针(样张 v2 冻结批 T3——演出四项可触发+探针断言覆盖):
# ①死亡碎裂(standee mark_dead → 碎块计数)②意图三档动画(damage/buff/debuff/charge 摇定不炸+徽标可见)
# ③胜利纹章+失败掸灰+旗光收回(battle_end open_victory/open_defeat → fx_probe 计数)
# ④结算轮光带(settlement_fx play_round_sweep → 子节点落位)。
# 运行:Godot_console.exe --headless --path . res://smoke/fx_probe.tscn
extends Node
func _ready() -> void:
await get_tree().process_frame
var problems: Array = []
# ---- ①② 站桩侧:死亡碎裂 + 意图三档 ----
var sb_script := load("res://presentation/views/standee_body.gd")
var sb = sb_script.new("enemy")
sb.box = Vector2(150, 176) # 站桩盒几何(Node2D 自定义字段——非 Control.size)
add_child(sb)
sb.set_data({"display_name": "地精", "enemy_id": "goblin", "hp": 10, "max_hp": 10})
await get_tree().process_frame
# 意图三档:特殊准备(damage/buff/debuff)与蓄力档(heavy)摇定——动画触发+徽标可见
for tier in [["damage", "slash"], ["buff", "atk_up"], ["debuff", "weaken_hit"], ["damage", "heavy"]]:
sb.set_pending(tier[0], tier[1])
if not sb.intent_probe().visible:
problems.append("意图徽标应可见(%s/%s)" % [tier[0], tier[1]])
await get_tree().process_frame
sb.set_pending("basic", "") # 档①普通攻击=零表示
if sb.intent_probe().visible:
problems.append("普通攻击徽标应零表示(§4.6.4 档①)")
# 死亡碎裂
sb.mark_dead()
await get_tree().process_frame
var dp: Dictionary = sb.fx_probe()
if int(dp.get("death_shards", 0)) != 5:
problems.append("死亡碎块应 5 块(§8.4 冻结值),实得 %s" % [dp.get("death_shards")])
if not sb.is_dead():
problems.append("mark_dead 后 is_dead 应真")
# ①b 命中火花(迭代 6 T4 终稿——打击瞬间特效计数)
var sb2 = sb_script.new("enemy")
sb2.box = Vector2(150, 176)
add_child(sb2)
sb2.play_hit_impact()
await get_tree().process_frame
var hp2: Dictionary = sb2.fx_probe()
if int(hp2.get("hit_sparks", 0)) != 6:
problems.append("命中火花应 6 枚(迭代 6 T4),实得 %s" % [hp2.get("hit_sparks")])
# ---- ③ 终局侧:胜利纹章 / 失败掸灰+旗光 ----
var end_ov := BattleEndOverlay.new()
add_child(end_ov)
var br := Rect2(300, 300, 400, 300)
end_ov.open_victory("胜利", "继续", br)
await get_tree().process_frame
var vp: Dictionary = end_ov.fx_probe()
if int(vp.get("crest_shards", 0)) != 10:
problems.append("胜利纹章碎砖应 10 枚(§2.4),实得 %s" % [vp.get("crest_shards")])
end_ov.close()
end_ov.open_defeat("失败", "重来", br)
await get_tree().process_frame
var fp: Dictionary = end_ov.fx_probe()
if int(fp.get("dust_motes", 0)) != 8:
problems.append("失败掸灰尘絮应 8 枚(§2.5),实得 %s" % [fp.get("dust_motes")])
if int(fp.get("flag_dots", 0)) != 8:
problems.append("旗光收回应 8 点,实得 %s" % [fp.get("flag_dots")])
end_ov.close()
# ---- ④ 结算轮光带(含前导亮缘) ----
var sfx := SettlementFxView.new()
add_child(sfx)
sfx.play_round_sweep(br) # 光带几何全由 board_rect 驱动——无需容器尺寸
await get_tree().process_frame
var n_fx := 0
for ch in sfx.get_children():
if ch is ColorRect:
n_fx += 1
if n_fx < 2:
problems.append("结算光带应含带体+前导缘线 ≥2 节点,实得 %d" % n_fx)
# ---- ⑤ 主角三态切换(迭代 6 T3——attack/hit 短驻自回 + 死亡复活态回 idle) ----
var hero_sb = sb_script.new("hero")
hero_sb.box = Vector2(150, 176)
add_child(hero_sb)
var t_idle: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_PORTRAIT)
var t_atk: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_ATTACK)
var t_hit: Texture2D = UiKit.load_tex(UiKit.TEX_HERO_HIT)
if t_idle == null or t_atk == null or t_hit == null:
problems.append("主角三态素材缺位(portrait/attack/hit——迭代 6 T3)")
else:
hero_sb.set_portrait_texture(t_idle, true)
hero_sb.register_hero_state("idle", t_idle)
hero_sb.register_hero_state("attack", t_atk)
hero_sb.register_hero_state("hit", t_hit)
hero_sb.set_hero_state("attack")
if String(hero_sb.hero_state_probe().get("state", "")) != "attack":
problems.append("主角攻击态切换失败(set_hero_state attack)")
await get_tree().create_timer(UiKit.motion(0.5)).timeout
if String(hero_sb.hero_state_probe().get("state", "")) != "idle":
problems.append("attack 态未短驻自回 idle(0.35s)")
hero_sb.set_hero_state("hit")
if String(hero_sb.hero_state_probe().get("state", "")) != "hit":
problems.append("主角受击态切换失败(set_hero_state hit)")
hero_sb.set_hero_state("idle")
hero_sb.mark_dead()
hero_sb.set_hero(6, 10) # 新战斗开场 HP 回正——死亡残位复活(敌侧 set_data 同款语义)
if hero_sb.is_dead() or String(hero_sb.hero_state_probe().get("state", "")) != "idle":
problems.append("主角死亡后 set_hero 复活失败(is_dead 应假+态回 idle)")
# ---- ⑥ 主角受击/死亡路由(迭代 6 T5——battle_stage 主角早退拆除的呈现链断言) ----
var db2 = load("res://core/config_db.gd").new()
if db2.load_tables("res://data/tables", false):
var stage2 = BattleStageView.new(db2)
add_child(stage2)
var flash_before: int = int(stage2.hero_standee.fx_probe().get("flash_count", 0))
stage2.on_step_row_fx({"type": "damage", "target": "hero", "killed": false})
await get_tree().create_timer(UiKit.motion(0.45)).timeout # >到达拍 0.33s
var hfx: Dictionary = stage2.hero_standee.fx_probe()
if int(hfx.get("flash_count", 0)) <= flash_before:
problems.append("主角受击闪白未触发(on_step_row_fx hero 路由——迭代 6 T5)")
if int(hfx.get("hit_sparks", 0)) < 6:
problems.append("主角受击火花未触发(演出对等——迭代 6 T5)")
stage2.on_step_row_fx({"type": "damage", "target": "hero", "killed": true})
await get_tree().create_timer(UiKit.motion(0.45)).timeout
if not stage2.hero_standee.is_dead():
problems.append("主角击杀塌陷未触发(killed 行——迭代 6 T5)")
stage2.queue_free()
else:
problems.append("fx_probe⑥ 表加载失败(config_db)")
# ---- 汇总 ----
if problems.is_empty():
print("[fx_probe] PASS — 死亡碎裂5块/命中火花6/意图三档+零表示/胜利纹章10/失败掸灰8+旗光8/结算光带+缘线/主角三态切换 全链路通过")
get_tree().quit(0)
else:
for p in problems:
print("[fx_probe] FAIL — " + p)
print("[fx_probe] FAIL — %d 项断言未过" % problems.size())
get_tree().quit(1)