v0.5.0 · pre-1.0 · Gen 9 doubles

A doubles battle simulator built to be branched.

vgc-engine is a from-scratch Pokémon battle simulator in Rust, designed for the people writing search and learning code: MCTS rollouts, endgame solvers, self-play data. Fixed-size state, a heap-free step(), cheap clones and a Python module you can import.

Pokémon Showdown is the compatibility oracle. On a sealed sample of 1,300 real Reg M-C battles, 97.9–98.9% replay identically to Showdown. Good enough for local self-play, not yet a replacement for Showdown as the final evaluator. See the evidence.

One joint action fanning out into a chance frontier Illustration: a doubles field with two slots per side. One joint action leads to a branch point, which fans out into several weighted next states. B1 B2 A1 A2 turn n · state 0x… step() p .58 p .22 p .10 p .06 p .04 Σp = 1
Illustrative diagram, synthetic probabilities. One joint action, one frontier of weighted next states.

01 · Who it's for

For developers who need to play out a turn thousands of times.

Pokémon Showdown is the reference simulator, but it is JavaScript, event-driven and allocation-heavy. That is fine for a ladder. It is a bottleneck when a search tree wants to clone a battle, try every joint action and step each branch. vgc-engine is written for that loop.

Search & planning

Clone a battle, enumerate legal choices per slot, step each joint action, keep the best. Battle has no interior sharing, so a copy is a full independent state.

ML pipelines

A pyo3 module, vgc_engine, plugs into Python training and evaluation code. State round-trips through compact bytes or inspectable JSON.

Game-theory & endgames

The vgc-solver crate enumerates outcome frontiers and solves small endgames, returning mixed policies for both sides with provenance and node counts. Every solve stops at a hard time budget and still returns a usable answer.

Mechanics & tooling

A vgc CLI for damage, speed and EV questions, team legality checks for the Champions regulations, and a differential harness against Showdown.

02 · Inside the engine

Data-oriented from the battle struct up.

The design takes its discipline from pkmn/engine and its answers from Pokémon Showdown, then aims both at modern doubles.

  1. Fixed-size state, heap-free step

    Battle state is a plain fixed-size struct. Handles, not pointers: MonId, MoveId, SideRef. The project rule is no heap allocation inside step(), and an allocation probe over 500 battles reported 0 step allocations at the 2026-10-06 finish-line check.

  2. Tables generated at build time

    build.rs turns @pkmn/dex JSON into Rust source for species, moves, items, abilities and the type chart. Champions is a data overlay, not a fork of the code.

  3. Doubles as a first-class format

    Slot targeting, spread-move damage, redirection, the Protect family, weather, terrain, screens, Tailwind and Trick Room live in shared modules. Since 0.5.0, legal_choices can also offer your partner as a target (opt-in ally_targets; off by default because it changes the action space).

  4. Two Gen 9 doubles rule sets

    Standard Gen 9 doubles with Terastallization, and the Champions overlay (Reg M-B, Reg M-C) with Mega Evolution, Stat Points and Champions move data, selected by format id.

  5. Chance frontiers on demand

    Behind the chance feature, Battle::step_chance() returns every possible next state of one turn with its prior. Today it wraps a record-and-replay pass. Native per-site branching is planned.

  6. Showdown's own PRNG, opt-in

    The ps-rng feature adds a bit-exact port of Showdown's PRNG with its draw order and a per-draw trace, for seeded differential testing. Off by default, and absent from default builds.

03 · How a frontier is built

Watch one turn become a probability distribution.

This is how vgc-solver enumerates outcomes, replayed on a toy model in your browser. Pick a joint action and step through the four passes.

Illustrative. Synthetic HP, damage and accuracy values computed by a small script on this page. Nothing here runs or reports on the engine.

Side A's joint action
Target state

Record pass

Loading the toy model…

The four passes match the documented v1 algorithm in vgc-solver: record the visited draw sites, take the cross product of their outcomes, replay each cell with a keyed RNG, and merge by canonical state hash. The toy keeps three site kinds per hit (accuracy, crit, 16 damage rolls). Real turns have more.

04 · Quickstart

From a team list to a branched turn.

There is no PyPI package or GitHub release yet, so you build from source. You need Rust (stable), Python 3.9+ and curl. The data build reads the @pkmn/dex JSON from VGC_DEX_DIR; the steps below fetch the same pinned copy CI uses.

