1988 · Electronic Arts

Wasteland

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

How the C64 version of Interplay’s role-playing game draws its world, writes it back to the disk as the party plays, throws its dice, and hides its credits on a wall in Needles.

01 · The play screen

The play screen, rebuilt from memory

The map, its frame, the clock, the gauge on the right, the menu bar, and the roster are all drawn into one bitmap. Space shows the roster over the bottom of the map.

Two frames of play, recorded in the emulator a moment apart, and drawn here by the site's model of the video chip from the 9,001 bytes it reads: the colours at $5C00, the bitmap at $6000, and one byte at $7FFF, with the colour memory. Drawn the same way, every one of the 104,448 pixels of each frame, border included, matches the emulator's own picture.

The C64 has a text mode, and Wasteland never uses it. The screen is a multicolour bitmap at $6000, in which each pixel is two dots wide. Each 8 by 8 cell has four colours: two from its byte at $5C00, one from the colour memory, and the background, which every cell shares. Every letter on the screen is drawn into that bitmap by the engine, from a font of its own at $C600 ($1F03). The font has 102 glyphs, from the space up to the corners of the frame and the steps of the gauge.

The screen shows 24 rows of cells, not 25 ($7F55). The bitmap's last row, $7E00 to $7F3F, is hidden, except for a moment while the screen shakes ($077A), and the memory from $7E00 up is where the game loads each of its programs: the title, the Ranger Center, the game itself, and the rest.

The clock in the top right corner is the game's own: hours and minutes ($B143). The gauge below it is a Geiger counter's ($0AA1). It lights from the bottom as radiation comes into view, and stays dark when nobody in the party carries a Geiger counter.

02 · The map window

Walking the world map

The game shows the world through a window 19 squares wide and 9 high, with the party in the middle. Walk it with I, J, K and L, the game's own keys ($7F18).

Time - Square -

The view as the game draws it: 19 by 9 squares, each drawn by a port of the engine's square routine ($0B53), with the party's marker, tile 7, on the middle square ($AF1D). The screen cuts half a square off every edge. The window refuses the steps the game refuses ($AD5B), shows the messages the squares show, and moves the clock as a step does ($AF32). It starts where the party stood in a frame recorded in play.

A square is 16 screen pixels each way, so the 19 by 9 squares need 304 by 144 pixels. The screen shows 288 by 128 of them, and half of every square on the edge is cut off ($2323).

The party never leaves the middle of the window, so each step moves the world under it. The game does not draw the window again. Instead, the game moves the picture one square and draws the two rows or columns on the side the picture moved towards: the new edge, and the one beside it that was cut in half before ($AE2F, $AE4E, $AE6D, $AE8C). The window here draws all 171 squares every time, which gives the same picture.

Before each step the game asks whether the square ahead lets the party in ($AD5B). An edge of the map, a group of monsters, a failed check, an exit answered N, and an obstacle all say no. The window asks the same question, with a port checked against the game's code for every square of every map in all four directions. The window answers Y when an exit asks, so the party can walk across the square of a town, but it stays on the world map. It never passes a check, because it has no party.

