quest'n'click 4.5.0 · r1

Built for quest'n'click 4.5.0, revision r1. Whether it is published, and which one is current, is recorded at publication.json.

Sub-path, cache and service worker, incomplete deploys, going back

Français · Type: explanation + troubleshooting · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.2.5

A game that plays well on npm run dev can still break once online: served under /<repo>/ instead of /, kept old by a service worker, half uploaded, or rolled back over newer saves. This page explains how the build handles each, and how to tell which one you are looking at.

Sub-path: BASE_PATH

A site at the root of its domain (https://play.example.org/) needs nothing. A site under a path (https://<user>.github.io/<repo>/) must be built with that path:

# in the created project or the engine repository
BASE_PATH=/my-repo/ npm run build

BASE_PATH (default /) becomes Vite's base (tools/vite/plugins.ts). It is written into:

Images, sounds and videos are addressed relatively (assets/img/<id>.webp?v=…), resolved against the page's address, so they follow the page wherever it is served, as long as the address ends with /.

Write BASE_PATH with a slash at both ends. The manifest is built by joining strings: BASE_PATH=/my-repo gives icons at /my-repoicons/….

Cache and service worker

The service worker is generated by vite-plugin-pwa (Workbox) from vite.config.ts.

What it keeps, and how.

WhatWhen it is cachedStrategy
The app shell: **/*.{js,css,html,ttf,webmanifest}, icons/*.pngAt install (precache)From the cache; a new build brings a new list
Images, assets/img/First use or warm-upCache first, up to 2,000 files, 90 days
Sounds, assets/audio/First use or warm-upCache first, range requests, up to 600 files, 90 days
Videos, assets/video/First use or warm-upCache first, range requests, up to 10 files
Full fonts, fonts/The first time a character needs themCache first, up to 10 files
data/ (the sealed ending's file)Each requestNetwork first, the cache after 4 s without an answer

Left out of the precache: assets/img, assets/audio, assets/video, data, assets/tools (the Studio and the dev tools), studio-demo, fonts, and assets/reality for a game that declares no reality.

Warm-up. From the title screen and after each room change, the engine fetches in the background the current room, the rooms directly reachable and their audio, in batches sized by GameDef.assetBudgets. Then, once per page, the rest of the game (GameDef.offline, default full): every image, effect, voice, music and video, batch by batch while the browser is idle, paused while the page is hidden. Nothing is fetched on a "save data" or 2G connection; music and video wait for better than 3G. offline: 'nearby' keeps only the room-scoped warm-up.

Versions. Each image or sound address carries ?v=<hash>: one hash of every file under public/assets, so any change to an asset gives every asset a new address and the old cache entries are no longer used. The sealed ending's file is always asked from the network first.

What the player is told. The warm-up ends with a status (App.offlineStatus, offlineReady):

StatusMeaning
completeEvery file of the plan is in the cache
partialWith its reason: network (and the files that failed), save-data, slow, quota (the browser reports less than 64 MB free before it starts)
skippedNothing was tried; worker: the service worker did not take control of the page in time, so fetching would have cached nothing
offoffline: 'nearby'

The pause menu shows it on its "Offline" row (ui.offlineStatus; no row with offline: 'nearby'): "312/400…" while it runs, then "complete", or "312/400 ⚠ retry" (the texts ui.offlineComplete and ui.offlineRetry; the demo game says "whole game cached" and "tap to retry"). A tap retries a partial or skipped warm-up; a file already in the Cache API is never fetched twice, so the warm-up resumes across reloads with no state of its own. With offline: 'nearby', a room never visited nor warmed may still need the network.

Updates. The worker claims the page at its first activation (clientsClaim), so the first visit's warm-up goes through it. A new version never activates on its own (skipWaiting: false): when a new sw.js is found, the game shows "A new version is ready." with "Save and update", which saves the game, waits for the save to be written, then activates the new worker and reloads. Old precaches are removed then (cleanupOutdatedCaches).

npm run e2e:pwa (engine repository) requires complete, then checks every file of the plan against the cache offline and renders a room never visited. It proves this on Chromium; WebKit cannot navigate offline under automation and reports "skipped" (exit 3), which only the CI's --allow-skip accepts.

Navigation fallback and /docs/

Applies from 4.5.0 · feature since 4.5.0

Offline, the worker answers a navigation inside its scope with the cached index.html (navigateFallback). From 4.5.0 it does not for an address containing /docs/ (navigateFallbackDenylist: [/\/docs\//]): the project's Pages serves the documentation under /<repo>/docs/, beside the game, and a reader who once played must get that page, not the game.

Guarantee · sw-skips-docs · referenced · executed on aff6caab7514 · scope self-checked by the author (AI), not reviewed The game's service worker is configured to never answer an address containing /docs/ with the game: the pattern matches the documentation's pages under Pages' base path and none of the game's pages. The test reads the configuration and the pattern; it does not run a browser.

For your game, this means: do not serve it under a path that contains /docs/ (for example https://example.org/docs/my-game/). It plays online, but offline the fallback no longer covers it: a navigation the precache does not hold under its exact address (one with a query string, for instance) fails.

Incomplete deploy

A deploy is incomplete when the host serves the new index.html (or sw.js) while files it names are missing: an upload stopped half-way, files copied in the wrong order, a CDN that has the new page and the old scripts.

Avoid it: deploy atomically (GitHub Pages and a Clever Cloud deploy publish a whole build at once), or upload index.html and sw.js last; serve index.html and sw.js with no-cache (Other static hosts).

Going back to an earlier version

Deploying the build of an earlier commit puts it back: for the players, it is just another new version.

So: going back is safe when the newer version did not change saveVersion. When it did, prefer fixing forward.

Symptoms, causes and fixes

SymptomLikely causeDiagnosisFix
Works on npm run dev, blank page under /<repo>/Built without BASE_PATHNetwork panel: 404 on /assets/index-….js (no /<repo>/)Build with BASE_PATH=/<repo>/
Icons or install missing, /<repo>icons/… 404BASE_PATH without its trailing slashmanifest.webmanifest's iconsBASE_PATH=/<repo>/
Images missing under /<repo> (no final slash)The page's address does not end with /Image URLs resolved to /assets/img/…Link to /<repo>/; Pages redirects
The old version stays after a deployThe new worker waits for the playerDeveloper tools: a worker "waiting to activate"Click "Save and update"; close every tab of the game
No update banner at allsw.js cached by the host or a CDN; or the new worker's precache failsNetwork panel on sw.js; the console during installno-cache on sw.js; complete the upload
Blank page for new visitors onlyIncomplete deploy404 on hashed scriptsUpload the whole dist/ again
Offline, a room asks for the networkpartial warm-up, or offline: 'nearby'The pause menu's statusTap to retry online; or offline: 'full'
Continue fails after a rollbackThe save is from a newer saveVersionThe console's errorRedeploy the newer version
Offline, an address of the game under a /docs/… path fails to loadThe denylist, by designThe address contains /docs/Serve the game under another path

Check it is fixed

Neighbouring cases

Alternatives set aside

Trade-offs

Decisions and ADRs


Edit the source of this page · Report an error