Sunday, September 15, 2019

The Basic mechanics of Ultima

Ultima, like Akalabeth, was coded mostly in BASIC, and the code is right on the disk, in the same format as Richard Garriott had typed it so long ago.

Compared to Wizardry, whose Pascal source code I spent most of last August analyzing, Ultima has generally simpler gameplay mechanics to decipher, and BASIC is more of a beginner’s language. So this ought to be a cake walk in comparison, right?

Well, no. First of all, most of the work in understanding Wizardry had already been done for me, as it had been originally distributed in compiled P-code format. Thomas William Ewers had reverse-engineered it into human-readable source, and picked his own names for the variables and functions. They weren’t always the most sensible names, such as LUCKSKIL[0] for “Save Vs. Death,” but were much preferable to Garriott’s arbitrary two-letter names, such as AM for “Index of the monster currently being fought in the overworld” (or “alertness status of the guards/Mondain”).

Secondly, BASIC, in my opinion, was never intended to be used for a game as big in scope as Ultima, but it was the best tool available to most developers of the time, and like so many other developers, Garriott had to horrifically abuse the BASIC syntax to get things done with it, not least because having comments and meaningful variable names eats up precious memory. Observe this line from the Dungeon routine:
2075  PRINT "HIT! DAMAGE=   ";D:MN(MB + ZX,2) = MN(MB + ZX,2) - D: IF MN(MB + ZX,2) < 0 THEN MN(MB + ZX,0) = 1: PRINT MN$(MB + ZX);" KILLED!   ":X = ZX:DN%(PX + DX * ZZ,PY + DY * ZZ) = DN%(PX + DX * ZZ,PY + DY * ZZ) - 100 * ZX: GOTO 2293

This example isn’t especially an especially bad one – I could have picked plenty that are far less comprehensible. Here one can probably figure out the gist of what the line is trying to do, but I think in its partial comprehensibility, it illustrates the difficulty of my task better than gibberish like this does:
95 TN = T1:DR = 0: POKE  - 16301,0: POKE  - 16297,0: POKE  - 16300,0: POKE  - 16304,0: SCALE= 1: ROT= 0: HCOLOR= 3: POKE 232,116: POKE 233,14:AM = 0: DEF  FN PN(T2) =  PEEK (2816 + (PX + DX) * 22 + PY + DY): DEF  FN MX(T4) =  PEEK (2816 + (MX(X) + DX) * 22 + MY(X)): DEF  FN MY(T4) =  PEEK (2816 + MX(X) * 22 + MY(X) + DY)

Pascal was designed to encourage structured coding, largely as a reaction to BASIC’s shortcomings, but Apple Pascal was bleeding edge technology when Ultima was under development. Take this sample routine from Wizardry, which is fairly understandable thanks to the structure and meaningful variable names.

            TOTAL := MINADD;
            WHILE TRIES > 0 DO
                TOTAL := TOTAL + (RANDOM MOD AVEAMT) + 1;
                TRIES := TRIES - 1
            CALCULAT := TOTAL

So, comprehending Ultima’s BASIC code is in some ways more difficult than Wizardry’s Pascal, but I’ve tried my best. It's a smaller task, since there's a lot less of it. There's also not all that much data - most of the tables I've made are based on formulas and hard-coded BASIC, rather than actual on-disk tables as seen so extensively in Wizardry.

Ultima is neatly divided into a number of separate BASIC programs, each one small enough to fit into the computer’s memory one at a time, leaving room for a few variables that get shared between programs. The programs are:
  • Castle
  • Dungeon
  • Initialize Display
  • Outside
  • Player Disk
  • Space
  • Time Machine
  • Town
  • Ultima

Not all of these are interesting to me. Ultima is the title screen. Initialize Display is the self-explanatory character creator, some variable initializations, and various low-level routines that I don’t care to decipher. Space is a pretty basic action game that doesn’t lend itself well to data obsession. Player Disk is the looping demo and disk copying utility. In addition, not all of the game code appears to be present in the readable files. For instance, I couldn’t find any data representing monster’s internal stats, including their names (but I’ve assembled most of it through observation), but it has to be there somewhere.

Throughout this post, I will refer to some BASIC functions. One of the most important is:

