wasmcart

C and C++

The reference path, and the one existing games arrive on.

The ABI header is C, the reference carts are C, and an existing C or SDL2 game ports without being rewritten.

licence

A whole cart

Three exports and a struct. This is the complete program:

#include "wasmcart.h"

#define WIDTH 320
#define HEIGHT 240

static uint32_t framebuffer[WIDTH * HEIGHT];
static wc_info_t info;

__attribute__((export_name("wc_get_info")))
wc_info_t* wc_get_info(void) {
    info.version = 3;
    info.width = WIDTH;
    info.height = HEIGHT;
    info.fb_ptr = (uint32_t)(uintptr_t)framebuffer;
    return &info;
}

__attribute__((export_name("wc_init")))
void wc_init(void) {}

__attribute__((export_name("wc_render")))
void wc_render(void) {
    for (int i = 0; i < WIDTH * HEIGHT; i++)
        framebuffer[i] = 0xFFFF0000;
}

Build and run

emcc -sSTANDALONE_WASM=1 -sALLOW_MEMORY_GROWTH=1 --no-entry -O2 -o cart.wasm cart.c

npx wasmcart pack --wasm cart.wasm --name my-game \
  --width 320 --height 240 -o my-game.wasc

npx wasmcart my-game.wasc

Emscripten with -sSTANDALONE_WASM=1, or wasi-sdk. The one thing that matters is that the output is standalone WASM rather than a JS-glue bundle expecting a browser to boot it.

The headers do the boilerplate

The spec repo's include/ directory ships reusable headers, so a cart rarely writes the struct by hand:

wasmcart.h

The ABI header: wc_info_t and wc_pad_t, the flags, and the GL and host imports. Include this first.

wc_cart.h

Cart boilerplate: buffer declarations plus WC_FILL_INFO, and the opt-in debug fields a harness can read.

wc_fb.h

2D drawing straight into the framebuffer: fill_rect, blit, alpha blend.

wc_gl.h / wc_gl_blit.h

Shader compile and link, VAO and VBO helpers, and a CPU-to-GPU blit for software renderers that still want to present through GL.

wc_math.h and friends

sin, cos, sqrt and atan2 without libm, plus wc_mat4.h and wc_vec3.h for 3D.

wc_pcm_mixer.h

A multi-channel PCM mixer and WAV parser, so audio does not need an external library either.

Porting an existing game

That is its own path, and the more common one. wasmcart-sdl2 is an SDL2 backend whose video and audio drivers target the cart's framebuffer and audio ring, so a game links against it instead of a windowing system. Its joystick backend surfaces the host pads as real SDL devices, rumble included.

C++ uses the same toolchain and the same headers. Engine-scale C++ works: the constraint is the GL surface, not the language.

What actually decides it

Two requirements do the real filtering, and neither is about syntax. The toolchain must emit standalone WASM exporting wc_get_info, wc_init and wc_render. And for GPU carts, every GL function must be declared as a WASM import at compile time: there is no eglGetProcAddress in WebAssembly, so a runtime that discovers GL entry points dynamically cannot draw.

← All languages