harvest op: split a garment off an already-rigged clothed character by material. Blender's
mesh.separate(type='MATERIAL') carries the vertex groups onto the separated piece, so the
garment lands ALREADY skinned to the donor's skeleton — no fit, no weight transfer, and it
deforms correctly at the elbow because it was authored to. Unused groups are purged after.
Triaged all 43 clothed donors on ultra (headless Blender, run where the files are rather than
moving 3 GB). Measured what the LoRA bench could only guess at: only 11/43 (25%) are separable.
The rest bake skin and cloth onto one fused atlas and cannot be split at all. The best targets
are the Ready Player Me avatars (Wolf3D_Outfit_Top/Bottom/Footwear — a clean 3-way split) and
the Mixamo characters (Topmat/Bottommat/Shoesmat); most of the man_*/woman_* set is fused.
Triage also over-reported at first: the skin regex missed 'mouth', so mouth materials showed up
as garments. Widened.
Proving it end to end exposed three real bugs in `assemble`, none of which erred — they all
failed silently, which is why they had survived:
· Bone names are NOT uniform across the fleet, contrary to the "one skeleton" contract in the
README. character_kit and NPCFACTORY use 'mixamorig:Hips'; Ready Player Me donors use bare
'Hips'; Blender suffixes '.001' on any import collision. Vertex groups therefore pointed at
bones that did not exist and the garment sat frozen in bind pose while the body animated.
Now matched on the bare name, so a garment harvested off one rig drives another.
· Donors are not authored at a common scale. character_kit rigs are ~0.06 m tall, a Ready
Player Me donor ~1.5 m, so the harvested top arrived 27.41x oversized and swallowed the
scene. Rescaled by a shared hips->head bone span, the same trick character_kit's
assemble.fit_part uses.
· Material-less bone-widget meshes (a 42-vert 'Icosphere' in character_kit rigs) rode along and
wrecked the scene bbox, which drives both the HUD height and the viewer's auto-framing — one
stray widget framed the camera on the widget and rendered the character as a speck.
NPCFACTORY's render_plates.py had already worked around the same thing.
Verified: a garment harvested from a Ready Player Me donor, assembled onto character_kit's
hum_character, deforming correctly mid-stride through a Running clip. Honest caveat unchanged —
the garment inherits its donor's proportions, so it reads loose on a slimmer body and there is
no cloth sim to save it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The LoRA bench concluded the SD1.5 lane was invalid for garments. That was true of the
CHECKPOINT, not the lane: Hyper_Realism_1.2 was the only one the farm could reach and it is a
person-photoreal merge that framed every garment as worn-and-cropped no matter how hard you
negative-prompted. Given four non-person checkpoints, the lane works — and now beats flux.
MODELBEAST patches (John's explicit blessing; flux_local deliberately untouched because a
parallel session owned it and had already deployed LoRA + Z-Image support there):
· comfyui_sd now STACKS LoRAs. It hard-coded a single LoraLoader, so style+texture together
was impossible; `lora` now takes a list, each entry optionally `name=0.6`, chained.
· comfyui_sd gains ControlNet (loader + ApplyAdvanced + staged control image). The payoff for
a wardrobe is recolouring one garment into N colourways that share a byte-identical
silhouette, so a single cut-out alpha is reusable across every variant.
· Per-node preflight widened to LoRAs and ControlNets, and the arch-mismatch warning now fires
per LoRA. A wrong-base LoRA is a SILENT no-op, so everything fails loudly instead.
· Deployed to all three nodes that run comfyui_sd (m3ultra, ultra=m1, m4pro=m4), each backed up.
· GET /api/assets now honours ?parent_job=. It declared no such parameter, so FastAPI silently
dropped it and every caller got the whole table — 2,578 rows for a bogus id. Callers that
took rows[0] were right only by accident of newest-first ordering. Verified: bogus -> 0 rows,
real -> only its own. Restarted the queue in a zero-active-jobs window.
· Copied 4 non-person checkpoints ultra -> m3ultra over the fast LAN (12.6 GB).
wardrobegod:
· `sd` backend on /api/gen, defaulting to juggernautXL_ragnarok, with checkpoint/lora/negative
passthrough. Measured on one prompt+seed: a full isolated flat-lay where Hyper_Realism crops
a worn garment.
· flat3d template gains 'no hanger, no coat hanger' — a wire hanger appeared in most SD product
shots and keys into the cutout as if it were garment. doll2d already had the clause.
Verified end-to-end: 7/7 salvage cells ran (including a 2-LoRA stack, previously impossible),
and a Coogi knit generated through wardrobegod's own /api/gen came back isolated on white with
legible raised-knit texture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A 17-agent bench classified 103 models across the 125GB civit library and ran 12 live LoRA
tests. Headline: NO LoRA BEATS THE PLAIN BASELINE — all 12 came back unusable, so the
recommended recipe for both tiers contains zero LoRAs. The failure is at the checkpoint, not
the LoRAs: comfyui_sd is the only farm operator that loads a LoRA and it can reach exactly one
checkpoint, a person-photoreal SD1.5 merge that draws a woman for any clothing noun no matter
how hard you negative-prompt it. flux_local, the lane that actually works, has no lora param
at all. The two lanes are disjoint, so no LoRA here got a fair garment evaluation.
Real bug found and fixed: the farm IGNORES ?parent_job=. A bogus id still returns the entire
asset table (measured: 2,578 rows, 34 distinct parents), so mb_outputs()[0] was only ever
correct because the table happens to come back newest-first — two jobs in flight would
download each other's images, which is exactly what happened to two agents in the bench.
Now filtered client-side.
Measured changes:
· Default backend CF -> flux_local klein-4b. At matched prompt and seed, klein holds corduroy
wale across the garment where CF flux-1-schnell flattens it to smooth canvas. CF stays
selectable: ~2s and never queues behind the farm's serial GPU lane.
· Seeds are reproducible. /api/gen used time.time()%99991 so nothing could ever be reproduced,
and /api/doll/gen inherited the operator default seed=42 so a phrase produced a byte-identical
image forever and reroll did nothing. Both now hash the phrase + a variant counter; rerolling
is bumping variant.
· The painterly white doll2d template that actually shipped the 441 sprites wasn't in this repo
at all — only djsim's later green variant. Added, and made the default; green is now the
documented escape hatch for plaid and busy patterns, since the bench measured klein on pure
white keying 7/7 flood seeds on both a dark solid and a busy multicolour knit.
· Doll generation moved 1024 -> 704 to match the canvas, so we stop downscaling before the key
and softening the edges the keyer depends on.
· Templates hoisted to one registry; /api/gen returns the resolved prompt and seed and the UI
shows them under the preview.
· Local keyer warns loudly: flat3d product shots come back on studio grey with a drop shadow
(border 215-244, zero pixels >=250), which only partially seeds the >232 gate. RMBG is
semantic and unaffected.
Verified: seeds deterministic and variant-sensitive, templates resolve view/year, and a live
klein-4b generation produced a flat-lay corduroy jacket with legible wale and no person.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every anatomy body is an independent TRELLIS reconstruction: no shared topology, no vertex
correspondence, its own UV atlas. That kills garment portability — a fitted mesh can't move
between bodies. Reskin sidesteps the whole problem by repainting the atlas the body already
has, so it works on unstructured meshes and needs no rig.
Chain (/api/reskin), all local except one farm call:
render_plates.py -> bind-pose front/back ortho plates, EEVEE, 1024, transparent
mflux_image_edit -> "put <garment> from image 2 onto the person in image 1", per plate
align_plate.py -> re-fit the edited figure to the original bbox. The edit model redraws
at its own scale/offset, and the projection bake assumes the original
framing, so skipping this makes the body sample background.
bake_skin.py -> Cycles DIFFUSE projection onto the mesh's real UVs, front/back split
on normal.Y
Costume swapping in the browser is just a material.map change, so pre-baked outfits toggle
instantly with no re-bake; the bare map is retained so it's reversible.
Fixed a real defect in the inherited baker: planar front/back projection has no idea a head
is round, so side-facing skull polygons sampled whatever front-plate pixel sat at their x/z
and painted a ghost second face down the side of the head (clearly visible on phrtt2). Since
this tier repaints CLOTHES, the bake now keeps the body's own texture above a neck-height
cutoff (default 0.87 of body height, -1 to disable), sampling it through the mesh's real UVs.
Verified end-to-end on phrtt2 (1 mesh / 1 UVMap / 1 material / 4,364 tris — the reskin class):
farm try-on produced a convincing corduroy jacket holding the exact T-pose, and the bake put
it on the model. Known ceiling, unchanged and by design: the silhouette does not move, so this
sells tight and printed garments and reads wrong for a heavy coat.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The flat cut-outs were the cheapest content on the board and the only tier with a shipping
consumer, but wardrobegod couldn't show one: scan() filtered PNGs out of garments/ (fixed in
Phase 0) and there was no compositor, no slots, no preview. Now there is a working dress-up.
· library/doll/ seeded with 90sDJsim's 441 shipped sprites (165 top, 110 bottom, 60 shoes,
57 hat, base body) — an instant starter wardrobe rather than a cold start.
· tools/doll.py wraps djsim's compositor. It deliberately does NOT import doll_composite:
that module pulls numpy at import time for a keyer and an HSV recolour we never use (the
farm's RMBG-2.0 does our keying), and numpy isn't installable here under PEP 668. Instead
the measured anchors are parsed out of its source with ast, so there is still exactly one
source of truth and they cannot drift; place() is a pure-PIL port.
· slugify() reproduces djsim's item_art() _art_slug character for character, verified against
it. That equality IS the drop-in contract — a near-miss silently falls back to generic slot
art instead of erroring, so it's the kind of bug you'd ship without noticing.
· /api/doll/gen runs the proven pipeline: flux_local on SOLID GREEN -> farm RMBG-2.0 ->
staged to 704x1408. Green is not a preference: grey and checkerboard backgrounds eat
garments in the key, and flux can't spell, so no text ever goes in the prompt.
· /api/doll/compose composes server-side with PIL, so the browser preview and the exported
PNG are the same bytes rather than two renderers that drift.
· /api/doll/export writes into a LIVE game directory, so it defaults to a dry run and refuses
any stem without _i_ — overwriting a generic slot fallback would restyle every unarted item
in a shipping game.
· UI: a doll 2D tab with per-slot pickers, randomise, strip, and export.
Verified: composed a dressed doll from real sprites (correct base->bottom->shoes->top->hat
order), catalogue reports 392 layers, export dry-run and the generic-art guard both behave.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
/api/gen prefers Cloudflare flux-1-schnell (free ~10k neurons/day) over the
farm; /api/rmbg falls back to a Pillow border-flood white-keyer when no
MB_TOKEN (flood from edges through near-white only — traps-ledger rule, white
ON the garment survives). Creds in gitignored .env, lifted from ultra.
Verified end-to-end: brown-corduroy-jacket flux'd, keyed, in the wardrobe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>