reorg(characters): ship-time folders — lena_base_v01 ships, lane moves to work/lena

REGISTRY rewritten around the central rule: a character folder is born only
when a body ships to ariki-game (<character>_base_v<NN> = ship ordinal).
lena_nude dissolves accordingly:
- characters/female/lena_base_v01/ — SHIPPED 2026-08-10: AccuRig GLB carrier,
  T-pose/rig FBX + JSON, previews, frozen README
- characters/work/lena/ — the live lane: recipes 01-47 (incl. new 36-47:
  refill/sheets/clay/despeckle/musculature/spin/AccuRig export/graft/pose QC),
  masters (athletic_v04 blend + textures, accurig blend), lane-history README
- hires_claude/hires_work intermediates (blends, logs, probes) pruned

Supporting docs: AGENTS.md, working-files rule, rig-graft plan addendum,
originals README, prune_lane.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 07:17:15 -07:00
parent 4bbd1a1732
commit 3d8825f5a9
263 changed files with 3262 additions and 26313 deletions
+39 -3
View File
@@ -66,14 +66,50 @@ Census facts that shape the plan:
3. **AccuRig** (manual, Jeremy/Ozlem): rig the bait FBX. Export rigged FBX.
4. **Graft** (headless Blender): import rigged bait FBX + pristine GLB; snap rest
poses; transfer skeleton + skin weights decimated→full-res by nearest-surface
with tight max-distance; **hands get special handling** (per-finger masked
transfer or reduced distance + limit-totals + normalize) — this is where the
old hand mangling gets fixed. Export GLB: original mesh + original packed
with tight max-distance. Export GLB: original mesh + original packed
textures + new rig. FBX artifacts are discarded.
**Hands are no longer part of this step — see "Hands" below.**
5. **QC**: pose sweep incl. finger curls; then (if/when destined for game) the
existing AccuRig→Quaternius conversion (`ariki-game/tools/make_male_ib_quatskin_accurig.py`
lineage) picks it up.
## Hands: transplanted, not solved (RESOLVED 2026-08-07)
The hands come off the bait entirely and are transplanted from a body that already
ships working ones — `tools/graft_hands.py`, wrist-aligned. Three measurements
decided this:
- **There is nothing to solve.** The nude lane never touched the hands: hand-region
displacement from the Tripo original is mean **0.010 mm**, p50 exactly 0.000, only
0.06% beyond 1 mm (the 12.6% vertex churn there is micron jitter from the v03
despeckle pass). A donor's hand weights fit this mesh as-is.
- **A mitten rig would cost real animation.** Fingers move *relative to each other*
by up to **89°** over a clip (fertility 89.4°, taming 77.6°, nd_01 46.7°,
canoedismount 26.4°); only `war_dance_01` and `hakadance1_static` hold them still.
So four of six dances would visibly flatten. Rejected.
- **The bone list is not negotiable anyway.** 40 of the 65 joints are hands, every
clip carries tracks for all 40, and `tools/verify_body_variant.py` gates on "65
joints in identical order". A mitten could only ever have been a *skinning* choice.
`graft_hands.py` edits the glTF node graph directly rather than round-tripping the
armature through Blender, because Blender re-derives bone rest orientation from
edit-bone head/tail — the exact failure `ariki-game/tools/rig_pose_gate.py` exists to
catch. Validated: two self-tests (identity, and a 0.55× + 7° similarity) place every
hand joint to **5e-16 u** and recover 100% of donor weights; output passes
`verify_body_variant.py`; and `rig_pose_gate.py` reports **max error 0.000° over 260
(bone, clip, sample) comparisons** under the two most finger-heavy dances.
Two self-inflicted bugs the similarity test caught, worth not re-introducing: the
wrist scale gets applied **twice** if you compose the target's wrist matrix without
stripping its own scale first (the forearm ratio was measured in that same scaled
space), and the weight transfer must convert donor-local → world → target-local, not
compare raw POSITION arrays across two files.
**Still open on hands:** if AccuRig returns no wrist joint at all once the hands are
cut from the bait, `graft_hands.py` aborts — the `--wrist-from-forearm` fallback is
specified but not implemented, because what AccuRig actually returns is unknown until
step 3 runs.
## Naming + step 2 status (RESOLVED 2026-08-04, later same day)
Jeremy raised the hands budget and released the bait step. Built with
+1 -1
View File
@@ -50,7 +50,7 @@ head of the live chain. Unpin the head's parent once the head is accepted.
## Pruning
```
python tools/prune_lane.py characters/female/lena_nude --recursive # dry run
python tools/prune_lane.py characters/work/lena --recursive # dry run
python tools/prune_lane.py <lane> --apply # delete scratch
```
+5 -4
View File
@@ -43,10 +43,11 @@ AGENTS.md ← you are here
tools/ ← Blender-headless retargeters, loop tools, prop export,
md_bridge* (drive Marvelous Designer), tailor/ (garment
authoring: recipes, measurements, QC, screenshots)
characters/ ← character body models: originals/ (read-only Tripo sources) +
per-character working folders (male/mako, female/lena).
Naming law in characters/REGISTRY.md — applies to all new
derived models
characters/ ← character body models: originals/ (read-only Tripo sources),
work/<character>/ (live lanes: recipes + masters), and one
frozen folder per SHIPPED body (female/lena_base_v01).
A folder is born at ship time, not at experiment time —
naming law in characters/REGISTRY.md
clothing/ ← garment mesh → game-ready outfit GLB (staged Blender pipeline)
garments/ ← raw incoming garment sources + design advice notes
exchange/ ← files in flight between Mac and PC (see exchange/GUIDE.md)
+134 -86
View File
@@ -1,87 +1,111 @@
# Character Model Registry
Naming law for `characters/`. **Scope: this system applies only to newly created
variants derived from mako and lena** — every such artifact from here on, *wherever
it is produced*. (This clause used to read "anything born in this folder", which let
the AccuRig rig baits get built into `ariki-game/assets/models/characters/rig-work/`
and slip the net entirely; they were moved here 2026-08-06. A character artifact
generated in another repo is still governed by this file and still belongs under
`characters/`.)
Naming law for `characters/`. **Scope: newly created bodies derived from mako and
lena** — every such artifact from here on, *wherever it is produced*. A character
artifact generated in another repo is still governed by this file and still belongs
under `characters/`.
Legacy models elsewhere — game-side `derived-bodies/`, old intermediates, historical
race-sources — keep their names and are NOT registered here; they will eventually be
replaced by files that follow this system.
race-sources — keep their names and are NOT registered here.
Modeled on the dance registry (`.agents/wiki/dances/REGISTRY.md`): numbers are never
reused, superseded files are archived, never deleted.
---
## Filename grammar
## The central rule: a folder is born at ship time
**A character folder exists only for a body that has shipped to ariki-game.** Not for
a variant, not for an experiment, not for a promising candidate. Until it ships, it is
lane material and it lives in `characters/work/`.
```
<character>[_<variant>...]_<stage>_<format>_v<NN>.<ext>
characters/
originals/ immutable delivered sources — never modified, never renamed
female/
lena_base_v01/ SHIPPED 2026-08-10 <- frozen
lena_base_v02/ the next one to ship
male/
mako_base_v01/ (none yet — mako has never shipped)
work/
lena/ the live lane: recipes + masters. Grammar-exempt.
mako/
rig-work/ disposable AccuRig carriers. Grammar-exempt.
archive/ superseded SHIPPED folders only
```
- **character** — registered name from the Characters table below (`lena`, `mako`).
New characters get a row here before their first file exists.
- **variant** — optional sculpt-level descriptors (`elder`, `muscular`). A new
variant is a new folder and a new Characters-table row.
- **stage** — exactly one token from the closed vocabulary below.
- **format** — file format tag matching the extension (`fbx`, `glb`).
- **v\<NN\>** — mandatory two-digit version, per-artifact.
### Folder grammar
```
<character>_base_v<NN>
```
`NN` is the **ship ordinal** for that character — the 1st shipped lena is
`lena_base_v01`, the 2nd is `lena_base_v02`. It counts ships, not attempts. Numbers
are monotonic and never reused, even if a ship is later rolled back.
This replaces the old variant-folder scheme (`lena`, `lena_nude`, `lena_elder`). A
nude body, an elder body and a re-sculpt are no longer three folders — they are
whichever of them actually ships, numbered in the order they ship. What distinguishes
them is recorded in the ship folder's README, not in its name.
### File grammar inside a ship folder
```
<character>_base_<stage>_<format>_v<NN>.<ext>
```
`NN` is **the folder's ship number**, repeated — not a per-artifact counter. Every
file in `lena_base_v01/` ends `_v01`. This is the one real break from the old law
(old rule 4: "version is per-artifact"), and it is deliberate: per-artifact counters
are what produced ten `decimated_glb_vNN` files sitting next to four
`sculpt_glb_vNN` files with no way to tell which went together. Now the version
identifies the ship, and everything carrying it belongs to that ship.
All lowercase snake_case, `[a-z0-9_]` only. No dates, no status words, no hyphens.
### Stage vocabulary (closed set — extending it is an edit to this table)
#### Stage vocabulary (closed set — extending it is an edit to this table)
| token | meaning |
|---|---|
| `sculpt` | unrigged full-res mesh (sculpt-level alteration) |
| `sculpt` | unrigged full-res mesh |
| `decimated` | post-decimation, unrigged |
| `tpose` | pose-prep export for a rigging tool |
| `accurig` | AccuRig output, CC_Base_* skeleton |
| `mixamo` | Mixamo-rigged (legacy lane) |
| `accurig` | AccuRig output / AccuRig-skeleton body |
| `quatskin` | 65-bone Quaternius UAL game skeleton |
| `lod<NN>` | LOD at NN% of tris (appended after `quatskin`) |
| `toned` | skin-tone pass (appended after `quatskin`) |
## The rules
### The rules
1. **Originals are read-only; delivered names immutable** — even misspelled or
inconsistent (`female_lena_tripo` keeps its missing `_v1`). Identity is recorded
in this registry, never fixed on disk. Renaming would fork identity vs the
name-synced mirror in `ariki-game/assets/models/characters/race-sources/` and
risk breaking FBX-internal texture paths.
2. Every new derived file matches the grammar above and starts with a registered
character name. Canonical male spelling is **`mako`** — the AccuRig-era `moka`
typo and `moko` are never used in new files.
3. Exactly one stage token per filename.
4. Version is per-artifact; **ancestry lives in this registry, never in the name**
(a `quatskin_v01` built from `decimated_v03` does not encode the parent's version).
5. **Status lives in this registry, never in a filename** — no `_candidate`,
1. **Originals are read-only; delivered names immutable** — even misspelled
(`female_lena_tripo` keeps its missing `_v1`). Identity is recorded here, never
fixed on disk: renaming would fork identity against the name-synced mirror in
`ariki-game/assets/models/characters/race-sources/` and can break FBX-internal
texture paths.
2. **A ship folder is frozen on ship.** Nothing inside it is ever edited, re-exported
or re-graded. A change of any kind means the next `_vNN` folder. This is what makes
the folder a truthful record of what the game actually has.
3. **A ship folder holds only what the ship needs**: the shipped body, the inputs that
cannot be regenerated by script, a `preview/` render set, and a README. Working
`.blend` files, QA renders, probes and superseded stages do not belong here — they
are lane material.
4. Exactly one stage token per filename.
5. **Ancestry lives in this registry and the folder README, never in a filename.**
6. **Status lives in this registry, never in a filename** — no `_candidate`,
`_final`, `_locked`.
6. New geometry identity = new character-variant folder (e.g. `female/lena_elder/`);
re-run of the same recipe = version bump. Test: "would the game treat it as a
different character/body?" → new variant; "a better attempt at the same thing?"
→ bump. Numbers never reused.
7. FBX or loose-texture models get a folder named exactly the model basename
(e.g. `mako_accurig_fbx_v01/`); loose textures inside are `<character>_<map>.<ext>`
(`mako_basecolor.jpg`) — no version chains in texture names. `.fbm/` folders and
Tripo UUID internals are never renamed.
8. **Boundary law:** snake_case = animation-repo working names; PascalCase `Ariki_*`
= game-repo ship names. The rename happens exactly once, at import into
`ariki-game/assets/quaternius/derived-bodies/`, and is recorded in the ship-name
column below. Never create `Ariki_*` files in this repo.
9. Superseded files move to `characters/archive/` — never deleted, never renamed.
10. Every new file under `characters/` (outside `archive/`) gets a row in the
Derived artifacts table before its first commit.
11. **`characters/rig-work/` is exempt from the grammar and from rule 10** — it
holds disposable AccuRig rig carriers ("baits"): decimated FBX copies whose
only job is to carry a skeleton out of AccuRig and back. They are never
shipped and never contribute geometry to a registered artifact (the graft
takes geometry from the GLB parent and only the skeleton from the bait), so
they get no row of their own — instead the grafted body's **notes** column
names the bait it got its skeleton from. Keeping their tool-era names is
deliberate: they must stay recognisable as throwaway. See that folder's
`README.md`.
7. FBX or loose-texture models get a folder named exactly the model basename;
loose textures inside are `<character>_<map>.<ext>` — no version chains in texture
names. `.fbm/` folders and Tripo UUID internals are never renamed.
8. **Boundary law:** snake_case = animation-repo working names; PascalCase `Ariki_*` =
game-repo ship names. The rename happens exactly once, at import into
`ariki-game/assets/quaternius/derived-bodies/`, and is recorded in the ship table
below. Never create `Ariki_*` files in this repo.
9. **A superseded ship folder moves to `characters/archive/` — never deleted.** This
applies to ship folders only. Lane material is governed by
`.agents/rules/working-files.md` and is deleted freely.
10. Every ship folder gets a row in the Ships table before its first commit.
11. **`characters/work/` and `characters/rig-work/` are exempt from the grammar and
from rule 10.** They hold recipes, masters and disposable rig carriers. See
`.agents/rules/working-files.md` and `rig-work/README.md`.
---
## Originals
@@ -90,16 +114,15 @@ Never modified, never renamed, never resaved in place. Verify with
| file | sha256 | delivered by | tool | date | identity note |
|---|---|---|---|---|---|
| `originals/male/male_base_bald_tripo_v1.fbx` | `f7c884bb8b8a71351b1448fa355658459a61a2537fdd48f9c72e52709693608c` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | mako generation 1, unrigged A-pose ~1.9M tris |
| `originals/male/male_base_bald_tripo_v1.glb` | `59df0de4b097428324f6b9048e0eeb7e8e034062aa8c8dad7e1eba1937be8aca` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | mako generation 1 |
| `originals/female/female_lena_tripo.fbx` | `c3a7cb1bf4958b27bd81b6bf73fd132c88f7a582d045c8936992d5d22bfd4e8e` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | lena generation 1 — delivered name has no `_v1`; treat as v1 |
| `originals/female/female_lena_tripo.glb` | `de17a1e7d56cefc3e07596bfb08efc65b41d04fac709c27b76500729e4fbb4b8` | Özlem | Tripo | 2026-08-04 (copied from ariki-game race-sources) | lena generation 1 |
| `originals/male/male_base_bald_tripo_v1.fbx` | `f7c884bb8b8a71351b1448fa355658459a61a2537fdd48f9c72e52709693608c` | Özlem | Tripo | 2026-08-04 | mako generation 1, unrigged A-pose ~1.9M tris |
| `originals/male/male_base_bald_tripo_v1.glb` | `59df0de4b097428324f6b9048e0eeb7e8e034062aa8c8dad7e1eba1937be8aca` | Özlem | Tripo | 2026-08-04 | mako generation 1 |
| `originals/female/female_lena_tripo.fbx` | `c3a7cb1bf4958b27bd81b6bf73fd132c88f7a582d045c8936992d5d22bfd4e8e` | Özlem | Tripo | 2026-08-04 | lena generation 1 — delivered name has no `_v1`; treat as v1 |
| `originals/female/female_lena_tripo.glb` | `de17a1e7d56cefc3e07596bfb08efc65b41d04fac709c27b76500729e4fbb4b8` | Özlem | Tripo | 2026-08-04 | lena generation 1 |
### Canonical-name aliases
Byte-identical copies of the originals under grammar-conforming names, so tools can
reference canonical names without touching the immutable delivered files. Same
read-only guard, same checksums as their sources above.
Byte-identical copies under grammar-conforming names, so tools can reference canonical
names without touching the immutable delivered files.
| file | byte-identical to | date |
|---|---|---|
@@ -110,23 +133,48 @@ read-only guard, same checksums as their sources above.
## Characters
| character | gender | parent original | description | status |
|---|---|---|---|---|
| `mako` | male | `male_base_bald_tripo_v1` | bald base male, Tripo sculpt. Spelling history: game wiki "Mako", AccuRig-era typo "moka" — canonical filename token is `mako` | active |
| `lena` | female | `female_lena_tripo` | base female, Tripo sculpt | active |
| `lena_nude` | female | `female_lena_tripo` | lena with the sculpted-in underwear removed: no bra, no briefs, breasts with volume and no nipples, featureless crotch. Separate variant rather than a version of `lena` because the game selects it as a different body (rule 6) | active |
| character | gender | parent original | ships so far |
|---|---|---|---|
| `lena` | female | `female_lena_tripo` | `lena_base_v01` |
| `mako` | male | `male_base_bald_tripo_v1` | none |
## Derived artifacts
Spelling history for the male: game wiki "Mako", AccuRig-era typo "moka", also "moko".
Canonical filename token is **`mako`**; the others are never used in new files.
Status vocabulary: `wip / candidate / approved / shipped / archived / rejected`.
---
| name | character | parent file | stage | tool/script | date | status | ship name | notes |
|---|---|---|---|---|---|---|---|---|
| `female/lena_nude/lena_nude_quatskin_glb_v01.glb` | `lena_nude` | game-side `Ariki_Female_QuatSkin.glb` (+ `Ariki_Female_QuatSkin_ChestV1.glb` as breast-shape donor) | `quatskin` | `tools/make_lena_nude_body.py` (`--size 1.0`) | 2026-08-04 | candidate | `Ariki_Female_QuatSkin_Nude.glb` (not yet released) | sha256 `931c0948a3b0201dbfbdd4d2671c3ae41688afc592a29e332bc2bab234aa1ad3`. Texture embedded, internal image name `lena_nude_basecolor` (no ship name baked inside a WIP asset). 31,670 v / 65 joints / 1.777 m; passes `tools/verify_body_variant.py` against the canonical body. Vertex count is below stock by design — the base mesh's open slits are welded shut, since the painted underwear had been hiding them. `--size` is breast volume; a different size is a version bump, not a new variant |
| `female/lena_nude/lena_nude_basecolor.png` | `lena_nude` | game-side `Ariki_Female_QuatSkin_Lena_Body_Toned.png` | — (texture) | `tools/_bake_nude_body_texture.py` | 2026-08-04 | candidate | `Ariki_Female_QuatSkin_Nude_Lena_Body_Toned_nude.png` (not yet released) | sha256 `576fcf614cfcb19befd5bd4fb57210b526db2754f7d35468b21252f31528c9f2`. 4096² albedo with bra and briefs inpainted out to uniform skin, no tan line. Loose copy for iteration; the shipped GLB embeds it. Unversioned per rule 7 |
| `female/lena_nude/lena_nude_sculpt_glb_v01.glb` | `lena_nude` | `originals/female/female_lena_tripo.glb` (hires, pre-decimation) + game-side `Ariki_Male_QuatBody_D_RigCandidate.glb` (crotch donor) | `sculpt` | `female/lena_nude/hires_claude/` pipeline (00 weld → 03 sculpt → 06 crotch+membrane → 05/05b texture+grade → 08 export); reference `female/lena_nude/reference_breasts.jpg` | 2026-08-05 | candidate | — (game asset derives via decimation later) | sha256 `cb781cf376dc046b108d1c00e2368e0ed7e74fde5a783bedc6fc435818e4b2c7`. 953,966 v / 1,907,931 tris / 0.9792 units tall, unrigged. Full-density nude: garment membrane-removed, C-cup round breasts (apex projection 0.040 units, cleavage gap 7 mm), no nipples; crotch = male-GLB donor + bi-harmonic gusset heal (featureless). Basecolor + normal + rm embedded; fabric repainted by 3D-nearest skin fill, rosy chest V graded out to uniform skin. Masters: `hires_claude/06_final.blend` (geometry), `07_polished.blend` (textured) |
| `female/lena_nude/lena_nude_sculpt_glb_v02.glb` | `lena_nude` | v01 geometry + `originals/female/female_lena_tripo.glb` textures | `sculpt` | `hires_claude/05_texture.py` in `patch` mode (garment texels only, mirror-averaged fill from her own skin) + `06f` fold melt; master `hires_claude/10_patch.blend` | 2026-08-05 | candidate | — | sha256 `4a8c811305352a5fc60a58f6146c6797de03131da56dbd4073f90580d2e59e9b`. Same nude geometry as v01 but ORIGINAL skin everywhere (rosy chest V, arm/leg tones kept); only the bra/briefs/strap/crotch texels are refilled. Jeremy's preference after v01's global tone grade changed too much of her look |
| `female/lena_nude/lena_nude_sculpt_glb_v04.glb` | `lena_nude` | `lena_nude_sculpt_glb_v03.glb` (master `hires_claude/29b_final.blend`) | `sculpt` | `hires_claude/33_bust_variants.py` (size study) → `34_apply_bust.py --k 0.5`; master `hires_claude/34_v04.blend` | 2026-08-06 | candidate | — | sha256 `88931098f27915f5a498a1bccc014e800af7e8fff3e1d43faf8b97616c307910`. 860,389 v / 1,721,805 tris, geometry identical to v03 except the bust. **Guided by the approved concept turnaround** (`exchange/INBOX/lena-base-turnaround`, GPT Image 2, approved by Can 2026-08-06): `32_ref_measure.py` compared reference silhouette against mesh and found proportions already agree — 6.43 vs 6.08 heads, waist/hip 0.504 vs 0.505, widths within 5% — with the bust the one real gap. Breast forms rescaled to 50% of their projection off a bi-harmonic chest wall, taking bust projection +0.0210 → **+0.0155 H against the reference's +0.0156**. 68,781 verts moved (max 27.8 mm, median 5.5 mm in-region), zero movement outside the chest region, height unchanged. Texture/UVs untouched from v03. Caveats: the projection metric saturates below k≈0.5 (k=0.35 also reads +0.0154) and the reference reading may be inflated by the T-pose arm crossing the chest in the side view, so 0.5 is a floor rather than a midpoint; front renders cannot distinguish sizes (frontal key light flattens the chest) — judge from the side |
| `female/lena_nude/lena_nude_decimated_glb_v01.glb` | `lena_nude` | `lena_nude_sculpt_glb_v03.glb` (master `hires_claude/29b_final.blend`) | `decimated` | `hires_claude/30_decimate.py` (COLLAPSE ratio **0.037728**, bisected to hit the vertex target) + `30b_fix_winding.py` | 2026-08-06 | candidate | — | sha256 `b11940bb46732ae0a60e1624e88e76fa48dcc3e3af49a6363348521bb7c52cc8`. 31,964 v / 64,957 tris in Blender (46,810 / 64,957 as exported, UV seams split verts) — 3.72% of the sculpt, matched to `lena_nude_quatskin_glb_v01.glb`'s 31,670 v at Jeremy's request. Unrigged, no skins, transforms applied; basecolor + normal + rm embedded. Height preserved to +0.12 mm, bbox unchanged. **Built as input for the re-atlas lane** (`24_seams.py`): collapse destroys Tripo's 5,870-chart UV layout, so the carried TEXCOORD_0 smears badly and is expected to be replaced, not reused. Residual: 25 backfacing tris of 64,957 (down from 61; the remainder sit in non-manifold knots where consistent-orientation oscillates), plus the sculpt's inherited 835 boundary / 1649 non-manifold edges |
| `female/lena_nude/lena_nude_sculpt_glb_v03.glb` | `lena_nude` | v02 chain head `hires_claude/10_welded.blend` | `sculpt` | `hires_claude/` 17 line heal → 26 median despeckle + cleavage fillet → 25 flipped-face repair → 29 on-body tone levelling → 08 export; masters `27_clean.blend` (geometry), `29b_final.blend` (textured) | 2026-08-06 | candidate | — | sha256 `7a8fe4c45d3af6acb7b39c2fac5fcc2aadf910f8d61a4a7d046911125a6e8baa`. 860,389 v / 1,721,805 tris. Jeremy's three v02 notes: (1) cut lines — reduced, not gone: shading kinks >12° 9704 → ~6900, ribcage/thigh lines gone frontally, waistband + underbust still legible under raking light; (2) discolouration — the pale bra/briefs panels are gone, on-body tone gap patch-vs-skin 0.0102 → 0.0068 luma; (3) cleavage — sternum fillet radius 9.5 mm → no sharp sample, notch depth 44 → 31 mm. Also 1511 → 10 flipped faces (the black dashes). Still open: 1649 non-manifold + 830 boundary edges (every fill refused them). See the lane README for the four dead ends this version ruled out |
| `female/lena_nude/lena_nude_decimated_glb_v02.glb` | `lena_nude` | `lena_nude_decimated_glb_v01.glb` (geometry byte-for-byte unchanged) | `decimated` | `hires_claude/24_seams.py` (anatomical seams + minimum-stretch unwrap) then `hires_claude/31_reatlas.py` (map resample) | 2026-08-06 | candidate | — | sha256 `cd3d53550470ae5a1958dc473ce11793f746b13f78ac911e6d426b6f7b9f5825`. **Re-atlas only — same 31,964 v / 64,957 tris, same shape, same material, unrigged.** Replaces Tripo's 5,870-chart atlas with 14 anatomical charts (torso, head, 2 arms, 2 legs, 2 hands ×2, 2 feet ×2): coverage 62.0→68.6%, texel density spread 1.8x→1.00x, per-triangle area-scale p95 2.55→1.77 / p99 4.25→2.27. Exported vertex count drops 46,810→42,980 because the new layout has far fewer UV seams. Maps resampled into the new layout at 4096², normal map rotated per face into its new tangent frame (p50 81°). Renders identical to v01: mean pixel \|Δ\| 0.0012, p99 0.0118 over five views (`hires_claude/33_ab.py`). Version bump, not a new variant, per rule 6 — same body, better atlas |
## Ships
Status vocabulary: `shipped / superseded / rolled-back`.
| folder | character | date | status | ship name in ariki-game | sha256 (repo-side body) |
|---|---|---|---|---|---|
| `female/lena_base_v01` | `lena` | 2026-08-10 | shipped | `Ariki_Female_QuatSkin_Nude.glb`, and swapped into `Ariki_Female_QuatSkin.glb` | `0d602accef0992a6a0dfffa5b73b1f9b702400df5258ff7517f786f11050f4d0` |
### `lena_base_v01` — what it is
Lena with the sculpted-in underwear removed (no bra, no briefs, breasts with volume
and no nipples, featureless crotch), athletic definition in the normal map, on the
65-bone Quaternius contract. She is now the game's female base body, not a side
variant — `Ariki_Female_QuatSkin.glb` was swapped to her on 2026-08-10, with the
previous body kept as `Ariki_Female_QuatSkin_PRESWAP_2026-08-10.glb.bak`.
Ancestry, condensed from the 20 intermediate stages that produced her (all pruned
2026-08-11; the recipes in `work/lena/` regenerate any of them from
`originals/female/female_lena_tripo.glb`):
| step | what happened |
|---|---|
| sculpt | garment membrane-removed, crotch healed with a male-GLB donor + bi-harmonic gusset, bust rescaled to match the concept turnaround approved by Can 2026-08-06 → 860,389 v |
| decimate | COLLAPSE 0.037728 → 31,964 v (3.72% of the sculpt) |
| re-atlas | Tripo's 5,870-chart UV soup replaced with 14 anatomical charts; maps resampled at 4096², normal map rotated per face into its new tangent frame |
| refill | breast/underwear texels re-authored by a harmonic solve with Dirichlet boundaries, so the fill equals her real skin at the mask edge — no ghost outline |
| despeckle | two defects, not one: 892 folded edges collapsed/dissolved (geometry) + 44k local-contrast texels inpainted (Tripo scan noise) → 31,111 v |
| musculature | anatomy into the NORMAL map only, geometry untouched; 3D-space gradient projected into per-texel tangent frames so the back midline seam does not tear |
| rig | AccuRig 1.10.1822.1, manual. 101 CC_Base_* bones. Vertex order preserved, so weights grafted **index-exact**, not nearest-surface — this is what avoided the mangled fingers of the July lane |
| quatskin | `ariki-game/tools/make_lena_nude_quatskin_accurig.py` → 65 joints, 1.777 m, rigid-mitt fingers |
**Departure from the approved concept:** the turnaround Can approved is a smooth
mannequin with no muscle definition. The shipped body has athletic definition
(`42_musculature.py` strength 1.0, `SPINE=0.34`). Flagged here so it is a known
decision rather than a drift.
+42
View File
@@ -0,0 +1,42 @@
# lena_base_v01 — SHIPPED 2026-08-10
**Frozen.** Nothing in this folder is ever edited or re-exported (REGISTRY rule 2).
Any change at all becomes `lena_base_v02`.
The game's female base body. Nude sculpt (no bra, no briefs, no nipples, featureless
crotch), athletic definition in the normal map, 65-bone Quaternius skeleton.
## Contents
| file | what |
|---|---|
| `lena_base_accurig_glb_v01.glb` | the body as it left this repo — mesh + atlas + AccuRig skeleton, 31,111 v / 101 joints. `doubleSided=true` |
| `rig/lena_base_accurig_fbx_v01/` | **irreplaceable.** Output of a manual AccuRig 1.10.1822.1 session — no script regenerates this. Settings JSON kept alongside as provenance |
| `rig/lena_base_tpose_fbx_v01.fbx` | the carrier fed *into* that AccuRig session. Regenerable (`45_accurig_export.py`) but kept so the rig is reproducible end to end |
| `preview/` | renders of the shipped body |
## Where it went
Converted to the 65-bone contract by `ariki-game/tools/make_lena_nude_quatskin_accurig.py`
and imported as:
- `Ariki_Female_QuatSkin_Nude.glb` — sha256 `75a51668fe6f084b60995fd257afd2ee8e4e6317d1676ff793675ca018f237b0`
- `Ariki_Female_QuatSkin.glb` — same body, swapped in on 2026-08-10. The body it
replaced is kept as `Ariki_Female_QuatSkin_PRESWAP_2026-08-10.glb.bak`.
Textures ship loose as `Ariki_Female_QuatSkin_Nude_athletic_v04_{base,normal,rm}.jpg`.
## Two facts that bite
- **`doubleSided=true` is load-bearing.** Patches of the mesh are wound inward; a
single-sided material culls them into grey holes across her face and underbust.
- **Two corrections live in the graft**, both solved by least-squares fit rather than
hardcoded: the T-pose carrier was exported at 1.700 m (scale 0.576058) and AccuRig
shifted the body ~11.77 mm. Same family as the July lane's 4 cm offset trap.
## Rebuilding / continuing
Recipes and masters are in `characters/work/lena/`. Start from
`masters/athletic_v04.blend` (the game-res head, 31,111 v) or `masters/34_v04.blend`
(the full-res sculpt head, 860,389 v). Full stage-by-stage history including the dead
ends is in `work/lena/README_lane_history.md`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

