1988 · Thalamus

Armalyte

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

A horizontal shoot 'em up for two players, or for one player with a drone that flies beside the ship. Every boss fights against a clock: last out its time and it blows itself up, paying the same as if it had been shot.

01 · The play screen

The play screen, rebuilt from memory

Level 1, at its first stretch of scenery, drawn by this page from one frame of the game's memory and the video chip's registers.

One frame of level 1 at tile column 36, recorded in VICE with every write to the video chip during the frame, and drawn here line by line with the state in force when the beam got there: 104,448 of 104,448 pixels match the emulator's own picture. Player 1's lives were set to five for the recording, which is why the scoreboard shows a 5.

The whole screen is playfield: 25 rows of characters, built column by column from the level's map and scrolled left one pixel a frame ($E33E). The scoreboard sits below it, in the lower border. At raster line $FA the interrupt ($A07C) switches to 24-row mode for two lines, which keeps the border open, and lays out all eight hardware sprites as the panel: score, lives, batteries, generator, and weapons, drawn into the sprites' own pixels as they change ($99FA).

Space is drawn too. Every blank cell holds a star character that is solid black, and the star itself is one pair of pixels in a shared colour: $D023 for the far stars, $D022 for the near ones. The VIC's background colour, $D021, is white here and only colours scenery. Each script event can change all three, so the stars and the scenery change colour together as a level goes on.

02 · The levels

Every level, scrolled at the game's speed

A level is a map five tiles high and 256 tiles long. The map below is drawn from the level's own bytes; Play scrolls it one pixel a frame and stops where the game stops for a boss. Each level opens 36 tile columns in, where the scenery has begun.

The whole map in miniature, each tile shaded by how much of it is scenery, with a tick for each tile column that starts an attack wave, a pod for every game, a pod for two-player games only, script events, and the boss fights. Click it to jump. Each stretch is drawn in the colours the script sets when the scroll reaches it. The time counts frames at 50.125 a second, with each boss fight taking its whole time limit, as it does when nobody shoots the boss.

A level's map is five rows of 256 tile numbers ($3934), and each tile is five by five characters (its address from $3E34 and $3EFC), so the map is 25 characters high, the height of the screen, and 1,280 wide. A tile column passes in 40 frames and the whole map in 10,240, about 204 seconds. Level 7 is the exception: its scroll starts at column 127, halfway along, so it is half as long.

Levels 4 and 7 have no scenery at all. Every entry of their maps is tile 0, and tile 0 is 25 gaps, so both are fought in open space with only the stars behind. Level 7 still carries level 1's tiles, byte for byte, which its map never uses.

The scenery's characters ($D000, copied to $7900 at the start) are solid to the ship: any character from $21 to $F5 under the ship's nose destroys it. Character $20 is a gap, and the column builder puts that row's star character in its place.

The enemies are not in the map. A separate table marks the tile columns that start an attack wave ($142C), with the wave's number and size beside it; each wave lists its objects in five bytes: x, y, type, path, and the frames before it appears. The script, eleven events at $9700, changes the colours, sets the enemies' speed and the number of shots they may have on screen, starts the bosses, and stops the scroll for them.

03 · Music

Music: Martin Walker's tunes, played by the game's own driver

Martin Walker wrote four tunes for Armalyte: the title's, the demo's, the high-score page's, and the loading screen's. The player below runs the game's music routine, ported from its code, once a frame through a model of the SID chip.

The port of music_play ($C059) reads the tunes from the engine's own bytes. Run beside the game's code in a 6502 simulator, the port made the same writes to the SID, in the same order, in every frame of each tune (ten minutes of the two that repeat) and of 200 random tunes, which with the four run every instruction of the routine. The game never uses the SID's filter. The lowest notes, down to 35 Hz in the title tune, are below what laptop and phone speakers play well.

TuneWhere it playsLength
Titlethe title pages, and the ending's last screen3:32.6, then again from the start
Demothe demo (F7 on the title), which ends when the tune does3:00.9, with a fade in its last 2 s
High scoresthe high-score page, and the ending's five pictures2:43.5, fading over its last 36 s
Loadingthe loading screen, while a level loadseach voice loops on its own

