OSG Errata

The book "Master of Orion - The Official Strategy Guide" (ISBN 1-55958-507-2), henceforth OSG, has many errors. This document aims to collect all the cases where the OSG and actual v1.3 behaviour differ.

The errors are organized into the chapters they appear in. The first paragraph of each entry describes the error and reality. The rest is elaboration and pointing at the proof.

The function names correspond with the ones 1oom and the IDB files use. The hexadecimal offsets refer to v1.3 DOS executables, either orion.exe or starmap.exe depending on if the error relates to galaxy generation (orion.exe) or gameplay (starmap.exe).

This document is far from complete.

Chapter 3

For Richer Or Poorer

Table 3-7 (page 55) claims that the (Ultra) Poor planets are of type Minimal, Desert, Steppe, Arid or Ocean. The text (page 52) claims that the types are Steppe, Arid, Ocean, Jungle and Terran. The text is correct and the table is wrong.

The code (game_generate_planets at 0x2e389) checks for "Steppe or better" (0x2efb2, 9 == Steppe).

Chapter 5

Ship Maintenance

OSG (pages 83, 386) claims that the build cost of star gate is 3000 BC and maintenance is 300 BC per turn. The manual (page 69) claims 2000 BC and 100 BC per turn. The v1.3 readme.txt and the ingame text claim 3000 BC and say nothing of maintenance. The truth is that the build cost is 3000 BC and maintenance is 100 BC per turn.

The ship build phase (game_turn_build_ship, 0xbb30) has the instructions "cmp [bp+prod], 3000" (0xbd15) and "add ax, -3000" (0xbd52). The maintenance calculation (game_update_maint_costs, 0x8737) has the star gate code (from 0x88ea to 0x892c) with the instruction "add empiretechorbit.ship_maint_bc[bx], 100" (0x8922).

Chapter 6

The Ecology of Industrial Growth

OSG claims (page 97) that refusing to pay for waste cleanup creates pollution. This is not (always) true. Given suitable population, robotic controls and waste reduction, the waste cleans itself.

The waste generation code (game_turn_build_eco at 0xa356) does something like this:

operated_factories = min(population * colonist_operates_N_factories, factories)
w1 = operated_factories * waste_scale / 10;
w2 = ((100 - (waste * 100) / max_pop_base) * w1) / 100;
waste += w2;
if (rebellion) waste = 0;
cleanup_cost = waste / have_eco_restoration_N;
w3 = operated_factories * colonist_operates_N_factories / 10;
w4 = ((100 - (waste * 100) / max_pop_base) * w3) / 100;
waste += w4;
if (!silicoid) {
    if (cleanup_cost > eco_production) {
        waste -= have_eco_restoration_N - eco_production;
        eco_production = 0;
    } else {
        eco_production -= cleanup_cost;
        waste = 0;
    }
}
if (waste < 0) waste = 0;
if ((max_pop_base - waste) < 10) waste = max_pop_base - 10;
if (waste < 0) waste = 0;
The value w4 (game_turn_build_eco_sub1 at 0xae49) can be negative if waste is larger than max_pop_base at that point. If w4 > w2, waste is cleaned regardless of eco_production. Note the weird w3 calculation and that w4 not added to the cleanup cost. It appears as if the second waste addition is old broken code that was not removed when adding the new working code.

Ignoring rebellion and assuming sufficient factories are available, the waste calculation is:

w_n <- w + ((100 - ((w * 100) / m)) * p * o * s / 10) / 100 + ((100 - (((w + ((100 - ((w * 100) / m)) * p * o * s / 10) / 100) * 100) / m)) * p * o * o / 10) / 100
... where w_n is the new waste amount, w is the old waste amount, m is max population base (without terraforming), p is current population, o is factories operated per population and s is waste reduction scale (10, 8, ... 0). For example with Terraforming +50 on a 100 pop planet (p == 150, m == 100), Robotic Controls III (o == 3) and no waste reduction (s == 10), w_n always smaller than w. Getting Waste Reduction 80% (s == 8) changes the situation so that w_n is larger than w. Simplifying the formula and expressing w_n < w in terms of m, p, o, and s is left as an exercise for the reader.

