saumwacht-godot/tools/verify_ringborke.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

60 lines
3.2 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("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)