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>
24 KiB
Plan: Clothing-pipeline unification — one entry point, QC gates at every seam
Status: Implemented 2026-07-31 (phases 1–4 built; catalog JSON still deferred) · Author: Fable 5 session 2026-07-31 · Implementer: 5-agent parallel build + integration pass, 2026-07-31 — see §8
0. Context (you have no other context — read this fully)
The clothing lane turns a garment idea into an outfit part an ariki-game character wears. It has three halves that work but do not talk to each other:
- MD upstream (this repo): Marvelous Designer 2024, driven over a TCP bridge
(
tools/md_bridge.py, port 18900). Per-garment scripts intools/tailor/draft panels, sew, drape on the Lena avatar FBX, texture, and snapshot. Session start is a human click (Plugin > TinqsMDBridge); MD's UI freezes while a script runs. - Blender downstream (this repo):
clothing/garment_pipeline.py— seven config-driven headless stages (census prepare fit reduce bake skin export), each checkpointing a.blend+ QA renders intowork/<name>/, exporting per-slot GLBs onto the shared 65-bone Quaternius skeleton intoariki-game/assets/quaternius/outfits/<set>/. - Game side (
ariki-game, read-only for this repo's tooling by convention): hand-editsrc/Character/OutfitCatalog.csto register the item, force a Godot reimport, spawnClothingTestBedviatools/game.sh, click through Idle/Walk/Dance, and eyeball the result.
The lane is PROVEN — the downloaded-dress pilot (2026-07-30) and the MD-authored kapa haka set (pari + piupiu, 2026-07-31) are in game. But both kapa haka pieces shipped with pose-dependent defects invisible to every QA artifact the pipeline produces: the pari neckline gapes open in walk/dance, and a thigh punches through the piupiu skirt. All pipeline QA renders are rest pose — the one pose that cannot fail. A separate agent owns those two weight fixes; this document is the pipeline-level response: an assessment, a design that combines the three halves into one entry point, and the QC gates that would have caught both bugs before the game.
Source of truth for how the lane works today: .agents/wiki/architecture/clothing-lane.md
and clothing/README.md. This document is a proposal for how it should work; on
adoption, fold the outcome into clothing-lane.md.
1. Assessment
1.1 What already works (keep it)
- Config-driven downstream. New garment = new JSON in
clothing/configs/, not new code. The schema already expresses island→part mapping, bodyshell parts, alignment, fit masks, weld bands, proxies, weight modes, textures, per-part tri budgets. - Checkpointed stages.
00_census.blend … 50_skin.blendmean any stage can be re-run in isolation; resume semantics exist for free. - Determinism fingerprint.
exportprints a SHA1 over all garment vertices (garment_pipeline.py:984-989). - The seam is crossed. MD-authored garments arrive at game budget, pre-fitted to
Ariki_Female_QuatSkin.glb—reduceis only hard for downloaded meshes. - All the automation hooks exist on the game side.
game.sh spawn(multi-instance,SPAWN_BUILD=1,MOCK_ONLY=1), agent API (/health /screenshot /navigate /state /scene /ui /command /input /console),game.sh clean-import,tools/asset_pipeline.py check.
1.2 What costs time (the streamlining targets)
| Cost | Evidence |
|---|---|
| 4× duplicated MD garment scripts | md_pari.py, md_piupiu.py, md_tee_v1.py, md_skirt_v1.py share a ~100% identical shell (NewProject → ImportFBX at op.scale=10.0 → fabric → arrange → simulate → snapshot); only panels/seams differ |
| Config copy-paste | piupiu.json vs piupiu_sb.json: 48 lines, differing in exactly 4 values (name, body, weights, export.set) + a note |
| Six manual CLI invocations per garment | one blender --background … --stage <s> per stage, run by hand in order |
| Hand-authored configs | read census.json, guess island→part mapping, trial-and-error alignment against QA renders |
| Code-edit registration | 2 Add() lines + possibly a BaseDirFor case in OutfitCatalog.cs, then a C# rebuild |
| Blunt reimport | .bin-only changes are invisible to Godot's .gltf hash; today's fix is clean-import (wipes ALL of .godot/imported/) or hand-deleting entries |
| Eyeball sign-off | spawn test bed, click Idle/Walk/Dance, look |
1.3 What is structurally unsafe
- Rest-pose-only QA. Every
qa_*.pngthe pipeline writes is the fitting pose. Both shipped bugs were invisible in it. (clothing/README.md"QA renders only prove the pose that cannot fail".) - Silent catalog fallback.
OutfitCatalog.BaseDirFor(OutfitCatalog.cs:88-98) takes a bare-stringsetIdand its_ =>arm silently resolves to the Fantasy pack folder — a typo'd set name mis-resolves without error. Missing assets are warn-only (OutfitCatalog.cs:71-78): the character just stays naked/previous. .binstaleness.GLTF_SEPARATEexport keeps geometry/UVs in the sidecar.bin; Godot hashes only the.gltf, so a vertex-only re-export silently ships the old mesh.
1.4 Corrections to the record (verified 2026-07-31)
Earlier session notes contained four errors that materially change the design:
| Prior claim | Truth | Consequence |
|---|---|---|
| "FBX export from MD is manual" | export_api.ExportFBX(path, op) (+ ExportOBJ/ExportZPrj/ExportZPac) is scriptable — exact flags in .claude/skills/marvelous-designer/SKILL.md:182-190 (op.bExportGarment=True; op.bExportAvatar=False); the 4 garment scripts simply never call it |
The entire MD half can run unattended after the one session click |
ExportSnapshot3D is the mesh export |
It is the viewport PNG (the vision-QC loop); mesh export is ExportFBX |
Don't build on the wrong call |
| "5 duplicated MD scripts" | 4 garment scripts; md_recon.py is an API-introspection tool and stays standalone |
Template scope |
tools/vision-compare.py exists (referenced by ariki-game/tools/visual-qa.sh) |
It was never written | The in-game gate has no judge today; VLM-vs-pixel-diff is a real decision, not wiring |
One permanent constraint, confirmed: MD exposes no mesh introspection to Python
(GetClothPositions() stays empty). Every upstream drape judgement is image-based,
by necessity, until CLO exposes more.
Two enabling facts: the config's export block already carries gender/set/out_dir
and each part carries slot, so the catalog's exact {Gender}_{Set}_{Slot}.gltf path is
derivable from the config today; and the agent API's GET /console surfaces
ClothingTestBed's per-slot load-failure prints, so outfit load failure is
machine-detectable without vision.
2. The unified pipeline
2.1 One config, additive blocks
Keep clothing/configs/<garment>.json as the single per-garment artifact and grow it
— the existing Blender schema stays byte-compatible, so garment_pipeline.py needs no
migration:
{
"extends": "piupiu.json", // NEW: overlay inheritance
"name": "...", "source": "...", "body": "...",
"md": { // NEW: upstream draft params (what the 4 scripts differ on)
"avatar_fbx": "...", "zfab": "...", "texture": "...",
"panels": [...], "seams": [...], "arrangements": [...],
"sim_frames": 300, "strengthen": [...],
"export_basename": "lena_<garment>_v<N>"
},
"align": {...}, "parts": {...}, // UNCHANGED: existing Blender schema
"export": { "gender": "Female", "set": "Kapahaka", "out_dir": "..." }, // exists
"catalog": { // NEW: what Add() needs that nothing else holds
"id": "kapahaka_legs_f", "displayName": "Piupiu",
"charisma": 0.05, "workSpeed": 0.04, "category": "casual"
},
"expect": {...} // NEW: QC thresholds (§3)
}
extendsis the cheapest real win:piupiu_sb.jsonbecomes ~8 lines. Overlay = deep-merge child over parent, arrays replaced whole.mdblock + one template scripttools/tailor/draft_garment.pyreplaces the 4 copy-paste scripts. The identical shell becomes the template; panels/seams/arrangements come from the config. Hard-won MD lessons (mm units, +y up in 2D, whole-edge seam indices, look up arrangement points by name,SetArrangementthenResetClothArrangement) live in ONE place instead of four.catalogblock carries only what the C#Add(id, displayName, slot, slotSuffix, charisma, workSpeed, category, set:, gender:)signature needs and the pipeline has never held;slot/set/genderderive from existing blocks.
2.2 One orchestrator
clothing/garment.py <config> [--from <stage>] [--to <stage>] [--only <stage>]
over one ordered stage list spanning all three worlds:
draft drape publish │ census prepare fit reduce bake skin export │ register import verify
└──── MD bridge ──────┘└──────── Blender headless ────────────────┘└──── ariki-game ─────┘
A driver, not a rewrite. Each stage shells out to the tool that already owns it:
| Stage group | Shells to | Notes |
|---|---|---|
draft/drape/publish |
python tools/md_bridge.py --file <generated> |
Script generated from the md block via draft_garment.py. --ping first; if no session, fail fast printing the human instruction ("click Plugin > TinqsMDBridge"). Raise the idle timeout (~1 min per 300 sim frames). publish = scripted ExportZPrj + ExportFBX + ExportZPac (§1.4). |
census … export |
blender --background --python garment_pipeline.py -- --config X --stage Y |
Unchanged CLI; --from/--to is a loop. Standalone stage runs keep working exactly as today. |
register/import/verify |
bash ariki-game/tools/game.sh … + agent API |
§2.4 and gates G6/G7/G8. Decided: orchestrator lives in tinqs/animation/clothing/, cross-repo shell-out to game.sh is acceptable. |
Checkpoint numbering (00_ … 50_) already gives resume: --from fit loads
10_prepare.blend exactly as today.
2.3 The automation boundary (permanent, human-only)
- Session start is a click — Plugin > TinqsMDBridge, once per MD session (not per
garment). The orchestrator detects (
--ping) and instructs; it can never perform it. - MD's UI freezes during a blocking call — no mid-drape supervision; results are
visible only after
Simulatereturns. - No mesh introspection in MD — upstream QC is image-based forever (§1.4).
Everything else — including the FBX export previously believed manual — is scriptable. That is the headline of this assessment.
2.4 Registration: emit, don't edit (catalog JSON deferred)
Decided 2026-07-31: defer the data-driven OutfitCatalog refactor.
- Cost today is 2
Add()lines per set (+1BaseDirForcase when a new folder appears); the refactor is not small (Initialize()is all hardcoded calls;ClothingItemhas 10 init-only properties to round-trip). Revisit at ~10 sets. - The real risk is not typing, it is the silent
setIdfallback (§1.3) — gate G8 (a ~30-line lint) buys that safety without the refactor. - The
registerstage therefore emits the exactAdd(...)line(s) and any neededBaseDirForcase to stdout +work/<name>/register.cs.txtfor paste-in. Codegen-as-text, not codegen-as-edit — honest about who ownssrc/.
3. QC gates (where agents/checks slot in)
Ranked by value = known-pain-caught ÷ effort. Both shipped bugs were pose-dependent, so
the ranking is driven by how early a pose-dependent failure can be caught. Thresholds
live in the config's expect block.
| # | Gate | After stage | Kind |
|---|---|---|---|
| G5 | Posed penetration + gape sweep — the centrepiece | skin |
scriptable |
| G6 | In-game motion QC | import |
agent-with-vision (hard part scriptable) |
| G3 | Rest-pose penetration | fit |
scriptable |
| G2 | Census sanity | census |
scriptable |
| G8 | Catalog ↔ asset lint | register |
scriptable |
| G7 | Targeted reimport verify | import |
scriptable |
| G4 | Reduce budget + silhouette | reduce |
scriptable |
| G1 | Drape placement | drape |
hybrid |
G5 — posed penetration + gape sweep. Headless Blender loads 50_skin.blend (it
already contains BODY, RIG, and the skinned GARM_* parts). Apply real clip poses —
the UAL animation packs (ariki-game/assets/quaternius/anim/UAL1.glb etc.) are authored
on the same 65-bone skeleton — or synthetic extremes (arm raise, deep step, torso twist).
Per sampled frame, evaluate the armature-deformed meshes and BVH-test: (i) garment verts
inside the body, (ii) body verts exposed inside a part's declared coverage band.
Pass/fail: expect.max_penetrating_verts (e.g. 0 verts >1 mm inside) and
expect.max_gape_verts across all frames. This is the only gate that would have
caught BOTH shipped bugs — deterministically, headless, pre-game, re-runnable. One risk
to verify during implementation: bone-name compatibility between the UAL pack armature
and the derived-body RIG (retarget or pose-copy if names drift).
G6 — in-game motion QC. SCENE=clothing_test_bed MOCK_ONLY=1 game.sh spawn →
POST /navigate {"button":"Idle"|"Walk"|"Dance"} → GET /screenshot per clip → judge.
Two signals: hard = GET /console contains no outfit load error (machine-checkable
today); soft = visual verdict on poke-through/gaping/texture. Decided 2026-07-31:
VLM/agent judge for a new garment's first sign-off, then bless those frames as the
baseline so re-runs become a deterministic pixel diff. This also finally gives
visual-qa.sh the comparator it references but never had (§1.4). Small ariki-game
enabler needed (approved): ClothingTestBed hardcodes its opening set
(DefaultBodyF = "kapahaka_body_f" etc., ClothingTestBed.cs:81-84) — add an env-var/CLI
override so a new set is directly reachable instead of blind Cycle Body presses.
G3 — rest-pose penetration. The same BVH test as G5, rest pose only, run right after
fit — a cheap early-fail subset that saves reduce/bake/skin time on a bad fit.
G2 — census sanity. census.json already records per-island verts/tris/z/x/centroid.
Check against expect.islands (count, per-mapped-island vert count and z-span
tolerance). Catches the silent killer: an MD re-export reorders islands and the config
maps the wrong ones.
G8 — catalog ↔ asset lint. Every catalog entry's resolved
{baseDir}/{Gender}_{Set}_{Slot}.gltf exists on disk, and every outfit GLB on disk has a
catalog entry. Zero unmatched either way. ~30 lines; kills the silent BaseDirFor
fallback footgun (§1.3) without the JSON refactor.
G7 — targeted reimport verify. Replace blunt clean-import in the garment loop:
delete only that asset's two .godot/imported/ entries (the README already prescribes
exactly this by hand), run --import, then confirm via /console + refreshed
.gltf.import. Closes the .bin-staleness hole (§1.3) without nuking the whole cache.
G4 — reduce budget + silhouette. Tris vs parts[].tris; pixel IoU of the HI vs
reduced QA renders ≥ threshold. Low urgency: MD-authored garments arrive at budget (both
kapa haka pieces never hit their ceilings) — this gate mostly guards downloaded meshes.
G1 — drape placement. The thresholds already exist as prose — md_pari.py:4 reads
"QC targets: band top ~1.31 m (above bust), hem ~1.05 m (waist) ±3 cm". Move them into
expect.bands, upgrade tools/tailor/qc_placement.py to read them (instead of its
hardcoded landmark table), and add a vision pass on the ExportSnapshot3D PNG. That one
move takes drape QC from eyeballed to gated.
4. Phased adoption
| Phase | Content | Effort | Why this order |
|---|---|---|---|
| 1 | extends overlay + expect block + G2 + G8 + orchestrator skeleton driving Blender stages only |
~½ day | Zero new subsystems, no MD, no game, no human in the loop. Immediately kills the 48-line config copy and the six-call ritual. |
| 2 | draft_garment.py template + md block + scripted ExportZPrj/FBX/ZPac in the same bridge call + G1 |
~1 day | Collapses 4 scripts to 1; MD half becomes unattended after the single session click. |
| 3 | G5 posed sweep (+ G3 as its rest-pose subset) | ~1 day | The money phase — catches the class of bug that has actually shipped, before the game. |
| 4 | G7 targeted reimport + ClothingTestBed set override + G6 motion QC + baseline blessing | ~1 day | Real shaders/import/AnimationTree; only meaningful after 1–3. Needs the small approved ariki-game edits. |
| 5 | Data-driven OutfitCatalog JSON |
deferred | Revisit ~10 sets; G8 buys the safety now. |
G4 rides along whenever convenient (it rarely bites for MD-authored garments) rather than blocking any phase.
5. Resolved decisions (Jeremy, 2026-07-31)
- Orchestrator home:
tinqs/animation/clothing/garment.py; game stages shell out cross-repo toariki-game/tools/game.sh. - G6 judge: VLM/agent vision judge for first sign-off; bless those frames; re-runs are deterministic pixel diffs.
- OutfitCatalog: stay code-edit; pipeline emits
Add()lines; add the G8 lint; JSON refactor deferred (~10 sets). ariki-game/srcedits: small enablers approved (ClothingTestBed set override, later testbed hooks). The pari/piupiu weight-fix agent ownsgarment_pipeline.py's skin stage + those two configs — coordinate before touching either.
6. Leave alone
garment_pipeline.py's--stageCLI and config schema — additive only; the orchestrator wraps, never rewrites.- The
skinstage internals andpari.json/piupiu.jsonweight fields — owned by the weight-fix agent right now. md_recon.py— introspection tool, not a garment script; does not fold into the template..agents/wiki/architecture/clothing-lane.md— cite it; fold outcomes in on adoption; never fork its content.
7. Appendix — file/line references
| Fact | Where |
|---|---|
| Stage implementations | clothing/garment_pipeline.py: census 224–248, prepare 295–505, fit 542–598, reduce 653–706, bake 712–768, skin 908–957 (weight modes 920–944, skirt_bones 836–905), export 964–989 (vertex SHA1 984–989) |
| Config parse / work dir / CLI | garment_pipeline.py:1006, :36-39, :994-1008 |
| MD bridge protocol/session | tools/md_bridge.py (port/env :88, :22; run/ping :58-68), docs/md-bridge.md:8-94 |
| Scriptable MD exports (flags) | .claude/skills/marvelous-designer/SKILL.md:182-190 |
| MD API lessons | tools/tailor/md_tee_v1.py:12-28, md_skirt_v1.py:6-25 |
| Drape QC thresholds as prose | tools/tailor/md_pari.py:4; classifier tools/tailor/qc_placement.py:24-34, bands :51-80 |
| Config diff piupiu vs piupiu_sb | clothing/configs/piupiu.json / piupiu_sb.json — name, body (_SkirtRig), parts.Piupiu.weights, export.set |
| Catalog path resolution + silent fallback | ariki-game/src/Character/OutfitCatalog.cs:57-98 (warn-only 71–78, BaseDirFor _ => 96–97), Add() pattern in Initialize() 116+ |
| Test bed buttons / defaults / load-fail HUD | ariki-game/src/Testing/ClothingTestBed.cs:449-516, :81-84, :88-89 + :279-281 |
| Spawn / engine / import / clean-import | ariki-game/tools/game.sh:633-749, :39-70, :832-841 (+ asset check :848), :478-482 |
| Agent API endpoints | game.sh:215-242 (/console via AgentServer) |
| Visual QA harness (judge missing) | ariki-game/tools/visual-qa.sh:1-77 — calls tools/vision-compare.py, which does not exist |
| Reimport gotcha (hand fix prescribed) | clothing/README.md:143-147 |
| Reference logic in older converters | ariki-game/tools/cc_clothing_to_quaternius.py (skeleton-frame alignment 66–73, KDTree copy 110–127, A-pose check 198–205), make_islander_outfits.py (region cuts 103–149) |
8. Build outcome (2026-07-31)
Built in one session by five parallel agents plus an integration pass. As-built
interfaces are documented in clothing/PIPELINE-CONTRACT.md (marked AS-BUILT); the
one-command usage and gate table are in clothing/README.md.
| Lane | Delivered |
|---|---|
| A | clothing/garment.py — the orchestrator: extends resolution, deep-merge, path absolutization, 13 stages, gate scheduling, register codegen, --selftest |
| B | gates/g2_census.py (island census sanity), gates/g8_catalog_lint.py (catalog↔asset lint, --all / --require-registered) |
| C | gates/g5_posed_sweep.py — posed penetration + gape sweep; G3 is its --rest mode |
| D | tools/tailor/draft_garment.py (MD script generator), tools/tailor/qc_placement.py, gates/g1_drape.py |
| E | ariki-game/tools/targeted_reimport.sh (G7), ariki-game/tools/clothing_motion_qa.sh (G6), ClothingTestBed.ReportLoad |
Acceptance test
configs/tests/piupiu_sb_test.json (scratch export target, set KapahakaSBTest) run
end to end, census..export, --no-gate-stop:
| Stage | Time | Gate |
|---|---|---|
| census | 8.0 s | G2 PASS — 1 island, 3620 v / 7104 tri, matched expect.islands |
| prepare | 4.7 s | — |
| fit | 3.3 s | G3 FAIL — 1 vert, 8.0 mm deep, at rest |
| reduce | 3.1 s | — |
| skin | 3.4 s | G5 FAIL — see below |
| export | 3.3 s | — |
G5 reproduced the shipped piupiu bug headlessly, in 14.9 s, from the real game
clips (Idle_Loop from UAL1, Walk_Fwd_Loop from UAL2; 65/65 pack bones matched
the RIG, the 16 skirt bones ride their parents):
rest 1 penetrating vert (max 8.0 mm)
Idle @0/12/25/38/50/62 229 217 216 231 216 216 (max 75.2 – 85.2 mm)
Walk @0/7/13/20/27/33 169 134 61 90 128 63 (max 70.0 – 87.9 mm)
That is the thigh punching through the skirt: ~1 vert visible in the pose the whole pipeline used to sign off on, and 216–231 verts up to 8.5 cm deep the moment the character stands still and breathes. 13 failing frames, with front + closeup QA renders for six of them. The gate is worth its cost.
The pari neckline gape did NOT reproduce in the Blender checkpoint — it is not present in the skinned mesh G5 measures. Its origin is therefore in-game (attach, material, or LOD), which makes it G6 territory, not G5. Worth stating plainly because the pre-build assumption was that both shipped defects were the same class of bug.
Real findings the gates produced
- G8 found an uncommitted
OutfitCatalogregistration hunk in the working tree — a garment registered locally and never pushed, which the lint surfaced immediately. On the test set it correctly reportsKapahakaSBTesthas noBaseDirForarm and so would silently resolve into the Fantasy pack folder. - G1 measured the shipped garments against their own written QC targets and found both off: pari's hem sits +3.9 cm high, and the piupiu's bands +8 cm / +14 cm. Those targets had lived as prose in the garment scripts' headers since they were written; nobody had ever checked them against a render.
/consoledid not surface load failures at all untilClothingTestBed.ReportLoadwas added —GD.Printdoes not feed the agent API's ring buffer. Every "the bed looks fine" sign-off before that fix was reading an empty channel.- G6's pixel diff has a measured floor: same outfit re-run moves ~0.002 of the frame, an entirely different outfit moves 0.014, and by frame 3 phase drift alone (0.0095) matches the signal. So G6 catches gross regressions (garment vanished, failed to load, swapped) and nothing subtler. G5 owns the subtle bug class — which is the right split, because G5 is headless, deterministic and 15 s.
Still deferred
Catalog-as-JSON (§2.4) — register still emits work/<name>/register.cs.txt for a human
to paste, by design. Nothing in the build depends on changing that.