I read a cool article on Slay the Spire 2’s random number generator.
The game has multiple generators in order to isolate aspects of the game otherwise you get RNG manipulation like step counting in Stardew Valley. So their system looks like
\[ \begin{aligned} Y^{1} &\sim \operatorname{RNG}(\text{seed} + \operatorname{hash}(\text{“purpose1”})) \\ Y^{2} &\sim \operatorname{RNG}(\text{seed} + \operatorname{hash}(\text{“purpose2”})) \\ Y^{3} &\sim \operatorname{RNG}(\text{seed} + \operatorname{hash}(\text{“purpose3”})) \end{aligned} \]
All well and good except for the RNG function that they use is roughly linear with the following recurrence relationship:
\[ Y_{n} = (Y_{n-24} - Y_{n-55}) \bmod M \]
and the initialization which is a little complicated but is linear in
the seed. This introduces correlations across draws because the
initial seeds are linearly related to each other (hash("purpose2") -
hash("purpose1")) and with linear operations the resulting outputs
then remain linearly related!
The blogger could figure this out both through large scale simulations
and also, you know, looking into the code after the fact. What I found
the most interesting is how narrow this bug was. Like if they used a
different generator this wouldn’t have happened. If they seeded the
RNG like RNG(hash(seed + "purpose")) it wouldn’t happen (though you
would still have “local” correlation). Just fascinating!