This returns a random floating point number smaller than X, with an even distribution. For instance, RND(20) may return 19.99999, and may return 0, but it will not return 20 or any negative. Values that need to be used as integers will always be rounded down, so if I say, for instance, that weapon damage is RND(20) + 1, this means it can be anywhere from 1 to 20, since weapon damage is always an integer.

Occasionally, the RND function is used in this manner:

This will return a random number smaller than 1, but distribution will be more frequent on low values. So for example, this formula:
RND(1)^2 * 5 + 1
will return anything from 1 to 5, but 1’s will be returned almost half of the time.

Common tables



Index Weapon Town/Castle Dungeon/Mondain Outdoors
0 Hands
1 Dagger
2 Mace
3 Axe
4 Rope and spikes No attack, saves vs. traps
5 Sword
6 Great sword
7 Bow and arrows Ranged Ranged Ranged
8 Amulet Ranged No attack No attack, +16 magic missile damage
9 Wand Ranged No attack No attack, +18 magic missile damage
10 Staff Ranged No attack No attack, +20 magic missile damage
11 Triangle Ranged +22 magic missile damage
12 Pistol Ranged Ranged Ranged
13 Light sword
14 Phazor Ranged Ranged Ranged
15 Blaster Ranged Ranged Ranged

The wild differences in ranged capability for some weapons are likely due to Garriott forgetting to code the rules consistently across the various BASIC modules. The amulet, wand, and staff are all over the place, being non-weapon magic enhancers outdoors, completely useless in dungeons, and ranged weapons in towns and castles.

The index serves as the base stat from which all other weapon stats – price, accuracy, and damage – are calculated.

In the towns and castles, weapon damage is:
RND(Strength + Index/2) + 1

In the dungeons and against Mondain, weapon damage is:
RND(Strength/5 + Index*3) + Strength/5

In the overworld, weapon damage is:
RND(Strength + Index) + 1


Index Armor
0 Nothing
1 Leather
2 Chain
3 Plate
4 Vacuum
5 Reflect

Armor reduces likelihood of getting hit, but does not reduce damage when you do.

In the towns, your odds of getting hit are:
22.5/(Agility + Index*4)

In the castles, your odds of getting hit are:
40/(Agility + Index*4)

Outdoors and dungeons use their own convoluted rules. I’ll get to them later.


Index Spell Use
0 Prayer Outdoors
1 Open Dungeon
2 Unlock Dungeon
3 Magic Missile Outdoors + dungeon + Mondain
4 Steal Nowhere
5 Ladder Down Dungeon
6 Ladder Up Dungeon
7 Blink Dungeon + Mondain
8 Create Dungeon + Mondain
9 Destroy Dungeon + Mondain
10 Kill Outdoors + dungeon + Mondain

The Steal spell seems to be completely useless. You can’t cast spells in town or the castle, and there’s nothing to steal anywhere else.

Blink, create, destroy, and kill are for wizards only.


Index Vehicle Food rate Time rate Notes
0 Nothing 0.5 1.6
1 Horse 0.43 1.46
2 Cart 0.36 1.31 Improves carrying capacity
3 Raft 0.29 1.17 Water only
4 Frigate 0.21 1.03 Water only, has guns
5 Air car 0.14 0.89 Can't enter woods, has guns
6 Shuttle Goes to space

On experience and leveling

Leveling in Ultima does not work as you might expect. Experience points have nothing to do with it; they are just a currency used to purchase spells. Nor does your level have anything to do with your strength in combat.

Levels are determined entirely by how much “time” has passed in-game. And there are only three things that levels do. First, the shops in town will stock better items as you gain levels, with the best items only available at level 7. Second, monsters in the overworld gain more HP as you gain levels. Third, you must be level 8 or higher to use the time machine and win the game.

Each level represents 1000 passed time units. Time is advanced in the following ways:
  • Passing anywhere by idling consumes 0.05 food and advances time 0.5 units.
  • Passing in the town, castle, or dungeon by pressing space consumes 0.01 food and advances time 0.1 units.
  • Moving in the town or castle consumes 0.01 food and advances time 0.1 units.
  • Moving in the dungeon consumes 0.1 food and advances time 1 unit.
  • Moving outdoors consumes up to 0.5 food and advances time up to 1.6 units.

But most efficiently of all is:
  • Each parsed command while outdoors advances time 0.6 units.

So if you want to level quickly, just go outdoors and rapidly press a key that doesn’t do anything, like ‘W’. You won’t even consume food! Though you will have to contend with monsters. Or you can do what I did and put the emulator into warp speed and keep your finger on the ‘A’ key for a few minutes.

