Files
animation/plans/kapahaka-garment-handoff-2026-07-31.md
T
jeremy 3ba86b2ea8 feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC.

Content:
- characters/  Lena/male body lanes, bakes, texture work, run logs
- clothing/    garment pipeline, configs, gates, contract docs
- garments/    MD-authored garment sources (.zprj/.zpac)
- UAL-Lib/     Universal Animation Library 2 source (.blend/.fbx/.glb)
- tools/       blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent
- docs/, plans/, dev/, .agents/plans/

Repo hygiene:
- .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the
  Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the
  ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out
  on purpose — ~250 are already tracked raw and converting them would
  rewrite every one without shrinking history.
- .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable
  from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies
  dropped by a `>/dev/null` redirect on Windows).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:55:43 -07:00

535 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Handoff: kapa haka garments — MD side done, downstream not started
**From:** MD session, 2026-07-31 · **Repo:** `C:\Users\Jeremy\tinqs\animation`
**Read first:** `.claude/skills/marvelous-designer/SKILL.md`, `.agents/wiki/architecture/clothing-lane.md`, `clothing/README.md`
> ### ⚠️ NAMING CHANGED — no cultural names on test garments (Jeremy, 2026-07-31)
> The garments this doc calls **piupiu v3** and **pari v4** ship as **`bottom_test1_f`** and
> **`top_test1_f`**. Cultural names (piupiu, tāniko, pari, pareu, kapa haka) are phased out of
> **outfit IDs, set names, asset filenames, textures and UI labels** from here on: a
> first-pass test asset carrying a real cultural name asserts an accuracy it does not have,
> and the name then propagates into places that are expensive to walk back. A garment can
> earn its real name once the form is right and someone with the standing to say so signs
> off. Canonical statement: `ariki-game/src/Character/OutfitCatalog.cs` class doc.
>
> **Applies to the delivery, not to history.** MD source filenames
> (`lena_piupiu_v3.*`, `md_piupiu_v3.py`, `lena_pari_v4.*`) stay as they are — they are
> versioned, referenced by the recipes and by `backup/2026-07-31-pre-v3/MANIFEST.md`, and
> `tools/tailor/` is untracked, so that backup is the only safety net. Rename nothing there.
> New files from here take the new scheme.
>
> **Case matters — two conventions, don't conflate them:**
> item IDs are **snake_case + gender suffix** (`bottom_test1_f`); set ids are **PascalCase**
> (`Test1`) because they compose asset filenames as `{Gender}_{Set}_{Slot}.gltf`; configs,
> textures and set folders are **lowercase** (`bottom_test1.json`, `outfits/test1/`).
>
> Concretely, for §2.A/§2.B below:
> - config `clothing/configs/bottom_test1.json` (not `piupiu_v3.json`), `top_test1.json`
> - `export.set` → **`Test1`** · `export.out_dir` → `…/ariki-game/assets/quaternius/outfits/test1`
> - so outputs are `Female_Test1_Legs.gltf` / `Female_Test1_Body.gltf`
> - texture → `bottom_test1.png` (was `piupiu.png`)
> - the `register` stage should emit
> `Add("bottom_test1_f", "Bottom Test 1", OutfitSlot.Legs, "Legs", …, set: "Test1", gender: 1);`
> - ariki-game catalog entries `top_test1_f` / `bottom_test1_f` are already written and
> commented out in `OutfitCatalog.cs`, awaiting these files
## 🔁 REPRODUCING THIS, AND THE BODY QUESTION — 2026-08-01
### How to rebuild it: `python clothing/reproduce_test1.py`
`garment.py` **cannot** express this build. The skirt's weights are owned by
`skirt_garment_weights.py`, which must run **after `export` and before `import`**;
re-running the pipeline the obvious way (`--from census --to import`) silently ships
`garment_pipeline.py`'s 2-segment blend and reads as a rig regression. The new script is
the enforcement, and it reads the tuning from the config's `post_export` block so the four
env vars can't drift or be forgotten. `--dry-run` prints the sequence; `--texture`
regenerates the texture first.
### How this body differs from the regular Lena — it is ONLY the skeleton
| | regular `Ariki_Female_QuatSkin.glb` | `..._SkirtRig_4seg.glb` |
|---|---|---|
| nodes | 67 | 99 |
| skin joints | 65 | **97** |
| skirt bones | **0** | **32** (8 strands × 4 segments) |
| mesh | `Lena_Female`, 32713 v | `Lena_Female`, 32733 v |
| material / images | `MI_Body_Lena`, 1 | identical |
Same mesh, same material, same texture. The **only** difference is 32 extra bones
parented to `pelvis` plus the matching joint list. Consequences:
- **`BODY_OVERRIDE` is mandatory.** The garment's skin references `skirt_NN_0M` by name; on
the standard body those bones do not exist, so the hem gets no weights and collapses.
- **It is a side file on purpose** (their wiki): a garment's skin must carry every bone it
references, so body and garments have to change segment count together. There are now
three variants — `_SkirtRig` (16), `_SkirtRig_4seg` (32), `_SkirtRig_32x5` (160).
- **To ship on real characters the ring must be promoted onto the canonical body.** Until
then Test1 is test-bed-only. `_SkirtRig_32x5` (32 strands × 5 segments) is newer than the
wiki and unreferenced by the catalog — **ask the rig session** whether it supersedes 4seg
before building against it; our garment's 32 geometric strands would map 1:1 to it.
### Look: what was fixed, and what is left
**FIXED — the texture must be V-only.** `bottom_test1.png` was authored for a solid panel:
it painted its own vertical strand lines (now doubled up with the geometry) and its band
region varied along U (measured horizontal stdev **6883** per row vs 013 on clean rows).
Every geometric strand samples a different U slice, so the woven bands misaligned
strand-to-strand and read as noise. Now generated by
`tools/tailor/textures/make_bottom_test1.py`, which collapses each row to its median
(keeping the vertical rhythm, killing the horizontal variance) and adds back low-contrast
fibre striation: **mean horizontal stdev per row 50.2 → 5.9**. In game the bands now read
as continuous woven rows. Original kept as `bottom_test1_prestrand.png`.
**The rule generalises: any strand/gappy garment needs a V-only texture.**
**CHECKED, NOT A BUG — the "translucency".** At 1:1 it is *gaps*, not alpha: strands are
opaque and you see body and the inside of the back strands through the spaces. Materials
are correct — `doubleSided: true`, no `alphaMode` (opaque), textures RGB with no alpha.
**STILL OPEN**
1. **The top's neckline gape** — the biggest remaining visual defect. Present in game, not
in MD, so it is skinning/deformation (G6 class), and G5 does not catch it.
2. Fibre striation reads as faint dashes at extreme zoom — lower `STRIATION` or raise
`STRIATION_PERIOD` if it ever matters.
3. `top_test1.png` has NOT been given the same treatment; the top is a solid panel so
V-only does not apply, but its 39-vert rest penetration is unexamined.
---
## ✅ LEG CLIPPING FIXED IN THE BED — 2026-08-01
Jeremy: "the legs are clipping through the skirt still … what should happen is the skirt
moves with the leg but you never see the leg skin." Fixed; verified across Walk **and**
Dance, front and back.
**My first read was wrong.** I looked at a small render and called it "strands parting
correctly". Zoomed in, the thigh was punching straight through and being drawn on top of
the cloth. It was real clipping.
**Root cause was mine, and it was not the strand rebuild.** The rebuild changed
CONSTRUCTION but never added CLEARANCE — measured cloth radius 0.190 hip / 0.201 thigh /
0.202 knee against a body of 0.180.22, i.e. still the 13 cm ease the skirt-rig session
flagged at the very start. Strands made the gaps *legitimate*; they did not move cloth off
the leg.
**Two changes, both needed:**
1. **`fit.hem_mm` 14 → 80.** Gives the skirt a real BELL the leg swings *inside*, instead
of cloth lying on the thigh for the leg to punch through. Graded 0 at hip → full at hem,
so the waistband still rides the hip. **45 was not enough; 80 holds.**
2. **Thigh follow 0.128 → 0.698**, via `SKIRT_HEM_FREE=0.02 SKIRT_FOLLOW_MAX=0.70
SKIRT_CONTACT_R=0.22 SKIRT_FALLOFF=0.14`. `HEM_FREE`'s default of 0.55 put this skirt's
whole KNEE region (hem y 0.451, knee y 0.517) inside the no-follow band. This is the
influence that makes the skirt *travel with the knee* — exactly what Jeremy described.
**⚠️ TOLD THE SKIRT-RIG SESSION:** I added **env overrides** to their
`clothing/skirt_garment_weights.py` (`FOLLOW_MAX / CONTACT_R / FALLOFF / HEM_FREE`).
**Defaults are unchanged**, so `kapahaka_legs_sb_f` and anything else is unaffected — but
they own that file and should know. Their comment explains `FOLLOW_MAX` was cut 0.55 → 0.20
because the hem rode up on the swing; that tuning was against the SHORT continuous piupiu,
and this longer strand skirt needs different numbers. Per-garment tuning is why the
overrides exist rather than a changed default.
**⚠️ ORDER-SENSITIVE MANUAL STEP.** The weights command is recorded in
`configs/bottomTest1.json` under `post_export`. It must run AFTER `export` and BEFORE
`import`, and re-running the pipeline without re-running it silently ships the 2-segment
blend. Nothing enforces this — `garment.py` has no stage for it.
**Not chased:** the cloth reads slightly translucent in-game (legs faintly visible through
strands) — likely a material/alpha or single-sided issue, not clipping. And the **top's
neckline gape is still open** (below).
---
## ✅ IN GAME — downstream complete 2026-07-31
Both garments are through the pipeline and **on Lena in the clothing test bed**, all three
clips. Configs: `clothing/configs/bottomTest1.json`, `topTest1.json`. Set `Test1` →
`ariki-game/assets/quaternius/outfits/test1/Female_Test1_{Legs,Body}.gltf`.
Spawn (BODY_OVERRIDE is mandatory — the 4-segment ring only exists on that body):
```bash
SESSION_ID=md-clothing MOCK_ONLY=1 SCENE=clothing_test_bed BED_GENDER=1 SKIRT_RIG_DEBUG=1 BODY_OVERRIDE=res://assets/quaternius/derived-bodies/Ariki_Female_QuatSkin_SkirtRig_4seg.glb WAIT=1 bash tools/game.sh spawn
```
The bed already defaults to this set (`DefaultBodyF/LegsF` in `ClothingTestBed.cs`).
### What worked
- **Placement needs NO z_nudge.** `final_top = bone_head_z(top_bone) + z_nudge`, so with
`z_nudge = 0` the bottom lands band-top 1.076 / hem 0.451 and the top lands hem 1.052 —
all inside their QC targets. The old configs' 0.037 / 0.0483 were compensating for the
older, badly-placed drapes. Don't copy them forward.
- **G2 PASS** on both. **G8 PASS** (38 entries / 38 assets). **G7 PASS** on both reimports.
- **The bottom reads correctly in game** across Idle / Walk / Dance: strands hang, part
around the moving leg, the waistband rides the hip, no hitching to one side. Leg visible
between strands reads as intentional — the design premise holds up in motion.
- `clothing/skirt_garment_weights.py` (it MOVED here from ariki-game) rebound 32 joints /
8 strands × 4 segments with **no dead strands** and a 1.96 heaviest:lightest ratio.
### Two things that need knowing
**1. `planar_deg` must be 0 for strand garments.** `reduce` applies a planar DISSOLVE
*unconditionally*, before the tri-budget check. The default 5 collapsed the bottom from
3012 → **884 verts** on flat strands, destroying the vertical rows the 4-segment ring needs
to bend (the rig session's ask 4). The 8000-tri budget was never the constraint (4136).
**2. G5 is NOT predictive for a skirt-boned garment — do not read its number as truth.**
At full density G5 measures the bottom at 259357 penetrating verts in Walk, *worse* than
v2's 61169. Two reasons that number is not the shipped behaviour, and the in-game result
disagrees with it:
- It measures `garment_pipeline.py`'s **2-segment** `strand_weights`, which
`skirt_garment_weights.py` then overwrites (their wiki: skirt weights have ONE owner).
- Blender **cannot run the spring simulation**, which is the thing that moves strands
out of a leg's way.
The renders confirm the penetration is geometrically real *in that pose with those
weights* — it is simply not the pose/weights the game uses. **The bed is the only valid
test for this garment class.** Note also the earlier 4648-vert reading was taken at 884
verts and is not comparable to v2's 2573-vert baseline.
### The one real defect: the top's neckline gapes in game
Not present in MD — the MD drape is a modest rounded scoop; in game it is a plunging V
that opens down the sternum, exposing the body's tank strap and skin. This is the same
**G6-class in-game defect** the earlier plan recorded for the old pari and explicitly noted
"did NOT reproduce in the Blender checkpoint". It has now reproduced on the new top, so it
is a property of the skinning/deformation, not of the authoring. G5 does not catch it (its
39-vert rest reading is a different measurement). **This is the next job.** Suspect the
free neckline edge taking body weights across the sternum via `weights: "dress"`.
---
## ⏸ RESUME HERE — state at 2026-07-31 end of session
**MD bridge is stopped.** Both garments are exported and their recipes reproduce them.
Nothing has been through the downstream Blender pipeline, so nothing is in the game and
there are no penetration numbers for either mesh.
| | file | state |
|---|---|---|
| bottom (`bottomTest1`) | `tools/tailor/lena_piupiu_v3.*` | 32 discrete flax strands. **Re-opened and fixed 2026-07-31** — see below. 3022 verts, hem 0.461 m (in tolerance), band top 1.086 m (6 mm over; take `align.z_nudge` 0.037 → 0.031). Front/back/side all clean and symmetric. |
| top (`topTest1`) | `tools/tailor/lena_pari_v4.*` | 1406 verts, hem 1.082 m. Five defects found and fixed. Two cosmetic issues remain. |
**Bottom — what the resumed pass found.** Two hypotheses were wrong and the third was
a bug I had introduced:
1. *Inside-out back panel* (the top's disease) — **not present.** Untextured, both front
and back read white. Worth knowing why: the skirt arranges on `Leg_Skirt_*` and the
top on `Body_*_Center_1`, and those families evidently differ in orientation.
2. *The inherited `50/0` arrangement split* — **not a bug here, and load-bearing.** Swept
all four combos with a validated control: `50/0` and `0/50` both drape (band top 1.093
/ 1.095) while `50/50` and `0/0` drop the skirt **on the floor** (0.10 / 0.14). For
`Leg_Skirt_*` the x values must DIFFER; for the top's `Body_*_Center_1` they must
MATCH. **Never harmonise the two recipes.** Both skills were corrected — the rule as I
first wrote it was wrong.
3. *The actual defect* — my comb outline was **asymmetric**. The leftmost tooth got a
half-gap and the rightmost did not (the loop skipped its band-bottom step point on the
first iteration), and the closing band edge was **slanted** rather than vertical. The
fingerprint said so — `[60.0, 60.075, 60.0, 60.075]` — and I dismissed the 0.075 as
rounding. Fixed: all four edges now measure 60.0, the side-seam notch is gone, and
placement improved as a side effect (band top 1.097 → 1.086).
Residual on the bottom: a small grey wedge where the band's unfinished top edge curls,
on both sides. It sits under the top garment's hem, so it is likely invisible in game —
confirm rather than assume.
**Top — the open question, and it is well-defined.** The remaining diagonal back crease
IS solved in principle: it is the back panel being inside out, and `MIRROR_BACK = True`
plus the `PAIRS` index remap eliminates it completely. It is not shipped because it
changes the fit and coverage regresses (510 under-covers, 560 collapses). **The v5 job is
a fitting pass, not a debugging one:** enable the mirror, then re-fit `W_BACK` / `TAPER` /
`TOP_BACK` from scratch somewhere between 510 and 560. Details in §1.
**Recommended next move — do this before more MD time.** Run both garments through the
downstream pipeline (§2.A, §2.B) and see what survives `fit` and decimation. Some of the
remaining cosmetic issues may not reach the game at all, and G5 will give the first real
number on whether the strand rebuild fixed the leg clipping.
**New skill content written this session:**
`.claude/skills/marvelous-designer/references/tooling.md` — which API call for which job,
verified signatures, the render/measure toolkit, the seam-pairing decision table, harness
patterns. `SKILL.md` was also corrected: its strengthen rule and its seam-pairing rule
were both stated unconditionally and are actually conditional.
---
You are picking up a garment job mid-flight. The **upstream (Marvelous Designer) half is
finished and exported**; the **downstream (Blender → ariki-game) half has not been run at
all**. Nothing from this session is in the game yet, and no penetration numbers exist for
the new meshes. Your job is mostly downstream, plus one blocked handoff to another agent.
---
## 0. Orientation — one thing everyone gets wrong
**The garment sources live in THIS repo, at `tools/tailor/`.** A concurrent session
concluded "there is no source file for this garment, only the exported glTF" and proposed
recreating it. That was wrong — they searched `ariki-game`. The game mesh
`Pattern_31891` is literally the front-panel group inside
`tools/tailor/lena_piupiu_v2_garment.obj`. Every garment has a `.zprj` (editable source)
plus a per-garment recipe script `tools/tailor/md_*.py`. Never recreate; re-export.
**Backup:** everything as it stood before this session is in
`tools/tailor/backup/2026-07-31-pre-v3/` (55 files, 86 MB, SHA1-verified, with
`MANIFEST.md` carrying restore commands). `tools/tailor/` is **untracked in git**, so that
folder is the only safety net. Don't delete it, and don't `git add` the 9.7 MB `.zprj`
files casually.
---
## 1. What this session produced
Two new garments, both draped on `Ariki_Female_QuatSkin.glb` and exported four ways
(`.zprj` / `_garment.fbx` / `_garment.obj` / `.zpac`) into `tools/tailor/`.
### Piupiu v3 — rebuilt as discrete flax strands
`lena_piupiu_v3.*`, recipe `tools/tailor/md_piupiu_v3.py`, render
`tools/tailor/screenshots/lena_piupiu_v3.png`.
v2 was a taut pencil wrap — waist cut 1000 mm against Lena's 1095 mm hips, i.e. tighter
than the hip it passes over, leaving 13 cm of ease. G3 measured 8 mm penetration **at
rest**; G5 measured 216231 verts up to 8.5 cm deep across Idle/Walk. v3 is 32 discrete
strands hanging from a rigid 60 mm waistband (Jeremy's call, 2026-07-31), built as two
comb-shaped panels with the strands cut into the outline and only the two band side edges
sewn. Rationale: a real piupiu *is* strands, gaps make "leg visible between strands"
correct rather than a defect, strands can't crease like a tube, and 32 strands = 4 per
bundle on the rig's 8-strand ring.
Measured off the exported OBJ:
| | v2 | v3 | target |
|---|---|---|---|
| verts (pre-decimate) | 3686 | **3012** | — |
| waistband top | — | **1.089 m** | 1.05 ±0.03 |
| hem | — | **0.468 m** | 0.45 ±0.04 |
| verts above bone ring (y 0.952) | 40% | **22.8%** | lower is better |
Band top is 9 mm over tolerance — absorb it downstream by taking
`align.z_nudge` from `0.037` to `0.028`, which also lands the hem at ~0.459.
### Pari v4 / topTest1 — flat front and back
`lena_pari_v4.*`, recipe `tools/tailor/md_pari_v4.py`, renders
`screenshots/lena_pari_v4.png` (front) + `lena_pari_v4_back.png` (back).
Sweep harness kept at `tools/tailor/md_pari_seamsweep.py`.
**ROOT CAUSE — a twisted side seam, not surplus cloth.** `md_pari.py` sews all four
seams with `AddSeamlinePairGroup(..., False, False)`. For the SIDE seams that is wrong:
it twists the panel, so part of the back turns inside out and rucks up, leaving the
lower back bare. The skill's rule reads "same line index on **mirrored** front/back
panels with `(False, False)`" — but `panel()` returns the *identical* point list for
both panels, only offset by dx, so they are **not mirrored**. Front-right and back-right
run in opposite directions once wrapped on the body, and joining them needs both edges
reversed: **`(True, True)`**. Proven by sweeping all four combos at fixed geometry:
`(0,0)`, `(0,1)` and `(1,0)` all produce the reverse-face flap; only `(1,1)` is clean.
Shoulder seams stay `(False, False)` — they were always right, which is why the front
never looked wrong. `md_piupiu_v3.py` dodged this by pairing right↔left.
**Fourth fix — back neckline height.** `SCOOP_BACK` was 45 mm, which put the back
neckline BELOW the body's baked-in tank neckline, so the tank showed as a cream band at
the back neck — it reads as the shirt not meeting the back and clipping through. Covering
that baked-in underwear is the entire reason the straps exist (per the skill). `SCOOP_BACK
= 15` closes it.
**Third fix — symmetric arrangement.** `md_pari.py` calls
`SetArrangementPosition(pf, 50, …)` but `SetArrangementPosition(pb, 0, …)`. That 50/0
pair is asymmetric, and it produced a folded flap on the **right shoulder only** (left
shoulder clean) — the cloth's reverse face poking through, which reads as the shirt
clipping. An asymmetric defect needs an asymmetric input and this was the only one. Both
panels now use `x = 50`. **`md_piupiu.py` carries the same 50/0 pair**, so check the
bottom for the same defect when you next open it — it looks inherited, not intended.
**Second fix — taper.** The panels were rectangles: cut for a 1083 mm bust while the hem
sits on a 675 mm waist, so ~400 mm of surplus could only fold. `TAPER = 95` mm off each
side edge at the hem gives 380 + 320 = 700 mm vs waist 675 + 25 ease. Use the SAME taper
on both panels — side-seam length is `sqrt(TAPER² + SIDE_Y²)` = 208 mm on both, so the
whole-edge pairing stays matched; different tapers skew it.
| | v3 shipped | v4 |
|---|---|---|
| verts | 1889 | **1406** |
| hem | — | **1.082 m** (target 1.05 ±0.03) |
| back | reverse-face flap, bare lower back | flat, fully covered, hem level |
**Do not chase "band top 1.31 m".** That target predates the straps — it was written for
the original strapless tube. With straps, max-z is the strap over the shoulder (~1.44 m;
`shoulder_z` is 1.397). The target is stale for this design, not a failure.
**Tried and reverted, with reasons, all recorded in the recipe header:** width rebalance
(570/510 kept as correct tailoring but it was NOT the cause; 600/480 dragged the front
motif onto the back), `SetPatternStrengthen` (flattens the back but rotates the garment,
because the sides are sewn only over y 0185 and the straps are 90 mm — so the failure
catalogue's soft-from-frame-0 rule applies only to garments with fully sewn sides),
shortening the back panel to `TOP_BACK 315` (good theory, changed nothing), and crossing
every seam (garment slides to the hips — cross-wired straps cancel).
**THE BACK CREASE IS SOLVED IN PRINCIPLE BUT NOT SHIPPED — read before touching it.**
The persistent diagonal crease across the back was the back panel being **inside out**.
Cloth renders WHITE on its front face and GREY on its back; untextured, the front view is
white but the back view is grey, because both panels are drafted with the same winding, so
when MD arranges one behind the body its front face points INTO the body.
`MIRROR_BACK = True` **plus** remapping the seam pair indices (the `PAIRS` block —
front `shoulder_l` ↔ back `shoulder_r`, front `side_r` ↔ back `side_l`, flags back to
`(False, False)`) **eliminates the crease completely** — the back comes out flat and
smooth. That is the proof of cause.
It is NOT shipped because it changes the effective fit and coverage regresses:
- `W_BACK 510` + mirror → flat back, but the panel under-covers: the body's tank shows at
the lower back and the front panel wraps round the sides.
- `W_BACK 560` + mirror → collapses into a diagonal band. Worse than either.
A correct v5 enables the mirror and then **re-fits `W_BACK` / `TAPER` / `TOP_BACK` from
scratch**, somewhere between 510 and 560. Shipped state is un-mirrored, because coverage
beats a crease. This is also why `SIDE_FLAGS` must stay `(True, True)` — it is compensating
for the un-mirrored panel.
**Also tested and rejected:** a concave armhole scye (`ARMHOLE_IN`, mid-armhole point
pulled inward, indices computed rather than hardcoded). Jeremy's hypothesis was that the
crease was surplus width bunching where the panels meet, and the arithmetic supported it —
bust and hem are matched (1080 vs 1083; 700 vs 675) but the panels stay near-full width
above the bust as the body narrows, ~968 mm of cloth at underarm height, with the side seam
only running y 0..185 so the edges above it are free. At `ARMHOLE_IN = 55` the crease
survived **unchanged**, so surplus upper width is not the cause. Left at 0.
**Remaining in the shipped state:** the diagonal back crease, and a small patch at the
lower centre back where the body's tank shows below the hem.
**Harness warning:** `tools/tailor/md_pari_armsweep.py` is BROKEN — its control case
(`arm00`, which should reproduce the working garment) drops the garment on the floor, so
all four of its results are void. `md_pari_seamsweep.py` worked. Don't trust armsweep
output until the harness is fixed; iterate in `md_pari_v4.py` itself, which is known good.
The pari's *other* known defect — the in-game neckline gape — **did not reproduce** in the
Blender checkpoint, so it is G6/in-game territory (attach, material, or LOD), not
authoring.
---
## 2. Work outstanding, in priority order
### A. Run piupiu v3 through the downstream pipeline and get a G5 number — the proof
Nothing about v3 is validated in-game. This is the payoff and it's fast (`census..export`
ran in 26 s in the pipeline's own acceptance test).
1. Write `clothing/configs/piupiu_v3.json`. **Do not edit `piupiu.json` or
`piupiu_sb.json`** — a concurrent session owns those. Copy
`clothing/configs/tests/piupiu_sb_test.json` (it already has an `expect` block and a
**scratch** export target) and change:
- `source` → `tools/tailor/lena_piupiu_v3_garment.fbx`
- `align.z_nudge` → `0.028` (see above)
- `expect.islands` → **rewrite from the new census, don't reuse v2's numbers.** v2's
block expects 34003800 verts; v3 is 3012.
2. **Island mapping is the live hazard here.** MD renumbers patterns every session — the
v3 groups are `Pattern_18856` / `Pattern_20333`, where v2's were `Pattern_31891` /
`Pattern_32004`. `clothing/README.md` calls a config mapping the wrong islands "the
silent killer", and G2 exists to catch it. Read `work/piupiu_v3/census.json` and map
from that, not from memory.
3. Run it, then read the G5 output and compare against v2's baseline
(`rest 1 vert / Idle 216231 verts / Walk 61169 verts`, max 8.5 cm).
4. **Set expectations honestly when you report:** clearance alone cannot fix this. The
hem target sits 0.495 m below the hip pivot, so 30° of hip flexion sweeps the leg
~25 cm forward — more than any believable piupiu silhouette clears. v3 should
eliminate the rest-pose failure and cut the motion numbers a lot; the runtime
spring-bone capsules do the last part. Don't claim more than the numbers show.
### B. Run pari v4 / topTest1 downstream
New config `clothing/configs/topTest1.json`; leave the shipped `pari.json` intact.
1363 verts. The seam fix means the back geometry changed substantially from v3, so
rewrite `expect.islands` from the new census rather than reusing v3's numbers — same
hazard as §2.A. The in-game neckline gape is G6 territory (see §1).
### C. ~~Unblock the skirt-rig session~~ — DONE on their side, your move now
**Step 1 is already delivered.** They shipped the 32-skirt-joint body:
```
ariki-game/assets/quaternius/derived-bodies/Ariki_Female_QuatSkin_SkirtRig_4seg.glb
97 joints (65 + 8 strands × 4 segments) · fracs 0.15/0.20/0.30/0.35
collision particles at y 0.884 / 0.793 / 0.656 / 0.497
```
It is a **side file on purpose** — a garment's skin must carry every bone it references, so
a body and its garments have to change segment count together, and the live 2-segment body
must keep working until a 32-joint garment exists. Point your config's `body` at `_4seg`;
they promote it over the canonical name once your export validates.
⚠️ **The tool paths in this section moved repos.** Clothing authoring now lives in THIS
repo — ariki-game receives delivered outfits and no longer holds authoring code:
| Was (ariki-game) | Now (this repo) |
|---|---|
| `tools/make_skirt_rig_body.py` | `clothing/skirt_rig_body.py` |
| `tools/skirt_garment_weights.py` | `clothing/skirt_garment_weights.py` |
Both resolve asset paths into the game checkout as a sibling; override with
`ARIKI_GAME_ROOT`. Segment count is now `SKIRT_SEGMENT_FRACS=0.15,0.2,0.3,0.35` (env) —
no source edit needed to build a different ring.
So the remaining sequence is entirely yours: **re-run the pipeline against `_4seg`** (the
glTF exporter emits every armature bone as a joint, so the skin picks up all 97
automatically — no pipeline change), **then run `clothing/skirt_garment_weights.py`**,
which reads segment count off the rig and needs no changes.
Also tell them: v3's 32 strands are uniform strips, which answers their ask 4 (azimuthal
density was varying 56:1) **by construction**, and the 60 mm band answers ask 5 (40% →
22.8% of verts above the ring).
### D. Pipeline bug worth fixing: G5 measures weights the game never uses
`clothing/garment_pipeline.py:876` `strand_weights` is hardcoded to two segments (root +
one tip). The game's actual weights come from `skirt_garment_weights.py`, run afterwards,
which overwrites them. So **G5's verdict and the test bed will disagree** until the
pipeline's own blend goes N-segment. Not urgent for shipping, very confusing if unknown.
Worse than a disagreeing gate, now that both tools live in this repo: **whichever runs last
wins and nothing enforces the order.** Re-running the pipeline after the weights tool
silently ships the 2-segment blend and reads as a rig regression. Agreed resolution —
**skirt weights have ONE owner, `skirt_garment_weights.py`.** For `weights: "skirt_bones"`
configs the pipeline should skip strand weighting entirely (or emit zeros) and G5 should
measure the file *after* the weights tool runs. Alternative, equally fine: port the
N-segment blend into the pipeline and delete the separate tool. Either way, one owner.
### E. Fold the findings into the docs
The MD skill's API-facts table and failure catalogue should gain the items in §3 —
especially the no-rear-view discovery, which is the reason a back defect shipped at all.
---
## 3. Verified traps (all cost time this session)
| Fact | Consequence |
|---|---|
| **`SetCamViewPoint` has NO rear view.** All 8 shot: 0 bottom, 1 & 3 front quadrants, 2 front, 4/6/7 sides, 5 top. | `ExportSnapshot3D` structurally cannot show the back. Every screenshot in this repo is a front view, which is how the pari's ballooning back shipped. Use `export_api.ExportTurntableImages(4)` — **index 2 is the back**. |
| `ExportTurntableImages` ignores its path arg in the `(int)` overload | It writes into MD's own output folder (`%LOCALAPPDATA%\CLO Virtual Fashion\Marvelous Designer Personal\<n>\output*.png`). The return value is the only way to find the files. The `(path, count, w, h, i)` overload returned `[]`. |
| `SetArrangementPosition` takes **4 ints** | A float raises `TypeError`. |
| Skirts arrange on **`Leg_Skirt_Front` / `Leg_Skirt_Back`** with y=92 | v2 used `Body_*_Waist` and G1 measured it +8/+14 cm high. Switching fixed placement to within 9 mm. |
| A light garment **does not slide to the waist** | A 60 mm band has none of v2's solid-panel mass, so it stays exactly where it's arranged. Arrangement height *is* the placement. First v3 drape sat at the chest. |
| **OBJ exports in mm; the FBX path reads as decimetres** | The census reads dm and `align.scale_z: 0.1` converts. The two exporters disagree — cost one wasted run. |
| `qc_placement.py` cannot measure a strand garment | Its pixel classifier needs a solid silhouette; on v3 it reported a 1.71 m span (nonsense). `md_piupiu_v3.py` now measures the exported OBJ geometry directly instead. |
| `fabric_api` has **no physics setters** | Stiffness comes from the `.zfab` preset. Stiff ladder: `V2_Woven_Canvas_1` < `V2_Woven_Denim_1` < `V2_Non-Fabric_Tyvek_1`. |
| `fit`'s `wrap_mode: "OUTSIDE"` only moves verts that are **inside** the target | Ease manufactured in MD survives the pipeline, and `hem_mm: 14` pushes too-close hem fabric further out. They're complementary, not fighting. |
| **`SetArrangementPosition` x must match across panels** | `md_pari.py` and `md_piupiu.py` both use front `x=50` / back `x=0`. That asymmetry folds one shoulder only, showing the cloth's reverse face — it reads as the garment clipping through. Use the same x on both panels. |
| **Untextured renders show face orientation** | Cloth is WHITE on its front face, GREY on its back. A garment panel showing grey from outside is inside out. This is the fastest way to tell a genuine fold from a winding problem — and a busy motif hides both completely. |
| **Seam reversal flags matter on non-mirrored panels** | `AddSeamlinePairGroup`'s two booleans reverse edge traversal. Identically-drafted (non-mirrored) front/back panels sewn same-index need **`(True, True)`** on the side seams; `(False, False)` twists the panel and turns part of it inside out. Diagnose by rendering UNTEXTURED — the cloth's reverse face is the tell. A busy motif hides it completely. |
| **The strengthen rule is not universal** | The skill says soft-from-frame-0 bunches, and strengthening *does* flatten the pari's back — but it also **rotates** the garment, because the pari's sides are sewn only over y 0185 and its straps are 90 mm, leaving it under-constrained. Strengthen applies to garments whose sides are fully sewn. Where it doesn't, remove surplus cloth instead. |
| Writing `tools/tailor/*.py` with Python's **locale codec corrupts them** | These files contain box-drawing and em-dash characters. Always `io.open(..., encoding='utf-8')`. A `open(p,'w').write(...)` cp1252 round-trip broke `md_piupiu_v3.py` and the bridge then failed to read it. |
---
## 4. Session mechanics
- MD needs a **human click** (Plugin → TinqsMDBridge) to start; you cannot start one.
MD's UI is frozen for the whole session ("Not Responding" is normal).
- The bridge was **stopped** at the end of this session, so MD is idle and usable. If you
need it again, ask Jeremy to click, then `python tools/md_bridge.py --ping`.
- `python tools/md_bridge.py --stop` when you hand back a result — Jeremy inspects in
MD's viewport and can't while the bridge holds the main thread.
- `Simulate()` needs a raised client `--timeout` (~1 min per 300 frames; use `--timeout 880`).
## 5. Don't break these
- `tools/tailor/backup/2026-07-31-pre-v3/` — the only copy of the pre-session state.
- `clothing/configs/piupiu.json`, `piupiu_sb.json` and the `Female_KapahakaSB_*` outputs —
the skirt-rig session owns them; they are the live comparison in the test bed and the
deprecated set gets deleted only once `Test1` validates. Write `Test1` to new configs and
a new `outfits/test1/` folder; leave these alone.
- v1/v2/v3 garment files — versioned, never overwritten. Keep it that way.
- `ariki-game` is read-only by convention for this repo's tooling; the `register` stage
emits a paste-in `Add(...)` line rather than editing `src/`.