Load the game once, not on every launch
The cold-start complaints — late fonts, zombies missing from the stats screen — were one header. server.py sent no-cache, must-revalidate for everything, so a fresh launch revalidated ~230 sprites and three fonts before the game had a full set of frames. Assets under /assets now cache for a year when the URL carries ?v= (changing the file changes the URL, which is what SPRV is for) and a day when it does not; the fonts get a ?v= too. HTML and JS stay no-cache, so a deploy is still visible. Two client-side halves of the same report: a dropped sprite request used to be permanent for the session — the sheet stayed undefined and the zombie drew as some other design — so each one gets one retry. And the splash now holds its buttons back behind a loading bar until the last sheet has landed and both faces are ready, instead of letting you open the stats screen while half of it is still in flight. The bar is driven by setInterval, not rAF: rAF does not fire in a hidden tab, and an app that loads while backgrounded would come back to a frozen bar and a menu with no buttons on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a1a8a61422
commit
c5a9746bc1
4 files changed
+80
-13
No files matched your search
@@ -6,7 +6,7 @@
|
||||
import { TUNE, COURT, COURTS, TOURS, UMP_H, UMP_X, EATEN_RESTART } from './config.js';
|
||||
import { P, O, ball, score, G, setupPoint, selectCourt, selectTour } from './state.js';
|
||||
import { update, restartRun, keyPressed, solveShot, inServeBox, inCourt, forceZombies, splatPoint, deliverTin, selectWeapon, grantWeapon, showModal, dismissModal, startDuel, resolveInterlude, flickTarget, startBossFight, endBossFight , toggleTestMode } from './gameplay.js';
|
||||
import { fitViewport, render, proj, RES, SPRV } from './render.js';
|
||||
import { fitViewport, render, proj, RES, SPRV, LOAD } from './render.js';
|
||||
import { voice, ambient, menuMusic, menuDuck } from './audio.js';
|
||||
import { zombies, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, piles, gore, parts, pellets, extras, shots, explode, hurtPlayer, killPlayer , spawnBarrel } from './zombies.js';
|
||||
import { cv } from './dom.js';
|
||||
@@ -183,6 +183,29 @@ document.getElementById('stTabs').addEventListener('click', e => {
|
||||
});
|
||||
refreshSplash();
|
||||
|
||||
// THE BOOT GATE. render.js fires all ~230 sprite requests at import; this only watches them and
|
||||
// keeps the splash's buttons out of reach until the last one has landed and both faces are ready.
|
||||
// Without it a cold start could open the stats screen — which hangs <img> tags on the same files —
|
||||
// or start a rally while half the sheets were still in flight, and the missing ones drew as
|
||||
// whatever the fallback was. onerror counts too (render.js), so a dead network still finishes the
|
||||
// bar rather than parking the player on it forever.
|
||||
const splash = document.getElementById('splash'), bootFill = document.querySelector('#boot i');
|
||||
splash.classList.add('busy');
|
||||
const loaded = () => !splash.classList.contains('busy');
|
||||
// setInterval, NOT requestAnimationFrame: rAF does not fire in a hidden tab, and a phone that
|
||||
// launches the app and locks — or an install that loads in the background — would come back to a
|
||||
// bar frozen at whatever it reached and a menu with no buttons on it. An interval still ticks
|
||||
// (throttled to about a second), so the gate always opens.
|
||||
const tick = setInterval(function watchLoad() {
|
||||
const frac = LOAD.total ? LOAD.done / LOAD.total : 1;
|
||||
bootFill.style.width = (frac * 100).toFixed(1) + '%';
|
||||
if (frac < 1) return;
|
||||
clearInterval(tick);
|
||||
// fonts.ready resolves once nothing is pending — including the three faces warmed above — so the
|
||||
// first canvas frame is never the system serif.
|
||||
(document.fonts ? document.fonts.ready : Promise.resolve()).then(() => splash.classList.remove('busy'));
|
||||
}, 100);
|
||||
|
||||
// THE FIRST TAP ANYWHERE ON THE OVERLAY STARTS THE THEME, and does nothing else — the screens have
|
||||
// real buttons now. This is the gesture the autoplay policy is waiting for, which is why the theme
|
||||
// is not started at boot. menuMusic itself is a no-op once the bed exists (loopBed's beds.has
|
||||
@@ -203,6 +226,7 @@ addEventListener('keydown', e => {
|
||||
const stage = intro.dataset.stage;
|
||||
if (e.key === 'Escape') return go('splash');
|
||||
if (stage === 'splash') {
|
||||
if (!loaded()) return; // the boot bar hides the buttons; a key must not walk past it
|
||||
if (e.key === 'Enter' || e.key === ' ') go('setup');
|
||||
else if (e.key === 's' || e.key === 'S') openStats();
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user