2026-08-17 14:53:20 -07:00
|
|
|
|
# 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
|
2026-08-18 11:33:15 -07:00
|
|
|
|
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):
|
2026-08-17 14:53:20 -07:00
|
|
|
|
|
|
|
|
|
|
```csharp
|
2026-08-18 11:33:15 -07:00
|
|
|
|
// delta = canonicalRest^-1 * pose; target = thisBodysRest * delta
|
|
|
|
|
|
var target = boneRest * (canonRest.Inverse() * pose);
|
|
|
|
|
|
skeleton.SetBonePoseRotation(idx, target);
|
2026-08-17 14:53:20 -07:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-18 11:33:15 -07:00
|
|
|
|
`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.
|
2026-08-17 14:53:20 -07:00
|
|
|
|
|
|
|
|
|
|
## Poses
|
|
|
|
|
|
|
|
|
|
|
|
| File | Source (Kevin packs) | Runtime status |
|
|
|
|
|
|
|---|---|---|
|
2026-08-18 11:33:15 -07:00
|
|
|
|
| `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 exp01–exp05 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.
|
2026-08-17 14:53:20 -07:00
|
|
|
|
|
|
|
|
|
|
## 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`).
|
2026-08-18 11:33:15 -07:00
|
|
|
|
- `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 ~1–2.4 cm (benign, sub-pixel in
|
|
|
|
|
|
engine) while fist/grip reach 10–15 cm (real, visible needles).
|