Lane B R33: THE WANTLIST in the crate panel · the arrival toast · the build-race fix the shot found
hud.js: THE WANTLIST section in the crate (non-empty only — no header over nothing): hunted
rows, ✕ stops hunting, refresh watches wants length so an open panel follows adds/clears.
THE FIX: R32's game surface BUILT only in street update() frames — a door entered fast after
boot had no crate/bar inside at all, and the interior tickGame() refreshed a UI that never
existed. Every gate idles on the street long enough to build, so green gates missed it; the
R33 money-shot script entered faster and exposed it. tickGame() now builds-or-refreshes in
ANY mode (shots are a detector, not decoration — filed in LANE_F_NOTES §33).
index.html: game.traveled at boot → '🚌 <town> — the ride cost a day. Day N' toast (save.js
already charged the fare and re-keyed the week before the shell read game.day).
This commit is contained in:
parent
37773bc81d
commit
7b26e45fa0
@ -281,6 +281,9 @@ const gigState = (GIGS_ON && plan.gigs && plan.gigs.length) ? createGigState({ p
|
|||||||
// day-1 stock streams (§30.3). Skipped entirely when either layer is off.
|
// day-1 stock streams (§30.3). Skipped entirely when either layer is off.
|
||||||
const weekNightOf = (day) => (Math.max(1, day) - 1) % 7;
|
const weekNightOf = (day) => (Math.max(1, day) - 1) % 7;
|
||||||
if (game && gigState) gigState.setWeekNight(weekNightOf(game.day));
|
if (game && gigState) gigState.setWeekNight(weekNightOf(game.day));
|
||||||
|
// [R33 — TRAVEL COSTS A DAY] this boot adopted a save from another town: the ride ate a day (save.js
|
||||||
|
// adopt() did the increment + pull-pruning; weekNight above already read the incremented day). Tell John.
|
||||||
|
if (game && game.traveled) hud.showToast(`🚌 ${plan.name || 'a new town'} — the ride cost a day. Day ${game.day}`, 4);
|
||||||
// [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,
|
||||||
|
|||||||
@ -173,7 +173,7 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
wrap.append(panel, bar); document.body.appendChild(wrap);
|
wrap.append(panel, bar); document.body.appendChild(wrap);
|
||||||
const elDay = bar.querySelector('#pc-day'), elCash = bar.querySelector('#pc-cash'),
|
const elDay = bar.querySelector('#pc-day'), elCash = bar.querySelector('#pc-cash'),
|
||||||
btnCrate = bar.querySelector('#pc-crate-btn'), btnSleep = bar.querySelector('#pc-sleep-btn');
|
btnCrate = bar.querySelector('#pc-crate-btn'), btnSleep = bar.querySelector('#pc-sleep-btn');
|
||||||
let shownDay = null, shownCash = null, shownCount = null, open = false;
|
let shownDay = null, shownCash = null, shownCount = null, shownWants = null, open = false;
|
||||||
|
|
||||||
function rebuildList() {
|
function rebuildList() {
|
||||||
const col = game.collection || [];
|
const col = game.collection || [];
|
||||||
@ -184,7 +184,7 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
panel.appendChild(h);
|
panel.appendChild(h);
|
||||||
if (!col.length) {
|
if (!col.length) {
|
||||||
const d = document.createElement('div'); d.className = 'pc-crate-empty';
|
const d = document.createElement('div'); d.className = 'pc-crate-empty';
|
||||||
d.textContent = 'Empty — go dig.'; panel.appendChild(d); return;
|
d.textContent = 'Empty — go dig.'; panel.appendChild(d);
|
||||||
}
|
}
|
||||||
for (let i = col.length - 1; i >= 0; i--) { // newest find first
|
for (let i = col.length - 1; i >= 0; i--) { // newest find first
|
||||||
const e = col[i];
|
const e = col[i];
|
||||||
@ -202,6 +202,29 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
main.append(t, a, meta);
|
main.append(t, a, meta);
|
||||||
row.append(th, main); panel.appendChild(row);
|
row.append(th, main); panel.appendChild(row);
|
||||||
}
|
}
|
||||||
|
// [R33] THE WANTLIST — what you're hunting (☆ on a pull card adds; ✕ here removes; buying one
|
||||||
|
// clears it automatically). Section exists only when non-empty — no stray header over nothing.
|
||||||
|
const wants = game.wants || [];
|
||||||
|
if (wants.length) {
|
||||||
|
const wh = document.createElement('h3');
|
||||||
|
wh.style.marginTop = '10px';
|
||||||
|
wh.textContent = `THE WANTLIST — ${wants.length} hunted`;
|
||||||
|
panel.appendChild(wh);
|
||||||
|
for (const w of wants) {
|
||||||
|
const row = document.createElement('div'); row.className = 'pc-crate-row';
|
||||||
|
const main = document.createElement('div'); main.className = 'pc-crate-main';
|
||||||
|
const t = document.createElement('div'); t.className = 'pc-crate-title';
|
||||||
|
t.textContent = w.artist || w.title || w.sku;
|
||||||
|
const a = document.createElement('div'); a.className = 'pc-crate-artist';
|
||||||
|
a.textContent = w.artist && w.title ? `“${w.title}”` : '';
|
||||||
|
main.append(t, a);
|
||||||
|
const x = document.createElement('div');
|
||||||
|
x.textContent = '✕'; x.title = 'stop hunting this';
|
||||||
|
x.style.cssText = 'cursor:pointer;opacity:.6;padding:0 4px;flex:none';
|
||||||
|
x.onclick = () => { game.removeWant(w); rebuildList(); };
|
||||||
|
row.append(main, x); panel.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function setOpen(v) {
|
function setOpen(v) {
|
||||||
open = v; panel.style.display = v ? 'block' : 'none';
|
open = v; panel.style.display = v ? 'block' : 'none';
|
||||||
@ -222,6 +245,8 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
shownCount = col.length; btnCrate.textContent = `CRATE ${shownCount}`;
|
shownCount = col.length; btnCrate.textContent = `CRATE ${shownCount}`;
|
||||||
if (open) rebuildList();
|
if (open) rebuildList();
|
||||||
}
|
}
|
||||||
|
const nw = (game.wants || []).length; // [R33] want add/clear refreshes an open panel too
|
||||||
|
if (nw !== shownWants) { shownWants = nw; if (open) rebuildList(); }
|
||||||
// [R32] the surface yields to the map and to the dig/sell cards (they own the screen), and shows
|
// [R32] the surface yields to the map and to the dig/sell cards (they own the screen), and shows
|
||||||
// everywhere else — street AND interior. Cheap: a style write only when the answer changes.
|
// everywhere else — street AND interior. Cheap: a style write only when the answer changes.
|
||||||
const P = window.PROCITY, im = P && P.interiorMode;
|
const P = window.PROCITY, im = P && P.interiorMode;
|
||||||
@ -281,14 +306,11 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
frame++;
|
frame++;
|
||||||
if (frame % 6 === 0) raycastDoor();
|
if (frame % 6 === 0) raycastDoor();
|
||||||
|
|
||||||
// v7 game surface — lazy construction (the shell builds the game after the HUD; null under
|
// v7 game surface — lazy construction + refresh both live in tickGame() (R33 fix: the build used
|
||||||
// ?classic=1 / ?game=0 ⇒ this never fires), then cheap getter polls offset from the raycast frames.
|
// to happen ONLY here in street frames, so a door entered fast after boot had no crate/bar inside
|
||||||
if (!gameUI) {
|
// at all — the interior branch could refresh a surface that never got built). Throttled here; the
|
||||||
if (frame % 30 === 0) {
|
// interior/map branches call tickGame() per frame (cheap: change-guarded property reads).
|
||||||
const g = window.PROCITY && window.PROCITY.game;
|
if (frame % 6 === 3) tickGame();
|
||||||
if (g) gameUI = buildGameUI(g);
|
|
||||||
}
|
|
||||||
} else if (frame % 6 === 3) gameUI.refresh();
|
|
||||||
|
|
||||||
// tooltip — shows CLOSED (with opening hour) when the shop is shut at the current hour (§3.5)
|
// tooltip — shows CLOSED (with opening hour) when the shop is shut at the current hour (§3.5)
|
||||||
if (hovered) {
|
if (hovered) {
|
||||||
@ -333,9 +355,17 @@ export function createHUD({ camera, renderer, plan, getDoorMeshes, onEnterShop }
|
|||||||
root.remove();
|
root.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// [R32] the game surface ticks in EVERY mode (the street path reaches it via update(); interior/map
|
// [R32/R33] the game surface ticks in EVERY mode (the street path reaches it via update(); interior/
|
||||||
// call this directly) — day/cash/crate stay live while you shop, and the card-yield check runs there.
|
// map call this directly) — and BUILDS here too, so the surface exists no matter which mode first
|
||||||
function tickGame() { if (gameUI) gameUI.refresh(); }
|
// sees a truthy game. day/cash/crate stay live while you shop; the card-yield check runs here.
|
||||||
|
function tickGame() {
|
||||||
|
if (!gameUI) {
|
||||||
|
const g = window.PROCITY && window.PROCITY.game;
|
||||||
|
if (g) gameUI = buildGameUI(g);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gameUI.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
return { update, tickToast, showToast, setVisible, tickGame, dispose, getHovered: () => hovered,
|
return { update, tickToast, showToast, setVisible, tickGame, dispose, getHovered: () => hovered,
|
||||||
getFps: () => fpsShown };
|
getFps: () => fpsShown };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user