← Experimental Atari Multiplayer Arcade

How Wizard of Wor 4-Player Was Made

An experimental four-player modification of Roklan's 1981 Atari 400/800 version of Wizard of Wor, built on 5 and 6 October 2026

This is an experimental modification of Roklan's 1981 Wizard of Wor for the Atari 400 and 800, changed so that four people can play at the same time with joysticks 1 to 4. It was made by AI coding agents (Claude Code), working from a disassembly of the original program, and it was checked with automated tests in an emulator. This page describes what the agents did, how the result was checked, and what is still imperfect or uncertain. It is based on the project's own development log, source code and test records.

Contents
  1. The goal
  2. How the original works
  3. From disk to source code
  4. The hardware budget: four players, four missiles
  5. Four worriors in a two-worrior program
  6. Cages, corners and monsters
  7. Scores, the top line and the title screen
  8. Bonus dungeons and high scores
  9. Timing
  10. Testing
  11. Bugs and fixes
  12. Version 1.3: drop-in rejoin
  13. What is still imperfect or uncertain
  14. Lessons
  15. Version history
  16. Credits

1. The goal

The brief was short: four simultaneous players on the Atari 800's four joystick ports, two new worrior colours, and the two new players starting in the top-left and top-right corners of the dungeon. The commented disassembly was to be kept as a separate piece of work. The Atari 400 and 800 have four joystick ports; the later XL and XE models have two. The original game supports one or two players.

PlayerColourJoystickStarts inScore shown in
1Yellowport 2bottom-right cage (as in the original)right score box
2Blueport 1bottom-left cage (as in the original)left score box
3Green (new)port 3top-left cornertop line, left
4Purple (new)port 4top-right cornertop line, right

The order is the original's: a one-player game is the yellow worrior on joystick 2, and two players add blue on joystick 1. Joysticks 3 and 4 add green and purple.

The original Wizard of Wor title screen with two rows of worrior icons, yellow and blue, and the 1981 Roklan and Bally/Midway copyright lines
The original title screen, set to two players.
The 4-player title screen in version 1.2 with four rows of worrior icons in yellow, blue, green and purple, and the line 4-PLAYER V1.2 B0007 under the logo
The four-player title in v1.2: one row of icons per player, and the version and build number under the logo.

2. How the original works

Roklan Corporation's Atari 400/800 version of the Midway arcade game came on a boot disk. Its title screen reads "COPYRIGHTED (C)(P) BY ROKLAN CORP 1981 / LICENSED FROM BALLY/MIDWAY MFG CO 1981". Everything below comes from the reconstructed source and from checks in the emulator.

The disk and the boot loader

SectorsContents
1-3boot loader, loaded by the OS to $0600-$077F
4not loaded; read once (and the data ignored) by the copy protection
5-185the game, $1000-$6A6B: 181 sectors, 23,148 bytes after merging
720the HALL OF FAME high-score table, read at power-on and written back on request

The loader refuses to run with a cartridge inserted, stores $DA at $03FF (a value the copy protection needs later), and reads the first 12 game sectors straight to $1000. After that it reads sectors in pairs into $7800 and $7900, re-reading the second sector until the order flags (byte $7F of each sector) differ, and copies 2 × 127 bytes in the order the flag gives. Then come 16 more straight sectors, and so on. At the end it sets DOSVEC to $165C, the real entry point. The repeated re-read looks like a duplicate-sector check on the original disk; on the disk image the flags simply differ, and what exactly the original disk did there is not known.

Memory

AddressContents
$0080-$00FFzero-page variables
$0600-$065Fsix monster slots of 16 bytes
$0660-$06FDgame variables
$0800-$0BFFplayer/missile memory at double-line resolution (PMBASE = $08); the title text shares the unused low part
$0C00-$0DFFRAM font: the OS glyphs plus score-box, radar-frame and arrow glyphs
$0E00-$0F5Bhigh-score work area, the session table, the sector-720 buffer
$1000-$165Bgame font, title-logo font, display lists
$165C-$4BA4code
$4BA5-$67B7texts, tables, 20 mazes, monster sprites, worrior shapes
$7500-$7E87playfield: ANTIC mode D, 160×61 pixels in 4 colours, 40 bytes per line
$7E88-$7F3Bcages, the banner line, score boxes and radar frame

The game never touches $0401-$05FF, $6A6C-$74D7 or anything from $8100 up. That free space later held the four-player additions.

Who runs when

Players, missiles and monsters

