← 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.
- What the agents did. One agent reconstructed the program as it sits in memory from the original boot disk and turned it into assembler source that rebuilds the original byte for byte and can be moved in memory. It then changed that source: one hardware sprite and one missile per worrior, two new worriors (green and purple) starting in the top corners, a score line for them, four-player title, bonus and high-score screens, and later a drop-in option for players who are out. A second agent, working in parallel, turned the same source into a fully commented disassembly that is kept as a separate piece of work.
- How it was checked. Headless tests on an emulated Atari 800: random-input games played to game over, dungeon progression through the Worluk and Wizard phases (also run against the original for comparison), targeted tests of scoring, rejoining and the title options, and timing probes. All of the testing described here was done in emulation.
- What is imperfect. Some visual compromises, an XL/XE detection gap, and open questions about the original are listed in section 13.
- The goal
- How the original works
- From disk to source code
- The hardware budget: four players, four missiles
- Four worriors in a two-worrior program
- Cages, corners and monsters
- Scores, the top line and the title screen
- Bonus dungeons and high scores
- Timing
- Testing
- Bugs and fixes
- Version 1.3: drop-in rejoin
- What is still imperfect or uncertain
- Lessons
- Version history
- 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.
| Player | Colour | Joystick | Starts in | Score shown in |
|---|---|---|---|---|
| 1 | Yellow | port 2 | bottom-right cage (as in the original) | right score box |
| 2 | Blue | port 1 | bottom-left cage (as in the original) | left score box |
| 3 | Green (new) | port 3 | top-left corner | top line, left |
| 4 | Purple (new) | port 4 | top-right corner | top 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.
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
| Sectors | Contents |
|---|---|
| 1-3 | boot loader, loaded by the OS to $0600-$077F |
| 4 | not loaded; read once (and the data ignored) by the copy protection |
| 5-185 | the game, $1000-$6A6B: 181 sectors, 23,148 bytes after merging |
| 720 | the 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
| Address | Contents |
|---|---|
| $0080-$00FF | zero-page variables |
| $0600-$065F | six monster slots of 16 bytes |
| $0660-$06FD | game variables |
| $0800-$0BFF | player/missile memory at double-line resolution (PMBASE = $08); the title text shares the unused low part |
| $0C00-$0DFF | RAM font: the OS glyphs plus score-box, radar-frame and arrow glyphs |
| $0E00-$0F5B | high-score work area, the session table, the sector-720 buffer |
| $1000-$165B | game font, title-logo font, display lists |
| $165C-$4BA4 | code |
| $4BA5-$67B7 | texts, tables, 20 mazes, monster sprites, worrior shapes |
| $7500-$7E87 | playfield: ANTIC mode D, 160×61 pixels in 4 colours, 40 bytes per line |
| $7E88-$7F3B | cages, 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
- The deferred vertical-blank interrupt (VBI) runs the worriors: joysticks and fire buttons, movement (one pixel every 3 frames), shots, hits, the death animation, cage countdowns, sound effects, player and missile positions, and the monsters' shots.
- Display-list interrupts (DLIs): DLI #0 at the end of the maze draws the waiting worriors in their cages with the same hardware players and latches the missile-to-player collisions; DLI #1 switches the character set for the score and radar lines; DLI #2 positions the radar blips and then moves one monster. Monster movement runs inside an interrupt near the bottom of the screen.
- The main loop, once per frame: console keys and pause, worrior shots against monsters (scoring), speed control, radar drawing, monster decisions, spawning, and the dungeon sequence (Worluk, Wizard, bonus screens, next maze).
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.
Copy protection
- The boot loader puts
$DAat$03FF. - At the first game start, a routine uses that value to rewrite a decoy routine into
the real check (from
$DA+$2A=$04it builds the real operands). The rewritten check reads disk sector 4 (the result is not tested), stores$60(RTS) at$33F6and sets a flag at$0400. - The routine that prints the big banners (GET READY, GO, GAME OVER) ends with a
BCSto$33F6. On disk that byte is$20, so without the patch the CPU executes aJSR $BEA5and 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
- The game DLI restores registers with
PLA / TYA / PLA / TAX / PLA, aTYAwhereTAYwas meant. It is harmless because Y is not changed on that path. - The Worluk phase erases a missile with X still holding the radar routine's slot offset, so it clears unrelated bytes instead. Harmless in practice.
- An unused display list, a never-called routine that would erase
$1000-$8FFF, and a fragment of a disk-mastering program left at the end of the image.
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
- A tracing disassembler follows the code from its entry points so code and data are told apart, and a source generator writes ca65 assembler source with real mnemonics, symbolic labels and named variables, taking its names from a separate symbols file.
- The generated source reassembled byte-identical to the memory image.
- Relocation test. Byte-identical only shows that the addresses came out the same. To
find addresses that were still hard-coded, the code segment was linked at
$8100and the old code area filled with$00(theBRKopcode), so any stray jump into it would crash. Attract mode and a full two-player game ran to GAME OVER, and the program counter was never seen in the old area. - Free memory, measured. Watching RAM during attract mode and play showed that
$0480-$05FFand$8100-$BBFFwere never touched. It also showed that the bitmap clear routine wipes$7500-$80FF, so nothing new could go there.
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
- Every routine has a header (purpose, inputs, outputs) with automatically generated "called by / jumped to from" cross references, and every branch target has a name.
- Display lists are decoded one instruction per line, texts are shown as strings, the 20 mazes as ASCII maps, and the monster and worrior sprites as pixel pictures.
- The variable names were redone from a census of every use of every RAM address. Several
first-pass names turned out to be wrong: the monster X and Y copies were swapped, the "monsters
alive" counter actually counts empty slots, the cage-door "warning" routines actually
open the doors, and the flag first called "double score" is the Wizard's frame divider (the
real double-score flag is the next byte,
$BE). - The boot loader got its own hand-written source, verified against the 384 bytes of sectors 1-3.
- Both sources regenerate and reassemble byte-identical; a fresh build from a clean copy produced the same source again.
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
- Per-player variables. The original keeps blue's and yellow's state in 2-byte slots in
zero page and page 6. These became 4-entry arrays at
$0480, in page 4, which neither the original nor the OS uses. Because the source is symbolic, this was mostly a change of equates: the assembler picked the new (absolute instead of zero-page) addressing modes and instruction lengths by itself. - Loops instead of unrolled code. The original handles blue and yellow with two unrolled
copies of the same code. Those were replaced by calls to new routines that loop over four
worriors. The original code segment shrank from
$165C-$4BA4to$165C-$439B, leaving about 2 KB free there. - New code went to
$8100upwards ($8100-$8F05in v1.3, about 1,850 lines of new assembly). The edits inside the original source are marked; 76 lines carry the marker. - A recorded patch script made the first round of edits. It finds each edit by the original address kept in the line comments and stops if an anchor is not found exactly once. It was kept for the record; after that the edited source became the master copy.
- A plain boot loader replaced the original: it loads the original part at
$1000and the new part at$8100, so the game needs a 48K machine with no cartridge. The copy protection was removed. - Separate scratch memory for the two threads. VBI code uses its own zero-page pointers and temporaries, main-loop code uses others, and the two are never mixed, because the VBI can interrupt the main loop anywhere.
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.
- Monster start positions. Two of the original's six starting monsters begin in the top corners. With three or four players those two start two rows lower instead.
- Whom the monsters hunt. In the original, odd monster slots hunt blue and even slots hunt yellow. A new routine spreads the monsters over all four worriors and, as in the original, only hunts worriors that have moved their stick at least once.
- The Worluk and the Wizard appear in a cell in line with an active worrior, now any of the four, under the same rules as before. Their touch is detected through the player-to-playfield collision registers, now latched for all four players, together with the missile-to-player collisions, by the DLI at the end of the maze.
- Shooting another worrior is worth 1000 points and kills it, as in the original. A test had green shoot purple: purple died and green scored 1000.
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
- Bonus worriors. As in the original, dungeons 3 and 12 give a bonus worrior. Every player still in the game gets one, and the BONUS PLAYER screen shows a figure for each of them.
- High scores. The original has two tables of six: HIGH SCORES for the session and the HALL OF FAME, which is kept on disk sector 720 and written back when S is pressed. After a game it inserts blue's and yellow's scores and marks each new entry with a flashing arrow in that player's colour. In a three- or four-player game the same insertion runs a second time with green's and purple's scores copied into the blue and yellow slots. Their arrows are plain red, because that screen has no free colour register left for green and purple.
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:
- The VBI got longer. Handling four worriors makes the game VBI longer, and an early probe recorded it ending as late as scanline 74, while the maze starts at scanline 32. Anything the VBI sets after the beam has passed a line only shows on the next frame. That caused the "green ghost" (see section 11); since the fix, the player positions are written first, before the first visible line.
- Hardware writes go through shadows. The worrior colours are written only to the OS shadow registers, which the OS copies during vertical blank, and the shape routine no longer writes horizontal positions in the middle of a frame.
- Interrupt-safe sharing: the missile lock and the separate scratch memory described above.
- v1.3 measurements. The probe was repeated on scratch builds with the same random
four-player play. The highest
VCOUNT(which counts pairs of scanlines) at the end of the VBI was 48 for v1.2 and 50 for v1.3, and in a quiet scene 11 and 14; the difference is the per-frame drop-in checks.
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.
| Test | What it checks |
|---|---|
| Random-input soak | Whole 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 cheat | Random 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 progression | Dungeons 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 checks | Worrior against worrior, the XL/XE guard on four operating systems, the original one-player game using joystick 2. |
| VBI timing probe | How 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
- The soak test once reported "2 deaths" in a one-player game with 3 worriors. It sampled every 6 frames and counted a death when a timer went from 0 to non-zero, so die, respawn, walk out and die again inside one sample counted once. Per-frame sampling over 34 games accounted for every worrior: not a game bug. The test now counts "timer went up".
- The boot sometimes went straight into the long attract cycle, so the test helper now starts a game from the title or from any attract screen.
- Settings poked into memory before the program's cold start were undone by it: the first drop-in soak meant to run with drop-in off ran with it on and never ended. The helper now waits for the cold start.
- A game nobody touches never ends, because the monsters ignore a worrior that has never moved its stick. This is the original's behaviour. The drop-in soak therefore ends with "no more fire button", not "no input".
- One run of the two-player drop-in soak failed with "nobody came back": the random input never gave an out player a fresh press while the other was still playing. That was chance, not a bug. The soak now checks the precise rule instead: every fresh press by an out worrior while another one is surely still playing must bring it back within that step. About 50 such presses in five extra soaks: none missed.
11. Bugs and fixes
: for 10, the screen code after 9. Green's and
purple's scores also showed nothing until their first points.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
- Out means: in this game, no worriors left, not in the maze and not dying. The death animation is over (about 3.2 seconds after the hit) and the worrior has been parked.
- A new routine at the end of every game VBI (not while paused, not in the attract demo) checks each worrior. An out worrior's fire button must first be seen released and then pressed, so a button held down from before it went out does not count.
- A rejoin is allowed when drop-in is on, the game is not over and somebody else is still playing (a dying worrior counts as playing). If everybody is out at the same moment, it is the normal game over.
- The rejoining worrior's best run so far is saved for the high-score table, its score is reset to 0 exactly as at a game start, it gets as many worriors as a new game gives (the OPTION setting), and it enters through the normal routine: blue and yellow from the cage with the door countdown, green and purple blinking in their corner, invulnerable while waiting. Only players chosen for this game can come back.
- Bonus worriors needed no change: dungeons 3 and 12 already give one to everybody with worriors left or in the maze, which includes a player who came back.
- At game over, each player's entry in the high-score tables is the better of the best earlier run and the final score.
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.
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.
Two problems handled before release
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.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
- Single-colour worriors. With one player per worrior, the original's red gun overlay is gone.
- Plain red high-score arrows for green's and purple's new entries.
- XL/XE detection. On an XL or XE only one or two players are offered, which is intended. AltirraOS-XL is not recognised as an XL operating system, so there 3-4 players can be selected even though joysticks 3 and 4 do not exist.
- The executable-file version is for emulators only: it loads over DOS 2.x, so it cannot run from a DOS disk, and saving high scores needs the boot disk.
- Decimal-mode exposure in the original VBI code was left as it was.
- Emulation only. All of the testing described here was done in an emulator. Most of the play in testing was random input plus a test cheat; the development log mentions human play only of the v1.0 release, which is how the ghost bug was found. In the progression test the worriors stand still, so the boss sometimes walks into them.
- An unexplained timeout. One early test run had a single socket timeout. It did not reproduce in more than 20 further four-player games and no CPU crash was recorded, so it was put down to the emulator process stalling, most likely under host load. That explanation was not proven.
- Open questions about the original: why the copy protection reads sector 4 at all, what
the original disk did when the loader re-read a sector pair, and why one routine writes
$8Fto the audio control register and immediately clears it again. - Names in the four-player source are the first-pass names, some of them known to be wrong. The commented disassembly has the corrected ones.
14. Lessons
- Disassemble what is in memory, not what is on disk. A custom boot loader can rearrange the data. Re-implementing the loader and comparing its output with a RAM dump settled the question.
- Byte-identical is not enough. Moving the code and filling the old area with
BRKshows whether any address is still hard-coded. - Measure free memory during play instead of trusting a memory map; the bitmap clear routine reached further than the bitmap.
- On the Atari, a longer VBI changes what the beam sees. Anything set after the top lines are drawn shows up one frame late. Values that the display needs first should be written first.
- Anything shared between an interrupt and the main loop (missiles, pointers, scratch memory, the decimal flag) needs an owner, a lock or a separate copy.
- Check the original before calling something a bug. The test cheat's deadlock and the idle game that never ends were both original behaviour.
- Tests on random input should check a precise rule, not an outcome that chance may not produce.
15. Version history
| Build | Date and time | Version | Changes |
|---|---|---|---|
| b0001 | 5 Oct 2026, 12:46 | 0.1 | First playable build (internal) |
| b0002 | 5 Oct, 13:03 | 0.2 | Internal: 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 |
| b0003 | 5 Oct, 13:12 | 1.0 | First release: XL/XE guard (1-2 players there), dead code removed |
| b0004 | 5 Oct, 14:00 | 1.1 | Fix for the green "ghost": player positions set at the start of the VBI |
| b0006 | 5 Oct, 14:10 | 1.2 | Version and build number on the title screen |
| b0007 | 5 Oct, 14:11 | 1.2 | Build counter fixed, no code change |
| b0008 | 6 Oct, 00:49 | 1.3 | Drop-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.
| When | Step |
|---|---|
| January 2026 | First disassembly attempt, from raw disk sectors (wrong past $167F) |
| 5 Oct, 08:06-08:35 | Setup, emulator harness, the real memory image, a byte-identical and relocatable source |
| 5 Oct, 12:20-12:50 | Four-player design and first playable build |
| 5 Oct, 12:22-13:18 | Commented disassembly, by a second agent in parallel |
| 5 Oct, 12:50-13:25 | Bonus and high-score screens, tests, XL/XE guard; v1.0 |
| 5 Oct, 13:55-14:11 | Ghost fix (v1.1), version on the title (v1.2) |
| 6 Oct, 00:10-00:50 | Drop-in rejoin, tests; v1.3 |
Credits
- Midway created the Wizard of Wor arcade game; Roklan Corp. made the Atari 400/800 version (1981), licensed from Bally/Midway. Much of the code that runs in the 4-player edition is still Roklan's.
- No outside disassembly or source code was used: the agents rebuilt the program from the original boot disk themselves (section 3).
- Tools used throughout: the cc65 assembler and linker, the atari800 emulator (the online arcade runs it compiled to WebAssembly), and AltirraOS by Avery Lee, the free replacement operating system the online arcade boots.