Town + Castle common rules

In both the towns and castles, it’s possible to steal armor, weapons, or food. Thieves have an easier time overall, but castles are more difficult than towns for thieves, and towns are more difficult than castles for non-thieves.

Successfully stealing armor performs this calculation:
RND(1)^2 * 5 + 1
And then you receive the armor with the corresponding index value.

Successfully stealing a weapon performs this calculation:
RND(1)^3 * 15 + 1
And then you receive the weapon with the corresponding index value.

Successfully stealing food gets you RND(30) + 1 units.

Ranged weapons can hit at up to 5 tiles away.

Alerted guards will approach if their range is within 9 tiles. Distance is calculated by the Pythagorean Theorem.

I’ve had trouble figuring out the odds that Iolo/Gwino will steal something, but when they do, it will be your worst unequipped weapon, and the odds that you notice are Wisdom/50.


Towns with odd indices stock these weapons, based on your level.

Weapon Power Min level Base price
Dagger 1 1 6
Axe 3 1 14
Sword 5 1 30
Bow and Arrows 7 4 54
Wand 9 4 86
Triangle 11 7 126
Light sword 13 7 174
Blaster 15 7 230

Towns with even indices stock these weapons, based on your level.

Weapon Power Min level Base price
Mace 2 1 9
Rope and spikes 4 1 21
Great sword 6 4 41
Amulet 8 4 69
Staff 10 4 105
Pistol 12 7 149
Phazor 14 7 201

The base price is subject to a percentage discount of Intelligence/200. The final purchase price is determined by this formula:
(1 – Intelligence/200) * (WeaponPower ^ 2) + 5

Selling weapons gets you a percentage rate on the buying price of Charisma/50.

Armor is also stocked according to your level, and doesn’t vary between towns.

Armor Power Min level Base price
Leather 1 1 50
Chain 2 1 100
Plate 3 1 150
Vacuum 4 3 200
Reflect 5 3 250

The base price is subject to a percentage discount of Intelligence/200. The final purchase price is determined by this formula:
(1 – Intelligence/200) * (ArmorPower * 50)

Selling armor gets you a percentage rate on the buying price of Charisma/50.

Your carrying capacity is equal to (Strength * 4) + (Carts * 20). Your carrying load is (Gold/100) + Armor + Weapons. Each armor or weapon counts for 1 unit, regardless of type. Capacity vs. load is checked only when buying weapons or armor.

Vehicles are stocked according to your level.

Vehicle Power Min level Base price
Horse 1 1 40
Cart 2 1 160
Raft 3 1 360
Frigate 4 1 640
Air car 5 4 1000
Shuttle 6 4 1440

The base price is subject to a percentage discount of Intelligence/200. The final purchase price is equivalent to this formula:
(40 - Intelligence/5)* (VehicleIndex^2)

Spells are always fully stocked.

Base price Index Base price
Open 1 5
Unlock 2 10
Magic Missile 3 15
Steal 4 20
Ladder Down 5 25
Ladder Up 6 30
Blink 7 35
Create 8 40
Destroy 9 45
Kill 10 50

Spells cost both gold and experience, in equal amounts. The base price of each spell is [Spell index] * 5. It is then subject to a percentage discount of Wisdom/200. Algebraically, the price can be expressed as:
[Spell index] * (5 – Wisdom/40)

Food packs cost 5 – Intelligence/20.

If you consume a total number of drinks greater than Stamina/5 or Wisdom/5, and are standing near the wench, you will be seduced. You’ll lose all of your gold and one point of Wisdom if you have 6 or more.

When you buy a drink and are not seduced, there is a 70% chance of hearing a randomly chosen tip. All tips have a 10% chance unless otherwise noted.

  • YOU SHOULD GO BACK IN TIME! (20% odds)
  • THIS IS A GREAT GAME! (20% odds)

Targets have the following values:

Target AC HP EXP
Guard 1 (left of the entrance) -8 20 50
Guard 2 (right of the entrance) -7 20 50
Guard 3 (near the food shop) -6 20 50
Guard 4 (near the armour shop) -5 20 50
Guard 5 (near the pub) -4 20 50
Guard 6 (near the magic shop) -3 20 50
Bard -2 0 20
Thief -1 0 40
Merchant 0 0 25
Wench 1 0 10

