wasmcart

Zig

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.

the wasmcart-zig release licence

Try it in one command

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+.

Start from the template

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.

Or take it as a dependency

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.

A whole cart

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.

Rendering with GL

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 ...
}

Building without build.zig

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

What you get

Zero imports possible

A 2D cart needs nothing from the host beyond the three exports. Nothing is linked in that your code did not ask for.

comptime does the boilerplate

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.

Deterministic RNG included

wc.Rng plus wc_set_seed gives the reproducible frame sequences wasmcart's replay guarantee depends on.

Pinned to Zig 0.16

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.

← All languages