wardrobegod/tools/build_wardrobe_manifest.py
type-two 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>
2026-07-24 16:07:13 +10:00

114 lines
10 KiB
Python

#!/usr/bin/env python3
"""Emit tools/art_wardrobe90.tsv — per-item doll art for the 16 base CLOTHES + all 1990
fashion-range items. Stems are <slot>_i_<slug> so doll_composite.install picks the slot
and the server derives the same key (see item_art_slug in server.py — SAME regex).
Descriptions carry each brand's signature look; 'no text, no logos' stays in the
template (flux garbles text; the silhouette + colours carry the brand)."""
import json, os, re
ROOT = os.path.join(os.path.dirname(__file__), '..')
slug = lambda s: re.sub(r'[^a-z0-9]+', '_', s.lower()).strip('_')
VIEW = {
'hat': 'front view',
'top': 'front view laid flat with the sleeves down',
'bottom': 'front view laid perfectly flat on the ground',
'shoes': 'the pair side by side, front view',
}
TAIL = ('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 coat hanger, no text, no logos')
def prompt(desc, slot):
return (f'paper doll clothing item for a dress-up game, {desc}, 1990 australia, '
f'{VIEW[slot]}, {TAIL}')
# ---- base CLOTHES (parody brands) — name -> (slot, description) ----
BASE = {
'Kangaruu Bucket Hat': ('hat', 'a worn khaki cotton bucket hat with a small embroidered kangaroo patch shape'),
'Snapback (Faded)': ('hat', 'a faded sun-bleached black snapback baseball cap, slightly bent flat brim'),
'Feltways Fedora': ('hat', 'a sharp charcoal grey felt fedora with a black band'),
'Terry Towelling Hat': ('hat', 'a classic white terry towelling cricket bucket hat with a soft floppy brim'),
'Mombo Loud Shirt': ('top', 'a loud short-sleeve surf art shirt covered in wild colourful cartoon dog and sun graphics, chaotic bold print'),
'Stoosh Hoodie': ('top', 'a heavyweight faded charcoal pullover street hoodie with a big front pocket'),
'Silk Camp Collar': ('top', 'an elegant slate blue silk camp collar short-sleeve shirt with a subtle sheen'),
'Band Tee (Bootleg)': ('top', 'a boxy black bootleg band t-shirt with a cracked faded skull and lightning print'),
'Phat Cargo Pants': ('bottom', 'a pair of extremely baggy sand-coloured cargo pants with huge side pockets'),
'Pressed Slacks': ('bottom', 'a pair of sharply pressed charcoal dress slacks with a crisp centre crease'),
'Boardies': ('bottom', 'a pair of bright teal and coral surf board shorts with a wave panel print'),
'Raver JNCOs': ('bottom', 'a pair of enormous ultra-wide-leg denim rave jeans, huge flared openings'),
'Adidos Shelltops': ('shoes', 'a pair of white leather shell-toe sneakers with three dark stripes on the sides'),
'Doc Martys 8-Eye': ('shoes', 'a pair of cherry red leather 8-eyelet lace-up boots with yellow stitching around the sole'),
'Double-Pluggers': ('shoes', 'a pair of cheap blue and white rubber thong sandals, flat and simple'),
'Hi-Top Pumps': ('shoes', 'a pair of chunky white and orange hi-top basketball sneakers with a small pump button on the tongue'),
}
# ---- 1990 fashion ranges — item name -> description (slot/vibe come from culture.json) ----
RANGE = {
'Bootleg Acid House Smiley Tee': 'a cheap boxy white t-shirt with a big yellow acid house smiley face print, slightly crooked bootleg print',
'Bootleg Hip-Hop Crew Tee': 'a cheap boxy black t-shirt with a faded colourful hip-hop crew graffiti graphic, bootleg quality print',
'Womens Neon Crop Tee': 'a womens bright neon pink cropped t-shirt, boxy 1990 rave style',
'adidas Track Jacket': 'a black zip-up track jacket with three crisp white stripes down each sleeve and a small trefoil-shaped chest emblem',
'adidas Track Pants': 'a pair of black track pants with three white stripes down each leg, elastic ankle cuffs',
'Womens adidas Track Skirt': 'a womens black sporty mini skirt with three white stripes down each side',
'Coogi Sweater': 'a wild multicoloured heavily textured 3d knitted sweater, swirling raised knit patterns in purple teal gold and red',
'Womens Coogi Knit Dress': 'a womens fitted knee-length knitted dress in wild swirling multicoloured 3d textured knit, purple teal and gold',
'Coogi Knit Beanie': 'a thick knitted beanie in swirling multicoloured textured knit, purple teal gold and red',
'Rip Curl Boardshort': 'a pair of surf board shorts with bold colour-blocked panels in navy aqua and white with a wave curve motif',
'Rip Curl Bucket Hat': 'a navy and aqua surf brand bucket hat with a small wave curve embroidery shape',
'Womens Rip Curl Surf Tank': 'a womens aqua and white surf tank top with a sun-faded wave print',
'Kmart Plain White Tee': 'a plain cheap white cotton crew-neck t-shirt, slightly thin fabric',
'Kmart Cargo Shorts': 'a pair of plain beige budget cargo shorts with simple side pockets',
'Womens Kmart Bike Shorts': 'a pair of womens plain black lycra bike shorts',
'Stussy Heavyweight Tee': 'a heavyweight boxy white streetwear t-shirt with a black hand-drawn scrawled signature-style graphic',
'Stussy Heavy Brushed Cargo': 'a pair of heavy olive brushed-cotton streetwear cargo pants, wide straight legs',
'Stussy Pro Block Cap': 'a black streetwear baseball cap with a white hand-drawn scrawl embroidery shape on the front',
'Versace Medusa Silk Shirt': 'a luxurious black silk shirt covered in an ornate gold baroque scroll print with a medusa head medallion motif',
'Versace Medusa Jeans': 'a pair of black designer jeans with ornate gold baroque print down the legs and gold hardware',
'Womens Versace Stiletto Heels': 'a pair of womens gold and black designer stiletto heels with ornate gold details',
'Billabong Wave Logo Tee': 'a faded blue surf t-shirt with a big white curling wave graphic across the chest',
'Billabong Walkshort': 'a pair of casual grey-blue cotton surf walkshorts, knee length',
'Womens Billabong One Piece Swimsuit': 'a womens bright colour-blocked one piece swimsuit in teal pink and yellow panels',
'Dunlop Volley Sneakers': 'a pair of classic plain white canvas tennis sneakers with a thin green and gold trim stripe and gum herringbone sole',
'Dunlop Volley High Tops': 'a pair of white canvas high-top tennis sneakers with green and gold trim and gum sole',
'Dunlop Sports Socks': 'a pair of white sports crew socks with two green stripes at the top, standing upright side by side',
'Nike Air Max 90 Sneakers': 'a pair of chunky white grey and infrared running sneakers with a visible air cushion window in the heel and a curved side swoosh shape',
'Nike Air Track Jacket': 'a colour-blocked track jacket in white grey and infrared red with a curved swoosh-shaped chest emblem',
'Womens Nike Air Max 90': 'a pair of womens white grey and pink running sneakers with a visible air cushion window in the heel and curved side swoosh shape',
'Polo Bear Sweater': 'a navy knitted sweater with a large knitted-in teddy bear wearing a suit on the chest',
'Polo Chino Pants': 'a pair of neatly pressed khaki chino pants with a small embroidered polo player shape at the pocket',
'Polo Cable Knit Hat': 'a cream cable-knit winter beanie with a folded brim',
'Mambo Loud Shirt': 'a loud open-collar surf art shirt covered in chaotic colourful cartoon artwork of a farting dog and suns and waves, wild bold print',
'Mambo Graphic Tee': 'a white t-shirt with a big colourful chaotic cartoon surf-art graphic of a barking dog',
'Womens Mambo Art Dress': 'a womens short summer dress covered in loud colourful cartoon surf artwork print',
'Army Disposal Camo Pants': 'a pair of authentic military surplus camouflage pants in australian bush camo, hard-wearing',
'Army Disposal Camo Jacket': 'a military surplus camouflage field jacket in australian bush camo with chest pockets',
'Army Disposal Boonie Hat': 'a military surplus olive green boonie hat with a wide soft brim and chin cord',
'Fila Settanta Track Jacket': 'a retro tennis track jacket in white with navy and red colour-block bands across the chest and a small boxy F-shaped emblem',
'Fila Settanta Track Pants': 'a pair of white retro tennis track pants with navy and red piping down the legs',
'Fila Logo Cap': 'a white sports baseball cap with navy and red colour blocking and a small boxy emblem shape',
'Moschino Belt Buckle Dress': 'a womens fitted black designer mini dress with a print of gold belts and buckles wrapping around it',
'Moschino Logo Blazer': 'a sharp black designer blazer with oversized gold buttons and gold chain trim details',
'Womens Moschino Gold Jeans': 'a pair of womens black designer jeans with bold gold lettering-style shapes printed down one leg and gold hardware',
'Quiksilver Boardshort': 'a pair of surf board shorts in a bold red white and black geometric mountain-and-wave panel print',
'Quiksilver Rash Guard': 'a fitted long-sleeve surf rash guard in black with a red and white curved mountain-and-wave chest panel',
'Womens Quiksilver Surf Shorts': 'a pair of womens short surf shorts in a bright mountain-and-wave geometric print',
}
rows, missing = [], []
for name, (slot, desc) in BASE.items():
rows.append((f'{slot}_i_{slug(name)}', prompt(desc, slot)))
culture = json.load(open(os.path.join(ROOT, 'data', 'culture.json')))
for f in culture['1990']['fashion']:
for it in f['items']:
d = RANGE.get(it['name'])
if not d: missing.append(it['name']); continue
rows.append((f"{it['slot']}_i_{slug(it['name'])}", prompt(d, it['slot'])))
out = os.path.join(ROOT, 'tools', 'art_wardrobe90.tsv')
with open(out, 'w') as fh:
fh.write(f'# {len(rows)} wardrobe items — 704x704 doll garment layers. base CLOTHES + 1990 ranges.\n')
for stem, p in rows: fh.write(f'{stem}\t{p}\n')
print(f'wrote {out}: {len(rows)} prompts', f'MISSING DESCRIPTIONS: {missing}' if missing else '(all covered)')