Fantasy console · Spec v0.1

The handheld that never shipped. Carts are WebAssembly.

WASM-AD is an open standard for a 240×160 console with Game Boy Advance–style hardware. A cart is one .wasm file that draws by writing registers and video memory, in any language that compiles to WebAssembly.

L R POWER AD A B START SELECT

Booting…

←↑↓→ D-pad X A Z B A/S L/R Enter Start · gamepads and touch work too

Cart slot

Insert a cart

Every cart below is a plain WebAssembly module running on the reference renderer, right in this page.

Hardware

GBA-era specs, bit for bit

Palettes, tiles, maps and sprite attributes use the Game Boy Advance's own formats, so decades of documentation and tools still apply.

240×160pixels at 60 frames per second
32,768colors, RGB555, in 16+16 palette banks
4scrolling tile layers, or a bitmap mode
128sprites, 8×8 up to 64×64
32affine matrices for rotation and scaling
αalpha blending, fades, windows, mosaic
160scanline callbacks per frame for raster effects
32 KBbattery-backed save RAM

How it works

The hardware is memory

There is no drawing API. A cart writes tiles, maps, palettes and sprites into fixed addresses of its own linear memory, laid out like the GBA's. Sixty times a second the console calls update() and turns that memory into a frame.

Any language

Rust, Zig, C, AssemblyScript, Go: if it compiles to wasm and can write to an address, it can make a cart. No bindings needed.

Save states for free

All state lives in linear memory. Snapshot it between frames and you have save states, rewind and replays.

Deterministic

No clock, no random syscalls: one boot seed plus the inputs fully determine a run. Netplay and golden-image tests just work.

One file

Code, art and metadata ship in a single .wasm, at most 32 MiB, like a cartridge. One import: trace.

Get started

A cart in twenty lines

The Rust SDK wraps the registers in typed helpers and emulates the hardware natively, so cart logic is testable with plain cargo test.

use wasm_ad::*;

wasm_ad::cart! { title: "My Cart", author: "me" }

#[unsafe(no_mangle)]
pub extern "C" fn start() {
    bg_palette()[0] = rgb(40, 60, 160);  // backdrop
    obj_palette()[1] = rgb(255, 216, 64);
    obj_tiles()[0] = [0x11; 32];         // a solid 8×8 tile
    reg::DISPCNT.write(dispcnt::MODE0 | dispcnt::OBJ | dispcnt::OBJ_1D);
}

#[unsafe(no_mangle)]
pub extern "C" fn update() {
    let x = (frame() % 240) as i32;
    oam::set(0, Sprite::new(x, 76, SpriteSize::S8x8, 0));
}
1

Add the SDK

wasm-ad = { git = "https://github.com/raskell-io/wasm-ad" }
2

Keep out of the hardware region

Two linker flags put your data and stack above 0x28000. The README has the .cargo/config.toml.

3

Build and play

cargo build --release --target wasm32-unknown-unknown

Drop the .wasm into the cart slot above.