gasm
Game assembly: write a game once, compile it to WebAssembly, run it everywhere
GitHub · Website & playable demos · ABI spec
About
A game is one .wasm file. A runner is a small program that loads it and hands it a stable interface for pixels, sound, input, files, a GPU and a network, and nothing else. There's a native runner (Rust: wasmtime, wgpu, winit), a browser runner (WebAssembly and WebGPU), and a headless one in Node for CI. The same bytes run on all of them.
The idea isn't new. libretro did it for emulator cores, Flash did it for a decade, and the browser already is a universal game runtime. What gasm tries is the browser's reach without the browser: a native runner can compile the module ahead of time for platforms that forbid JIT, a game can't touch anything it wasn't given, and a new platform needs a few hundred lines of runner rather than a port of every game.
The interface
The core is 11 functions: log, time, frame rate, launch parameters, an RGBA framebuffer, float audio, four virtual gamepads, and read-only assets. Three optional modules sit on top:
| Module | What it is |
|---|---|
gasm:gfx | A compact WebGPU subset with WGSL shaders. The browser forwards it almost 1:1; natively wgpu runs it on Metal, Vulkan and D3D12 |
gasm:net | WebSocket-style messages on every runner, because browsers can't open raw sockets |
gasm:storage | A per-game key/value store for saves. The runner picks the namespace, so a game can only see its own |
Resources are created from JSON descriptors that mirror WebGPU, and every per-frame call takes plain numbers. That removes all struct marshalling across the 32/64-bit boundary. Pointers are offsets into the game's own memory, and the runner bounds-checks every one.
Determinism is the product
Same inputs give the same frames, audio and game state on every engine. It's tested, not assumed: headless runners hash every presented frame, every audio buffer and every GPU upload, and CI compares wasmtime JIT, wasmtime AOT and V8 on Linux, macOS and Windows. Eight cases, from a C test pattern to 2,400 frames of scripted NES gameplay, match to the bit.
Everything else leans on that. The online mode needs no game state on the wire. Test failures reproduce. Replays and rollback netcode become possible later.
Games in the repo
| Game | What it shows |
|---|---|
| sumo | 3D two-player arena: push the other ball off the platform. WebGPU rendering, deterministic float physics, lockstep netcode through a relay, native players against browser players |
| nes | tetanes-core compiled unmodified to wasm. Passes blargg's CPU instruction, CPU timing and APU test ROMs; battery saves go through gasm:storage |
| triangle | The smallest GPU program, about 50 lines of Rust |
| test-pattern | About 70 lines of C, proof that the interface doesn't care about the language |
Games are written in Rust against a small gasm crate (a Game trait and a game! macro), or in C against gasm.h. The crate also builds natively against a stub host, so any game runs as a normal binary under lldb, and native-vs-wasm parity is one command.
Numbers
Measured on an Apple M1 Pro, emulation only, 6,000 frames of the homebrew shooter Blade Buster:
| Build | Time | vs native |
|---|---|---|
| Native Rust (release, LTO) | 7.1 s | 1.0× |
| wasm, wasmtime AOT | 10.8 s | 1.5× |
| wasm, V8 | 11.2 s | 1.6× |
Still about 9× faster than the console itself. Sumo holds 60 fps with 4× MSAA in both the native runner and the browser.
Lockstep, briefly
Both players run the identical simulation and exchange only inputs, each scheduled four frames (≈66 ms) ahead: 7 bytes per frame. A state hash goes across every two seconds, so a desync would announce itself rather than drift. Player two's camera looks from the other side of the arena, so their arrows are mirrored into world space before they're sent. The relay knows nothing about games. It pairs peers in rooms, announces joins and leaves, and forwards bytes.
Status
| ABI v0: core, gfx, net, storage | Experimental, will change |
| Native runner: macOS, Linux, Windows | Tested in CI on all three |
| Browser runner (WebGPU, AudioWorklet, Gamepad API) | Working in Chrome; Safari 26+ and Firefox should work but are untested |
| Online play | Works on a LAN or through your own relay; no public relay |
| Releases | macOS .app bundles per demo, CLI archives for macOS, Linux x86_64/arm64, Windows |
| Next | Rollback netplay (snapshot the guest's memory), textures, .gasm packages, a wasm2c runner for consoles |
MIT licensed. The NES core is tetanes-core (MIT/Apache-2.0).