The Atari's GTIA chip has four "players" (8-pixel-wide sprites, one colour each) and four 2-pixel "missiles". The original uses all of them for two worriors. Each worrior is two players: a body (P0 blue, P1 yellow) and a red gun overlay (P2, P3), 10 rows high. Missiles M0 and M1 are the worriors' shots, M2 and M3 the monsters'. Below the maze, P0-P2 are reused as radar blips, showing slots 0-2 on even frames and 3-5 on odd frames.

The monsters are not hardware sprites. They are 12×8-pixel software sprites drawn into the bitmap: the old image is erased with an AND mask and the new one ORed in, using 108 pre-shifted images. The maze is 11×6 cells; each maze is stored as 33 bytes, one nibble of open directions per cell. Scores are kept directly in the score boxes as screen codes and added in decimal mode.

The original game in play: a blue maze with monsters, the yellow worrior in its cage at the bottom right with reserve worriors beside it, and the blue and yellow score boxes below
The original, one player. The yellow worrior waits in its cage at the bottom right with its reserves beside it; the red digit is the cage-door countdown. Each worrior figure here is two hardware players, a yellow body and a red gun.

Copy protection

  1. The boot loader puts $DA at $03FF.
  2. At the first game start, a routine uses that value to rewrite a decoy routine into the real check (from $DA+$2A = $04 it builds the real operands). The rewritten check reads disk sector 4 (the result is not tested), stores $60 (RTS) at $33F6 and sets a flag at $0400.
  3. The routine that prints the big banners (GET READY, GO, GAME OVER) ends with a BCS to $33F6. On disk that byte is $20, so without the patch the CPU executes a JSR $BEA5 and crashes at the first GET READY.

All of this was confirmed in the emulator: before the first game $33F6 holds $20; after START it holds $60 and $0400 = 1. Why the check reads sector 4 at all is not clear, since the status is compared but never tested.

Small oddities found in the original

3. From disk to source code

A first attempt that read the disk wrong

An earlier attempt, in January 2026, had disassembled the raw disk sectors 5-185 as if the loader copied them straight to $1000. It rebuilt the disk image byte for byte, but the program in memory is different: after the first 12 sectors the loader merges sector pairs and keeps 127 bytes of each. From $167F on, every address in that disassembly was wrong, and 254 of every 256 bytes were shifted. It also took $1AF2 for the entry point; that address is an RTS that the OS calls as its initialisation vector, and the real entry point is $165C. The old work was left untouched and marked as not to be used as a reference.

The program as it is in memory

The agent wrote a Python re-implementation of the boot loader. Its output, the memory image $1000-$6A6B (23,148 bytes), matched a RAM dump of the running game byte for byte. Everything after this was built from that image.

A source that rebuilds the original and can move

The code is fully symbolic and can be moved. The data segments stay at their original addresses because many tables are addressed through immediate high bytes, each of which would have to become a symbol before the data could move.

The commented disassembly

While the four-player work went on, a second agent turned the first-pass source and symbols into a fully commented disassembly of about 11,800 lines, kept separately from the four-player project. Every code line keeps its original address and bytes:

        lda  #<(hs_buf+7)               ; 1677  A9 E3     hs_ptr -> first score digit of the hall of fame

The four-player source had already been forked from the first pass, so it still uses the first-pass names, including the wrong ones. The commented disassembly lists the corrections.

4. The hardware budget: four players, four missiles

This was the central problem. The original uses all four players for two worriors and all four missiles for two worriors' shots plus two monster shots. Four worriors drawn the original way would need eight players.

One player per worrior

A Python script reads the original body and gun shapes from the memory image and ORs them together into single-player shapes: two walking frames and two firing stages per direction, 10 bytes each. Worrior i is player i: P0 blue, P1 yellow, P2 green, P3 purple. The caged figures and the bonus-screen figures use the same shapes. The cost is visible: the worriors in the maze are now one colour each, without the red gun. (The reserve icons and the title icons are drawn with characters and bitmap graphics, not hardware players, and keep it.)

One missile per worrior, two of them shared

Missile i is worrior i's shot. M2 and M3 are shared with the monsters and the Wizard: a monster may fire with M2 or M3 while green's or purple's gun is idle, and a byte per missile (m_own, $FF = monster shot) records whose shot is in it.

This created a race between the two halves of the program. Monster shots are assigned by the main loop, and fire buttons are read in the VBI, which can interrupt the main loop at any instruction. The main loop therefore sets a lock byte (mb_lock) while it assigns a monster shot, and the VBI does not hand M2 or M3 to a worrior while the lock is set or a monster request is pending (an excerpt from the fire-button loop, with one label renamed):

        lda     m_y,x           ; missile busy (own shot, or monster shot)
        bne     skip_fire
        cpx     #2
        bcc     :+
        lda     m_dir,x         ; M2/M3: also free of monster requests
        bne     skip_fire
        lda     mb_lock
        bne     skip_fire

