extends SceneTree ## Bear attack-FX test. The four attack effects (crater, smash debris, left/right claw slash) ## live in Assets/fx/Bear_FX.glb as meshes that rest at scale 0 at Siim's baked positions. The ## GLB carries its own AnimationPlayer whose clips scale one mesh up at its strike frame and ## back to 0 (self-hiding) while pinning the others at 0. bear.gd fires the matching clip on ## each strike (see _play_fx). This asserts each clip pops its own mesh, self-hides by the end, ## and never leaves another effect on-screen — so a mis-wired trigger or a broken clip is caught ## headlessly. Run: godot --headless --script res://tests/bear_fx_test.gd var _pass := 0 var _fail := 0 var _fx_player: AnimationPlayer = null var _fx: Dictionary = {} # short name -> Node3D func _init() -> void: _run.call_deferred() func _ok(c: bool, m: String) -> void: if c: _pass += 1 print(" PASS: ", m) else: _fail += 1 print(" FAIL: ", m) func _run() -> void: var bear := (load("res://Bear.tscn") as PackedScene).instantiate() root.add_child(bear) await physics_frame var fx_root := bear.find_child("Bear_FX", true, false) _ok(fx_root != null, "Bear_FX instanced in the scene") if fx_root == null: _finish() return _fx_player = fx_root.get_node_or_null("AnimationPlayer") as AnimationPlayer _ok(_fx_player != null, "found the Bear_FX AnimationPlayer") if _fx_player == null: _finish() return for n: String in ["jump_smash", "smash_hit", "claw_attack_R", "claw_attack_L"]: _fx[n] = bear.find_child("Bear_fx_" + n, true, false) _ok(_fx[n] != null, "found Bear_fx_" + n) if _fx.values().has(null): _finish() return _ok(not _vis("jump_smash") and not _vis("smash_hit") and not _vis("claw_attack_R") and not _vis("claw_attack_L"), "all FX hidden at rest (scale 0)") # Each clip pops only its own mesh at the strike, then self-hides, leaving nothing stale. await _check_clip("Bear_fx_claw_attack_L", "claw_attack_L") await _check_clip("Bear_fx_claw_attack_R", "claw_attack_R") await _check_clip("Bear_fx_smash_hit", "smash_hit") await _check_clip("Bear_fx_jump_smash", "jump_smash") _finish() # Play a clip to completion in real process mode (so bear.gd's animation_finished hide runs): # the clip's own mesh scales up somewhere in the middle, every other mesh stays hidden, and the # mesh is back to scale 0 once the clip has finished. func _check_clip(clip: String, owner_key: String) -> void: _fx_player.play(clip) _fx_player.seek(0.0, true) var peak := 0.0 var others_clean := true var frames := 0 while _fx_player.is_playing() and frames < 600: await process_frame frames += 1 peak = maxf(peak, (_fx[owner_key] as Node3D).scale.length()) for k: String in _fx.keys(): if k != owner_key and _vis(k): others_clean = false # Hide happens on animation_finished (immediately for most, after a linger for the crater), # so poll a bounded window rather than assume it's gone on the very next frame. var settle := 0 while _vis(owner_key) and settle < 600: await process_frame settle += 1 _ok(peak > 0.05, clip + ": its mesh pops during the clip") _ok(others_clean, clip + ": no other effect shows while it plays") _ok(not _vis(owner_key), clip + ": hidden again after the clip ends") func _vis(n: String) -> bool: return (_fx[n] as Node3D).scale.length() > 0.05 func _finish() -> void: print("Results: %d passed, %d failed" % [_pass, _fail]) quit(1 if _fail > 0 else 0)