Commodore 64 · 1985 · Mastertronic (M.A.D.)

Master of Magic: the source

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

Every routine, table and variable the game uses, with the names and descriptions given to them. Click an operand to follow it; the counter beside a label lists what refers to it. The reference below the fold is rendered from the game's facts file.

Technical reference: memory map, timing, tables, cheats

Current truth for this game. The workflow lives in kit/skills/; how this understanding developed lives in agent-history.md. Every fact names the routine or table it comes from. Unless marked live, a fact comes from reading the code in the snapshot named in orientation.md; the live tests are listed at the end.

Numbers: objects and creatures share one numbering, 0 to 63, called entries here. Entry 0 is the player, $01–$20 are the 32 objects and $21–$3F the 31 creatures. Tables indexed by entry sit at the addresses given; a table used only for creatures keeps its bytes at base + $21 to base + $3F.

Build

Remember's crack of the 1985 Mastertronic tape (release 15; orientation.md has the image, the loader and the trainer). The game proper starts at $07F9; nothing of the crack is called after it. No version string or build identifier was found in the game's text.

$8000–$8008 holds 18 08 18 08 and CBM80 (C3 C2 CD 38 30): the signature the KERNAL looks for on a reset and on RESTORE, with both its cold and warm start vectors set to $0818, the game's restart.

The file was saved from a memory image of the game that had already run: the creatures' home addresses ($4CCF–$4D0C) are loaded in the state the new-game set-up leaves them in (below, under Creatures), and $A477–$A47D, $4F06–$4F45 and $41D9–$4587 hold leftovers of other programs.

Memory layout

ThingWhere
Entry: stack, $00/$01, KERNAL screen clear, BASIC RAM init$07F9
Restart (the CBM80 vectors), title, new game, main loop$0818, $0924, $0824
Engine code: menu, movement, sight, pictures, verbs, creatures, combat, interrupt$0802–$2F52
Top band character set: letters, map tiles, figures, doors$3000–$37FF
Top band sprite shapes: the player's dot, seven view figures$3800–$39FF
Sprite register images for the top and bottom bands$3A00, $3A20
Spell upkeep, spell hits, creature death, wanderers$3A40–$3BCA
Line-of-sight ray tables$3BCB–$3CBA
The view's 121-cell arrays; the 64-entry object arrays$3D45–$3EAF, $3EB0–$412F
Menu verbs$4130–$41C7
Leftover monitor code, never run$41D9–$4587
Nouns, messages$4588–$4946
Creature movement tables, object and creature property tables$4947–$5080
Distance grid, play-screen frame, EXAMINE texts, spells$5081–$57E2
Picture-strip tables, the ending$57E3–$5B26
Title, story scroller, demonstration start, controls and instructions$5B27–$5E4D
Picture sheet: 30 pictures as one multicolour bitmap$6000–$7F3F
CBM80, title scroller text, controls screen, five instruction pages$8000–$8F99
The dungeon map, four levels$9000–$9FFF
Tile tables: creature exits, glyph addresses, colours$A000–$A4FF
The DEAD sprite; the recorded demonstration and its seed$A500, $A540–$A7FF
Bottom band screen matrix, sprite pointers, colour images$A800–$AFFF
Bottom band bitmap: the pillars and the twelve picture slots$B400–$BF3F
Music driver and three tunes$C000–$CBE6
Title picture: screen matrix, bitmap under the KERNAL$CC00–$CFE7, $E000–$FF3F

Start, restart and reset

The play screen

The screen is split into three bands by the raster interrupt $2653, entered through $0314 ($2627 installs it).

BandFrom rasterModeHoldsSet up at
0$28 (40)multicolour text, VIC bank 0, screen $0400, characters $3000; sprites from $3A00view window, status line, message window$2675
1$8E (142)the same text, background colour 8the menu, rows 12–15$275E
2$B6 (182)multicolour bitmap, VIC bank 2, bitmap $A000, matrix $A800; sprites from $3A20the picture strip, rows 17–24$27AE

Main loop and the menu

$0824 redraws the bars, then, while $03C7 is zero, builds and runs the main menu ($11A2), which returns an option 1–17. The option indexes $4C3A for an even offset into $4C03, and the handler's address is written into the operand of the JSR at $0846 ($0847/$0848): the dispatch is self-modifying code. $4C27 gives $03AF the action's time in world passes, and $08CC runs them after the handler returns, whether the handler did anything or not. When $03C7 becomes non-zero the game is over; $7B is the win.

OptionVerbHandlerPassesOffered when ($11A2, counts from $128F)
0WALK$08B0until firenever
1RUN$08B9until firealways
2PICK UP$195C5an object lies on the player's cell
3PUT DOWN$1AED4a hand holds something ($4C01, $4C02 not $FF)
4INVENTORY$1A3012always
5OPEN$1B9D8the player's cell $04CD shows a shut door
6CLOSE$1C318it shows an open door
7WEAR$1D0010a wearable object ($4A81) is carried and not worn
8TAKE OFF$1D8B10something is worn ($4AC1 = 2)
9PUT IN$1E215a container is carried and a hand holds something
10TAKE OUT$1F155a container is carried
11LOOK IN$1FCB10a container is carried
12ATTACK$29FC10a creature, living or dead, is on the player's cell
13SWAP$20A53a hand holds something
14EXAMINE$2CD410always
15DRINK$2D5E10a potion ($4B41) is held
16CAST$2E2320no spell is in force ($57D2 = $FF)
17UNCAST$2F2210a spell is in force

