From 5f3dcb92ee097f3f1c3656570c4d1a473e95e3aa Mon Sep 17 00:00:00 2001 From: "J. Oster" Date: Wed, 15 Apr 2026 20:15:23 +0200 Subject: [PATCH] Simplify stalemate detection in qsearch. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conditions for no pawn pushes, non-pawn material and last captured piece are already such restrictive that we don't need to artificially set checkers bitboard for legal move generation. Also moving the check for available pawn pushes at first place, as it should already filter about 90% of cases. Passed STC and LTC non-regression tests. LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 171232 W: 44202 L: 44131 D: 82899 Ptnml(0-2): 466, 18809, 47023, 18824, 494 https://tests.stockfishchess.org/tests/view/69d3623b61a12cebe17ededa LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 55662 W: 14424 L: 14247 D: 26991 Ptnml(0-2): 38, 5479, 16609, 5678, 27 https://tests.stockfishchess.org/tests/view/69d64cada07a818b0a25f95e And also neutral on 10k fixed games test with custom stalemate book. Elo: 0.24 ± 0.9 (95%) LOS: 69.9% Total: 10000 W: 4681 L: 4674 D: 645 Ptnml(0-2): 0, 87, 4819, 94, 0 nElo: 1.81 ± 6.8 (95%) PairsRatio: 1.08 https://tests.stockfishchess.org/tests/view/69d7e4183ca80bdf151d43b8 closes https://github.com/official-stockfish/Stockfish/pull/6715 Bench: 2984258 --- src/search.cpp | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 82f6c6750..2dcfb86d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1711,32 +1711,28 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } } - // Step 9. Check for mate + // Step 9. Check for mate and stalemate // All legal moves have been searched. A special case: if we are // in check and no legal moves were found, it is checkmate. - if (ss->inCheck && bestValue == -VALUE_INFINITE) + if (!moveCount) { - assert(!MoveList(pos).size()); - return mated_in(ss->ply); // Plies to mate from the root + if (ss->inCheck) // Checkmate! + { + assert(!MoveList(pos).size()); + return mated_in(ss->ply); // Plies to mate from the root + } + + // Only check for stalemate under specific conditions + Color us = pos.side_to_move(); + if (!(pawn_single_push_bb(us, pos.pieces(us, PAWN)) & ~pos.pieces()) + && !pos.non_pawn_material(us) && type_of(pos.captured_piece()) >= KNIGHT + && !MoveList(pos).size()) + bestValue = VALUE_DRAW; } if (!is_decisive(bestValue) && bestValue > beta) bestValue = (bestValue + beta) / 2; - Color us = pos.side_to_move(); - if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us) - && type_of(pos.captured_piece()) >= ROOK) - { - if (!(pawn_single_push_bb(us, pos.pieces(us, PAWN)) - & ~pos.pieces())) // no pawn pushes available - { - pos.state()->checkersBB = Rank1BB; // search for legal king-moves only - if (!MoveList(pos).size()) // stalemate - bestValue = VALUE_DRAW; - pos.state()->checkersBB = 0; - } - } - // Save gathered info in transposition table. The static evaluation // is saved as it was before adjustment by correction history. ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit,