The rope is where the player spends most of the night and it was still coloured primitives behind a single street plate. - kebabShop: the moral centre of the game (design §5) was three rects and the word KEBABS. The warm spill on the footpath stays procedural — it is the only warm light in the scene and it is what says the shop is open. - front:<venueId>: one facade per rung of the ladder, so a promotion is visible from the footpath. ROOM also loses the procedural NOT TONIGHT sign, because "no sign. no socials." is its actual entry in data/venues.ts. - Footpath: a street lamp, a bin, a waiting cab, and brass stanchions on the carpet. The cab is alpha'd back and depth-1 so it never looks clickable next to the kebab shop, which is. - poster4..poster9, so four venues stop sharing one wall of three. Mangled lettering is correct on posters and always has been. And a composition bug I put in this same wave: the per-venue street plates were first prompted as "frontage", so The Royal's backdrop was a whole pub with its own central door and the venue facade then sat on top of it — two pub fronts, two doors. Voltage's original plate has always been a WALL, which is what a backdrop has to be. Re-prompted all three as blank walls and regenerated. No test would have caught that; I found it by looking at the screen. 127 sprites now, none missing. Gate: lint clean, build clean, 821 tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
355 lines
17 KiB
Python
355 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""assets.py — the not-tonight generation manifest (docs/VENUES.md §6).
|
|
|
|
One entry per sprite the three new venues need. `run_batch.py` walks this list;
|
|
nothing here executes anything itself, so it stays readable and diffable.
|
|
|
|
Two routes, per the A/B recorded in docs/SPACES.md:
|
|
|
|
flat — flux draws the finished sprite. For things with no volume: signage,
|
|
screens, boards, wall art, backdrops. Flux picking its own camera does
|
|
not matter when the object IS a flat plane.
|
|
mesh — flux draws a clean PRODUCT SHOT, TRELLIS.2 reconstructs it, Blender
|
|
renders it from the ONE fixed ortho camera every prop shares. For
|
|
anything with volume. The product shot is deliberately NOT a top-down:
|
|
TRELLIS reconstructs best from a clear view of the object, and the
|
|
game's camera angle is applied at render time by `rake`.
|
|
|
|
`rake` follows the house rule the hard way round (LANEHANDOVER, FABLE-SOLO-25):
|
|
**60° for low and wide, ~76° for tall.** Lowering the rake for a tall object to
|
|
"see more face" is wrong — a tall object is already all face, and at 55° it
|
|
renders as a featureless slab.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import NamedTuple
|
|
|
|
# Shared prompt tails. The mesh tail is tuned for TRELLIS: one object, no
|
|
# cropping, plain ground, even light, nothing for the reconstructor to mistake
|
|
# for geometry.
|
|
MESH_TAIL = (
|
|
"single object centred, full object visible, product photograph, "
|
|
"plain mid-grey seamless background, soft even studio lighting, sharp focus, "
|
|
"no people, no text, no lettering, no watermark"
|
|
)
|
|
FLAT_TAIL = (
|
|
"front-on orthographic view, flat to camera, no perspective, "
|
|
"dark background, grimy nightclub, video game texture art, "
|
|
# flux likes to sign its work in the bottom corner. Invisible on a 16px
|
|
# prop, very visible on a 640px street plate.
|
|
"no watermark, no logo, no signature"
|
|
)
|
|
|
|
|
|
class Asset(NamedTuple):
|
|
kind: str
|
|
route: str # 'flat' | 'mesh'
|
|
size: tuple[int, int] # ship size in px, must match PROP_SIZES/TARGETS
|
|
prompt: str
|
|
seed: int
|
|
rake: int = 60 # mesh route only
|
|
gen: tuple[int, int] = (1024, 1024) # generation resolution
|
|
quantise: bool = True
|
|
# True for PLATES (backdrops, skylines, a roller door filling its wall):
|
|
# they are meant to be opaque, and the importer's near-black-to-alpha pass
|
|
# would eat exactly the parts that make a night scene look like night.
|
|
# False for OBJECTS, which want cutting out of their background.
|
|
keep_bg: bool = False
|
|
|
|
|
|
def M(kind: str, size: tuple[int, int], seed: int, rake: int, subject: str) -> Asset:
|
|
return Asset(kind, "mesh", size, f"{subject}, {MESH_TAIL}", seed, rake=rake)
|
|
|
|
|
|
def F(kind: str, size: tuple[int, int], seed: int, subject: str,
|
|
gen: tuple[int, int] = (1024, 1024), quantise: bool = True,
|
|
keep_bg: bool = False) -> Asset:
|
|
return Asset(kind, "flat", size, f"{subject}, {FLAT_TAIL}", seed,
|
|
gen=gen, quantise=quantise, keep_bg=keep_bg)
|
|
|
|
|
|
# ---- The Royal — suburban pub -------------------------------------------------
|
|
ROYAL = [
|
|
M("pokie", (16, 32), 4400, 76,
|
|
"a single poker machine gaming cabinet, glowing screen, red and gold trim, worn"),
|
|
M("bistroTable", (16, 16), 4401, 60,
|
|
"a small square pub bistro table with two dining chairs, laminate top"),
|
|
M("picnicTable", (32, 16), 4402, 60,
|
|
"a weathered wooden picnic table with attached bench seats, beer garden furniture"),
|
|
M("trough", (48, 16), 4403, 70,
|
|
"a long stainless steel trough urinal, wall mounted, public bar toilet fixture"),
|
|
M("keg", (16, 16), 4404, 76,
|
|
"a stack of two stainless steel beer kegs, brewery barrels"),
|
|
M("jukebox", (16, 32), 4405, 76,
|
|
"a classic jukebox with illuminated arched top, chrome and coloured lights"),
|
|
M("raffleDrum", (16, 16), 4406, 60,
|
|
"a small wire mesh raffle barrel on a stand with a crank handle"),
|
|
M("bainMarie", (48, 16), 4407, 60,
|
|
"a stainless steel bain-marie hot food counter with sneeze guard, pub bistro"),
|
|
M("beerUmbrella", (32, 32), 4408, 80,
|
|
"a large outdoor patio umbrella open, canvas canopy seen from high above"),
|
|
M("ashtray", (16, 16), 4409, 60,
|
|
"a battered metal outdoor ashtray bin on a short pole"),
|
|
F("tabBoard", (48, 16), 4410,
|
|
"a bank of three wall mounted betting screens showing horse racing odds, glowing blue"),
|
|
F("dartboard", (16, 16), 4411,
|
|
"a dartboard on a scuffed wooden backboard with darts in it"),
|
|
F("tvSport", (32, 16), 4412,
|
|
"a wall mounted flat screen television showing a night rugby match, glowing"),
|
|
]
|
|
|
|
# ---- Elevate — rooftop --------------------------------------------------------
|
|
ELEVATE = [
|
|
M("cocktailBar", (96, 16), 4420, 60,
|
|
"a short modern cocktail bar counter with backlit bottle shelving, marble top"),
|
|
M("banquette", (32, 32), 4421, 60,
|
|
"a curved upholstered velvet banquette booth seat, luxury lounge furniture"),
|
|
M("ropePost", (16, 16), 4422, 76,
|
|
"a single polished brass rope stanchion post with red velvet rope"),
|
|
M("planter", (32, 16), 4423, 60,
|
|
"a long rectangular concrete planter box full of green foliage and grasses"),
|
|
M("balustrade", (64, 16), 4424, 76,
|
|
"a section of frameless glass balustrade railing with a steel handrail"),
|
|
M("djPlinth", (48, 32), 4425, 60,
|
|
"a raised white platform plinth stage with a DJ controller on top"),
|
|
M("marbleSink", (48, 16), 4426, 70,
|
|
"a luxury marble bathroom vanity with two vessel basins and brass taps"),
|
|
M("ashUrn", (16, 16), 4427, 76,
|
|
"a tall stainless steel standing ashtray urn, hotel lobby style"),
|
|
M("champBucket", (16, 16), 4428, 60,
|
|
"a silver champagne bucket on a stand full of ice and a bottle"),
|
|
M("loungeChair", (16, 16), 4429, 60,
|
|
"a low modern outdoor rattan lounge chair with a cushion"),
|
|
F("liftDoor", (32, 32), 4430,
|
|
"a pair of closed brushed steel lift elevator doors with a call panel"),
|
|
F("skylineCard", (128, 32), 4431,
|
|
"a distant night city skyline of lit office towers, warm gold windows, "
|
|
"seen across a dark horizon", gen=(2048, 512), quantise=False, keep_bg=True),
|
|
]
|
|
|
|
# ---- ROOM — concrete warehouse -----------------------------------------------
|
|
ROOM = [
|
|
M("stackF1", (32, 48), 4440, 76,
|
|
"a huge professional nightclub speaker stack tower, three stacked black "
|
|
"bass bins and horns, sound system rig"),
|
|
M("subBass", (32, 16), 4441, 60,
|
|
"a large black subwoofer bass bin cabinet lying wide, professional audio"),
|
|
M("plywoodBar", (128, 16), 4442, 60,
|
|
"a rough improvised bar counter made of plywood sheets on scaffolding poles"),
|
|
M("pallet", (32, 16), 4443, 60,
|
|
"a stack of two wooden shipping pallets"),
|
|
M("wheelieBin", (16, 16), 4444, 76,
|
|
"a large plastic wheelie rubbish bin with the lid closed, industrial"),
|
|
M("oldCouch", (48, 16), 4445, 60,
|
|
"a sagging ruined old fabric three seater couch, stained and torn"),
|
|
M("pillar", (16, 16), 4446, 76,
|
|
"a square raw concrete structural column pillar, industrial warehouse"),
|
|
M("strobe", (16, 16), 4447, 76,
|
|
"a black professional strobe light fixture on a short stand"),
|
|
M("smokeMachine", (16, 16), 4448, 60,
|
|
"a black fog smoke machine unit with a nozzle, stage equipment"),
|
|
M("cableSnake", (32, 16), 4449, 60,
|
|
"a coiled bundle of thick black stage power cables taped to the floor"),
|
|
M("brokenBasin", (32, 16), 4450, 70,
|
|
"a cracked dirty ceramic wall mounted bathroom basin with exposed pipes"),
|
|
M("canStack", (16, 16), 4451, 60,
|
|
"a stack of plain unbranded aluminium drink cans in a cardboard tray"),
|
|
M("cdj", (32, 32), 4452, 60,
|
|
"a professional CDJ media player deck, black with a large jog wheel"),
|
|
F("rollerDoor", (64, 32), 4453,
|
|
"a large corrugated steel roller shutter door, half open, loading dock",
|
|
keep_bg=True),
|
|
F("ruleBoard", (32, 32), 4454,
|
|
"a scuffed black letter board sign with white push-in plastic letters, "
|
|
"cryptic house rules, chipped frame"),
|
|
]
|
|
|
|
# ---- Material plates (docs/VENUES.md §1: `FloorLayout.materials`) -------------
|
|
# Stamped one-per-tile and TINTED to the venue palette, so these want to be
|
|
# grain and pattern, not colour — the hue is applied at bake time. 16x16 is the
|
|
# game's tile, and a plate that reads as "carpet" at 16px is coloured noise with
|
|
# structure, which is also what pub carpet looks like from head height.
|
|
TEXTURE_TAIL = (
|
|
"seamless repeating texture, flat lay, straight overhead, evenly lit, "
|
|
"no objects, no text, no watermark, tileable"
|
|
)
|
|
|
|
|
|
def T(kind: str, seed: int, subject: str) -> Asset:
|
|
return Asset(f"tex:{kind}", "flat", (16, 16), f"{subject}, {TEXTURE_TAIL}",
|
|
seed, gen=(1024, 1024), quantise=False, keep_bg=True)
|
|
|
|
|
|
TEXTURES = [
|
|
T("carpet", 4490, "gaudy patterned red and navy pub carpet, worn, swirling 1990s design"),
|
|
T("concrete", 4491, "raw grey polished concrete warehouse floor, cracks and stains"),
|
|
T("deck", 4492, "pale grey composite timber rooftop decking boards"),
|
|
T("parquet", 4493, "scuffed wooden parquet dance floor, herringbone blocks"),
|
|
T("looTile", 4494, "small square white ceramic bathroom floor tiles with grimy grout"),
|
|
T("paving", 4495, "grey concrete paver slabs, wet, outdoor courtyard"),
|
|
T("plaster", 4496, "flat painted plaster wall, scuffed and nicotine stained"),
|
|
T("brick", 4497, "dark painted brick wall, mortar lines"),
|
|
T("timber", 4498, "varnished dark timber bar counter top, grain visible"),
|
|
T("steel", 4499, "brushed stainless steel panel, faint scratches"),
|
|
]
|
|
|
|
# ---- Wall surfaces (flat by nature — flux draws these directly) ---------------
|
|
WALLS = [
|
|
F("mirror", (32, 16), 4480,
|
|
"a large wall mirror in a plain frame reflecting dim room lights"),
|
|
F("graffiti", (32, 16), 4481,
|
|
"a concrete toilet wall completely covered in layered marker tags and scrawl"),
|
|
]
|
|
|
|
# ---- Staff figures (set dressing props, never patrons) ------------------------
|
|
STAFF = [
|
|
M("staffBartender", (16, 32), 4460, 76,
|
|
"a single bartender standing, black shirt, apron, arms at sides, full body"),
|
|
M("staffDj", (16, 32), 4461, 76,
|
|
"a single DJ standing wearing headphones around the neck, full body"),
|
|
M("staffGlassie", (16, 32), 4462, 76,
|
|
"a single hospitality worker standing holding a stack of empty pint glasses, full body"),
|
|
M("staffSecurity", (16, 32), 4463, 76,
|
|
"a single security guard standing, black bomber jacket, arms folded, full body"),
|
|
]
|
|
|
|
# ---- Door-scene street plates, one per venue ---------------------------------
|
|
STREETS = [
|
|
F("street:theRoyal", (640, 176), 4470,
|
|
"a wide blank painted brick pub wall at night, green tiled dado, torn posters, "
|
|
"a downpipe, wet footpath, warm amber spill, NO doors, NO entrance, no windows",
|
|
gen=(2048, 576), quantise=False, keep_bg=True),
|
|
F("street:elevate", (640, 176), 4471,
|
|
"a wide polished marble and dark glass building wall at night, planters along the base, "
|
|
"cool white downlight, NO doors, NO entrance",
|
|
gen=(2048, 576), quantise=False, keep_bg=True),
|
|
F("street:room", (640, 176), 4472,
|
|
"a wide blank unmarked concrete warehouse wall at night, no signage, heavy graffiti, "
|
|
"sodium orange light, puddles, rain, NO doors, NO entrance",
|
|
gen=(2048, 576), quantise=False, keep_bg=True),
|
|
]
|
|
|
|
# ---- Wave 2: filling the rooms out ------------------------------------------
|
|
# Reviewers flagged two genuinely empty spaces (The Royal's central carpet, ROOM's
|
|
# east hall) and the venues share almost no small dressing — the fire kit, signage
|
|
# and bins every real room is full of and no game room ever has.
|
|
FILLER = [
|
|
# The Royal's middle
|
|
M("pieWarmer", (32, 16), 4500, 60,
|
|
"a glass fronted heated pie warmer cabinet on a pub counter"),
|
|
M("binRound", (16, 16), 4501, 76,
|
|
"a round stainless steel swing top rubbish bin"),
|
|
M("sandwichBoard", (16, 16), 4502, 60,
|
|
"a wooden A-frame chalkboard sandwich board sign standing open"),
|
|
F("noticeBoard", (32, 16), 4503,
|
|
"a cork pub notice board covered in pinned paper flyers and raffle tickets"),
|
|
F("ceilingFan", (32, 32), 4504,
|
|
"a dusty four blade ceiling fan seen from directly below, dark"),
|
|
# ROOM's east hall and general industrial
|
|
M("flightCase", (32, 16), 4505, 60,
|
|
"a black road flight case with metal corners and latches"),
|
|
M("barrier", (32, 16), 4506, 76,
|
|
"a galvanised steel crowd control barrier fence section"),
|
|
M("distroBox", (16, 16), 4507, 76,
|
|
"a grey industrial electrical distribution box with thick cables"),
|
|
F("fireHose", (16, 16), 4508,
|
|
"a red fire hose reel mounted on a wall"),
|
|
# Elevate
|
|
M("firepit", (32, 16), 4509, 60,
|
|
"a modern rectangular gas fire pit table with glass surround, lit"),
|
|
M("topiary", (16, 16), 4510, 76,
|
|
"a neatly clipped ball topiary shrub in a tall planter"),
|
|
# Every venue: the kit a real room has and a game room never does
|
|
M("fireExt", (16, 16), 4511, 76,
|
|
"a red fire extinguisher on a wall bracket"),
|
|
F("exitSign", (16, 16), 4512,
|
|
"a glowing green illuminated fire exit sign, running man pictogram"),
|
|
F("cctv", (16, 16), 4513,
|
|
"a small white dome CCTV security camera on a ceiling mount"),
|
|
# More staff figures (set dressing, never patrons)
|
|
M("staffCleaner", (16, 32), 4514, 76,
|
|
"a single cleaner standing holding a mop, hi vis vest, full body"),
|
|
M("staffChef", (16, 32), 4515, 76,
|
|
"a single kitchen cook standing in whites and apron, arms at sides, full body"),
|
|
]
|
|
|
|
# ---- Wave 3: the door scene -------------------------------------------------
|
|
# The rope is where the player spends most of the night and it was still 28
|
|
# coloured rectangles behind the one street plate. All FRONT-ON and all flat:
|
|
# this is a tableau, not a top-down room, so flux picking its own camera —
|
|
# the thing that forces furniture onto the mesh route — does not apply.
|
|
DOOR_TAIL = (
|
|
"front-on flat elevation, night, wet, video game background art, "
|
|
"no people, no watermark, no logo"
|
|
)
|
|
|
|
|
|
def D(kind: str, size: tuple[int, int], seed: int, subject: str,
|
|
gen: tuple[int, int] = (1024, 1024)) -> Asset:
|
|
return Asset(kind, "flat", size, f"{subject}, {DOOR_TAIL}", seed,
|
|
gen=gen, quantise=False, keep_bg=True)
|
|
|
|
|
|
def DCUT(kind: str, size: tuple[int, int], seed: int, subject: str) -> Asset:
|
|
"""Door-scene object that wants cutting out of its background."""
|
|
return Asset(kind, "flat", size, f"{subject}, {DOOR_TAIL}", seed,
|
|
gen=(1024, 1024), quantise=True, keep_bg=False)
|
|
|
|
|
|
DOORSCENE = [
|
|
# The kebab shop is the moral centre of the whole game (design §5) and it was
|
|
# three rectangles and the word KEBABS.
|
|
D("kebabShop", (112, 106), 4520,
|
|
"a late night kebab takeaway shopfront, glowing yellow sign board, "
|
|
"steamy window, vertical rotisserie inside, tiled counter"),
|
|
# One venue façade per rung of the ladder — the first thing you see of a
|
|
# promotion, standing in front of that venue's street plate.
|
|
D("front:theRoyal", (116, 140), 4521,
|
|
"a suburban Australian pub facade at night, painted brick, frosted windows, "
|
|
"a lit beer brand sign, single timber door"),
|
|
D("front:voltage", (116, 140), 4522,
|
|
"a nightclub facade at night, black painted brick, pink neon strip, "
|
|
"a dark recessed doorway"),
|
|
D("front:elevate", (116, 140), 4523,
|
|
"a polished hotel lobby entrance at night, glass and brushed steel, "
|
|
"warm downlight, a lift lobby visible inside"),
|
|
D("front:room", (116, 140), 4524,
|
|
"a blank concrete warehouse facade at night, no signage at all, "
|
|
"one steel service door, sodium light above it"),
|
|
DCUT("stanchion", (16, 32), 4525,
|
|
"a single brass rope barrier stanchion post, front view, isolated on black"),
|
|
DCUT("streetBin", (24, 32), 4526,
|
|
"a battered council street rubbish bin, front view, isolated on black"),
|
|
DCUT("streetLamp", (24, 96), 4527,
|
|
"a tall street lamp post lit with orange sodium light, front view, isolated on black"),
|
|
DCUT("taxi", (96, 48), 4528,
|
|
"a waiting taxi cab seen from the side at night, headlights on, isolated on black"),
|
|
# Six more gig posters. Mangled text is CORRECT here and always has been —
|
|
# docs/SPACES.md keeps "no text" out of the poster prompts on purpose,
|
|
# because unreadable print is what a real pub poster wall looks like.
|
|
*[
|
|
Asset(f"poster{i}", "flat", (32, 16), p, 4530 + i, gen=(512, 768), quantise=True)
|
|
for i, p in enumerate([
|
|
"a grimy 90s Australian pub rock gig poster, portrait, torn edges",
|
|
"a rave flyer poster, acid green and magenta, portrait, torn edges",
|
|
"a missing cat poster stapled to a pole, portrait, weathered",
|
|
"a techno club night poster, stark black and white, portrait",
|
|
"a meat raffle and poker night notice, portrait, photocopied",
|
|
"a covers band poster with a bad photo of the band, portrait",
|
|
], start=4)
|
|
],
|
|
]
|
|
|
|
ALL: list[Asset] = [
|
|
*ROYAL, *ELEVATE, *ROOM, *TEXTURES, *WALLS, *STAFF, *FILLER, *STREETS,
|
|
*DOORSCENE,
|
|
]
|
|
|
|
BY_KIND = {a.kind: a for a in ALL}
|
|
|
|
if __name__ == "__main__":
|
|
flat = sum(1 for a in ALL if a.route == "flat")
|
|
print(f"{len(ALL)} assets — {len(ALL) - flat} mesh, {flat} flat")
|
|
for a in ALL:
|
|
print(f" {a.route:4} {a.kind:16} {a.size[0]:>4}x{a.size[1]:<3} rake={a.rake}")
|