--- 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/.glb --name --rigged # From a Sketchfab UID (needs SKETCHFAB_API_TOKEN) python tools/pipeline.py --uid --name # From an unrigged mesh — auto-rigs, and accepts the quality loss python tools/pipeline.py --input --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 --name --rigged --grow-cloth ``` Then, once, so Godot sees the new files: ```bash godot --headless --path . --import ``` The result is `assets/characters/skins/.glb` + `.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.