diff --git a/web/index.html b/web/index.html index 1adad8e..4bb56a6 100644 --- a/web/index.html +++ b/web/index.html @@ -32,6 +32,7 @@

PROCITY

A procedurally generated, walkable 90s-Australian shopping town. Every door opens. Dig the crates, sell the flips, sleep — the town remembers.
+
WASD move · shift run · mouse look · click a door · E riffle a bin / sell at the counter · C your crate · SLEEP to save · [ ] time · M map
@@ -63,6 +64,8 @@ import { loadPedFleet } from './js/citizens/rigs.js'; // [Lane F §3 import { VenueQueue } from './js/citizens/queue.js'; // [Lane F R13] Lane D outdoor gig queue (?gigs=1) import { preloadManifest, preloadStockPack } from './js/interiors/interiors.js'; // [Lane F] Lane E manifest + Lane C/E stock pack import { createGame } from './js/world/save.js'; // [Lane F R30] v7 THE SAVE CORE (contract: LANE_F_NOTES §30) +import { ARTISTS } from './js/interiors/dig.js'; // [R35] the parody roster — a fresh game's seeded first want +import { rng, pick } from './js/core/prng.js'; // [R35] seeded first-want draw (runtime stream, fresh key) main().catch((err) => { console.error('[procity] init failed:', err); @@ -292,6 +295,40 @@ if (game) { + (bootNews.length ? ' · ' + bootNews.join(' · ') : ''), bootNews.length ? 7 : 4); else if (bootNews.length) hud.showToast(bootNews.join(' · '), 6); } + +// ── [R35 — THE FIRST FIVE MINUTES (charter #7)] ──────────────────────────────────────────────── +// A stranger arrives with cash and A WANT, and three toast-beats walk the whole hustle: dig → +// buy → sell-or-list. FRESH games only (day 1, empty crate, empty auction house) — a returning +// save gets its town back, not a tutorial. ?game=0 / ?classic=1 never reach this block. The +// first want is SEEDED (citySeed → the parody roster), so the splash line, the keeper rumors, +// and the tour all point at the same hunt on a given seed — deterministic, gate-checkable. +if (game && game.day === 1 && !game.collection.length && !game.listings.length) { + if (!game.wants.length) game.recordWant({ artist: pick(rng(plan.citySeed, 'firstwant', 0), ARTISTS) }); + const hunt = document.getElementById('pc-hunt'); + if (hunt && game.wants.length) { + hunt.textContent = `You're hunting ${game.wants[0].artist} — $${game.cash} in your pocket.`; + hunt.style.display = 'block'; + } + let stage = 1; + const go = document.getElementById('pc-go'); + if (go) go.addEventListener('click', () => + setTimeout(() => { if (stage === 1) hud.showToast('① find a record shop — walk in, press E at a crate', 7); }, 900), + { once: true }); + window.addEventListener('procity:digOpen', () => { + if (stage === 1) { stage = 2; hud.showToast('② riffle with the wheel · pull a sleeve · mind the guide — BUY', 7); } + }); + let n3 = 0; + const offBeats = game.wallet.onChange(() => { + if (stage <= 2 && game.collection.length) { + stage = 3; n3 = game.collection.length; + hud.showToast('③ yours now. Sell it at a counter (E) — or ⚖ LIST it in the crate and SLEEP on it', 8); + } else if (stage === 3 && (game.listings.length || game.collection.length < n3)) { + stage = 4; + hud.showToast('That’s the whole hustle. ☆ want things · keepers gossip · 🚌 towns cost a day. Good digging.', 9); + offBeats(); + } + }); +} // [Lane F R9] occupancyOf closure defers to `citizens` (declared just below); only invoked at enter() // time (door click), long after citizens is initialized — so the forward reference is safe. const interiorMode = createInteriorMode({ THREE, renderer, camera, plan, fleet, useGLB: !NOASSETS, diff --git a/web/js/interiors/dig.js b/web/js/interiors/dig.js index daff7cb..b02a248 100644 --- a/web/js/interiors/dig.js +++ b/web/js/interiors/dig.js @@ -17,8 +17,9 @@ const SLEEVE = [0.31, 0.31, 0.0060]; // w, h, thickness (packed a touc // 90s-Australian crate genres → sleeve card colour. const GENRE = [['ROCK', '#7a2a3a'], ['SOUL', '#b0894a'], ['JAZZ', '#2a4a6a'], ['DISCO', '#8a3a7a'], ['COUNTRY', '#6a5a2a'], ['PUNK', '#3a3a3a'], ['REGGAE', '#2a6a4a'], ['POP', '#b05a8a']]; -const ARTISTS = ['THE VERANDAHS', 'DEZ & THE UTES', 'MERINO', 'HILLS HOIST', 'THE ESKIES', 'GOON BAG', - 'STOBIE POLES', 'THE FIBROS', 'WARATAH', 'THONGS', 'THE CLOTHESLINE', 'GALAH']; +// [R35] exported: the shell seeds a fresh game's FIRST WANT from this roster (THE FIRST FIVE MINUTES). +export const ARTISTS = ['THE VERANDAHS', 'DEZ & THE UTES', 'MERINO', 'HILLS HOIST', 'THE ESKIES', 'GOON BAG', + 'STOBIE POLES', 'THE FIBROS', 'WARATAH', 'THONGS', 'THE CLOTHESLINE', 'GALAH']; const TITLES = ['Sunburnt', 'Back o’ Bourke', 'Servo at Midnight', 'Long Weekend', 'Tinny', 'Southerly Buster', 'Milk Bar Dreams', 'Cane Toad Blues', 'Ute Muster', 'Arvo']; const CONDS = ['VG+', 'VG', 'G+', 'NM', 'EX']; diff --git a/web/js/world/hud.js b/web/js/world/hud.js index 9b7879c..678d3ce 100644 --- a/web/js/world/hud.js +++ b/web/js/world/hud.js @@ -21,7 +21,7 @@ const CSS = ` #pc-dbg b{color:#ffd75e;font-weight:600} #pc-dbg .warn{color:#ff9a6a} #pc-clock{font-size:15px;margin-bottom:3px} -#pc-toast{position:absolute;left:50%;bottom:64px;transform:translateX(-50%);background:rgba(20,16,10,.9);padding:10px 18px;border-radius:10px;font-size:15px;opacity:0;transition:opacity .25s;text-shadow:0 1px 2px #000} +#pc-toast{position:fixed;left:50%;bottom:64px;transform:translateX(-50%);background:rgba(20,16,10,.9);padding:10px 18px;border-radius:10px;font-size:15px;opacity:0;transition:opacity .25s;text-shadow:0 1px 2px #000;z-index:31;max-width:80vw;font:15px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;color:#f4efe6;pointer-events:none} #pc-help{position:absolute;left:12px;bottom:12px;background:rgba(20,16,10,.5);padding:7px 11px;border-radius:8px;opacity:.85} #pc-townpick{position:absolute;left:10px;top:10px;pointer-events:auto;opacity:.8;transition:opacity .12s} #pc-townpick:hover{opacity:1} @@ -67,6 +67,9 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop } const $ = (id) => root.querySelector('#' + id); const elTip = $('pc-tip'), elClock = $('pc-clock'), elFps = $('pc-fps'), elDraws = $('pc-draws'), elTris = $('pc-tris'), elChunks = $('pc-chunks'), elToast = $('pc-toast'); + // [R35] toasts surface in EVERY mode (beats fire inside shops; a SLEEP taken at a counter must + // still show its morning paper) — same body-mount rule as #pc-game, same dispose duty. + document.body.appendChild(elToast); $('pc-town').textContent = plan.name || '—'; $('pc-seed').textContent = plan.citySeed; @@ -379,6 +382,7 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop } renderer.domElement.removeEventListener('click', onClick); if (onGameKey) removeEventListener('keydown', onGameKey); if (gameUI && gameUI.wrap) gameUI.wrap.remove(); // [R32] the game surface lives on document.body now + elToast.remove(); // [R35] so does the toast root.remove(); }