+ Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves);
+ RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove);
+
+ if (legalMoves[0].tbRank != rm.tbRank)
+ break;
+
+ ply++;
+
+ auto& st = sts.emplace_back();
+ pos.do_move(pvMove, st);
+
+ // Do not allow for repetitions or drawing moves along the PV in TB regime
+ if (config.rootInTB && pos.is_draw(ply))
+ {
+ pos.undo_move(pvMove);
+ ply--;
+ break;
+ }
+
+ // Full PV shown will thus be validated and end in TB.
+ // If we cannot validate the full PV in time, we do not show it.
+ if (config.rootInTB && time_abort())
+ break;
+ }
+
+ // Resize the PV to the correct part
+ rootMove.pv.resize(ply);
+
+ // Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info
+ // using top ranked moves (minimal DTZ), which gives optimal mates only for simple
+ // endgames e.g. KRvK.
+ while (!pos.is_draw(0))
+ {
+ if (time_abort())
+ break;
+
+ RootMoves legalMoves;
+ for (const auto& m : MoveList<LEGAL>(pos))
+ {
+ auto& rm = legalMoves.emplace_back(m);
+ StateInfo tmpSI;
+ pos.do_move(m, tmpSI);
+ // Give a score of each move to break DTZ ties restricting opponent mobility,
+ // but not giving the opponent a capture.
+ for (const auto& mOpp : MoveList<LEGAL>(pos))
+ rm.tbRank -= pos.capture(mOpp) ? 100 : 1;
+ pos.undo_move(m);
+ }
+
+ // Mate found
+ if (legalMoves.size() == 0)
+ break;
+
+ // Sort moves according to their above assigned rank.
+ // This will break ties for moves with equal DTZ in rank_root_moves.
+ std::stable_sort(
+ legalMoves.begin(), legalMoves.end(),
+ [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });
+
+ // The winning side tries to minimize DTZ, the losing side maximizes it
+ Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true);
+
+ // If DTZ is not available we might not find a mate, so we bail out
+ if (!config.rootInTB || config.cardinality > 0)
+ break;
+
+ ply++;
+
+ Move& pvMove = legalMoves[0].pv[0];
+ rootMove.pv.push_back(pvMove);
+ auto& st = sts.emplace_back();
+ pos.do_move(pvMove, st);
+ }
+
+ // Finding a draw in this function is an exceptional case, that cannot happen
+ // during engine game play, since we have a winning score, and play correctly
+ // with TB support. However, it can be that a position is draw due to the 50 move
+ // rule if it has been been reached on the board with a non-optimal 50 move counter
+ // (e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 ) which TB with dtz counter rounding
+ // cannot always correctly rank. See also
+ // https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495
+ // We adjust the score to match the found PV. Note that a TB loss score can be
+ // displayed if the engine did not find a drawing move yet, but eventually search
+ // will figure it out (e.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 )
+ if (pos.is_draw(0))
+ v = VALUE_DRAW;
+
+ // Undo the PV moves
+ for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it)
+ pos.undo_move(*it);
+
+ // Inform if we couldn't get a full extension in time
+ if (time_abort())
+ sync_cout
+ << "info string Syzygy based PV extension requires more time, increase Move Overhead as needed."
+ << sync_endl;
+}
+
+void SearchManager::pv(Search::Worker& worker,
+ const ThreadPool& threads,
+ const TranspositionTable& tt,
+ Depth depth) {
+
+ const auto nodes = threads.nodes_searched();
+ auto& rootMoves = worker.rootMoves;
+ auto& pos = worker.rootPos;
+ size_t pvIdx = worker.pvIdx;
+ size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
+ uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);