Commodore 64 · 1985 · Novagen Software

Mercenary: the source

This minisite was contributed by air. It’s currently claimed by air who is editing it to reach a Gold tier standard.

Every routine, table and variable the game uses, with the names and descriptions given to them. Click an operand to follow it; the counter beside a label lists what refers to it. The reference below the fold is rendered from the game's facts file.

Technical reference: memory map, timing, tables, cheats

Current truth for this game. The workflow lives in kit/skills/; how this understanding developed lives in agent-history.md. Every fact names the routine or table it comes from. Unless marked live, a fact comes from reading the code in work/handover-7200.vsf (the start-up, orientation.md) or work/play-ground-0808.vsf (play). Numbers are decimal unless written with $. A square is written XX-YY: column X, then row Y, as the LOC readout shows it.

Build

The reset trap

The original's $8004 holds CBM80, so the KERNAL's reset routine takes the game for a cartridge and jumps through $8000 to $2170:

$2170  SEI
$2171  LDX #$00
$2173  STA $2180,X
$2176  DEX
$2177  BNE $2171     original: offset $F8
$2179  INC $2175
$217C  JMP $2170

The branch goes back to LDX #$00, so X is 0 at every store: the loop writes A to $2180 for ever and never reaches the INC/JMP that would have wiped memory page by page. Branching to $2173 (offset $FA) would have made it a wipe. The crack zeroes the signature and changes the offset to $F9, which lands on the $00 operand of LDX, a BRK. Live: a reset of the original leaves the program counter in $2171-$2177 with a screen of garbage (reference/reset-original-hangs.png); the crack resets to BASIC's READY (reference/reset-crack-basic.png).

Memory map

WhatWhere
Zero pageposition, angles, speed, maths accumulators, printer, keys; swapped with $BF02-$BFFF around KERNAL calls ($B8F6)
Scene tables$0100-$01BF, $02C0-$02FF, $0340-$07FF: 64 vertex slots (world, view, projected, screen, clip flags), edges $0580/$05C0
Event scriptsword table $0800-$0865 (51 scripts), byte code $0866-$1248
Dictionary239 words $1249-$151B, pointers $2120/$2210
Canned messages64 token lists $151F-$16D2, pointers $20A0/$20E0
Object models32 at $1700-$2098, 23 at $3B25-$3FFE; pointers $2800/$2840
After save/load, reset trap$2160 (SEI, JMP $80BA), $2170 (above), inside the dictionary's pointer table
Room pointers$2300/$23C0 (rooms $01-$AE)
Objects64; positions in nine split tables: X $2880/$2480/$2540, height $28C0/$24C0/$2580, Y $2900/$2500/$25C0 (low, middle, high); room $2940; flags $29C0; objects in view $2981 on (count $B2)
Citybuilding model per square $2600/$2700; square status $2B00; roads $2C00-$2FFF; view radius by height $2A00
Rooms174 records $3000-$3B23 (room $AE starts at $3B16; $3B24 spare)
Bitmaps (bank 1)$4000-$553F and $6000-$753F, 160 × 136 multicolour pixels each, double-buffered
Hand-over copier$5000-$502E
Drawing tablesrow addresses $5540/$55D0, pixel masks $5660, column offsets $5700, clear masks $7760
Spinning points, script variables$57A0-$57DF, $57E0-$57EF
Maths tableslogarithm $5800, antilogarithm $5900, sine $5A00/$5B00
Screen matrix$5C00: rows 0-16 the view's colours, rows 17-24 the panel, message window $5F78-$5F8E, sprite pointers $5FF8
Openingscript $7001-$71B3, start-up $7200-$75D5 (hand-over only)
Road lists (play)points $7540, edges $7580/$75C0
Gun sight sprite, dial tape$7640, $7680-$76ED
Credits$7700 (BCD), offer $7704, figures as text $7720
Panel character set$7800-$7FFF
Cartridge header, angle and BCD tables$8000-$80B9
Code$80BA-$BBC1 with tables inside, and $BD00-$BE9E (row fills)
Missiles and ships in flight$BDA3-$BDFF
Motion record, carried list, counters, script state$BEA0-$BEFF
Zero-page copy$BF00-$BFFF
Building models29 at $C000-$CF66; 15 road pieces and 62 more at $E000-$FFCF
VectorsNMI $FFFA ($8009), IRQ $FFFE

