// 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); }