Chapter 7

Oracle Interface

OSG claims (page 119) that the Oracle Interface halves the target's shield when using beam weapons. This is not true. The Oracle Interface does nothing. (It does affect battle AI damage estimation, but incorrectly.)

This zip contains an artificial save to test this. The player's attacking ships "no oracle" and "yes oracle" have 99 lasers (damage 1-4) and the latter has the Oracle interface. The defending planet has Class V + I shields. The ship "bomber" has Class IV shields. If the Oracle Interface worked, the "yes oracle" ship could damage the "bomber" ship. This is not the case. (The planet is kept safe by the "beam vs. planet" damage halving.)

Chapter 8

Resolving Land Combat

OSG claims (pages 146-147) that during land combat a tied die roll results in both sides losing a population point and that both sides losing their final point simultaneosly results in a denuded colony. This is not true. The tie is resolved in favor of one of the sides and one of the sides always wins.

The land combat core (game_ground_kill at 0x81a92) in pseudocode:

v1 = 1d100 + side_1_force
v2 = 1d100 + side_2_force
if (v1 <= v2) {
    side_1_pop -= 1
} else {
    side_2_pop -= 1
}
The code is called until either side has 0 troops.

Chapter 10

Research Discovery Base Cost Formula (1)

OSG claims (pages 210-211) that the AI players use the Average level difficulty multiplier (30) for research discovery costs. This is not true. The AIs use the Simple multiplier (20).

The code which sets the cost (game_tech_start_next at 0x190dc) uses the first item (20) of tbl_tech_costmuld (0x368ce) for AI players (see 0x19175, 0x19179).

Research Discovery Base Cost Formula (2)

OSG fails to mention (pages 210-211) that the research cost for the (non-AI) player is rounded down to the nearest multiple of 10.

The code which sets the cost (game_tech_start_next at 0x190dc) does "cost /= 10; cost *= 10;" (from 0x19238 to 0x19246) for the non-AI player.

The Limited Research List (1)

OSG claims (page 213) that the limited research list must include at least one Planetary Shield. This is not true. Instead one of the techs from Planetology with same tech level is guaranteed.

The code which checks for essential tech (game_generate_research at 0x32cce) does something like this (0x331de):

if (tech_field == 3) {
    if (researchlist[tech_field] has none of { 12, 22, 32, 42 }) {
        got_essentials = false;
    }
}
The problem is that tech field 3 is Planetology while Force Field is tech field 2. This results is giving every race at least one of Controlled Inferno Environment, Atmospheric Terraforming, Terraforming +60 or Advanced Cloning. Since the first two are not available for Silicoids, one of the last two is always included for them.

This zip contains a save generated with v1.3 which has no Planetary Shields for the player. Playing from the save onwards until enough Force Field is researched to determine if this is true is left as an exercise for the reader.

The Limited Research List (2)

OSG claims (page 213) that the limited research list must include at least one missile (i.e., either a missile, rocket or torpedo) technology. This is true but not quite accurate. Not all missiles and none of the torpedoes are considered as missiles in the list generation.

The code which checks for essential tech (game_generate_research at 0x32cce) does something like this (0x332cc):

if (tech_field == TECH_FIELD_WEAPON) {
    if (researchlist[tech_field] has none of { 4, 8, 11, 18, 27, 34, 41, 44 }) {
        got_essentials = false;
    }
}
The code ignores the Merculite Missiles (14), Pulson Missiles (29) and all the torpedoes (23, 40, 43, 50).

Chapter 11

Break Alliance (or Nonaggression Pact) with Another Race

OSG claims (page 236) that the audience option Break Alliance with Another Race appears if the races have only a Non-Aggression Pact. This is not true. The option appears only for races with alliances with other races.

