{"id":46302297,"url":"https://github.com/sb2bg/sykora","last_synced_at":"2026-03-04T11:16:04.689Z","repository":{"id":268341247,"uuid":"904038078","full_name":"sb2bg/sykora","owner":"sb2bg","description":"End-to-end chess engine written in Zig supporting the UCI interface. Play me on Lichess!","archived":false,"fork":false,"pushed_at":"2026-02-26T08:34:15.000Z","size":2185,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-26T08:45:15.089Z","etag":null,"topics":["chess","chess-engine","uci","zig"],"latest_commit_sha":null,"homepage":"https://lichess.org/@/SykoraBot","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sb2bg.png","metadata":{"files":{"readme":"README.md","changelog":"history/.gitignore","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-12-16T06:13:25.000Z","updated_at":"2026-02-26T08:34:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"99ae650b-f687-4957-9cdf-6e57a90393e9","html_url":"https://github.com/sb2bg/sykora","commit_stats":null,"previous_names":["sb2bg/sykora"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sb2bg/sykora","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sb2bg%2Fsykora","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sb2bg%2Fsykora/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sb2bg%2Fsykora/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sb2bg%2Fsykora/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sb2bg","download_url":"https://codeload.github.com/sb2bg/sykora/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sb2bg%2Fsykora/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30078682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T08:01:56.766Z","status":"ssl_error","status_checked_at":"2026-03-04T08:00:42.919Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chess","chess-engine","uci","zig"],"created_at":"2026-03-04T11:16:04.184Z","updated_at":"2026-03-04T11:16:04.669Z","avatar_url":"https://github.com/sb2bg.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sykora\n\n[![Lichess bullet rating](https://lichess-shield.vercel.app/api?username=sykorabot\u0026format=bullet)](https://lichess.org/@/sykorabot/perf/bullet)\n[![Lichess blitz rating](https://lichess-shield.vercel.app/api?username=sykorabot\u0026format=blitz)](https://lichess.org/@/sykorabot/perf/blitz)\n[![Lichess rapid rating](https://lichess-shield.vercel.app/api?username=sykorabot\u0026format=rapid)](https://lichess.org/@/sykorabot/perf/rapid)\n[![CI Regression](https://github.com/sb2bg/sykora/actions/workflows/ci-regression.yml/badge.svg)](https://github.com/sb2bg/sykora/actions/workflows/ci-regression.yml)\n\n\u003cimg src=\"https://github.com/sb2bg/sykora/blob/main/assets/logo.png\" width=\"200\" alt=\"Sykora Logo\"\u003e\n\nSykora is a UCI chess engine written from scratch in Zig. It features magic bitboard move generation, a full alpha-beta search with LMR/null-move/futility pruning, Lazy SMP parallel search, a hand-tuned classical evaluation, and NNUE evaluation trained via the [Bullet](https://github.com/jw1912/bullet) trainer. An NNUE net is embedded in the binary and enabled by default. Sykora plays live on Lichess as [SykoraBot](https://lichess.org/@/sykorabot).\n\n## Features\n\n### Engine Core\n\n- Bitboard-based board representation with fast occupancy/piece set operations.\n- Precomputed attack tables for king/knight/pawn moves.\n- Magic bitboards for rook and bishop attacks (queen attacks via composition).\n- Full legal move generation including castling, en passant, promotions, and check legality filtering.\n- Incremental make/unmake move pipeline with Zobrist hashing.\n- Polyglot-compatible en-passant hash handling.\n\n### Search\n\n- Negamax alpha-beta search with iterative deepening.\n- Aspiration windows around prior iteration score.\n- Principal Variation Search (PVS).\n- Transposition table with aging and depth-preferred replacement.\n- Move ordering pipeline:\n  - TT move\n  - SEE-scored captures\n  - Killer moves\n  - History and counter-move scoring for quiets\n  - Deferred bad captures\n- Pruning and reduction framework:\n  - Mate distance pruning\n  - Check extension\n  - Null-move pruning with verification at higher depths\n  - Reverse futility pruning\n  - Futility pruning\n  - Razoring\n  - Late Move Reductions (LMR)\n  - Late Move Pruning (LMP)\n- Quiescence search with:\n  - Check evasions when in check\n  - Delta pruning\n  - SEE-based pruning of clearly losing captures\n- Repetition detection with contempt shaping and cycle penalties.\n- 50-move-rule handling.\n- Basic time management for clocked play.\n- Evaluation cache for expensive eval paths.\n\n### Evaluation\n\n- **NNUE evaluation** (default, embedded in binary):\n  - `768 -\u003e Nx2 -\u003e 1` architecture with SCReLU activation\n  - Trained on high-depth self-play data via the Bullet trainer\n  - Incremental accumulator updates during search\n  - Custom `SYKNNUE2` network format with auto-detected activation type\n  - Backward-compatible with `SYKNNUE1` format (defaults to ReLU)\n  - Blendable with classical eval via `NnueBlend` (default: 100 = pure NNUE)\n- **Classical handcrafted evaluation** (fallback):\n  - Material and piece-square tables\n  - Pawn structure terms (isolated/doubled/backward/passed)\n  - Mobility terms\n  - King safety and castling terms\n  - Endgame mop-up/king activity terms\n\n### Parallel Search\n\n- Lazy SMP-style parallel search with helper threads.\n- Shared transposition table across threads.\n- Best-move voting across main/helper results.\n\n### UCI and Developer Commands\n\n- Standard UCI command support (`uci`, `isready`, `position`, `go`, `stop`, `setoption`, `ucinewgame`, `quit`).\n- `display` helper command for board/FEN/hash inspection.\n- `perft` helper command with:\n  - Fast node count mode\n  - `stats` mode (captures, checks, promotions, mates, etc.)\n  - `divide` mode\n\n### Tooling\n\n- Perft and movegen shell tests (`utils/test`).\n- NPS benchmarking (`utils/bench/nps.py`).\n- STS runner (`utils/sts/sts.py`).\n- Engine-vs-engine self-play tooling (`utils/match`).\n- Long-term experiment history and ratings workflow (`utils/history`).\n- Automated tuning loop (`utils/tuning/tune_loop.py`).\n- NNUE data prep/training/export pipelines (`utils/nnue`, `utils/data`).\n- Lichess play/challenge bot tooling (`utils/bot`).\n\n## UCI Options\n\n| Option           | Type   | Default | Description                                                |\n| ---------------- | ------ | ------- | ---------------------------------------------------------- |\n| `Debug Log File` | string | `\"\"`    | Path for debug logging                                     |\n| `UseNNUE`        | bool   | `true`  | Enable NNUE evaluation (embedded net loads automatically)  |\n| `EvalFile`       | string | `\"\"`    | Path to external `.sknnue` file (overrides embedded net)   |\n| `NnueBlend`      | int    | `100`   | NNUE/classical blend (0 = classical only, 100 = pure NNUE) |\n| `NnueScale`      | int    | `100`   | NNUE output scaling factor (10..400)                       |\n| `Threads`        | int    | `1`     | Search threads (1..64, Lazy SMP)                           |\n| `Hash`           | int    | `64`    | Transposition table size in MB (1..4096)                   |\n\nThe activation function (ReLU or SCReLU) is auto-detected from the network file header -- no manual configuration needed.\n\n## Prerequisites\n\n- [Zig](https://ziglang.org/) compiler (latest stable version recommended)\n- Python 3.10+ (for benchmark, STS, match, history, tuning, NNUE, and bot utilities)\n- A UCI-compatible chess GUI (like Arena, Cutechess, or similar)\n\nFor Python tooling, install dependencies as needed:\n\n```bash\n# Core analysis/benchmark utilities\npython -m pip install chess\n\n# Lichess bot utilities\npython -m pip install berserk python-dotenv\n```\n\n## Building\n\nTo build the project, run:\n\n```bash\nzig build\n```\n\nThis will create an executable named `sykora` in the `zig-out/bin` directory. The embedded NNUE net (`src/net.sknnue`) is compiled into the binary -- no external files needed to play at full strength.\n\n## Running\n\nTo run the engine:\n\n```bash\nzig build run\n```\n\nOr directly run the executable:\n\n```bash\n./zig-out/bin/sykora\n```\n\nThe engine starts with NNUE enabled and the embedded net loaded. No configuration is required for normal use.\n\n## Play On Lichess\n\nSykora is available on Lichess at [SykoraBot](https://lichess.org/@/sykorabot).\n\nTo run your own bot instance against Lichess:\n\n```bash\nexport LICHESS_API_TOKEN=\"\u003cyour_lichess_bot_token\u003e\"\nexport ENGINE_PATH=\"./zig-out/bin/sykora\"\npython utils/bot/lichess_bot.py\n```\n\nTo issue a challenge from your token/account:\n\n```bash\n# challenge a specific user\npython utils/bot/challenge_bot.py some_username --minutes 3 --increment 2\n\n# or pick a random online bot\npython utils/bot/challenge_bot.py --random-online-bot --minutes 3 --increment 2\n```\n\n## Testing\n\nTo run the test suite:\n\n```bash\nzig build test\n```\n\nTo run perft validation:\n\n```bash\nutils/test/test_perft_suite.sh\n```\n\nTo benchmark search speed (NPS):\n\n```bash\npython utils/bench/nps.py --engine ./zig-out/bin/sykora --depth 10\npython utils/bench/nps.py --engine ./zig-out/bin/sykora --movetime-ms 500 --runs 2\n```\n\nTo run Strategic Test Suite (STS) EPD files (requires `python-chess`):\n\n```bash\npython utils/sts/sts.py --epd /path/to/sts --pattern \"STS*.epd\" --engine ./zig-out/bin/sykora --movetime-ms 300\n```\n\nTo run engine-vs-engine self-play (also requires `python-chess`):\n\n```bash\n# Baseline vs candidate, 80 games, 200ms/move, balanced openings\npython utils/match/selfplay.py ./old_sykora ./zig-out/bin/sykora --name1 old --name2 new --games 80 --movetime-ms 200\n```\n\nUseful variants:\n\n```bash\n# More stable signal (recommended for commits you may keep)\npython utils/match/selfplay.py ./old_sykora ./zig-out/bin/sykora --games 200 --movetime-ms 200\n\n# Fixed-depth comparison\npython utils/match/selfplay.py ./old_sykora ./zig-out/bin/sykora --games 120 --depth 8\n\n# Save all PGNs for manual inspection\npython utils/match/selfplay.py ./old_sykora ./zig-out/bin/sykora --games 80 --output-dir ./selfplay_pgn\n```\n\nThe script prints an estimated Elo difference (`candidate - baseline`), a 95% confidence interval, and a p-value versus equal strength.\n\nStatus codes (useful for CI):\n\n- `utils/sts/sts.py`\n  - `0`: success\n  - `1`: runtime/input error (missing EPD, engine startup failure, parse failure, etc.)\n  - `2`: invalid CLI options (for example malformed `--engine-opt`)\n- `utils/match/selfplay.py`\n  - `0`: candidate score \u003e baseline score\n  - `1`: baseline score \u003e candidate score\n  - `2`: exact tie\n  - `\u003e2`: unexpected failure (engine/protocol/runtime error)\n\nThe `CI Regression` workflow (`.github/workflows/ci-regression.yml`) runs STS on the current build, then self-play versus `HEAD^`. By default it fails when the candidate loses and allows ties (`SELFPLAY_ALLOW_TIES=true`).\n\nTo compare current working tree against your configured baseline in one command:\n\n```bash\n# history/current_baseline.txt can contain either:\n# 1) a snapshot id (history/engines/\u003cid\u003e/engine), or\n# 2) a direct path to a baseline binary\nutils/match/selfplay_vs_ref.sh --games 120 --movetime-ms 200\n\n# or override baseline at runtime\nutils/match/selfplay_vs_ref.sh --baseline history/engines/\u003csnapshot_id\u003e/engine --games 200 --depth 8\n```\n\nThis wrapper builds only the current working tree in `ReleaseFast`, runs self-play vs the baseline, and prints the same Elo/confidence summary.\n\nFor long-term version tracking, use the history ledger:\n\n```bash\n# Initialize ledger folders\npython utils/history/history.py init\n\n# Snapshot current engine build\npython utils/history/history.py snapshot --label \"experiment-a\" --notes \"describe your change\"\n\n# List snapshots, run archived match, recompute global ratings\npython utils/history/history.py list-engines\npython utils/history/history.py match \u003cengine_id_A\u003e \u003cengine_id_B\u003e --games 120 --movetime-ms 200\npython utils/history/history.py ratings --plot\npython utils/history/history.py sts \u003cengine_id\u003e --movetime-ms 100\n\n# Auto pit strongest vs weakest and render a network graph\npython utils/history/history.py match-extremes --min-games 20 --games 80 --movetime-ms 120\npython utils/history/history.py network --top-n 12 --min-games 10 --min-edge-games 2\n```\n\nSee `history/README.md` for folder schema and full workflow.\n\nOne-command tuning loop (STS gate + archived self-play + auto-promotion):\n\n```bash\n# First run: create baseline from a known engine binary\npython utils/tuning/tune_loop.py --bootstrap-baseline-engine old_versions/old_sykora --candidate-label \"first-pass\"\n\n# Normal run: compare current code to history/current_baseline.txt\npython utils/tuning/tune_loop.py --candidate-label \"eval-tweak\" --candidate-notes \"describe change\"\n```\n\nQuick vs serious settings:\n\n```bash\n# Quick loop (default): 6 STS themes, 20 self-play games at 80ms/move\npython utils/tuning/tune_loop.py --candidate-label \"quick-iter\"\n\n# Serious confirmation before keeping a major change\npython utils/tuning/tune_loop.py --candidate-label \"confirm\" --sp-games 120 --sp-movetime-ms 150 --max-p-value 0.2\n```\n\n## NNUE\n\nSykora uses a `768 -\u003e Nx2 -\u003e 1` NNUE architecture with dual-perspective accumulator updates and SCReLU activation, trained via the [Bullet](https://github.com/jw1912/bullet) trainer.\n\n### How It Works\n\n- The embedded net (`src/net.sknnue`) is compiled into the binary and loaded automatically at startup.\n- NNUE is enabled by default (`UseNNUE = true`, `NnueBlend = 100`).\n- The activation function (ReLU or SCReLU) is stored in the network file header and auto-detected on load.\n- To use a different net, set `EvalFile` to the path of an external `.sknnue` file.\n- To blend NNUE with classical eval, lower `NnueBlend` (e.g., `50` for 50/50, `0` for classical only).\n\n### Network Format (SYKNNUE2)\n\n```\n8 bytes   magic: \"SYKNNUE2\"\nu16       version: 2\nu16       hidden_size\nu8        activation_type (0=ReLU, 1=SCReLU)\ni32       output_bias\ni16[hidden_size]                accumulator biases\ni16[768 * hidden_size]          input -\u003e accumulator weights\ni16[2 * hidden_size]            output weights (stm half, nstm half)\n```\n\nAll values are little-endian. The older `SYKNNUE1` format (no activation byte, defaults to ReLU) is still supported for backward compatibility.\n\n### Training Pipeline\n\nTraining uses the [Bullet](https://github.com/jw1912/bullet) NNUE trainer with CUDA. Two data formats are supported:\n\n**Using binpack data:**\n\n```bash\npython utils/nnue/bullet/train_cuda_longrun.py \\\n  --dataset data/training.binpack \\\n  --data-format binpack \\\n  --bullet-repo nnue/bullet_repo \\\n  --output-root nnue/models/bullet \\\n  --hidden 256 --end-superbatch 320 --threads 8\n```\n\n**Using BulletFormat .data files:**\n\n```bash\npython utils/nnue/bullet/train_cuda_longrun.py \\\n  --dataset nnue/data/bullet/train/train_main.data \\\n  --bullet-repo nnue/bullet_repo \\\n  --output-root nnue/models/bullet \\\n  --hidden 256 --end-superbatch 320 --threads 8\n```\n\n**Multiple datasets** can be passed space-separated:\n\n```bash\npython utils/nnue/bullet/train_cuda_longrun.py \\\n  --dataset data/set1.binpack data/set2.binpack \\\n  --data-format binpack \\\n  ...\n```\n\n**Resuming from a checkpoint:**\n\n```bash\npython utils/nnue/bullet/train_cuda_longrun.py \\\n  --dataset data/test80.binpack \\\n  --data-format binpack \\\n  --resume nnue/models/bullet/\u003crun_id\u003e/checkpoints/\u003ccheckpoint\u003e/raw.bin \\\n  --start-superbatch 161 --end-superbatch 320 \\\n  ...\n```\n\n### Self-Play Data Generation\n\nSykora can generate its own training data via the `gensfen` command:\n\n```bash\n# Generate positions using the engine's own evaluation\n./zig-out/bin/sykora gensfen output.data depth 7 count 1000000 threads 4\n```\n\n### Exporting a Trained Net\n\nConvert a Bullet checkpoint to `.sknnue`:\n\n```bash\n# From quantised.bin (default after Bullet training)\npython utils/nnue/bullet/bullet_quantised_to_sknnue.py \\\n  nnue/models/bullet/\u003crun_id\u003e/checkpoints/\u003ccheckpoint\u003e/quantised.bin \\\n  output.sknnue --activation screlu\n\n# From NPZ export\npython utils/nnue/bullet/export_npz_to_sknnue.py \\\n  checkpoint.npz output.sknnue --activation screlu\n```\n\n### Embedding a New Net\n\nTo update the embedded net in the binary:\n\n```bash\ncp output.sknnue src/net.sknnue\nzig build\n```\n\n### Gating Checkpoints\n\n```bash\npython utils/nnue/bullet/gate_checkpoints.py \\\n  --checkpoints-dir nnue/models/bullet/\u003crun_id\u003e/checkpoints \\\n  --engine ./zig-out/bin/sykora \\\n  --blend 100 --nnue-scale 100 \\\n  --sts-epd epd --sts-movetime-ms 40 --sts-max-positions 400 \\\n  --selfplay-games 80 --selfplay-movetime-ms 120 --selfplay-top-k 3 \\\n  --threads 1 --hash-mb 64 \\\n  --min-elo 0 --max-p-value 0.25 \\\n  --promote-to nnue/syk_nnue_best.sknnue\n```\n\nFull process spec: `specs/nnue_training_spec.md`.\n\n## Documentation\n\n- `engine-interface.md` - Detailed documentation of the engine interface\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsb2bg%2Fsykora","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsb2bg%2Fsykora","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsb2bg%2Fsykora/lists"}