Files
animation/.agents/plans/flat-handpose-runtime-2026-08-17.md
T
jeremy cf336f3b5b feat(lena-hands): exp09 graded cross-digit finger weights + honest tear metrics
The exp05 verdict ("weights alone cannot clear the bar on this mesh") was
measured against a hard partition that was itself causing much of the tearing.
exp01-exp05 gave every vert to exactly ONE digit -- 08_finger_weights.py had an
explicit `elif lo in FING: continue  # other digit: hard wall` -- which
guarantees the fused inter-digit bridges tear by the full finger separation,
because a single edge ring absorbs the whole gap.

Replace that with graded blending (WEB_BLEND_R0): a vert's fraction toward its
nearest other digit ramps 0 -> 0.5 as r = d_own/(d_own+d_other) goes 0.35 -> 0.5,
so both sides of the equidistance valley reach 50/50 and the field is continuous
across the boundary. Applied after the smoother (whose hard wall would erode beta
exactly where it must survive) and evaluated in the neighbour digit's own
arc-length frame. WEB_BLEND_SKIP excludes the thumb: its transform is opposition,
not curl, so its frame does not correspond to a finger's.

Measured against an identical baseline (same input, blend the only variable),
real tears (>=1mm rest length) drop 23-60% with NO regression on flat, the pose
that ships: fist_r 1178 -> 530, grip_r 561 -> 222, fist_l 979 -> 633,
grip_l 420 -> 322; total >5x 2487 -> 1307; p99.9 better on every pose;
flat unchanged at 0/1. fin_bones confirms the mechanism rather than just the
count -- the middle<->ring and pinky<->ring families leave the top classes while
the thumb/palm ones are untouched to the edge (182 -> 182, 148 -> 148).

This does NOT make fist/grip shippable: 222-633 real tears still reads as a
destroyed hand in clay renders, and the residual is now ~53% thumb-pad-fused-to
-palm, which is topology and needs mesh surgery or the v02 rebake. Flat and
relaxed are the shippable poses; fist/grip belong to the morph lane for now.

Also here:
- README: the solver's input is v02/..._exp03.glb, NOT exp01. exp01 is pre-hand
  -fit (converter steps 2b/2c); its finger groups sit on the wrist and overlap
  the real finger by 1.6cm, so a solve from it silently zeroes every _02/_03 bone
  -- rigid stick fingers and a torn flat -- while weight sums stay 1.0 and every
  assert passes. Cost three wasted bakes and one false "the solver regressed".
- Seed assert demanded >=100 seeds while the radius loop caps at
  SEED_AXIS_R_MAX, which left pinky (88 seeds at 16mm) can never satisfy; the two
  constants were mutually unsatisfiable. Now >=60, and it is documented as a
  sanity gate rather than a quality bar.
- Detwist poses tested at last: real but marginal (fist_r 38.7x -> 28.7x,
  grip_r 30.0x -> 17.3x, left hand flat). A knob, not a fix.
- edge_stretch_cmp.py / skin_bone_territory.py / handpose_trim_hand_obj.py:
  judge tears by rest length and absolute posed growth, not raw ratio; audit
  whether a bone owns any verts at all (thumb_01 owns ZERO in exp05); and trim
  an arm-sized skin dump to the hand before rendering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:33:15 -07:00