Start-up and the opening

Timing

The main loop

$855C, looping through JMP $855C at $859E and $85EF. Every pass runs one script operation ($8AB2) and the hired ship ($8634), then one of two branches on $A8 (0 on the surface, non-zero inside a complex; set by the movement code at $A1E8, $A298, $A2F3, cleared by CTRL + Q at $80FD and by a door out to the surface at $90C2):

SurfaceComplex
$A544 sines and cosines of the view angles$A544
$94E0 the square: reload its building when the square changes$AF6E wait for line 224, swap buffers, fill sky and ground
$A320 view matrix (in a craft)$939E keep the player inside the walls
$A4B2 turn the building's spinning points$9437, $90BE doors
$AF6E swap and fill$96D8, $96E7 the room's box
$9054 roads (skipped when $EF is set, beyond ±99)$9706 objects, keys T and B
the square's building, when $77 = 0$B2FE keys and stick
object 0's step ($85C5), $9706 objects$A165 movement
$B2FE, $A165
fire, the hit follow-up, moving objects, the attack ship

Weapons and moving objects therefore run only on the surface.

Numbers

The view

Drawing

The panel

The city

SquareScriptName (from the script)Model
03-0836Novabill (no reprisal)$E88D, a giant head
02-0337the author's advert$CE24, spelling ENCOUNTER!
12-0338Sabins Cube$CF25, a cube on its corner, turning
06-0039Walton Monument$EF57
06-0340St. Stallards$F3D3
10-0141Moorby Arch$F2D4
07-0042Tyler Point$F003
08-0743Bosher Stadium$C3D1
12-1344Jordan Airport$E1D3
02-0445Vector Henge$EC4A
07-0946the brother-in-law's house$E655, a cottage
01-0447Mechanoid Fort$EBE9
15-0248Coach and Horses$F448
13-04, 03-13, 09-13, 04-15, 09-1549TRAITOR!$C82E, the Commodore logo
14-05, 14-08, 12-11, 06-15, 11-1550GOOD SHOW!$C7B2, the Atari logo

Live: the logos (reference/sign-commodore-1304.png, reference/sign-atari-1405.png), and "TRAITOR!" when 13-04's sign was shot (below). The manual's Science Museum at 03-01 ($FE98) has no name of its own: $2B00 = $43 gives it script 35, the one for unnamed Mechanoid squares. Scripts 36-48 print "YOU HAVE JUST DESTROYED" and the name, and all but 36 then set off an attack by the square's owner.

Objects

Moving

RecordCraftCeiling byteThrustTop speedZzap!64 13
0the brother-in-law's new ship (object 0)$5A0.906**75 (11475) / 3837—
1Dominion Dart$0A ($5A with the Poweramp)0.391 (doubled)4950 / 1650 (9900 / 3300)4950 / 1650
2car (ground only)—0.195825825
3jet$5A0.1957400 / 8257400 / 825
4Palyar diamond$010.3911650 / 16501650 / 1650
5land Dart (ground only)—0.90638373837
6CHEESE$5A0.7819900 / 33009900 / 3300
7interstellar ship————

Lifts

Underground

Weapons and attacks

Money and trade

ObjectPalyarMechanoid
Neutron Fuel (29)200,000 ($88)250,000 ($46)
Energy Crystal (31)100,000 ($AD)100,000 ($32)
Large Box (34)35,000 ($93)80,000 ($30)
Useful Armament (35)65,000 ($92)120,000 ($22)
Winchester (43)100,000 ($90)250,000 ($12)
Databank (45)100,000 ($AA)
Mechanoid (12)250,000 ($9A)
Essential 12939 Supply (42)50,000 ($9B)
Gold (38)100,000 ($A2)
Catering Provisions (44)60,000 ($A3)
Medical Supplies (41)40,000 ($A5)

