Source Code Guide
Pikafish consists of modules with distinct jobs. Search decides which lines deserve attention, evaluation scores positions, the position module handles moves, undoing moves, and rules, and the runtime layer coordinates them while exchanging commands and results with the GUI.
This series is based on official Pikafish commit 1c66b9b. Each page links to the relevant source. A shared algorithm name does not mean that other engines or versions implement it in exactly the same way.
Start with Your Question
| What do you want to understand? | Start here |
|---|---|
| How does the engine find good moves among so many possibilities? | Search Algorithms: ordering, caching, pruning, reductions, and extensions. |
What does evaluate.cpp do, and how does NNUE score a position? | Position Evaluation and NNUE: input features, incremental computation, network output, and evaluation corrections. |
| How is the board stored, and how are moves, undoing moves, and rules handled? | Position State and Rules: bitboards, state, hash keys, legality, and move history. |
| How does a GUI start the engine, and how do threads and memory work? | Engine Runtime and Communication: command handling, result output, resource allocation, and diagnostics. |
To adjust software settings, start with UCI Options. If you are writing a GUI or another program that connects to the engine, also read UCI Protocol.
The Stockfish NNUE Documentation provides more detailed general background. For the current Pikafish implementation, read the evaluation page above first.
Follow a “Start Analysis” Request
- Prepare to run.
main.cppinitializes attack tables and data needed by positions, then enters the UCI command loop. - Receive a position. The UCI layer parses
position. The runtime layer sets up the position, then plays the suppliedmovesone by one and saves their states. A FEN of the current board alone cannot restore the full earlier move history. - Receive a search request.
gocarries limits such as time and depth.Enginechecks the network and passes the request to the search threads. - Explore continuations. Search uses move generation and legality checks, makes moves, and recurses. It calls evaluation when it needs an estimate of a position. After finishing a branch, it undoes the move to return to the previous position.
- Report results. The runtime layer passes search information through callbacks. The UCI layer outputs
infoand the finalbestmove, and the GUI decides how to display them.
Here, Engine is the C++ class that coordinates this work, not another independent engine. go starts a background search; the command loop must remain able to receive commands such as stop.
Sources: program entry point, the Engine interface, starting searches and setting positions. For the full command flow, see Engine Runtime and Communication.
File Map: Where to Look
You do not have to read every file in order. Find the relevant responsibility first, then follow the function calls.
Search and Evaluation
| File | Main responsibility |
|---|---|
search.cpp | Iterative deepening, PVS, pruning, depth adjustments, quiescence search, and handling search results. |
movepick.cpp, history.h | The order in which candidate moves are picked, and history statistics collected during search. |
evaluate.cpp | Calling NNUE and adjusting its result into a static evaluation used by search. |
nnue/network.cpp and nnue/nnue_architecture.h | Reading and writing networks, the inference entry point, and connections between network layers. |
nnue/features/, nnue/nnue_accumulator.cpp | Converting positions into network inputs and reusing computation between nearby positions. |
Positions, Moves, and Rules
| File | Main responsibility |
|---|---|
types.h, bitboard.h | Basic definitions for squares, pieces, moves, and more, plus bitboard operations. |
attacks.cpp, attacks.h | Initializing and querying attack tables, including chariot and cannon lines, horse legs, elephant eyes, and other attack relationships. |
movegen.cpp | Generating candidates as needed: captures, non-captures, evasions, and so on. |
position.h, position.cpp | Storing and updating positions, checking king safety, undoing moves, estimating exchanges, and handling history-dependent rules. |
Runtime, Communication, and Tools
| File | Main responsibility |
|---|---|
main.cpp, engine.cpp | Program entry and coordination of positions, networks, threads, options, and other resources. |
uci.cpp, ucioption.cpp | Receiving commands, parsing parameters, outputting results, and handling options. |
score.cpp | Converting internal evaluations to scores for external use; it does not choose the best move. |
thread.cpp, timeman.cpp, tt.cpp | Threads, time budgets, and transposition-table storage. |
numa.h, memory.cpp | Hardware placement of threads and memory, memory allocation, and related support. |
perft.h, benchmark.cpp | Enumerating legal move sequences or running preset test workloads; these check different things. |
Makefile, universal/ | Build configuration and runtime implementation selection in universal packages. |
This map groups files by reading topic; it does not imply that calls only go one way between modules. Search, for example, accesses positions, evaluation, the transposition table, and history tables, while the runtime layer prepares these resources.
Distinctions to Keep in Mind
Search scores, static evaluations, and displayed scores belong to different stages. A value returned by evaluate() is not necessarily the final score shown by the GUI. Nor should a cp output be read directly as a win probability or Elo rating. See the evaluation process and score output.
The current position and its history are different things too. Identical piece placement may come with a different side to move, move-limit count, or earlier sequence of checks and chases. When reading cache and rule code, check which state information each uses.
Verify names and comments against the actual calls. Pikafish retains the Stockfish namespace and some upstream terminology. A familiar name does not mean the Xiangqi version uses every chess mechanism. Check the conditions under which the function is called and which branch actually runs.
Finding a method does not establish fixed parameters. Pruning margins, network architecture, and build choices can all change. The pinned commit links keep the explanations tied to the code; newer versions need to be checked again.
How to Check a Change
Different checks answer different questions:
- Builds and starts successfully: shows that this build path basically works, but does not prove that rules and search are correct.
- Perft: checks whether candidate generation, legality filtering, and making and undoing moves together produce the expected counts. It does not replace history-dependent tests for perpetual check, perpetual chase, and similar rules.
- Rule and position cases: use a clearly specified board and complete move history to check terminal positions, scores, or rule handling.
- Benchmark: helps observe node counts, time, and other measurements on preset workloads. Except for the change being tested, keep conditions such as engine version, network, threads, hardware, and parameters the same.
- Game testing: directly measures how a change affects playing results, requiring enough games and consistent conditions. Greater speed, deeper search, or solving one position cannot alone establish greater overall playing strength.
For more detail, see Engine Runtime and Communication. For an introduction to game testing, see How to Test Engines.
