Correlated randomness in Slay the Spire 2
Slay the Spire 2 is plagued by correlated randomness, a subtle bug rooted in C#'s System.Random where distinct game events aren't truly independent. This flaw leads to surprisingly predictable outcomes for things like Neow's Bones curses, potion drops, and card rewards, affecting gameplay even for unaware players. The post provides a deep technical dive into this programming oversight, offering a simple fix and sparking fascinating discussion on PRNG design.
The Lowdown
The popular roguelike deckbuilder, Slay the Spire 2, has a significant and unexpected issue: its 'random' events are often highly correlated. This isn't due to malicious design, but a subtle problem with how the game initializes its many pseudorandom number generators (PRNGs) using C#'s System.Random class, specifically the linearity of its seed processing.
Here are some key findings from the investigation:
- Neow's Bones Curse Bias: When choosing Neow's Bones in the Underdocks, players are ~54% likely to get 'Debt', significantly higher than expected, due to a correlation between the Act 1 variant, Neow's options, and the curse RNG.
- Relic Rarity Skew: The 'Large Capsule' relic never offers common relics and biases heavily towards rare options, especially in Underdocks (where it's also extremely rare to appear).
- Impossible Cards: The 'Trash Heap' event in Underdocks can never offer the 'Rebound' card, a fact confirmed by players unable to complete their Compendium.
- Predictable Potion Drops: The first fight has a 76% chance of dropping a potion in Underdocks, but only a 4% chance in Overgrowth, influenced by the initial Neow options.
- Targeted Lightning Orbs: Early game lightning orbs for the Defect character are 75% likely to hit the leftmost enemy in the first fight of Underdocks, a critical piece of information for combat strategy.
- Distant Correlations: Even Act 2 events like the 'Doll Room' and Act 3 Ancient options (Pael, Tezcatara) can be predicted with surprising accuracy based on early-game RNG outcomes like Neow's relics or the gold dropped in the first combat.
The underlying cause is that although Slay the Spire 2 attempts to use different seeds for its various RNGs (seed + hash("something")), the System.Random implementation in C# makes each SeedArray entry and subsequent output linearly dependent on the absolute value of the initial seed. This means that if seeds differ by a fixed amount, their outputs will also differ in a predictable, linear fashion, leading to widespread correlations. The author stresses that this is a bug that should be fixed, noting its greater impact on unaware players compared to similar issues in Slay the Spire 1, and proposes a simple solution by replacing System.Random with a non-linear PRNG like PCG32.
Ultimately, this deep dive reveals how a seemingly minor detail in PRNG implementation can have far-reaching and unintended consequences, profoundly shaping the 'random' experience of a complex strategy game.
The Gossip
Programming PRNG Perils
Commenters delved into the broader implications of implementing pseudo-random number generators (PRNGs), particularly the challenge of backward compatibility. The discussion highlighted Hyrum's Law, where any observable behavior, once released, becomes part of the API, making changes difficult. This explains why older, less-than-ideal PRNGs like GNU libc's `rand()` persist. Many argued that standard library PRNGs shouldn't be seedable, or at least should default to external entropy, to allow for future improvements without breaking user-relied sequences, suggesting that users needing specific sequences should explicitly choose their PRNG engine.
Strategic Seeds and Save Scumming
The community explored the developers' rationale behind using multiple, seedable RNGs in Slay the Spire. The primary goal is to ensure "seed fairness," allowing players to share game seeds that result in identical runs, crucial for competitive play or community challenges. This design also serves to mitigate "savescumming," where players might replay events to manipulate RNG outcomes to their advantage. The game's unique save mechanism, which advances RNGs upon loading, further underscores the intent to maintain consistent run integrity and prevent easy exploitation of random events.