Skip to content

feat: PVS, aspiration windows, richer Lazy SMP, tapered eval, pondering, Polyglot book - #10

Merged
cjunius merged 10 commits into
mainfrom
feat/pvs-aspiration-ponder-eval-book
Aug 31, 2026
Merged

cjunius merged 10 commits into
mainfrom
feat/pvs-aspiration-ponder-eval-book

Conversation

@cjunius

@cjunius cjunius commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Works through the docs/design.md search + evaluation backlog on one branch,
one commit per feature group.

Search

  • Principal variation search (6e3f75d) — full window for the first move at
    each node, null-window scout + re-search for the rest, LMR folded in as an
    extra reduction on the scout. Adds a triangular PV table, so SearchResult
    and the info line now carry the real principal variation.
  • Aspiration windows (6c08c1c) — from depth 5 each iteration starts inside
    a ±25 cp window around the previous score, doubling the failing side.
  • Richer Lazy SMP (68ee9a2) — a 6-bit TT generation ages out the previous
    search's entries; helper workers skew their root move order and use a wider,
    asymmetric aspiration window.

Evaluation (160a0ea)

  • Tapered PeSTO material + piece-square tables, interpolated by game phase.
  • New terms, each an (mg, eg) pair folded into the taper: passed pawns,
    mobility (N/B/R/Q), king safety (attacker weight in the king ring + missing
    pawn shield), tempo.
  • A colour-mirror symmetry test guards the whole evaluation.

Pondering (5d1bb51)

  • engine.Search gains Stop / Ponder / PonderHit / Info; it runs on a
    board copy and streams each iteration through the callback.
  • The UCI layer runs the search on a goroutine with a mutex-serialised stdout:
    stop aborts immediately, go ponder + ponderhit work, bestmove carries
    a ponder move, and winc/binc feed the time budget. New Ponder option.

Opening book (52d72bd)

  • Hand-rolled Polyglot support (no new dependency): PolyglotKey (validated
    against the format's canonical key vectors), OpenBook, Book.Probe.
  • New OwnBook / BookFile options; a book hit is played with no search.

Deferred

Syzygy tablebases — no mature pure-Go prober and the build is strictly
CGO_ENABLED=0. Rationale in docs/adr/0003-defer-syzygy-tablebases.md.

Verification

  • go test -race ./... green; new tests for PV, aspiration parity, TT ageing,
    eval symmetry, async stop / ponderhit, and the Polyglot key vectors.
  • go mod tidy clean (no new modules).
  • Manual UCI smoke: streamed info … pv …, stop, ponder/ponderhit, and
    an instant book move all confirmed.
  • Not yet SPRT-tested — strength deltas are unproven pending a match.

🤖 Generated with Claude Code

cjunius and others added 10 commits August 30, 2026 20:34
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the LMR-only null-window logic with full principal variation search:
the first move at each node keeps a full window at full depth, later moves are
scouted with a null window (and an LMR reduction for late quiets) and only
re-searched at full depth / full window when the scout beats alpha.

Add a triangular PV table to the searcher so the search reports the actual
principal variation, not just the best move. SearchResult gains PV.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
From depth 5, each iterative-deepening iteration first searches a narrow
window centred on the previous iteration's score. searchRoot now takes an
explicit [alpha, beta] and reports a fail-soft score; searchDepth wraps it,
doubling the delta on whichever side fails until the score lands inside the
window or it opens fully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- TT entries carry a 6-bit generation; NewSearch() bumps it and store()
  treats older-generation entries as free space, so each search reclaims the
  previous search's table instead of being blocked by its deeper entries.
  Search() calls NewSearch() once per invocation.
- Lazy-SMP helpers skew their root move order (each scouts a different
  alternative to the hash move first) and use a wider, asymmetric aspiration
  window so they diverge onto different parts of the tree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the single piece-square table set with the PeSTO middlegame/endgame
tables and material values, interpolated by game phase. Layer on positional
terms, each accumulated as an (mg, eg) pair and folded into the taper:

- passed pawns, bonus scaled by rank (endgame-weighted)
- piece mobility for knights/bishops/rooks/queens
- king safety: attacker weight in the king ring + a missing-pawn-shield
  penalty (middlegame only)
- a small tempo bonus for the side to move

A colour-mirror symmetry test guards the whole evaluation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
engine.Search grows Stop / Ponder / PonderHit / Info fields: it runs on a
copy of the board, streams each completed iteration through the Info callback,
and honours an external abort flag. Pondering ignores the clock until
PonderHit is observed, then arms the budget from that moment.

The UCI layer runs the search on a goroutine. 'go' returns immediately; a
mutex serialises stdout; 'stop' aborts; 'ponderhit' converts a ponder search
onto its budget; 'go ponder' holds the bestmove until ponderhit or stop.
bestmove now carries a 'ponder' move from the PV. Added winc/binc to the time
budget and the Ponder option.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hand-rolled Polyglot support (no new dependency): PolyglotKey computes the
book's Zobrist key (validated against the format's canonical key vectors),
and OpenBook / Book.Probe read a .bin file and return the highest-weighted
legal move for a position, translating the Polyglot castling and promotion
move encoding.

UCI gains OwnBook and BookFile options; when a book move is available the
'go' handler emits it immediately and skips the search (never while pondering).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move the delivered backlog items into the Implemented sections of design.md,
architecture.md and engine-strength.md; refresh the README feature list,
roadmap and UCI options table; note the pre-change performance numbers; add
ADR 0003 recording why Syzygy tablebases are deferred.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extract go-argument parsing into parseGoArgs to bring handleGo under the
cyclomatic-complexity limit; drop two nolint:gosec directives gosec does not
need on provably-bounded square conversions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop the 5s deadline (both searches are depth-bounded, so they always
finish) and lower the fixed depth from 6 to 5. On a -race CI runner the
depth-6 search was exceeding the deadline, leaving the full-window baseline
with no result to compare against.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 92.07709% with 37 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/engine/search.go 86.72% 15 Missing ⚠️
internal/engine/polyglot.go 85.52% 11 Missing ⚠️
internal/uci/uci.go 92.56% 9 Missing ⚠️
internal/engine/eval_terms.go 98.18% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cjunius
cjunius merged commit 307f166 into main Aug 31, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants