summaryrefslogtreecommitdiff
path: root/src/chess.h
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-05 21:52:40 -0700
committerScott Gasch <[email protected]>2026-09-05 21:52:40 -0700
commit9e995e7c39a83ae9b5ba86f3346e0281744bf773 (patch)
tree27b4587287b0259fba2811084aba1f60c9e1e6bb /src/chess.h
parent53694883d63674a97a3960514dd4e0a4e1b67f01 (diff)
King-safety recalibration, lazy-eval material floor, eval hot-path trimming
Recalibrate iKingSwingP90 against the bitboard-rewritten CountKingSafetyDefects (~1.28B samples via new CALIBRATE_POSITIONAL/ CALIBRATE_BASE_MARGIN/CALIBRATE_MARGIN_SAFETY diagnostic build flags, board_representation/EVAL.md section 9). Add LAZY_EVAL_MIN_MATERIAL: measured the regular lazy exit's real swing exceeding its own assumed margin 20.6% of the time in near-bare-king endgames (vs <=0.36% elsewhere) -- skip lazy eval entirely below that material floor. Double the stale search.c/searchsup.c CountKingSafetyDefects extension thresholds as a stopgap pending their own recalibration. Eval hot-path trimming (measured via EVAL_TIME, ~1759 -> ~1386 avg cycles/eval on a representative middlegame position): - Pull _GetFileStormDefects out of EstimatePositionalScore's hot path (cost more than the "cheap cached lookup" it was assumed to be, running on ~90% of all Eval() calls). - Add pos->bbOccupiedSide[2], incrementally maintained alongside bbOccupied, so _BuildFriendlySideBB is a field read instead of a 6-term OR. - Switch CoorFromBitBoardRank8ToRank1/Rank1ToRank8 to the existing static-inline FastFirstBit/FastLastBit (same bsf/bsr instruction, no call/ret overhead). - Defer EvalPasserRaces' uRacerDist/fDontCountMeOut past its no-passer early return. - Remove the mailbox-era "max mobility in a row" term from _EvalBishop/_EvalRook (no bitboard-mobility equivalent need for it). - Simplify _EvalBishopPairs and rook file-openness/passer bonuses to flat DNA-tunable constants instead of distance/pawn-count-scaled tables, rook file-openness now a branchless bitboard-indexed lookup. - Remove pos->cPiece (write-only, no reader anywhere). - Collapse WHITE/BLACK mirror-branches (castle-rights block, rook-trapped-in-corner) to color-indexed constants. - Close the PAWN_BIT..KING_BIT gap (bits 7-3 -> bits 4-0), removing the bvPattern >>= 3 before its KING_COUNTER_BY_ATTACK_PATTERN lookup. This also fixes a real bug introduced earlier this session when _WhoControlsSquareFast was converted to read these constants directly: g_SwapTable is only [32][32], but the old bit values (up to 0xF8) indexed far out of bounds on any attacked square -- data.c's InitializeSwapTable was always built assuming the bits 0-4 range this change now actually produces. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
Diffstat (limited to 'src/chess.h')
-rwxr-xr-xsrc/chess.h118
1 files changed, 109 insertions, 9 deletions
diff --git a/src/chess.h b/src/chess.h
index acab153..8d16837 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -560,13 +560,17 @@ typedef union _MOVE
// pattern from those same bbXAttacks reads, then index
// KING_COUNTER_BY_ATTACK_PATTERN/g_SwapTable with it. Kept exactly as
// before; only their old *storage* mechanism (ATTACK_BITV) is gone.
-#define PAWN_BIT 0x00000080UL
-#define MINOR_BIT 0x00000040UL
-#define ROOK_BIT 0x00000020UL
-#define QUEEN_BIT 0x00000010UL
+// Bits 1-4 (2026-09-06: were 4-7, a leftover from a retired count field
+// that used to live in bits 0-2 -- closing that gap removes the
+// bvPattern >>= 3 that used to be needed before the
+// KING_COUNTER_BY_ATTACK_PATTERN lookup, eval.c).
+#define PAWN_BIT 0x00000010UL
+#define MINOR_BIT 0x00000008UL
+#define ROOK_BIT 0x00000004UL
+#define QUEEN_BIT 0x00000002UL
// King never x-rays (it can't move through a blocker), so there's no
// KING_XRAY_BIT to go with this.
-#define KING_BIT 0x00000008UL
+#define KING_BIT 0x00000001UL
#define INVALID_PIECE_INDEX (17)
#define IS_VALID_PIECE_INDEX(x) ((x) < INVALID_PIECE_INDEX)
@@ -650,6 +654,14 @@ typedef struct _POSITION
// not as something callers should call directly anymore.
BITBOARD bbOccupied;
+ // Per-side occupancy, same incremental-maintenance treatment as
+ // bbOccupied above -- _BuildFriendlySideBB (generate.c) used to
+ // rebuild this from scratch (a 6-term OR of bbPieces+bbPawns+king)
+ // on every call; read far more often per node than a position
+ // changes per move, so incremental beats on-the-fly OR-ing here
+ // for the same reason it already does for bbOccupied.
+ BITBOARD bbOccupiedSide[2];
+
// Eval()-scoped attack-bitboard accumulators, one pair per piece
// family, populated by Eval()'s piece-by-piece walk (pawns, then
// knights/bishops, then rooks, then queens, then king) and read by
@@ -718,11 +730,8 @@ typedef struct _POSITION
SCORE iScore[2];
SCORE iReducedMaterialDownScaler[2];
SCORE iTempScore;
- ULONG uMinMobility[2];
- COOR cPiece;
ULONG uMinorsAtHome[2];
BITBOARD bb;
- ULONG uPiecesPointingAtKing[2];
}
POSITION;
@@ -896,6 +905,19 @@ typedef struct _COUNTERS
}
pawnhash;
+ // EXPERIMENTAL, prototype-only -- see rgKingSafetyHash and
+ // _GetFileStormDefects (eval.c). Cycle counters let this be
+ // measured with a plain PERF_COUNTERS build, no separate
+ // EVAL_TIME rebuild needed.
+ struct
+ {
+ UINT64 u64Probes;
+ UINT64 u64Hits;
+ UINT64 u64CyclesHit;
+ UINT64 u64CyclesMiss;
+ }
+ kingsafetyhash;
+
struct
{
UINT64 u64TotalNodeCount;
@@ -975,6 +997,21 @@ typedef struct _COUNTERS
UINT64 u64CyclesEvalPreLazy;
UINT64 u64CyclesEvalPostLazyMisc;
UINT64 u64CyclesEvalAttackTablePop;
+
+ //
+ // 2026-09-06 (board_representation/EVAL.md section 9): finer
+ // breakdown of what's inside u64CyclesEvalPreLazy, to find out
+ // exactly what got more expensive there this session.
+ // u64CyclesEvalLazyDecision times the whole material-gate +
+ // margin-check + EstimatePositionalScore block; the other two
+ // further split EstimatePositionalScore's own two calibrated
+ // sub-calls (each already summed over both colors). Everything
+ // else in "pre-lazy" (material/passers/bad-trades/bishop-pairs)
+ // is u64CyclesEvalPreLazy minus u64CyclesEvalLazyDecision.
+ //
+ UINT64 u64CyclesEvalLazyDecision;
+ UINT64 u64CyclesEvalCountKingSafetyDefects;
+ UINT64 u64CyclesEvalFileStormDefects;
}
tree;
@@ -1098,7 +1135,6 @@ typedef struct _PLY_INFO
MOVE PV[MAX_PLY_PER_SEARCH];
SCORE iKingScore[2];
- ULONG uMinMobility[2];
UINT64 u64NonPawnSig;
UINT64 u64PawnSig;
UINT64 u64Sig;
@@ -1123,6 +1159,18 @@ typedef struct _PAWN_HASH_ENTRY
}
PAWN_HASH_ENTRY;
+// EXPERIMENTAL, prototype-only (2026-09-05) -- see
+// SEARCHER_THREAD_CONTEXT's rgKingSafetyHash comment. Same table size
+// as the pawn hash for a fair, direct hit-rate comparison against the
+// already-measured numbers.
+#define KING_SAFETY_HASH_TABLE_SIZE PAWN_HASH_TABLE_SIZE
+typedef struct _KING_SAFETY_HASH_ENTRY
+{
+ UINT64 u64Key;
+ ULONG uFileStormDefects[2];
+}
+KING_SAFETY_HASH_ENTRY;
+
#define NUM_SPLIT_PTRS_IN_CONTEXT (8)
//
@@ -1159,6 +1207,31 @@ typedef struct _SEARCHER_THREAD_CONTEXT
SCORE iRootScore;
ULONG uRootDepth;
PAWN_HASH_ENTRY rgPawnHash[PAWN_HASH_TABLE_SIZE];
+ // EXPERIMENTAL, prototype-only (2026-09-05): real cache for
+ // _EvalKing's open-file/storming-pawn defect count only (the
+ // DO_KING_SAFETY_THRESHOLD-gated "safety branch" terms -- measured
+ // ~96% hit rate in general/middlegame positions). Deliberately
+ // does NOT cover the u<8-gated "endgame branch" terms (king-
+ // supporting-own-passer, in/out-of-action) -- those measured a
+ // much worse ~75-85% hit rate in bare K+P endgames, and are cheap
+ // enough in that low-piece-count regime that caching them isn't
+ // expected to pay off; they stay computed directly in _EvalKing,
+ // uncached, per board_representation/EVAL.md section 9's
+ // king-safety-hash discussion. Keyed on (pos->u64PawnSig, both
+ // kings' squares) -- both sides' defect counts are computed and
+ // stored together on a miss, so whichever king _EvalKing evaluates
+ // second within the same Eval() call is a guaranteed hit.
+ KING_SAFETY_HASH_ENTRY rgKingSafetyHash[KING_SAFETY_HASH_TABLE_SIZE];
+#ifdef CALIBRATE_POSITIONAL
+ // Set/cleared once per Eval() call, around the cheap base-margin
+ // ("super lazy") gate that decides whether EstimatePositionalScore
+ // would even run in a normal build -- _RecordPositionalCalibration
+ // only logs a sample when this is set, so the calibration histogram
+ // matches the population EstimatePositionalScore actually sees in
+ // real (uninstrumented) search, not every node Eval() is ever
+ // called on.
+ FLAG fCalibrateCandidate;
+#endif
CHAR szLastPV[SMALL_STRING_LEN_CHAR];
}
SEARCHER_THREAD_CONTEXT;
@@ -3058,6 +3131,18 @@ PawnHashLookup(SEARCHER_THREAD_CONTEXT *ctx);
#define LAZY_EVAL_BASE_MARGIN (75) // cheap material-only lazy exit margin;
// widened by EstimatePositionalScore
// if this isn't enough on its own
+// board_representation/EVAL.md section 9 (2026-09-06): below this
+// combined pos->uArmyScaler[WHITE]+pos->uArmyScaler[BLACK] total, both
+// stages of lazy eval are skipped entirely (always full eval). Measured
+// directly via CALIBRATE_MARGIN_SAFETY: the regular (widened-margin)
+// lazy exit's real swing exceeded its own assumed margin 20.6% of the
+// time in the 0-7 combined-scaler bucket (n=1229) -- a near-bare-king
+// endgame's positional swings (opposition, zugzwang, breakthrough
+// timing) are too discontinuous for the margin's "small and bounded"
+// assumption to hold. Set at 16 (start of the material bucket that
+// measured a clean 0.05% rate) rather than exactly at the 0-7 boundary,
+// since the 8-15 bucket's own 0% reading was too thin (n=50) to trust.
+#define LAZY_EVAL_MIN_MATERIAL 16
extern const int g_iAhead[2];
extern const int g_iBehind[2];
@@ -3089,6 +3174,21 @@ ULONG
CountKingSafetyDefects(POSITION *pos,
ULONG uSide);
+#ifdef CALIBRATE_POSITIONAL
+void
+DumpPositionalCalibration(void);
+#endif
+
+#ifdef CALIBRATE_BASE_MARGIN
+void
+DumpBaseMarginCalibration(void);
+#endif
+
+#ifdef CALIBRATE_MARGIN_SAFETY
+void
+DumpMarginSafetyCalibration(void);
+#endif
+
//
// testeval.c
//