@@ -0,0 +1,429 @@
{
"rename this": {
"Version": "1.10.1822.1",
"Scene": {
"Name": true,
"SupportShaderSelect": true
},
"Export Directory": "C:/Users/Jeremy/tinqs/animation/characters/female/lena_nude/rig",
"Object": {
"rename this": {
"Generation": "AccuRig",
"Physics": {
"Collision Shapes": {
"RL_BoneRoot": {},
"CC_Base_Hip": {},
"CC_Base_Pelvis": {},
"CC_Base_L_Thigh": {},
"CC_Base_L_Calf": {},
"CC_Base_L_Foot": {},
"CC_Base_L_ToeBaseShareBone": {},
"CC_Base_L_ToeBase": {},
"CC_Base_L_PinkyToe1": {},
"CC_Base_L_RingToe1": {},
"CC_Base_L_MidToe1": {},
"CC_Base_L_IndexToe1": {},
"CC_Base_L_BigToe1": {},
"CC_Base_L_CalfTwist01": {},
"CC_Base_L_CalfTwist02": {},
"CC_Base_L_KneeShareBone": {},
"CC_Base_L_ThighTwist01": {},
"CC_Base_L_ThighTwist02": {},
"CC_Base_R_Thigh": {},
"CC_Base_R_ThighTwist01": {},
"CC_Base_R_ThighTwist02": {},
"CC_Base_R_Calf": {},
"CC_Base_R_Foot": {},
"CC_Base_R_ToeBase": {},
"CC_Base_R_PinkyToe1": {},
"CC_Base_R_BigToe1": {},
"CC_Base_R_IndexToe1": {},
"CC_Base_R_MidToe1": {},
"CC_Base_R_RingToe1": {},
"CC_Base_R_ToeBaseShareBone": {},
"CC_Base_R_KneeShareBone": {},
"CC_Base_R_CalfTwist01": {},
"CC_Base_R_CalfTwist02": {},
"CC_Base_Waist": {},
"CC_Base_Spine01": {},
"CC_Base_Spine02": {},
"CC_Base_NeckTwist01": {},
"CC_Base_NeckTwist02": {},
"CC_Base_Head": {},
"CC_Base_FacialBone": {},
"CC_Base_JawRoot": {},
"CC_Base_Tongue01": {},
"CC_Base_Tongue02": {},
"CC_Base_Tongue03": {},
"CC_Base_Teeth02": {},
"CC_Base_R_Eye": {},
"CC_Base_L_Eye": {},
"CC_Base_UpperJaw": {},
"CC_Base_Teeth01": {},
"CC_Base_L_Clavicle": {},
"CC_Base_L_Upperarm": {},
"CC_Base_L_Forearm": {},
"CC_Base_L_ForearmTwist01": {},
"CC_Base_L_ForearmTwist02": {},
"CC_Base_L_ElbowShareBone": {},
"CC_Base_L_Hand": {},
"CC_Base_L_Mid1": {},
"CC_Base_L_Mid2": {},
"CC_Base_L_Mid3": {},
"CC_Base_L_Index1": {},
"CC_Base_L_Index2": {},
"CC_Base_L_Index3": {},
"CC_Base_L_Ring1": {},
"CC_Base_L_Ring2": {},
"CC_Base_L_Ring3": {},
"CC_Base_L_Pinky1": {},
"CC_Base_L_Pinky2": {},
"CC_Base_L_Pinky3": {},
"CC_Base_L_Thumb1": {},
"CC_Base_L_Thumb2": {},
"CC_Base_L_Thumb3": {},
"CC_Base_L_UpperarmTwist01": {},
"CC_Base_L_UpperarmTwist02": {},
"CC_Base_R_Clavicle": {},
"CC_Base_R_Upperarm": {},
"CC_Base_R_UpperarmTwist01": {},
"CC_Base_R_UpperarmTwist02": {},
"CC_Base_R_Forearm": {},
"CC_Base_R_ForearmTwist01": {},
"CC_Base_R_ForearmTwist02": {},
"CC_Base_R_ElbowShareBone": {},
"CC_Base_R_Hand": {},
"CC_Base_R_Mid1": {},
"CC_Base_R_Mid2": {},
"CC_Base_R_Mid3": {},
"CC_Base_R_Ring1": {},
"CC_Base_R_Ring2": {},
"CC_Base_R_Ring3": {},
"CC_Base_R_Thumb1": {},
"CC_Base_R_Thumb2": {},
"CC_Base_R_Thumb3": {},
"CC_Base_R_Index1": {},
"CC_Base_R_Index2": {},
"CC_Base_R_Index3": {},
"CC_Base_R_Pinky1": {},
"CC_Base_R_Pinky2": {},
"CC_Base_R_Pinky3": {},
"CC_Base_R_RibsTwist": {},
"CC_Base_R_Breast": {},
"CC_Base_L_RibsTwist": {},
"CC_Base_L_Breast": {},
"meshes_0_": {}
}
},
"ExpressionSet": "4",
"Expression": {
"V_None": {
"Bones": {}
},
"V_Open": {
"Bones": {}
},
"V_Explosive": {
"Bones": {}
},
"V_Dental_Lip": {
"Bones": {}
},
"V_Tight_O": {
"Bones": {}
},
"V_Tight": {
"Bones": {}
},
"V_Wide": {
"Bones": {}
},
"V_Affricate": {
"Bones": {}
},
"V_Lip_Open": {
"Bones": {}
},
"V_Tongue_up": {
"Bones": {}
},
"V_Tongue_Raise": {
"Bones": {}
},
"V_Tongue_Out": {
"Bones": {}
},
"V_Tongue_Narrow": {
"Bones": {}
},
"V_Tongue_Lower": {
"Bones": {}
},
"V_Tongue_Curl_U": {
"Bones": {}
},
"V_Tongue_Curl_D": {
"Bones": {}
},
"Brow_Raise_Inner_L": {
"Bones": {}
},
"Brow_Raise_Inner_R": {
"Bones": {}
},
"Brow_Raise_Outer_L": {
"Bones": {}
},
"Brow_Raise_Outer_R": {
"Bones": {}
},
"Brow_Drop_L": {
"Bones": {}
},
"Brow_Drop_R": {
"Bones": {}
},
"Nose_Sneer_L": {
"Bones": {}
},
"Nose_Sneer_R": {
"Bones": {}
},
"Cheek_Raise_L": {
"Bones": {}
},
"Cheek_Raise_R": {
"Bones": {}
},
"Cheek_Puff_L": {
"Bones": {}
},
"Cheek_Puff_R": {
"Bones": {}
},
"Head_Turn_Up": {
"Bones": {}
},
"Head_Turn_Down": {
"Bones": {}
},
"Head_Turn_L": {
"Bones": {}
},
"Head_Turn_R": {
"Bones": {}
},
"Head_Tilt_L": {
"Bones": {}
},
"Head_Tilt_R": {
"Bones": {}
},
"Head_L": {
"Bones": {}
},
"Head_R": {
"Bones": {}
},
"Head_Forward": {
"Bones": {}
},
"Head_Backward": {
"Bones": {}
},
"Jaw_Open": {
"Bones": {}
},
"Jaw_Forward": {
"Bones": {}
},
"Jaw_L": {
"Bones": {}
},
"Jaw_R": {
"Bones": {}
},
"Tongue_Out": {
"Bones": {}
},
"Tongue_Up": {
"Bones": {}
},
"Tongue_Down": {
"Bones": {}
},
"Tongue_Tip_Up": {
"Bones": {}
},
"Tongue_Tip_Down": {
"Bones": {}
},
"Tongue_Narrow": {
"Bones": {}
},
"Tongue_Wide": {
"Bones": {}
},
"Tongue_Roll": {
"Bones": {}
},
"Tongue_L": {
"Bones": {}
},
"Tongue_R": {
"Bones": {}
},
"Tongue_Bulge_L": {
"Bones": {}
},
"Tongue_Bulge_R": {
"Bones": {}
},
"Eye_Blink_L": {
"Bones": {}
},
"Eye_Blink_R": {
"Bones": {}
},
"Eye_Squint_L": {
"Bones": {}
},
"Eye_Squint_R": {
"Bones": {}
},
"Eye_Wide_L": {
"Bones": {}
},
"Eye_Wide_R": {
"Bones": {}
},
"Eye_L_Look_L": {
"Bones": {}
},
"Eye_R_Look_L": {
"Bones": {}
},
"Eye_L_Look_R": {
"Bones": {}
},
"Eye_R_Look_R": {
"Bones": {}
},
"Eye_L_Look_Up": {
"Bones": {}
},
"Eye_R_Look_Up": {
"Bones": {}
},
"Eye_L_Look_Down": {
"Bones": {}
},
"Eye_R_Look_Down": {
"Bones": {}
},
"Mouth_Smile_L": {
"Bones": {}
},
"Mouth_Smile_R": {
"Bones": {}
},
"Mouth_Frown_L": {
"Bones": {}
},
"Mouth_Frown_R": {
"Bones": {}
},
"Mouth_Stretch_L": {
"Bones": {}
},
"Mouth_Stretch_R": {
"Bones": {}
},
"Mouth_Dimple_L": {
"Bones": {}
},
"Mouth_Dimple_R": {
"Bones": {}
},
"Mouth_Press_L": {
"Bones": {}
},
"Mouth_Press_R": {
"Bones": {}
},
"Mouth_Pucker": {
"Bones": {}
},
"Mouth_Funnel": {
"Bones": {}
},
"Mouth_Roll_In_Upper": {
"Bones": {}
},
"Mouth_Roll_In_Lower": {
"Bones": {}
},
"Mouth_L": {
"Bones": {}
},
"Mouth_R": {
"Bones": {}
},
"Mouth_Shrug_Upper": {
"Bones": {}
},
"Mouth_Shrug_Lower": {
"Bones": {}
},
"Mouth_Up_Upper_L": {
"Bones": {}
},
"Mouth_Up_Upper_R": {
"Bones": {}
},
"Mouth_Down_Lower_L": {
"Bones": {}
},
"Mouth_Down_Lower_R": {
"Bones": {}
},
"Mouth_Close": {
"Bones": {}
}
},
"Constraint": {},
"Meshes": {
"meshes_0_": {
"SubD Level": 0,
"Materials": {
"Default": {
"Material Type": "Pbr",
"MultiUV Index": 0,
"Two Side": true,
"Diffuse Color": [
255.0,
255.0,
255.0
],
"Ambient Color": [
255.0,
255.0,
255.0
],
"Specular Color": [
255.0,
255.0,
255.0
],
"Opacity": 1.0,
"Self Illumination": 0.0,
"Textures": {},
"Resource Textures": {}
}
}
}
}
}
}
}
}
@@ -1,61 +0,0 @@
# Handoff: decimated nude Lena → retexture / re-atlas lane
**2026-08-06.** Jeremy asked for the healed nude Lena decimated to the game-body budget and passed
to the retexture / re-atlas work, "to see how they do".
## The file
`characters/female/lena_nude/lena_nude_decimated_glb_v01.glb`
| | |
|---|---|
| sha256 | `b11940bb46732ae0a60e1624e88e76fa48dcc3e3af49a6363348521bb7c52cc8` |
| parent | `lena_nude_sculpt_glb_v03.glb` (master `hires_claude/29b_final.blend`) |
| recipe | `hires_claude/30_decimate.py` then `30b_fix_winding.py` |
| ratio | Decimate **COLLAPSE 0.037728** — bisected, not guessed |
| density | 31,964 v / 64,957 tris in Blender (46,810 v as exported; UV seams split verts) |
| why that density | matched to the shipped `lena_nude_quatskin_glb_v01.glb` (31,670 v), Jeremy's call |
| rig | none — unrigged, no skins, transforms applied |
| maps | basecolor + normal + rm embedded (3 images, wired baseColor / metallicRoughness / normal) |
Ready to load directly: `24_seams.py` accepts a `.glb` as its first argument and applies transforms
itself, so this drops in with no preparation.
## Read this before you spend a pass on the UVs
**The carried `TEXCOORD_0` is not worth preserving.** Collapse decimation shreds Tripo's
5,870-chart atlas — in the textured review render (`hires_claude/review_30/chest_tex_0.png`) the
transplanted skin grain smears into streaks and the throat picks up a red blotch. That is expected
and is exactly the problem the ~14-chart anatomical atlas solves. Judge this mesh from
`review_30/full_clay_0.png` (clay, no material) and treat the UVs as scrap.
**Decimation improved one thing.** The waistband/underbust lines that survive on the hires sculpt
are nearly invisible at 32k — the collapse averaged them out. So the residual line problem is a
hires-only concern; don't go hunting for it here.
## Known defects being handed over
- **25 backfacing triangles** of 64,957. Down from 61 via `30b_fix_winding.py`, but the remainder
sit in non-manifold knots where `normals_make_consistent` oscillates (61 → 260 → 39 → 25 → 25).
They render black. If your pass rebuilds topology, they will likely vanish for free.
- **835 boundary edges / 1649 non-manifold edges**, inherited from the sculpt. Every fill attempt
refused them (`bmesh.ops.holes_fill`, edit-mode `mesh.fill_holes`, and a per-loop pass) — they
are dangling flaps and slivers, not perforations. Documented in the lane README.
- **The head is untouched original Tripo output.** Every geometry pass in this lane stopped below
the chin (z < 0.90) on purpose, so lips/nostrils/eyes still have the source smearing. If the
retexture covers the face, that is new ground, not a regression.
- **She reads glossy** — the `rm` map was neutralised to a median over the old garment texels, so
the whole body is shinier than skin should be. Untouched deliberately: an earlier attempt at
correcting roughness made her plasticky (see the lane README's fourth dead end).
## Provenance the three fixes came from
v03 addressed Jeremy's three notes on v02 — cut lines, discoloured bra/crotch patches, and the
cleavage. Measured outcomes and the four dead ends that were ruled out on the way are in
`README.md` under "Hi-res sculpt v03". Worth skimming before re-treading the geometry.
## Numbering note
This lane had two agents working in it simultaneously on 2026-08-06. `20`/`21` exist twice under
different filenames (`20_atlas_probe.py` + `20_cleavage.py`, `21_seam_probe.py` + `21_tone.py`).
Nothing was overwritten, but pick a fresh number rather than assuming the next one is free.
@@ -1,87 +0,0 @@
# Handoff: re-atlased nude Lena — `lena_nude_decimated_glb_v02.glb`
**2026-08-06.** Jeremy asked for the decimated nude body re-atlased and versioned. This is a
**UV + texture change only**: the geometry is byte-for-byte the v01 mesh.
## The file
`characters/female/lena_nude/lena_nude_decimated_glb_v02.glb`
| | |
|---|---|
| sha256 | `cd3d53550470ae5a1958dc473ce11793f746b13f78ac911e6d426b6f7b9f5825` |
| parent | `lena_nude_decimated_glb_v01.glb` |
| recipe | `hires_claude/24_seams.py``hires_claude/31_reatlas.py` |
| mesh | 31,964 v / 64,957 tris in Blender — **identical to v01** |
| exported verts | 42,980 (v01: 46,810) — the new layout needs fewer seam splits |
| maps | basecolor + normal + rm embedded, 4096², JPEG q95, correct colorspaces |
| rig | none — unrigged, transforms applied |
Rebuild:
```
blender --background --python 24_seams.py -- ../lena_nude_decimated_glb_v01.glb atlas_v02
blender --background --python 31_reatlas.py -- atlas_v02/seamed.blend atlas_v02 \
../lena_nude_decimated_glb_v02.glb
```
## What changed
| | Tripo atlas (v01) | new atlas (v02) |
|---|---|---|
| UV islands | 5,870 | 968 |
| charts holding 99% of the area | 84 | **14** |
| atlas coverage | 62.0% | **68.6%** |
| texel-density spread | 1.8× | **1.00×** |
| per-triangle area-scale p95 / p99 | — | 1.77 / 2.27 |
The 14 charts are torso, head, two arms, two legs, two hands (back + palm each) and two feet
(upper + sole each). Seams run up the back midline, the back of each arm and the inner leg, with
rings at neck, shoulder, wrist, hip and ankle — every landmark measured off the mesh's own
cross-section radius profiles, so the rules port to any density.
**It looks the same, on purpose.** Five matched views diff at mean \|Δ\| 0.0012, p99 0.0118, with
0.19% of body pixels over 0.05 (`33_ab.py`). Her original tones are untouched — this pass bought a
paintable, uniform-density layout, not a new look.
## Three traps this pass hit, so you don't
1. **Colorspace is not cosmetic.** `image.pixels` returns *linear* values for an sRGB image and
*raw* values for a Non-Color one, and re-encodes the same way on save. The normal and rm maps
are Non-Color; writing them into default (sRGB) images gamma-encoded them, so 0.5 came back as
0.21 — every stored normal became a large false perturbation and she rendered dark and
wet-plastic. New maps must inherit `colorspace_settings.name` from their source.
2. **Use the minimum-stretch solver, not angle-based.** ABF is conformal: it preserves angles and
is free to crush area. At her folds it squashed 4,502 triangles (3.7% of her surface) below one
texel, and they came out untextured. `method='MINIMUM_STRETCH'` took per-triangle area-scale p05
from 0.03 to 0.33 and the untextured count from 4,502 to 569 (0.56%, all genuine sub-texel
slivers absorbed by the 16 px padding pass).
3. **Watch the LOW tail of the distortion stat.** p95/p99 measure stretching; it was p05 = 0.03 —
crushing — that was losing whole patches. I read past it twice.
Also tried and rejected: splitting the 1,649 non-manifold edges before unwrapping. It cost +3,512
verts and made the collapsed-face count *worse* (4,502 → 5,482), because the collapse was a solver
problem, not a topology one.
## Still broken, and not from this pass
**The ragged grey tears on the face, across the underbust, and as flecks down the arms and shins
are in v01 already** — I rendered v01 through the same script to confirm, and they are identical in
position and shape (`hires_claude/beauty_input_v01/` vs `beauty_atlas_v02/`). They are the 835
boundary edges / dangling flaps the v01 handoff lists as a known defect. A re-atlas cannot fix
geometry. They need mesh surgery before this body ships.
The other v01 defects carry over untouched: 25 backfacing triangles, the head still being original
Tripo output, and the glossy `rm` map.
## What this does NOT do
Jeremy's stated goal is to **keep her original texture and replace only the breast and underwear
region**. This pass deliberately does not touch colour — it only rehouses it. The re-authoring step
(a gradient-domain fill pinned to her surrounding skin at the boundary, so the old bra/briefs
outline cannot ghost) is the next stage, and it is much easier now that the charts are anatomical
and uniform-density.
Note that keeping her original texture keeps its known incoherence: her feet read 0.11 luma darker
and 50% redder than her belly, and the rosy chest V remains. Measured in
`hires_claude/21_seam_probe.py`; see the `lena-tripo-atlas-chart-soup` note.
-195
View File
@@ -1,195 +0,0 @@
# lena_nude — nude body variant (WIP, not released)
Lena's body with the underwear removed: no bra, no briefs, breasts with volume and **no
nipples**, smooth featureless crotch. Staged here until it is approved for the game.
| file | role |
|---|---|
| `lena_nude_quatskin_glb_v01.glb` | the body — 65-bone Quaternius game skeleton, texture **embedded** (self-contained) |
| `lena_nude_basecolor.png` | the baked albedo, loose, for further iteration |
Ship name at release is `Ariki_Female_QuatSkin_Nude.glb` in
`ariki-game/assets/quaternius/derived-bodies/`. Per `../../REGISTRY.md` rule 8 that rename happens
exactly once, on import — never create `Ariki_*` files in this repo.
## How it was made
Generators live in **this** repo (moved out of ariki-game 2026-08-06 — they author a character,
so `../../REGISTRY.md` governs them; see `.agents/wiki/ARCHITECTURE.md` "Tool provenance"). They
still *read* the canonical rigged bodies from the game repo (`Ariki_Female_QuatSkin.glb` and the
`_ChestV1` breast-sculpt donor), resolved via `$ARIKI_GAME` or a sibling `ariki-game/` checkout —
if neither resolves they abort with the path they tried, rather than reading the wrong file.
Run from `C:\Users\Jeremy\tinqs\animation`:
```
blender --background --python tools/_bake_nude_body_texture.py
# defaults to writing lena_nude_basecolor.png into THIS folder
blender --background --python tools/make_lena_nude_body.py -- \
--out characters/female/lena_nude/lena_nude_quatskin_glb_v01.glb \
--size 1.0 \
--texture characters/female/lena_nude/lena_nude_basecolor.png
python tools/verify_body_variant.py \
characters/female/lena_nude/lena_nude_quatskin_glb_v01.glb
```
`--size` is breast volume as a multiple of the `_ChestV1` sculpt (0.75 / 1.0 / 1.25 were
rendered for review; **v01 ships 1.0**). A new size is a **version bump**, not a new variant —
same recipe, different parameter. (This README and `../../REGISTRY.md` both called the flag
`--scale` until 2026-08-06; the script has always parsed `--size`.)
## Facts worth knowing before editing this
- **The underwear is the body surface.** A ray-crossing census finds exactly two surface crossings
at every height — thigh, belly, bra cup, briefs alike. There is no skin underneath, so the
garment can only be flattened into the skin, never deleted. Deleting it opens a hole, which is
precisely the defect in the game repo's `Ariki_Female_QuatSkin_Bare.glb` (black cavity across
the chest, visible even untextured). Do not use `_Bare` geometry for anything.
- **The base mesh is not watertight** (3,809 boundary edges). The painted underwear was hiding
open slits; with it gone they read as a dotted line of black triangles, so the builder welds
them shut in the torso band. That is where this file's lower vertex count comes from.
- **Use clay renders to judge geometry.** `tools/_render_body_closeup.py --clay` drops the
material; it is the only reliable way to tell a mesh defect from a painted one. Several dead
ends here came from mistaking painted bra shadow for a dent, and vice versa.
## Working files
This lane follows `.agents/rules/working-files.md`: **a version is a milestone,
not a step.** Fixing the bra produces one saved file at the end, not one per cut.
`hires_claude/.lanekeep` pins the only `.blend` files that survive here — the
masters named in the Derived-artifacts table of `../../REGISTRY.md`, plus the
live chain head. Everything else a step script wrote is scratch: the `NN_*.py`
recipe plus `masks.npz` regenerates it from the master above it.
```
python tools/prune_lane.py characters/female/lena_nude --recursive # dry run
```
Two things this lane learned the hard way:
- **Check for a running Blender before pruning.** A heal step takes ~7 minutes
and writes only at the end; the lane gained a new head (`17_healed.blend`)
during the audit that produced this section.
- `hires_work/` is the abandoned GLM-agent attempt (see its
`glm_run_attempt*.log`), superseded by `hires_claude/`. It has no `.lanekeep`,
so every `.blend` in it classifies as scratch.
The root-level `lena_sculpt_glb_v01-nude-Zephyr.blend` is hand-authored — no
step script rebuilds it — so the pruner reports it as UNKNOWN and never touches
it. Decide its fate by hand.
## Status
Verified against the canonical body: 1 primitive, identical attribute set (no tangents), material
`MI_Body_Lena`, 65 joints in identical order, height 1.777 m, inverse-bind drift 4.6e-6, weights
normalised, texture embedded. Posed sweep under a UAL clip is clean (no spikes or tearing).
Known cosmetic limits: a faint tonal seam where the fill meets untouched skin at extreme zoom, and
no skin-pore detail inside the filled area — that smoothness is also what guarantees no nipple or
crotch detail can appear.
Full history and the reasoning behind each choice: `~/.claude/plans/i-have-a-lena-velvet-ripple.md`.
## v04 — bust matched to the approved concept turnaround (2026-08-06)
`lena_nude_sculpt_glb_v04.glb`, master `hires_claude/34_v04.blend`. Recipes
`32_ref_measure.py` (measure) → `33_bust_variants.py` (size study) → `34_apply_bust.py` (k=0.5).
Guiding art: `exchange/INBOX/lena-base-turnaround/` — front/side/back T-pose renders, GPT Image 2,
approved by Can 2026-08-06.
**The reference is the same character, not a new one.** Measured against the front/side silhouettes:
| landmark | reference | v03 mesh |
|---|---|---|
| heads tall | 6.43 | 6.08 |
| waist / hip | 0.504 | 0.505 |
| waist width | 0.1260 H | 0.1318 H |
| hip width | 0.2500 H | 0.2608 H |
| **bust projection** | **+0.0156 H** | **+0.0210 H** |
Proportions already agree and the widths sit inside silhouette-threshold noise for generated art, so
only the bust was changed: the breast forms are rescaled to 50% of their projection off a
bi-harmonic chest wall, landing at +0.0155 H. Nothing outside the chest region moves (verified 0.0000 mm)
and height is unchanged, so every v03 heal survives.
Two traps for whoever tunes this next:
- **The projection metric saturates below k≈0.5** — k=0.35 measures +0.0154, indistinguishable from
k=0.5. Below that the "deepest slice" row migrates and stops tracking breast volume, so the number
cannot be pushed lower meaningfully. Treat 0.5 as a floor, not a midpoint. The reference reading may
also be inflated by the T-pose arm crossing the chest in a side view, which would mean the real
reference bust is smaller still.
- **Front renders cannot tell the sizes apart.** k=0.5, 0.65 and 0.8 look identical head-on because
the frontal key light flattens the chest. Judge from the side view only. Variants are in
`hires_claude/review_33/k{100,080,065,050,035}/`.
Re-running at another k takes seconds: `_bust_wall_cache.npy` holds the 8-minute chest-wall solve.
Not addressed, and the largest remaining gap to the concept art: the reference is a **smooth
mannequin** with no scan noise, while this mesh still carries speckles and dents on the hips and
thighs, plus the untouched original head.
## Decimated for the retexture / re-atlas lane (2026-08-06)
`lena_nude_decimated_glb_v01.glb` — v03 collapsed to the game-body budget (31,964 v / 64,957 tris,
ratio 0.037728) as input for `hires_claude/24_seams.py`. Recipe `30_decimate.py` + `30b_fix_winding.py`.
**Read `HANDOFF_decimated_v01.md` before working on it** — in particular, its carried UVs are scrap
(collapse shreds Tripo's 5,870-chart atlas) and it ships with 25 known backfacing triangles.
## Re-atlased (2026-08-06)
`lena_nude_decimated_glb_v02.glb`**v01's geometry with a new UV atlas and resampled maps.**
Same 31,964 v / 64,957 tris; Tripo's 5,870 charts replaced by 14 anatomical ones (coverage
62.0 → 68.6%, texel-density spread 1.8× → 1.00×). Recipe `hires_claude/24_seams.py`
`hires_claude/31_reatlas.py`. It deliberately **looks identical** to v01 (mean pixel |Δ| 0.0012
across five views) — the win is a paintable, uniform-density layout, not a new look.
**Read `HANDOFF_reatlas_v02.md`**, especially the colorspace and unwrap-solver traps, and note that
the grey tears on the face/underbust are v01's dangling-flap defect and are still there.
## Hi-res sculpt v03 (2026-08-06) — Jeremy's three notes on v02
`lena_nude_sculpt_glb_v03.glb`, masters `hires_claude/27_clean.blend` (geometry) and
`29b_final.blend` (textured). Recipes: `17_line_heal.py``26_finish.py`
`25_speckle.py --apply``29_tone3d.py``08_export.py`.
| note | measured outcome |
|---|---|
| cut lines | shading kinks >12° 9704 → ~6900; flipped faces (the black dashes) 1511 → 10. Ribcage and thigh lines gone frontally; waistband + underbust still legible under raking light |
| discoloured bra/crotch | on-body tone gap, patch interior vs surrounding skin: 0.0102 → 0.0068 luma. The pale panels in `beauty_v02_healed/` are gone in `beauty_v03b/` |
| cleavage | sternum fillet radius 9.5 mm → no sharp sample at any height; notch depth 44 → 31 mm |
### Four dead ends this version ruled out — do not repeat them
1. **The lines are not cracks; do not weld.** Stage 15 assumed the panel seams were disconnected
vertex runs. They are not: the mesh has only 830 boundary edges in 689 loops of 35 edges, and
for kink verts the nearest vertex outside the 3-ring sits at 1.58× the local edge length
(median) against 1.73× for control skin. Welding at eps 1.2 mm — about the 1.85 mm mean edge
length — merged ordinary neighbours and tore the mesh, 830 → 24k boundary edges.
2. **They are not in the custom normals either.** Corner-vs-vertex deviation is 0.008° mean, 15
loops above 5°; clearing custom split normals changes nothing.
3. **A membrane is the wrong operator, and widening it is worse.** The relief is ONE VERTEX WIDE
(0.23 mm median at the centre, back to the 0.074 mm background by ring 2), so a collar-fixed
membrane pins itself to the defect's own shoulders. GROW 4 and 8 were no better than GROW 2
while moving material up to 38 mm. A **ring median** is correct: it deletes a 1-vertex outlier
and returns anything broader unchanged, so the underbust fold, clavicles and navel survive by
construction instead of by a tuned threshold.
4. **Never level tone from atlas-adjacent texels.** A Poisson solve whose boundary condition was
the mismatch against UV neighbours turned the patches into pale grey panels — UV adjacency is
not body adjacency, so the border mismatch came from other body parts and gutter. This is the
warning already in `05_texture.py`'s header. Sample on the body (KD over skin verts in 3D),
smooth the correction over the mesh graph, then rasterise.
Two process traps worth keeping: **restore the real material before saving**`18/20/25`
originally rendered clay last and then saved, so the file on disk kept the clay material and lost
its texture wiring, which is why the first tone attempt aborted with "could not find the wired
basecolor". And **the pristine textures are not inside working blends**: Blender purges the orphan
`.002/.003` copies on save, so read them from `00_welded.blend`.
Still open: 1649 non-manifold and 830 boundary edges — `bmesh.ops.holes_fill`, the edit-mode
`mesh.fill_holes` operator, and a per-loop pass all refused every one, which is why
`normals_make_consistent` could only reach 1198 of 1511 flipped faces before explicit per-face
reversal finished the job. None of this is propagated to the game body
(`lena_nude_quatskin_glb_v01.glb`), which is built independently on the 65-bone Quaternius rig.
@@ -1,36 +0,0 @@
# .lanekeep — .blend files pinned as MASTER in this lane.
# Everything else here that a step script wrote is scratch: regenerable by
# re-running the NN_*.py recipe from the pinned master above it.
# Read by tools/prune_lane.py. One filename per line, # for comments.
#
# Rule: a .blend earns a line here only when a registered artifact in
# characters/REGISTRY.md was built from it, or it is the head of the live chain.
# Intermediate attempts (the 06b-06h melt series, the 10_healed/rimheal/seamheal
# series) never get pinned — only the state at the end of the fix.
# --- phase roots ---
00_welded.blend # raw original, slits welded shut. Root of every branch;
# keeps a re-sculpt from needing a fresh weld+isolate pass.
# --- masters of registered artifacts (see characters/REGISTRY.md) ---
06_final.blend # lena_nude_sculpt_glb_v01.glb — geometry master
07_polished.blend # lena_nude_sculpt_glb_v01.glb — textured master
10_patch.blend # lena_nude_sculpt_glb_v02.glb — master (original skin retained)
27_clean.blend # lena_nude_sculpt_glb_v03.glb — geometry master (phase boundary:
# line heal + cleavage fillet + flipped-face repair, pre-texture)
29b_final.blend # lena_nude_sculpt_glb_v03.glb — textured master, HEAD 2026-08-06.
# 27_clean + on-body tone levelling (29_tone3d.py str 1.0, 25 passes)
34_v04.blend # lena_nude_sculpt_glb_v04.glb — master, HEAD 2026-08-06. 29b_final with
# the bust rescaled to k=0.5 per the approved concept turnaround.
10_welded.blend # parent of the v03 chain. Kept as the fallback until v03/v04 are
# accepted; 11-15 regenerate it from 10_patch, so unpin then.
# _bust_wall_cache.npy is the bi-harmonic chest wall for 29b_final (an 8-minute CG solve).
# Keep it while bust size is still in play: 33/34 reuse it and a different k then costs
# seconds instead of minutes. It is regenerable, and invalid for any other mesh — both
# scripts assert its shape against the vertex count before trusting it.
# 17_healed / 18_smoothnrm / 22_lines / 23_cleavage / 26_geo / 29_final and the
# rejected atlas-tone attempt were per-step snapshots and have been pruned; the
# 17/25/26/29 recipes rebuild them from 10_welded.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

