A modern-web game rebuilt in pure modern JavaScript for GitHub Pages, fast LLM iteration, and mobile-controller play. It remains a tribute to the Molez game I played in the 90s.
- Static-first app built with Vite and deployable to GitHub Pages.
- Desktop host at
/with a seeded arena, join code, QR code, roster, and deterministic round telemetry. - Mobile controller at
/controller/with thumb-friendly touch controls. - Deterministic simulation separated from rendering for reproducible browser tests.
- Transport abstraction with:
- local
BroadcastChannelmode for same-browser / same-origin fast iteration - manual
peermode that bootstraps WebRTC with QR + reply codes and no signaling server - optional managed relay mode via
VITE_SIGNALING_URLfor remote mobile devices
- local
- Browser-first automation via Playwright.
- Architecture decision records under
/docs/adr. - CI/CD workflow that builds, audits, tests, uploads artifacts, and deploys to Pages on green.
npm ci
npm run dev
npm run build
npm run test:e2e
npm run security:audit- Run
npm run devfor hot reload. - Run
npm run test:e2efor browser-level validation. - Run
npm run test:e2e:framerateto execute the framerate regression pack (includes CPU-throttled diagnostics and webcanvas renderer verification). - Use deterministic URLs such as:
/?session=test-host-123&transport=local&fixture=showcase&seed=1337&test=1/?session=test-host-123&transport=local&fixture=showcase&seed=1337&test=1/controller/?session=test-host-123&transport=local&name=Ace
- Playwright captures traces, screenshots, and videos on failure.
The frontend is fully static and works on GitHub Pages. Remote phone controllers can either use a manual peer-to-peer WebRTC bootstrap (QR invite + reply code) or an optional managed relay/signaling service.
local— usesBroadcastChannel; ideal for local browser automation and same-browser development.peer— uses WebRTC data channels with a manual reply-code handshake; no signaling server required.relay— uses a configurable WebSocket endpoint fromVITE_SIGNALING_URL.
If transport=relay is requested without VITE_SIGNALING_URL, the app safely falls back to local mode and explains that in the UI.
- Session IDs are validated and normalized.
- Controller names are sanitized before rendering.
- Controller traffic is treated as untrusted input and normalized before use.
- A CSP is embedded in both entry HTML files.
- CI runs
npm audit --audit-level=high. - No secrets are stored in the static client.
Web Bluetooth is intentionally not the primary controller transport. It has weak cross-platform support, high pairing friction, and does not fit browser-to-browser remote play. See ADR-007.
/src/host— host UI and orchestration/src/controller— mobile controller UI/src/game— deterministic simulation, fixtures, renderer/src/network— local and relay transports/src/shared— protocol, query, session, and config utilities/tests/e2e— Playwright browser tests/docs/adr— architecture decision records
Set a public relay URL before running relay mode:
export VITE_SIGNALING_URL=wss://your-managed-relay.example/ws
npm run devThe relay service must scope messages by session ID and reject malformed payloads. The static app never embeds secrets for that service.