A browser front-end for LEGO® Racers (1999), running the racers-portable engine reimplementation compiled to WebAssembly. It's a splash-screen launcher — Run Game, Configuration, Read Me — in the spirit of isle.pizza.
- Svelte 5 + Vite 8 single-page app; no backend.
- The game itself is the Emscripten build of racers-portable (
LEGORacers.js+LEGORacers.wasm). The loader is injected only when you press Run Game, after the chosen settings are turned into command-line arguments — LEGO Racers has no config file, so every setting is an argv flag. - The original game data (
LEGO.JAM,*.tunmusic, intro movies) is streamed over HTTP from/racers/through WebAssembly's fetch filesystem using Range requests. - A Workbox service worker precaches the app shell and can install the game data for offline play.
Because the game uses WebAssembly threads / SharedArrayBuffer, the site must be served
cross-origin isolated:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
- Node.js 20+
- A built racers-portable web target —
LEGORacers.js/LEGORacers.wasm - An original LEGO Racers install (for the
/racers/game data)
npm install
npm run prepare:assets # symlink LEGORacers.js/.wasm from the racers-portable web build
npm run dev # http://localhost:5173npm run prepare:assetslinks the binaries fromRACERS_BUILD_WEB(default../racers-portable/build-web).- The dev/preview server streams the game data from
RACERS_DATA_DIR(default/Users/foxtacles/Projects/LEGO Racers) with Range support + the isolation headers above.
Scripts:
| Script | Purpose |
|---|---|
npm run dev |
Vite dev server. Service worker is off (offline install shows a note). |
npm run build |
Production build into dist/ (copies the binaries in, injects the Workbox manifest). |
npm run preview |
Serve the production build — service worker on, so test offline play here. |
npm run check |
svelte-check (0 warnings enforced). |
Settings from the Configuration screen are stored in localStorage and translated to
Module.arguments at launch (src/core/config.js):
| Setting | Argument |
|---|---|
| Aspect ratio | --scale letterbox | --scale stretch |
| Render resolution | --resolution native | --resolution original |
| Skip intro videos | -novideo |
Fullscreen is a live browser toggle (not persisted). Language is selected in-game.
--renderer is intentionally omitted — the web build has a single WebGL2 backend.
dist/ is uploaded to a Cloudflare R2 bucket (racers) with wrangler
(scripts/deploy.sh):
npx wrangler login # once
npm run deploy:assets # first deploy — code + images/audio/pdf/workbox
npm run deploy # later deploys — code onlyThe game data (/racers/*) is hosted separately. Whatever Cloudflare edge serves the
bucket must add the cross-origin-isolation headers above and serve HTTP Range for the game
data. Override the bucket with R2_BUCKET.
src/
main.js Emscripten Module bootstrap + Svelte mount
App.svelte shell (pages, popups, canvas, footer)
stores.js Svelte stores
app.css global styles / design tokens
core/ emscripten launch, config→argv, audio, service worker, routing
lib/ UI components (menu, config, read-me, popups, LEGO-brick buttons)
src-sw/sw.js service worker (Workbox precache + Range-cache for /racers/)
scripts/ prepare (symlinks), workbox-inject, deploy