Files
Papay-Shooter/.claude/skills/character-pipeline/SKILL.md
T
Nicholas ButzkeandClaude Opus 5 2ce2175d99 docs(skill): record what four non-library rigs taught the role resolver
Names lie and anatomy does not — the four VRoid imports each broke role
resolution differently, and the lesson generalises: anything guessing
anatomy from a bone name needs a structural fallback. Also lists what is
shipping and which characters have chains that do not simulate.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-26 13:46:23 -04:00

159 lines
7.9 KiB
Markdown

---
name: character-pipeline
description: Import, rig, stylize and animate an anime-styled character into Papaya-Shooter as a selectable skin — keeping the model's own skeleton, artist weights and separate body/cloth/hair meshes, with cloth and hair driven by the spring solver. Use when adding a new playable character, re-importing an existing one, debugging skinning/cloth/hair/animation problems on a character, or changing the cel-shaded look. Triggers on "add a character", "import a skin", "new playable model", "skirt clipping", "hair flailing", "T-posing", "character looks squashed".
---
# Character pipeline
Turns a source model into a playable, cel-shaded, cloth-simulated character skin.
The whole design follows one principle, which is also what the Hoyoverse-class
anime pipelines (Genshin / Star Rail / Zenless Zone Zero) are built on:
> **The character is not one object. It is a body, a set of garments, and hair —
> authored separately, rigged separately, and moved by different systems.**
> The body is skinned and animated. The garments and hair are bone chains that
> the animation never touches; physics moves them. Keeping those separate is
> what makes the result read as an anime character instead of a mannequin in a
> painted-on costume.
Everything below exists to protect that separation.
## The one rule
**If a model arrives with a skeleton, that skeleton ships.** Its bones, its
artist-painted weights, its per-part meshes and its skirt/hair chains all
survive. Only the ANIMATION is moved onto it.
The old route (`strip_rig.py``autorig.py``merge_animations.py`) solved a
bone-*naming* problem by destroying the asset — 18 meshes became 1, 21 skirt
bones and ~50 hair bones became 0, and 16% of vertices ended up pulled by both
legs. `tools/rig_map.py` solves naming properly now. **Never reach for
`strip_rig.py` or `--rebind`** unless the model genuinely has no skeleton at all.
## Doing it
```bash
# From an already-rigged local model (the normal case)
python tools/pipeline.py --input assets/characters/incoming/<name>.glb --name <name> --rigged
# From a Sketchfab UID (needs SKETCHFAB_API_TOKEN)
python tools/pipeline.py --uid <uid> --name <name>
# From an unrigged mesh — auto-rigs, and accepts the quality loss
python tools/pipeline.py --input <mesh.glb> --name <name>
# Rigged (or auto-rigged) but with NO skirt/hair bones — grow them, or the
# costume is welded solid and the spring solver has nothing to simulate
python tools/pipeline.py --input <model.glb> --name <name> --rigged --grow-cloth
```
Then, once, so Godot sees the new files:
```bash
godot --headless --path . --import
```
The result is `assets/characters/skins/<name>.glb` + `<name>.rig.json`, and a
registry entry in `skins.json` that `SkinManager` picks up with no code change.
Blender is required (`BLENDER_PATH`, or auto-found under
`C:\Program Files\Blender Foundation`). Godot lives at
`C:\Program Files\Godot\Godot_v4.7-stable_win64_console.exe`.
## The stages, and what each one protects
| Stage | Where | Protects |
|---|---|---|
| Fix unlit/emissive materials | `tools/gltf_fix.py` | Textures surviving import at all |
| Resolve bone ROLES, not names | `tools/rig_map.py` | The model's own skeleton |
| Rebuild parenting | `retarget.py::rebuild_hierarchy` | Limbs/cloth following the hips |
| Grow cloth chains (opt-in) | `tools/cloth_bones.py` | A costume that has no bones being able to move at all |
| Subdivide cloth panels | `retarget.py::subdivide_cloth_panels` | A skirt being able to bend at all |
| Retarget clips as rest-relative deltas | `retarget.py::retarget_clip` | Limbs not being twisted by foreign bone roll |
| Leave cosmetic bones unkeyed | `export_optimize_animation_keep_anim_armature=False` | Physics owning the cloth |
| Write the rig sidecar | `retarget.py::describe_rig` | The runtime never re-guessing anatomy |
| Cel look | `LevelMaterials.apply_character_look` | The model's own line-work not being re-lit |
| Cloth + hair | `characters/spring_bones.gd` | Clothes reading as clothes |
## Read before you touch anything
Load the reference that matches what you are doing. They are short and each one
is a list of things that cost a debugging cycle to learn.
- **`references/separation.md`** — body vs. garments vs. hair: what must stay
separate, how cloth chains are detected and classed, why cloth is never
skinned to a leg, and the ZZZ-convention mapping.
- **`references/growing-cloth-bones.md`** — what to do when the source has no
skirt or hair bones: how the chains are fitted to the geometry and re-weighted.
- **`references/rigging.md`** — role resolution, hierarchy rebuild, cloth panel
subdivision, twist bones, joint helpers, the retarget maths.
- **`references/cloth-and-hair.md`** — the position-based spring solver, its
colliders, per-class tuning, collision hulls, LOD and cost.
- **`references/stylization.md`** — cel shading, outlines, the imported
line-work trap, eyes, materials.
- **`references/verification.md`** — every measuring tool, what each one
actually measures, and the pose-reading trap that invalidated all of them
once. **Read this before trusting any measurement.**
## Non-negotiables
1. **Never join meshes.** Per-part meshes are how body, cloth and hair stay
separable — for materials, for the outline pass, and for the cloth solver's
hull extraction.
2. **Never key cosmetic bones.** If a clip has tracks on skirt/hair bones, the
AnimationPlayer overwrites the solver every frame and the cloth goes rigid.
3. **Never skin cloth to a leg.** A vertex weighted 0.9 to a thigh cannot be
moved by its own cloth bone, so the solver loses the authority to push it out
of that thigh — and the leg still overtakes it. There was a
`bind_cloth_to_legs()`; it is deleted, and the note above its grave in
`retarget.py` says why.
4. **Never run `SkinLegRepair` on authored weights.** It snaps weights and
deletes triangles. It exists only to undo auto-rigging. It is gated on
`weights_authored`, which is MEASURED, not assumed.
5. **Measure from inside the modifier pass.** See `references/verification.md`.
## After importing
```bash
godot --headless --path . -s res://debug/spawn_smoke_test.gd # 29 checks
godot --headless --path . -s res://debug/cloth_clip_check.gd # leg-through-cloth
godot --headless --path . -s res://debug/cloth_settle_check.gd # idle stability
godot --headless --path . -s res://debug/cloth_perf_check.gd # ms per character
godot --headless --path . -s res://debug/travel_dir_check.gd # legs face travel
```
What "good" looks like on Taila, for calibration:
| Measure | Good | Bad |
|---|---|---|
| Idle skirt movement | < 0.1 deg/frame | 0.5+, or never decaying |
| Leg inside cloth, idle/walk | < 25 mm | 100 mm |
| Leg inside cloth, run/slide/dash | ~95 mm *(current, unsolved)* | — |
| Solver cost | ~2.6 ms/character | 10 ms |
| Stride vs. travel direction | < 10° (except a capped sidestep) | 90° |
## Characters currently shipping
| Skin | Source | Meshes | Cloth chains |
|---|---|---|---|
| taila | rigged, Sketchfab CC-BY | 18 | 35 |
| kiyoko | VRoid, CC-BY | 13 | 20 |
| aria | VRoid, CC-BY | 15 | 15 |
| momo | VRoid, CC-BY | 5 | 9 |
| hikari | VRoid, CC-BY | 13 | 10 *(zero-length — do not simulate)* |
| miku | unrigged source, auto-rigged | 1 | 19 *(hair grown)* |
| mannequin | Quaternius CC0, from the animation library | 1 | 0 *(no costume)* |
## Known-unsolved
- **Peak cloth clipping** in a run, slide and dash sits at ~95 mm of thigh
inside the skirt. Idle, walk and fall are clean. The solver sees the contact
and pushes on it every iteration; the remaining gap is a standing fight
between the collision and the garment's own shape constraints.
- **No foot IK.** Feet do not plant on ground height, so stairs and uneven
ground read as sliding.
- **No strafe or backpedal clips.** Direction is conveyed by yawing the hips
(`SkinnedPlayerModel._update_travel`), which is capped, so a pure sidestep
still runs its legs ~40° off the direction of travel.