# Pipeline contract — **AS-BUILT (2026-07-31)** This started as the build agreement between five parallel agents. It is now the as-built description of what actually exists and what the interfaces really are; where the original agreement and the code diverged, **the code won and this file was corrected**. Source design: `.agents/plans/clothing-pipeline-unification-2026-07-31.md`. Corrections against the original (2026-07-31 pre-build) text are flagged **[as-built]** so anyone holding the old version can see what moved. ## File ownership (parallel build — do not edit outside your lane) | Owner | Files | |---|---| | A (orchestrator) | `clothing/garment.py` | | B (lint gates) | `clothing/gates/g2_census.py`, `clothing/gates/g8_catalog_lint.py` | | C (pose gates) | `clothing/gates/g5_posed_sweep.py` (G3 = its `--rest` mode) | | D (MD template) | `tools/tailor/draft_garment.py`, `tools/tailor/qc_placement.py`, `clothing/gates/g1_drape.py` | | E (game side) | `ariki-game/tools/targeted_reimport.sh`, `ariki-game/tools/clothing_motion_qa.sh`, `ariki-game/src/Testing/ClothingTestBed.cs` (env override only) | **Nobody** touches: `clothing/garment_pipeline.py`, `configs/pari.json`, `configs/piupiu.json` (weight-fix agent owns those), `configs/piupiu_sb.json`, `.agents/wiki/**`. New test/demo configs go in `clothing/configs/tests/`. No commits, no pushes. ## Config resolution - `garment.py` resolves `extends` (single parent, path relative to `configs/`): deep-merge child over parent; **arrays and scalars replace whole**, objects merge per-key. - The resolved config is written to `work//resolved.json`. Every stage and gate is invoked with that file, never the raw config. `` comes from the child config. ### Path absolutization **[as-built]** Before `resolved.json` is written, `garment.py` rewrites these fields from relative to **absolute**, resolving against the `clothing/` directory (the same base `garment_pipeline.py` uses as `HERE`). Already-absolute values are untouched: ``` source · body · parts.*.texture · md.avatar_fbx · md.zfab · md.texture md.snapshot · md.presim_snapshot · md.export_dir · export.out_dir ``` This is not cosmetic. The three consumer families disagree about what a relative path is relative to: | Consumer | Base for a relative path | |---|---| | `garment_pipeline.py` | `HERE` = `clothing/` (`garment_pipeline.py:265`, `:1005`) | | `tools/tailor/draft_garment.py` | the **config file's own directory**, then repo root | | `clothing/gates/g1_drape.py` (`_resolve`) | the **config file's own directory**, then repo root, then cwd | `resolved.json` lives in `work//`, so `"texture": "../tools/tailor/textures/piupiu.png"` resolves correctly for the pipeline and to the non-existent `clothing/work/tools/...` for the other two. Absolutizing at the resolver makes all three agree. Asserted by `garment.py --selftest`. **Config authors keep writing relative paths** — they are relative to `clothing/`, as they always were. ## Stage list (orchestrator) ``` draft drape publish | census prepare fit reduce bake skin export | register import verify +---- MD bridge ----+ +-------- Blender headless -------------+ +---- ariki-game ----+ ``` - Blender stages shell to (unchanged CLI): `"$BLENDER" --background --python clothing/garment_pipeline.py -- --config work//resolved.json --stage ` where `BLENDER` defaults to `C:/Program Files/Blender Foundation/Blender 5.1/blender.exe` (env `BLENDER` or `--blender` overrides). - MD stages: `python tools/tailor/draft_garment.py --config work//resolved.json --emit work//md_script.py --stage ` then `python tools/md_bridge.py --file work//md_script.py --timeout `; orchestrator pings first (`md_bridge.py --ping`) and fails fast with the human instruction ("click Plugin > TinqsMDBridge in MD") when no session. **[as-built]** `draft_garment.py` does accept `--stage` (`draft|drape|publish|all`, default `all`) and additionally `--timeout-hint`, which prints the MD-bridge timeout this garment needs instead of emitting. - `register`: emit-not-edit — write `work//register.cs.txt` containing the exact `Add(...)` line(s) + `BaseDirFor` case if the set's folder is new. Data comes from `catalog` + `export` + `parts[].slot`. **[as-built]** the `BaseDirFor` res:// path is derived from where `export.out_dir` sits **inside the game repo**, not from its basename; an out_dir outside the repo emits a placeholder plus a loud note. - `import`: agent E's `targeted_reimport.sh` — see the CLI below. - `verify`: agent E's `clothing_motion_qa.sh` — see the CLI below. - `bake` is skipped unless the resolved config sets `"bake": true` (matches today's "optional, slow" reality). ### Agent E's real CLIs **[as-built]** The original contract sketched these as `bash