feat(search): killer/history ordering, null-move pruning, LMR - #9
Merged
Merged
Conversation
Add three search heuristics on top of the transposition table: - Killer moves + history heuristic. Quiet moves that cause a beta cut-off become killers for their ply (two slots) and accumulate a depth^2 bonus in a [side][from][to] history table; orderMoves ranks killers just below captures and sorts the remaining quiets by history. Both tables live on the per-worker searcher, so Lazy SMP stays lock-free. - Null-move pruning (R=2, R=3 from depth 6) with the usual guards: not in check, depth >= 3, non-mate beta, non-pawn material, no consecutive nulls. The pass position is built through FEN because dragontoothmg keeps the Zobrist hash and en-passant square in unexported fields and a stale hash would corrupt the shared TT. - Late move reductions. Late quiet moves that neither give nor evade check are searched 1-2 ply shallower and re-searched at full depth only when the reduced search beats alpha. negamax is split into outOfTime / terminalScore / tryNullMove / searchMove helpers to keep each within the complexity budget. Depth-8 from the opening drops from ~5.9M nodes to ~167k; the single-threaded search now reaches depth 12 in ~1s. Tests: new engine internal tests for nullMoveBoard, hasNonPawnMaterial, isQuiet, recordCutoff (killers + history cap), orderMoves ranking, and tryNullMove guard conditions; external tests for mate-finding and tactical soundness with pruning active; broader UCI coverage (setoption, ucinewgame, clock budget, malformed input); a cmd/gochess main_test. Package coverage: engine 90.4%, uci 95.8%, cmd 88.4% (overall 91.5%). Docs: README, architecture, design, performance, engine-strength and CHANGELOG updated for the new heuristics and refreshed benchmark tables. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds three classic search heuristics on top of the transposition table and Lazy SMP that landed in #8.
Killer moves + history heuristic
[side][from][to]history table,+= depth²per cut-off, clamped at1<<22.orderMovesnow ranks: TT move → promotions → MVV-LVA captures → killers → quiets by history score.searcher, so Lazy SMP stays lock-free.Null-move pruning
R = 2,R = 3from depth 6.depth >= 3, non-mate beta, side-to-move has non-pawn material (zugzwang), no consecutive nulls, static eval ≥ beta.dragontoothmgkeeps the Zobrist hash and en-passant square in unexported fields, so a plain struct copy would feed a stale hash into the shared TT.Late move reductions
negamaxis split intooutOfTime/terminalScore/tryNullMove/searchMoveto stay within the gocyclo budget.Impact
Single-threaded,
Hash 128, from the start position:The engine now reaches depth 12 from the opening in roughly the wall-clock depth 8 used to take. Node rate is ~unchanged (~2.5M nps).
Tests
nullMoveBoard,hasNonPawnMaterial,isQuiet,recordCutoff(killer shift + history cap),orderMovesranking,tryNullMoveguard matrix, board-unmodified invariant.setoptionHash/Threads (incl. clamping and bad values),ucinewgamereset, clock-budgetgo, malformedpositioninputs.cmd/gochess/main_test.gocoveringrunBench,runPerft, dispatch, and the UCI entry point.Coverage: engine 90.4%, uci 95.8%, cmd 88.4% — 91.5% overall.
golangci-lint(v2.13.2),gofumpt,go vet,go test -race ./..., and the full perft suite all pass.Docs
README,
docs/architecture.md,docs/design.md,docs/performance.md,docs/engine-strength.md, andCHANGELOG.mdupdated for the new heuristics and refreshed benchmark tables. The strength estimate is revised upward (~1600 → ~2000 CCRL-blitz, still an estimate pending SPRT).🤖 Generated with Claude Code