extends Node var checks: int = 0 var failures: Array[String] = [] func check(ok: bool, label: String) -> void: checks += 1 if not ok: failures.append(label) printerr("RIG_FAIL: ",label) func _ready() -> void: call_deferred("run") func run() -> void: var a: Node3D = AssetLibrary.instantiate_fighter("w04") var b: Node3D = AssetLibrary.instantiate_fighter("w04") add_child(a) add_child(b) check(a.skeleton.get_bone_count()==27,"27 imported bones") check(a.player!=b.player and a.skeleton!=b.skeleton,"independent skeleton and playback") for clip: String in ["idle","start","walk","stop","attack_a","attack_b","hit","ability","death"]: check(a.clips.has(clip),"imported clip "+clip) check(a.clip_length(clip)>.3,"clip duration "+clip) var left: int = a.skeleton.find_bone("Thigh.L") var other: Quaternion = b.skeleton.get_bone_pose_rotation(left) a.preview("walk",.35) check(a.skeleton.get_bone_pose_rotation(left).angle_to(other)>.05,"walking deforms weighted leg") check(b.skeleton.get_bone_pose_rotation(left).is_equal_approx(other),"second instance keeps pose") a.preview("attack_a",.9) var finger: int = a.skeleton.find_bone("Finger0.L") a.preview("idle",0) check(a.skeleton.get_bone_pose_rotation(finger).is_equal_approx(b.skeleton.get_bone_pose_rotation(finger)),"idle restores fingers after attack") a.update_combat(1.0/60,0,true,.6,0,0,0,true) check(a.current_clip=="attack_a" and absf(a.player.current_animation_position-.54)<.001,"first attack prepares before damage") a.update_combat(1.0/60,0,true,0,1,0,0,true) check(absf(a.player.current_animation_position-.9)<.001,"primary damage frame matches authored contact") a.update_combat(1.0/60,0,true,.15,1,0,0,true) check(a.player.current_animation_position>.9,"attack follow-through after damage") a.update_combat(1.0/60,0,true,.65,1,0,0,true) check(a.current_clip=="attack_b","alternating right attack") var clock: float = a.player.current_animation_position a.update_combat(0,0,true,.65,1,0,0,true) check(is_equal_approx(a.player.current_animation_position,clock),"zero battle delta does not advance playback") a.update_combat(1.0/60,0,true,.66,1,.5,.18,true) check(a.current_clip=="attack_b","reactions preserve attack contact clock") a.update_combat(1.0/60,0,false,0,1,0,0,false) check(a.dead and a.current_clip=="death","death uses imported clip") for i in range(180):a.update_combat(1.0/60,0,false,0,1,0,0,false) check(absf(a.player.current_animation_position-a.clip_length("death"))<.001,"death holds final pose without looping") var unit: Node3D = preload("res://scripts/combat/unit.gd").new() add_child(unit) unit.setup(DataCatalog.get_unit("w04"),1,0,false,Vector2i.ZERO,0,true) unit.receive_damage(100000,0) unit.refresh_visuals(.1) check(not unit.alive and unit.visible and unit._model.dead,"dead unit remains visible for its death animation") check(not unit._bars.visible and not unit._selection.visible,"dead health and selection are hidden") unit.free() a.free() b.free() var f := FileAccess.open("res://docs/phase11-rig-verification.json",FileAccess.WRITE) f.store_string(JSON.stringify({"checks":checks,"failures":failures},"\t"));f.close() print("RIG_PASS: ",checks," checks; ",failures.size()," failures") await Soundscape.quit_game(0 if failures.is_empty() else 1)