diff --git a/LANEHANDOVER.md b/LANEHANDOVER.md index 0341091..80f6b08 100644 --- a/LANEHANDOVER.md +++ b/LANEHANDOVER.md @@ -2496,3 +2496,34 @@ had already split `barServeBreach` out of `barServe` so the RSA question can actually fire. I only had to supply the missing words and the variety rule. `tests/incidentReport.test.ts` now guards the whole class: every loaded kind must have a bespoke prompt, not the generic line. + +## SESSION — FABLE-SOLO-29 · 2026-08-09 + +**Branch:** main (solo, Fable) · **Gate:** lint ✓ build ✓ test ✓ (842 tests) + +### The two between-shift screens stop being rectangles +The roster board and the back office are seen EVERY night and were both drawn +entirely from coloured primitives. Added to `tools/gen/assets.py` in the +existing idiom (new `BETWEEN` group): +- `plate:corkboard` — a lit cork noticeboard, flat route, keep_bg, no quantise +- `plate:backoffice` — a cramped panelled office, one bulb, empty desk +- `sorter` — MESH route (rake 58), the COIN-U-SORT itself. `quantise=False` + deliberately: it is a ~130px UI hero, not a 16px floor prop, and 16 colours + turns a grey machine to porridge. + +Both scenes keep their drawn version as the fallback — a missing plate has +always meant placeholders, never a broken screen. The sorter image goes INSIDE +the existing container so every shake and bob still works untouched. + +### RosterScene needs its own preload, uniquely +It is scene[0]: it runs before anything has loaded, so it cannot rely on the +manifest prewarm it goes on to START. `preload()` pulls the corkboard and only +the corkboard; the other ~130 sprites still warm in the background behind the +board. + +### A legibility regression the plate caused, and the fix +Dropping real art behind the roster put the heading directly on the plate's +fluorescent tube — light type on the brightest thing in the image. The board +moved down to y=194 (258 tall) and the heading moved off it onto the dark wall +at y=16/38. Worth remembering: adding a background can break foreground text +that was fine over a flat colour. diff --git a/docs/ASSETS.md b/docs/ASSETS.md index fbb58f1..9d6805b 100644 --- a/docs/ASSETS.md +++ b/docs/ASSETS.md @@ -70,7 +70,7 @@ furniture (bar, booths, stools) could come straight from its fit pipeline. (794 MB, 26,910 files) — prior AI-gen pools; sweep for club/street/neon/night material before generating fresh. - m4pro SPACEY `MRP PHOTOSHOP FILES` (4.4 GB PSDs) — design-language source - (posters/flyers could become in-game wall dressing). m4pro is HANDS-OFF; access + (posters/flyers could become in-game wall dressing). m4pro is reachable (HANDS-OFF label retired 2026-08-06 — never true); access via the SMB mount on m3ultra if needed. ### 4. Generation pipeline (MODELBEAST) diff --git a/docs/ASSETS.md.bak-20260806 b/docs/ASSETS.md.bak-20260806 new file mode 100644 index 0000000..fbb58f1 --- /dev/null +++ b/docs/ASSETS.md.bak-20260806 @@ -0,0 +1,118 @@ +# NOT TONIGHT — Fleet Asset Notes + +*What already exists on John's network that this game can reuse, borrow from, or +generate with. Sourced from the fleet-wide `ASSET_INVENTORY_2026-07-18.md` and +`GAME_ASSET_AUDIT_2026-07-18.md` (both on ultra `~/Documents/`). Compiled +2026-07-19.* + +**Context for lanes:** v0.1–0.3 use PROCEDURAL placeholder art (coloured-pixel +paper-doll, synthesized audio) — nothing below is needed before the v0.4 art pass. +This doc exists so the art pass starts from the library, not from scratch. Nothing +here changes the `renderDoll` / layer contracts. + +## Directly relevant (v0.4 art pass) + +### 1. 90sDJsim paper-doll wardrobe — the big one +`ultra:~/Documents/90sDJsim/web/world/media/doll/` — **441 authored clothing +sprites** across tops/bottoms/shoes/hats/bags, brand-flavoured (coogi, dr_martens, +nike_air_max, moschino, quiksilver, kmart, disruptor_ii…), dynamically loaded by +`os.listdir` slot convention. +- **Reuse the taxonomy**: their slot/brand/type naming should inform + `data/outfits.ts` vocabulary (we already have shoes/legs/top/outer/hair/accessory + — cross-check their catalog for types we missed, e.g. bucket hats, bumbags). +- **Reuse the art via pixelation**: 90s wardrobe reads perfectly as 2026 Valley + fashion-in-quotes. A downscale/quantize pass (sprite → 32×48 palette-locked + pixel layer) could convert the catalog into real doll layers. Prototype on 10 + sprites before committing — pixelated photo-sprites can look mushy; may need a + cleanup pass or MODELBEAST-assisted redraw per sprite. + +### 2. RIGROOM — the character × animation factory (surveyed 2026-07-19) +**This supersedes the old "animation references" note. The fleet has a full +character/animation pipeline, live and organised:** +- **RIGROOM library** (`ultra:~/meshgod-backups/library`, 2.4 GB): **607 + animation FBX** in themed packs + 59 character files. Web UI live at + `http://100.91.239.7:8010/rigroom` (characters × animations pairing, three.js + preview, instant Mixamo-ready FBX download). CLI: + `MESHGOD/scripts/rigroom.py classify|sort|scan`. +- Clips that read like THIS game's shot list: `Searching_Pockets` (pat-down), + `Standing_Arguing` + a full boxing/knockout pack (floor fights), + `Shaking_Head_No` (the deny), `Inspecting`/`Looking_Around` (ID check), + `Leaning_Idle02_Smoking` (smoking area), dozens of idles/sits, dance packs, + `Brutal To Happy Walking`, zombie stagger (maggot walk), 12 DJ motions. + Raw source: `m3ultra:~/Documents/Rokoko_Free_Mocap_FBX_263` (606 FBX). +- **Modular character kit** (`m3ultra:~/Documents/character_kit_modular`, + canonical `ultra:~/Documents/character_kit/modular/`): GODVERSE socket + standard — head/hands/feet/torso swap on one shared 65-bone `mixamorig:` + skeleton, 40 embedded actions, every library clip retargets for free. +- **repose** (`m3ultra:~/Documents/repose`): photo → T-pose views (SD1.5 + + ControlNet, all-local) → MeshGod multi-view → autorig. A face becomes a + rigged character without leaving the tailnet. +- **For NOT TONIGHT (v0.4 art pass), the pre-render path is now fully paved:** + modular kit character + RIGROOM clip → Blender orthographic top-down/front → + pixelate → sprite sheets behind the existing `renderDoll` poses. The outfit + variety could even come from kit part swaps. Same pipeline, one venue's + worth of NPC animation for free. Decide at v0.4; contracts don't move. +- Also present, unsurveyed depth: `MOCAPGOD` (empty queue — service scaffold), + `mixamo_upload` staging, `MOTIONLIB` (2,703 FBX, ultra) still the deep pool. + +### 2b. procity (m3ultra) — the 90s-Aussie town sim +Walkable procedural shopping town (three.js, no build step), built by lanes +like ours. Pipeline: 186 GLB/FBX with fit/normalize/decimate tooling +(`procity_fit_*` props: arcade cabinet, clothes racks, shelving), 21 peds. +The **prop pipeline conventions** (batch manifests, decimation results JSON) +are the pattern to copy if NOT TONIGHT ever goes 3D-prop-assisted; the venue +furniture (bar, booths, stools) could come straight from its fit pipeline. + +### 3. Venue / street reference art +- `ultra:~/Documents/90sDJsim/web/world/media/` named scene/venue art — 100 wired + widescreen club/venue images. Mood reference for our neon-dark interiors. +- `gdrive:ASSETS FROM AI` (2.47 GB, 3,239 files) + `gdrive:IMAGE-DATABASE` + (794 MB, 26,910 files) — prior AI-gen pools; sweep for club/street/neon/night + material before generating fresh. +- m4pro SPACEY `MRP PHOTOSHOP FILES` (4.4 GB PSDs) — design-language source + (posters/flyers could become in-game wall dressing). m4pro is HANDS-OFF; access + via the SMB mount on m3ultra if needed. + +### 4. Generation pipeline (MODELBEAST) +m3ultra central queue :8777 (see fleet skill). For NOT TONIGHT this is an +**image generator, not a mesh generator** — we need no GLBs. Useful for: patron +face/outfit reference sheets to hand-pixel from, venue backdrop concepts, Dazza's +contact photo, joke ID photos. The thriftgod `gen_assets.py` + 640px `sips` +convention (m3ultra `~/Documents/thriftgod/`) is a proven prompt→asset-dir pattern +worth cloning for an `art_src/` pipeline here. + +## Explicitly NOT for this game + +- **All 3D mesh libraries** (`3D=models`, `3D-STORE`, FBX, femalemeshed, + NPCFACTORY, MODELBEAST GLB output) — no 3D in this game; only via the Option-B + prerender path above, never at runtime. +- **Audio archives** (bandcamp ~1 TB, YT-AUDIO, MONSTER MIXES). The design is + locked to a fully synthesized WebAudio techno engine (design §5 / LANE_JUICE) — + the low-pass location trick needs a synth we control, and shipping archive audio + is a licensing minefield. John's own MONSTER MIXES are legally his — park the + idea of a licensed-mix easter egg (jukebox in the kebab shop?) as a someday-note. +- **dealgod image pool** (2.4M product photos) + `recordgod_disc_images` — product + photography, wrong domain. (One cursed exception: pat-down contraband items + could be pixelated from real dealgod product shots. Someday-note.) +- **thriftgod 2D dressing** — authored 640px painterly JPGs, wrong style; its + *pipeline* is the reusable part (see §4). + +## Ground rules for the eventual art pass + +**Licensing: none needed.** This is a private tailnet sandbox game, never public — +John's call (2026-07-19). Anything on the fleet or gdrive is fair game, including +the audio archives and MONSTER MIXES. (If that ever changes and the game goes +public, this section is the checklist to revisit first.) + +1. Everything funnels through the existing contracts (`renderDoll` poses, outfit + slots) — art swaps in behind interfaces, callers never change. +2. One palette, one pixel density (32×48 queue dolls at 640×360) decided ONCE + before any batch conversion; retrofitting mixed densities is the classic trap. +3. Log each imported batch (source → transform → dest) in this file — not for + licensing, just so we can regenerate/retrace later. +4. Check the MeshGod gallery + MODELBEAST `data/` (m3ultra) before generating + anything — the inventory's rule "catalog before regenerating" applies here too. +5. The synthesized techno engine stays the core soundtrack (the location-filter + trick needs a synth we control), but real archive audio is now allowed as + layers/easter eggs — e.g. MONSTER MIXES on a kebab-shop jukebox, real crowd + ambience beds under the synth. diff --git a/public/props/manifest.json b/public/props/manifest.json index 63e20bd..0df7a65 100644 --- a/public/props/manifest.json +++ b/public/props/manifest.json @@ -68,6 +68,8 @@ "pillar", "plant", "planter", + "plate_backoffice", + "plate_corkboard", "plywoodBar", "pokie", "poolTable", @@ -88,6 +90,7 @@ "sinkRow", "skylineCard", "smokeMachine", + "sorter", "speaker", "stackF1", "staffBartender", diff --git a/public/props/plate_backoffice.png b/public/props/plate_backoffice.png new file mode 100644 index 0000000..23e819c Binary files /dev/null and b/public/props/plate_backoffice.png differ diff --git a/public/props/plate_corkboard.png b/public/props/plate_corkboard.png new file mode 100644 index 0000000..93b8b4c Binary files /dev/null and b/public/props/plate_corkboard.png differ diff --git a/public/props/sorter.png b/public/props/sorter.png new file mode 100644 index 0000000..cef9d4e Binary files /dev/null and b/public/props/sorter.png differ diff --git a/src/scenes/shared/HaulDeskScene.ts b/src/scenes/shared/HaulDeskScene.ts index 87cc067..ba0876f 100644 --- a/src/scenes/shared/HaulDeskScene.ts +++ b/src/scenes/shared/HaulDeskScene.ts @@ -66,11 +66,16 @@ export class HaulDeskScene extends Phaser.Scene { const t = tallyHaul(this.args.haul, this.args.pocketedBaggie); this.lines = t.lines; - this.add.rectangle(W / 2, H / 2, W, H, 0x14110c); - // the desk: a slab of laminate under a single tired bulb - this.add.ellipse(W / 2, 96, 460, 220, 0x2a2318, 0.55); - this.add.rectangle(W / 2, 250, W, 220, 0x3a2f1e); - this.add.rectangle(W / 2, 142, W, 3, 0x241c12); + // The room. The drawn version stays as the fallback: a missing plate has + // always meant placeholders rather than a broken screen. + if (this.textures.exists('gen:prop:plate_backoffice')) { + this.add.image(0, 0, 'gen:prop:plate_backoffice').setOrigin(0, 0).setDisplaySize(W, H); + } else { + this.add.rectangle(W / 2, H / 2, W, H, 0x14110c); + this.add.ellipse(W / 2, 96, 460, 220, 0x2a2318, 0.55); + this.add.rectangle(W / 2, 250, W, 220, 0x3a2f1e); + this.add.rectangle(W / 2, 142, W, 3, 0x241c12); + } this.add .text(W / 2, 20, HAUL_UI.title, { fontFamily: MONO, fontSize: '15px', color: '#f0e4d0' }) @@ -85,7 +90,7 @@ export class HaulDeskScene extends Phaser.Scene { .text(W / 2, 60, '', { fontFamily: MONO, fontSize: '9px', color: '#e8dcc4', align: 'center', wordWrap: { width: 420 } }) .setOrigin(0.5, 0); this.quipText = this.add - .text(W / 2, 300, '', { fontFamily: MONO, fontSize: '8px', color: '#a09070', align: 'center', wordWrap: { width: 460 } }) + .text(W / 2, 300, '', { fontFamily: MONO, fontSize: '8px', color: '#c0ac84', align: 'center', wordWrap: { width: 460 } }) .setOrigin(0.5, 0); this.tallyText = this.add .text(W - 18, 250, '$0', { fontFamily: MONO, fontSize: '26px', color: '#f0d060' }) @@ -131,16 +136,22 @@ export class HaulDeskScene extends Phaser.Scene { const y = 210; const c = this.add.container(x, y); this.sorter = c; - c.add(this.add.rectangle(0, 0, 150, 92, 0x5a5348).setStrokeStyle(2, 0x3a352c)); - c.add(this.add.rectangle(0, -38, 116, 20, 0x241f18)); // hopper mouth - c.add(this.add.rectangle(0, -46, 132, 8, 0x6a6355)); - for (let i = -2; i <= 2; i++) { - c.add(this.add.rectangle(i * 26, 22, 18, 26, 0x2a251c)); // coin channels + // The machine goes INSIDE the container so every shake and bob below still + // works whether it is real art or the drawn fallback. + if (this.textures.exists('gen:prop:sorter')) { + c.add(this.add.image(0, 0, 'gen:prop:sorter').setDisplaySize(132, 100)); + } else { + c.add(this.add.rectangle(0, 0, 150, 92, 0x5a5348).setStrokeStyle(2, 0x3a352c)); + c.add(this.add.rectangle(0, -38, 116, 20, 0x241f18)); // hopper mouth + c.add(this.add.rectangle(0, -46, 132, 8, 0x6a6355)); + for (let i = -2; i <= 2; i++) { + c.add(this.add.rectangle(i * 26, 22, 18, 26, 0x2a251c)); // coin channels + } + c.add(this.add.rectangle(0, 50, 150, 10, 0x46403a)); // the tray } - c.add(this.add.rectangle(0, 50, 150, 10, 0x46403a)); // the tray c.add( this.add - .text(0, -8, HAUL_UI.machineName, { fontFamily: MONO, fontSize: '7px', color: '#9a9080' }) + .text(0, 62, HAUL_UI.machineName, { fontFamily: MONO, fontSize: '7px', color: '#8a8070' }) .setOrigin(0.5), ); } diff --git a/src/scenes/shared/RosterScene.ts b/src/scenes/shared/RosterScene.ts index 937ae91..520d553 100644 --- a/src/scenes/shared/RosterScene.ts +++ b/src/scenes/shared/RosterScene.ts @@ -39,6 +39,16 @@ export class RosterScene extends Phaser.Scene { this.carried = data ?? {}; } + /** + * The board's own plate, and only that. RosterScene is scene[0] — it runs + * before anything has loaded, so unlike every other screen it cannot rely on + * the manifest prewarm it goes on to start. One small image is worth the + * wait; the rest of the art is warmed in the background by prewarmProps(). + */ + preload(): void { + this.load.image('gen:prop:plate_corkboard', 'props/plate_corkboard.png'); + } + /** * Warm the prop textures while the player reads the board. * @@ -89,15 +99,19 @@ export class RosterScene extends Phaser.Scene { // the back office: unpainted, honest this.add.rectangle(W / 2, H / 2, W, H, 0x14100c); - this.add.rectangle(W / 2, H / 2 - 8, 560, 280, 0x3a2a18).setStrokeStyle(3, 0x241a10); + if (this.textures.exists('gen:prop:plate_corkboard')) { + this.add.image(W / 2, 194, 'gen:prop:plate_corkboard').setDisplaySize(560, 258); + } else { + this.add.rectangle(W / 2, 194, 560, 258, 0x3a2a18).setStrokeStyle(3, 0x241a10); + } this.add - .text(W / 2, 42, `${venue.name} — ${NIGHT_NAMES[nightIndex] ?? ''}`, { + .text(W / 2, 16, `${venue.name} — ${NIGHT_NAMES[nightIndex] ?? ''}`, { fontFamily: MONO, fontSize: '16px', color: '#f0e4d0', }) .setOrigin(0.5); this.add - .text(W / 2, 62, 'THE ROSTER · take a shift', { + .text(W / 2, 38, 'THE ROSTER · take a shift', { fontFamily: MONO, fontSize: '8px', color: '#a89878', }) .setOrigin(0.5); diff --git a/tools/gen/assets.py b/tools/gen/assets.py index 0dce151..58077c7 100644 --- a/tools/gen/assets.py +++ b/tools/gen/assets.py @@ -346,9 +346,33 @@ DOORSCENE = [ ], ] + +# ---- Between-shift screens --------------------------------------------------- +# The roster board and the back office are the two screens the player sees EVERY +# night that were still drawn entirely from coloured rectangles. Both are plates +# (keep_bg, no quantise): they fill their frame and are meant to be opaque. +BETWEEN = [ + F("plate:corkboard", (560, 280), 4600, + "a battered cork noticeboard filling the whole frame, pinholes and old staples, " + "warm fluorescent light from above, a strip of timber frame at the edge, " + "NOTHING pinned to it, no paper, no notes, empty board", + gen=(1536, 768), quantise=False, keep_bg=True), + F("plate:backoffice", (640, 360), 4601, + "a cramped nightclub back office at night seen straight on, cheap laminate desk " + "surface across the lower half, dark panelled wall above, a wall calendar, " + "a taped-up roster, one tired ceiling bulb, NOTHING on the desk, empty desk", + gen=(1536, 864), quantise=False, keep_bg=True), + # The sorter is a UI hero at ~150px, not a 16px floor prop, so it keeps its + # full palette — quantised to 16 colours a grey machine turns to porridge. + Asset("sorter", "mesh", (128, 96), + "a battered desktop mechanical coin sorting machine, metal hopper on top, " + "five coin channels down the front, output tray at the base, worn enamel, " + f"{MESH_TAIL}", 4602, rake=58, quantise=False), +] + ALL: list[Asset] = [ *ROYAL, *ELEVATE, *ROOM, *TEXTURES, *WALLS, *STAFF, *FILLER, *STREETS, - *DOORSCENE, + *DOORSCENE, *BETWEEN, ] BY_KIND = {a.kind: a for a in ALL}