summaryrefslogtreecommitdiff
path: root/src/chess.h
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-25 08:44:25 -0700
committerScott Gasch <[email protected]>2026-09-25 08:44:25 -0700
commit9b28080f8b3dca4588cb2aaaf748451f45e3c626 (patch)
tree54e0aff5d6ee1520c590c7c04a5ae2f8299e7497 /src/chess.h
parent66740dc89c7351c120c25788d0079055034f4942 (diff)
Two ideas:
1. Nullmove R=3 + depth/6 along with some ShouldWeDoNullmoveSearch tweaking and calibrating. 2. Probe EGTB everywhere in the search as long as the table/position is resident in memory.
Diffstat (limited to 'src/chess.h')
-rwxr-xr-xsrc/chess.h102
1 files changed, 96 insertions, 6 deletions
diff --git a/src/chess.h b/src/chess.h
index 72289ad..ff218a9 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -941,6 +941,36 @@ MOVE_STACK;
// size the evidence-bucket counter arrays.
#define CM_EVIDENCE_BUCKETS (7)
+// TEST_NULL calibration harness (search.c's null-move precision/recall/
+// EV measurement -- see _NullCalibBucketIndex's comment in search.c for
+// the full design). Revised 2026-09-24: dropped fThreat/fSkipNull/
+// en-prise-or-trapped as bucket dimensions -- all three are already
+// validated, non-tunable binary production gates (fThreat/fSkipNull
+// gate the call to WeShouldTryNullmovePruning in search.c; en-prise/
+// trapped was shown NOT to predict false-positive risk, only recall),
+// so the calibration probe now just requires fThreat==FALSE &&
+// fSkipNull==FALSE as part of structural eligibility instead of
+// spending bucket space re-measuring them. Added a second depth
+// dimension (outer, pre-reduction uDepth, alongside the null probe's
+// own post-reduction uNullDepth) and a second material dimension
+// (mover-only piece count -- TotalNonPawnCount(pos, pos->uToMove) --
+// alongside the existing MAX(white,black) count) -- both deliberately
+// independent of the current SelectNullmoveRFactor R schedule, so a
+// cost model fit from this data stays valid if R changes.
+//
+// Per-ply, 0..15 directly plus one "16+" overflow bucket -- fine enough
+// to fit a cost-vs-depth growth curve (EBF-style) and extrapolate to
+// depths never directly observed at the sd used for a given run. Used
+// for both depth dimensions (same banding, reused).
+#define NC_DEPTH_BUCKETS (17)
+#define NC_DEFICIT_BUCKETS (6) // (iEval-iBeta) cp bands
+// Non-pawn piece count bands: 0-1,2,3-5,6+. Used for both material
+// dimensions (same banding, reused).
+#define NC_MATERIAL_BUCKETS (4)
+#define NC_TOTAL_BUCKETS \
+ (NC_DEPTH_BUCKETS * NC_DEPTH_BUCKETS * NC_DEFICIT_BUCKETS * \
+ NC_MATERIAL_BUCKETS * NC_MATERIAL_BUCKETS)
+
// ----------------------------------------------------------------------
//
// Accumulators
@@ -1018,11 +1048,35 @@ typedef struct _COUNTERS
UINT64 u64NullMoves;
UINT64 u64NullMoveSuccess;
#ifdef TEST_NULL
- UINT64 u64QuickNullSuccess;
- UINT64 u64QuickNullDeferredSuccess;
- UINT64 u64QuickNullFailures;
- UINT64 u64AvoidNullSuccess;
- UINT64 u64AvoidNullFailures;
+ // Precision/recall calibration for the null-move try/avoid
+ // decision. Indexed by _NullCalibBucketIndex() (search.c).
+ // Per cell: how often we attempted a (diagnostic, always-run)
+ // null-move probe, how often the probe itself failed high, how
+ // often the real move loop at that node *actually* failed high
+ // (ground truth, read off Search()'s own iBestScore vs iBeta at
+ // its "end:" label), and how often both agreed. From these four
+ // counts: precision = uBothFH/uNullFH, recall = uBothFH/uRealFH.
+ UINT64 u64NCAttempts[NC_TOTAL_BUCKETS];
+ UINT64 u64NCNullFH[NC_TOTAL_BUCKETS];
+ UINT64 u64NCRealFH[NC_TOTAL_BUCKETS];
+ UINT64 u64NCBothFH[NC_TOTAL_BUCKETS];
+ // Cost side of the try/skip decision: sum (not average -- divide
+ // by u64NCAttempts at report time) of the node cost of the
+ // diagnostic null probe itself, and of the immediately-following
+ // real first move's full-depth search (the "R=0" alternative),
+ // per bucket. Lets a report compute avg-nodes-under-null vs.
+ // avg-nodes-under-first-move and the try-vs-skip break-even
+ // this implies, given the bucket's own nullFH rate.
+ UINT64 u64NCNullProbeNodeSum[NC_TOTAL_BUCKETS];
+ UINT64 u64NCFirstMoveNodeSum[NC_TOTAL_BUCKETS];
+ // True total cost of however the real search actually resolved
+ // this node (one move, several moves, or production's own
+ // separate null-move success) -- node count from right after
+ // the diagnostic probe returns through this node's "end:".
+ // Strictly more honest than u64NCFirstMoveNodeSum for the
+ // try-vs-skip EV calc, which only approximates "cost of the
+ // real alternative" as move 1 alone.
+ UINT64 u64NCRealLoopNodeSum[NC_TOTAL_BUCKETS];
#endif
UINT64 u64LazyEvals;
UINT64 u64SuperLazyEvals;
@@ -1652,6 +1706,15 @@ typedef struct _GAME_OPTIONS
UINT64 u64LeftoverFH;
UINT64 u64CMEvidenceTries[CM_EVIDENCE_BUCKETS];
UINT64 u64CMEvidenceFH[CM_EVIDENCE_BUCKETS];
+#ifdef TEST_NULL
+ UINT64 u64NCAttempts[NC_TOTAL_BUCKETS];
+ UINT64 u64NCNullFH[NC_TOTAL_BUCKETS];
+ UINT64 u64NCRealFH[NC_TOTAL_BUCKETS];
+ UINT64 u64NCBothFH[NC_TOTAL_BUCKETS];
+ UINT64 u64NCNullProbeNodeSum[NC_TOTAL_BUCKETS];
+ UINT64 u64NCFirstMoveNodeSum[NC_TOTAL_BUCKETS];
+ UINT64 u64NCRealLoopNodeSum[NC_TOTAL_BUCKETS];
+#endif
CHAR szLogfile[SMALL_STRING_LEN_CHAR];
CHAR szEGTBPath[SMALL_STRING_LEN_CHAR];
CHAR szBookName[SMALL_STRING_LEN_CHAR];
@@ -4225,6 +4288,9 @@ void
TestRecognExhaustiveKNKP(void);
void
+TestRecognExhaustiveKQKP(void);
+
+void
TestRecognExhaustiveKPK(void);
void
@@ -4254,6 +4320,9 @@ ProbeEGTB(SEARCHER_THREAD_CONTEXT *ctx, SCORE *score);
FLAG
ProbeEGTBRoot(SEARCHER_THREAD_CONTEXT *ctx, MOVE *pmv, SCORE *piScore);
+FLAG
+EgtbProbeLikelyResident(SEARCHER_THREAD_CONTEXT *ctx);
+
void
InitializeEGTB(void);
@@ -4324,13 +4393,30 @@ GenerateRandomLegalSymetricPosition(POSITION *pos);
#define RECOGN_LOWER (3)
#define RECOGN_EGTB (4)
+//
+// EGTB probing mode for RecognLookup's 3rd argument -- trinary rather
+// than a plain FLAG so "probe only if we believe the page is resident"
+// is a distinct option from "always probe" and "never probe". Letting
+// RecognLookup itself decide (rather than making the caller compute
+// EgtbProbeLikelyResident up front, unconditionally, as a function
+// argument) means the residency check -- which can now involve real
+// mincore() syscalls for 6-man tables, see EgtbProbeLikelyResident's
+// own comment -- is only ever paid on the branch that's actually about
+// to decide whether to call ProbeEGTB, i.e. skipped entirely at every
+// node one of the cheap hand-written interior recognizers already
+// resolves. See CLAUDE.md's EGTB residency section.
+//
+#define EGTB_PROBE_NEVER (0)
+#define EGTB_PROBE_ALWAYS (1)
+#define EGTB_PROBE_IF_RESIDENT (2)
+
void
InitializeInteriorNodeRecognizers(void);
ULONG
RecognLookup(SEARCHER_THREAD_CONTEXT *ctx,
SCORE *piScore,
- FLAG fProbeEGTB);
+ ULONG uEgtbProbeMode);
//
// Shared, ctx-free KP+KP* "unstoppable passer" static analysis --
@@ -4368,4 +4454,8 @@ ULONG
_RecognizeKNKP(SEARCHER_THREAD_CONTEXT *ctx,
SCORE *piScore);
+ULONG
+_RecognizeKQKP(SEARCHER_THREAD_CONTEXT *ctx,
+ SCORE *piScore);
+
#endif // CHESS