wardrobegod/STATE_AND_PLAN.md
type-two b1381d22d2 Phase 0+1: unblock the bench, then make it dress and move
Phase 0 — the fixes that were blocking every tier:
· /api/blender had no allowed() guard, unlike every other path-taking endpoint. Under the
  documented WG_HOST=0.0.0.0 that was an unauthenticated arbitrary-file -> Blender import +
  library-write for any tailnet peer. Now gated on allowed() + isfile.
· scan() listed garments as models only, but /api/hangable writes .png there — so every
  cut-out the generator produced was invisible in its own UI and the whole 2D paper-doll
  route dead-ended. garments now lists models AND images.
· mb_download() wrote json.dumps(data) whenever the farm response wasn't bytes, and mb_req
  parses any JSON-200 into a dict — so a farm error body got written *into* the target
  .png/.glb and the job was still marked done. Now raises instead.
· MB_TOKEN is read off disk from backnforth/.env, so the farm tiers (RMBG-2.0, image-edit
  try-on, trellis) stop silently dying when a launch forgets to export it.
· glb_of() keyed its cache on the basename alone, so two sources that slug the same served
  each other's geometry; the key now carries a hash of the full path.
· JOBS grew forever and glb_of minted an orphan job on every cache miss inside GET /glb.
  run_blender() now tolerates jid=None and finished jobs get reaped.

Phase 1 — a dressed, animated character on screen:
· clip transport: the mixer used to blind-play animations[0] with no way to reach the rest.
  Clip dropdown + play/pause + scrub + speed, with the scrub head following playback.
