Skip to content

Bots and strategies

This section is for anyone who wants a program, not a person, to play gorge: a Go developer adding a policy to the engine, a Python or Java author wiring an existing strategy to a server, or a benchmark harness. This page surveys the bots gorge ships and the three ways to plug one in, and helps you pick. The pages after it walk through each surface with working code.

Facts on this section's pages were checked against the code at commit 513a2a0c3 (2026-10-21). Unless a figure says otherwise, latencies were measured on 2026-10-20 at ad3777023 on an AMD Ryzen 9 9950X over loopback, with the command named next to them.

The one contract every bot answers

Whatever the transport, a bot does one thing: the engine poses a decision listing every legal option, and the bot answers with an intent naming option indices (glossary). The engine validates the answer against what it offered, so a bot can choose badly but never cheat its way to an illegal move. There are thirteen decision kinds and the set is closed (decision.Kinds, engine/decision/decision.go): priority, target, attackers, blockers, mulligan, modes, trigger_order, trigger_optional, commander_zone, choose, replacement, arrange and starting_player. Conceding is an option on a priority decision, not a kind. Playing a seat over HTTP explains how to answer each one.

A bot sees one seat's redacted view of the game (its own hand, the public board, counts for hidden zones), never the opponent's hand or the library order.

The bots gorge ships

Five policies register themselves in the bot registry (bots/registry.go) from the packages bots/all links, so every gorge binary that imports bots/all (gorged, mtgsim, cmd/spellbench-gorge-agent, your own program) has all five. Each entry's bots.Info carries its label, tier, formats, seat limit, a description, its recorded strength claims and its cost. GET /api/bot-policies on a running server returns exactly that record for the policies the server offers.

Name Label Tier Formats, seats Offered by gorged Package
bot Bot production constructed and Commander, any yes; the caretaker of every built-in policy bots/bot
forge-heuristic Forge heuristic experimental constructed and Commander, any yes bots/forgeheuristic
lethal-pressure Lethal Pressure experimental constructed and Commander, any yes; gorged's default -bot-policy bots/lethalpressure
sb-tactical sb-tactical (SpellBench tactical heuristic) experimental constructed, 2 no: reads every seat's decklist bots/sbtactical
az-redeal az-redeal (AlphaZero search) experimental constructed, 2 no: reads every seat's decklist bots/azredeal

What each one does

bot is the production heuristic: hand-written rules (bots/botpolicy, driven by bots/heuristic) that score each offered option. On a table with automatic mana payment (the default for hosted bots, -bot-auto-mana) it casts through the engine's payment plans and picks attacks and blocks with a two-ply combat simulation; on a manual-tap table it is the plain heuristic. It answers the London mulligan with a re-expression of Forge's keep/bottom rule (botpolicy.MulliganForge). It is the baseline every other policy is measured against, and the bot that answers for a human seat whose player is gone.

forge-heuristic is bot, built by the same constructor (bot.BaseWithSim, so the same combat simulation, payment plans and mulligan), with every opt-in rule family re-expressed from Forge's AI switched on. Each family is a clean-room rule in gorge's own vocabulary, not translated Forge code:

Family Rules armed
A1 removal timing hold a one-shot destroy, damage or bounce spell until the best opposing creature is a real threat (about a 4/4, less with evasion), the spell is lethal to a face, or it could never hit a creature
A2 targeting prefer a land the opponent's colours depend on, prefer the opponent's prominent colour, skip a target that is a strictly worse copy of another of their creatures
A3 Forge AI hints a card's PlayMain1 hint earns a first-main-phase bonus; a RemoveDeck card is never chosen
A4 deck-shape posture an aggro deck releases its last held-back blocker, the combat simulation weighs life by deck shape, and the mana reserve scales by shape (a control deck keeps instant mana up)
A5 attachments aim an Aura by its AttachAILogic hint (boons on its own creatures, curses on the opponent's), rank its own creatures as carriers, hold a boon Aura while nothing can carry it
A6 timing counter only the top foreign spell; hold draw and token spells for Forge's window (a sorcery until the second main phase, an instant draw until an opponent's end step, an instant token until an opponent attacks); cast an instant pump only at declare blockers, and only when it saves a creature, wins a fight or makes an unblocked attack lethal
A7 combat (inside the simulation) gang blocks, a gang-aware predicted reply, Double Strike in blocked combat

