summaryrefslogtreecommitdiff
path: root/src/chess.h
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-05 00:52:13 -0700
committerScott Gasch <[email protected]>2026-09-05 00:52:13 -0700
commit57502d6e205bc802fc9b89fd1534add5efc4b9ea (patch)
treecd400f2538d093a41ceaee932fc21df5dc5f54e1 /src/chess.h
parent2ce3570dc6aa67f82dd6f02fc4e518be3e33aabf (diff)
Retire pawns' bvAttacks writes in favor of pos->bbPawnAttacks[2]
First piece type converted per board_representation/EVAL.md section 2: pawns no longer write into rgSquare[c|8].bvAttacks at all. Every consumer of "does a pawn attack this square" now reads pos->bbPawnAttacks[2] directly -- a plain bitboard, computed fresh each Eval() call from pos->bbPawns[] via the same shift-and-mask technique generate.c's _GenerateAllPawnMovesBB already uses (zero per-pawn mailbox iteration, vs. up to 16 delta+IS_ON_BOARD checks before). Knight/bishop/rook/queen/king are unchanged -- still populate/read their own bvAttacks bits (uMinor/uRook/uQueen/uKing) the old way until their own conversions land. Consumer changes, all in eval.c: - UNSAFE_FOR_MINOR retired as a macro (it only ever tested the pawn bit) -- its 3 call sites (knight, bishop x2) now test bbPawnAttacks directly. - UNSAFE_FOR_ROOK/_QUEEN masks narrowed to drop the now-dead pawn bit; call sites OR in an explicit bbPawnAttacks test alongside the narrowed bvAttacks read. - Two direct .small.uPawn reads (bishop's transient-pawn mobility credit, bishop's defended-pawn bonus) switched to bbPawnAttacks tests. - _EvalKing's bvAttack/bvDefend (the real king-danger computation) OR the bitboard bit back in at both read points, careful to preserve the original ordering where bvDefend must reflect the king's own just-set defend bit. - _WhoControlsSquareFast (used by passer-race/trapped-piece/danger code) ORs the bitboard bit back into its g_SwapTable index at the same bit position PAWN_BIT always occupied. - Two bugs caught by manually auditing every remaining |8 site after the fact (not by any test failing): _EvalPawns' own pawn-duo and backward-pawn detection read bvAttacks.uWholeThing at a point in Eval()'s sequence where only pawns could have written it -- once pawns stopped writing there, both checks went permanently dead silently. Fixed to read bbPawnAttacks directly. No self-test caught this; it's exactly the gap EVAL.md section 5's planned exact-score harness is meant to close. EVAL_TIME instrumentation: split _EvalPawns' cycle counter into pawn-hash hit/miss buckets (chess.h/eval.c/root.c), answering whether the hash is still worth it now that attack-bit population is nearly free. Measured on one sd12 benchmark position: hits average 90.4 cycles, misses average 1741.3 cycles (~19x), 97.15% hit rate -- the hash stays a clear win; the miss cost was never mostly attack-bit population (that's a separate ~1% bucket now, down from ~3.6%), it's the isolated/doubled/duo/backward-pawn scoring loops, which still do real per-pawn work on a miss. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
Diffstat (limited to 'src/chess.h')
-rwxr-xr-xsrc/chess.h51
1 files changed, 48 insertions, 3 deletions
diff --git a/src/chess.h b/src/chess.h
index 41c8734..7ff9994 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -595,9 +595,17 @@ typedef union _ATTACK_BITV
}
ATTACK_BITV;
-#define UNSAFE_FOR_MINOR(x) ((ULONG)((x).uWholeThing) & 0x00000080UL)
-#define UNSAFE_FOR_ROOK(x) ((ULONG)((x).uWholeThing) & 0x000000C0UL)
-#define UNSAFE_FOR_QUEEN(x) ((ULONG)((x).uWholeThing) & 0x000000E0UL)
+// 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
@@ -688,6 +696,26 @@ 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.
+ BITBOARD bbPawnAttacks[2];
+
ULONG uWhiteSqBishopCount[2]; // num bishops on white squares
SCORE iMaterialBalance[2]; // material balance
@@ -926,6 +954,23 @@ typedef struct _COUNTERS
// its own accumulator directly.
//
UINT64 u64CyclesEvalPawns;
+ //
+ // Split of the counter above by pawn-hash outcome (board_
+ // representation/EVAL.md section 0c/0d's "is the hash still
+ // worth it now that attack-bit population is cheap" question)
+ // -- Hit = _EvalPawns returned early via the hash (fDeferred
+ // TRUE); Miss = full recompute, including
+ // _PopulatePawnAttackBits and the isolated/doubled/duo/
+ // backward-pawn scoring loops (fDeferred FALSE). Hit+Miss
+ // calls sum to u64FullEvals-ish call count, not to
+ // u64CyclesEvalPawns exactly (that's still measured as one
+ // contiguous window; these are the same window, just bucketed
+ // by outcome after the fact).
+ //
+ UINT64 u64CyclesEvalPawnsHit;
+ UINT64 u64CyclesEvalPawnsMiss;
+ UINT64 u64CountEvalPawnsHit;
+ UINT64 u64CountEvalPawnsMiss;
UINT64 u64CyclesEvalKnight;
UINT64 u64CyclesEvalBishop;
UINT64 u64CyclesEvalRook;