@@ -1,60 +0,0 @@
12:26:35 | INFO: Data are loaded, start creating Blender stuff
12:26:35 | INFO: Blender create Mesh node meshes[0]
12:26:35 | INFO: glTF import finished in 0.16s
[seam 0.4s] body 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c' 46810v 64957f
Info: Removed 14846 vertices
Info: Removed: 0 vertices, 0 edges, 0 faces
Info: Removed: 0 vertices, 0 edges, 0 faces
[seam 0.6s] clean[after decimate]: 31964v 64957f nonmanifold_edges=1649 degenerate_faces=0
axes up=z lr=x fb=y height 0.9793 units
single-leg radius profile (u -> radius), the ankle is the narrow point:
0.01:45 0.01:51 0.03:50 0.04:42 0.04:35 0.06:29 0.07:27 0.08:25 0.08:25 0.10:25 0.11:23 0.11:26 0.12:26 0.14:27 0.15:31 0.15:33 0.17:34 0.17:37 0.18:38 0.20:39 0.21:41 0.21:41 0.23:41 0.23:40 0.24:40 0.26:39 0.27:38 0.28:38 0.29:40 0.29:39 0.30:40 0.32:40 0.33:42 0.34:45 0.34:48
arm radius profile (|s| -> radius), the wrist is the narrow point before the hand:
0.36:28 0.37:28 0.39:27 0.41:27 0.42:26 0.44:25 0.46:25 0.47:26 0.49:26 0.50:28 0.52:28 0.54:28 0.55:28 0.57:28 0.59:27 0.60:26 0.62:24 0.63:23 0.65:22 0.67:21 0.68:20 0.70:20 0.72:18 0.73:19 0.75:19 0.76:25 0.78:28 0.80:30 0.81:29 0.83:29 0.85:31 0.86:23 0.88:22 0.89:21 0.91:22 0.93:18 0.94:19 0.96:17 0.98:15 0.99:8
LANDMARKS neck_u 0.825 hip_u 0.415 ankle_u 0.085 arm_in 0.350 wrist 0.748 (fractions of height / half-span)
foot split at u 0.018 (upper|sole) hand split at d +0.0040 (back|palm)
arm centre line u at |s|=[0.35, 0.45, 0.55, 0.65, 0.75]: [0.727, 0.722, 0.721, 0.705, 0.689]
leg(+) centre line |s| at u=[0.08, 0.17, 0.25, 0.33, 0.41]: [0.23, 0.199, 0.197, 0.145, 0.081]
armpit u 0.713 -> relief cut across the back at that height
seam 'ankle rings': 203 edges
seam 'arm back line': 371 edges
seam 'armpit relief': 116 edges
seam 'back midline': 314 edges
seam 'foot sole line': 493 edges
seam 'hand palm line': 267 edges
seam 'hip ring': 370 edges
seam 'leg inner line': 135 edges
seam 'neck ring': 113 edges
seam 'shoulder rings': 263 edges
seam 'wrist rings': 181 edges
[seam 2.0s] marked 2826 seam edges
[seam 2.0s] uv layers: ['UVMap_tripo', 'UVMap_atlas'] active=UVMap_atlas
[seam 8.2s] unwrapped + packed
=== NEW ATLAS (Tripo baseline in brackets) ===
islands 968 [5870] 99%-of-area islands 14 [84]
coverage 68.6% [62.0] confetti <20 faces 953 [5763]
texel density 2.29..2.29 px/mm -> 1.00x spread [1.8x]
per-triangle area-scale vs median, real charts only: p05 0.33 p95 1.77 p99 2.27 (1.0 = undistorted)
the charts: # faces uv_area% 3D cm2 stretch_p95 where
815 21024 26.011 8290.6 2.00 torso mid
478 5544 8.739 2785.4 1.69 leg R
811 5600 8.733 2783.4 1.38 leg L
653 9887 7.861 2505.5 2.53 head mid
887 2741 3.089 984.7 1.81 arm L
154 2596 3.074 979.7 1.83 arm R
833 2139 1.921 612.2 1.21 foot L
310 2151 1.915 610.5 1.63 foot R
192 2146 1.284 409.2 1.18 foot R
834 2084 1.269 404.5 1.21 foot L
967 2025 1.115 355.4 1.40 hand L
71 1997 1.109 353.3 2.28 hand R
77 1954 1.103 351.7 1.32 hand R
931 1961 1.090 347.4 1.39 hand L
Info: Saved as "seamed.blend"
[seam 13.9s] wrote C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\atlas_v02\charts.png + seamed.blend
SEAMS_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 KiB

