Files
animation/characters/work/lena_leafbikini/README.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

14 KiB
Raw Blame History

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
06_open_in_blender.py opens a lane mesh in the Blender GUI, framed on her front, Material Preview
07_fill_crotch.py v02: Barbie-fills the crotch — melts the briefs leaves into a smooth featureless surface, bust holes stay open. See below

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)

v02 — the Barbie crotch fill (stage 07)

Target: "Barbie doll anatomy" — a completely smooth, undifferentiated pelvic surface, no cleft, no features, belly flowing into thighs. Jeremy's "take the distance and angle between the two sides" is precisely a bi-harmonic membrane: the rim supplies POSITION, the collar behind it supplies SLOPE through the second Laplacian application, and L²x = 0 cannot invent detail. Bust holes stay cut open (v01 behaviour); only the briefs band gets the treatment.

Getting there burned through roughly ten runs; the recipe header carries the full autopsy, the short version is:

attempt verdict
triangle_fill the v01 holes + membrane the briefs rim is ONE ~2,500-vert loop snaking front → between the legs → back; beauty triangulation bridges the WRONG BANKS at the bends, and the rim itself still carried leaf-root crumple the membrane faithfully anchored to
+ rim erosion + Delaunay flips + double solve better rims, same disease — flips are local, the mis-bridging is global; sliver strands shot off the hips
melt instead of fill (the pivot) the leaf shell IS the disk that spans the hole — the scan's own manifold surface. Free its verts + a skin collar, solve; wrong-bank bridging becomes impossible. This produced the first genuinely smooth pelvis
proudness detectors + deflation for the leftovers fully-masked leaves are PENDANT BALLOONS — closed shells whose excess area the (no-maximum-principle) bi-harmonic parks as smooth raised caps. Smoothing-built references partially follow any bump wider than their radius, and Taubin references are worse than useless here: shape-preserving by design, a 1.5 cm cap sits inside the passband and reads ~0
density excision + scar refill catches the FLAT wads (the melted shell lies ~4 layers deep, and piles would z-fight) but not inflated caps, which sit BELOW the piled median. Scar refill needs: per-sweep densify scope with a floored target (a sliver scar once re-densified everything to 3.3M faces / 17 h), a slit weld first (scars meeting Tripo slits are ribbons, not loops — fill ops refuse them, and degenerate output crashed Blender once), and a >900-edge refusal
occlusion / fin / diffusion detectors for the last four "caps" all near-zero. A pixel-ray probe finally explained why: the last bumps were not leaf debris at all — they are the melt web itself bridging taut over the inguinal hollow. There was never skin under the vine, and every rim-anchored method spans its anchors by construction
+ the flow finish (the guarantee) damped pure-Laplacian flow over the whole changed region, no detector at all: the maximum principle sinks any pendant cap while the 5-10× wider pubic web barely moves. Ramp weight 0→1 over 3 rings — at 8 rings the hip-vine channel (1020 rings wide) sat entirely in the damped zone
+ the solid-welt pass some leaf roots are SOLID skin-painted ridges, not shells — free them (0.5 mm proudness against a whole-band 400-sweep reference; honest skin reads p95 +0.13 mm) and re-solve locally; converges in 2 passes (5,375 verts, then 1)

Pipeline as shipped in 07_fill_crotch.py: weld → cut bust open (+despike) → free briefs leaves + collar → remnant sweep (relaxed hue/blown-white key near the melt, no speckle filter) → bi-harmonic melt (matrix-free Jacobi-PCG, harmonic warm start) → solid-welt pass ×2 → density excision of flat wads + occlusion pass → slit weld + degenerate dissolve → three scar-fill sweeps + patch solve → flow finish. Melted faces get one donor texel (thigh-band vertex nearest the band's median tone with the most neutral normal texel) — the atlas is Tripo chart soup, so interpolating UVs across the fill would sample garbage.

"$B" --background --factory-startup --python $L/07_fill_crotch.py -- \
     characters/originals/female/female_lena_leafbikini_tripo.glb $L/leaf_mask.npz \
     $L/v02/lena_leafbikini_crotchfill_sculpt_glb_v02.glb --free-rings 3

Open

  • The hip-side shelf bulges are a known consequence, not a bug to chase further. Where the vine crossed the inguinal crease there was never skin underneath, and "take the distance and angle between the two sides" — the membrane — spans its anchors by construction. Any method anchored at the rims produces the same taut bridge (harmonic, bi-harmonic, and flow all agree; the maximum principle protects a two-sided span). Killing them means SCULPTING the crease through the strip — invented anatomy, its own decision, its own stage. Ten runs of detector archaeology confirmed there is nothing foreign left there to remove.
  • The bust holes are not filled, by request (v01 deliverable; v02 fills the crotch only). Whatever fills the bust has to rebuild breast anatomy, not just span it — see tools/make_lena_nude_body.py for how the nude lane sculpted hers procedurally.
  • The base-colour map still carries the leaves and their baked contact shadows. Around the v01 holes and the v02 fill alike, the surviving skin reads darker than her surrounding tone — an albedo re-author job for a later stage (the nude lane's harmonic refill is the template).
  • Both artifacts are unrigged, because they were authored on the pristine original. They transfer to the shipped body's frame 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 either mesh. Re-running 05/07 against the shipped GLB is a first-argument change — but the result is a new ship folder, never an edit to the frozen one.

Finger-weight re-solve (08_finger_weights.py) — how to run it

The input is v02/lena_leafbikini_quatskin_fingers_glb_exp03.glb, not exp01. This is the one thing to get right; guessing it cost three wasted bakes on 2026-08-18.

"C:/Program Files/Blender Foundation/Blender 5.1/blender.exe" --background \
  --factory-startup --python characters/work/lena_leafbikini/08_finger_weights.py -- \
  characters/work/lena_leafbikini/v02/lena_leafbikini_quatskin_fingers_glb_exp03.glb \
  characters/work/lena_leafbikini/v02/lena_leafbikini_quatskin_fingers_glb_<tag>.glb

exp01 and exp03 carry the SAME weight groups (identical vertex sets — index_l is 6,985 verts in both) but different meshes: 91,445 verts differ, by up to 12.5 cm. exp01 is the pre-hand-fit body, so on it those groups land on the wrist/palm, overlapping the real index finger by only 1.6 cm. Solving from exp01 therefore produces a body where the arc-length param s never clears the phalanx-1 threshold and every _02/_03 finger bone gets exactly zero weight — rigid stick fingers hinging at the knuckle, and a torn flat pose. It fails silently: weight sums are 1.0, the asserts pass, the export succeeds. Confirm a good run by the label counts — from exp03, side l is index 4,000 / middle 4,718 (these match exp05's groups) and chain-continuity repaired is ~400 per side, not 0.

Verify a bake with tools/handpose_bake_preview.py + tools/handpose_skin_to_obj.py + edge-stretch; judge on real tears (>=1mm rest length) and visible needles (>=1cm posed), not raw ratios. Flat must stay at 0-1 real tears per hand — that pose ships.

Measurement tools for this lane: tools/edge_stretch_cmp.py (stretch with the real-tear / visible-needle split, several builds side by side), tools/skin_bone_territory.py (how many verts each finger bone actually owns — catches a starved chain), tools/fin_bones.py (classifies torn edges by bone pair, which is what tells you which defect you are looking at), tools/handpose_trim_hand_obj.py (trim an arm-sized skin dump to the hand, or a bbox-framing renderer puts the hand in a corner).