diff options
| author | Scott Gasch <[email protected]> | 2026-09-10 23:12:04 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-10 23:12:04 -0700 |
| commit | 8a4985116ae6536db3f77b8ca8c81d7e8f73843d (patch) | |
| tree | 7ac75d1ab593b2521045ce9c5ab66d8e1af9ccf3 /src/eval.c | |
| parent | 9d1e32db698855b939c4bc5df6b6a82509dd381c (diff) | |
Fix ONE_LEGAL_MOVE hash-move-shrink bug, tighten extension taper, add
a scoped check-evasion qsearch futility prune, and fix a pre-existing
PV-corruption crash.
- chess.h/generate.c: ONE_LEGAL_MOVE now reads a uTrueEscapeCount
snapshot taken before _ScoreAllEscapes excludes an already-tried
hash move, instead of the live post-exclusion MOVE_COUNT -- the old
version misreported "one legal reply to check" whenever the hash
move happened to be one of exactly two legal replies.
- root.c: g_uSoftExtendLimit/g_uHardExtendLimit taper onset moved from
2x/4x nominal search depth to 1.5x/3x (uniform 0.75x scaling of the
same band shape) -- a forcing chain of check/singular-reply
extensions could previously run fully unthrottled out to double the
nominal depth before any taper at all kicked in.
- search.c: QSearchFromCheckNoStandPat gains a scoped futility prune
for check-evasion replies, active only when this side already had a
stand-pat opportunity earlier in the line (fCouldStandPat). Only
applies to non-checking moves, uses the same calibrated
FUTILITY_BASE_MARGIN_BY_SOURCE table normal QSearch already trusts
(not a separate, unvalidated margin), computes its Eval() call
lazily (only once a non-checking candidate is actually reached, not
unconditionally per node), and never fires until at least one legal
move has already been searched (uLegalMoves > 0), so it can never
discard every reply to a check.
- search.c/searchsup.c: split CommonSearchInit's "too deep" bailout
into two separately-counted triggers (the rare MAX_PLY_PER_SEARCH-1
array-safety ceiling vs. the much more frequently-hit
g_uHardExtendLimit, now reachable sooner given the tightened taper
above) and surfaced both counts in root.c's per-search report.
- searchsup.c: UpdatePV no longer copies a child ply's stale PV tail
forward after writing a sentinel move (HASHMOVE/RECOGNMOVE/
DRAWMOVE/MATEMOVE) into the current ply's slot -- sPlyInfo[] is
reused across unrelated nodes at the same ply, so an earlier,
unrelated node's leftover sentinel in that slot could reproduce the
same sentinel value twice in a row and trip WalkPV's terminator
assert (util.c:1093), reproducing under MP via debug_smoke_test.sh.
Pre-existing, confirmed on unmodified HEAD; not introduced by any of
the above.
- eval_tune/match_play.py: EngineStats now tracks running average
search depth (already-parsed per-move data, just never surfaced)
and prints it alongside nps/ebf in the periodic status line, not
just the final summary. Added a ^C handler that dumps the same
CANDIDATE_SCORE/CANDIDATE_STATS/HEAD_STATS summary from whatever
games completed so far, then exits immediately rather than waiting
for the ThreadPoolExecutor's default wait=True teardown to let
in-flight games finish naturally.
Validated: precommit_check.sh (self-test + DEBUG smoke test) passing
throughout; self-play gauntlet (match_play.py --st 1) trending net
positive against HEAD through 1000 games.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TBtwX9aPrvwiM3gh4VnnZi
Diffstat (limited to 'src/eval.c')
| -rw-r--r-- | src/eval.c | 78 |
1 files changed, 75 insertions, 3 deletions
@@ -951,6 +951,39 @@ BITBOARD BBADJACENT_FILES[8] = }; // +// Same as BBADJACENT_FILES, but including the file itself -- callers +// that used to OR in BBFILE[file] separately at every call (only one +// site, eval.c's _EvalKing) can index this instead and skip the extra +// load + OR. +// +BITBOARD BBADJACENT_FILES_INCLUSIVE[8] = +{ + // A = 0 + BBFILEA | BBFILEB, + + // B = 1 + BBFILEA | BBFILEB | BBFILEC, + + // C = 2 + BBFILEB | BBFILEC | BBFILED, + + // D = 3 + BBFILEC | BBFILED | BBFILEE, + + // E = 4 + BBFILED | BBFILEE | BBFILEF, + + // F = 5 + BBFILEE | BBFILEF | BBFILEG, + + // G = 6 + BBFILEF | BBFILEG | BBFILEH, + + // H = 7 + BBFILEG | BBFILEH +}; + +// // The ranks preceeding one, indexed by rank/color of pawn (used in // eval.c) // @@ -4231,6 +4264,20 @@ Return value: // uQueenNearKing += (((bvAttack | bvXray) & QUEEN_BIT) != 0); + // + // Tried a branchless rewrite of this attacked/unattacked + // split (2026-09-09): compute both sides' contributions + // and blend via 0/1 multiply instead of branching. Measured + // *worse* under EVAL_TIME (king bucket 17.9% vs 16.0-16.1% + // baseline, full-eval cost ~3330 vs ~3270 cyc/call, + // consistent across 3 runs each on a fixed sd9 position) -- + // the fOccupied ? : select that stayed in that version + // still compiled to a real CMP/branch (confirmed in + // objdump), so the rewrite paid for extra multiplies/masks + // on top of a branch that was never actually eliminated. + // Reverted; not a win here, don't retry without a way to + // force the compare into a cmov/select first. + // if (bvAttack != 0) { bvPattern |= (bvAttack | bvXray); @@ -4307,7 +4354,7 @@ Return value: #ifdef CALIBRATE_POSITIONAL // KING_SAFETY_BY_COUNTER is <= 0 throughout (a penalty); the // calibration histogram bins magnitude, matching iKingSwingP90's - // own all-positive-magnitude shape. + // own all-positive-mOBagnitude shape. ASSERT(i <= 0); _RecordPositionalCalibration(ctx, pos, pHash, uColor, (ULONG)(-i)); #endif @@ -4394,7 +4441,7 @@ Return value: // cSquare = FILE(c); bb = pHash->bbPasserLocations[uColor] & - (BBADJACENT_FILES[cSquare] | BBFILE[cSquare]); + BBADJACENT_FILES_INCLUSIVE[cSquare]; if (bb) { while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) @@ -5257,6 +5304,9 @@ Return value: ASSERT((pos->iMaterialBalance[WHITE] * -1) == pos->iMaterialBalance[BLACK]); +#ifdef EVAL_TIME + UINT64 u64MaterialSetupTimer = SystemReadTimeStampCounter(); +#endif pos->iScore[uColor] = (pos->uPawnMaterial[uColor] + pos->uNonPawnMaterial[uColor]); pos->iScore[xColor] = @@ -5283,6 +5333,10 @@ Return value: ASSERT(pos->uArmyScaler[BLACK] <= 31); ASSERT(pos->uArmyScaler[WHITE] >= 0); ASSERT(pos->uArmyScaler[WHITE] <= 31); +#ifdef EVAL_TIME + ctx->sCounters.tree.u64CyclesEvalMaterialSetup += + (SystemReadTimeStampCounter() - u64MaterialSetupTimer); +#endif // Super-lazy exit point. #ifdef LAZY_EVAL @@ -5431,6 +5485,9 @@ Return value: Trace("After pawns:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif +#ifdef EVAL_TIME + UINT64 u64PasserBadtradeBishopTimer = SystemReadTimeStampCounter(); +#endif // // Look for won passers races. Note: This code cannot trust the // state of the attack tables yet! @@ -5467,6 +5524,10 @@ Return value: Trace("After bishop pair bonus:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif +#ifdef EVAL_TIME + ctx->sCounters.tree.u64CyclesEvalPasserBadtradeBishop += + (SystemReadTimeStampCounter() - u64PasserBadtradeBishopTimer); +#endif #ifdef LAZY_EVAL // @@ -5589,6 +5650,10 @@ Return value: #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesEvalLazyDecision += (SystemReadTimeStampCounter() - uLazyDecisionTimer); + ctx->sCounters.tree.u64CyclesLazyExit += + (SystemReadTimeStampCounter() - uTimer); + ctx->sCounters.tree.u64CyclesInEval += + (SystemReadTimeStampCounter() - uTimer); #endif goto end; #endif @@ -5614,6 +5679,10 @@ Return value: #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesEvalLazyDecision += (SystemReadTimeStampCounter() - uLazyDecisionTimer); + ctx->sCounters.tree.u64CyclesLazyExit += + (SystemReadTimeStampCounter() - uTimer); + ctx->sCounters.tree.u64CyclesInEval += + (SystemReadTimeStampCounter() - uTimer); #endif goto end; #endif @@ -5932,7 +6001,8 @@ Return value: pos->iScore[FLIP(pos->uToMove)]); // Drive drawish situations towards the draw score. - EvalLookForDrawishSituations(pos, &iScoreForSideToMove); + TIMED_EVAL_CALL(ctx, u64CyclesEvalDrawish, + EvalLookForDrawishSituations(pos, &iScoreForSideToMove)); // // TODO: detect and discourage blocked positions? @@ -6034,6 +6104,8 @@ Return value: #endif #ifdef EVAL_TIME + ctx->sCounters.tree.u64CyclesFullEvalExit += (SystemReadTimeStampCounter() - + uTimer); ctx->sCounters.tree.u64CyclesInEval += (SystemReadTimeStampCounter() - uTimer); #endif |