A step on the world map takes four minutes (the map record's byte +$35), so 15 steps make an hour ($AF32). From 18:00 to 05:59 the world map's 36 radiation squares are drawn as the radiation sign ($0B80): press Midnight to see them. On the world map, one step in 100 can also bring a random encounter ($B015). The window does not roll for one.

There is no joystick. C64-Wiki and GameBase64 say the game takes one in port 2, but the only code that reads the control ports is the keyboard scan ($2967), and it never reads the port that stick is on. Held there in play, a stick moves nothing. A stick in port 1 shares its lines with the keyboard, so pushing it types keys: up brought up the first character's sheet.

03 · Pictures

Pictures that move by exclusive or

The title screen moves. A satellite drifts in from the right and turns, a line flashes between it and the top of the picture, and the satellite bursts into rings, while stars twinkle around the planet. The portraits in the picture window move too: the doctor blinks and talks.

ChannelScript entryDelayLast frame

The title and five portraits, drawn and moved by a port of the engine's picture code ($0549 to $06E0) from the bytes in the Source tab. Step runs the tick until the channels step once. The table gives each channel's script entry, the steps before it reads the next one, and the frame it drew last. A box switches a channel's frames off. The port was run beside the engine's own code on the kit's 6502 simulator: 1,088 cases and 5,649,956 ticks, the bitmap compared after every tick or every step, no difference. The emulator's screenshots of the title and the Ranger Center show the same pictures.

A picture is a header and its frames. The header's first four bytes give its area: first column, first pixel row, last column and last pixel row. Five words follow, each counted from the start of the picture: the base frame, then the scripts of up to four channels, with 0 for a channel the picture does not use. Starting a picture ($0586) draws the base frame over the whole area and puts each channel at the start of its script. Each of the five portraits here covers columns 1 to 11 and pixel rows 16 to 99. The title covers columns 1 to 32 and pixel rows 8 to 135. Its own header says column 36, and the start-up cuts that to 32 after unpacking it ($8001), which leaves the logo in columns 33 to 36 alone.

A frame is stored column by column. Each block gives a column, a pixel row, and an escape byte, then the bytes that go down that column. At the bottom of the area they carry on at the top of the next column. The escape byte followed by a count and a value makes a run. A block whose column is $FF ends the frame. The base frame is stored over what was there. Every other frame is combined with the screen by exclusive or (XOR). The decoder ($0651) rewrites two of its own instructions to choose between the two ($0648, $064E).

XOR undoes itself: drawing the same frame twice puts back what was there. One frame both makes a change and takes it away. The doctor's channel 1 draws the same four bytes six times in a row, and his mouth opens and closes three times. A byte of 0 would change nothing, so in these frames a 0 ends the block, and the next block says where to carry on.

Each channel follows a script of three-byte entries: a delay and a frame. When its delay runs out, the channel draws the next entry's frame and takes that entry's delay. A $FF where the next entry would be sends the channel back to the start of its script ($05F0 to $0646).

The channels step once in every 512 calls of the animation tick ($05C5), which counts them in $B1 and $B2. The engine calls the tick each time it polls the keyboard ($2534) and 192 times for each step of the text window's scrolling ($2161), so a picture moves while the game waits for a key or prints. On the title that comes to about seven steps a second.

The boxes in the table take a channel's frames out of the picture. The page keeps what each channel has XORed in so far and XORs it out again, which leaves the picture as if that channel had never run. The game has a switch of its own for each channel, $5BFB to $5BFE, which stops a channel where it is ($05EB). The destruction of Base Cochise uses them. Module 4 holds channel 1 still while the picture of the base is shown, then, after the screen shakes, stops channel 0 and lets channel 1 run ($CA27, $CA54).

On the title, channels 1 to 3 make ten stars twinkle by flipping one bit of each. Channel 0's 51 entries are the satellite, and they do not quite cancel out. One run through them, 74 steps, leaves five pixels near the top of the planet a different colour, and the next run changes them back.

The title is drawn from the start-up's copy, which it unpacks at $8A00. The portraits are packed on the disk. The Ranger Center's header asks for 72 pages ($7E05) and the loader ($2890) reads them all, but the program is only the first eight. The rest is what follows it on side 1, five packed portraits among it ($B500 to $C5D1). The unpacker is not kept in memory: each time, the engine reads it from track 35 over the third of the font's four pages, the lower-case letters at $C800, runs it, and reads the font's page back ($0524). The page unpacks the portraits with a port of that code, into the buffer at $CA00 where the engine unpacks them ($2631).

04 · Sound

Sound: three effects, and one of them never sounds

Every sound the game itself makes comes from a table of three records, played on one voice of the SID: an explosion, a click, and a third that is never heard. The title and the radio program bring sounds of their own.

Loading the sounds…

Every sound in the game, played by a port of the routine that makes it through the site's model of the SID, with the bytes each one is made from. The slider sets the Geiger counter's distance to the radiation. Each port keeps the routine's timing to the cycle: a test ran the game's own code on the kit's 6502 simulator beside the port, 1,789 runs and 172,557 SID writes, and every write came in the same frame, at the same cycle, with the same value. The player changes the SID's registers once a frame (20 ms), and three sounds lose something to that. The whistle's 243 steps are heard as four. The fanfare's 7 ms gaps between notes last 20 ms or vanish. Close to radiation, two clicks in one frame play as one. The title's explosions are a low rumble, nearly a third of it below 100 Hz, which laptop and phone speakers barely play: listen with headphones. The pitches are a PAL machine's.

The engine has one short routine for sound ($C993), and it plays on the first of the SID's three voices. A sound is an 11-byte record in a table of three ($C9BC): the voice's frequency, pulse width and envelope, the three filter registers, and a control byte. The routine turns the voice's gate off, sets the volume to 15, and writes the record: bytes 0 to 6 to the voice's registers from the top down, bytes 7 to 9 to the filter's, and the control byte last. The control byte picks the waveform, and its bit 0, the gate, starts the sound.

Record 0 is an explosion: noise that dies away. It plays while the screen shakes for half a second ($0746), which happens when the party uses one of seven items, from the grenade to the RPG-7, at events in twelve of the maps, and when Base Cochise is destroyed ($CA4C).

Record 22 is a short click of noise. Every step the party takes clicks with it ($AE26), the Geiger counter clicks with it, and so does the title as it types its story ($8427).

Record 11 never makes a sound. Its control byte, $A4, asks for noise and sawtooth with the gate bit clear, and a voice whose gate stays shut stays silent. The Ranger Center plays record 11 when it refuses a choice, then waits 1.2 seconds ($8188), and the game does the same after one of its messages ($92BC). Modules 1 and 2 call it too. The what-if button plays the record with the gate bit set. Noise mixed with another waveform is something the model only approximates, so the result is a guess.

The Geiger counter clicks from the game's main loop ($7E63). Every fifth pass of the loop counts a number down. At 0 the counter clicks, and the next pass draws a new number from 1 to the distance plus 1 ($24C4). The distance is to the nearest radiation square in view, about 10 for each square, from a table of 50 ($0921). The loop goes round about a thousand times a second, 20 times a frame by a count in the emulator, so the clicks come at random, faster as the radiation gets nearer. With none in view, or nobody carrying a Geiger counter, the distance is $FF and nothing clicks. The bar beside the slider is the game's gauge ($0AA1): 18 segments, one fewer lit for every 8 of the distance.

DistanceWhere the radiation isClicks a second
2on the party's square72
10one square away32
14one square diagonally24
50five squares away8.0
98the corner of the view3.9

The title has sounds of its own, from a routine in the start-up program that can use any voice ($7E03). The routine adds the voice's offset, 0 for voice 1 and 7 for voice 2, to every register it writes but the volume, the filter's included, as if each voice had a filter of its own. The SID has one, at $D415 to $D417. For voice 2 the three filter bytes land at $D41C to $D41E, past the volume register, the last of the chip's sound registers. Nothing is lost. They are the same three bytes that voice 1's record has just written to the real filter registers, and those send no voice through the filter.

The siren comes as the title's story turns to war ($7E77). Two sawtooth voices climb slowly together, hold, fall, and fade, eleven seconds in all. Then the gauge on the right fills, two whistles fall from 3,806 Hz to 168 Hz in 75 ms each ($7EFA), and 14 explosions follow at the intervals of a table ($8133). The title's explosion is not the game's record 0. The title plays noise at so low a frequency that the noise changes only about 750 times a second, a rumble rather than a crack ($7F13).

The radio program plays a six-note fanfare before the Cochise reward and before each promotion ($7E06). Its frequencies are tuned to the American machine's clock, on which the notes are C5, C5, C5, E5, F5, and G5. A PAL machine plays every note 65 cents flat.

The Ranger Center's program loads with SID code in it ($8621 to $8694), but the code is the fanfare's. The load reads past the end of the Ranger Center's program into the radio program's file, which follows it on the disk, and nothing in the Ranger Center calls it. The Ranger Center's only sound is the silent record 11.

05 · Radiation

Bug: armour stops radiation only where the message number is even

A radiation square burns every member of the party who is not wearing a rad suit. The code means armour to be no help against it, and on most squares armour is a help after all: the square's message number decides.

MapSquaresDamageMessageArmour
Map 0, The world map223d6The ground seems to glow here. (23)no help
Map 0, The world map145d6The ground seems to glow here. (23)no help
Map 19, Base Cochise, robot assembly184d6A wave of heat washes over you and the ground shimmers. (28)counts
Map 19, Base Cochise, robot assembly206d6Your eyes tear and a ringing fills your ears. (29)no help
Map 19, Base Cochise, robot assembly2210d6Your whole body feels ready to combust, and your skin itches mightily. (30)counts
Map 20, Base Cochise, the reactor222d6On the floor is an open container of U235. (69)no help
Map 20, Base Cochise, the reactor163d6The floor is covered with Plutonium dust. (70)counts
Map 20, Base Cochise, the reactor64d6A pile of Wastelandium glows brightly here. (71)no help
Map 26, Needles422d6none (0)counts
Map 26, Needles124d6none (0)counts
Map 31, Needles, waste pit and ammo bunker16d6A broken radioactive waste container is lying here buried in the trash. (12)counts
Map 38, Las Vegas, the Mushroom Cloud temple164d6none (0)counts

Every record of a radiation square in the 42 maps, read from the maps' listings: its message (byte 0), its dice (byte 1), and the squares that use it. The messages are decoded by the port in "Five bits a letter" below. A message of 0 shows nothing. Each map opens on the Maps tab.

The world map at night, the party on a radiation square: The ground seems to glow here.

The handler for radiation squares ($82E0) shows the square's message, then rolls the square's dice of damage for each member in turn. A member wearing the rad suit, item 41, is passed over. Before the first member the handler sets the flag at $A0 that tells the damage routine to skip the armour roll ($82E7).

The damage is taken off through a routine shared with the squares that test the party ($9082). For CON, that routine sets the same flag again, from bit 0 of byte 0 of the square's record ($90AF-$90B4). On a square that tests the party, byte 0 holds flags, and bit 0 set means armour is no help. On a radiation square, byte 0 is the number of the message it shows. So the armour roll, one die for each point of the member's armour class ($A8BB-$A8C5), is skipped where the message number is odd, as the handler meant, and made where it is even.

Seven of the twelve records have an even number, among them the 10d6 squares of the robot assembly at Base Cochise. The world map's two records both show message 23, so armour is no help there.

In the emulator, the party stepped onto the world map's 5d6 square with every member's armour class set to 30. With message 23 the four members lost 22, 23, 16, and 14 CON. With the record's byte changed to 22, the armour roll was made four times and nobody lost any.

06 · Experience

A kill in melee earns double experience

A monster killed with fists or a hand weapon (a knife, a club, an ax, a chainsaw, or a proton ax) gives its killer twice the experience a shot gives for the same kill. The melee round adds the kill's worth twice, one call after the other. Guns, rockets, and explosives add it once.

The manual never spells this out. Its section on hand-to-hand combat (page 16) says the Rangers have an advantage over the citizens in hand-to-hand combat, and nothing about experience. Its section on promotion (page 11) says You're awarded experience points each time you accomplish a difficult task or win a difficult battle, and nothing about how a kill is made.

A kill in melee

  1. $815DAD D6 80LDA melee_line_index_op
  2. $816020 E8 A9JSR kill_experience
  3. $816320 DD 03JSR api_add_experience
  4. $816620 DD 03JSR api_add_experience

Experience 0

A kill by a shot

  1. $A713AD 5D A6LDA damage_line_op
  2. $A71620 E8 A9JSR kill_experience
  3. $A71920 DD 03JSR api_add_experience
  4. $A71CA9 00LDA #$00

Experience 0

The two places the game pays for a kill, from the Source tab: the end of a melee attack and the end of a shot. Step runs one line of each. The kill is worth 40 here, as each Shambler Ghoul was in the emulator. There, three kills in melee each took their killer's experience from 0 to 80, 40 at each of the two calls.

The kill's worth comes from $A9E8: the base value in the monster's record, added once for each of its armour dice and once more. $03DD adds that number to the experience of the character whose turn it is, and leaves the number in place. The melee code calls it at $8163 and again at $8166, with nothing between that could change the number or the character. A shot calls it once ($A719), and so does an explosion for each monster it kills ($A08A).

Map 1's graveyard: KIT hammers the Shambler Ghoul for 6 points of damage killing it.

The game prints the kill and not the experience, so nothing on the screen shows the second payment. Experience brings promotion, the first at 1,024 for a new Ranger ($7F1B). Each promotion gives two points to spend on attributes and a higher rank, and a skill improves with use only while its level is below the rank. Every kill made in melee counts twice toward the next promotion.

07 · Checks and shots

Two dice that keep rolling

Every skill check, attribute check, and party member's shot comes down to one throw: two six-sided dice, added up, and thrown again whenever they match. The repeats add on, so a pair of sixes followed by a 3 and a 4 makes 19 ($0845). Each repeat is a one-in-six chance, so high totals are rare but possible.

A check has a target of 15 + 5 × its difficulty ($0867). For a skill check, the throw plus the attribute that goes with the skill plus 4 for each level of the skill must reach it ($0C6A). The table at $0DD8 pairs each skill with its attribute, such as Climb with AGL, Picklock with DEX, and Medic with IQ. An attribute check adds the attribute alone ($0D0A). A throw under 5 fails whatever the bonus, which is one throw in nine.

Every throw of 5 or more is added to the character's experience, pass or fail ($0C41). A pass can also raise the skill a level, but only while the level is below the character's rank and below the difficulty. Then a d10 no higher than half of (difficulty − level), rounded down, plus 1 raises it ($0CA2). A new character has rank 1 ($7FDD), so no skill improves by use before the first promotion.

– chance to pass

The chance is exact for fair dice: every way the throw can reach each total is counted, doubles and all. The chart is the throw alone; red totals fail at once. Roll runs the game's own check, ported from the engine ($0C29, $0D0A) and tested against the original code on a 6502 simulator: 27,324 skill checks over every skill, levels 0 to 10 and difficulties 0 to 31, and 3,072 attribute checks, with the same random bytes, gave the same results and used the same bytes.

A shot uses the same throw ($A584). To it are added DEX, 4 for each level of the weapon's skill, and the fire mode: 4 for a burst, the rounds left in the gun for automatic fire, and 10 for an explosive ($A5D6). A full Uzi on automatic adds 40. The total must reach a target that grows with the distance, rounded up to 5 feet, at one of three rates ($A793, $BD48). Pistols, thrown weapons, and explosives start at 0 and add 5 for every 5 feet. SMGs, carbines, the Flamethrower, the Ion beamer, the Mangler, and the Sabot rocket start at 10 and add 2. Every other gun, from the rifles to the Meson cannon, the LAW, and the RPG-7, starts at 20 and adds 1, so beyond 25 feet a rifle is an easier shot than a pistol, and beyond 50 feet than an SMG.

The target is 7 higher when the monster the code looks at has no gun ($A76E). The line it looks at is the one numbered like the target's group, which is not always the line being shot at ($A74B, $A766).

A throw of 3 is not a miss by itself. One more die is thrown, and a 1 spoils the shot however easy it was: rockets and explosives fail to detonate, and any other gun jams and loses its rounds ($A58D). That is one shot in 108.

A burst that hits lands 1 to 3 hits ($A0B3), and automatic fire one d4 of hits for every 4 rounds ($A98C). A burst spends 3 rounds and automatic fire all of them, hit or miss ($A02B, $A220).

Each hit rolls the weapon's dice, adds the attacker's luck bonus, and takes off the monster's armour roll, stopping at 0 ($A7DE, $A9AD). Anti-tank weapons roll 2 × their dice − the armour when the armour is no more than their dice, and the armour roll is not taken off ($A818, $A677). An explosive rolls its damage once, without luck, and every monster in the group takes it less its own armour roll ($A054, $AA90). The luck bonus has no floor: a hit it takes below 0 kills, as the throwing knife's section below shows.

Fire
– chance to hit

A party member's shot at a monster group, with the guns, rockets and explosives of the item table ($3108). The chart is the damage of one hit after armour; 0 is in red. Fists and hand weapons fight in melee, which works differently and is not shown. Fire runs the game's routines, ported and tested on the simulator against the original code with the same random bytes: the hit roll with every item in the table (15,360 cases), the damage with every item against armour 0 to 15 and beyond (10,368), the hits of bursts and automatic fire (5,436), and the range targets for every distance (606). Which monster each hit lands on is drawn at random too; that draw is left out.

The chances above are given twice. The first is exact for fair dice. The second comes from the pairs the engine's own code threw on the simulator ($24E3), taking each repeat to fall the way a first pair does. They differ because the game's dice are not fair, as the next section shows.

08 · Random numbers

Bug: the dice come up doubles one throw in five

Two fair dice match one throw in six. Wasteland's match one throw in five, and a pair of ones comes up twice as often as it should. A double means the dice are thrown again, so the bias reaches every check and every shot on this page.

– doubles

fewer than fairmore than fair

The first pair of the open throw, first die down and second across. In the emulator, a stub in play threw 4,096 pairs with the game's own routines and the same instructions between the two dice as the throw itself ($084A-$0850), each pair after the seed had been stirred 1 to 256 times, as waiting for a key stirs it. On the simulator, the engine's code threw 2,000,000 pairs on the kit's 6502 simulator from random moments: a random raster line, cycle, timer value, and seed for each.

The game has no table of random numbers. Each random byte is the sum of three numbers ($24E3): the raster line the video chip is drawing, the low byte of a timer that counts down once a cycle, and the last random byte plus one. A die ($24D6) keeps the byte's lowest three bits, 0 to 7, throws again at once on a 6 or a 7, and adds 1. The raster line and the timer move with the clock, so a byte made a few cycles after another depends on it. After a draw of 7, the die thrown again at once comes out 2 or 3 two times in five, where a fair die would give 2 or 3 one time in three.

Every skill or attribute check fails at once on a throw below 5 ($0C73, $0D29). That takes a first pair of 1 and 2 or 1 and 3, one throw in nine with fair dice. The game's dice do it 13.1% of the time on the simulator and 12.9% in the emulator. The checks and shots above give the chances both ways.

A single die is uneven too. In 5,000 dice from the Ranger Center's attribute rolls, watched in the emulator, ones came up 1,043 times, fours 727, and sixes 826, where fair dice give 833 of each.

09 · Creating a character

Bug: the attribute roll that loses its best dice

A new Ranger's attributes are meant to be the best three of five dice. A slip in the sort loses the best dice instead, and the roll averages barely more than three dice would.

Creating a character at the Ranger Center rolls ST, IQ, LK, SP, AGL, DEX, and CHR, then MAXCON as one more roll + 18 ($7FDD). Each roll throws five dice into five slots, the first in slot 4 and the last in slot 0 ($8382). Then an exchange loop moves larger dice up into the higher slots, and the roll adds slots 2, 3, and 4 ($83B0). Sorted properly, those would be the best three of five.

The sort goes down from slot 4 to slot 1. Each pass loads the die in its slot once ($8397) and compares it with every slot below. When a slot holds a larger die, the two change places. The pass should now go on with the larger die it has just taken in. Instead, the branch at $83AB goes back to the compare, so the pass keeps comparing the small die it loaded. A second exchange in the same pass then writes over the larger die that moved up, and the small die ends up in two slots. Thrown in the order 1, 2, 3, 4, 5, the slots end as 1, 1, 1, 1, 5, and the roll is 7 where the best three make 12.

Thrown, in order
In hand

The game's roll
The best three
What a working sort leaves
the game's rollthe best three of fivethree dice, for comparison

The ranger program's routine, ported step by step: in hand is the die the pass loaded, slot X is outlined in blue and the slot it is compared with in amber. The port was run against the original code on a 6502 simulator for every one of the 7,776 ways five dice can fall, comparison by comparison, and again for 3,000 throws with the game's own dice routine; every slot and every sum matched.

Over all 7,776 throws the roll averages 10.62, barely more than three plain dice at 10.5, where the best three of five would average 13.43. The roll comes out lower than the best three in 72% of throws and is never higher, so a new Ranger's seven attributes lose about 20 points between them on average. Watched in the emulator, 32 rolls at the Ranger Center ended slot for slot as the loop predicts, and 21 of them came out below the best three of their dice.

10 · Damage

Bug: an unlucky Ranger's throwing knife can kill anything

A shot's damage is the weapon's dice plus the attacker's luck bonus, and below an LK of 9 the bonus is negative. When it takes the sum below 0, the number wraps round to 65,535, and whatever was hit dies. One weapon can get there: a Throwing knife, thrown by a Ranger with an LK of 3 or 4, when both of its dice show 1.

The fight in map 1's graveyard: Hell Razor attacks and hits 1 Shambler Ghoul for 65532 points of damage spinning it into a dance of death. Thrasher misses.
In the emulator, Hell Razor with an LK of 3 and a Throwing knife, against the Shambler Ghouls in map 1's graveyard. The knife's roll, 10, was changed to 2, a 1 and a 1, as the dice handed it back (at $A7E7). The sum came out as 65,535, and the ghoul's armour took 3 off it.
  1. $A7DE20 F4 A7JSR weapon_damage_roll
  2. $A7E18D EC A7STA damage_roll_lo_op
  3. $A7E48E F0 A7STX damage_roll_hi_op
  4. $A7E720 B8 A7JSR luck_bonus
  5. $A7EA18CLC
  6. $A7EB69 00ADC #$00
  7. $A7ED48PHA
  8. $A7EE8ATXA
  9. $A7EF69 00ADC #$00
  10. $A7F1AATAX
  11. $A7F268PLA
  12. $A7F360RTS

The routine that works out a shot's damage. The two stores write the roll into the operands of the two ADC instructions, and the luck bonus comes back from $A7B8 in X and A. Nothing after the second ADC looks at the carry.

LKLuck bonusA sum below 0 needs a roll of
3 or 4−32 or less: the Throwing knife's two dice both showing 1
5 or 6−21: no thrown or fired weapon rolls fewer than two dice
7 or 8−10: never
9 and up0 or morenever

The bonus is (LK − 9) / 2 rounded down below 9, 0 from 9 to 12, and (LK − 12) / 2 from 13 ($A7C3). The lowest attribute a Ranger can be rolled is 3.

A shot's damage is worked out by one routine ($A7DE). It rolls the weapon's dice ($A7F4), then adds the attacker's luck bonus with two plain additions, the low byte and then the high byte. A negative bonus is held as a 16-bit number whose high byte is $FF, so a roll smaller than the penalty comes out as a number near 65,535. The monster's armour roll takes a few points off that. The rest is more than any monster has: the most is about 40,000, for the Night Terror in Finster's mind maze (map 23), and the subtraction from the monster's hit points leaves it dead ($A6CC-$A6DF). A blow in melee adds the same bonus through the engine's own addition ($07C6), which stops at 0, so a punch can do no damage but never wraps.

Only one weapon has dice few enough. An LK of 3 or 4 takes 3 off, so the roll has to be 2: two dice showing 1. Every gun and rocket rolls three dice or more, and explosives leave the luck out, which leaves the Throwing knife's 2d6. With fair dice the knife shows a 1 and a 1 once in 36 throws, and a Throwing knife is used up when it is thrown ($A53A). No loot square in the 42 maps holds one. Christina, who can join the party in downtown Needles, carries one ($40AE).

The attribute roll in the section above makes the low LK easier to come by. Of the 7,776 ways its five dice can fall, 261 give an LK of 3 or 4, where the best three of five would give 6.

11 · Text

Five bits a letter, and an alphabet for every program

Wasteland's messages, from the copyright notice on the title screen to the words on a gravestone, are stored as runs of five-bit codes. Only the menus and a few prompts are plain strings ($1DE1).

A code is a number from 0 to 31. Codes 0 to 29 each pick one entry of a 60-byte alphabet at the start of the text block. Code 30 makes the next letter a capital. Code 31 says that the next code reads the alphabet's second half, entries 30 and up ($28F4). The reader takes the bits from each byte lowest first, five at a time, so a code often starts in one byte and ends in the next ($291D).

The packed-text widget needs JavaScript.

Pick a text block and a message, then play it or step through it a code at a time. The bits are shown in the order the game reads them, bit 0 of each byte first; each group of five is one code. The alphabet below lights the entry each code reads, and the screen fills in as the game draws it. Change the character, the count or the pronoun to see the switches choose. The decoder and the printer are ports of the engine's own routines, checked against the game's code on a 6502 simulator for all 4,920 messages in the 52 blocks: 0 differences.

The engine, the game, the title and start-up, the Ranger Center, the radio, the utilities, four of the location modules, and each of the 42 maps carry a block and an alphabet of their own. Every alphabet is in order of use, so a common letter costs five bits, a rare one ten, and a capital five more. Each block has its own order, though every map's alphabet starts with the space and e. The engine's starts with the code that switches between one and many, because 88 of its item names hold three of them each.

After the alphabet comes a table with one word for every four messages ($1E0E). The word says where that group of four starts. To print the third message of a group, the game decodes the two before it and throws them away, because no table entry says where a message inside a group begins. The table stays a quarter of the size, at the price of up to three wasted messages each time the game prints one.

Alphabet entries below $10 are not letters but orders to the printer ($1F42): a new line, the character's name, a wait for RETURN (space and back-arrow go on too), and the framed text window. Three of them choose between words. {0C}his{0C}her{0C} prints the first word for a man and the second for a woman. {0E}him{0E}her{0E}it{0E} prints the word chosen for the creature the game is describing. {0A}one{0A}many{0A} looks at a count ($2063). The engine's item names use it, so Kni{0A}fe{0A}ves{0A} prints Knife or Knives.

The printer does not draw a letter the moment it arrives. With word wrap on ($1F92), letters collect in a 41-byte line buffer ($F49C) until the line is full or a new line starts. The game then draws the line up to its last space, and the word cut off there moves to the start of the next line ($200C).

The 52 blocks take 142,029 bytes. The 4,920 messages in them hold 201,043 characters, which as plain text with a zero after each message would take 205,963 bytes. Packing saves 63,934 bytes, nearly a third.

12 · Credits

Secret: the credits are a poster on a wall in Needles

The title screen names one person, Alan Pavlish, who wrote the program. The full credits are inside the game, as a film poster on a wall in downtown Needles, and five of the people they name turn up elsewhere in the game: on a gravestone, a wanted poster, a sign, a plaque, and a wall of chalked names.


    

Screens from the emulator, in order. The party was put in each map with the game's own map loader, next to the square, and the button carries the key that was pressed next: K walks south, I north, and RETURN goes on with a message. Under the screens is the message as the map's text block holds it ($514F, $4300), decoded by the port in "Five bits a letter" above.

WhoCredited forWhereWhat the square says
Ken St. Andredesign, mapsMap 1, Quartz: a grave at column 27, row 24The gravestone is inscribed: Ken St. Andre, 4/28/47--4/28/87.
Nishan HossepianmapsMap 13, Sleeper Base, level 2: six wallsA sign on the wall here reads: "Wanted for crimes against computer software, Nishan Hossepian, $5000 Dead or Alive."
Bill DuganmapsMap 33, Needles downtown: column 7, row 27A sign just inside the entrance says "Bill Dugan, Representative, Temple of Blood."
Bill Dugan, Chris ChristensenmapsMap 34, Needles police station: a wall at column 24, row 13Chalked on the wall: Dugan, B -- Rad overdose / Christensen, C -- Anthrax / Gonif, T -- Cirrhosis
Stance NixonmapsMap 38, the Mushroom Cloud temple in Las Vegas: a wall at column 1, row 12This is a poster of a man in T-shirt and shorts typing at a computer. The plaque under the picture reads STANCE THE GREAT MAP MAKER.

Every message of the 52 text blocks, 4,920 in all, decoded and searched for the names on the poster. The squares come from the maps' records: each of these messages is shown by the squares named, in the map as it loads. Each map opens on the Maps tab.

The poster is one square of wall, at column 20, row 25 of map 33. Walking into it shows a message and turns the square into the next of four, so each bump shows the next poster: The Radheads! coming soon, the credits, Humongous and the Mutants! next week, and Mario, Luigi and the Fireballs! next month, then the first again.

The credits give 26 credits to 20 people, under programming, design, maps, graphics, general help, and playtest and development, and end "Rated PG-13. Coming soon to a theatre near you." Apart from the title's own lines, which name Electronic Arts, Interplay Productions, and Alan Pavlish, no other text in the game credits anyone.

The poster lists Ken St. Andre under design, beside Michael A. Stackpole, and again under maps. The two dates on his gravestone are forty years apart to the day.

13 · The disks

Four disk sides and no file names

Apart from its two boot files, Wasteland keeps nothing on its disks that a directory listing would show. Its own loader reads sectors by number, and every map, program, and save has a fixed track and sector on the four sides.

This picture needs JavaScript and the site’s c64.js.

The four sides. A row for each track, track 35 at the top, and a box for each sector, coloured by what the side holds there. Pick a file and its sectors light up in the order the loader reads them. Logical numbers lines the sectors up as the files count them, Physical order as they pass the drive’s head. The places come from the directories on the four master sides. The order is the engine’s own arithmetic ($FFAC, $FD0B), ported and run against the original on a 6502 simulator for every pair of track and sector bytes, 131,072 cases, and for every file through the game’s own read and write calls, with no difference.

A file is three numbers: a track, a logical sector, and a count of 256-byte pages. The loader reads that sector, steps to the next, and stops when the count runs out ($FF00). The numbers for the maps and the item tables are kept in two directories on track 35, four bytes a file: the track, the sector, the count, and the side ($2790). Every side carries both directories, but only the side that holds a file fills in its place. Elsewhere the entry is three zeros and the side. The game reads the side first, prints INSERT SIDE and that number until the side is in the drive, and then reads the directory again from it ($27C3).

Logical sectors are the files’ own numbering. A table for each speed zone turns them into the drive’s physical sectors, which are numbered in the order they pass the head: logical n is physical 10n mod 21 on tracks 1-17, 8n mod 19 on tracks 18-24, 7n mod 18 on tracks 25-30, and 6n mod 17 on tracks 31-35 ($FFD3, $FDEA, $FDD8, $FFE8). A file runs down from its first logical sector, and in every zone the next one is 11 physical places further round. Ten other sectors pass the head between two of a file’s.

The count is all a load knows of a file’s length, and the Ranger Center’s is too big. The program is eight pages, but its header asks for 72, so the load also brings in the 64 sectors after it: the radio program, side 1’s two item tables, packed modules, tile sets, and portraits. Nothing in the Ranger Center reads them, and the game program loads over all of it when the party leaves ($7EEE).

One place off

After logical sector 0 the loader moves down a track, skipping track 18, and starts the new track at the logical sector a 35-byte table gives ($FD0B, table $C930). The table holds each track’s top sector, track 1’s first. The loader reads it by the track number, which counts from 1, so it gets the entry for the track above: the one it has just left. Within a speed zone the tracks have the same top sector, and this changes nothing. At three zone edges it does. Track 30 is entered at logical sector 16, track 31’s top, where its own is 17. Track 24 is entered at 17 where its own is 18. Track 17 gets the entry for track 18, which the step never enters, and that entry is 0. The strip under the map shows the 35 bytes, and the three that differ open a file that crosses them.

The disks were written by the same rule, so the game reads what was meant. The world map starts at track 20, logical sector 9, runs through track 19, takes the one sector of track 17 the step gives it, and goes on at track 16. The other 20 sectors of track 17 hold filler on every side, and so do the top sectors of tracks 24 and 30. Read with each track entered at its top, the world map would come out with 1,477 wrong bytes, and maps 2, 5, 11, and 19, the game program, and the Ranger Center would go wrong as well. The game writes through the same routine, so a map saved back lands where it will next be read.

The world is written back

The maps on a side are the world as the party left it. Leaving a map writes its pages back to the sectors they were read from ($2856, called from $25BD). A loot bag’s contents are rolled the first time it is opened, and the roll goes back to the disk with the map ($9194). A shop writes its item table back after a sale or a purchase, so its stock lasts ($CB56), except after a purchase that sells out its stock, which leaves without writing it ($CBBA). Maps 5 and 11 can escape this. The flags sector, track 35 logical sector 8, sends every map number from $80 to $BF to map 5 and from $C0 to $FC to map 11 ($2621), and a map entered by one of those numbers is never written back ($2858).

The save and its sum

The party itself lives in memory at $F400-$FBFF: the party tables on the first page, then a page for each character. Saving copies the fourteen zero-page bytes that hold the map number, the party’s place, the clock, and the game time into the first page. The save then adds the eight pages up into a one-byte sum, scrambles them with it, and writes page n to track 35, logical sector 7 − n ($18EA).

This widget needs JavaScript and the site’s c64.js.

One page of the save. Left, the page in memory as the play snapshot holds it. Right, the same page as the last save wrote it to the disk, made by the port’s scramble. Change a member’s cash in the game and save, patch it on the disk with the old sum left in place, or write the whole save unscrambled with 0 for the sum, and the start-up’s reading of the result appears below. The port matched a save the game made in the emulator, byte for byte (cash 12,345, sum $6B). The sum, the scramble, the save and the start-up’s check were each run against the game’s own code on a 6502 simulator, 1,549 cases, with no difference.

The sum ($1918) runs over bytes 0 to 254 of each page. Each byte is added to it, then the byte’s offset, with the carry from the first addition going into the second. The scramble ($1944) EORs each of those bytes with twice its offset and with the sum. Byte 255 of each page is left out of both, and the sum itself is kept there on the first page, in clear, at $F4FF.

There is no slot to choose. The game saves at the end of every change of map, when the new map’s side is in the drive ($2600), and Save asks for side 1 first ($8820). The save goes to whichever side is in the drive, with no side check, so each side keeps a save of its own. At the title, Start asks "Use last saved game (Y/N)?". Y loads side 1’s save ($8284), and N reads all four sides and takes the save with the longest game time ($8228).

The start-up reads the eight sectors back, unscrambles them with the byte at $F4FF, and sums them again ($830E, $8321). A save whose sum does not match is not refused. The start-up EORs every byte with twice its offset once more and goes on ($832F), which leaves each byte as it was stored on the disk, EORed with the byte at $F4FF alone. A party whose cash was changed on the disk comes back with its names and numbers garbled. A save written unscrambled, with 0 at $F4FF, comes back exactly as written, unless its sum happens to be 0. What the second EOR was meant for is not known.

Masters and play disks

The game will not play on its master disks. Track 35, logical sector 8 of every side ends in three bytes: $D7 $CC and the side number on a master, $00 $CC and the side on a copy. Before the game uses a side, it compares them with the byte in $ED, then $CC, then the side it wants ($1897). In play $ED is 0, so only a copy passes. The start-up sets $ED to $D7 only around its check of side 1 before it loads the utilities ($81E4), which is how Utils loads from a master.

This widget needs JavaScript.

The side check. The last three bytes of track 35, logical sector 8, against the three the check wants. The game’s check ran on a 6502 simulator over 6,912 combinations of the three bytes, $ED and the side asked for, then from the start-up’s Utils and in play on all four sides of the masters and the copies. The port gave the same answer every time.

The utilities’ Copy reads the identity itself and copies only a master ($8068). It moves a side as 642 sectors, from track 35, logical sector 16 down to track 1, logical sector 0, with the loader’s own step ($833F), and writes $00 where the master has $D7 ($83E7). The step never reaches 41 sectors: all of track 18, track 17 above logical sector 0, and the top sectors of tracks 24 and 30. On sides 1 and 3 track 18 holds the directory that names the boot files, so a copy cannot be booted, and the game always starts from the master. The last choice in the picker above shows a whole copy.

14 · Disk side 3

Secret: side 3 carries an older version of the title program

The second disk's first side has a boot sector of its own, named "A JERKVISION PRODUCTION", and an earlier build of the program that shows the title. The older build shows a different picture, has none of the title's sounds, and cannot run from its own disk.

Side 1's title: the planet and the WASTELAND logo, the story text, and Start, Utils and Written by Alan Pavlish on the bar.
Side 1's start-up, as the game boots.
Side 3's start-up: a planet in blue and grey with no logo, stripes at the right of the picture, and Computer defense initiative activated.
Side 3's start-up, run over side 1's engine with side 3 in the drive.
Side 1Side 3
Wheretrack 4, logical sector 16, 16 pages loaded to $7E00
Where $7E00 jumps$7F2A$7E06
Bytes that differ2,766 of 4,096
Copyright lineCopyright 1986-88 Interplay Productions.Copyright 1986,87 Interplay Productions.
Title soundsthe siren, the whistles and the explosionsthree calls to RTS stubs ($7E03-$7E05); no SID register is written
Boot sector's nameWASTELANDA JERKVISION PRODUCTION

Both screens are from the emulator. Side 3's program was put in place of side 1's at the moment the boot hands over to it, with side 3 in the drive. It unpacks its picture from side 3's track 3 and stops at "Computer defense initiative activated." Side 3's addresses are its own, at the load address side 1's start-up shares, and have no Source page.

Booted on its own, side 3 does not get as far. Its boot file loads track 34, logical sector 10 of side 3 to $0200-$30FF, where side 1 keeps the engine and side 3 keeps other bytes. The first raster interrupt after the hand-over jumps through the engine's table at $04F9 into them, and the processor halts on a JAM instruction at $2931.