1984 · First Star Software

Spy vs Spy

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

Two spies search the same embassy at the same moment, each on their own half of the screen. The computer's spy plays with the map in front of it: it plans every route with a breadth-first search and knows from the start where every item and trap is.

01 · Simulvision

Two monitors drawn into one bitmap

Each spy has a monitor of their own, white above and black below, and both play at once.

One frame of a two-player game, rebuilt from memory by the site's renderer from the video chip's registers, the bitmap at $4000 and the screen matrix at $6000, recorded in the emulator (NTSC). 94,840 of its 94,848 pixels match the emulator's picture. The 8 that differ are two bitmap bytes in the trapulators, $43AD and $52AD, which the game rewrote while the frame was being drawn; the rebuild uses memory as the frame began.

The whole play screen is one multicolour bitmap. The monitor frames, the trapulators and the panels come from the disk as a finished picture (the file S2), and the game draws the two room views into it: nine character rows each, one above the other.

Two raster interrupts split the frame (irq_handler $8FA6, lines from $1C5C). At line 51 the first sets the top monitor's background colour and moves sprites 0-3 to the white spy's trapulator; at line 151 the second sets the bottom monitor's colour and moves the same four sprites down to the black spy's. "Where the interrupts fire" marks both lines on the frame.

The spies are not sprites. "Sprites off" removes every sprite from the frame, and both spies stay where they were. They are drawn into the bitmap from strips of shape data (draw_strip_line $794C), shifted to any pixel through pre-built tables and masked so the room shows around them. Moving a spy means copying a clean piece of the room back over the old position (restore_box $7A03) before drawing the new one. Sprites are kept for small things: the trapulator's arrow and inventory, the object in a spy's hand, and the aeroplane at the airport.

02 · The music

Music: two voices that loop at different lengths

The theme plays all through a game, on two of the chip's three voices; the third is kept for the sound effects.

The game's own music driver, ported from play_music ($954E) and played through the site's model of the SID at the NTSC clock, from the tune bytes $23DA-$254C. Checked against the original code run in a 6502 simulator: 20,000 frames, with the music switched off and on again with S, every register write the same and in the same order. The filter is never used. The lowest notes, below 100 Hz, barely sound on laptop and phone speakers.

A note is three bytes: the pitch and octave, the length, and the instrument. The pitch indexes twelve frequencies of the top octave ($23E2, $23EE) and the octave says how many times to halve them. The length picks one of eight durations, from 5 to 80 interrupts ($23DA); a length code of 8 or more ties the note on without striking it again. Each of 16 instruments is eight bytes ($240A): a waveform, a pulse width, an envelope, and a slide or vibrato that nudges the frequency every few interrupts.

The two voices loop at different lengths. Voice 2 reads 29 notes and starts again every 320 interrupts; voice 3 reads 41 notes in 960, exactly three of voice 2's loops. The whole theme therefore repeats every 960 interrupts, 16 seconds on an NTSC machine. The piano roll shows it: voice 2's notes repeat three times for every pass of voice 3.

Pressing S switches the music off (music_off $0263). Switching it back on starts both voices from the top rather than where they stopped.

03 · The embassy

Every level's embassy has a fixed map

The manual calls the embassy "selectable, yet randomly generated". The rooms and the doors between them are the same in every game of a level; what changes is what is in them.

Each level's map, drawn from the level tables in the listing: six door links for each room (level_layout_lo $BC61), and where the map button draws it (level_map_lo $BC71; the low four bits are the column, the next three the row, the top bit the floor). Rooms outlined in orange are the four that can hold the exit; ⇅ marks a ladder or a hole to the other floor. Click a room for its doors.

Each new game, build_embassy ($7EF8) copies the level's door links into the room records and then rolls the rest: which of four rooms has the exit, each room's colour, where along its wall each door stands, up to three pieces of furniture, one per wall, and which pieces hide the five items. The remedies go with the furniture: every piece of two kinds on the left wall holds a fire bucket, two kinds on the right hold wire cutters, and one kind each on the back wall holds the scissors or the umbrella (furniture_contents $0900). A spy who knows the furniture knows where the remedies are.

LevelRoomsMinutesTraps shownTraps allowedItem chanceExit roomsFloors

From level_rooms $BC91, level_minutes $1B0E, item_chance $BCA9 (the chance that an empty piece of furniture gets an item as they are dealt out) and exit_choices $BCC1, from which one entry is picked at random: level 1 lists room 3 twice, so its exit is in room 3 half the time. "Traps allowed" is section 07.

04 · The computer's spy

The computer's spy plans its routes with a breadth-first search

In a one-player game the black spy is the computer, and it moves through the embassy as if it had the map in front of it.

The search, ported from route_search ($A9EF). Click a room to set the goal (red outline), then another for the start (green). Each round, the search looks through every door of every room it reached in the round before, highest room number first and the doors from the last to the first, and marks the rooms behind them; the darker the blue, the later the round. Checked against the game's own routine in a 6502 simulator, with every level built by the game itself: all 6,667 pairs of start and goal give the same rounds and the same route.

The computer keeps a stack of tasks and picks its next goal from what each spy carries and what is already in the briefcase (cpu_choose_goal $A847). A goal can be a room, a room whose furniture holds a particular remedy, or a room where one of the items lies. The search runs until it reaches a room that passes the test, then walks back through the "came from" list to make the route (route_to_waypoints $AB03), one waypoint at each door.

The test reads the game's own tables of where the items are ($0210) and what each piece of furniture holds. The computer never has to search: it knows where every item, remedy and trap is from the start.

The IQ setting decides what the computer does with that knowledge. It checks IQ − 1 rooms of its route ahead for traps, and below IQ 3 skips the check three times in four (cpu_check_route $9D01). The IQ also sets how often it thinks of running from a fight (cpu_flee $A30F) and how fast it swings the club, from every 13 turns at IQ 1 to every 2 at IQ 5 (iq_swing_rate $1B1E).

