(back to project page)

Missile Command Video Memory

Missile Command's display has some unusual characteristics. The display width is a friendly 256 pixels wide, but the height is an odd 231 lines (4:3.61 ratio). It uses a typical indexed color scheme, with an 8-entry palette. Most of the screen has 2 bits per pixel and so can only show 4 at a time, but the last 32 lines of the screen support 3 bits per pixel, so all colors can be shown. The colors in the palette can be changed on the fly, providing low-cost color-cycling animation for highlights and explosions.

While many other contemporary games used sprites or vector generators or other hardware tricks to speed up rendering, Missile Command just plots pixels into a frame buffer. It does, however, have one trick up its sleeve.

Most instructions that access RAM will see the 2bpp pixel buffer from $0640 to $3fff, with the 3rd bit stored from $0200 to $05ff. When an instruction whose opcode has the bit pattern %---00001 takes more than 5 cycles, however, the hardware asserts the MADSEL (Multiplexed Address Select) signal. This remaps the memory access so that addresses from $1900 to $ffff provide a linear view into the frame buffer, with one byte per pixel (%XY0----- for 2-bit areas, %XYZ----- for 3-bit areas).

To trigger MADSEL accesses, the code will use indexed indirect addressing mode instructions, i.e. "LDA (zp,X)" and "STA (zp,X)". These have opcodes $81/$a1, and always take 6 cycles on the 6502. Almost all rendering in the game is done with MADSEL mode, the only exceptions being clearing parts of the screen and scrolling the text at the bottom while in attract mode.

MADSEL is slower for bulk writes, like erasing the screen, because it changes one pixel per byte rather than 4 or 8. When setting individual pixels it's faster, because the address calculation is simpler and it isn't necessary to read the existing pixels and mask away the bits that don't change before setting the new value.

Colors

In the normal (non-MADSEL) view of video memory, the 2bpp RAM area stores 4 pixels per byte, split across the high and low nibbles. If the bits in the first byte are %ABCDEFGH, then the color index for the pixel at X=0 would be %00000DH0, X=1 is %00000CG0, etc. This means the 2bpp areas use the even-numbered entries in the palette.

The 3rd pixel bit is a bit more convoluted. There are 8 pixels per byte, but the memory map puts the first 16 lines in the even-numbered bytes, and the last 16 lines in the odd-numbered bytes. Within a byte %ABCDEFGH, X=0 would use bit H, X=1 would use bit G, etc. The value is combined with the 2bpp data as the low bit, allowing use of the odd-numbered palette entries.

The color palette is 3-bit RGB. The values have the form %----RGB-, where 0=on and 1=off. This yields:

RGB HexColor
%000$00White
%001$02Yellow
%010$04Magenta
%011$06Red
%100$08Cyan
%101$0aGreen
%110$0cBlue
%111$0eBlack

Missile Command is available in a "cocktail" cabinet, for which players sit on opposite sides during a 2-player game. This requires rotating the screen 180 degrees, which can be done by flipping the graphics vertically and horizontally. The vertical flip must be done in hardware because of the 3bpp color zone, but the horizontal flip is done in software. (Not everything is flipped; for example, the city backdrop will be mirror-imaged for cocktail player 2.)

Palette Usage

