diff options
| author | Scott Gasch <[email protected]> | 2026-09-08 18:47:22 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-08 18:47:22 -0700 |
| commit | 379a03bbd993247c8de9c1f1b163fcfab2fa1d69 (patch) | |
| tree | f14f59d43ab613ca33ae9ff874d256784ada710c /src/chess.h | |
| parent | 85b71cf793a85074d8d4483b21a2a06c84553d1d (diff) | |
Land super-lazy exit, material-based lazy floor, qsearch futility rework
Brings in the last remaining piece from stash@{0}: the super-lazy exit
point (material-only pre-check before the regular lazy gate), a
material-bucket floor under the regular lazy exit's margin
(iSwingFloorByArmy), and search.c's qsearch futility rework
(FUTILITY_BASE_MARGIN_BY_SOURCE, indexed by which Eval() exit tier
produced the score). Required Eval()'s signature change from a single
SCORE* to SCORE(*)[2] (positional estimate per side instead of one
munged magnitude) -- search.c's futility margin folds in
rgiPositional[pos->uToMove], which the earlier bad-trades investigation
found to be a meaningfully predictive signal.
search.c and chess.h brought in wholesale from the stash (both were
either completely untouched by prior commits or contained no
divergence worth preserving). eval.c required hand-merging on top of
this session's already-applied bad-trades fix, B-over-N removal, and
xColor/reorder cleanups -- ported the super-lazy exit block, the
regular-lazy material floor, the per-color piPositional writes (all
three exit sites: super-lazy, regular-lazy x2, full-eval), the
super-lazy calibration harness (RecordSuperLazyMarginSafetySwing,
dual-regime DumpMarginSafetyCalibration), and moved uArmyScaler/
uNumTrapped initialization to match the new ordering the super-lazy
exit depends on.
Verified: clean release + DEBUG build (only the previously-flagged
_EvalTrappedPieces warning), DEBUG smoke test pass, and a 40-game
st1 match against clean 434fa04 (score 0.487, llr -0.04) -- landing
this margin machinery as-is from the stash, before any retuning, does
not regress strength on its own. This confirms the original
regression (0.15-0.225 score seen early in this investigation) was
fully explained by the bad-trades unsigned-underflow bug, not by these
margins being unsound.
Values are the original, as-derived-from-calibration ones (see
inline comments: SUPER_LAZY_MARGIN_BY_ARMY from 100 positions/sd8
with ~15-20% headroom, iSwingFloorByArmy from 1500 positions/sd8
with ~25% headroom, FUTILITY_BASE_MARGIN_BY_SOURCE from a separate
1500-position/sd6 surprise-rate calibration). Not yet retuned --
suspected to carry more headroom than necessary, which costs real
search speed (speed=depth). Next step: re-run the margin-safety
calibration fresh against the current build and trim the super-lazy
and regular-lazy floor headroom down from the built-in database,
leaving the qsearch futility margins alone (different calibration
method, already risk-tolerant by construction).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
Diffstat (limited to 'src/chess.h')
| -rwxr-xr-x | src/chess.h | 161 |
1 files changed, 111 insertions, 50 deletions
diff --git a/src/chess.h b/src/chess.h index 702defb..78c0f94 100755 --- a/src/chess.h +++ b/src/chess.h @@ -941,19 +941,9 @@ typedef struct _COUNTERS UINT64 u64AvoidNullSuccess; UINT64 u64AvoidNullFailures; #endif - UINT64 u64EvalHashHits; UINT64 u64LazyEvals; - UINT64 u64FullEvals; - // Placeholder counters for root.c's per-tier eval-exit - // reporting -- eval.c's super-lazy exit itself hasn't been - // re-applied yet (see stash), so these always read 0 for now; - // that's accurate, not a stub bug, since no super-lazy exit - // exists in this build to increment them. UINT64 u64SuperLazyEvals; - UINT64 u64CyclesSuperLazyExit; - UINT64 u64CyclesLazyExit; - UINT64 u64CyclesFullEvalExit; - UINT64 u64CyclesEvalSuperLazy; + UINT64 u64FullEvals; UINT64 u64CyclesInEval; // @@ -1021,6 +1011,30 @@ typedef struct _COUNTERS UINT64 u64CyclesEvalLazyDecision; UINT64 u64CyclesEvalCountKingSafetyDefects; UINT64 u64CyclesEvalFileStormDefects; + + // + // 2026-09-07: the super-lazy exit (eval.c, before the regular + // LAZY_EVAL block) is a much cheaper material-only check than + // the regular lazy decision, so it gets its own cycle counter + // rather than folding into u64CyclesEvalLazyDecision -- a hit + // here never reaches the regular lazy-decision code at all. + // + UINT64 u64CyclesEvalSuperLazy; + + // Per-exit-path *total call cost*, i.e. the full entry-to-exit + // elapsed time for a call that left via that path -- not to be + // confused with the sub-timers above, which measure a single + // segment's cost regardless of how the call eventually exited. + // These three are mutually exclusive by construction (each + // Eval() call adds to exactly one) and sum to u64CyclesInEval, + // so u64CyclesInEval / (u64SuperLazyEvals + u64LazyEvals + + // u64FullEvals) is a real overall average, and each bucket + // divided by its own matching count (u64SuperLazyEvals, + // u64LazyEvals, u64FullEvals) gives a clean per-path average -- + // see root.c's eval-exit-breakdown report. + UINT64 u64CyclesSuperLazyExit; + UINT64 u64CyclesLazyExit; + UINT64 u64CyclesFullEvalExit; } tree; @@ -1241,6 +1255,24 @@ typedef struct _SEARCHER_THREAD_CONTEXT // called on. FLAG fCalibrateCandidate; #endif + // Which Eval() exit tier supplied the piPositional value most + // recently returned to this thread -- set inside Eval() itself + // (all three exit points), read right after the Eval() call in + // QSearch. Was CALIBRATE_QSEARCH_FUTILITY-only; promoted to + // unconditional 2026-09-08 since QSearch's real futility margin + // now indexes FUTILITY_BASE_MARGIN_BY_SOURCE with it, not just + // the calibration harness. See EVAL_POSITIONAL_SOURCE_* below. + ULONG uLastPositionalSource; +#ifdef CALIBRATE_QSEARCH_FUTILITY + // TRUE for the duration of a diagnostic "what if we hadn't pruned + // this move" re-search -- search.c's qsearch futility gate checks + // this and forces iFutility wide open so the diagnostic subtree + // itself isn't contaminated by the same pruning being measured. + // Never set recursively: a rejection can't occur while this is + // already TRUE (iFutility being wide open means nothing gets + // rejected), so there's no risk of runaway nested diagnosis. + FLAG fDiagUnprunedSubtree; +#endif CHAR szLastPV[SMALL_STRING_LEN_CHAR]; } SEARCHER_THREAD_CONTEXT; @@ -2820,14 +2852,27 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx); // search.c // #define QPLIES_OF_NON_CAPTURE_CHECKS (2) -#define FUTILITY_BASE_MARGIN (50) -// Compatibility aliases for root.c/main.c's per-tier reporting, which -// expects these three names -- search.c hasn't been split into -// per-tier margins yet (still one flat FUTILITY_BASE_MARGIN), so all -// three alias the same value until that split is re-applied. -#define FUTILITY_BASE_MARGIN_FULL FUTILITY_BASE_MARGIN -#define FUTILITY_BASE_MARGIN_LAZY FUTILITY_BASE_MARGIN -#define FUTILITY_BASE_MARGIN_SUPERLAZY FUTILITY_BASE_MARGIN + +// Which Eval() exit tier supplied a given piPositional value -- +// SEARCHER_THREAD_CONTEXT.uLastPositionalSource above. Was diagnostic- +// only (CALIBRATE_QSEARCH_FUTILITY); promoted to unconditional +// 2026-09-08 once FUTILITY_BASE_MARGIN_BY_SOURCE (search.c) made it +// load-bearing for real search behavior, not just measurement. +#define EVAL_POSITIONAL_SOURCE_FULL (0) +#define EVAL_POSITIONAL_SOURCE_LAZY (1) +#define EVAL_POSITIONAL_SOURCE_SUPERLAZY (2) +#define EVAL_POSITIONAL_SOURCE_COUNT (3) + +// Per-tier qsearch futility base margins -- see search.c's +// FUTILITY_BASE_MARGIN_BY_SOURCE for the full derivation. Named here +// (rather than only as array entries in search.c) so eval.c's +// piPositional floor -- which only ever applies on the full-eval +// exit -- can reference the matching tier's constant directly instead +// of a stale flat value. +#define FUTILITY_BASE_MARGIN_FULL (450) +#define FUTILITY_BASE_MARGIN_LAZY (225) +#define FUTILITY_BASE_MARGIN_SUPERLAZY (275) + // Measured: disabling this entirely (see lmr_testing/RESULTS.md) is a // clear net loss across ringers/confident_quick/hard_quick, so IID itself // is load-bearing. The "is the top move crappy" gate in search.c's DO_IID @@ -3144,7 +3189,7 @@ PawnHashLookup(SEARCHER_THREAD_CONTEXT *ctx); // #define LAZY_EVAL #define LAZE_EVAL_BASE_SCORE 10 -#define LAZY_EVAL_BASE_MARGIN (75) // cheap material-only lazy exit margin; +#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 @@ -3162,13 +3207,6 @@ PawnHashLookup(SEARCHER_THREAD_CONTEXT *ctx); extern const int g_iAhead[2]; extern const int g_iBehind[2]; -// No-op placeholder: eval.c's ROOK_FULL_HALF_OPEN_BONUS static cache -// (the thing InitEval() is meant to (re)build, called at startup and -// after every DNA reload) hasn't been re-applied yet -- see stash. -// Nothing to initialize until that cache exists. -void -InitEval(void); - ULONG DNABufferSizeBytes(); @@ -3184,14 +3222,15 @@ ImportEvalDNA(char *p); FLAG ReadEvalDNA(char *szFilename); - +void +InitEval(); SCORE -Eval(SEARCHER_THREAD_CONTEXT *, SCORE, SCORE, SCORE *); +Eval(SEARCHER_THREAD_CONTEXT *, SCORE, SCORE, SCORE (*)[2]); FLAG _EvalPasserRacesAgainstLoneKings(POSITION *, - PAWN_HASH_ENTRY *); + PAWN_HASH_ENTRY *); ULONG CountKingSafetyDefects(POSITION *pos, @@ -3212,6 +3251,22 @@ void DumpMarginSafetyCalibration(void); #endif +#ifdef CALIBRATE_QSEARCH_FUTILITY +// Which futility gate in _ShouldWeConsiderThisMove rejected a move -- +// see search.c's _QFutRecordSample. The recapture gate (used to be +// QFUT_GATE_RECAPTURE) was retired 2026-09-08 along with the +100 +// recapture bonus itself -- see the "recapture-shaped" comment in +// _ShouldWeConsiderThisMove for why (fRecaptureShaped never checked +// mv.cTo == mvLast.cTo, so it wasn't testing real recaptures). +#define QFUT_GATE_GENERIC_CAPTURE (0) +#define QFUT_GATE_CHECK_ROOK (1) +#define QFUT_GATE_CHECK_BISHOP (2) +#define QFUT_GATE_COUNT (3) + +void +DumpQSearchFutilityCalibration(void); +#endif + // // testeval.c // @@ -3352,22 +3407,13 @@ ParallelCompareUlong(ULONG uComparand, void *pComparators); ULONG CDECL ParallelCompareVector(void *pComparand, void *pComparators); -void CDECL -GetAttacks(SEE_LIST *pList, - POSITION *pos, - COOR cSquare, - ULONG uSide); - -void CDECL -SlowGetAttacks(SEE_LIST *pList, - POSITION *pos, - COOR cSquare, - ULONG uSide); - -// board_representation/MIGRATION.md section 3: bbPieces/bbPawns-backed -// GetAttacks primitive, verified correct (20,000-position sweep) and -// faster than asm GetAttacks (0.53-0.89x cycles/call across -// opening/middlegame/endgame -- see section 3's benchmark writeup). +// board_representation/MIGRATION.md sections 3/6/7: bbPieces/bbPawns- +// backed GetAttacks primitive. Verified correct (20,000-position +// sweep) and faster than the old mailbox implementation (0.53-0.89x +// cycles/call across opening/middlegame/endgame). Now the only +// GetAttacks implementation -- the old asm/CROUTINES mailbox versions +// (SlowGetAttacks, asm GetAttacks) were retired 2026-09-06 once all of +// section 7's retirement criteria cleared. void CDECL _GetAttacksBB(SEE_LIST *pList, POSITION *pos, @@ -3386,10 +3432,9 @@ _WhoAttacksSquareBB(POSITION *pos, BITBOARD bbOccupied); // _GetAttacksBB is the only implementation as of 2026-09-06 (see -// MIGRATION.md section 6) -- verified correct and faster than the old -// asm/CROUTINES mailbox versions (SlowGetAttacks, asm GetAttacks), -// which have been retired. Every call site written against the name -// "GetAttacks" didn't need touching when the default changed. +// MIGRATION.md section 7) -- this macro exists only so call sites +// written against the name "GetAttacks" didn't need touching when the +// old asm/CROUTINES mailbox versions were retired. #define GetAttacks _GetAttacksBB #ifdef _X86_ @@ -3596,6 +3641,12 @@ COMMAND(BenchCommand); void TestDraw(void); +void +TestRecogn(void); + +void +TestRecognExhaustiveKNKP(void); + // // probe.c // @@ -3683,6 +3734,16 @@ RecognLookup(SEARCHER_THREAD_CONTEXT *ctx, SCORE *piScore, FLAG fProbeEGTB); +// +// Unregistered/experimental recognizers, exposed only so testrecogn.c +// can exhaustively validate a candidate before it's ever re-registered +// in InitializeInteriorNodeRecognizers. Not reachable from the normal +// RecognLookup dispatch path. +// +ULONG +_RecognizeKNKP(SEARCHER_THREAD_CONTEXT *ctx, + SCORE *piScore); + SCORE GetRoughEvalScore(IN SEARCHER_THREAD_CONTEXT *ctx, IN SCORE iAlpha, |
