441 lines
30 KiB
Markdown
441 lines
30 KiB
Markdown
# 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).*
|
||
|
||
---
|
||
## BUILD STATUS — 2026-07-24
|
||
|
||
| phase | state | commit | demo |
|
||
|---|---|---|---|
|
||
| **0 — unblock** | ✅ done | `b1381d2` | security hole closed, PNG garments visible, `farm3D ✓` |
|
||
| **1 — dress + move** | ✅ done | `b1381d2` | tradie walks in a red-tinted felt hat, restored from disk after reload |
|
||
| **2 — reskin** | ✅ done | `cfca670` | phrtt2 wearing a baked corduroy jacket; head-ghosting bug found + fixed |
|
||
| **3 — 2D paper doll** | ✅ done | `294af7c` | 392 layers, per-slot swap, randomise, export dry-run to djsim |
|
||
| **4 — donor harvest** | ✅ done | `8119e2d` | harvested garment on a *different* body, bending through a Running clip |
|
||
| **5 — dressing room** | ✅ done | `305fbf9` | thumbnail grid, manifest, rig→LOD→dress bench |
|
||
| **SD salvage + MODELBEAST** | ✅ done | `5c80e65` | juggernautXL beats flux for garments; LoRA stacking + ControlNet + `parent_job` fix |
|
||
| **NPCFACTORY folded in** | ✅ done | `037f6ef` | vendored three.js (no CDN), 17 NPCs, `unitfix` |
|
||
| **nsfw tagging + gate** | ✅ done | `d6c0ba4` | "only dressed ships" is now enforced, not a README line |
|
||
| **export hub + unified wardrobe** | ✅ done | `9012e3e` | 401-item grid, 3GOD publish, honest 409s |
|
||
|
||
### Export reality (verified against each consumer's real loader, 2026-07-24)
|
||
| target | file drop works? | what's actually needed |
|
||
|---|---|---|
|
||
| **djsim doll** | ✅ yes | self-healing loader — drop + restart |
|
||
| **3GOD depot** | ✅ yes | `POST http://100.94.195.115:8788/api/upload` **direct over tailnet** — the `digalot.fyi` front 403s because auth trusts the socket peer |
|
||
| djsim ped | ❌ no | `rigs.js PED_NAMES` (:20-27) hardcoded array |
|
||
| procity ped | ❌ no | ped name array; props via `pipeline/publish.py` on **m3ultra** (only box with `_normalized/`) |
|
||
| thriftgod prop | ❌ no | literal `HERO_FLOOR`/`HERO_COUNTER` strings (index.html:1322-1325) |
|
||
| not-tonight | ❌ no | **no garment/doll/character art path exists at all** |
|
||
|
||
**thriftgod has no paper-doll, atlas or reskin consumer** — zero grep hits across `web/` and `tools/`.
|
||
Don't generate art for it expecting somewhere to put it.
|
||
|
||
**What John can do right now:** `python3 server.py` → open `:8150`.
|
||
· *wardrobe tab* — load a rigged NPC, attach + tint props, save/reload the dress-up, play/scrub clips,
|
||
RESKIN a nude body with any garment and toggle costumes instantly.
|
||
· *doll 2D tab* — dress a paper doll from 392 real garments, randomise, export to 90sDJsim.
|
||
|
||
**Corrections to the recon below, found while building:**
|
||
- The live farm exposes **26** operators, not 36 (the 36 was the code registry; not all are hot).
|
||
`/api/operators` returns **display names**, so `mflux_image_edit` appears as *"Edit Image (local, Qwen)"*.
|
||
- Two live operators the plan under-sold: **SF3D** (fast image→GLB draft) and **MoMask** (text→motion BVH).
|
||
- `numpy` is **not** installable on JING5 (PEP 668) — the doll tier parses djsim's anchors out of source
|
||
with `ast` instead of importing its numpy-dependent compositor.
|
||
- Reskin's inherited baker ghosted a second face onto the side of the head; fixed with a neck-height cutoff.
|
||
- `riggermore` / `rigroom` do not exist under those names anywhere on the fleet — the rigging bench is **MIRPAMO**.
|
||
---
|
||
|
||
## 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.
|
||
|
||
---
|
||
## 3GOD → absorbed (2026-07-24)
|
||
|
||
**Retired:** the name, the repo (`ssh://git@100.71.119.27:222/monster/3GOD.git`), and the viewer
|
||
UI — wardrobegod's stage/grid/publish covers all of it.
|
||
|
||
**Kept and absorbed into `depot/`:** the asset CDN. It holds **773 live assets** and two shipping
|
||
games fetch from it *at runtime* (thriftgod `index.html:1176`, procity `loaders.js:7`), so the
|
||
service and its `/3god` URL stay exactly as they are. The name now survives only as a URL path.
|
||
|
||
Checked before discarding the UI: **0 of 773 assets had tags or notes**, so nothing needed
|
||
migrating. Tagging lives in `library/index.json`.
|
||
|
||
Two things `depot/README.md` records because they're expensive to rediscover:
|
||
1. Publish **direct over the tailnet** (`100.94.195.115:8788`) — the Cloudflare front 403s because
|
||
auth trusts the raw socket peer. thriftgod's own `prop_campaign.py --publish` is broken for this.
|
||
2. `clean_name` **deletes** illegal characters, so `shop!-cat.glb` silently resolves to an
|
||
existing *different* mesh. `god3_name()` reproduces the rule and refuses to overwrite.
|