Correlated randomness in Slay the Spire 2

rdmuser1 pts0 comments

Correlated randomness in Slay the Spire 2 - Andy Tockman

portfolio

music

conlangs

video games

board games

puzzles

square dancing

food

Correlated randomness in Slay the Spire 2<br>2026-06-13<br>very long (6880 words) gamesvideo gamesslay the spiremath

Here are three true statements about the game of Slay the Spire 2<br>(in single player):

If you pick Neow's Bones in the Underdocks,<br>the random curse is ~54% likely to be Debt.*

It is impossible to receive Rebound from the Trash Heap event.

Your first fight is 76% likely to drop a potion in Underdocks,<br>and 4% likely to drop a potion in Overgrowth.**

(* assuming neither of the relics from Neow's Bones is New Leaf or Kaleidoscope)

(** assuming your Neow relic doesn't give cards or other relics)

(*** all on the current beta patch, v0.107.0)

What?!

Why? The culprit is unexpected correlation between different random number generators --<br>knowing the first output of one of the game's RNGs<br>gives information that helps predict the first output of all of the others.

The random number generators of Slay the Spire 2

For now,<br>I will give an extremely simplified explanation of this correlation.<br>If you want more details,<br>I will go into much greater depth at the end of this post.<br>If you don't care,<br>you can skip this section to see all the funny examples below.

The phenomenon of "correlated RNG" (or "CRNG")<br>is already known in the Slay the Spire community,<br>because Slay the Spire 1 had a similar issue,<br>described in detail in Forgotten Arbiter's blog post.[1]

Briefly,<br>in Spire 1,<br>the game used several distinct pseudorandom number generators,<br>to prevent e.g. randomness within a combat<br>from influencing future card rewards.<br>However,<br>they were all initialized to the same starting state,<br>which meant they produced the same sequence of numbers.<br>A crafty player could therefore pay attention to the results of past random events<br>and gain information about future random events.

In an attempt to avoid the same problem,<br>Spire 2 initializes its pseudorandom number generators<br>to different states.<br>The code looks something like this<br>(highly simplified for didactic purposes):

Rng UpFront = new Rng(seed + hash("up_front"));<br>Rng Shuffle = new Rng(seed + hash("shuffle"));<br>Rng UnknownMapPoint = new Rng(seed + hash("unknown_map_point"));<br>Rng CombatCardGeneration = new Rng(seed + hash("combat_card_generation"));<br>Rng CombatPotionGeneration = new Rng(seed + hash("combat_potion_generation"));<br>Rng CombatCardSelection = new Rng(seed + hash("combat_card_selection"));<br>Rng CombatEnergyCosts = new Rng(seed + hash("combat_energy_costs"));<br>Rng CombatTargets = new Rng(seed + hash("combat_targets"));<br>Rng MonsterAi = new Rng(seed + hash("monster_ai"));<br>Rng Niche = new Rng(seed + hash("niche"));<br>Rng CombatOrbGeneration = new Rng(seed + hash("combat_orbs"));<br>Rng TreasureRoomRelics = new Rng(seed + hash("treasure_room_relics"));<br>// ...

There are many more random number generators in the game<br>that I have not listed for brevity;<br>notably,<br>every event has its own RNG.

The hash function essentially produces a "random-looking" number<br>from the input string,<br>but the number is always the same for the same input.<br>So the idea is that the RNG states are shuffled around,<br>but the same seed still always results in the same run.

The problem comes when these seeds are passed<br>to the stock System.Random class in C#.<br>Unfortunately,<br>the pseudorandom number generation algorithm<br>used in C#<br>is almost entirely "linear" in the starting seed.

What this means exactly is a bit complicated --<br>again, I will go into greater detail later in this post.<br>But the consequence is that two RNGs<br>whose seeds differ by a known fixed amount<br>have their outputs differ by a fuzzier but still-exploitable amount.

How exploitable,<br>you might ask?<br>Well...

Here is a big pile of consequences of CRNG,<br>ranging from amusing-but-unimportant<br>to legitimately impactful on gameplay<br>(some of them even to casual players unaware of it!).

Neow's Bones

I'll start with the first example from the intro.<br>If you pick Neow's Bones in Underdocks,<br>the "random" curse you receive<br>actually has the following approximate distribution:

Clumsy<br>0.10%

Debt<br>54.25%

Decay<br>40.32%

Doubt<br>1.50%

Guilty<br>0%

Injury<br>0%

Normality<br>0%

Regret<br>0%

Shame<br>0%

Writhe<br>3.82%

However,<br>in Overgrowth,<br>you instead get a curse from this distribution:

Clumsy<br>0.51%

Debt<br>0%

Decay<br>0%

Doubt<br>0%

Guilty<br>0.19%

Injury<br>5.53%

Normality<br>1.18%

Regret<br>0%

Shame<br>18.85%

Writhe<br>73.74%

This one is quite funny to me --<br>people all over Reddit and Discord have been lamenting their terrible luck<br>that they keep rolling Debt from Neow's Bones.[2]<br>Even before discovering CRNG,<br>I saw some of these posts insisting it seemed more frequent than random.<br>It is hard to express how instantaneously my brain automatically dismissed them<br>as textbook confirmation bias.<br>And yet...

To understand this one,<br>we need to correlate three sources of randomness:

The "curse relic" available from Neow<br>comes from a call to Neow's...

seed hash random from spire neow

Related Articles