The Kill Screen
In the last post, I wrote about nine instructions that run the entire fractional speed system in Pac-Man. This time I wanted to find the ones that break it.
I’ve known about the split screen my whole adult life — every arcade history book mentions it, every documentary shows the same garbled right half of the maze. What I’d never done is open the disassembly and look at the actual code that fails. With the ROMs already pulled and cross-referenced from the speed-system work, it only took an afternoon.
The bug turns out to be two bugs, compounding. A misplaced INC A in the setup at $2BF0, and the peculiar semantics of a single loop instruction about fifteen bytes later. Either one on its own would have been survivable. Together they overshoot by a factor of thirty-six.
The setup#
Pac-Man stores the current board number in a single byte of work RAM at $4E13. Zero-indexed: the cherry board is $00, strawberry is $01, and the 256th board is therefore $FF. No second byte, no flag, no guard.
The routine that redraws the row of fruit icons in the bottom-right corner of the screen sits at $2BF0. Its first six instructions set up the loop that does the actual damage:
2BF0 LD A,(#4E13) ; A ← current board (0-based)
2BF3 INC A ; A ← board number for fruit logic
2BF4 CP #08 ; is it < 8?
2BF6 JP NC,#2C2E ; no → branch to "high-level" path
2BF9 LD DE,#3B08 ; yes → DE = fruit table base
2BFC LD B,A ; B = fruit-loop counter (should be 1..7)
On board 256, LD A,(#4E13) loads $FF. The next instruction, INC A, overflows to $00. And here is where things start to fall apart: INC r on the Z80 does not affect the carry flag. Only S, Z, H, P/V and N are updated. Even if the programmer had thought to check for overflow with JP C on the next line, it wouldn’t have caught anything. The Zero flag is set, but nothing tests it. CP #08 against A = 0 leaves NC clear. The high-level branch is skipped. LD B,A dutifully loads B = 0 into the loop counter.
That’s the first bug. On its own it would have produced the correct failure mode: draw zero fruits. Mildly ugly, probably survivable. The second bug lives about fifteen bytes later, at the tail of the loop body:
2C17 DJNZ #2C02 ; B--; if non-zero, jump back to top of loop
DJNZ is the Z80’s dedicated looping instruction — “decrement and jump if non-zero” — and it implements decrement-before-test semantics. Starting with B = 0, the first pass produces B = $FF and jumps back. The loop runs a full 256 iterations before B wraps to zero again, reading ~512 bytes of whatever happens to follow the fruit table in code ROM and painting them into VRAM as tile data.
A loop that should run seven times runs 256. The first thirteen iterations still paint valid fruit icons — I'll come back to that — and the remaining 243 paint whatever bytes of code ROM the linker happened to place next to the fruit table.
Why only the right half?#
The first thing anyone notices about the split screen is that the left half of the maze is pristine. The top score bar renders. Pac-Man and the ghosts animate normally. Only the right half is confetti.
This is a direct consequence of Pac-Man’s unusual video-RAM layout, which is itself a consequence of the cabinet. Namco rotated the CRT 90° clockwise so the playfield would be taller than it was wide. To keep raster reads linear against the rotated tube, the 28×32 playfield region of VRAM was stored column-major, right-to-left — each 32-byte column running top-to-bottom down the physical screen. The bottom two rows and top two rows stayed linear 32-byte strips, which is what makes the fruit bar work as a 2×2 block drawer.
For the first 13 iterations of the runaway loop, HL is still inside the bottom fruit bar, so the loop faithfully paints 13 real fruit icons: cherry, strawberry, two peaches, two apples, two grapes, two Galaxians, two bells, and a key. You can see them stacked along the bottom edge in the kill-screen image.
From iteration 16 onward, HL crosses into the column-major region. The carefully designed HL, HL+1, HL+$1F, HL+$20 pattern that was supposed to paint a 2×2 square on the fruit bar now paints a 2-column × 2-row vertical stripe on the playfield. Advancing HL by 2 per iteration means “move two tiles down.” Wrapping at +$20 means “shift one column left.” The loop paints vertical ribbons of garbage down the right side of the maze, column by column, marching inward from the edge until B finally wraps back to zero.
The left half survives because the loop runs out of iterations before HL ever reaches it.
Mathematically unwinnable#
The bug doesn’t crash the game. Pac-Man can still move. The ghosts still chase. Collision detection is tile-based, and many of the corrupted tiles happen to be walkable. On a Pac-Man board this is actually a problem, because the level-clear check strictly requires the dot-eaten counter to hit 244 — the standard 240 dots plus 4 energizers.
The corrupted right half contains nine stray dots, scattered wherever an overrun byte coincidentally equaled $0F or $10 (the tile codes for dot and energizer). It's worth saying that the number nine is an accident of the linker: whichever bytes of code ROM happened to sit immediately after the fruit table in 1980 are what determined how many walkable pellets materialized on the kill screen. It happens to be nine. On a differently laid-out build it would be some other small number. The canonical figure is a property of this specific binary, not of the bug itself.
The undamaged left half has 122 dots. That’s 131 on first entry — 113 short of what you need. The fruit-draw routine re-runs after each death, which respawns the nine right-side dots but not the 122 already-eaten ones on the left. Arriving with the maximum five reserve lives, a player can reach at most 122 + 6 × 9 = 176 dots. Still short. The board cannot be cleared on unmodified hardware no matter how skillfully it is played.
This is what gives the perfect-Pac-Man run its precise ceiling of 3,333,360 points. The register overflow sets the existence of the ceiling; the specific number comes from the scoring table doubling ghosts from 200 to 1,600, from the fruit point schedule, from the nine accessible kill-screen dots, from the six-lives maximum. None of the scoring is arbitrary. But without the overflow at $4E13, there would be no ceiling to hit.
Don Hodges’ eleven bytes#
In 2007, Don Hodges published a patch that fixes the bug in place. Earlier attempts had worked around it — Mark Spaeth’s 20-byte fix simply refuses to let $4E13 increment past $FE, pinning the game forever on board 255 — but Hodges wanted the actual logic corrected.
His trick is to move the INC A to after the bounds check, and adjust the constants so the comparison lands correctly:
; patched
2BF0 LD A,(#4E13)
2BF3 CP #07 ; was INC A / CP #08
2BF5 JP NC,#2C2E ; branches for stored level ≥ 7
2BF8 INC A ; INC moved here (low-level path only)
2BF9 LD DE,#3B08
With a couple of matching constant tweaks in the high-level path and two bytes of checksum padding in free ROM space, the patch comes to nine changed bytes plus two. On board 256 (A = $FF), CP #07 now sets NC, execution branches to the high-level path, A is clamped to a safe index, and the loop draws exactly seven keys. The game completes the board normally and wraps cleanly to level 0 with ninth-key difficulty preserved. Effectively an endless game.
The elegance of the fix is that it uses the same number of instructions as the original. The bug wasn’t a missing check. It was a misordered one. INC A was sitting between the load and the compare when it should have been on the other side of the branch.
What actually failed#
The standard telling of this bug is that a programmer forgot a bounds check. That’s not quite right. The routine is correct for every level Namco’s designers realistically tested. It is structurally incapable of noticing its own failure, and each piece of that failure is small enough to look reasonable in isolation.
INC A doesn’t touch Carry. DJNZ decrements before it tests. The fruit table has no terminator. The sprite hardware is independent, so Pac-Man keeps animating through the garbage. Collision is tile-based, so the player keeps moving. At 60 frames per second, every one of those individually reasonable choices compounds into a game that looks broken but keeps running.
And it goes one layer further down than the code. The reason the split stays confined to the right half is the column-major VRAM. The reason the VRAM is column-major is the rotated CRT. The reason the CRT is rotated is that Namco wanted a taller playfield than their stock monitor provided. A decision about the shape of the picture tube, made years before any of this code was written, is what kept level 256 from being a full-screen crash. Each piece looks reasonable in isolation, all the way down to the orientation of the glass.
That’s the part I find interesting. Not the missing check — those are everywhere in code from 1980 — but the fact that the game’s graceful degradation is what preserved the bug as a cultural artifact. A crash at level 256 would have been fixed in a revision ROM and forgotten. A playable-but-unwinnable board became the mathematical ceiling of competitive arcade scoring for three decades.
Toru Iwatani has said in interviews that the team “never thought players would reach that level, so there is no celebratory ending.” There wasn’t supposed to be a level 256. There was supposed to be a stream of boards that kept getting harder until the quarter ran out. What the players found instead was a wall — not because the designers put one there, but because one misplaced instruction and one off-by-one loop primitive compounded a register overflow into a geometric one.
Nine instructions to make the ghosts move at 75% of Pac-Man’s speed. Two to end the game forever. The Z80 is nothing if not economical.