@@ -1,17 +0,0 @@
00:01.922 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\28_final.blend"
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\09_beauty.py:19: DeprecationWarning: 'World.use_nodes' is expected to be removed in Blender 6.0
wd.use_nodes = True
00:05.078 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\full_front.png'
rendered full_front
00:06.062 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\full_40.png'
rendered full_40
00:07.000 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\full_back.png'
rendered full_back
00:07.984 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\chest.png'
rendered chest
00:09.031 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03\hip.png'
rendered hip
BEAUTY_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
@@ -1,17 +0,0 @@
00:01.890 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\29b_final.blend"
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\09_beauty.py:19: DeprecationWarning: 'World.use_nodes' is expected to be removed in Blender 6.0
wd.use_nodes = True
00:04.640 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\full_front.png'
rendered full_front
00:05.594 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\full_40.png'
rendered full_40
00:06.484 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\full_back.png'
rendered full_back
00:07.453 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\chest.png'
rendered chest
00:08.422 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\beauty_v03b\hip.png'
rendered hip
BEAUTY_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

@@ -1,17 +0,0 @@
00:01.797 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\atlas_v02\reatlased.blend"
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\09_beauty.py:19: DeprecationWarning: 'World.use_nodes' is expected to be removed in Blender 6.0
wd.use_nodes = True
00:03.812 render | Saved: 'C:\beauty_atlas_v02\full_front.png'
rendered full_front
00:04.172 render | Saved: 'C:\beauty_atlas_v02\full_40.png'
rendered full_40
00:04.500 render | Saved: 'C:\beauty_atlas_v02\full_back.png'
rendered full_back
00:04.875 render | Saved: 'C:\beauty_atlas_v02\chest.png'
rendered chest
00:05.265 render | Saved: 'C:\beauty_atlas_v02\hip.png'
rendered hip
BEAUTY_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 KiB

