Simplify stalemate detection in qsearch.

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
This commit is contained in:
J. Oster
2026-04-15 20:15:36 +02:00
committed by Joost VandeVondele
parent 3cce652564
commit 5f3dcb92ee
+14 -18
View File
@@ -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 // All legal moves have been searched. A special case: if we are
// in check and no legal moves were found, it is checkmate. // in check and no legal moves were found, it is checkmate.
if (ss->inCheck && bestValue == -VALUE_INFINITE) if (!moveCount)
{ {
assert(!MoveList<LEGAL>(pos).size()); if (ss->inCheck) // Checkmate!
return mated_in(ss->ply); // Plies to mate from the root {
assert(!MoveList<LEGAL>(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<LEGAL>(pos).size())
bestValue = VALUE_DRAW;
} }
if (!is_decisive(bestValue) && bestValue > beta) if (!is_decisive(bestValue) && bestValue > beta)
bestValue = (bestValue + beta) / 2; 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<LEGAL>(pos).size()) // stalemate
bestValue = VALUE_DRAW;
pos.state()->checkersBB = 0;
}
}
// Save gathered info in transposition table. The static evaluation // Save gathered info in transposition table. The static evaluation
// is saved as it was before adjustment by correction history. // is saved as it was before adjustment by correction history.
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit, ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit,