Lane C+B R35 (v7.0 close #2): THE FIRST FIVE MINUTES — arrive hunting, three beats walk the hustle
A FRESH game (day 1 · empty crate · empty auction house) seeds ONE artist want off
rng(citySeed,'firstwant',0) × the parody roster (dig.js ARTISTS exported) — the splash names
the mission before the first step ('You're hunting HILLS HOIST — $191 in your pocket'), and
the keeper rumors point at the same hunt from shop one. Three toast-beats ride EXISTING events
(zero new systems): ① find a record shop (on the go-click) → ② riffle · mind the guide · BUY
(first digOpen) → ③ sell at a counter or ⚖ LIST and sleep (first find, via wallet.onChange) →
the send-off on the first sale-or-listing (the listener removes itself). A returning save gets
its town back, not a tutorial (fresh-detection = state, no save flag — the delta law stays
clean); ?game=0 / ?classic=1 never reach the block. #pc-toast body-mounted (the #pc-game rule):
beats fire INSIDE shops, and a SLEEP taken at a counter now shows its morning paper.
This commit is contained in:
parent
c9e233a7e5
commit
55b984e0b0
@ -32,6 +32,7 @@
|
|||||||
<h1>PROCITY</h1>
|
<h1>PROCITY</h1>
|
||||||
<div class="sub">A procedurally generated, walkable 90s-Australian shopping town.
|
<div class="sub">A procedurally generated, walkable 90s-Australian shopping town.
|
||||||
Every door opens. Dig the crates, sell the flips, sleep — the town remembers.</div>
|
Every door opens. Dig the crates, sell the flips, sleep — the town remembers.</div>
|
||||||
|
<div class="sub" id="pc-hunt" style="color:#ffd75e;display:none"></div>
|
||||||
<button id="pc-go">walk into town</button>
|
<button id="pc-go">walk into town</button>
|
||||||
<div class="seed" id="pc-startseed"></div>
|
<div class="seed" id="pc-startseed"></div>
|
||||||
<div class="sub" style="font-size:12px;opacity:.7">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</div>
|
<div class="sub" style="font-size:12px;opacity:.7">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</div>
|
||||||
@ -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 { 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 { 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 { 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) => {
|
main().catch((err) => {
|
||||||
console.error('[procity] init failed:', err);
|
console.error('[procity] init failed:', err);
|
||||||
@ -292,6 +295,40 @@ if (game) {
|
|||||||
+ (bootNews.length ? ' · ' + bootNews.join(' · ') : ''), bootNews.length ? 7 : 4);
|
+ (bootNews.length ? ' · ' + bootNews.join(' · ') : ''), bootNews.length ? 7 : 4);
|
||||||
else if (bootNews.length) hud.showToast(bootNews.join(' · '), 6);
|
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()
|
// [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.
|
// time (door click), long after citizens is initialized — so the forward reference is safe.
|
||||||
const interiorMode = createInteriorMode({ THREE, renderer, camera, plan, fleet, useGLB: !NOASSETS,
|
const interiorMode = createInteriorMode({ THREE, renderer, camera, plan, fleet, useGLB: !NOASSETS,
|
||||||
|
|||||||
@ -17,7 +17,8 @@ const SLEEVE = [0.31, 0.31, 0.0060]; // w, h, thickness (packed a touc
|
|||||||
// 90s-Australian crate genres → sleeve card colour.
|
// 90s-Australian crate genres → sleeve card colour.
|
||||||
const GENRE = [['ROCK', '#7a2a3a'], ['SOUL', '#b0894a'], ['JAZZ', '#2a4a6a'], ['DISCO', '#8a3a7a'],
|
const GENRE = [['ROCK', '#7a2a3a'], ['SOUL', '#b0894a'], ['JAZZ', '#2a4a6a'], ['DISCO', '#8a3a7a'],
|
||||||
['COUNTRY', '#6a5a2a'], ['PUNK', '#3a3a3a'], ['REGGAE', '#2a6a4a'], ['POP', '#b05a8a']];
|
['COUNTRY', '#6a5a2a'], ['PUNK', '#3a3a3a'], ['REGGAE', '#2a6a4a'], ['POP', '#b05a8a']];
|
||||||
const ARTISTS = ['THE VERANDAHS', 'DEZ & THE UTES', 'MERINO', 'HILLS HOIST', 'THE ESKIES', 'GOON BAG',
|
// [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'];
|
'STOBIE POLES', 'THE FIBROS', 'WARATAH', 'THONGS', 'THE CLOTHESLINE', 'GALAH'];
|
||||||
const TITLES = ['Sunburnt', 'Back o’ Bourke', 'Servo at Midnight', 'Long Weekend', 'Tinny',
|
const TITLES = ['Sunburnt', 'Back o’ Bourke', 'Servo at Midnight', 'Long Weekend', 'Tinny',
|
||||||
'Southerly Buster', 'Milk Bar Dreams', 'Cane Toad Blues', 'Ute Muster', 'Arvo'];
|
'Southerly Buster', 'Milk Bar Dreams', 'Cane Toad Blues', 'Ute Muster', 'Arvo'];
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const CSS = `
|
|||||||
#pc-dbg b{color:#ffd75e;font-weight:600}
|
#pc-dbg b{color:#ffd75e;font-weight:600}
|
||||||
#pc-dbg .warn{color:#ff9a6a}
|
#pc-dbg .warn{color:#ff9a6a}
|
||||||
#pc-clock{font-size:15px;margin-bottom:3px}
|
#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-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{position:absolute;left:10px;top:10px;pointer-events:auto;opacity:.8;transition:opacity .12s}
|
||||||
#pc-townpick:hover{opacity:1}
|
#pc-townpick:hover{opacity:1}
|
||||||
@ -67,6 +67,9 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
const $ = (id) => root.querySelector('#' + id);
|
const $ = (id) => root.querySelector('#' + id);
|
||||||
const elTip = $('pc-tip'), elClock = $('pc-clock'), elFps = $('pc-fps'), elDraws = $('pc-draws'),
|
const elTip = $('pc-tip'), elClock = $('pc-clock'), elFps = $('pc-fps'), elDraws = $('pc-draws'),
|
||||||
elTris = $('pc-tris'), elChunks = $('pc-chunks'), elToast = $('pc-toast');
|
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-town').textContent = plan.name || '—';
|
||||||
$('pc-seed').textContent = plan.citySeed;
|
$('pc-seed').textContent = plan.citySeed;
|
||||||
|
|
||||||
@ -379,6 +382,7 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
renderer.domElement.removeEventListener('click', onClick);
|
renderer.domElement.removeEventListener('click', onClick);
|
||||||
if (onGameKey) removeEventListener('keydown', onGameKey);
|
if (onGameKey) removeEventListener('keydown', onGameKey);
|
||||||
if (gameUI && gameUI.wrap) gameUI.wrap.remove(); // [R32] the game surface lives on document.body now
|
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();
|
root.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user