The best total, the Dart not bought: 1,400,000 + the 500,000 reward + 9000 = 1,909,000, the maximum Zzap!64 18 printed.

The event scripts

OpHandlerDoes
0nonethe table entry is $0000; no script uses it
1$8C36print the tokens that follow, up to a 0
2$8C4Ezero the seconds clock $E3-$E4
3, 4, 5$8BB2, $8BA3, $8BBEjump, call, return
6$8B47test the square: $74 and $7A against two operands
7$8AF0test clock < operand
8, 9$8C57, $8B41call machine code (9 tests its carry); no script uses them, and neither could return correctly ($8C69 returns into op 13)
10$8AE5test operand < a new random byte $E8
11$8B60test the last key (SHIFT and CTRL ignored), then clear it and beep
12$8B37test object $57EE carried ($941A)
13, 14, 21$8C6C, $8C73, $8C98set, add, and sum four-byte BCD figures (13 turns the ADC at $8C89 into BIT)
15, 30$8BF5, $8CDBset and copy the script variables $57E0-$57EF (setting $57ED also writes "XX-YY" at $BEF9)
16, 17, 18$8CF3, $8CFF, $8B2Dset, clear and test bits of $BEC0
19$8B24test the player in room $57EF ($CD)
20, 27$8D22, $8D34print canned message n (27 picks n at random)
22, 23$8D46, $8B03add to and test $BEBD (saturating at +127/−128); no script uses them
24, 25$8AD0, $8D0Dtest a byte of memory > value; write a byte
26$8B72test square $57ED destroyed ($2B00 bit 7)
28$8D40forget the key ($B4B2)
29$8B17test object $57EE in room $57EF ($2940)
31$8B57test the player in square $57ED ($B7); no script uses it
32, 33$8BDB, $8B7Cset or clear, and test, $29C0 bits of object $57EE
34$8B0Etest the craft the player is in ($A9)

Text

Three alphabets on one character set ($7800):

WhatGlyph for a characterSeen
Message windowASCII + $80: $C1 is A, $A0 space, $ED ?, $EE !, $EF ' (the letters are holes in solid cells)live: "DO YOU WANT TO BUY?" read from $5F7A
Panel labelsASCII − $20: $21 is A (EL is $25 $2C), the Atari's internal screen codelive, screen row 18
Figuresthe value: $00-$09 are 0-9; $8B +, $8D −live, the LOC, ALT and SPEED readouts

Stored text is ASCII in capitals, the last character of a word with bit 7 set. In messages, values 0-9 stand for the digits, $0A-$0F for

DART"), and $6D, $6E, $6F for ?, ! and '. "LOAD NO. O-9 ?" is written with the letter O, which has the same glyph as the digit 0 ($7E78 and $7800 hold the same bytes). Inside a word or a literal a zero byte prints as 0; only the print operation's scan for the end of its text ($8C40) would stop at one, so a zero in a script's literal would leave the script pointer short. A message is a list of tokens, ended by 0 ($8E1E):

TokenMeaning
$01-$07word n of the dictionary, printed straight after the previous one (endings such as S and ED)
$08-$EEword n, after a blank
$EFa blank
$F0-$F7the word whose number is in $57D8-$57DF, after a blank; no message uses them
$F8-$FEfigure 8 + (token AND 7), the four BCD bytes at $7700 + 4 × (token AND 7), formatted into $7720
$FFliteral text follows, up to the character with bit 7 set

Controls

