summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-10 23:12:04 -0700
committerScott Gasch <[email protected]>2026-09-10 23:12:04 -0700
commit8a4985116ae6536db3f77b8ca8c81d7e8f73843d (patch)
tree7ac75d1ab593b2521045ce9c5ab66d8e1af9ccf3 /src/eval.c
parent9d1e32db698855b939c4bc5df6b6a82509dd381c (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.c78
1 files changed, 75 insertions, 3 deletions
diff --git a/src/eval.c b/src/eval.c
index 26b7ace..edb1e0c 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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