summaryrefslogtreecommitdiff
path: root/src/search.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-08-26 11:50:25 -0700
committerScott Gasch <[email protected]>2026-08-26 11:50:25 -0700
commit7857096f39e16619a42ee85b4aa593abd846b74a (patch)
treece697561b7e6c0978f53323256c0ab9038662e3e /src/search.c
parent01f5b71fd484801f7b06df3bfb0156f5bb69fef7 (diff)
Replace ctx->uPositional with a data-calibrated Eval() return value.
uPositional was a per-thread EWMA of abs(material - true score) used to size lazy-eval and futility margins. It was history-derived (reflecting whatever recent, unrelated positions looked like) rather than derived from the position actually being margined, and its update/consumption was tangled with EVAL_HASH (now disabled). Eval() now takes an optional SCORE *piPositional out-param and fills it in on every return path: exact (abs(material-delta)) on a full eval, or an estimate from a new EstimatePositionalScore() on a lazy exit. EstimatePositionalScore()'s two terms (king-safety-defect-bucketed, and a flat residual for mobility/passers/everything else) are calibrated from ~1.6M measured full-eval samples (p90 of the actual swing), not guessed -- an initial guessed version measurably regressed ECM solve rate (630 vs a 650 baseline at sn=4M); the recalibrated version is back at parity (649/879). search.c's qsearch futility now reads the value Eval() just computed instead of the stale/shared ctx field. Also removes QSearchInDangerNoStandPat and SideCanStandPat, dead since the danger-hash check that fed them was already commented out (e08387a) -- they depended on the same enprise/ trapped-piece data this conversation is about to move off of g_PositionHash entirely. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/search.c')
-rwxr-xr-xsrc/search.c175
1 files changed, 4 insertions, 171 deletions
diff --git a/src/search.c b/src/search.c
index 0f814ea..4ea09b0 100755
--- a/src/search.c
+++ b/src/search.c
@@ -999,164 +999,6 @@ QSearchFromCheckNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx,
}
-
-/**
-
-Routine description:
-
- The QSearch (Quiescence Search) is a selective search called when
- there is no remaining depth in Search. Its job is to search only
- moves that stabilize the position -- once it is quiescence (quiet)
- we will run a static evaluation on it and return the score.
-
- This branch of the QSearch is called when the side to move is in
- danger somehow -- either he has two pieces en prise on the board
- or some piece that seems trapped. We do not allow him an
- opportunity to stand pat in this position.
-
-Parameters:
-
- IN SEARCHER_THREAD_CONTEXT *ctx,
- IN SCORE iAlpha,
- IN SCORE iBeta
- IN SCORE iEval
-
-Return value:
-
- SCORE
-
-**/
-SCORE
-QSearchInDangerNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx,
- IN SCORE iAlpha,
- IN SCORE iBeta)
-{
- POSITION *pos = &ctx->sPosition;
- CUMULATIVE_SEARCH_FLAGS *pf = &ctx->sSearchFlags;
- ULONG x;
- FLAG fIncludeChecks;
- SCORE iBestScore = iAlpha;
- SCORE iScore;
- SCORE iFutility;
- SCORE iEval = GetRoughEvalScore(ctx, iAlpha, iBeta, TRUE);
- MOVE mv;
- ULONG uLegalMoves = 0;
- static ULONG _WhatToGen[] =
- {
- GENERATE_CAPTURES_PROMS,
- GENERATE_CAPTURES_PROMS_CHECKS
- };
-
-
- // Set futility:
- //
- // iEval + move_value + margin < alpha
- // move_value < alpha - margin - iEval
- iFutility = 0;
- if (iAlpha < +NMATE)
- {
- iFutility = (iAlpha -
- (FUTILITY_BASE_MARGIN + ctx->uPositional) -
- iEval);
- iFutility = MAX0(iFutility);
- }
-
- // We suspect that the guy on move is in sad shape if we're
- // here... he has more than one piece en prise or he seems to
- // have a piece trapped. Allow him to play checks in order to try
- // to save the situation.
- ASSERT(!InCheck(pos, pos->uToMove));
- fIncludeChecks = ((pf->uQsearchDepth < pf->uQsearchCheckDepth) &&
- (pf->fCouldStandPat[FLIP(pos->uToMove)] == FALSE));
- GenerateMoves(ctx, NULLMOVE, _WhatToGen[fIncludeChecks]);
-
- for (x = ctx->sMoveStack.uBegin[ctx->uPly];
- x < ctx->sMoveStack.uEnd[ctx->uPly];
- x++)
- {
- SelectBestNoHistory(ctx, x);
- mv = ctx->sMoveStack.mvf[x].mv;
-#ifdef DEBUG
- ASSERT(0 == (ctx->sMoveStack.mvf[x].bvFlags & MVF_MOVE_SEARCHED));
- ctx->sMoveStack.mvf[x].bvFlags |= MVF_MOVE_SEARCHED;
-#endif
-
- // Prune except when pruning all moves could cause us to return
- // -INFINITY (mated) erroneously.
- if (iAlpha > -INFINITY)
- {
- if (ctx->sMoveStack.mvf[x].iValue <= 0)
- {
- ASSERT(SanityCheckMoves(ctx, x, VERIFY_BEFORE | VERIFY_AFTER));
- goto end;
- }
- if (FALSE == _ShouldWeConsiderThisMove(ctx,
- x,
- iFutility,
- TRUE))
- {
- continue;
- }
- }
-
- if (FALSE == fIncludeChecks)
- {
- mv.bvFlags |= WouldGiveCheck(ctx, mv);
- }
-
- if (MakeMove(ctx, mv))
- {
- uLegalMoves++;
- pf->uQsearchNodes++;
- pf->uQsearchDepth++;
- iScore = -QSearch(ctx,
- -iBeta,
- -iAlpha);
- pf->uQsearchDepth--;
- UnmakeMove(ctx, mv);
- if (WE_SHOULD_STOP_SEARCHING) goto end;
-
- if (iScore > iBestScore)
- {
- iBestScore = iScore;
- ctx->sPlyInfo[ctx->uPly].mvBest = mv;
- if (iScore > iAlpha)
- {
- if (iScore >= iBeta)
- {
- KEEP_TRACK_OF_FIRST_MOVE_FHs(uLegalMoves == 1);
- ASSERT(SanityCheckMoves(ctx, x, VERIFY_BEFORE));
- goto end;
- }
- else
- {
- UpdatePV(ctx, mv);
- StoreExactScore(mv, pos, iScore, 0, FALSE, ctx->uPly);
- iAlpha = iScore;
- }
- }
- }
- }
- }
- ASSERT(SanityCheckMoves(ctx, x, VERIFY_BEFORE));
-
- end:
- // If iAlpha is -INFINITY and side on move has no captures,
- // promotes or checks then we must make up a "stand pat" score
- // here. We don't want to let him stand pat with iEval because
- // the board looks dangerous. But we likewise don't want to say
- // "mated" because he's not.
- ASSERT((uLegalMoves > 0) || (iBestScore == iAlpha));
- if (iBestScore == -INFINITY)
- {
- ASSERT(iAlpha == -INFINITY);
- iBestScore = iEval - VALUE_QUEEN;
- }
- ASSERT(IS_VALID_SCORE(iBestScore) || WE_SHOULD_STOP_SEARCHING);
- return(iBestScore);
-}
-
-
/**
Routine description:
@@ -1193,6 +1035,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
SCORE iScore;
SCORE iEval;
SCORE iFutility;
+ SCORE iPositional;
ULONG x;
ULONG uLegalMoves;
FLAG fIncludeChecks;
@@ -1269,16 +1112,6 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
}
ASSERT(!InCheck(pos, pos->uToMove));
- // Even if the side is not in check, do not let him stand pat if
- // his position looks dangerous (i.e. more than one piece en prise
- // or a piece trapped). Fail low if there's nothing that looks
- // good on this line.
- //if (SideCanStandPat(pos, pos->uToMove) == FALSE)
- //{
- // iBestScore = QSearchInDangerNoStandPat(ctx, iAlpha, iBeta);
- // goto end;
- //}
-
// If we get here then side on move is not in check and this
// position looks ok enough to allow him the option to stand pat
// -or- we missed when we probed the dangerhash. Also remember
@@ -1288,7 +1121,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
// discovers a mate, there's no force since a stand pat
// opportunity exists right here.
uLegalMoves = 0;
- iEval = iBestScore = Eval(ctx, iAlpha, iBeta);
+ iEval = iBestScore = Eval(ctx, iAlpha, iBeta, &iPositional);
if (iBestScore > iAlpha)
{
iAlpha = iBestScore;
@@ -1312,7 +1145,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
iFutility = 0;
if (iAlpha < +NMATE)
{
- iFutility = iAlpha - (FUTILITY_BASE_MARGIN + ctx->uPositional) - iEval;
+ iFutility = iAlpha - (FUTILITY_BASE_MARGIN + iPositional) - iEval;
iFutility = MAX0(iFutility);
}
@@ -1400,7 +1233,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
{
iFutility = (iAlpha -
(FUTILITY_BASE_MARGIN +
- ctx->uPositional) -
+ iPositional) -
iEval);
iFutility = MAX0(iFutility);
}