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.
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.
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.
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.
My favourite bug came from my own test harness. The port rendered the title screen with black vertical bars all over it:

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:
(lo, hi) maps straight across as lo, hi, 0, 0.--BBGGRR), not two bytes per colour.$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:

(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.
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.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.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:
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.
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.