diff options
| author | Scott Gasch <[email protected]> | 2026-09-25 08:44:25 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-25 08:44:25 -0700 |
| commit | 9b28080f8b3dca4588cb2aaaf748451f45e3c626 (patch) | |
| tree | 54e0aff5d6ee1520c590c7c04a5ae2f8299e7497 /src/chess.h | |
| parent | 66740dc89c7351c120c25788d0079055034f4942 (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-x | src/chess.h | 102 |
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 |
