From 1cebdafc1473484f47d11786e734983d47edab14 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" <28635489+robertnurnberg@users.noreply.github.com> Date: Sun, 17 May 2026 20:44:44 +0200 Subject: [PATCH] Help aborted searches remember mate scores This patch improves mate finding results by protecting mate scores from being forgotten in a later iteration. This applies especially to aborted searches, but also to completed iterations in e.g. `go depth` scenarios. The only visible change to the user is the final UCI info line. The case of TB wins/losses was excluded from this patch on purpose. The reason is that in some corner cases related to rounding in some DTZ Syzygy EGTBs, later searches may turn a TB loss into a draw, for example. See #5175 and the example ``` info depth 5 seldepth 5 multipv 1 score cp -20000 nodes 122 nps 8714 hashfull 0 tbhits 26 time 14 pv h1b1 a7b6 f6e5 b6b1 e5f4 c8c3 f4g5 d7h7 g5f4 b1f5 f4f5 c3e5 f5g6 e5f4 g6h7 f4g5 h7h8 b8c7 h8h7 c7d6 h7h8 d6e5 h8h7 e5f6 h7h8 g5g7 info depth 6 seldepth 9 multipv 1 score cp 0 nodes 255 nps 18214 hashfull 0 tbhits 26 time 14 pv h1h2 d7d6 f6g5 a7e7 ``` from #5414. Matetrack for patch: ``` Using ./stockfish on matetrack.epd with --nodes 1000000 Engine ID: Stockfish dev-20260510-dbbc3ed1 Total FENs: 6554 Found mates: 3054 Best mates: 2224 ``` This is slightly more/equal compared to master (3040 and 2224), see https://github.com/vondele/matetrack/blob/faf7cf3be95ab7dfdcde6e035de28a30887cd8a5/matetrack1000000.csv#L4368. Passed STC non-reg: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108224 W: 27565 L: 27429 D: 53230 Ptnml(0-2): 212, 11755, 30064, 11847, 234 https://tests.stockfishchess.org/tests/view/6a00899e9392f0c31721406c closes https://github.com/official-stockfish/Stockfish/pull/6816 No functional change --- src/search.cpp | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 088319104..5e4b4ca2b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -272,8 +272,9 @@ bool Search::Worker::iterative_deepening() { Depth lastBestMoveDepth = 0; Value alpha, beta; - Value bestValue = -VALUE_INFINITE; - Color us = rootPos.side_to_move(); + Value bestValue = -VALUE_INFINITE; + Value lastIterationScore = -VALUE_INFINITE; + Color us = rootPos.side_to_move(); double timeReduction = 1, totBestMoveChanges = 0; int delta, iterIdx = 0; @@ -461,33 +462,48 @@ bool Search::Worker::iterative_deepening() { break; } + const bool forgottenMate = lastIterationScore != -VALUE_INFINITE + && is_mate_or_mated(lastIterationScore) + && (std::abs(rootMoves[0].score) < std::abs(lastIterationScore) + || rootMoves[0].score_is_bound()); + if (!threads.stop) { if (lastIterationPV.empty() || rootMoves[0].pv[0] != lastIterationPV[0]) lastBestMoveDepth = rootDepth; - lastIterationPV = rootMoves[0].pv; + // Do not replace (shorter) mate scores from a previous iteration. + if (!forgottenMate) + { + lastIterationPV = rootMoves[0].pv; + lastIterationScore = rootMoves[0].score; + } } + const bool abortedLossSearch = + threads.stop && !pvIdx && rootMoves[0].score != -VALUE_INFINITE + && is_loss(rootMoves[0].score) && !rootMoves[0].score_is_bound(); + // An exact mated-in/TB-loss score from an aborted search cannot be trusted: the // loss could be delayed or refuted upon exploring the remaining root-moves. // Thus here we roll back to the score from the previous iteration. - else if (!pvIdx && rootMoves[0].score != -VALUE_INFINITE && is_loss(rootMoves[0].score) - && !rootMoves[0].score_is_bound()) + // We do the same if a search has failed to recover a mate score that was found + // in a previous iteration. + if (abortedLossSearch || (rootMoves[0].score != -VALUE_INFINITE && forgottenMate)) { - // Bring the last best move to the front for best thread selection. if (!lastIterationPV.empty()) { Utility::move_to_front(rootMoves, [&lastPV = std::as_const(lastIterationPV)]( const auto& rm) { return rm == lastPV[0]; }); rootMoves[0].pv = lastIterationPV; - rootMoves[0].score = rootMoves[0].uciScore = rootMoves[0].previousScore; + rootMoves[0].score = rootMoves[0].uciScore = lastIterationScore; + rootMoves[0].unset_bound_flags(); if (mainThread) uciPvSent = false; } // For an aborted d1 search we label the loss score as a lower bound. - else + else if (abortedLossSearch) rootMoves[0].scoreLowerbound = true; }