docs(skill): compact the character pipeline into a reusable skill
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]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
040b595397
commit
daf9627ece
@@ -0,0 +1,94 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user