Skip to content

Atari 2600

StableTested and in active use. Breaking changes will be called out. CPU only — the Metal compiler cannot handle 6502 opcode dispatch.

A full Atari 2600 emulator written in Mojo: the 6502 CPU, the TIA video and audio chip, and the RIOT RAM/IO/timer chip. It runs original ROMs, and its cycle-accurate TIA is the sole renderer.

This is not a shortcut to more environments. It exists to answer a specific question about the native arcade engines: are they faithful? Validating a reimplementation against the actual hardware behaviour is a stronger check than comparing training curves.

ComponentScope
6502 CPUfull instruction set, cycle counting
TIAcycle-accurate video, playfield, sprites, collisions
RIOT128 bytes RAM, timers, controller I/O
Cartridgestandard bank-switching schemes

Around 30,000 CPU instructions execute per rendered frame.

One compiled binary plays every game. AtariGame is a value-enum id, and everything game-specific dispatches at runtime:

game.rom_file() # "roms/<name>.bin", ale-py naming
game.num_actions() # minimal action set size
game.action(idx) # the idx-th ALE action
game_signals(game, ram) # score, reward, lives, terminal — read from RAM

Runtime dispatch is deliberate. The lookup costs a handful of RAM reads per step, against ~30k emulated instructions per frame — invisible — and it avoids compiling one binary per game.

Score, reward and termination are extracted by reading the known RAM addresses for each title, which is how ALE does it too. The registry carries 103 games (NUM_GAMES in noeira/envs/atari/games/registry.mojo), verified frame-exact against reference behaviour.

The emulator supports several preprocessing pipelines, matching the conventions different papers assume:

ModeOutputUsed by
0RAM (128 bytes)RAM-based agents
14×84×84 grayscale stackstandard DQN / Rainbow
2RGB-96EfficientZero V2
3grayscale-96, single frameDreamerV3
4grayscale-96, 4-frame stackDreamerV3 variants
5grayscale-64, single frameDreamerV3 reference resolution

Matching a paper’s preprocessing exactly matters more than it sounds — resolution and frame-stacking choices materially change what a world model can learn.

ROMs are not distributed with the project. Fetch the canonical Farama bundle:

Terminal window
pixi run setup-roms

Then:

Terminal window
pixi run mojo run -I . examples/arcade_games/play_atari_pong.mojo
pixi run mojo run -I . examples/arcade_games/atari_smoke_all.mojo

It works, and DreamerV3 on emulated Pong is the project’s most substantial result — a full reproduction reaching a perfect +21 game by 90k steps and averaging +14–16 at 100k, using the grayscale-96 single-frame mode.

Terminal window
pixi run -e nvidia mojo run -I . examples/atari/dreamerv3_atari_pong_training.mojo
pixi run -e nvidia mojo run -I . examples/arcade_games/rainbow_atari_pong_pixel_training_gpu.mojo