①standee_body.play_hit_impact:立绘盒中心六枚短火花放射(程序化色块,确定性方向 i*60°,亮金GOLD_LIT/暖白WARM 交替=物理打击语义不占 危险红=雷/奖励金=收益 位;0.12s 一拍即回收);fx_probe 增 hit_sparks 计数+第六断言段 ②弹道到达拍对齐:on_step_row_fx 敌方路径 闪白/火花/击杀塌陷 延迟 0.33s≈弹道飞行(0.16+0.19)——呈现链序=弹道→闪白+火花→血条→灰化;突发多定时器互不干扰(flash 重启/死亡幂等),卡片释放 is_instance_valid 守卫,树外即时执行 ③flash_hit 强度时长复核=维持现行(1.6,1.4,1.2)/0.08s+回0.2s——白光可读不糊,±30% 白名单内零调整;④伤害飘字子项代拍板=不做(血条已有「当前/最大」即时数字面,飘字=同一伤害第二呈现触 UI 禁重复展示铁律;审计备注项未立项)
118 lines
5.4 KiB
GDScript
118 lines
5.4 KiB
GDScript
# 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)")
|
||
|
||
# ---- 汇总 ----
|
||
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)
|