Your odds of missing an attack are:
50/(75 + Agility + WeaponPower + [TargetAC *3])

A guard’s attack has these odds of hitting you:
22.5/(Agility + ArmorPower*4)

Guard attacks inflict damage of 1% of your HP, plus RND(10).

Dropping gold into the lake grants you (1.5 * gold) HP. You will also receive 4 daggers the first time per visit.

Stealing has a 15% chance of failure if you are a thief, and a 40.5% chance of failure otherwise. It always fails if the guards are alerted.


Dropping gold into the west fountain grants (Gold/10) points to a randomly chosen stat, up to a maximum of 99 points. Possible stats are:
  • HP
  • Strength
  • Agility
  • Stamina
  • Charisma
  • Wisdom
  • Intelligence

If “HP” is selected, odds are you’ll wind up reducing it to 99 points.

Dropping 10 or more gold into the center-north fountain grants a random weapon.

Dropping gold into the center-south fountain grants (Gold*5) food.

When you receive the white gem from Shamino for killing a Balron, you are also permitted nine rewards from the armour store, weapon store, or food store.

Armour store rewards grant you a randomly chosen armor, with even distribution.

Weapon store rewards grant you a randomly chosen weapon, by this formula:
RND(1)^2 * 15 + 1

Food rewards gives you RND(30) + 1 food units.

You may also steal from the stores, which has a 20% chance of failure if you are a thief, and a 36% chance of failure otherwise. It always fails if the guards are alerted. Looting odds are not as good as the reward odds, and match the odds of stealing from merchants.

Your reward for completing landmark quests is more strength, in the amount of:
9.9 – OldStrength/10

Gold may be donated in increments of 10, to a maximum of 90. The reward is higher if you donate a larger portion of your gold.

The reward is HP by this formula:
3 * Donation^2 / Gold

So, if you have 10 gold and donate all of it, you receive
3 * 100/10 = 30 HP

A special case is made for donating 90 gold. It guarantees at least 135 HP, even if you are so rich that the formula says it should buy you less than that.

Targets have the following values:

Target AC HP EXP
Guard 1 (by the entrance) -8 500 50
Guard 2 (by the gate, north) -7 500 50
Guard 3 (by the gate, south) -6 500 50
Guard 4 (center-north fountain) -5 500 50
Guard 5 (center-south fountain) -4 500 50
Guard 6 (throne room) -3 500 50
Jester -2 0 30
King -1 Infinite 1000
Merchant 0 0 25
Princess 1 0 10

Your approximate odds of missing an attack are:
50/(50 + Agility + WeaponPower + [TargetAC *3])

Killing the jester gives you a key to a random cell. Your inventory will show you whether it is cell 1 or cell 2.

Attacking the king performs this calculation:
Agility * RND(1)^3

If the result is greater than 50, then the king dies.

A guard’s attack has these odds of hitting you:
40/(Agility + ArmorPower*4)

Guard attacks inflict damage of 1/75th of your HP, plus RND(20).


Every mention of “level” here refers to the dungeon level, unless otherwise noted.

Ultima’s dungeons, unsurprisingly, have quite a bit of code from Akalabeth. The dungeon routine does involve a bit more data than Akalabeth, but not much. Monster stats are all derived from two hard-coded stats.

One big difference from Akalabeth is that dungeons always have ten levels, and the monsters are divided into five groups of ascending difficulty. Every two dungeon levels corresponds to one monster group, and may spawn any monster in that group.

First, here is a table of all monsters and their root stats. Four of the monsters, thieves, gelatinous cubes, gremlins, and mind whippers, will do special things when they hit you in lieu of doing damage if they can, and will damage you normally if they cannot.

Name Set MB MN Notes
Ranger 1 20 1
Skeleton 1 20 2
Thief 1 20 3 Steals your worst unequipped weapon
Giant Rat 1 20 4
Bat 1 20 5
Spider 2 25 1
Viper 2 25 2
Orc 2 25 3
Cyclops 2 25 4
Gelatinous Cube 2 25 5 Destroys your equipped armor
Ettin 3 30 1
Mimic 3 30 2 Looks like a chest
Lizard Man 3 30 3
Minotaur 3 30 4
Carrion Creeper 3 30 5
Tangler 4 35 1
Gremlin 4 35 2 Steals half of your food
Wandering Eyes 4 35 3
Wraith 4 35 4
Liche 4 35 5
Invisible Seeker 5 40 1 Invisible, but battle console works normally
Mind Whipper 5 40 2 50% chance of setting your intelligence to (Intelligence * 0.6 + 5)
Zorn 5 40 3
Daemon 5 40 4
Balron 5 40 5

