Files
animation/characters/work/lena_leafbikini
jeremy 36fc0494e8 feat(hand-shapes): morph-target hand-pose solver — fist/grip that actually deform
The morph lane's blocking bug was believed to be coincident duplicate verts hidden
from the solver's convergence gate. Welding was necessary but nowhere near
sufficient: the previous relaxation could satisfy every stretch bar without posing
anything, so the numbers it reported were not measuring the thing they claimed.

Measured on Ariki_Female_QuatSkin_LowPoly_40, the tracked "left hand is fully
clean" baseline was a morph that moved fingertips 0.3cm, and the right hand was a
rigid 6.5cm translation of the whole hand with its shape intact. Gated Laplacian
diffusion of a delta field has a null space — constants — and edge stretch cannot
see any of it: a rigid translation stretches no edge and neither does a collapse to
zero. Both exits report perfect bars.

Six defects fixed, each with its measurement in the code comments:

  weld_roi           356 duplicate groups solved twice, deltas up to 2.55cm apart
  stitch_components  hand is 6 overlapping sheets with 1-10mm gaps; Dijkstra
                     cannot cross one, so each bone saw only its own sheet
  refit_fingers      skeleton finger chain ran to 19.8cm; the flesh ends at 14.0cm,
                     so curl_02/curl_03 drove almost no weight (shipped skeleton
                     untouched — this is solver-local scaffolding)
  solve_weights      chain-arc partition of unity; the old 8mm isotropic kernels
                     left ZERO of 17,480 verts owned above 0.85, and a 3-way blend
                     averages the curl away. Plus a support cutoff: outside every
                     kernel, renormalized 1e-81 noise had handed a mid-palm vertex
                     index_02_r=0.50 and flung it 29cm
  build_roi          joint-sphere ROI, so the rim is a wrist band and not a fractal
                     of interior chart holes
  relax              strain-only edge projection + ROI-border seam constraints,
                     replacing the diffusion described above

All 8 shapes now pass every bar with real deformation: p99.9 <= 1.58x, zero edges
over 5x, zero needles with the sliver floor removed, seam <= 3.2mm, cross-hand
independence exactly 0.0000cm, mesh fingertip travel 4.6-6.2cm on fist and
3.5-4.7cm on grip (both hands). Gates now report mesh travel and seam alongside
stretch, because stretch alone cannot gate this lane.

Verified in anim_hand_test_bed: fist and grip read as a real curl on both hands,
static and mid-dance, no fins/shards/needles. It is a loose fist rather than a
clenched one — her fingers are ~4-5cm past the knuckles.

Demo GLB stays out of git (122.6 MB, ariki-game/scratchpad/). Ship decision, the
thumb-axis refit, and the TDR crash from 8 dense targets are open — see README.

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

work/lena_leafbikini — the leaf-bikini lena lane

Grammar-exempt (REGISTRY rule 11). This lane has already produced one ship — characters/female/lena_leafbikini_base_v01, the game's female default since 2026-08-13, which is frozen (REGISTRY rule 2). Everything still here is lane material governed by .agents/rules/working-files.md; when the next body ships it graduates into characters/female/lena_leafbikini_base_v02/ and gets its own Ships row.

Parent original: characters/originals/female/female_lena_leafbikini_tripo.glb (sha256 977104ba…66cf29, 1,029,360 v / 1,992,503 f, 0.9792 units tall → 1 unit = 1.815 of a 1.777 m body). Read-only, and re-hashed by every stage that opens it.

Layout