The Wizard's fireballs use the same missiles, and he may only take one that is free or already his.

Joysticks 3 and 4

On the 400/800, joysticks 1 and 2 are read from PIA port A and joysticks 3 and 4 from port B, one nibble per stick. The fire buttons are TRIG0-TRIG3, read from the hardware as the original does. One small routine gives every worrior its stick:

stick_nibble4:
        cpx     #2
        bcs     :+
        lda     PORTA
        bcc     :++
:       lda     PORTB
:       pha
        txa
        lsr     a               ; odd worrior -> high nibble
        pla
        bcc     :+
        lsr     a
        lsr     a
        lsr     a
        lsr     a
:       rts

An XL or XE has only two ports. At power-on the game looks at the OS's RESET vector: the 400/800 OS lives at $D800-$FFFF, while the XL/XE OS starts at $C000, so a vector below $D800 means XL/XE, and SELECT then offers only 1-2 players. This was checked with four operating systems: the 800's OS-B ($F125) and AltirraOS-800 ($EFF7) allow 1-4 players, the real XL OS ($C2AA) allows 1-2, and AltirraOS-XL ($EEB8) is not recognised.

5. Four worriors in a two-worrior program

6. Cages, corners and monsters

Blue and yellow keep the original cages: the waiting worrior stands in its cage, the door opens after a second, a big digit counts down from 10, and at 0 the worrior is pushed out. Green and purple have no cage. They start in the corner cells 0 and 10 of the 11×6 maze, where a waiting worrior blinks and cannot be hurt. It enters when its player pushes the stick, or by itself when a 10-step countdown, shown as a red digit on the top line, runs out. Every worrior, new or returning, now goes through one routine that puts it back at its start and sets it waiting.

A four-player game in dungeon 2: green and purple worriors in the upper part of the maze, blue and yellow near the bottom, exploding monsters, the green and purple counts on the top line and blue and yellow score boxes below
Dungeon 2 of a four-player test game under random input. Green (top left) and purple (top right) have their worriors-left counts and scores on the top line; blue and yellow keep the original score boxes.
The Wizard of Wor phase with the maze drawn in red, the banner WIZARD OF WOR, the Wizard in the middle of the maze, and green and purple waiting in the top corners
The Wizard phase in a four-player test game. The Wizard can appear in line with any of the four worriors.

7. Scores, the top line and the title screen

A new line above the maze

Green and purple needed a place for their scores and remaining worriors. The game display lists got an ANTIC mode-6 text line at the top, and the maze stays on the same scanline as before:

dl_game4:
        .byte   $70,$70
        .byte   $C6                     ; DLI + LMS mode 6: the HUD
        .word   hud_text
        .byte   $4D
        .word   bitmap
        .res    59,$0D
        .byte   $8D,$04,$84,$8D,$07,$07,$07
        .byte   $41
        .word   dl_game4

In mode 6 each character takes its colour from one of four colour registers, chosen by the top two bits of its code. At the start of every game VBI the colour registers COLPF0 and COLPF1 are set to green ($C8) and purple ($5A) and the RAM font is selected for that line; a DLI at its end restores the maze colours and font after a WSYNC. The line's 20 characters hold green's icon and worriors left, its countdown digit and 6-digit score on the left, and the same for purple, mirrored, on the right. The score routine was rewritten to add points to any of the four scores, two in the original score boxes and two in this line.

The title screen

SELECT cycles 1, 2, 3, 4 players and the title shows one row of worrior icons per player, as many icons as each player gets (OPTION still sets 3, 5 or 7). A new title display list has four icon rows; two extra DLIs recolour one colour register for the green and purple rows. Since v1.2 a line under the logo shows the version and build number, so it is clear which disk is running. In v1.3 a second line shows the drop-in setting; the top margin was cut by 8 lines so the screen keeps its height of 216 lines, because the online arcade crops the picture at 224 lines.

8. Bonus dungeons and high scores

The BONUS PLAYER screen with four worrior figures: green and blue on the left, yellow and purple on the right
The BONUS PLAYER screen with all four worriors.
The HIGH SCORES table with four new entries marked by arrows: two plain red arrows, one blue and one yellow
The session table after a four-player test game: blue's and yellow's entries have arrows in their colours, green's and purple's plain red ones.

9. Timing

