> 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.
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.
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)
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.
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...
I'm so happy most modern language stdlibs decided having a guaranteed order for maps and other collections is a good idea.
Although I can definitely respect Go's decision to always iterate over maps in a random order.
> 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...
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)
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.
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.)
It also gives you the option of serialising the RNG states directly instead of using the counter hack.
i wonder how the StS developer could get this far without "ownership" of the behavior that this blog author called "linearity." in these kinds of games you simulate bajillions of random games and check metrics after every player action, including whether or not you observe sufficient randomness in shuffles and stuff, as a matter of debugging something this complicated.
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.