diff options
| author | Scott Gasch <[email protected]> | 2026-08-27 16:24:15 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-08-27 16:24:15 -0700 |
| commit | eaed74ec737e5ac38a6c47d3f3d857d13a3153b3 (patch) | |
| tree | 89f1bcaca3970c2c914c1e54520b19727600a38b /src/search.c | |
| parent | 2c44820a5cd952f3596dc34095a653cf461daef7 (diff) | |
Fix QSearch stand-pat gating bug and ComputeMoveScore's SEE contamination; tune singular-reply-to-check margin.
QSearch stand-pat: the "deny stand pat when material is genuinely in
trouble" check was gated on fCouldStandPat (has this side had a chance
to stand pat earlier in this qsearch line). That's wrong -- whether an
ancestor node had a moment of safety says nothing about whether *this*
node's material danger is real; a hanging piece doesn't stop hanging
because the position was quiet three plies ago. fCouldStandPat's
legitimate uses (search.c:950, 1190/1193) are about deciding whether a
*whole line* looks forcing enough to justify extra qsearch depth/
breadth, a different question from per-node stand-pat correctness.
Removed the gate; the material-in-trouble check now always denies
stand-pat, regardless of history.
ComputeMoveScore: for winning/even captures and promotions, the value
extracted from the move-ordering sort key (generate.c's _ScoreAllMoves)
included a flat +120 ordering bias plus small MVV-LVA tie-break nudges
(PIECE_VALUE_OVER_100 terms) baked in on top of the real SEE/
material-diff value. Harmless for its original sorting purpose (every
capture gets the same treatment), but this function's callers
(futility pruning, the singular-reply-to-check extension) use the
result as an eval-axis quantity compared against material-scale
margins -- the contamination doesn't belong there. Subtracted the
ordering-only bias back out to recover pure SEE/material-diff, same
axis as the raw PIECE_VALUE() fallback used when no move-stack index is
available. Quiet-move and losing-capture handling were already correct
(both collapse to a clean, uncontaminated value).
Also bumped the singular-reply-to-check margin (225 -> 400): confirmed
via direct A/B on the quick suites that this is a real, independent
improvement on top of the SEE fix, not just compensating for it --
reverting to 225 measurably regressed both ecm_confident_quick.ep_
(89->88/90) and ecm_hard_quick.ep_ (10->6/90) versus keeping 400.
Verified against pristine baseline (no LMR in this binary) on the
three-suite protocol (ecm_ringers.ep_, ecm_confident_quick.ep_,
ecm_hard_quick.ep_, sn=5M, book disabled): 11/11 ringers (matches
baseline exactly), 89/90 confident (vs baseline's 90/90), 10/90 hard
(vs baseline's 4/90) -- a real net improvement over baseline with zero
LMR involved, considerably stronger than any state reached earlier in
this session's LMR-only experimentation.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YGSMkwjqiCk4XhbfN7ugD2
Diffstat (limited to 'src/search.c')
| -rwxr-xr-x | src/search.c | 21 |
1 files changed, 14 insertions, 7 deletions
diff --git a/src/search.c b/src/search.c index 6c8a945..e0339cf 100755 --- a/src/search.c +++ b/src/search.c @@ -943,8 +943,9 @@ QSearchFromCheckNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx, uMoveCount = MOVE_COUNT(ctx, ctx->uPly); if (uMoveCount > 0) { - // Consider extending the number of qsearch plies for our opponent - // if this looks good. + // Consider extending the number of qsearch check-generating + // plies for our opponent if this looks good -- we have not + // yet been able to stand pat and they might mate us. if ((pf->uQsearchDepth < pf->uQsearchCheckDepth) && (pf->uQsearchDepth < g_uIterateDepth / 4) && (pf->fCouldStandPat[pos->uToMove] == FALSE) && @@ -1144,11 +1145,17 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, // If that Eval (above) was full (i.e. not lazy) it may have set // en prise and trapped piece indicators. Likewise, other nodes // at this depth may have set en prise piece hints. If these are - // set and valid, it means this is not a "quiet" position. If the - // side on the move has not been able to stand pat yet, don't let - // them now -- force them to play a move and recurse. - if (0 != ValueOfMaterialInTroubleDespiteMove(ctx, pos->uToMove) && - FALSE == ctx->sSearchFlags.fCouldStandPat[pos->uToMove]) + // set and valid, it means this is not a "quiet" position -- don't + // let this side stand pat, force them to play a move and recurse. + // This is deliberately independent of fCouldStandPat: whether an + // ancestor node in this qsearch line had a moment of safety says + // nothing about whether *this* node's material danger is real -- + // a hanging piece doesn't stop hanging because the position was + // quiet three plies ago. fCouldStandPat's job is different (see + // its other uses: deciding whether a *whole line* looks forcing + // enough to justify extra qsearch depth/breadth), not gating + // per-node stand-pat correctness. + if (0 != ValueOfMaterialInTroubleDespiteMove(ctx, pos->uToMove)) { iBestScore = iAlpha; } |