World passes

One pass of $08CC: six times one ray of the view ($0B25) and one call of the creature clock ($2205); every 50th pass new destinations for the wanderers ($3B87); the bars; the clock; the spell's upkeep ($3A40); for WALK and RUN the player's movement tick ($0ABF) and the picture strip ($1695); the view figures ($0FEF); the staircases ($20BC).

The dungeon map

Line of sight

The player's movement

Objects and what the player carries

EntriesObjects
$01armour
$02helmet
$03–$06daggers: two plain, the dagger of wood ($05), the Dagger of Death ($06)
$07backpack, the only container
$08–$09, $0Aswords, axe
$0B–$0Fpotions
$10–$14rings: blue stone ($10), Protection from Evil ($11), Dexterity ($12), two plain
$15the Amulet of Immortality
$16–$17maces (the picture is a flail)
$18–$1Dscrolls
$1E–$20shields

The win and the end of a game

Creatures

EntriesKindBodyDefenceTo hitStrengthSpeedChases forFights with
$21–$25orcs15, 15, 13, 12, 1220, 20, 25, 25, 3015, 20, 20, 20, 2510–186–96hands, or a held weapon
$26wizard204020302560FIREBALL SPELL
$27–$2Cskeletons2015–2515–2815–195–620hands, or a held weapon
$2D–$30hellhounds10, 10, 12, 1520, 20, 15, 2022, 22, 22, 2553–41teeth
$31–$37bats1–610–1518–2125–100teeth
$38–$3Aspiders10, 12, 153530, 33, 35104–610sting
$3B–$3Csnakes205020314$3B flees; $3C 0sting
$3D, $3Evampires5060, 2536, 3840530teeth, or a held weapon
$3Fminotaur501334521360a held sword
$00the player30402518——the right hand

Body $4E85 (copied to $4EC5 at a new game), defence $4DC7, to hit $4E05, strength $4D89, speed $4B81, pursuit $4D49 AND $7F, natural weapon $4F66.

Combat

Magic

SpellCostEffectPower ($57D4)
MAGIC MISSILE615 off one creature in sight20
FIREBALL1015 off every creature in sight closer than 17 on the distance grid (its own cell, the 8 around, 4 two cells away straight)60
MAGICAL SHIELD18 off every creature's attack roll, until UNCAST; 1 mind every 100 passes—
ENERGY DRAIN57 off one creature in sight60

Potions

DRINK ($2D5E) lists the potions the player holds (not those in the backpack); the potion is gone afterwards ($3F70 = $FF).

PotionLabel (EXAMINE)Effect
$0B, $0F"POTION OF HEALING"body + 8–15, at most 30
$0C"ALUMROF ECNEGILLETNI ARTXE" (EXTRA INTELLIGENCE FORMULA backwards)mind + 8–15, at most 30
$0D"IT SMELLS PUNGENT, THERE IS NO LABEL"body halved
$0E"POTION OF ORCANIAN INTELLECT"mind halved

Nothing else raises body strength or mind: every write to $4EC5 and $4F46 was listed.

Text

Pictures

The title, the demonstration and the recorder

Random numbers

Sound

Hardware registers

From every code block ($0802–$2F52, $3A40–$3BCA, $5B27–$5E43, $C000–$C32E, $CBC3–$CBE6).

RegisterUseWhere
$D000–$D010, $D015, $D017, $D01B–$D01D, $D027–$D02Esprite registers, written by the play interrupt from $3A00 (band 0) and $3A20 (band 2)$26A5–$275B, $27ED–$287D
$D011, $D012, $D016, $D018the bands and the title's halves: mode, next raster line, bases$2632–$27E2, $0A18–$0A22, $5B3B–$5DA0
$D019, $D01Araster interrupt acknowledge and enable$2653, $5D08; $263F, $5B32, $5B9F, $5C4B
$D01Fsprite-to-background collisions, read in two bands, collected into $038D in band 2$267D, $27BB
$D020–$D023border, backgrounds, band 1's colour 8$0A27–$0A36, $269F, $2770, $27E7, $5B54–$5C65
$D400–$D406, $D418voice 1 and volume, set directly for a footstep$2782–$27A2
$D400–$D406 per voice, $D40B, $D412, $D418the music driver$C030–$C325
$D415–$D417never written: no filter
$D800–$DBFFcolour RAM: the title's fill, the scroller row$5B6A–$5B73, $5E1A
$DC00joystick port 2$0CC6
$DC0Dread to acknowledge a CIA interrupt in play$265D
$DD00, $DD02VIC bank per band and per title half$268D–$269A, $27C4–$27D3, $5C3B–$5C48, $5D35–$5D70

