Top
Best
New

Posted by rdmuser 8 hours ago

Correlated randomness in Slay the Spire 2(tck.mn)
219 points | 65 comments
account42 4 hours ago|
> Implementing a PRNG within the codebase instead of calling the C# standard library has an additional advantage: seeds are guaranteed to be the same on all platforms. In Spire 1, seeds on the desktop version of the game were different from seeds on the mobile version of the game, because the standard library implementation of PRNG differed between platforms. It is also worth mentioning that the standard library implementation might change over time, which would break all past seeds.

This is the correct conclusion - game developers should consider gameplay-relevant random generators part of their gameplay code rather than platform code.

fc417fc802 4 hours ago||
More than just that, procgen as a whole requires an entirely different level of vigilance to avoid nondeterminism creeping in if the game requires it to be reproducible. None of the inputs to the procgen algorithm can be allowed to even so much as brush up against code you aren't actively exerting complete control over, and care is required to avoid inadvertently encountering any platform specific hardware quirks.
rcxdude 4 hours ago|||
It can also be a good idea to split the RNG into a tree for different areas (i.e. seed multiple RNGs from the main one), so that adjusting the generation for one aspect doesn't shift around everything else in a seed (especially in something like Minecraft where different parts of the world might be generated on different versions of the game).

(Note this is roughly what slay the spire did, but if they were to use a 'master' RNG output as the source of these sub-seeds then these correlations would also not be a problem. With a custom implementation they could save the RNG state directly as opposed to hacking around calling the RNG X times on loading a save)

maxbond 4 hours ago|||
What's burned me before is iterating over hash maps. B-tree maps (or hash maps that are guaranteed to iterate in insertion order, or any fixed order) are your friend.
connicpu 3 hours ago||
I've been burned by this outside games as well. Computation heavy process, in production it writes the inputs into the output as well for reproducibility, but some of them used unordered maps so it would slightly differ when you loaded it back up. Long term solution was to stop using unordered maps in general, but short term we had to make sure the inputs got sorted before being inserted so they would have the same insertion order every time...
bilekas 4 hours ago|||
> It is also worth mentioning that the standard library implementation might change over time, which would break all past seeds.

If the stdLib changes and you need to use the same, then you're unfortunately going to be suck with porting the previous version into your own library. It's pretty forward thinking from the devs here, I would love to see my boss' face if I told him we need time to port some of the stdLib incase they update it in the future.

I had to check for my own curiosity, but it looks like the Random class has not been updated in 12 or so years. At least in the inital subset of framework to core.

https://github.com/microsoft/referencesource/commits/main/ms...

swiftcoder 3 hours ago|||
Be glad you work on top of a relatively standardised platform! The C standard doesn't specify any details of the implementation backing rand(), so a bunch of platforms have wildly different implementations, and they change over time (FreeBSD swapped theirs out in 202, for example)
mellinoe 2 hours ago||||
IIRC in the modern .NET runtime, System.Random should come from here, which is updated somewhat regularly: https://github.com/dotnet/runtime/commits/main/src/libraries.... Although, whether any of these is a behavioral breaking change isn't immediately clear; most are just API additions.
Smaug123 3 hours ago|||
For what it’s worth, Claude did this without even being asked when I had it implement /dev/urandom in my deterministic dotnet runtime. (Fun fact: if the runtime only ever receives zero bytes from /dev/urandom then it will hang on attempting to initialise System.Random! That was the first way I asked for it to be implemented.)
godwinson__4-8 3 hours ago||
Sometimes it is useful to deal with a platform where such things are not even available, never mind platform dependent. Then see how quickly your code breaks.

Standard library invocations - including random number generation - often break entirely when targeting wasm freestanding for instance, as in that case there is really very little "platform" to speak of.

jcalx 3 hours ago||
Combining this article and discovery of an unwinnable seed in the original Slay the Spire [0] — I've always pondered the existence of some kind of "RNG hell", where a game uses the time as its random seed and, due to some quirk of the hashing function and the game mechanics, the game is rendered completely unwinnable for (say) four days straight. (Sometimes it feels like I'm in it!)