While playing the game, most elements appear in one of four colors: one for the background, two for "stable" foreground elements, and one that is updated on every frame. (The palette entry is simply incremented. Because colors are xxxxRGBx, it changes visibly every other frame, so it's actually cycling at ~30Hz.) The set of palette entries that cycle is determined by a bit mask (stored at $ec).

The colors in the palette are determined by the wave number. On the first wave, the background is black, the incoming missile trails are red, and player missile trails are blue. On the 3rd wave, the incoming missile trails are green instead.

The color assignments used for wave 1 are:

#ValColorUses
0$0eBlackBackground, scrolling text foreground
1$02YellowCity / launcher backdrop, scrolling text background
2$06RedICBM trails, smart bombs, scores
3$08CyanCity color #1
4$++(cycle)ABM target 'X', missile heads, explosions
5$++(cycle)(unused)
6$0cBlue(unused)
7$0cBlueABM trails, Crosshairs, 1UP arrow, ABM ammo icons, city color #2

The list of colors used for each wave can be found on the wave guide page.

The code that draws explosions is careful to preserve the 3rd bit. This prevents explosions from eroding the city backdrop. Because the cities are drawn with odd-numbered palette entries, explosions that cover a city briefly obscure it (because palette entries 4 and 5 are cycling at the same rate), then replace the city colors (3/7) with the background color (0/1). This leaves the city outline in the city-backdrop color, creating the sense of wreckage rather than an explosion-shaped hole. The code that draws the missile head and trail does not do this, so you'll see a small divot in the city outline where the missile hit.

The code that erases the crosshairs will restore the previous contents, unless it appears that something drew over the crosshairs (based on the pixel color). This is why you can move the crosshairs over things, like the text at the start of the wave or the flashing target 'X', without erasing it from the screen.

Video Memory Line Start Addresses

This table shows the location in memory where line N starts. The "Addr" column shows the MADSEL-mode address, while "Bit2" and "Bit3" show the address in "normal" address decoding mode.

The software treats line 0 as being at the bottom of the screen. This makes the calculation of the base address trivial. For the 256-bytes-per-line mode, you simply take the line number, exclusive-OR it with $ff, and use that as the high byte of the pointer (the bottom line starts at $ff00). For the 64-bytes-per-line mode, you set up the pointer the same way, then divide by 4 (the bottom line starts at $3fc0). Accessing the 3rd pixel bit in "normal" memory is more complicated, but that's done infrequently, with the address hard-coded into the instructions.

Line Addr Bit2 Bit3   Line Addr Bit2 Bit3
230$1900$0640 114$8d00$2340
229$1a00$0680 113$8e00$2380
228$1b00$06c0 112$8f00$23c0
227$1c00$0700 111$9000$2400
226$1d00$0740 110$9100$2440
225$1e00$0780 109$9200$2480
224$1f00$07c0 108$9300$24c0
223$2000$0800 107$9400$2500
222$2100$0840 106$9500$2540
221$2200$0880 105$9600$2580
220$2300$08c0 104$9700$25c0
219$2400$0900 103$9800$2600
218$2500$0940 102$9900$2640
217$2600$0980 101$9a00$2680
216$2700$09c0 100$9b00$26c0
215$2800$0a00 99$9c00$2700
214$2900$0a40 98$9d00$2740
213$2a00$0a80 97$9e00$2780
212$2b00$0ac0 96$9f00$27c0
211$2c00$0b00 95$a000$2800
210$2d00$0b40 94$a100$2840
209$2e00$0b80 93$a200$2880
208$2f00$0bc0 92$a300$28c0
207$3000$0c00 91$a400$2900
206$3100$0c40 90$a500$2940
205$3200$0c80 89$a600$2980
204$3300$0cc0 88$a700$29c0
203$3400$0d00 87$a800$2a00
202$3500$0d40 86$a900$2a40
201$3600$0d80 85$aa00$2a80
200$3700$0dc0 84$ab00$2ac0
199$3800$0e00 83$ac00$2b00
198$3900$0e40 82$ad00$2b40
197$3a00$0e80 81$ae00$2b80
196$3b00$0ec0 80$af00$2bc0
195$3c00$0f00 79$b000$2c00
194$3d00$0f40 78$b100$2c40
193$3e00$0f80 77$b200$2c80
192$3f00$0fc0 76$b300$2cc0
191$4000$1000 75$b400$2d00
190$4100$1040 74$b500$2d40
189$4200$1080 73$b600$2d80
188$4300$10c0 72$b700$2dc0
187$4400$1100 71$b800$2e00
186$4500$1140 70$b900$2e40
185$4600$1180 69$ba00$2e80
184$4700$11c0 68$bb00$2ec0
183$4800$1200 67$bc00$2f00
182$4900$1240 66$bd00$2f40
181$4a00$1280 65$be00$2f80
180$4b00$12c0 64$bf00$2fc0
179$4c00$1300 63$c000$3000
178$4d00$1340 62$c100$3040
177$4e00$1380 61$c200$3080
176$4f00$13c0 60$c300$30c0
175$5000$1400 59$c400$3100
174$5100$1440 58$c500$3140
173$5200$1480 57$c600$3180
172$5300$14c0 56$c700$31c0
171$5400$1500 55$c800$3200
170$5500$1540 54$c900$3240
169$5600$1580 53$ca00$3280
168$5700$15c0 52$cb00$32c0
167$5800$1600 51$cc00$3300
166$5900$1640 50$cd00$3340
165$5a00$1680 49$ce00$3380
164$5b00$16c0 48$cf00$33c0
163$5c00$1700 47$d000$3400
162$5d00$1740 46$d100$3440
161$5e00$1780 45$d200$3480
160$5f00$17c0 44$d300$34c0
159$6000$1800 43$d400$3500
158$6100$1840 42$d500$3540
157$6200$1880 41$d600$3580
156$6300$18c0 40$d700$35c0
155$6400$1900 39$d800$3600
154$6500$1940 38$d900$3640
153$6600$1980 37$da00$3680
152$6700$19c0 36$db00$36c0
151$6800$1a00 35$dc00$3700
150$6900$1a40 34$dd00$3740
149$6a00$1a80 33$de00$3780
148$6b00$1ac0 32$df00$37c0
147$6c00$1b00 31$e000$3800$0200
146$6d00$1b40 30$e100$3840$0240
145$6e00$1b80 29$e200$3880$0280
144$6f00$1bc0 28$e300$38c0$02c0
143$7000$1c00 27$e400$3900$0300
142$7100$1c40 26$e500$3940$0340
141$7200$1c80 25$e600$3980$0380
140$7300$1cc0 24$e700$39c0$03c0
139$7400$1d00 23$e800$3a00$0400
138$7500$1d40 22$e900$3a40$0440
137$7600$1d80 21$ea00$3a80$0480
136$7700$1dc0 20$eb00$3ac0$04c0
135$7800$1e00 19$ec00$3b00$0500
134$7900$1e40 18$ed00$3b40$0540
133$7a00$1e80 17$ee00$3b80$0580
132$7b00$1ec0 16$ef00$3bc0$05c0
131$7c00$1f00 15$f000$3c00$0201
130$7d00$1f40 14$f100$3c40$0241
129$7e00$1f80 13$f200$3c80$0281
128$7f00$1fc0 12$f300$3cc0$02c1
127$8000$2000 11$f400$3d00$0301
126$8100$2040 10$f500$3d40$0341
125$8200$2080 9$f600$3d80$0381
124$8300$20c0 8$f700$3dc0$03c1
123$8400$2100 7$f800$3e00$0401
122$8500$2140 6$f900$3e40$0441
121$8600$2180 5$fa00$3e80$0481
120$8700$21c0 4$fb00$3ec0$04c1
119$8800$2200 3$fc00$3f00$0501
118$8900$2240 2$fd00$3f40$0541
117$8a00$2280 1$fe00$3f80$0581
116$8b00$22c0 0$ff00$3fc0$05c1
115$8c00$2300

Copyright 2021 by Andy McFadden