DEV Community

zaiqltd
zaiqltd

Posted on

How we built a gate-accurate Game Boy emulator in Rust (and got dmg-acid2 pixel-perfect)

We are Zaiq, an engineering studio. We built Revenant, a Game Boy and Game Boy Color emulator, from scratch in Rust, compiled to WebAssembly, with a playable arcade that runs in your browser. No emulation libraries: every chip is written by hand.

Play it right now: https://zaiqltd.github.io/revenant/
Code and the full accuracy scorecard: https://github.com/zaiqltd/revenant

Here is what we built, and what "accurate" actually means.

"It runs Pokemon" is the wrong bar

Most emulators clear one bar: a game boots and looks roughly right. That is the easy 90%. The interesting part is the last 10%, the timing edge cases real cartridges depend on, the ones that only show up when you emulate the machine on its own 4.194304 MHz clock, cycle by cycle.

So we measured ourselves against the bar the hardware sets for itself: the community's test ROMs. The headline result: dmg-acid2 renders pixel-perfect, 0 of 23040 pixels different from real hardware, on both the original DMG and the Game Boy Color. We currently pass 130 of 279 of the canonical accuracy gates, and the full scoreboard is in the repo so anyone can check.

The CPU: stepped one T-cycle at a time

The SM83 core is not just a set of opcodes, it is a set of opcodes with precise sub-instruction timing. Reads and writes land on specific cycles within an instruction, and peripherals see those accesses as they happen, not batched at the end.

So Revenant steps the CPU one T-cycle at a time and advances the rest of the machine in lockstep. That is more work than the common "run the whole instruction, then total the cycles" approach, but it is the only way to get the halt bug, interrupt timing, and the PPU interactions right.

The PPU: a real pixel FIFO, not a scanline blit

The shortcut for graphics is to render a whole scanline at once. It is simple, and it is wrong: it cannot reproduce mid-scanline register changes, which real games and every serious test ROM use.

Revenant implements the actual pixel FIFO: background and sprite pixels are fetched and shifted out one at a time, with the fetcher and the LCD controller modelled as the state machines they are on hardware. That is what makes dmg-acid2, the PPU's torture test, come out pixel-perfect.

The rest of the machine

  • A 4-channel APU (two pulse channels, wave, noise), so the games have real sound.
  • The cartridge mappers: MBC1, MBC2, MBC3, MBC5, including the MBC3 real-time clock.
  • Full Game Boy Color support: double-speed mode, HDMA, and the colour palettes.
  • Battery saves persist in the browser, per cartridge.

Shipping it to the browser

The core is plain Rust compiled to WebAssembly, so the same code that passes the test ROMs in a headless harness also runs the arcade in your browser at full speed. On top we put a live CPU and PPU debugger that ticks alongside the running game, and instruction-level rewind, so you can step the machine backwards when something looks wrong.

The arcade has zero copyrighted content

We did not want a demo that asks you to "go find a ROM." So we wrote a tiny SM83 assembler (an original 8x8 font, APU blip helpers) and hand-assembled 11 original homebrew games: Snake, Breakout, Blocks, Flap, Blaster, Pong, Crosser, Maze, Memory, Dodge, and the first thing the emulator ever drew, a movable smiley. Each has a title screen, a score, a game-over screen, and sound. Or drop in your own .gb / .gbc file.

Why we built it

Not for a client. We built it because the team that can emulate a 1989 console at the cycle level is the team you want on your hardest problem. That is the standard we hold at Zaiq, and we aim it at real business problems, with AI as the edge.

Play Revenant: https://zaiqltd.github.io/revenant/
If you have a hard problem, bring it: https://zaiq.co.za

Top comments (0)