Each voice follows a track of its own: a list of pattern numbers, each with a repeat count, which can loop and can set a fade (music_track_next, $C43C). A pattern is a run of notes. Each note is a length byte and a note byte, with a third byte between them when the note changes instrument or starts a slide. Lengths count steps. A step is two frames in the demo and high-score tunes, 25 a second. In the title and loading tunes every fourth step lasts three frames, four steps in nine frames, so those two move unevenly, at about 22 steps a second.

The 21 instruments are 16 bytes each, and most of their sound comes from changing the chip's registers every frame. An arpeggio alternates the note with a second one, a frame apiece, which the ear hears as a chord. In five instruments the second note is 7 to 12 semitones above the first, and in instruments 1, 2 and 4 it is a fixed high note. Instruments 1 and 2 also flip between the triangle and noise waveforms every frame. A pulse sweep widens and narrows the pulse wave between two limits, and vibrato starts after a delay of up to 75 frames. A note that begins a new sound turns its voice's gate off and on again within the same frame, so the envelope starts its attack again.

The loading tune is three loops of different lengths. Its voices repeat every 576, 1,296 and 720 frames, about 11.5, 25.9 and 14.4 seconds, so the three line up again only after 25,920 frames, 8 minutes 37 seconds. On the first load, the one before the title, the loading screen starts a fade when its block counter reaches 18 (loading_irq, $B702).

04 · Bosses

Every boss can be outlasted

A boss fight stops the scroll for a time the level sets. When the time is up, the boss blows itself up, and the game pays out as if the player had destroyed it.

LevelMid-level boss, where and for how longHits to destroy itEnd boss, for how longHits to destroy it
1column 214, 2,880 frames (57 s)core 9012,288 frames (245 s)core 160
2column 175, 3,840 frames (77 s)core 1007,680 frames (153 s)core 120, after 2 guards of 100
3column 182, 4,320 frames (86 s)core 1207,680 frames (153 s)core 320
4column 49, 5,760 frames (115 s)core 40, after 2 guards of 12012,288 frames (245 s)core 40, after 3 guards of 100
5none12,240 frames (244 s)core 480
6column 180, 2,880 frames (57 s)core 8, after 3 guards of 60, 40 and 4012,240 frames (244 s)core 300
7none12,288 frames (245 s)core 260, after 2 guards of 120
8column 167, 1,440 frames (29 s); column 242, 4,800 frames (96 s)core 23012,240 frames (244 s)core 100, after 2 guards of 200

From each level's script (the stop times, $9730 in a level, in units of 48 frames) and its two boss tables ($9778 for the end boss, $97D8 for the mid-level boss). The core takes no hits until its guards are gone. The seconds are at the PAL frame rate, 50.125 a second.

The script runs once every eight frames, at the end of each scroll step, and it keeps running while the scroll is stopped ($A93E). A stop counts down in units of six runs, 48 frames, and 0 counts as 256. When the count runs out the script calls level_end ($AC20), the same routine that destroying the boss's core calls, and every object on the screen explodes.

What happens next depends on where the scroll is. Away from the map's end, a mid-level fight pays 50,000 points and the scroll moves on. At the end, the end-of-level sequence plays for 148 frames and level_complete ($AE25) pays one million points and an extra life to each player. A player who hides from the end boss for four minutes gets exactly what a player who shot it gets. In level 1, with no input at all, the mid-level boss went at the end of its 2,880 frames with the score going from 0 to 50,000, and the end boss went after 12,288 frames and the level was completed.

Levels 5 and 7 have no mid-level boss: no event in their scripts starts one, though level 7 carries a copy of level 4's in its table. Level 8 starts its mid-level boss twice, at columns 167 and 242.

05 · Munitions pods

Shooting a pod changes what it gives

A munitions pod changes into the next enhancement each time it has taken enough hits. Flown into, it gives the one it shows, with one exception.

Each pod drawn from level 1's sprite frames in its own colour: the unshot pod's animation starts at frame $1E; the seven enhancements use the frames at $A64C, need the hits at $A654 before the next change, and take the colours at $A65C. The unshot pod's 10 hits come from its object type, 5 (or 6, the pod only two-player games have, which is light red).