Monsters of set X may spawn on level (X*2 – 1) or (X*2).

MB is a calculated value representing the difficulty of the monster group. It is equal to:
Set * 5 + 15

MN represents the monster’s toughness relative to other monsters in its group. Quest monsters are always MN=5.

MB and MN are used for a variety of calculations.

For the rest of the stats, I’ve split the tables up by level, since monsters have different stats on different levels. All stats here are presented as ranges.

Level 1

Name HP1 HP2 Att Dam Def EXP G
Ranger 10-10 15-16 3-12 2-2 1-2 1-5 9-17
Skeleton 10-11 15-18 3-12 2-3 1-3 1-10 9-17
Thief 10-12 15-20 3-12 2-4 1-4 1-15 9-17
Giant Rat 10-13 15-22 3-12 2-5 1-5 1-20 9-17
Bat 10-14 15-24 3-12 2-6 1-6 1-25 9-17

Level 2

Name HP1 HP2 Att Dam Def EXP G
Ranger 10-13 15-22 6-15 4-7 2-3 2-11 9-44
Skeleton 10-17 15-30 6-15 4-11 2-4 2-21 9-44
Thief 10-21 15-38 6-15 4-15 2-5 2-31 9-44
Giant Rat 10-25 15-46 6-15 4-19 2-6 2-41 9-44
Bat 10-29 15-54 6-15 4-23 2-7 2-51 9-44

Level 3

Name HP1 HP2 Att Dam Def EXP G
Spider 10-18 15-32 9-18 6-14 3-5.67 3-17 9-89
Viper 10-27 15-50 9-18 6-23 3-6.67 3-32 9-89
Orc 10-36 15-68 9-18 6-32 3-7.67 3-47 9-89
Cyclops 10-45 15-86 9-18 6-41 3-8.67 3-62 9-89
Gelatinous Cube 10-54 15-104 9-18 6-50 3-9.67 3-77 9-89

Level 4

Name HP1 HP2 Att Dam Def EXP G
Spider 10-25 15-46 12-20 8-23 4-6.67 4-23 9-152
Viper 10-41 15-78 12-20 8-39 4-7.67 4-43 9-152
Orc 10-57 15-110 12-20 8-55 4-8.67 4-63 9-152
Cyclops 10-73 15-142 12-20 8-71 4-9.67 4-83 9-152
Gelatinous Cube 10-89 15-174 12-20 8-87 4-10.67 4-103 9-152

Level 5

Name HP1 HP2 Att Dam Def EXP G
Ettin 10-34 15-64 15-20 10-34 5-9.33 5-29 9-233
Mimic 10-59 15-114 15-20 10-59 5-10.33 5-54 9-233
Lizard Man 10-84 15-164 15-20 10-84 5-11.33 5-79 9-233
Minotaur 10-109 15-214 15-20 10-109 5-12.33 5-104 9-233
Carrion Creeper 10-134 15-264 15-20 10-134 5-13.33 5-129 9-233

Level 6

Name HP1 HP2 Att Dam Def EXP G
Ettin 10-45 15-86 18-20 12-47 6-10.33 6-35 9-332
Mimic 10-81 15-158 18-20 12-83 6-11.33 6-65 9-332
Lizard Man 10-117 15-230 18-20 12-119 6-12.33 6-95 9-332
Minotaur 10-153 15-302 18-20 12-155 6-13.33 6-125 9-332
Carrion Creeper 10-189 15-374 18-20 12-191 6-14.33 6-155 9-332

Level 7

Name HP1 HP2 Att Dam Def EXP G
Tangler 10-58 15-112 20-20 14-62 7-13 7-41 9-449
Gremlin 10-107 15-210 20-20 14-111 7-14 7-76 9-449
Wandering Eyes 10-156 15-308 20-20 14-160 7-15 7-111 9-449
Wraith 10-205 15-406 20-20 14-209 7-16 7-146 9-449
Liche 10-254 15-504 20-20 14-258 7-17 7-181 9-449

Level 8