git clone https://github.com/colefoster/vgc-engine.git
cd vgc-engine

# @pkmn/dex JSON for build.rs (localdex 0.1.1, as in CI)
mkdir -p .dex
curl -fsSL https://registry.npmjs.org/localdex/-/localdex-0.1.1.tgz | tar xz -C .dex
export VGC_DEX_DIR="$PWD/.dex/package/data"

# Build and install the Python module (vgc_engine 0.5.0).
# ps-rng enables ps_seed and Showdown sessions; without it they raise.
python3 -m venv .venv
.venv/bin/pip install maturin
.venv/bin/maturin build --release -m crates/vgc-engine-py/Cargo.toml \
    --features ps-rng -i .venv/bin/python -o dist
.venv/bin/pip install dist/vgc_engine-0.5.0-*.whl
.venv/bin/python -c "import vgc_engine; print('ok')"

# Optional: the vgc CLI
cargo build --release -p vgc-engine-cli --bin vgc
./target/release/vgc calc chomp rillaboom eq

Verified on macOS arm64 with Python 3.9 and 3.12 from a fresh clone of main at 0.5.0 (2026-10-24). On Linux, CI runs cargo test, and wheels for x86_64 serving hosts are built natively with --features ps-rng.

Full build notes, the Python API and protocol docs live in the repo.

Read the README on GitHub

05 · What you can do with it today

Seven concrete jobs, each mapped to a real surface.

Rollouts for MCTS

Clone, step joint actions, read HP and terminal results.

Battle.clone · step_move · legal_choices

Chance-aware search

Enumerate every next state of a turn with its prior, deduplicated.

step_chance · vgc-solver

Small endgames

Solve for mixed policies under a hard deadline, alone or across sampled opponent sets.

solve_endgame_anytime · solve_endgame_worlds · solve_endgame_exact

Start from a real replay

Rebuild the position at turn N of a Showdown replay, filling hidden sets from a usage prior.

position_from_replay · from_position · vgc recon

Snapshots & datasets

Persist states mid-game and restore them elsewhere.

to_bytes · from_bytes · to_json

Team legality

Check a Showdown export against Champions Reg M-B or Reg M-C rules.

parse_and_verify

Damage & speed questions

Ranges, survival, speed tiers, best move, matchups, EV thresholds.

vgc calc · survives · outspeeds · ko-evs

06 · Evidence ledger

What has been measured, and what hasn't passed yet.

Every number here comes from a document in the repo, labelled by when it was measured. The headline: about 98–99% of unseen real Reg M-C battles replay turn for turn exactly as on Showdown. That is good enough for local self-play. The original phase gates are still formally open, and Showdown stays the final evaluator.

Current Accuracy report, 2026-10-11

  • 97.9%of 1,300 battles in a sealed sample (run once, never tuned on) replay identically on Showdown's own PRNG. 98.9% with every random outcome forced to match.
  • 99.76%per-turn agreement on that sealed sample on Showdown's PRNG; 99.88% under forced RNG.
  • 99.7–99.9%clean battles on the three tuning and held-out samples. These were partly fitted, so read the sealed number as the honest one.
  • ~20real mechanic bugs fixed in that round (Alluring Voice confusion, Powder immunity order, Helping Hand timing and more), on top of the draw-order fixes.

Read the accuracy report

Current Release 0.5.0

  • opt-inally targets in legal_choices, checked against Showdown on 7 positions. Off by default.
  • fixesfor niche doubles mechanics: Mean Look, Block, Jaw Lock and Fairy Lock traps; Parting Shot and Pollen Puff on an ally; Quick Guard with Triage and Gale Wings; Utility Umbrella; Weakness Policy.
  • serdefix: to_bytes / from_bytes round-trips again.

Release notes · Fix log

Current Solver budgets, on main since 0.5.0

  • 0.5 mslargest overrun of the 1.5 s served budget on 1,031 real endgame positions. The deadline and replay_budget are checked before every simulated turn on every solver path; panics are caught.
  • 0.85 → 0.24largest value error against a brute-force reference, after a frontier-merge bug that made results depend on enumeration order was fixed.
  • best-so-faran over-budget solve returns a well-formed result flagged truncated with a reason, never nothing.

Solver coverage map