05 · Score and rank

The airport pays 7 points for every second left

Points come from fighting, trapping and stealing, but the escape is worth more than all of them.

Score0
Held as0000
Clock7:00
Rank-

The game's scoring, ported from score_event ($8937, tables at $1D23) and final_score ($89AE). Checked against both routines in a 6502 simulator over 600,000 random events and 3,000 escapes, including scores below zero and above 9,900.

The score is four decimal digits. A spy who escapes gets 7 points for each second left on the clock, which on a 24-minute level can be over 10,000 by itself; every event in the table is worth 80 points or less, and most of them cost clock time too. The rank is just the thousands digit of the final score, one of ten from A KNEE HIGH SPY to GRAND MASTER SPY. On level 1, with 7 minutes, an instant escape would be worth 2,940.

Scores below zero are kept as the ten's complement with a sign flag, so a spy can climb back out of the hole, and the airport shows a negative score as 0. The top end is capped clumsily: an addition that runs past 9,999 sets the top two digits to 99 and keeps the bottom two, so 9,990 plus 80 for a won fight becomes 9,970.

06 · The clock

Secret: a second on the clock lasts 1.67 real seconds

Level 1 gives each spy 7 minutes. They last 11 minutes 40 seconds.

Real time0:00
Spy's clock0:00
Frames0
Frames a second100

The game's clock against a real one, at the NTSC frame rate of 59.83 a second. tick_clock ($9362) counts a hundredths digit from 99 down to 0 once a frame before it takes a second off. Measured in the emulator: in 600 frames the interrupt ran 600 × 2 times and a spy's clock lost 6 seconds.

The clock counts in hundredths of a second, and counts one hundredth each frame. That would be right on a machine running at 100 frames a second; on a 60 Hz machine every game second is 100 frames, 1.67 seconds long. Every time in the game stretches by the same factor, the penalties included: the 15 seconds for looking at the map cost 25 real seconds. A penalty does not come off the clock at once; it is queued and paid one second every ten frames, with a beep for each.

07 · Traps

Bug: the trapulator gives one trap fewer than the options screen promises

The options screen offers "TOTAL TRAPS AVAILABLE..12" on level 1. A spy can set 11.

LevelRoomsShownAllowed

Shown is twice the room count, as the options screen draws it (options_loop, $A0DD). Allowed is what the trapulator hands out ($9949). Tested in the emulator on level 1: with the trap count set to 10 the trapulator handed over a bomb; with 11 it refused.

The two numbers come from different variables. The options screen doubles the level's room count. The trapulator doubles $0225, which holds the room count less one, and refuses a trap once the spy's count is past that, so the count can reach twice the rooms less one. Time bombs are not counted, but once the limit is passed the trapulator refuses them too.

Each trap has one remedy, carried from the furniture: the fire bucket for the bomb, the wire cutters for the spring, the umbrella for the bucket of water over a door, the scissors for the gun on a string. Bombs and springs go into furniture; the bucket and the gun go on a closed door. A spy who walks into a trap without its remedy loses 80 points and 20 seconds and lies stunned for a while; with the remedy, the trap is used up and the spy gains 40 points for 10 seconds.

08 · The manual

Where the manual and the code disagree

The manual was written for three computers. On the Commodore, the code does these things differently.

The manual saysThe code doesWhere
The embassy is randomly generatedEach level has one fixed map; furniture, items, colours, door places and the exit are randombuild_embassy $7EF8
About 7 blows of the club killTen hits knock a spy outspy_walk $6B3D
12 traps on level 111 (section 07)$9949
Bread crumbs show the way back up to 9 roomsTen places are kept, on levels 1-4 onlytrail_step $8A93
Space or fire starts the gameOnly fire starts it; space is not readoptions_loop $A00B

09 · NTSC

Secret: on a PAL machine the game never starts

Outside North America, the disk loads and then the border keeps changing colour for ever.

Before play, wait_ntsc_sync ($8E38) turns interrupts off and waits for eleven raster interrupts in a row, each within 1,536 polls of the video chip's interrupt register, about 18,400 cycles. An American NTSC frame, 17,095 processor cycles, arrives in time. A PAL frame, 19,656 cycles, does not, and the routine starts again, changing the border colour each time. The game never reads the flag the Commodore's own ROM keeps for PAL or NTSC. Seen in the emulator: on PAL the load ends in that loop; on NTSC the same image plays.

10 · Leftovers

The assembler's symbol table is still on the disk

The end of the main program file holds part of the symbol table from the computer that assembled it, with the original programmer's names for the computer player's fighting code.

WhereNameValueThe listing's name

Decoded on this page from the listing's bytes at $BEC4-$BFEF: each entry a length byte (the length, mostly with $80 added), the name and a two-byte value; the first and the last are cut off. Where a value is the address of a routine the listing names, its name is beside it.

The names fit the code they point at: HOMECMBT at $A2B6 steers the computer's spy towards the other in a fight, UNHOMCMB at $A30F moves it away, and UNHOMIQ1 and UNHOMIQ2 are that routine's branches for IQ 1 and IQ 2. A second fragment at $254D holds names from an earlier build. The routine at $BFF0 was written over the table's last entries, a late fix squeezed into spare room.

Three more things were left in. A switch at $6603, 0 on the disk, would make every game record both joysticks into the tables the attract demo replays, which is presumably how the demo's moves were made. The computer player writes the number of each step it reaches into $D024, a colour register that the screen mode in use never shows: a debug trace left switched on. And $2DAC-$2DFF is a byte-for-byte copy of code from $8AAC, saved into the end of a data file.