summaryrefslogtreecommitdiff
path: root/src/searchsup.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-13 23:22:10 -0700
committerScott Gasch <[email protected]>2026-10-13 23:22:10 -0700
commitdad4620a92f51a1d0825fc99fa98fd2b16c0f280 (patch)
tree865a0f210b6d3ad49779617ba73a3e04c02aad4e /src/searchsup.c
parent7400001fc2d43a507b54281f9860c60177860fe5 (diff)
Add alpha-side static null-move skip gate and Search() perf hoists
Reuses the joint FH% table on the iEval <= iAlpha side to skip the null-move probe attempt when it's predicted unlikely to succeed (EV-neutral: the real move loop still runs regardless). Also hoists several node-constant computations (ImproveBucket, MP split-eligibility checks) out of the per-move loop in Search(), and adds GNUmakefile overrides for sweeping NULLMOVE_SKIP_FH_CEILING_THOUSANDTHS and SKIP_SELECT_AFTER_N_MOVES from the command line. Self-play vs. head is trending positive overnight (1020 games, llr=+1.36, score=0.527) but not yet conclusive -- committing now to checkpoint the change while the match continues. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/searchsup.c')
-rw-r--r--src/searchsup.c85
1 files changed, 63 insertions, 22 deletions
diff --git a/src/searchsup.c b/src/searchsup.c
index 66851a5..53e7a85 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -503,6 +503,7 @@ GapBetaBucket(SCORE iGapBeta)
return MINU(u, GAP_BETA_BUCKETS - 1);
}
+
// See chess.h's MOVE_NUM_BUCKETS comment. uMoveNum is 1-based (the
// caller passes uLegalMoves, right after MakeMove succeeded) -- index
// 0 is never produced.
@@ -1040,10 +1041,24 @@ Return value:
return(iLocalExtend);
}
-#define LMP_BASE_MOVENUM (8) // never prune before this many
- // legal moves already tried
-#define LMP_PER_PLY_MOVENUM (1) // + this many moves of extra
- // slack per remaining ply
+// 2026-10-13: LMP_BASE_MOVENUM/LMP_PER_PLY_MOVENUM/LMP_MAX_REMAINING_DEPTH
+// are NOT wired into ShouldLatePrune below -- there is no move-number
+// floor and no depth ceiling in this function today, despite this
+// comment block and the docstring below once describing four checks.
+// uLegalMoves is still a parameter (kept for signature/call-site
+// stability and because MoveNumBucket already folds move number into
+// uPredictedFH upstream, in search.c, before this function ever sees
+// it) but it is unread here. LMP_MAX_REMAINING_DEPTH never existed as
+// code, only as a comment; LMP_BASE_MOVENUM/LMP_PER_PLY_MOVENUM are
+// defined and then never referenced -- dead macros, not a disabled
+// feature flag. Left defined rather than deleted only because a
+// future re-introduction of an explicit floor/ceiling (e.g. if the
+// table's move-number axis turns out too coarse near the root once
+// EFP is folded into this function -- see the EFP/LMP merge
+// discussion) would want exactly these names back. If that doesn't
+// happen, delete them.
+#define LMP_BASE_MOVENUM (8) // currently unused, see above
+#define LMP_PER_PLY_MOVENUM (1) // currently unused, see above
#define LMP_DYNAMIC_MIN_SAMPLES (1) // well-evidenced, not a guess
#define LMP_DYNAMIC_MAX_PCT_THOUSANDTHS (1)
@@ -1068,33 +1083,59 @@ Routine description:
rather than recomputing them -- search.c's move loop already looks
them up once per move for LMP/LMR/EFP to share.
- Four remaining checks, any one of which refuses to prune:
- 1. Depth ceiling (LMP_MAX_REMAINING_DEPTH) -- see the KNOWN ISSUE
- comment above this function: backwards relative to our own
- joint-table evidence, kept as a placeholder pending a per-cell
- sample-count re-bake, not a validated bound.
- 2. Move-number floor (LMP_BASE_MOVENUM + per-ply slack) -- don't
- gamble this early in the move list regardless of what either
- FH signal says.
- 3. Dynamic signal: any real, nonzero observed fail-high history
- on this exact move identity.
- 4. Static signal: the joint table's own prediction for this
- move's situation, outside the strictest (safest) tier, or no
- data at all (sentinel) -- unlike GetLMRReduction's more
- permissive treatment of a sentinel for mere reduction, overt
- pruning requires real, favorable data.
+ 2026-10-13: corrected from an earlier version of this comment that
+ claimed four checks including a move-number floor and a depth
+ ceiling -- neither exists in the body below (see the dead-macro
+ note above). As actually implemented, there are only two checks,
+ either of which refuses to prune:
+ 1. Dynamic signal: real, well-evidenced fail-high history on
+ this exact move identity that isn't essentially zero -- the
+ ultra-safe tail, not "probably fine."
+ 2. Static signal: the joint table's own prediction
+ (uPredictedFH) for this move's (depth, hang, gap, late,
+ improve) situation, checked against a per-remaining-depth
+ threshold (uLMPStaticMaxPctThousandths[]) that tightens as
+ remaining depth grows -- unlike GetLMRReduction's more
+ permissive treatment of missing data for mere reduction,
+ overt pruning requires a favorable prediction.
+ There is accordingly no explicit guard today against pruning the
+ 8th move at the root the instant the table happens to look
+ favorable, nor any floor stopping this from firing arbitrarily
+ deep in the tree -- both are purely emergent from
+ uLMPStaticMaxPctThousandths[]'s own shape (very permissive near
+ remaining depth 0, essentially 0 by remaining depth 10) rather
+ than from any dedicated gate. If that turns out to be too loose in
+ practice (e.g. once EFP's margin-based alpha-side pruning is
+ folded into this same table-driven path and starts operating at
+ the 1-2-ply horizon EFP currently owns), re-introduce an explicit
+ floor/ceiling using the macros above rather than reshaping the
+ static-threshold array to fake one.
+
+ The uPredictedFH == JOINT_TABLE_SENTINEL check below is currently
+ unreachable in practice: g_uJointFHThousandths (data.c) is fully
+ backoff-smoothed (shrinkage to a coarser marginal rather than a
+ missing-data sentinel) as of the splice-dual bake that added the
+ improve axis -- "No 0xffff sentinels remain" per that table's own
+ header comment. Kept as a defensive check (cheap, and correct even
+ if the table is ever rebaked with real gaps again), not because
+ it's expected to fire today.
Parameters:
ctx : current searcher thread context (uPly only,
for the sanity ASSERT).
uRemainingDepth : remaining depth below this move.
- uLegalMoves : legal moves tried at this node so far.
+ uLegalMoves : legal moves tried at this node so far --
+ currently unused by this function (see the
+ dead-macro note above); kept in the
+ signature for call-site stability.
uFHAttempts : this move identity's dynamic sample count.
uFHPctThousandths : this move identity's dynamic fail-high rate.
uPredictedFH : the joint table's static prediction for this
- move's (depth, hang, gap, late) situation, or
- JOINT_TABLE_SENTINEL if no data.
+ move's (depth, hang, gap, late, improve)
+ situation, or JOINT_TABLE_SENTINEL if no data
+ (see note above on why this is effectively
+ dead today).
Return value: