1986 · Mastertronic

Storm

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

A warrior and a wizard fight through a hundred rooms where nothing is a sprite: every figure is redrawn into the character set at each step. The magic that should clear a room counts its monsters, and after a kill the count lies.

01 · The game

A rescue through a hundred rooms

Storm's title screen: the panel, the credits scrolling and the first room behind

The title, drawn over the first room.

Agravain in the first room with monsters lined up near their generator

Agravain in the first room, with six monsters out.

The wizard Una Cum has taken Corrine, Prince Storm's wife, to his lair under a ruined abbey. Storm, a warrior, goes after her with an old friend, the wizard Agravain Undead. Each player has a joystick, and one player alone plays Agravain.

The lair is a hundred rooms, one screen each. Generators in the rooms make monsters that walk towards the players and drain their energy by touch, and the energy also runs down with hunger, so the players eat the food they find and drink bottles of restorative fluid. Keys open locked doors, and three snake brooches open the laboratory where Corrine is held. Finding her ends the game; when every player's energy has run out, the game is over.

02 · The title tune

Music: one tune, on the title only

David Whittaker's tune plays while the title waits and stops the moment play starts. A game has no music at all, only effects.

The title tune played by the game's own music driver: its bytes, $9000-$99C1, run once a frame on the kit's 6502 simulator in the page, heard through the site's model of the SID. Checked against the same bytes called on the simulator directly: every register write of 7,000 frames matches. The bass goes down to about 31 Hz, which laptop and phone speakers barely play.

The driver is the file storm-c, the one HVSC lists as Whittaker's Storm: music_init at $9000 starts it and music_play at $911B plays a frame of it. The title calls the first, and the interrupt that draws the panel calls the second once a frame while title_mode is set. When a game starts, music_stop at $9227 silences the chip.

Each voice reads an order list of patterns (tune_orders), and each pattern is a run of bytes: below $80 a note, $80-$93 a command such as a waveform, a drum, or the end of the pattern, $C0-$CF an arpeggio and $E0-$FF a note's length. A tick comes every five frames, and the whole tune is 640 ticks, so it loops every 64 seconds.

VoiceIntro, 128 ticksThen, 512 ticks
1bass_intro four timesbass and drums under A minor, F, C, and G (bass_a ... bass_g), eight times
2chime_intro: ring-modulated chimes on Ceach chord as an arpeggio (chord_a ... chord_g), eight times
3pad_intro: a held C with an octave jumpthe melody in four-bar phrases: tune_1, 1, 2, 2, 3, 3, 1, 1

03 · A room

Each room is four short lists and its doorways

A room is drawn from four lists, of floor pieces, walls, generators, and objects, and four bits that say where its outer wall has doorways. Turn each part off and on to see what it draws.

Drawn by the game's own routines, run on the kit's 6502 simulator in the page over the listing's bytes, after reset_walls and reset_objects have put every room back as a new game finds it. Checked against the same code called on the simulator directly: in all 100 rooms, drawn whole and with each list left out, every byte of memory matches. Maps shows every room.

The play area is 18 cells across and 18 down, a cell being two characters wide and one high (cell_address). Each room has a pointer into five tables, and each list ends early at a zero byte. Floor pieces (floor_ptrs) are runs of 4-by-2-character decorations; walls (wall_ptrs) are runs of single cells in one of three styles; generators (gen_ptrs) and objects (obj_ptrs) are one record each. The outer wall comes from no list: draw_border draws it with the last wall record's style and cuts a doorway where exit_bits has a bit set. In rooms 37 and 81, whose walls mix two styles, the outer wall takes the second.

The rooms sit on a map of 16 by 12 places (room_map), each holding a room number in decimal, tens and units, or $FF for none. Leaving by a side moves one place along it, and by the top or the bottom sixteen. The game starts in room 0, at row 4, column 6.

A trap or a pickup changes the lists themselves: a taken object gets bit 7 of its type set, and a wall a trap removes gets bit 7 of its third byte. A room stays as the players left it until a new game runs reset_walls and reset_objects.

04 · Monsters

One monster moves at a time, straight at Agravain

Every 50 passes of the main loop a generator puts out a monster, and on each pass one monster takes a step. Run the game's own code and watch them come.

Each pass runs move_monster ($5FB6) and then spawn ($5E11), as the main loop does, on the page's 6502, over the room enter_room drew, with Agravain where a new game puts him (start_places), standing still. Run plays 19 passes a second, the rate counted in the emulator with six monsters out. Checked against the kit's 6502 simulator over 2,000 passes in each of the 47 rooms offered here: every byte of memory matches after every pass.

spawn counts passes, and at the 50th (spawn_period) it takes the next generator in turn. If that generator still stands and fewer than ten monsters are out, it tries the space above it, then to its right, below, and to its left, and puts a monster of the generator's kind into the first slot of ten that is empty. Kinds 1 to 3 start with as many hit points as their number, kinds 4 to 6 with two fewer.

move_monster takes the slots in turn, one a pass, so with ten out each monster steps once in ten passes. An empty slot costs a busy wait (delay), which keeps a pass about as long however many monsters there are. The monster heads for Agravain, or in a two-player game for Storm when he is nearer, and step_monster tries the vertical step first, then the horizontal one, each only if nothing is in the way. There is no path finding: a wall between a monster and its target holds it there, which is why monsters gather against walls.

Nothing here is a sprite. compose builds each figure's characters as it moves: for every character it ANDs the picture with a mask made from the floor character beneath and ORs in that floor character, so floor decorations stay on top and the figure fills the gaps. Ten monsters take characters $A0-$EF, eight each; the two players, the objects, and the generators have ranges of their own. When a step finds a player in the way, touch_damage charges him four times the monster's kind byte. What that costs him is the next section's subject, and a spell that should clear the room has a bug of its own.

