Commodore 64 · 1982 · Brøderbund Software

Choplifter

A 16 KB cartridge that draws every frame twice over in software and never switches on a sprite.

01 · One frame, rebuilt from memory

The picture is drawn one pixel at a time

The C64 has eight hardware sprites and Choplifter uses none of them. The game never writes $D015, the register that switches them on, and the register reads zero all the way through a game. The helicopter, the tanks, the jets, the people and the buildings are all plotted into a multicolour bitmap by the routine at $8FD3, a byte at a time.

What the screen costs in colour, it gets back in freedom. A multicolour bitmap pixel is two bits wide and can take one of four colours, and three of those four are fixed per character cell. Choplifter sets the play area to the same two values everywhere: the video matrix holds $2E for the sixteen sky rows and $26 for the five rows of ground, and colour memory holds 1 in all 840 cells of the play area. Red, light blue and white, on a background the raster interrupt changes on the way down the screen.

The same moment as the emulator draws it

The same moment out of the emulator, for comparison. The canvas above is built from the snapshot's bitmap, video matrix, colour memory and the character set copy at $5000, with the background switched at display line 173 the way the raster interrupt switches it.

02 · The shape table

Seventy-five bitmaps and no sprites

Everything the game can draw is in one table. $8009 holds 75 pointers and $80A1 to $8A8A holds the bitmaps. A shape is a width in bits, a height in rows, and then the rows, top first.

The width is in bits, not pixels, and thirty of the seventy-five are an odd number of bits wide. A shape is a run of bits that gets plotted wherever the object's X puts it, so which bit pairs with which is decided at draw time. The same bytes give different colours at an odd and an even offset. The two buttons below read the same data both ways.

03 · Two screens

Nothing is cleared, only un-drawn

There are two complete displays. VIC bank 0 has its bitmap at $2000 and its colour matrix at $0400; bank 1 has them at $6000 and $4400. One is shown while the other is drawn. $8D16 flips bit 6 of $0F, which is exclusive-ored into the high byte of every bitmap address, and $8D1F writes the matching bank into $DD00. One set of row tables serves both buffers because $40 is the whole difference between them.

Clearing a 8,000-byte bitmap twice a frame is not affordable, so the game never clears one. Every object that is drawn files a rectangle in a list, and next time that buffer comes round the list is replayed to paint those rectangles out. The sizes come from a table of 26 classes at $94AF, the lists live at $0D03 and $0DA3 with room for forty rectangles each, and $BBE6 swaps them.

classcolumnsrowsbytes rubbed out

The table at $94AF, 26 entries. $AA38 pushes a class and a position onto the list for the buffer being drawn; $BBE6 swaps the two lists and replays the other one to rub the previous frame out.

04 · Controls

One button, three meanings, decided by a count

The manual describes a short press, a medium press and a long press. In the code there is one threshold. The raster interrupt counts frames into $96 while the button is held; on release, $9774 compares that count with 15. Under 15 a shot leaves the helicopter. At 15 or more the machine starts turning, and it keeps turning while the button stays down, which is what makes a long hold come all the way round.

The direction is decided once, on the frame the count crosses 15, and the stick is only consulted when the helicopter is already near nose-on. At a full left or right facing the stick is ignored and the hold simply flips the machine to face the other way. Holding fire while landed does nothing at all.

The helicopter turned to face the viewer

The raster interrupt counts frames into $96 while the button is down. $9774 reads the release: under 15 it calls $97D2 and a shot leaves the helicopter; 15 or more it calls $97F1 and the machine turns.

05 · The tank

Five gun angles and a bearing window

A tank has three pieces, and each one is filed in the frame's object list on its own, so the hull, the turret and the barrel can be hit separately. The aiming is a table lookup. $A249 divides the bearing to the helicopter into five windows, one per gun position, and the tank moves its gun one position per fire-timer expiry towards the window the helicopter is in. It only fires on a tick when the gun is already pointing the right way, which is why a tank that has just been flown over takes several seconds to answer.

helicopter bearing

The gun has five positions. $A249 holds the bearing each one covers and $A244 the X offset the barrel is drawn at. The tank steps one position per fire-timer expiry towards the window the helicopter is in, and fires only once it is already pointing there.

06 · The jet

Four canned manoeuvres, stepped one record a frame

