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
This commit is contained in:
Robert Nurnberg
2026-02-28 11:55:03 +01:00
committed by Joost VandeVondele
parent a6d055d7e2
commit 46ac9a7e6a
3 changed files with 13 additions and 11 deletions
+10 -8
View File
@@ -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.
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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(); }