spire-mentor

How the AI plays

Two different machines share the work. A tree search decides every action inside a fight. A small transformer, trained by reinforcement learning, decides everything between fights. Neither ever sees information a human player could not.

How the transformer was trained and promoted is on its own page. This page describes what runs at decision time, as deployed.

The AI beating Ironclad at Ascension 9 in the real game, through the Spire Mentor mod. The advisor panel at the top left shows the search's ranking of each move.

Two halves

startfightrewardpathfightrewardeventpatheliterelicrestshoppathfightbossfight: MCTS on every action (1,000 sims, 4,000 at bosses)anything else: one forward pass of the run policyAct 1, schematically: who decides at each stop
Fights go to the search one action at a time; every other choice is one pass of the run policy. Single-option prompts are taken automatically.

The split is strict. The transformer never plays a card, and it gives the search no priors: the search starts every decision from a uniform prior over legal actions. Combat is where exact lookahead pays most, because one core plays about 59,000 random fights a second (benchmarks). Run-level choices such as which card to take or which path to walk have consequences tens of floors away, which is where a learned policy earns its keep.

A typical Ironclad run at Ascension 9 contains about 335 combat decisions (56% in common fights, 20% in elites, 24% in bosses). In September 2026 they took roughly 570,000 simulations in total; the current version plays a decision with only one legal move without a search, so it spends somewhat fewer.

Hidden information

The engine knows the true order of the draw pile and the state of every random stream. A search that reads them is clairvoyant: it would know which card comes next and what the enemy will roll. The deployed search instead starts from a belief root built from what the player can see:

What theengine knowsWhere thesearch startsBashBashStrike?Defend?Anger?Strike?Defend?Strike?knownpinned on topunknown cards: canonical order; each draw is a chance nodedraw orderRandom streams: the true cursors are discarded and reseeded from the search's own seed.
The search never sees the real draw order. Cards whose position the player knows stay put; the rest are drawn as chance outcomes.
  • the unknown part of the draw pile is put into a canonical order, so no real order is committed;
  • each draw from it is decided at draw time, as a chance node;
  • every combat random stream is reseeded from the search's own seed, discarding the true cursors;
  • cards the player knows the position of, such as a card put on top of the pile, stay where they are.

The chosen action is therefore a function of player-observable information only. Enemy intents for the current turn are visible, as they are in the game; later enemy moves are chance nodes.

Honesty costs a lot, and the project measured how much. On 2,000 paired Ironclad A0 runs, the PPO champion of the time won 81.25% with a clairvoyant search and 55.75% with the honest one, a 25.5-point gap. Honest search became the default on 2026-07-12. Every champion on this site was trained and measured with it, and win rates from before that date are not comparable.

What a fight is worth

The search does not score a fight as a bare win or loss. Values are arranged in bands, so that a certain loss never outranks an uncertain line and a win is still a win however it looks:

−1.5−1.0−0.50+0.5+1.0+1.5lostmore damage dealt ranks highersearch stopped mid-fighttanh(evaluation of 216 features)wonHP and potions keptEvery lost fight scores below every unfinished one; every win above.
The three value bands do not overlap, so the ordering between them is absolute.
OutcomeValueRanked by
Won the fight+1.0 to +1.5HP left, potions kept (one potion ≈ 8 HP), a small bonus for finishing early; minus a penalty if a thief escaped with gold or cards
Search stopped mid-fight−1 to +1tanh of a linear evaluation over 216 features of the position, fitted to outcomes
Lost the fight−1.45 to −1.05enemy HP removed (60%), own HP kept (25%), turns survived (15%)

In short, the search maximises the chance of winning the fight first, then the resources carried out of it. Potions are ordinary actions inside the search, priced by the same potion term.

The run policy

Everything outside combat with two or more options goes to a transformer, one per character. It decides:

  • the path on the map;
  • card rewards (including skipping), relic rewards, boss relics and treasure;
  • shop purchases and card removal;
  • rest sites (rest or smith, and which card to upgrade);
  • event options;
  • deck changes the game asks for: remove, upgrade, transform, enchant;
  • which potion to drop when the belt is full.

How it learned

