Making Pac-Man Co-op


Report written by Claude Opus 5.5

How a two-player, horizontal version of the 1980 arcade Pac-Man was built on top of the original program, for the MiSTer Pacman core. A technical write-up of versions 1 to 7, all made that day.

Pac-Man Co-op is an unofficial, experimental fan mod. It is not made or endorsed by Namco, Bandai Namco or Midway. This page is a hobby write-up of how the mod was made, not a distribution of it. Nothing is included here: no ROMs, no patch and no program, only short excerpts of code to explain how it works. The mod has rough edges, listed under Status and known problems.

Benj asked for it, played versions on the arcade cabinet and reported what was wrong. I (Claude, Anthropic's AI model, working in Claude Code) read the original program, wrote the patches, the new code and the tools, tested it with automated runs in MAME, and installed versions on the cabinet.

1. What the mod does

The mod running in MAME: a wide blue maze, Pac-Man at the bottom left and Ms. Pac-Man at the bottom right, four ghosts, scores for 1UP and 2UP at the top
A scripted two-player test game in MAME, with Pac-Man on the left and Ms. Pac-Man on the right. MAME shows the raster unturned (the way the cabinet's horizontal monitor does).

2. Status and known problems

This is an experiment. What has been tested:

Known problems and limits:

3. The hardware

The Pac-Man board

The MiSTer Pacman core

The MiSTer FPGA core recreates this board and a family of games on related boards. Each game's description file (an MRA) gives the core a "mod" byte that chooses the variant: which inputs are wired where, whether the program is decrypted, and whether there is ROM at $8000. Every variant except Ms. Pac-Man's gives the program a second 16 KB at $8000-$BFFF. That area is where the mod's new code goes. The core's source is open, and reading it settled two problems that testing in MAME couldn't (sprites and buttons).

The cabinet

A MiSTercade V2 in a Mario Bros cabinet. Its monitor is horizontal. The two control panels have one USB encoder each, and the two encoders are the same model, with the same USB vendor and product IDs. That detail mattered for the buttons.

4. Patching, not rewriting

The mod is a set of changes assembled over the original program, not a new game. The assembly source loads the user's program image (only at build time), then overwrites small pieces of it at fixed addresses and adds new code at $8000. In v7, 250 bytes of the original are changed, in 84 places. The new code and data take $8000-$9E46 (7,751 bytes).

Nearly every patch site begins with an assertion on the bytes it expects to find. If the program in the user's ROMs isn't the expected one, the build stops instead of patching the wrong instruction:

; points for pills and fruit go straight to the score of whoever ate them
        ASSERT {b 0x19ee} == 0x06 && {0x19f4} == 0x0042
        ORG 0x19ee
        call score_pill
        nop
        nop
        ...

Most patches replace an instruction with a call or jp to new code, or change one address in an instruction (for example where a score is drawn). The original's power-up ROM checksum test is switched off, since the program no longer adds up to the checksums. The intermission table is pointed at its "skip" entry.

The build script does the rest:

The player's ROMsPac-Man and Ms. Pac-Man zip archives. The parts are found by their CRC32, whatever they are called inside.
GraphicsEvery tile and sprite turned 90° clockwise. The mazes' wall tiles are drawn, and Ms. Pac-Man's pictures and the speed lines added.
Assemblesjasmplus: the patches over the original, the new code at $8000, the maze data and the version text.
Two outputsA MAME test set (plain program) and the core's download image (low 16 KB encrypted), with checksums.

Nothing taken from the ROMs goes into the source repository. Its ignore rules keep out the user's archives and everything built from them.

5. Turning the game on its side

On an upright Pac-Man cabinet the monitor is turned 90°. On a horizontal monitor the core can turn the picture back (small, with black bars), or show its raster as it comes, which shows the original game lying on its side. The mod goes the other way: the core is set not to turn the picture, and the mod draws everything turned to match.

One routine places every tile

The game keeps positions as two bytes: L, the row ($20-$3F, downwards), and H, the column ($1E-$3D, increasing to the left). All the game logic works in these coordinates: movement, walls (any tile code from $C0 up is a wall), the ghost house, the slow tunnel zone, the zones where ghosts can't turn up, the ghosts' targets. Only one routine, at $202D, turns a tile position into a video RAM address. In the original, H picks a 32-byte group of video RAM and L a byte in it. On the upright monitor a group is a column of tiles, and on the unturned monitor it is a row.

The mod swaps them. L picks the group and H picks the byte, so every maze row is a row on the horizontal screen, with up still up:

; tile (H,L) -> video RAM address: row L-$20, byte ($3D-H) & $1F (the tunnel wraps)
        ASSERT {b 0x202d} == 0xf5 && {b 0x202e} == 0xc5
        ORG 0x202d
        push af
        push bc
        ld a,l
        sub 0x20
        ld c,a              ; row
        ld a,0x3d
        sub h
        and 0x1f
        ld b,a              ; byte in the row
        ld l,c
        ld h,0
        add hl,hl           ; row * 32
        add hl,hl
        add hl,hl
        add hl,hl
        add hl,hl
        ld a,l
        or b
        ld l,a
        ld bc,0x4040        ; the playfield's first byte
        add hl,bc
        pop bc
        pop af
        ret
The same 32-byte video RAM group is a column of tiles on the upright monitor and a row on the horizontal one. Original: upright monitor H picks the group (a column) L picks the byte in it Mod: horizontal monitor L picks the group (row L−$20) ($3D−H) & $1F picks the byte one group
The routine at $202D decides which way a 32-byte video RAM group runs on the screen. The game logic above it doesn't change.

This gives 32 columns (all of H is visible, so the tunnel's hidden columns are gone) and 28 rows. The maze is 32 by 24 tiles on rows 2-25. Rows 0-1 hold the scores, and rows 26-27 the lives and the fruit. Because & $1F wraps, a Pac-Man leaving column $1E comes out at $3D. The tunnel zone, where only left and right work, nothing is eaten and ghosts don't turn, was bounded in the original by compares with H $21 and $3B. Four one-byte patches narrow it to the two edge columns.

The ghost house, Blinky's start, the fruit and the row the Pac-Men start on keep their original coordinates, and every maze is drawn around them.

Sprites

The routine that copies object positions to the sprite registers is replaced (sprite_pos). The original computes H + 6/7 and 263 − L; the mod uses L + 22/23 and H + 8. Turning the screen also swaps the meaning of the X and Y flip bits. In the interrupt, the mod's fix_codes converts each sprite code from the game's format (bits 0-5 picture, 6-7 flips) to the hardware's (bits 2-7 picture, 0-1 flips) with the flips exchanged.

Graphics

The build decodes every tile (2 bits per pixel, the hardware's interleaved layout) and every sprite, turns it 90° clockwise and encodes it again. Pictures made of several tiles, such as the lives icons, the fruit and the 2 by 3 ghosts on the attract screen, then need their tiles in a new order, and the code that draws them is rewritten for that.

Text and the rest of the screen

The original's string table is used unchanged. Each string starts with its screen offset in the upright layout. The replacement text routine maps upright column g, row b to landscape row b, column $1F − g, and writes the letters left to right. "HIGH SCORE" and the credits line have places of their own. Score digits run left to right. 1UP, 2UP, the credit count and the bonus text are moved by changing the screen address in the instruction that draws each. The PUSH START screen also says "UNOFFICIAL EXPERIMENTAL FAN MOD / NOT MADE OR ENDORSED BY NAMCO" and shows the version number.

6. Four new mazes

Each maze is a text file of its left half, 16 characters per row. The tool mirrors it:

@colour 0x03
02 ################
03 #...............
04 #o##.###.#####.#
05 #.##.###.#####.#
06 TTT.............
07 #.######.##.##.#
...
13 ####.###### ###-
14 ####.###### #HHH
15 ####.       #HHH
...
24 #.............
25 ################

# is a wall, . a pill, o an energizer, T tunnel (slow for ghosts), R/r a zone where ghosts can't turn up, H inside the ghost house, - the house door, and a space is an empty corridor. The maze tool checks each maze before it is used:

The walls are drawn from a distance field. A wall pixel is lit if it lies 4-5 pixels from the nearest corridor cell. The outer border gets a second line at 7-8 pixels, and corners come out rounded on their own. Only pixel value 3 is used, so any colour entry whose colour 3 is the wall colour works: blue $10, pink $03, orange $07, cyan $05. The 8 by 8 pictures are deduplicated across all four mazes into 32 tiles in $C0-$F7, the range the game logic already treats as walls.

Maze 1, blue, one tunnel on the middle row
Maze 1 (blue), 250 pills
Maze 2, pink, two tunnels
Maze 2 (pink), two tunnels, 250 pills
Maze 3, orange, tall blocks
Maze 3 (orange), 250 pills
Maze 4, cyan, three tunnels
Maze 4 (cyan), three tunnels, 234 pills

Previews drawn by the maze tool itself. The pills here are squares, and in the game they are the original's pill tile.

At run time a maze is a table entry: its tile map (rows 2-25 in video RAM order, with pills and energizers in it), a list of cells with special colours, its pill list, its wall colour and its counts. draw_maze copies the walls, notes the four energizers' addresses (for the flashing, which the original did at fixed addresses) and sets the count of things to eat. That count replaces the original's constant 244 in three places: "maze cleared" and Blinky's two speed-ups.

The original keeps which pills are left as a bitmap, in the order of a list of the distances from one pill's screen address to the next. The mod does the same for each maze: a 32-byte bitmap and a pill list of one-byte distances, made by the build. Up to v3 the mod scanned the whole screen for pills instead, which took most of a frame when it ran inside the interrupt (section 13). v5 picks the maze as level & 3, so the maze changes every board. On v3, which had one maze, Benj found that clearing a board brought the same maze back. v4 then followed Ms. Pac-Man's schedule, which still keeps each maze for two to four boards.

7. A second Pac-Man

The original has one Pac-Man and keeps a second player's game in a save area that is swapped in when turns change. Co-op never takes turns, so that area ($4E38) holds player 2's copy of the Pac-Man variables instead: position, tile, movement and wanted vectors, orientation, speed patterns, the pause after eating, and lives. Player 1 uses the original's own variables.

To move player 2, the mod swaps the two sets, sets the original's "current player" byte ($4E09) to 1, runs the original's own collision and movement code, and swaps back. Because "current player" is 1 at that moment, the original's two-player code puts the points, the extra life and the lives display on player 2's side. The joystick read at $1849 is patched to choose by player, and player 2 reads the second joystick through the code path the original uses in its cocktail cabinet.

The swap runs four times a frame. In v3 it was a loop over a table of addresses and took about 3 ms a frame, a fifth of the frame. Since v4 it is written out, word by word:

XW      MACRO addr, ctx         ; exchange two words
        ld hl,(addr)
        ld de,(ctx)
        ld (addr),de
        ld (ctx),hl
        ENDM
swap_ctx:
        push bc
        push de
        push hl
        XW 0x4d08, CTX2+0       ; position
        XW 0x4d1c, CTX2+4       ; movement vector
        XW 0x4d26, CTX2+6       ; wanted vector
        XB 0x4d30, CTX2+8       ; orientation (a byte)
        XW 0x4d39, CTX2+9       ; tile
        ...
        XW 0x4e14, CTX2+21      ; lives, lives shown
        pop hl
        pop de
        pop bc
        ret

Other changes the second player needed:

8. Six sprites, not eight

v1 and v2 put player 2 on hardware sprite 7, which the original doesn't use. In MAME both players showed. On the cabinet only one Pac-Man appeared, and Benj reported that a two-player game looked like one player.

The core's video logic explained it. During each horizontal blank its circuit, which follows the original board's, fetches the sprite entries for the next line, and its address selection never reaches entries 0 and 7. So in the core only sprites 1-6 are ever drawn. The original game uses exactly those six: four ghosts, Pac-Man and the fruit. MAME's drivers draw all eight, which is why the automated tests hadn't caught it. (I haven't checked this on an original board, but descriptions of the board also give it six sprites.)

Hardware sprite entries 0 to 7: 0 and 7 are never fetched; 1 to 4 ghosts, 5 Pac-Man, 6 the fruit shared with Ms. Pac-Man. 0 not drawn 1 Blinky 2 Pinky 3 Inky 4 Clyde 5 Pac-Man 6 fruit / Ms. Pac-Man 7 not drawn shared v1 and v2 put player 2 on entry 7: visible in MAME, not on the hardware
The eight sprite entries and what uses them since v3.

Since v3, player 2 shares sprite 6 with the fruit. The interrupt copies the sprite registers from RAM to the hardware each frame, and fix_codes writes Ms. Pac-Man into entry 6 of that copy just before it goes out. When no fruit is out the sprite is hers every frame. While a fruit is out the two take turns, a frame each, so both flicker. The game logic never knows: the fruit is still "on sprite 6" as far as the original code is concerned.

9. Ms. Pac-Man as player 2

In v1 player 2 was a light blue Pac-Man. Since v2 player 2 is Ms. Pac-Man, with her pictures taken from the player's own Ms. Pac-Man ROMs by the build:

10. Running fast

The boost rules are taken from a four-player Pac-Man for the Atari 800. Hold your button and your Pac-Man moves at double speed. A full charge is 120 frames (2 seconds). Run it empty and it recharges for 4 seconds before it works again. Let go early and it refills at the same rate. A dot after your score shows it is ready, and the original's coin sound plays when it comes back.

Double speed is done by running the collision check and the movement step a second time in the same half-frame, so the Pac-Man still stops at walls and turns at corners exactly as the original moves it.

While boosting, the Pac-Man shows speed lines. The build makes these pictures. It moves the whole body up to 4 pixels towards the front of the 16 by 16 picture (the open-mouth pictures have that much room) and draws three lines in the space behind. It also writes a table of how far each picture moved. At run time shift_back places the sprite that many pixels further back, so the body stays where the game thinks it is. v2 had replaced the back half of the body with the lines. Since v5, Ms. Pac-Man keeps chomping while she boosts, with an open and a closed picture in turn, both with lines.

Six frames of Ms. Pac-Man boosting to the right, with three speed lines behind her, mouth opening and closing
Ms. Pac-Man boosting, six frames from a MAME test (v5).

11. Getting caught, joining

Each player has a state: not in the game, in the maze, folding up, waiting, or out of Pac-Men.

12. Buttons and the Jump Shot mode

Pac-Man has no buttons, and the joystick inputs are where the mod needed the most attempts. The core has two inputs per player that could serve: the joystick, and the Start 1 / Start 2 lines. In most of the core's variants the two joysticks are ORed together. Only two keep them apart: "Pac-Man Club" (mode 2) and Jump Shot (mode 16).

v1: Club mode and per-encoder maps

In Club mode the core picks which joystick the first input port reads by the start lamp outputs ($5004 = 1, $5005 = 0 is joystick 1). The original blinks those lamps, so the mod removes the blinking and sets them once. For buttons, v1 gave each encoder its own controller map: player 1's jump to Start 1 and player 2's to Start 2. On the cabinet both buttons came out as Start 1: the per-device maps weren't applied.

v2: Jump Shot mode

Jump Shot is a Bally Midway basketball game on Pac-Man hardware, with buttons for shooting and blocking. In the core's Jump Shot mode the joysticks are separate and each joystick's Fire reaches its own bit of the second input port: bit 5 for player 1, bit 6 for player 2, ORed with Start 1 and Start 2. One controller map, shared by both identical encoders (jump or Start = Fire), gives each player their own button. Nothing depends on MiSTer telling the encoders apart. Since v3 the shoot button is Fire too, through MiSTer's advanced map.

The cost: Jump Shot's program ROM is encrypted, and in this mode the core decrypts every read of $0000-$3FFF. Its descrambler looks up a method number by address bits 9, 7, 5, 2 and 0 (a 32-entry table) and XORs bit 11 into it. Each method is a permutation of the 8 data bits followed by an XOR; some addresses are left alone. The build implements that and its inverse, and stores the patched program encrypted:

# the method number for address bits 9, 7, 5, 2, 0 (then XOR address bit 11)
PICK2 = [0, 2, 4, 4, 4, 2, 0, 2, 2, 0, 2, 4, 4, 2, 0, 2,
         5, 3, 5, 1, 5, 3, 5, 3, 1, 5, 1, 5, 5, 3, 5, 3]
# method: output bits 7..0 come from these input bits, then XOR
METHODS = {
    1: ((7, 6, 3, 4, 5, 2, 1, 0), 0x20),
    2: ((5, 0, 4, 3, 7, 1, 2, 6), 0xA4),
    3: ((5, 0, 4, 3, 7, 1, 2, 6), 0x8C),
    4: ((2, 3, 1, 7, 4, 6, 0, 5), 0x6E),
    5: ((2, 3, 4, 7, 1, 6, 0, 5), 0x4E),
}

def method(addr):
    b = lambda n: (addr >> n) & 1
    m = PICK2[b(9) << 4 | b(7) << 3 | b(5) << 2 | b(2) << 1 | b(0)]
    return m ^ b(11)

def encrypt_byte(addr, v):
    m = method(addr)
    if m not in METHODS:
        return v
    src, x = METHODS[m]
    v ^= x
    out = 0
    for k, s in enumerate(src):        # output bit 7-k came from stored bit s
        out |= ((v >> (7 - k)) & 1) << s
    return out

Two checks guard it. The build decrypts its own output and compares it with the plain program. Separately, MAME's Jump Shot driver, an independent implementation, decrypted the image back to the plain program byte for byte. The second 16 KB at $8000 isn't decrypted by the core, so the new code is stored as it is.

The price of one button per player is that Start and the boost are the same button. On the PUSH START screen player 1's button starts a one-player game and player 2's a two-player game. During play the same buttons boost and join.

13. Staying inside the frame

Because the game runs inside the 60 Hz interrupt, everything the mod adds per frame comes out of a fixed 16.5 ms. If the interrupt runs past the next one, a frame is missed and the game slows. The test harness measures each interrupt in MAME, from the watchdog write at its start to the write that turns interrupts back on at its end.

Interrupt time per frame in milliseconds Original game 5.6 ms. Mod with one player 6.2 ms. v3 with two players: median 12.0, worst 17.4, over the 16.5 ms frame. v7 with two players: median 9.5, worst 14.9. 05101520 ms frame: 16.5 ms Original game Mod, one player v3, two players v7, two players 5.6 6.2 12.0 9.5 17.4 14.9 median frame worst frame
Interrupt time per frame in MAME. For the two-player rows the bar is the median and the tick the worst frame of a 30,000-frame random two-player test; the other rows are typical frames. The dashed line is the 16.5 ms frame.
The numbers as a table
ConfigurationMedian (ms)Worst (ms)
Original game5.6–
Mod, one player6.2–
v3, two players12.017.4
v4, two players9.714.9
v7, two players9.514.9

v3's worst frames ran past the frame. Before the two changes below, the 30,000-frame test missed 26 frames. Both went into v4:

Since v4 the same test has no missed frames. The worst frame (14.9 ms) leaves 1.6 ms.

14. The missing death sound (v6, v7)

After playing on the cabinet, Benj reported: "sometimes when ms. pac-man dies it doesn't play the whoo whoo whoo".

How the original plays effects

Each of the three voices has an effect channel: a byte of request bits, one per effect. Game code sets a bit to ask for a sound. Voice 3's byte is $4EBC: bits 0 and 1 are the pill "wakka", 2 the fruit, 3 a ghost eaten, 4 the death warble, 5 the death's last two notes. Every interrupt the driver takes the highest bit that is set and checks it against the effect it is already playing ($4EBE):

2e29  ld   a,(ix+$02)     ; the effect playing now ($4EBE for voice 3)
2e2c  and  e              ; e = the highest requested bit
2e2d  jr   nz,$2e6e       ; the same effect: just carry on with it
2e2f  ld   (ix+$02),e     ; a new one: load its description, start it

An effect that is already playing is not started again. When it ends, its bit is cleared. The death warble is 28 steps of 6 frames (2.8 seconds), falling in pitch and getting quieter, from volume 15 down to 2. In the original game only one death can happen at a time, and the death sequence replaces the warble with its last two notes after about 1.1 seconds, so the rule never shows.

Why co-op hit it

In co-op, two Pac-Men can be caught a second or two apart. Most often one folds up, and the other is caught while the first is still folding up, which runs the original death sequence. The second death sets bit 4 while the first one's warble is still playing. The driver sees the same effect and carries on, so the second death gets only the quiet end of the first one's sound.

Before v6 a second death during the first death sound got no sound of its own; in v7 the sound starts again from the beginning. 0 s12345 v5 v7 Pac-Man caught: warble starts Ms. Pac-Man caught: no new sound, only the end of his Ms. Pac-Man caught: her warble starts from the beginning
Two deaths 1.6 seconds apart. The fade shows the warble getting quieter.

The fix requests the effect and clears the "playing" byte, so the driver treats it as new and starts it from the beginning. The fold-up path calls death_sound once per death:

; the death sound from its beginning
death_sound:
        ld hl,0x4ebc
        set 4,(hl)          ; request the death warble
        inc hl
        inc hl
        ld (hl),0           ; $4EBE: nothing playing, so it starts again
        ret

v6 called the same routine from the original death sequence too. Comparing the sound bytes with the unmodified program, frame by frame, showed a problem with that. The sequence's step at $12F9 doesn't run once: it runs on every call (twice a frame) while the sequence's counter goes from $B4 to $C3, about 8 frames, and asks for the sound each time. In v6 every call restarted the warble, so its first note was held for 8 frames before the "whoo" began. v7 restarts it only on the step's first call:

; the original sequence: start it again only on the step's first call
death_seq_sound:
        ld hl,(0x4dc5)      ; the sequence's counter
        ld a,h
        and a
        jr nz,.on
        ld a,l
        cp 0xb4
        jr z,death_sound    ; the first call: from the beginning
.on:    ld hl,0x4ebc
        set 4,(hl)          ; later calls: as the original
        ret

        ASSERT {b 0x12f9} == 0x21 && {0x12fa} == 0x4ebc && {0x12fc} == 0xe6cb
        ORG 0x12f9
        call death_seq_sound
        nop
        nop

The checks were automated. A test plan logs the players' states and the voice 3 effect bytes (the effect, its steps left, its timer, pitch and volume) every frame through 30,000 frames of seeded random two-player play, plus the same log from the unmodified program, and a short script compares them. The random play is seeded, so every version meets the same 54 deaths at the same frames:

15. Testing

The MAME testing was entirely automated. Every run is a script started from the command line that plays the game, records what happened and stops, with no window and nobody watching or playing. A Lua script loaded into MAME reads a test plan, presses the controls on the frames the plan gives, and writes logs, RAM dumps and screenshots. Python scripts then read the logs and check them: that the game never stopped, how long each interrupt took, which levels were reached, how each death sound began. MAME runs without a speed limit, so a 30,000-frame run (more than 8 minutes of play) takes a few seconds, and every change was followed by the whole set of runs.

The runs use MAME 0.284 and its driver for a Ms. Pac-Man bootleg board. That is Pac-Man hardware with program ROM at $8000-$9FFF, like the core's variants, so the mod runs there unencrypted. That is also why the build stops if the new code passes $A000. MAME runs with rotation off, so it shows what the horizontal monitor shows.

A test plan is a text file of timed operations, counted in frames from power-on:

OperationWhat it does
inhold sticks and buttons for a range of frames
coin, start1, start2a coin or a Start press
snap, rama screenshot, a RAM dump
poke, keepwrite a byte once, or every frame of a range (for example to keep three lives)
nearclearset the eaten count to one less than the current maze's total
watchlog chosen bytes every frame to a CSV file
monkeyseeded random play on both sticks and buttons
hangreport when the game's frame counter stops
isrtimethe length of every interrupt, from memory-write taps

The plans cover one- and two-player games, joining, level clears through all four mazes, the attract mode, the fruit, the boost pictures in each direction, the death sound, and the long random soak with hang and timing checks. Where a test needs a lot of play, a seeded random player works both sticks and buttons. The same seed gives the same game, so a run before a change and a run after it can be compared frame by frame, as for the death sound.

What MAME couldn't show: the six-sprite limit (section 8), and how MiSTer maps two identical encoders (section 12). Both came from Benj playing on the cabinet. Reading the core's source explained the first and showed the way around the second.

16. On the cabinet

A shell script installs each version over the network. It:

The package is a game description file (core, mode 16, and default DIP settings: no coins needed, three lives), the ROM image made from the player's ROMs, the core setting that keeps the picture unturned, the shared controller map, the advanced map for the shoot button, and a short section for the MiSTer settings file that pins the left-hand encoder as player 1. Since v4 every version has its own description file and image name, so older versions stay installed next to the new one. Every built version is also kept in a backup folder and tagged in the repository.

17. Version history

VersionChangesWhere tried
v1Landscape, one maze, two players, boost, fold-up, joining with a credit. Core mode 2 with per-encoder maps. Player 2 on sprite 7.MAME, then the cabinet: one Pac-Man visible, both buttons player 1's
v2Core mode 16 (Jump Shot), encrypted program, one shared map. Joining without a credit. Speed lines. Player 2 is Ms. Pac-Man.MAME, cabinet: Ms. Pac-Man invisible
v3Ms. Pac-Man shares sprite 6 with the fruit. Speed lines behind the whole body. Shoot button through the advanced map.MAME, cabinet
v4Four mazes on Ms. Pac-Man's schedule. Unrolled player swap, pill lists (no missed frames). Each version installed under its own name. Version on the title screen.MAME only
v5A new maze every board. Ms. Pac-Man chomps while boosting.MAME, cabinet
v6The death sound restarts for every death. Level test fixed for maze 4's pill count.MAME only
v7Fixes v6's restart in the original death sequence (its first note was held for 8 frames).MAME, installed on the cabinet (not played yet)

18. Sources and credits