---
title: 'Adventures in AI: Recompiling Game Boy Games for the Sega Master System'
url: 'https://dmcmillan.co.uk/blog/recompiling-game-boy-games-for-the-sega-master-system'
markdown: 'https://dmcmillan.co.uk/blog/recompiling-game-boy-games-for-the-sega-master-system.md'
date: '2026-10-19'
description: 'The Game Boy and the Sega Master System have a lot more in common than their marketing departments would have liked. I spent the weekend building gb2sms, a proof-of-concept static recompiler that takes a Game Boy ROM and produces a Master System cartridge image. Tetris now boots, plays at full spee…'
taxonomy:
  tag:
    - AI
    - 'Game Boy'
    - 'Master System'
    - Z80
---

# Adventures in AI: Recompiling Game Boy Games for the Sega Master System

## [Adventures in AI: Recompiling Game Boy Games for the Sega Master System](https://dmcmillan.co.uk/blog/recompiling-game-boy-games-for-the-sega-master-system)

    19th Oct 2026   [AI](https://dmcmillan.co.uk/blog/tag:AI#body-wrapper) [Game Boy](https://dmcmillan.co.uk/blog/tag:Game%20Boy#body-wrapper) [Master System](https://dmcmillan.co.uk/blog/tag:Master%20System#body-wrapper) [Z80](https://dmcmillan.co.uk/blog/tag:Z80#body-wrapper)  

The Game Boy and the Sega Master System have a lot more in common than their marketing departments would have liked. I spent the weekend building **gb2sms**, a proof-of-concept static recompiler that takes a Game Boy ROM and produces a Master System cartridge image. Tetris now boots, plays at full speed, clears lines and makes music on a Sega console.

I didn't write it alone. Two AI models did most of the typing, each on the part it was better at.

## The models

**DeepSeek V4.1 Flash** did the original work: the first version of the pipeline, from the ROM disassembler and SM83-to-Z80 translation to the first recompiled images. It's fast and cheap, and for code where the target is clear, like "decode this instruction set" or "convert this tile format", it got on with it.

It struggled with debugging. Once a bug needed a hypothesis tested against an emulator trace, rejected, and replaced with a better one, it would get stuck in loops of "Hmm... Hmm!", circling the same idea without making progress.

**Claude Opus 5.5**, running in Claude Code, took over for the difficult parts, mostly bug-finding. A recompiler fails quietly. Nothing crashes where the mistake is: the game runs off into the wrong memory a few thousand instructions later. Finding the cause meant writing new tools as it went: Lua scripts for the Mesen emulator to watch memory writes and record the last instructions before a crash, lockstep comparisons against a Game Boy emulator, and profilers to see where the frame time went. All the bugs below were found that way.

My job was to set the goal, decide what counted as "working", and keep checking the results on real emulators instead of trusting the tests. As you'll see, that turned out to matter.

## Why this is even possible

The Game Boy's CPU, the Sharp SM83, is a cut-down cousin of the Zilog Z80, which is what the Master System uses. About **82% of the opcodes are byte-identical**. The memory maps line up surprisingly well too:

|  | Game Boy | Master System | What it means |
|---|---|---|---|
| CPU | SM83 @ 4.19 MHz | Z80 @ 3.58 MHz | 82% of opcodes identical |
| ROM | `$0000-$7FFF` | `$0000-$7FFF` | identical |
| Work RAM | `$C000-$DFFF` | `$C000-$DFFF` | identical |
| VRAM | memory-mapped at `$8000` | behind I/O ports | needs a shim |
| Sound | 2 square, wave, noise | 3 square, noise | wave channel has to be faked |

So before writing any shim code, the CPU, RAM and ROM model already sits at the same addresses. The real work is in the two places where the hardware differs: video and sound.

## The pipeline

```
ROM.gb -> map.py     code/data map: follows vectors, RST targets, jump tables
       -> recomp.py  SM83 -> Z80 translation, relocation, stubs
       -> shim.py    GB I/O shadow -> SMS VDP / PSG / joypad / interrupts
       -> game.sms
```

The hardest part is **telling code from data**. If you translate a byte that's really part of a table, you corrupt the table. If you miss a piece of code, its Game Boy bytes stay in place and run as Z80 instructions that mean something else. GB `ld a,[nn]` (`$FA`) happens to be Z80 `jp m,nn`, and that's a very efficient way to end up running VRAM. `map.py` runs a fixpoint traversal and learns the specific idioms games use for dispatch: inline `rst $28` tables, `ld hl,T / add hl,bc / jp hl`, and handler arrays reached through `jp (hl)`.

Most instructions translate one-for-one in place. Anything that grows when translated, such as `ldh` (high-page I/O) or `ld a,[hl+]`, either becomes an RST call to a shim or, if it's hot, the whole region gets moved somewhere with more room and the original location is left holding a three-byte jump.

## The bug every test agreed with

My favourite bug came from my own test harness. The port rendered the title screen with black vertical bars all over it:

![The Game Boy on the left, the first Master System build on the right](https://dmcmillan.co.uk/images/c/c/b/e/8/ccbe8b11893d95aba95406bbfc3093822b00c0c7-before-bars.png)

My tile-diff tool reported **0 of 12,288 bytes wrong**. My pixel-diff tool was happy as well. The problem was that I'd written the tile and palette conversion for the **Game Gear / Mega Drive** formats, and I'd written the checking tools with the same wrong idea of the format. Everything matched because everything was wrong in exactly the same way.

On a Master System in mode 4:

- Tiles are **planar**: four bytes per row, one per bitplane. A Game Boy row `(lo, hi)` maps straight across as `lo, hi, 0, 0`.
- CRAM is **32 single-byte entries** (`--BBGGRR`), not two bytes per colour.
- The sprite table needs a `$D0` terminator, or uninitialised VRAM draws 64 sprites of noise.

The only way to catch it was a tiny test ROM whose correct output is obvious, run on a real emulator (Emulicious). With the fix in, the title screen comes up with all four greys correct:

![Tetris title screen running on an emulated Master System](https://dmcmillan.co.uk/user/pages/03.blog/recompiling-game-boy-games-for-the-sega-master-system/after-planar.png)

(The junk down the right and bottom is the area outside the Game Boy's 160x144 window, which isn't masked yet.)

The lesson is one I'll keep: **a reference model written by the same person as the implementation isn't a reference.**

## Other bugs worth a mention

- **Negative numbers mistaken for pointers.** Tetris walks up the playfield with `ld bc,$FFE0 / add hl,bc`, which is -32. The recompiler saw `$FFE0`, decided it was a pointer into high RAM and moved it, so every line clear wrote rows over the port's own code. The fix follows each constant to where it's first used. Added to `hl`, it's a number. Dereferenced, it's a pointer.
- **`$0066` isn't spare.** The Z80's NMI vector (the Master System's pause button) is at `$0066`, which in Tetris is in the middle of the serial interrupt handler. Stamping a jump there crashed the game at the end of the attract demo.
- **Half-speed menus.** Profiling in Mesen showed 50% of all executed instructions going through the `ldh` shim. A rewrite using `ex (sp),hl`, plus direct addressing in relocated code, took the menu from 49% to 100% of Game Boy speed.

## Sound

The Game Boy APU is emulated at the register level. The game's writes land in a shadow copy, and once per frame the difference is converted into SN76489 PSG commands: frequency dividers, volume envelopes, and a noise channel that steals a tone voice for loud drums. The Game Boy's wave channel has no Master System equivalent, so for now it plays as a square wave at the fundamental.

The numbers look good: against the Game Boy, the title music has an envelope correlation of 0.96. But that only measures timing and loudness, meaning the notes start and stop in the right places. It says nothing about whether it sounds nice, and it doesn't. Have a listen.

Neither clip is a hardware recording. The Game Boy clip renders Tetris's own APU register writes through a model of the Game Boy sound chip. The Master System clip is the recompiled ROM running in a Z80 simulator, with every byte it sends to the PSG rendered through a model of the SN76489.

**Game Boy (reference)**

**Master System (the port)**

**Side by side**: Game Boy in the left ear and the port in the right, lined up on the start of the title music. Headphones help.

Yes, the Master System version sounds rough. Here's what you're hearing:

- **No start-up "ping".** The two-note chime at the start of the Game Boy clip isn't Tetris. It comes from the Game Boy's boot ROM, built into the console, which runs before any cartridge code. The port never runs that boot ROM, so there's no chime.
- **The bass.** The Game Boy plays it on its wave channel, which can play any shape of wave. The SN76489 only has square waves, so the bass becomes a buzzy square, and anything below about 110 Hz comes out an octave too high.
- **The balance.** One of the melody voices comes out about 6 dB louder than it should.
- **The drums.** The SN76489's noise channel can only run at a few fixed rates, or follow one of the tone voices, so the Game Boy's drum sounds can't be copied directly. The converter's own figures put the drum noise rate 95–99% away from what the Game Boy asks for. Worse, I suspect the chip model used to render these clips gets the noise clock wrong, so even this may not be exactly what a real Master System would play.

What I can say is that the port does what it was designed to do. The bytes the recompiled ROM sends to the sound chip match the offline converter note for note. The problem is the design, not the port, and the sound needs another pass.

## Where it's at

Tetris works: the title, the menus, the attract demo and gameplay with line clears, all at full speed, and it stays instruction-for-instruction in step with the Game Boy for thousands of main-loop iterations. Alleyway, the second test game, is surfacing the next set of general problems: STAT interrupts, the window layer (the Master System doesn't have one), and a game that clears memory where my port keeps its code.

The real goal isn't Nintendo's back catalogue. It's homebrew and GB Studio games, so people can put their own Game Boy projects on a Master System. Tetris is just the most thoroughly understood test corpus there is.

 [ Previous Post](https://dmcmillan.co.uk/blog/imaging-an-amstrad-pcw-disk)

---

## Navigation

- Parent: [Blog](https://dmcmillan.co.uk/blog.md)
- Next: [Imaging an Amstrad PCW disk](https://dmcmillan.co.uk/blog/imaging-an-amstrad-pcw-disk.md)
