summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-13 15:03:09 -0700
committerScott Gasch <[email protected]>2026-09-13 15:03:09 -0700
commit78d1889fe38db1fd45b58509c8b3bd535ce691b7 (patch)
tree994a213816c19c6654faf89eca74880f0fa79f47 /src/eval.c
parentd2699c47d6d0abf0f018e5ba37d4f1bfe05ecd33 (diff)
Fix several MP/qsearch correctness bugs found by auditing ctx->uPly
indexing and per-thread state across MakeMove/recursion boundaries; add a position-signature stamp so full-eval-only data can be validated cheaply instead of approximated. - chess.h/searchsup.c/search.c/root.c: add NODE_TYPE (PV/CUT/ALL) classification, set centrally in CommonSearchInit, replacing the old binary fPvNode flag. Fixes split.c's node-type check reading stale data on helper threads (they never call CommonSearchInit for the split-point ply) and removes a redundant, always-true parent-type check in search.c's own LMR gate. - split.c: StartParallelSearch now copies the split point's eNodeType into SPLIT_INFO for helper threads to read (mirroring the existing iEval/iImprovement pattern); a split-node fail-high is now detected explicitly in search.c's move loop instead of falling through to the generic no_more_moves classifier, which misread it as a fail-low (skipping killer/history updates and storing an inverted-sense upper bound instead of a lower bound); ThreadUnderTerminatingSplit now checks all nested split pointers instead of just the first, so a thread notices when an inner split terminates while it's still helping an outer one; MAX_SPLITS bumped 20->64 for higher thread counts. - chess.h/searchsup.c/search.c: convert SEARCHER_THREAD_CONTEXT.uLastPositionalSource (a single per-thread scalar) to PLY_INFO.uLastPositionalSource (per-ply). The scalar version was clobbered whenever QSearch's own move loop recursed before a later iteration read it back, corrupting which qsearch futility margin got applied; also fixes the same hazard in the newly-added check-evasion futility prune. - chess.h/generate.c/searchsup.c: generalize move-scoring interpretation via a new MOVE_SCORING_SCHEME tag (per-ply, set by whichever generate.c routine last scored that ply's moves) and a single shared DecodeMoveValueEstimate, replacing scheme-blind decode logic in ComputeMoveScore and _ShouldWeConsiderThisMove. - chess.h/eval.c/dynamic.c: stamp PLY_INFO.u64FullEvalSig with the position signature whenever a node completes a full (non-lazy) Eval() call. cEnprise/pEnprise/cTrapped/pTrapped/iKingScore/the eval-scoped bb*Attacks bitboards are only valid when this matches the position's live signature; _EnpriseSlotValid/_TrappedSlotValid now compare sigs directly instead of re-checking piece identity on the live board. - searchsup.c: CommonSearchInit clears pi->mvBest on both its IsDraw and too-deep early-exit paths to prevent a later sibling node from reading stale mvBest data left by an unrelated earlier visit to the same ply. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C9r36BydPnfpyYLFhkTo6d
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c32
1 files changed, 22 insertions, 10 deletions
diff --git a/src/eval.c b/src/eval.c
index edb1e0c..0a8f71e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -5385,7 +5385,7 @@ Return value:
// govern from here.
(*piPositional)[WHITE] = 0;
(*piPositional)[BLACK] = 0;
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_SUPERLAZY;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_SUPERLAZY;
}
#ifdef EVAL_TIME
ctx->sCounters.tree.u64CyclesEvalSuperLazy +=
@@ -5419,7 +5419,7 @@ Return value:
{
(*piPositional)[WHITE] = 0;
(*piPositional)[BLACK] = 0;
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_SUPERLAZY;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_SUPERLAZY;
}
#ifdef EVAL_TIME
ctx->sCounters.tree.u64CyclesEvalSuperLazy +=
@@ -5632,7 +5632,7 @@ Return value:
{
(*piPositional)[WHITE] = iAlphaMargin;
(*piPositional)[BLACK] = iAlphaMargin;
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_LAZY;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_LAZY;
}
#ifdef EVAL_TIME
ctx->sCounters.tree.u64CyclesEvalPreLazy +=
@@ -5665,7 +5665,7 @@ Return value:
{
(*piPositional)[WHITE] = iBetaMargin;
(*piPositional)[BLACK] = iBetaMargin;
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_LAZY;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_LAZY;
}
#ifdef EVAL_TIME
ctx->sCounters.tree.u64CyclesEvalPreLazy +=
@@ -5963,15 +5963,27 @@ Return value:
// _EvalLookForDanger/_EvalTrappedPieces only ever *add* a hint
// when they find one; a full eval that finds nothing this time
// would otherwise leave a stale, unrelated hint from an earlier
- // visit to this ply sitting here (still piece-identity-valid by
- // coincidence). Clear this ply's self-danger slots first so a
- // clean full eval reliably means a clean slate.
+ // visit to this ply sitting here. Clear this ply's self-danger
+ // slots first so a clean full eval reliably means a clean slate.
//
ctx->cEnprise[ctx->uPly][0] = ctx->cEnprise[ctx->uPly][1] = ILLEGAL_COOR;
ctx->cTrapped[ctx->uPly] = ILLEGAL_COOR;
_EvalLookForDanger(ctx);
_EvalTrappedPieces(ctx);
+ // We are now committed to the full-eval path (no more lazy/
+ // super-lazy exits below this point) -- stamp this ply's
+ // PLY_INFO with the position we just fully evaluated so every
+ // "REQUIRES SIG MATCH" field this call populates (iKingScore
+ // below; cEnprise/cTrapped above; the eval-scoped bb*Attacks
+ // tables) can be validated by a downstream reader with one cheap
+ // compare against this position's live signature, instead of the
+ // old per-field piece-identity re-check. See PLY_INFO's own
+ // comment in chess.h for why this, not u64Sig, is the field to
+ // compare against.
+ ctx->sPlyInfo[ctx->uPly].u64FullEvalSig =
+ (pos->u64NonPawnSig ^ pos->u64PawnSig);
+
// TODO: endgame-specific knowledge? e.g. B over N in an endgame
// with 2 pawn wings?
@@ -6031,7 +6043,7 @@ Return value:
MAX(pos->iScore[BLACK] -
(SCORE)(pos->uPawnMaterial[BLACK] + pos->uNonPawnMaterial[BLACK]),
-(FUTILITY_BASE_MARGIN_FULL / 2));
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_FULL;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_FULL;
}
g_Options.iLastEvalScore = iScoreForSideToMove;
@@ -6069,7 +6081,7 @@ Return value:
// purposes, not what a normal build actually returns here
// -- fix it back up to match the restored (super-lazy)
// values.
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_SUPERLAZY;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_SUPERLAZY;
}
}
else if (TRUE == fWouldHaveExited)
@@ -6098,7 +6110,7 @@ Return value:
{
(*piPositional)[WHITE] = iSavedLazyPositional;
(*piPositional)[BLACK] = iSavedLazyPositional;
- ctx->uLastPositionalSource = EVAL_POSITIONAL_SOURCE_LAZY;
+ ctx->sPlyInfo[ctx->uPly].uLastPositionalSource = EVAL_POSITIONAL_SOURCE_LAZY;
}
}
#endif