saumwacht-godot/tools/verify_funken.gd
Martin Stickelbroeck c2b447610c Baseline: Saumwacht 0.15.0 (Phase 20A) vor dem Umbau zu Champions of Luneraria
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>
2026-09-27 20:00:36 +02:00

62 lines
3.3 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("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)