SearchA-ZA › Asteroids

Asteroids

1979 Arcade Open source · Public domain Online

Asteroids (Atari, 1979), designed by Lyle Rains and Ed Logg, is the vector-graphics arcade classic in which you pilot a triangular ship in open space, rotating, thrusting and firing to break drifting asteroids into ever-smaller fragments while dodging flying saucers. This build runs the original Atari board in the browser: a MOS 6502 driving the Atari Digital Vector Generator, with the original ROMs. It boots straight into attract mode, the rotating asteroids drifting across the vector screen, and is wired to the emulators.org in-frame debugger so you can single-step the 6502, read and write the registers and the full 64K memory, watch the vector RAM display list build word by word, and set execution breakpoints and write watchpoints.

Visit the official site ↗

Runs on: Web browser

Asteroids Online Emulator

Play Asteroids using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
AsteroidsAsteroidsAsteroidsgreyOpen ⛶

Machine emulated

The Asteroids arcade board (Atari, 1979) is a single MOS 6502 at 1.512 MHz driving the Atari Digital Vector Generator (DVG): there is no framebuffer, so the CPU builds a list of lines in the 2 KB it shares with the DVG and the DVG steers the beam to draw them on an X-Y monitor. It is one of the defining games of the golden age of arcade video games.

Chips

Notes

Embedding

Asteroids is a MOS 6502 at 1.512 MHz driving the Atari Digital Vector Generator (DVG) -- there is no framebuffer; the DVG reads a display list of lines from the memory it shares with the CPU and steers the beam to draw them. This build vendors a self-hosted machine (no CDN): a JavaScript port of Mike Chambers' public-domain Fake6502 core, our DVG interpreter, and the original Atari ROMs, driven from a host-owned loop so the debugger can control it.

Boot. The three 2 KB program ROMs load at $6800–$7FFF (mirrored high so the reset/NMI vectors resolve) and the 2 KB vector ROM at $5000; the 6502 starts from $FFFC and drops straight into attract mode:

var m = new AsteroidsMachine();   // loads ROMs, builds 6502 + DVG
m.reset();                        // pulls the reset vector at $FFFC
// one display field: 4 NMIs (~246 Hz) of 6144 cycles, then draw the DVG list
for (var s = 0; s < 4; s++) { m.cpu.nmi(); runCycles(6144); }
m.renderTo(ctx, W, H);

The machine is plain objects. Everything the host and debugger need is a field or method on the machine or its CPU:

MemberKindWhat it does
m.cpu.step()methodFetch, decode and run one 6502 instruction; returns its cycle count. The single-step primitive.
m.cpu.nmi()methodPulse the non-maskable interrupt -- the ~246 Hz tick that drives the whole game.
m.dvgGo()methodRun the DVG over vector RAM/ROM and collect the frame's line segments. Triggered when the CPU strobes $3000.
m.dbgRead(a) / m.memWrite(a,v)methodSide-effect-free read and the CPU write path -- what the debugger's memory views read and poke.
m.setInput(id,down)methodDrive a control (left/right/thrust/fire/hyper/coin/start1) into the IN0/IN1 latches.
m.cpu.a/x/y/sp/pc/flagsfieldThe live 6502 registers, read and written directly.

Video. Each field the loop runs one display's worth of 6502 (four NMI slices), then walks the DVG display list the game left in vector RAM and strokes the lines to a 760×570 canvas.

Debugger integration

The debugger drives a host-owned run loop: because the 6502 interpreter runs one instruction at a time, the loop can pause, single-step and check breakpoints between any two instructions -- no changes to the CPU core are needed. window.EMU_BOOT.transport exposes the controls the shared debugger calls:

  • pause / resume / isPaused -- stop or restart the requestAnimationFrame loop.
  • stepInsn(n) -- call cpu.step() exactly n times and redraw, so a single step advances the PC by one instruction.
  • step(n) -- advance n whole display fields (each with its four NMIs).
  • breakpoints -- a Set of PC values. When non-empty the loop runs instruction-by-instruction and pauses before executing a watched address; when empty it runs a field at speed, so an idle debugger costs nothing.
  • watchpoints -- the machine's memWrite is the single write path; it flags a hit when a watched address is written and the loop pauses on it. Because the 6502's registers and the whole 64 KB map are ordinary JavaScript, registers are read and written directly and memory is exposed side-effect-free.

The plug-in (asteroids-debug.js) reads these hooks and calls EmuKit.defineMachine with the 6502 register set bound to the shared mos6502 disassembler, four memory chips (the 64 KB bus, the program ROM, the vector RAM display list and the vector ROM shape library), and the on-screen control panel. A neat trick for a vector machine: point the Vector RAM hex window at $4000 and single-step -- you can watch the game build the frame's line list word by word before it strobes the DVG.

