0467c53727
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0467c53727 |
ship-check for digalot.fyi/wardrobe: relative UI paths (subpath-proxy safe) + POST body cap
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9012e3e652 |
Export hub, unified wardrobe, warm thumbs, body-type heights
EXPORT HUB. An 8-agent pass read every consumer's ACTUAL loader code (not its docs) and adversarially re-verified each contract, because writing a wrong filename into a live game directory corrupts shipping assets silently. The result reframed the whole feature: of six targets, only TWO ingest by file drop. · 3GOD depot is wireable today, but NOT the way the plan assumed. Its auth trusts the raw socket peer against a tailnet allow-list, so through the digalot.fyi Cloudflare front the peer is CF and every write 403s. Measured live: /api/list reports authed=false via CF, authed=true direct on http://100.94.195.115:8788. (thriftgod's shipped prop_campaign.py --publish defaults to the CF URL and is broken for exactly this reason.) This is the high-value target — thriftgod AND procity both read their props from it. · The depot's clean_name DELETES illegal characters instead of substituting, so 'shop!-cat.glb' becomes 'shop-cat.glb' — an existing, different mesh — and is then served silently. Reproduced the rule exactly and added a pre-flight collision check that refuses unless overwrite is set. · The other four are NOT file drops and the hub says so instead of showing a green tick over a no-op: djsim peds and procity peds load from hardcoded name arrays, thriftgod's props come from literal HERO_FLOOR/HERO_COUNTER strings, and not-tonight has no garment/doll/character art path at all (find public -type d returns only public and public/props). Each returns 409 naming the exact file and line to edit. · thriftgod, the "most important 3D consumer", has NO paper-doll, atlas or reskin consumer — zero grep hits across web/ and tools/. Worth knowing before generating art for it. · nsfw-tagged assets are refused on every target, including the ready ones. UNIFIED WARDROBE. The 392 doll layers lived in a separate catalogue search couldn't reach, so "find the coogi jumper" only ever looked at the 9 3D garments. Both tiers now share one manifest and one grid: 401 items, tier filter, slot counts spanning both. Doll slots map to canonical ones (top->torso, bottom->legs, shoes->feet, hat->head) and are NOT marked as guesses, because the filename prefix is what djsim itself resolves art by. THUMBS. /api/thumbs/warm pre-renders everything, deliberately SERIAL — each 3D thumb launches its own Blender and a grid's worth at once buries the machine. 346 rendered, 0 failed. 2D layers are cropped to their alpha bbox first: a doll layer is a garment-sized patch on a 704x1408 canvas, so a plain downscale gives a mostly-empty tile. BODY-TYPE HEIGHTS. small 1.58 / medium 1.72 / large 1.88 / tall 1.95 / child 1.30 / obese 1.74 / stocky 1.68, stored per body and applied at export. This is the other half of unitfix only repairing impossible scales: the library keeps real proportions, the game gets a sane height. Verified in-browser, no JS errors: 401-item grid, cross-tier search, tier filter, export hub returning the registry line for each unready target, nsfw refused on a ready target. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
305fbf9447 |
Phase 5: the dressing room — thumbnails, a manifest, and the rig/LOD/dress bench on one page
The wardrobe had grown past the point where a list of filenames is usable: 20 bodies and a garments list you had to read rather than look at. Three things close that. THUMBNAILS. New `thumb` blender op — fixed front ortho camera, EEVEE, transparent film, bind pose (a thumb caught mid-clip is unreadable), framed on the real mesh with helper widgets excluded or a bone widget decides the crop. /thumb?p= renders lazily and caches on a hash of the source path. 2D layers skip Blender entirely and downscale through PIL — 12ms vs a process launch. First paint of a fresh grid is slow because every 3D card spawns a render; after that they're cached. Worth knowing before it looks broken. MANIFEST (library/index.json, written atomically so a half-write can't blank the wardrobe). Canonical slots and a layer int that drives BOTH the 2D doll stack and 3D ordering, so a garment sorts the same in either product. Slots are GUESSED from the filename as a convenience and the guess is marked as such — the UI shows `head?` with a "confirm it" note, and setting a slot explicitly clears the inferred flag. A guess presented as fact is how a library quietly fills with wrong data. Props (boombox, camera, thermos) correctly come back unset rather than being forced into a garment slot. THE BENCH, in the order you actually work: fix units -> rig (MIRPAMO) -> LOD -> dress. LOD is four preset buttons at the tiers the games budget for (hero 60k / npc 18k / crowd 6k / tiny 2k) instead of typing tri counts. /api/rig shells out to MIRPAMO, and the UI states its real limitation up front: 22 bones, no fingers — fine for locomotion, but a 65-bone donor garment will leave cuffs under-driven, which is exactly the mismatch that bit us in Phase 4. Grid, slot tabs with counts, search over name+tags, metadata editor, LOD presets and the bench buttons all verified in-browser with no JS errors. nsfw tagging from the previous commit carries through: tagged assets stay hidden behind the toggle and out of the grid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d6c0ba4c0e |
NSFW tagging: make "only dressed outfits ship" an enforced rule instead of a README line
The bodies list had grown to 20+ entries mixing nude anatomical bases, dressed NPCs and assembled outputs with no way to tell them apart — and the house rule "bases stay in the library, only dressed outfits ship into games" existed as exactly one unenforced line in README.md:45. Tagging without a gate would just be labelling; the gate is the point. Follows the convention already in use across the fleet rather than inventing a second one: a '-nsfw' suffix before the extension, as in ~/Documents/FBX (106 of 124 files tagged, with a matching `nsfw` column in _INVENTORY.csv). Filenames beat a sidecar database here because these assets get rsynced between six machines constantly — a tag in the name travels with the file, cannot drift out of sync, and is visible in Finder. · scan() reports `nsfw` per asset; the UI shows a badge, hides bases by default behind a "show bases" toggle, and says how many are hidden rather than silently shortening the list. · POST /api/tag renames an asset to add or remove the suffix. · Export into a live game directory now REFUSES anything nsfw-tagged (403). Verified both ways: a tagged stem is blocked, an untagged one still exports. · An assembled result INHERITS the body's tag. Clearing it automatically because "a garment was added" would be a silent wrong call — a hat does not clothe a nude base — and the failure mode is shipping a nude model into a game, so untagging stays deliberate. Verified in-browser: 1 base hidden with the count shown, badge renders, toggle reveals it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
037f6ef0af |
Fold NPCFACTORY in: vendor three.js, take the banks, and fix units without flattening body types
NPCFACTORY and wardrobegod were the same product — wardrobegod had already absorbed its reskin engine, and keeping two benches means two half-libraries and two export paths. Folded, keeping the wardrobegod name and codebase (370 lines there vs ~1,100 here). · Vendored three.js r175 from NPCFACTORY, replacing the unpkg CDN importmap. This was real drift, not tidiness: a CDN import breaks offline and can't be lifted into a game build. NPCFACTORY lacked OrbitControls (it used PointerLock), so that one addon was fetched at the matching revision. Added a guarded /vendor/ static route. Verified in-browser: zero CDN requests, 5 vendor files, model still loads. · Took the 17 rigged walk-animated NPCs and 6 parts. Bodies 4 -> 20. · New `unitfix` op. Deliberately NOT scale-to-height: a 0.06m human is a UNIT error, but normalising everything to 1.72m would erase the small/medium/large/obese range the library is meant to carry. So it only corrects heights outside 0.5-3.0m — physically impossible for a human — and leaves real proportions alone as data. Verified both ways: hum_character 0.0576m -> 1.72m, tradie 1.000m left untouched. Three bugs found while building it, two of them pre-existing: · `is_helper`/`real_meshes`/`bbox_of` factored out. Material-less bone widgets (a radius-1 42-vert Icosphere, so exactly 2.0 units tall) were being measured INSTEAD of the character — every body reported 2.000m. This poisoned the `scale` op too, which has been measuring widgets all along; NPCFACTORY's render_plates.py had independently worked around the same thing by framing on the dominant mesh. · `transform_apply` under temp_override(selected_editable_objects=...) SEGFAULTS Blender 5.1.2 on rigs with parented children. A segfault can't be caught, so it's avoided rather than handled: glTF encodes node scale natively, so setting the root transform is sufficient and every downstream measurement still reads correctly. Confirmed `scale` still round-trips (1.00m -> 1.72m, re-measured). · My own bulk edit replaced only ONE of the two crash-prone call sites and reported "replaced 1" — I didn't check for a second, which is why `scale` worked while `unitfix` kept crashing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5c80e65d58 |
Salvage the SD lane: patch MODELBEAST, add the SD backend, fix the assets filter at source
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> |
||
|
|
e3d1a0aa13 |
Act on the LoRA bench: fix a real farm bug, flip to the sharper backend, make gen reproducible
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> |
||
|
|
cfca6708ab |
Phase 2: reskin engine — dress the nude TRELLIS bodies without fitting anything
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>
|
||
|
|
294af7cf1d |
Phase 3: the 2D paper-doll tier — 392 garments you can actually change
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> |
||
|
|
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>
|
||
|
|
6f32c08aa9 |
CF Workers AI backend: flux direct (backnforth contract) + local border-flood keyer
/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> |
||
|
|
58354d9b66 |
WARDROBEGOD v1 — wardrobe generator bench (bodies, fits, garment gen pipeline)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |