← Experimental Atari Multiplayer Arcade
How Donkey Kong 4-Player Was Made
A record of an experiment in using AI coding agents to modify a vintage game · 4-Player v0.1, build 0038 · 6 and 7 October 2026
- What this is
- The goal and the starting point
- Source code from a cartridge
- How the original works
- Patching in place, and four traps
- Four players in a one-player game
- The rules of the race
- Drawing four Marios: three attempts
- Colour changes on the right line
- Scoreboard and title
- The music, the death jingle and the chest beat
- The CPU budget
- A sprite editor
- Automated testing
- Bugs, including the agent's own
- Timeline and numbers
- Known limits and open questions
- Lessons
- Credits
1. What this is
Donkey Kong 4-Player is an experimental modification of Atari's 1983 Donkey Kong cartridge for the Atari 400/800, programmed by Landon Dyer. Up to four people play at the same time, each with their own Mario on their own joystick port, on the same board: the first Mario to reach the top ends the board. The modification was made by an AI coding agent (Claude Code) working from the machine code in the cartridge. Testing was done automatically in an emulator; problems seen while the game was played during development were reported back to the agent and fixed.
- What the agent did. In two sessions, late on 6 October and on the morning of 7 October 2026, it measured which bytes of the cartridge are code, generated assembler source that rebuilds the cartridge byte for byte, removed four copy-protection traps, and then patched the original program in place, keeping every original routine at its original address. The new code runs the original's Mario routine once for each player, draws four Marios at the same time without flicker (one hardware player and one missile each, with colours changed line by line by display list interrupts), and changes the rules for a race: unlimited hammers, hammer fights, no deaths from falls. There were 38 builds; the current one is version 0.1, build 0038.
- How it was checked. A byte-identical rebuild of the original; a check in every build that no original routine moved; boot tests of the cartridge, program file and boot disk on an emulated Atari 800 (and of the cartridge on an XL); scripted event tests; a soak test with four random players on every board; a test that checks every pixel of the four Marios against their frame data; and CPU measurements.
- What is imperfect or uncertain. The four Marios are approximations of the original Mario (about 87% of its pixels exact); with four players the barrels and fireballs are updated less often than in the original, so they move more slowly on three of the four boards; some rules differ from the original on purpose; and everything was tested in an emulator. Details are in section 17.
This page was written from the project's development log, technical notes and build list. "The agent" means the Claude Code session that did the work.
2. The goal and the starting point
The request was a four-player simultaneous Donkey Kong for the Atari 800: one to four players selectable, all playing at once, unlimited hammers, players able to kill each other with hammers, so that the game becomes a race to the top; and no death from falling from a height.
The starting point was the original cartridge image: Atari's 1983 Donkey Kong for the Atari
400/800, a 16K cartridge at $8000-$BFFF. Unlike Demon Attack's cartridge, it had no
free space at all. The original is a one-player game; its two-player games take turns, swapping
the two players' data between turns.
"Four-player simultaneous" meant four Marios on joysticks 1–4, which only the Atari 400 and 800 have (the later XL and XE models have two joystick ports).
| Player | Joystick | Mario's colours (cap and overalls) |
|---|---|---|
| 1 | port 1 | red (Mario) |
| 2 | port 2 | green (Luigi) |
| 3 | port 3 | yellow |
| 4 | port 4 | white |
Shirts, hair and the hammer are blue for everybody, and the faces are skin colour; section 8 explains why.
3. Source code from a cartridge
The method and most of the tools came over from the Joust 4-Player work, with fixes made during the Demon Attack work: a headless test harness, a 6502 disassembler, a source generator, and a private build of the atari800 emulator with coverage counters, a fixed random seed and a write-watch.
- Coverage. Six games of the original (one and two players, different difficulties) were played with random joystick input, including ladders and jumps, and restarted at game over: 3,703 instruction addresses in the ROM ran.
- Source. The generator wrote 6,219 lines of ca65 source with 4,776 instructions, 1,074 of
which never ran in those games. Assembled, it was byte-identical with the cartridge. One
manual mark was needed: a table after a
LDX $8F36,Y : BNE(whose value is never 0) is data, not code. The display list interrupt and the sound engine's handlers, which are reached only through a table of jump addresses, were added as code entry points.
4. How the original works
Reading the generated source gave this picture of the original:
- The screen is one ANTIC mode E bitmap: 160 × 198 pixels in four colours, 40 bytes a
line, in RAM at
$2100-$3FEF. The display list is in RAM at$1200(a short one in ROM, 23 blank lines, jumps to it). Girders, ladders, barrels, fireballs, springs, elevators, pies, Donkey Kong, Pauline and the hammers on the board are all drawn into that bitmap by the CPU. - Mario is three hardware players on top of each other: players 0 and 2 for the red parts
and the skin, player 1 for the blue parts. His hammer is missiles 0–2. Facing left, he is
mirrored through a bit-reverse table at
$0D00. - Player 3 shows Pauline's bonus items (umbrella, hat, purse), moved between bands of the
screen by a display list interrupt (DLI). The collision register tells when Mario touches one.
On the first board the DLI also changes a playfield colour (
COLPF2) at two lines. - Its own VBI (the OS's is not used) sets the colours and the display list and reads the
triggers and
PORTAinto the joystick shadows (joysticks 1 and 2 only). A sound engine plays the tunes and effects from 16 sound slots. - The main loop waits for the VBI, then runs the timers and Mario, the spawning of new objects, Donkey Kong, and then up to 15 objects round-robin until the next VBI, each at most every second frame. A busy frame slows the enemies, not the game. On boards 0 and 1 the game waits about 11,000 cycles a frame for the next VBI, on board 3 about 8,000; on board 2 (the elevators) it does not wait at all.
- Mario's state machine has five states: placed, walking, on a ladder, falling, jumping. Falling (walking off an edge, or a jump that ends more than 17 lines lower) ends in the death routine when he lands.
- The four boards: 0 girders and barrels, 1 rivets, 2 elevators, 3 conveyors and pies. Each
board's data is unpacked to
$1800-$19FF(a grid of girders, ladders and hammers, and the floor heights). - Memory: the original fills 16K of RAM: the bit-reverse table, variables at
$0E00, line address tables at$1000, the display list, player/missile memory at$1300-$17FF(single-line resolution), the board data, a music buffer at$1C00, and the bitmap.
LDA $8648 : ADC $8649 : CMP #$20: two bytes of the program code must add up to
$20. If the code has been moved, every jump becomes a
deadly fall.| Hardware | Original | 4-player |
|---|---|---|
| Players 0–2 | Mario's three colour layers | Marios 1–3, one player each |
| Player 3 | the bonus items, moved by the DLI | Mario 4 (the items are drawn into the bitmap) |
| Missiles | 0–2: Mario's hammer | 0–3: one per Mario, all blue ("fifth player" colour) |
| DLIs | board 0's colour change; player 3's bands | the four Marios' colour changes, with board 0's colour change merged in |
| Joysticks | 1–2 | 1–4 (ports 3 and 4 through PORTB, TRIG2, TRIG3) |
5. Patching in place, and four traps
For Joust and Demon Attack the generated source was made relocatable, and a relocation test
showed that it could be moved. Donkey Kong was handled differently: the original is
patched in place. Every original address stays where it is, and every edit is the same
size as what it replaces: a JSR or JMP into new code, or
NOPs. The source passes many addresses in registers and inside packed board data, so
making all of it relocatable would have been a long job with little use: the ROM is full anyway,
and all new code has to go into RAM. Every build compares the address of every label of the
generated source with the new build and fails if one has moved.
The layout is a 32K image: the new code and its variables at $4000-$77FF, the
original at $8000-$BFFF. As a cartridge it is a 32K XEGS bank-switched cartridge
(type 12) that copies the new code into RAM at power-on, so it needs 32K of RAM; the program file
and the boot disk load both parts on a 48K machine and start the game at $8513.
The cartridge needed a loader, and there was no free byte for one. The original's two-player
swap routine (16 bytes at $B1A3), not needed when everybody plays at once, became the
cartridge's boot code: it selects bank 1 and jumps to a loader there, which copies the new code
into RAM and starts the game. The three calls of the swap routine became NOPs. One more
constraint: the game's screen clear wipes $2100-$40FF, which includes the first page of
the new code; only the loader, which runs once at power-on, lives there.
In ROM the program cannot change itself. In RAM (the program file and the boot disk) the first
test builds died within seconds. The coverage emulator's write-watch on $8000-$BFFF
found four writes into the program's own address space:
- The title unpacks a data block onto code at
$8A17. The jump's checksum (above) reads exactly this instruction, so it had to stay: only its destination operand was changed, to$C000(unmapped on a 400/800). - The line address table points every line below the screen (lines 198–255) to
$9680, which is code. Now$C000. - The VBI writes a 0 into the title logo's data at
$8808on every frame of play. NowNOP NOP. - The DLI stores through a pointer whose high byte the main loop sets to a random value
$80-$BFevery frame: a random byte of the program on every DLI. NowNOP NOP.
After that, the write-watch saw no write into $8000-$BFFF during 4,000 frames on
each board, and the unchanged game booted as a cartridge, a program file and a boot disk on an
Atari 800, and as a cartridge on an XL (builds b0001–b0004). From then on every build was
kept, together with the sources that made it.
6. Four players in a one-player game
The original has one Mario, kept in a set of variables. The new player loop runs the
original's Mario routine once per player, with that player's state copied into the
original's variables first and copied back afterwards: $69-$7B, $82,
$BB and $BC in zero page and $0E31-$0E33, 25 bytes per
player. The original already reads the joystick as STICK0,X and the button as
STRIG0,X, with X holding the current player of its alternating two-player game, so
setting X to each player in turn makes each Mario follow his own joystick. Scores are swapped only
around the score routine (16 bytes per player, the first of them the number of men left).
- Joysticks 3 and 4 are read from
PORTBand the triggersTRIG2/TRIG3on a 400/800. On an XL or XE the game offers one or two players (the 400/800 operating system is recognised by its RESET vector at$D800or higher). The fire-button latch moved from the VBI into the player loop. - The enemies chase one target Mario at a time; the target changes every 128 frames.
- Points for a smashed barrel or fireball go to the Mario who smashed it.
- A test mailbox (b0010): writes from the test scripts into a player's variables got lost when they arrived while that player's state was swapped in. The scripts now leave their writes in a small queue in RAM, and the game applies them at a moment when no player is swapped in.
7. The rules of the race
- Falls don't kill. Landing after a fall goes to a new routine that puts Mario down safely, instead of the death routine.
- Unlimited hammers. The hammers stay on the board and are no longer erased; a Mario jumps at one to take it, one at a time.
- Hammer fights. A hammer kills another Mario it touches, using the original's hammer box, and gives 800 points to the hammer's owner.
- Deaths. A Mario hit by a barrel, fireball and so on loses a man and comes back at the start of the board, protected for 2 seconds. Each player starts with 2 men in reserve (and gets one more at the original's bonus score). A player with no men left is out; the game is over when everybody is out.
- The race. The first Mario to reach Pauline ends the board and gets the bonus; then everybody goes on to the next board. At the end of a board the winner is handed to the original's end animation: the four-Mario display is switched off, the board's own DLI is restored, and the original routine draws the winner, in the winner's colours (b0018).
- Items. The umbrella, hat and purse are drawn into the bitmap (player 3 is a Mario now) and score for whoever walks over them, found by coordinates instead of the collision register.
- The bonus timer counts down as in the original, but at 0 it no longer kills: it stays at 0, and that board's winner gets no bonus (b0031).
8. Drawing four Marios: three attempts
The original Mario uses three of the four hardware players and three missiles. Four Marios on the same scanlines leave each of them one player and one missile. The pictures in this section show the bottom floor after the same joystick input in each build: the Marios walk right for different lengths of time, then stop.
One colour each (b0005–b0010)
In the first version each Mario was one player, and the original's three colour layers were merged into one 8-pixel-wide shape in a single colour. It worked, but the Marios were single-colour shapes, and the next request was for full-colour Marios, possibly multiplexed with flicker.
A flicker multiplexer (b0011–b0020)
The second version drew every Mario as the original's full-colour Mario again (players 0–2 and missiles 0–2, exactly as the original's drawing routine places them, including a one-pixel offset the original's arithmetic leaves when the offset is negative), each player in their own colours. A DLI just above each Mario moved the three players and the missiles to him and set his colours, so Marios at different heights all appeared in the same frame. Marios whose lines overlapped had to take turns: the one not shown for the longest went first. Two overlapping Marios flickered at 30 Hz each, four at 15 Hz.
The player/missile memory was double-buffered: the original's area and a second one at
$7800. The main thread drew the next frame into the hidden buffer, only where its
Marios had changed, and the VBI flipped the buffers and the DLI event lists. Two bugs on the way:
the second buffer was first placed at $4800, inside the new code once it was copied
to RAM, and the emulator hung at the first board; and the VBI switched the multiplexer off before
the board had started (fixed with a two-step on switch).
The flicker was the expected weak point. The next request was for four Marios at the same time, with some detail, without flicker.
One player and one missile each (b0023–b0030)
The version in use since then rests on a detail of the hardware. ANTIC mode E, the original's
screen mode, never shows the fourth playfield colour (COLPF3). GTIA's
"fifth player" option (a bit in PRIOR) gives all four missiles that colour. So
COLPF3 was set to blue ($86), the colour every Mario shares: shirt, hair
and the hammer. Each Mario is now:
- one missile: two blue blocks, 1, 2 or 4 pixels wide each, behind the player;
- one player: an 8-pixel-wide detail layer in front, with one colour per scanline,
changed by DLIs down the screen: the player's own colour (Mario
$32red, Luigi$C4green,$1Ayellow,$0Cwhite), skin ($2A) or blue.
All four Marios are shown in every frame, side by side, on any floor.
The frames are computed by a generator. For each of the 26 frames it searches the player's 8-pixel window, the missile's width and position, and for each row the player's colour and the two missile bits that reproduce the original best, with a penalty for every colour change (each change costs a DLI). It uses the positions the original really shows (with the one-pixel offset above), keeps the hammer in front and always blue, and gives the walking frames one shared colour layout (cap rows 0–2, skin rows 3–7, the rest in the player's colour), so a step changes no DLI. The result: about 87% of the original's pixels exact, and 1.72 colour changes per frame on average.
The work is split three ways:
- Main thread, after the players' moves: each Mario's frame, top line and position. When a colour layout or a line has changed, it builds the next list of DLI events: the Marios' colour changes (cached per Mario) and board 0's own colour change, merged by line, one event per line with all five colours. There are two event sets of 24 events; the VBI switches between them.
- VBI, early: positions, missile widths, the new event set and its DLI bits in the display list, and the first colours.
- VBI, late (at its very end): the shapes of the Marios whose frame or line has changed, each in its own player memory and missile bits.
9. Colour changes on the right line
The next play report said the Marios in the game looked squished compared with the frames in the sprite editor (section 13). The editor showed the right frames; the game drew them wrong. From b0023 to b0032 every colour change came one row late.
The reason is timing. In a mode E line, ANTIC's screen fetches and memory refresh leave the CPU
about 59 of the line's 114 cycles. A DLI arrives at about cycle 8 of its line, and the
STA WSYNC that waits for the horizontal blank must come before about cycle 105, or the
CPU waits for the end of the next line. That leaves about 47 CPU cycles, and the NMI plus the
operating system's dispatch take 18 of them. The first DLI loaded five colours before its
STA WSYNC and got there too late. The fix took five builds:
| Build | Change | Result |
|---|---|---|
| b0033 | The next event's colours prepared by the previous DLI; three registers loaded before WSYNC | still one line late |
| b0034 | Only A and X loaded before WSYNC | on time; but a new pixel test found 75 of 4,628 checked pixels wrong: an event two lines after another lost its colours |
| b0035 | Every event is its own piece of DLI code, with its colours as immediate operands; each event points the DLI vector at the next event's code | 16 cycles to WSYNC; the five writes in the horizontal blank; done before the next DLI. Left: 58 of 4,314 pixels wrong, all from changes one line after another Mario's, which were merged into the earlier event (one row early) |
| b0036 | Two-line events: a change one line after an event goes into the event's second half, which waits for a second WSYNC and writes all five colours again | 76 bytes per event, 3.6K for the 48 events; the emulator hung (see section 15) |
| b0037 | The hang fixed | 0 of 5,102 and 0 of 9,088 pixels wrong; in longer runs (1,500 frames per board) 6 of 12,347 one row off, where three changes fall on consecutive lines |
b0034 failed in a different way: its DLI fetched the next event after the colour writes, about 98 cycles of work, and still ran when the next DLI came two lines later. NMIs are not masked, so the second DLI ran inside the first; one event was repeated and the next lost. In b0035 each event needs no fetching at all, because the main thread writes the colours straight into the event's code, which is possible because the new code runs from RAM. One event, in its one-line form (simplified):
EVENT: pha ; (the NMI and the OS dispatch: 18 cycles)
txa
pha
lda #c0 ; the colours are operands: the main thread
ldx #c1 ; writes them when it builds the event list
sta WSYNC ; 16 cycles in: well before the deadline
sta COLPM0 ; the writes land in the horizontal blank
stx COLPM1
lda #c2
sta COLPM2
lda #c3
sta COLPM3
lda #pf2
sta COLPF2 ; board 0's own colour change
... ; (two-line events: a second WSYNC, five more writes)
lda #<NEXT ; the next event's code
sta VDSLST
lda #>NEXT
sta VDSLST+1
pla
tax
pla
rti
10. Scoreboard and title
The original's scoreboard has room for one player. A four-player scoreboard needs room on every board, so the free areas were measured over 4,000 frames per board, including the "HELP!" text (Pauline moves only on the rivet board, between x 44 and 96). Free on every board: x 0–39 on lines 0–23 (on the elevator board Donkey Kong starts at line 24), x 124–159 on lines 0–14, and x 136–159 on lines 15–27 (board 3's "HELP!" reaches x 135). The first layout cleared part of Pauline, and a later one overlapped board 3's "HELP!"; the final one:
- left: one line per player with the player's number, score and men in reserve;
- right:
HIand the high score,Land the level withBONUS, and the bonus timer below.
Lines of the cartridge's 6-line characters placed 6 lines apart touched each other. The scoreboard therefore uses 5-line characters, made from the cartridge's own font without its row 2 (and the "6" without row 1), only on the boards; the title keeps the original characters. The original's life icons are no longer drawn.
On the title screen, SELECT now chooses 1, 2, 3 or 4 players ("SELECT FOUR PLAYER GAME"; the original offered a two-player game), OPTION picks the difficulty as in the original, and START plays. A version line was added.
11. The music, the death jingle and the chest beat
Four of the play reports during development were about sound. All four go back to one difference from the original: the original stops the board when Mario dies, and the 4-player game does not.
"The music stopped" (b0021–b0022)
All tunes play from one buffer at $1C00. The board's song is unpacked there, and so
is the death jingle when Mario dies. The original reloads the song at every new life; in the
4-player game the board goes on after a death, so from the first death on the tune played the
jingle's data, with the jingle's noise distortions on the tune's notes. Measured before the fix:
after one death, the tune's channel sounded noise in 329 of 700 frames. The tune was also
restarted while a Mario was dying. b0021 and b0022 reloaded the song after the jingle and played
the jingle only for the last Mario standing; other deaths made only their bang, and the tune went
on.
"A single low note" (b0031)
That bang alone was the "single low note" heard when a Mario died while others were still playing. Since b0031 every death plays the original's bang and then the death jingle; the tunes stop for the jingle and come back two or three frames after it ends. Measured with four random players over 4,000 frames: 12 deaths, 7 jingles (deaths close together share one), and the tune back after each.
"A mystery tone" (b0032)
A tone still sounded while players died. The jingle (288 bytes) was unpacked over the tables the
other players' sound effects read (walk, jump, bang: $1C00-$1D1F), so while one Mario
died the others' effects played jingle bytes. Now the jingle is unpacked once at power-on into its
own buffer at $7800 (the multiplexer's second player/missile area, unused since b0023),
and a new hook in the sound engine plays it from there. The tone is gone, and the jingle's notes
are identical to the original's.
DONK DONK DONK (b0038)
The last report was a sound like Donkey Kong going "DONK DONK DONK" a few seconds after a player died. That is his chest beat: three beats, each played as the bang, the same sound as a death. On boards 1–3 he beats his chest every 256 frames (about 4.3 seconds); on board 0 when the count of barrels thrown, modulo 8, reaches 6. The original does the same (its beats were measured at frames 181, 868, 1124 and so on), but it freezes Donkey Kong while Mario dies and restarts the board after a death, so the beat never followed a death there. In the 4-player game it came within a few seconds of nearly every death. Since b0038 the chest beat is silent while any player is alive and not dying. He still beats his chest: the animation and the barrel timing are unchanged, and so are the death's bang and the oil drum's.
12. The CPU budget
The original's main loop updates the barrels, fireballs and other objects round-robin with whatever time is left before the next VBI, each at most every second frame. Four Marios take time from that, so the measure used was the objects' update interval: the average number of frames between two updates of the same object (2.0 is the fastest the original allows). A load test measured it with four random players on each board, and a profiler, using the coverage emulator's counters, measured the cycles of each routine.
| Build | Board 0 | Board 1 | Board 2 | Board 3 |
|---|---|---|---|---|
| Original, one player | 2.13 | 2.13 | 3.83 | 2.13 |
| b0020, flicker multiplexer | 3.23 | 2.14 | 6.24 | 2.26 |
| b0023, first one-player-one-missile version | 3.9 | 2.2 | 8.2 | 4.1 |
| b0030 | 3.30 | 2.18 | 5.60 | 2.90 |
| b0037/b0038 | 3.13 | 2.15 | 5.53 | 2.97 |
Object update interval in frames, four random players (one for the original). Lower is faster.
In b0023 building the DLI events cost about 4,100 cycles a frame. b0024–b0027 cut it down: events rebuilt only when a frame or line had changed, cached change lists per Mario, a five-way merge, one colour layout for the whole walk, a tuned colour-change penalty in the frame generator, one variable fewer in the per-player swap, and no item checks on board 0. In b0030 the four-Mario display cost about 2,400 cycles a frame (the flicker multiplexer about 3,900), with 2.4–5.9 DLI events per frame. The rest of the 4-player cost is the original's own per-Mario work, run four times (Mario's routine and the object hit checks), and the copying of the players' state (about 1,550 cycles). The Marios' update ran in 92–98% of frames.
13. A sprite editor
A sprite editor was requested and added (b0031), so that the computed four-Mario frames can be changed by hand. It is a web page that works under the same rules as the hardware: each row of a frame is one player row of 8 pixels in one colour (the player's own, skin or blue) plus the two blue blocks of the missile, 1, 2 or 4 pixels wide and at the same place in every row. The original Mario is shown as dots for reference; counters show how close a frame is to the original and how many colour changes (DLIs) it costs; and a preview shows the result in a 4:3 TV picture of the first board, in all four players' colours, with the walk, climb, hammer, jump and death animations. Saved edits replace the computed frames in the next build. The editor has its own test, run in headless Chrome (load, edit, save, reload, reset). In build 0038 all frames are still the computed ones.
14. Automated testing
All tests ran headless: the emulator with dummy video and audio, at turbo speed, on a private control socket, with no windows and without touching any other session's emulator. They used an emulated Atari 800 with Atari's OS-B ROM, and an emulated XL for the XL check.
| Test | What it checks |
|---|---|
| Source checks | The generated source rebuilds the original cartridge byte for byte; every build checks that each label of the original is still at its original address, and lists the byte ranges that differ from the cartridge. |
| Write-watch | No write into the program's address space during 4,000 frames on each board (the traps). |
| Smoke test | The cartridge, the program file and the boot disk boot on an Atari 800 with OS-B, and the cartridge on an XL; a game starts, the board appears and Mario moves. |
| Event tests | Set-ups made by writing RAM through the test mailbox: walking off a girder's end, Mario lands alive; Mario 1's hammer kills Mario 2 next to him, the points go to Mario 1 and the death's bang plays; jumping at a hammer takes it once and it stays on the board; on board 1 over 600 frames Donkey Kong beats his chest twice and no bang plays. |
| Soak test | Four players with random joysticks on each board: the game keeps running; Mario updates and DLI events per frame. |
| Pixel test | Four random players on each board; screenshots taken only after a complete VBI; every player pixel of every Mario that did not move is checked against the colour its row should have. |
| CPU tests | The objects' update interval per board, and the cycles spent in each routine. |
| Editor test | The sprite editor in headless Chrome. |
Questions about the original's behaviour were answered by running the original cartridge in the same harness: the barrel on the ladder (section 7) and the times of Donkey Kong's chest beats (section 11).
15. Bugs, including the agent's own
$11 to Mario's frame number, past the end
of the frame tables. Now a Mario who already holds a hammer does not take another; an event test
checks it.TAX in the event builder made X zero, so every new event list
was written into set 0, while the DLIs were running that set: stray DLI bits, and zero colours in
set 1. The emulator hung. Fixed in b0037.16. Timeline and numbers
| Time | Build | Step |
|---|---|---|
| 6 Oct 23:45 | — | Project set up; tools taken over from the Joust 4-Player work |
| 23:47–00:30 | — | Coverage, generated source (byte-identical), the first memory map |
| 7 Oct 00:01–00:09 | b0001–b0004 | The original in the 32K 4-player layout; four traps removed |
| 00:19–00:30 | b0005–b0010 | Four Marios (one colour each), joysticks 3–4, the new rules, scoreboard, SELECT 1–4 players, version line, test mailbox |
| 00:43–01:08 | b0011–b0020 | Full-colour Marios with a flicker multiplexer; hammer grab fix; scoreboard with 5-line characters; the winner in the end animation |
| 07:14–07:16 | b0021–b0022 | The music after a death |
| 07:41–08:05 | b0023–b0030 | Four Marios at once: one player and one missile each; the CPU cost cut; the sliding playfield fixed |
| 08:43 | b0031 | Every death plays the bang and the jingle; the bonus timer kills nobody; the sprite editor |
| 09:02 | b0032 | The death jingle in its own buffer (the "mystery tone") |
| 09:05–09:38 | b0033–b0037 | Colour changes on the right line; the pixel test |
| 09:48 | b0038 | Version 0.1, build 0038: Donkey Kong's chest beat silent while anybody plays |
| Cartridge | 32K XEGS bank-switched (type 12): the original's 16K at $8000-$BFFF, patched in place; the new code and data (9,601 bytes, including 3.6K of DLI event code) and 684 bytes of variables at $4000-$682C, leaving about 4K of $4000-$77FF free; the death jingle's buffer at $7800 |
| Source | 2,065 lines of new assembly in one new module; the generated source of the original, forked, with 90 lines changed (all marked) by recorded patch scripts, at about 35 places |
| Builds | 38, every one kept (cartridge, raw ROM, program file, boot disk, and the sources that made it) |
| Tests | source checks, write-watch, smoke (4 boots), event tests, soak and pixel tests on all four boards, CPU tests, editor test |
17. Known limits and open questions
- The Marios are approximations. One player and one missile cannot reproduce the original three-player Mario exactly: about 87% of the original's pixels are exact. Every row of a Mario's player layer has one colour; all blue parts share one blue, and the hammer a Mario holds is always blue. Where three colour changes fall on consecutive lines, one comes a row early (6 of 12,347 checked pixels in the longest pixel test).
- Slower enemies. With four players the objects are updated less often than in the original one-player game: every 3.13 frames instead of 2.13 on board 0, 5.53 instead of 3.83 on board 2, 2.97 instead of 2.13 on board 3 (board 1 is nearly unchanged). Barrels, fireballs and the other objects move correspondingly more slowly there. The Marios' own update was skipped in 2–8% of frames in the measurements.
- Rules changed on purpose. Falls don't kill; hammers are unlimited and kill other Marios; the bonus timer never kills and the board's winner gets no bonus when it has run out; the enemies chase one Mario at a time, changing every 128 frames; Donkey Kong's chest beat is silent while anybody plays. Barrels rolling down ladders still kill, as in the original.
- Sound. Deaths close together share one jingle, and the music stops for each jingle. The silence at the moment of a death, which the original always had, is kept only for the last Mario standing.
- Hardware. Three or four players need an Atari 400 or 800, the only models with joystick ports 3 and 4; on an XL/XE the game offers one or two players. The cartridge needs 32K of RAM, the disk and the program file 48K.
- Emulator only. The development log records no testing on real Atari hardware and no sessions with four people playing. The random players of the soak and pixel tests do not play like people. Several bugs were found only through play reports, so bugs in situations neither the tests nor the players provoked may remain. The development tests used Atari's OS-B; the online arcade boots the free AltirraOS instead. Before the game went into the arcade it was checked there: on AltirraOS the game offers four players, and games with one player and three CPU-steered Marios ran from the title through to GAME OVER, with every copy of the machine in step.
18. Lessons
- Check the original before fixing a "bug". The death on the ladder was the original's rule, and the chest beat after deaths was the original's behaviour; both only looked wrong in a game that goes on after a death.
- Interrupt code has a deadline in cycles. For ten builds every colour change came one row
late, and the cause was a handful of instructions before a
STA WSYNC. A test that compares every pixel with the frame data turned "squished" into a number and found the next problem (nested DLIs) as well. - A shared buffer is shared. The death jingle broke the music twice, first by overwriting the song and then by overwriting the effects' tables.
- A long VBI moves the screen. Work added to the VBI before the display list restart delayed the whole playfield.
- In-place patching suits a full ROM. Keeping every original address made each change small and checkable by the build, at the price of putting all new code in RAM.
Credits
- Donkey Kong © 1981 Nintendo. The Atari 400/800 version © 1983 Atari, programmed by Landon Dyer, is the version that was converted, and its program, patched in place, still runs the game: every one of its routines is at its original address.
- The development log records no outside disassembly, source code or notes: the agent generated the source from the cartridge itself with the project's own tools, carried over from the Joust and Demon Attack 4-player work (section 3).
- Tools: the cc65 assembler and linker (ca65 and ld65) by Ullrich von Bassewitz and the cc65 contributors; the atari800 emulator by the Atari800 development team (the project's copy with a control socket, and a private build with coverage counters, a fixed random seed and a write-watch; the online arcade runs atari800 compiled to WebAssembly); ImageMagick for the screenshots; Python for the tools and tests; headless Chrome for the sprite editor's test; Atari's OS-B ROM in the tests; and AltirraOS by Avery Lee, the free replacement operating system the online arcade boots.
- The 4-player modification, its tools and its tests were made by Claude Code with Opus 5.5, as an experiment.