diff options
Diffstat (limited to 'src/eval.c')
| -rwxr-xr-x | src/eval.c | 742 |
1 files changed, 135 insertions, 607 deletions
@@ -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); } // |
