Everything about getting an anime-styled character into the game — import, stylization, the body/garment/hair separation, rigging, retargeting, cloth and hair physics, and how to measure any of it — collected into .claude/skills/character-pipeline/. Organised around the principle the Hoyoverse-class pipelines are built on and that every failure in this project traced back to: a character is not one object. It is a body, a set of garments and hair, authored and rigged separately and moved by different systems. The body is skinned and animated; the garments and hair are bone chains the animation never touches and physics moves. The skill's non-negotiables are the four ways that separation has been destroyed here before — joining meshes, keying cosmetic bones, skinning cloth to a leg, and running the auto-rig repair on authored weights. references/verification.md leads with the trap that invalidated every cloth measurement ever taken in this repo: Godot restores bone poses after the modifier pass, so a tool that reads them afterwards measures the animation and never sees what any modifier did. Also records what is known-unsolved, with numbers: peak cloth clipping in a run/slide/dash, no foot IK, no strafe clips. Co-Authored-By: Claude Opus 5 <[email protected]>
4.2 KiB
Rigging and retargeting
Roles, not names
tools/rig_map.py resolves a skeleton to ROLES — hips, spine[], neck,
head, and limb[(role, side)] for thigh/shin/foot/toe/shoulder/upper_arm/ forearm/hand. Matching is by whole tokens plus anatomy (chain length, position,
which bone is a child of which), so a Rigify DEF-thigh.L, a Mixamo
mixamorig:LeftUpLeg and a bespoke Bip01_L_Thigh all land on the same role.
This is what removed the need to destroy foreign skeletons. roles.missing_core()
is the gate: if the core roles cannot be found the pipeline stops rather than
guessing.
The resolved roles are written to <model>.rig.json and read at runtime by
ShooterPoseModifier._resolve, which aliases its library-flavoured names
(DEF-hips, DEF-spine.001…) onto whatever this rig calls them. Taila's hips
are DEF-spine, her head is DEF-spine.006, and she has no bone with "neck"
in its name at all — unresolved, every lean, aim pitch and slide head-lift
silently did nothing.
Rebuilding the hierarchy
A Rigify DEF-rig exports its chain roots parented straight to the armature root, because Rigify drives them by constraint rather than by hierarchy. Left that way, rotating the hips leaves the legs, skirt and hair floating in place.
rebuild_hierarchy re-attaches orphans: by anatomy where the role is known, and
by rest geometry (nearest plausible parent) otherwise. Cloth may only attach to
the trunk.
Subdividing cloth panels
subdivide_cloth_panels(arm, meshes, roles, segments=4).
A skirt panel that is a single bone from the waist is a rigid flap: it can only rotate about its own head, and a contact near that head is unreachable at any angle. Splitting each panel into a chain is what lets it bend, and it is why the ZZZ-convention skirt is a grid rather than a fan.
On Taila this turns 21 panel bones into 21 chains of 4. The segment lengths come out uneven (49/49/49/141 mm) because the last segment runs on to the hem.
Weights are redistributed along the panel as it is split, so the mesh follows the new chain.
Twist bones
A forearm or thigh twist bone takes half the roll of its parent so the skin does
not candy-wrap. They are detected (is_segment_of) and recorded in the sidecar's
twist list. They are also folded into the limb when measuring collider radii:
most of a thigh's surface belongs to DEF-thigh.L.001, and what is left
dominated by DEF-thigh.L is mostly hip flare, which fitted a 0.154 m radius —
a 30 cm thigh.
Joint helpers
SkinJointHelper.install runs for EVERY model however it was rigged. Linear-blend
skinning collapses any joint by cos(angle/2) no matter how good the weights are;
measured at the knee, 0.77 without helpers against 0.99 with. They are updated
LAST, inside the modification pass, so each helper tracks whatever final rotation
its child bone ended up with.
The retarget maths
Bake each clip as a rest-relative delta:
R_world = src_pose_rot * src_rest_rot⁻¹ what the clip does
tgt_rot = R_world * tgt_rest_rot done to THIS rig
Copying absolute world orientation instead — which is what a constraint bake does — forces the library's bone ROLL onto a mesh bound with a different one, and twists every limb by a constant offset.
Also handled: a facing correction (facing_correction) when the library and the
character face different ways, and a hips-height scale so a short character does
not float.
Export flags that matter
export_bake_animation=False,
export_optimize_animation_keep_anim_armature=False,
keep_anim_armature forces a track onto every bone whether or not the clip
touches it. Off, the skirt and hair export with no tracks at all and belong
entirely to the spring solver. This one flag is the animation/physics split.
Height normalisation
flatten_and_scale(arm, meshes, TARGET_HEIGHT) — default 1.75 m. Applied before
the retarget so the library's stride matches the character's legs.
When a model has no skeleton
tools/autorig.py will fit one, and the pipeline accepts the quality loss:
nearest-bone weights, cross-leg bleed, no cloth chains. weights_authored comes
out false, SkinLegRepair runs at load to snap the worst of it, and the
character will have no secondary motion. Prefer finding a rigged source.