3ba86b2ea8
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>
535 lines
34 KiB
Markdown
535 lines
34 KiB
Markdown
# 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 **68–83** per row vs 0–13 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.18–0.22, i.e. still the 1–3 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 259–357 penetrating verts in Walk, *worse* than
|
||
v2's 61–169. 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 46–48-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 1–3 cm of ease. G3 measured 8 mm penetration **at
|
||
rest**; G5 measured 216–231 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 0–185 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 3400–3800 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 216–231 verts / Walk 61–169 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 5–6: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 0–185 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/`.
|