summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-05 01:54:59 -0700
committerScott Gasch <[email protected]>2026-09-05 01:54:59 -0700
commit7e3e6b69969cf6657f518102be515702da6a7343 (patch)
treee050341206349907ea14fe7f1f37f11d0b54dac9 /src
parent6e8645015f73a09f57e592fbf911e66db845b327 (diff)
Knight mobility: bitboard rewrite, first bbMinorAttacks contributor
_EvalKnight's mobility loop (per-square g_iNDeltas delta-walk + NMobCaseTable switch dispatch) replaced entirely with g_KnightAttacksBB[c] (generate.c's precomputed table, already used by move generation) plus two bitboard masks -- knights never x-ray or have a battery partner, so the old four-case table collapses to "enemy non-pawn: count unconditionally" and "empty-or-enemy-pawn: count unless pawn-unsafe." Adds POSITION::bbMinorAttacks[2] (chess.h), the first of the bbMinorAttacks/bbRookAttacks/bbQueenAttacks accumulators from board_representation/EVAL.md section 2 -- knight ORs its full attack set in directly, no per-square bit-scan needed. Bishop is not converted yet and still writes its own minor-bit contribution into the old per-square rgSquare[c|8].bvAttacks structure. Since knight stopped writing that old structure, every consumer that needs "does any minor attack this square" (rook/queen's mobility- safety check, king's danger computation, _WhoControlsSquareFast) now goes through a new transitional helper, _IsSquareAttackedByMinor, which ORs the new bitboard (knight) with the old bvAttacks bit (bishop) in exactly one place rather than each call site hand-rolling its own combination -- this collapses to a plain bbMinorAttacks read once bishop converts too, and the helper goes away entirely. _IsSquareAttackedByMinor is DEBUG-asserted against an independent recomputation (g_KnightAttacksBB / _BishopAttacksBB, both already- trusted primitives from move generation, unrelated to either the old ray-walk's bit bookkeeping or the new accumulator) so a bug in either mechanism fails loudly in any DEBUG build/smoke-test run rather than silently drifting into a wrong score deep in search. Verified via precommit_check.sh and a direct before/after node-count comparison (sd10, r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/ R1BQ1RK1 w - - 0 1): byte-identical, 3125335 nodes both builds. This landed as a deliberately small, single-piece-type step after an earlier attempt to convert knight+bishop+rook+queen+king in one combined change produced a real bug (a byte-scale mismatch in xray bit reconstruction) that was hard to isolate with five things changed at once, and was reverted back to 6e86450 rather than debugged further. Bishop, rook, queen, and _EvalKing's own conversion are follow-up steps, each to be landed and verified the same way, one at a time. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h13
-rwxr-xr-xsrc/eval.c185
2 files changed, 108 insertions, 90 deletions
diff --git a/src/chess.h b/src/chess.h
index 7ff9994..2e9189a 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -716,6 +716,19 @@ typedef struct _POSITION
// bbRookAttacks/bbQueenAttacks that will retire the rest of it.
BITBOARD bbPawnAttacks[2];
+ // First mover of the bbMinorAttacks pair (knight only for now --
+ // bishop is a separate, later step; see board_representation/
+ // EVAL.md section 2). "Which squares does this side's knight(s)
+ // attack" -- computed via g_KnightAttacksBB[c] and OR'd in per
+ // knight, 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. Bishop still writes its own minor-bit
+ // contribution into the old rgSquare[c|8].bvAttacks structure for
+ // now -- every consumer that needs "any minor's attack," not just
+ // knight's, ORs both sources together until bishop converts too.
+ BITBOARD bbMinorAttacks[2];
+
ULONG uWhiteSqBishopCount[2]; // num bishops on white squares
SCORE iMaterialBalance[2]; // material balance
diff --git a/src/eval.c b/src/eval.c
index 7287caf..10a1327 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -1152,6 +1152,59 @@ 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;
+}
+
+
static ULONG
_WhoControlsSquareFast(IN POSITION *pos,
IN COOR c)
@@ -1186,11 +1239,13 @@ Return value:
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);
+ PAWN_BIT : 0) |
+ (_IsSquareAttackedByMinor(pos, WHITE, c) ? MINOR_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);
+ PAWN_BIT : 0) |
+ (_IsSquareAttackedByMinor(pos, BLACK, c) ? MINOR_BIT : 0);
ULONG u;
PIECE p;
CHAR ch;
@@ -1260,6 +1315,7 @@ static void
_ClearAttackTables(IN OUT POSITION *pos)
{
register COOR c = 8;
+ pos->bbMinorAttacks[WHITE] = pos->bbMinorAttacks[BLACK] = 0;
#if 1
CLEAR_A_SQ; c += 16;
CLEAR_A_SQ; c += 16;
@@ -2840,41 +2896,6 @@ Return value:
**/
{
static const int iPawnStart[2] = { -17, +15 };
- static const UCHAR NMobCaseTable[2][14] =
- {
- {// (black)
- NMOB_MOBILE_SQUARE, // EMPTY_SQUARE (0)
- NMOB_INVALID, // INVALID_PIECE (1)
- NMOB_FRIEND, // BLACK_PAWN (2)
- NMOB_MOBILE_SQUARE, // WHITE_PAWN (3)
- NMOB_FRIEND, // BLACK_KNIGHT (4)
- NMOB_ENEMY_OTHER, // WHITE_KNIGHT (5)
- NMOB_FRIEND, // BLACK_BISHOP (6)
- NMOB_ENEMY_OTHER, // WHITE_BISHOP (7)
- NMOB_FRIEND, // BLACK_ROOK (8)
- NMOB_ENEMY_OTHER, // WHITE_ROOK (9)
- NMOB_FRIEND, // BLACK_QUEEN (10)
- NMOB_ENEMY_OTHER, // WHITE_QUEEN (11)
- NMOB_FRIEND, // BLACK_KING (12)
- NMOB_ENEMY_OTHER, // WHITE_KING (13)
- },
- {// (white)
- NMOB_MOBILE_SQUARE, // EMPTY_SQUARE (0)
- NMOB_INVALID, // INVALID_PIECE (1)
- NMOB_MOBILE_SQUARE, // BLACK_PAWN (2)
- NMOB_FRIEND, // WHITE_PAWN (3)
- NMOB_ENEMY_OTHER, // BLACK_KNIGHT (4)
- NMOB_FRIEND, // WHITE_KNIGHT (5)
- NMOB_ENEMY_OTHER, // BLACK_BISHOP (6)
- NMOB_FRIEND, // WHITE_BISHOP (7)
- NMOB_ENEMY_OTHER, // BLACK_ROOK (8)
- NMOB_FRIEND, // WHITE_ROOK (9)
- NMOB_ENEMY_OTHER, // BLACK_QUEEN (10)
- NMOB_FRIEND, // WHITE_QUEEN (11)
- NMOB_ENEMY_OTHER, // BLACK_KING (12)
- NMOB_FRIEND, // WHITE_KING (13)
- }
- };
static const COOR cKnightAtHome[2][2] =
{
{ B8, G8 }, // BLACK
@@ -2885,7 +2906,6 @@ Return value:
BITBOARD bb;
ULONG uColor;
ULONG uPawnsSupporting;
- ULONG u;
ULONG uMobilitySquares;
SCORE i;
ULONG uDist;
@@ -3045,57 +3065,34 @@ Return value:
//
//
- // Do mobilility and piece relevance. Also update attack tables.
+ // Do mobility and piece relevance. Also update attack tables.
+ //
+ // board_representation/EVAL.md section 1b/2: g_KnightAttacksBB[c]
+ // (generate.c's precomputed table, already used by move
+ // generation) is exactly the old per-square g_iNDeltas walk's
+ // destination set, IS_ON_BOARD baked in at table-build time -- no
+ // per-square branch, no mailbox read, no switch. Knights never
+ // x-ray or have a battery partner, so the old NMobCaseTable's four
+ // cases collapse to two bitboard masks: NMOB_ENEMY_OTHER (any
+ // enemy non-pawn -- count unconditionally) and NMOB_MOBILE_SQUARE
+ // (empty or enemy pawn -- count unless pawn-unsafe). NMOB_FRIEND
+ // needs no term at all, it's just "neither of the above."
//
- uMobilitySquares = 0;
- u = 0;
- ASSERT(g_iNDeltas[u] != 0);
- do
{
- cSquare = c + g_iNDeltas[u];
- if (IS_ON_BOARD(cSquare))
- {
- //
- // Always update the attack bits
- //
- ASSERT((cSquare + 8) == (cSquare | 8));
- pos->rgSquare[cSquare|8].bvAttacks[uColor].small.uMinor = 1;
-
- //
- // See what we hit. Dispatched via switch instead of an
- // indirect call through a function pointer -- same
- // rationale as the bishop ray-walk above.
- //
- p = pos->rgSquare[cSquare].pPiece;
- switch (NMobCaseTable[uColor][p])
- {
- case NMOB_MOBILE_SQUARE:
- uMobilitySquares +=
- !(pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare));
- break;
+ BITBOARD bbAttack = g_KnightAttacksBB[c];
+ BITBOARD bbFriendOcc = _BuildFriendlySideBB(pos, uColor);
+ BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, FLIP(uColor)) &
+ ~pos->bbPawns[FLIP(uColor)];
+ BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
+ BITBOARD bbMobility;
- case NMOB_ENEMY_OTHER:
- uMobilitySquares += 1;
- break;
-
- case NMOB_FRIEND:
- break;
-
- case NMOB_INVALID:
- default:
- UtilPanic(SHOULD_NOT_GET_HERE,
- NULL, NULL, NULL, NULL,
- __FILE__, __LINE__);
- break;
- }
+ pos->bbMinorAttacks[uColor] |= bbAttack;
- //
- // IDEA: bonus for hitting friendly pawn?
- //
- }
- u++;
+ bbMobility = (bbAttack & bbEnemyNonPawnOcc) |
+ (bbAttack & ~bbFriendOcc & ~bbEnemyNonPawnOcc &
+ ~bbUnsafeForMinor);
+ uMobilitySquares = CountBits(bbMobility);
}
- while(g_iNDeltas[u] != 0);
ASSERT(uMobilitySquares >= 0);
ASSERT(uMobilitySquares <= 8);
EVAL_TERM(uColor,
@@ -3381,14 +3378,14 @@ Return value:
case RMOB_EMPTY:
uCurrentMobility +=
!((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) ||
- UNSAFE_FOR_ROOK(pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)]));
+ _IsSquareAttackedByMinor(pos, FLIP(uColor), cSquare));
fStop = FALSE;
break;
case RMOB_ENEMY_LESS:
uCurrentMobility +=
!((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) ||
- UNSAFE_FOR_ROOK(pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)]));
+ _IsSquareAttackedByMinor(pos, FLIP(uColor), cSquare));
fStop = TRUE;
break;
@@ -3684,14 +3681,18 @@ Return value:
case QMOB_EMPTY:
uTotalMobility +=
!((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) ||
- UNSAFE_FOR_QUEEN(pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)]));
+ _IsSquareAttackedByMinor(pos, FLIP(uColor), cSquare) ||
+ (pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)].uWholeThing &
+ ROOK_BIT));
fStop = FALSE;
break;
case QMOB_ENEMY_LESS:
uTotalMobility +=
!((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) ||
- UNSAFE_FOR_QUEEN(pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)]));
+ _IsSquareAttackedByMinor(pos, FLIP(uColor), cSquare) ||
+ (pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)].uWholeThing &
+ ROOK_BIT));
fStop = TRUE;
break;
@@ -3935,7 +3936,9 @@ Return value:
//
bvAttack = pos->rgSquare[cSquare|8].bvAttacks[ufColor].uSmall |
((pos->bbPawnAttacks[ufColor] & COOR_TO_BB(cSquare)) ?
- PAWN_BIT : 0);
+ PAWN_BIT : 0) |
+ (_IsSquareAttackedByMinor(pos, ufColor, cSquare) ?
+ MINOR_BIT : 0);
{
COOR cRealSquare = cSquare;
cSquare |= 8;
@@ -3943,7 +3946,9 @@ Return value:
pos->rgSquare[cSquare].bvAttacks[uColor].small.uKing = 1;
bvDefend = pos->rgSquare[cSquare].bvAttacks[uColor].uSmall |
((pos->bbPawnAttacks[uColor] & COOR_TO_BB(cRealSquare)) ?
- PAWN_BIT : 0);
+ PAWN_BIT : 0) |
+ (_IsSquareAttackedByMinor(pos, uColor, cRealSquare) ?
+ MINOR_BIT : 0);
}
//