diff --git a/LANEHANDOVER.md b/LANEHANDOVER.md index 008e2ef..c2edae2 100644 --- a/LANEHANDOVER.md +++ b/LANEHANDOVER.md @@ -2294,3 +2294,45 @@ missing art**. 26/26 this wave, no failures. - Cloudflare's 10k neurons reset 00:00 UTC — a batch started after that gets ~150 free images before it ever touches the farm. Both waves today ran entirely on MODELBEAST because the allocation was already spent. + +## SESSION — FABLE-SOLO-26c · 2026-07-29 (wave 3, same session) + +**Branch:** main · **Gate:** lint ✓ build ✓ test ✓ (821 tests) · **Deployed** + verified. +John: "we can use local flux as well cloudflare creds is just a bonus." + +### The door scene stops being 28 rectangles +The rope is where the player spends most of the night and it was still coloured +primitives behind one street plate. Now: +- **`kebabShop`** — the moral centre of the game (design §5) was three rects and + the word KEBABS. The warm spill onto the footpath stays procedural either way; + it is the only warm light in the scene and it is what says OPEN. +- **`front:`** — one venue façade per rung. The Royal is a pub front, + ROOM is a blank wall with one steel door. The promotion is now visible from the + footpath before you walk in. ROOM also suppresses the procedural NOT TONIGHT + sign, because "no sign. no socials." is literally its entry in data/venues.ts. +- Footpath dressing: `streetLamp`, `streetBin`, `taxi`, and `stanchion` posts on + the red carpet. The taxi is alpha'd back and depth-1 so it never reads as + clickable next to the kebab shop, which is. +- Six more posters (`poster4`–`poster9`) so four venues stop sharing one wall of + three. Mangled lettering on these is CORRECT (docs/SPACES.md) — the poster + prompts are the only ones that do not say "no text". + +### The composition bug I shipped into the same wave +First pass prompted the per-venue street plates as **"frontage"**, so The Royal's +backdrop was a whole pub with its own central door — and then the venue façade +was drawn 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 ("NO doors, NO entrance") and regenerated: 50s each, free, local. +Caught by looking at the screen, not by any test — there is no assertion that +would have found it. + +### Totals +**127 sprites.** 95 prop kinds + 10 materials + the door-scene art, none missing. +15/15 this wave plus 3 regenerated plates, no failures. + +### Note on Cloudflare +Still spent both times today: the free 10k neurons reset at **00:00 UTC**, which +is 10:00 AEST — so an evening session in Australia will essentially always find +the allocation gone and run local. That is fine (the farm is free and unlimited), +but it means the CF-first tier only ever pays off for a morning batch. Every +sprite this session is MODELBEAST-rendered. diff --git a/public/props/front_elevate.png b/public/props/front_elevate.png new file mode 100644 index 0000000..1b724a0 Binary files /dev/null and b/public/props/front_elevate.png differ diff --git a/public/props/front_room.png b/public/props/front_room.png new file mode 100644 index 0000000..81f53e4 Binary files /dev/null and b/public/props/front_room.png differ diff --git a/public/props/front_theRoyal.png b/public/props/front_theRoyal.png new file mode 100644 index 0000000..1a835a6 Binary files /dev/null and b/public/props/front_theRoyal.png differ diff --git a/public/props/front_voltage.png b/public/props/front_voltage.png new file mode 100644 index 0000000..d0cf3eb Binary files /dev/null and b/public/props/front_voltage.png differ diff --git a/public/props/kebabShop.png b/public/props/kebabShop.png new file mode 100644 index 0000000..36df4df Binary files /dev/null and b/public/props/kebabShop.png differ diff --git a/public/props/manifest.json b/public/props/manifest.json index 2e35baf..63e20bd 100644 --- a/public/props/manifest.json +++ b/public/props/manifest.json @@ -39,6 +39,10 @@ "fireHose", "firepit", "flightCase", + "front_elevate", + "front_room", + "front_theRoyal", + "front_voltage", "glassRack", "glassStack", "graffiti", @@ -46,6 +50,7 @@ "highTable", "iceWell", "jukebox", + "kebabShop", "keg", "liftDoor", "loungeChair", @@ -69,6 +74,12 @@ "poster1", "poster2", "poster3", + "poster4", + "poster5", + "poster6", + "poster7", + "poster8", + "poster9", "raffleDrum", "rollerDoor", "ropePost", @@ -86,6 +97,9 @@ "staffGlassie", "staffSecurity", "stampPodium", + "stanchion", + "streetBin", + "streetLamp", "street_backdrop", "street_elevate", "street_room", @@ -94,6 +108,7 @@ "subBass", "tabBoard", "taps", + "taxi", "tex_brick", "tex_carpet", "tex_concrete", diff --git a/public/props/poster4.png b/public/props/poster4.png new file mode 100644 index 0000000..15cf229 Binary files /dev/null and b/public/props/poster4.png differ diff --git a/public/props/poster5.png b/public/props/poster5.png new file mode 100644 index 0000000..566ad34 Binary files /dev/null and b/public/props/poster5.png differ diff --git a/public/props/poster6.png b/public/props/poster6.png new file mode 100644 index 0000000..e631e33 Binary files /dev/null and b/public/props/poster6.png differ diff --git a/public/props/poster7.png b/public/props/poster7.png new file mode 100644 index 0000000..7ccdd4c Binary files /dev/null and b/public/props/poster7.png differ diff --git a/public/props/poster8.png b/public/props/poster8.png new file mode 100644 index 0000000..19f7db6 Binary files /dev/null and b/public/props/poster8.png differ diff --git a/public/props/poster9.png b/public/props/poster9.png new file mode 100644 index 0000000..139ed47 Binary files /dev/null and b/public/props/poster9.png differ diff --git a/public/props/stanchion.png b/public/props/stanchion.png new file mode 100644 index 0000000..a41118e Binary files /dev/null and b/public/props/stanchion.png differ diff --git a/public/props/streetBin.png b/public/props/streetBin.png new file mode 100644 index 0000000..dfcfebd Binary files /dev/null and b/public/props/streetBin.png differ diff --git a/public/props/streetLamp.png b/public/props/streetLamp.png new file mode 100644 index 0000000..f32a842 Binary files /dev/null and b/public/props/streetLamp.png differ diff --git a/public/props/street_elevate.png b/public/props/street_elevate.png index 952e6fd..82475aa 100644 Binary files a/public/props/street_elevate.png and b/public/props/street_elevate.png differ diff --git a/public/props/street_room.png b/public/props/street_room.png index eb94e8e..33dfba9 100644 Binary files a/public/props/street_room.png and b/public/props/street_room.png differ diff --git a/public/props/street_theRoyal.png b/public/props/street_theRoyal.png index 8020972..a78df53 100644 Binary files a/public/props/street_theRoyal.png and b/public/props/street_theRoyal.png differ diff --git a/public/props/taxi.png b/public/props/taxi.png new file mode 100644 index 0000000..9fc4daa Binary files /dev/null and b/public/props/taxi.png differ diff --git a/src/scenes/door/DoorScene.ts b/src/scenes/door/DoorScene.ts index df4376a..5dd8a8b 100644 --- a/src/scenes/door/DoorScene.ts +++ b/src/scenes/door/DoorScene.ts @@ -285,20 +285,39 @@ export class DoorScene extends Phaser.Scene { this.add.rectangle(W / 2, 176, W, 2, 0x2a2a38); // kebab shop, stage left — visible all night, the moral centre (design §5) - this.add.rectangle(62, 120, 112, 106, 0x2a1c08); - this.add.rectangle(62, 88, 96, 18, DOOR_PALETTE.kebab, 0.9); - this.add.text(28, 82, 'KEBABS', { fontFamily: MONO, fontSize: '12px', color: '#3a2404' }); + if (this.textures.exists('gen:prop:kebabShop')) { + this.add.image(62, 120, 'gen:prop:kebabShop').setDisplaySize(112, 106); + } else { + this.add.rectangle(62, 120, 112, 106, 0x2a1c08); + this.add.rectangle(62, 88, 96, 18, DOOR_PALETTE.kebab, 0.9); + this.add.text(28, 82, 'KEBABS', { fontFamily: MONO, fontSize: '12px', color: '#3a2404' }); + } + // The warm spill onto the footpath stays either way — it is what tells you + // the shop is OPEN, and it is the only warm light in the scene. this.add.rectangle(62, 148, 92, 46, 0xe8c060, 0.12); // The whole shopfront becomes a button when Dazza wants feeding. this.kebabGlow = this.add.rectangle(62, 120, 112, 106, 0xe8c060, 0).setInteractive(); this.kebabGlow.on('pointerdown', () => this.startKebabRun()); this.add.rectangle(62, 214, 96, 8, DOOR_PALETTE.kebab, 0.13); - // the venue - this.add.rectangle(DOOR_X, 108, 116, 140, 0x181828); - this.add.rectangle(DOOR_X, 58, 92, 16, DOOR_PALETTE.neon, 0.92); - this.add.text(DOOR_X - 40, 51, 'NOT TONIGHT', { fontFamily: MONO, fontSize: '10px', color: '#2a0818' }); - this.add.rectangle(DOOR_X, 140, 34, 74, 0x05050a); + // The venue you are working tonight, standing in front of its own street. + // The Royal is a pub front, ROOM is a blank wall with one steel door — the + // promotion is meant to be visible from the footpath before you go in. + const front = this.textures.exists(`gen:prop:front_${this.night.venue.id}`) + ? `gen:prop:front_${this.night.venue.id}` + : null; + if (front) { + this.add.image(DOOR_X, 108, front).setDisplaySize(116, 140); + } else { + this.add.rectangle(DOOR_X, 108, 116, 140, 0x181828); + } + // The venue's own sign stays procedural: it carries the game's title and + // ROOM is defined by NOT having one (data/venues.ts — "no sign. no socials"). + if (this.night.venue.id !== 'room') { + this.add.rectangle(DOOR_X, 58, 92, 16, DOOR_PALETTE.neon, 0.92); + this.add.text(DOOR_X - 40, 51, 'NOT TONIGHT', { fontFamily: MONO, fontSize: '10px', color: '#2a0818' }); + } + if (!front) this.add.rectangle(DOOR_X, 140, 34, 74, 0x05050a); this.add.rectangle(DOOR_X, 214, 66, 8, DOOR_PALETTE.neon, 0.16); // The doorway is how you get to the floor — click it to step inside. @@ -319,8 +338,28 @@ export class DoorScene extends Phaser.Scene { // The red carpet: a strip of somewhere-better between rope and door, // flanked by brass posts. Bodies parked here are rendered by // syncCarpetSprites; the strip is set dressing. + // Footpath dressing. The street had one backdrop plate and 76px of empty + // grey between the kebab shop and the rope; a lamp, a bin and a waiting cab + // are what is actually outside a venue at 1am. + if (this.textures.exists('gen:prop:streetLamp')) { + this.add.image(196, 214, 'gen:prop:streetLamp').setOrigin(0.5, 1).setDisplaySize(24, 96).setDepth(1); + } + if (this.textures.exists('gen:prop:streetBin')) { + this.add.image(232, 216, 'gen:prop:streetBin').setOrigin(0.5, 1).setDisplaySize(24, 32).setDepth(1); + } + if (this.textures.exists('gen:prop:taxi')) { + // Parked far left, behind everything — it is scenery, not a destination, + // and it must never look clickable next to the kebab shop, which is. + this.add.image(96, 244, 'gen:prop:taxi').setOrigin(0.5, 1).setDisplaySize(96, 48).setDepth(1).setAlpha(0.85); + } + if (this.textures.exists('gen:prop:carpet')) { this.add.image(420, 236, 'gen:prop:carpet').setOrigin(0.5, 1).setDisplaySize(132, 34).setDepth(2); + if (this.textures.exists('gen:prop:stanchion')) { + for (const px of [356, 484]) { + this.add.image(px, 232, 'gen:prop:stanchion').setOrigin(0.5, 1).setDisplaySize(14, 30).setDepth(3); + } + } } else { this.add.rectangle(420, 232, 124, 12, 0x8a1428).setDepth(2); this.add.rectangle(420, 227, 124, 2, 0xa8283c).setDepth(2); diff --git a/src/scenes/floor/FloorView.ts b/src/scenes/floor/FloorView.ts index c82b096..586f680 100644 --- a/src/scenes/floor/FloorView.ts +++ b/src/scenes/floor/FloorView.ts @@ -773,6 +773,8 @@ const PROP_SIZES: Record = { barrier: [32, 16], distroBox: [16, 16], fireHose: [16, 16], firepit: [32, 16], topiary: [16, 16], fireExt: [16, 16], exitSign: [16, 16], cctv: [16, 16], staffCleaner: [16, 32], staffChef: [16, 32], + poster4: [32, 16], poster5: [32, 16], poster6: [32, 16], + poster7: [32, 16], poster8: [32, 16], poster9: [32, 16], }; function bakePropPlaceholder(scene: Phaser.Scene, kind: PropKind): string { diff --git a/src/scenes/floor/layouts/elevate.ts b/src/scenes/floor/layouts/elevate.ts index 3039fbf..eb25951 100644 --- a/src/scenes/floor/layouts/elevate.ts +++ b/src/scenes/floor/layouts/elevate.ts @@ -263,7 +263,7 @@ const PROPS: readonly PropDef[] = Object.freeze([ P('highC', 'highTable', 43, 24, 1, 1), P('highD', 'highTable', 20, 24, 1, 1), P('loungeC', 'loungeChair', 41, 32, 2, 1), - P('terraceArt', 'poster2', 15, 14, 1, 2), + P('terraceArt', 'poster7', 15, 14, 1, 2), P('glassie', 'staffGlassie', 44, 20, 1, 1), // Wave 2: a rooftop bar's actual furniture, and the safety kit every venue has. diff --git a/src/scenes/floor/layouts/room.ts b/src/scenes/floor/layouts/room.ts index ac6f0e0..e27b5dc 100644 --- a/src/scenes/floor/layouts/room.ts +++ b/src/scenes/floor/layouts/room.ts @@ -260,6 +260,11 @@ const ROOM_PROPS: readonly PropDef[] = Object.freeze([ P('extBar', 'fireExt', 9, 22, 1, 1), P('exitHall', 'exitSign', 9, 18, 1, 1), P('cleaner', 'staffCleaner', 64, 33, 1, 2), + + // Flyposting by the entry. ROOM has no signage of its own (that is the whole + // conceit) but nothing stops other people's parties going up on its wall. + P('flyA', 'poster5', 14, 16, 2, 1), + P('flyB', 'poster7', 14, 21, 2, 1), ]); const L = ( diff --git a/src/scenes/floor/layouts/theRoyal.ts b/src/scenes/floor/layouts/theRoyal.ts index fa1b2d5..ca639ad 100644 --- a/src/scenes/floor/layouts/theRoyal.ts +++ b/src/scenes/floor/layouts/theRoyal.ts @@ -270,8 +270,8 @@ const PROPS: readonly PropDef[] = Object.freeze([ // entry corridor P('poster1', 'poster1', 3, 13, 2, 1), - P('poster2', 'poster2', 6, 13, 2, 1), - P('poster3', 'poster3', 9, 13, 2, 1), + P('poster2', 'poster8', 6, 13, 2, 1), + P('poster3', 'poster9', 9, 13, 2, 1), // Wave 2. The review counted four props across ~500 tiles of central carpet — // "more genuinely empty carpet than any shipped room". A pub that size has diff --git a/src/scenes/floor/venueMap.ts b/src/scenes/floor/venueMap.ts index 02a1525..0d930a4 100644 --- a/src/scenes/floor/venueMap.ts +++ b/src/scenes/floor/venueMap.ts @@ -236,7 +236,12 @@ export type PropKind = // review found genuinely empty (The Royal's middle, ROOM's east hall). | 'pieWarmer' | 'binRound' | 'sandwichBoard' | 'noticeBoard' | 'ceilingFan' | 'flightCase' | 'barrier' | 'distroBox' | 'fireHose' | 'firepit' | 'topiary' - | 'fireExt' | 'exitSign' | 'cctv' | 'staffCleaner' | 'staffChef'; + | 'fireExt' | 'exitSign' | 'cctv' | 'staffCleaner' | 'staffChef' + // Six more gig posters, so four venues stop sharing one wall of three. + // Mangled lettering is CORRECT on these (docs/SPACES.md): unreadable print + // is what a real pub poster wall looks like, which is why the poster prompts + // are the only ones that do not say "no text". + | 'poster4' | 'poster5' | 'poster6' | 'poster7' | 'poster8' | 'poster9'; export interface PropEmissive { colour: number; diff --git a/tools/gen/assets.py b/tools/gen/assets.py index 09c2980..a16b746 100644 --- a/tools/gen/assets.py +++ b/tools/gen/assets.py @@ -216,16 +216,16 @@ STAFF = [ # ---- Door-scene street plates, one per venue --------------------------------- STREETS = [ F("street:theRoyal", (640, 176), 4470, - "a wide suburban Australian pub frontage at night, painted brick, tiled dado, " - "a lit beer sign, bins, wet footpath, warm amber light", + "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 lobby entrance of a rooftop bar at night, brushed steel lift doors, " - "marble, planters, cool white downlight, glass", + "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 unmarked concrete warehouse wall at night, no signage, a single steel door, " - "graffiti, sodium orange streetlight, puddles, rain", + "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), ] @@ -273,8 +273,76 @@ FILLER = [ "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}