The rules live in bots/botpolicy (TargetRules, RemovalRules, TimingRules, AIHints, AttachRules, PostureRules, CombatRules), and bots/forgeheuristic.New shows how to arm any subset on a policy of your own.

lethal-pressure is the older heuristic configuration: the plain heuristic plus the AR7 lethal-pressure attack rule (a creature whose unblocked power reaches the defender's life attacks even into a cheaper blocker). It has no combat simulation on any table, and it keeps the heuristic's original mulligan (a one-in-three coin, whatever the hand).

sb-tactical is the scored tactical heuristic from the SpellBench project, in its hosted shape. It values creatures, timing, the damage race and removal over what its seat can see, reads printed card facts from the card registry, and at priority prices its plays through a payment planner built on an honest root: a copy of the game with every hidden card redealt from what the seat has seen. That redeal draws from each seat's real decklist, which is why the policy is marked Env and is never seated against a person.

az-redeal is an AlphaZero-style tree search (bots/azmcts) with no network: a uniform prior and a heuristic leaf value. At each priority, attackers, blockers and target decision it runs 100 simulations, each over a freshly redealt world built from its own observations, and bot answers every other decision. Like sb-tactical it builds its worlds over every seat's real decklist (Env), so it plays only where a driver supplies those worlds (see below). A hosted search seat has a 2-second budget per decision (hostedDecisionDeadlineMS); a search that runs out plays bot's answer.

Recorded strength

These are the claims each registry entry records (bots.Info.Strength), with the setting and source it names; GET /api/bot-policies serves the same text. Percentages are win rates with 95% confidence intervals. Several were measured against an older bot, as noted.

Policy Claim Setting Source
bot 53.17% [52.68, 53.65] constructed, 53.69% [52.73, 54.64] Commander, against the plain auto-pay bot held-out auto-pay tables, before the Forge mulligan became the default commit 66aa546dd
bot 31.5% [27.1, 36.2] against Forge's AI over 400 games (28.5% with the coin mulligan it replaced) Forge games over a stdio bridge, the 12 Legacy decks, 144 ordered pairs, seats traded Forge bridge runs, 2026-10-20
forge-heuristic 32.0% [27.6, 36.7] against Forge's AI over 400 games, +0.5 points over bot with the same mulligan (inside noise) as above Forge bridge run, 2026-10-20
forge-heuristic 46.8% [41.9, 51.6] against bot over 400 games, indistinguishable from the 46.5% bot-vs-bot control constructed, seats traded; measured before it gained the combat simulation and the Forge mulligan A2 target rules
lethal-pressure 51.65% [50.10, 53.20] against the bot before AR7 held-out, manual mana archived AR7 design note (docs/design/ARCHIVE.md)
sb-tactical 110-18 against the SpellBench bot arm on the Pauper kernel decks (benchmark seed), 106-22 at seed 99991, 87-41 on FDN mirrors; Elo 1507 against bot's 1242 SpellBench's measured table SpellBench agent design §3.4
az-redeal 70.5% [68.5, 72.5] against bot over 2,000 games (+20.5 points over the 50.0% control); its 25-simulation variant rated 1382 against bot's 1264 UW Tempo against the five mono decks with true lists visible; SpellBench's eight-deck mirror pool SpellBench agent design §12.5

A current check, run for this page on 2026-10-21 at 513a2a0c3 with the matchup program on Bot-vs-bot games (every pair of the 12 Legacy decks, 20 games per pair, seats traded): forge-heuristic won 636 of 1,320 games against bot (48.2%, ±2.7 points) where bot against itself won 630 (47.7%). Today the two are indistinguishable in 1v1 constructed. Cost per decision is on Bots: cost and strength.

How to select one

Where How
gorged, play-vs-bot POST /api/games with "bot_policy": "<name>"; omit it to get the server's -bot-policy (default lethal-pressure). -bot-policies a,b,… limits what the server offers (default: every registered policy that does not read decklists).
gorged, bot-only tables -bot-policy <name> seats that policy in every bot seat of the startup tables.
mtgsim -a <name> on seat 0, -b <name> on seat 1; every other seat plays the plain heuristic.
A Go program bots.New(name, bots.Options{Seed, AutoPayMana, Deps: bots.Deps{Cards: reg}}) after a blank import of bots/all; testkit/bench drives it.
SpellBench v2 spellbench-gorge-agent -policy <name> serves it as a v2 agent (SpellBench v2 agents).

-bot-common-sense (default off) wraps bot, forge-heuristic and sb-tactical in the bots/sanity guard, which drops hostile removal that could only hit the bot's own permanents.

sb-tactical and az-redeal need a driver that supplies their honest roots and the card registry. testkit/bench.PlayGame with Hooks{EnvSeats: true, HonestRoot: true} does, so they play at full strength in your own Go program. mtgsim does not: -a sb-tactical fails with Deps.Cards is nil, and -a az-redeal runs but never searches. A gorged server never offers them, because a play-vs-bot table always seats a person and -bot-policy refuses a decklist-reading policy.

Which bot to pick

  • To play against, or as the opponent for an HTTP bot: bot. It is the production policy, and no policy a server offers has measured stronger. Start gorged with -bot-policy bot to make it the default; the flag's own default is lethal-pressure.
  • As the baseline for your own policy: bot, with a bot-against-bot control on the same seeds.
  • A Forge-style opponent: forge-heuristic. It plays like bot with Forge's targeting, timing and attachment habits, and measures the same.
  • A stronger two-player sparring partner in a bench: sb-tactical (a heuristic; its registry entry estimates about 1 ms per decision) or az-redeal (the strongest recorded claim, at roughly a quarter of a second per searched decision). Both read every seat's decklist, so use them only where that is acceptable.
  • lethal-pressure keeps the older heuristic configuration for tables that want it. On an auto-pay table it does without the combat simulation bot uses.

Other policies at runtime

  • A SpellBench v2 agent registered with -sb-agent name=/path/to/agent becomes a policy like any other (SpellBench v2 agents).
  • Network-backed search bots. gorged reserves a few policy names and flags for search bots that need a trained network (see gorged -h); a downstream binary links an implementation in through gorged.RegisterNetBot (server/gorged/netbots.go). gorge ships the inference code but no trained networks, so on a plain gorged those flags refuse to start.
  • Your own Go policy, linked into a binary of your own (Writing a native Go bot).

bots/searchseat (a determinized rollout seat), bots/searchprobe and the learned-policy runtime in bots/policynet are building blocks for benches; they are not registered policies.

Three ways to plug a bot in

one process: gorged, mtgsim or testkit/bench rules engine Decision → Intent match loop server/host or bench ① native Go seats bot, forge-heuristic, az-redeal, yours Decide(ctx, View, Decision) ② HumanSeat + httpapi parks the decision until an intent /pending /intent /view ③ SpellBench seat adapter server/spellbench/seat (-sb-agent) hello · game_start · choose process boundary HTTP client Python, Java, a browser… any machine that can reach it v2 agent process spawned per match, any language same machine only HTTP + JSON bearer token stdin/stdout NDJSON ① has nothing outside: the seat is a Go value in the same address space

The integration surfaces. Native seats are called directly; an HTTP client answers a parked HumanSeat across the network; a SpellBench v2 agent is a child process on the same machine.
  1. Native Go (guide). Implement seat.Seat, register it with bots.Register, link it into gorged or a bench. The fastest path and the only one that can search: a Go seat can clone the engine or ask the host for an honest redealt world.
  2. HTTP seat (guide, examples). A server started with -vsbot (or -humans) parks a seat's decision behind GET .../pending; any program that speaks HTTP and JSON answers it with POST .../intent. One decision per round trip, any language, any machine. This is the surface the Python and Java examples use.
  3. SpellBench v2 (guide). gorge speaks the SpellBench benchmark protocol in both directions: an external v2 agent can take a gorge seat (-sb-agent), and any gorge bot can be served as a v2 agent (cmd/spellbench-gorge-agent). Its transport is newline-delimited JSON on a child process's stdin and stdout, so it is for agents that already speak v2, not for new remote bots.

Which one should I use?

flowchart TD
  q1{Is the bot written in Go,<br/>or can it be?}
  q1 -->|yes| q2{Does it need to search,<br/>clone the engine, or play<br/>millions of games?}
  q2 -->|yes| go[Native Go seat<br/>bots.Register]
  q2 -->|no, but it should run<br/>inside the server| go
  q2 -->|no, and it must stay<br/>a separate program| http
  q1 -->|no| q3{Does it already speak<br/>SpellBench v2?}
  q3 -->|yes| sb[SpellBench v2 agent<br/>-sb-agent name=/path]
  q3 -->|no| http[HTTP seat<br/>POST /api/games, /pending, /intent]
  http --> note1[one decision per round trip,<br/>any language, any machine]
  sb --> note2[child process on the server box,<br/>benchmark-compatible]
  go --> note3[microseconds per decision,<br/>deterministic, can search]

How the surfaces compare

Native Go seat HTTP seat SpellBench v2 agent seat
Language Go anything with HTTP and JSON anything that reads and writes lines on stdio
Transport a function call HTTP/1.1 + JSON; SSE for push notifications stdin/stdout, one JSON object per line
Where the bot runs inside the host process anywhere that can reach the server a child process the host spawns, one per match
Latency per decision 21–34 µs for a whole two-seat game step, engine and both seats included (go run ./cmd/duel in examples/bots/go, 40 games) GET /pending 0.12 ms + POST /intent 0.11 ms median; GET /view 0.50 ms; next decision visible 0.13 ms after the answer when polling flat out, 0.24 ms via SSE (measure_latency.py, 10 games). The example agents average 1.6–3.2 ms per decision with their polling backoff choose round trip 0.05 ms median (a Python agent that picks candidate 0) to 0.18 ms (gorge's own bot served over v2); about 0.4 s to start the gorge agent process per match
Hidden information the seat is handed a redacted View; the code is trusted (it runs in the server) enforced by the server: the bearer token names the one seat it may read and act for the host projects a neutral v2 observation for the agent's seat
Can it search or clone the engine? yes: Engine.Clone, CloneHypothetical, or an honest root via bots.EnvSeat no engine access; bring your own simulator no, unless the agent embeds one (gorge's own v2 agent does)
Determinism a pure function of the seed: replays byte-identically the server records every intent, so a finished game replays; the bot's own randomness is yours to seed the host passes an agent_seed; the game replays from its log
Many games at once goroutines (testkit/bench.RunPairs) one token per seat; open as many games as the server allows (-max-on-demand-tables, default 32 retained). 16 Python agents in parallel: 4,156 decisions in 1.27 s one process per seat per match
What happens on a timeout hosted search seats have a 2 s budget and fall back to their heuristic gorged waits forever by default; the seat's caretaker bot answers only when the table shuts down the host's per-decision budget (2 s in gorged, none in mtgsim); a timeout forfeits the agent's game

The HTTP numbers come from the measurement loop described on Playing a seat over HTTP; the SpellBench numbers from a relay that timed each request between mtgsim and the agent (SpellBench v2 agents). The operator-facing assessment of each surface, and what it would take to make them fit for remote agents at scale, is the interface review.

Where next