The development log records no speed work for this conversion. Monsters still move one per frame inside the bottom DLI, a worrior still walks one pixel every 3 frames, and the dungeon progression test found the Worluk and Wizard phases and the transitions between dungeons comparable in length to the original's. The timing problems that did come up were about when code runs within a frame:

10. Testing

All tests ran headless: an atari800 emulator with no window, a private control socket and the real Atari 800 OS-B ROM, in Atari 800 mode because only the 800 has joystick ports 3 and 4. The tests read memory, set joysticks, press console keys and save screenshots. For reaching later dungeons they use a test cheat that kills all monsters at once.

TestWhat it checks
Random-input soakWhole games with 1-4 players mashing all joysticks. Fails if the CPU runs outside the program (crash), if a game does not reach GAME OVER and the title (hang), if lives or scores go out of range, or if a missile stays blocked ("frozen missile").
Soak with the kill-all cheatRandom play with all monsters killed every 25-30 seconds: four-player games reached dungeon 6 and three-player games dungeon 5, with about 30 deaths per game, monster shots in M2/M3 and points for all four players.
Dungeon progressionDungeons 1-9 with the Worluk and Wizard phases, with two and four players, timing each phase; it can also run the original disk for comparison.
Targeted checksWorrior against worrior, the XL/XE guard on four operating systems, the original one-player game using joystick 2.
VBI timing probeHow far down the screen the game VBI runs.
Drop-in (v1.3)60 checks of the rejoin rules and the D key; see section 12.

The full set on the v1.3 build passed: soaks with 1, 2 and 4 players (4 players twice), a four-player kill-all soak to dungeon 5 with drop-in off, a four-player drop-in soak (12 rejoins, then game over once the fire buttons were left alone) and a two-player one (4 rejoins), the progression test with four players to dungeon 5, and all 60 drop-in checks. The drop-in tests also passed on AltirraOS-800, the operating system the online arcade uses.

Problems in the tests themselves

11. Bugs and fixes

Green's points went to yellow (first playable build, fixed in the next). Scoring still went through the original score routine, which only knows the blue and yellow score boxes, so green's points landed on yellow's score. A new score routine for all four players replaced it. The cause was found by watching every write to the "whose score" variable with the emulator's memory watch.
Green and purple could stop firing for good (fixed in the second build). When a monster's or the Wizard's shot ended, the original left the missile's Y position set; with only monsters using M2/M3 it never mattered. For green and purple a non-zero Y means "missile busy", so after the first monster shot in their missile they could never fire again. The soak test's frozen-missile check caught it, and the Y position is now cleared in all four places a monster shot ends.
A colon instead of 10 (fixed in the second build). The new countdown on the top line showed : for 10, the screen code after 9. Green's and purple's scores also showed nothing until their first points.
The green "ghost" (found after v1.0, fixed in v1.1). After the first release was played, the project's owner reported a second copy of the green worrior appearing in the middle of the screen on the same lines, and purple's upper rows sometimes missing. Cause: at the bottom of the screen the radar DLI moves P0-P2 to the radar blips in the middle of the screen, and the cage DLI parks P2 and P3 at position 0. The game VBI only put the worriors back at the end of its work. The original has the same structure, but its shorter VBI rarely ran that late; the four-player VBI often did, so the top rows of the maze were drawn with the radar's and cage's positions. Measured: 13 of 46 frames. The fix keeps the positions in a shadow table and writes all four to the hardware as the first thing in the game VBI. Measured after the fix: 0 of about 1,800 frames.
The bug: a partial copy of the green worrior near the top middle of the maze, the real green worrior at the top left with its upper rows missing, and purple at the top right with its upper rows missing
Before the fix: a partial green copy at the top middle, while the real green and purple worriors near the top corners are missing their upper rows.
After the fix: green and purple worriors drawn complete in the top-left and top-right corners
After the fix (v1.3 shown): green and purple complete in their corners.
Not a bug: the test cheat. Used within about 20 seconds of a dungeon start, the kill-all cheat deadlocked the game, and it deadlocked the original the same way (a monster-turn toggle freezes in a wait loop). The cheat now ends that phase first.

12. Version 1.3: drop-in rejoin

For the online arcade, a player who had lost all their worriors had to watch until everybody else was out. The option chosen was drop-in: such a player can come back with a fresh set of worriors and a score of 0. It is a title option, on by default.

The rules

The hint

While a player may come back, FIRE blinks in its place (on and off every 16 frames, from bit 4 of the frame counter), but not during GET READY. For blue and yellow it is written with ROM letters into the top edge of the score box. For green and purple it has to fit over three cells of the top line (icon, worriors left, countdown), so "FIRE" was drawn as three new glyphs ("FI", "R", "E", 24 pixels in all) in the font slots of <, = and >; all of the game's text tables were checked to confirm those three characters never appear on a screen that uses that font. The score bytes that the online arcade reads are never used for the hint.

