summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-05 10:37:55 -0700
committerScott Gasch <[email protected]>2026-09-05 10:37:55 -0700
commit5405191f8c519333006417a5a3c31bc3e186e42e (patch)
tree1355d4c1cfecbe1777f92df2ecadbb17d8215e37 /src/eval.c
parentaad154a5a07d2793e52a71415cc5c59276a8f629 (diff)
Retire ATTACK_BITV/bvAttacks and the c|8 shadow-index mechanism entirely
Now that king (the last piece writing it) has converted to a bitboard accumulator, nothing writes rgSquare[c|8].bvAttacks any more -- deletes the whole mechanism rather than leaving a known-dead struct around: - chess.h: ATTACK_BITV union gone. SQUARE collapses from a union-with-ATTACK_BITV to a plain {pPiece, uIndex} struct (the #pragma pack(1) that only existed for ATTACK_BITV's bitfield layout goes too). UNSAFE_FOR_ROOK/UNSAFE_FOR_QUEEN and the whole-word MINOR_XRAY_BIT/ ROOK_XRAY_BIT/QUEEN_XRAY_BIT constants deleted -- confirmed unused (only ever referenced in stale comments, not code) now that every consumer reads bbXAttacks bitboards directly. PAWN_BIT/MINOR_BIT/ ROOK_BIT/QUEEN_BIT/KING_BIT stay: they're a separate, still-live local bit-packing scheme _EvalKing/_WhoControlsSquareFast use to build a per-square attack-pattern index into KING_COUNTER_BY_ ATTACK_PATTERN/g_SwapTable, unrelated to the retired storage struct. - eval.c: _ClearAttackTables drops its entire macro-unrolled, 128-square clearing loop (CLEAR_A_SQ/CLEAR_A_RANK/CLEAR_SHORT_RANK, all deleted) -- it only ever existed to zero the old per-square struct; clearing the 7 bbXAttacks accumulators is the whole function now. The transitional _IsSquareAttackedByX/_IsSquareXrayedByX helpers (minor/rook/queen/king, 8 functions total) are deleted outright, not just simplified -- their only remaining purpose was bridging to the now-gone struct, and their DEBUG cross-checks were explicitly migration-only scaffolding, not a permanent invariant. Call sites (_WhoControlsSquareFast, _EvalKing's bvAttack/bvXray/bvDefend) read the bbXAttacks bitboards directly instead. _WhoControlsSquareFast simplifies to a flat OR of 8 bitboard membership tests per color, down from raw struct reads plus 7 helper calls each. Found and fixed one real, pre-existing bug while doing this (flagged and confirmed with the user before touching it, kept as its own documented change rather than silently folded into the mechanical rename): _EvaluateCandidatePasser's helper-pawn-safety gate read rgSquare[c1+8].bvAttacks[...].uWholeThing, but this function runs from _EvalPawns -- the first piece type Eval() evaluates each call, before any non-pawn piece (or, since commit 57502d6 retired pawns' own bvAttacks write, even pawns) has written anything there. That word has therefore been unconditionally zero, and the gate unconditionally true (a silent no-op), since 57502d6 landed -- not something today's cleanup introduced. Left exactly as dead/unconditional (deleted the now-meaningless condition, kept the body it always ran anyway) rather than fixed, since a real fix changes eval scoring and deserves its own before/after check, documented inline for a future session. Verification: precommit_check.sh (self-test + DEBUG smoke test) passes. tests/ecm_ringers.ep_ at sd10 vs. the immediately preceding commit: solve parity holds exactly (10/11 both), and final (depth-10) node counts are byte-identical for all 11 positions -- the bar for a change meant to be purely mechanical, unlike king's own conversion. One harmless artifact noted: ECM.750 has a different depth-6 *intermediate* best move (a shallow tie-break flip) that already resolves to the identical PV and node count by depth 7 and holds through depth 10 -- not chased further since the actual (depth-10) result matches exactly and search is deterministic at --cpus 1. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
Diffstat (limited to 'src/eval.c')
-rwxr-xr-xsrc/eval.c742
1 files changed, 135 insertions, 607 deletions
diff --git a/src/eval.c b/src/eval.c
index 6adf27b..1ca2beb 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -1152,403 +1152,6 @@ Return value:
}
-//
-// Transitional helper, board_representation/EVAL.md section 2: during
-// the piece-by-piece migration off bvAttacks, knight now writes its
-// attacks into pos->bbMinorAttacks directly while bishop (not
-// converted yet) still writes the old per-square
-// rgSquare[c|8].bvAttacks[color].small.uMinor bit. Every consumer
-// that needs "does any minor (knight or bishop) attack this square"
-// goes through this one function instead of each hand-rolling its own
-// OR of the two sources -- once bishop converts too, this collapses
-// to a plain pos->bbMinorAttacks[color] read and this whole function
-// goes away; it is not meant to be a permanent fixture.
-//
-// DEBUG-only: cross-checked against an independent recomputation
-// (g_KnightAttacksBB / _BishopAttacksBB, both already-trusted
-// primitives used elsewhere in move generation, entirely separate
-// code from either the ray-walk's uBit bookkeeping or the new
-// bitboard population) so a bug in either mechanism fails loudly here
-// instead of silently drifting into a wrong score several plies deep
-// in search -- exactly the failure mode a prior, larger version of
-// this same migration hit and had to be rolled back for.
-//
-static FLAG
-_IsSquareAttackedByMinor(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = ((pos->bbMinorAttacks[uColor] & sq) != 0) ||
- (pos->rgSquare[c|8].bvAttacks[uColor].small.uMinor != 0);
-#ifdef DEBUG
- {
- BITBOARD bbTrueMinorAttacks = 0;
- ULONG u;
- for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
- {
- COOR cPiece = pos->cNonPawns[uColor][u];
- PIECE p = pos->rgSquare[cPiece].pPiece;
- if (IS_KNIGHT(p))
- {
- bbTrueMinorAttacks |= g_KnightAttacksBB[cPiece];
- }
- else if (IS_BISHOP(p))
- {
- bbTrueMinorAttacks |= _BishopAttacksBB(cPiece, pos->bbOccupied);
- }
- }
- ASSERT(((bbTrueMinorAttacks & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
-//
-// Companion to _IsSquareAttackedByMinor, same transitional purpose:
-// bishop is the only minor that ever x-rays (knights don't), and its
-// x-ray bit is moving fully to pos->bbMinorXrayAttacks in this same
-// change -- unlike the direct-attack case, there's no old-structure
-// fallback to bridge here (nothing else has ever written a minor
-// x-ray bit), so this is just a plain bitboard read. Goes away once
-// rook/queen convert and _EvalKing/_WhoControlsSquareFast read their
-// xray bitboards directly instead of going through any helper.
-//
-// Deliberate behavior change from the old ray-walk, made for speed
-// per direct instruction (2026-09-05): the old BMobCaseTable's
-// fStop=FALSE for BMOB_FRIEND_XRAY/BMOB_ENEMY_GREATER meant the walk
-// kept going -- and kept counting mobility -- through however many
-// x-ray-worthy blockers were stacked consecutively on one ray (e.g. a
-// bishop x-raying an enemy rook, then continuing to x-ray *through*
-// an enemy king sitting right behind it too). _EvalBishop's bitboard
-// version only extends *one* hop past the first x-ray-worthy blocker
-// (recompute with just that blocker excluded, keep what's newly
-// revealed) -- it does not check whether the newly-revealed terminal
-// square is itself x-ray-worthy and continue again. This is simpler
-// and faster, and the position it changes behavior on (>=2 specific
-// piece types stacked on the same diagonal) is rare enough that
-// trading exact fidelity for it was judged worthwhile; if that
-// judgment turns out wrong, the fix is a bounded loop repeating the
-// "exclude terminal blocker, recompute" step until it stops finding a
-// new x-ray-worthy terminal, not a design change.
-//
-// DEBUG-only: cross-checked against a from-scratch mailbox ray-walk
-// that mirrors this same single-hop rule directly (not the old,
-// unbounded-chain rule) -- deliberately not sharing any code with
-// _EvalBishop's own "recompute with blocker excluded" bitboard
-// technique, so this is a genuinely independent check on that
-// technique, not a restatement of it.
-//
-static FLAG
-_IsSquareXrayedByMinor(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = (pos->bbMinorXrayAttacks[uColor] & sq) != 0;
-#ifdef DEBUG
- {
- BITBOARD bbTrueXray = 0;
- ULONG u;
- for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
- {
- COOR cBishop = pos->cNonPawns[uColor][u];
- ULONG d;
- if (!IS_BISHOP(pos->rgSquare[cBishop].pPiece))
- {
- continue;
- }
- for (d = 0; d < 4; d++)
- {
- // Chains: after marking squares past one x-ray-worthy
- // blocker up to the next piece, re-examine that next
- // piece and keep going if it's worthy too (matches the
- // production chain-following loop above, and rook's
- // identical cross-check).
- COOR cWalk = cBishop + g_iBDeltas[d];
- while (IS_ON_BOARD(cWalk) && IS_EMPTY(pos->rgSquare[cWalk].pPiece))
- {
- cWalk += g_iBDeltas[d];
- }
- while (IS_ON_BOARD(cWalk))
- {
- PIECE pq = pos->rgSquare[cWalk].pPiece;
- FLAG fWorthy;
- if (GET_COLOR(pq) == uColor)
- {
- fWorthy = (IS_BISHOP(pq) || IS_QUEEN(pq));
- }
- else
- {
- fWorthy = (IS_ROOK(pq) || IS_QUEEN(pq) ||
- IS_KING(pq));
- }
- if (!fWorthy)
- {
- break;
- }
- cWalk += g_iBDeltas[d];
- while (IS_ON_BOARD(cWalk))
- {
- bbTrueXray |= COOR_TO_BB(cWalk);
- if (!IS_EMPTY(pos->rgSquare[cWalk].pPiece))
- {
- break;
- }
- cWalk += g_iBDeltas[d];
- }
- }
- }
- }
- ASSERT(((bbTrueXray & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
-
-//
-// Rook's turn to convert (board_representation/EVAL.md section 9,
-// 2026-09-05) -- same transitional purpose and same DEBUG cross-check
-// discipline as _IsSquareAttackedByMinor/_IsSquareXrayedByMinor above,
-// now that rook no longer writes ROOK_BIT/ROOK_XRAY_BIT into
-// rgSquare[c|8].bvAttacks at all. Goes away once queen converts and
-// _WhoControlsSquareFast reads pos->bbRookAttacks/bbRookXrayAttacks
-// directly instead of going through a helper.
-//
-static FLAG
-_IsSquareAttackedByRook(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = (pos->bbRookAttacks[uColor] & sq) != 0;
-#ifdef DEBUG
- {
- BITBOARD bbTrueRookAttacks = 0;
- ULONG u;
- for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
- {
- COOR cPiece = pos->cNonPawns[uColor][u];
- if (IS_ROOK(pos->rgSquare[cPiece].pPiece))
- {
- bbTrueRookAttacks |= _RookAttacksBB(cPiece, pos->bbOccupied);
- }
- }
- ASSERT(((bbTrueRookAttacks & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
-//
-// Companion to _IsSquareAttackedByRook, same single-hop x-ray
-// simplification as bishop's _IsSquareXrayedByMinor (deliberate speed
-// tradeoff, not full chain-following -- see _EvalRook's mobility
-// comment). Cross-checked against an independent from-scratch mailbox
-// walk mirroring that same single-hop rule.
-//
-static FLAG
-_IsSquareXrayedByRook(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = (pos->bbRookXrayAttacks[uColor] & sq) != 0;
-#ifdef DEBUG
- {
- BITBOARD bbTrueXray = 0;
- ULONG u;
- for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
- {
- COOR cRook = pos->cNonPawns[uColor][u];
- ULONG d;
- if (!IS_ROOK(pos->rgSquare[cRook].pPiece))
- {
- continue;
- }
- for (d = 0; d < 4; d++)
- {
- // Unlike bishop's single-hop cross-check, this chains:
- // after marking squares past one x-ray-worthy blocker
- // up to the next piece, re-examine that next piece and
- // keep going if it's worthy too (matches the
- // production chain-following loop above, and the
- // stashed first attempt's blocker-to-blocker bit-scan
- // walk).
- COOR cWalk = cRook + g_iRDeltas[d];
- while (IS_ON_BOARD(cWalk) && IS_EMPTY(pos->rgSquare[cWalk].pPiece))
- {
- cWalk += g_iRDeltas[d];
- }
- while (IS_ON_BOARD(cWalk))
- {
- PIECE pq = pos->rgSquare[cWalk].pPiece;
- FLAG fWorthy;
- if (GET_COLOR(pq) == uColor)
- {
- fWorthy = (IS_ROOK(pq) || IS_QUEEN(pq));
- }
- else
- {
- fWorthy = (IS_QUEEN(pq) || IS_KING(pq));
- }
- if (!fWorthy)
- {
- break;
- }
- cWalk += g_iRDeltas[d];
- while (IS_ON_BOARD(cWalk))
- {
- bbTrueXray |= COOR_TO_BB(cWalk);
- if (!IS_EMPTY(pos->rgSquare[cWalk].pPiece))
- {
- break;
- }
- cWalk += g_iRDeltas[d];
- }
- }
- }
- }
- ASSERT(((bbTrueXray & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
-//
-// Queen's turn to convert (board_representation/EVAL.md section 9,
-// 2026-09-05) -- same transitional purpose and same DEBUG cross-check
-// discipline as _IsSquareAttackedByRook/_IsSquareXrayedByRook above,
-// now that queen no longer writes QUEEN_BIT/QUEEN_XRAY_BIT into
-// rgSquare[c|8].bvAttacks at all. Goes away once king converts and
-// _EvalKing reads pos->bbQueenAttacks/bbQueenXrayAttacks directly.
-//
-static FLAG
-_IsSquareAttackedByQueen(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = (pos->bbQueenAttacks[uColor] & sq) != 0;
-#ifdef DEBUG
- {
- BITBOARD bbTrueQueenAttacks = 0;
- ULONG u;
- for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
- {
- COOR cPiece = pos->cNonPawns[uColor][u];
- if (IS_QUEEN(pos->rgSquare[cPiece].pPiece))
- {
- bbTrueQueenAttacks |= _RookAttacksBB(cPiece, pos->bbOccupied) |
- _BishopAttacksBB(cPiece, pos->bbOccupied);
- }
- }
- ASSERT(((bbTrueQueenAttacks & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
-//
-// Companion to _IsSquareAttackedByQueen. Unlike rook/bishop, queen's
-// own case table never x-rays through *any* enemy piece (QMOB_ENEMY_GE
-// always stops) -- only through a friendly queen, or a friendly rook
-// on an orthogonal ray / friendly bishop on a diagonal ray (the same
-// per-ray-family split _EvalQueen's own mobility walk uses). Chains
-// arbitrarily deep, same as rook's own x-ray (not single-hop).
-//
-static FLAG
-_IsSquareXrayedByQueen(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = (pos->bbQueenXrayAttacks[uColor] & sq) != 0;
-#ifdef DEBUG
- {
- BITBOARD bbTrueXray = 0;
- ULONG u;
- for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
- {
- COOR cQueen = pos->cNonPawns[uColor][u];
- ULONG d;
- if (!IS_QUEEN(pos->rgSquare[cQueen].pPiece))
- {
- continue;
- }
- for (d = 0; d < 8; d++)
- {
- COOR cWalk = cQueen + g_iQKDeltas[d];
- FLAG fOrthogonal;
-
- if (!IS_ON_BOARD(cWalk))
- {
- continue;
- }
- fOrthogonal = (((cWalk & 0xF0) == (cQueen & 0xF0)) ||
- ((cWalk & 0x0F) == (cQueen & 0x0F)));
- while (IS_ON_BOARD(cWalk) && IS_EMPTY(pos->rgSquare[cWalk].pPiece))
- {
- cWalk += g_iQKDeltas[d];
- }
- while (IS_ON_BOARD(cWalk))
- {
- PIECE pq = pos->rgSquare[cWalk].pPiece;
- FLAG fWorthy;
-
- if (GET_COLOR(pq) != uColor)
- {
- break;
- }
- fWorthy = IS_QUEEN(pq) ||
- (fOrthogonal ? IS_ROOK(pq) : IS_BISHOP(pq));
- if (!fWorthy)
- {
- break;
- }
- cWalk += g_iQKDeltas[d];
- while (IS_ON_BOARD(cWalk))
- {
- bbTrueXray |= COOR_TO_BB(cWalk);
- if (!IS_EMPTY(pos->rgSquare[cWalk].pPiece))
- {
- break;
- }
- cWalk += g_iQKDeltas[d];
- }
- }
- }
- }
- ASSERT(((bbTrueXray & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
-//
-// King's turn to convert (board_representation/EVAL.md section 9,
-// 2026-09-05) -- the last piece type contributing to bvAttacks. Same
-// transitional-helper purpose as the others, but simpler: no mobility
-// computation, no x-ray (a king can't move through a blocker), so
-// there's no companion _IsSquareXrayedByKing.
-//
-static FLAG
-_IsSquareAttackedByKing(IN POSITION *pos,
- IN ULONG uColor,
- IN COOR c)
-{
- BITBOARD sq = COOR_TO_BB(c);
- FLAG fResult = (pos->bbKingAttacks[uColor] & sq) != 0;
-#ifdef DEBUG
- {
- BITBOARD bbTrueKingAttacks = g_KingAttacksBB[pos->cNonPawns[uColor][0]];
- ASSERT(((bbTrueKingAttacks & sq) != 0) == (fResult != 0));
- }
-#endif
- return fResult;
-}
-
static ULONG
_WhoControlsSquareFast(IN POSITION *pos,
IN COOR c)
@@ -1574,55 +1177,46 @@ Return value:
**/
{
//
- // bvAttacks no longer carries the pawn bit (pawns write
- // pos->bbPawnAttacks[2] directly instead, see
- // _PopulatePawnAttackBits) -- OR it back in at its usual bit
- // position (PAWN_BIT) so g_SwapTable's indexing below sees the
- // same bit shape it always has.
+ // board_representation/EVAL.md section 9: bvAttacks/ATTACK_BITV
+ // retired entirely now that king (the last piece to write it) has
+ // converted -- every side's presence at this square is read
+ // straight off its own bbXAttacks accumulator instead. Bit
+ // positions (PAWN_BIT/MINOR_BIT/ROOK_BIT/QUEEN_BIT/KING_BIT) are
+ // unchanged so g_SwapTable's indexing below still sees the same
+ // shape it always has; x-ray-only presence (bbMinorXrayAttacks/
+ // bbRookXrayAttacks/bbQueenXrayAttacks) ORs into the same
+ // byte-scale bit as its direct-attack counterpart, matching the
+ // old struct's .uSmall/.uXray sharing one bit position for
+ // "attacks or x-rays" (see g_SwapTable's own construction). King
+ // has no x-ray (can't move through a blocker). This function is
+ // only ever called after *both* kings finish evaluating (the
+ // passed-pawn re-check and trapped-piece/danger passes all run
+ // after Eval()'s king-eval block), so pos->bbKingAttacks is always
+ // fully populated for both colors here -- unlike _EvalKing's own,
+ // deliberately asymmetric internal bvAttack (see that function's
+ // comment), this is a plain, symmetric fact query.
//
- // .uXray is a standalone byte view (see _IsSquareXrayedByMinor's
- // comment on why the byte-scale MINOR_BIT, not MINOR_XRAY_BIT, is
- // the right constant to OR in here) -- bishop's, rook's, and
- // queen's xray contributions all moved to pos->bbMinorXrayAttacks/
- // bbRookXrayAttacks/bbQueenXrayAttacks. King has no x-ray (can't
- // move through a blocker), and its direct-attack contribution is
- // added symmetrically for both colors below via
- // _IsSquareAttackedByKing -- unlike _EvalKing's own internal
- // bvAttack (which deliberately drops the *enemy* king's
- // contribution, see that function's comment on the black-then-
- // white evaluation-order asymmetry this sidesteps), this function
- // is only ever called after *both* kings have finished evaluating
- // (the passed-pawn re-check runs after the king-eval block in
- // Eval()'s own sequencing), so pos->bbKingAttacks is always fully
- // populated for both colors by the time this runs -- no asymmetry
- // to worry about here, this is a plain, symmetric fact query.
- ULONG uWhite = pos->rgSquare[c|8].bvAttacks[WHITE].uSmall |
- pos->rgSquare[c|8].bvAttacks[WHITE].uXray |
- ((pos->bbPawnAttacks[WHITE] & COOR_TO_BB(c)) ?
- PAWN_BIT : 0) |
- (_IsSquareAttackedByMinor(pos, WHITE, c) ? MINOR_BIT : 0) |
- (_IsSquareXrayedByMinor(pos, WHITE, c) ? MINOR_BIT : 0) |
- (_IsSquareAttackedByRook(pos, WHITE, c) ? ROOK_BIT : 0) |
- (_IsSquareXrayedByRook(pos, WHITE, c) ? ROOK_BIT : 0) |
- (_IsSquareAttackedByQueen(pos, WHITE, c) ? QUEEN_BIT : 0) |
- (_IsSquareXrayedByQueen(pos, WHITE, c) ? QUEEN_BIT : 0) |
- (_IsSquareAttackedByKing(pos, WHITE, c) ? KING_BIT : 0);
- ULONG uBlack = pos->rgSquare[c|8].bvAttacks[BLACK].uSmall |
- pos->rgSquare[c|8].bvAttacks[BLACK].uXray |
- ((pos->bbPawnAttacks[BLACK] & COOR_TO_BB(c)) ?
- PAWN_BIT : 0) |
- (_IsSquareAttackedByMinor(pos, BLACK, c) ? MINOR_BIT : 0) |
- (_IsSquareXrayedByMinor(pos, BLACK, c) ? MINOR_BIT : 0) |
- (_IsSquareAttackedByRook(pos, BLACK, c) ? ROOK_BIT : 0) |
- (_IsSquareXrayedByRook(pos, BLACK, c) ? ROOK_BIT : 0) |
- (_IsSquareAttackedByQueen(pos, BLACK, c) ? QUEEN_BIT : 0) |
- (_IsSquareXrayedByQueen(pos, BLACK, c) ? QUEEN_BIT : 0) |
- (_IsSquareAttackedByKing(pos, BLACK, c) ? KING_BIT : 0);
+ BITBOARD sq = COOR_TO_BB(c);
+ ULONG uWhite = ((pos->bbPawnAttacks[WHITE] & sq) ? PAWN_BIT : 0) |
+ ((pos->bbMinorAttacks[WHITE] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbMinorXrayAttacks[WHITE] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbRookAttacks[WHITE] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbRookXrayAttacks[WHITE] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbQueenAttacks[WHITE] & sq) ? QUEEN_BIT : 0) |
+ ((pos->bbQueenXrayAttacks[WHITE] & sq) ? QUEEN_BIT : 0) |
+ ((pos->bbKingAttacks[WHITE] & sq) ? KING_BIT : 0);
+ ULONG uBlack = ((pos->bbPawnAttacks[BLACK] & sq) ? PAWN_BIT : 0) |
+ ((pos->bbMinorAttacks[BLACK] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbMinorXrayAttacks[BLACK] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbRookAttacks[BLACK] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbRookXrayAttacks[BLACK] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbQueenAttacks[BLACK] & sq) ? QUEEN_BIT : 0) |
+ ((pos->bbQueenXrayAttacks[BLACK] & sq) ? QUEEN_BIT : 0) |
+ ((pos->bbKingAttacks[BLACK] & sq) ? KING_BIT : 0);
ULONG u;
PIECE p;
CHAR ch;
- ASSERT((c + 8) == (c | 8));
ASSERT((uWhite & 0xFFFFFF00) == 0);
ASSERT((uBlack & 0xFFFFFF00) == 0);
@@ -1649,7 +1243,13 @@ Return value:
Routine description:
- Zero out the attack table before building it.
+ Zero out the Eval()-scoped attack-bitboard accumulators before
+ building them for this call. Used to clear the old per-square
+ rgSquare[c|8].bvAttacks/ATTACK_BITV structure too (a full-board
+ macro-unrolled loop, since every square's storage needed zeroing);
+ retired along with that structure (2026-09-05, board_
+ representation/EVAL.md section 9) -- nothing left to clear but the
+ bbXAttacks accumulators themselves.
Parameters:
@@ -1660,33 +1260,9 @@ Return value:
void
**/
-#define CLEAR_A_SQ \
- pos->rgSquare[c].bvAttacks[0].uWholeThing = 0; \
- pos->rgSquare[c].bvAttacks[1].uWholeThing = 0;
-
-#define CLEAR_A_RANK \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c += 9;
-
-#define CLEAR_SHORT_RANK \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c++; \
- CLEAR_A_SQ; c += 10;
-
static void
_ClearAttackTables(IN OUT POSITION *pos)
{
- register COOR c = 8;
pos->bbMinorAttacks[WHITE] = pos->bbMinorAttacks[BLACK] = 0;
pos->bbMinorXrayAttacks[WHITE] = pos->bbMinorXrayAttacks[BLACK] = 0;
pos->bbRookAttacks[WHITE] = pos->bbRookAttacks[BLACK] = 0;
@@ -1694,33 +1270,6 @@ _ClearAttackTables(IN OUT POSITION *pos)
pos->bbQueenAttacks[WHITE] = pos->bbQueenAttacks[BLACK] = 0;
pos->bbQueenXrayAttacks[WHITE] = pos->bbQueenXrayAttacks[BLACK] = 0;
pos->bbKingAttacks[WHITE] = pos->bbKingAttacks[BLACK] = 0;
-#if 1
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c += 16;
- CLEAR_A_SQ; c = 9;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
- CLEAR_SHORT_RANK;
-#else
- CLEAR_A_RANK;
- CLEAR_A_RANK;
- CLEAR_A_RANK;
- CLEAR_A_RANK;
- CLEAR_A_RANK;
- CLEAR_A_RANK;
- CLEAR_A_RANK;
- CLEAR_A_RANK;
-#endif
}
@@ -1926,6 +1475,24 @@ Return value:
// IDEA: scale the candidate passer bonus based on rank AND on
// the distance the helper(s) have to go to get into position.
//
+ // KNOWN BUG, found 2026-09-05 while removing rgSquare[c|8].bvAttacks
+ // entirely (board_representation/EVAL.md section 9), NOT fixed
+ // here -- flagged for its own separate investigation instead of
+ // being bundled into a mechanical cleanup commit. This function
+ // runs from _EvalPawns, which is the *first* piece type evaluated
+ // each Eval() call -- every non-pawn piece (and, since commit
+ // 57502d6, pawns themselves) writes its attack bits later in the
+ // same call, so the "is c1 safe to advance a helper pawn into"
+ // check below has read an always-zero attack table for as long as
+ // bvAttacks has existed in its post-57502d6 form. The condition
+ // this used to gate on (skip a candidate passer if its helper
+ // square isn't actually safe to advance into) has therefore been
+ // unconditionally true -- a silent no-op -- since that commit,
+ // not something introduced by today's cleanup. Preserved exactly
+ // as that already-dead behavior (unconditional) rather than
+ // "fixed" here, since a real fix changes eval scoring and deserves
+ // its own before/after check, not one buried in a rename commit.
+ //
uHelpers = 0;
d1 = 16 * g_iAhead[uColor];
ASSERT(-d1 == 16 * g_iBehind[uColor]);
@@ -1933,39 +1500,33 @@ Return value:
if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1]))
{
ASSERT(pos->bbPawns[uColor] & BBFILE[FILE(c1)]);
- if (!(pos->rgSquare[c1 + 8].bvAttacks[FLIP(uColor)].uWholeThing) ||
- (pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing))
+ //
+ // The square c1 the place a helper pawn must get to in
+ // order to aide the candidate past a sentry.
+ //
+ if (pos->rgSquare[c1].pPiece == pHelper)
+ {
+ uHelpers = 1;
+ goto do_left;
+ }
+
+ //
+ // There is no helper pawn in the support position yet.
+ // See if one can get there.
+ //
+ c1 = c1 - d1;
+ while (IS_ON_BOARD(c1))
{
- //
- // The square c1 the place a helper pawn must get to in
- // order to aide the candidate past a sentry.
- //
if (pos->rgSquare[c1].pPiece == pHelper)
{
uHelpers = 1;
- goto do_left;
+ break;
}
-
- //
- // There is no helper pawn in the support position yet.
- // See if one can get there.
- //
- c1 = c1 - d1;
- while (IS_ON_BOARD(c1) &&
- ((!(pos->rgSquare[c1+8].bvAttacks[FLIP(uColor)].uWholeThing)) ||
- (pos->rgSquare[c1+8].bvAttacks[uColor].uWholeThing)))
+ else if (pos->rgSquare[c1].pPiece == pSentry)
{
- if (pos->rgSquare[c1].pPiece == pHelper)
- {
- uHelpers = 1;
- break;
- }
- else if (pos->rgSquare[c1].pPiece == pSentry)
- {
- break;
- }
- c1 = c1 - d1;
+ break;
}
+ c1 = c1 - d1;
}
}
@@ -1975,39 +1536,33 @@ Return value:
{
ASSERT(pos->bbPawns[uColor] & BBFILE[FILE(c1)]);
- if (!(pos->rgSquare[c1 + 8].bvAttacks[FLIP(uColor)].uWholeThing) ||
- (pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing))
+ //
+ // The square c1 is the place a helper pawn must get to in
+ // order to aide the candidate.
+ //
+ if (pos->rgSquare[c1].pPiece == pHelper)
+ {
+ uHelpers++;
+ goto done_helpers;
+ }
+
+ //
+ // There is no pawn in the left support position yet. See
+ // if one can get there.
+ //
+ c1 -= d1;
+ while (IS_ON_BOARD(c1))
{
- //
- // The square c1 is the place a helper pawn must get to in
- // order to aide the candidate.
- //
if (pos->rgSquare[c1].pPiece == pHelper)
{
uHelpers++;
- goto done_helpers;
+ break;
}
-
- //
- // There is no pawn in the left support position yet. See
- // if one can get there.
- //
- c1 -= d1;
- while (IS_ON_BOARD(c1) &&
- ((!(pos->rgSquare[c1+8].bvAttacks[FLIP(uColor)].uWholeThing)) ||
- (pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing)))
+ else if (pos->rgSquare[c1].pPiece == pSentry)
{
- if (pos->rgSquare[c1].pPiece == pHelper)
- {
- uHelpers++;
- break;
- }
- else if (pos->rgSquare[c1].pPiece == pSentry)
- {
- break;
- }
- c1 -= d1;
+ break;
}
+ c1 -= d1;
}
}
@@ -2224,14 +1779,14 @@ Return value:
// diagonals need the *opposite* file excluded to prevent same-row
// wraparound) -- not repeated here.
//
-// Unlike the old version, this does NOT write PAWN_BIT into
-// rgSquare[c|8].bvAttacks -- pos->bbPawnAttacks[2] (chess.h) is now
-// the single source of truth for "does a pawn attack this square",
-// read directly by every consumer (UNSAFE_FOR_MINOR's old callers,
-// UNSAFE_FOR_ROOK/_QUEEN, _EvalKing's bvAttack/bvDefend). Knight/
-// bishop/rook/queen/king still populate/read the rest of bvAttacks
-// (uMinor/uRook/uQueen/uKing) the old way, so _ClearAttackTables(pos)
-// still needs to run here first.
+// Unlike the old version, this does NOT write PAWN_BIT into the old
+// rgSquare[c|8].bvAttacks/ATTACK_BITV mechanism (retired entirely as
+// of 2026-09-05, once king -- the last piece writing it -- converted
+// too) -- pos->bbPawnAttacks[2] (chess.h) is the single source of
+// truth for "does a pawn attack this square", read directly by every
+// consumer. _ClearAttackTables(pos) still needs to run here first, to
+// zero the other bbXAttacks accumulators knight/bishop/rook/queen/king
+// populate as they each evaluate.
static void
_PopulatePawnAttackBits(IN OUT POSITION *pos)
/**
@@ -4210,62 +3765,35 @@ Return value:
p = pos->rgSquare[cSquare].pPiece;
//
- // bvAttacks no longer carries the pawn bit (pawns write
- // pos->bbPawnAttacks[2] directly instead, see
- // _PopulatePawnAttackBits) -- OR it back in here from the
- // bitboard, keyed off the real board square (cSquare,
- // before the |8 below flips it into the invisible-half
- // storage index bvAttacks itself uses).
- //
- // No enemy-king contribution here, by direct instruction
- // (2026-09-05): the old mailbox version was already
- // asymmetric here (kings evaluate black-then-white, so
- // white's computation could see black's already-written
- // king bit but black's could never see white's, since
- // white hadn't run yet) -- rather than preserve or
- // "upgrade" that asymmetry now that both colors go through
- // an explicit helper either way, neither side sees the
- // enemy king as a threat here, matching the side that
- // already couldn't.
+ // board_representation/EVAL.md section 9: bvAttacks/
+ // ATTACK_BITV retired entirely -- every bit here now comes
+ // straight from a bbXAttacks accumulator read, no more
+ // c|8 shadow-index struct storage or per-square writes.
+ // No enemy-king contribution in bvAttack, by direct
+ // instruction (see this function's header comment on the
+ // black-then-white evaluation-order asymmetry this
+ // sidesteps). bvDefend's own-king bit is real, load-
+ // bearing signal (see the bvDefend &= ~8 below, which
+ // strips it back out when the square is x-rayed or
+ // multiply attacked -- "a lone king isn't adequate defense
+ // against that") -- not just self-consistency noise, so
+ // it keeps its own-color check.
//
- bvAttack = pos->rgSquare[cSquare|8].bvAttacks[ufColor].uSmall |
- ((pos->bbPawnAttacks[ufColor] & COOR_TO_BB(cSquare)) ?
- PAWN_BIT : 0) |
- (_IsSquareAttackedByMinor(pos, ufColor, cSquare) ?
- MINOR_BIT : 0) |
- (_IsSquareAttackedByRook(pos, ufColor, cSquare) ?
- ROOK_BIT : 0) |
- (_IsSquareAttackedByQueen(pos, ufColor, cSquare) ?
- QUEEN_BIT : 0);
{
- COOR cRealSquare = cSquare;
- cSquare |= 8;
- bvXray = pos->rgSquare[cSquare].bvAttacks[ufColor].uXray |
- (_IsSquareXrayedByMinor(pos, ufColor, cRealSquare) ?
- MINOR_BIT : 0) |
- (_IsSquareXrayedByRook(pos, ufColor, cRealSquare) ?
- ROOK_BIT : 0) |
- (_IsSquareXrayedByQueen(pos, ufColor, cRealSquare) ?
- QUEEN_BIT : 0);
- //
- // bvDefend's own-king bit is real, load-bearing signal
- // (see the bvDefend &= ~8 below, which strips it back
- // out when the square is x-rayed or multiply attacked
- // -- "a lone king isn't adequate defense against
- // that") -- not just self-consistency noise, so this
- // one keeps its own-color check.
- //
- bvDefend = pos->rgSquare[cSquare].bvAttacks[uColor].uSmall |
- ((pos->bbPawnAttacks[uColor] & COOR_TO_BB(cRealSquare)) ?
- PAWN_BIT : 0) |
- (_IsSquareAttackedByMinor(pos, uColor, cRealSquare) ?
- MINOR_BIT : 0) |
- (_IsSquareAttackedByRook(pos, uColor, cRealSquare) ?
- ROOK_BIT : 0) |
- (_IsSquareAttackedByQueen(pos, uColor, cRealSquare) ?
- QUEEN_BIT : 0) |
- (_IsSquareAttackedByKing(pos, uColor, cRealSquare) ?
- KING_BIT : 0);
+ BITBOARD sq = COOR_TO_BB(cSquare);
+
+ bvAttack = ((pos->bbPawnAttacks[ufColor] & sq) ? PAWN_BIT : 0) |
+ ((pos->bbMinorAttacks[ufColor] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbRookAttacks[ufColor] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbQueenAttacks[ufColor] & sq) ? QUEEN_BIT : 0);
+ bvXray = ((pos->bbMinorXrayAttacks[ufColor] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbRookXrayAttacks[ufColor] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbQueenXrayAttacks[ufColor] & sq) ? QUEEN_BIT : 0);
+ bvDefend = ((pos->bbPawnAttacks[uColor] & sq) ? PAWN_BIT : 0) |
+ ((pos->bbMinorAttacks[uColor] & sq) ? MINOR_BIT : 0) |
+ ((pos->bbRookAttacks[uColor] & sq) ? ROOK_BIT : 0) |
+ ((pos->bbQueenAttacks[uColor] & sq) ? QUEEN_BIT : 0) |
+ ((pos->bbKingAttacks[uColor] & sq) ? KING_BIT : 0);
}
//