Name HP1 HP2 Att Dam Def EXP G
Tangler 10-73 15-142 20-20 16-79 8-14 8-47 9-584
Gremlin 10-137 15-270 20-20 16-143 8-15 8-87 9-584
Wandering Eyes 10-201 15-398 20-20 16-207 8-16 8-127 9-584
Wraith 10-265 15-526 20-20 16-271 8-17 8-167 9-584
Liche 10-329 15-654 20-20 16-335 8-18 8-207 9-584

Level 9

Name HP1 HP2 Att Dam Def EXP G
Invisible Seeker 10-90 15-176 20-20 18-98 9-16.67 9-53 9-737
Mind Whipper 10-171 15-338 20-20 18-179 9-17.67 9-98 9-737
Zorn 10-252 15-500 20-20 18-260 9-18.67 9-143 9-737
Daemon 10-333 15-662 20-20 18-341 9-19.67 9-188 9-737
Balron 10-414 15-824 20-20 18-422 9-20 9-233 9-737

Level 10

Name HP1 HP2 Att Dam Def EXP G
Invisible Seeker 10-109 15-214 20-20 20-119 10-17.67 10-59 9-908
Mind Whipper 10-209 15-414 20-20 20-219 10-18.67 10-109 9-908
Zorn 10-309 15-614 20-20 20-319 10-19.67 10-159 9-908
Daemon 10-409 15-814 20-20 20-419 10-20 10-209 9-908
Balron 10-509 15-1014 20-20 20-519 10-20 10-259 9-908

HP1 is the monster’s maximum HP if it is one of the initially spawning monsters when you enter. Minimum HP1 is always 10. The formula is:
RND(MN*Level^2) + 10

HP2 is the monster’s maximum HP if it spawned after you killed another monster. Minimum HP2 is always 15. The formula is:
RND(2*MN*Level^2) + 15

Att is the range for attack rolls. Minimum is always 3 * Level. The formula for attack rolls is:
RND(10) + 3*Level
But it will never exceed 20.

Your defense roll will be:
RND(Stamina/3) + 3*ArmorPower
There is no upper limit, but a 20 or higher is a guaranteed miss.

If the monster’s attack roll beats your defense roll, you take damage, or the monster uses its special attack move if it has one.

Dam is the range of damage the monster may do to you. The formula is:
RND(MN*Level^2) + (2*Level)

Def is the range for defense rolls. The formula for defense rolls is:
RND(MN + (MB – 20)/3) + Level
But it will never exceed 20.

When attacking, your attack accuracy is:
RND(Agility/4 + WeaponPower)

The monster’s defense is then rolled. If your accuracy beats it, then you hit.

Damage from your hits will be:
RND(Strength/5 + WeaponPower*3) + Strength/5

Ranged weapons have unlimited range, but don’t pass through traps, doors, or false walls.

If you kill a monster, another one, not necessarily of the same type, but always one of a type not currently in the dungeon, will spawn in a random unoccupied space, and will have HP in the range of the HP2 column.

XP is the immediate experience reward for defeating the monster. Minimum is always equal to Level. When you leave the dungeon, you will be granted HP equal to double all of the experience accumulated in the trip. The formula for XP is:
RND(Level * MN * 5) + Level

G is the gold reward. The formula is:
RND(Level^2 * 9) + 9

Each dungeon follows this template:

On level 1, the Up ladder will be in the top-left corner instead. You enter facing south.

On all even levels, the Up and Down ladders switch places.

On level 10, there will be no Down ladder.

When a level is generated, each yellow and orange spot will be a randomly chosen feature, according to these odds:
  • 10% nothing
  • 30% false wall
  • 50% door
  • 5% pit
  • 5% trap

Level 10 will not have pits or traps, but I don’t really follow the logic that replaces them with something else.

Then, chests, coffins, and fields will be placed. The total number of them will be equal to the level number, and they will be placed in random orange spots, or one spot below an orange spot. They will override any features already present, except for ladders.

Then, three monsters spawn, always of different types. They will be placed in random unoccupied squares.

Falling into a trap without possessing any rope & spikes descends you a level and inflicts RND(Level*10) + Level damage.

Pressing I when facing a trap will turn it into a pit.

Pressing I when facing a false wall will turn it into a door.

Opening a coffin will always be successful if the space in front of you is occupied.

