Out Run
Out Run is Sega's 1986 arcade driving game, running here in the browser from its authentic program ROMs. The board is unusual for its day: two Motorola 68000 CPUs (a main CPU for the game and a sub CPU for the road and object maths) plus a Z80 for sound, driving Sega's System-16B tilemap video, a custom road generator and a zoomed-sprite chip. The road, the Ferrari and the roadside sprites all render through the attract and demo; sound is silent.
There is no standalone JavaScript 68000 on the site, so this build vendors a standalone 68000 execution core — Karl Stenerud's Musashi (MIT) — compiled to WebAssembly on its own and wrapped in a hand-written Out Run board (the Sega 315-5195 memory mapper, the two 68000s driven through Musashi's context switch, and the System-16B tilemap and palette video). A small sampling API over Musashi's register and memory interface feeds the shared in-frame debugger, so you get genuine live 68000 registers, side-effect-free memory, single-instruction step, execution breakpoints and write watchpoints.
Out Run Online Emulator
Play Out Run using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Out Run | Out Run | Out Run | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
Out Run's board is two Motorola 68000s (a main CPU and a sub CPU) plus a Z80 for sound. The site's other 68000 machines are whole WASM-bundled emulators, so there was no standalone JS 68000 to reuse. This build vendors a standalone 68000 execution core — Karl Stenerud's Musashi (MIT) — compiled to WebAssembly on its own, and wraps it in a small hand-written Out Run board (outrun.c): the Sega 315-5195 memory mapper, the two 68000s driven through Musashi's context switch, the System-16B tilemap + palette video, and the I/O. The board is a port of MAME's sega/segaorun.cpp and sega/segaic16.cpp. Everything is self-hosted (no CDN): outrun.js / outrun.wasm and the program, tile, road and sprite ROMs.
// outrun.js is a MODULARIZE factory; instantiate it, stage the ROMs, boot.
var m = await Module({ locateFile: f => SRC + f }); // finds outrun.wasm
m.HEAPU8.set(mainrom, m._wasm_main_rom()); // staged into the C ROM arrays
m.HEAPU8.set(subrom, m._wasm_sub_rom());
m.HEAPU8.set(gfx, m._wasm_gfx_rom()); // tiles
m.HEAPU8.set(road, m._wasm_road_rom()); // road gfx
m.HEAPU8.set(spr, m._wasm_sprite_rom()); // zoomed sprites (32-bit LE)
m._wasm_start(); // reset both 68000s and run
The 4 program EPROMs are byte-interleaved the way the hardware wires them (even byte on the upper data lane, odd on the lower), exactly as MAME's ROM_LOAD16_BYTE does, before staging. Rendering is manual: the board rasters into a 320×224 RGBA framebuffer in the WASM heap, which we blit to an off-screen canvas each frame and draw scaled to the visible canvas.
var rgba = new Uint8ClampedArray(m.HEAPU8.buffer, m._wasm_fb_ptr(), 320*224*4);
img.data.set(rgba); bctx.putImageData(img, 0, 0);
ctx.drawImage(back, 0,0,320,224, 0,0, canvas.width, canvas.height);
Input is the real cabinet's controls. Steering, gas and brake are analogue, read through the board's ADC0804: we push a byte per channel (_wasm_set_adc) — steering centres at 0x80, the pedals run 0x00…0xff. Start, coin and the gear-shift toggle are digital bits on the SERVICE port (_wasm_set_dport).
Debugger integration
This is a WebAssembly build, yet it gets the same live debugger as the pure-JavaScript machines — real 68000 registers, real memory, a real single-instruction step, plus execution breakpoints and write watchpoints. The core is Musashi, whose register file is reachable through m68k_get_reg / m68k_set_reg, and whose m68k_execute(1) runs exactly one instruction. We expose these through a small sampling API appended to the board.
1 · What was added. A block of EMSCRIPTEN_KEEPALIVE functions in outrun.c read and write live state through Musashi's own API. They are sampling functions: the debugger calls them a few times a second while its window is open, and once per Step. There is no per-instruction or per-cycle hook in the hot path (the frame runs at native speed). Because Out Run has two 68000s, the sampling functions always select the main CPU's saved Musashi context before reading, so the debugger inspects the main CPU.
// outrun.c — sampling only, over the MAIN 68000's context
static void dbg_main(void){ m68k_set_context(CTX_MAIN); CUR = 0; }
u32 wasm_dbg_reg(int i){ dbg_main(); // 0-7 = D0-D7, 8-15 = A0-A7
return i<8 ? m68k_get_reg(0,M68K_REG_D0+i)
: m68k_get_reg(0,M68K_REG_A0+(i-8)); }
u32 wasm_dbg_read(u32 a){ return main_read8(a); } // side-effect-free bus read
void wasm_dbg_step(void){ m68k_set_context(CTX_MAIN); m68k_execute(1); m68k_get_context(CTX_MAIN); }
2 · What is REAL. D0-D7, A0-A7, PC, SR (with the X N Z V C S flags) and USP/SSP are read and written live off Musashi's register file. Step i runs exactly one 68000 instruction. Memory reads resolve the program ROM, work RAM, tile / text / palette / sprite RAM and the sub-CPU shared RAM directly (returning safe values for I/O windows), so auto-polling the hex / disasm view never kicks the watchdog or clears a latch. The disassembly uses the shared m68000 decoder.
3 · Breakpoints and watchpoints without a hot-path hook. When nothing is armed the loop runs a whole frame at native speed (_wasm_tick). As soon as a breakpoint or watchpoint is set, the loop switches to stepping the main 68000 one instruction at a time with _wasm_dbg_step, comparing the live PC against the breakpoint set and the watched addresses against their last sampled value, and pausing on a hit — a pure host-side check that costs nothing when the debugger is closed.
Architecture
Sega's Out Run (1986) is one of the most influential arcade racers. Its board is unusually rich for its day:
- Two Motorola 68000s — a main CPU (game logic, the debugger target) and a sub CPU (road and object maths), sharing work RAM and the road buffers. Both run on Musashi; a single WASM module drives the two of them by swapping Musashi's CPU context (
m68k_get_context/m68k_set_context) between the two buses each slice. - Sega 315-5195 memory mapper — Out Run has no fixed address map; the main 68000 programs this chip at boot to lay out ROM, work RAM, the tile / text / palette / sprite RAM, the I/O and the shared window to the sub CPU. The board presents Out Run's fixed decoded layout directly.
- System-16B tilemap video — two scrolling 128×64 tile planes (built from 64×32 pages) plus a fixed 64×28 text layer, 8×8 three-plane tiles, through a 4096-entry palette (five bits per channel with a shade/hilight bit). This draws the clouds, hills and text.
- Sega road generator — the custom road chip fills each scanline below the horizon from a road ROM: two road layers with per-line horizontal scroll, colour tables and a priority PAL for the stripes and shoulders, plus the solid sky fill above the horizon. This is the perspective road you drive on.
- Zoomed-sprite chip — Out Run's sprite generator scales each sprite in both axes from a 1 MB sprite ROM (hardware zoom, flip, per-line pitch, four banks). This draws the Ferrari, the traffic and the roadside objects, mixed against the tilemap and road by a 4-level priority.
- Zilog Z80 + sound — a Z80 with a YM2151 and Sega PCM drive the music and engine sound. This build is silent.
How to build this artefact. Toolchain: Homebrew emscripten (emcc on PATH). Generate Musashi's opcode tables, then compile the core + the board:
# 1. generate Musashi's opcode handlers (m68kmake reads m68k_in.c)
cc -o m68kmake m68kmake.c && ./m68kmake
# 2. compile the standalone 68000 core + the Out Run board to wasm
emcc -O2 outrun.c m68kcpu.c m68kops.c m68kdasm.c softfloat/softfloat.c -o outrun.js -sMODULARIZE=1 -sEXPORT_NAME=Module -sALLOW_MEMORY_GROWTH=1 -sEXPORTED_FUNCTIONS=_wasm_start,_wasm_tick,_wasm_dbg_pc,... # -> outrun.js + .wasm
Single-threaded (no SharedArrayBuffer), so it hosts anywhere. Musashi has no dynarec, so nothing needs writable-executable memory.