Fix undefined behavior in stalemate trap detection.

An important part of it is a function which detects if we can move a king or
pawn. If this function called before quiet move generation phase the end of the
checked move list is undefined (Thanks to AliceRoselia pointing out we have
some problem because of wrong bench in a test).

This problem exists also in master but is really rarely triggered (thanks to
vondele which found one) but it seen significant more often if the capture
order score is increased.

This fix now simply assumes in this case (and other similiar cases) that a king
or pawn can move without checking any moves.

Passed non-regression STC (with default book):
LLR: 3.03 (-2.94,2.94) <-1.75,0.25>
Total: 203008 W: 52465 L: 52424 D: 98119
Ptnml(0-2): 498, 20293, 59893, 20310, 510
https://tests.stockfishchess.org/tests/view/68a06ec5fd8719b088c8dc1a

Passed non-regression STC (with stalemates book):
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 31616 W: 14418 L: 14366 D: 2832
Ptnml(0-2): 1, 189, 15375, 243, 0
https://tests.stockfishchess.org/tests/view/68a07538fd8719b088c8dc1f

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

Bench: 2996176
This commit is contained in:
Stefan Geschwentner
2025-08-17 22:35:03 +02:00
committed by Joost VandeVondele
parent 4b6b13ce5a
commit c56bd10cb5
2 changed files with 8 additions and 3 deletions
+7 -2
View File
@@ -318,8 +318,13 @@ void MovePicker::skip_quiet_moves() { skipQuiets = true; }
// this function must be called after all quiet moves and captures have been generated
bool MovePicker::can_move_king_or_pawn() const {
// SEE negative captures shouldn't be returned in GOOD_CAPTURE stage
assert(stage > GOOD_CAPTURE && stage != EVASION_INIT);
assert((GOOD_QUIET <= stage && stage <= BAD_QUIET) || stage == EVASION);
// Until good capture state no quiet moves are generated for comparison so simply assume king or pawns can move.
// Do the same for other states that don't have a valid available move list.
if ((GOOD_QUIET > stage || stage > BAD_QUIET) && stage != EVASION)
return true;
for (const ExtMove* m = moves; m < endGenerated; ++m)
{
+1 -1
View File
@@ -67,7 +67,7 @@ class MovePicker {
const PieceToHistory** continuationHistory;
const PawnHistory* pawnHistory;
Move ttMove;
ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated;
ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated = moves;
int stage;
int threshold;
Depth depth;