· rigid attach stopped being a lie. It cloned into three.js and POSTed nothing ("preview
  only"), so every hat placement died on reload. Attachments now carry their bone/offset/
  tint and persist via /api/outfit -> library/outfits/<name>.json, reloadable from a chip.
· colourway swatches retint the last attached item (materials are cloned per instance so a
  tint doesn't bleed), giving free variants with no re-generation.
· flat cut-out garments get a 2D preview pane — the paper-doll tier had no way to be seen.

Verified end-to-end in the browser: tradie_at_wanwalk (22-bone mixamorig) walks wearing a
red-tinted felt hat on mixamorigHead, saved, page reloaded, restored from disk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:43:02 +10:00

378 lines
26 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.

# WARDROBEGOD — state of play + how to improve it heaps
*Recon 2026-07-24 across JING5 / ultra / m3ultra. Every fleet claim below was spot-verified live (see §B).*
## TL;DR
The **bench works, the wardrobe is empty, and one bug is dangerous.** The three-column three.js
dress-up shell runs, the local Blender pipeline (convert / scale / decimate-keep-weights / **fit** /
assemble) works, and the Cloudflare flux generator works — but `library/` holds **2 bodies + 1 garment
PNG (which is invisible to its own UI)**, `out/` and `jobs/` are empty, the trellis tier is dead (no
`MB_TOKEN`), and `/api/blender` is an **unauthenticated arbitrary-file → Blender** primitive.
Meanwhile the fleet already contains *everything wardrobegod is trying to become*, several times over:
- **NPCFACTORY** (ultra) — a further-along dress-up bench: **17 finished rigged, walk-animated NPC GLBs**
+ a **completed reskin engine** (paint clothes onto a body's own UV atlas).
- **90sDJsim** (ultra) — a shipped $0 2D wardrobe: **441 authored transparent-PNG garment sprites** + a
self-healing doll compositor.
- **MODELBEAST** (m3ultra :8777, **UP, 26 live operators**) — free local **virtual try-on**
(`mflux_image_edit` = "Edit Image (local, Qwen)": *"put the shirt from image 2 on the person in image 1"*),
flux, RMBG-2.0, TRELLIS.2, Hunyuan3D, SF3D, layered-RGBA cutouts, MoMask text→motion.
- **character_kit_modular** (ultra) — a socket-swap costume engine on rigged mixamorig bases.
- **MIRPAMO** (everywhere) — a local, offline Mixamo (auto-rig → 22-bone mixamorig + retarget).
- **~2,394 + 2,548 + ~163** animation clips already on disk.
**The decision:** stop treating wardrobegod as a Blender-op bench and make it the one thing the fleet
*lacks* — the **canonical garment/outfit LIBRARY + a dressing-room authoring UI + a multi-consumer
EXPORT HUB**. The dressing *engines* exist five times over; the connective tissue exists zero times.
Graft the fastest proven engines onto that spine (reskin, try-on, the 2D doll, donor-harvest).
---
## A. Where it's at — honest state of play
### Works today
- **`server.py`** (stdlib ThreadingHTTPServer :8150): `/api/lib`, `/glb?p=` (Blender convert-cache),
`/file?p=`, `/api/upload`, `/api/blender` (op dispatch), `/api/gen` (CF flux), `/api/rmbg`,
`/api/to3d`, `/api/hangable`. No build step.
- **`blender_ops.py`** (166 lines): `convert / scale / decimate-keep-weights / fit / assemble`. The
**fit** op (data_transfer `VGROUP_WEIGHTS POLYINTERP_NEAREST` + armature parent,
`blender_ops.py:128-132`) is the proven wardrobe mechanic — wrap it, don't rewrite it.
- **`web/index.html`** (316 lines): OrbitControls + GLTFLoader stage, 3 tabs, body/garment lists,
mixer that auto-plays `animations[0]`.
- **CF flux** `/api/gen``@cf/black-forest-labs/flux-1-schnell` works with only the CF creds in `.env`.
- Blender 5.1.2 + PIL present on JING5 — the whole local pipeline runs with **no farm dependency**.
### Content truth (the real problem)
```
library/bodies/ phrtt2.glb (1.67M), droidte2-18k.glb (7.97M)
library/gen/ brown-corduroy-jacket.png, -cut.png
library/garments/ brown-corduroy-jacket-cut.png ← INVISIBLE to the UI (bug #2)
out/ jobs/ EMPTY
.env CLOUDFLARE_* only — NO MB_TOKEN
```
**Zero garments have ever been fitted to any body.**
### The bodies are not the consistent A-pose set the brief assumed
The 6 `~/Documents/anatomy` FBX (verified by headless render): **2 A-pose** (`dudescholng`,
`2_views_3d`), **2 T-pose** (`phrtt2`, `droidte2`), 2 arms-down/contrapposto. **5 of 6 are unrigged**;
only `droidte2` has a skeleton (41-bone reduced mixamo) **and it's already clothed** (shorts+boots),
482k-tri raw. Each is an independent TRELLIS reconstruction — **no shared topology, own UV atlas**.
→ **One fitted garment can't be reused across bodies; each needs its own per-body fit, and only
shrinkwrap/proximity works (no vertex correspondence).**
### The real bugs (file:line)
1. **SECURITY — `/api/blender` is unguarded.** `allowed()` guards `/file /glb /api/rmbg /api/to3d
/api/hangable` but **not** `/api/blender``fx()` passes `a['path']/a['body']/a['garment']/
a['garments']` straight into `glb_of()`→`run_blender()` (`server.py:238-265`). Under the documented
`WG_HOST=0.0.0.0` (`server.py:7`) that's an **unauthenticated arbitrary-file → Blender import +
library-write** for any tailnet peer. `allowed()` itself is sound (`server.py:58-61`) — it's just
not called here. **Fix before any 0.0.0.0 exposure.**
2. **`scan()` hides PNG garments.** `server.py:67` uses `MODEL_EXT` for `garments`, but `/api/hangable`
writes a **.png** there (`server.py:374`). Every hanging-texture / doll cutout is silently filtered
out — the whole 2D route dead-ends in an unshowable directory. **One-line fix, prerequisite for 2D.**
3. **`glb_of` cache-key collision.** `server.py:96` keys on `slug(basename(path))` only, single-source
mtime check (`:97`). Same-basename files in different dirs collide → serve wrong geometry.
4. **Silent corruption in `mb_download`.** `server.py:156-157` writes `json.dumps(data)` when the farm
response isn't bytes; a JSON error body gets written **into** the target `.png/.glb` and the job is
marked **done**. Affects `/api/gen /api/rmbg /api/to3d`.
5. **`JOBS` leaks unbounded** (`server.py:50`), and `glb_of` spawns a `convert` job on every cache miss
even inside synchronous GET `/glb` (`server.py:98`), orphaning records.
6. **Local rmbg keyer is crude** — iterative border flood-fill through near-white (`>232`), binary
alpha, `server.py:329-344`. On a non-white bg it never seeds → **silent no-op opaque copy**.
7. **Rigid-attach is ephemeral**`web/index.html:215-225` clones into three.js and **POSTs nothing**;
the toast admits *"preview only."* Attach transforms live only in browser memory.
8. **`fit`**: picks body as `max(meshes, key=len(vertex_groups))` → multi-part body donates weights from
one mesh only (`blender_ops.py:115-117`); no `matrix_parent_inverse` (`:131`).
9. **`assemble` trusts an unenforced bone-name contract** (`blender_ops.py:151-161`).
10. No auth / CORS / rate-limit / delete / thumbnails / search; gen filenames overwrite silently.
> **Not a bug (verified, don't touch):** the suspected `decimate` `modifier_move_up` infinite-loop /
> 30-min hang **does not reproduce** on Blender 5.1.2 — the operator returns `{'FINISHED'}`, loop exits
> in one iteration. Code smell only.
### Convention drift
- three.js from **unpkg CDN** (`index.html:133-136`) — house style is **vendored** r175 (breaks offline
/ un-shippable into a game build).
- README frames the generator as MODELBEAST-only; commit `6f32c08` quietly made **CF the primary** flux
backend — a cloud path outside the LOCAL-FIRST doctrine.
- "Only dressed outfits ship into games" is **one unenforced README line** (`README.md:45`) — no code gate.
---
## B. What the fleet already gives us (verified live 2026-07-24)
### MODELBEAST — `http://100.89.131.57:8777` (m3ultra), **UP, 26 live operators** ✓
Bearer `MB_TOKEN` (on disk at `~/Documents/backnforth/.env`, len 44 — never paste). Contract:
`POST /api/assets` (multipart) → `POST /api/jobs {operator, asset_ids|asset_id, params}` → poll
`GET /api/jobs/{id}` → outputs = assets with `parent_job==id``GET /api/assets/{id}/file`.
`main.py:290-293` already accepts an **`asset_ids` array** → multi-image try-on is submittable today.
Wardrobe-critical live operators (display name → slug):
| Display name | Slug | Use |
|---|---|---|
| **Edit Image (local, Qwen)** | `mflux_image_edit` | **virtual try-on** — 1-3 imgs, *"put the shirt from image 2 on the person in image 1"*. The key unused capability. |
| Qwen-Image-Layered (local) | `qwen_layered_local` | image → N editable RGBA layers = the "paper cut-out layers" rep, generated |
| FLUX (local, MLX) | `flux_local` | flux2-klein-4b, ~40s, ungated |
| Remove Background (local) | `bg_remove_local` | RMBG-2.0 matting (better than the local keyer) |
| SD1.5 + anatomy LoRAs / SD+LoRA (ComfyUI) | `sd_local` | OpenPose T-pose ControlNet + IP-Adapter identity lock |
| TRELLIS.2 (SOTA) / TRELLIS.2 MLX | `trellis*` | image → GLB (~2-5 min) — **single-image only** |
| Hunyuan3D 2.1 (local MLX) | `hunyuan3d_mlx` | image → GLB, no HF login |
| **SF3D (fast draft)** | `sf3d` | image → textured GLB, seconds — good for quick garment blockouts |
| **Motion (MoMask)** | text → BVH motion (+mp4) — text-to-animation | |
| Upscale (SeedVR2), Brush 3DGS Train | — | polish / gaussian-splat |
Copyable stdlib CLI: `~/Documents/MODELBEAST/mb``./mb run flux_local -p prompt=... --wait --download out/`.
Launch pattern: `export MB_TOKEN=$(grep '^MB_TOKEN=' ~/Documents/backnforth/.env | cut -d= -f2); python3 server.py`.
### NPCFACTORY (ultra `~/Documents/NPCFACTORY`, 410M) — the further-along sibling ✓17 banks confirmed
- Same stdlib + **vendored** three.js house style; already has orbit stage + clip dropdown +
speed/height-normalise, click-a-part bone-attach dress-up, recipe JSON, in-UI Blender bank-builder.
- **17 finished rigged, walk-animated NPC GLBs** in `library/banks/` (tradie/cop/goth/nanna/postie/
busdriver/bizdad… 7-25MB each) — instant dressed+animated characters to load.
- **The reskin trio** (E6 COMPLETE, all 3 confirmed on disk): `tools/render_plates.py` (bind-pose
front/back EEVEE ortho plates, 1024, transparent), `tools/align_plate.py`, `tools/bake_skin.py`
(Cycles DIFFUSE projection bake onto the rig's own UVs, front/back via `Normal.Y<0`). `archetype_batch.py`
= batch factory template.
- **Constraint:** reskin only works on **single-mesh / single-UVMap / single-material** bodies —
**John's nude TRELLIS-2 figures are exactly this class.** (`droidte2` is disqualified: multi-part, clothed.)
### 90sDJsim (ultra `~/Documents/90sDJsim`) — the 2D wardrobe, already shipped ✓441 sprites confirmed
- **`web/world/media/doll/` = 441 transparent-PNG garment layers**, `<slot>_i_<slug>.png`. Runtime
compositor `doll.js` (704×1408, ORDER base→bottom→shoes→top→hat, bag last). `doll_layout.json` nudges.
- **Self-healing loader** `server.py:167-178`: `_art_slug = re.sub(r'[^a-z0-9]+','_',name.lower()).strip('_')`.
**Drop a PNG + restart = that garment wears its own art, zero code change.**
- **$0 generator:** `tools/{gen_wardrobe,doll_composite,doll_stage,build_wardrobe_manifest}.py` — prompt
`flux_local` on **solid green**`bg_remove_local` → stage onto 704×1408 via measured `SLOT_DEF`
anchors. Prompt template + 321 authored items + 432 reference JPGs exist.
- **Caveat:** the full doll pipeline is **ONLY on ultra**; the JING5 checkout is stripped → export-to-djsim
must rsync to ultra.
### character_kit + character_kit_modular (ultra canonical `~/Documents/character_kit/`)
- Rigged mixamorig bases (65-bone) + already-rigged wearable props (`rigged/{w_beanie,w_felthat}.glb`,
`props/modular/{beanie,buckethat,felthat,boombox,camera}.glb`).
- `scripts/merge_anims.py` — base rig + N anim-only FBX → **one multi-clip GLB** (NLA tracks).
- `modular/scripts/`: `cut_parts.py` (cut a mixamorig char into swappable socket parts), `assemble.py`
(`attach_part`, `fit_part` cross-rig auto-scale, `attach_attachment` bbox-fit+bone-parent),
`texture_variants.py` (numpy hue-mask recolour). `parts_library/library.json` = the garment-registry
schema to copy.
### Animation banks
- **ultra `~/Documents/MOTIONLIB/green/mixamo_full` = 2,394 anim-only mixamorig FBX** (1.3G, shippable
w/ attribution) + `cmu_bvh` 2,548 BVH + 100style/accad/kaykit/quaternius.
- **~163 local mixamorig clips** already on JING5+ultra — enough for a showcase set now, no downloads.
### Garment donors + bodies
- **ultra `~/Documents/3D=models/characters/people-normal/` = ~40 clothed, mixamorig-rigged FBX**
(`man_suit_01`, `man_dj_streetwear_01`, `woman_raver_01`, `man_worker_hivis_01`…). **Exclude
`actorcore-unreal/` (RED, native-only).**
- Clean multi-LOD nude base: **m3ultra `~/Documents/trellis2-bench/anatomy_{2000,8000,30000,50000,800k}.glb`**.
- ultra `3d-from-m5/AUDIT.md`: of 4278 files, **43 are rigged + game-budget (≤10k tris)**.
- **fluxgod** (JING5) — `mb.py/a1111.py/openrouter.py` multi-lane client with restart-safe queue +
autocrop. Copy wholesale rather than re-implement CF-only.
### Two honest fleet limits (do NOT build against these)
- **TRELLIS.2 is single-image** — do NOT build a "4-view sheet → TRELLIS" bridge; multi-view is only for
reskin plates / QC.
- **No live vision model** (m4pro Ollama = text `qwen2.5:7b` + `nomic-embed` only) → **auto-tagging
garments is NOT an existing capability** (needs a `qwen2.5-vl` pull or paid OpenRouter). MODELBEAST has
a local text LLM (Qwen3-30B) but not a captioner.
- **No garment/clothing LoRA exists** — styled-garment fidelity is prompt-only until `brush_train` makes one.
---
## C. The plan — phased, each ending in a demo
**Guiding principle:** the data model (§D) is cheap stdlib JSON — it goes in Phase 1 and every phase
populates it. Fast/proven engines first (reskin, try-on, 2D doll), quality-core cloth mid (donor
harvest), UI polish last.
### Phase 0 — Unblock (½ day)
1. **Bug #2** (`server.py:67`): add `.png` to `garments` scan exts → cutouts/doll layers become visible.
2. **Bug #1** (`server.py:238-265`): call `allowed()` on the blender-op paths before any 0.0.0.0 exposure.
3. **Export `MB_TOKEN`** at launch (unlocks try-on, RMBG-2.0, trellis) — LaunchAgent or launcher script.
4. **Bug #4** (`server.py:156-157`): check content-type/magic bytes before writing a farm response; mark
the job `error` on a JSON body.
### Phase 1 — "It dresses and it moves" (1 day) → dressed, animated character on screen
- **Track 1 — the stage moves:** add a **clip dropdown + play/pause/scrub** (the mixer exists at
`index.html:191-194`; there's no selector). Build clips via `character_kit/scripts/merge_anims.py`
(base rig + ~10 local clips → one multi-clip GLB, NLA tracks) → rsync to JING5.
- **Track 2 — it dresses:** load a rigged mixamorig base; rsync one NPCFACTORY bank NPC (already
dressed+animated) as the guaranteed floor. Attach a rigid hat (`character_kit/rigged/w_felthat.glb`)
via the existing bone-attach, but **make it persist** (POST an `outfit.attach` record, not just an
in-browser clone). Add runtime `material.color` colourway tinting (free).
- **Demo:** a rigged character **walks on screen wearing an attached hat**, clip dropdown switches
animations, a colour swatch retints it.
- **Stretch (starts the real nude-figure track):** kick off the first reskin bake on `phrtt2` (T-pose,
single-mesh/UV — ideal reskin class): `mirpamo rig phrtt2.glb``render_plates` → repaint → `bake_skin`.
### Phase 2 — Reskin engine: paint clothes onto nude bodies (2-3 days)
Copy the NPCFACTORY trio (137 lines). New `POST /api/reskin`, mirroring `run_blender`+`job_thread`:
```
1. blender -b -P render_plates.py -- body_rigged.glb plates/ → front.png, back.png
2. MB job mflux_image_edit asset_ids=[front.png, garment_cut.png]
"put the garment from image 2 onto the person in image 1, keep exact pose+silhouette, plain bg"
3. blender -b -P align_plate.py -- edited_front.png front.png aligned_front.png
4. blender -b -P bake_skin.py -- body_rigged.glb aligned_front.png aligned_back.png costume.png
5. three.js: material.map = TextureLoader().load(costume.png); needsUpdate=true
```
A **costume dropdown** swaps `material.map` at runtime — instant, free, reversible toggling between
pre-baked outfits.
- **Demo:** pick a nude TRELLIS body → pick/generate a garment → walking clothed character; flip a
dropdown to change costumes instantly.
- **Ceiling (say it out loud):** the nude *silhouette never changes* — sells tight/printed garments &
uniforms & mid-distance figures, but a jacket reads as a jacket-print bodysuit. Hero silhouette
garments need Phase 4.
### Phase 3 — The 2D paper-doll product + first export contract (2-3 days)
Adopt `90sDJsim/tools/{gen_wardrobe,doll_composite,doll_stage,build_wardrobe_manifest}.py` wholesale.
Emit `media/doll/<slot>_i_<slug>.png` honoring `_art_slug` exactly. Wire `qwen_layered_local` /
`bg_remove_local` for matting-grade cutouts (upgrading the crude local keyer). Import the **441 sprites**
as a starter pack. `POST /api/export {target:"djsim", outfit}` writing over rsync to ultra.
- **Demo:** generate a garment → it appears as a doll layer → **drops into 90sDJsim with zero consumer
code change** (self-healing `DOLL_ART`). Highest-ROI, lowest-risk value on the board.
### Phase 4 — Quality-core: real 3D cloth via donor harvest + hide-masks (8-9 days)
New `blender_ops.py` op `harvest`: import a clothed mixamorig donor → `bpy.ops.mesh.separate(type=
'MATERIAL')` (**Blender copies all vertex groups onto the separated garment automatically — the whole
trick**) → purge empty groups → `attach_part``<donor>__<material>-harvest.glb`. `/api/wear` runs
`assemble.py fit_part(anchor=mixamorig:Hips)`. License-gate: exclude `actorcore-unreal/`.
Batch the ~40 donors → **~80-150 pre-weighted staple garments** at $0.
- **Graft from proxy-standard:** author `hide_torso/hide_upperarm/hide_thigh/hide_shin/hide_foot/
hide_scalp` vertex groups once, apply an inverted `MASK` modifier per garment's `hides` list —
**replacing the 3mm DISPLACE "puff" hack** (`blender_ops.py:122-124`) — so nude skin stops poking through.
- **Demo:** a real jacket (collar/cuff volume) on a mixamorig body **bending correctly through a walk
clip**, no skin poke-through. The only route that looks like a person in actual clothes.
- **Ceiling:** garments captive to donor proportions (clip/gap on dissimilar bodies), wardrobe
donor-bounded, 65-bone donor vs 22-bone MIRPAMO leaves cuff verts under-driven.
### Phase 5 — Dressing Room UI + full export hub (1 week)
Rebuild `web/index.html` as a real dressing room: **vendor three.js r175** (drop the CDN), thumbnail
garment grid with slot tabs + tag search + license filter, live click-to-swap composing by *reference*
(never re-bake per click), a 2D-doll preview toggle (reuse `doll.js` verbatim), save/load/randomize
outfits, and an **export dropdown** (djsim | thriftgod | procity | not-tonight | glb). Add a `render_thumb`
Blender op (EEVEE 256×256 A-pose). Gate every export on `license != "red"` — the only actual enforcement
of "only dressed/green ships."
---
## D. The data model (actual JSON — stdlib files, no DB)
`library/index.json` written on every mutation, read once by the UI. Replaces `scan()` (which returns
only `{name,path,kb,home}` and drops PNGs).
**Slots & layers**
```
slot ∈ head | torso | legs | feet | hand_l | hand_r | accessory | bag
layer (draw/stack int; drives 3D normal-push AND 2D doll ORDER):
skin=0 underwear=10 legs=20 feet=25 torso=30 outer=40 head=50 accessory=60 bag=70
```
**Garment record** (`library/garments/<slug>.json`)
```json
{
"slug": "coogi-multi-knit", "title": "Coogi multicolour knit",
"slot": "torso", "layer": 30, "skeleton": "mixamorig",
"hides": ["hide_torso","hide_upperarm"],
"tags": ["knit","90s","multicolour","oversized"], "brand": "coogi-parody",
"license": "green",
"colorway": { "base": "#8a2b6d", "tintable": true },
"reps": {
"flat_png": "garments/coogi-multi-knit.png",
"doll_layer": "garments/coogi-multi-knit.doll.png",
"sprite": "garments/coogi-multi-knit.sprite.png",
"glb": "garments/coogi-multi-knit-harvest.glb",
"skin_tex": "garments/coogi-multi-knit.reskin.png",
"thumb": "garments/coogi-multi-knit.thumb.png"
},
"source": { "prompt": "...", "backend": "flux_local", "seed": 42 },
"provenance": { "created": "2026-07-24T...", "donor": null }
}
```
**Outfit record** (`library/outfits/<name>.json`)
```json
{
"name": "raver-fit-01", "body": "bodies/phrtt2_rigged.glb",
"slots": { "torso":"coogi-multi-knit","legs":"baggy-jnco","feet":"doc-8eye","head":"bucket-hat" },
"layerOrder":[ "legs","feet","torso","head","bag" ],
"anim": "wan_walk",
"attach": { "bucket-hat": { "bone":"mixamorig:Head","scale":1.0,"y":0.0,"z":0.0 } }
}
```
The `attach` block **bakes the currently-ephemeral rigid-attach state** (`index.html:215-225`) so
hats/props survive reload + export.
**Export contracts** (verified against each consumer's real loader)
| Target | Contract | Slot map |
|---|---|---|
| **90sDJsim / thriftgod doll** | write `reps.doll_layer``90sDJsim/web/world/media/doll/<slot>_i_<slug>.png`; `<slug>` = `re.sub(r'[^a-z0-9]+','_',name.lower()).strip('_')`. Self-healing `DOLL_ART` = drop file + restart. Rsync to ultra. | torso→top, legs→bottom, feet→shoes, head→hat, bag→bag |
| **thriftgod / procity racks** | emit `reps.flat_png` as `fittings.js garment({image})` plane texture / PROCITY `StockSlot{kind:'garment'}` | slot → rack `kind:'garment'` |
| **not-tonight** | emit `reps.sprite` (32×48, quantized to the 14-entry `outfits.ts PALETTE`) | head→hair/accessory, torso→top, outer→outer, legs→legs, feet→shoes |
| **3D GLB (hero/games)** | `assemble(body,{torso:garment})` or `fit``out/<outfit>.glb` → 3GOD depot | native mixamorig |
Schema is a **superset of** `character_kit_modular/parts_library/library.json` → parts flow between
benches (anti-silo, not a new competing format).
---
## E. The 2D paper-doll track (first-class, not a fallback)
John explicitly has "2D paper cut-out" garments, they're the **cheapest content**, and `GODVERSE_MAP.md`
already recommends *"clothes cutouts on a hanger card OVER 3D garment mesh."* Mostly **adoption**, not building:
- Generator exists & is $0: `90sDJsim/tools/gen_wardrobe.py` — prompt → `flux_local` on **solid green**
`bg_remove_local``doll_stage.py` onto 704×1408 via measured `SLOT_DEF` anchors.
- Battle-tested prompt (`build_wardrobe_manifest.py`): *"paper doll clothing item for a dress-up game,
`<desc>`, 1990 australia, `<view>`, floating centered on a plain pure white background, painterly
stylized 3d game illustration, full item visible, no body, no person, no mannequin, no hanger, no text,
no logos."* Lift verbatim, incl. per-slot VIEW phrasing.
- Content on hand: **441 sprites + 321 items + 432 reference JPGs.**
- Upgrade: `qwen_layered_local` → RGBA layers = the literal "layered paper cut-out" rep, cleaner than the keyer.
- **Prerequisite: fix bug #2** or none of this is ever visible.
- **Honest scope:** the djsim doll dresses **one fixed 704×1408 front-view silhouette** — it **cannot**
dress arbitrary 3D bodies. The dressing room is two products sharing one library: a 2D doll authoring
view (djsim/not-tonight) and a 3D stage (reskin/harvest/socket). Toggle honestly; never pretend a flat
sprite is dressing the 3D body.
---
## F. Open questions (only John can answer)
1. **wardrobegod vs NPCFACTORY** — keep wardrobegod as the JING5 library/authoring/export hub and *borrow*
NPCFACTORY's stage+banks (plan assumes this), or physically **merge** the two?
2. **Canonical nude base + consent** — rig/reskin target: mint from `trellis2-bench/anatomy_8000.glb`, use
`phrtt2` (ideal reskin class), or the 43 rigged game-budget set? Hard `license!="red"` export gate?
3. **`MB_TOKEN` persistence** — OK to add a LaunchAgent / boot-time read from `backnforth/.env`?
4. **Where the canonical library physically lives** — JING5 (author), ultra (doll pipeline runs), or 3GOD depot?
5. **First export target** — 90sDJsim (biggest existing 2D consumer, zero-code drop-in) or 3D-in-thriftgod?
6. **Hero garments** — accept painted-on reskin for crowd/mid, donor-harvest for staples; invest in
socket-torso swap for true hero couture (bigger build, not in this plan)?
7. **Garment LoRA** — curate a set + `brush_train` for on-brand 90s Aussie op-shop garments, or prompt-only for now?
---
## G. Risks & what will look bad (no sugar-coating)
- **Reskin is a painted-on illusion** — zero added geometry; unchanged nude silhouette + a jacket *print*
is a dead giveaway at close range; can't layer, can't drape; re-bake to change (runtime swap only
toggles *already-baked* costumes). Single-mesh/UV bodies only.
- **Virtual try-on is a 2D still** — `mflux_image_edit` gives a believable clothed image in ~2 min (great
for browsing) but doesn't animate and drifts identity/pose. Preview surface, not hero render.
- **Donor cloth is proportion-captive** — weights transfer perfectly, but a suit molded to `man_suit_01`
floats/clips on a slim TRELLIS figure (no cloth sim). Wardrobe is donor-bounded. 65-bone vs 22-bone
leaves cuffs under-driven. Unknown fraction of donors are single-atlas (skin+cloth fused) → won't
separate cleanly — real number unknown until triage runs.
- **3D fit on unstructured topology is per-body & clips** — no shared topology → fitted garment doesn't
transfer across bodies; expect clipping/sleeve mismatch on non-A-pose bodies; MIRPAMO can candy-wrap
joints on non-manifold soup.
- **The 2D doll can't dress the real 3D bodies** — one fixed front-view silhouette only.
- **Generation fidelity is prompt-only** (no garment LoRA) — exotic garments hit-or-miss.
- **Export writes into live game dirs** — needs dry-run + `license!="red"` gate + write only
`<slot>_i_<slug>.png` (never overwrite generic fallbacks).
- **Overlap with NPCFACTORY is real** — build this as the library/export layer NPCFACTORY lacks, not a
third isolated dress-up stage.
- **Auto-tagging is not available today** (no live vision model) — treat as manual or paid-OpenRouter.
- **Farm GPU = 1 job/node** — batch gen queues; push flux/RMBG to the 2nd MODELBEAST node on ultra.
**One-liner for John:** by end of week you get a real "change clothes + walk" button (reskin + rigid
attach + clip playback) and a best-in-class 2D wardrobe that drops straight into 90sDJsim; good-looking
real-cloth 3D comes in Phase 4 from harvesting donors you already own; and the thing you'll use every day
— the dressing-room library + export hub — is the spine that turns every engine into value for five
existing consumers.