1989 · Origin Systems

Space Rogue

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/space-rogue with me to Gold.

What the code does that the game never tells the player. The answer typed to the copy protection is written into the picture behind it, and the beeps at the start of the story are a message in Morse.

01 · The manual question

Bug: the answer you type is stored inside the star field's picture

An old game starts with a question about the manual. A wrong answer brings back the star field with "ACCESS DENIED" over it, and that star field is not quite the one the question was asked over.

The question's star field
After the answer

Both pictures are unpacked here from SPACE as the title holds it ($B700), with unpack_picture's rules ($712D); the text over them is left out. test_discoveries.js runs the game's own routine on the same bytes and gets the same picture and border.

The intro loads the packed star field, SPACE, to $B700, where its stream runs on to $BF53. The buffer for a typed line is at $BF00 (line_buffer), inside that stream. The question is asked over the star field unpacked before anything is typed. A wrong answer sends the game to access_denied ($7927), which unpacks SPACE again, now with the answer and the zero that closes it written over the stream 84 bytes before its end.

Those bytes unpack into the lower half of the screen matrix, which holds two of the four colours of each character cell. The unpacker reads the typed letters as counts and colours, so cells there change colour. When it reads the answer's closing zero as a control byte, the picture ends early and the rest of the screen keeps the question's colours. The byte after the end marker is the border colour, so a wrong answer can also turn the border blue or grey.

A manual question in white over the star field, black border
The question, in VICE.
ACCESS DENIED over the star field, now with a grey border and a band of yellow and red stars
The same game after the answer "zz": the border is grey and a band of stars is yellow and red.

Seen in VICE (PAL C64) on 15 October 2026. The buffer the screen showed matched the page's prediction for "zz" byte for byte, except under the two lines of text.

02 · The interrupt

Bug: the game can freeze at "Old or New Game?"

The title, the intro and the ending each switch the screen to a bitmap with one routine, bitmap_mode. Called at the wrong moment of the frame, it moves the raster interrupt to a line the video chip never draws, and the game freezes.

The code is bitmap_mode in the intro ($6FD7); the title's ($2823) and the ending's ($6FE2) are the same. test_discoveries.js runs it with the beam on every line of a PAL frame.

The routine sets the bitmap bit with three instructions: read the control register $D011, set bit 5, write it back. Bit 7 of that register means two different things. Read, it is bit 8 of the line the beam is drawing. Written, it is bit 8 of the line the raster interrupt waits for, whose low byte stays at $50. When the routine runs while the beam is on line 256 or any later line, the interrupt moves from line 80 to line 336.

A PAL frame has 312 lines and an NTSC frame 263, so line 336 never comes. The game reads the keyboard and counts frames in that interrupt, and it waits there for a key that can no longer arrive. Lines 256 to 311 are 56 of PAL's 312, so a call landing at a random moment hangs about one time in six on a PAL machine, and on NTSC (7 lines of 263) about one time in 38.

Seen in VICE (PAL C64): two clean boots out of two hung at "Old or New Game?", the interrupt stopped. Clearing bit 7 of $D011 with the machine paused brought it back each time.

03 · HIVE!

Bug: HIVE! raises mounds at full speed from the first drop

The arcade game in the cantinas raises a new Manchi mound after a random wait, and the code means that wait to shorten on later drops. A branch that tests the wrong way round keeps the shortest wait from drop 1.

meant what the code does

Every wait the code can choose for each drop, one mark for each of random_upto's 91 results. "Meant" runs the same code with the branch the other way round. test_discoveries.js runs hive_raise_mound for every drop from 0 to 255 and every result.

When a mound rises (hive_raise_mound, $59BA), the timer for the next one is set from the drop number: 150 − 8 × drop, plus a random 0 to 90. The subtraction is done in one byte. The branch at $59E4 keeps its result only when the subtraction borrows, that is when it goes below zero, and otherwise puts 1 in its place. Up to drop 18 the result is never below zero, so every drop gets the shortest wait the code allows: on drop 1 the mounds come about four times as often as the code was written to allow, and by drop 18 the two nearly agree.

From drop 19 the subtraction does borrow, and the code keeps a number that has wrapped round to just under 256: the waits split between a very short one and one of over 150 passes. At drop 32, 8 × 32 is 256, which is 0 in one byte, and the pattern starts again. The timer counts passes of the drop's loop, twice as fast while there are no mounds standing.

04 · The distress call

Secret: the distress call's Morse says PLAY ULTIMA

The story opens with the Princess Blue picking up a distress beacon, and the first page plays it as beeps. They are Morse code: SOS SOS PLAY ULTIMA SOS SOS.

