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("g05") var b: Node3D = AssetLibrary.instantiate_fighter("g05") add_child(a) add_child(b) check(a.skeleton.get_bone_count() == 26, "26 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 arm: int = a.skeleton.find_bone("UpperArm.R") var weapon: int = a.skeleton.find_bone("Weapon") var crystal: int = a.skeleton.find_bone("Crystal") var bolt: int = a.skeleton.find_bone("Bolt") var thigh: int = a.skeleton.find_bone("Thigh.L") check(arm >= 0 and weapon >= 0 and crystal >= 0 and bolt >= 0, "lance rig joints present") check(thigh >= 0, "has legs") check(a.attack_cycle == ["attack_a", "attack_b", "attack_a", "ability"], "four-hit lance cycle") var rest_thigh: Quaternion = b.skeleton.get_bone_pose_rotation(thigh) var rest_weapon: Quaternion = b.skeleton.get_bone_pose_rotation(weapon) var rest_crystal: Quaternion = b.skeleton.get_bone_pose_rotation(crystal) a.preview("walk", .40) check(a.skeleton.get_bone_pose_rotation(thigh).angle_to(rest_thigh) > .1, "walk strides legs") a.preview("attack_a", .90) check(a.skeleton.get_bone_pose_rotation(weapon).angle_to(rest_weapon) > .02, "free shot aims lance") check(a.skeleton.get_bone_pose_rotation(arm).angle_to(b.skeleton.get_bone_pose_rotation(arm)) > .01, "shot settles arm on lance") a.preview("ability", .90) check(a.skeleton.get_bone_pose_rotation(crystal).angle_to(rest_crystal) > .2, "charged shot spins crystal") a.preview("attack_a", .98) check(a.skeleton.get_bone_pose_position(bolt).distance_to(b.skeleton.get_bone_pose_position(bolt)) > .01, "breech slides after contact") b.preview("idle", 0) a.preview("idle", 0) check(a.skeleton.get_bone_pose_rotation(weapon).is_equal_approx(b.skeleton.get_bone_pose_rotation(weapon)), "idle restores lance hold") a.update_combat(1.0 / 60, 0, true, .6, 0, 0, 0, true) check(a.current_clip == "attack_a", "combat free shot") a.update_combat(1.0 / 60, 0, true, 0, 1, 0, 0, true) check(absf(a.player.current_animation_position - .9) < .001, "contact frame at 0.90 s") a.update_combat(1.0 / 60, 0, true, .65, 1, 0, 0, true) check(a.current_clip == "attack_b", "alternating braced shot") a.update_combat(1.0 / 60, 0, true, .65, 2, 0, 0, true) check(a.current_clip == "attack_a", "third shot free again") a.update_combat(1.0 / 60, 0, true, .65, 3, 0, 0, true) check(a.current_clip == "ability", "fourth hit is the charged lance shot") a.update_combat(1.0 / 60, 0, false, 0, 1, 0, 0, false) check(a.dead and a.current_clip == "death", "death clip") a.free() b.free() var f := FileAccess.open("res://docs/phase18-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)