Engine Runtime and Communication
The GUI handles the board, buttons, and clocks. The engine receives positions, calculates, and returns results. They communicate through text commands, one line at a time. Clicking “Analyze” involves more than calling a search function: the position must be set, threads started, updates reported, and completion signaled.
This page was checked against official Pikafish commit 1c66b9b. It explains how these parts work together. For settings, see UCI Options; for how the engine searches, see Search Algorithms.
From Startup to a Returned Move
main.cpp initializes attack tables and data needed by positions, then creates UCIEngine and enters the command loop. uci.cpp reads and writes the protocol. Engine manages the position, options, threads, transposition table, and network, while worker threads perform the search.
| Command or output | Role in this search |
|---|---|
uci → uciok | Reports the engine name and supported options, completing the handshake. |
setoption | Changes an option; some settings also reallocate threads or memory. |
position ... | Sets a starting position, then plays the moves after moves to reach the target. |
go ... | Reads time, depth, or node limits, checks the network, prepares the root position, and starts search. |
info ... | Reports depth, scores, node counts, and principal variations during search. |
stop | Requests that this search end; results follow after the workers finish. |
bestmove ... | Announces the final move from this search; it does not mean the GUI has played it yet. |
After starting search, the command loop can still receive stop. stop sets a stop flag rather than forcibly killing threads. The main search thread waits for the other workers to finish, then reports bestmove through a callback. quit requests a stop and exits the program.
A bestmove line may also include ponder, the expected reply from the opponent. Actual background thinking starts when the GUI sends go ponder. If the opponent plays the predicted move, ponderhit switches it to normal thinking. An expected reply in the output alone does not mean the engine has automatically begun a background search.
Sources: startup, command dispatch, starting and finishing search.
Positions, History, and Readiness: More Than the Board
Each position command rebuilds the position state. The supplied moves are validated and played one by one, leaving a history chain. Sending only the final FEN cannot restore the earlier moves, which can affect repetition, perpetual-check, and perpetual-chase adjudication.
ucinewgame clears the transposition table and search history; it does not reset the board to the initial position. A new position still needs a position command. In the current code, an unparseable position or illegal move in the supplied history produces a CRITICAL ERROR and exits. Programs connecting to the engine should retain the error message.
readyok does not mean “search has finished.” The current isready handler replies directly while search may continue. To end a running search, send stop and wait for bestmove. End search before changing options too, because setoption handling waits for the current search to finish. Option code checks types and value ranges before applying updates.
Sources: rebuilding the position and history, waiting before option changes and handling position errors, option validation.
Threads and Memory: Share Useful Information, Search Separate Branches
Workers keep their own search positions and progress, with their own root states prepared before starting. Earlier position history is shared read-only. Threads in the same engine instance share the transposition table; some history statistics are organized by NUMA node, while others remain local to each thread. Threads are not assigned one piece each, and each extra thread does not create another full copy of Hash.
NUMA means that accessing nearby and distant memory can have different costs on a multicore machine. The relevant code places threads and network copies accordingly. Memory code also provides aligned allocation, attempts to use large pages, and fallbacks. These mechanisms improve data access; they do not change Xiangqi movement rules.
Thread count affects total memory use, but Hash only sets the transposition-table budget. It is not the process's total memory budget. Networks, history tables, thread stacks, and other data also take space. Threads duplicate some work, so doubling them does not double playing strength.
Time management budgets search from remaining time, increments, and other inputs, then adjusts as search proceeds. See Threads and Time Allocation.
Sources: preparing threads and root states, shared and thread-local data, memory allocation.
cp, mate, and wdl: Three Different Meanings
Raw UCI search scores are from the perspective of the side to move in the root position. Positive values favor that side. A GUI may convert them to Red's perspective, so keep this distinction in mind when reading logs.
| Output | How to interpret it |
|---|---|
score cp 100 | An ordinary evaluation converted to the display scale; it is neither a 100% win probability nor a 100 Elo advantage. |
score mate 3 | Distance to a decisive result using UCI's move-count convention. Positive means a win for this side; negative means a loss. |
wdl 300 600 100 | Model estimates for win/draw/loss, in parts per thousand. This example means 30%/60%/10%. |
Ordinary scores are currently converted with round(100 × v / a), where v is the internal score and a varies with the material on the board. Thus, 100 cp does not mechanically mean a fixed advantage of one pawn. WDL comes from a fitted model, and the three values sum to 1000. They are not three classes output directly by NNUE, nor guaranteed outcomes of this game.
Internal decisive scores are first converted to remaining plies, then to mate moves. For example, a positive distance of three plies is displayed as mate 2. Xiangqi stalemate losses and some rule-based losses also use these decisive scores, so mate does not necessarily mean that the game can end only by checkmate. If lowerbound or upperbound is present, interpret the score as a bound.
For how evaluation feeds into search, see Position Evaluation and NNUE. Sources: score classification, cp/mate/WDL conversion, root-search output, decisive rule scores.
Runtime Checks: What Each Tool Measures
Use these debug commands after search has stopped. Some GUIs may not provide a way to enter them directly.
| Tool | Purpose and limits |
|---|---|
d | Shows the position actually held by the engine, helping check whether it matches the GUI. |
eval | Shows an evaluation breakdown of the current position; it does not perform a new full search. |
compiler | Shows the compiler, build architecture, enabled instruction sets, and related information. |
bench | Runs a set of positions and limits, reporting nodes and time; useful for comparing runtime behavior. |
speedtest | Runs a speed-test sequence after warming up, reporting threads, Hash, time, node speed, and other information. |
go perft 3 | Counts legal move sequences to the specified depth, mainly checking move generation and making and undoing moves. |
Perft recursion does not evaluate positions or stop according to the full perpetual-check/perpetual-chase rules. However, the current Engine::perft() entry point still checks the network. Perft counts paths: different paths reaching the same board must not be merged into one. It cannot replace rule regressions or playing-strength tests.
NPS means search nodes per second. When comparing builds, keep code, network, positions, threads, and test conditions fixed. Different search strategies may do different amounts of work per node, so NPS alone cannot establish which engine is stronger.
Sources: diagnostic commands, bench parameters, speedtest execution and reporting, perft.
Builds and Compression: Separate Responsibilities
Makefile controls the target architecture, compiler, and optimization. Normal builds choose their instruction set at compile time. An x86-64 universal build packages multiple versions in one file and checks CPU features at startup to select an entry point. The current dispatch code also avoids the slower BMI2 path on some older AMD CPUs; it does not simply pick the newest instruction-set name.
To check what this run is using, enter compiler and read Compilation architecture and Compilation settings. After universal dispatch selects a version, that version returns its own build information. It does not benchmark every version at startup to find the fastest.
External compressed networks are read using Zstandard: misc.cpp decompresses the byte stream, then the network loader parses and validates the parameters. The compression dependency reads files, NNUE evaluates positions, and search chooses moves. A compression library in the repository is not another playing-strength algorithm.
Sources: build targets, universal x86 dispatch, compiler information, reading compressed streams, loading external networks. For the complete file map, see Source Code Guide.
