Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
5.1 KiB
GDScript
88 lines
5.1 KiB
GDScript
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("g06")
|
|
var b: Node3D = AssetLibrary.instantiate_fighter("g06")
|
|
add_child(a)
|
|
add_child(b)
|
|
check(a.skeleton.get_bone_count() == 20, "20 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 weapon: int = a.skeleton.find_bone("Weapon")
|
|
var draw: int = a.skeleton.find_bone("Draw")
|
|
var bolt: int = a.skeleton.find_bone("Bolt")
|
|
var quarrel: int = a.skeleton.find_bone("Quarrel")
|
|
var gear: int = a.skeleton.find_bone("Gear")
|
|
var tip: int = a.skeleton.find_bone("LimbTip.L")
|
|
var thigh: int = a.skeleton.find_bone("Thigh.L")
|
|
var foot: int = a.skeleton.find_bone("Foot.L")
|
|
check(weapon >= 0 and draw >= 0 and bolt >= 0 and quarrel >= 0 and gear >= 0 and tip >= 0, "ballista rig joints present")
|
|
check(thigh >= 0 and a.skeleton.find_bone("Tarsus.L") >= 0 and foot >= 0, "digitigrade legs with feet")
|
|
check(a.skeleton.find_bone("Head") < 0 and a.skeleton.find_bone("UpperArm.L") < 0, "headless walker without arms")
|
|
check(a.skeleton.find_bone("Spine") >= 0, "turret joint receives the hit overlay")
|
|
check(a.attack_cycle == ["attack_a", "attack_b", "attack_a", "ability"], "four-hit ballista cycle")
|
|
var rest_tip: Quaternion = b.skeleton.get_bone_pose_rotation(tip)
|
|
var rest_gear: Quaternion = b.skeleton.get_bone_pose_rotation(gear)
|
|
var rest_quarrel: Quaternion = b.skeleton.get_bone_pose_rotation(quarrel)
|
|
var rest_draw: Vector3 = b.skeleton.get_bone_pose_position(draw)
|
|
var rest_bolt: Vector3 = b.skeleton.get_bone_pose_position(bolt)
|
|
# Touchdown (foot ahead) and lift-off (foot behind) swing the thigh through the stride.
|
|
a.preview("walk", 0)
|
|
var reach: Quaternion = a.skeleton.get_bone_pose_rotation(thigh)
|
|
a.preview("walk", .70)
|
|
check(a.skeleton.get_bone_pose_rotation(thigh).angle_to(reach) > .1, "walk strides legs")
|
|
# Left foot is planted from 0.10 s to 0.30 s: it slides back exactly with the 0.52 m stride.
|
|
a.preview("walk", .10)
|
|
var planted: Vector3 = a.skeleton.get_bone_global_pose(foot).origin
|
|
a.preview("walk", .30)
|
|
var later: Vector3 = a.skeleton.get_bone_global_pose(foot).origin
|
|
check(absf((planted.z - later.z) - .52 / 1.2 * .2) < .003, "planted foot travels with the stride")
|
|
check(absf(planted.y - later.y) < .002 and absf(planted.x - later.x) < .002, "planted foot keeps ground contact")
|
|
a.preview("walk", .90)
|
|
check(a.skeleton.get_bone_global_pose(foot).origin.y > planted.y + .05, "swing foot lifts off")
|
|
a.preview("attack_a", .90)
|
|
check(a.skeleton.get_bone_pose_position(draw).distance_to(rest_draw) < .01, "string still cocked at contact")
|
|
a.preview("attack_a", .95)
|
|
check(a.skeleton.get_bone_pose_position(draw).distance_to(rest_draw) > .2, "string snaps forward on release")
|
|
check(a.skeleton.get_bone_pose_rotation(tip).angle_to(rest_tip) > .1, "crystal limbs spring forward")
|
|
a.preview("attack_a", .97)
|
|
check(a.skeleton.get_bone_pose_scale(bolt).x < .05, "bolt leaves the rail after contact")
|
|
a.preview("attack_a", 1.10)
|
|
check(a.skeleton.get_bone_pose_rotation(gear).angle_to(rest_gear) > .3, "crank winds during reload")
|
|
a.preview("attack_a", 1.30)
|
|
check(a.skeleton.get_bone_pose_scale(bolt).x > .99 and a.skeleton.get_bone_pose_position(bolt).distance_to(rest_bolt) < .005, "reloaded for the next shot")
|
|
a.preview("ability", .80)
|
|
check(a.skeleton.get_bone_pose_rotation(quarrel).angle_to(rest_quarrel) > .1, "charged shot spins quarrel")
|
|
check(a.skeleton.get_bone_pose_scale(quarrel).x > 1.05, "charged quarrel swells")
|
|
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 ballista 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 armor-piercing 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/phase19-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)
|