diff options
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); } // |