Decoded from morse_text ($7397), each character drawn to scale in frames, a fiftieth of a second on a PAL machine; the lamp sends it at that speed. test_discoveries.js runs distress_call ($7359) and compares every tone and silence with this timeline.

The message is 86 bytes, one for each dot, dash and gap: a dot is a 2-frame tone, a dash a 6-frame tone, each followed by 2 frames of silence. Between letters the gap is 6 frames, but inside SOS it is only 4, so each SOS runs together as one signal, the way radio operators send it. Words are 18 frames apart. The story text calls the beacon a distress call and says nothing about what it spells. None of the sources read for this page mentions the message (the C64-Wiki's page and the QuestBusters review of November 1989).

05 · The ninth system

Secret: there is no Malir gate that leads to Ja-Karn

Side 2 of the disks holds nine system maps, and the ninth, Ja-Karn, is the Manchi's. Its map does not list any gates, and none of the other eight systems have a gate leading to it.

A gate is a map entry of type $14 whose extra byte points into four-byte records at $5630: the system it leads to, the sector it arrives in and the rings the jump must pass (gate_jump, $7134). The gates of Karonus to Zed N27 lead only among those eight, Deneb and Bassruti with four each. The map loader takes the system's digit from $071D (load_system_map, $2E7A), so the only way to Ja-Karn, the file Q9, is for something to set it to 9: one of the talk scripts does, with a plot flag, and sends the ship to sector 0, 0 (station talk_end, $E7C7). Docking anywhere in Ja-Karn wins the game (dock, $7094).

Ja-Karn's danger is 85, the highest of the nine, where the others run from 30 to 53, and all its ship class weight is on one class, which Ships and combat shows as Wasps and Vultures. Maps draws it with the other eight.

06 · Picking locks

Bug: picking a lock with anyone in sight always gets you caught

A locked door or a safe in a base can be picked. With nobody about, the lock opens one time in three. The code meant to give the player an even chance of not being noticed when someone is in sight, and gives none.

pick_lock ($B79E) and the first instruction of the engine's random number routine (random, $A2C5), from the listings.

After find_watcher finds someone in view, the test is LDA random and a branch if the result is positive. LDA reads the byte at the routine's address instead of calling it, and that byte is $AD, the routine's own first instruction. $AD has its top bit set, so the branch is never taken and the alarm goes every time. With the intended JSR, a random byte would have let the attempt go ahead half the time.

07 · Controls

Secret: a second joystick sets the speed

Flight reads both joystick ports. The stick in port 2 steers and fires; pushing the stick in port 1 forward or back changes the set speed, as + and − do.

joystick_speed ($242C) reads port 1's up and down lines and moves the set speed a step for each push, refusing to take it past zero in one push. None of the sources read for this page mentions a second joystick.

08 · Pauses

Bug: every pause lasts 0.86 seconds, whatever length is asked for

The title, the intro and the ending wait between their pictures with the same routine, pause ($20E0), which is passed the length of the wait in A.

The routine first clears any key waiting, and the routine that does that leaves A at $FF. The length the caller passed is gone before the loop starts, so every pause runs its 26-cycle loop 32,640 times, about 0.86 seconds. The credits stay up for 24 pauses, about 21 seconds (show_credits, $2D84).

09 · The ending

Bug: the truce page starts a row lower than the others

The ending tells its story in pages of text over three pictures. The page that says "Peace is at hand." begins one row lower than the rest.

story_truce ($78F7) sets only the cursor's row. The six full lines of the page before left the column past the window's right edge, so the truce's first character starts a new line.

10 · The Manchi

Secret: the Manchi's opinion of you is the beam's ammunition count

The player has a standing with each faction, from −2 to 2, and a computer ship treats the player by its faction's standing. The Manchi's standing is the same byte as the beam weapon's ammunition count, which never goes down, and the count reads as hostile.

The standings are the bytes from $072E, one for each faction by its number, and faction_attitude ($387E) reads $072E plus the faction when a ship looks at the player. The weapons' counts start at $0731, one for each weapon in the order R selects them: the beam, then the SM-1, Nova and plasma torpedo missiles carried. The Manchi are faction 3, so their standing is $0731, where the beam's count is $FE, which as a standing is −2, hostile. A launch uses up a missile, but the beam's count is never touched, and change_standing ($3C90), which a kill calls, leaves faction 3 alone. The byte stays $FE for the whole game.

The Manchi are the game's enemy, so the effect is what the story wants: they attack the player on sight, as they do in a fight on Ships and combat with the Manchi box ticked.