Evidence & methods
A ledger, not a leaderboard
Every number on this site is either exact arithmetic over a rule listed below, a clearly labelled heuristic, or a measured simulator result with its sample size and interval. Where nothing has been measured, this page says “not yet evaluated”. Last audited rules apply to Slay the Spire 2 main v0.107.1.
Rule register
45 rules, each with its evidence
29
Verified against the game's own logic or official statements
2
Corroborated by independent secondary sources
1
Single secondary source or unverified
13
Recorded fact (build, implementation note)
Evidence classes: E1 inspection of the game’s own logic for v0.107.1 (read-only, local, nothing redistributed) · E2 official Mega Crit statements · E3/E4/E5 published wikis and calculators · E6 SLAY2’s own exact computation. A rule is “verified” only when its logic was read or officially stated, not merely repeated by a wiki.
Run structure
| Rule | What the game does | Evidence |
|---|---|---|
| RUN-1 | A run has exactly 3 acts. Act index 0 = Overgrowth *or* Underdocks, 1 = Hive, 2 = Glory. DeprecatedAct has index -1 and is never used. "Act 1-4" in third-party simulators counts four act *models*, not four acts. | E1 Models.Acts/{Overgrowth,Underdocks,Hive,Glory,DeprecatedAct}.cs (Index), Models/ActModel.cs GetRandomList, Models/ModelDb.cs ActsByIndex; E3 VERIFIED-E1 |
| RUN-2 | After the last act's boss, the game enters the scripted TheArchitect event room; when it completes (IsVictoryRoom) WinRun() ends the run as a victory. The event contains no player-damaging logic, so *win = clear the final act (both bosses at A10)*. There is no Heart, no keys, no Act 4. | E1 Runs/RunManager.cs EnterNextAct/WinRun, Models.Events/TheArchitect.cs VERIFIED-E1 |
| RUN-3 | Underdocks (alternate Act 1) must be unlocked; in single player an undiscovered non-default act takes priority, then the game rolls between available acts. | E1 ActModel.GetRandomList; E3 /acts VERIFIED-E1 |
| RUN-4 | Double Boss (A10): the last act additionally gets a second, different boss chosen at run start from that act's other boss encounters (up-front RNG - hidden until shown). | E1 RunManager.cs (act setup loop); E5 VERIFIED-E1 |
| RUN-5 | Act map: 7 columns; 17/17/16/15 floors (Overgrowth/Underdocks/Hive/Glory) in solo, one fewer room in co-op; 3 merchants; 5 elites (8 with A1); first room is a fight; elites/rests never in first five rooms; a rest site always precedes the boss. | E3 /acts SECONDARY |
Ascension (cumulative: level N includes 1..N)
| Rule | What the game does | Evidence |
|---|---|---|
| ASC-1 | Enum: 0 None, 1 SwarmingElites, 2 WearyTraveler, 3 Poverty, 4 TightBelt, 5 AscendersBane, 6 Inflation, 7 Scarcity, 8 ToughEnemies, 9 DeadlyEnemies, 10 DoubleBoss. Max = 10 (maxAscensionAllowed). HasLevel(x) = level >= x. There is no A11-A20. | E1 Entities.Ascension/AscensionLevel.cs, AscensionManager.cs; E5; E3 VERIFIED-E1 |
| ASC-2 | A3 Poverty: encounter gold min/max = (int)(base * 0.75) (truncating): monster 7-15, elite 26-33, boss 75 (base 10-20 / 35-45 / 100). | E1 Models/EncounterModel.cs, Helpers/AscensionHelper.cs VERIFIED-E1 |
| ASC-3 | A4 Tight Belt: -1 potion slot. A5: Ascender's Bane added to the starting deck (floor 1). | E1 AscensionManager.ApplyEffectsTo VERIFIED-E1 |
| ASC-4 | A6 Inflation: card removal costs 100 + 50*n instead of 75 + 25*n (n = removals used). (Older "Gloom: fewer rest sites" text is stale.) | E1 Entities.Merchant/MerchantCardRemovalEntry.cs; E5 (changed v0.103.2) VERIFIED-E1 |
| ASC-5 | A7 Scarcity: see CARD-3..CARD-6 and UPG-1. | E1 Odds/CardRarityOdds.cs, Factories/CardFactory.cs VERIFIED-E1 |
| ASC-6 | A2 Weary Traveler (Ancients heal 80% of missing HP), A1 (+3 elites), A8/A9 (enemy HP / damage) | E5, E3 CORROBORATED |
Card reward rarity (the "rare offset")
| Rule | What the game does | Evidence |
|---|---|---|
| CARD-1 | Rarity roll with offset o: draw r ~ U[0,1); T_rare = baseRare + o (not clamped); rare if r < T_rare; uncommon if r < baseUncommon + T_rare; else common. Hence P(rare)=clamp(baseRare+o,0,1) and the uncommon/common boundary shifts with the offset (regular fight at the -5 start: 0 / 35 / 65 %, not 0 / 37 / 63 %). | E1 Odds/CardRarityOdds.cs RollWithoutChangingFutureOdds; E3 wording VERIFIED-E1 |
| CARD-2 | Base odds (rare / uncommon): regular 3% / 37%, elite 10% / 40%, shop 9% / 37%, boss 100% rare (offset ignored in the roll). | E1 CardRarityOdds.GetBaseOdds; E3; E4 VERIFIED-E1 |
| CARD-3 | A7 base rare: regular 1.49% (not 1.5), elite 5%, shop 4.5%; uncommon unchanged. | E1 CardRarityOdds.RegularRareOdds/EliteRareOdds/ShopRareOdds; E3 (E4 prose omits it) VERIFIED-E1 |
| CARD-4 | After each card of an Encounter-sourced reward whose odds type is Regular/Elite/Boss: rare -> offset = -0.05; non-rare -> offset = min(offset + growth, 0.40). Cards in a pack are rolled sequentially, so later cards see earlier results; cards after a rare in the *same* pack restart from -0.05. | E1 CardRarityOdds.Roll, Factories/CardFactory.cs RollForRarity VERIFIED-E1 |
| CARD-5 | Boss rewards also reset the counter (Roll(BossEncounter) returns Rare -> offset = -0.05) even though the roll ignores the offset. (sts.wiki only says "offset ignored".) | E1 CardRarityOdds.Roll VERIFIED-E1 |
| CARD-6 | Shop cards read the offset without advancing it (RollWithoutChangingFutureOdds(Shop)); non-encounter sources (events, relic packs such as Orrery) use RollWithBaseOdds: rare if r < baseRare, uncommon if r < baseUncommon (not cumulative) -> 3 / 34 / 63 % at A0 regular odds. Neither advances the counter. | E1 CardRarityOdds, CardFactory.cs; E3 ("3% / 34% / 63%") VERIFIED-E1 |
| CARD-7 | If no eligible card exists at the rolled rarity, the roll steps to the next-higher rarity with wrapping (GetNextAllowedRarity). A card is then drawn uniformly from the eligible cards of that rarity not already in the pack. | E1 CardFactory.cs VERIFIED-E1 |
| CARD-8 | Derived published checks that my model reproduces exactly: offset table (28 cells incl. A7), "1+ rare right after a rare": regular 0 %, elite 16.95 %; stationary run averages regular 19.72 % / elite 30.91 %. | E6 vs E3, E4 CORROBORATED |
| CARD-9 | Per-player odds sets (co-op: each seat has its own counters). | E1 Odds/PlayerOddsSet.cs VERIFIED-E1 |
Upgrades on reward cards
| Rule | What the game does | Evidence |
|---|---|---|
| UPG-1 | After each pack card: r ~ U[0,1); if the card is upgradable: odds = actIndex * scaling for non-rare cards (scaling = 0.25, A7 0.125), 0 for rares; upgrade if r <= odds. => Act 1: 0 %, Act 2: 25 % (12.5 % A7), Act 3: 50 % (25 % A7). Shop cards never upgrade (base -999999999). Relics/events may modify via hooks (not modelled). | E1 Factories/CardFactory.cs RollForUpgrade, UpgradedCardOddScaling; E3 VERIFIED-E1 |
Potions
| Rule | What the game does | Evidence |
|---|---|---|
| POT-1 | Stored chance c starts 0.40. After each eligible fight: threshold = c + (Elite ? 0.25*0.5 = 0.125 : 0); r ~ U[0,1); if forced | |
| POT-2 | Forced drops (Hook.ShouldForcePotionReward, e.g. White Beast Statue) also decrement c in v0.107.1. Beta v0.108.0 changed this ("no longer decreases your normal potion odds"). Pin per build. | E1; E2 beta v0.108.0 notes VERIFIED-E1 |
| POT-3 | Dropped potion rarity: r<=0.10 rare, r<=0.35 uncommon, else common => 10 / 25 / 65 %. | E1 Factories/PotionFactory.cs; E3, E4 VERIFIED-E1 |
| POT-4 | v0.107.1 fixed a bug where the follow-up potion odds could rise instead of fall after an elite potion reward. Data from builds before 0.107.1 is not comparable. | E2 (v0.107.1 notes) VERIFIED |
Unknown "?" rooms
| Rule | What the game does | Evidence |
|---|---|---|
| UNK-1 | Counters start at base: fight 0.10, elite -1 (disabled), treasure 0.02, shop 0.03. One roll r ~ U[0,1]; scan fight -> (elite) -> treasure -> shop accumulating only positive counters; the first with r <= cumulative wins; otherwise event (1 - sum of positive counters). | E1 Odds/UnknownMapPointOdds.cs Roll; E4 VERIFIED-E1 |
| UNK-2 | After each roll the rolled type resets to its base; every other enabled type gains +base (hook-modifiable). Counters reset to base between acts (ResetToBase). The marginal event/fight/shop/treasure mix for the n-th "?" of an act: 85/10/3/2, 71.13/19/5.91/3.96, 60.34/25.30/8.56/5.80, 53.47/28.27/10.80/7.46, 49.94/28.62/12.56/8.88, 48.35/27.83/13.80/10.03 %. | E1; E6 vs E4 table VERIFIED-E1 |
| UNK-3 | A profile's very first run is scripted: first two "?" rooms are events, the third is a monster fight. | E1 (UnlockState.NumberOfRuns == 0) VERIFIED-E1 |
| UNK-4 | Not modelled: the room-type *blacklist* supplied by callers and relic hooks (ModifyUnknownMapPointRoomTypes, ModifyOddsIncreaseForUnrolledRoomType). | E1 |
Merchant
| Rule | What the game does | Evidence |
|---|---|---|
| SHOP-1 | Stock: 5 character cards of types [Attack, Attack, Skill, Skill, Power] (rarity rolled per slot via shop odds, fallback by card type), one uniformly random character slot on sale; 2 colorless cards (one Uncommon, one Rare); 3 relics (two rarity-rolled 50/33/17 % common/uncommon/rare, one "Shop"-rarity); 3 potions; one card-removal service. | E1 Entities.Merchant/MerchantInventory.cs, RelicFactory.RollRarity; E3 VERIFIED-E1 |
| SHOP-2 | Card price = round(base * U[0.95,1.05]); base common 50 / uncommon 75 / rare 150; colorless base x1.15 rounded (86 / 173); on sale: integer /2. | E1 MerchantCardEntry.cs VERIFIED-E1 |
| SHOP-3 | Relic price = round(MerchantCost * U[0.85,1.15]); MerchantCost: common 175, uncommon 225, rare 275, shop 200. Potion price = round(base * U[0.95,1.05]), base common 50 / uncommon 75 / rare 100. | E1 Models/RelicModel.cs, MerchantRelicEntry.cs, MerchantPotionEntry.cs VERIFIED-E1 |
| SHOP-4 | Card removal cost = Base + Increase * removalsUsed; A0: 75 + 25n; A6+: 100 + 50n. | E1 MerchantCardRemovalEntry.cs VERIFIED-E1 |
| SHOP-5 | Final price may be modified by relic hooks (ModifyMerchantPrice); not modelled. | E1 MerchantEntry.Cost |
Gold
| Rule | What the game does | Evidence |
|---|---|---|
| GOLD-1 | Combat gold = uniform integer in [min, max]: monster 10-20, elite 35-45, boss 100 (Poverty: see ASC-2); scaled by the room's GoldProportion in special encounters (e.g. escaped Gremlins). | E1 Rewards/GoldReward.cs, EncounterModel.cs, RewardsSet.cs VERIFIED-E1 |
Draw mathematics (no game source needed)
| Rule | What the game does | Evidence |
|---|---|---|
| DRAW-1 | Drawing n cards without replacement from N with K targets: P(>=1)=1-C(N-K,n)/C(N,n); full PMF is hypergeometric. Valid only for a uniformly shuffled draw pile with no reshuffle during the draw; retain/ethereal/exhaust/innate/discard/orbs/shuffle-triggers invalidate it (the UI must say so). | E6 (pure math) |
Build/patch facts
| Rule | What the game does | Evidence |
|---|---|---|
| BLD-1 | Main v0.107.1 (2026-06-18 "Major Update #2"), Steam build 23811903. Beta v0.111.0 (2026-08-13), build id 24724944 (2026-08-14 update). Newest announcement at retrieval: Neowsletter 2026-09-25 ("can't give a specific timeline ... for the next patch"). | E2, E4, E1 GAME_VERSIONS.md |
| BLD-2 | v0.107.1 replaced the PRNG (xoshiro256): seeds from earlier builds do not reproduce; seeds must never be compared across builds. | E2 |
| BLD-3 | Beta v0.108-v0.111 changed several card rarities (e.g. Echoing Slash, Bloodletting, Cruelty, Dominate, Accelerant, Flanking, Ignition) and White Beast Statue's potion interaction. Pools and odds are therefore build-specific. | E2 |
| BLD-4 | Act-3 boss Doormaker was removed and replaced by Aeonglass (main v0.107.1). | E2 |
Implementation status (engines, tests) - updated 2026-10-25
| Rule | What the game does | Evidence |
|---|---|---|
| UPG-1 | upgrade.ts | G P |
| DRAW-1 | draw.ts, combinatorics.ts | G B Q |
| POT-5 | Potion rolls happen after normal fights, elites and bosses; elites add +12.5 points to the roll only. The final boss gives no gold, cards, potion or relic. | E1 Rewards/RewardsSet.cs; E3 sts.wiki card-rewards page |
| CARD-10 | Relics that change card packs (Prayer Wheel: second regular pack after fights; White Star: all-rare pack after elites; Lasting Candy; Driftwood: reroll; Pael's Wing: sacrifice; Orrery: five packs from base odds; Dingy Rug/Prismatic Gem: pool changes; Molten Egg/Silver Crucible/Lava Lamp: upgrades) are not modelled; the advisor must say "relic X changes this" instead of silently using base rules. | E3 sts.wiki |
| SHOP-6 | Merchant relics from treasure rolls are common 50 % / uncommon 33 % / rare 17 %; elite relics use the same split. | E3, E4 |
How the exact odds are checked
Four independent layers
Independent implementation
A separate Python oracle (exact fractions) generated 720 golden cases across 14 kinds of question; the TypeScript engine must match every one with exact equality.
Published tables
The pity-offset table (28 cells, A0 and A7), base odds, after-a-rare chances, run averages, the “?” table, shop price ranges, card removal and gold ranges are asserted against what the wikis publish.
Brute force and properties
Hypergeometric draws are compared with explicit subset enumeration, reshuffles with permutations, and posteriors with enumeration of every outcome path. Property tests cover distributions that must sum to exactly one.
Mutation checks
Deliberately changing a rule constant (the A7 rate, the boss reset) makes the suite fail, which shows the tests can see the rules they claim to protect.
Known limit: the game compares 32-bit floats, SLAY2 uses ideal decimals; the difference is below 1e-6 and is stated on every result.
Simulator results · research only
What a third-party simulator says about simple policies
These runs use a community Python re-implementation of STS2, driven by SLAY2’s own harness (it classifies simulator errors and timeouts instead of counting them as losses). They are baselines for building better policies, not statements about real win rates. 14 simulated games have been won so far: rules_v2 7 of 1,996 completed games (held-out seeds; 7 of 7 wins re-run and replayed hash for hash); rules_v2 6 of 1,994 completed games (held-out seeds; 6 of 6 wins re-run and replayed hash for hash); rules_v2 1 of 1,987 completed games (held-out seeds; 1 of 1 wins re-run and replayed hash for hash); the intervals below show how uncertain each batch is.
| Policy | Cohort | Completed games | Wins | Win rate (95% Wilson) | Mean floor | Partition |
|---|---|---|---|---|---|---|
| random_legalladder-random_legal-ironclad-a0-first_run-train-n100 | Ironclad A0 · first-run (scripted) | 100 | 0 | 0.00% (0.00%–3.70%) | 4.9 | development (train partition): not a held-out result |
| random_legalladder-random_legal-ironclad-a0-veteran-train-n2000 | Ironclad A0 · veteran profile | 1999 | 0 | 0.00% (0.00%–0.19%) | 4.47 | development (train partition): not a held-out result |
| random_legal40 of 40 sampled games re-derivedrandom_legal-ironclad-a0-veteran-val-n2000 | Ironclad A0 · veteran profile | 2000 | 0 | 0.00% (0.00%–0.19%) | 4.48 | held-out |
| rules_v140 of 40 sampled games re-derivedrules_v1-defect-a0-veteran-val-n2000 | Defect A0 · veteran profile | 1999 | 0 | 0.00% (0.00%–0.19%) | 15.93 | held-out |
| rules_v1rules_v1-ironclad-a0-veteran-train-n300 | Ironclad A0 · veteran profile | 300 | 0 | 0.00% (0.00%–1.26%) | 18.29 | development (train partition): not a held-out result |
| rules_v140 of 40 sampled games re-derivedrules_v1-ironclad-a0-veteran-val-n2000 | Ironclad A0 · veteran profile | 1997 | 0 | 0.00% (0.00%–0.19%) | 18.08 | held-out |
| rules_v140 of 40 sampled games re-derivedrules_v1-necrobinder-a0-veteran-val-n2000 | Necrobinder A0 · veteran profile | 1997 | 0 | 0.00% (0.00%–0.19%) | 17.63 | held-out |
| rules_v140 of 40 sampled games re-derivedrules_v1-regent-a0-veteran-val-n2000 | Regent A0 · veteran profile | 1987 | 0 | 0.00% (0.00%–0.19%) | 13.54 | held-out |
| rules_v140 of 40 sampled games re-derivedrules_v1-silent-a0-veteran-val-n2000 | Silent A0 · veteran profile | 1999 | 0 | 0.00% (0.00%–0.19%) | 13.36 | held-out |
| rules_v2 (values=experiments/cardvalue/defect-a0-v1.json)1,999 of 2,000 games re-derived exactly40 of 40 sampled games re-derivedrules_v2-defect-a0-veteran-val-n2000 | Defect A0 · veteran profile | 1996 | 7 | 0.35% (0.17%–0.72%) | 21.3 | held-out |
| rules_v2rules_v2-ironclad-a0-veteran-dev-n500 | Ironclad A0 · veteran profile | 499 | 0 | 0.00% (0.00%–0.76%) | 21.12 | development (dev partition): not a held-out result |
| rules_v22,000 of 2,000 games re-derived exactly40 of 40 sampled games re-derivedrules_v2-ironclad-a0-veteran-val-n2000 | Ironclad A0 · veteran profile | 1994 | 6 | 0.30% (0.14%–0.65%) | 21.07 | held-out |
| rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json)1,997 of 2,000 games re-derived exactly40 of 40 sampled games re-derivedrules_v2-necrobinder-a0-veteran-val-n2000 | Necrobinder A0 · veteran profile | 1999 | 0 | 0.00% (0.00%–0.19%) | 19.63 | held-out |
| rules_v2 (values=experiments/cardvalue/regent-a0-v1.json)2,000 of 2,000 games re-derived exactly40 of 40 sampled games re-derivedrules_v2-regent-a0-veteran-val-n2000 | Regent A0 · veteran profile | 1987 | 1 | 0.05% (0.01%–0.28%) | 17.84 | held-out |
| rules_v2 (values=experiments/cardvalue/silent-a0-v1.json)1,998 of 2,000 games re-derived exactly40 of 40 sampled games re-derivedrules_v2-silent-a0-veteran-val-n2000 | Silent A0 · veteran profile | 2000 | 0 | 0.00% (0.00%–0.19%) | 14.34 | held-out |
| search_v1before isolation fixsearch_v1-ironclad-a0-veteran-train-n200 | Ironclad A0 · veteran profile | 200 | 0 | 0.00% (0.00%–1.88%) | 16.04 | development (train partition): not a held-out result |
| search_v3 (scope=boss)before isolation fixsearch_v3-boss-ironclad-a0-veteran-dev-n500 | Ironclad A0 · veteran profile | 500 | 0 | 0.00% (0.00%–0.76%) | 21.91 | development (dev partition): not a held-out result |
Paired comparisons
Two policies played the same seeds, so the difference is measured seed by seed. Only seeds where both reached a win or a death count; anything else is listed as excluded, never scored as a loss.
| A vs B | Paired games | Mean floor A · B | A − B (95% bootstrap) | Past the Act-1 boss A · B | Wins A · B | Partition |
|---|---|---|---|---|---|---|
| rules_v1 vs random_legalIronclad A0 · veteran · sha 0a44243426 | 1997 (+3 excluded) | 18.08 · 4.48 | +13.60 (+13.31 to +13.90) | 43.3% · 0.0%p = 8.1e-261 | 0 · 0 | held-out |
| rules_v1 vs search_v1before isolation fixIronclad A0 · veteran · sha fb860edec2 | 200 | 18.41 · 16.04 | +2.38 (+1.24 to +3.54) | 44.5% · 28.0%p = 0.00018 | 0 · 0 | development (train partition): not a held-out result |
| rules_v2 (values=experiments/cardvalue/defect-a0-v1.json) vs rules_v1Defect A0 · veteran · sha e08a4a4b15 | 1996 (+4 excluded) | 21.30 · 15.93 | +5.37 (+5.00 to +5.75) | 76.2% · 34.5%p = 7.2e-167 | 7 · 0 | held-out |
| rules_v2 vs rules_v1Ironclad A0 · veteran · sha 6c62f58379 | 1991 (+9 excluded) | 21.05 · 18.08 | +2.96 (+2.61 to +3.32) | 65.1% · 43.3%p = 8.8e-57 | 6 · 0 | held-out |
| rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json) vs rules_v1Necrobinder A0 · veteran · sha c1158801ba | 1997 (+3 excluded) | 19.62 · 17.63 | +1.99 (+1.69 to +2.30) | 62.1% · 47.8%p = 5.9e-30 | 0 · 0 | held-out |
| rules_v2 (values=experiments/cardvalue/regent-a0-v1.json) vs rules_v1Regent A0 · veteran · sha 091ffbceb8 | 1978 (+22 excluded) | 17.85 · 13.54 | +4.32 (+4.00 to +4.63) | 49.7% · 14.0%p = 3.9e-154 | 1 · 0 | held-out |
| rules_v2 (values=experiments/cardvalue/silent-a0-v1.json) vs rules_v1Silent A0 · veteran · sha f8da020731 | 1999 (+1 excluded) | 14.34 · 13.36 | +0.97 (+0.74 to +1.20) | 18.5% · 11.5%p = 8.5e-13 | 0 · 0 | held-out |
| search_v3 (scope=boss) vs rules_v2before isolation fixIronclad A0 · veteran · sha fcc65c6198 | 499 (+1 excluded) | 21.90 · 21.12 | +0.78 (+0.29 to +1.27) | 71.9% · 64.1%p = 3.8e-6 | 0 · 0 | development (dev partition): not a held-out result |
Reading these honestly
- For Ironclad, mean floor rises from 4.5 (random legal moves, 2000 games) to 18.1 (hand-written rules, 1997 games), so the harness does measure skill.
- rules_v1 reached more floors than random_legal: +13.60 floors for rules_v1 relative to random_legal (95% bootstrap interval +13.31 to +13.90, 1997 paired games); rules_v1 took 1.7× as long per game. Past the Act-1 boss: 43.3% for rules_v1 and 0.0% for random_legal (865 games only rules_v1, 0 only random_legal; exact McNemar p = 8.1e-261).
- rules_v1 reached more floors than search_v1: +2.38 floors for rules_v1 relative to search_v1 (95% bootstrap interval +1.24 to +3.54, 200 paired games); search_v1 took 19.7× as long per game. Past the Act-1 boss: 44.5% for rules_v1 and 28.0% for search_v1 (54 games only rules_v1, 21 only search_v1; exact McNemar p = 0.00018).
- rules_v2 (values=experiments/cardvalue/defect-a0-v1.json) reached more floors than rules_v1: +5.37 floors for rules_v2 (values=experiments/cardvalue/defect-a0-v1.json) relative to rules_v1 (95% bootstrap interval +5.00 to +5.75, 1996 paired games); rules_v2 (values=experiments/cardvalue/defect-a0-v1.json) took 1.1× as long per game. Past the Act-1 boss: 76.2% for rules_v2 (values=experiments/cardvalue/defect-a0-v1.json) and 34.5% for rules_v1 (937 games only rules_v2 (values=experiments/cardvalue/defect-a0-v1.json), 106 only rules_v1; exact McNemar p = 7.2e-167).
- rules_v2 reached more floors than rules_v1: +2.96 floors for rules_v2 relative to rules_v1 (95% bootstrap interval +2.61 to +3.32, 1991 paired games); rules_v2 took 1.4× as long per game. Past the Act-1 boss: 65.1% for rules_v2 and 43.3% for rules_v1 (608 games only rules_v2, 175 only rules_v1; exact McNemar p = 8.8e-57).
- rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json) reached more floors than rules_v1: +1.99 floors for rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json) relative to rules_v1 (95% bootstrap interval +1.69 to +2.30, 1997 paired games); rules_v1 took 1.1× as long per game. Past the Act-1 boss: 62.1% for rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json) and 47.8% for rules_v1 (468 games only rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json), 182 only rules_v1; exact McNemar p = 5.9e-30).
- rules_v2 (values=experiments/cardvalue/regent-a0-v1.json) reached more floors than rules_v1: +4.32 floors for rules_v2 (values=experiments/cardvalue/regent-a0-v1.json) relative to rules_v1 (95% bootstrap interval +4.00 to +4.63, 1978 paired games); rules_v2 (values=experiments/cardvalue/regent-a0-v1.json) took 1.1× as long per game. Past the Act-1 boss: 49.7% for rules_v2 (values=experiments/cardvalue/regent-a0-v1.json) and 14.0% for rules_v1 (771 games only rules_v2 (values=experiments/cardvalue/regent-a0-v1.json), 65 only rules_v1; exact McNemar p = 3.9e-154).
- rules_v2 (values=experiments/cardvalue/silent-a0-v1.json) reached more floors than rules_v1: +0.97 floors for rules_v2 (values=experiments/cardvalue/silent-a0-v1.json) relative to rules_v1 (95% bootstrap interval +0.74 to +1.20, 1999 paired games); rules_v1 took 1.3× as long per game. Past the Act-1 boss: 18.5% for rules_v2 (values=experiments/cardvalue/silent-a0-v1.json) and 11.5% for rules_v1 (263 games only rules_v2 (values=experiments/cardvalue/silent-a0-v1.json), 123 only rules_v1; exact McNemar p = 8.5e-13).
- search_v3 (scope=boss) reached more floors than rules_v2: +0.78 floors for search_v3 (scope=boss) relative to rules_v2 (95% bootstrap interval +0.29 to +1.27, 499 paired games); search_v3 (scope=boss) took 16.7× as long per game. Past the Act-1 boss: 71.9% for search_v3 (scope=boss) and 64.1% for rules_v2 (55 games only search_v3 (scope=boss), 16 only rules_v2; exact McNemar p = 3.8e-6).
- Batches on the “train” partition are development results, not held-out evidence.
- Ascension 10 results from this simulator would not be Double Boss results (see gaps).
Known simulator gaps
- G1-double-boss-not-enteredA10 Double Boss is not modelled: the second Act-3 boss is rolled but the map has no second boss point and the run is won after the first boss (game: StandardActMap.SecondBossMapPoint, one row above the boss). Simulator A10 win rates are therefore NOT Double Boss win rates.
- G2-a8-hp-scalingLiving Shield, Scroll of Biting and The Merchant??? ignore the A8 HP increase (97 of 100 audited enemy rows match).
- G3-sovereign-blade-crashis_sovereign_blade(None) raises in rare power/attack interactions (~1 in 2,000 random games); such games are reported as simulator_error and excluded from win rates.
- G4-rng-familyThe simulator ports .NET System.Random; the game uses xoshiro256** (v0.107.1). Distributions match where audited, but no seed reproduces a live run and per-card roll order differs.
- G5-no-live-parityState transitions have not been compared with a live STS2 build (no owned game in this environment).
- G6-undefined-name-in-reward-rollA reward path calls `_roll_random_relic_rewards`, a name the pinned simulator never defines (NameError): 2 of 2,000 rules_v1 validation games ended as simulator_error and are excluded from win rates, never counted as losses.
- G7-no-legal-actionsIn 1 of 2,000 rules_v1 validation games the run was not over but the simulator offered no legal action; the episode is reported as simulator_error and excluded from win rates.
- G8-dragon-fruit-lucky-fysh-shopBuying a card in a shop while holding Lucky Fysh (+15 gold per card added) and Dragon Fruit (max HP on gold gained) raises `TypeError: unhashable type: 'PlayerState'` in the pinned simulator (Dragon Fruit's gold handler looks the owner up in a dict keyed by creature). Seen in 1 of 2,000 rules_v1 Silent validation games (seed 605866524, Act 2); the episode is reported as simulator_error and excluded from win rates and paired comparisons.
- G9-particle-wall-loopRegent's Particle Wall (catalog: 0 energy and 2 stars; gains block and returns to hand) is offered again at cost 0 after every play in the pinned simulator, and the rules policies keep playing it: 33 of 5,000 Regent exploration games (0.66 %) reached the 20,000-step limit with block in the hundreds of thousands. Such games end as timeout, are excluded from card values and paired comparisons and are never read as losses. Whether the simulator charges the stars was not examined; the policies have no guard against replaying a card that returns to hand.
- G10-first-game-card-id-shiftThe pinned simulator caches some card lookups lazily; the first lookup of a card in a process takes the next card-instance id without returning it (e.g. the first upgrade of a Vicious card, via capture_self_mutating_card_progress). So a game's ids, and in rare situations a pile (Anger's added copy was missing from the discard pile), depended on how many games its process had already played. The harness now puts every process in the same state before its first game (warmup.py, ADR-037); batches written before that fix were produced with the first games of each worker in the other state, and are checked by `audit rederive`.
Every batch is reproducible from a sealed seed manifest; content hashes: ladder:570dd90b36 · ladder:3ab5c40521 · random_legal:6039113dcd · rules_v1:2773566cc7 · rules_v1:f0b6fb7eec · rules_v1:cb67cffc76 · rules_v1:ef7ce115f7 · rules_v1:20345eef47 · rules_v1:ebf95798ba · rules_v2:b4a6768e1e · rules_v2:c6d0120c6c · rules_v2:70a62f3df4 · rules_v2:124099adcf · rules_v2:a20b58d12a · rules_v2:b9f4863b17 · search_v1:5edbd67004 · search_v3:9c6212b8c8
Simulator results · research only
What randomised drafting says a card is worth
In simulated runs the card-reward choice was replaced, 60% of the time, by a random pick among the offered cards and “skip”. Chance rather than taste decided which cards reached the Act-1 boss, so a regression of “got past the boss” on the deck, the HP, the potions and the relics gives each card a marginal value: the change in log-odds from one more copy. The protocol and the way each value is judged (a held-out test of whether playing by the values beats playing by the hand-written ratings, written down before the held-out games were played) are the same for every class.
Defect
validated in simulation3,212 simulated Defect runs reached the Act-1 boss; 40.3% got past it. The model predicts held-out games with AUC 0.903 (log-loss 0.385 against 0.492 without the cards); intervals resample whole game seeds (200 draws). 21 of 92 measured cards have an interval above zero, 13 below, 58 include it. Correlation with the hand-written rating the advisor uses (Spearman): 0.27.
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Claw | +1.86 | +1.61 to +2.17 | ×6.4 | 429 |
| Adaptive Strike | +1.52 | +1.04 to +2.11 | ×4.6 | 91 |
| Bulk Up | +1.47 | +1.26 to +1.75 | ×4.3 | 564 |
| Ball Lightning | +1.46 | +1.27 to +1.70 | ×4.3 | 722 |
| Rocket Punch | +0.84 | +0.58 to +1.14 | ×2.3 | 415 |
| Lightning Rod | +0.79 | +0.57 to +1.08 | ×2.2 | 391 |
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Double Energy | −0.88 | −1.38 to −0.33 | ×0.42 | 70 |
| Capacitor | −0.66 | −1.06 to −0.22 | ×0.52 | 117 |
| Shatter | −0.63 | −1.12 to −0.17 | ×0.53 | 96 |
| Defragment | −0.60 | −1.08 to −0.15 | ×0.55 | 51 |
| Fusion | −0.55 | −1.10 to +0.00 | ×0.58 | 65 |
| Hotfix | −0.54 | −0.89 to −0.27 | ×0.58 | 328 |
Past the Act-1 boss
34.5% → 76.2%
p = 7.2e-167
Mean floor reached
15.93 → 21.30
+5.37 (+5.00 to +5.75)
Wins
0 → 7
p = 0.016, each re-derived
Does acting on the Defect values help? (pre-registered test)
rules_v2 is rules_v1 with one change: it takes the offered card with the largest measured value if that value is above zero (and the deck is under 32 cards), otherwise it skips. Both played the same 2,000 held-out seeds (1,996 completed by both). Past the Act-1 boss: 76.2% for rules_v2 (values=experiments/cardvalue/defect-a0-v1.json) and 34.5% for rules_v1 (937 games only rules_v2 (values=experiments/cardvalue/defect-a0-v1.json), 106 only rules_v1; exact McNemar p = 7.2e-167). Mean floor reached 21.30 against 15.93 (+5.37, 95% bootstrap interval +5.00 to +5.75). Wins: 7 against 0 (p = 0.016). Significance level for this class: 0.0125; the primary endpoint passed.
Ironclad
validated in simulation3,812 simulated Ironclad runs reached the Act-1 boss; 40.9% got past it. The model predicts held-out games with AUC 0.871 (log-loss 0.439 against 0.503 without the cards); intervals resample whole game seeds (200 draws). 15 of 95 measured cards have an interval above zero, 6 below, 74 include it. Correlation with the hand-written rating the advisor uses (Spearman): 0.31.
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Rampage | +1.74 | +1.58 to +2.00 | ×5.7 | 493 |
| Fight Me! | +1.12 | +0.99 to +1.34 | ×3.1 | 647 |
| Anger | +0.70 | +0.56 to +0.88 | ×2.0 | 871 |
| Thrash | +0.55 | +0.19 to +0.86 | ×1.7 | 52 |
| Taunt | +0.47 | +0.30 to +0.70 | ×1.6 | 483 |
| Brand | +0.46 | +0.15 to +0.77 | ×1.6 | 117 |
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Whirlwind | −0.47 | −0.77 to −0.22 | ×0.63 | 234 |
| Infernal Blade | −0.36 | −0.59 to −0.11 | ×0.70 | 115 |
| Armaments | −0.28 | −0.54 to −0.07 | ×0.75 | 493 |
| Feel No Pain | −0.26 | −0.57 to +0.02 | ×0.77 | 141 |
| Unmovable | −0.26 | −0.46 to −0.02 | ×0.77 | 44 |
| Drum of Battle | −0.25 | −0.48 to −0.07 | ×0.78 | 463 |
Past the Act-1 boss
43.3% → 65.1%
p = 8.8e-57
Mean floor reached
18.08 → 21.05
+2.96 (+2.61 to +3.32)
Wins
0 → 6
p = 0.031, each re-derived
Does acting on the Ironclad values help? (pre-registered test)
rules_v2 is rules_v1 with one change: it takes the offered card with the largest measured value if that value is above zero (and the deck is under 32 cards), otherwise it skips. Both played the same 2,000 held-out seeds (1,991 completed by both). Past the Act-1 boss: 65.1% for rules_v2 and 43.3% for rules_v1 (608 games only rules_v2, 175 only rules_v1; exact McNemar p = 8.8e-57). Mean floor reached 21.05 against 18.08 (+2.96, 95% bootstrap interval +2.61 to +3.32). Wins: 6 against 0 (p = 0.031). Significance level for this class: 0.05; the primary endpoint passed.
Necrobinder
validated in simulation3,389 simulated Necrobinder runs reached the Act-1 boss; 44.0% got past it. The model predicts held-out games with AUC 0.915 (log-loss 0.367 against 0.406 without the cards); intervals resample whole game seeds (200 draws). 18 of 91 measured cards have an interval above zero, 11 below, 62 include it. Correlation with the hand-written rating the advisor uses (Spearman): 0.43.
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Melancholy | +1.30 | +1.05 to +1.68 | ×3.7 | 337 |
| Hang | +1.06 | +0.56 to +1.53 | ×2.9 | 81 |
| Undeath | +0.91 | +0.31 to +1.52 | ×2.5 | 86 |
| Fetch | +0.81 | +0.54 to +1.15 | ×2.3 | 322 |
| Delay | +0.79 | +0.55 to +1.07 | ×2.2 | 493 |
| Flash of Steel | +0.71 | +0.24 to +1.20 | ×2.0 | 30 |
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Neurosurge | −0.92 | −1.32 to −0.56 | ×0.40 | 172 |
| End of Days | −0.75 | −1.18 to −0.25 | ×0.47 | 33 |
| Time's Up | −0.58 | −1.13 to −0.05 | ×0.56 | 36 |
| Putrefy | −0.58 | −1.15 to −0.08 | ×0.56 | 74 |
| Reanimate | −0.47 | −1.05 to −0.04 | ×0.62 | 41 |
| Spoils Map | −0.44 | −0.85 to −0.05 | ×0.65 | 170 |
Past the Act-1 boss
47.8% → 62.1%
p = 5.9e-30
Mean floor reached
17.63 → 19.62
+1.99 (+1.69 to +2.30)
Wins
0 → 0
p = 1.0
Does acting on the Necrobinder values help? (pre-registered test)
rules_v2 is rules_v1 with one change: it takes the offered card with the largest measured value if that value is above zero (and the deck is under 32 cards), otherwise it skips. Both played the same 2,000 held-out seeds (1,997 completed by both). Past the Act-1 boss: 62.1% for rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json) and 47.8% for rules_v1 (468 games only rules_v2 (values=experiments/cardvalue/necrobinder-a0-v1.json), 182 only rules_v1; exact McNemar p = 5.9e-30). Mean floor reached 19.62 against 17.63 (+1.99, 95% bootstrap interval +1.69 to +2.30). Wins: 0 against 0 (p = 1.0). Significance level for this class: 0.0125; the primary endpoint passed.
Regent
validated in simulation2,703 simulated Regent runs reached the Act-1 boss; 22.4% got past it. The model predicts held-out games with AUC 0.898 (log-loss 0.323 against 0.398 without the cards); intervals resample whole game seeds (200 draws). 12 of 88 measured cards have an interval above zero, 14 below, 62 include it. Correlation with the hand-written rating the advisor uses (Spearman): 0.07.
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Kingly Punch | +2.25 | +2.05 to +2.65 | ×9.5 | 334 |
| Shining Strike | +1.79 | +1.57 to +2.26 | ×6.0 | 325 |
| Kingly Kick | +1.55 | +1.26 to +1.96 | ×4.7 | 277 |
| Bulwark | +0.65 | +0.26 to +1.05 | ×1.9 | 150 |
| Hegemony | +0.59 | +0.34 to +0.87 | ×1.8 | 479 |
| Gather Light | +0.59 | +0.33 to +0.92 | ×1.8 | 500 |
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Spoils Map | −0.65 | −1.08 to −0.20 | ×0.52 | 131 |
| Celestial Might | −0.52 | −0.93 to −0.20 | ×0.60 | 273 |
| Alignment | −0.48 | −0.79 to −0.18 | ×0.62 | 270 |
| Reflect | −0.47 | −0.86 to −0.06 | ×0.62 | 372 |
| Radiate | −0.43 | −0.93 to −0.04 | ×0.65 | 180 |
| Bundle of Joy | −0.42 | −0.94 to −0.00 | ×0.66 | 61 |
Past the Act-1 boss
14.0% → 49.7%
p = 3.9e-154
Mean floor reached
13.54 → 17.85
+4.32 (+4.00 to +4.63)
Wins
0 → 1
p = 1.0, each re-derived
Does acting on the Regent values help? (pre-registered test)
rules_v2 is rules_v1 with one change: it takes the offered card with the largest measured value if that value is above zero (and the deck is under 32 cards), otherwise it skips. Both played the same 2,000 held-out seeds (1,978 completed by both). Past the Act-1 boss: 49.7% for rules_v2 (values=experiments/cardvalue/regent-a0-v1.json) and 14.0% for rules_v1 (771 games only rules_v2 (values=experiments/cardvalue/regent-a0-v1.json), 65 only rules_v1; exact McNemar p = 3.9e-154). Mean floor reached 17.85 against 13.54 (+4.32, 95% bootstrap interval +4.00 to +4.63). Wins: 1 against 0 (p = 1.0). Significance level for this class: 0.0125; the primary endpoint passed.
Silent
validated in simulation2,332 simulated Silent runs reached the Act-1 boss; 10.7% got past it. The model predicts held-out games with AUC 0.878 (log-loss 0.241 against 0.263 without the cards); intervals resample whole game seeds (200 draws). 14 of 75 measured cards have an interval above zero, 10 below, 51 include it. Correlation with the hand-written rating the advisor uses (Spearman): 0.43.
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Dramatic Entrance | +1.07 | +0.43 to +1.65 | ×2.9 | 41 |
| Noxious Fumes | +0.86 | +0.24 to +1.32 | ×2.4 | 109 |
| Footwork | +0.81 | +0.54 to +1.10 | ×2.3 | 430 |
| Flash of Steel | +0.75 | +0.01 to +1.38 | ×2.1 | 31 |
| Leading Strike | +0.70 | +0.33 to +1.09 | ×2.0 | 204 |
| Echoing Slash | +0.59 | +0.04 to +1.15 | ×1.8 | 127 |
| Card | Value | 95% interval | Odds | Runs |
|---|---|---|---|---|
| Acrobatics | −0.88 | −1.42 to −0.39 | ×0.42 | 249 |
| Spoils Map | −0.82 | −1.16 to −0.44 | ×0.44 | 119 |
| Bubble Bubble | −0.76 | −1.09 to −0.36 | ×0.47 | 52 |
| Speedster | −0.75 | −1.13 to −0.33 | ×0.47 | 66 |
| Calculated Gamble | −0.73 | −0.95 to −0.43 | ×0.48 | 57 |
| Expertise | −0.66 | −1.20 to −0.22 | ×0.52 | 241 |
Past the Act-1 boss
11.5% → 18.5%
p = 8.5e-13
Mean floor reached
13.36 → 14.34
+0.97 (+0.74 to +1.20)
Wins
0 → 0
p = 1.0
Does acting on the Silent values help? (pre-registered test)
rules_v2 is rules_v1 with one change: it takes the offered card with the largest measured value if that value is above zero (and the deck is under 32 cards), otherwise it skips. Both played the same 2,000 held-out seeds (1,999 completed by both). Past the Act-1 boss: 18.5% for rules_v2 (values=experiments/cardvalue/silent-a0-v1.json) and 11.5% for rules_v1 (263 games only rules_v2 (values=experiments/cardvalue/silent-a0-v1.json), 123 only rules_v1; exact McNemar p = 8.5e-13). Mean floor reached 14.34 against 13.36 (+0.97, 95% bootstrap interval +0.74 to +1.20). Wins: 0 against 0 (p = 1.0). Significance level for this class: 0.0125; the primary endpoint passed.
- Simulated, not measured in the live game: the simulator has not been compared with a running STS2 build.
- Specific to the pilot: the rules policy plays the cards, so a card it plays badly is undervalued and one it plays well is overvalued. Synergies are not modelled (the model is additive; a small neural network did not predict held-out games better).
- A value is conditional on the HP, potions and relics a run brought to the boss; it is not a tier list, and shops, events and rests were not randomised.
- Data, code and the test protocols are in the repository:
experiments/cardvalue/defect-a0-v1.json(sha 4e3cd4615738…),experiments/cardvalue/ironclad-a0-v1.json(sha 8b3fe2f4d88b…),experiments/cardvalue/necrobinder-a0-v1.json(sha 5634d67a81f7…),experiments/cardvalue/regent-a0-v1.json(sha 1a8d0a8861db…),experiments/cardvalue/silent-a0-v1.json(sha fa088419e45e…).
Not yet evaluated
Things this site does not know
- Advisor win delta
- The card advisor's own index is still a heuristic and says so. Measured values from simulated runs exist (above and on the codex) and a policy that follows them beat the hand-written ratings in simulation, but the advisor does not use them yet and nothing here is a win probability for the live game.
- Live-game parity of the simulator
- Not verified: no owned game copy was available. Simulator win rates are model output.
- Beta build v0.111.0
- Unsupported: rules not verified (card rarities and the White Beast Statue potion interaction already differ).
- Co-op
- Unsupported: per-seat odds exist in the game, scaling and shared decisions are unverified.
- Neural policies
- None deployed, and no advice on this site comes from a neural network. A research combat network trained locally has not beaten the rules baseline on held-out simulated combats (record: experiments/rl/results in the repository).