The jet does not steer. It has five states, four of which are lists of four-byte records read one per frame: a shape number, a step in X, and two vertical deltas. State 0 is not a list at all, it is the cruise, one shape held indefinitely. The reversal that makes the jet look as though it has thought about it is eighteen records that decelerate from +9 through +2 to −12 and flip the facing byte at the end.

07 · Progression

What gets harder, and by how much

difficulty $8Dlive tanks allowedtype-9 aircraft allowed

Difficulty is not the sortie number. It is $8D, raised at $9EBA the first time the player unloads after a pickup, so it tracks how well the player is doing rather than how long the game has run. At 0 only tanks can appear.

08 · The sound

Both engine voices are noise

Two of the SID's three voices carry the helicopter and both are set to the noise waveform. $9975 writes $81 to $D404: bit 7 selects noise, bit 0 opens the gate, and it stays open. Voice 1 is the rumble, and its frequency low byte is a variable the game walks: $0B5E climbs towards $C0 in steps of $10 while the machine is off the ground and falls back towards $30 when it lands. Voice 2 is the chop, fixed at $041A with a short envelope, re-gated every $94 frames, and $94 shortens from 17 to 8 as the rumble rises. The blades speed up because the engine does.

The line is the SID noise rate the value produces, f = value × 985248 / 16777216 on PAL. The sound is a browser approximation of filtered noise at that rate, not a SID emulation.

09 · Quirks and bugs

Things a player who finished it would not know

Seven of them, each traced to the bytes that produce it.

A shot fired while turning cannot hit anything

Objects carry two vertical numbers: a height and a base row, which stand in for depth. Everything that can be hit sits on the player's base row, $58, and the first thing the collision code at $B344 does is check that a shot is still on it. When the helicopter fires, $AD24 gives the shot a per-frame base-row step from the table at $AD86, indexed by how far round the machine has turned: -2, -2, -1, 0. Only a shot fired from a full side-on facing gets zero and stays on the player's row. Every other shot walks off it within a frame or two and is refused for the rest of its life. It still flies, it still looks right, and it passes through everything.

The free list is built four links too long

Actors are 10-byte records chained through a free list. $AA11 builds that chain with ldy #$1E and three deys, which gives 27 passes where 23 would do. The first 24 links are correct. The 25th wraps the index register and writes a link of 4 into the last record, and the three after it land in the middle of slots 1, 2 and 3. Nothing goes wrong, because the game has never had more than 24 actors alive at once; if it ever did, the 25th would be handed a record straddling the list head.

Half the tanks are born outside the world

A tank spawns on whichever side of the helicopter the code picks. The left-hand branch at $9F3D does a proper 16-bit subtraction, $55/$56 minus $01B0, into the actor's world X. The right-hand branch at $9F54 adds $B0 to the low byte with no carry and then stores $00 into $0B79, the high byte. The tank's world X is $00xx whatever the helicopter's is, which is off the left-hand end of a world that starts at $02B0, and $A16B deletes it on its first update. Nothing leaks and nothing crashes. The tank simply never arrives.

RESTORE is defused with a borrowed instruction

Pressing RESTORE raises an NMI, which the KERNAL sends through the vector at $0318. Choplifter points that vector at $9AAB, which is not a routine. It is the rti that ends the raster interrupt handler, one byte past the pla before it. The NMI arrives, returns immediately, and costs the game nothing.

The ground is not drawn

At raster $E0 the interrupt writes colour $0C to both $D020 and $D021. Below that line the bitmap is almost entirely zero, so what fills the bottom quarter of the screen is the background register showing through. The blue strip along the top of it is the only part anybody draws.

The game and the screen run on different clocks

The raster interrupt fires 51 times a second; the main loop completed 83 passes in 201 of those frames, about 21 a second, and the figure moves with how much is on screen. Some counters are stepped by the interrupt (the trigger hold $96, the rotor beat $95) and some by the loop (the pass counter $89, the message timers), so a busy frame stretches one and not the other.

Sixteen to a shed, not eighteen

The manual says the hostages are locked in barracks, eighteen per barracks. $BE52 puts 16 in each of the first three sheds and 8 in the fourth, which starts a sortie already open, with its other 8 walking about outside. That is 64, the number the game ends on when $0CEE reaches $40.