If you appreciate the work done within the wiki, please consider supporting The Cutting Room Floor on Patreon. Thanks for all your support!

Bugs:Pac-Man (Arcade)

From The Cutting Room Floor
Jump to navigation Jump to search

This page details bugs of Pac-Man (Arcade).

Level 256 Split Screen

The graphical garbage seen in Level 256.

Perhaps one of the most infamous bugs in gaming, the game renders garbage at Level 256. It is commonly referred to as "Split Screen" due to the right half of the screen looking garbled, while the left half is still intact.

The bug was originally analyzed and documented by Don Hodge on his website, where he also proposes a possible fix.

Why the Bug Happens

This happens due to a bug in the code responsible for retrieving the number of fruits to draw at the bottom right corner of the screen. Inside the routine, it loads the number of the current level (being 0-based and thus 255 for Level 256, or 0xFF in hexadecimal), then increases it by one.

0x2BF0  LD A,(#4E13)  ; Loads the current level number stored at 0x4E13 into register A.
0x2BF3  INC A         ; Increases the register by one.

In the case of Level 256, this overflows the register and makes it loop back to 0 (0x00) since a byte cannot hold a number higher than 255, simply restarting from 0 if that is the case. The code does not check if an overflow has happened (which could be possible due to a so-called carry flag set in the CPU registers). In fact, it simply uses the resulting number in a check on which fruits to draw.

The check is done to compute the offset into a table which holds the fruits to draw. Levels 3-4 for example both draw an Orange, thus this "fruit table" is required to look up these duplicate occurrences of fruits. The table holds the following fruits in this order:

  1. Cherry
  2. Strawberry
  3. Orange (1st occurrence)
  4. Orange (2nd occurrence)
  5. Apple (1st occurrence)
  6. Apple (2nd occurrence)
  7. Melon (1st occurrence)
  8. Melon (2nd occurrence)
  9. Galaxian (1st occurrence)
  10. Galaxian (2nd occurrence)
  11. Bell (1st occurrence)
  12. Bell (2nd occurrence)
  13. Key (1st occurrence)
  14. Key (2nd occurrence)
  15. Key (3rd occurrence)
  16. Key (4th occurrence)
  17. Key (5th occurrence)
  18. Key (6th occurrence)
  19. Key (7th occurrence)
  20. Key (8th occurrence)

The problem with the check now is that it only expects values starting at 1, which is the value for the first level (internally Level 0, then being increased by one). Due to the overflow with 0xFF, the check gets value 0 - something the programmers did not expect.

So what does the check do exactly with the number it gets?

  • When the value is lower than 8, it draws fruits starting with the Cherry from the first level. A loop is used to draw only the fruits from the first to the given value (since only the Cherry and not more should be seen in Level 1).
  • If the value is between 8 and 18, it uses the current level number as the current offset into the fruit table. Remember that fruits scroll starting with Level 8, as a maximum of 7 fruits are drawn, so only the most recent 7 are visible.
  • If the value is above 18, it always starts drawing fruits from the first Key. According to the fruit table, it will always draw 7 Keys then (there's no reason for an 8th Key to be in it, but it happens to be).
0x2BF4  CP #08       ; Is the value lower than 8? (yes for level 256, being 0x00).
0x2BF6  JP NC,#2C2E  ;     No: Compute another offset into the fruit table (the code at 0x2C2E is not shown here).
0x2BF9  LD DE,#3B08  ;     Yes: Places the offset to the cherry in the fruit table in the DE register.

So far, this is still not a big issue. However, the aforementioned loop is coded in a way it does not work together with value 0. To implement a loop in assembly language, the counter is stored in register B. It is decremented at the end of the loop and then checked if it is 0 to stop the loop from being started over again.

0x2BFC  LD B,A        ; Prepare the loop, store the counter in B. Initialize it with the value we got before.
0x2BFD  LD C,#07      ; (Prepare a loop drawing black tiles to cover old fruits.)
0x2BFF  LD HL,#4004   ; Stores the location in video memory where to draw the first fruit (0x4004) in register HL.
0x2C02  1A LD A,(DE)  ; Start of the loop (stores the value of the fruit table in register A).
... Fruit drawing code. Replaces 4 tiles on screen (fruits are of 2x2 tiles size) in order of the video memory ...
0x2C14  INC HL        ; After current fruit is drawn, increase HL twice to point to tile of the next fruit.
0x2C15  INC HL
0x2C16  DEC C         ; (Update the loop to draw the black tiles to cover old fruits.)
0x2C17  DJNZ #2C02    ; Decrements B (the loop counter) by 1. Exits if it is 0, otherwise runs the loop again.

In Level 256, the loop counter is initialized with 0 from the start (line 0x2BFC). When decremented to possibly restart the loop again (line 0x2C17), it underflows back to 255 (since a byte can't hold values lower than 0, thus 0 minus 1 results in 255, or 0xFF). The loop now repeats as often as required to bring 255 back to 0.

How the Garbage Is Placed

Video memory locations as mapped to the screen.

What this means is that the game is now instructed to loop over the fruit drawing code 256 times - these are by far too many fruits to draw. Running out of entries in the fruit table (which also point to the video memory holding the graphics of the fruits to draw), it starts accessing other parts of the video memory, grabbing seemingly random tiles of graphics and drawing them onto the screen into increasing video memory locations.

But why does this actually cause the right half of the screen to be overwritten? This is because the video memory in Pac-Man is not laid out as in modern computers (which are increasing video memory locations from top left to bottom right). It is separated into three parts:

  • Video memory starts with the fruit bar at the bottom. It consists of two rows of tiles, the first tile starting at the top-right tile of the first fruit. The tile offsets increases towards the left, then into the second line.
  • The maze, following the fruit bar directly in memory. The first tile is at the top right corner and then runs down before proceeding into the next column (unlike the fruit bar which is row-major).
  • The score bar at the top, laid out like the fruit bar, following the maze memory.

As said, when the fruit drawing routine is looped 256 times, it moves forward in video memory. The first line of the fruit bar is completely filled with fruits from right to left. Then it continues drawing fruits into the second line of the fruit table. Here it already begins drawing the lower parts of the fruits into the right-most column of the maze, from top to bottom (due to the transpose of the maze video memory), since that is what the game thinks would be the "third" line of the fruit table. It then proceeds drawing fruits starting from the "third" line of the fruit table, which - as we know - is the first column of the maze. It starts scrambling the 2nd column at this time, too. Even if there would be real fruits to draw now, they would look split into 4 parts since the memory is transposed compared to the fruit bar, and the fruit drawing routine of course does not care about that.

The game continues drawing until more than half of the maze video memory has been overwritten with garbled data, and B finally becoming 0 before it is checked to end the loop. In fact, the game has no code or bug which exactly trashes "half" the maze; it just happens to be about (but actually more than) 50% of the maze memory.

Causing the Level To Be Unfinishable

To finish a level, the game checks if 244 dots have been eaten.

When the maze memory is being overwritten by the fruits - which are drawn at level start and every time a life is lost - most dots are erased with it. Only 9 tiles still get a dot flag written to.

Every time the player loses a life (assuming he still has some in this high level), the fruits are redrawn, and the possibly eaten dots are placed again in the garbled area. The player can reach a maximum of 6 lives in this game, and even when bringing them all into Level 256, causing the 9 dots to be placed again 6 times at each death, there are still not enough dots to eat to finish the level, and thus the level end never triggers.

It can be manually triggered with a hardware dip switch (the "Rack Test" feature), which throws the game back into Level 1 with the scores still intact, though of course this is not seen as "normal" gameplay. There are some leftover graphics in the bottom corners of the screen which do not get redrawn under normal circumstances and actually stay visible until the machine is reset.

Wrong Ghost Behavior When Pac-Man Faces Up

To do:
Use information (especially add images similar to the ones) from Don Hodges' page.

When the ghosts switch from scatter mode into chase mode to attack Pac-Man, each one uses a different logic to actually target him:

  • Blinky (red ghost) directly targets the tile the Pac-Man actor is seen on.
  • Pinky (pink ghost) targets the spot 4 tiles in front of Pac-Man's mouth (e.g. 4 tiles away from the direction he is pointing to).
  • Inky (cyan ghost) targets the spot 2 tiles in front of Pac-Man's mouth, then adds the distance from that spot to Blinky to it.
  • Clyde (orange ghost) only targets Pac-Man like Blinky when he is further away than 8 tiles, otherwise switches to scatter mode.

Pinky and Inky's logics have a bug, however, which causes them to compute the tile in front of his mouth incorrectly when Pac-Man is pointing up. It turns out that this will actually compute the tile not only 4 (or 2) tiles (upwards) away from his mouth, but also 4 (or 2) tiles to the left of it.

To understand the bug, it is required to know how the distance to a tile afar is represented in memory. Basically, a 16-bit directional vector is used as seen in the following table. These values only make sense when remembering that the origin of the coordinate system of the maze is at the top-right rather than top-left, and that 0xFF to 0x80 means -1 to -127 in an 8-bit value (while 0x00 to 0x7F is 0 to 127).

Direction Mathematical representation Memory representation
Up (0, -1) (0x00, 0xFF) = 0x00FF
Down (0, 1) (0x00, 0x01) = 0x0001
Left (1, 0) (0x01, 0x00) = 0x0100
Right (-1, 0) (0xFF, 0x00) = 0xFF00

To now compute more than 1 tile afar into a specific direction, the game simply multiplies the vector by the required distance. Purposefully making use of the values overflowing when multiplying the Right vector, this seems to result in the correct distance vector. Let's see the results when trying to retrieve the spot 4 tiles down, left, or right:

Direction Original vector Resulting vector (multiplied by 4)
Down 0x0001 0x0004 = (0x00, 0x04) = (0, 4)
Left 0x0100 0x0400 = (0x04, 0x00) = (4, 0)
Right 0xFF00 0xFC00 (overflowing 4 times) = (0xFC, 0x00) = (-4, 0)

However, this does not work out when multiplying the Up vector, as no overflow happens since the value is treated of 16-bit size:

Direction Original vector Resulting vector (multiplied by 4)
Up 0x00FF 0x03FC (not overflowing) = (0x03, 0xFC) = (3, -4)

Since a 16-bit value can take more than just 0xFF and the multiplication is done in 16-bit, the value does not overflow and instead "spills over" into the X offset, making the direction not only go 4 tiles up, but also 3 tiles left. When this incorrect movement vector is added to the position of Pac-Man then another "spill over" from the Y component to the X component will occur. As the result, the calculated target will be (4, -4) relative to the position of Pac-Man.

Inky uses the same buggy logic, he thus computes a tile offset 2 to the left when trying to get the spot 2 tiles up.

N.B., when moving Pac-Man or ghosts, the game's code calculates the x- and y- coordinates independently with two 8-bit operations, instead of using a single 16-bit vector for both coordinates at the same time. This is why regular movement is calculated correctly.

Passing Through Ghosts Without Getting Hurt

Due to the way the collisions with ghosts are detected, it is possible to pass through ghosts when approaching them exactly in the frame where Pac-Man and the ghost both switch the tiles they're seen on by the collision detection code. Requiring this to happen on the exact frame, this happens very rarely, though it has been incorporated in professional level solving patterns (Billy Mitchell once executed a Cherry level pattern abusing this glitch several times in one run).

Frightened Ghost Behavior Isn't Really Random

The frightened (blue) ghosts use a pseudo random number generator (PRNG) to decide which direction to take at an intersection. However, this PRNG is reset every time a level starts or a life is lost, so their behavior is predictive when moving exactly the same as Pac-Man (like it is done with level solving patterns).

This is not generally seen as a bug and might be intended by the developers, though it could be a programming oversight as well.