Nobody wrote down which choices are good. The network was trained with reinforcement learning (PPO): it played hundreds of thousands of simulated runs, with the combat search fighting every fight, and after each batch it made the choices that had led to better-than-expected runs a little more likely and the others a little less likely. A second output, the value head, learned alongside it to predict how well a run will go from a given situation; that prediction is what decides whether an outcome counts as better or worse than expected. Each update is kept small, which is the "proximal" in Proximal Policy Optimization. The training page walks through this step by step.

What it sees

The state is a set of tokens. Each token is the sum of a section embedding, up to twelve categorical embeddings and a projection of continuous features. There is no positional encoding: apart from its section, a token's place in the sequence carries no meaning.

TokenCarries
State (one)decision type, act boss, HP, gold, act, floor, ascension
Deck card (one per card)card identity, upgrade, enchantment
Relic, potionidentity
Map noderoom type, position relative to the player, reachability, elites ahead (min/max), distance to the next rest site
Candidate action (one per option)what the option is: a path step with its forward-cone features, a card or relic with its shop price, an event option with its gold, HP, relic, potion and card effects

Vocabularies cover 580 cards, 297 relics, 63 potions, 69 events, 23 enchantments and 12 act bosses.

The network

Statedecision, act boss, HP, gold, floor, ascensionDeckone token per cardRelicsPotionsMapone per map nodeOptionsone per candidate actionembedsection+ 12 categorical+ 25 continuoustransformer3 layers · 4 headsd 192 · SwiGLUno positionseach option token→ shared linear → logitInflame 0.58Pommel Strike 0.31Skip 0.11softmax; play takes the topmean-pool all tokens→ value V (trainingreturn, not a win %)≤ 320 tokens and ≤ 160 options per decision; about 2 million parameters.
The run policy. The option probabilities shown are illustrative; at play time the top option is taken.
Architecturepre-norm transformer: RMSNorm, multi-head attention, SwiGLU MLP (4× width)
Sizedmodel 192, 3 layers, 4 heads; about 2.05 M parameters
Policy headone shared linear layer scores each candidate-action token; illegal slots are masked; softmax over the candidates
Value headmean-pool over all tokens; predicts the training return, not a win probability
Fixed shapes320 tokens, 160 action slots. Overflow is a hard error, never a silent truncation; each promotion gate checks peak use stays well inside (the Regent gate peaked at 164 tokens and 48 slots)

Scoring candidates with a shared head over their own tokens is what lets one network handle a three-way event, a twelve-item shop and a forty-card removal screen with the same weights.

Choosing

At play time the policy is greedy: it takes the highest-probability option. (During training it samples from the probabilities instead, so it keeps trying options it is unsure about.) Options that differ only in their target (remove which card, smith which card) first pool their probability by kind, so a well-liked action is not out-voted because its mass was split across many targets. One rule sits on top: an event choice that would kill the player outright is vetoed.

One run, in order

  1. Map. The policy picks the next node.
  2. Fight. Every action with more than one legal option is searched: 1,000 simulations, or 4,000 in a boss room.
  3. Rewards, shops, rest sites, events, treasure, deck changes. One forward pass each.
  4. Repeat to the final boss, or until the character dies.

Fallbacks are counted, never silent. If a search returns nothing usable, a fixed heuristic decides and the fallback counter records it; the release smoke test saw 1 such case in 2,527 combat decisions. In training there is no fallback class at all: every decision is the net, an automatic single option, or the combat agent.

The numbers on screen

  • "est. win" in the mod, and percentages on cards in the workbench, come from the search: of the simulations that started with this move, the fraction that reached a won fight within the rollout. It is a fight-level number, measured on belief samples, and not calibrated.
  • The mod's evaluation bar shows the search's mean value for the recommended move.
  • The policy's V is the value head's estimate of the training return. It is shown as a score, never as a percentage, because it is not a win probability.

Where it runs

HostSearchPolicy
Simulator and gatesnative RustONNX via tract, pure Rust, CPU, batch 1
Browser workbenchthe same Rust crate compiled to WebAssembly, in a Web Workerthe same tract runtime inside the WebAssembly module; the ~8.5 MB model is fetched once and cached for offline use
Spire Mentor modThe mod itself only reads the game and draws the HUD. It starts a separate advisor process that bundles the Rust engine and the champion models, mirrors the live state into the engine and answers over a local socket.

The models are pinned by SHA-256 in sts2-pycli/data/ppo/champions.toml. A missing or mismatched champion is a load error, not a silent swap to something weaker. The Rust encoder matches the Python one exactly, and tract matches PyTorch to within 1e-4.