Files
animation/hand-poses/README.md
T

86 lines
5.0 KiB
Markdown
Raw Normal View History

# Hand poses — pre-ship staging for ariki-game
Canonical hand-pose library. Poses live here first; when a pose is adopted for the
game it is **copied** to `ariki-game/assets/quaternius/hand-poses/` (keep the two
byte-identical — the runtime `HandPoseLayer` reads the game copy).
## Format
`{"bones": {"<bone_name>": [x, y, z, w], ...}}` — glTF node-local quaternions on the
**canonical Quaternius skeleton**, which is exactly Godot bone-pose space for these
bodies.
40 bones per pose, including `*_04_leaf_*` tip bones. Some bodies lack the leaf
bones — **skip bones `FindBone` returns -1 for**, never error.
**Apply REST-RELATIVE, not directly** (runtime does this as of 2026-08-18):
```csharp
// delta = canonicalRest^-1 * pose; target = thisBodysRest * delta
var target = boneRest * (canonRest.Inverse() * pose);
skeleton.SetBonePoseRotation(idx, target);
```
`canonical_rest.json` (regenerate with `tools/make_canonical_rest.py`) holds the canonical
finger rests this correction needs. On a rig whose finger rest matches canonical the
correction is algebraically a no-op, so nothing changes for the Quaternius/QuatSkin
bodies. On one that deviates it is the only correct form — Mako's `*_01` knuckles sit
**11.5°** off canonical, and writing poses straight in wrenched them away from his own
rest and tore the palm/wrist boundary.
## Poses
| File | Source (Kevin packs) | Runtime status |
|---|---|---|
| `pose_flat.json` | **= canonical REST** (see below) | **SHIPPED** — verified in-engine on Mako 2026-08-18 |
| `pose_relaxed.json` | `HandWave01` f0 (~10° off rest) | staged |
| `pose_fist.json` | `AttackPunch01_R/L` f7, merged (~61° off rest) | blocked on finger-weight repair (shreds on exp01exp05 and on Mako) |
| `pose_grip.json` | `CombatIdle1H01` f0, both hands (~44° off rest) | blocked, same repair |
### `pose_flat` is the REST pose — and that is why it is useful
Measured 2026-08-18: `pose_flat.json` matches `kevin_female_combat.glb`'s finger rests to
**0.04°** — it is the canonical rest pose, not a separately harvested "flat hand". Do not
expect it to straighten a hand that is already at rest.
It is still the load-bearing pose, because **the shipped clips do not hold fingers at
rest** — they pin them in a permanent curl (frozen tracks, spread 0.0°). `UAL1 Idle_Loop`
holds the fingers **53.9° off rest on average, up to 86.8°** (`thumb_03_r`) — near a
clench. So the FLAT layer's real job is to undo that baked-in curl.
On Mako that curl is what shreds his hands. It does not fling verts (max displacement only
23.3 cm, i.e. legitimate fingertip travel) — it **tears**: 2,258 edges stretched >5×, max
198×, which rips his fused hand open into sheets. Turning FLAT on removes the bulk of it.
Evidence (in-engine, the only honest judge here):
`characters/work/mako/handfix/review/ingame_handcam_flat_{OFF,ON}.png` and the
`ingame_mako_{left,right}_hand_flat_ON_zoom.png` crops. **Residual:** at close range his
RIGHT hand still shows a torn patch with FLAT on — his right hand owns far less finger
geometry than his left (4,283 verts vs 10,532; `middle_02_r` owns just 157), so it is not
fully fixed. FLAT only holds the FINGER bones at rest; `hand_l/r` still follow the clip.
## Verification (before shipping a pose)
- `tools/handpose_bake_preview.py body.glb pose.json out.glb` — bake into rest rotations.
- `tools/skin_displacement_check.py posed.glb original.glb` — Godot-exact LBS travel;
cm-scale = sane, m-scale = broken. **Blind to fin tearing** — pair it with the
edge-stretch check (`tools/edge_stretch.py`).
- `tools/handpose_skin_to_obj.py` + `tools/handpose_render_objs.py` — clay render. Never
judge by importing a baked-pose GLB into Blender (false shards). Pass an ABSOLUTE outdir
(a relative one silently writes nothing), name inputs `<pose>_hand_<l|r>.obj` (the glob
requires it), and crop to the hand with `tools/obj_crop.py` or the arm dominates the frame.
- `tools/skin_lever_audit.py body.glb` — finds bindings whose joint is implausibly far away
in rest, and verts bound across the midline to the opposite hand. **Invisible at rest**,
so nothing else catches them: this is what found Mako's 444 cross-hand verts.
- `tools/skin_crosshand_repair.py in.glb out.glb` — repairs those by inpainting from the
mesh's own healthy neighbours (`--diagnose` to preview).
- `tools/hand_bone_ownership.py body.glb [l|r]` — which finger bones actually own geometry.
Run it before trusting a pose on a new body: Mako is a **two-finger rig**, so only his
thumb and middle chains own verts and index/ring/pinky own nothing.
- `tools/rest_deviation.py canonical.glb other.glb` — per-bone finger rest deviation, i.e.
whether a body needs the rest-relative correction.
**Ratio alone is not the tearing gate.** `edge_stretch.py` reports huge ratios on this
mesh's sub-millimetre sliver edges (unwelded duplicates) — judge the **absolute posed
length**. On Mako, flat's worst stretched edge reaches ~12.4 cm (benign, sub-pixel in
engine) while fist/grip reach 1015 cm (real, visible needles).