summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-08-27 16:24:15 -0700
committerScott Gasch <[email protected]>2026-08-27 16:24:15 -0700
commiteaed74ec737e5ac38a6c47d3f3d857d13a3153b3 (patch)
tree89f1bcaca3970c2c914c1e54520b19727600a38b /src
parent2c44820a5cd952f3596dc34095a653cf461daef7 (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')
-rwxr-xr-xsrc/search.c21
-rw-r--r--src/searchsup.c17
2 files changed, 30 insertions, 8 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;
}
diff --git a/src/searchsup.c b/src/searchsup.c
index c7ac842..8d15e57 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -252,6 +252,21 @@ ComputeMoveScore(IN SEARCHER_THREAD_CONTEXT *ctx,
{
ASSERT(iMoveScore > 0);
iMoveScore &= STRIP_OFF_FLAGS;
+
+ // _ScoreAllMoves (generate.c) bakes a flat +120 move-ordering
+ // bias plus small MVV-LVA tie-break nudges
+ // (PIECE_VALUE_OVER_100 terms) on top of the real SEE/
+ // material-diff value for winning/even captures and
+ // promotions -- harmless for sorting (every capture gets
+ // the same treatment), but this function's callers use the
+ // result as an eval-axis quantity (compared against
+ // material-scale margins in futility/extension code), so
+ // back the ordering-only bias back out to recover the pure
+ // SEE/material-diff value, same axis as the raw
+ // PIECE_VALUE() fallback below.
+ iMoveScore -= (PIECE_VALUE_OVER_100(mv.pCaptured) + 120 +
+ PIECE_VALUE_OVER_100(mv.pPromoted) -
+ PIECE_VALUE_OVER_100(mv.pMoved));
}
else
{
@@ -439,7 +454,7 @@ Return value:
// Singular response to check...
if ((mv.pCaptured) &&
- (iRoughEval + 225 < iAlpha) &&
+ (iRoughEval + 400 < iAlpha) &&
(iMoveScore + 75 > iAlpha))
{
*piExtend += ONE_PLY;