Flying into a pod ($9A94) adds its enhancement. The first four (forward fire, the tail gun, the vertical cannon, and the trident) add to the ship when there is room and only play a sound. Everything else also makes the ship invulnerable for 255 frames, about five seconds: an enhancement the ship already has in full, converge, a generator, a battery, and a pod that has not been shot at all.

The order is fixed: forward fire, the tail gun, the vertical cannon, the trident, converge, a generator, and a battery, and then round again. The wrap has a flaw ($A5FE): it sets the pod's type to 0, an unshot pod's, while giving it forward fire's frame, colour and hits. Flown into, that pod gives only an unshot pod's five seconds of invulnerability. After another 11 hits (6 with two players) it turns into the real forward fire, which looks the same. Converge needs the tail gun and forward fire at its third level; it turns the tail gun's two shots round to fire forwards beside the main gun.

Invulnerable ships fly through walls. The scenery test ($9E05) only looks at a ship in normal play, state 2, and a flashing ship is in state 3.

06 · Weapons

Every shot is a character on the screen

The players' shots are not sprites. They are characters written into the screen, so the game finds what they hit by reading the screen back.

The shots each super weapon puts on the screen around the ship's place (the outlined cell), drawn with level 1's shot characters, each with an arrow the way it moves. Read from the patterns at $F07D.

WeaponWhat it firesShot slotsCharacter
Forward firetwo shots in flight at a level's start, four with the enhancement, six with converge0-3$F6
Tail guna pair backwards, a row above and a row below4-5$F9
Tridenta pair forwards, one column behind, a row above and below6-7$FA
Vertical cannona pair, one up and one down8-9$F7, $F8
Super weapon Aten shots across the ship's row, three columns apart, starting beyond any wall; 2 charges10 free$FE
Super weapon Bfive shots: two ahead of the ship, one flying back from its nose, and two from behind it flying forward; 2 charges5 free$FC, $FD
Super weapon Ctwo shots side by side; 1 charge2 free$FF

Each player has ten shot slots ($EB78). A shot puts back the character it covered, moves one place, and stops at the screen's edge or on a solid character. Pressing fire fires the normal weapons, and so does letting go of it.

Held for 15 frames, a third of a second, fire tries the super weapon chosen with the Commodore key ($EC87). The super weapon needs enough free slots and the charges, which it takes from the generator's own charge when that is enough, or else from a battery. The manual says super weapon B costs one charge. It costs two ($F486): fired from a full generator, A and B each took 2 and C took 1.

An enemy dies when a shot character, anything from $F6 up, is in the cells it covers ($A54C). The test does not ask who fired, so the drone's shots kill as surely as the player's.

07 · Energy

How the generator fills the batteries

Super weapons run on charges. The generator rebuilds them on a clock, and more generators make the clock faster.

GeneratorsA tick everyA battery, or the generator, filled every
061 frames488 frames (9.7 s)
151 frames408 frames (8.1 s)
241 frames328 frames (6.5 s)
331 frames248 frames (4.9 s)
419 frames152 frames (3.0 s)

From the table at $EB73 (60, 50, 40, 30, 18) and generators_recharge ($EB00). Measured in the emulator from an empty generator: the rebuild count stepped every 61 frames with no generator and every 19 with four, and the charge was full after 488 and 152 frames.

Generators
Empty batteries

The game's generators_recharge ported line for line and run once a frame, for player 1, from an empty generator. Checked against the routine itself on a 6502 simulator from level 1's memory: 1,810 starting states, every counter value from 0 to 255 among them, and 291,200 calls with no difference; with no batteries it charges the generator in 488 frames with no generators and 152 with four, as measured in the emulator.

Once a frame a counter runs from 0 up to the table's value and then ticks, so each tick is one frame longer than the number in the table. Seven ticks rebuild the generator's count. The eighth fills a battery that is not full, all six charges at once, and the rebuild starts again; when no battery is left to fill, it gives the generator its own six charges.

