summaryrefslogtreecommitdiff
path: root/src/board.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-15 14:43:17 -0700
committerScott Gasch <[email protected]>2026-10-15 14:43:17 -0700
commitac7572166bd24868fcaf5980a91bb82fe61ba677 (patch)
tree8c583c3c631690e752d62bff674733358b48d1e2 /src/board.c
parent993b6eeb2a9ac7b659c473b6883fa53af1ad3a0b (diff)
NNUE accumulator struct scaffolding; fix 3 bugs found by DEBUG+TEST; add that gate
Partial checkpoint on the nnue-accumulator-plumbing branch, before the MakeMove/UnmakeMove hookup itself: - chess.h: NNUE_ACCUMULATOR struct (256 floats, pre-activation, float32 for now -- quantization to int16 deliberately deferred until a real quantization-aware export pass exists, per docs/NNUE.md). Added the live, always-current SEARCHER_THREAD_CONTEXT.sNnueAccum[2] (one per color) and PLY_INFO.sNnueAccumBackup (the king-move full-recompute backup slot, DEBUG-only color tag declared for a later ASSERT). LIGHTWEIGHT_SEARCHER_CONTEXT also mirrors sNnueAccum[2] at the same relative position -- omitting that broke its byte-compatible-prefix contract with the full context the moment MakeMove/UnmakeMove will start touching it unconditionally (caught by this session's own TestSan self-test failure). - Combining DEBUG=1 and TEST=1 for the first time (apparently ever) while chasing that failure immediately turned up three more real, pre-existing bugs, all in self-test-only code whose assert-guarded invariants had never been checked with asserts actually compiled in: - board.c: IsBoardLegal now rejects an en passant square whose rank doesn't match the side to move (and verifies the double-moved pawn/empty origin square), closing a gap VALID_EP_SQUARE alone can't catch. - testgenerate.c: TestCapturesAndPromotionsGenerator's fixture table had a self-contradictory FEN this new IsBoardLegal check would have caught; replaced with a genuine Black-to-move en passant position. - testgenerate.c: TestFindUnblockedSquaresSpeed called _GenerateAllMoves directly instead of the GenerateMoves wrapper, skipping the DEBUG-only pStack->board[uPly] snapshot WouldGiveCheck's own ASSERT checks against. - precommit_check.sh / CLAUDE.md: added the DEBUG=1 TEST=1 combination as a permanent third pre-commit gate, given what its first-ever run turned up. Real cost (~235s of ~266s total, unoptimized build dominates), judged worth it. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/board.c')
-rw-r--r--src/board.c46
1 files changed, 46 insertions, 0 deletions
diff --git a/src/board.c b/src/board.c
index 0f3db09..80b5acb 100644
--- a/src/board.c
+++ b/src/board.c
@@ -191,6 +191,52 @@ Return value:
{
return(FALSE);
}
+
+ // En passant square must be consistent with whose move it is, not
+ // just "a plausible ep square in isolation" (chess.h's
+ // VALID_EP_SQUARE already checks that -- rank 3 or 6 -- but not
+ // against uToMove). An ep square records the square a just-moved
+ // pawn passed over: if it's White to move, BLACK must have just
+ // double-pushed (passing over rank 6); if Black to move, WHITE
+ // must have (passing over rank 3). Also verify the double-moved
+ // pawn is actually there and its origin square is now empty --
+ // not reachable through real play otherwise. 2026-10-15: a
+ // malformed hand-built FEN (here, testgenerate.c's
+ // TestCapturesAndPromotionsGenerator fixture table) fell through
+ // this exact gap -- ep square on rank 6 with BLACK to move,
+ // self-contradictory, but VALID_EP_SQUARE alone can't catch it.
+ if (IS_ON_BOARD(pos->cEpSquare))
+ {
+ COOR cEp = pos->cEpSquare;
+ ULONG uJustMoved = FLIP(pos->uToMove);
+ COOR cDoubleMovedPawn, cOriginSquare;
+
+ if (WHITE == pos->uToMove)
+ {
+ if (!RANK6(cEp))
+ {
+ return(FALSE);
+ }
+ cDoubleMovedPawn = FILE_RANK_TO_COOR(FILE(cEp), 5);
+ cOriginSquare = FILE_RANK_TO_COOR(FILE(cEp), 7);
+ }
+ else
+ {
+ if (!RANK3(cEp))
+ {
+ return(FALSE);
+ }
+ cDoubleMovedPawn = FILE_RANK_TO_COOR(FILE(cEp), 4);
+ cOriginSquare = FILE_RANK_TO_COOR(FILE(cEp), 2);
+ }
+
+ if (!IS_PAWN(pos->rgSquare[cDoubleMovedPawn].pPiece) ||
+ (GET_COLOR(pos->rgSquare[cDoubleMovedPawn].pPiece) != uJustMoved) ||
+ !IS_EMPTY(pos->rgSquare[cOriginSquare].pPiece))
+ {
+ return(FALSE);
+ }
+ }
return(TRUE);
}