A four-player game in which the blue player is out: the word FIRE appears in the top edge of the blue score box above the score 1230
Blue is out with 1230 points: "FIRE" blinks in the top edge of its score box. Pressing fire brings blue back through the cage with a score of 0; the 1230 is kept for the high-score table.

The title option

The D key toggles drop-in on the title screen (SHIFT and CONTROL are ignored), and a new line shows [D] DROP-IN ON or OFF. The setting is kept in RAM only and is on at power-on and after RESET. Keyboard input left over from the attract screens is cleared, so a D pressed there cannot toggle it. The online arcade cannot send key presses, so there drop-in is always on.

The v1.3 title screen with the lines 4-PLAYER V1.3 B0008 and [D] DROP-IN ON under the logo and one row of yellow worrior icons
The v1.3 title, set to one player, with the drop-in line under the version.

Two problems handled before release

Reserve icons ran the VBI into the monster interrupts. After a rejoin, blue's and yellow's reserve worriors have to be redrawn as bitmap icons. The first version drew them all at once. A double rejoin with 7 worriors each ran that VBI to VCOUNT 115 (scanline 230), into the bottom DLIs, which blit monsters with the same source and destination pointers. Now at most one icon is drawn per frame, about 20 scanlines of work, and the rejoin frame ends by VCOUNT 27. Because the redraw follows the current number of worriors left, the icons also come out right if the worrior leaves the cage before they are all drawn; that was tested.
Decimal mode inside an interrupt. The 6502 does not clear its decimal flag when an interrupt starts, and the score routine runs with SED. The new drop-in code, which runs in the VBI, therefore begins with CLD; the RTI restores the main loop's flags. The original VBI code has the same exposure (its additions would run in decimal mode if a VBI landed inside the score routine). That was noted and left unchanged.

Memory and testing

The new state took a few bytes in the free page-4/5 area (a per-player drop-in byte at $04FC-$04FF, the setting at $0501, the icon redraw counters at $050C-$050D, the best runs at $05E0-$05F7). The RAM addresses the online arcade reads (screen, number of players, who is in the game, the four scores) did not change. A dedicated test covered: the option at power-on and the D key; a player who is out with the fire button held is not let back in for 120 frames while the hint blinks; a fresh press brings it back with the right number of worriors, score 0, waiting in its cage, best run kept, reserve icons redrawn; it leaves by the countdown; yellow rejoins and walks out at once; green's hint, rejoin and stick entry; purple may still come back while the last other worrior is dying; when all four are killed together nobody comes back and the high-score tables hold the best runs (1230 and 2470, not the final 40 and 10); in a two-player game the fire buttons on joysticks 3 and 4 do nothing; with drop-in off there is no hint and the game ends as before. All 60 checks passed.

13. What is still imperfect or uncertain

14. Lessons

15. Version history

BuildDate and timeVersionChanges
b00015 Oct 2026, 12:460.1First playable build (internal)
b00025 Oct, 13:030.2Internal: scores for all four players, M2/M3 freed after monster and Wizard shots, red countdown on the top line, scores shown from 0, BONUS PLAYER screen with four figures, high scores for green and purple
b00035 Oct, 13:121.0First release: XL/XE guard (1-2 players there), dead code removed
b00045 Oct, 14:001.1Fix for the green "ghost": player positions set at the start of the VBI
b00065 Oct, 14:101.2Version and build number on the title screen
b00075 Oct, 14:111.2Build counter fixed, no code change
b00086 Oct, 00:491.3Drop-in rejoin (title option D, on by default), FIRE hint, best run kept for the high scores

There is no b0005: the new build script miscounted once. From v1.2 on every build was kept automatically together with the sources that produced it, and the earlier four builds were recovered from backups. Source files were copied before every edit, and the development log was only ever appended to.

WhenStep
January 2026First disassembly attempt, from raw disk sectors (wrong past $167F)
5 Oct, 08:06-08:35Setup, emulator harness, the real memory image, a byte-identical and relocatable source
5 Oct, 12:20-12:50Four-player design and first playable build
5 Oct, 12:22-13:18Commented disassembly, by a second agent in parallel
5 Oct, 12:50-13:25Bonus and high-score screens, tests, XL/XE guard; v1.0
5 Oct, 13:55-14:11Ghost fix (v1.1), version on the title (v1.2)
6 Oct, 00:10-00:50Drop-in rejoin, tests; v1.3

Credits