From 4ce6a76b0946e4ba943d29c506c9e2fb00601efd Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Thu, 27 Aug 2026 07:41:41 -0700 Subject: Baseline: uPositional data-calibrated fix, enprise/trapped hints, 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. --- src/lmr_testing/searchsup_GetLMRReduction.c | 114 ++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/lmr_testing/searchsup_GetLMRReduction.c (limited to 'src/lmr_testing/searchsup_GetLMRReduction.c') diff --git a/src/lmr_testing/searchsup_GetLMRReduction.c b/src/lmr_testing/searchsup_GetLMRReduction.c new file mode 100644 index 0000000..0ee69f6 --- /dev/null +++ b/src/lmr_testing/searchsup_GetLMRReduction.c @@ -0,0 +1,114 @@ +// Replace searchsup.c's GetLMRReduction body with this (signature/name +// unchanged, so search.c/split.c call sites need no changes). + +INT +GetLMRReduction(IN SCORE iRoughEval, + IN SCORE iAlpha, + IN SCORE iBeta, + IN SEARCHER_THREAD_CONTEXT *ctx, + IN ULONG uRemainingDepth, + IN ULONG uLegalMoves, + IN MOVE mv, + IN ULONG uMoveNum, + IN INT iExtend) +/** + +Routine description: + + Decide how much (if any) to reduce this move's search depth by -- + graded LMR, replacing the old fixed -ONE_PLY history pruning. + Note: this function is called after the move has been played on + the board (ctx->uPly is already the *child's* ply; ctx->uPly - 1 + is the node whose move loop we're in, i.e. the parent of the + search we're about to reduce). + + PV-parent protection: if the node whose move this is (uPly - 1) + was itself reached with a wide window (a genuine PV node, not + just non-null-window), don't reduce at all here. This is + deliberately about the *parent*, not "am I a PV node myself" -- + every non-first move at a PV node is searched null-window + regardless (standard PVS), so that alone doesn't distinguish + "one ply below a real PV" from "deep inside an already-non-PV + subtree". Confirmed empirically (not just theoretically) to + matter: without this, graded LMR measured worse than baseline; + with it, break-even. Grandparent protection (uPly - 2) was + tried and measured worse (23/30, EBF 4.258 vs this config's + 24/30, EBF 4.223 on ecm_quick.ep_ @ sn=4M) -- not worth it. + +Parameters: + + SEARCHER_THREAD_CONTEXT *ctx, + ULONG uRemainingDepth, + ULONG uLegalMoves, + MOVE mv, + INT iExtend + +Return value: + + INT : 0 if no reduction, else a negative ply-fraction (ONE_PLY + units) suitable for adding directly into iExtend. + +**/ +{ + ULONG uDepthPly, uMoveIdx; + INT iReduction; + + ASSERT(ctx->uPly > 0); + ASSERT(mv.uMove); + ASSERT((uMoveNum > 0) || (uLegalMoves == 0)); + if ((uRemainingDepth >= TWO_PLY) && + (iBeta == (iAlpha + 1)) && + (uLegalMoves > 3) && + (0 == iExtend) && + (!IS_ESCAPING_CHECK(mv)) && + (!IS_CAPTURE_OR_PROMOTION(mv)) && + (!IS_CHECKING_MOVE(mv)) && + (!IS_SAME_MOVE(mv, ctx->mvKiller[ctx->uPly-1][0])) && + (!IS_SAME_MOVE(mv, ctx->mvKiller[ctx->uPly-1][1])) && + ((ctx->uPly < 3) || + (!IS_SAME_MOVE(mv, ctx->mvKiller[ctx->uPly-3][0]) && + !IS_SAME_MOVE(mv, ctx->mvKiller[ctx->uPly-3][1]))) && + (!IS_SAME_MOVE(mv, ctx->mvCounter[MOVE_TO_INDEX(ctx->sPlyInfo[ctx->uPly-1].mv)][0])) && + (!IS_SAME_MOVE(mv, ctx->mvCounter[MOVE_TO_INDEX(ctx->sPlyInfo[ctx->uPly-1].mv)][1])) && + (GetMoveFailHighPercentage(mv) <= 10)) + { + ASSERT(!InCheck(&ctx->sPosition, ctx->sPosition.uToMove)); + uDepthPly = MINU(uRemainingDepth / ONE_PLY, MAX_PLY_PER_SEARCH); + uMoveIdx = MINU(uLegalMoves, LMR_TABLE_MAX_MOVES); + + // Base reduction (matches the old fixed -ONE_PLY history-pruning + // behavior) plus a graded extra on top from the table, same + // structure as the prior validated implementation. + iReduction = -(ONE_PLY + g_iLMRQuietReduction[uDepthPly][uMoveIdx]); + + // NOTE: tried an "improving" signal here (Crafty/Berserk/SF-style, + // comparing static eval to 2 plies ago), both with + // GetRoughEvalScore's material-only fallback and with a real + // Eval() call (LAZY_EVAL-fast-pathed) feeding pi->iEval -- both + // measured identically worse (23/30, EBF 4.252 vs this config's + // 24/30, EBF 4.223). The signal itself isn't paying off here, not + // just the eval-quality proxy; not worth pursuing further without + // a different formulation. + + // Soft PV-adjacency discount (Crafty-style): if the parent node + // (whose move loop we're in) was itself a genuine PV node, reduce + // one ply less rather than skipping the reduction outright. + if (TRUE == ctx->sPlyInfo[ctx->uPly - 1].fPvNode) + { + iReduction += ONE_PLY; + if (iReduction > 0) iReduction = 0; + } + if (0 == iReduction) return(0); + + // Never reduce past leaving less than TWO_PLY of remaining depth -- + // a reduced child must still get a real search, not be treated as + // a leaf/qsearch node by accident. + if ((INT)uRemainingDepth + iReduction < TWO_PLY) + { + iReduction = -MAX((INT)uRemainingDepth - TWO_PLY, 0); + } + ASSERT(iReduction <= 0); + return(iReduction); + } + return(0); +} -- cgit v1.3