1983 · Virgin Games

Falcon Patrol

This minisite was contributed by air. It’s agent-generated and needs a human editor. Clone https://github.com/gamesexplained/gamesexplained and follow kit/START.md to curate games/c64/falcon-patrol with me to Gold.

A jump jet, six desert airstrips and a horizon that wraps. Almost nothing on the screen is what it appears to be made of.

01 · One frame, rebuilt from memory

The play screen, drawn from the snapshot

A reconstruction, not a screenshot. Screen memory, colour memory and the character set are read out of a saved machine state and drawn here by hand, so every pixel below has a byte behind it.

Screen memory at $0400, colour memory at $D800, characters at $3000. The background colour is not one value: the interrupt at $4AC0 changes it twice on the way down the screen.

The three bands are the whole of what the interrupt does. It fires at raster lines $01, $8A and $D2, writes a background colour, points itself at the next line and leaves. Sky, ground and panel are one screen wearing three different backgrounds.

02 · The character set

256 glyphs doing six jobs

The font holds the digits, an alphabet, the town, the runway, the radar canvas and the blast crater. Click any glyph to see its bytes and where it is used.

Glyph $00 · digit 0 · bytes at $3000

The digits sit at $00 to $09, so a glyph's index is the digit it draws and the score can be written straight from a counter. The letters run $81 to $9A, which is the C64 screen code for a letter plus $80. A search for text in the usual encodings finds nothing at all in this game.

03 · Controls

The input byte the game writes for you

The joystick is read once a frame into $0C. Before anything uses it, the game rewrites it. Ceilings, floors, take-off and dying are all the same mechanism: forge the byte, and every routine downstream believes the pilot did it.

What the player is holding

What the aircraft is doing

The byte the game acts on

$FF

Nothing held, nothing forced.

Bits are active low: a clear bit means held. Rules at $5705, $5718, $5724, $5731 and $573D.

Two of these are restrictions a player feels without being told. Below sprite Y $7C the fire bit is forced high, so the guns stop working near the ground. And a stationary aircraft cannot shoot at all: $5790 returns early when the horizontal velocity is zero.

Take-off and death look alike and are one instruction apart. Taking off runs ora #$FE, which sets every bit except the climb, so the only control still connected is up. Dying runs lda #$FD, which sets the byte outright to down-held. The first waits for the player. The second does not.

04 · The world

256 columns, six airstrips, no edges

The map is one byte per column in a table at $3800. The index wraps in eight bits, so flying far enough in either direction brings the same airstrip back around.

world_x $00

Each mark is one map column. The six airstrips are written $16 $17 $16 $17 at columns 4, 40, 84, 136, 172 and 200. A bombed strip becomes $6F $70 and only comes back when a life is lost.

Landing wants three things at once, tested at $559A: the aircraft stopped, at sprite Y exactly $84, with an intact strip under both of the two columns it samples. Getting two of the three gives nothing.

05 · Difficulty

The wave size reads your score, not your progress

Every write-up of this game says the waves grow as you survive them. The code at $4A80 never counts waves. It reads two digits of the score.

0 points → 2 aircraft

ScoreAircraft in a wave
0 – 3992
400 – 14993
1500 – 99994
10000 – 103992

The routine tests the thousands digit $13 and the hundreds digit $14. It never looks at the ten-thousands digit $12.

Score ten thousand and the difficulty resets to the opening wave, because the digit that rolled over is the one nobody tests. Kills are worth 25, 50, 100 and 200 within a wave: the award starts at 25 and $5E10 shifts it left after each one.

06 · Collision

Why the missiles go through

The game has a long-standing reputation for shots that pass through an enemy without effect. The reason is in how a hit is detected: the missile and the aircraft are different kinds of object, and only one of them can raise a collision.

no collision

The aircraft is a hardware sprite. The missile is a character, glyph $12, written into screen memory. Two things of different kinds cannot raise a sprite-to-sprite collision, so the hit is found from the other side.

When an enemy sprite reports that it overlaps the character layer, $4262 works out the cell under it and reads three cells. If one of them holds a glyph between $0C and $13, the aircraft dies. The missile has to be inside that three-cell window at the moment the register is read. A shot that is one cell early or late overlaps on screen and counts for nothing.

07 · Sound

Music: the title tune, and two effects that sweep the filter

Twenty-nine notes, stored as raw SID frequency values in six tables at $2A00, with the note lengths in a seventh at $2AE0. The first player below reads those bytes. The second runs two of the game's sound effects from its own code, through a model of the SID that includes the filter.

note 0 / 29

Each column is one note: the three bars are voices 1, 2 and 3, and the width is how long it is held. Frequency to pitch uses the PAL clock, 985248 Hz divided by 16777216.

