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>