@@ -1,38 +0,0 @@
00:01.812 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\26_geo.blend"
[spk 0.3s] in: 860389v 1721805f custom_normals=True
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\25_speckle.py:217: DeprecationWarning: 'Material.use_nodes' is expected to be removed in Blender 6.0
clay.use_nodes = True
00:15.812 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\chest_clay_0.png'
FLIPPED FACES: 1511 of 1721805 (0.0878%) [dot<0 vs smoothed field]
nearly-perpendicular (|dot|<0.2): 442
flipped-face z histogram:
z 0.002-0.083: 212
z 0.083-0.164: 63
z 0.164-0.246: 25
z 0.246-0.327: 33
z 0.327-0.409: 95
z 0.409-0.490: 37
z 0.490-0.572: 100
z 0.572-0.653: 212
z 0.653-0.735: 292
z 0.735-0.816: 115
z 0.816-0.898: 145
z 0.898-0.979: 182
FACE COMPONENTS: 1 (largest [1721805])
shards <= 200 faces: 0 components, 0 faces total
SLIVERS: 0 faces with area < (0.01 mm-unit)^2; min area 4.542e-10, p01 4.805e-08
[spk 14.0s] render chest_clay_0
00:16.281 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\chest_clay_40.png'
[spk 14.5s] render chest_clay_40
00:17.500 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\chest_tex_0.png'
[spk 15.7s] render chest_tex_0
00:17.953 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\hip_clay_0.png'
[spk 16.1s] render hip_clay_0
00:18.343 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\full_clay_0.png'
[spk 16.5s] render full_clay_0
00:19.187 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_25diag\full_tex_0.png'
[spk 17.4s] render full_tex_0
SPK_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

