Compare commits
2 Commits
c9e233a7e5
...
25cef5983e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25cef5983e | ||
|
|
55b984e0b0 |
@ -4,6 +4,19 @@
|
||||
|
||||
---
|
||||
|
||||
## Round 35 (v7.0 close, wave 2) — THE FIRST FIVE MINUTES (Fable, solo, JING5)
|
||||
|
||||
A fresh game arrives HUNTING: one seeded artist want (`rng(citySeed,'firstwant',0)` × the
|
||||
parody roster) named on the splash with the pocket money ("You're hunting HILLS HOIST — $191
|
||||
in your pocket"), pointing the rumors and the tour at the same target. Three toast-beats walk
|
||||
the hustle off existing events (walk-in → digOpen → first find → first sale-or-listing, then
|
||||
the listener removes itself). Returning saves get no tutorial; ?game=0/classic never reach it.
|
||||
#pc-toast body-mounted so beats and morning papers surface inside shops too. STRICT
|
||||
`smoke_first_five` green (roster want + splash · real-path beats · returning/?game=0 controls
|
||||
· cross-browser seed determinism). Full record: LANE_F_NOTES §35.
|
||||
|
||||
---
|
||||
|
||||
## Round 34 (v7.0 close, wave 1) — GODBAY: the offline auction (Fable, solo, JING5)
|
||||
|
||||
⚖ LIST from the crate (mail-in consignment) → `listings` on procity-save/1 → resolves the day
|
||||
|
||||
@ -1870,3 +1870,21 @@ m3ultra. If a third machine joins the rotation, sync that dir (49 M) or re-run E
|
||||
"⚖ SOLD Cane Toad Blues — hammer $22, $19 after fees" · the ride to melbourne hammered a
|
||||
listing on arrival · ?game=0 has no crate to list from · 0 console errors.
|
||||
- G's later LIVE tier must match these laws (the ladder rule: the offline tier is the contract).
|
||||
|
||||
## §35 — R35: THE FIRST FIVE MINUTES (Fable, solo, JING5)
|
||||
|
||||
Fresh-game detection = day 1 ∧ empty collection ∧ empty listings (no flag in the save — the
|
||||
delta law stays clean; a returning empty-crate player re-seeing beats is onboarding working,
|
||||
not a bug). Seeded first want: `rng(citySeed,'firstwant',0)` × dig.js ARTISTS (exported R35);
|
||||
guarded on empty wants so the persisted want isn't re-added. Splash gains #pc-hunt (fresh+game
|
||||
only). Beats: ① on the go-click (900ms), ② on first `procity:digOpen`, ③ on first find via
|
||||
wallet.onChange, send-off on first sale-or-listing (listener self-removes). #pc-toast body-
|
||||
mounted (z 31) — visible in interiors/map; hud.dispose removes it. Gate `smoke_first_five`
|
||||
STRICT (see ROUND35_INSTRUCTIONS). All v7.0 SYSTEMS are now built — tour + freeze + tag remain,
|
||||
tag gated on John's playtest.
|
||||
- **Cross-round amendment the suite caught (the reason full runs exist):** R35's seeded first
|
||||
want changed the FRESH-BOOT CONTRACT (wants starts at 1, not 0) and the R33 gate's three
|
||||
fresh-boot assumptions went red on the full run — green solo, red together. Amended to a
|
||||
baseline snapshot (base = wants at boot; card adds base+1; the rumor may name ANY live want;
|
||||
buying clears ITS want back to base). A feature that moves a boot invariant must sweep every
|
||||
gate that assumed it — grep the harness for the invariant before calling a round closed.
|
||||
|
||||
36
docs/LANES/ROUND35_INSTRUCTIONS.md
Normal file
36
docs/LANES/ROUND35_INSTRUCTIONS.md
Normal file
@ -0,0 +1,36 @@
|
||||
# PROCITY — Round 35 instructions (from Fable, integrator)
|
||||
|
||||
Date: 2026-07-20 · **R34 closed all green** (GODBAY hammers). Round 35 is **THE FIRST FIVE
|
||||
MINUTES** ([V7_THE_GAME.md](../V7_THE_GAME.md) system #7 — "smallest system, biggest
|
||||
stranger-facing payoff"), the last unbuilt v7.0 system before the tour/freeze/tag.
|
||||
|
||||
## What ships
|
||||
|
||||
1. **You arrive with a want.** A FRESH game (day 1, empty crate, empty auction house) seeds ONE
|
||||
artist want off `rng(citySeed,'firstwant',0)` × C's parody roster (ARTISTS now exported) —
|
||||
so the splash, the keeper rumors, and the tour all point at the same hunt on a given seed.
|
||||
The splash names it: "You're hunting HILLS HOIST — $191 in your pocket." A returning save
|
||||
gets its town back, not a tutorial; ?game=0 / classic never reach the block.
|
||||
2. **Three toast-beats walk the hustle** (fresh games only): ① find a record shop / E at a
|
||||
crate (on walk-in) → ② riffle · mind the guide · BUY (first digOpen) → ③ sell at a counter
|
||||
or ⚖ LIST and sleep (first find banked) → the send-off ("that's the whole hustle") on the
|
||||
first sale-or-listing. Driven off existing events + wallet.onChange — zero new systems.
|
||||
3. **Toasts surface in every mode** — #pc-toast moved to document.body (the #pc-game body-mount
|
||||
rule): beats fire INSIDE shops, and a SLEEP taken at a counter now shows its morning paper.
|
||||
|
||||
## Gate (F): `smoke_first_five` (STRICT)
|
||||
|
||||
Fresh game: 1 seeded want from the roster + the splash names hunt AND cash · beats ②/③/send-off
|
||||
through the REAL paths (E at crate / dig buy / crate ⚖) · a returning save gets NO tutorial
|
||||
(splash line hidden, no ② on dig) · a second fresh browser hunts the SAME artist (seeded, not
|
||||
random) · ?game=0 shows nothing · 0 console errors.
|
||||
|
||||
## ROUND CLOSED (Fable) — ALL GREEN
|
||||
|
||||
`qa.sh --strict --matrix`: 7 passed · 0 failed · 0 warn · 0 skipped, MATRIX 10 towns green
|
||||
(final tree). Gate run: hunts HILLS HOIST with $191, beats walked through the real dig/buy/list
|
||||
paths, returning-save and ?game=0 controls clean, second fresh browser hunts the same artist,
|
||||
0 console errors. Money shot: `docs/shots/v7_beta/first_five_splash.png`.
|
||||
|
||||
**v7.0 remaining:** the tour ("day one to first gem") · docs freeze · the tag — and the tag
|
||||
waits for John's playtest (the alpha precedent: hands before tags).
|
||||
BIN
docs/shots/v7_beta/first_five_splash.png
Normal file
BIN
docs/shots/v7_beta/first_five_splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 378 KiB |
@ -2158,6 +2158,9 @@ def smoke_wantlist_travel(p):
|
||||
c.dispatchEvent(new PointerEvent('pointerdown', { clientX: 640, clientY: 360, bubbles: true }));
|
||||
c.dispatchEvent(new PointerEvent('pointerup', { clientX: 640, clientY: 360, bubbles: true })); }""")
|
||||
pg.wait_for_timeout(300)
|
||||
# [R35 amendment] a FRESH game already hunts ONE seeded want (the first-five system) — this
|
||||
# gate's fresh-boot baseline is that want, and the card adds a SECOND on top of it.
|
||||
base = pg.evaluate("() => window.PROCITY.game.wants.length")
|
||||
rw = pg.evaluate("""() => {
|
||||
const w = document.querySelector('.pcdg-panel .want');
|
||||
if (!w) return { err: 'no WANT line on the card' };
|
||||
@ -2165,11 +2168,14 @@ def smoke_wantlist_travel(p):
|
||||
const g = window.PROCITY.game, ex = JSON.parse(g.export());
|
||||
return { before, after: w.textContent, wants: ex.wants || [], live: g.wants.length }; }""")
|
||||
if rw.get('err'): FAIL(f"wantlist: {rw['err']}"); return
|
||||
if 'WANT IT' in rw['before'] and 'ON THE WANTLIST' in rw['after'] and len(rw['wants']) == 1 and rw['live'] == 1:
|
||||
OK(f"wantlist: ☆→★ through the real card — the save carries {rw['wants'][0]}")
|
||||
if 'WANT IT' in rw['before'] and 'ON THE WANTLIST' in rw['after'] and len(rw['wants']) == base + 1 and rw['live'] == base + 1:
|
||||
OK(f"wantlist: ☆→★ through the real card — the save carries {len(rw['wants'])} want(s) "
|
||||
f"(the seeded hunt + {rw['wants'][-1]})")
|
||||
else:
|
||||
FAIL(f"wantlist: want did not take — {rw}")
|
||||
artist = (rw['wants'][0].get('artist') or rw['wants'][0].get('title') or '').upper()
|
||||
FAIL(f"wantlist: want did not take — base {base}, {rw}")
|
||||
# the rumor may pick ANY current want (seeded index over the live list)
|
||||
artists = [str(w.get('artist') or w.get('title') or '').upper() for w in rw['wants']]
|
||||
artist = artists[-1]
|
||||
pg.evaluate("""() => { const p = document.querySelector('.pcdg-panel');
|
||||
const x = p && p.querySelector('.x'); if (x) x.click();
|
||||
const o = document.querySelector('.pcdg-out'); if (o) o.click(); }""")
|
||||
@ -2190,20 +2196,21 @@ def smoke_wantlist_travel(p):
|
||||
seen.push(s.id);
|
||||
}
|
||||
return { rumor: null, tried: seen.length }; }""")
|
||||
if rr.get('rumor') and artist and artist in rr['rumor']:
|
||||
if rr.get('rumor') and any(a and a in rr['rumor'] for a in artists):
|
||||
OK(f"rumor: a keeper muttered after {rr['tried']} shop(s) — {rr['rumor'][:80]}")
|
||||
else:
|
||||
FAIL(f"rumor: no keeper named {artist!r} in {rr.get('tried')} open shops — {rr}")
|
||||
FAIL(f"rumor: no keeper named any of {artists} in {rr.get('tried')} open shops — {rr}")
|
||||
# (c) buy the wanted sleeve → the want clears itself
|
||||
r1 = _enter_record_shop(pg)
|
||||
if r1.get('err'): FAIL(f"wantlist/buy: {r1['err']}"); return
|
||||
_at_bin(pg)
|
||||
rb = _dig_buy(pg)
|
||||
rc = pg.evaluate("() => ({ wants: window.PROCITY.game.wants.length, n: window.PROCITY.game.collection.length })")
|
||||
if not rb.get('err') and not rb.get('disabled') and rc['wants'] == 0 and rc['n'] >= 1:
|
||||
OK('wantlist: bought the hunted sleeve — the want cleared itself, the find is in the crate')
|
||||
if not rb.get('err') and not rb.get('disabled') and rc['wants'] == base and rc['n'] >= 1:
|
||||
OK('wantlist: bought the hunted sleeve — ITS want cleared itself (the seeded hunt stays), '
|
||||
'the find is in the crate')
|
||||
else:
|
||||
FAIL(f"wantlist/buy: {rb} · after {rc}")
|
||||
FAIL(f"wantlist/buy: {rb} · after {rc} (want base {base})")
|
||||
base = pg.evaluate("() => ({ day: window.PROCITY.game.day, cash: window.PROCITY.game.cash, n: window.PROCITY.game.collection.length })")
|
||||
# (d) travel: another town on the SAME save — the ride costs exactly one day
|
||||
boot(pg, 'plansrc=osm&town=melbourne')
|
||||
@ -2357,6 +2364,89 @@ def smoke_godbay(p):
|
||||
b.close()
|
||||
|
||||
|
||||
def smoke_first_five(p):
|
||||
"""R35 gate — THE FIRST FIVE MINUTES (charter #7). Legs: (a) a FRESH game arrives with a seeded
|
||||
first want (parody roster) and the splash names the hunt + the pocket money; (b) the want is
|
||||
the SAME artist in a second fresh browser (seeded, not random); (c) the beats walk the hustle:
|
||||
E at a crate → '②', a buy → '③', a ⚖ LIST → the send-off; (d) a RETURNING save gets no
|
||||
tutorial (splash line hidden, no '②' on dig); (e) ?game=0 shows no hunt line at all."""
|
||||
head('GATE R35: THE FIRST FIVE MINUTES — seeded want · splash · beats (+ returning/?game=0 controls)')
|
||||
ROSTER = {'THE VERANDAHS', 'DEZ & THE UTES', 'MERINO', 'HILLS HOIST', 'THE ESKIES', 'GOON BAG',
|
||||
'STOBIE POLES', 'THE FIBROS', 'WARATAH', 'THONGS', 'THE CLOTHESLINE', 'GALAH'}
|
||||
b, pg, errs = new_page(p)
|
||||
try:
|
||||
boot(pg, '')
|
||||
fa = pg.evaluate("""() => { const g = window.PROCITY.game, h = document.getElementById('pc-hunt');
|
||||
return { wants: g.wants.map(w => w.artist), cash: g.cash,
|
||||
hunt: h ? { shown: h.style.display !== 'none', text: h.textContent } : null }; }""")
|
||||
if (len(fa['wants']) == 1 and fa['wants'][0] in ROSTER and fa['hunt'] and fa['hunt']['shown']
|
||||
and fa['wants'][0] in fa['hunt']['text'] and f"${fa['cash']}" in fa['hunt']['text']):
|
||||
OK(f"first-five: fresh game hunts {fa['wants'][0]} with ${fa['cash']} — the splash says so")
|
||||
else:
|
||||
FAIL(f"first-five: seeded want/splash wrong — {fa}")
|
||||
# (c) the beats, through the real paths
|
||||
r0 = _enter_record_shop(pg)
|
||||
if r0.get('err'): FAIL(f"first-five: {r0['err']}"); return
|
||||
pg.evaluate("""() => { const g = window.PROCITY.game;
|
||||
const ex = JSON.parse(g.export()); ex.cash = 5000; ex.savedAt = Date.now(); g.import(JSON.stringify(ex)); }""")
|
||||
pg.evaluate("() => window.DBG.setSegment(2)")
|
||||
_at_bin(pg)
|
||||
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||
pg.wait_for_timeout(350)
|
||||
t2 = pg.evaluate("() => (document.getElementById('pc-toast') || {}).textContent || ''")
|
||||
pg.evaluate("() => { const o = document.querySelector('.pcdg-out'); if (o) o.click(); }")
|
||||
pg.wait_for_timeout(200)
|
||||
_at_bin(pg)
|
||||
rb = _dig_buy(pg)
|
||||
t3 = pg.evaluate("() => (document.getElementById('pc-toast') || {}).textContent || ''")
|
||||
rl = pg.evaluate("""() => {
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyC' }));
|
||||
const lb = document.querySelector('#pc-crate .pc-crate-row div[title*="GodBay"]');
|
||||
if (lb) lb.click();
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyC' }));
|
||||
return (document.getElementById('pc-toast') || {}).textContent || ''; }""")
|
||||
if '②' in t2 and '③' in t3 and 'whole hustle' in rl:
|
||||
OK('first-five: the beats walked the hustle — ② at the crate, ③ on the buy, the send-off on the ⚖')
|
||||
else:
|
||||
FAIL(f"first-five: beats wrong — ②:{('②' in t2)} ③:{('③' in t3)} send-off:{('whole hustle' in rl)}")
|
||||
# (d) a returning save gets no tutorial
|
||||
boot(pg, '')
|
||||
rd = pg.evaluate("""() => { const h = document.getElementById('pc-hunt');
|
||||
return { shown: h && h.style.display !== 'none', n: window.PROCITY.game.collection.length,
|
||||
nl: window.PROCITY.game.listings.length }; }""")
|
||||
r1 = _enter_record_shop(pg)
|
||||
if r1.get('err'): FAIL(f"first-five/return: {r1['err']}")
|
||||
else:
|
||||
_at_bin(pg)
|
||||
pg.evaluate("() => window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' }))")
|
||||
pg.wait_for_timeout(350)
|
||||
tr = pg.evaluate("""() => { const t = document.getElementById('pc-toast');
|
||||
return { text: t ? t.textContent : '', vis: t ? t.style.opacity : '0' }; }""")
|
||||
if not rd['shown'] and not ('②' in tr['text'] and tr['vis'] == '1'):
|
||||
OK(f"first-five/return: a save with {rd['n']} find(s)/{rd['nl']} listing(s) gets no tutorial")
|
||||
else:
|
||||
FAIL(f"first-five/return: tutorial leaked to a returning save — {rd} {tr}")
|
||||
# (b) determinism: a second FRESH browser hunts the same artist
|
||||
b2, pg2, _ = new_page(p)
|
||||
try:
|
||||
boot(pg2, '')
|
||||
w2 = pg2.evaluate("() => window.PROCITY.game.wants.map(w => w.artist)")
|
||||
if w2 == fa['wants']: OK(f"first-five: a second fresh browser hunts the same {w2[0]} — seeded, not random")
|
||||
else: FAIL(f"first-five: fresh want differs across browsers — {fa['wants']} vs {w2}")
|
||||
finally:
|
||||
b2.close()
|
||||
# (e) ?game=0 — no hunt line
|
||||
boot(pg, 'game=0')
|
||||
g0 = pg.evaluate("""() => { const h = document.getElementById('pc-hunt');
|
||||
return h ? h.style.display !== 'none' : false; }""")
|
||||
if not g0: OK('first-five/control: ?game=0 — no hunt, no tutorial')
|
||||
else: FAIL('first-five/control: game=0 shows the hunt line')
|
||||
if errs: FAIL(f"first-five: {len(errs)} console error(s); first: {errs[0][:140]}")
|
||||
else: OK('first-five: 0 console errors')
|
||||
finally:
|
||||
b.close()
|
||||
|
||||
|
||||
def smoke_classic_purity(p):
|
||||
"""R30 gate (c) — ?classic=1 and ?game=0: ZERO Storage-prototype calls (instrumented before any
|
||||
page script), zero game DOM (B's ids), game null. Default boot is the POSITIVE control (the
|
||||
@ -2513,6 +2603,7 @@ def main():
|
||||
smoke_bible(p) # R32 — THE BIBLE (bands · gems · ledger) + per-day scarcity + ?game=0 control
|
||||
smoke_wantlist_travel(p) # R33 — wantlist · keeper rumor · travel-costs-a-day (+ controls)
|
||||
smoke_godbay(p) # R34 — GODBAY offline auction (one hammer · bounded · morning paper)
|
||||
smoke_first_five(p) # R35 — the first five minutes (seeded want · splash · beats + controls)
|
||||
smoke_tier2(p) # R27 — the kill-the-server gate (charter law #1)
|
||||
smoke_real_crate(p) # R24 — THE REAL CRATE (per-shop atlas identity · soft fall · no-op off the path)
|
||||
finally:
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
<h1>PROCITY</h1>
|
||||
<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>
|
||||
<div class="sub" id="pc-hunt" style="color:#ffd75e;display:none"></div>
|
||||
<button id="pc-go">walk into town</button>
|
||||
<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>
|
||||
@ -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,
|
||||
|
||||
@ -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'];
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user