[0] https://oohbleh.github.io/losing-seed/

kami23 2 hours ago||
I haven't had time to read the whole article, but I really appreciate the cross section of the world that reads HackerNews and plays STS2. STS1 and STS2 are my favorite games and to see this pop up here brought a big smile on my face. Thanks for sharing.
iliveinberlin 4 hours ago||
This is also the cause of the thing in Minecraft where you find surface clay, move X blocks over, and dig straight down into diamonds.
stdc105 5 hours ago||
Interestingly, StS2 got this problem because it was using C# System.Random in Godot, while the RNG class in GDScript (Godot Engine's own scripting language) is using PCG32 which should be free of this particular problem.
darepublic 2 hours ago||
> (By the way, floor 2 Corpse Slugs will both be attacking on turn 1 less than 3% of the time. How nice of them!)

I assumed that was just deterministic. Didn't realize the game permitted such a challenge on floor 2 :(

FromTheFirstIn 5 hours ago||
This is such a great article- I’ve had so many runs where it’s felt like “why am I always getting this random card?” And now I’ll know! Thank you!
cevn 3 hours ago||
I wonder if this can explain something happening to me. If I select "random" at character select, I had a run of 30 or 40 where I never received the Silent. Defect seem to come up more often than it should, and Ironclad less often.
jszymborski 4 hours ago||
> The phenomenon of "correlated RNG" (or "CRNG")

This is a pretty funny abbreviation since CRNG is sometimes "cryptographic random number generator", which would not be susceptible to this correlation. Albeit I think CSRNG is more common.

Lalabadie 4 hours ago|
The criteria for calling a RNG "cryptographically secure" are incompatible with the game design goals here.

The game needs a RNG that's stable when seeded, for reproducible runs. I look for the same kind of qualities when doing generative art.

In comparison, a CSPRNG should be safe from oracle attacks, which is essentially the opposite goal.

gpm 3 hours ago|||
CSPRNGs are absolutely seedable deterministic functions that will result in entirety reprodible runs.

The only difference is that if you don't know the seed it is computationally difficult to predict the next value given the previous ones. But that's not something any game dev is ever going to want to do (or waste time trying to do)

HansHamster 3 hours ago|||
I would expect all RNG algorithms to be deterministic and stable with their seed, but the cryptographically secure ones to have some additional properties like making it unfeasible to reverse the seed from the output, having a very long period or strong guarantees on the distribution of the output. It's just that using a 'secure' algorithm is often overkill for a game when you don't really need those extra guarantees.
antitoi 1 hour ago|
I don't understand the motivation for using multiple RNGs in the first place. If the game had one global, seed-able source of randomness, would this problem just disappear?
cptroot 1 hour ago||
The motivation boils down to trying to make runs with the same starting seed feel "similar" in meaningful ways. It's "better" in a vibes way if both you and I were offered the same card choices in runs with the same seed, even if we took different amounts of turns on the first battle.
kibwen 56 minutes ago||
The main reason to allow users to set seeds manually is to allow players to share seeds among themselves. And the reason that players want to share seeds is because players will find exceptionally rare seeds that other players might want to try out. This sort of exceptional rarity might take the form "the first shop in act 1 sells a relic that gives you 2 potion slots, then the act 2 ancient offers a relic that gives you 4 potion slots, then the act 3 ancient offers a relic that fills all your potions slots at the start of every combat". However, when players share seeds, they aren't sharing the exact series of inputs they performed in that game. This means that the relics that have been "randomly" offered like the ones above need to be offered on the same seed regardless of any prior player decision. And player decisions can generally cause the RNG to advance an arbitrary number of times. So this means that you want to have entirely separate RNGs for every thing that the player has any power to influence, because this makes the randomness of a single seed more usefully reproducible in practice.
More comments...