multiPV: protect mated-ins and allow thread selection

This PR is a replacement for #6652 and takes into account the changes made to this part of the code since then.

In multiPV analysis master may suppress genuine proven mated-in scores, if the iteration for a secondary PV line is aborted. In very rare cases, it may also evict a proven mated-in score from the best PV line with a score from an incomplete iteration of a secondary PV line. This PR avoids both.

In addition, we allow best thread selection in multithreaded multiPV searches. This was originally disabled in #524.

Single PV searches are not affected by this patch at all.

Fixes #6642.

closes https://github.com/official-stockfish/Stockfish/pull/6728

No functional change.
This commit is contained in:
Robert Nurnberg
2026-04-26 09:24:34 +02:00
committed by Joost VandeVondele
parent e17725f445
commit 50e8ff1e23
+20 -2
View File
@@ -235,7 +235,7 @@ void Search::Worker::start_searching() {
Skill skill =
Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0);
if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled())
if (!limits.depth && !skill.enabled())
bestThread = threads.get_best_thread()->worker.get();
main_manager()->bestPreviousScore = bestThread->rootMoves[0].score;
@@ -426,6 +426,24 @@ bool Search::Worker::iterative_deepening() {
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
}
// In multiPV analysis we do not let aborted searches spoil mated-in/
// TB loss scores from a completed search in an earlier PV line.
// A mated-in/TB loss from an aborted search for pvIdx > 0 can only become
// bestmove in the sorting below, if the current bestmove (and hence also
// the previously searched pvIdx - 1 line) is already a proven loss.
if (threads.stop && pvIdx && is_loss(rootMoves[pvIdx - 1].score)
&& rootMoves[pvIdx] < rootMoves[pvIdx - 1])
{
rootMoves[pvIdx].score = rootMoves[pvIdx].uciScore =
(rootMoves[pvIdx].previousScore != -VALUE_INFINITE
&& rootMoves[pvIdx].previousScore < rootMoves[pvIdx - 1].score)
? rootMoves[pvIdx].previousScore
: rootMoves[pvIdx - 1].score;
rootMoves[pvIdx].previousScore = -VALUE_INFINITE;
rootMoves[pvIdx].scoreLowerbound = rootMoves[pvIdx].scoreUpperbound = false;
rootMoves[pvIdx].pv.resize(1);
}
// Sort the PV lines searched so far and update the GUI
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
@@ -452,7 +470,7 @@ bool Search::Worker::iterative_deepening() {
// A 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 (rootMoves[0].score != -VALUE_INFINITE && is_loss(rootMoves[0].score))
else if (!pvIdx && rootMoves[0].score != -VALUE_INFINITE && is_loss(rootMoves[0].score))
{
// Bring the last best move to the front for best thread selection.
if (!lastIterationPV.empty())