@@ -1,27 +0,0 @@
import bpy, sys
import numpy as np
W = r"C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude"
bpy.ops.wm.open_mainfile(filepath=W + r"\00_welded.blend")
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
n = len(ob.data.vertices)
co0 = np.empty(n*3); ob.data.vertices.foreach_get("co", co0); co0 = co0.reshape(-1,3)
bpy.ops.wm.open_mainfile(filepath=W + r"\03_sculpted.blend")
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
co1 = np.empty(n*3); ob.data.vertices.foreach_get("co", co1); co1 = co1.reshape(-1,3)
disp = np.linalg.norm(co1-co0, axis=1)
M = np.load(W + r"\masks.npz")
A = np.load(W + r"\masks_active.npz")
act = A["active"]; garment = M["garment"]; cups = M["cups"]; shield = M["shield"]
# regions to interrogate (welded coords)
zones = {
"bow": (np.abs(co0[:,0])<0.03) & (co0[:,2]>0.715) & (co0[:,2]<0.760) & (co0[:,1]<-0.05),
"strap_tuft":(np.abs(co0[:,0])>0.035)&(np.abs(co0[:,0])<0.09)&(co0[:,2]>0.80)&(co0[:,2]<0.86),
"armpit": (np.abs(co0[:,0])>0.10)&(np.abs(co0[:,0])<0.15)&(co0[:,2]>0.66)&(co0[:,2]<0.75)&(co0[:,1]>-0.02),
"good_cup": (np.abs(co0[:,0])>0.03)&(np.abs(co0[:,0])<0.06)&(co0[:,2]>0.66)&(co0[:,2]<0.70)&(co0[:,1]<-0.06),
}
for nm, m in zones.items():
if not m.sum():
print(nm, "EMPTY"); continue
print(f"{nm}: n={m.sum():6d} garment={100*garment[m].mean():5.1f}% act={100*act[m].mean():5.1f}% "
f"disp mm p50={np.median(disp[m])*1000:.2f} p90={np.percentile(disp[m],90)*1000:.2f}")
print("DBG_BOW_DONE")
@@ -1,92 +0,0 @@
# Diagnose the inter-thigh residue below the crotch snap window.
# blender --background --python dbg_gusset.py -- <blend> <outdir>
import bpy, sys, math, os
import numpy as np
from mathutils import Vector, Euler
argv = sys.argv[sys.argv.index("--") + 1:]
BLEND, OUT = argv[0], argv[1]
os.makedirs(OUT, exist_ok=True)
bpy.ops.wm.open_mainfile(filepath=BLEND)
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
key=lambda o: len(o.data.vertices))
me = ob.data
n_v = len(me.vertices)
co = np.empty(n_v * 3)
me.vertices.foreach_get("co", co)
co = co.reshape(-1, 3)
# numeric: midline strip profile — for each z slice near the crotch, front-most and
# back-most y, and the x extent of geometry in the inter-thigh gap
print("== midline (|x|<0.01) y-range per z slice ==")
for z0 in np.arange(0.34, 0.50, 0.01):
m = (np.abs(co[:, 0]) < 0.01) & (co[:, 2] >= z0) & (co[:, 2] < z0 + 0.01)
if m.sum() == 0:
print(f" z {z0:.2f}: EMPTY")
continue
ys = co[m, 1]
print(f" z {z0:.2f}: n={m.sum():5d} y [{ys.min():+.4f} .. {ys.max():+.4f}]")
print("== inter-thigh occupancy: verts with |x|<0.06 per z slice ==")
for z0 in np.arange(0.30, 0.46, 0.01):
m = (np.abs(co[:, 0]) < 0.06) & (co[:, 2] >= z0) & (co[:, 2] < z0 + 0.01)
print(f" z {z0:.2f}: n={m.sum():5d}", end="")
if m.sum():
print(f" x [{co[m,0].min():+.4f}..{co[m,0].max():+.4f}]"
f" y [{co[m,1].min():+.4f}..{co[m,1].max():+.4f}]")
else:
print()
# renders: clay, shadowless, tight on the crotch — front, and from below looking up
for o in list(bpy.data.objects):
if o.type in ('LIGHT', 'CAMERA'):
bpy.data.objects.remove(o, do_unlink=True)
sc = bpy.context.scene
sc.render.engine = 'BLENDER_EEVEE'
sc.render.resolution_x = sc.render.resolution_y = 1000
sc.render.film_transparent = False
wd = bpy.data.worlds.new("w")
wd.use_nodes = True
wd.node_tree.nodes["Background"].inputs[0].default_value = (0.18, 0.18, 0.18, 1)
sc.world = wd
mat = bpy.data.materials.new("clay")
mat.use_nodes = True
b = mat.node_tree.nodes["Principled BSDF"]
b.inputs["Base Color"].default_value = (0.8, 0.8, 0.8, 1)
b.inputs["Roughness"].default_value = 0.6
ob.data.materials.clear()
ob.data.materials.append(mat)
def sun(rot, e):
ld = bpy.data.lights.new("s", 'SUN')
ld.energy = e
ld.use_shadow = False
lo = bpy.data.objects.new("s", ld)
lo.rotation_euler = rot
bpy.context.collection.objects.link(lo)
sun(Euler((math.radians(60), 0, math.radians(-30))), 3.0)
sun(Euler((math.radians(120), 0, math.radians(150))), 1.2)
cam = bpy.data.cameras.new("c")
cam.lens = 85
cob = bpy.data.objects.new("c", cam)
bpy.context.collection.objects.link(cob)
sc.camera = cob
views = [
# (name, location, rotation) — model front = -Y
("front_tight", Vector((0.0, -0.55, 0.41)), Euler((math.radians(90), 0, 0))),
("below_up", Vector((0.0, -0.30, 0.05)), Euler((math.radians(50), 0, 0))),
("side_gap", Vector((-0.45, -0.35, 0.40)), Euler((math.radians(88), 0, math.radians(-52)))),
]
for name, loc, rot in views:
cob.location = loc
cob.rotation_euler = rot
sc.render.filepath = os.path.join(OUT, f"{name}.png")
bpy.ops.render.render(write_still=True)
print(f"rendered {name}")
print("DBG_DONE")
@@ -1,37 +0,0 @@
# Probe: (1) duplicate coincident verts along the stomach/waist lines, (2) where the heal
# strip actually was vs where the lines are.
# blender --background --python dbg_lines.py -- <blend> <masks.npz>
import bpy, sys
import numpy as np
argv = sys.argv[sys.argv.index("--") + 1:]
BLEND, MASKS = argv[0], argv[1]
bpy.ops.wm.open_mainfile(filepath=BLEND)
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
key=lambda o: len(o.data.vertices))
me = ob.data
n_v = len(me.vertices)
co = np.empty(n_v * 3)
me.vertices.foreach_get("co", co)
co = co.reshape(-1, 3)
print(f"verts: {n_v}")
# coincident duplicates: exact-position hash
key = np.round(co * 1e5).astype(np.int64)
kview = key[:, 0] * 73856093 ^ key[:, 1] * 19349663 ^ key[:, 2] * 83492791
uniq, counts = np.unique(kview, return_counts=True)
dup_groups = (counts > 1).sum()
print(f"coincident position groups: {dup_groups} (verts in dups: {counts[counts>1].sum()})")
# where are the dups? histogram by z in the torso front
from collections import Counter
dupset = set(uniq[counts > 1].tolist())
isdup = np.array([k in dupset for k in kview])
front = (np.abs(co[:, 0]) < 0.06) & (co[:, 1] < 0)
print("z-slice | dup verts (front) | hemband verts (front)")
M = np.load(MASKS)
hemband = M["hemband"]
for z0 in np.arange(0.44, 0.68, 0.02):
zi = (co[:, 2] >= z0) & (co[:, 2] < z0 + 0.02) & front
print(f" {z0:.2f}-{z0+0.02:.2f}: dup {int((zi & isdup).sum()):6d} hem {int((zi & hemband).sum()):6d}")
print("PROBE_DONE")
@@ -1,44 +0,0 @@
import bpy, sys
import numpy as np
argv = sys.argv[sys.argv.index("--") + 1:]
bpy.ops.wm.open_mainfile(filepath=argv[0])
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
me = ob.data
act = np.load(argv[1])["active"]
lay = me.color_attributes.new(name="dbg", type='FLOAT_COLOR', domain='POINT')
n = len(me.vertices)
cols = np.zeros((n, 4), dtype=np.float32)
cols[:, 3] = 1.0
cols[act, 0] = 1.0 # red = replaced
cols[~act, 1] = 0.6 # green = untouched
lay.data.foreach_set("color", cols.reshape(-1))
mat = bpy.data.materials.new("dbg")
mat.use_nodes = True
nt = mat.node_tree
vc = nt.nodes.new("ShaderNodeVertexColor")
vc.layer_name = "dbg"
bsdf = next(x for x in nt.nodes if x.type == 'BSDF_PRINCIPLED')
nt.links.new(vc.outputs["Color"], bsdf.inputs["Base Color"])
me.materials.clear()
me.materials.append(mat)
import math
from mathutils import Vector
scn = bpy.context.scene
w = bpy.data.worlds.new("W"); w.color=(0.2,0.2,0.2); scn.world = w
sun = bpy.data.objects.new("S", bpy.data.lights.new("S",'SUN'))
sun.data.energy = 3; sun.data.use_shadow = False
sun.rotation_euler = (math.radians(60),0,math.radians(75))
bpy.context.collection.objects.link(sun)
cam = bpy.data.objects.new("C", bpy.data.cameras.new("C"))
cam.data.lens = 85
bpy.context.collection.objects.link(cam)
scn.camera = cam
scn.render.engine = 'BLENDER_EEVEE'
scn.render.resolution_x = scn.render.resolution_y = 1000
ctr = Vector((0,0,0.675))
yaw = math.radians(40)
cam.location = ctr + Vector((math.sin(yaw)*0.66, -math.cos(yaw)*0.66, 0.02))
cam.rotation_euler = (ctr - cam.location).to_track_quat('-Z','Y').to_euler()
scn.render.filepath = argv[2]
bpy.ops.render.render(write_still=True)
print("DBG_OK")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

