summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-10 10:29:48 -0700
committerScott Gasch <[email protected]>2026-10-10 10:29:48 -0700
commit03545c4600df81b1a47dbc63fc606c065e857d8b (patch)
treea433fdce69fd27359679ad91e0ebedd52e2506cd /src/eval.c
parentfbdcb9d76dd2444d8bc631331eaeb9435cc247f8 (diff)
Eval mobility/pseudo-mobility tweaks; drop unproductive move-ordering experiments.
eval.c: add bishop/rook x-ray pseudo-mobility terms (squares seen past a directly-attacked higher-value piece), trim pawn-centrality edge penalty and bishop-pair bonus. chess.h/dynamic.c/generate.c/search.c/searchsup.c: revert three move-ordering experiments (recapture bonus, flee-to-safety GOOD_MOVE promotion, ply-2 move-pair bonus) that calibrated with real fail-high signal but never converted into a measurable node-count or solve-rate win on the curated suites -- see MOVE_ORDERING_IDEAS.md for the evidence and reasoning behind each drop. Add MOVE_ORDERING_EVIDENCE_COLLECTION as its own GNUmakefile flag, independent of PERF_COUNTERS, for any future such harness. command.c: remove the KING_DEFECT_DUMP-gated evalkingdump command, dead since the king-safety remine work moved to eval_tune/king_safety_data/. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c890
1 files changed, 340 insertions, 550 deletions
diff --git a/src/eval.c b/src/eval.c
index 50a5e6f..a909953 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -192,13 +192,13 @@ COOR JUMPING_RANK[2] = { 7, 2 };
static SCORE PAWN_CENTRALITY_BONUS[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,
- -8, 0, 0, 0, 0, 0, 0, -8, 0,0,0,0,0,0,0,0,
- -8, 0, 5, 5, 5, 5, 0, -8, 0,0,0,0,0,0,0,0,
- -8, 0, 5, 8, 8, 5, 0, -8, 0,0,0,0,0,0,0,0,
+ -5, 0, 0, 0, 0, 0, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 5, 5, 5, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 8, 8, 5, 0, -5, 0,0,0,0,0,0,0,0,
// -------------------------------------------------------------------
- -8, 0, 5, 8, 8, 5, 0, -8, 0,0,0,0,0,0,0,0,
- -8, 0, 5, 5, 5, 5, 0, -8, 0,0,0,0,0,0,0,0,
- -8, 0, 0, 0, 0, 0, 0, -8, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 8, 8, 5, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 5, 5, 5, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 0, 0, 0, 0, 0, -5, 0,0,0,0,0,0,0,0,
0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0
};
@@ -412,7 +412,7 @@ static SCORE UNDEVELOPED_MINORS_IN_OPENING[5] =
// pieces on the board except two minors each."
// --Larry Kaufman, IM
// "Evaluation of Material Imbalance"
-static SCORE BISHOP_PAIR_BONUS = 40;
+static SCORE BISHOP_PAIR_BONUS = 30;
static SCORE STATIONARY_PAWN_ON_BISHOP_COLOR[128] =
{
@@ -449,6 +449,22 @@ static SCORE BISHOP_MOBILITY_BY_SQUARES[14] =
};// ^ |
+//
+// Pseudo-mobility: squares this bishop can "see" past a directly-attacked
+// higher-value enemy piece (rook/queen/king) -- real positional pressure
+// (the squares a capture or a moving-away enemy piece would open up), not
+// real mobility (the bishop cannot actually move there this ply). No
+// attempt to model whether the bishop itself is defended -- that's a
+// search-shaped question (SEE/exchange-accurate, whose-move-is-it), not
+// an eval one; see MOBILITY_BITBOARD_REWRITE.md. Deliberately smaller
+// than BISHOP_MOBILITY_BY_SQUARES throughout.
+//
+static SCORE BISHOP_XRAY_MOBILITY_BY_SQUARES[9] =
+{// 0 1 2 3 4 5 6 7 8
+ +0, +1, +1, +2, +2, +3, +3, +4, +4
+};
+
+
static SCORE BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[9] =
{// 0 1 2 3 4 5 6 7 8
+0, +33, +28, +18, +8, +4, +0, -10, -16
@@ -582,6 +598,19 @@ static SCORE ROOK_MOBILITY_BY_SQUARES[15] =
//
+// Pseudo-mobility: squares this rook can "see" past a directly-attacked
+// enemy queen or king -- same idea as BISHOP_XRAY_MOBILITY_BY_SQUARES
+// above, same caveats (no defended/undefended modeling -- that's
+// search's job). Deliberately smaller than ROOK_MOBILITY_BY_SQUARES
+// throughout.
+//
+static SCORE ROOK_XRAY_MOBILITY_BY_SQUARES[9] =
+{// 0 1 2 3 4 5 6 7 8
+ +0, +1, +1, +2, +2, +3, +3, +4, +4
+};
+
+
+//
// Queen eval terms
// ---------------------------------------------------------------------------
//
@@ -933,6 +962,7 @@ static DNA_BASE_SIZE g_EvalDNA[] = {
DNA_ARRAY(STATIONARY_PAWN_ON_BISHOP_COLOR),
DNA_ARRAY(TRANSIENT_PAWN_ON_BISHOP_COLOR),
DNA_ARRAY(BISHOP_MOBILITY_BY_SQUARES),
+ DNA_ARRAY(BISHOP_XRAY_MOBILITY_BY_SQUARES),
DNA_ARRAY(BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING),
DNA_ARRAY(KNIGHT_CENTRALITY_BONUS),
DNA_ARRAY(KNIGHT_KING_TROPISM_BONUS),
@@ -951,6 +981,7 @@ static DNA_BASE_SIZE g_EvalDNA[] = {
DNA_VAR(ROOK_CONNECTED_VERT),
DNA_VAR(ROOK_CONNECTED_HORIZ),
DNA_ARRAY(ROOK_MOBILITY_BY_SQUARES),
+ DNA_ARRAY(ROOK_XRAY_MOBILITY_BY_SQUARES),
DNA_ARRAY(QUEEN_MOBILITY_BY_SQUARES),
DNA_ARRAY(QUEEN_KING_TROPISM),
DNA_MATRIX(KING_INITIAL_COUNTER_BY_LOCATION),
@@ -2833,88 +2864,6 @@ Return value:
}
-#ifdef KING_DEFECT_DUMP
-static void
-_ComputeFileStormDefectsSplit(IN POSITION *pos,
- IN const PAWN_HASH_ENTRY *pHash,
- IN ULONG uColor,
- OUT ULONG *puOpenFile,
- OUT ULONG *puStormingPawn)
-/**
-
-Routine description:
-
- Debug-only sibling of _ComputeFileStormDefects that reports its two
- independent sub-ideas (open/half-open file bonus vs. storming-pawn
- distance penalty) separately instead of pre-summed, so a regression
- can weigh them independently -- they already live in two distinct
- hand-picked tables (KingFileDefects / KingStormingPawnDefects) with
- no structural change needed to retune them individually once this
- tells us their relative importance. See CLAUDE.md's king-safety
- discussion.
-
- 2026-09-15: DELIBERATELY DIVERGED from _ComputeFileStormDefects as
- of the linear-SCORE rewrite (king_safety_data/HANDOFF.md's
- 2026-09-15 addendum) -- the real path now returns signed centipawns
- off a differently-shaped table, but this dump sibling must keep
- reporting the OLD unsigned counter-style values so
- king_features_v4.csv-shaped mining data stays interpretable/
- comparable across sessions (extract_king_features.py, this project's
- whole regression pipeline, assumes these units). Traversal shape
- (own file + two adjacent, same v/distance logic) still matches --
- only the two tables' actual values are now intentionally frozen at
- their pre-rewrite originals, not kept in sync.
-
-**/
-{
- static ULONG KingStormingPawnDefects[8] =
- {
- +0, +7, +5, +2, +0, +0, +0, +0
- };
- static ULONG KingFileDefects[3] =
- {
- +4, +2, +0
- };
- ULONG ufColor = FLIP(uColor);
- COOR c = KingPosition(pos, uColor);
- COOR cFileSq, cSquare;
- ULONG u, v;
- BITBOARD bb;
- int w;
-
- *puOpenFile = 0;
- *puStormingPawn = 0;
- for (w = -1; w <= 1; w++)
- {
- cFileSq = c + w;
- if (!IS_ON_BOARD(cFileSq))
- {
- continue;
- }
- u = FILE(cFileSq) + 1;
- v = (pHash->uCountPerFile[WHITE][u] > 0) +
- (pHash->uCountPerFile[BLACK][u] > 0);
- ASSERT((v >= 0) && (v <= 2));
- *puOpenFile += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8))));
- bb = pos->bbPawns[ufColor] & BBFILE[u - 1];
- if (bb)
- {
- if (uColor == WHITE)
- {
- cSquare = CoorFromBitBoardRank1ToRank8(&bb);
- }
- else
- {
- cSquare = CoorFromBitBoardRank8ToRank1(&bb);
- }
- ASSERT(IS_ON_BOARD(cSquare));
- *puStormingPawn += KingStormingPawnDefects[DISTANCE(cSquare, c)];
- }
- }
-}
-#endif
-
-
static SCORE
_GetFileStormDefects(IN SEARCHER_THREAD_CONTEXT *ctx,
IN POSITION *pos,
@@ -4377,7 +4326,8 @@ Return value:
static void
_EvalBishop(IN OUT POSITION *pos,
IN COOR c,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyRQK)
/**
Routine description:
@@ -4387,6 +4337,10 @@ Parameters:
POSITION *pos,
COOR c,
PAWN_HASH_ENTRY *pHash,
+ BITBOARD bbEnemyRQK -- enemy rook|queen|king, precomputed once per
+ color by Eval()'s driver instead of rebuilt by every bishop of
+ that color (see MOBILITY_BITBOARD_REWRITE.md) -- depends only on
+ static piece locations, no eval-ordering dependency.
Return value:
@@ -4444,98 +4398,79 @@ Return value:
pHash->bbStationaryPawns[BLACK]) & bbMask;
//
- // Bishop mobility (and update attack tables) -- board_representation/
- // EVAL.md section 1b: _BishopAttacksBB(c, pos->bbOccupied)
- // (generate.c's magic-bitboard slider lookup, already used by move
- // generation) gives the whole ray-to-first-blocker attack set in
- // one shot -- no per-square delta walk, no mailbox read, no switch
- // dispatch. The old BMobCaseTable's seven cases collapse to:
- // - terminal enemy non-pawn (opposing minor/rook/queen/king):
- // counts unconditionally -- covers both BMOB_ENEMY_SAME (no
- // x-ray) and BMOB_ENEMY_GREATER (x-rays past too); the
- // x-ray/no-x-ray distinction only matters for attack-bit
- // population below, not the mobility count itself.
- // - empty or terminal enemy pawn: counts unless pawn-unsafe
- // (BMOB_EMPTY / BMOB_ENEMY_PAWN) -- both terminal-square
- // categories together are exactly "not friend-occupied and not
- // enemy-non-pawn-occupied," the same partition knight's
- // reduction already uses.
+ // Bishop mobility (and update attack tables) -- single magic-bitboard
+ // probe, no chain walk (see MOBILITY_BITBOARD_REWRITE.md). The old
+ // seven-case BMobCaseTable collapses to pure mask arithmetic over the
+ // one-shot first-blocker attack set:
+ // - terminal enemy non-pawn (minor/rook/queen/king): counts
+ // unconditionally -- this is a capture, always "real" mobility.
+ // - empty or terminal enemy pawn: counts unless pawn-unsafe -- both
+ // terminal-square categories together are exactly "not
+ // friend-occupied and not enemy-non-pawn-occupied," the same
+ // partition knight's reduction uses.
// - terminal friendly, non-stationary pawn on this bishop's own
- // color complex (BMOB_FRIEND_PAWN): still counts, the one case
- // that isn't a pure occupancy mask -- bbPc (already computed
- // above for the good/bad transient-pawn scoring) is exactly
- // "non-stationary pawns of either color on this color complex";
- // ANDing with pos->bbPawns[uColor] and the attack set isolates
- // just this bishop's own transient pawns.
- // - any other friendly piece (knight/rook/king): blocks, no
- // count, no x-ray -- simply excluded by the friendly-occupied
- // mask, nothing else needed.
+ // color complex: still counts -- bbPc (already computed above for
+ // the good/bad transient-pawn scoring) is exactly "non-stationary
+ // pawns of either color on this color complex."
+ // - any other friendly piece (knight/rook/king): blocks, no count.
+ //
+ // Deliberately NOT replicated: mobility/connectivity credit for
+ // squares beyond a friendly bishop/queen or enemy rook/queen/king --
+ // a bishop cannot physically move past a piece it would capture in
+ // one ply, so that was never "real mobility" to begin with (dropping
+ // it is a correctness fix, not just a simplification). See
+ // MOBILITY_BITBOARD_REWRITE.md for the king-ring x-ray replacement,
+ // which is the one piece of information this loses that's worth
+ // deliberately replacing below.
//
BITBOARD bbAttack = _BishopAttacksBB(c, pos->bbOccupied);
BITBOARD bbEnemySame = pos->bbPieces[xColor][BISHOP] |
pos->bbPieces[xColor][KNIGHT];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][ROOK] |
- pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(KingPosition(pos, xColor));
- BITBOARD bbFriendBQ = pos->bbPieces[uColor][BISHOP] |
- pos->bbPieces[uColor][QUEEN];
BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
- BITBOARD bbExclude = 0;
- BITBOARD bbSeen = 0;
- BITBOARD bbLayer = bbAttack;
- BITBOARD bbMobility = 0;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbFirstLayerMask = 0;
+ BITBOARD bbEnemyPawnHere = bbAttack & pos->bbPawns[xColor];
+ BITBOARD bbTransientHere = bbAttack & pos->bbPawns[uColor] & bbPc;
+ BITBOARD bbEnemyGEHere = bbAttack & bbEnemyRQK;
+ BITBOARD bbMobility;
+ BITBOARD bbXray;
pos->bbMinorAttacks[uColor] |= bbAttack;
+ bbMobility = (bbAttack & ~pos->bbOccupied & ~bbUnsafeForMinor) |
+ (bbAttack & bbEnemySame) |
+ bbEnemyGEHere |
+ (bbEnemyPawnHere & ~bbUnsafeForMinor) |
+ bbTransientHere;
+
//
- // Unified chain walk over both mobility and x-ray population
- // in one pass -- same technique and same reason as rook's
- // identical rewrite (board_representation/EVAL.md section 9):
- // the old mailbox walk's BMOB_FRIEND_XRAY/BMOB_ENEMY_GREATER
- // cases don't just x-ray past their blocker for attack-bit
- // purposes, they keep walking (fStop=FALSE) and keep crediting
- // mobility for whatever lies beyond. The originally-committed
- // bitboard version (and its single-hop-then-chain-following
- // x-ray fix) only ever handled the near side for *mobility*,
- // an under-count caught the same way rook's was: a two-bishop
- // battery on one diagonal with open squares beyond (old
- // mailbox: 12/8 mobility for the two bishops; near-side-only
- // bitboard version: 11/5).
+ // King-ring x-ray, replacing the old chain's attack-bit population:
+ // only pay for an extra magic probe when this bishop already
+ // directly attacks an enemy rook/queen/king square (bbEnemyGEHere,
+ // already computed above for mobility -- free to test). Every bit
+ // in bbEnemyGEHere is, by construction, the terminal blocker of a
+ // *different* diagonal ray, so masking all of them off at once and
+ // re-probing in a single magic lookup gives the exact same union a
+ // per-blocker loop would (removing one ray's blocker can't affect a
+ // different ray's result) -- no need to isolate and remove them one
+ // at a time. No deeper chaining needed either way, since the only
+ // consumer (_EvalKingSafety) only ever ANDs this against the king's
+ // ring, never walks further.
//
- for (;;)
- {
- BITBOARD bbNew = bbLayer & ~bbSeen;
- BITBOARD bbBlockersHere = bbNew & pos->bbOccupied;
- BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied;
- BITBOARD bbFriendBQHere = bbBlockersHere & bbFriendBQ;
- BITBOARD bbEnemySameHere = bbBlockersHere & bbEnemySame;
- BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue;
- BITBOARD bbFriendPawnHere = bbBlockersHere & pos->bbPawns[uColor];
- BITBOARD bbEnemyPawnHere = bbBlockersHere &
- pos->bbPawns[xColor];
- BITBOARD bbTransientHere = bbFriendPawnHere & bbPc;
- BITBOARD bbContinueHere;
+ bbXray = bbEnemyGEHere ?
+ _BishopAttacksBB(c, pos->bbOccupied & ~bbEnemyGEHere) : 0;
+ pos->bbMinorXrayAttacks[uColor] |= bbXray;
- bbSeen |= bbNew;
- bbXrayAccum |= (bbNew & bbFirstLayerMask);
- bbFirstLayerMask = ~(BITBOARD)0;
-
- bbMobility |= (bbEmptyHere & ~bbUnsafeForMinor) |
- bbEnemySameHere | bbEnemyGEHere |
- (bbEnemyPawnHere & ~bbUnsafeForMinor) |
- bbTransientHere;
-
- bbContinueHere = bbFriendBQHere | bbEnemyGEHere;
- if (!bbContinueHere)
- {
- break;
- }
- bbExclude |= bbContinueHere;
- bbLayer = _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude);
- }
- pos->bbMinorXrayAttacks[uColor] |= bbXrayAccum;
+ // Pseudo-mobility bonus for squares seen past the higher-value
+ // blocker -- see BISHOP_XRAY_MOBILITY_BY_SQUARES's comment. bbXray
+ // already includes the blocker's own square (redundant with
+ // bbAttack, which already counts it as a capture), so subtracting
+ // bbAttack leaves exactly the net-new squares revealed behind it.
+ EVAL_TERM(uColor,
+ BISHOP,
+ c,
+ pos->iScore[uColor],
+ BISHOP_XRAY_MOBILITY_BY_SQUARES[
+ MINU(FastCountBits(bbXray & ~bbAttack), 8)],
+ "xray pseudo-mobility");
uTotalMobility = FastCountBits(bbMobility);
@@ -4845,7 +4780,9 @@ Return value:
static void
_EvalRook(IN OUT POSITION *pos,
IN COOR c,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyQK,
+ IN BITBOARD bbUnsafeForRook)
/**
Routine description:
@@ -4855,6 +4792,12 @@ Parameters:
IN POSITION *pos,
IN COOR c,
IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyQK -- enemy queen|king, precomputed once per color
+ by Eval()'s driver (see MOBILITY_BITBOARD_REWRITE.md) -- depends
+ only on static piece locations.
+ IN BITBOARD bbUnsafeForRook -- enemy pawn|minor attacks, precomputed
+ once per color right before the rook loops start, valid because
+ both colors' knight+bishop passes are already complete by then.
Return value:
@@ -4911,125 +4854,88 @@ Return value:
(FastCountBits(pos->bbPawns[uColor]) + FastCountBits(pos->bbPawns[xColor])));
//
- // Rook mobility (and update attack tables) -- board_representation/
- // EVAL.md section 1b/9: _RookAttacksBB(c, pos->bbOccupied)
- // (generate.c's magic-bitboard slider lookup) gives the whole
- // ray-to-first-blocker attack set in one shot, same technique
- // already landed for knight/bishop. The old RMobCaseTable's six
- // live cases collapse to:
- // - terminal enemy rook/queen/king (RMOB_ENEMY_SAME/_GREATER):
- // counts unconditionally, no unsafe check -- capturing an
- // equal-or-higher piece is never "unsafe" in the mobility
- // sense. Queen/king additionally x-ray past (_GREATER); rook
- // does not (_SAME).
- // - empty or terminal enemy pawn/knight/bishop (RMOB_EMPTY /
- // RMOB_ENEMY_LESS): counts unless the square is attacked by an
- // enemy pawn or minor -- both terminal-square categories
- // together are exactly "not friend-occupied and not
- // enemy-rook/queen/king-occupied," the same partition
- // bishop/knight's reductions already use with their own value
- // thresholds.
- // - any friendly piece (RMOB_FRIEND_BLOCK/_ROOK/_QUEEN): never
- // counts, simply excluded by the friendly-occupied mask.
- // Friendly rook/queen additionally x-ray past (no mobility
- // credit at the blocker itself, matching the old table); a
- // friendly rook blocker also earns the connected-rook bonus.
+ // Rook mobility (and update attack tables) -- single magic-bitboard
+ // probe, no chain walk (see MOBILITY_BITBOARD_REWRITE.md). The old
+ // RMobCaseTable's six cases collapse to mask arithmetic over the
+ // one-shot first-blocker attack set:
+ // - terminal enemy rook/queen/king: counts unconditionally -- a
+ // capture is always "real" mobility regardless of value.
+ // - empty or terminal enemy pawn/knight/bishop: counts unless the
+ // square is attacked by an enemy pawn or minor -- both
+ // terminal-square categories together are exactly "not
+ // friend-occupied and not enemy-rook/queen/king-occupied," the
+ // same partition bishop/knight's reductions use.
+ // - any friendly piece: blocks, no count. A friendly rook
+ // specifically also earns the connected-rook bonus (direct
+ // attack only now, not chained through a further blocker).
+ //
+ // Deliberately NOT replicated: mobility/connectivity credit for
+ // squares beyond a friendly rook/queen or enemy queen/king -- a
+ // rook cannot physically move past a piece it would capture in one
+ // ply, so that was never "real mobility" to begin with (dropping it
+ // is a correctness fix, not just a simplification). See
+ // MOBILITY_BITBOARD_REWRITE.md for the king-ring x-ray replacement,
+ // which is the one piece of information this loses that's worth
+ // deliberately replacing below.
//
-
BITBOARD bbAttack = _RookAttacksBB(c, pos->bbOccupied);
BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
BITBOARD bbEnemySame = pos->bbPieces[xColor][ROOK];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(KingPosition(pos, xColor));
- BITBOARD bbFriendRQ = pos->bbPieces[uColor][ROOK] |
- pos->bbPieces[uColor][QUEEN];
- BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[xColor] |
- pos->bbMinorAttacks[xColor];
- BITBOARD bbExclude = 0;
- BITBOARD bbSeen = 0;
- BITBOARD bbLayer = bbAttack;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbFirstLayerMask = 0;
+ BITBOARD bbEnemyGEHere = bbAttack & bbEnemyQK;
+ BITBOARD bbEnemyLEHere = bbAttack & bbEnemyOcc &
+ ~bbEnemySame & ~bbEnemyGEHere;
+ BITBOARD bbFriendRookHere = bbAttack & pos->bbPieces[uColor][ROOK];
+ BITBOARD bbXray;
- bbMobility = 0;
pos->bbRookAttacks[uColor] |= bbAttack;
- //
- // Unified chain walk over both mobility and x-ray population
- // in one pass, board_representation/EVAL.md section 9. The
- // old mailbox walk's RMOB_FRIEND_ROOK/_QUEEN and
- // RMOB_ENEMY_GREATER cases don't just x-ray past their blocker
- // for attack-bit purposes -- they keep walking (fStop=FALSE)
- // and keep crediting mobility for whatever safe/empty squares
- // and further captures lie beyond, however many such blockers
- // are stacked on one ray. A first version of this conversion
- // (committed, since fixed) only handled the near side (up to
- // the first blocker) for mobility and treated the chain purely
- // as an attack-bit population exercise -- caught by comparing
- // against the old mailbox walk directly on a battery position
- // (two same-color rooks on an open file: old code credited 13
- // mobility to the far rook's near companion via squares beyond
- // it, the near-side-only version credited only 9). Fixed by
- // reusing the exact same "recompute with blockers excluded"
- // chain used for x-ray population to also accumulate mobility
- // credit at every layer, not just the first.
- //
- // Each iteration classifies the newly-revealed blockers on
- // each still-open ray: friendly rook/queen and enemy queen/
- // king continue the chain (recompute with them excluded too);
- // everything else (friendly non-R/Q, enemy pawn/knight/bishop,
- // enemy rook, or the board edge) stops that ray. Bounded: each
- // iteration either terminates or consumes at least one new
- // piece into the exclusion set, so this can loop at most
- // once per piece on the board.
- //
- for (;;)
- {
- BITBOARD bbNew = bbLayer & ~bbSeen;
- BITBOARD bbBlockersHere = bbNew & pos->bbOccupied;
- BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied;
- BITBOARD bbFriendRQHere = bbBlockersHere & bbFriendRQ;
- BITBOARD bbEnemySameHere = bbBlockersHere & bbEnemySame;
- BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue;
- BITBOARD bbEnemyLEHere = bbBlockersHere & bbEnemyOcc &
- ~bbEnemySameHere & ~bbEnemyGEHere;
- BITBOARD bbFriendRookHere = bbBlockersHere &
- pos->bbPieces[uColor][ROOK];
- BITBOARD bbContinueHere;
-
- bbSeen |= bbNew;
- bbXrayAccum |= (bbNew & bbFirstLayerMask);
- bbFirstLayerMask = ~(BITBOARD)0;
+ bbMobility = (bbAttack & ~pos->bbOccupied & ~bbUnsafeForRook) |
+ (bbAttack & bbEnemySame) |
+ bbEnemyGEHere |
+ (bbEnemyLEHere & ~bbUnsafeForRook);
- bbMobility |= (bbEmptyHere & ~bbUnsafeForRook) |
- bbEnemySameHere | bbEnemyGEHere |
- (bbEnemyLEHere & ~bbUnsafeForRook);
+ // Connected-rook bonus -- direct attack only now; see the comment
+ // above.
+ while (bbFriendRookHere)
+ {
+ COOR cBlocker = CoorFromBitBoardRank8ToRank1(&bbFriendRookHere);
+ FLAG fHoriz = (RANK(cBlocker) == RANK(c));
+ EVAL_TERM(uColor,
+ ROOK,
+ cBlocker,
+ pos->iScore[uColor],
+ (ROOK_CONNECTED_HORIZ * fHoriz +
+ ROOK_CONNECTED_VERT * FLIP(fHoriz)),
+ "rooks connected");
+ }
- // Connected-rook bonus fires at every friendly rook found
- // along the chain, not just the first (matches the old
- // walk: each RMOB_FRIEND_ROOK hit its own EVAL_TERM call).
- while (bbFriendRookHere)
- {
- COOR cBlocker = CoorFromBitBoardRank8ToRank1(&bbFriendRookHere);
- FLAG fHoriz = (RANK(cBlocker) == RANK(c));
- EVAL_TERM(uColor,
- ROOK,
- cBlocker,
- pos->iScore[uColor],
- (ROOK_CONNECTED_HORIZ * fHoriz +
- ROOK_CONNECTED_VERT * FLIP(fHoriz)),
- "rooks connected");
- }
+ //
+ // King-ring x-ray, replacing the old chain's attack-bit population:
+ // only pay for an extra magic probe when this rook already directly
+ // attacks the enemy queen or king square (bbEnemyGEHere, already
+ // computed above for mobility -- free to test). bbEnemyGEHere has
+ // at most two bits (queen, king), each the terminal blocker of a
+ // *different* ray -- masking both off at once and re-probing in a
+ // single magic lookup gives the same union a per-blocker loop would
+ // (one ray's blocker removal can't affect the other ray's result).
+ // No deeper chaining needed either way, since the only consumer
+ // (_EvalKingSafety) only ever ANDs this against the king's ring,
+ // never walks further.
+ //
+ bbXray = bbEnemyGEHere ?
+ _RookAttacksBB(c, pos->bbOccupied & ~bbEnemyGEHere) : 0;
+ pos->bbRookXrayAttacks[uColor] |= bbXray;
- bbContinueHere = bbFriendRQHere | bbEnemyGEHere;
- if (!bbContinueHere)
- {
- break;
- }
- bbExclude |= bbContinueHere;
- bbLayer = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude);
- }
- pos->bbRookXrayAttacks[uColor] |= bbXrayAccum;
+ // Pseudo-mobility bonus for squares seen past the queen/king blocker
+ // -- see ROOK_XRAY_MOBILITY_BY_SQUARES's comment, same reasoning as
+ // bishop's identical term.
+ EVAL_TERM(uColor,
+ ROOK,
+ c,
+ pos->iScore[uColor],
+ ROOK_XRAY_MOBILITY_BY_SQUARES[
+ MINU(FastCountBits(bbXray & ~bbAttack), 8)],
+ "xray pseudo-mobility");
uTotalMobility = FastCountBits(bbMobility);
ASSERT(uTotalMobility <= 14);
@@ -5060,7 +4966,9 @@ Return value:
static void
_EvalQueen(IN OUT POSITION *pos,
IN COOR c,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyQK,
+ IN BITBOARD bbUnsafeForQueen)
/**
Parameters:
@@ -5068,6 +4976,13 @@ Parameters:
POSITION *pos,
COOR c,
PAWN_HASH_ENTRY *pHash,
+ BITBOARD bbEnemyQK -- enemy queen|king, precomputed once per color by
+ Eval()'s driver -- identical formula to _EvalRook's bbEnemyQK, so
+ the same precomputed value is reused for both (see
+ MOBILITY_BITBOARD_REWRITE.md).
+ BITBOARD bbUnsafeForQueen -- precomputed once per color right before
+ the queen loops start, valid because both colors' pawn/minor/rook
+ passes are already complete by then.
Return value:
@@ -5079,113 +4994,54 @@ Return value:
ULONG uColor;
ULONG xColor;
ULONG uTotalMobility;
- ULONG u;
- COOR cKing;
ASSERT(IS_ON_BOARD(c));
ASSERT(p && IS_QUEEN(p));
uColor = GET_COLOR(p);
ASSERT(IS_VALID_COLOR(uColor));
xColor = FLIP(uColor);
- cKing = KingPosition(pos, xColor);
- ASSERT(IS_ON_BOARD(cKing));
- ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
+ ASSERT(IS_ON_BOARD(KingPosition(pos, xColor)));
+ ASSERT(IS_KING(pos->rgSquare[KingPosition(pos, xColor)].pPiece));
//
- // Do queen mobility (and update attack tables) -- two-pass
- // rook-direction/bishop-direction magic lookups, board_
- // representation/EVAL.md section 1b/9: MOVEGEN_MIGRATION.md
- // already found (in the stashed first bitboard-eval attempt's
- // _EvalQueenOccupancyBB PoC) that a combined 8-ray table measures
- // *slower* than reusing the rook/bishop tables in two passes --
- // reuse that structure here too, don't rediscover the regression.
- // Each pass is its own unified chain walk, identical technique to
- // rook's/bishop's own conversions (including their fix for
- // crediting mobility beyond a battery partner, not just x-ray
- // attack bits): continue through a friendly queen in both passes,
- // a friendly rook only in the rook-direction pass, a friendly
- // bishop only in the bishop-direction pass (QMOB_FRIEND_ROOK/
- // _BISHOP's old per-ray-family split falls out for free from
- // *which* lookup a blocker shows up in -- no fOrthogonalRay flag
- // needed). Unlike rook/bishop, queen never x-rays through *any*
- // enemy piece (QMOB_ENEMY_GE always stopped in the old table, the
- // one case that's a *fewer*-cases asymmetry versus bishop, not
- // more), so the enemy side of each pass's continue-set is empty.
+ // Queen mobility (and update attack tables) -- two single magic
+ // probes (rook-direction + bishop-direction), unioned, then one
+ // shared mask pass. No chain walk (see MOBILITY_BITBOARD_REWRITE.md):
+ // - terminal enemy queen/king: counts unconditionally -- a capture
+ // is always "real" mobility.
+ // - empty or terminal enemy pawn/minor/rook: counts unless the
+ // square is attacked by an enemy pawn, minor, rook, or (modulo
+ // our own defense) the enemy king -- same partition
+ // bishop/knight/rook's reductions use, just with a bigger
+ // "cheaper piece" set since the queen is the most valuable.
+ // - any friendly piece: blocks, no count.
//
- BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
- BITBOARD bbEnemyGE = pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(cKing);
- //
- // Enemy king attacks are also unsafe for the queen -- a free
- // O(1) table lookup by king square, independent of eval order
- // (unlike the enemy minor/rook attacks above, which do need
- // both colors' knight/bishop/rook passes to finish first, and
- // already have by queen's turn: see the eval call order in
- // Eval() below). Only unsafe when the queen wouldn't be
- // recaptured by something of our own first -- if a friendly
- // pawn/minor/rook already covers that square, the king can't
- // just take the queen for free. Every one of those tables (ours
- // AND the enemy's) is fully populated by now: pawns, minors,
- // and rooks all run their own eval pass before queen does.
+ // Deliberately NOT replicated: mobility credit for squares beyond a
+ // friendly rook/bishop/queen battery partner -- a queen cannot
+ // physically move past a piece it shares a square with in one ply,
+ // so (same as bishop/rook) that was never "real mobility" to begin
+ // with. Also NOT replicated, and needing no replacement: the old
+ // code's x-ray-attack population. Unlike bishop/rook, queen's old
+ // chain only ever continued through a FRIENDLY blocker (it never
+ // x-rayed through any enemy piece at all -- QMOB_ENEMY_GE always
+ // stopped the ray in the original mailbox table). By the same
+ // redundancy argument used to drop bishop/rook's friendly-blocker
+ // x-ray case, that information was always a duplicate of the
+ // blocking piece's own direct-attack pass -- there's no enemy-GE
+ // case here to replace, so _EvalKingSafety's queen-x-ray reads
+ // (pos->bbQueenXrayAttacks[xColor]) see nothing lost by simply never
+ // populating it.
//
- BITBOARD bbUnsafeForQueen = pos->bbPawnAttacks[xColor] |
- pos->bbMinorAttacks[xColor] |
- pos->bbRookAttacks[xColor] |
- (g_KingAttacksBB[cKing] &
- ~(pos->bbPawnAttacks[uColor] |
- pos->bbMinorAttacks[uColor] |
- pos->bbRookAttacks[uColor]));
- BITBOARD bbAttackTotal = 0;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbMobility = 0;
-
- ULONG uPass;
- for (uPass = 0; uPass < 2; uPass++)
- {
- BITBOARD bbFriendContinue = (uPass == 0) ?
- (pos->bbPieces[uColor][ROOK] | pos->bbPieces[uColor][QUEEN]) :
- (pos->bbPieces[uColor][BISHOP] | pos->bbPieces[uColor][QUEEN]);
- BITBOARD bbInitial = (uPass == 0) ?
- _RookAttacksBB(c, pos->bbOccupied) :
- _BishopAttacksBB(c, pos->bbOccupied);
- BITBOARD bbExclude = 0;
- BITBOARD bbSeen = 0;
- BITBOARD bbLayer = bbInitial;
- BITBOARD bbFirstLayerMask = 0;
-
- bbAttackTotal |= bbInitial;
-
- for (;;)
- {
- BITBOARD bbNew = bbLayer & ~bbSeen;
- BITBOARD bbBlockersHere = bbNew & pos->bbOccupied;
- BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied;
- BITBOARD bbFriendContinueHere = bbBlockersHere &
- bbFriendContinue;
- BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGE;
- BITBOARD bbEnemyLEHere = bbBlockersHere & bbEnemyOcc &
- ~bbEnemyGEHere;
-
- bbSeen |= bbNew;
- bbXrayAccum |= (bbNew & bbFirstLayerMask);
- bbFirstLayerMask = ~(BITBOARD)0;
-
- bbMobility |= (bbEmptyHere & ~bbUnsafeForQueen) |
- bbEnemyGEHere |
- (bbEnemyLEHere & ~bbUnsafeForQueen);
+ BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
+ BITBOARD bbAttackTotal = _RookAttacksBB(c, pos->bbOccupied) |
+ _BishopAttacksBB(c, pos->bbOccupied);
+ BITBOARD bbEnemyGEHere = bbAttackTotal & bbEnemyQK;
+ BITBOARD bbEnemyLEHere = bbAttackTotal & bbEnemyOcc & ~bbEnemyGEHere;
+ BITBOARD bbMobility = (bbAttackTotal & ~pos->bbOccupied & ~bbUnsafeForQueen) |
+ bbEnemyGEHere |
+ (bbEnemyLEHere & ~bbUnsafeForQueen);
- if (!bbFriendContinueHere)
- {
- break;
- }
- bbExclude |= bbFriendContinueHere;
- bbLayer = (uPass == 0) ?
- _RookAttacksBB(c, pos->bbOccupied & ~bbExclude) :
- _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude);
- }
- }
pos->bbQueenAttacks[uColor] |= bbAttackTotal;
- pos->bbQueenXrayAttacks[uColor] |= bbXrayAccum;
uTotalMobility = FastCountBits(bbMobility);
ASSERT(uTotalMobility <= 27);
@@ -5246,10 +5102,6 @@ Return value:
{
BITV bvPattern;
ULONG uQueenNearKing;
-#ifdef KING_DEFECT_DUMP
- COOR cSquare;
- KING_DEFECT_DUMP_INFO *pDump = &ctx->sKingDefectDump[uColor];
-#endif
SCORE iKingScore = 0;
// 2026-09-15: linear-SCORE rewrite -- see king_safety_data/
@@ -5378,105 +5230,9 @@ Return value:
iSafety += KING_BACK_FLIGHT_BONUS *
(SCORE)FastCountBits(bbBack & bbEmpty & ~bbEnemyAtkAny);
-#ifdef KING_DEFECT_DUMP
- {
- // Debug-only, per-square breakdown for the mining pipeline
- // (extract_king_features.py) -- kept fully independent of
- // the bitboard computation above, same traversal shape as
- // the pre-rewrite loop so king_features_v4.csv's column
- // semantics stay unchanged. Does not feed iSafety.
- static INT KingSafetyDeltas[9] =
- {
- -17, -16, -15,
- -1, +1,
- +15, +16, +17, 0
- };
- ULONG uSlot = 0;
- BITV bvSlotAttack, bvSlotXray, bvSlotDefend;
-
- do
- {
- cSquare = c + KingSafetyDeltas[uSlot];
- if (IS_ON_BOARD(cSquare))
- {
- BITBOARD sq = COOR_TO_BB(cSquare);
- BITV bvEither;
-
- bvSlotAttack = ((pos->bbPawnAttacks[xColor] & sq) ? PAWN_BIT : 0) |
- ((pos->bbMinorAttacks[xColor] & sq) ? MINOR_BIT : 0) |
- ((pos->bbRookAttacks[xColor] & sq) ? ROOK_BIT : 0) |
- ((pos->bbQueenAttacks[xColor] & sq) ? QUEEN_BIT : 0);
- bvSlotXray = ((pos->bbMinorXrayAttacks[xColor] & sq) ? MINOR_BIT : 0) |
- ((pos->bbRookXrayAttacks[xColor] & sq) ? ROOK_BIT : 0) |
- ((pos->bbQueenXrayAttacks[xColor] & sq) ? QUEEN_BIT : 0);
- bvSlotDefend = ((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);
-
- bvEither = bvSlotAttack | bvSlotXray;
- ASSERT(uSlot < KING_RING_NUM_SLOTS);
- pDump->fOnBoard[uSlot] = TRUE;
- pDump->fAttackedByPieceType[uSlot][KING_RING_PAWN] =
- ((bvEither & PAWN_BIT) != 0);
- pDump->fAttackedByPieceType[uSlot][KING_RING_MINOR] =
- ((bvEither & MINOR_BIT) != 0);
- pDump->fAttackedByPieceType[uSlot][KING_RING_ROOK] =
- ((bvEither & ROOK_BIT) != 0);
- pDump->fAttackedByPieceType[uSlot][KING_RING_QUEEN] =
- ((bvEither & QUEEN_BIT) != 0);
- if (bvSlotAttack != 0)
- {
- BITV bvDefendForDump = bvSlotDefend;
- if (bvSlotXray || (bvSlotAttack & (bvSlotAttack - 1)))
- {
- bvDefendForDump &= ~KING_BIT;
- }
- if (bvDefendForDump == 0)
- {
- pDump->fAttackedUndefended[uSlot] = TRUE;
- }
- else
- {
- pDump->fAttackedDefended[uSlot] = TRUE;
- }
- }
- else
- {
- if (bvSlotXray != 0)
- {
- pDump->fXrayedUnattacked[uSlot] = TRUE;
- }
- if (!(pos->bbOccupied & sq))
- {
- pDump->fEmptyFlight[uSlot] = TRUE;
- }
- }
- }
- uSlot++;
- }
- while (KingSafetyDeltas[uSlot] != 0);
- }
-#endif
-
- //
// _ComputeFileStormDefects's comment.
- //
iSafety += _GetFileStormDefects(ctx, pos, pHash, uColor);
-#ifdef KING_DEFECT_DUMP
- _ComputeFileStormDefectsSplit(pos, pHash, uColor,
- &pDump->uFileStormOpenFile,
- &pDump->uFileStormStormingPawn);
- pDump->uFileStormDefects = pDump->uFileStormOpenFile +
- pDump->uFileStormStormingPawn;
-#endif
-
iSafety += KING_INITIAL_LOCATION_SCALE * KING_EVAL_INITIAL_LOCATION[uColor][c];
-#ifdef KING_DEFECT_DUMP
- pDump->uInitialCounterByLocation =
- (ULONG)KING_EVAL_INITIAL_LOCATION[uColor][c];
-#endif
// Danger-magnitude acceleration -- see KING_DANGER_ACCELERATOR's
// comment above. Applied here (as part of the real per-king
@@ -5513,21 +5269,6 @@ Return value:
iSafety,
"king safety");
-#ifdef KING_DEFECT_DUMP
- pDump->iRealSafety = iSafety;
- pDump->uQueenNearKing = uQueenNearKing;
- // uCounterTotal/uSafetyTableValue no longer have a coherent
- // meaning post-rewrite (no more shared uCounter/KING_SAFETY_
- // BY_COUNTER lookup) -- left zeroed by the memset above rather
- // than populated with something misleading. Both were REFERENCE-
- // only columns in the training pipeline (train_king_safety_
- // model.py explicitly excludes them from the feature set), so
- // this doesn't affect the regression itself.
- pDump->fCastled = pos->fCastled[uColor];
- pDump->uMinorsAtHome = pos->uMinorsAtHome[uColor];
- pDump->uAttackerArmyScaler = pos->uArmyScaler[xColor];
-#endif
-
// Bonus for castling / penalty for loss of castle. Also, if
// side has not yet castled AND castling is still a live
// option, be concerned with undeveloped minor pieces.
@@ -5551,56 +5292,51 @@ Return value:
// copy-pasted blocks -- one fewer per-piece branch for the
// processor to mispredict (board_representation/EVAL.md
// section 9).
- if (FALSE == pos->fCastled[uColor])
+ ULONG uDevelopmentScaler = CASTLE_AND_DEVELOPMENT_SCALER[pos->uArmyScaler[uColor]];
+ if (uDevelopmentScaler > 0)
{
- ULONG uDevelopmentScaler = CASTLE_AND_DEVELOPMENT_SCALER[pos->uArmyScaler[uColor]];
- if (uDevelopmentScaler > 0)
- {
- static const BITV CASTLE_SHORT[2] =
- { CASTLE_BLACK_SHORT, CASTLE_WHITE_SHORT };
- static const BITV CASTLE_LONG[2] =
- { CASTLE_BLACK_LONG, CASTLE_WHITE_LONG };
- BITV bvBothRights = CASTLE_SHORT[uColor] | CASTLE_LONG[uColor];
- // One popcount answers both "can this side still
- // castle at all" (>0, gates development below) and
- // "how many options are missing" (2-count, scales
- // the persistent penalty below) -- replaces two
- // separate (==0) branches/multiplies/EVAL_TERM calls
- // with one shared count.
- ULONG uRightsRemaining =
- FastCountBits(pos->bvCastleInfo & bvBothRights);
- ASSERT(uRightsRemaining <= 2);
+ static const BITV CASTLE_SHORT[2] =
+ { CASTLE_BLACK_SHORT, CASTLE_WHITE_SHORT };
+ static const BITV CASTLE_LONG[2] =
+ { CASTLE_BLACK_LONG, CASTLE_WHITE_LONG };
+ static const BITV bvBothRights[2] =
+ { CASTLE_SHORT[BLACK] | CASTLE_LONG[BLACK],
+ CASTLE_SHORT[WHITE] | CASTLE_LONG[WHITE] };
- // Branchless: (uRightsRemaining > 0) is 0 or 1, multiplied
- // straight into the term instead of an `if` -- nothing
- // left to mispredict.
- ASSERT(pos->uMinorsAtHome[uColor] <= 4);
- EVAL_TERM(uColor,
- 0,
- ILLEGAL_COOR,
- pos->iScore[uColor],
- (SCORE)((UNDEVELOPED_MINORS_IN_OPENING[pos->uMinorsAtHome[uColor]] *
- (SCORE)uDevelopmentScaler) / 8),
- "development");
+ ASSERT(pos->uMinorsAtHome[uColor] <= 4);
+ EVAL_TERM(uColor,
+ 0,
+ ILLEGAL_COOR,
+ pos->iScore[uColor],
+ (SCORE)((UNDEVELOPED_MINORS_IN_OPENING[pos->uMinorsAtHome[uColor]] *
+ (SCORE)uDevelopmentScaler) / 8),
+ "development");
- // Deliberately NOT gated on uRightsRemaining > 0 --
- // see this block's header comment. Fires at full
- // weight (both options gone) even long after the
- // rights were lost, for as long as uDevelopmentScaler
- // itself stays nonzero.
- EVAL_TERM(uColor,
- KING,
- c,
- iKingScore,
- (SCORE)((KING_MISSING_ONE_CASTLE_OPTION *
- (SCORE)(2 - uRightsRemaining) *
- (SCORE)uDevelopmentScaler) / 8),
- "missing castle option(s)");
-#ifdef KING_DEFECT_DUMP
- pDump->fMissingCastleShort = ((CASTLE_SHORT[uColor] & pos->bvCastleInfo) == 0);
- pDump->fMissingCastleLong = ((CASTLE_LONG[uColor] & pos->bvCastleInfo) == 0);
-#endif
- }
+ // One popcount answers both "can this side still
+ // castle at all" (>0, gates development below) and
+ // "how many options are missing" (2-count, scales
+ // the persistent penalty below) -- replaces two
+ // separate (==0) branches/multiplies/EVAL_TERM calls
+ // with one shared count.
+ ULONG uRightsRemaining =
+ FastCountBits(pos->bvCastleInfo & bvBothRights[uColor]);
+ ASSERT(uRightsRemaining <= 2);
+
+ // Deliberately NOT gated on uRightsRemaining > 0 --
+ // see this block's header comment. Fires at full
+ // weight (both options gone) even long after the
+ // rights were lost, for as long as uDevelopmentScaler
+ // itself stays nonzero.
+ EVAL_TERM(uColor,
+ KING,
+ c,
+ iKingScore,
+ (SCORE)(
+ (FALSE == pos->fCastled[uColor]) *
+ (KING_MISSING_ONE_CASTLE_OPTION *
+ (SCORE)(2 - uRightsRemaining) *
+ (SCORE)uDevelopmentScaler) / 8),
+ "missing castle option(s)");
}
return iKingScore;
}
@@ -5756,12 +5492,6 @@ Return value:
// Update attack bitvs for squares around the king.
BITBOARD bbRing = g_KingAttacksBB[c];
pos->bbKingAttacks[uColor] |= bbRing;
-#ifdef KING_DEFECT_DUMP
- {
- KING_DEFECT_DUMP_INFO *pDump = &ctx->sKingDefectDump[uColor];
- memset(pDump, 0, sizeof(KING_DEFECT_DUMP_INFO));
- }
-#endif
// Safety-vs-activity split -- see this function's header comment
// (2026-10-06 restructuring). Don't bother with king safety when
@@ -7883,6 +7613,29 @@ Return value:
// Evaluate individual pieces.
//
+ // Precomputed once per color instead of rebuilt by every bishop/
+ // rook/queen of that color (see MOBILITY_BITBOARD_REWRITE.md's
+ // "squeeze it" follow-up). bbEnemyRQK/bbEnemyQK depend only on
+ // static piece locations -- no ordering dependency, safe to build
+ // right here before any piece loop runs. bbUnsafeForRook/
+ // bbUnsafeForQueen are filled in below, at the loop boundary where
+ // their inputs are guaranteed complete (both colors' knight+bishop
+ // passes before rook; both colors' pawn/minor/rook passes before
+ // queen) -- NOT incrementally mutated during a loop, which would
+ // let one piece's own attack leak into its same-color sibling's
+ // "unsafe" mask.
+ BITBOARD bbEnemyRQK[2];
+ BITBOARD bbEnemyQK[2];
+ BITBOARD bbUnsafeForRook[2];
+ BITBOARD bbUnsafeForQueen[2];
+
+ bbEnemyQK[WHITE] = pos->bbPieces[WHITE][QUEEN] |
+ COOR_TO_BB(pos->cKingSq[WHITE]);
+ bbEnemyQK[BLACK] = pos->bbPieces[BLACK][QUEEN] |
+ COOR_TO_BB(pos->cKingSq[BLACK]);
+ bbEnemyRQK[WHITE] = bbEnemyQK[WHITE] | pos->bbPieces[WHITE][ROOK];
+ bbEnemyRQK[BLACK] = bbEnemyQK[BLACK] | pos->bbPieces[BLACK][ROOK];
+
// Knights.
bb = pos->bbPieces[uColor][KNIGHT];
while (bb)
@@ -7930,7 +7683,7 @@ Return value:
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
+ _EvalBishop(pos, c, pHash, bbEnemyRQK[xColor]));
#ifdef EVAL_DUMP
Trace("After our B at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7949,7 +7702,7 @@ Return value:
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
+ _EvalBishop(pos, c, pHash, bbEnemyRQK[uColor]));
#ifdef EVAL_DUMP
Trace("After enemy B at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7960,6 +7713,14 @@ Return value:
#endif
}
+ // Both colors' knight+bishop passes are complete now -- safe to
+ // build both colors' "unsafe for rook" masks once, here, instead of
+ // every rook rebuilding its own color's mask from scratch.
+ bbUnsafeForRook[WHITE] = pos->bbPawnAttacks[BLACK] |
+ pos->bbMinorAttacks[BLACK];
+ bbUnsafeForRook[BLACK] = pos->bbPawnAttacks[WHITE] |
+ pos->bbMinorAttacks[WHITE];
+
// Rooks.
bb = pos->bbPieces[uColor][ROOK];
while (bb)
@@ -7967,7 +7728,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalRook,
+ _EvalRook(pos, c, pHash, bbEnemyQK[xColor],
+ bbUnsafeForRook[uColor]));
#ifdef EVAL_DUMP
Trace("After our R at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7985,7 +7748,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalRook,
+ _EvalRook(pos, c, pHash, bbEnemyQK[uColor],
+ bbUnsafeForRook[xColor]));
#ifdef EVAL_DUMP
Trace("After enemy R at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7996,6 +7761,27 @@ Return value:
#endif
}
+ // Both colors' pawn/minor/rook passes are complete now -- safe to
+ // build both colors' "unsafe for queen" masks once, here, instead
+ // of every queen rebuilding its own color's mask from scratch (most
+ // positions only have one queen per side anyway, so this mainly
+ // pays off on promoted-extra-queen positions, but it's free either
+ // way).
+ bbUnsafeForQueen[WHITE] = pos->bbPawnAttacks[BLACK] |
+ pos->bbMinorAttacks[BLACK] |
+ pos->bbRookAttacks[BLACK] |
+ (g_KingAttacksBB[pos->cKingSq[BLACK]] &
+ ~(pos->bbPawnAttacks[WHITE] |
+ pos->bbMinorAttacks[WHITE] |
+ pos->bbRookAttacks[WHITE]));
+ bbUnsafeForQueen[BLACK] = pos->bbPawnAttacks[WHITE] |
+ pos->bbMinorAttacks[WHITE] |
+ pos->bbRookAttacks[WHITE] |
+ (g_KingAttacksBB[pos->cKingSq[WHITE]] &
+ ~(pos->bbPawnAttacks[BLACK] |
+ pos->bbMinorAttacks[BLACK] |
+ pos->bbRookAttacks[BLACK]));
+
// Our queen(s).
bb = pos->bbPieces[uColor][QUEEN];
while (bb)
@@ -8003,7 +7789,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen,
+ _EvalQueen(pos, c, pHash, bbEnemyQK[xColor],
+ bbUnsafeForQueen[uColor]));
#ifdef EVAL_DUMP
Trace("After our Q at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -8021,7 +7809,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen,
+ _EvalQueen(pos, c, pHash, bbEnemyQK[uColor],
+ bbUnsafeForQueen[xColor]));
#ifdef EVAL_DUMP
Trace("After enemy Q at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),