Skip to main content

Misty

A fog-of-war chess engine.

Fog of war (dark) chess is chess where you only see the squares your own pieces could move to. You never see your opponent's moves, only their consequences: a piece of yours disappears, a square you watch changes. Playing it well means reasoning over every position consistent with what you've observed so far, and mid-game that set can run into the millions.

Misty follows the architecture of Obscuro (Zhang & Sandholm, ICLR 2026), the first superhuman fog-of-war chess AI:

concern approach
belief exact enumeration of all positions consistent with the observation history
search scope knowledge-limited subgames over sampled information sets
search one-sided growing-tree CFR with PCFR+
evaluation Stockfish at depth 1 on subgame leaves
commit purification plus a resolve gadget at move selection

The hot paths (belief updates, visibility, observation derivation) run in Rust, measured at roughly 500x over the Python reference on the enumeration loop. The Python implementations stay maintained as the oracle: the test suite replays real games and requires Rust and Python to agree byte-for-byte at every ply.

Misty plays live at mistboard.com. Strength is gauged against humans; engine-vs-engine results only gate development.

Install

pip install misty-chess
brew install stockfish   # or: apt-get install stockfish

The package imports as fow_chess. Python 3.11+. Stockfish is invoked as a subprocess for leaf evaluation and must be on PATH (or set FOW_STOCKFISH).

The PyPI package is pure Python and runs everywhere; results are identical to the accelerated build but the belief hot path is ~500x slower, and the engine logs a warning saying so. For real strength at real time controls, build the Rust extension (binary wheels are planned):

git clone <repo> && cd misty
pip install -e '.[dev]'
cd fow_rust && maturin develop --release

Play a game

from fow_chess import EngineV2Strategy, RandomStrategy
from fow_chess.selfplay import play_game

result = play_game(
    EngineV2Strategy(seed=0, time_budget_seconds=5.0),
    RandomStrategy(seed=1),
)
print(result.winner, result.end_reason, result.plies)

Strength configurations are named profiles in fow_chess.engine_profile.PROFILES. The served engine is a named profile, so a bakeoff and production run identical configuration by construction.

Analyze a game

The engine ships with a post-game analyzer built for imperfect information. TruthGrader runs Stockfish at a fixed depth on the true board of a finished game; analyze_game_deep replays the game through the engine's exact belief and its own search, then classifies each mistake three ways: the true position was missing from the belief, it was absent from the sampled search set, or it was seen and the engine still chose badly. That split (belief / sample / decision) says which lever would have prevented the mistake.

import chess
from fow_chess import TruthGrader, analyze_game_deep

moves = [chess.Move.from_uci(u) for u in ("f2f3", "e7e5", "g2g4")]
with TruthGrader(depth=12) as grader:
    rows = analyze_game_deep(moves, chess.WHITE, grader=grader)
for r in rows:
    if r.verdict:
        print(r.ply, r.uci, f"-{r.grade.cp_loss}cp", r.verdict)

Serving

The engine speaks a JSON protocol over stdio, documented in docs/protocol/engine-protocol.md. scripts/live_move_worker.py is the long-lived worker; scripts/live_move_runner.py is the one-shot fallback. The server sends redacted observations only. The engine never receives hidden opponent state, and post-game full-information analysis is a separate request type, never a flag on the live one.

Development

pip install -e '.[dev]'
cd fow_rust && maturin develop --release && cd ..
pytest -n auto                                  # full suite, ~40s
cargo test --manifest-path fow_rust/Cargo.toml  # native parity pins

FOW_REQUIRE_RUST=1 makes a missing or stale Rust extension fatal instead of silently falling back to the slow Python path. Every architectural change ships behind a flag defaulting to prior behavior, then gets validated by bakeoff before the default flips.

License

GPL-3.0-or-later.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

misty_chess-0.1.0.tar.gz (379.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

misty_chess-0.1.0-py3-none-any.whl (272.5 kB view details)

Uploaded Python 3

File details

Details for the file misty_chess-0.1.0.tar.gz.

File metadata

  • Download URL: misty_chess-0.1.0.tar.gz
  • Upload date:
  • Size: 379.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for misty_chess-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bcfd1d60aeb4550d291d1655fa6b8830b9ce0960069d6d7dd5438a3f8ee0ac7a
MD5 eaef6f1dbe503f6aca9d3068e16ed5a5
BLAKE2b-256 b64654132411f63aecc9747eaf0c8a199d0339d54a1ac82ecf037ea8d4098596

See more details on using hashes here.

File details

Details for the file misty_chess-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: misty_chess-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 272.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for misty_chess-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 06a7987513c15815853d79a1742a6977fa4fcf16e911c82655aac9ac66fbca80
MD5 fbf7979f8d665b3e58d0314c0f5629e2
BLAKE2b-256 2db80ddc6a111659a6475709384d043c898ca8b31ee544633233213d024efabe

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page