diff options
| author | Scott Gasch <[email protected]> | 2026-09-05 10:37:55 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-05 10:37:55 -0700 |
| commit | 5405191f8c519333006417a5a3c31bc3e186e42e (patch) | |
| tree | 1355d4c1cfecbe1777f92df2ecadbb17d8215e37 /src | |
| parent | aad154a5a07d2793e52a71415cc5c59276a8f629 (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')
| -rwxr-xr-x | src/chess.h | 242 | ||||
| -rwxr-xr-x | src/command.c | 3 | ||||
| -rwxr-xr-x | src/eval.c | 742 |
3 files changed, 209 insertions, 778 deletions
diff --git a/src/chess.h b/src/chess.h index aababc0..acab153 100755 --- a/src/chess.h +++ b/src/chess.h @@ -542,105 +542,55 @@ typedef union _MOVE // ---------------------------------------------------------------------- -#pragma pack(1) -typedef union _ATTACK_BITV -{ - ULONG uWholeThing; - struct - { - union - { - UCHAR uSmall; - struct - { - UCHAR uNumAttacks : 3; // 0..2 - UCHAR uKing : 1; // 4..7 - UCHAR uQueen : 1; - UCHAR uRook : 1; - UCHAR uMinor : 1; - UCHAR uPawn : 1; - } small; - }; - - // -------------------- - union - { - USHORT uBig; - struct - { - USHORT uKing : 1; // 8..23 - USHORT uQueens : 4; - USHORT uRooks : 4; - USHORT uMinors : 4; - USHORT uPawns : 2; - USHORT uUnusedFlag1 : 1; - } big; - }; - - // ------------------- - union - { - UCHAR uXray; - struct - { - UCHAR uNumXrays : 3; - UCHAR uUnusedFlag2 : 1; - UCHAR uQueen : 1; // 24..28 - UCHAR uRook : 1; - UCHAR uBishop : 1; - UCHAR uUnusedFlag3 : 1; - } xray; - }; - }; -} -ATTACK_BITV; - -// UNSAFE_FOR_MINOR retired as a bvAttacks-based macro 2026-09-05: -// pawns no longer write PAWN_BIT into bvAttacks (see POSITION's -// bbPawnAttacks[2] comment, chess.h) -- it was PAWN_BIT alone, so -// every former call site now just tests -// (pos->bbPawnAttacks[enemy] & COOR_TO_BB(sq)) directly, no macro -// needed. UNSAFE_FOR_ROOK/_QUEEN's masks below are narrowed to drop -// PAWN_BIT (0x80), which would otherwise silently always read 0 now -// that nothing sets it -- callers combine these with an explicit -// bbPawnAttacks test instead (see eval.c's _EvalRook/_EvalQueen). -#define UNSAFE_FOR_ROOK(x) ((ULONG)((x).uWholeThing) & 0x00000040UL) -#define UNSAFE_FOR_QUEEN(x) ((ULONG)((x).uWholeThing) & 0x00000060UL) - -#define PAWN_BIT 0x00000080UL -#define MINOR_BIT 0x00000040UL -#define MINOR_XRAY_BIT 0x40000000UL -#define ROOK_BIT 0x00000020UL -#define ROOK_XRAY_BIT 0x20000000UL -#define QUEEN_BIT 0x00000010UL -#define QUEEN_XRAY_BIT 0x10000000UL +// ATTACK_BITV/bvAttacks retired entirely 2026-09-05 (board_ +// representation/EVAL.md section 9): the per-square, per-color +// attack-bit union that used to live at rgSquare[c|8] (the "c|8" +// shadow-index trick -- reusing the same 128-entry array the 0x88 +// board layout already needs for its own off-board sentinels, at +// the indices real piece coordinates never use) is gone now that +// king -- the last piece type writing it -- has converted to a +// bitboard accumulator like every other piece type before it. Every +// consumer reads a pos->bbXAttacks[color] bitboard directly instead. +// +// PAWN_BIT/MINOR_BIT/ROOK_BIT/QUEEN_BIT/KING_BIT below are NOT part +// of that retired mechanism -- they're a separate, still-live +// byte-scale bit-packing scheme _EvalKing/_WhoControlsSquareFast use +// locally (BITV-typed variables, not struct fields) to build a +// per-square "which piece types attack/defend/x-ray this square" +// pattern from those same bbXAttacks reads, then index +// KING_COUNTER_BY_ATTACK_PATTERN/g_SwapTable with it. Kept exactly as +// before; only their old *storage* mechanism (ATTACK_BITV) is gone. +#define PAWN_BIT 0x00000080UL +#define MINOR_BIT 0x00000040UL +#define ROOK_BIT 0x00000020UL +#define QUEEN_BIT 0x00000010UL // King never x-rays (it can't move through a blocker), so there's no -// KING_XRAY_BIT to go with this -- matches struct _ATTACK_BITV's -// .small.uKing bit position (byte 0, bit 3). -#define KING_BIT 0x00000008UL +// KING_XRAY_BIT to go with this. +#define KING_BIT 0x00000008UL #define INVALID_PIECE_INDEX (17) #define IS_VALID_PIECE_INDEX(x) ((x) < INVALID_PIECE_INDEX) -typedef union _SQUARE +typedef struct _SQUARE { - struct - { - PIECE pPiece; - ULONG uIndex; - }; - ATTACK_BITV bvAttacks[2]; + PIECE pPiece; + ULONG uIndex; } SQUARE; -#pragma pack() // // POSITION // typedef struct _POSITION { - SQUARE rgSquare[128]; // where the pieces are, - // also, the attack table + SQUARE rgSquare[128]; // where the pieces are + // (0x88 board layout -- the + // upper half of each rank's + // 16-entry span is off-board + // sentinel space, no longer + // double-purposed as attack- + // bit storage now that + // ATTACK_BITV is retired) UINT64 u64NonPawnSig; // hash signature UINT64 u64PawnSig; // pawn hash signature ULONG uToMove; // whose turn? @@ -700,97 +650,51 @@ typedef struct _POSITION // not as something callers should call directly anymore. BITBOARD bbOccupied; - // First mover of board_representation/EVAL.md section 2's - // bvAttacks replacement, added 2026-09-05: "which squares does - // this side's pawns attack," computed fresh once per Eval() call - // from bbPawns via a single shift-and-mask (see - // _PopulatePawnAttackBits in eval.c, same technique as - // generate.c's _GenerateAllPawnMovesBB) -- zero per-pawn mailbox - // iteration, so unlike bbPieces/bbPawns/bbOccupied above this is - // NOT incrementally maintained across moves; it's plain Eval()- - // scoped scratch space, recomputed every call the same way - // pos->iScore[] is. Pawns no longer write their attack bit into - // rgSquare[c|8].bvAttacks at all -- every consumer of "does an - // enemy/friendly pawn attack this square" reads this bitboard - // directly instead (UNSAFE_FOR_MINOR/_ROOK/_QUEEN's pawn - // component, _EvalKing's bvAttack/bvDefend). Knight/bishop/rook/ - // queen/king still populate/read bvAttacks for their own bits - // (uMinor/uRook/uQueen/uKing) until their own conversions land -- - // see EVAL.md section 2 for the planned bbMinorAttacks/ - // bbRookAttacks/bbQueenAttacks that will retire the rest of it. + // Eval()-scoped attack-bitboard accumulators, one pair per piece + // family, populated by Eval()'s piece-by-piece walk (pawns, then + // knights/bishops, then rooks, then queens, then king) and read by + // _EvalKing/_WhoControlsSquareFast to answer "which side attacks + // this square, and with what." Landed piece-by-piece as + // board_representation/EVAL.md's bitboard-eval migration replaced + // the old per-square, per-color rgSquare[c|8] ATTACK_BITV + // mechanism (2026-09-04 through 2026-09-05, pawns first, king + // last) -- see EVAL.md for that history. All are cleared once per + // Eval() call in _ClearAttackTables and accumulated via |= as each + // piece evaluates (not incrementally maintained across moves, the + // way bbPieces/bbPawns/bbOccupied above are). + // + // bbPawnAttacks is the odd one out: computed in one shot from + // bbPawns via a shift-and-mask (_PopulatePawnAttackBits, same + // technique as generate.c's _GenerateAllPawnMovesBB), not + // per-piece, and assigned wholesale rather than OR-accumulated. + // + // Only bishop/rook/queen have a matching bbXXrayAttacks: squares + // seen *through* a friendly same-or-greater-value battery partner + // (and, for rook/bishop specifically, through an x-rayable enemy + // piece too -- queen's own case never x-rays past an enemy piece). + // Knight and king can't x-ray at all (no blocker-piercing move). + // Bishop's x-ray chains arbitrarily deep, same as rook's/queen's, + // not the single-hop approximation it originally shipped with (see + // EVAL.md's progress log for why that was revisited). BITBOARD bbPawnAttacks[2]; - - // "Which squares does this side's knight(s) or bishop(s) attack" -- - // knight ORs in g_KnightAttacksBB[c] directly (no per-square - // bit-scan needed); bishop ORs in _BishopAttacksBB(c, - // pos->bbOccupied), generate.c's magic-bitboard slider lookup. - // Eval()-scoped scratch like bbPawnAttacks above, cleared once per - // Eval() call in _ClearAttackTables since (unlike bbPawnAttacks) - // it's accumulated across multiple pieces via |=, not assigned - // wholesale. Rook/queen/king are not converted yet and still write - // their own bits into the old rgSquare[c|8].bvAttacks structure -- - // see _IsSquareAttackedByMinor (eval.c), the transitional helper - // every consumer goes through until they convert too. BITBOARD bbMinorAttacks[2]; - - // Squares seen *through* a friendly bishop/queen battery partner - // or an x-rayable enemy rook/queen/king, for bishops only (knights - // never x-ray) -- see _EvalBishop's mobility loop. Same lifetime/ - // clearing discipline as bbMinorAttacks above. Nothing else writes - // this yet (rook/queen have their own xray bits still living in - // the old bvAttacks structure), so unlike bbMinorAttacks' direct- - // bit case there's no old-structure fallback to combine with -- - // see _IsSquareXrayedByMinor. BITBOARD bbMinorXrayAttacks[2]; - - // Rook's turn to convert (board_representation/EVAL.md section - // 9, 2026-09-05): "which squares does this side's rook(s) attack," - // ORed in via _RookAttacksBB(c, pos->bbOccupied) straight from - // _EvalRook's mobility computation, same Eval()-scoped/cleared- - // per-call lifetime as bbMinorAttacks. bbRookXrayAttacks mirrors - // bbMinorXrayAttacks: squares seen past a friendly rook/queen - // battery partner or an x-rayable enemy queen/king, single-hop - // only (same deliberate simplification bishop already made, see - // _EvalRook's mobility comment). Queen/king haven't converted yet - // and still write their own bits into the old bvAttacks structure. BITBOARD bbRookAttacks[2]; BITBOARD bbRookXrayAttacks[2]; - - // Queen's turn to convert (board_representation/EVAL.md section - // 9, 2026-09-05): "which squares does this side's queen(s) - // attack," ORed in from the two-pass rook-direction/bishop- - // direction magic lookups _EvalQueen's mobility computation - // already needs (MOVEGEN_MIGRATION.md's own already-tested - // "combined 8-ray table measured slower than reusing the - // rook/bishop tables in two passes" finding -- reuse that - // structure here too). bbQueenXrayAttacks mirrors - // bbRookXrayAttacks/bbMinorXrayAttacks: squares seen past a - // friendly queen/rook/bishop battery partner, chained arbitrarily - // deep like rook's (not single-hop like bishop's original cut) -- - // queen's own case table never x-rays past an enemy piece (unlike - // rook/bishop), only through friendly battery partners. King - // hasn't converted yet and still writes its own bits into the old - // bvAttacks structure. BITBOARD bbQueenAttacks[2]; BITBOARD bbQueenXrayAttacks[2]; - - // King's turn to convert (board_representation/EVAL.md section 9, - // 2026-09-05) -- the last piece type contributing to the old - // bvAttacks/ATTACK_BITV mechanism. Just g_KingAttacksBB[c] - // (generate.c's precomputed table, already used by move - // generation), no mobility computation involved and no x-ray - // (a king can't move through a blocker). Same Eval()-scoped/ - // cleared-per-call lifetime as every other bbXAttacks accumulator - // above -- this also reproduces, for free, an existing - // order-dependent asymmetry _EvalKing's mailbox version already - // had: kings are evaluated black-then-white (Eval()'s fixed - // order), so white's king-safety computation can see black's - // already-written attack bits but not vice versa. Once this - // lands, nothing writes bvAttacks/ATTACK_BITV any more -- see - // EVAL.md section 9 for the planned follow-up that deletes the - // whole mechanism and simplifies the transitional - // _IsSquareAttackedByX/_IsSquareXrayedByX helpers into plain - // bitboard reads. + // bbKingAttacks reproduces, for free, an existing order-dependent + // asymmetry _EvalKing already had before this bitboard conversion: + // kings are evaluated black-then-white (Eval()'s fixed order), so + // by the time white's king-safety computation runs it can see + // black's already-written attacks but black's own computation + // never could (white hadn't run yet). _EvalKing's own bvAttack + // deliberately keeps this asymmetric (drops the enemy king's + // contribution entirely, matching the side that already couldn't + // see it, by direct instruction) -- but every other bbKingAttacks + // reader (_WhoControlsSquareFast, only ever called after *both* + // kings finish evaluating) sees a fully populated, symmetric + // bitboard for both colors. BITBOARD bbKingAttacks[2]; ULONG uWhiteSqBishopCount[2]; // num bishops on white squares diff --git a/src/command.c b/src/command.c index 37686ed..a51bb33 100755 --- a/src/command.c +++ b/src/command.c @@ -267,7 +267,6 @@ Return value: Trace("sizeof(PAWN_HASH_ENTRY). . . . . . . . . %u bytes\n" "sizeof(HASH_ENTRY) . . . . . . . . . . . %u bytes\n" "sizeof(MOVE) . . . . . . . . . . . . . . %u bytes\n" - "sizeof(ATTACK_BITV). . . . . . . . . . . %u bytes\n" "sizeof(SQUARE) . . . . . . . . . . . . . %u bytes\n" "sizeof(POSITION) . . . . . . . . . . . . %u bytes\n" "sizeof(MOVE_STACK) . . . . . . . . . . . %u bytes\n" @@ -287,7 +286,7 @@ Return value: "-------------------------------------------------\n" "Current main hash table size . . . . . . %u bytes (~%u Mb)\n", sizeof(PAWN_HASH_ENTRY), sizeof(HASH_ENTRY), sizeof(MOVE), - sizeof(ATTACK_BITV), sizeof(SQUARE), sizeof(POSITION), + sizeof(SQUARE), sizeof(POSITION), sizeof(MOVE_STACK), sizeof(PLY_INFO), sizeof(COUNTERS), sizeof(SEARCHER_THREAD_CONTEXT), sizeof(GAME_OPTIONS), sizeof(MOVE_TIMER), sizeof(PIECE_DATA), sizeof(VECTOR_DELTA), @@ -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); } // |
