Auto-translated
I sometimes read threads about offline Tournaments on HP, mostly to improve my knowledge of the game mechanics; the guys occasionally find some very non-obvious things.
Disconnects are not uncommon in online play, and sometimes upon repeating a turn transfer, the computer moves differently than it did initially. Here is a compilation of posts from a discussion among offline players on this matter:
handbookhmm.ru/1-moral-luck-ppb
Although the article was written for Heroes 1, as far as I know, the PRNG in 2 differs only by re-initialization at the start of battle depending on the armies, and in 3, dependency on in-battle animation was also added; otherwise, it's the same. As can be guessed from the posts above, I admit there might be other differences, but...
R is a pseudo-random number used for the needs of algorithms responsible for "random" events in the game (including the roll for morale and luck).
The current value of R is a four-byte value (double word, DWORD) stored in RAM at the moment the check begins.
This number generates a sequence of pseudo-random numbers according to a known algorithm, so knowing the current (or initial) value of R is enough to calculate any other.
During the check itself, a new pseudo-random number is generated, which is then used to determine the result of the check. Schematically, it looks like this: R[0] or Seed -> R[1] -> R[2] -> ... The Seed value initializes the PRNG (it may depend, for example, on the current time; it is generated during game launch, which is why restarting is so useful in many cases). Every game event requiring a die roll takes the value remaining from the previous generation as the Seed, but uses a new one obtained from the previous one via a special algorithm. An exception* is the level-up, where the previous random number is the value of a function whose arguments are the hero's level and the number of their skill tree.
The level increases identically on all machines, so if a save is loaded immediately after gaining a level, there should be no differences. However, if the player enters a town and opens the fort window or looks at the stats of some unit in their army, the value of R will change (in the creature animation cycle).
Some other events do not use the previous random value as a "starter." For example, the Seed for each battle is calculated separately and is not a random number, as the splitting of neutrals into stacks, the appearance of a upgraded stack, and the generation of obstacles on the battlefield are also not random (they depend on the coordinates of the tile where the battle takes place). By the way, this is exactly why a timer/stopwatch can be used to "catch" morale and luck.
Whether this function uses the global PRNG can be checked, for example, as follows. Before each load of the save considered in this thread, first load another, pre-prepared save (dummy), where some hero takes an experience chest and increases their level. Now load the actual tournament save and transfer the turn. Record the result and repeat the described procedure several times. Between loading the dummy save and transferring the turn in the tournament save, do not exit the game, do not open the fort window or the unit information window, and do not change the game volume settings. Now try loading some other dummy save, dummy1, instead of the dummy save, in which a different hero increases their level. Sooner or later, you should find such a dummyN save after loading which the computer hero will always attack, and anyone can repeat this on any machine.
And how many different unique numbers for the seed can be generated via level-up?
Good question. We need to calculate how many different values the following function can take:
R(Level, TreeNo) = (343FDh * Level + 26497h * TreeNo + 259DFh) * 343FDh + 269EC3h
Moreover, overflow is possible here, so it is necessary to add and 0FFFFFFFFh at the end.
The order is not difficult to determine. Since TreeNo, as I understand from the description, is fixed for each specific hero, the function can take only 75 values (this is how many times a hero's level can be correctly increased, according to FizMiG).
75 must be multiplied by 255, since on the "left" save, absolutely any hero can be chosen to gain a level. Total: 75 * 255 = 19125.
Added after 6 minutes
I see a difficulty for online play here: when loading a save, a new random number is generated at least once — when choosing the monster whose animation will accompany the load. I don't know if this disrupts the PRNG settings and I don't know if it is regenerated during the animation process (like with units on the map).
Also, one could ask Baratorch to implement the seed selection for the computer's turn to be independent of R, as it is for the level-up. For example, link it to the game day number; then repeating the turn (with exact army preservation) would result in a deterministic computer move.
Disconnects are not uncommon in online play, and sometimes upon repeating a turn transfer, the computer moves differently than it did initially. Here is a compilation of posts from a discussion among offline players on this matter:
handbookhmm.ru/1-moral-luck-ppb
Although the article was written for Heroes 1, as far as I know, the PRNG in 2 differs only by re-initialization at the start of battle depending on the armies, and in 3, dependency on in-battle animation was also added; otherwise, it's the same. As can be guessed from the posts above, I admit there might be other differences, but...
R is a pseudo-random number used for the needs of algorithms responsible for "random" events in the game (including the roll for morale and luck).
The current value of R is a four-byte value (double word, DWORD) stored in RAM at the moment the check begins.
This number generates a sequence of pseudo-random numbers according to a known algorithm, so knowing the current (or initial) value of R is enough to calculate any other.
During the check itself, a new pseudo-random number is generated, which is then used to determine the result of the check. Schematically, it looks like this: R[0] or Seed -> R[1] -> R[2] -> ... The Seed value initializes the PRNG (it may depend, for example, on the current time; it is generated during game launch, which is why restarting is so useful in many cases). Every game event requiring a die roll takes the value remaining from the previous generation as the Seed, but uses a new one obtained from the previous one via a special algorithm. An exception* is the level-up, where the previous random number is the value of a function whose arguments are the hero's level and the number of their skill tree.
The level increases identically on all machines, so if a save is loaded immediately after gaining a level, there should be no differences. However, if the player enters a town and opens the fort window or looks at the stats of some unit in their army, the value of R will change (in the creature animation cycle).
Some other events do not use the previous random value as a "starter." For example, the Seed for each battle is calculated separately and is not a random number, as the splitting of neutrals into stacks, the appearance of a upgraded stack, and the generation of obstacles on the battlefield are also not random (they depend on the coordinates of the tile where the battle takes place). By the way, this is exactly why a timer/stopwatch can be used to "catch" morale and luck.
Whether this function uses the global PRNG can be checked, for example, as follows. Before each load of the save considered in this thread, first load another, pre-prepared save (dummy), where some hero takes an experience chest and increases their level. Now load the actual tournament save and transfer the turn. Record the result and repeat the described procedure several times. Between loading the dummy save and transferring the turn in the tournament save, do not exit the game, do not open the fort window or the unit information window, and do not change the game volume settings. Now try loading some other dummy save, dummy1, instead of the dummy save, in which a different hero increases their level. Sooner or later, you should find such a dummyN save after loading which the computer hero will always attack, and anyone can repeat this on any machine.
And how many different unique numbers for the seed can be generated via level-up?
Good question. We need to calculate how many different values the following function can take:
R(Level, TreeNo) = (343FDh * Level + 26497h * TreeNo + 259DFh) * 343FDh + 269EC3h
Moreover, overflow is possible here, so it is necessary to add and 0FFFFFFFFh at the end.
The order is not difficult to determine. Since TreeNo, as I understand from the description, is fixed for each specific hero, the function can take only 75 values (this is how many times a hero's level can be correctly increased, according to FizMiG).
75 must be multiplied by 255, since on the "left" save, absolutely any hero can be chosen to gain a level. Total: 75 * 255 = 19125.
Added after 6 minutes
I see a difficulty for online play here: when loading a save, a new random number is generated at least once — when choosing the monster whose animation will accompany the load. I don't know if this disrupts the PRNG settings and I don't know if it is regenerated during the animation process (like with units on the map).
Also, one could ask Baratorch to implement the seed selection for the computer's turn to be independent of R, as it is for the level-up. For example, link it to the game day number; then repeating the turn (with exact army preservation) would result in a deterministic computer move.