zig-chess/docs/uci-engine-architecture-plan.md

12 KiB

UCI Engine Architecture Plan

Goal

Support two families of executables:

  1. GUI/UI executable

    • Renders and tracks games.
    • Lets humans play/edit/replay games.
    • Can let people play against bots.
    • Can spawn bot/engine executables as subprocesses on demand.
    • Speaks UCI to engine subprocesses.
    • Can optionally use a user-selected external UCI engine, such as Stockfish, for playback analysis.
    • Eventually stores games in a database.
  2. Bot/engine executables

    • No GUI.
    • Implement chess engine logic.
    • Speak UCI over stdin/stdout.
    • Can be launched by the GUI.
    • Can also run as long-lived processes connected to external services such as lichess.org through a service adapter.

Important terminology

In UCI terminology, the engine is the side that implements the UCI command loop over stdin/stdout.

The GUI/app is the UCI controller/client:

GUI/controller
├── starts engine process
├── writes UCI commands to engine stdin
├── reads UCI responses from engine stdout
└── applies bestmove to the current game

The engine executable is the UCI engine/server-like process:

Engine
├── reads commands from stdin
├── maintains/searches position
├── writes info/bestmove responses to stdout
└── exits on quit

Avoid naming the GUI-side wrapper a "server" in code. Prefer names like:

UciEngineClient
UciController
EngineProcess

Desired long-term process architecture

                  ┌─────────────────────┐
                  │ zig-chess-gui       │
                  │                     │
                  │ - Vulkan UI         │
                  │ - game tracking     │
                  │ - human input       │
                  │ - playback          │
                  │ - database later    │
                  │ - UCI client        │
                  └──────────┬──────────┘
                             │ UCI stdin/stdout
                             ▼
                  ┌─────────────────────┐
                  │ zig-chess-engine-*  │
                  │                     │
                  │ - evaluation        │
                  │ - search            │
                  │ - move selection    │
                  │ - UCI command loop  │
                  └─────────────────────┘

Future external-service architecture:

                  ┌─────────────────────┐
                  │ lichess-adapter     │
                  │                     │
                  │ - lichess API       │
                  │ - account/game I/O  │
                  │ - UCI client        │
                  └──────────┬──────────┘
                             │ UCI stdin/stdout or socket/process bridge
                             ▼
                  ┌─────────────────────┐
                  │ zig-chess-engine-*  │
                  └─────────────────────┘

Key design principle:

GUI ───────────────┐
                   ├── UCI ── engine executable
Lichess adapter ───┘

The engine should not know whether it is being used by the GUI, a test harness, or a lichess adapter.

Suggested source layout

Possible future layout:

src/
├── chess/              // board/game/FEN/PGN/legal moves
├── main.zig            // current GUI entry point, or later src/gui/main.zig
├── uci/
│   ├── protocol.zig    // shared UCI parse/format types
│   ├── client.zig      // GUI/service side: controls engine process
│   └── server.zig      // engine side: stdin/stdout command loop
├── engine/
│   ├── engine.zig      // common engine interface
│   ├── eval.zig        // static evaluation
│   ├── search.zig      // search algorithms
│   └── perft.zig       // move-generator validation tooling
└── bots/
    ├── random.zig      // random/legal-move bot executable
    ├── material.zig    // material-eval bot executable
    └── search.zig      // stronger search bot executable

This layout can evolve. The important boundary is that engine executables communicate with outside controllers through UCI.

Build targets

Eventually build.zig should produce multiple executables, for example:

zig-chess-gui
zig-chess-random-bot
zig-chess-material-bot
zig-chess-search-bot

The GUI target links rendering/UI code.

The engine targets should avoid linking Vulkan/windowing code.

Analysis engine policy

The GUI should support an optional external UCI engine for real-time analysis during game playback.

Primary intended use case:

Playback mode
├── user steps through game
├── GUI sends current position to analysis engine
├── analysis engine returns eval/PV/best line
└── GUI displays analysis to the user

This analysis engine is separate from project bot engines:

  • It is for user-facing analysis only.
  • It must not be used by project bot engines as their search/evaluation implementation.
  • Project bots should have their own evaluation/search logic.
  • The GUI should treat the analysis engine as just another external UCI process.

Stockfish licensing policy

Stockfish is GPL-3.0 licensed. Because this project is intended to be MIT licensed, do not bundle Stockfish directly into the executable or source distribution by default.

Preferred approaches:

  1. User-provided engine path

    • User installs Stockfish or another UCI engine separately.
    • GUI stores/configures the path.
    • GUI launches it as an external process.
  2. Optional download flow

    • GUI can offer to download Stockfish or guide the user through downloading it.
    • The download should be explicit and optional.
    • The UI should clearly identify the engine and its license before download/use.
    • Keep downloaded engine files outside the MIT-licensed source tree/release bundle unless licensing obligations are intentionally handled.
  3. Generic UCI analysis engine support

    • Do not hard-code Stockfish as the only option.
    • Any compatible UCI engine path should work.

Analysis engine UI/config needs

