diff options
| author | Scott Gasch <[email protected]> | 2026-09-13 15:03:09 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-13 15:03:09 -0700 |
| commit | 78d1889fe38db1fd45b58509c8b3bd535ce691b7 (patch) | |
| tree | 994a213816c19c6654faf89eca74880f0fa79f47 /src/split.c | |
| parent | d2699c47d6d0abf0f018e5ba37d4f1bfe05ecd33 (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/split.c')
| -rw-r--r-- | src/split.c | 24 |
1 files changed, 20 insertions, 4 deletions
diff --git a/src/split.c b/src/split.c index f8da7ed..3a69ab4 100644 --- a/src/split.c +++ b/src/split.c @@ -81,7 +81,13 @@ Revision History: extern ULONG g_uIterateDepth; ULONG g_uNumHelperThreads = 0; -#define MAX_SPLITS (20) +// Sized for up to 64 threads (--cpus' documented max, main.c) each +// holding a handful of nested split pointers (NUM_SPLIT_PTRS_IN_CONTEXT, +// chess.h) at once -- exhausting this pool just degrades gracefully +// (StartParallelSearch falls back to serial search), but a ceiling +// tuned for a handful of threads bites harder the more of them are +// actually configured, and each SPLIT_INFO is only a few KB. +#define MAX_SPLITS (64) #define IDLE ((ULONG)-1) void @@ -570,6 +576,7 @@ Return value: sizeof(POSITION)); g_SplitInfo[u].iEval = ctx->sPlyInfo[ctx->uPly].iEval; g_SplitInfo[u].iImprovement = iImprovement; + g_SplitInfo[u].eNodeType = ctx->sPlyInfo[ctx->uPly].eNodeType; #if DEBUG g_SplitInfo[u].uSplitPly = ctx->uPly; memcpy(&(g_SplitInfo[u].sSplitPosition), @@ -886,7 +893,17 @@ Return value: if (iScore >= g_SplitInfo[u].iBeta) { // - // We failed high so we want to update the split node + // We failed high so we want to update the split node. + // Do NOT touch g_SplitInfo[u].iAlpha here -- the caller + // (StartParallelSearch's caller in search.c) asserts + // iAlpha stays < iBeta after a split returns, and + // detects fail-high purely via iBestScore/iScore, not + // via alpha having moved. (See search.c's handling of + // this return for the other half of this fix: it must + // special-case iBestScore >= iBeta itself rather than + // falling into the generic no_more_moves classifier, + // which infers "exact score" vs. "fail-high" purely + // from whether alpha moved.) // g_SplitInfo[u].fTerminate = TRUE; g_SplitInfo[u].PV[0] = NULLMOVE; @@ -1152,8 +1169,7 @@ Return value: // the deterministic missing-gate bug it actually was. // if ((uDepth > TWO_PLY) && - !(ctx->sPlyInfo[ctx->uPly].fPvNode) && - !(ctx->sPlyInfo[ctx->uPly - 1].fPvNode) && + (NODE_PV != g_SplitInfo[u].eNodeType) && ((g_SplitInfo[u].uAlreadyDone + uMoveNum + 1) > 5) && (0 == iExtend) && (!IS_ESCAPING_CHECK(mv)) && |
