Documentation: PRNG Quirks

From Noita Wiki
Jump to navigation Jump to search

While Noita random number generation is very high quality compared to some more egregious offenders like Minecraft, there are still a few subtle quirks that can cause inconsistencies or unusual behavior.

Concepts

Every function in Noita's pseudorandom number generation (PRNG) are essentially composed of calls to two key functions: SetRandomSeed(x, y) and Next(). The first function uses the world seed and two numeric inputs to set the state of the generator. The second number updates the state using a relatively simple function, then outputs the state divided by a certain factor to downscale it to the range [0, 1]. Other functions, like Random(a, b), ProceduralRandom(x, y), and all other functions in the Noita random API internally call one or both of these functions, and merely apply some transformations to the outputs.

As world seed is fixed, we can think of the total output of the random number generator as a 3D cube of fixed values: two axes for each possible input to SetRandomSeed(x, y), and the third axis representing each successive call to Next(). By calling SetRandomSeed(x, y), we move the state of the generator to (x, y, 0), and by calling Next() we move the generator from (x, y, z) to (x, y, z + 1) and then return the number in (x, y, z + 1). Remember that this cube is only a theoretical representation for the sake of conceptualization and does not actually exist in the code. Furthermore, the inputs to SetRandomSeed(x, y) do not necessarily represent spatial coordinates, with some functions using the frame number or a separate internal state for seeding.

RNG Overlap

Overlap refers to the situation where two distinct parts of code use the same cell for different things. Normally, this doesn't happen, since each call to Next() moves the state forward a cell on the Z axis. However, if SetRandomSeed(x, y) is called again on the same coordinates, the Z position of the state is returned to 0, and subsequent calls to Next() will return the same sequence of values as before, since the values in the cube never change.

Prior to the Mar. 11 2023 update, chests spawned in Extra Max HP spawn locations checked if the value in (x, y, 0), where (x, y) is the position of the spawn location in the world, was less than 0.7 and greater than 0.3, and spawned a chest if so. Otherwise, an Extra Max HP or nothing was spawned in the location. Then, (x, y, 1) and (x, y, 2) were used for checks related to spawning mimics. When the chest is opened, it calls SetRandomSeed(x, y) again and returns the state of the random number generator to (x, y, 0). Then, this same number, which must be between 0.3 and 0.7 for a chest to have spawned in the first place, is used for the first roll of the loot table. In this case, almost half of the chest loot table, like bomb spawns which require (x, y, 0) < 0.07, or heart mimics, which require 0.94 < (x, y, 0) < 0.95, could never spawn, since the value at (x, y, 0) could not have such a value.

RNG overlap requires SetRandomSeed(x, y) to be used twice with the same coordinates, and the function is rarely ever called twice in the code, and there are offsets applied in the few cases where it is. As a result, RNG overlap is quite rare. Currently, it is only known to affect Great Treasure Chest Sampo odds in heart spawn locations, in the same way that normal chests were affected prior.

Correlation

Correlation refers to a lack of independence between consecutive calls in a random number generator. A perfect random number generator would pick a completely random value every time, but in practice, low-quality PRNGs like the one used in Next() can have subtle correlation. In practice, this means that subsequent values skew very, very slightly towards previous values. In practice, this only happens in extreme cases, where the value of a cell is within a few ten thousandths of 0 or 1. In these cases, like in the Great Treasure Chest Sampo spawns, which require a value >0.99999, the next value is skewed heavily towards one end of the range. In the previous example, we see in practice that the next value is never lower than ~0.82, although values remain almost evenly distributed in the range [0.82, 1]. This results in the intended 1/1000 chance for an Orb of True Knowledge to replace the Sampo (which requires 0.998 < (x, y, 1) < 0.999) is actually much closer to 1 in 180, more than 5 times higher than the developers intended.

Since this correlation only manifests at the extremes of the random number generator, it is almost never seen in practice. There are very few single events ingame that have a probability of less than the ~1 in 20000 that is required for correlation to start to be significant. Orb chances in Great Treasure chests are the only known influence. In addition, SetRandomSeed(x, y) uses a far more robust algorithm for generating states, so there is no correlation whatsoever along the X or Y axes of the random state cube, or with world seed. Only the Z axis is affected.

Miscellaneous Info

  • While the world seed for every run is fixed, your NG+ number is added to the world seed for the purposes of PRNG seeding. For instance, with world seed 1234 and NG+5, the PRNG uses world seed 1239. This is necessary to produce different generation in every NG+ loop.
  • While world seeds are nominally 32 bit, the most significant bit of the world seed is truncated by the PRNG, and seeds >2147483647 aren't generated naturally ingame. Such seeds can only be reached by seed changing tools, although they aren't particularly interesting, as each seed is identical in every way to itself - 2148483648, or the same seed in binary with the most significant bit chopped off.