summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-08-25 23:22:29 -0700
committerScott Gasch <[email protected]>2026-08-25 23:22:29 -0700
commit2245550e3c0344bcba093b24dc00f36b462fc940 (patch)
treeecef582eccd6588b0ca9d90fbaf76fe2fd1c2eef /src
parentba803fe406ee2470aa461578f530d292380ec241 (diff)
Tame check-extension compounding; verified neutral in self-play (0.4955)
Fixes a real pathology: checks along a long unbroken forcing line could extend for free (net zero cost against the qsearch boundary), letting tree size blow up multiple orders of magnitude on positions like a near-all-check forced mate (ECM.089: 4.5B nodes / 31min at depth 12 before this change). - Main-search check extension: gate on SEE soundness (a losing sacrifice check gets a small consolation QUARTER_PLY instead of the full bonus a sound check gets), and flatten the sound-check bonus to a flat THREE_QUARTERS_PLY instead of a near-free ONE_PLY. - Lower the qsearch entry threshold to match (THREE_QUARTERS_PLY instead of ONE_PLY) so a lone check still buys one extra full-width ply as before; root.c trims QUARTER_PLY off the per-iteration depth budget so this doesn't add a blanket 1/4 ply to every search. - Qsearch's own check-widening (QSearchFromCheckNoStandPat) now relies on fCouldStandPat history plus a g_uIterateDepth/4 ceiling instead of an unconditional per-check grant, and QPLIES_OF_NON_CAPTURE_CHECKS moved from 1 to 2 to cover both "enter qsearch already in check" and "opponent's reply is the first real check" cases with one baseline window instead of ad hoc attacker-color tracking. Net effect on ECM.089 (sn 4M canary): ~7.5x fewer nodes and ~4x less time at depth 12 versus the original, unbounded behavior. Costs solve count on the full ECM suite (879 pos, sn 4M): 650 baseline -> 636 here -- expected and accepted, since ECM is unusually check-extension-heavy tactics and not representative of real games. Self-play vs baseline (1000 games, st 1) came back at B_SCORE=0.4955, ELO=-3.1+/-21.5 -- statistically neutral, confirming the fix costs nothing in real play. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h16
-rwxr-xr-xsrc/main.c1
-rw-r--r--src/poshash.c2
-rwxr-xr-xsrc/root.c18
-rwxr-xr-xsrc/search.c129
-rw-r--r--src/searchsup.c87
-rwxr-xr-xsrc/split.c30
7 files changed, 97 insertions, 186 deletions
diff --git a/src/chess.h b/src/chess.h
index abf802e..8861ef1 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -819,10 +819,6 @@ typedef struct _COUNTERS
UINT64 u64LazyEvals;
UINT64 u64FullEvals;
UINT64 u64CyclesInEval;
- UINT64 u64HistoryPrunes;
- UINT64 u64HistoryPruneReSearches;
- UINT64 u64FutilityCandidates;
- UINT64 u64FutilityPrunes;
}
tree;
@@ -932,7 +928,6 @@ typedef struct _PLY_INFO
#endif
SCORE iEval;
INT iExtensionAmount;
- FLAG fIsPVNode; // (iBeta != iAlpha+1) at this ply
FLAG fInCheck;
FLAG fInQsearch;
MOVE mv;
@@ -2285,7 +2280,7 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx);
//
// search.c
//
-#define QPLIES_OF_NON_CAPTURE_CHECKS (1)
+#define QPLIES_OF_NON_CAPTURE_CHECKS (2)
#define FUTILITY_BASE_MARGIN (50) // + ctx->uPositional (min 100)
#define DO_IID
#define IID_R_FACTOR (TWO_PLY + HALF_PLY)
@@ -2320,14 +2315,6 @@ ComputeMoveScore(IN SEARCHER_THREAD_CONTEXT *ctx,
FLAG
ThreadUnderTerminatingSplit(SEARCHER_THREAD_CONTEXT *);
-void
-InitializeLMRTable(void);
-
-INT
-ExtraReduction(IN ULONG uRemainingDepth,
- IN ULONG uMoveNum,
- IN ULONG uFailHighPct);
-
FLAG
WeShouldDoHistoryPruning(IN SCORE iRoughEval,
IN SCORE iAlpha,
@@ -2377,6 +2364,7 @@ ComputeMoveExtension(IN OUT SEARCHER_THREAD_CONTEXT *ctx,
IN ULONG uMoveNum,
IN SCORE iRoughEval,
IN ULONG uDepth,
+ IN SCORE iCheckSee,
IN OUT INT *piExtend);
SCORE
diff --git a/src/main.c b/src/main.c
index cae9877..01c8d26 100755
--- a/src/main.c
+++ b/src/main.c
@@ -465,7 +465,6 @@ Return value:
InitializeDistanceTable();
InitializeOpeningBook();
InitializeDynamicMoveOrdering();
- InitializeLMRTable();
InitializeHashSystem();
InitializePositionHashSystem();
#ifdef MP
diff --git a/src/poshash.c b/src/poshash.c
index e8f09b5..19d10ab 100644
--- a/src/poshash.c
+++ b/src/poshash.c
@@ -196,7 +196,7 @@ SideCanStandPat(POSITION *pos, ULONG uSide)
ULONG uLock = HashPositionToLockNumber(uEntry);
LOCK_POSITION_HASH(uLock);
#endif
- if (pHash->u64Sig == u64Sig)
+ if (pHash->u64Sig == u64Sig)
{
fStand = ((pHash->cTrapped[uSide] == ILLEGAL_COOR) &&
(pHash->uEnpriseCount[uSide] < 2));
diff --git a/src/root.c b/src/root.c
index 7c50a65..8d1adf8 100755
--- a/src/root.c
+++ b/src/root.c
@@ -456,17 +456,6 @@ Return value:
ASSERT(d);
Trace("Null move cutoff rate: %5.3f percent.\n",
((n / d) * 100.0));
- n = (double)(ctx->sCounters.tree.u64HistoryPruneReSearches);
- d = (double)(ctx->sCounters.tree.u64HistoryPrunes) + 1;
- Trace("History/LMR pruning: %"COMPILER_LONGLONG_UNSIGNED_FORMAT
- " reduced, %5.3f percent needed a full-depth re-search.\n",
- ctx->sCounters.tree.u64HistoryPrunes, ((n / d) * 100.0));
- n = (double)(ctx->sCounters.tree.u64FutilityPrunes);
- d = (double)(ctx->sCounters.tree.u64FutilityCandidates) + 1;
- Trace("Futility pruning: %"COMPILER_LONGLONG_UNSIGNED_FORMAT
- " candidates, %5.3f percent actually pruned "
- "(rest saved by fail-high%% / SEE gates).\n",
- ctx->sCounters.tree.u64FutilityCandidates, ((n / d) * 100.0));
if (ctx->sCounters.egtb.uProbes > 0)
{
n = (double)(ctx->sCounters.egtb.uHits);
@@ -1166,10 +1155,15 @@ Return value:
if (iBeta > INFINITY) iBeta = +INFINITY;
if (iAlpha < -INFINITY) iAlpha = -INFINITY;
if (iAlpha >= iBeta) iAlpha = iBeta - 1;
+ // Was + HALF_PLY. Trimmed by QUARTER_PLY to compensate for
+ // search.c's qsearch-entry threshold moving from ONE_PLY down
+ // to THREE_QUARTERS_PLY -- that change alone would otherwise
+ // hand every line an extra ~1/4 ply of full-width search for
+ // free, not just check-heavy ones.
iScore = RootSearch(ctx,
iAlpha,
iBeta,
- uDepth * ONE_PLY + HALF_PLY);
+ uDepth * ONE_PLY + QUARTER_PLY);
if (g_MoveTimer.bvFlags & TIMER_STOPPING) break;
mv = ctx->mvRootMove;
diff --git a/src/search.c b/src/search.c
index ff7e661..00b37f2 100755
--- a/src/search.c
+++ b/src/search.c
@@ -153,7 +153,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
ULONG uStage = TRY_HASH_MOVE;
ULONG u;
ULONG uFutilityMargin = 0;
- SCORE iMoveSee = 0;
+ SCORE iCheckSee;
#ifdef DEBUG
ASSERT(IS_VALID_SCORE(iAlpha));
ASSERT(IS_VALID_SCORE(iBeta));
@@ -167,8 +167,15 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
mvBest.uMove = 0;
// Jump directly to Qsearch if remaining depth is low enough.
- // This is the only place Qsearch is entered.
- if (uDepth < ONE_PLY)
+ // This is the only place Qsearch is entered. Lowered from ONE_PLY to
+ // THREE_QUARTERS_PLY to match the check extension's new flat amount
+ // (searchsup.c's ComputeMoveExtension) -- a lone check (or short run
+ // of them) still buys exactly one extra full-width ply as before, but
+ // a long unbroken chain now pays QUARTER_PLY of real cost per check
+ // instead of extending for free. root.c compensates by trimming the
+ // same QUARTER_PLY off the per-iteration depth budget so this doesn't
+ // just add a blanket 1/4 ply to every search.
+ if (uDepth < THREE_QUARTERS_PLY)
{
pf->fCouldStandPat[BLACK] = pf->fCouldStandPat[WHITE] = FALSE;
pf->uQsearchNodes = pf->uQsearchDepth = 0;
@@ -192,7 +199,6 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
}
DTEnterNode(ctx, uDepth, FALSE, iAlpha, iBeta);
iInitialAlpha = iAlpha;
- pi->fIsPVNode = (iBeta != iAlpha + 1);
ASSERT((IS_CHECKING_MOVE(mvLast) && (TRUE == pi->fInCheck)) ||
(!IS_CHECKING_MOVE(mvLast) && (FALSE == pi->fInCheck)));
@@ -430,19 +436,8 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// prune away moves we will also make sure there is
// no per-move extension.
ASSERT(!uFutilityMargin);
- // Narrowed from "<= TWO_PLY" to "< TWO_PLY": ExtraReduction's
- // own safety cap guarantees a reduced move's child lands
- // with remaining depth >= TWO_PLY, which used to hand every
- // heavily-reduced line straight into futility's activation
- // floor at exactly that value -- double jeopardy on the
- // same move by construction, not by chance. Excluding just
- // that exact floor value (not the whole fractional-depth
- // window below it) un-stacks that specific overlap.
- if (FALSE && // EXPERIMENT: disabled for the unscaled-LMR-table
- // test -- isolate the new reduction table's
- // effect without also stacking futility pruning
- (iRoughEval + VALUE_ROOK <= iAlpha) &&
- (uDepth < TWO_PLY) &&
+ if ((iRoughEval + VALUE_ROOK <= iAlpha) &&
+ (uDepth <= TWO_PLY) &&
(ctx->uPly >= 2) &&
(iOrigExtend == 0) &&
(ctx->sPlyInfo[ctx->uPly - 2].iExtensionAmount <= 0) &&
@@ -530,14 +525,15 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
}
#endif
- // SEE must be computed on the PRE-move position (it internally
- // simulates removing mv.pMoved from mv.cFrom and adding x-rays --
- // see see.c) -- so grab it now, before MakeMove mutates *pos in
- // place, for the futility check below to use. Only bother when
- // futility mode is actually active at this node.
- if (uFutilityMargin)
+ // SEE must be computed on the PRE-move position -- see.c's
+ // exchange walk needs the piece still sitting on cFrom. Only
+ // needed for checking moves, where ComputeMoveExtension uses it
+ // to gate the check extension on soundness (Crafty-style: don't
+ // extend a checking move that's really just a losing sacrifice).
+ iCheckSee = 0;
+ if (IS_CHECKING_MOVE(mv))
{
- iMoveSee = SEE(pos, mv);
+ iCheckSee = SEE(pos, mv);
}
if (TRUE == MakeMove(ctx, mv))
@@ -555,6 +551,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
(x - 1), // Note: x==0 if doing mvHash
iRoughEval,
uDepth,
+ iCheckSee,
&iExtend);
// Cap how many extension plies this line may spend in total
@@ -579,22 +576,11 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
iExtend))
{
ASSERT(iExtend == 0);
- iExtend = -(ONE_PLY + ExtraReduction(uDepth, (x - 1),
- GetMoveFailHighPercentage(mv)));
- pi->iExtensionAmount = iExtend;
- INC(ctx->sCounters.tree.u64HistoryPrunes);
+ iExtend = -ONE_PLY;
+ pi->iExtensionAmount = -ONE_PLY;
}
- // Maybe even "futility prune" this move away. Require two
- // independent corroborating signals on top of the static
- // margin, not just the move's ordering score alone: this
- // specific move must have rarely caused a beta cutoff
- // historically (same threshold WeShouldDoHistoryPruning
- // uses), and it must not show a tactical gain per SEE --
- // ComputeMoveScore alone is PSQT/history/killer-bonus for
- // quiet moves, with no exchange evaluation at all, so it
- // was pruning purely on a positional-ordering number.
-#ifdef PERF_COUNTERS
+ // Maybe even "futility prune" this move away.
if ((x != 0) &&
(uLegalMoves > 1) &&
(uFutilityMargin) &&
@@ -602,25 +588,6 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
(iExtend <= 0) &&
(!IS_ESCAPING_CHECK(mv)))
{
- INC(ctx->sCounters.tree.u64FutilityCandidates);
- }
-#endif
- if ((x != 0) &&
- (uLegalMoves > 1) &&
- (uFutilityMargin) &&
- (ComputeMoveScore(ctx, mv, (x - 1)) < uFutilityMargin) &&
- (iExtend <= 0) &&
- (!IS_ESCAPING_CHECK(mv)) &&
- // Stricter than WeShouldDoHistoryPruning's <= 10: that one
- // gates a reduction with a fail-high re-search safety net
- // (search.c's own counters show ~2% wrong-guess rate is
- // fine there); this gates an outright, unverified skip --
- // no re-search, no recovery if wrong -- so demand much
- // stronger evidence the move is truly hopeless.
- (GetMoveFailHighPercentage(mv) <= 3) &&
- (iMoveSee <= 0))
- {
- INC(ctx->sCounters.tree.u64FutilityPrunes);
// TODO: test this more carefully
ASSERT(!IS_CHECKING_MOVE(mv));
UnmakeMove(ctx, mv);
@@ -652,9 +619,8 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// Research deeper if history pruning failed
if ((iExtend < 0) && (iScore >= iBeta))
{
- uNextDepth -= iExtend; // undo the full reduction, whatever its magnitude
+ uNextDepth += ONE_PLY;
pi->iExtensionAmount = 0;
- INC(ctx->sCounters.tree.u64HistoryPruneReSearches);
iScore = -Search(ctx, -iBeta, -iAlpha, uNextDepth);
}
UnmakeMove(ctx, mv);
@@ -946,20 +912,19 @@ QSearchFromCheckNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx,
uMoveCount = MOVE_COUNT(ctx, ctx->uPly);
if (uMoveCount > 0)
{
- // Consider extending the number of qsearch plies we consider
- // non-capture checks at during this line.
- if ((pf->fCouldStandPat[pos->uToMove] == FALSE) &&
- (pf->uQsearchDepth < g_uIterateDepth / 2) &&
- (CountKingSafetyDefects(pos, pos->uToMove) > 1))
+ // Consider extending the number of qsearch plies for our opponent
+ // if this looks good.
+ if ((pf->uQsearchDepth < pf->uQsearchCheckDepth) &&
+ (pf->uQsearchDepth < g_uIterateDepth / 4) &&
+ (pf->fCouldStandPat[pos->uToMove] == FALSE) &&
+ (CountKingSafetyDefects(pos, pos->uToMove) > 2))
{
- if (uMoveCount == 1) {
+ if ((uMoveCount == 1) ||
+ (NUM_KING_MOVES(ctx, ctx->uPly) == 0) ||
+ (NUM_CHECKING_PIECES(ctx, ctx->uPly) > 1))
+ {
uQsearchCheckExtension = 2;
INC(ctx->sCounters.extension.uQExtend);
- } else if ((uMoveCount == 2) ||
- (NUM_KING_MOVES(ctx, ctx->uPly) == 0) ||
- (NUM_CHECKING_PIECES(ctx, ctx->uPly) > 1)) {
- uQsearchCheckExtension = 1;
- INC(ctx->sCounters.extension.uQExtend);
}
ctx->sPlyInfo[ctx->uPly].iExtensionAmount = uQsearchCheckExtension;
}
@@ -1080,7 +1045,7 @@ QSearchInDangerNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx,
// Set futility:
- //
+ //
// iEval + move_value + margin < alpha
// move_value < alpha - margin - iEval
iFutility = 0;
@@ -1097,7 +1062,8 @@ QSearchInDangerNoStandPat(IN SEARCHER_THREAD_CONTEXT *ctx,
// 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 < g_uIterateDepth / 3);
+ fIncludeChecks = ((pf->uQsearchDepth < pf->uQsearchCheckDepth) &&
+ (pf->fCouldStandPat[FLIP(pos->uToMove)] == FALSE));
GenerateMoves(ctx, NULLMOVE, _WhatToGen[fIncludeChecks]);
for (x = ctx->sMoveStack.uBegin[ctx->uPly];
@@ -1313,7 +1279,10 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
// position looks ok enough to allow him the option to stand pat
// -or- we missed when we probed the dangerhash. Also remember
// that this side has had the option to stand pat when searching
- // below this point.
+ // below this point. This also means the other side is not
+ // allowed to generate checks on this side because even if it
+ // discovers a mate, there's no force since a stand pat
+ // opportunity exists right here.
uLegalMoves = 0;
iEval = iBestScore = Eval(ctx, iAlpha, iBeta);
if (iBestScore > iAlpha)
@@ -1343,13 +1312,13 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
iFutility = MAX0(iFutility);
}
- // We know we are not in check. Generate moves (including checks
- // if we are below the threshold and the other side has never been
- // allowed to stand pat). Recurse.
- fIncludeChecks = ((pf->uQsearchDepth < pf->uQsearchCheckDepth) &&
- (pf->fCouldStandPat[FLIP(pos->uToMove)] == FALSE) &&
- (pos->uNonPawnMaterial[pos->uToMove] >
- (VALUE_KING + VALUE_BISHOP)));
+ // We know we are not in check. If we are early in the qsearch,
+ // and the other side has not yet been able to stand pat, and we
+ // have material, generate checks here too.
+ fIncludeChecks =
+ ((pf->uQsearchDepth < pf->uQsearchCheckDepth) &&
+ (pf->fCouldStandPat[FLIP(pos->uToMove)] == FALSE) &&
+ (pos->uNonPawnMaterial[pos->uToMove] > (VALUE_KING + VALUE_BISHOP)));
GenerateMoves(ctx, NULLMOVE, _WhatToGen[fIncludeChecks]);
for (x = ctx->sMoveStack.uBegin[ctx->uPly];
x < ctx->sMoveStack.uEnd[ctx->uPly];
diff --git a/src/searchsup.c b/src/searchsup.c
index 7af1a18..37eca66 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -18,7 +18,6 @@ Revision History:
**/
-#include <math.h>
#include "chess.h"
extern SCORE g_iRootScore[2];
@@ -225,8 +224,7 @@ Return value:
ASSERT((uMoveNum > 0) || (uLegalMoves == 0));
if ((uRemainingDepth >= TWO_PLY) &&
(iBeta == (iAlpha + 1)) &&
- (FALSE == ctx->sPlyInfo[ctx->uPly - 1].fIsPVNode) &&
- (uLegalMoves > 3) &&
+ (uLegalMoves > 5) &&
(0 == iExtend) &&
// (iRoughEval + ComputeMoveScore(ctx, mv, uMoveNum - 1) + 200 < iAlpha) &&
(!IS_ESCAPING_CHECK(mv)) &&
@@ -246,66 +244,10 @@ Return value:
}
-#define LMR_TABLE_MAX_DEPTH 32
-#define LMR_TABLE_MAX_MOVES 63
-
-static INT g_iLMRTable[LMR_TABLE_MAX_DEPTH + 1][LMR_TABLE_MAX_MOVES + 1];
-
-void
-InitializeLMRTable(void)
-{
- ULONG d, m;
- double r;
-
- g_iLMRTable[0][0] = 0;
- for (d = 0; d <= LMR_TABLE_MAX_DEPTH; d++)
- {
- for (m = 0; m <= LMR_TABLE_MAX_MOVES; m++)
- {
- if ((d < 1) || (m < 1))
- {
- g_iLMRTable[d][m] = 0;
- continue;
- }
- r = 0.7844 + (log((double)d) * log((double)m) / 2.4696);
- if (r < 0.0) r = 0.0;
- g_iLMRTable[d][m] = (INT)((r * ONE_PLY) + 0.5);
- }
- }
-}
-
-
-INT
-ExtraReduction(IN ULONG uRemainingDepth,
- IN ULONG uMoveNum,
- IN ULONG uFailHighPct)
-{
- INT iExtra;
- ULONG d = uRemainingDepth / ONE_PLY;
- ULONG m = uMoveNum;
-
- if (d > LMR_TABLE_MAX_DEPTH) d = LMR_TABLE_MAX_DEPTH;
- if (m > LMR_TABLE_MAX_MOVES) m = LMR_TABLE_MAX_MOVES;
- iExtra = g_iLMRTable[d][m];
-
- if (uFailHighPct == 0)
- {
- iExtra += QUARTER_PLY;
- }
-
- if ((INT)uRemainingDepth - ONE_PLY - iExtra < TWO_PLY)
- {
- iExtra = MAX((INT)uRemainingDepth - ONE_PLY - TWO_PLY, 0);
- }
- ASSERT(iExtra >= 0);
- return(iExtra);
-}
-
-
SCORE
ComputeMoveScore(IN SEARCHER_THREAD_CONTEXT *ctx,
IN MOVE mv,
- IN ULONG uMoveNum)
+ IN ULONG uMoveNum)
{
SCORE iMoveScore = (PIECE_VALUE(mv.pCaptured) +
PIECE_VALUE(mv.pPromoted));
@@ -335,6 +277,7 @@ ComputeMoveExtension(IN SEARCHER_THREAD_CONTEXT *ctx,
IN ULONG uMoveNum,
IN SCORE iRoughEval,
IN ULONG uDepth,
+ IN SCORE iCheckSee,
IN OUT INT *piExtend)
/**
@@ -391,18 +334,32 @@ Return value:
ASSERT(*piExtend >= 0);
#endif
- // Checking move extension.
+ // Checking move extension. Crafty-style soundness gate: a check
+ // that's really just a losing sacrifice (SEE < 0) still gets a small
+ // consolation extension rather than none at all -- SEE can't see
+ // multi-move combinations, so a "losing" sacrifice check is sometimes
+ // actually a winning sham sacrifice a few moves later, and giving it
+ // zero search priority risks missing exactly those. It just doesn't
+ // get to compound the way a materially sound check does.
if (IS_CHECKING_MOVE(mv))
{
ASSERT(InCheck(pos, pos->uToMove));
INC(ctx->sCounters.extension.uCheck);
- *piExtend += ONE_PLY;
- if (pos->uNonPawnMaterial[FLIP(pos->uToMove)] >
- (VALUE_KING + VALUE_BISHOP))
+ if (iCheckSee < 0)
{
+ *piExtend += QUARTER_PLY;
goto enforce_ceiling;
}
- *piExtend -= THREE_QUARTERS_PLY;
+ else
+ {
+ *piExtend += THREE_QUARTERS_PLY;
+ if (pos->uNonPawnMaterial[FLIP(pos->uToMove)] >
+ (VALUE_KING + VALUE_BISHOP))
+ {
+ goto enforce_ceiling;
+ }
+ *piExtend -= HALF_PLY;
+ }
}
// Pawn push extension
diff --git a/src/split.c b/src/split.c
index b30cdb6..703f6a8 100755
--- a/src/split.c
+++ b/src/split.c
@@ -1046,6 +1046,7 @@ Return value:
INT iOrigExtend;
INT iExtend;
MOVE mv;
+ SCORE iCheckSee;
#ifdef DEBUG
POSITION board;
@@ -1080,22 +1081,30 @@ Return value:
ASSERT(iExtend >= -ONE_PLY);
ASSERT(iExtend <= +ONE_PLY);
+ // SEE on the PRE-move position -- see search.c's identical comment.
+ iCheckSee = 0;
+ if (IS_CHECKING_MOVE(mv))
+ {
+ iCheckSee = SEE(&ctx->sPosition, mv);
+ }
+
if (MakeMove(ctx, mv))
{
- ASSERT((IS_CHECKING_MOVE(mv) &&
+ ASSERT((IS_CHECKING_MOVE(mv) &&
InCheck(&ctx->sPosition, ctx->sPosition.uToMove)) ||
- (!IS_CHECKING_MOVE(mv) &&
+ (!IS_CHECKING_MOVE(mv) &&
!InCheck(&ctx->sPosition, ctx->sPosition.uToMove)));
iRoughEval = GetRoughEvalScore(ctx, iAlpha, iBeta, TRUE);
-
+
// Compute extension
- ComputeMoveExtension(ctx,
+ ComputeMoveExtension(ctx,
iAlpha,
iBeta,
- (ctx->sMoveStack.uBegin[ctx->uPly - 1] +
+ (ctx->sMoveStack.uBegin[ctx->uPly - 1] +
uMoveNum),
iRoughEval,
uDepth,
+ iCheckSee,
&iExtend);
//
@@ -1125,12 +1134,8 @@ Return value:
iExtend))
{
ASSERT(iExtend == 0);
- iExtend = -(ONE_PLY + ExtraReduction(uDepth,
- (g_SplitInfo[u].uAlreadyDone +
- uMoveNum + 1),
- GetMoveFailHighPercentage(mv)));
- ctx->sPlyInfo[ctx->uPly].iExtensionAmount = iExtend;
- INC(ctx->sCounters.tree.u64HistoryPrunes);
+ iExtend = -ONE_PLY;
+ ctx->sPlyInfo[ctx->uPly].iExtensionAmount = -ONE_PLY;
}
//
@@ -1151,9 +1156,8 @@ Return value:
//
if ((iExtend < 0) && (iScore >= iBeta))
{
- uDepth -= iExtend; // undo the full reduction, whatever its magnitude
+ uDepth += ONE_PLY;
ctx->sPlyInfo[ctx->uPly].iExtensionAmount = 0;
- INC(ctx->sCounters.tree.u64HistoryPruneReSearches);
iScore = -Search(ctx, -iBeta, -iAlpha, uDepth);
}
UnmakeMove(ctx, mv);