Otherwise, there is a 60% chance of success. If it fails, then this logic follows:
  • Pick a random monster to spawn
  • If the monster type is not already present, then spawn it.
  • If it is already present, then there’s an 80% chance of repeating from the top, and a 20% chance of just opening the coffin.

Monsters spawned from coffins will die in one hit, but this will cause another monster to spawn somewhere in the dungeon with its HP2 hitpoints.

Non-thieves have a (50% + Agility * 1%) chance to unlock chests. Thieves are always successful.

A failed unlock does [Level] damage.

Gold from coffins and chests pays the same as any monster on the level.

Casting spells has a (50% + Intelligence * 1%) chance of success for non-clerics. Clerics never fail.

Prayer always fails in dungeons. It looks as though prayer was once meant to invoke a random spell effect, but Garriott may have intentionally disabled this. Or it could just be a bug.

Magic Missile has a range of 5 spaces, and always hits for (Wisdom/2 + 11) damage, rounded down. It won’t pass through traps, doors, or false walls.

Steal does nothing.

Ladder Down doesn’t work on level 10.

Blink takes you to a random unoccupied spot in the dungeon.

Create and destroy have a range of 1 square, and create only works if the square is unoccupied.

Kill has a range of 1 square, and just kills. Doesn’t matter what kind of monster it is.


Time passes 0.6 units with each keyboard command.

Moving consumes (0.5 – VehiclePower/14) food and advances time (1 – VehiclePower/7) units, plus the 0.6 time units for the keyboard command.

Each step on land has a 5% chance of spawning a monster group.

Each step in the woods or at sea has a 10% chance of spawning a monster group.

I haven’t been able determine the monster group size ranges, but each monster type has a fixed maximum group size, and the formula for group size is:
RND(1)^2 * MaxGroup

If walking away from an encounter, odds of escaping are:
1/7 + Strength*3/1400 + Agility*3/1400 + VehiclePower*(1/14 – Strength/5600 – Agility/5600)

If you fail, then you are stuck in place for a turn and the enemy gets a free round of attacks on you.


Name Index Type Base HP EXP Def G
Ness creature 6 Sea 30-69 20 20 10-89
Giant squid 7 Sea 30-89 30 20 10-129
Dragon turtle 8 Sea 30-109 40 20 10-169
Giant octopus 9 Sea 30-129 50 20 10-209
Hood 10 Woods 30-49 5 15 10-29
Bear 11 Woods 30-69 10 20 10-49
Hidden Archer 12 Woods 30-89 20 20 10-89
Dark Knight 13 Woods 30-109 30 20 10-129
Evil Trent 14 Woods 30-129 40 20 10-169
Thief 15 Land 10-19 10 20 10-49
Orc 16 Land 10-29 20 20 10-89
Knight 17 Land 10-39 30 20 10-129
Necromancer 18 Land 10-49 40 20 10-169
Evil Ranger 19 Land 10-59 45 20 10-189
Wandering Warlock 20 Land 10-69 50 20 10-209

Sea creatures and archers may only be hit with ranged weapons.

Index is used for HP calculations.

Base HP represents the range of HP that the first monster in the group might have. This only applies to the first in the group; the rest will have much less.

The formula for sea monsters is:
RND(20*Index – 80)) + 30

The formula for woods monsters is:
RND(20*Index – 180) + 30

The formula for land monsters is:
RND(10*Index – 140) + 10

In addition, the first monster in the group will receive additional HP based on the amount of time passed.
+ RND(1)^2 * Time/100

That is Time/100, and not Time/1000, so the maximum bonus is roughly ten times your level.

Exp is a fixed amount, and I couldn't find where it is defined in the code, but it wasn't hard to determine from observation.

Defense is strangely coded. It’s programmed to be equal to EXP + 10, capped at 20. But Hoods are the only monsters weak enough not to hit that cap.

When attacking, your attack accuracy will be:
RND(20) + Strength/5 + Agility/5 + WeaponPower

If this beats the monster’s defense, you hit and do this much damage:
RND(WeaponPower + Strength) + 1

Firing vehicle guns always has an 80% chance to hit. Damage done is:
RND(10 * VehiclePower) + 30

If you kill a monster, and there are more in the group, the next one’s HP will be:
RND(1)^2 * PlayerLevel + RND(20)

When monsters attack, their attack accuracy, per monster, will be:

