A UCI chess engine written from scratch in Zig.
Magic bitboards, alpha-beta with modern pruning and reductions, Lazy SMP, embedded NNUE trained via Bullet.
Requires Zig 0.15.2.
zig build -Doptimize=ReleaseFast
./zig-out/bin/sykoraEngine is compatible with any UCI GUI (Arena, Cutechess, etc.)
Sykora is tested by CCRL. Current entries:
| Version | CCRL Rating | Rank | Elo vs 0.1.0 |
|---|---|---|---|
Sykora 0.2.2 64-bit |
3240 |
163 |
+872 |
Sykora 0.2.1 64-bit |
N/A | N/A | N/A |
Sykora 0.1.0 64-bit |
2368 |
423 |
baseline |
Engine Core: bitboards, magic move generation, Zobrist hashing
- Bitboard-based board representation with fast occupancy/piece set operations.
- Precomputed attack tables for king/knight/pawn moves.
- Magic bitboards for rook and bishop attacks (queen attacks via composition).
- Full legal move generation including castling, en passant, promotions, and check legality filtering.
- Incremental make/unmake move pipeline with Zobrist hashing.
- Polyglot-compatible en-passant hash handling.
Search: PVS, aspiration windows, full pruning/reduction stack
- Negamax alpha-beta search with iterative deepening.
- Aspiration windows around prior iteration score.
- Principal Variation Search (PVS).
- Transposition table with aging and depth-preferred replacement.
- Move ordering pipeline:
- TT move
- SEE-scored captures
- Killer moves
- History and counter-move scoring for quiets
- Deferred bad captures
- Pruning and reduction framework:
- Mate distance pruning
- Check extension
- Null-move pruning with verification at higher depths
- Reverse futility pruning
- Futility pruning
- Razoring
- Late Move Reductions (LMR)
- Late Move Pruning (LMP)
- Quiescence search with:
- Check evasions when in check
- Delta pruning
- SEE-based pruning of clearly losing captures
- Repetition detection.
- 50-move-rule handling.
- Time management for clocked play.
- Evaluation cache for expensive eval paths.
Evaluation: NNUE (default) with classical fallback
- NNUE evaluation (default, embedded in binary):
SYKNNUE3andSYKNNUE4network loading- Legacy
768 -> Nx2 -> 1and mirrored king-bucketed sparse-input nets - SCReLU activation with incremental accumulators during search
- Trained on high-depth self-play data via the Bullet trainer
- King-bucket training path via
nnue/bullet_repo/examples/sykora_bucketed.rs - Blendable with classical eval via
NnueBlend(default:100= pure NNUE)
- Classical handcrafted evaluation (fallback):
- Material and piece-square tables
- Pawn structure terms (isolated/doubled/backward/passed)
- Mobility terms
- King safety and castling terms
- Endgame mop-up/king activity terms
Parallel Search: Lazy SMP with shared TT
- Lazy SMP-style parallel search with helper threads.
- Shared transposition table across threads.
- Best-move voting across main/helper results.
UCI and Developer Commands
- Standard UCI command support (
uci,isready,position,go,stop,setoption,ucinewgame,quit). displayhelper command for board/FEN/hash inspection.perfthelper command with:- Fast node count mode
statsmode (captures, checks, promotions, mates, etc.)dividemode
Tooling: perft, NPS, STS, SPRT, ratings, NNUE pipelines
- Perft and movegen shell tests (
utils/test). - NPS benchmarking (
utils/bench/nps.py). - STS runner (
utils/sts/sts.py). - Engine-vs-engine runners (
utils/match, used internally byutils/history). - Long-term archived experiment history, SPRT, and ratings workflow (
utils/history). - NNUE data prep/training/export pipelines (
utils/nnue,utils/data).
| Option | Type | Default | Description |
|---|---|---|---|
Debug Log File |
string | <empty> |
Path for debug logging |
UseNNUE |
bool | true |
Enable NNUE evaluation (embedded net loads automatically) |
EvalFile |
string | <empty> |
Path to external .sknnue file (overrides embedded net) |
NnueBlend |
int | 100 |
NNUE/classical blend (0 = classical only, 100 = pure NNUE) |
NnueScale |
int | 100 |
NNUE output scaling factor (10..400) |
Threads |
int | 1 |
Search threads (1..64, Lazy SMP) |
Hash |
int | 128 |
Transposition table size in MB (1..4096) |
The activation function (ReLU or SCReLU) is auto-detected from the network file header.
- Zig compiler (
0.15.2currently used for local builds) - Python 3.10+ (for benchmark, STS, match, history, NNUE, and bot utilities)
- A UCI-compatible chess GUI (like Arena, Cutechess, or similar)
For Python tooling, install dependencies as needed:
# Core analysis/benchmark utilities
python -m pip install chessFor NNUE training on a fresh machine, bootstrap the Bullet dependency once:
python3 utils/nnue/bullet/bootstrap.py --build-utilsThe tracked training runner lives under utils/nnue/bullet_runner/; datasets, checkpoints, and Bullet build output remain untracked.
Build a release binary:
zig build -Doptimize=ReleaseFastThe executable lands at zig-out/bin/sykora. The embedded NNUE net (src/net.sknnue) is compiled in.
Run directly, or via the build system:
./zig-out/bin/sykora
zig build run -Doptimize=ReleaseFastTo run the test suite:
zig build -Doptimize=ReleaseFast testTo run perft validation:
utils/test/test_perft_suite.shTo benchmark search speed (NPS):
python utils/bench/nps.py --engine ./zig-out/bin/sykora --depth 10
python utils/bench/nps.py --engine ./zig-out/bin/sykora --movetime-ms 500 --runs 2To run Strategic Test Suite (STS) EPD files (requires python-chess):
python utils/sts/sts.py --epd /path/to/sts --pattern "STS*.epd" --engine ./zig-out/bin/sykora --movetime-ms 300STS is still available as a diagnostic tool, but it is not the recommended promotion signal for NNUE changes.
The canonical engine-vs-engine workflow is the archived history.py flow:
# Initialize ledger folders
python utils/history/history.py init
# Snapshot two builds you want to compare
python utils/history/history.py snapshot --engine ./old_sykora --label "baseline" --engine-id baseline
python utils/history/history.py snapshot --engine ./zig-out/bin/sykora --label "candidate" --engine-id candidate
# Archived fixed-game selfplay
python utils/history/history.py list-engines
python utils/history/history.py selfplay baseline candidate --games 120 --movetime-ms 200
# Archived SPRT
python utils/history/history.py sprt baseline candidate \
--elo0 -30 --elo1 30 \
--games-per-batch 12 --max-games 360 \
--movetime-ms 80 --max-plies 220 \
--threads 1 --hash-mb 64 --shuffle-openings
# Ratings and graph data are built from archived selfplay only
python utils/history/history.py ratings --plot
python utils/history/history.py network --top-n 12 --min-games 10 --min-edge-games 2
# Optional: diagnostic STS run for a snapshot
python utils/history/history.py sts candidate --movetime-ms 100Every archived selfplay/SPRT run writes settings, summary JSON, stdout/stderr logs, and reproducibility metadata under history/.
The Release SPRT workflow (.github/workflows/sprt.yml) uses the same archived history.py sprt path.
Low-level runners under utils/match/ remain available, but they are implementation details rather than the recommended user/agent entrypoints.
See history/README.md for folder schema and the archived workflow.
Sykora supports both legacy 768 -> Nx2 -> 1 nets and mirrored king-bucketed nets with dual-perspective accumulator updates and SCReLU activation. The engine can load both SYKNNUE3 and SYKNNUE4 files.
- The embedded net (
src/net.sknnue) is compiled into the binary and loaded automatically at startup. - NNUE is enabled by default (
UseNNUE = true,NnueBlend = 100,NnueScale = 100). - The activation function is stored in the network file header and auto-detected on load.
- To use a different net, set
EvalFileto the path of an external.sknnuefile. NnueScalescales the NNUE score before it is fed into the search.
For exact file-format details, see specs/syknnue4_spec.md and src/nnue.zig.
Training uses the Bullet trainer. Helper scripts for bootstrap, training, gating, and export live under utils/nnue/bullet/.
Using binpack data:
python utils/nnue/bullet/train_cuda_longrun.py \
--dataset data/training.binpack \
--data-format binpack \
--bullet-repo nnue/bullet_repo \
--output-root nnue/models/bullet \
--network-format syk3 \
--hidden 256 --end-superbatch 320 --threads 8Using BulletFormat .data files:
python utils/nnue/bullet/train_cuda_longrun.py \
--dataset nnue/data/bullet/train/train_main.data \
--bullet-repo nnue/bullet_repo \
--output-root nnue/models/bullet \
--network-format syk3 \
--hidden 256 --end-superbatch 320 --threads 8Multiple datasets can be passed space-separated:
python utils/nnue/bullet/train_cuda_longrun.py \
--dataset data/set1.binpack data/set2.binpack \
--data-format binpack \
...Resuming from a checkpoint:
python utils/nnue/bullet/train_cuda_longrun.py \
--dataset data/test80.binpack \
--data-format binpack \
--resume nnue/models/bullet/<run_id>/checkpoints/<checkpoint>/raw.bin \
--start-superbatch 161 --end-superbatch 320 \
...Training a SYKNNUE4 baseline:
python utils/nnue/bullet/train_cuda_longrun.py \
--dataset data/training.binpack \
--data-format binpack \
--network-format syk4 \
--bucket-layout sykora16 \
--hidden 1536 \
--dense-l1 16 --dense-l2 32 \
--end-superbatch 320 --threads 8Sykora can generate its own training data via the gensfen command:
# Generate Bullet-format training data
./zig-out/bin/sykora gensfen --output output.data --games 100000 --depth 7Export a SYKNNUE4 checkpoint:
python utils/nnue/bullet/checkpoint_raw_to_npz.py \
--input nnue/models/bullet/<run_id>/checkpoints/<checkpoint> \
--output checkpoint_syk4.npz
python utils/nnue/bullet/export_npz_to_syk4.py \
--input checkpoint_syk4.npz \
--output-net output.sknnueTo update the embedded net in the binary:
cp output.sknnue src/net.sknnue
zig build -Doptimize=ReleaseFastpython utils/nnue/bullet/gate_checkpoints.py \
--checkpoints-dir nnue/models/bullet/<run_id>/checkpoints \
--engine ./zig-out/bin/sykora \
--blend 100 --nnue-scale 100 \
--selfplay-games 80 --selfplay-movetime-ms 120 --selfplay-top-k 3 \
--threads 1 --hash-mb 64 \
--min-elo 0 --max-p-value 0.25 \
--promote-to nnue/syk_nnue_best.sknnueThis gate now evaluates recent checkpoints by selfplay only. STS is intentionally not part of the checkpoint promotion path.
SYKNNUE4 design spec: specs/syknnue4_spec.md.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License. See the LICENSE file for details.