Random numbers in Dragon Warrior III by vaxherd
ガイド
/
Random numbers in Dragon Warrior III by vaxherd
更新済み 7 years ago 投稿者: pjplustwo

How the random number generator is seeded

The random number generator is seeded from the save file checksum whenever a save file is saved (or checked, on reset -- note that while the files are checked in slot order, DW3 swaps the positions of save files to put the current file in the first slot, so the seed on reset is not necessarily from file #3). See the bottom of this file for details of how this works and how it can be abused to break the RNG. On reset with a fresh cart (backup RAM not initialized) or in which the file in the third slot is save file #3 and is empty, the initial seed when the main menu opens is $3D27.

Random numbers in character creation

When creating characters in a new file:

  • If the character is not the hero, choose the name by taking the low 2 bits of a random number as an index into a table of 4 predefined names (each character has a different list of 4 possible names).
  • Take the predefined base values for the character's class and add the low bit of a random number to each (in the order Luck, Intelligence, Vitality, Agility, Strength).
  • The file is then checksummed (after other non-random initialization like inventory setup), resetting the RNG seed.

When creating a character at Luisa's Place 2F:

  • Store the predefined base stats for the selected character class.
  • Take a multi-random number in the range 0 through 4 (as in battles; see below) and add 2; this is the number of stats to increment.
  • For that number of times, take a multi-random number in the range 0 through 4 and increment the stat selected by that index (in the order Strength, Agility, Vitality, Intelligence, Luck).
  • The file is checksummed after other character setup is complete.

Random numbers on the field

  • When entering a new map, a random number is taken and discarded.

  • When an NPC is about to move, a random number is taken and the low 2 bits are used to select the direction the NPC moves in. (NPCs move based on the global frame timer: NPCs 0 and 1 move on frames $00 and $80, NPCs 2 and 3 move on frames $10 and $90, etc. Movement is suppressed if the NPC is offscreen or in a different map room.)

  • Numbness healing while moving: For each numb character, take a random number at each step; numbness heals if the low 4 bits of the random number are zero.

  • While on the non-Alefgard overworld and neither the current tile nor the last 3 tiles are map entrances, every 64 frames (when the low 6 bits of the $90 frame counter are zero):

    • Take a random number. If the low 3 bits are zero, take another random number.
    • Take another random number.
  • On the overworld, on each step (before checking for encounters), add ((seed & 31) + 223) / 256 to the time-of-day timer. The timer is an 8.8 fixed-point value with a period of 204; "night" is any value 120 or greater. Note that this does not take a new random number from the RNG, but just uses the current seed value.

  • If in a zone (overworld) or map (town/dungeon) with enemies, encounters are checked for on each step (see $8222 in bank 0):

    • If in a low encounter rate area (16/256, 64/256, 128/256), take a random number and skip the encounter check if the number is greater than or equal to the /256 chance.
    • Multiply the encounter rate (1 for low encounter rate areas) by:
      • On the overworld:
        • Water: 4 during the day, 5 at night
        • Grass tiles: 10 during the day, 13 at night
        • Woods, desert, ice tiles: 15 during the day, 19 at night
        • Heavy forest, swamp tiles: 18 during the day, 22 at night
        • Hills: 25 during the day, 31 at night
        • Other tiles: encounter not possible, abort
      • In dungeons: 84 after a room transition, 10 elsewhere But set the encounter rate to 100 if any party member has the Golden Claw (equipped or not), or if the product is greater than
      1. Then take a random number, and an encounter occurs if the random number is less than the encounter rate.
    • If an encounter occurs, use a random number to choose one of the 14 encounters in the group (encounters are weighted based on tables, see 0/$90F3). If on the overworld, and:
      • during daytime, the chosen encounter is 4, 10, or 13; or
      • during nighttime, the chosen encounter is 0 or 6, or if the chosen encounter is $FF, repeat up to 98 more times, then abort the encounter.
    • Choose enemy groups:
      • If the encounter is index $00 through $04, then:
        • Add a group of 1 of the base enemy to the encounter.
        • For up to 19 tries, choose a random number from 0 through 5 (using $C3F1, which repeatedly takes random numbers and masks off high bits [$F8, in this case] until the masked value is in range). If the number is not the same as the encounter index, the number is valid for the current day/night state (if on the overworld), the enemy ID of that encounter is not $FF, and that enemy has not been seen yet:
          • Add a group of that enemy (count 1) and mark it as seen.
          • Compare a random number against the chance of adding another group (this chance is part of the encounter data); if greater or equal, stop adding enemies.
          • Advance the additional-group-chance index, and if it reaches 2, stop adding enemies.
        • Choose a random number from 0 through 2. If it is zero, stop. Otherwise, choose another random number from 0 through the highest defined enemy group index (or 4, if both additional-group chances were consumed); if that group is not the encounter $05 enemy, add 1 to the group count. Then repeat this step.
      • If the encounter is index $05, add a group of 1 of the base enemy to the encounter; then choose a random number from 0 through 4, and add 8 of that enemy to the encounter. If on the overworld and the selected encounter is invalid for the current day/night state, or if the selected encounter ID is $FF, repeat 18 more times, then skip adding a second group.
      • If the encounter is index $06 through $0A, choose a random number of enemies from the random range selected for the encounter and set that as the solitary enemy group's count. Note that if the random range is a power of 2 (e.g. 4-7), $C3F1 masks off one bit too few, so the random number range check will fail half the time.
      • If the encounter is index $0B, there is no randomness (one fixed enemy in the encounter).
      • If the encounter is index $0C or $0D, then for each of 4 enemy groups, choose a random count from the random range defined for the group. This is performed even if the random range is 1 (constant count) and even if the group does not exist in the encounter (enemy ID is $FF).

Random numbers in battle (and damage calculations, etc.)

Aside from the regular RNG function ($C3B8, below just "random number"), the battle system uses several non-straightforward methods to get random numbers. I refer to them with the following terms (see the noted routine addresses in bank 4 for implementations).

  • "multi-random number" ($ACBE): a random number taken after discarding a count of numbers from the stream equal to the value of $6A68 plus 1; see also the note below about "why battle outcomes can differ even with the same RNG seed".

  • "multi-random number in a range" ($ACA3): a multi-random number multiplied by a range and divided by 256. For a range R, this gives values (approximately) evenly distributed from 0 through R-1 without needing to repeatedly take random numbers as with $C3F1.

  • "random-16 number" ($BA5C): a random number computed by summing the low 5 bits of 16 consecutive random seeds (each taken after discarding a random number from the stream) and subtracting 120, then repeating until the result is in the range [0,255]. This gives a result weighted toward 128, similar to how rolling two fair six-sided dice will result in a sum of 7 more often than 2 or 12.

  • "random-32 number" ($BA64): like random-16, but computed by summing the low 4 bits of 32 consecutive random seeds and subtracting 112.

The last two methods have a theoretical risk of infinite-looping if the RNG gets stuck in a hole, since the counter ($00A4) which is normally added to the RNG seed when returning a random number is ignored. Fortunately, the two holes in the actual function used by the RNG land within the [0,255] range, so they're safe; seed $5FEA gives results of 40 for random-16 and 208 for random-32, while $AFF5 gives results of 216 for random-16 and 48 for random-32.

Battles proceed as follows (omitting some non-random logic for brevity):

  • At startup:

    • For each Shadow is in the party:
      • Take a random-16 number.
      • Let N be that number times the lead party member's level, divided by 100, plus one; if N is greater than 130, set it to 130.
      • Take a multi-random number in the range 0 through N-1, and set the effective enemy ID to that value.
    • Set HP for each enemy by subtracting a multi-random number in the range [0,(floor(base_HP/4)+1)%256) from the enemy's base HP. (The mod-256 is relevant only for Zoma, whose base HP is 1023 so HP/4+1 overflows to zero.)
    • Choose a random party member as the focus target for each enemy group (regardless of whether the enemy's data has the focus-attacks flag set).
    • If the battle is not a non-interactive or preset battle:
      • If the preemptive/back type (copied to $49 from table at $8ADB in bank 0) is 1 and a random number is less than 32, the encounter is a back attack (1 free turn for enemies).
      • Otherwise, if the preemptive/back type is not 3 and a random number is less than 8, the encounter is a back attack.
      • Otherwise, if the preemptive/back type is 2 and a random number is less than 32, the encounter is a preemptive attack (1 free turn for the party).
      • Otherwise, if the preemptive/back type is not 3 and a random number is less than 8, the encounter is a preemptive attack.
  • At the beginning of each turn, for each of the 12 battle character slots (4 party member slots followed by 8 enemy slots), take a multi-random number in the range [agility/4,agility) to determine action order for that turn; higher values go sooner, and later slots win ties over earlier slots. All slots are processed, even if the slot is empty or the associated character or enemy is dead.

  • If not a non-interactive battle, then for each party member:

    • If the party is ironized, attempting to run, or in a back attack, skip.
    • If the character is asleep, dead, or numb, skip.
    • If the character is in BeDragon status, take a random number. If the number is 0, the character attacks the first enemy group; if not, the character chooses action 4 if the low bit of the number is 0, otherwise action 5. (Both of these actions are the breath attack; it may be that BeDragoned characters were originally intended to have two actions, but that was changed to just the breath attack during development.)
    • Otherwise, show the character's battle command menu and let the player enter a command.
    • If the player chooses Run on the first character's menu:
      • If the battle is a preset encounter, the attempt fails.
      • Otherwise, if in the first turn of a preemptive attack or if the lead party member's level is at least 10 levels higher than the highest enemy level in the encounter, the attempt succeeds.
      • Otherwise, take a random number; the attempt fails if the number is less than the chance selected by the number of previous failed run attempts (128, 128, 64, 0 -- so the fourth run attempt always succeeds).
  • If an interactive battle, then unless Run was chosen, take a random number and store the low 4 bits in $6A68 (the multi-random discard counter).

  • Choose actions for each enemy:

    • If the enemy is dead, do nothing.
    • If the AI type (see notes below about enemy data) is 2, do nothing (actions will be chosen when the enemy actually takes its turn).
    • Choose an action and target:
      • Choose an action:
        • If the action chance selector is 3 (preset action order), choose the next action in sequence.
        • Otherwise, take a multi-random number, and choose an action based on that number and the chance table for the action chance selector.
      • Choose a target for the action: (note that in non-interactive battles, party targets are still selected here but will be changed by the confusion handler at turn resolution)
        • If the AI type is 0:
          • If the action targets a party member or is "assess the situation", parry, flee, or "call for help (same enemy)", choose a random target from all living party members.
          • If the action is Vivify or Revive, choose a random target from all dead enemies.
          • Otherwise, choose an appropriate enemy target (logic omitted for brevity).
        • Otherwise:
          • If the action is a spell, then:
            • If the enemy does not have enough MP for the spell:
              • If the AI type is 2, choose no target (try another command).
              • Otherwise, if the enemy has already used the action in the current battle, choose no target.
              • Otherwise, mark the action as having been used and continue.
            • If the enemy is in StopSpell status and the AI type is 2, choose no target.
          • Choose a target based on the action:
            • If "assessing the situation", parrying, or using a damage breath attack, choose a dummy target to cause the command to be accepted.
            • If attacking:
              • If the enemy focuses attacks on a single character:
                • If the character is alive, choose that target.
                • Otherwise, if at least one other party member is alive, choose a random living party member.
                • Otherwise, choose the existing target.
              • Otherwise, for each living party member, take a multi-random number; if it is less than or equal to the cumulative probability at that party slot, that character is chosen as the target. (This is the only case in which party order makes a difference, aside from a very slightly greater probability to choose the last party member due to off-by-one and rounding errors in unweighted random selection.)
            • If fleeing, choose a dummy target if the enemy's level plus 5 is less than the lead party member's level; otherwise, choose no target. (Thus, enemies who have the Flee action will not use it unless their level is at least 6 lower than the lead party member's level.)
            • If casting a damage spell:
              • If the AI type is 2, choose a random living party member who is not in Bounce status.
              • Otherwise, choose a random living party member.
            • If casting Increase:
              • If the AI type is 1-3, choose a random enemy target whose defense is at least 768. (This appears to be a bug in the code; the branch targets after the defense check should probably have been switched. As a result, "smart AI" enemies will never use Increase unless a Metal Slime or Metal Babble is in the encounter.)
              • Otherwise (AI type 0), choose a random enemy target.
            • (Logic for other actions omitted for brevity.)
      • If no target could be chosen, retry the action choice, but for chance selectors other than 3, exclude the previously chosen action from the set of possible actions and spread its chance evenly among the chances for all remaining actions in the action list (rounding down, so that the last action gets all of the remainder).
      • If no target could be chosen for any action, set the action to a normal attack.
    • If the action count selector is 3, choose a second action:
      • If the action chance selector is 3, choose the next action in sequence, just like the first action.
      • Otherwise, take a multi-random number and choose an action as for the first one.
      • Choose a target for the action as for the first action.
    • If the action count selector is 1, take a random number, and if the low bit is 1, choose a second action.
    • If the action count selector is 2, take a random number. If the low 2 bits are 0, do nothing (only one action). Otherwise choose a second action, and if the low 2 bits of the random number were 1 or 2, choose a third action in the same manner as the second.
  • Resolve the turn. For each character (party or enemy) in turn order:

    • If a party member:
      • Skip the turn if the party is ironized or fleeing.
      • Skip the turn if the character is not present or dead.
      • If numb, print a numbness message and skip the turn.
      • If asleep, take a multi-random number and compare against the chance of waking for the number of sleep turns remaining (255, 128, 85, 32). If less than or equal to that chance, wake up the character; in any case, print an appropriate message and skip the turn.
      • If the character has the Noh Mask equipped or has Confused status, select a random action for confusion (logic omitted here for brevity).
      • If the character is a Goof-off, take a random number; if less than 65, select a random Goof-off action (logic omitted here for brevity).
      • Resolve the character's action.
    • If an enemy:
      • If in the first turn of a preemptive attack, skip the turn.
      • If the party attempted and failed to run, take a multi-random number; if at least 192 (1/4 chance), skip the turn.
      • If no action was chosen, skip the turn.
      • If the Chance time-stop effect is active, skip the turn.
      • If the enemy's AI type is 2:
        • If the action count selector is 1 or 2, take a multi-random number in the range [0,2], replace it with 2 if it is 0, and use that as the number of actions.
        • For each action, choose an action as described above, then evaluate the action as described below; treat the enemy's AI type as 1 for this purpose.
      • Otherwise (the enemy's AI type is not 2), evaluate the action:
        • If asleep, take a multi-random number and compare against the chance of waking for the number of sleep turns remaining (255, 192, 128, 64). If less than or equal to that chance, wake up the enemy; in any case, print an appropriate message and skip the action.
        • If the enemy is confused or the battle is non-interactive, choose a new target (and possibly change the action):
          • If the action is any of:
            • 0 (assess the situation)
            • 1 (parry)
            • 4-6 (attack + status effect)
            • 7 (flee)
            • 8, 59-63 (call for help)
            • 16-18 (status effect breath)
            • 40 (Chaos)
            • 43 (freezing wave)
            • 44 (Bounce)
            • 47 (Vivify)
            • 48 (Revive) then change it to 2 (normal attack) and continue with the next step.
          • If the action is a single-target action (2, 3, 9, 19-22, 31, 33, 36, 39, 42, 49, 50, 51, 54, 55, 56):
            • If the action targets the party:
              • If there is at least one enemy group other than the attacker's group living enemies, randomly choose one such group, then randomly choose the target from that enemy group.
              • Otherwise, if there are living enemies other than the attacker, choose a random enemy from the attacker's group.
              • Otherwise, leave the original target alone.
            • Otherwise, if the battle is an interactive battle, choose a random living party member as the target.
            • Otherwise (the battle is a non-interactive battle), choose the attacker as the target.
          • Otherwise (the action is a multi-target action):
            • If the action targets the party:
              • If there is at least one enemy group other than the attacker's group living enemies, randomly choose one such group as the target.
              • Otherwise, if there are living enemies other than the attacker, choose the attacker's group as the target.
              • Otherwise, leave the original target alone.
            • Otherwise, if the battle is an interactive battle, select the party member with the same index as the index of the originally targeted enemy group.
            • Otherwise (the battle is a non-interactive battle), select the attacker's group as the target.
        • Resolve the action.
        • If the enemy is not asleep and has a second action:
          • Evaluate the second action.
          • If the enemy has a third action, evaluate the third action.
  • At the end of each turn:

    • For each enemy with a nonzero HP regeneration selector, add a multi-random number in the associated range to the enemy's HP, capping at the enemy type's base HP (not the initial HP selected when battle started).
  • At the end of battle:

    • Multiply base experience by 1/3, rounding any fraction up, and add that amount to the base experience to get the total experience earned from the battle. Note that the code truncates the addend to the low 16 bits, so (in theory) if you killed 5 or more Metal Babbles in one battle, you would lose out on 65536 experience.
    • Divide the total experience by the number of living party members, rounding any fraction up, and add it to each living party member.
    • If the enemy can drop an item, then:
      • If the drop rate index is 0, the item is always dropped.
      • Otherwise, if the drop rate index is not 0, calculate the drop rate as 1 << (6 - index). The item is dropped if a random number is less than the drop rate.
      • Otherwise, take a random number. If it is 0, then the item is dropped if a second random number is less than 32.
    • Multiply base gold by 1/5, rounding any fraction up, and add that value to the base gold to get the total gold for the battle.
    • Add the total gold to the party's gold.
    • If the low 2 bits of a random number are 0 and a living merchant is in the party, add 1/8 of the total gold + 1 to the party's gold. (Note that the random number check is performed whether or not a merchant is in the party.)
    • Check each party member for leveling up. If a level up occurs:
      • Check the current value of each stat (in the order Strength, Agility, Vitality, Luck, Intelligence, maximum HP, maximum MP) against the upper limit for the character's class and current level. If the current value is greater than (or equal to, for maximum HP and MP) the limit, take the low bit of a random number as the level-up bonus. Otherwise:
        • For base stats, take a random-16 number, multiply it by the base increment for the class and current level, and divide by 110; if the result is zero, take the low bit of a random number instead.
        • For maximum HP and MP, take the bonus applied to Vitality or Intelligence respectively (but zero if the "low bit of a random number" method was used). If that value is zero, the HP or MP bonus is also zero; otherwise, double the value, add a multi-random number in the range [-2,+2], and use the result as the bonus.
      • For each spell learnable by the character's class:
        • Check whether the spell is learned this level (this check is done whether or not the spell is already known):
          • If the spell's learn chance type (¤) is 1 and the character's new level is at least the spell's level, take a random number and learn the spell if bit 3 ($08) of the number is set.
          • If the spell's learn chance type is 0 and the character's new level is in the range [L,L+2] (where L is the spell level), compare the character's new Intelligence against the Intelligence target for the character's old level and obtain the learn chance:
            • If current < target - 15, the chance is $00 at level L, $80 at level L+1, and $FF at level L+2.
            • Else if current < target + 10, the chance is $80 at level L and $FF at level L+1 and L+2.
            • Else the chance is $FF at all levels. If the chance is not zero, take a random number; if the number is less than or equal to the chance, the spell is learned.
        • If the spell is learned this level, iterate over the class's spell list; if the character's spell bits corresponding to the spell ID are 0, set them to 1 and display the "learned a new spell" message. (If any bit is 1, the check stops immediately, which is why the Luisa registration bug -- a 1-byte overrun on a new character's inventory which overwrites the hero's first spell byte when the 12th stored character is created -- prevents the hero from learning Heal on the field spell list.)
        • Note that the Goof-off's possible random actions are handled as a set of 36 "spells", each of which is learned with chance type 1 and a base level equal to the ability index. The "learned a new spell" message is suppressed for Goof-offs.

(¤) Each spell has, in addition to its base learning level, a bit indicating which of two algorithms is used to check whether the spell has been learned. The following spells are type 1: Firebal (7), Infermost (36), Beat (22), Sacrifice (41), Limbo (20), RobMagic (15), SpeedUP (5), Awake (16), Surround (7), Chaos (27), Transform (37), BeDragon (34), Sap (8), Upper (4), Increase (9), Bounce (24), Bikill (21), Chance (40) All others are type 0.

Other general notes:

  • When the battle system needs to choose a random target, it takes a multi-random number and divides that by (255 / number of possible targets). If the result is out of range, the last possible target is chosen.

  • The standard formula for computing base damage for a physical attack is as follows:

    • Compute the attacker's attack power minus half of the target's defense power.
    • If at least 2, multiply by a random-32 number bounded to [102,153] and divided by 4, then divide by 64 to get the base damage value. (This results in an average damage of attack/2 - defense/4, varying by up to 20% on either side of the average.)
    • Otherwise, use the low bit of a multi-random number as the base damage value.
  • When a party member attacks an enemy group:

    • If the attacker has the Armor of Hades equipped, take a random number and abort the attack ("Can't move because of a curse!") if the number is at least 170 (slightly more than 1/3 chance).
    • If the attacker has the Sword of Destruction equipped, take a random number and abort the attack if the number is at least 192 (1/4 chance).
    • If the attacker has the Zombie Slasher equipped, perform a resistance check against holy damage for the targeted enemy. (There is not an explicit list of zombie enemies in the code; instead, the Zombie Slasher deals extra damage to any enemy if the enemy fails a resistance check against the damage.)
    • For each non-dead enemy in the selected enemy group:
      • Compute the base damage using the standard formula. Then take a multi-random number in the range 0 through 255 minus the attacker's intelligence, divide by 4, multiply that by the base damage, and add the high byte of the product to the base damage to get the final damage.
    • If any enemies took damage greater than their current HP, choose the enemy with the greatest remaining HP as the target (the enemy with a greater index wins ties). Otherwise, choose the enemy which would have the greatest remaining floor(HP/4) after the attack as the target (again, the greater index wins ties).
    • If the attacker is in Surround status, take a multi-random number; the attack misses if the number is less than 160.
    • If the attacker has the Demon Axe equipped, take a multi-random number; the attack misses if the number is less than 32.
    • If the attacker is in Bikill status, double the damage value.
    • If the attacker does not have the Poison Needle equipped:
      • Check for evasion:
        • If on the first turn of a preemptive attack, if the target is asleep, or if the target's evade rate is zero, the attack always hits.
        • Otherwise, take a multi-random number; the attack misses if the number is less than the evade rate.
      • Check for a critical hit:
        • If the attacker is a Fighter, take a multi-random number and make the attack a critical hit if the number is less than the attacker's level.
        • Otherwise, if the attacker has the Sword of Destruction or the Demon Axe equipped, the critical rate is 32, otherwise 4. Take a multi-random number and make the attack a critical hit if the number is less than the critical rate.
    • If the attack is a critical hit, recompute the damage by taking a multi-random number in the range [54,64], multiplying that value by the attacker's attack power, dividing by 64, and adding 1.
    • If the weapon does bonus damage to the enemy (Zombie Slasher or Dragon Killer against relevant enemies), take a multi-random number and add that number plus 16 to the damage.
    • If the weapon is the Poison Needle, take a multi-random number; if less than 32 and the encounter is not a preset encounter, the enemy is killed, otherwise damage is set to 1. (Note that this implies the Poison Needle can never do 0 damage -- it will always either hit for 1 damage or kill the enemy.)
    • If the enemy is parrying, halve the damage value and add 1.
    • Apply the damage (reduce the target's HP).
    • If the Multi-Edge Sword is equipped, apply 1/4 damage + 1 to the attacker.
  • When an enemy attacks a party member:

    • Compute the base damage using the standard formula, except that if the value of (attack power - half of defense power) satisfies all of the following conditions:
      • less than 256,
      • no greater than 1/8 of the attacker's attack power, and
      • greater than 2, then instead compute base damage as a multi-random number in the range 0 through (attack power - half of defense power - 1).
    • If the target has the Cloak of Evasion equipped, take a random number; if the number is at least 204 (~20% chance), the attack misses.
    • Take a multi-random number; if less than 4, the attack misses.
    • If the attacker is in Surround status, take a multi-random number; the attack misses if the number is less than 160.
    • If the target has the Shield of Sorrow equipped (and does not have the Cloak of Evasion or Swordedge Armor equipped), reduce the damage by half.
    • If the attacker is in Bikill status, double the damage value.
    • Apply the damage (reduce the target's HP).
    • If the party member dies, numb and poison statuses are cleared (confuse and bounce are not cleared).
    • Otherwise, if the target has the Swordedge Armor equipped, apply half the final damage plus 1 to the enemy.
    • Otherwise, if the target has the Shield of Sorrow equipped (and does not have the Cloak of Evasion equipped), choose a random living party member other than the target and apply the same damage to that character.
    • Apply any special effect of the attack (sleep, confusion, numbness) if the target is still alive.
  • When an enemy attacks another enemy:

    • Compute the base damage as if the enemy was attacking the party member whose index is the low 2 bits of the target enemy's index.
    • Discard that value and recompute damage using the same algorithm as for attacking a party member, but using the target enemy's defense power instead.
    • Handle Surround and Bikill status as when attacking a party member.
    • If the target is parrying, halve the damage value and add 1.
    • Apply damage to the target.
  • When checking a party member's resistance to a status effect which has a chance to miss:

    • Let the effect's base hit rate (out of 256) be H.
    • The chance C (out of 256) that the effect hits the party member is C = (((384 - luck) / 2) ¤ H) / 128. (Conceptually, the base hit rate is multiplied by a factor from 0.5 to 1.5 based on the target character's Luck stat.)
    • If C >= 256, the effect hits.
    • Otherwise, take a multi-random number; if it is less than C, the effect hits. Base hit rates for standard effects are:
    • Beat: 32 (16 if Angel's Robe equipped)
    • Chaos: 64
    • Limbo: 50
    • Numb: 32
    • Poison: 96
    • Sap/Defence: 190
    • Sleep: 96
    • Slow: 192
    • StopSpell: 96
    • Surround: 160
    • Vivify: 128
  • When checking an enemy's resistance to a damage spell or status effect, if the enemy is partially resistant (resistance index 1 or 2), the enemy resists the damage or effect if a multi-random number is less than the resistance chance (77 or ~30% for index 1, 179 or ~70% for index 2).

  • When any character uses a damage spell on an enemy, damage for each target is computed as a multi-random number in the appropriate range:

    • Blaze: [8,14)
    • Blazemore: [70,90)
    • Blazemost: [160,200)
    • Firebal: [16,24)
    • Firebane: [30,42)
    • Firevolt: [88,112)
    • Bang: [16,24)
    • Boom: [52,68)
    • Explodet: [120,160)
    • IceBolt: [25,35)
    • Snowblast: [42,58)
    • Snowstorm: [88,112)
    • IceSpears: [60,80)
    • Infernos: [8,24)
    • Infermore: [25,55)
    • Infermost: [60,120)
    • Zap: [70,90)
    • Lightning: [175,225) Spell resistance is checked after damage is computed.
  • When an enemy uses a damage spell or breath attack on a party member, damage for each target is computed as a multi-random number in the appropriate range:

    • Blaze: [7,12)
    • Blazemore: [52,62)
    • Blazemost: [92,128)
    • IceBolt: [16,24)
    • Firebal: [10,18)
    • Firebane: [22,34)
    • Explodet: [60,80)
    • Snowblast: [32,42)
    • Snowstorm: [55,67)
    • Infernos: [6,18)
    • Infermore: [14,34)
    • Infermost: [30,62)
    • Flaming breath (weak): [6,10)
    • Flaming breath (medium): [30,40)
    • Flaming breath (strong): [80,100)
    • Blizzard breath (weak): [9,21)
    • Blizzard breath (medium): [40,60)
    • Blizzard breath (strong): [100,140) Damage is reduce to 1/4 + 1 if the attack is a spell and the target has the Armor of Hades equipped, or to 2/3 if any of the below apply:
    • the target has the Armor of Radiance or Water Flying Cloth equipped
    • the attack is a breath attack and:
      • the target has Barrier status, or
      • the target has the Shield of Heroes equipped
    • the attack is a fire breath attack and the target has the Dragon Mail equipped
    • the attack is a spell attack and the target has the Magic Armor or Sacred Robe equipped
  • When an enemy uses a breath attack on another enemy, damage for each target is computed as a multi-random number in the appropriate range:

    • Flaming breath (weak): [4,10)
    • Flaming breath (medium): [10,40)
    • Flaming breath (strong): [20,100)
    • Blizzard breath (weak): [12,21)
    • Blizzard breath (medium): [20,60)
    • Blizzard breath (strong): [40,140) The large damage ranges here appear to be due to a bug: the code that reads the base and range values ($8DDF in bank 4) gets them backwards, storing the base damage in the zero-page address used to hold the random range and vice versa.
  • When a party member uses a Medical Herb:

    • Take a multi-random number in the range [35,50).
    • If the target is not Zoma, heal the target by that amount.
    • Otherwise, take a multi-random number in the range [0,219), add 229, and damage Zoma by the low 8 bits of that amount. (This is a bug in the code to assign damage from healing spells, which is also used for herbs: it doesn't check for the herb effect code ($41), so it reads past the end of the damage range table and takes bytes from the following routine's code as the damage range.)
  • When a party member casts Beat on an enemy, if the enemy makes a successful resistance check against death, a second check is performed. This has the effect of squaring (and thus lowering) the enemy's resistance rate -- 30% death resistance becomes 9% resistance to Beat, and 70% death resistance becomes 49% resistance to Beat. Defeat does not make this second check, so the normal death resistance rate applies.

  • When a party member or enemy casts Beat or Defeat on a party member, if the party member has the Sacred Amulet equipped, the effect always misses (thus skipping the resistance check).

  • When using the Wizard's Ring:

    • Add a multi-random number in the range [10,25) to the user's MP.
    • Take a random number; if it is less than 25 (~10% chance), the ring breaks.
  • When using the Chance spell:

    • If the encounter is not a preset encounter, take a multi-random number in the range [0,16] and execute that effect.
    • Otherwise (the encounter is a preset encounter), take the low 4 bits of a multi-random number; if that value is at least 12, execute effect 1, otherwise execute effect 16. The possible effects are:
    • 0: Party sleep / enemy flee ("something unbelievably frightening")
    • 1: Healusall
    • 2: Revive on all dead party members if any, else Healusall
    • 3: Heal all party members for [41,48] HP
    • 4: Confuse all party members and enemies
    • 5: Kill all enemies ("<enemy> shatters into pieces")
    • 6: Kill all enemies (same as effect 5)
    • 7: Nullify spells ("fierce darkness")
    • 8: All party attacks become criticals ("additional Attack Power")
    • 9: Put all party members and enemies to sleep
    • 10: Caster gets 3 free turns ("time ceases")
    • 11: Randomize party formation ("party changes its formation")
    • 12: Enemies "depart"
    • 13: Party gets a free turn ("the foe is taken off guard")
    • 14: Steal all MP from all enemies
    • 15: Cure numbness on all party members
    • 16: No effect ("Chance...chance...chance...")
  • Enemy data can be found at address $B2D3 in bank 0 (also entry $6A in the BRK #$07 array). The data is a list of 23-byte entries, one per enemy; corresponding enemy names are stored as two sequences of strings in bank 2, the first line's text at $B3BC and the second line's text at $B8BF. The data structure is as follows:

    • Byte 0, bits 7-6: bits 4-3 of evade rate
    • Byte 0, bits 5-0: level
    • Bytes 1-2: experience
    • Byte 3: speed
    • Byte 4: gold (low 8 bits)
    • Byte 5: attack power (low 8 bits)
    • Byte 6: defense power (low 8 bits)
    • Byte 7: base HP (low 8 bits)
    • Byte 8: MP
    • Byte 9, bit 7: bit 2 of evade rate (evade rate is a multiple of 4)
    • Byte 9, bits 6-0: item dropped ($7F = none)
    • Bytes 10-17, bits 5-0: possible actions
    • Bytes 10-11, bit 7: AI type selector (byte 11 has the high bit)
      • Type 0: random target choice
      • Type 1: smart target choice
      • Type 2: smart target choice, and action choice is delayed until enemy acts
    • Bytes 12-13, bit 7: action chance selector
      • Type 0: equal chance for all 8 actions
      • Type 1: $12, $16, $1A, $1E, $22, $26, $2A, $2E (/256)
      • Type 2: $02, $04, $06, $08, $0A, $0C, $0E, $C8 (/256)
      • Type 3: fixed action sequence
    • Bytes 14-15, bit 7: number of actions per turn
      • For AI type 2: 1, 1-2, 1-2, 2
      • For other AI types: 1, 1-2, 2, 1-3
    • Bytes 16-17, bit 7: HP regeneration selector
      • Type 0: no regeneration
      • Type 1: 16-23 HP/turn
      • Type 2: 44-55 HP/turn
      • Type 3: 90-109 HP/turn
    • Byte 18, bits 7-6: fire damage resistance
    • Byte 18, bits 5-4: ice damage resistance
    • Byte 18, bits 3-2: wind damage resistance
    • Byte 18, bits 1-0: gold (high 2 bits)
    • Byte 19, bits 7-6: lightning damage resistance
    • Byte 19, bits 5-4: instant death resistance
    • Byte 19, bits 3-2: Sacrifice resistance
    • Byte 19, bits 1-0: attack power (high 2 bits)
    • Byte 20, bits 7-6: Sleep resistance
    • Byte 20, bits 5-4: StopSpell resistance
    • Byte 20, bits 3-2: Sap resistance
    • Byte 20, bits 1-0: defense power (high 2 bits)
    • Byte 21, bits 7-6: Surround resistance
    • Byte 21, bits 5-4: RobMagic resistance
    • Byte 21, bits 3-2: Chaos resistance
    • Byte 21, bits 1-0: base HP (high 2 bits)
    • Byte 22, bits 7-6: Slow / Limbo resistance
    • Byte 22, bits 5-4: Expel / Fairy Water / Zombie Slasher resistance
    • Byte 22, bit 3: focus-fire flag (if set, the enemy continually attacks the same character until they die)
    • Byte 22, bits 2-0: item drop chance (100%, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256, 1/2048)

Why battle outcomes can differ even with the same RNG seed

Battles use the same RNG as the rest of the game, but with a twist: Many routines that use randomness discard a certain number of random numbers from the RNG sequence before obtaining the actual random number to be used. The "certain number" is stored at address $6A68, which is updated with a random number in the range [0,15] during each turn of battle, after all characters' commands have been entered. (Attempting to run does not change this value.)

The catch is that $6A68 is not cleared or initialized on reset. Since that address is within the backup RAM area, the value will be retained across reset and power-off. This is why, for example, battle outcomes when battling Metal Slimes near Dhama in a speedrun will differ even when resetting the RNG with a fixed seed from a separate file on each reset. $6A68 is not even cleared when initializing backup RAM on a fresh cart, so the value in that case is unpredictable (unless running on an emulator, in which case the emulator normally clears backup RAM to zero when the game is first run).

Details of the random number generator (and how to break it)

Dragon Warrior III has a very nearly high-quality random number generator for the NES era, using a 16-bit LFSR (linear feedback shift register) with a period of 65534 and adding the low 8 bits of the LFSR to an 8-bit counter to produce the final 8-bit random number. This gives an RNG sequence length of LCM(65534,256) = 8,388,352 values.

The random number generator algorithm is: for (16 iterations) { seed = LFSR(seed, 1) } counter += 1; return (byte)(seed + counter); where LFSR(seed, input) = (seed << 1) ^ ((seed>>15 ^ input) ? $1021 : 0)

I say "very nearly" high-quality because there are two initial seeds which give an LFSR sequence length of just 2: $5FEA and $AFF5. This property itself is unavoidable in any LFSR equation, but the problem is that the game does not attempt to avoid these seeds. Since the RNG algorithm clocks the LFSR 16 times per random number, this effectively means that if the seed is ever set to one of these two values, the RNG will simply return the counter plus a constant value (either $EA or $F5).

Now, since those two seeds are not part of the 65534-long sequence, it should normally be impossible to encounter them in the first place. But it turns out there's one place in the code which calls the LFSR shift function with non-constant input bits: the save file checksum routine. The game calculates a 16-bit checksum for each save file (787 bytes of data) by setting an initial seed of $3A3A, passing each bit of the data as the input bit to the LFSR function, and taking the resulting LFSR value as the checksum (the routine can be found at $ACD0 in bank 7). The game doesn't check whether that checksum is one of the two values above that generate a short sequence, so if we can generate a save file with one of those values as the checksum, we can make the RNG misbehave.

When creating a new file, we directly control a total of 10 bytes: - The hero's name (8 bytes at offset $114) - The hero's sex (bit 3 of the byte at offset $48) - The message speed (1 byte at offset $311) We also indirectly control the names and status of the autogenerated party characters; the names are picked randomly from a pool of 4 names for each character, and each stat randomly (50% chance) has 1 added to the base value. The save file is checksummed after generating each of the four initial characters; naturally, we need the seed after the final checksum to be one of the two short-sequence values.

There are many name/sex/speed combinations which produce the desired result in a new save file, which can then be used to reset the RNG seed on the initial menu screen by changing the message speed for the file (to the same speed it's already set at). Some short names include: File: 1 Name: pP Sex: F Speed: 7 Checksum: $5FEA File: 1 Name: iGa Sex: M Speed: 7 Checksum: $5FEA File: 2 Name: am Sex: F Speed: 6 Checksum: $AFF5 File: 2 Name: E? Sex: F Speed: 5 Checksum: $5FEA File: 2 Name: DOg Sex: M Speed: 7 Checksum: $5FEA File: 2 Name: Ndt Sex: M Speed: 6 Checksum: $AFF5 File: 3 Name: -a Sex: M Speed: 6 Checksum: $AFF5 File: 3 Name: Qg Sex: M Speed: 7 Checksum: $5FEA File: 3 Name: mop Sex: F Speed: 7 Checksum: $5FEA

For reference, the mapping from text characters to name data is: a...z = $0B...$24 A...Z = $25...$3E Space = $50 ' = $68 , = $6A - = $6B . = $6C ( = $6D ) = $6E ? = $6F ! = $70 Names are padded with $00 to a length of 8 bytes.

One caveat with "breaking" the RNG in this fashion is that it can cause the game to lock up when starting an encounter. This happens due to poor design of the core "ranged random number" function ($C3F1) combined with the lack of a safety valve in one part of the encounter generation logic. Specifically, in the "Choose a random number from 0 through 2" item described earlier (at $8573 in bank 0), if the encounter has two groups and the next RNG output on entry to that step is 1 or 2 (mod 4), the following will occur:

  • The low 2 bits of the first random number returned are nonzero, so the loop is not terminated.
  • The next ranged random number call (to get a group index) will see a random number outside the requested range of 0 through 1, so it will consume random numbers until it reaches one whose low 2 bits are zero, giving group index 0.
  • At the next iteration, the low 2 bits of the first random number will equal 1, so the loop is not terminated.
  • The next ranged random call will get group index 0, and so on ad infinitum. The code places no limit on the number of loop iterations, so if the loop enters this state, the game will halt.

An interesting consequence of using one of these two seeds is that physical attacks in battles which use the standard damage formula will always give either the maximum ($5FEA) or minimum ($AFF5) possible damage, and level-up bonuses for the five basic stats will be consistently low ($5FEA) or high ($AFF5).

Random numbers in other NES Dragon Warrior games

The LFSR function used in Dragon Warrior III's random number generator dates all the way back to the original Dragon Warrior. There, the function was used only to compute save file checksums (see $FC2A); the RNG was seeded from the 16-bit checksum and subsequently updated with the formula seed <- seed ¤ 3 + 129 (see $C55B), taking the low byte of the seed as the RNG output. This function produces two independent sequences of 32768 values depending on the starting seed, and does not have any "holes" with extremely short sequences. The RNG seed is stored at $94.

Dragon Warrior II was the first game in the series to use the LFSR for random number generation as well. It uses essentially the same logic as DW3, except that it does not keep a counter to add to the seed when generating RNG output, so if the seed falls into one of the holes, the RNG's output will become constant. The RNG seed is stored at $32.

Dragon Warrior IV uses the same RNG as DW3. The RNG seed is stored at $12, and the output counter is stored at $050D.