89 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Handoff: runtime FLAT hand pose in ariki-game (2026-08-17)
> **STATUS 2026-08-17 (session "flatpose"): DONE — all 3 acceptance criteria verified.**
> Implementation (uncommitted, in ariki-game): `src/Animation/HandPoseLayer.cs`
> (SkeletonModifier3D, per-hand 0..1 slerp blend, Mako/MixamoSkin excluded, missing bones
> skipped), PlayerController wiring (`SetFlatHands`, attached to `AnimatedSkeleton`),
> DanceTeam/DancerRig plumbing, bed hotkeys **H** (toggle, active team) + **J** (hand cam),
> HUD `hands=FLAT` flag, pose copied to `assets/quaternius/hand-poses/pose_flat.json`.
> Verified in the dance bed (agent API screenshots): exp01 body fingers visibly flatten
> mid-dance with no spikes; shipped mitt body = silent no-op, zero console errors; nothing
> committed. Test body copy `derived-bodies/lena_leafbikini_quatskin_fingers_glb_exp01.glb`
> is untracked/test-only — do not ship it from here. One Vulkan device-lost crash occurred
> during testing (RX 5700 XT TDR) — unrelated to this code, relaunch cured it.
**Goal:** the female player character can hold a FLAT hand (fingers straight, together)
at runtime, applied as a layer on top of any playing animation. Flat only — fist and
grip are blocked on a weight repair that is running in a parallel lane (see "Scope
fence" below).
## Why this works at all
- ~70100% of finger tracks in the shipped dance clips are frozen at rest, so a
per-frame finger override loses nothing from the animations.
- Hand poses were harvested from the Kevin packs into `animation/hand-poses/`:
`pose_flat.json`, `pose_relaxed.json`, `pose_fist.json`, `pose_grip.json`.
- 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. Apply directly:
`skeleton.SetBonePoseRotation(skeleton.FindBone(name), new Quaternion(x, y, z, w))`.
No rest-relative correction on canonical rigs (that hack is only for Mako, out of
scope here).
- 40 bones per pose file, including `*_04_leaf_*` tip bones. Some bodies lack the
leaf bones — **skip bones that FindBone returns -1 for**, never error.
## Body situation (the trap that makes testing confusing)
- The SHIPPED female body `assets/quaternius/derived-bodies/Ariki_Female_QuatSkin.glb`
has **zero finger weights** (the converter deliberately folds fingers into the hand
bone — "rigid mitt"). Applying the pose to her is correct code but shows NOTHING.
- A finger-weighted candidate exists and the flat pose is validated on it
(max displacement 1.8 cm, clean QA renders in
`animation/characters/work/lena_leafbikini/v02/review/`):
`animation/characters/work/lena_leafbikini/v02/lena_leafbikini_quatskin_fingers_glb_exp01.glb`
Use it as the TEST body.
- Mako (`Ariki_Male_Mako.glb`) has corrupt cross-hand finger weights — do NOT enable
the pose layer on him; a finger curl throws verts metres. Female/canonical only.
## Implementation pointers
- Pattern to copy: `ariki-game/src/Animation/PlayerIKRig.cs` — post-animation bone
modification. The hand-pose layer is the same idea: after the AnimationTree/Player
updates, write the pose quats onto the finger bones each frame while the layer is
active. Keep an on/off (and ideally a blend weight 0..1 slerping from the animated
pose) per hand.
- Test bed: `ariki-game/src/Testing/Dance/DanceTestBed.cs`. `DANCE_BODY_GLB` env var
swaps the bed's body — point it at the exp01 GLB above (see comment near line 82).
Run with `MOCK_ONLY=1`.
- ENGINE TRAP: the S3 engine is v1.0.0 (Godot 4.6.2) and the repo is on SDK 4.7.
If you locally downgrade `csproj`/`project.godot` to launch, **NEVER commit those
lines**.
## Verification tools (animation repo)
- `tools/handpose_bake_preview.py body.glb pose.json out.glb` — bakes a pose into a
GLB's rest rotations (what the runtime layer should reproduce).
- `tools/skin_displacement_check.py posed.glb original.glb` — Godot-exact LBS math;
flat on exp01 reads max 1.8 cm / median 0.27 cm. Meter-scale numbers = broken.
- `tools/handpose_skin_to_obj.py posed.glb l|r out.obj` +
`tools/handpose_render_objs.py` (blender --background) — the only honest VISUAL
check. **Do not judge by importing a baked-pose GLB into Blender and rendering:
the importer ignores the rest-vs-bind rewrite and draws false shards.**
## Scope fence
- Do not swap or re-export any shipped body (`Ariki_Female_QuatSkin.glb`,
`characters/female/lena_leafbikini_base_v01/` is frozen). The finger-weighted body
ships from the parallel weight-repair lane, not from this task.
- Do not touch `ariki-game/tools/make_lena_fullres_quatskin.py`; its
`LENA_RIGID_FINGERS` default must stay `"1"`.
- Flat pose only. Fist/grip activation waits for the repaired weights.
## Acceptance
1. In the dance test bed with `DANCE_BODY_GLB` = exp01, toggling the layer while a
dance plays visibly straightens/flattens the fingers, no vertex spikes, and the
rest of the animation is unaffected.
2. On the shipped mitt-handed body the layer is a silent no-op (no errors).
3. Nothing committed in either repo changes any shipped asset or engine version.