The function audience_menu_treaty (0x6910e) checks only for alliance (type 2, from 0x691f4 to 0x69246, "cmp" at 0x69226). This zip contains two artificial saves to test this. In the _alliance.gam save everyone has an Alliance (type 2) with everyone (easily checked with the Races -> Report). In the _nap.gam save everyone has a Non-Aggression Pact (type 1) with everyone (a hexdiff tool shows the only differences are in the treaty tables at 0x71f0, 0x7fc4 and 0x8d98). The option appears only with the _alliance.gam save.

Chapter 12

How to Beat the High Cost of Spying

OSG claims (page 269) that the cost of a new spy is doubled for each existing spy in the given empire. This is not true. The first spy hired each turn costs computer tech level times 2 plus 25 and every additional spy doubles the cost, regardless of target empire.

The buggy spy hiring code (game_spy_build at 0xc05f) in pseudocode:

for (each player) {
    spycost = tech.percent[COMPUTER] * 2 + 25;  // current amount of spies is ignored!
    if (race == RACE_DARLOK) {
        spycost /= 2;
    }
    for (each opponent) {
        // spycost is not recalculated between opponents!
        if (spying_slider[opponent] != 0)) {
            spyfund = (total_production_bc * spying_slider[opponent]) / 1000 + next_spyfund[opponent];
            while (spyfund >= spycost) {
                spies[opponent]++;
                spyfund -= spycost;
                spycost *= 2;
            }
            next_spyfund[opponent] = spyfund;
        }
    }
}
The display code (ui_races_draw_cb at 0x7c2e9) recalculates "spycost" for each opponent, but as "tech.percent[COMPUTER] * spies[opponent] + 25" (see 0x7c839) with is still not what the OSG describes.

The Spread of Rebellion

OSG claims (page 276) that the chance of rebellion spreading to a colony is 1 percent per each currently rebelling colony. This is not true. The chance is 0.

The buggy rebellion code (game_turn_unrest_hmm1 at 0xf4ed) in pseudocode:

for (each player) {
    table[player] = 0;
}
for (each player) {
    chance = table[player] * 2;   // always 0
    planet = game_planet_get_random(player);    // returns PLANET_NONE for dead players
    if ((planet != PLANET_NONE) && (planet.unrest != PLANET_UNREST_REBELLION))
      || (planet.unrest != PLANET_UNREST_RESOLVED)   // PLANET_NONE not checked!
    ) {
        if (chance > get_random_number_from_1_to(100)) {  // never true!
            planet.unrest = PLANET_UNREST_REBELLION;
            planet.rebels = planet.pop / 2;
        }
    }
}
The function would spread rebellion if the table was filled with the amount of rebelling colonies, but such code simply does not exist. The result is that rebellion does not spread.

No News Is Not Good News

OSG claims (page 281) that successful AI espionage is reported to the player only if they are identified. This is not true. Every stolen technology is reported to be stolen by the actual spy race. No news is good news.

The code (game_spy_espionage at 0x88430) stores (at 0x81f35) the field, tech and spy to tables stolen_my_{field, tech, spy} (save offsets 0xde80 + { 0x22e, 0x23a, 0x246 } + player_index). This is done before every tech steal (game_tech_get_new call at 0x885ef). The stolen_my_spy value is set to unknown (-1) or the framed race. However, the code which chooses what to display (game_spy_esp_human at 0xf3a0, from 0xf467 to 0xf4b0) ignores the value completely and gives the draw code the actual spy (see 0xf47e). Furthermore, the draw code (ui_spy_stolen_draw_cb at 0x73853) does not handle the "unknown spy" (-1) case but would read the race from a negative index.

Chapter 14

Donation

OSG claims (page 304) that a donation is 10 times the turn number. This is not true. The amount is the turn number times 10 rounded down to nearest 100, or 500 if the turn is less than 50.

The code (game_event_run at 0x12a86, from 0x146ac to 0x146ce) does MAX(floor((turn * 10) / 100) * 100, 500).

Earthquake

OSG claims (page 304) that a planet hit with an earthquake has 30 or more population. This is not true. The limit is 50.

The event filtering code (game_event_new at 0x1222f, from 0x1238d to 0x125ec) rejects an earthquake (event type in SI, quake == 2) if the planet has less than 50 population (see 0x12402).