The keyboard reader $B281 scans the matrix itself and forms a key number row × 8 + column (the KERNAL's numbering), with bit 6 for SHIFT and bit 7 for CTRL; the last key found wins, and $E9 is latched when the number changes. The joystick is control port 2 ($B2FE): directions to $80, fire to $81; stick input skips the keyboard for that pass.

KeyNumberWhere
D (drop)$12$B33F → $98FE
L (leave a craft)$2A$B349 → $98C8
+ and − (throttle trim, while held)$28, $2B$B356, $B361 → $B499
CTRL + S, CTRL + L (save, load)$8D, $AA$B36C, $B377 → $B382: SAVE/LOAD NO. O-9 ?, the digit through $BC50 into $825E, PRESS RETURN WHEN READY, RETURN → $819E, anything else cancels
CTRL + RETURN (pause, until the next new key, which is then acted on)$81$B335
CTRL + Q (quit the situation)$BE$B3E5: stack reset, JMP $80E8
E (lift)$0E$B3F6 → $B3FD
digits, SHIFT + digits, SPACE, ← (throttle)through $BC50$B47E
T (take), B (board)$16, $1C$970A, $970E in $9706
Y (yes)$19script key tests (op 11), $0AB6, $10CF

Sound

All through $B958 (voice 1) or $B93A (voice 2), four bytes a sound from $B987; $B910 loads all 25 SID registers from $B921 at start-up (volume 15, filter off).

SoundVoiceEventWhere
01key beep: E, D, T/B, CTRL + Q/S/L, the save digit, a script key match; L only in a craft; the throttle keys only on the surface in a craft$B94D
41building destroyed$876E
81a missile launched, yours or the attacker's$8882, $8947
$0C1the attacker's missile hits you$8983
$101your missile hits a ship$884B
$141walking into a wall$9413
$181crash or hit flash$B24D
$1C, $201, 2transporter chime$AEDC, $AEE1
$242engine$B61A
$28, $2C2gate-off fadesstart-up
$302lift hum, pitch from $BBA5 each frame$BA87, $BAE9

Save and load

Hardware register census

Every absolute reference in the listing (work/census.txt); indexed references cover the ranges shown.

RegisterWritten atRead at
$D000, $D001, $D010, $D017, $D01B, $D01D (sprite 0 at 160, 116, expanded, in front of the view)$817F, $8189, $8191, $8177, $816C, $817A
$D011 (modes, per band)$81BF, $B9C3, $BA00, $BA42$B250
$D012 (raster)$8169, $B9DC, $BA14, $BA31$731C, $74E8, $AA9E, $AF76, $B255
$D015 (sprite enable)$937D
$D016$B9CD, $BA4A
$D018$B9BE, $B9FB, $BA3F
$D019, $D01A (raster interrupt only)$8171, $B9E1, $BA19, $BA36; $8174, $81A5
$D020 (black border)$818E
$D021 (per band)$B9C8, $BA05, $BA45
$D027 (sprite 0 colour)$8184, $91DF, $B23E
$D400-$D418 (all, at start-up)$B915,X; $D400-$D40D $AF45,X
$D401, $D404-$D406 (voice 1 or 2 by Y)$B963-$B97B,Y; $D404 also $922B, $94D3
$D408, $D40B, $D40D (voice 2)$B607, $BA87, $BAE9; $741E, $743B, $AF4E, $BB33; $B613
$D40F (voice 3, the tick), $D412$8DCD, $8DF9; $B91D
$D800-$DBE7 (colour RAM)$80CE, $BB61-$BB6A (the view), $B4CC-$B536 (panel)
$DC00-$DC03 (keyboard, port 2)$B28E, $B29A, $B283, $B300, $B28A$B291, $B29D, $B303
$DC0D, $DD0D (CIA interrupts off)$8147, $814A$814D, $8150
$DD00 (VIC bank 1)$8142

The scripts also poke $D404 and $D40B. Nothing touches the sprite registers beyond sprite 0, $D01C, $D022-$D026, or the CIA timers.

Corner cases

Live tests

Cheats

Pokes that change the game within its own parameters. Each one names the variable it changes and whether it has been tested live. Untested pokes are labelled as candidates.

EffectPokeStatus
Stand on the 08-08 lift patch (on foot at 08-08, then press E: down into the Colony Craft's hangar)$73 = $72, $79 = $72 (X and Y middle bytes); in VICE's monitor > 73 72 and > 79 72live
routines tables variables strings branch labels

Loading listing…