summaryrefslogtreecommitdiff
path: root/src/eval.c
AgeCommit message (Collapse)Author
5 daysKnight mobility: bitboard rewrite, first bbMinorAttacks contributorScott Gasch
_EvalKnight's mobility loop (per-square g_iNDeltas delta-walk + NMobCaseTable switch dispatch) replaced entirely with g_KnightAttacksBB[c] (generate.c's precomputed table, already used by move generation) plus two bitboard masks -- knights never x-ray or have a battery partner, so the old four-case table collapses to "enemy non-pawn: count unconditionally" and "empty-or-enemy-pawn: count unless pawn-unsafe." Adds POSITION::bbMinorAttacks[2] (chess.h), the first of the bbMinorAttacks/bbRookAttacks/bbQueenAttacks accumulators from board_representation/EVAL.md section 2 -- knight ORs its full attack set in directly, no per-square bit-scan needed. Bishop is not converted yet and still writes its own minor-bit contribution into the old per-square rgSquare[c|8].bvAttacks structure. Since knight stopped writing that old structure, every consumer that needs "does any minor attack this square" (rook/queen's mobility- safety check, king's danger computation, _WhoControlsSquareFast) now goes through a new transitional helper, _IsSquareAttackedByMinor, which ORs the new bitboard (knight) with the old bvAttacks bit (bishop) in exactly one place rather than each call site hand-rolling its own combination -- this collapses to a plain bbMinorAttacks read once bishop converts too, and the helper goes away entirely. _IsSquareAttackedByMinor is DEBUG-asserted against an independent recomputation (g_KnightAttacksBB / _BishopAttacksBB, both already- trusted primitives from move generation, unrelated to either the old ray-walk's bit bookkeeping or the new accumulator) so a bug in either mechanism fails loudly in any DEBUG build/smoke-test run rather than silently drifting into a wrong score deep in search. Verified via precommit_check.sh and a direct before/after node-count comparison (sd10, r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/ R1BQ1RK1 w - - 0 1): byte-identical, 3125335 nodes both builds. This landed as a deliberately small, single-piece-type step after an earlier attempt to convert knight+bishop+rook+queen+king in one combined change produced a real bug (a byte-scale mismatch in xray bit reconstruction) that was hard to isolate with five things changed at once, and was reverted back to 6e86450 rather than debugged further. Bishop, rook, queen, and _EvalKing's own conversion are follow-up steps, each to be landed and verified the same way, one at a time. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
5 daysReplace Eval()'s piece-dispatch loop with per-type bitboard walksScott Gasch
The old loop walked cNonPawns[color][1..N] (a flat, arbitrarily- ordered list mixing knight/bishop/rook/queen), doing a mailbox lookup plus a p&0x4/IS_KNIGHT branch per piece to decide which _Eval* function to call, then stashing rooks/queens into cDefer/uDefer arrays to evaluate in a later pass -- a hard-to-predict branch per piece on top of a mailbox read the callee already redoes for its own ASSERT. pos->bbPieces[color][PIECE_TYPE] already exists (incrementally maintained by move.c) and was simply unused by Eval() until now. Replaced the whole dispatch with a direct per-type bitboard walk: knights, then bishops, for side-to-move, then the same for the other side, then rooks both colors, then queens both colors -- same phase order as before (load-bearing for bvAttacks accumulation), just sourced from a bitboard instead of a mixed list + runtime type dispatch. Retires the cDefer/uDefer bookkeeping entirely -- with each type its own direct walk, "rooks after minors" is just "do that walk after this one," nothing to defer. Verified genuinely behavior-neutral, not just crash-free: built the pre-change and post-change binaries side by side and diffed --batch --command output against tests/ecm_ringers.ep_ at sd10. Node counts, scores, and PVs are byte-for-byte identical across all 11 positions; only wall-clock time and NPS differ, consistently in the new version's favor (~3-5% faster on this suite, e.g. 1088239 nps -> 1143141 nps, 1560092 -> 1616844). Also verified via the normal precommit_check.sh gate. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
5 daysRetire pawns' bvAttacks writes in favor of pos->bbPawnAttacks[2]Scott Gasch
First piece type converted per board_representation/EVAL.md section 2: pawns no longer write into rgSquare[c|8].bvAttacks at all. Every consumer of "does a pawn attack this square" now reads pos->bbPawnAttacks[2] directly -- a plain bitboard, computed fresh each Eval() call from pos->bbPawns[] via the same shift-and-mask technique generate.c's _GenerateAllPawnMovesBB already uses (zero per-pawn mailbox iteration, vs. up to 16 delta+IS_ON_BOARD checks before). Knight/bishop/rook/queen/king are unchanged -- still populate/read their own bvAttacks bits (uMinor/uRook/uQueen/uKing) the old way until their own conversions land. Consumer changes, all in eval.c: - UNSAFE_FOR_MINOR retired as a macro (it only ever tested the pawn bit) -- its 3 call sites (knight, bishop x2) now test bbPawnAttacks directly. - UNSAFE_FOR_ROOK/_QUEEN masks narrowed to drop the now-dead pawn bit; call sites OR in an explicit bbPawnAttacks test alongside the narrowed bvAttacks read. - Two direct .small.uPawn reads (bishop's transient-pawn mobility credit, bishop's defended-pawn bonus) switched to bbPawnAttacks tests. - _EvalKing's bvAttack/bvDefend (the real king-danger computation) OR the bitboard bit back in at both read points, careful to preserve the original ordering where bvDefend must reflect the king's own just-set defend bit. - _WhoControlsSquareFast (used by passer-race/trapped-piece/danger code) ORs the bitboard bit back into its g_SwapTable index at the same bit position PAWN_BIT always occupied. - Two bugs caught by manually auditing every remaining |8 site after the fact (not by any test failing): _EvalPawns' own pawn-duo and backward-pawn detection read bvAttacks.uWholeThing at a point in Eval()'s sequence where only pawns could have written it -- once pawns stopped writing there, both checks went permanently dead silently. Fixed to read bbPawnAttacks directly. No self-test caught this; it's exactly the gap EVAL.md section 5's planned exact-score harness is meant to close. EVAL_TIME instrumentation: split _EvalPawns' cycle counter into pawn-hash hit/miss buckets (chess.h/eval.c/root.c), answering whether the hash is still worth it now that attack-bit population is nearly free. Measured on one sd12 benchmark position: hits average 90.4 cycles, misses average 1741.3 cycles (~19x), 97.15% hit rate -- the hash stays a clear win; the miss cost was never mostly attack-bit population (that's a separate ~1% bucket now, down from ~3.6%), it's the isolated/doubled/duo/backward-pawn scoring loops, which still do real per-pawn work on a miss. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
5 daysAdd Eval() bitboard-migration plan, per-term EVAL_TIME cycle breakdown, drop ↵Scott Gasch
redundant pawn-location bitboard board_representation/EVAL.md: rewritten migration plan for a bitboard-backed Eval() (mobility ray-walks + bvAttacks replacement), plus a performance-philosophy section recording the profiling-first, cut-aggressively-except-mobility/safety-awareness approach agreed on this session, and findings on CountKingSafetyDefects' structural inability to share bvAttacks-derived state with _EvalKing. EVAL_TIME per-term instrumentation (chess.h/eval.c/root.c): breaks the existing whole-Eval() cycle counter down by pawns/knight/bishop/ rook/queen/king, the always-paid pre-lazy-exit segment, and the full-eval-only post-lazy segment, printed alongside the existing "Avg. cpu cycles in eval" line. Diagnostic only (EVAL_TIME-gated), no effect on the normal release profile. Drop PAWN_HASH_ENTRY's bbPawnLocations[2]: it duplicated POSITION's own incrementally-maintained bbPawns[2], rebuilt bit-by-bit on every pawn-hash miss for no reason. eval.c now reads pos->bbPawns[] directly; removed a stale per-iteration invariant assert in _EvalPawns that only made sense when the bitboard was being built bit-by-bit in that same loop. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
6 daysFix passed-pawn bitboard bit-clear bug, LMR gate coupling, inline hot ↵Scott Gasch
bitboard helpers - bitboard.c: CoorFromBitBoardRank1ToRank8 cleared the lowest set bit unconditionally instead of the reported (highest) one, silently mis-walking doubled-pawn files in eval.c's passed-pawn detection. - search.c/searchsup.c: move GetLMRReduction's precondition checks from inside the function to the caller in search.c (pre-existing work), finishing the split with a matching gate in split.c's HelpSearch -- the parallel-search call site had no gate at all, letting it call GetLMRReduction unconditionally (including for checking moves), reachable only under real multithreading (--cpus > 1) and the intermittent root cause of assertion crashes seen under --cpus 4. - eval.c: redirect CountBits/CoorFromBitBoardRank8ToRank1/ CoorFromBitBoardRank1ToRank8 to inline compiler-builtin versions (gated !CROUTINES) instead of the real out-of-line asm calls, on eval.c's ~20 existing production call sites. CountBits' asm body isn't O(1) popcnt, it's a Kernighan bit-clearing loop plus call overhead, paid on every Eval() call. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Jntky4yGUTyQVaGCXms4F2
6 daysFix all build warnings across release/DEBUG/TEST profilesScott Gasch
Clean gmake GENETIC=1 PERF_COUNTERS=1 MP=1 SIXTYFOUR=1 build had 100 warnings; DEBUG=1 and TEST=1 builds had more once actually exercised. - OFFSET_OF/CONTAINING_STRUCT (chess.h) and PTR_TO_ALLOC_HASH (unix.c) truncated pointers through 32-bit ULONG before use in offset/hash arithmetic on this 64-bit build -- routed through size_t instead. - Diagnostic int<->void* round-trips (command.c, root.c, split.c, sig.c, data.c, unix.c, util.c) widened/narrowed via size_t to avoid implicit truncation. - ABS_DIFF on unsigned COOR now casts to int before abs(). - Dropped -fexpensive-optimizations (GCC-only, clang silently ignores it) from GNUmakefile. - Removed genuinely dead variables (book.c, gamelist.c, split.c, testgenerate.c, testhash.c). - Guarded DEBUG/PERF_COUNTERS/_X86_-only variables and the _CMEvidenceBucket helper under the #ifdef that actually reads them, since ASSERT/EVAL_TERM/KEEP_TRACK_OF_FIRST_MOVE_FHs compile away outside those builds. - Added missing prototypes for SlidePawn, SlidePawnWithoutSigs, SlidePieceWithoutSigs (move.c), previously undeclared in chess.h. - Removed dead _SystemIsRoot (unix.c). Verified via precommit_check.sh: TEST=1 self-test suite passes, DEBUG=1 smoke test (10 random ECM positions, sd 4) passes with no crashes/assertions, release build restored -- all three profiles now build with zero warnings. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014Cmv11sJZqVfanrPh6UnWE
10 daysImproving for nullmove, eval speedup, remove old cruft, bugfix in split / MP.Scott Gasch
10 daysReplace bishop/knight/rook/queen mobility jump tables with inline switch ↵Scott Gasch
dispatch. The per-square mobility ray-walk called through a function pointer for every square visited (piece type varies square to square, so the CPU's indirect-branch predictor couldn't learn it), and nearly every handler reduced to a couple of constant increments and a stop/continue flag. Replaced each PMOBILITY_HELPER table with a small case-tag table and an inlined switch, dropping all now-dead handler functions and the PMOBILITY_HELPER typedef. Also hoisted the ray-invariant rank/file direction test (rook's "connected" bonus, queen's bishop/rook xray cases) out of the per-square switch to compute once per ray instead. Verified semantics-preserving: node counts, solve/fail sets, and PVs are byte-for-byte identical to head_reference across all three curated ECM suites (ringers/confident_quick/hard_quick) at sd 10. bench shows a consistent ~6-7% NPS improvement over two back-to-back runs. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012Q8wbZABsyG7PZQUd9WBAw
11 daysAdd small static penalties for trapped pieces in _EvalTrappedPieces.Scott Gasch
Eval() already detects trapped/attacked pieces (for search hints via RecordEnprisePieceAtPly/RecordTrappedPiece) but never penalized them in the static score. Add two named, DNA-visible constants: a larger flag for the opponent-to-move/imminently-capturable case, a smaller one for the own-move/still-might-escape case -- flat "this is bad" nudges, not an attempt to price the material outcome, which search still owns. Verified flat on ecm_ringers/ecm_confident_quick vs head_reference at sd 10; ecm_hard_quick's lone flip (ECM.370) is a search-instability artifact of that specific position (its true evaluation was still moving through depth 14 in independent runs), not a real regression.
11 daysEval.c diet: kill exact-duplicate terms, turn down overlapping ones, fix a ↵Scott Gasch
real king-safety bug found along the way. A full-file pass over eval.c hunting for the "positional terms too loud" feedback from real chess programmers, following the concrete finding that Crafty prices most structural themes through one term where this codebase spread the same theme across several (passed pawns alone via 5-6 separate additive terms that can all fire for one pawn). Two categories of fix, applied per the rule "if it's counting the same thing twice, kill it; if it's a genuinely different angle on the same theme, turn it down rather than remove it": Pawns (pawn-hash cached, so free regardless of term count -- these are data/magnitude fixes, not perf fixes): - Removed ISOLATED_PAWN_PENALTY_BY_COUNT, a whole-position aggregate that re-priced the same uIsolated[] count already reflected by summing the per-pawn isolated term once per isolated pawn -- an exact duplicate, not a different angle. - CANDIDATE_PASSER_BY_RANK's "in endgame" bonus used to add the exact same value a second time (a literal clone of the term just added above it); now a /2 fractional modifier. - CONNECTED_PASSERS_BY_RANK / SUPPORTED_PASSER_BY_RANK / OUTSIDE_PASSER_BY_DISTANCE scaled to ~1/3 magnitude: each prices a genuinely distinct angle on "how good is this passer" (connected to a partner, pawn-defended, outside the opposing majority) and can stack for the same pawn, so turned down rather than removed. - ISOLATED_DOUBLED_PAWN turned down (-11 -> -5): a per-pawn kicker that stacks with the whole-position DOUBLED_PAWN_PENALTY_BY_COUNT aggregate for the isolated+doubled subset -- different angle (single-worst-case flag vs. whole-position severity), not a duplicate, but a real overlap worth trimming. Pieces (non-cached, real per-node cost, so these are also legibility/ perf fixes, not just magnitude): - Bishop: cut BISHOP_IN_CLOSED_POSITION outright -- it duplicated bishop mobility rather than adding a distinct angle (mobility already measures per-bishop diagonal blockage directly and more precisely than a coarse whole-board proxy). - Knight: killed a stale "don't block unmoved E2/D2 pawns" TODO (opening-book territory, not eval's job) and "a knight with an open file behind it is good" (dubious chess reasoning reusing an unrelated table -- the same lookup as the backward-pawn-blockade bonus, for a completely different concept). - Rook: killed ROOK_TRAPPING_EKING (a rook on the 7th/8th aligned with the enemy king is exactly the geometric pattern CountKingSafetyDefects' CHECK_VECTOR scan already folds into uPiecesPointingAtKing -- belongs in king safety, not a rook- specific bolt-on). Also removed pFriendRook, dead in the same block. - Queen: killed "pointing near enemy K" (QUEEN_ATTACKS_SQ_NEXT_TO_ KING) -- computed from the queen's own mobility ray-cast, direct- attacks only, duplicating what _EvalKing's real (non-lazy-estimate) danger computation already reads from the identical attack-table bits a few lines away. - cTrapped fixed from a single COOR per color to a small [2][4] list (_RecordTrappedCandidate): the old single-slot design let a later piece's zero-mobility candidacy silently overwrite an earlier one's on the same side, discarding a genuinely trapped-and- attacked piece. This fed into search too (RecordTrappedPiece's move-ordering hint), not just eval scoring. RecordTrappedPiece's own per-ply single slot is left alone per design (would double the cost on the branch that already computes it, this is the innermost eval loop) -- now reports the MOST VALUABLE of the candidates found, not just whichever was found last. King (the actual regression-and-recovery of this session): - Cutting the queen's "pointing near enemy K" term initially cost real solves (117->110 on the sd10 curated suites) despite being a correct duplication kill -- the general king-safety loop's per-square attacker accounting was piece-type-blind (a queen attacking a square near the king counted the same as a knight doing the same geometric thing), so removing the one place that priced queen-specific severity lost real fidelity, not just a duplicate. Fixed properly: added KING_QUEEN_PROXIMITY_DANGER, computed from bvAttacks[...].small.uQueen / .xray.uQueen bits the king-safety loop already reads for every one of its 11 squares -- free (no new attack-table work) and more accurate than the killed term (catches x-ray/latent queen threats it never did). Calibrated against the killed term's own empirical magnitude (uNearKing * 8, capped at 6) rather than guessed. Recovered to 118/191 (a new session-best), now with the fidelity gap actually closed instead of just removed. - KING_SUPPORTING_OWN_PASSER_BY_RANK split out from SUPPORTED_PASSER_BY_RANK, which _EvalKing's "kings in front of passers" endgame bonus was silently reusing -- pawn-support and king-escort are different concepts (fires when the KING stands next to its own passer, not when a pawn does); scaling the shared table down for its real purpose was silently also scaling the unrelated king-escort bonus. Seeded with the table's original (pre-scaling) hand-tuned magnitude. - Collapsed three copy-pasted file-scan blocks (c-1/c/c+1, identical logic repeated three times) into one loop -- confirmed behaviorally neutral by isolated sd10 suite testing before landing alongside the king-safety content changes. Net result across the three curated suites (sd10, vs. the hand-tuned+ bugfix baseline this built on): 117 -> 118, a new session best, with every intermediate checkpoint tested via EVAL_DUMP verification + precommit_check.sh + sd10 sweep before moving to the next change. Deliberately deferred, written down for a future session rather than attempted here: a holistic king-safety overhaul (the piece-type- tropism inconsistency across knight/bishop/queen/the general CountKingSafetyDefects scan goes deeper than tonight's scoped fixes), recalibrating EstimatePositionalScore's iKingSwingP90 lazy-eval margin table (the instrumentation that built it no longer exists in this tree, and today's changes have already shifted the true swing distribution it was calibrated against), and training a small king- danger classifier from TWIC checkmate games (snapshot king safety features at -10/-15 moves from real checkmates, not resignations) to calibrate whichever of the above happens first. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M9ZDiJhiUajUxh95mTXCFJ
11 daysDitch Texel-tuned eval constants for the pre-tuning hand-tuned baseline; fix ↵Scott Gasch
a stale king-safety data bug found along the way. The Texel/coordinate-descent auto-tuning pass (started at 29d73f4) left several eval terms with non-monotonic or outright sign-flipped values that several ASSERTs had to be silently commented out to tolerate (e.g. BACKWARD_SHIELDED_BY_LOCATION scoring a structural pawn defect as a +12..+17 bonus on most squares, PASSER_BONUS_AS_MATERIAL_COMES_OFF staying flat until the defending side was down to almost nothing). Restored all 54 differing constant tables to their last hand-tuned values (commit df8facc, pre-dating 29d73f4) mechanically -- table names/shapes are identical between the two commits, only values differ, so this is a pure data restore with none of the surrounding code-structure changes since df8facc reverted. Also fixes a real bug found while investigating: pos->uPiecesPointingAtKing[] was only refreshed inside EstimatePositionalScore's lazy-eval-margin path (eval.c ~5648), but _EvalKing reads it unconditionally on every full eval. Whenever a node's cheap material+pawn score wasn't close enough to the alpha/beta window to trigger that lazy-margin branch, the full eval proceeded straight to _EvalKing using a stale uPiecesPointingAtKing value left over from a prior, unrelated node -- silent, intermittent noise in king-safety scoring on an unpredictable subset of evaluations. Introduced 2026-08-24/26 (29d73f4, 7857096), so it predates and was baked into the Texel tuning pass being reverted here. Fixed by computing it once in an else branch when the lazy-margin path isn't taken, so it's refreshed exactly once per full eval either way (this is the innermost eval loop, so avoided doubling the cost on the branch that already computes it). sd10/sn5m results across the three curated suites (vs. head_reference, the prior Texel-tuned HEAD): sd10: ringers 9/11 (was 10), confident 85/90 (was 88), hard 23/90 (was 17) -- total 117 vs 115 sn5m: ringers 11/11 (was 10), confident 87/90 (was 89), hard 14/90 (was 13) -- total 112 vs 112 Net win at sd10, wash at sn5m, in both cases with a large swing toward ecm_hard_quick -- consistent with hand-tuned values being more internally coherent (monotonic curves, no sign flips, no double-counted whole-position aggregates layered on top of already-summed per-item terms) even though they were never retuned against this specific suite or these specific opponents. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01M9ZDiJhiUajUxh95mTXCFJ
13 daysRemove ctx->uPositional and the EVAL_HASH subsystem; fix GetRoughEvalScore.Scott Gasch
Finishes work left half-done in 7857096 ("Replace ctx->uPositional with a data-calibrated Eval() return value"): that commit added Eval()'s new piPositional out-param but never migrated GetRoughEvalScore onto it, so GetRoughEvalScore's mid/deep-tree fallback kept reading the old ctx->uPositional field -- a per-thread EWMA written only on full-eval calls and never touched by the (far more common) lazy-eval path, so it carried a stale value from whatever unrelated position last triggered a full eval, potentially many nodes/plies away. Combined with EVAL_HASH being long since disabled (its probe branch already dead), every GetRoughEvalScore call past ply 4 was effectively "material + garbage." Fixed by having GetRoughEvalScore just call Eval() directly -- its own lazy-exit machinery already is the cheap, calibrated estimate this function exists to provide, so there's no separate estimator to maintain. Removed ctx->uPositional entirely (struct field, its EWMA update in eval.c, both root.c init sites, split.c's cross-split propagation, testeval.c's reset) along with the entire EVAL_HASH subsystem (struct, table, Probe/StoreEvalHash, main.c's now-dead reporting branch, the GNUmakefile flag) -- confirmed unused elsewhere and explicitly being cut for good, not coming back in this form. Also fixed GetRoughEvalScore's prototype being wrongly declared inside #ifdef EVAL_HASH in chess.h even though the function itself is defined and called unconditionally -- this was the source of the recurring "call to undeclared function 'GetRoughEvalScore'" implicit-declaration warning seen throughout this session's builds. Separately, fixed QSearch to match its own documented intent: the en-prise/trapped-piece "don't let this side stand pat" check now only fires if the side hasn't already been allowed to stand pat earlier in this qsearch line (matching the comment above it, which already said this but the code never implemented it). Verified against baseline/typhoon_baseline (pristine, pre-session) on ecm_ringers.ep_ (4), ecm_hard_quick.ep_ (50-sample), and ecm_confident_quick.ep_ (40) at sn=5M, --cpus 1, book disabled: pristine baseline solves 3/50 on the hard sample; this commit solves 6/50, with the stand-pat fix and GetRoughEvalScore fix each contributing +1 independently confirmed. No regressions on the other two suites (4/4 and 40/40 unchanged throughout). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YGSMkwjqiCk4XhbfN7ugD2
14 daysBaseline: uPositional data-calibrated fix, enprise/trapped hints, ↵Scott Gasch
EBF/beta-cutoff/counter-move stats, script.c FPE fix. No LMR, no counter-move-driven move ordering (both explored separately, kept out for now -- counter-move measured worse, ~655->647 solved on ecm879 @ sn=4M with a leaner tree beforehand). Futility pruning restored. Verified: 647/879 solved, EBF 4.609 @ sn=4M; 684/879 solved, EBF 3.995 @ 20s/move, 1cpu, 256m hash (typhoon_baseline.log). The counter-move table is still written and its stats still tracked (dynamic.c) for diagnostic purposes, but generate.c no longer reads it for move ordering, so it has no effect on search behavior in this commit. lmr_testing/ holds the in-flight graded-LMR + counter-move code (not applied here) with notes on what was already tried and measured, so a future session can resume without re-deriving it.
2026-08-26Replace ctx->uPositional with a data-calibrated Eval() return value.Scott Gasch
uPositional was a per-thread EWMA of abs(material - true score) used to size lazy-eval and futility margins. It was history-derived (reflecting whatever recent, unrelated positions looked like) rather than derived from the position actually being margined, and its update/consumption was tangled with EVAL_HASH (now disabled). Eval() now takes an optional SCORE *piPositional out-param and fills it in on every return path: exact (abs(material-delta)) on a full eval, or an estimate from a new EstimatePositionalScore() on a lazy exit. EstimatePositionalScore()'s two terms (king-safety-defect-bucketed, and a flat residual for mobility/passers/everything else) are calibrated from ~1.6M measured full-eval samples (p90 of the actual swing), not guessed -- an initial guessed version measurably regressed ECM solve rate (630 vs a 650 baseline at sn=4M); the recalibrated version is back at parity (649/879). search.c's qsearch futility now reads the value Eval() just computed instead of the stale/shared ctx field. Also removes QSearchInDangerNoStandPat and SideCanStandPat, dead since the danger-hash check that fed them was already commented out (e08387a) -- they depended on the same enprise/ trapped-piece data this conversation is about to move off of g_PositionHash entirely. Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-24Started doing texel eval tuning.Scott Gasch
2026-08-23Clean X64 build and ported GetAttacks to x64.Scott Gasch
2018-10-03Update codebase to remove clang warnings (and a couple of legit errorsScott Gasch
it found)
2016-06-01Initial checkin for typhoon chess engine.Scott Gasch