The villains hum the ROM
While a villain is on the screen it hums: VILLAIN_WANDER calls BLIP_FROM_TABLE (in BLIP_BY_TURN) every turn, which plays twelve waves at a pitch picked from sixteen by the turn counter's low four bits. Each villain was meant to have its sixteen: the four tables at VILLAIN_PITCHES, between the creature's table and the next routine (BLIP_PITCHES), are exactly four times sixteen bytes, patterns between $20 and $90 like the creature's. The code misses them twice over. VILLAIN_WANDER loads the tables' address into HL, but BLIP_FROM_TABLE builds its address from the turn bits in HL plus BC, overwriting HL first; and the offset VILLAIN_WANDER puts in BC, the graphic turned left two bits and cut with AND $F0, keeps the graphic's bit 5 along with its kind, so it is $80, $90, $A0 or $B0 rather than 0, 16, 32 or 48. Every villain's pitches come from the ROM, from $0080 to $00BF -- a few bytes of code and the start of the ROM's table of BASIC keywords, pitches anywhere from 1 to 254 -- and the four tables are never read.
Run in the simulator (checked at every build): each villain put in the knight's cell a little way off, the pitch read at $C347 came from $00B0 to $00BF for the villain of graphics 108-111, $00A0-$00AF for 104-107, $0090-$009F for 100-103 and $0080-$008F for 96-99. The only reference to the tables in the code is VILLAIN_WANDER's LD HL (searched). Either half mended alone would not do; the poke mends both, and the villains then read their own tables. What the hum sounds like is on the sounds page.
A hundred per cent prints three wrong characters
After every game GAME_OVER prints the percentage of the game done (PERCENTAGE). Under a hundred PRINT_PERCENTAGE goes through the whole of PRINT_BCD, which first points FONT_BASE at the digits (FONT). At a hundred it prints three digits instead, and to start with the hundreds' lower digit it jumps into the routine at PRINT_BCD_LOW, past that line. GAME_OVER has just printed its text, which leaves FONT_BASE 48 characters lower, where the text font's code 0 would be -- bytes of the buildings' definitions -- so the 1, 0 and 0 come out as three meaningless characters. A hundred per cent is only reached by finishing the game, and the ending follows this screen, so every player who finishes with every cell visited sees it.
The game-over screen at a hundred per cent: three wrong characters after COMPLETED
Run in the simulator (at every build): a game started from the menu, and at the start of a turn every cell a knight can stand in marked visited and the four villains' records emptied, which the percentage counts as four destroyed. The game went to its game over by its own check (CHECK_QUEST_DONE), with PERCENT holding 1 and 0 and FONT_BASE the text font's as the percentage was printed; the picture is the screen when the score has been printed too. Watched in the emulator the same way: the screen's bytes were the same, byte for byte. With the poke, the same run prints 100.
The 157th game crashes
Only START sets the stack pointer. GAME_OVER is not called but jumped to, from routines the main loop calls (NEW_LIFE when the last life is taken, CHECK_QUEST_DONE when the last villain is gone), and it leaves by jumping to NEW_GAME or, for the ending, to MAIN_LOOP; and the end of the ending (ENDING_PIT_FRONT) jumps to NEW_GAME from inside an update routine. Nothing takes the return addresses back off: every game over leaves two bytes on the stack, and an ending four. The stack starts at $5E00 and grows down through what is left of the BASIC program towards the system variables, where NMIADD holds the JP (HL) that every update goes through (DISPATCH).
Run in the simulator (at every build): games started from the menu and ended at once, one after another, with the stack pointer read at the menu after each -- down by exactly 2 bytes a game, from $5DFE to $5CC6. At the menu after game 156 NMIADD no longer held JP (HL), and game 157 never got as far as the knight: its first update jumped through the byte the stack had left there. Watched in the emulator too, with the same result to the game: NMIADD changed at the 156th menu, and the 157th game stopped at $5ED8 with the panel drawn and an empty play area. An ending took the stack pointer at the menu down by four (the simulator, a finished game staged as in the hundred per cent).
The 157th game: the panel drawn, the play area empty, and nothing moving
Play goes deeper into the stack than a game over does, so a game that ends at a deeper point could fail a game or two sooner. Each game takes minutes, so no one is likely to meet this without a script; a reload starts the count again.
Only the first antibody can strike
Two antibodies can be in flight at once: the knight's throw takes the first free of the two antibody records (KNIGHT_THROWS). ANTIBODY_STRIKE, which the monsters and the creature ask whether an antibody has hit them, means to try both records, and loads the size of a record into DE for the step from one to the next -- but never adds it to IY. It tries the first record twice. An antibody thrown while another is still flying goes in the second record and passes through everything.
Run in the simulator: two antibodies thrown in quick succession with the game's own throw, and a monster of graphic 112 put four of the second's steps ahead of it. The second antibody passed through the place the monster stood, flew on until it met a wall and burst; the monster walked on and the score did not change. The same monster put ahead of a single antibody, in the first record, was struck the turn they met, and both burst for 2500 points.
A split overwrites the first monster
When the kinds of a monster of graphics 112-127 and of the antibody that strikes it add up to 2, four apart, MONSTER_HIT_TABLE sends the strike to HIT_SPLITS, for 1500 points. By its shape it means to copy the monster into an empty monster record and send the two off in opposite directions, each a quarter turn from the way it was going. But its search tests IY for an empty record, and IY is the antibody's record and the ones after it, while IX, which the nearness test reads, stays on the first monster record. So the struck monster is copied over the first monster record -- whatever is there -- unless that record is within a cell of the knight and none of the five records after the antibody's is empty, when nothing is copied. A monster in the first record copies itself onto itself, and does not split at all.
Run in the simulator: a monster of graphic 120 in the fourth monster record struck by an antibody of graphic 80, with a monster of graphic 64 two cells from the knight in the first record. After the strike the first record held a copy of the struck monster, on top of it and walking the other way, and the monster of 64 was simply gone -- no burst, no points of its own; the score went up by 1500. The same with the creature (graphic 136) in the first record.
The bonus is looked for outside the town
When there is no bonus PLACE_BONUS picks a cell for one: the knight's column or four to its right, and a row from four above his to three below. The column is wrapped round the town's 32 before the cell is looked up; the row is wrapped only as it is stored. Near the top or the bottom of the town the look-up (TOWN_CELL, in PLACE_IN_CELL, which checks nothing) reads a byte from outside the map -- a row of -4 to -1 is taken as 252 to 255 rows of 32 bytes, which lands in the sprites, and rows 32 to 34 in the table after the map -- and uses it as the cell's type, while the bonus goes into the wrapped row at the other edge of the town.
Run in the simulator, with the bonus record emptied at the start of each of 300 turns: with the knight in cell (1,1), 74 of the look-ups were outside the map; every one of those bytes let the bonus be placed, in rows 29 to 31, most of them solid, and every one was gone at its first update (SPEED_BONUS keeps a bonus only within three cells of him). With him in cell (26,30), 105 look-ups fell in the table after the map, and the bonus went to rows 0 and 1, solid cells, and vanished the same way. Harmless: at worst there is no bonus near him for a turn.
A monster that may not appear appears in the corner
Every fourth turn SPAWN_MONSTER brings a monster into the knight's cell or one of the eight round it. It copies the monster's first record (MONSTER_RECORD, graphic 128 at U 0, V 0) into a free monster record before it looks at the cell it chose, and if that cell is solid it returns -- leaving the monster at the very corner of the town, cell (0,0), which is solid. After its four turns of appearing (APPEARING_UPDATE) it becomes a monster like any other. Anywhere in the middle of the town its own update then finds it too far from the knight and empties its record: it has only kept a record busy for five turns.
In the town's top left-hand corner it is not so tidy. With the knight in cell (1,1), next to the corner, the stray is near enough to live; it walks off the map, to column or row 255, and NEAR_KNIGHT, which compares cells a byte at a time, finds 255 two cells from 1 and keeps it. Run in the simulator for 1500 turns in cell (1,1): three or four of the six monster records held a monster outside the map in all but 37 turns, and every one of them had started as a stray at (0,0). They are never drawn and never reach him, but they leave only two or three records for the monsters he can see. In cell (2,2), whose neighbours are all open, and in (16,16), none left the map. Read and run; nothing in the game says this is meant.
The knight never reaches his top speed
TOP_SPEED is 10, and 18 while the faster walk of the bonus lasts (SPEED_BONUS). With walk held, WALK_ON takes his speed halfway towards it each turn and then clears bit 0 to keep it even. The last step is always too small to survive that: from a standstill he goes 4, 6, 8 and stays at 8, and on the faster walk 8, 12, 14, 16 and stays at 16. When the faster walk runs out (UPDATE_KNIGHT) his speed is set straight to 8, where he would have been anyway. Perhaps the rounding was meant and the numbers were not; either way he walks at four-fifths and eight-ninths of the speeds written in the code.
Run in the simulator, walk held along an open street: 4, 6, 8, 8, 8 ... and, with the bonus just taken, 8, 12, 14, 16, 16 ... (and he still came to rest on the eight-unit grid when the key was let go). With TOP_SPEED two higher (the poke) he reaches 10 and 18.
A sixteenth thing in one cell would overwrite TURN_CELL
To draw the things in a cell, LIST_THINGS_IN_CELL puts the address of every record in it on DRAW_LIST and ends the list with a zero word. The list has room for fifteen addresses and the zero; nothing counts them. A sixteenth would put the zero over the first two bytes of the next routine, TURN_CELL, turning its LD A,(VIEW) into two NOPs and a stray third byte, and from then on it would turn a cell round when A happened to be odd rather than when the town is turned round.
Staged in the simulator: fourteen records put in the knight's cell beside his two. After one turn TURN_CELL's first two bytes were 0, and they stayed so after the extra records were taken away; with thirteen, fifteen in all, they were untouched. In play the most found in one cell was four (stage 2's measure over thirty cells). Sixteen is possible in principle -- his own two records, two antibodies just thrown, four finds and six monsters make fourteen, and the bonus he has walked up to and an object or two he has thrown down there would make sixteen -- but it would take a remarkable crowd. Latent.
A wall's colour runs one byte past the attribute buffer
The walls are coloured in the attribute buffer (ATTR_BUFFER) a strip two cells wide at a time, from the row of a wall's foot up to the top of the play area (COLOUR_WALL, COLOUR_STRIP). A strip whose left cell is the last of its row puts its second byte in the next row's first -- the row's hidden margin, which is never copied to the screen -- and on the top row, which is the buffer's last, one byte past the buffer: ATTR_SPILL. Nothing reads it; the buffer's clearing (SHOW_PLAY_AREA) starts just below it.
Run in the simulator: 1000 turns of walking and turning in 25 cells chosen at random, with a marker put in ATTR_SPILL at the start of each: 269 of the turns overwrote it, always with one of the walls' four colours, and the bytes after it (UNUSED_F195) never changed. Harmless.
A stray OUT pages a 128K's memory
READ_KEYS, byte for byte Knight Lore's, Alien 8's and Pentagram's, does OUT ($FD),A before IN A,($FE), with the half-rows to read in A. OUT (n),A puts A on the top half of the address bus, so the port is A * 256 + $FD. A 48K ignores it; a 128K takes any port with bits 15 and 1 clear as its paging port. Every half-row value with bit 7 clear is a paging write: the menu tune's any-key test (PLAY_TUNE_ONCE) reads with A = 0, which pages in the 128's editor ROM -- harmless, but the ROM bytes the game reads for chance and for the villains' hum are then that ROM's -- and the pause test (PAUSE) reads $7E at the end of every turn, which puts RAM bank 6 at $C000, where the code from $C000 up is, shows the other screen, and locks the paging.
Watched in the emulator's 128K, from the loaded game put into a 128K snapshot with the 48 BASIC ROM and bank 0 paged in and the paging unlocked, as the 128's Tape Loader leaves them: the menu worked, and by the first turn the paging port held 0; at the end of that turn it held $7E -- bank 6 at the top, the second screen, locked -- and the machine ran off into the BASIC ROM and stayed there over a blank screen. So on a 128K the game must be run in 48 mode. Alien 8 and Pentagram go the same way (Alien 8, Pentagram). The poke that removes the OUT is with the pokes.
The pick-up sound starts with a byte of code
EFFECT_NOTE plays a sound effect a note a turn, the last note first: an effect of n notes plays the n bytes after its address in EFFECT_TABLE, counting down, and never the byte at the address itself. So each effect starts with the next effect's first note. Effect 3, four notes when an object is taken up (OBJECT_LYING), is the last, and its first note is the byte after the table: the first byte of FIRE_SOUND's code, a half-wave count of 14 where the table's run from 48 to 128 -- a click well above the three low notes that follow. Pentagram has the same routine, but nothing there starts its effect 3.
Run in the simulator: an object put beside the knight and taken up; the four notes were read from $C3F4, $C3F3, $C3F2 and $C3F1.