diff options
Diffstat (limited to 'src/search.c')
| -rwxr-xr-x | src/search.c | 522 |
1 files changed, 474 insertions, 48 deletions
diff --git a/src/search.c b/src/search.c index 25318ed..fdcb955 100755 --- a/src/search.c +++ b/src/search.c @@ -1087,18 +1087,311 @@ Return value: FALSE if it can be skipped **/ -#define QSEARCH_CONSIDER_MARGIN (120) +#ifdef CALIBRATE_QSEARCH_FUTILITY +// 2026-09-08: measures whether _ShouldWeConsiderThisMove's futility +// gates are set correctly, by -- at the moment a move would be +// rejected -- actually searching it anyway (fully unpruned, via +// ctx->fDiagUnprunedSubtree) and checking whether it would genuinely +// have raised alpha. Never changes real search behavior: the +// diagnostic re-search's result is used only to log a sample, then +// discarded (same non-interference pattern as CALIBRATE_MARGIN_SAFETY +// in eval.c). +// +// Bucketed by (gate, margin-neutral distance short of the relevant +// threshold, piPositional bucket, which Eval() tier supplied +// piPositional) so a single run answers several questions at once: +// is the margin itself wide enough (distance-vs-surprise-rate curve +// within a gate), does piPositional actually predict surprises +// (compare curves across piPositional buckets at the same distance), +// and does that answer differ by tier (super-lazy vs regular-lazy vs +// full eval). +#define QFUT_DIST_BUCKETS (6) +#define QFUT_POS_BUCKETS (4) + +static UINT64 g_uQFutTries[QFUT_GATE_COUNT][QFUT_DIST_BUCKETS][QFUT_POS_BUCKETS][EVAL_POSITIONAL_SOURCE_COUNT]; +static UINT64 g_uQFutSurprises[QFUT_GATE_COUNT][QFUT_DIST_BUCKETS][QFUT_POS_BUCKETS][EVAL_POSITIONAL_SOURCE_COUNT]; + +static ULONG +_QFutDistanceBucket(IN SCORE iDistance) +/* iDistance: how far short of the relevant threshold this move was + (positive = short; a move that actually cleared the bar never gets + here, so this should always be > 0, but negative/zero is folded + into bucket 0 defensively rather than asserting -- a measurement + harness should never crash a calibration run over its own bucketing + edge case). */ +{ + if (iDistance <= 25) return(0); + if (iDistance <= 50) return(1); + if (iDistance <= 100) return(2); + if (iDistance <= 200) return(3); + if (iDistance <= 400) return(4); + return(5); +} + +static ULONG +_QFutPositionalBucket(IN SCORE iPositional) +{ + if (iPositional < 0) return(0); + if (iPositional < 25) return(1); + if (iPositional < 75) return(2); + return(3); +} + +static void +_QFutDiagnoseReject(IN SEARCHER_THREAD_CONTEXT *ctx, + IN ULONG uMoveNum, + IN SCORE iAlpha, + IN SCORE iBeta, + IN SCORE iPositional, + IN ULONG uPositionalSource, + IN ULONG uGate, + IN SCORE iDistance, + IN FLAG fGeneratedChecks) +/** + +Routine description: + + A move is about to be rejected by one of _ShouldWeConsiderThisMove's + futility gates. Before rejecting it for real, search it anyway + (fully unpruned) to see whether it would actually have raised + alpha, and log the outcome. The diagnostic search's result is + discarded -- this function never changes what the caller does. + +Return value: + + void + +**/ +{ + MOVE mv; + SCORE iScore; + ULONG uDistBucket, uPosBucket; + + // Never diagnose from inside an already-diagnostic (fully + // unpruned) subtree -- structurally shouldn't happen anyway, since + // nothing gets rejected while fDiagUnprunedSubtree is set, but + // guard explicitly rather than relying on that. + if (TRUE == ctx->fDiagUnprunedSubtree) + { + return; + } + ASSERT(uGate < QFUT_GATE_COUNT); + ASSERT(uPositionalSource < EVAL_POSITIONAL_SOURCE_COUNT); + + mv = ctx->sMoveStack.mvf[uMoveNum].mv; + // Mirror QSearch's own move loop exactly: GenerateMoves only tags + // MVF_CHECK/the checking-move bit when checks were actually being + // generated this ply. Skipping this (as an earlier version of this + // function did) leaves the flag unset on a move that objectively + // does give check, which MakeMove/ply-info bookkeeping trusts + // blindly -- the next ply's fInCheck-vs-InCheck() consistency + // ASSERT (searchsup.c) catches the mismatch immediately. + if (FALSE == fGeneratedChecks) + { + mv.bvFlags |= WouldGiveCheck(ctx, mv); + } + if (FALSE == MakeMove(ctx, mv)) + { + return; + } + ctx->fDiagUnprunedSubtree = TRUE; + ctx->sSearchFlags.uQsearchDepth++; + iScore = -QSearch(ctx, -iBeta, -iAlpha); + ctx->sSearchFlags.uQsearchDepth--; + ctx->fDiagUnprunedSubtree = FALSE; + UnmakeMove(ctx, mv); + + uDistBucket = _QFutDistanceBucket(iDistance); + uPosBucket = _QFutPositionalBucket(iPositional); + g_uQFutTries[uGate][uDistBucket][uPosBucket][uPositionalSource]++; + if (iScore > iAlpha) + { + g_uQFutSurprises[uGate][uDistBucket][uPosBucket][uPositionalSource]++; + } +} + + +static CHAR *g_szQFutGateNames[QFUT_GATE_COUNT] = +{ + "generic capture/promo", + "checking capture/promo (VALUE_ROOK)", + "quiet check (VALUE_BISHOP)", +}; +static CHAR *g_szQFutSourceNames[EVAL_POSITIONAL_SOURCE_COUNT] = +{ + "full eval", + "regular lazy", + "super lazy", +}; + +void +DumpQSearchFutilityCalibration(void) +/** + +Routine description: + + Print, per (gate, piPositional-source tier), the surprise rate + (fraction of diagnostically-re-searched rejects that actually + raised alpha) by distance-short-of-threshold bucket, and + separately by piPositional bucket at the widest distance bucket -- + read the former for "is this gate's margin wide enough," the + latter (compared across piPositional buckets at a fixed distance) + for "does piPositional actually predict surprises." + +Parameters: + + void + +Return value: + + void + +**/ +{ + ULONG g, d, p, s; + static CHAR *szDistLabel[QFUT_DIST_BUCKETS] = + { "0-25", "25-50", "50-100", "100-200", "200-400", "400+" }; + static CHAR *szPosLabel[QFUT_POS_BUCKETS] = + { "<0", "0-25", "25-75", "75+" }; + + for (g = 0; g < QFUT_GATE_COUNT; g++) + { + Trace("QSearch futility gate: %s\n", g_szQFutGateNames[g]); + for (s = 0; s < EVAL_POSITIONAL_SOURCE_COUNT; s++) + { + UINT64 u64TotalTries = 0; + for (d = 0; d < QFUT_DIST_BUCKETS; d++) + { + for (p = 0; p < QFUT_POS_BUCKETS; p++) + { + u64TotalTries += g_uQFutTries[g][d][p][s]; + } + } + if (0 == u64TotalTries) + { + continue; + } + Trace(" source=%s (n=%" COMPILER_LONGLONG_UNSIGNED_FORMAT "):\n", + g_szQFutSourceNames[s], u64TotalTries); + Trace(" by distance short of bar (summed over piPositional buckets):\n"); + for (d = 0; d < QFUT_DIST_BUCKETS; d++) + { + UINT64 u64Tries = 0, u64Surprises = 0; + for (p = 0; p < QFUT_POS_BUCKETS; p++) + { + u64Tries += g_uQFutTries[g][d][p][s]; + u64Surprises += g_uQFutSurprises[g][d][p][s]; + } + if (0 == u64Tries) + { + continue; + } + Trace(" dist %8s: %6.2f%% surprise rate (n=%" + COMPILER_LONGLONG_UNSIGNED_FORMAT ")\n", + szDistLabel[d], + 100.0 * (double)u64Surprises / (double)u64Tries, + u64Tries); + } + Trace(" by piPositional bucket (summed over distance buckets):\n"); + for (p = 0; p < QFUT_POS_BUCKETS; p++) + { + UINT64 u64Tries = 0, u64Surprises = 0; + for (d = 0; d < QFUT_DIST_BUCKETS; d++) + { + u64Tries += g_uQFutTries[g][d][p][s]; + u64Surprises += g_uQFutSurprises[g][d][p][s]; + } + if (0 == u64Tries) + { + continue; + } + Trace(" pos %6s: %6.2f%% surprise rate (n=%" + COMPILER_LONGLONG_UNSIGNED_FORMAT ")\n", + szPosLabel[p], + 100.0 * (double)u64Surprises / (double)u64Tries, + u64Tries); + } + // The marginals above can each look flat on their own even + // when piPositional genuinely matters (its effect might + // only show up at a fixed distance) -- this is the actual + // Q2 answer: read a single distance row across columns. If + // the percentages don't move across piPositional buckets + // at a fixed distance, it isn't predictive; if they fall + // as piPositional rises, it is. + Trace(" cross-tab, surprise%% (rows=distance, cols=piPositional " + "%s/%s/%s/%s):\n", + szPosLabel[0], szPosLabel[1], szPosLabel[2], szPosLabel[3]); + for (d = 0; d < QFUT_DIST_BUCKETS; d++) + { + UINT64 u64RowTries = 0; + for (p = 0; p < QFUT_POS_BUCKETS; p++) + { + u64RowTries += g_uQFutTries[g][d][p][s]; + } + if (0 == u64RowTries) + { + continue; + } + Trace(" dist %8s:", szDistLabel[d]); + for (p = 0; p < QFUT_POS_BUCKETS; p++) + { + UINT64 u64Tries = g_uQFutTries[g][d][p][s]; + if (0 == u64Tries) + { + Trace(" n/a "); + } + else + { + Trace(" %5.1f%% (n=%5" COMPILER_LONGLONG_UNSIGNED_FORMAT ")", + 100.0 * (double)g_uQFutSurprises[g][d][p][s] / + (double)u64Tries, + u64Tries); + } + } + Trace("\n"); + } + } + } +} +#endif // CALIBRATE_QSEARCH_FUTILITY + + +// Flat bonus added to a checking capture/promotion's own (real, +// winning/even) iMoveValue before comparing against iFutility -- +// replaces the retired VALUE_ROOK position-level cutoff for exactly +// this population (see _ShouldWeConsiderThisMove's comment at its use +// site for why). Provisional -- no calibration data yet for this +// specific split; re-measure with `calibrate qsearchfutility`. +#define CHECK_BONUS (150) + static FLAG INLINE _ShouldWeConsiderThisMove(IN SEARCHER_THREAD_CONTEXT *ctx, IN ULONG uMoveNum, IN SCORE iFutility, - IN FLAG fGeneratedChecks) + IN FLAG fGeneratedChecks +#ifdef CALIBRATE_QSEARCH_FUTILITY + , IN SCORE iAlpha + , IN SCORE iBeta + , IN SCORE iPositional + , IN ULONG uPositionalSource +#endif + ) { +#ifdef DEBUG MOVE mvLast = ctx->sPlyInfo[ctx->uPly - 1].mv; +#endif MOVE mv = ctx->sMoveStack.mvf[uMoveNum].mv; ULONG uColor; - SCORE i; + SCORE iMoveValue; + // TRUE once iMoveValue holds a real, comparable SEE-derived value + // (winning/even captures and promotions) rather than the raw, + // not-directly-comparable generation-time score a losing capture + // keeps. Used both for real control flow (the checking-move + // branch below needs to know which of two populations it's + // looking at) and, under CALIBRATE_QSEARCH_FUTILITY, to decide + // whether a reject is worth diagnosing. + FLAG fHaveMoveValue = FALSE; ASSERT(!IS_CHECKING_MOVE(mvLast)); ASSERT(!InCheck(&(ctx->sPosition), ctx->sPosition.uToMove)); @@ -1119,12 +1412,13 @@ _ShouldWeConsiderThisMove(IN SEARCHER_THREAD_CONTEXT *ctx, } } - i = ctx->sMoveStack.mvf[uMoveNum].iValue; - if (i >= SORT_THESE_FIRST) + iMoveValue = ctx->sMoveStack.mvf[uMoveNum].iValue; + if (iMoveValue >= SORT_THESE_FIRST) { - i &= STRIP_OFF_FLAGS; - ASSERT(i >= 0); - i -= MOVE_SCORE_ORDERING_BIAS(mv); + fHaveMoveValue = TRUE; + iMoveValue &= STRIP_OFF_FLAGS; + ASSERT(iMoveValue >= 0); + iMoveValue -= MOVE_SCORE_ORDERING_BIAS(mv); if (mv.pCaptured) { // If there are very few pieces left on the board, @@ -1151,21 +1445,30 @@ _ShouldWeConsiderThisMove(IN SEARCHER_THREAD_CONTEXT *ctx, } // Don't trust the SEE alone for alpha pruning decisions. - i = MAXU(i, PIECE_VALUE(mv.pCaptured)); + iMoveValue = MAXU(iMoveValue, PIECE_VALUE(mv.pCaptured)); - // Also try hard not to prune recaps, the bad trade - // penalty can make them look "futile" sometimes. - if ((PIECE_VALUE(mv.pCaptured) == - PIECE_VALUE(mvLast.pCaptured)) && - (i + 200 + QSEARCH_CONSIDER_MARGIN > iFutility)) - { - return(TRUE); - } + // RETIRED 2026-09-08: used to give recaptures (same + // captured-piece value as mvLast) a flat +100 bonus + // here ("the bad trade penalty can make them look + // futile sometimes"). CALIBRATE_QSEARCH_FUTILITY data + // showed it wasn't testing real recaptures at all -- + // this check never compared mv.cTo to mvLast.cTo, so + // "recapture-shaped" meant "captured a same-valued + // piece anywhere on the board," diluting genuine + // recaptures (usually safe) with unrelated captures + // (not specially safe) under one bonus. That mismatch + // is the more likely explanation for the gate's high, + // slowly-decaying surprise rate (13.25% at distance + // 0-25, still 3.29% at 400+) than the constant being + // merely too small. Removed rather than re-tuned; + // reintroduce with a same-square check if a bonus + // still looks warranted once the generic gate's own + // margin is fixed. } // Otherwise, even if a move is even/winning, make sure it // brings the score up to at least somewhere near alpha. - if (i + QSEARCH_CONSIDER_MARGIN > iFutility) + if (iMoveValue > iFutility) { return(TRUE); } @@ -1175,10 +1478,73 @@ _ShouldWeConsiderThisMove(IN SEARCHER_THREAD_CONTEXT *ctx, // that checked or a "futile" winning capture/prom that may or // may not check. Be more willing to play checking captures // even if they look bad. + // + // RETIRED VALUE_ROOK 2026-09-08: used to judge both + // populations below by a single position-level "iFutility < + // VALUE_ROOK" cutoff, discarding iMoveValue entirely even + // when a real one existed. CALIBRATE_QSEARCH_FUTILITY data + // showed a flat, non-decaying-with-distance surprise rate + // (8-16%, no better far past the threshold than right at it) + // -- the signature of a position-level test standing in for a + // move-level question it can't actually answer. Split into + // the two populations that were being conflated: a move with + // a real (winning/even) iMoveValue gets the same value-plus- + // flat-bonus treatment as any other capture; a move with no + // usable value (SEE already called it losing) falls back to + // GetCheckSee's real tactical judgment, exactly like the + // quiet-check (VALUE_BISHOP) branch already does below. if (IS_CHECKING_MOVE(mv) && (TRUE == fGeneratedChecks)) { - return(iFutility < +VALUE_ROOK); + if (TRUE == fHaveMoveValue) + { + FLAG fConsider = (iMoveValue + CHECK_BONUS > iFutility); +#ifdef CALIBRATE_QSEARCH_FUTILITY + if (FALSE == fConsider) + { + _QFutDiagnoseReject(ctx, uMoveNum, iAlpha, iBeta, + iPositional, uPositionalSource, + QFUT_GATE_CHECK_ROOK, + iFutility - (iMoveValue + CHECK_BONUS), + fGeneratedChecks); + } +#endif + return(fConsider); + } + { + FLAG fConsider = (GetCheckSee(ctx, mv, uMoveNum) >= 0); +#ifdef CALIBRATE_QSEARCH_FUTILITY + if (FALSE == fConsider) + { + // No value-level threshold left for this + // population (that's the point) -- iFutility + // itself is the only position-level number left + // to bucket by, used as-is rather than a + // difference from some retired constant. + _QFutDiagnoseReject(ctx, uMoveNum, iAlpha, iBeta, + iPositional, uPositionalSource, + QFUT_GATE_CHECK_ROOK, + iFutility, + fGeneratedChecks); + } +#endif + return(fConsider); + } + } + +#ifdef CALIBRATE_QSEARCH_FUTILITY + // Falls through to the final reject below -- either not a + // checking move, or a checking move we weren't generating + // checks for this ply (rare; the generic gate is still what + // decided this, so tag it the same way). + if (TRUE == fHaveMoveValue) + { + _QFutDiagnoseReject(ctx, uMoveNum, iAlpha, iBeta, + iPositional, uPositionalSource, + QFUT_GATE_GENERIC_CAPTURE, + iFutility - iMoveValue, + fGeneratedChecks); } +#endif } else { @@ -1194,7 +1560,20 @@ _ShouldWeConsiderThisMove(IN SEARCHER_THREAD_CONTEXT *ctx, { return(TRUE); } - return(GetCheckSee(ctx, mv, uMoveNum) >= 0); + { + FLAG fConsider = (GetCheckSee(ctx, mv, uMoveNum) >= 0); +#ifdef CALIBRATE_QSEARCH_FUTILITY + if (FALSE == fConsider) + { + _QFutDiagnoseReject(ctx, uMoveNum, iAlpha, iBeta, + iPositional, uPositionalSource, + QFUT_GATE_CHECK_BISHOP, + iFutility - (SCORE)VALUE_BISHOP, + fGeneratedChecks); + } +#endif + return(fConsider); + } } return(FALSE); } @@ -1249,21 +1628,6 @@ QSearchFromCheckNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx, if ((pf->uQsearchDepth < pf->uQsearchCheckDepth) && (pf->uQsearchDepth < g_uIterateDepth / 4) && (pf->fCouldStandPat[pos->uToMove] == FALSE) && - // - // Threshold doubled 2026-09-06 (board_representation/ - // EVAL.md section 9): was ">2", tuned against the old - // CHECK_VECTOR-based CountKingSafetyDefects. The bitboard - // rewrite (real blocker-aware slider attacks, queen 2x - // weighting) runs systematically hotter for the same - // underlying danger, so the un-rescaled old threshold was - // firing far more liberally than intended -- observed - // directly as a runaway check-extension cascade (85x+ - // branching for a single ply) on a real position. Not yet - // independently recalibrated against real data the way - // iKingSwingP90 was; doubling is a stopgap matching the - // rough inflation this counter picked up, pending a real - // measurement. - // (CountKingSafetyDefects(pos, pos->uToMove) > 4)) { if ((uMoveCount == 1) || @@ -1344,6 +1708,27 @@ QSearchFromCheckNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx, } +// 2026-09-08: was one flat FUTILITY_BASE_MARGIN (150) regardless of +// which Eval() exit tier produced iEval/rgiPositional this call. +// CALIBRATE_QSEARCH_FUTILITY data (1500 real-game positions, +// tests/twic_sample.ep_, sd 6) showed the three tiers need very +// different margins to reach a similar surprise rate: full-eval +// source was still failing 1.65-6.4% of diagnosed rejects even +// hundreds of centipawns short of the bar, regular-lazy was already +// close to safe (1.26% down to 0.43%), super-lazy was only risky very +// close to the bar (7.96% at distance 0-25, 0.54% by 400+). Indexed +// by ctx->uLastPositionalSource (set inside Eval() -- see +// EVAL_POSITIONAL_SOURCE_* in chess.h). Provisional, derived from one +// run at sd 6; re-derive with `calibrate qsearchfutility` if search +// behavior affecting typical qsearch iEval/iFutility gaps changes. +static const SCORE FUTILITY_BASE_MARGIN_BY_SOURCE[EVAL_POSITIONAL_SOURCE_COUNT] = +{ + FUTILITY_BASE_MARGIN_FULL, // EVAL_POSITIONAL_SOURCE_FULL + FUTILITY_BASE_MARGIN_LAZY, // EVAL_POSITIONAL_SOURCE_LAZY + FUTILITY_BASE_MARGIN_SUPERLAZY, // EVAL_POSITIONAL_SOURCE_SUPERLAZY +}; + + /** Routine description: @@ -1380,7 +1765,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, SCORE iScore; SCORE iEval; SCORE iFutility; - SCORE iPositional; + SCORE rgiPositional[2]; ULONG x; #ifdef PERF_COUNTERS ULONG uLegalMoves; @@ -1459,7 +1844,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, } ASSERT(!InCheck(pos, pos->uToMove)); - iEval = iBestScore = Eval(ctx, iAlpha, iBeta, &iPositional); + iEval = iBestScore = Eval(ctx, iAlpha, iBeta, &rgiPositional); // If that Eval (above) was full (i.e. not lazy) it may have set // en prise and trapped piece indicators. Likewise, other nodes @@ -1480,6 +1865,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, } else { + ctx->sSearchFlags.fCouldStandPat[pos->uToMove] = TRUE; if (iBestScore > iAlpha) { iAlpha = iBestScore; @@ -1490,7 +1876,6 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, goto end; } } - ctx->sSearchFlags.fCouldStandPat[pos->uToMove] = TRUE; } // He did not choose to stand pat here or we did not allow it. We @@ -1504,20 +1889,51 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, iFutility = 0; if (iAlpha < +NMATE) { - iFutility = iAlpha - (FUTILITY_BASE_MARGIN + iPositional) - iEval; +#ifdef CALIBRATE_QSEARCH_FUTILITY + if (TRUE == ctx->fDiagUnprunedSubtree) + { + // We're inside a diagnostic "what if this rejected move + // had been searched anyway" re-search (see + // _QFutDiagnoseReject) -- force every gate in this + // function wide open so the diagnostic subtree isn't + // contaminated by the same pruning it exists to measure. + iFutility = -NMATE; + } + else +#endif +#ifdef DIAG_NO_QSEARCH_FUTILITY + // Diagnostic-only (never defined in a normal build): forces + // _ShouldWeConsiderThisMove's gates wide open so every + // capture/checking-move margin question in this file passes + // trivially, to measure the node-count/time cost of qsearch + // futility pruning as a whole -- not for shipping, just for + // sizing how expensive a "fully unpruned diagnostic subtree" + // would be for the qsearch-futility calibration harness. + iFutility = -NMATE; +#else + ASSERT(ctx->uLastPositionalSource < EVAL_POSITIONAL_SOURCE_COUNT); + iFutility = iAlpha - + (FUTILITY_BASE_MARGIN_BY_SOURCE[ctx->uLastPositionalSource] + + rgiPositional[pos->uToMove]) - iEval; iFutility = MAX0(iFutility); +#endif } // We know we are not in check. If we are early in the qsearch, // and the other side has not yet been able to stand pat yet, and // we have material OR we have hanging pieces, generate checks // here too. Checks are a "good way" to escape from "trouble". - fIncludeChecks = ((pf->uQsearchDepth < pf->uQsearchCheckDepth) && - (((pf->fCouldStandPat[FLIP(pos->uToMove)] == FALSE) && - (pos->uNonPawnMaterial[pos->uToMove] > - (VALUE_KING + VALUE_BISHOP))) || - (FALSE == ctx->sSearchFlags.fCouldStandPat[pos->uToMove]))); - + fIncludeChecks = ( + (pf->uQsearchDepth < pf->uQsearchCheckDepth) && + ( + ( + (pf->fCouldStandPat[FLIP(pos->uToMove)] == FALSE) && + (pos->uNonPawnMaterial[pos->uToMove] > (VALUE_KING + VALUE_BISHOP)) + ) + || + (FALSE == ctx->sSearchFlags.fCouldStandPat[pos->uToMove]) + ) + ); GenerateMoves(ctx, NULLMOVE, _WhatToGen[fIncludeChecks]); #ifdef PERF_COUNTERS @@ -1546,7 +1962,14 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, if (FALSE == _ShouldWeConsiderThisMove(ctx, x, iFutility, - fIncludeChecks)) + fIncludeChecks +#ifdef CALIBRATE_QSEARCH_FUTILITY + , iAlpha + , iBeta + , rgiPositional[pos->uToMove] + , ctx->uLastPositionalSource +#endif + )) { continue; } @@ -1610,9 +2033,12 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, // Readjust futility margin here; it can be wider now. if (iAlpha < +NMATE) { + ASSERT(ctx->uLastPositionalSource < + EVAL_POSITIONAL_SOURCE_COUNT); iFutility = (iAlpha - - (FUTILITY_BASE_MARGIN + - iPositional) - + (FUTILITY_BASE_MARGIN_BY_SOURCE[ + ctx->uLastPositionalSource] + + rgiPositional[pos->uToMove]) - iEval); iFutility = MAX0(iFutility); } |