Eventually the GUI should provide:

  • analysis engine executable path
  • enable/disable analysis
  • analysis depth or movetime
  • optional thread/hash settings via UCI options
  • current eval display
  • principal variation display
  • best move display
  • start/stop analysis when stepping through playback

Suggested app-side abstraction:

AnalysisEngine
├── engine_process / UciEngineClient
├── executable_path
├── enabled
├── depth or movetime limit
├── latest_score
├── latest_pv
└── latest_bestmove

UCI protocol responsibilities

GUI/client side

The GUI should be able to:

  • Spawn an engine executable.
  • Send UCI commands.
  • Read engine output asynchronously or incrementally.
  • Track engine readiness.
  • Track available engine options.
  • Send current game position.
  • Request a move/search.
  • Stop a search.
  • Shut down the engine process cleanly.

Common commands sent by GUI:

uci
isready
ucinewgame
position startpos
position startpos moves e2e4 e7e5
position fen <fen fields> moves ...
go depth 5
go movetime 1000
stop
quit

Common responses parsed by GUI:

id name <name>
id author <author>
option name <name> type <type> ...
uciok
readyok
info depth 3 score cp 20 nodes 1234 time 10 pv e2e4 e7e5
bestmove e2e4
bestmove e2e4 ponder e7e5

Engine/server side

Each engine executable should:

  • Read one line at a time from stdin.
  • Parse UCI commands.
  • Maintain an internal position.
  • Search when given go.
  • Print bestmove ... when done.
  • Print info ... optionally during search.
  • Respond to stop promptly.
  • Exit on quit.

First milestone: minimal UCI engine executable

Implement an engine executable that supports only:

Input:

uci
isready
quit

Output:

id name ZigChess Random Bot
id author WayfinderAK
uciok
readyok

No search or move generation required yet.

Purpose:

  • Prove executable separation.
  • Prove stdin/stdout loop.
  • Prove GUI/test harness can launch and communicate with engine.

Second milestone: GUI-side engine process wrapper

Implement a UI/application-side wrapper that can:

  • Start an engine executable by path.
  • Send uci.
  • Wait for uciok.
  • Send isready.
  • Wait for readyok.
  • Send quit on shutdown.

Suggested name:

UciEngineClient

or:

EngineProcess

This code belongs on the GUI/controller side, not inside the engine bot.

Third milestone: position and bestmove

Add UCI support for:

position startpos
position startpos moves e2e4 e7e5
position fen <fen> moves ...
go depth 1
bestmove <move>

For the first engine, go depth 1 can simply pick the first legal move or a random legal move.

Move format is UCI long algebraic coordinate format:

e2e4
e7e8q
e1g1

This is different from SAN/PGN notation.

Fourth milestone: reusable protocol module

Create shared parse/format helpers for UCI text:

src/uci/protocol.zig

Potential responsibilities:

  • Parse GUI-to-engine commands.
  • Parse engine-to-GUI responses.
  • Format moves as UCI coordinate moves.
  • Parse UCI coordinate moves into from/to/promotion.
  • Format position ... commands from a Game move list.

Keep protocol parsing separate from subprocess management and separate from search/evaluation.

Fifth milestone: stronger engines

Once the protocol boundary works:

  • random legal move engine
  • material evaluation engine
  • shallow search engine
  • alpha-beta search engine
  • improved move ordering
  • transposition table
  • time management

Each stronger engine can be its own executable or selected by options/config.

Future lichess adapter

Do not put lichess-specific code inside the engine.

Instead, later create a separate adapter/service that:

  • talks to lichess.org APIs
  • manages authentication/account events
  • receives games/moves from lichess
  • launches or connects to an engine executable
  • sends positions/search commands via UCI
  • sends engine moves back to lichess

This keeps engines reusable by:

  • GUI
  • lichess adapter
  • local match runner
  • tournament harness
  • tests/benchmarks

Future playback analysis milestone

After the generic UCI client exists, add analysis support to playback mode:

  1. Let the user configure an external UCI engine path.
  2. Start that engine as an analysis process.
  3. Send uci / isready handshake.
  4. When playback cursor changes, send:
position fen <current playback FEN>
go depth <n>

or:

position fen <current playback FEN>
go movetime <ms>
  1. Parse info lines for score and PV.
  2. Display the latest analysis in the UI.
  3. Send stop when the user moves to another ply or disables analysis.
  4. Send quit when closing the app or changing engine path.

Keep this separate from bot-vs-human play. The analysis engine is an advisor for the user, not the implementation of project bots.

References

Primary UCI reference:

  • Stefan Meyer-Kahlen, Universal Chess Interface (UCI) protocol, April 2006. Commonly distributed as uci.txt and mirrored by chess-programming resources. Covers uci, isready, position, go, stop, quit, info, bestmove, and engine options.

Stockfish licensing/reference:

Useful related notation distinction:

  • UCI moves are coordinate moves such as e2e4 or e7e8q.
  • PGN/SAN moves are display/game-record notation such as e4, Nf3, O-O, Qxf7#.
  • The GUI move list should display PGN/SAN.
  • Engine communication should use UCI coordinate moves.