The keyboard comes from the KERNAL's scan ($C5, $028D); $DC01, the CIA timers and $D01E are never touched.

Leftovers

Live tests

The Play tab's port of the whole game was checked against the game itself: fed the built-in demonstration's input stream poll by poll, it reproduces a trace of the real demonstration recorded in the emulator, all 2,371 world passes, 6,462 bytes of state a pass (six bytes the interrupt owns excluded); and 14 scripted sessions covering the verbs, weapons, spells, potions, doors, the win and death give the same state, pass by pass, as the game's code run in a 6502 simulator.

Run on VICE via vice-mcp (build in game.json), from the snapshots in orientation.md.

TestResult
RESTORE with a stopping checkpoint on $0818stopped there; the title came back
Reset, then $00/$01 and the CPU's view of $A000 and $AC00$00/$17, BASIC ROM bytes where play shows the game's tables; title colours scrambled, empty view window
M in the menuthe music stopped; a second M started it
B and stick down on the controls screenthe five instruction pages
Amulet poked into the right hand, player onto $9677, PUT DOWN$03C7 = $7B, the ending
RUN with the music offten noise bursts in 2.5 s of walking, none standing still
RUN for 17.6 sthe clock advanced 21 seconds
CTRL+R on the title, a walk, then F$03D3 = 1; the buffer at $A540 took a new seed and the walk's records; F ended the game with the end mark $EF at $A561
A second recording (up, left, down from the start), then the title left to runthe next demonstration started from the saved seed and walked the same cells, $947B to $9479, ending at the same pixel
MAGIC MISSILE at a skeleton, both vampires and the minotaur, from six random statesthe same four results every time: 20 → 5, 50 → 50, 50 → 35, 50 → 50
INVENTORY with fire held during its passes, and without12 passes both times (checkpoint count on $08E1, with the interrupt as a control)
The view window against the map in play-runcell for cell, centred on $93F8
Non-stopping checkpoints on the title interrupt for exactly 100 frames$5D08 12,685 entries, $5D12 12,586, the two raster halves 99 each
The player in level 3's narrow corridor ($93C0) at in-cell x = 2 to 6, stick up for 2.5 sonly x = 4 moved (to $91C0); the others stayed where they were, bouncing
The player placed on the floor left of the pool edge $9BAA (level 2), then the stick righthe entered $52; the menu offered OPEN; OPEN printed OPEN DOOR. and changed no map byte
Body 0, the minotaur put on the player's cell, RUN"THE MINOTAUR ATTACKED YOU WITH ITS SWORD. HE HIT. YOU+RE DEAD..."; $24DE and the tune-1 call $086C each ran once
A skeleton with body 10, MAGIC MISSILE"YOU KILLED THE SKELETON.", body $FF, a band-2 sprite enabled ($3A30 = 1): DEAD over the picture
The trainer's two patches (cheats.md)body 5 kept through twelve of the minotaur's hits; MAGIC MISSILE left mind at 30 (24 without)
Wearing armour and two shields (60), PICK UP a shield, a mace, a potionthe shield refused with the weight message; the mace (68) and the potion (69) taken; PUT DOWN, SWAP and DRINK then offered

Cheats

Pokes that change the game within its own parameters. Each one names the variable it changes and whether it has been tested live. Untested pokes are labelled as candidates. Enter them from a monitor or a freezer while the game runs; addresses are given in hex and decimal.

EffectPokeStatus
Creatures' hits no longer lower body strength, and the pungent potion no longer halves it (the crack's trainer, option 1)$24D0-$24D2 = EA E9 00 (9424-9426: 234, 233, 0), turning SBC $038A in $24CC into NOP / SBC #$00; $2DF8 = AD (11768: 173), turning LSR $4EC5 into LDAlive: with body 5 and the minotaur on the player's cell, twelve hits left it at 5
Spells cost no mind, the shield's upkeep takes none and the Orcanian potion no longer halves it (the crack's trainer, option 2)$2E93-$2E95 = EA E9 00 (11923-11925: 234, 233, 0), in the spell cost; $3A66 = AD (14950: 173), the upkeep's DEC $4F46; $2E02 = AD (11778: 173), the potion's LSR $4F46live: MAGIC MISSILE left mind at 30, against 24 without the poke
Full body strength$4EC5 = 30 (20165: 30)candidate: the value the new game copies in from $4E85
Full mind$4F46 = 30 (20294: 30)candidate: the value the new game copies in from $4F05
The amulet in the right hand$4C01 = $15 (19457: 21), the right-hand slot; add $3F85 = 0 and $3FC5 = 0 (16261: 0, 16325: 0) so that the amulet's own location says the player has it, as INVENTORY and WEAR expectlive for $4C01 alone: with the player on the pedestal cell $9677, PUT DOWN won the game. The full poke is checked in the Play tab's port against the game's code
routines tables variables strings branch labels

Loading listing…