fix(pipeline): stand the character up before scaling; reach bones by role
Fixes the two root causes behind four of the seven reported breakages, and rejects the source that cannot be fixed. STAND UP FIRST. flatten_and_scale() now derives the up axis from the skeleton and rotates the model upright before measuring anything. Aria and Momo are correct: 1.75 m tall, 1.43 x 0.41 and 1.52 x 0.39 across, verified by render. The up vector is measured from the FEET to the HIPS, not from the hips to the head. The head is not a reliable landmark — the spine walk ends on the last non-cosmetic bone in the chain, which on a rig with a facial skeleton can sit BELOW the hips. Momo's did, so the first cut of this fix stood her neatly on her head: right size, right proportions, upside down. Feet cannot be mistaken. REACH BONES BY ROLE. SkinnedPlayerModel gained _role_bone(), and set_weapon uses it. Four characters could not hold a gun because one hardcoded lookup knew three spellings and their hands are called "Right wrist" and "J_Bip_R_Hand" — both resolved perfectly in the sidecar the whole time. HIKARI IS REJECTED. She now fails the gate: her feet and spine disagree about which way is up, so the stand-up correction cannot resolve her either, on top of zero-length cosmetic bones and a second armature that was smuggling its own clips into the export. That is not a tuning problem, it is a file that has been through two toolchains. De-registered and removed rather than shipped broken — which is what the gate is for. Six GLB skins remain, all passing. Smoke 0 failures, 11/11 movement tests. Still open, recorded in the skill: kiyoko faces backwards, miku's grown hair stretches under animation, the mannequin's rifle hold does not convince, and taila's front skirt clipping. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2b3e29dd30
commit
27c4117c25
@@ -922,7 +922,7 @@ func set_weapon(script_path: String) -> void:
|
||||
_measure_weapon(w)
|
||||
)
|
||||
|
||||
var hand_idx := _find_bone(["RightHand", "Hand_R", "hand.R"])
|
||||
var hand_idx := _role_bone("hand.R", ["RightHand", "Hand_R", "hand.R"])
|
||||
if hand_idx >= 0:
|
||||
_weapon_attachment = BoneAttachment3D.new()
|
||||
_weapon_attachment.name = "WeaponAttachment"
|
||||
@@ -995,6 +995,27 @@ func _measure_weapon(w: Node3D) -> void:
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
## A bone by its resolved ROLE, falling back to name matching.
|
||||
##
|
||||
## tools/rig_map.py resolves every rig to roles and writes them to the sidecar so
|
||||
## that nothing downstream has to guess a bone name — but a hardcoded lookup here
|
||||
## meant four characters could not hold a gun. Their hands resolve perfectly as
|
||||
## "Right wrist" and "J_Bip_R_Hand"; none of them matches a spelling this file
|
||||
## knew, so set_weapon fell through to parenting the weapon to the model root at
|
||||
## a fixed chest offset, where it is not attached to the character at all.
|
||||
##
|
||||
## The fallback stays for a model with no sidecar. It must never be the first
|
||||
## thing tried.
|
||||
func _role_bone(role: String, fallbacks: Array) -> int:
|
||||
var roles: Dictionary = _rig_info.get("roles", {})
|
||||
var actual := String(roles.get(role, ""))
|
||||
if actual != "" and skeleton:
|
||||
var b := skeleton.find_bone(actual)
|
||||
if b >= 0:
|
||||
return b
|
||||
return _find_bone(fallbacks)
|
||||
|
||||
|
||||
func _find_bone(name_parts: Array) -> int:
|
||||
if not skeleton:
|
||||
return -1
|
||||
|
||||
Reference in New Issue
Block a user