wasmcart
Write games in Zig. Ship a few hundred bytes.
Bindings, not a runtime. No interpreter, no GC, no engine: a cart compiles to a freestanding wasm32 module a host runs directly.
Three prebuilt carts ship with the release. Download a .wasc and run it, no Zig install needed:
npx wasmcart hello_gl.wasc
The GL cart is 3.2 KB. Nothing to install but Node 22+.
Everything below lives in wasmcart/wasmcart-zig.
cp -r template mygame && cd mygame
zig build
npx wasmcart pack --wasm zig-out/bin/cart.wasm --name mygame -o mygame.wasc
npx wasmcart mygame.wasc
That is the whole loop. The template already carries wasmcart.zig and wasmcart_gl.zig in src/, so there is nothing to fetch.
zig fetch --save=wasmcart \
https://github.com/wasmcart/wasmcart-zig/archive/refs/tags/v0.1.1.tar.gz
Then wire the module up in your build.zig and import it by name:
const dep = b.dependency("wasmcart", .{});
mod.addImport("wasmcart", dep.module("wasmcart"));
Both routes work. Copying the two files is worth knowing about because Zig's manifest format has changed in most recent releases, and bindings that never change are easy to vendor if a future Zig breaks the dependency route.
const wc = @import("wasmcart.zig");
pub const panic = wc.panic;
const cart = wc.Cart(.{ .width = 320, .height = 240 });
export fn wc_get_info() *wc.WcInfo { return cart.getInfo(); }
export fn wc_init() void {}
export fn wc_render() void { cart.clear(0xFF3050C0); }
wc.Cart is a comptime type that declares the buffers and fills the info struct. A cart in this shape compiles with zero imports, which means the host hands it nothing at all: it is pure computation writing pixels into its own memory.
Set gpu_api and the GL entry points come in as WASM imports, declared at compile time as the ABI requires:
const gl = wc.gl;
const cart = wc.Cart(.{ .width = 640, .height = 480, .gpu_api = wc.GPU_API_WEBGL2 });
export fn wc_render() void {
gl.glViewport(0, 0, 640, 480);
gl.glClearColor(0.05, 0.07, 0.12, 1.0);
gl.glClear(gl.COLOR_BUFFER_BIT);
// ... draw ...
}
If you would rather not use the build system:
zig build-exe cart.zig -target wasm32-freestanding \
-fno-entry -O ReleaseSmall \
--export=wc_get_info --export=wc_init --export=wc_render
A 2D cart needs nothing from the host beyond the three exports. Nothing is linked in that your code did not ask for.
wc.Cart(.{ ... }) generates the buffers and the info struct at compile time, so the ABI plumbing costs no runtime work and no hand-written statics.
wc.Rng plus wc_set_seed gives the reproducible frame sequences wasmcart's replay guarantee depends on.
Zig is pre-1.0 and build.zig changes shape most releases. Expect the build script to need edits on a different version; wasmcart.zig is plain structs and externs and travels better.