Architecture

Asteroids has no video RAM in the usual sense. A single MOS 6502 builds a list of vectors in the 2 KB it shares with the DVG, then strobes it; the DVG is a tiny stack machine that reads that list and draws lines on an X-Y monitor. Modelled here as plain JavaScript objects:

  • CPU -- a port of Fake6502 (NMOS 6502 with BCD, which the score uses), reaching memory only through the machine's memRead/memWrite, so the bus and I/O live in one place.
  • DVG -- the Digital Vector Generator: it walks the display list as opcodes -- VCTR long vector, SVEC short vector, LABS set position + global scale, JSRL/RTSL call and return into the shape ROM, HALT -- accumulating (dx,dy,brightness) line segments. Scale is a power-of-two divisor so the same asteroid ROM shape draws large or small.
  • Bus / I/O -- 1 KB RAM, the input ports (IN0: fire, hyperspace, coin clock, VG-halt; IN1: coins, start, thrust, rotate) and the DIP switches, plus the DVG "GO", watchdog and the discrete sound latches (now modelled -- see below).
  • ROMs -- 6 KB of 6502 program (three mask ROMs) and 2 KB of vector ROM holding the asteroid outlines, letters, digits, ships and saucers the display list calls into.

The one piece of timing the game truly needs is its ~246 Hz NMI (the 12.096 MHz master clock divided down); the host loop reproduces exactly that, four NMIs per ~61.5 Hz display field, and renders the DVG list at the end of each field.

Sound

Pattern S — authored synthesis of discrete analogue sound. Asteroids has no sound chip: every effect is a small discrete analogue circuit (VCOs, 555 timers, a noise generator and RC envelopes) that the 6502 gates and pitches by writing a handful of output latches. There is nothing to emulate register-for-register, so asteroids-sound.js is a faithful approximation of those circuits, driven from exactly the latch writes the board decodes.

Where it hooks. The machine's single write path (memWrite) already routes the DVG "GO" strobe at $3000; the sound latches sit just above it and were previously stubbed. Each write in $3600–$3E00 is now forwarded into the sound model, whose decode mirrors MAME's asteroid.cpp:

// $3600 explode: bits 2-5 = volume (0-15), bits 6-7 = size/pitch (0 large .. 3 small)
// $3A00 thump:   bit 4 = heartbeat gate, bits 0-3 = thump frequency
// $3C00 saucer siren  $3C01 saucer fire  $3C02 saucer big/small select
// $3C03 thrust  $3C04 player fire  $3C05 extra life        (each enabled by D7)
// $3E00 noise reset  (reseed the analogue noise generator)
if (a >= 0x3600 && a < 0x3f00) { if (self.sound) self.sound.write(a, v); return; }

The effects. Each is synthesised from the quantity its latch carries:

  • Thump-thump — the iconic heartbeat: a rising edge of the $3A00 gate fires one low decaying pulse (sine plus a little edge so it carries on small speakers), its pitch taken from the low four bits; the CPU re-strobes it, so the beat rate climbs as the wave clears, exactly as the hardware does.
  • Thrust — band-limited noise with a slow flutter, gated by $3C03, giving the rocket "whoosh" while thrust is held.
  • Fire — a fast downward-swept square "pew" retriggered on each $3C04 strobe (a separate, higher sweep for the saucer's fire at $3C01).
  • Explosions — an RC-decaying filtered-noise burst; the $3600 pitch latch picks the noise brightness and decay length across the three asteroid sizes (large = deep rumble, longest ring; small = sharp crack), and the ship's blast rings longest.
  • Saucer — a warbling repeating pulse while $3C00 is enabled, the $3C02 select bit choosing the big or small saucer's pitch.

Sample rate / pitch. The model is generated directly at EmuAudio.sampleRate — every phase increment and envelope slew is per-sample from the device rate — so pitch is correct at any rate with no resampling. Each video frame the loop calls machine.generateAudio(Math.round(EmuAudio.sampleRate/60)), which sums the effects, clamps and returns interleaved-stereo Int16 (mono duplicated to L/R), then EmuAudio.push()es it.

Mute contract. window.EMU_BOOT.transport exposes isMuted() and setMute(bool), delegating to EmuAudio. It starts muted (browsers block audio before a user gesture); the shell's Sound button unmutes from a real click. The machine sits in attract mode until you insert a coin and press 1P-Start; the discrete sounds play during real play, the thump-thump heartbeat beating continuously and the bang following when a drifting rock strikes the ship.

Caveat. The discrete timbres are faithful approximations, not a component-level emulation of the analogue schematic — there is no chip to reproduce exactly — but each effect is gated and pitched by the real CPU's writes to the real sound latches, so the sound tracks the game.