A player keeps batteries and generators from one level to the next ($B010, $B018), because they live outside the zero page that every level start clears. Not all of them: each load takes away two batteries and two generators ($B723, run twice), and every other enhancement goes with the zero page.

08 · The drone

The drone shoots for real

In a one-player game the second ship is a drone. It cannot be destroyed, soaks up enemy shots, and fires copies of the player's weapons.

The drone keeps to the side of the ship opposite the way the stick is pushed, stepping one cell at a time towards its place ($9E78); with the stick released the drone keeps the place the last direction gave it. SPACE detaches it, and the detached drone stays still on the screen until SPACE calls it back. The manual says a detached drone "glides along its own trajectory"; in the game it does not move.

Where the drone heads for each direction of the stick, from the offsets at $9EF8 (columns) and $9F03 (rows), indexed by the stick's four direction bits. The ship is the white cell and the drone's target the blue one, on the screen's grid of characters; the target is kept between columns 2 and 37 and rows 1 and 23.

Enemies never hit the drone, and their shots stop on it ($9CD3). It collects nothing. It fires forward fire, the tail gun, and the trident from its own place, but the vertical cannon starts behind the player's ship ($F0C4). Because hits are read from the screen, its shots destroy enemies, which the C64-Wiki says they do not.

The drone's presence changes the level too. Some wave entries are pods of type 6, which get no delay before appearing when the second ship is the drone, and an object with no delay never comes to life ($E3FF). So a two-player game has 123 more pods across the eight levels than the 174 every game has: more, as the manual says, not double, as Wikipedia has it.

09 · Sprites

At most thirty sprites in a frame

The machine has eight sprites. The game shows up to thirty in one frame by using the same ones again further down the screen.

The two ships always have sprites 0 and 1 ($E4D6). Up to twenty objects (enemies, pods, bosses, and explosions) share sprites 2 to 7: the multiplexer ($A305) places them in order down the screen, each waiting until the sprite it reuses has finished drawing. Below the playfield the eight scoreboard sprites take all eight again. That makes 22 in the playfield and 8 in the border, thirty in all, short of the "more than 30" one review claims.

The objects are sorted by height every frame with the stack page as a bucket table ($F4C8). The page is filled with $FF; each object's number is pushed into the first free byte at its height, and reading them back restores the $FF. The game checks that the page still holds $FF at its top, which is how it notices a freezer cartridge has been used (section 13).

The main loop never fell behind: run through all of level 1 in the emulator it made 25,299 passes in 25,298.5 frames, one a frame.

10 · A bug

Bug: the music cuts the starting shield short

Each ship starts a level flashing and invulnerable. The code means it to last 255 frames for both ships. Player 1 gets 102 and player 2 gets 204.

At a level's start level_setup_vars clears the zero page and then decrements the two invulnerability timers, $56 and $57, at $A260, turning their 0 into 255. In between it sets up the demo tune, and music_init reads the tune's tracks through the same two bytes (music_track_next, $C43C), leaving $CD67 there. The decrement turns $67 into 102 for ship 1 and $CD into 204 for ship 2.

In the emulator the shields lasted 103 and 205 frames counted from the main loop's first pass, the extra frame being the one before the countdown's first step. Player 2, or the drone, is covered twice as long as player 1 at the start of every level.

11 · A secret

Secret: every level file ends in an insult

The last sixteen bytes of each level, which no code uses, hold a message in screen codes.

LevelMessage
1DISHONEST JOHN
2DISHONEST JOHN
3BASTARD JOHN
4FVCKHEAD JOHN
5JOHN H IS A
6HARRIES FVCKER
7HAWKEYE'S SHIT
8BASTARD J.W.H.

Each level's $98F0-$98FF, in screen codes (A = 1), padded with $FE. The mover copies them into memory with the rest of the level's last page, and nothing reads them.

Levels 5 and 6 read on as one sentence. The game's credits name its producers as Paul Cooper and John Harries (section 12). Hawkeye was another Thalamus game of 1988.

12 · Another secret

Secret: the producers' credit is never shown

