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]>
95 lines
4.3 KiB
Markdown
95 lines
4.3 KiB
Markdown
# Verification — and the trap that invalidated all of it
|
||
|
||
## READ THIS FIRST
|
||
|
||
**Godot restores every bone's local pose after the `SkeletonModifier3D` pass.**
|
||
|
||
So calling `force_update_all_bone_transforms()` and reading
|
||
`get_bone_global_pose()` from a `SceneTree` script, from `_process`, or anywhere
|
||
outside that pass recomputes the globals from the **animation alone**. The
|
||
shooter pose layer and the cloth solver are simply not in what you measure.
|
||
|
||
`debug/cloth_clip_check.gd` did exactly this. It reported the same ~95 mm of
|
||
leg-inside-skirt with collision fully enabled **and with the collision call
|
||
commented out**. Every number ever taken from that tool before 2026-07-26 is
|
||
void, and several rounds of "tuning did nothing" in the history were reading a
|
||
pose the solver never touched.
|
||
|
||
**To measure a pose layer, add your own `SkeletonModifier3D` as a child of the
|
||
`Skeleton3D` AFTER the one you care about, and snapshot inside its
|
||
`_process_modification()`.** The `PoseProbe` class in `cloth_clip_check.gd` and
|
||
`travel_dir_check.gd` is the pattern.
|
||
|
||
Two related traps:
|
||
|
||
- **Headless runs uncapped**, so the engine delta is sub-millisecond and anything
|
||
integrated barely moves. Set `SpringBones.fixed_delta = 1.0/60.0`.
|
||
- **A single frame of a locomotion clip measures the clip.** A run cycle twists
|
||
the torso against the hips by tens of degrees twice per stride, swamping
|
||
anything a pose layer does. Average over a stride.
|
||
|
||
## The tools
|
||
|
||
| Tool | Measures | Good |
|
||
|---|---|---|
|
||
| `spawn_smoke_test.gd` | spawn, skins, anim tree, camera, state cycling | 29 OK, 0 failures |
|
||
| `cloth_clip_check.gd` | leg-inside-cloth per movement state, per vertex | idle < 25 mm |
|
||
| `cloth_settle_check.gd` | deg/frame at a dead idle, contacts/frame | skirt < 0.1, hair < 0.01 |
|
||
| `cloth_perf_check.gd` | ms per character per frame | ~2.6 ms |
|
||
| `cloth_allow_check.gd` | how much of each limb the rest-clearance cap makes the solver blind to | 17–35 mm on Taila |
|
||
| `cloth_stretch_check.gd` | mesh tearing between panels | no 3× edges |
|
||
| `travel_dir_check.gd` | stride direction vs. travel direction | < 10° except a capped sidestep |
|
||
| `limb_deform_check.gd` | joint collapse | knee ~0.99 |
|
||
| `verify_character.py` | meshes, bones, weights of a SOURCE model | several meshes, cloth bones present |
|
||
| `anim_capture.gd` / `orbit_capture.gd` | renders, for looking | — |
|
||
|
||
Run them:
|
||
|
||
```bash
|
||
godot --headless --path . -s res://debug/<tool>.gd
|
||
godot --headless --path . -s res://debug/<tool>.gd -- res://assets/characters/skins/<name>.glb
|
||
```
|
||
|
||
Scripts run with `-s` MUST extend `SceneTree`. A `Node` script never quits and
|
||
hangs forever.
|
||
|
||
## Measure the right quantity
|
||
|
||
`cloth_clip_check.gd` used to report "how much CLOSER the leg got than the artist
|
||
modelled it". A hem 200 mm clear of a shin legitimately comes 180 mm closer when
|
||
the leg kicks out in a slide, and counting that as a failure buried the real
|
||
clipping under motion the character is supposed to have. It now reports how far
|
||
INSIDE a capsule a cloth vertex is, over and above however far inside it was
|
||
modelled — only cloth actually within the capsule can be showing a leg through.
|
||
|
||
It also applies the collider's `from` offset, so it tests the same band of thigh
|
||
the solver is defending. Measuring the full bone tests the hip cap the solver
|
||
deliberately excludes and reports it as clipping no tuning can fix.
|
||
|
||
## Diagnosing "the solver isn't working"
|
||
|
||
In order:
|
||
|
||
1. **Is the measurement inside the modifier pass?** (Above. Do this first.)
|
||
2. **Does the solver SEE the contact?** `debug_hit_report()` — bone → deepest
|
||
overlap it found. If ~0 while the mesh is deep inside a leg, the collision
|
||
hull does not cover the geometry that is clipping.
|
||
3. **Does it CONVERGE?** `debug_residual_report()` — overlap left after the
|
||
relaxation. Seen 93 mm, left 95 mm is a standing fight, not slow convergence;
|
||
quadrupling the iterations will buy nothing. Find what is pulling back.
|
||
4. **Only then, tune.**
|
||
|
||
That order was learned the hard way: the drape, the bend limits, the backstop,
|
||
the iteration count and the hull sampling were each suspected and tested, and
|
||
the answer was in step 1.
|
||
|
||
## Also run
|
||
|
||
```bash
|
||
godot --headless --path . -s res://movement/tests/run_fsm_tests.gd # 11 tests
|
||
godot --headless --path . --check-only --script res://<file>.gd # syntax
|
||
```
|
||
|
||
Autoload identifiers report false "not found" errors under `--check-only` —
|
||
ignore those.
|