extends Node3D ## Spike T-1089 Synty intake — Godot QA scene (THROWAWAY, lives in ## client/spike_synty_qa/ only for the res:// path; source of truth is ## spikes/synty-intake/scripts/qa_scene.gd). ## ## Builds a CharacterVisual (average_m + peasant pants/shoes), reparents the ## transplanted Synty torso garment onto its skeleton (mirrors ## character_visual.gd:517-535), plays Walk, saves screenshots to ## spikes/synty-intake/out/qa/, then quits. const GARMENT_GLB := "res://spike_synty_qa/SK_SCFI_CIVL_09_10TORS_HU01_quaternius.glb" const SHOT_DIR := "/var/mnt/data/projects/settled-reach/spikes/synty-intake/out/qa/" var _cv: CharacterVisual = null var _cam: Camera3D = null var _garment_meshes: Array[MeshInstance3D] = [] func _ready() -> void: _setup_stage() _cv = CharacterVisual.new() add_child(_cv) var desc := CharacterVisualDescriptor.new() desc.body_type = CharacterVisualDescriptor.BodyType.AVERAGE_M desc.head_id = "head_001" desc.hair_id = "buzzed" desc.eyebrow_id = "regular" desc.skin_tone = 3 desc.clothing_slots = {"legs": "peasant_pants", "feet": "peasant_shoes"} _cv.load_descriptor(desc) var skel := _cv.get_skeleton() print("QA: skeleton bones = ", skel.get_bone_count() if skel else -1) _attach_garment(skel) _run_qa.call_deferred() func _setup_stage() -> void: var light := DirectionalLight3D.new() light.rotation_degrees = Vector3(-45, 30, 0) light.light_energy = 1.2 add_child(light) var env := Environment.new() env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_color = Color(0.8, 0.8, 0.85) env.ambient_light_energy = 0.9 env.background_mode = Environment.BG_COLOR env.background_color = Color(0.18, 0.19, 0.22) var we := WorldEnvironment.new() we.environment = env add_child(we) _cam = Camera3D.new() add_child(_cam) _set_cam_front() func _set_cam_front() -> void: _cam.position = Vector3(0, 1.25, 1.4) _cam.look_at_from_position(_cam.position, Vector3(0, 1.15, 0), Vector3.UP) func _set_cam_side() -> void: _cam.position = Vector3(1.4, 1.25, 0) _cam.look_at_from_position(_cam.position, Vector3(0, 1.15, 0), Vector3.UP) func _set_cam_back() -> void: _cam.position = Vector3(0, 1.25, -1.4) _cam.look_at_from_position(_cam.position, Vector3(0, 1.15, 0), Vector3.UP) func _attach_garment(skel: Skeleton3D) -> void: # Mirror character_visual.gd:517-535 — reparent skinned meshes onto OUR # skeleton; matching bone names in the Skin resource drive them. var scene := load(GARMENT_GLB) as PackedScene if scene == null: push_error("QA: garment GLB failed to load: " + GARMENT_GLB) return var inst := scene.instantiate() var stack: Array[Node] = [inst] while not stack.is_empty(): var n: Node = stack.pop_back() for c in n.get_children(): stack.append(c) if n is MeshInstance3D and (n as MeshInstance3D).skin != null: var mi := n as MeshInstance3D mi.get_parent().remove_child(mi) mi.owner = null skel.add_child(mi) _garment_meshes.append(mi) inst.queue_free() print("QA: garment skinned meshes attached = ", _garment_meshes.size()) for mi in _garment_meshes: print("QA: garment mesh '", mi.name, "' skin binds = ", mi.skin.get_bind_count()) func _shot(fname: String) -> void: await RenderingServer.frame_post_draw var img := get_viewport().get_texture().get_image() img.save_png(SHOT_DIR + fname) print("QA: saved ", fname) func _wait_frames(n: int) -> void: for i in n: await get_tree().process_frame func _run_qa() -> void: await _wait_frames(5) # report garment AABB sanity (explosion check) for mi in _garment_meshes: print("QA: garment local AABB = ", mi.get_aabb()) # --- idle --- await _shot("01_idle_front.png") _set_cam_side() await _shot("02_idle_side.png") _set_cam_back() await _shot("02b_idle_back.png") # --- walk --- var ap := _cv.get_animation_player() var names: Array[String] = [] if ap: for lib_name in ap.get_animation_library_list(): var lib := ap.get_animation_library(lib_name) for a in lib.get_animation_list(): names.append(str(lib_name) + "/" + str(a)) print("QA: animations = ", names) var walk := "" for n in names: if n.to_lower().contains("walk"): walk = n.get_slice("/", 1) if n.to_lower().ends_with("/walk"): break if walk == "": push_warning("QA: no walk animation found — staying on idle") else: print("QA: playing '", walk, "'") _cv.play_animation(walk) # mid-stride frames at two phases, front + side await _wait_frames(24) _set_cam_front() await _shot("03_walk_front_a.png") _set_cam_side() await _shot("04_walk_side_a.png") await _wait_frames(14) await _shot("05_walk_side_b.png") _set_cam_front() await _shot("06_walk_front_b.png") _set_cam_back() await _wait_frames(10) await _shot("07_walk_back.png") for mi in _garment_meshes: print("QA: post-walk garment local AABB = ", mi.get_aabb()) print("QA: DONE") get_tree().quit(0)