NN_*.py the recipes. These are the history — kilobytes each, and they regenerate any state below from the original
leaf_mask.npz the leaf mask (stage 04): per welded point, plus the vertex→welded map and the hue/sat/val the key was cut from. Regenerable, kept because stage 05 consumes it and re-deriving it costs a full import
v01/ stage output. gitignored (characters/work/*/v0[0-9]/) — registered artifacts inside it are git add -f'd by hand
v01/review/ QA renders. gitignored, regenerable, reused rather than re-minted

The stages

stage what it does
01_graft.py grafts the AccuRig skeleton onto the pristine GLB (nearest-surface, not index-exact — the bait was decimated 20:1). This is what produced lena_leafbikini_base_v01. Independent of 0205
02_probe_leaves.py what are the leaves? Reports the albedo key candidates and per-vertex proudness
03_render_leaves.py textured and clay turnaround of any mesh in the lane. The clay pass is what settled the question
04_leaf_mask.py builds and proves the leaf mask: hue key → component filter → close → hole fill → grow, baked to vertex colour and rendered
05_cut_leaves.py deletes the masked faces. Holes left open, on purpose

What the leaves turned out to be

The clay render (stage 03) shows every leaf, curled tip and hip vine with all materials stripped, so:

  1. They are real geometry, not paint. This is the opposite of Lena's game-body underwear, where a ray-crossing census found the bra was the skin and deleting it opened a hole — the whole reason tools/make_lena_nude_body.py re-fairs a rim band instead of cutting. Here there is a solid shell to remove.
  2. They are welded into the body. One Tripo mesh, one material, one UV map: the leaves are a bulge in the body surface, not a separable object, material slot or UV island. Nothing can be selected "by object"; the seam has to be found.
  3. The seam is both a colour edge and a crease — where a leaf meets skin the surface folds back on itself.

Why the key is hue

Stage 02 first keyed on green dominance, G - max(R, B), and topped out at 0.20: Tripo painted these leaves a dark desaturated olive (sRGB ~0.27/0.35/0.20), so the channel gap is only ~0.050.09 and any threshold catching the leaf also catches shadow noise on skin. Hue separates them outright — skin 2040°, leaf 6080° — and is invariant to exactly the thing that ruins the channel gap, which is how dark the pixel is.

Everything after the key is repaired on the mesh, over position-welded adjacency, not in texture space:

step why
min-comp 200 drops JPEG speckle
close 6 rings fills specular blowouts inside a leaf, where the highlight clips to near-white and its hue becomes noise. At --val-max 0.62 the hip vine leaves came back half-grey
enclosed-hole fill a mask hole fully surrounded by leaf is a mask defect, not a skin island
grow 2 rings lands the cut just outside the rim. A hole one ring too big is invisible; a leftover leaf stub is not
z ≤ 0.85 of height her irises and eyebrows are green too. Without this the key takes her eyes (929 verts)

Result: 239,561 welded points in exactly two islands — bust (z 0.6310.755 of height) and briefs (z 0.4550.594). Two islands is itself the check that nothing else keyed.

Welded adjacency is not optional anywhere here: the glTF importer splits every UV seam into separate Blender vertices, so on the raw mesh a face on a texture-chart border is indistinguishable from a face on a hole rim, and ring operations would leak along the seams (33,202 of the 1,029,360 verts are seam duplicates).

The cut rule

A face dies only if all of its vertices are masked. With the 2-ring grow that puts the surviving rim about one ring outside the green, i.e. just onto skin. Both directions of that asymmetry are deliberate: "any vertex masked" erodes a further ring into her skin and leaves single triangles hanging off every zig-zag in the mask edge, while a cut one ring short leaves leaf root standing proud — the one outcome that would make the file useless for judging the seam.

Then two passes that can only remove things that are already not part of a smooth surface: dangling-face erosion (a face with ≥2 welded-boundary edges is joined to the surface by at most one edge; no such face exists on a closed surface, so it cannot bite into skin) and detached-shell removal. On this mesh they found 2 faces and 0 shells — the colour cut was already clean, which is the strongest evidence the mask was right.

Regenerating v01/lena_leafbikini_leafcut_sculpt_glb_v01.glb

B="/c/Program Files/Blender Foundation/Blender 5.1/blender.exe"
L=characters/work/lena_leafbikini
"$B" --background --factory-startup --python $L/04_leaf_mask.py -- \
     characters/originals/female/female_lena_leafbikini_tripo.glb $L/v01/review
"$B" --background --factory-startup --python $L/05_cut_leaves.py -- \
     characters/originals/female/female_lena_leafbikini_tripo.glb $L/leaf_mask.npz \
     $L/v01/lena_leafbikini_leafcut_sculpt_glb_v01.glb
"$B" --background --factory-startup --python $L/03_render_leaves.py -- \
     $L/v01/lena_leafbikini_leafcut_sculpt_glb_v01.glb $L/v01/review cut

Stage 05's gates, all reported and the first two fatal:

gate result
every surviving vertex position present in the input 0 strays — this is a deletion and only a deletion
loose vertices 0
surviving shells 1
removed geometry's extent z 0.4550.755 of height (bust + briefs bands only)

Open

  • The holes are not filled, by request. Filling is a separate and harder decision: the crotch/gusset history in work/lena/06*.py is what happens when a membrane spans a wide footprint — it flattens the anatomy it spans, which is why the nude lane only ever faired a narrow rim band. Whatever fills these has to rebuild bust and crotch anatomy, not just span them.
  • The base-colour map still carries the leaves and their baked contact shadows. The rim of each hole is skin painted with leaf shadow, so it reads darker than her surrounding tone.
  • The cut is unrigged, because it was authored on the pristine original. It transfers to the shipped body for free: 01_graft.py moved the mesh 0.000000 mm, so lena_leafbikini_base_v01 carries the same 1,029,360 vertices in the same order and leaf_mask.npz indexes it directly. Cutting the shipped GLB is running stage 05 with a different first argument — but the result is a new ship folder, never an edit to the frozen one.