Bridge-C evidence spike: unitypackage extraction, rest-pose recon, garment transplant onto the Quaternius rig (Data Transfer POLYINTERP_NEAREST), 11-body batch fit, Godot QA scenes. Technical PASS; route nonetheless REJECTED by product call (cost, baked body-segment modularity, style) — full trail on T-1089. Salvage: source-agnostic transplant/batch-fit scripts (the G1-family pipeline for any donor mesh) and the T-1090 discovery (5 body types misrender bare). Extracted vendor payload (spikes/**/raw/) now gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
168 lines
4.8 KiB
GDScript
168 lines
4.8 KiB
GDScript
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)
|