05 · Energy

Armour that outweighs a drain stops it completely

Hunger and monsters' touches drain each player's energy. Armour stands in front of every drain, and while it is greater than the drain, nothing at all is lost.

Runs the game's own lose_energy ($7FB6) on the page's 6502, in decimal mode as the game does, with the player's armour, energy, and drain set as the main loop sets them. Checked against the kit's 6502 simulator over 21,060 cases (every armour from 0 to 12, 18 drains, 15 energies, and the three warning states, for both players): every byte of memory matches.

Agravain starts with 2,000 energy and Storm with 2,500, kept as four decimal digits (game_init). Every 25 passes of the main loop, about 1.3 seconds, hunger takes 2 from Agravain and 1 from Storm. A monster's touch adds four times its kind byte to the drain, so 4 for kind 1 up to 24 for kind 6; the kind byte also carries some monsters' animation, which makes some touches far worse.

lose_energy looks at the armour first. If the armour is greater than the drain, nothing is lost and the armour stays. Otherwise the armour goes down by one and the player loses the drain less the armour that is left. Below 1,000 and again below 500 a warning scrolls along the message line with a sound, and below zero the player dies.

PickupEffect (obj_effect)
Food200 energy
Restorative fluid500 energy
Armour2 for Agravain, 3 for Storm, while the armour is 9 or less
Jewel-filled chest1,000 points

So one piece of armour for Storm, or two for Agravain, ends hunger for good: armour 3 is greater than Storm's 1, and 4 greater than Agravain's 2. In the emulator, Agravain with armour 4 ended 500 frames with the energy he started with.

06 · Objects

The same picture can be food or a trap

Every object in a room is one of 28 pictures. A second byte decides whether touching it takes it, stops the player, or springs a trap.

Each picture drawn by the game's draw_object ($5D67) on the page's 6502, in its colours from colour_rows. Under each, how many of the rooms' records use it, by class: taken, solid or a trap.

An object record is three bytes: its place, its type (one of the pictures in obj_shapes) and its class. Class 1 is taken or opened by walking into it, class 2 is solid, and anything else is a cabbala symbol that springs the room's trap (touch_object). So the same picture can be food in one room and a trap in another: in room 97, four of the five plates of food are traps.

Doors are objects too. A type $0D door takes a key, and there are ten of each. Types $0F and $1A need special keys 7 and 8, one door each, and the key is kept. The laboratory door, type $1B in room 3, opens for exactly three snake brooches, and not for four; three can be taken, and a fourth, in room 32, is solid. A refused door plays a sound and its message, and the player stops.

A trap (trap_fire) erases every wall of its room marked for it and every object flagged the same way, the symbol included. Most open a way through walls; some take away food; the traps in rooms 17 and 41 remove nothing but themselves.

07 · The screen

Four raster interrupts split every frame

Four raster interrupts split the screen into a panel in the C64's own font, a scrolling message line, and the play area in the game's characters.

A frame of play recorded in the emulator (every write to the video chip, with the line and cycle it came on, and memory as the frame began) and drawn by the site's model of the chip. All 104,448 pixels match the emulator's picture. The panel and the message line use the character ROM; the page carries only the 19 characters the panel shows, 152 bytes from Commodore's character ROM 901225-01 (the one VICE runs), at offsets $0008-$01CF of its upper-case set.

The interrupt routine irq runs a list (irq_list) of four entries, each naming the routine for this line and the line of the next interrupt.

LineRoutineWhat it does
$58message_banda blue background and the message line's fine scroll
$60play_bandthe game's character set at $0800, a black background, multicolour, and one step of the sound effects
$E8frame_marksets frame_flag, the main loop's clock
$FFpanel_bandthe ROM font on red for the panel, the title's music, and the message line moved on

The main loop waits for frame_flag twice a pass, so a pass takes at least two frames, and longer when its work overruns: with six monsters out, 384 passes took 1,000 frames. The game enables no sprites ($D015 is 0).

08 · Controls

Turn, then walk

Left and right turn a player a quarter turn; up walks one cell the way he faces. Down is magic.

JoystickAgravain (port 1, $DC01)Storm (port 2, $DC00)
Left, rightturn a quarter, at most once every two passes (turn_delay)
Upone cell forward (walk)
Firea shot, when his last one has gone (control)
Downa mask: every generator in the room removednothing
Down and firea scroll: the monsters clearedan amulet: the monsters cleared

The players' controls are read every other pass. On the title, fire on either joystick starts; then left asks for one player and right for two, and up shows the instructions and down skips them. Masks, scrolls, and amulets, like keys and brooches, are counters the two players share.

09 · Sound effects

Sound: fifteen effects on two voices

Every sound in play is one of fifteen records of twenty bytes, swept on voices 1 and 3 once a frame.

Each effect played by the game's own code, sfx_start and then sfx_update once a frame ($5632-$5937), on the kit's 6502 simulator in the page. Checked against the same code called on the simulator directly: every register write of all 15 effects, 805 frames, matches. Each starts from a silenced chip, as the first effect of a game does.

A record (sfx_records) holds a duration, a step length, start frequencies, envelopes, waveforms, and pulse widths for both voices, a sweep for each, a repeat count, and a flag that adds a random byte to voice 1's pitch at each repeat. sfx_update sweeps both voices every frame, restarts them at each repeat, and stops the effect when its duration runs out. Effect 5 is never asked for.