For your defense, variable ‘D’ will be set to ArmorPower/5, but not greater than 0.5. Your defense value per attack will be:
(D + 1.5) * Agility
But it will not exceed 80.

If the attack exceeds your defense, then you take a random amount of damage not greater than the monster’s EXP value. If you take multiple hits during a turn, you won’t see the damage per-hit, only the total number of times you were hit, and your overall decrease in HP.

Visiting landmarks that grant stat boosts will grant the following amount:
INT(9.9 – OldStat/10)

Spells do not have any random chance of failure outdoors.

Prayer has a 33% chance of each of the following effects:
  • If there are monsters present, kill them all (no reward). If not, lose 20 food if you have more than 20, and the next effect happens.
  • If you have fewer than 10 HP, set HP to 10. If not, the next effect happens.
  • If you have fewer than 10 food, set food to 10. If not, no effect.

To be clear, there is both random chance and conditional logic involved. There are three steps, but there's a 33% chance of starting on each step. Let's say there are no monsters, you have less than 10 HP, and you have 21 food. There's a 33% chance that we start on step 1, you lose 20 food, and then step 2 happens and your HP is set to 10. There's a 33% chance that we start on step 2 and you just gain HP. And there's a 33% chance that we start on step 3 and nothing at all happens.

Magic missile does Wisdom/2 damage. If you have an amulet, wand, staff, or triangle equipped, it does an additional WeaponPower*2 damage. This weapon bonus only applies outdoors.

The Kill spell kills one monster in a group.

Time Machine

Getting burned does 10% damage to your HP.

Ranged weapons have a range of 4 tiles. Distance is calculated with the Pythagorean Theorem.

Mondain spawns with 1000 HP. He will be alerted if you attack him or stand near him.

Mondain periodically gains 10 HP. I think this happens every round, but am not sure.

If Mondain is unconscious and the gem is still intact, he will at some point gain 25 HP and regain consciousness. Again, I think this happens every round after he is unconscious, but am not sure.

Your attack accuracy roll is:
RND(Strength/2 + Agility/2) + 3*WeaponPower

Mondain’s defense roll is:
RND(100) + 50
But it will not exceed 70.

If your attack roll beats his defense roll, then you hit for this much damage:
RND(Strength/5) + WeaponPower*3 + Strength/5

When Mondain’s HP drops to 500, he turns into a bat.

Mondain’s melee attack has a roll of:

Your defense will be:
Strength/3 + Agility/3 + Stamina/3 + ArmorPower*2

If his attack beats your defense, then he inflicts damage of 1/25th of your HP, plus RND(20).

If Mondain is outside of melee range, but less than 7 tiles away from you, then there is a 50% chance of a magical attack. He has three possible spells:
  • Magic Missile
  • Mind Blaster
  • Psyonic Shock

Magic Missile’s odds of hitting you are:
(1 – Agility/100)*(1 – Intelligence/100)

If it hits, it does damage to 1/500th of your HP, plus RND(100).

Mind Blaster has a 30% chance of reducing all of your stats by 10%. Otherwise it misses.

Psyonic Shock has a 30% chance of doing up to 5% damage. Otherwise it misses. Kind of a pathetic spell.

Your own spells have a 30% chance of failure if you are not a cleric. Clerics never fail. In this light, I think Mondain's spells were meant to have a 30% chance of failure too, but instead have a 30% success rate thanks to Garriott using the wrong comparison operator.

Your spells fail if you are more than 6 tiles away from Mondain.

Your magic Missile does this much damage:
RND(Wisdom + Intelligence)

The Kill spell doubles Mondain’s HP!

Taking the gem does 75% damage to your HP.


  1. Spaghetti code at its finest!

    Interesting to see how it all fits together.

  2. "Non-thieves have a (50% - Agility * 1%) chance to unlock chests. Thieves are always successful."

    Is that correct? That would imply that better agility makes you worse at opening chests!

    1. Whoops! Good catch. That's the chance to FAIL to unlock chests. I rephrased it for clarity, but I forgot to inverse the formula. Corrected!

  3. This is great stuff! If you could do some reverse engineering and do a similar article for Ultima III or IV, that would be really something!

    1. Probably not going to happen. Ultima is BASIC, but the sequels are assembly. Ultima IV does have a ScummVM implementation in C++, but it is based on xu4 which claims to be a "recreation" and not a source port, so it is unlikely to be accurate.


Most popular posts