The Centipede display is a combination of two things: a 30x32 grid of 8x8 pixel tiles, and a set of sixteen 16x8 pixel "motion objects" (sprites). Tiles and motion objects have 2 color index bits per pixel.
If the game is being played on a cocktail cabinet, the display needs to be rotated 180 degrees when player 2 is active. The two high bits for tiles and motion object pictures are "flip flags", specifying horizontal and vertical flips. The horizontal flip flag is also applied when objects are moving from left to right.
The 30x32 grid is laid out in memory as if the display were rotated 90 degrees counter-clockwise. Address $0400 corresponds to the bottom-left corner, $041f is the top-left corner, and the next column over starts at $0420.
The bottom row of the screen (row 0) is not used during game play. During attract mode it holds a copyright notice. The top row (row 31) holds the scores. The play area is thus 30x30.
There are 64 playfield tiles:
Mushrooms, scores, and messages are all drawn with tiles. This makes text rendering extremely fast.
Setting bit 7 ($80) flips horizontally, bit 6 ($40) flips vertically.
Blank spaces are always set to $00, even when the screen is flipped, to make them easy to identify.
There are 64 motion object pictures:
(Transparent pixels are drawn black here.)
Setting bit 7 ($80) flips horizontally, bit 6 ($40) flips vertically.
The hardware supports 16 objects, with picture number, vertical position, horizontal position, and a color modifier for each. In RAM, the game maintains 16-entry arrays for picture, horizontal/vertical position, and horizontal/vertical velocity. Because the motion object flip flags can sometimes be determined by the global flip state and the object's direction of movement, some of the picture bits are given different meanings in RAM. For example, on a centipede segment, bit 6 is used as a head vs. body flag, and bit 7 is set when a segment is exploding or dead.
The position values start in the bottom-right corner of the screen, and increase as the object moves up and left. The screen is 240x256, matching the 30x32 playfield. The coordinate value specifies the placement of the bottom-left corner of the object, so vertical position $00 puts the object at the bottom row, and $f8 would put it in the top row. The right edge is zero, so at horizontal position $08 a 16-pixel-wide object is halfway off the right side of the screen, and at horizontal position $f8 it's halfway off the left edge of the screen.
Motion objects that overlap the row at the top of the screen (row 31) are clipped to avoid obscuring the scores. This can be seen when the centipede is first entering the play area.
The color modifier byte maps the three palette entries to new
values. It has the form %xx332211
, with a default value of
%xx111001
, which is a direct map: 3=%11, 2=%10, 1=%01. When a
centipede segment is identified as a body segment rather than a head, the
modifier is changed to %xx111101
, i.e. 2=%11. By mapping color
2 to color 3, the segment's eyes will have the same color as the body.
(See $39a0.)
Pictures $2c-2f are for a grasshopper animation. This is not used by the game.
Entries $36-38 are used to show the score for killing the spider. These graphics are stored flipped horizontally. This allows the code to set the horizontal-flip flag when drawing them, because it's used in some situations to indicate an inactive object.
Motion objects are drawn by the harware in order from lowest to highest. Because the player's shot is in slot 14, and the gun is in slot 15, the two can be drawn on top of each other with the net result that the un-fired shot appears to poke out of the top of the gun.
Tiles and motion objects use 2 bits per pixel, for a maximum of 4 colors. The color palettes for tiles and sprites are independent. Tile color #0 is used for background pixels, and is always set to $0f (black) except when in self-test mode. Sprite color #0 indicates transparency, so the palette value is ignored. All told, it's possible to have 7 different colors on the screen simultaneously, though in practice the game uses the same colors for both tiles and sprites (though not in the same order).
Colors values are defined in the palette with 4 bits, using one each for red/green/blue and one to specify "alternate" or "dark" colors. The MAME source code defines the alternate color flag this way:
/* when blue component is not 0, decrease it. When blue component is 0, */ /* decrease green component. */ if (b) b = 0xc0; else if (g) g = 0xc0;
The result is a hue shift, not just an intensity shift. "Dark cyan" becomes more green, "dark magenta" is more red, and "dark white" is a beige rather than a grey.
A nearby comment notes, "The alternate bit affects blue and green, not red. The way I weighted its effect might not be perfectly accurate, but is reasonably close." The actual colors displayed by a calibrated CRT may be different.
The hardware uses 0 for "on" and 1 for "off", yielding the following color values:
%DBGR | Hex | Color |
---|---|---|
%0000 | $00 | Beige |
%0001 | $01 | Greenish cyan |
%0010 | $02 | Reddish magenta |
%0011 | $03 | Dark blue |
%0100 | $04 | Orange |
%0101 | $05 | Dark green |
%0110 | $06 | Red |
%0111 | $07 | Black |
%1000 | $08 | White |
%1001 | $09 | Cyan |
%1010 | $0a | Magenta |
%1011 | $0b | Blue |
%1100 | $0c | Yellow |
%1101 | $0d | Green |
%1110 | $0e | Red |
%1111 | $0f | Black |
Red ($06/$0e) and black ($07/$0f) are doubled, yielding a total of 14 different colors.
The color assignments used for each wave are (see $267a):
Wave | Body / Mush | Legs / Gun | Eyes / Mush O / Text |
---|---|---|---|
1 | $0d green | $00 beige | $0e red |
2 | $02 r-magenta | $04 orange | $01 g-cyan |
3 | $0e red | $01 g-cyan | $0c yellow |
4 | $04 orange | $01 g-cyan | $0b blue |
5 | $01 g-cyan | $0c yellow | $0a magenta |
6 | $09 cyan | $0b blue | $04 orange |
7 | $0c yellow | $0d green | $0a magenta |
8 | $09 cyan | $0c yellow | $0e red |
9 | $0a magenta | $0e red | $01 g-cyan |
10 | $0b blue | $01 g-cyan | $04 orange |
11 | $01 g-cyan | $00 beige | $06 red |
12 | $0d green | $0e red | $0a magenta |
13 | $0e red | $0c yellow | $0b blue |
14 | $00 beige | $0d green | $02 r-magenta |
Wave 15 starts over, using the wave 1 colors.
The palette assignments and motion object color modifier values make this a bit complicated. It can be helpful to see all of the graphics rendered with the wave 1 tile colors:
The color blocks at $6a-6d are rendered in colors 0, 1, 2, and 3, in order. We can see that the non-poisoned mushroom ($7f) is green (1) with red trim (2), while centipedes are beige (3) with red eyes (2) and green legs (1). The per-wave palette assignment code ($267a) flips colors 1 and 3 for motion objects, which is why you actually see green bodies with beige legs when playing the first wave.
The layout of the motion object color modifier can be verified by modifying the ORA value at $39ac, from $39 (maps 1 -> 1) to $3b (maps 1 -> 3). We know that the centipede's legs are drawn in color 1, because they're green in the chart above, so we'd expect the change to make the legs turn green. (Spoiler: they do.)
Copyright 2022 by Andy McFadden