From 46ac9a7e6a656cece337bbfc226ab7caeb9fd72b Mon Sep 17 00:00:00 2001 From: Robert Nurnberg Date: Sat, 28 Feb 2026 11:00:17 +0100 Subject: [PATCH] prevent unproven mated-in scores in game play This PR removes the variable threads.abortedSearch introduced in #4990 and relies on threads.stop and completedDepth instead. The logic in master is only guaranteed to work in single threaded search. For multiple threads, as soon as threads.stop is set, some of the threads may abort their search, and so their mated-in scores are not reliable. Compared to master, this patch prevents unproven mated-in scores in these multi-threaded scenarios: in game play (when time management is used) when the uci commands quit or stop are received when maximum depth is reached for go mate commands The patch will change the bestmove in those very rare situations where the bug occurs in master. This is because bestmove is taken from rootMoves[0].pv[0], which will now no longer contain unproven mated-in PVs. In that sense the patch is "functional". But of course, it does not change bench. While at it, we also fix a bug in master that suppressed PV output for inexact mated-in scores on receiving threads.stop. In cases where uciScore < score with score not being a proven loss, and where the new bestmove was different from the last completed iteration, this led to a bestmove output that did not match the final PV (which was for an exact score). Fixes #6293. Fixes #6626. closes https://github.com/official-stockfish/Stockfish/pull/6636 No functional change --- src/search.cpp | 18 ++++++++++-------- src/thread.cpp | 4 ++-- src/thread.h | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 301baecf8..8c9c4f8bc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -423,12 +423,14 @@ void Search::Worker::iterative_deepening() { if (mainThread && (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000) - // A thread that aborted search can have mated-in/TB-loss PV and - // score that cannot be trusted, i.e. it can be delayed or refuted - // if we would have had time to fully search other root-moves. Thus - // we suppress this output and below pick a proven score/PV for this - // thread (from the previous iteration). - && !(threads.abortedSearch && is_loss(rootMoves[0].uciScore))) + // A thread that aborted search can have a mated-in/TB-loss score and + // PV that cannot be trusted, i.e. it can be delayed or refuted if we + // would have had time to fully search other root-moves. Thus here we + // suppress any exact mated-in/TB loss output and, if we do, below pick + // the score/PV from the previously completed iteration with the most + // recent bestmove change. + && !(threads.stop && is_loss(rootMoves[0].uciScore) + && rootMoves[0].score == rootMoves[0].uciScore)) main_manager()->pv(*this, threads, tt, rootDepth); if (threads.stop) @@ -440,7 +442,7 @@ void Search::Worker::iterative_deepening() { // We make sure not to pick an unproven mated-in score, // in case this thread prematurely stopped search (aborted-search). - if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE + if (completedDepth != rootDepth && rootMoves[0].score != -VALUE_INFINITE && is_loss(rootMoves[0].score)) { // Bring the last best move to the front for best thread selection. @@ -1963,7 +1965,7 @@ void SearchManager::check_time(Search::Worker& worker) { && ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) || (worker.limits.movetime && elapsed >= worker.limits.movetime) || (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes))) - worker.threads.stop = worker.threads.abortedSearch = true; + worker.threads.stop = true; } // Used to correct and extend PVs for moves that have a TB (but not a mate) score. diff --git a/src/thread.cpp b/src/thread.cpp index 441af563d..a2f59d5b1 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -290,8 +290,8 @@ void ThreadPool::start_thinking(const OptionsMap& options, main_thread()->wait_for_search_finished(); - main_manager()->stopOnPonderhit = stop = abortedSearch = false; - main_manager()->ponder = limits.ponderMode; + main_manager()->stopOnPonderhit = stop = false; + main_manager()->ponder = limits.ponderMode; increaseDepth = true; diff --git a/src/thread.h b/src/thread.h index f97e2b3f8..d6032d295 100644 --- a/src/thread.h +++ b/src/thread.h @@ -153,7 +153,7 @@ class ThreadPool { void ensure_network_replicated(); - std::atomic_bool stop, abortedSearch, increaseDepth; + std::atomic_bool stop, increaseDepth; auto cbegin() const noexcept { return threads.cbegin(); } auto begin() noexcept { return threads.begin(); }