diff options
| author | Scott Gasch <[email protected]> | 2026-08-29 15:03:54 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-08-29 15:03:54 -0700 |
| commit | 0b12137376929d96da81cd088d3d288cb1eec32d (patch) | |
| tree | 28cbd63a95629719bd6145c9b1c6024b34854f4a /src/movesup.c | |
| parent | a56b15320444fcfe3aabdd3768c81c733f3d776b (diff) | |
Dynamic move ordering overhaul: continuation-history, evidence-gated
countermove promotion, retired hung-piece-escape and NumLeftoverMovesToSelect.
Full session was built on a "measure the pick, not the game" methodology:
aggregate solve counts on curated suites are too noisy to tune move-ordering
knobs against, so most decisions here came from per-move fail-high/alpha-raise
rates at much larger sample sizes (leftover FH% instrumentation, a zero-
selection-budget diagnostic that isolates a single best-of-remaining pick,
and evidence-bucket calibration), not solve-count deltas alone. See
CLAUDE.md's "Dynamic move ordering experiments" section for the reusable
methodology and generate.c's _ScoreAllMoves comment for the resulting
ordering hierarchy.
Changes:
- Added g_ContinuationHistory: same growth/decay math as the existing
g_HistoryCounters butterfly table, additionally keyed by the previous
move, so its magnitude is self-calibrated rather than a hand-picked
constant. Flat, sufficient response across a 256x scale sweep.
- Countermove-table matches now get a real GOOD_MOVE-tier promotion
(previously the table was write-only, tracked for stats but never read
for ordering), but only when the match's own accumulated
history+continuation evidence clears COUNTERMOVE_EVIDENCE_THRESHOLD
(10,000) -- a raw match with no track record was shown to perform
identically to an ordinary leftover (~0.6-0.85% FH), so promoting on
match alone would have repeated hung-piece-escape's mistake below.
- Retired hung-piece-escape's unconditional GOOD_MOVE-tier promotion.
Evidence-calibration showed the overwhelming majority of triggers (a
zero-evidence population 250-1000x larger than countermove's) performed
at the plain-leftover baseline -- the promotion was mostly free tier-
escape treatment for moves that hadn't earned it. Replaced with
FLEE_BONUS, a flat same-tier nudge inside SelectBestWithHistory (never
escapes GOOD_MOVE/leftover classification, unlike a generation-time
promotion) at the magnitude found to plateau a same-tier-nudge sweep.
- Retired NumLeftoverMovesToSelect (the depth-indexed budget on how many
leftover moves got a full selection scan before falling back to
unsorted order). search.c's main move loop now always fully selects --
the leftover pool was shown to contain real, findable signal a bailout
budget was discarding for a node-count savings that didn't hold up net-
net once measured by solve counts and fail-high rates rather than raw
node counts (noisy on small suites independent of this change).
- Collapsed leftover-move instrumentation from sorted/raw pairs down to a
single set now that "raw" (unsorted fallback) is structurally
impossible; kept the countermove evidence-bucket calibration counters
(ongoing check that COUNTERMOVE_EVIDENCE_THRESHOLD stays well-
calibrated); removed the contested-node A/B harness and hung-piece
evidence calibration now that the decisions they were built to inform
are made.
Net effect on the three curated suites (sd 10): solve counts wash (tied,
+1, -1 across ringers/confident/hard), leftover fail-high rate improved
consistently on all three (the intended, directly-measured target of this
work). Not yet validated beyond sd 10 -- an sn-based run or
eval_tune/match_play.py head-to-head gate is the natural next check before
leaning on this as a proven strength gain rather than a directionally-
sound, sd-10-clean change.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014XePz6Sk4qQsTaP2jVJWJu
Diffstat (limited to 'src/movesup.c')
| -rwxr-xr-x | src/movesup.c | 44 |
1 files changed, 44 insertions, 0 deletions
diff --git a/src/movesup.c b/src/movesup.c index dec090c..33deae2 100755 --- a/src/movesup.c +++ b/src/movesup.c @@ -1017,11 +1017,36 @@ Return value: SCORE iVal; MOVE mv; MOVE_STACK_MOVE_VALUE_FLAGS mvfTemp; + MOVE mvLast; + ULONG uPrevContKey = 0; + FLAG fHaveContinuation = FALSE; + COOR cEnprise; ASSERT(ctx->sMoveStack.uBegin[ctx->uPly] <= uEnd); ASSERT(u >= ctx->sMoveStack.uBegin[ctx->uPly]); ASSERT(u < uEnd); + // Continuation history: "did a move like this tend to fail high + // right after a move like that?", keyed by (previous move, this + // move) at the same [piece][to] coarseness g_HistoryCounters + // already uses. Same accumulation/decay math as g_HistoryCounters + // (see _IncrementContinuationCounter/_DecrementContinuationCounter, + // dynamic.c) so its magnitude is empirically self-calibrated rather + // than a hand-picked constant -- a pair seen once contributes + // almost nothing, a pair that's fail-highed repeatedly at real + // depth naturally grows to compete with PSQT+history. + if ((ctx->uPly > 0) && (0 != (mvLast = (ctx->sPlyInfo[ctx->uPly - 1]).mv).uMove)) + { + uPrevContKey = MOVE_TO_CONT_KEY(mvLast); + fHaveContinuation = TRUE; + } + + // Flee-to-safety: a small, same-tier selection-time nudge (not a + // GOOD_MOVE promotion -- that class was retired, see generate.c's + // RETIRED comment) for a quiet move whose origin square is + // currently en prise. + cEnprise = FindEnprisePiece(ctx, ctx->sPosition.uToMove); + // // Linear search from u..ctx->sMoveStack.uEnd[ctx->uPly] for the // move with the best value. @@ -1031,6 +1056,16 @@ Return value: if (!IS_CAPTURE_OR_PROMOTION(mv)) { iBestVal += g_HistoryCounters[mv.pMoved][mv.cTo]; + if (TRUE == fHaveContinuation) + { + iBestVal += CONTINUATION_SCALE * + g_ContinuationHistory[(uPrevContKey * CONT_KEY_RANGE) + + MOVE_TO_CONT_KEY(mv)]; + } + if (mv.cFrom == cEnprise) + { + iBestVal += FLEE_BONUS; + } } uLoc = u; @@ -1041,6 +1076,15 @@ Return value: if (!IS_CAPTURE_OR_PROMOTION(mv)) { iVal += g_HistoryCounters[mv.pMoved][mv.cTo]; + if (TRUE == fHaveContinuation) + { + iVal += g_ContinuationHistory[(uPrevContKey * CONT_KEY_RANGE) + + MOVE_TO_CONT_KEY(mv)]; + } + if (mv.cFrom == cEnprise) + { + iVal += FLEE_BONUS; + } } if (iVal > iBestVal) { |