@@ -1,140 +0,0 @@
# Visual: paint masks as vertex colors and render the hip view — where are the lines
# relative to the garment mask (red), rim band (green), slit seams (blue)?
# blender --background --python dbg_maskviz.py -- <blend> <masks.npz> <raw.glb> <out.png>
import bpy, sys, math
import numpy as np
from mathutils import Vector, Euler
from mathutils.kdtree import KDTree
argv = sys.argv[sys.argv.index("--") + 1:]
BLEND, MASKS, RAW, OUT = argv[0], argv[1], argv[2], argv[3]
bpy.ops.wm.open_mainfile(filepath=BLEND)
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
key=lambda o: len(o.data.vertices))
me = ob.data
n_v = len(me.vertices)
co = np.empty(n_v * 3)
me.vertices.foreach_get("co", co)
co = co.reshape(-1, 3)
M = np.load(MASKS)
garment = M["garment"]
n_e = len(me.edges)
ev = np.empty(n_e * 2, dtype=np.int32)
me.edges.foreach_get("vertices", ev)
ev = ev.reshape(-1, 2)
before = set(bpy.data.objects)
bpy.ops.import_scene.gltf(filepath=RAW)
new = [o for o in bpy.data.objects if o not in before]
raw = max([o for o in new if o.type == 'MESH'], key=lambda o: len(o.data.vertices))
rme = raw.data
rn = len(rme.vertices)
rco = np.empty(rn * 3)
rme.vertices.foreach_get("co", rco)
rco = rco.reshape(-1, 3)
l_tot = np.empty(len(rme.polygons), dtype=np.int32)
rme.polygons.foreach_get("loop_total", l_tot)
l_start = np.empty(len(rme.polygons), dtype=np.int32)
rme.polygons.foreach_get("loop_start", l_start)
l_v = np.empty(len(rme.loops), dtype=np.int32)
rme.loops.foreach_get("vertex_index", l_v)
ecount = {}
for fs, ft in zip(l_start, l_tot):
idxs = l_v[fs:fs + ft]
for k in range(ft):
a, b = idxs[k], idxs[(k + 1) % ft]
kk = (a, b) if a < b else (b, a)
ecount[kk] = ecount.get(kk, 0) + 1
rbnd = np.zeros(rn, dtype=bool)
for (a, b), c in ecount.items():
if c == 1:
rbnd[a] = rbnd[b] = True
for o in new:
bpy.data.objects.remove(o, do_unlink=True)
kd = KDTree(n_v)
for i in range(n_v):
kd.insert(Vector(co[i]), i)
kd.balance()
seam = np.zeros(n_v, dtype=bool)
for p in rco[rbnd]:
hit = kd.find(Vector(p))
if hit[0] is not None and hit[2] < 0.002:
seam[hit[1]] = True
def grow(mask, rings):
m = mask.copy()
for _ in range(rings):
h = m[ev[:, 0]] | m[ev[:, 1]]
m2 = m.copy()
m2[ev[:, 0]] |= h
m2[ev[:, 1]] |= h
m = m2
return m
rim = grow(garment, 10) & ~(~grow(~garment, 10))
seam_g = grow(seam, 1)
col = np.ones((n_v, 4))
col[:, :3] = 0.75
col[garment, 0] = 0.9
col[garment, 1] = 0.45
col[garment, 2] = 0.45
col[rim, 0] = 0.3
col[rim, 1] = 0.8
col[rim, 2] = 0.3
col[seam_g, 0] = 0.15
col[seam_g, 1] = 0.25
col[seam_g, 2] = 0.95
vc = me.color_attributes.new("viz", 'FLOAT_COLOR', 'POINT')
vc.data.foreach_set("color", col.reshape(-1))
mat = bpy.data.materials.new("viz")
mat.use_nodes = True
nt = mat.node_tree
b = nt.nodes["Principled BSDF"]
attr = nt.nodes.new("ShaderNodeVertexColor")
attr.layer_name = "viz"
nt.links.new(attr.outputs["Color"], b.inputs["Base Color"])
b.inputs["Roughness"].default_value = 0.9
me.materials.clear()
me.materials.append(mat)
for o in list(bpy.data.objects):
if o.type in ('LIGHT', 'CAMERA'):
bpy.data.objects.remove(o, do_unlink=True)
sc = bpy.context.scene
sc.render.engine = 'BLENDER_EEVEE'
sc.render.resolution_x = sc.render.resolution_y = 1000
wd = bpy.data.worlds.new("w")
wd.use_nodes = True
wd.node_tree.nodes["Background"].inputs[0].default_value = (0.18, 0.18, 0.18, 1)
sc.world = wd
ld = bpy.data.lights.new("s", 'SUN')
ld.energy = 3.0
ld.use_shadow = False
lo = bpy.data.objects.new("s", ld)
lo.rotation_euler = Euler((math.radians(60), 0, math.radians(-30)))
bpy.context.collection.objects.link(lo)
ld2 = bpy.data.lights.new("s2", 'SUN')
ld2.energy = 1.2
ld2.use_shadow = False
lo2 = bpy.data.objects.new("s2", ld2)
lo2.rotation_euler = Euler((math.radians(120), 0, math.radians(150)))
bpy.context.collection.objects.link(lo2)
cam = bpy.data.cameras.new("c")
cam.lens = 60
cob = bpy.data.objects.new("c", cam)
bpy.context.collection.objects.link(cob)
sc.camera = cob
cob.location = Vector((0.0, -0.95, 0.53))
cob.rotation_euler = Euler((math.radians(90), 0, 0))
sc.render.filepath = OUT
bpy.ops.render.render(write_still=True)
print("VIZ_DONE")
@@ -1,52 +0,0 @@
# Probe: was the rosy V repainted? Sample per-vertex basecolor in the V region vs belly,
# and re-run the rosy classifier to see who it caught.
# blender --background --python dbg_rosy.py -- <07_textured.blend>
import bpy, sys
import numpy as np
BLEND = sys.argv[sys.argv.index("--") + 1]
bpy.ops.wm.open_mainfile(filepath=BLEND)
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
key=lambda o: len(o.data.vertices))
me = ob.data
n_v = len(me.vertices)
co = np.empty(n_v * 3)
me.vertices.foreach_get("co", co)
co = co.reshape(-1, 3)
base = next(i for i in bpy.data.images if "basecolor" in i.name.lower())
w, h = base.size
buf = np.empty(w * h * 4, dtype=np.float32)
base.pixels.foreach_get(buf)
rgb = buf.reshape(h, w, 4)
loops_v = np.empty(len(me.loops), dtype=np.int32)
me.loops.foreach_get("vertex_index", loops_v)
uv = np.empty(len(me.loops) * 2)
me.uv_layers.active.data.foreach_get("uv", uv)
uv = uv.reshape(-1, 2)
lx = np.clip(uv[:, 0], 0, 1) * (w - 1)
ly = np.clip(uv[:, 1], 0, 1) * (h - 1)
first_loop = np.full(n_v, len(loops_v), dtype=np.int64)
np.minimum.at(first_loop, loops_v, np.arange(len(loops_v), dtype=np.int64))
first_loop = np.minimum(first_loop, len(loops_v) - 1)
vcol = rgb[ly[first_loop].astype(int), lx[first_loop].astype(int), :3]
regions = {
"V_chest (z .78-.85, y<-.02)": (np.abs(co[:, 0]) < 0.06) & (co[:, 1] < -0.02) & (co[:, 2] > 0.78) & (co[:, 2] < 0.85),
"throat (z .85-.90, y<-.02)": (np.abs(co[:, 0]) < 0.04) & (co[:, 1] < -0.02) & (co[:, 2] > 0.85) & (co[:, 2] < 0.90),
"cup_R (z .68-.74, x<-.03)": (co[:, 0] < -0.03) & (co[:, 0] > -0.09) & (co[:, 1] < -0.02) & (co[:, 2] > 0.68) & (co[:, 2] < 0.74),
"belly (z .55-.60)": (np.abs(co[:, 0]) < 0.06) & (co[:, 1] < 0) & (co[:, 2] > 0.55) & (co[:, 2] < 0.60),
"shoulder (z .84-.86, |x|>.10)": (np.abs(co[:, 0]) > 0.10) & (np.abs(co[:, 0]) < 0.16) & (co[:, 2] > 0.84) & (co[:, 2] < 0.86),
}
band = (co[:, 2] > 0.30) & (co[:, 2] < 0.905)
rg = vcol[:, 0] - vcol[:, 1]
print(f"whole-band med rg: {np.median(rg[band]):.3f}")
for name, m in regions.items():
if not m.any():
print(f"{name}: EMPTY")
continue
c = vcol[m]
print(f"{name}: n={m.sum():6d} mean RGB ({c[:,0].mean():.3f},{c[:,1].mean():.3f},{c[:,2].mean():.3f}) "
f"rg med {np.median(rg[m]):.3f}")
print("PROBE_DONE")
@@ -1,68 +0,0 @@
00:05.516 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\10_welded.blend"
[diag 0.3s] mesh 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c': 860389 verts, 1721808 faces, custom_normals=True
BBOX z 0.000..0.979 x -0.463..0.463 y -0.093..0.089
TOPO boundary_edges=830 nonmanifold_edges=1649 degenerate_faces=3 loose_verts=0
BOUNDARY-EDGE z histogram (holes live here):
z 0.002-0.082: 176
z 0.082-0.163: 117
z 0.163-0.244: 25
z 0.244-0.325: 43
z 0.325-0.406: 179
z 0.406-0.487: 31
z 0.487-0.567: 103
z 0.567-0.648: 211
z 0.648-0.729: 329
z 0.729-0.810: 120
z 0.810-0.891: 169
z 0.891-0.972: 157
KINK > 8.0deg : 15535 verts (groove depth p05=-0.685 mm, median=+0.007 mm, p95=+0.865 mm)
KINK >12.0deg : 9704 verts (groove depth p05=-0.771 mm, median=+0.006 mm, p95=+0.866 mm)
KINK >20.0deg : 4767 verts (groove depth p05=-0.808 mm, median=+0.011 mm, p95=+0.912 mm)
KINK z histogram (the lines):
z 0.329-0.365: 28
z 0.365-0.400: 280
z 0.400-0.436: 138
z 0.436-0.472: 307
z 0.472-0.507: 430
z 0.507-0.543: 515
z 0.543-0.579: 399
z 0.614-0.650: 845
z 0.650-0.686: 423
z 0.686-0.722: 907
z 0.722-0.757: 844
z 0.757-0.793: 794
z 0.793-0.829: 30
z 0.829-0.864: 559
z 0.864-0.900: 3192
SPLIT-PANEL test on 4852 kink verts: 2850 (58.7%) have an unmerged twin within 0.8 mm
=== CLEAVAGE: medial corridor cross-sections y(x) ===
z=0.620 sternum y=-0.0679 max concave curvature 6.9 1/m -> fillet radius 145.5 mm
z=0.635 sternum y=-0.0695 max concave curvature 13.7 1/m -> fillet radius 73.2 mm
z=0.650 sternum y=-0.0720 max concave curvature 14.8 1/m -> fillet radius 67.6 mm
z=0.665 sternum y=-0.0749 max concave curvature 1.8 1/m -> fillet radius 552.2 mm
z=0.680 sternum y=-0.0789 max concave curvature 16.8 1/m -> fillet radius 59.4 mm
z=0.695 sternum y=-0.0833 max concave curvature 7.0 1/m -> fillet radius 142.0 mm
z=0.710 sternum y=-0.0772 max concave curvature 121.9 1/m -> fillet radius 8.2 mm <-- SHARP
z=0.725 sternum y=-0.0508 max concave curvature 400.7 1/m -> fillet radius 2.5 mm <-- SHARP
z=0.740 sternum y=-0.0436 max concave curvature 22.2 1/m -> fillet radius 45.0 mm
=== CLEAVAGE: depth of the notch (breast apex y vs sternum y) ===
z=0.620 sternum -0.0679 apex -0.0633 notch -4.5 mm
z=0.635 sternum -0.0695 apex -0.0657 notch -3.9 mm
z=0.650 sternum -0.0720 apex -0.0690 notch -2.9 mm
z=0.665 sternum -0.0749 apex -0.0749 notch +0.0 mm
z=0.680 sternum -0.0789 apex -0.0825 notch +3.6 mm
z=0.695 sternum -0.0835 apex -0.0891 notch +5.6 mm
z=0.710 sternum -0.0772 apex -0.0887 notch +11.6 mm
z=0.725 sternum -0.0509 apex -0.0752 notch +24.3 mm
z=0.740 sternum -0.0436 apex -0.0470 notch +3.3 mm
[diag 15.6s] current basecolor 4096x4096 'lena+glb_basecolor.jpg.003'
00:21.547 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\00_welded.blend"
[diag 17.0s] original basecolor 4096x4096 'lena+glb_basecolor.jpg'
DISCOLOUR: repainted texels = 0 (0.00% of atlas)
DIAG_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
@@ -1,14 +0,0 @@
00:01.875 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\29b_final.blend"
[export] tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c: 860389v 1721805f height 0.9792 units, images []
INFO Draco mesh compression is available, use library at C:\Program Files\Blender Foundation\Blender 5.1\5.1\scripts\addons_core\io_scene_gltf2\extern_draco.dll
08:55:39 | INFO: Starting glTF 2.0 export
08:55:40 | INFO: Extracting primitive: meshes[0]
08:55:42 | WARNING: More than one shader node tex image used for a texture. The resulting glTF sampler will behave like the first shader node tex image.
08:55:42 | INFO: Primitives created: 1
08:55:43 | INFO: Finished glTF 2.0 export in 3.3285176753997803 s
[export] WROTE ../lena_nude_sculpt_glb_v03.glb (5.0s)
EXPORT_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
@@ -1,40 +0,0 @@
00:01.891 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\10_welded.blend"
00:09.969 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\00_welded.blend"
===== 10_welded.blend =====
body 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c' 860389v materials=['tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c']
IMG 'lena+glb_basecolor.jpg' 4096x4096 packed=True file='' mean [0.6656 0.4327 0.2466] std [0.062 0.1002 0.0658]
IMG 'lena+glb_basecolor.jpg.002' 4096x4096 packed=True file='' mean [0.6601 0.4317 0.2562] std [0.0664 0.1041 0.0752]
IMG 'lena+glb_basecolor.jpg.003' 4096x4096 packed=True file='' mean [0.6601 0.4317 0.2562] std [0.0664 0.1041 0.0752]
IMG 'lena+glb_normal.jpg' 4096x4096 packed=True file='' mean [0.5001 0.4997 0.9995] std [0.0201 0.0204 0.0171]
IMG 'lena+glb_normal.jpg.002' 4096x4096 packed=True file='' mean [0.4999 0.4997 0.9991] std [0.0268 0.0269 0.0214]
IMG 'lena+glb_normal.jpg.003' 4096x4096 packed=True file='' mean [0.4999 0.4997 0.9991] std [0.0268 0.0269 0.0214]
IMG 'lena+glb_rm.jpg' 4096x4096 packed=True file='' mean [1. 0.4054 0.0322] std [0.0035 0.0987 0.0198]
IMG 'lena+glb_rm.jpg.002' 4096x4096 packed=True file='' mean [1. 0.4585 0.0304] std [0.004 0.1722 0.0204]
IMG 'lena+glb_rm.jpg.003' 4096x4096 packed=True file='' mean [1. 0.4585 0.0304] std [0.004 0.1722 0.0204]
MAT 'tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c':
TEX_IMAGE node 'Image Texture' image='lena+glb_basecolor.jpg' -> ['Principled BSDF.Base Color']
TEX_IMAGE node 'Image Texture.001' image='lena+glb_rm.jpg' -> ['Separate Color.Color']
TEX_IMAGE node 'Image Texture.002' image='lena+glb_normal.jpg' -> ['Normal Map.Color']
BSDF.Base Color <- Image Texture image='lena+glb_basecolor.jpg'
BSDF.Normal <- Normal Map
BSDF.Roughness <- Separate Color
BSDF.Metallic <- Separate Color
===== 00_welded.blend =====
body 'tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c' 953966v materials=['tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c']
IMG 'lena+glb_basecolor.jpg' 4096x4096 packed=True file='' mean [0.6601 0.4317 0.2562] std [0.0664 0.1041 0.0752]
IMG 'lena+glb_normal.jpg' 4096x4096 packed=True file='' mean [0.4999 0.4997 0.9991] std [0.0268 0.0269 0.0214]
IMG 'lena+glb_rm.jpg' 4096x4096 packed=True file='' mean [1. 0.4585 0.0304] std [0.004 0.1722 0.0204]
MAT 'tripo_material_9701d6b7-20ea-449d-8637-2fc9d4bc199c':
TEX_IMAGE node 'Image Texture' image='lena+glb_basecolor.jpg' -> ['Principled BSDF.Base Color']
TEX_IMAGE node 'Image Texture.001' image='lena+glb_rm.jpg' -> ['Separate Color.Color']
TEX_IMAGE node 'Image Texture.002' image='lena+glb_normal.jpg' -> ['Normal Map.Color']
BSDF.Base Color <- Image Texture image='lena+glb_basecolor.jpg'
BSDF.Normal <- Normal Map
BSDF.Roughness <- Separate Color
BSDF.Metallic <- Separate Color
IMAGES_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
@@ -1,20 +0,0 @@
00:01.954 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\10_welded.blend"
[line 0.3s] in: 860389v 1721808f
[line 2.1s] boundary loops: 689 (edges: 830)
[line 407.3s] pinholes filled: 689 loops, 0 skipped (> 8 edges or error)
[line 407.9s] deleted 3 degenerate faces
[line 409.7s] after fill: 860389v 1721805f boundary_edges=839 nonmanifold=1649
[line 410.5s] zone 699714 verts, protected 3442
[line 424.4s] round 1: seed 5111 -> band 12595 (S=19744) CG it=862 rel=9.9e-15 moved max 16.922 mm, median(band) 0.888 mm
[line 428.6s] after: seed mask 2790, kink>12deg 7449, kink>20deg 3649
[line 435.6s] round 2: seed 2790 -> band 7862 (S=13441) CG it=551 rel=9.1e-15 moved max 15.878 mm, median(band) 0.546 mm
[line 439.6s] after: seed mask 2154, kink>12deg 7186, kink>20deg 3680
[line 446.5s] round 3: seed 2154 -> band 6157 (S=10984) CG it=705 rel=7.6e-15 moved max 15.741 mm, median(band) 0.434 mm
[line 450.6s] after: seed mask 1860, kink>12deg 6994, kink>20deg 3607
[line 453.2s] TOTAL displacement: max 17.261 mm, 16589 verts moved >0.05 mm
Info: Saved as "17_healed.blend"
[line 453.3s] WROTE 17_healed.blend
LINE_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit
@@ -1,36 +0,0 @@
import bpy, sys
import numpy as np
W = r"C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude"
bpy.ops.wm.open_mainfile(filepath=W + r"\03_sculpted.blend")
ob = max([o for o in bpy.data.objects if o.type=='MESH'], key=lambda o: len(o.data.vertices))
me = ob.data
n_v = len(me.vertices)
co = np.empty(n_v*3); me.vertices.foreach_get("co", co); co = co.reshape(-1,3)
# face normals -> vertex normal variance (crease detector)
n_f = len(me.polygons)
fn = np.empty(n_f*3); me.polygons.foreach_get("normal", fn); fn = fn.reshape(-1,3)
lt = np.empty(n_f, dtype=np.int32); me.polygons.foreach_get("loop_total", lt)
ls = np.empty(n_f, dtype=np.int32); me.polygons.foreach_get("loop_start", ls)
lv = np.empty(len(me.loops), dtype=np.int32); me.loops.foreach_get("vertex_index", lv)
acc = np.zeros((n_v,3)); acc2 = np.zeros(n_v); cnt = np.zeros(n_v)
for fi in range(n_f):
for k in range(ls[fi], ls[fi]+lt[fi]):
vi = lv[k]
acc[vi] += fn[fi]; cnt[vi] += 1
mean = acc / np.maximum(cnt,1)[:,None]
ml = np.linalg.norm(mean, axis=1) # 1 = all normals aligned; low = crease/facet
window = (co[:,1] < 0) & (co[:,2] > 0.58) & (co[:,2] < 0.82) & (np.abs(co[:,0]) < 0.12)
bad = window & (ml < 0.90) & (cnt > 0)
print(f"creased verts in front-chest window: {bad.sum()}")
if bad.sum():
b = co[bad]
print(f"bbox: x [{b[:,0].min():+.3f},{b[:,0].max():+.3f}] "
f"y [{b[:,1].min():+.3f},{b[:,1].max():+.3f}] "
f"z [{b[:,2].min():+.3f},{b[:,2].max():+.3f}]")
# cluster by z-decile to see structure
for zlo in np.arange(0.58, 0.82, 0.02):
m = bad & (co[:,2] >= zlo) & (co[:,2] < zlo+0.02)
if m.sum() > 30:
print(f" z {zlo:.2f}-{zlo+0.02:.2f}: n={m.sum():5d} "
f"x[{co[m,0].min():+.3f},{co[m,0].max():+.3f}]")
print("LOCATE_DONE")
Binary file not shown.
@@ -1,26 +0,0 @@
00:01.797 blend | Read blend: "C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\17_healed.blend"
[nrm 0.3s] tripo_node_9701d6b7-20ea-449d-8637-2fc9d4bc199c: 860389v 1721805f 5165415 loops has_custom_normals=True
[nrm 0.5s] flat-shaded polygons: 208 of 1721805
CORNER-vs-VERTEX normal deviation: mean 0.008deg p50 0.002 p95 0.025 p99.9 0.034 max 90.000
loops deviating >5deg: 15 (0.000%)
loops deviating >15deg: 12 (0.000%)
torso verts whose corner normals deviate >10deg from smooth: 4
[nrm 2.8s] custom normals: had=True now has_custom_normals=False cleared=True
C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\18_normal_probe.py:120: DeprecationWarning: 'Material.use_nodes' is expected to be removed in Blender 6.0
clay.use_nodes = True
00:06.094 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\chest_clay_0.png'
[nrm 4.3s] render chest_clay_0
00:06.578 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\chest_clay_40.png'
[nrm 4.8s] render chest_clay_40
00:08.047 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\chest_tex_0.png'
[nrm 6.3s] render chest_tex_0
00:08.500 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\hip_clay_0.png'
[nrm 6.7s] render hip_clay_0
00:08.875 render | Saved: 'C:\Users\Jeremy\tinqs\animation\characters\female\lena_nude\hires_claude\review_18\full_clay_0.png'
[nrm 7.1s] render full_clay_0
Info: Saved as "18_smoothnrm.blend"
[nrm 7.2s] WROTE 18_smoothnrm.blend
NRM_DONE
Blender 5.1.2 (hash ec6e62d40fa9 built 2026-05-19 01:37:34)
Blender quit

Some files were not shown because too many files have changed in this diff Show More