Not yet passed Open items

  • +0.12 ppwin-rate change, i.e. no measurable lift, from letting the solver override a bot's play model in endgames, in pre-registered tests in a downstream bot project (95% CI −1.91 to +1.92). The solver gives correct, bounded answers; it does not by itself make a bot win more.
  • 1–2%of unseen battles still diverge, mostly in rare interactions: added types (Forest's Curse), batched double switches, contact-ability procs, some terrain and Life Orb edge cases.
  • —Speed claims are withheld. No representative performance workload has been agreed and measured.

Historical 2026-09 accuracy study

Only 16.5% of real Reg M-C battles were fully clean under forced RNG then. Most divergences were deterministic mechanics bugs, not RNG, and the PRNG port was already bit-exact. That set the fix-one-mechanic-at-a-time workflow. Read the study

Phase gates

Phase gate status
PhaseGateStatus
1 · SkeletonWorkspace, codegen, Python import, corpus reportDone
2 · Top mechanics1,000-replay differential vs Showdown, turn-agreement thresholdsOpen
3 · Long tail≥95% turn agreement on a ≥10k-battle corpus; Tera; ChampionsOpen
4 · Perf & integrationReplace Showdown in one consumer rollout path, with a measured ≥10× speedupAwaiting decision

The Phase 2 and 3 gates name a replay corpus that isn't available, so they haven't been formally run. The 1,300-battle samples above are the closest measurement, not a substitute. Tera and the Champions overlay landed ahead of the Phase 3 gate; phase numbers follow gates, not features. See PLAN.md and the finish-line checklist.

07 · Roadmap

Modern doubles first. Every other generation later.

The long-term goal is one shared codebase across all generations and formats. Today, only Gen 9 doubles is being built.

Implemented

  • Gen 9 doubles core: targeting, spread damage, redirection, Protect family, weather, terrain, screens, Tailwind, Trick Room
  • Terastallization
  • Champions overlay: Mega Evolution, Stat Points, Reg M-B / M-C rules
  • Python bindings: build, step, clone, snapshot, legal choices (ally targets opt-in)
  • Positions from real replays and boards, with a Reg M-C usage prior
  • Showdown protocol output and a local simulate-battle stdio sim
  • chance frontier (record/replay wrapper)
  • ps-rng Showdown PRNG mode
  • Endgame solvers (exact, anytime, sampled worlds) with hard time budgets
  • Damage-calc CLI, golden & conformance harnesses

In progress

  • The remaining 1–2% of divergent battles: added types, batched double switches, contact-ability procs
  • Re-measuring accuracy on fresh samples after each engine change
  • A build that doesn't need a separate dex download, then published wheels
  • Agreeing a representative workload for the performance gate

Future

  • Native per-site chance branching with copy-on-write state
  • Earlier generations, newest first (Gen 8 → 1), as negative deltas on shared code
  • A stable C ABI
  • Possibly a WASM build for browser tools

08 · Straight answers

Questions worth asking first.

Is it accurate enough to replace Showdown?

For local self-play, the 2026-10-11 accuracy report says yes: about 98–99% of unseen real Reg M-C battles replay turn for turn identically. As the final evaluator, not yet. The remaining errors cluster in rare interactions, and a policy trained only on engine self-play could learn to exploit one. Keep a periodic check against Showdown, and treat Showdown as ground truth.

Will the endgame solver make my bot win more?

Not on the evidence so far. In pre-registered tests in a downstream bot project, overriding a trained play model with the solver's endgame choice showed no measurable win-rate change. What the solver does guarantee is a correct answer for the position it was given, inside a hard time budget.

How much faster is it?

The design targets order-of-magnitude speedups for rollouts, and that is what the Phase 4 gate will measure. No speedup is claimed here until a representative workload is agreed and re-measured.

Which generations and formats work?

Gen 9 doubles: the standard Tera-legal rules and the Champions overlay (Reg M-B, Reg M-C). Earlier generations have not been started. Singles exists as a format value, but doubles is the target.

Can I make it match Showdown's random numbers?

Yes, for testing. Build with ps-rng and seed a battle with a Showdown seed string. Draw order is aligned at the call sites the engine can mirror, with a trace of every draw.

Can I pip install it?

Not yet. There is no PyPI package or GitHub release; build the wheel from source as in the quickstart.

What's the license?

MIT or Apache-2.0, at your option. Built binaries include data from @pkmn/dex and Pokémon Showdown (both MIT); see NOTICE and ATTRIBUTION.md.

Branch a turn. Check it against Showdown. Repeat.