Hello everyone, so a while ago I came across the following question from Volk asking how the probabilities of the different sleep rolls are distributed:
At the time I expected the probabilities would be roughly even, maybe with 1 sleep roll slightly more likely than the others. But after looking through the code, that is not the case at all, it is far more likely that you will get a 1 turn sleep (25% chance) than any of the other sleep rolls (all other rolls occur 12.5% of the time).
This is the case because of how the game determines the number of turns a Pokémon will sleep for. When the game determines how long it should put a Pokémon to sleep for, it generates a random number (well technically for link battles it generates them 10 at a time and then iterates through the ones it generated, but it doesn't matter for this) and checks if it is a multiple of 8; if it is zero then it generates a new random number and performs the same check, looping until it finds a random number which is not a multiple of 8. It then sets the number of turns that a Pokémon is sleeping for equal to that random number modulo 8.
In all fairness to the programmers, this approach would actually give a random number from 1 to 7 if the RNG formula was any good, however for link battles the RNG formula used is very bad. In order to generate a new random value for link battles, the game will multiply the value of the current random number by 5 and add 1 (all done modulo 256 since this is 8 bit arithmetic). But this means that when it attempts to put a Pokémon to sleep, if the random number it gets modulo 8 is zero, then the next random value generated modulo 8 will always be 1.
This affects the distribution of sleep turns heavily in link battles. If the initial random number used when determining if a Pokémon should be put to sleep is uniformly distributed between 0 and 255, then modulo 8 it is equally likely to get any value from 0 to 7 with a 1/8 chance for each. But since it re-rolls the random number if the initial one modulo 8 is zero, which always returns a random value that is 1 greater than a multiple of 8, then the chance of putting a Pokémon to sleep for only 1 turn is actually 1/4, while all other values remain at 1/8.
In pseudocode it looks like this:
This currently is not how Pokémon Showdown determines whether or not to put someone to sleep. Currently, Showdown generates a random number from 1 to 7 from a uniform distribution when it is determining how long a Pokémon should be put to sleep; which is not how it should work. In reality it should work as I described above; putting a Pokémon to sleep should last for a single turn 1/4 of the time, while all other values happen only 1/8th of the time. I have submitted a pull request which fixes this, which hopefully will be implemented.
Implications:
This affects the metagame in a number of ways. To list a few of the ways I would expect this affects the metagame:
If implemented, this would probably be the most significant change to the simulator's mechanics since 2015. I would like to hear what ideas other people have for how this would affect the metagame if this gets put into the simulators. Hopefully you've enjoyed this deep-dive into RBY's mechanics, cheers.
Addendum (GSC Sleep Mechanics):
After looking into how GSC sleep works (credit to Isa for asking me this), I found that because the RNG used for link-battles operates identically to how it operates in RBY and because how it rolls for sleep works almost identically (with a caveat I will address in a moment), a similar thing happens in GSC as well. The one difference between how GSC and RBY roll for sleep durations is that GSC re-rolls the RNG when the random value modulo 8 is either 0 or 7, while in RBY it only re-rolls when the random value is 0 modulo 8. The result of this difference is that in GSC both a 1-turn sleep and a 4-turn sleep have a 1/4 chance of occurring each, all other rolls still have a 1/8 chance of occurring, as is the case in RBY.
Code relevant for this discovery (credit to PokeRed, PokeYellow, and PokeCrystal decompilations):
Code used for handling when a Pokemon is put to sleep:
Pokemon Red: https://github.com/pret/pokered/blob/master/engine/battle/effects.asm#L26
Pokemon Yellow: https://github.com/pret/pokeyellow/blob/master/engine/battle/effects.asm#L26
Pokemon Crystal: https://github.com/pret/pokecrystal/blob/master/engine/battle/effect_commands.asm#L3615
Code used for generating RNG for battles:
Pokemon Red: https://github.com/pret/pokered/blob/master/engine/battle/core.asm#L6668
Pokemon Yellow: https://github.com/pret/pokeyellow/blob/master/engine/battle/core.asm#L6851
Pokemon Crystal: https://github.com/pret/pokecrystal/blob/master/engine/battle/core.asm#L6880
At the time I expected the probabilities would be roughly even, maybe with 1 sleep roll slightly more likely than the others. But after looking through the code, that is not the case at all, it is far more likely that you will get a 1 turn sleep (25% chance) than any of the other sleep rolls (all other rolls occur 12.5% of the time).
This is the case because of how the game determines the number of turns a Pokémon will sleep for. When the game determines how long it should put a Pokémon to sleep for, it generates a random number (well technically for link battles it generates them 10 at a time and then iterates through the ones it generated, but it doesn't matter for this) and checks if it is a multiple of 8; if it is zero then it generates a new random number and performs the same check, looping until it finds a random number which is not a multiple of 8. It then sets the number of turns that a Pokémon is sleeping for equal to that random number modulo 8.
In all fairness to the programmers, this approach would actually give a random number from 1 to 7 if the RNG formula was any good, however for link battles the RNG formula used is very bad. In order to generate a new random value for link battles, the game will multiply the value of the current random number by 5 and add 1 (all done modulo 256 since this is 8 bit arithmetic). But this means that when it attempts to put a Pokémon to sleep, if the random number it gets modulo 8 is zero, then the next random value generated modulo 8 will always be 1.
This affects the distribution of sleep turns heavily in link battles. If the initial random number used when determining if a Pokémon should be put to sleep is uniformly distributed between 0 and 255, then modulo 8 it is equally likely to get any value from 0 to 7 with a 1/8 chance for each. But since it re-rolls the random number if the initial one modulo 8 is zero, which always returns a random value that is 1 greater than a multiple of 8, then the chance of putting a Pokémon to sleep for only 1 turn is actually 1/4, while all other values remain at 1/8.
In pseudocode it looks like this:
C-like:
unsigned char rng_value; // initially a uniformly distributed random 8-bit value
...
unsigned char get_sleep_turns() {
unsigned char sleep_turns = rng_value & 7; // same as mod 8
while (sleep_turns == 0) {
rng_value = (rng_value * 5 + 1) & 255; // same as mod 256, 8 bit arithmetic is always done mod 256, I'm just including the & 255 for clarity
sleep_turns = rng_value & 7; // same as mod 8
}
return sleep_turns;
}
Implications:
This affects the metagame in a number of ways. To list a few of the ways I would expect this affects the metagame:
- It is now much riskier to immediately switch to Tauros as soon as you put an opponent to sleep, since it's now much likelier they wake up the turn it comes in.
- Jynx's matchup improves against lead Gengar since if Gengar puts Jynx to sleep for 1 turn, Jynx is more likely to instantly wake up, while if Jynx puts Gengar to sleep then since it's slower, it still gets a chance to land a Psychic.
- Lead Starmie, Alakazam, and Jolteon, are a bit better, since if they get put to sleep they are more likely to wake up early now. This matters especially for Jolteon, since an early wakeup lets it help stave off Zapdos better. I am unsure how much this buffs lead Starmie or Alakazam though.
If implemented, this would probably be the most significant change to the simulator's mechanics since 2015. I would like to hear what ideas other people have for how this would affect the metagame if this gets put into the simulators. Hopefully you've enjoyed this deep-dive into RBY's mechanics, cheers.
Addendum (GSC Sleep Mechanics):
After looking into how GSC sleep works (credit to Isa for asking me this), I found that because the RNG used for link-battles operates identically to how it operates in RBY and because how it rolls for sleep works almost identically (with a caveat I will address in a moment), a similar thing happens in GSC as well. The one difference between how GSC and RBY roll for sleep durations is that GSC re-rolls the RNG when the random value modulo 8 is either 0 or 7, while in RBY it only re-rolls when the random value is 0 modulo 8. The result of this difference is that in GSC both a 1-turn sleep and a 4-turn sleep have a 1/4 chance of occurring each, all other rolls still have a 1/8 chance of occurring, as is the case in RBY.
Code relevant for this discovery (credit to PokeRed, PokeYellow, and PokeCrystal decompilations):
Code used for handling when a Pokemon is put to sleep:
Pokemon Red: https://github.com/pret/pokered/blob/master/engine/battle/effects.asm#L26
Pokemon Yellow: https://github.com/pret/pokeyellow/blob/master/engine/battle/effects.asm#L26
Pokemon Crystal: https://github.com/pret/pokecrystal/blob/master/engine/battle/effect_commands.asm#L3615
Code used for generating RNG for battles:
Pokemon Red: https://github.com/pret/pokered/blob/master/engine/battle/core.asm#L6668
Pokemon Yellow: https://github.com/pret/pokeyellow/blob/master/engine/battle/core.asm#L6851
Pokemon Crystal: https://github.com/pret/pokecrystal/blob/master/engine/battle/core.asm#L6880
Last edited: