From 5297ba0a1a1aa0a15332e0d64ce6b32952342cac Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 22 Nov 2025 12:43:34 -0500 Subject: [PATCH] Move hindsight reductions above cutoffs Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 52640 W: 13701 L: 13361 D: 25578 Ptnml(0-2): 168, 6143, 13356, 6487, 166 https://tests.stockfishchess.org/tests/view/6918dc407ca8781852332339 Passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 207690 W: 53187 L: 52520 D: 101983 Ptnml(0-2): 93, 22515, 57994, 23118, 125 https://tests.stockfishchess.org/tests/view/6919dd307ca87818523324c7 closes https://github.com/official-stockfish/Stockfish/pull/6448 Bench: 2912398 --- src/search.cpp | 98 +++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e30f3dc3..c31a08c49 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -702,6 +702,56 @@ Value Search::Worker::search( // At this point, if excluded, skip straight to step 6, static eval. However, // to save indentation, we list the condition in all code between here and there. + // Step 6. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; + const auto correctionValue = correction_value(*this, pos, ss); + if (ss->inCheck) + { + // Skip early pruning when in check + ss->staticEval = eval = (ss - 2)->staticEval; + improving = false; + } + else if (excludedMove) + unadjustedStaticEval = eval = ss->staticEval; + else if (ss->ttHit) + { + // Never assume anything about values stored in TT + unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) + unadjustedStaticEval = evaluate(pos); + + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // ttValue can be used as a better position evaluation + if (is_valid(ttData.value) + && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttData.value; + } + else + { + unadjustedStaticEval = evaluate(pos); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // Static evaluation is saved as it was before adjustment by correction history + ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), + unadjustedStaticEval, tt.generation()); + } + + // Set up the improving flag, which is true if current static evaluation is + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we go back until we weren't in check) and is + // false otherwise. The improving flag is used in various pruning heuristics. + // Similarly, opponentWorsening is true if our static evaluation is better + // for us than at the last ply. + improving = ss->staticEval > (ss - 2)->staticEval; + opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + + // Hindsight adjustment of reductions based on static evaluation difference. + if (priorReduction >= 3 && !opponentWorsening) + depth++; + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) + depth--; + // At non-PV nodes we check for an early TT cutoff if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() @@ -799,41 +849,8 @@ Value Search::Worker::search( } } - // Step 6. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; - const auto correctionValue = correction_value(*this, pos, ss); if (ss->inCheck) - { - // Skip early pruning when in check - ss->staticEval = eval = (ss - 2)->staticEval; - improving = false; goto moves_loop; - } - else if (excludedMove) - unadjustedStaticEval = eval = ss->staticEval; - else if (ss->ttHit) - { - // Never assume anything about values stored in TT - unadjustedStaticEval = ttData.eval; - if (!is_valid(unadjustedStaticEval)) - unadjustedStaticEval = evaluate(pos); - - ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - - // ttValue can be used as a better position evaluation - if (is_valid(ttData.value) - && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) - eval = ttData.value; - } - else - { - unadjustedStaticEval = evaluate(pos); - ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - - // Static evaluation is saved as it was before adjustment by correction history - ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), - unadjustedStaticEval, tt.generation()); - } // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) @@ -845,21 +862,6 @@ Value Search::Worker::search( pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } - // Set up the improving flag, which is true if current static evaluation is - // bigger than the previous static evaluation at our turn (if we were in - // check at our previous move we go back until we weren't in check) and is - // false otherwise. The improving flag is used in various pruning heuristics. - // Similarly, opponentWorsening is true if our static evaluation is better - // for us than at the last ply. - improving = ss->staticEval > (ss - 2)->staticEval; - opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; - - // Hindsight adjustment of reductions based on static evaluation difference. - if (priorReduction >= 3 && !opponentWorsening) - depth++; - - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) - depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value.