Industrial Accident

OSG claims (page 304) that a planet hit with an industrial accident has 30 or more factories. This is not true. The number of factories is irrelevant.

The planet selection code (game_planet_get_random at 0x86ae2) does not check the amount of factories. The event filtering code (game_event_new at 0x1222f, from 0x1238d to 0x125ec) rejects an accident (event type in SI, accident == 4) only if the planet is owned by Silicoids (see 0x12527).

Plague (1)

OSG claims (page 305) that a planet hit with a plague has 30 or more population. This is not true. The amount of population is irrelevant.

The planet selection code (game_planet_get_random at 0x86ae2) does not check the amount of population. The event filtering code (game_event_new at 0x1222f, from 0x1238d to 0x125ec) rejects a plague (event type in SI, plague == 1) only if the planet is owned by Silicoids (see 0x12527).

Plague (2)

OSG claims (page 305) that a cost of the plague cure is from 3 to 10 times the planet's current gross production. This is not true. The cost multiplier range is from 1+K*2 to 8+K*2 where K is 0..4 for Simple..Impossible.

The plague event setup code (game_event_new at 0x1222f, from 0x12660 to 0x126a6) does a "1d8 + difficulty * 2" and multiplies it with the production value for the cost.

Super Nova

OSG claims (page 308) that a planet the time before Nova is from 5 to 15 years and the cost is from 5 to 15 times the planet's current gross production. This is not true. The ranges are from 11-K to 15-K where K is 0..4 for Simple..Impossible.

The nova event setup code (game_event_new at 0x1222f, from 0x126cd to 0x1272b) does a separate "1d5 + 10 - difficulty" for the years and the cost.

How to Disable Random Events (1)

OSG claims (page 308) that disabling events with Alt-EVENTS displays "Events Off". This is not true. The text is "No Events".

How to Disable Random Events (2)

OSG claims (pages 308-309) that re-enabling events with Alt-EVENTS resets the events and the "entire list will occur again from the point at which they were switched back on". This is misleading. While any ongoing event is reset (for example plague/Crytal disappears), no event which has once started will occur again.

The events are reset (game_event_new, from 0x12236 to 0x12309) but the "event has happened" flags (save offset 0xde84, 2B per flag) are not.

Chapter 15

Orion

OSG claims (pages 314-315) that the Guardian has Automated Repair System on Hard and Advanced Damage Control on Impossible. This is not true. Due to a bug the Guardian has Automated Repair (but shows ADC) on Hard and nothing on Impossible.

The buggy code (from 0x15921 to 0x1596b) in pseudocode:

if (difficulty == hard) { special2 = ADC; } else if (difficulty == hard) { special2 = AR; } // for display in Scan
if (difficulty == hard) { repair = 15; } else if (difficulty == hard) { repair = 30; } // actual repair

Beyond the 32,000-Ship Limit

OSG claims (page 316) that a ship stack showing 32000 ships may actually contain more, but the display is limited to 32000. This is not true. There is no 32000 limit in any display code, but values larger than 32767 are shown as negative.

There is game logic code that limits the ship count to 32000 (game_turn_limit_ships at 0xfa6e, game_turn_build_ships at 0xbb30) but negative ship numbers have been observed. For example of not limiting in the display code, see ui_starmap_obit_en_draw_cb (at 0x71529, 0x717bf has the display call, value copied at 0x71034).

The same paragraph also claims that the limit is 65256 ships. This rather strange value does not seem to appear in the code. The actual limit is very likely to be 65535 (shown as -1).

The source of the negative ship amounts is AI-AI battles (battle_ai_ai_resolve_do at 0x16553) where large weights cause overflows (mul32_u calls at 0x16d81 and 0x16ddb).

Appendix A

XVI. Housekeeping, D.

OSG claims (page 329) the game is automatically saved at the end of turn processing. This is not true. The saving is done only every 5 years and before calculating current technology levels.

The autosave code (game_autosave at 0xe64d) does "if ((year % 5) == 0) save();" and is called (at 0xcd24) before the tech update code (from 0xcd27 onwards).