The random numbers come from the same chip. Voice 3's oscillator output at $D41B is read whenever the game needs a number: which way a wave arrives, where it spawns, and which frame of the debris cloud a wreck shows.

Entering a name on the high-score table and being shot down both move the SID's filter cutoff by hand, one register write at a time. Neither follows the frame. Name entry has no clock at all: it waits by counting, 256 passes of inc $2D at a time. The crash runs once a game tick, 30 times a second.

Name entry. Accepting a letter calls sid_setup_name_entry ($4E00), which puts voice 1 through the filter at full resonance ($D417 = $F1), low-pass at volume 15 ($D418 = $1F), with the cutoff at $7F in $D416. name_entry_accept_letter ($4DE5) then takes voice 1 straight back out ($D417 = $F0) and gates a triangle ($D404 = $11), so the clicks while choosing are unfiltered. Choosing _, or a twelfth letter, plays the name back with voice 1 routed through the filter again. For each letter, $4E30 gates a sawtooth ($D404 = $21) at a pitch from the tables at $2FC0 and $2FE0, eight notes a semitone apart, repeated, and the loop at $4D46 writes $D416 255 times, from $FE down to $00, one step every 2,056 cycles. Each letter's sweep lasts 0.53 seconds before $D404 = $10 releases it. The script enters ACE. The stick is read once per pass of the cursor loop ($4DC0), 0.13 seconds, and it presses right once or twice between letters, holds each press of fire for 0.2 seconds, then walks left to _. The click's pitch is $17 in $D401 over whatever an earlier sound left in $D400, which the port starts at 0.

Shot down. flight_sound_update ($4800) runs once a tick while flight_flags bit 3 is set. On the tick of the hit it copies the 25 bytes at $4F19 into $D400-$D418 and gates all three voices. Voice 2 is noise synced to voice 1 and voice 3 a ring-modulated triangle, both through the filter at full resonance ($D417 = $F6), low-pass. Voice 1 is noise outside it. While the wreck falls, each tick raises the high frequency bytes of voices 1 and 3 by one and the cutoff by two, from $22 until it reaches $6D 38 ticks later. Once the wreck is down and stopped, the bytes at $4F32 put all three voices through the filter ($D417 = $F7) with noise on voices 1 and 2, and the cutoff falls one step a tick from $5F to $3F. $4866 then turns the gates off. The long releases of voices 1 and 3 fade until the life ends 191 ticks later and sid_silence ($4C30) zeroes the chip. The script is a jet hit in level flight at sprite Y $60. The game's own flight code drops the wreck a pixel a tick to the floor at $BF, where a falling wreck was seen to stop on a running machine: 96 ticks of dive.

The port was checked against the game's own routines running in a 6502 simulator on the game's bytes. All 814 writes of the name entry land on the same cycle, and all 403 of the crash on the same tick, in the same order, and the same frame by frame as the player hands them on. The port counts the processor's cycles as if nothing interrupted it or took cycles from it; on the machine, anything that does makes the name entry slower. The crash's ticks are 32,768 cycles apart, the timer's $7FFF latch. The engine noise loaded from $4F00 at the start of each life is filtered too, but it is not played here: its cutoff is set from an enemy aircraft's position ($5EDE-$5EF0), and the enemies' movement is not ported.

The Filter switch plays both sounds three ways, and changes them while they play. Off leaves the filter out. 6581 is the original C64's chip, on the cutoff curve the reSID emulator (version 0.16) measured from one such chip. That curve is nearly flat low down, so each name sweep spends its last quarter between 420 and 220 Hz, and the dive's cutoff climbs from about 250 to 2,800 Hz. 8580, the chip of later C64s, puts the same dive between about 1,700 and 5,400 Hz. Real 6581s differ from one another in where each setting puts the cutoff, and they distort, which this model does not (site/lib/sid.js).

08 · Quirks

Things the manual does not mention

09 · The copy on the disk

What the crack changed

The image analysed here is not the original release. It is a 1997 crack, and the group left their working notes in memory where the game never reads them.

first the game fucked up sometimes, I just changed something in the IRQ

in the game was no routine which sets the places downwards when you entered your name in the highscorelist, so I coded a routine

From a scroll text at $0E80, signed Back Alien / Remember, October 1997. Nothing in the running game reads it.

So the interrupt and the high-score table are theirs, and anything said here about how names are entered describes the crack rather than the 1983 game. Their trainers are single-byte pokes, and each one identifies a variable: turning DEC $1D into LDA $1D at $417C gives unlimited lives, which is as good a proof as exists that $1D holds the life count.