The title cycles through two pages of credits. A third pair of lines, "PRODUCED BY:" and "PAUL COOPER AND JOHN HARRIES", is in the game's strings but on no page.

    Each page's ten lines, read from $B953 and decoded from the strings at $9000, beside a screenshot of that page from the emulator. A line of icons shows as ▪.

    The title's texts are 41 strings ($9000), and each of the seven title pages lists the strings for its ten lines ($B953). The credit pages show strings 9 to 16: Dan Phillips, programming and game design; John Kemp, system programming and game design; Robin Levy, graphics, level and game design; Martin Walker, music and sound effects. Strings 17 and 18, the producers, appear in no page's list.

    13 · Protection

    The infinite-lives poke freezes the game

    The poke the C64-Wiki gives for infinite lives, POKE 59891,173, stops the machine within five frames. The game checks its own code every second frame.

    A life is lost by a DEC $B005 at $E9F3 for player 1, and another at $EA57 for player 2. The poke changes the first into a load, which takes nothing away. Every second frame anti_cheat_check ($F77D) compares both instructions and the branches after them with what they should be. When they differ the check switches the KERNAL ROM in over the game's own code, so its own next instruction is fetched from the ROM, and the machine runs on into a loop with interrupts off.

    Two more checks guard the game. At each level's start the stack page is filled with $FF, and if any of $0138-$013F holds anything else, as after a freezer cartridge has used the stack, the game wipes $B000-$FFFF and resets ($AFAE). On the title, CIA 1's timer B must show that one PAL frame has passed since the last check, give or take about 60 cycles ($BDC8), so a machine that has been stopped, or an NTSC machine, clears the engine's code from $E000 up and halts ($B845).

    The wiki's other poke, POKE 53792,96 for unlimited energy, writes to $D220. With the I/O chips in that address is a copy of the border colour register, and 96 sets the border to black, which it already is; in the emulator a 5 written there turned the border green. The pokes that do work are in the Source tab's cheats.

    14 · The ending

    The ending leads into a faster second round

    Finishing level 8 loads one more file: five pictures in a frame, each with a message typed out beneath it. Then the game goes back to level 1, keeping the score and lives, with the enemies faster.

    The ending's screen rebuilt from its bytes: the frame (bitmap $5000, colours $6C00 and $4D08, all moved up by $1000 before the display starts), the picture in the window ($0F00 on, 2,280 bytes each), and the message in the ending's own font ($6800) in the picture's three text colours ($0E7B). Pictures 0 to 3 were seen this way in the emulator.

    The ending is a program of its own ($0801). It takes the machine over and shows the pictures in a window of 20 by 12 cells, splitting the screen with a raster interrupt between bitmap mode for the pictures and text mode for the six rows of text. Each picture fades the window to black, copies its bitmap in, types its message one character a frame, then puts its colours in, which makes it appear. A picture stays for 768 frames, about fifteen seconds, or until fire is pressed; RUN/STOP skips to the last.

    On the last picture, RUN/STOP continues the game. The ending sets the level back to 1 and enters the front end, which loads level 1 and goes to "PRESS FIRE TO START" with the score and lives as they were ($0923). The title is not shown, and the title is the only thing that clears the count of completed games, which finishing level 8 has raised by one. Every script event with an enemy speed below 3 then has that count added, to at most 3, so the second round's slow waves come faster.

    15 · Loading

    One file per level, loaded over the engine

    The engine stays in memory for the whole game. Each level is a file of its own on side 2, loaded when the level is reached.

    Side 1 boots to a menu of three entries, and the first loads the engine, which unpacks itself and starts at $A000. The second is the loading picture on its own, and the third is a music demo of Walker's (section 16). From then on the engine loads everything else itself, by a one-letter name ($B3E1): D, E, L, H, U, N, A, and R for the eight levels, and M for the ending. The first load looks for D on side 1, does not find it, and puts up "TURN DISK TO SIDE B PRESS FIRE" ($B5CC), which is how the game asks for the other side.

    The fast loader ($B3FC) opens the file through the KERNAL, sends 256 bytes of its own code to the disk drive's memory and starts it there, then takes the file two bits at a time over the serial bus's clock and data lines, keeping off the raster lines where the video chip would stall the processor. Each file is packed. It unpacks itself, and a short routine of the engine's ($9680) moves it into place: $0200-$6FFF for the level's object types, paths, waves, map, tiles, and sprites, $9700-$98FF for its script and boss tables, and the memory under the I/O chips at $D000-$D6FF for its characters.

    Between levels the game keeps only the engine's state block ($B000-$B02F): level number, lives, score, batteries, generators, and settings. A game over goes back to $A000, so level 1 is loaded again before the title whenever another level is in memory.

    16 · Warbles

    Music: Walker's Warbles, the third program on the disk

    Side 1's menu has three entries, and the third is not part of the game. It is Martin Walker's own music demo disk from 1988, and what loads is his music editor's memory as he left it: sixteen tune slots, 128 pattern slots, and 32 instruments, of which four keys reach four tunes.

    The Walker's Warbles screen: a title, a row of four numbered keys with the first highlighted, a bar gauge to the right of them, and bands of colour down the left and right edges

    The demo with key 1 held. The bar at the right is the gauge: the raster lines the music took in this frame, eight lines to a cell. RUN/STOP fades whatever is playing, five frames to a volume step.

    All eight tunes the disk holds, the four with a key first. The port of the demo's play routine ($104D) reads them from its own bytes. Run beside the demo's code in a 6502 simulator, the port made the same writes to the SID, in the same order, in every frame of all sixteen headers for twenty thousand frames each, and of 200 random tunes with random instruments, which together run every instruction of the routine. Nothing here uses the SID's filter, though the volume is written as $1F, which selects the low pass.

    TuneKeyPatternsComes round after
    101242:09.3
    32175:06.4; the second and third voices repeat at 2:33.2
    03162:50.5
    14181:16.6
    2none30:03.8
    4none8, and pattern 0 on the first voicehours: the other two voices repeat at 0:28.7 and 2:33.2
    6none40:12.9
    13none8, after pattern 0 four thousand timeshours

    The demo's whole main loop is the colour bars down the edges of the screen ($2000): it waits for a raster line, writes the next colour from a table to the background, and steps the table on by one a frame so the bands crawl upwards; the top band runs through 32 colours and the bottom one through eight. Everything else happens in the raster interrupt ($1831), which takes the raster line before and after the music and leaves the difference in one byte ($18AE). That byte is the gauge. While key 4's tune played it read 17 to 19 lines most of the time and reached 27, against the 26 scan lines the screen claims; each cell of the bar is eight lines, so a full bar would be 40.

    The driver is Armalyte's, two years earlier and with less in it. There is no fade: the demo's own code writes the volume register over the driver's setting, one step every five frames ($1914). A track is only repeat counts and pattern numbers, with no loops and no fade command ($13F9), where the game's version has both. A new instrument does not force a note to start again. The frequency table and the vibrato tables are the same 232 bytes in both, and no instrument of the demo's that holds data matches any of the game's 21.

    The tie bit sits on the other end of the note here. A note byte with bit 7 set means the note runs on into the next one, so the driver reads the slot before overwriting it and starts a new sound only when the note before it was not tied ($10FE); a note that is not tied itself drops its gate for its last beat, which is how the envelope gets its release. Armalyte's driver asks instead whether the new note is tied to the one before, and restarts the envelope by writing the gate off and on inside one frame. Two three-byte variables are cleared and never read ($14B1), which the game's driver does not have at all.

    Four of the eight tunes have no key. Tune 2 is three patterns and four seconds. Tune 4 is tune 3 with the lead taken out, sharing its third track, and its first voice is a slot of zeros, which the reader takes as pattern 0 played 256 times over and over. Tune 13's track starts with sixteen entries of zeros, so it plays pattern 0 four thousand times before reaching the eight patterns it shares with tune 10. Three patterns in the array, 1, 7, and 8, are music no track plays at all.

    The screen says a typical piece lasting several minutes occupies 2.5k. The longest tune here, key 1's, packs into 735 bytes of tracks and patterns and uses thirteen instruments, 208 bytes; the driver, its variables and its tables are 1,399. That is 2,342 bytes in all.