Hey HN! I've been using Claude Code a lot lately and got curious whether it could port a full open-source game to run in the browser. Xonotic (a fast open-source arena FPS, think Quake III / Unreal Tournament) seemed like a good candidate: it's built on the DarkPlaces engine (real C + OpenGL), ships gigabytes of assets, and has actual multiplayer.
It's fully playable in the browser, no install or plugins. Pick a map like g-23 to drop into a match against bots.
Some of the technical work I wanted to highlight that I/Claude focused on that went into making it actually fast:
- The engine runs off the main thread. DarkPlaces is compiled to WASM/WebGL2, but the whole engine runs on a worker via Emscripten's PROXY_TO_PTHREAD, with the WebGL context owned directly by the worker through an OffscreenCanvas (zero cross-thread GL dispatch). That let me remove Asyncify entirely — the payoff is a steady frame loop (~4 ms/frame, 99.8% of frames under 16 ms in a profiled match).
- GPU texture transcoding. Every texture is Basis Universal / KTX2, transcoded at load time to whatever compressed format your GPU supports (BC7 on desktop, etc). That took the texture set from ~5.3 GB of TGAs down to a few hundred MB on disk and cut GPU memory ~4×. Audio is re-encoded to Ogg Vorbis.
- Streamed on-demand filesystem. Nothing is bundled. The engine reads through a virtual filesystem backed by Cloudflare R2; only a tiny boot set loads upfront, then each map prefetches its working set in parallel and streams the rest as surfaces actually draw. A full map's assets dropped from ~2.3 GB to ~320 MB per session.
- SIMD. Built with -msimd128, so the math and skeletal-animation paths vectorize to wasm128.
- Hosting. Cloudflare R2 (zero egress) behind a Worker, with COOP/COEP headers for SharedArrayBuffer/threads. Assets are immutable-cached and the engine binary revalidates, so reloads are cheap.
Multiplayer is peer-to-peer. Click Host Game and you get a 6-character invite code; a friend enters it and you connect directly browser-to-browser over a WebRTC DataChannel (configured unreliable/unordered to match the engine's UDP netcode).
A tiny Cloudflare Worker only relays the one-time WebRTC handshake — once you're connected, no game traffic touches any server. I tested a real 1v1 between Edmonton and Bangkok and it held up across the Pacific.
Would love feedback, so please report any bugs or glitches here and I'll patch asap.
astlouis44•1h ago
It's fully playable in the browser, no install or plugins. Pick a map like g-23 to drop into a match against bots.
Some of the technical work I wanted to highlight that I/Claude focused on that went into making it actually fast:
- The engine runs off the main thread. DarkPlaces is compiled to WASM/WebGL2, but the whole engine runs on a worker via Emscripten's PROXY_TO_PTHREAD, with the WebGL context owned directly by the worker through an OffscreenCanvas (zero cross-thread GL dispatch). That let me remove Asyncify entirely — the payoff is a steady frame loop (~4 ms/frame, 99.8% of frames under 16 ms in a profiled match).
- GPU texture transcoding. Every texture is Basis Universal / KTX2, transcoded at load time to whatever compressed format your GPU supports (BC7 on desktop, etc). That took the texture set from ~5.3 GB of TGAs down to a few hundred MB on disk and cut GPU memory ~4×. Audio is re-encoded to Ogg Vorbis.
- Streamed on-demand filesystem. Nothing is bundled. The engine reads through a virtual filesystem backed by Cloudflare R2; only a tiny boot set loads upfront, then each map prefetches its working set in parallel and streams the rest as surfaces actually draw. A full map's assets dropped from ~2.3 GB to ~320 MB per session.
- SIMD. Built with -msimd128, so the math and skeletal-animation paths vectorize to wasm128.
- Hosting. Cloudflare R2 (zero egress) behind a Worker, with COOP/COEP headers for SharedArrayBuffer/threads. Assets are immutable-cached and the engine binary revalidates, so reloads are cheap.
Multiplayer is peer-to-peer. Click Host Game and you get a 6-character invite code; a friend enters it and you connect directly browser-to-browser over a WebRTC DataChannel (configured unreliable/unordered to match the engine's UDP netcode).
A tiny Cloudflare Worker only relays the one-time WebRTC handshake — once you're connected, no game traffic touches any server. I tested a real 1v1 between Edmonton and Bangkok and it held up across the Pacific.
Would love feedback, so please report any bugs or glitches here and I'll patch asap.