summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h9
-rwxr-xr-xsrc/movesup.c10
-rwxr-xr-xsrc/search.c45
-rw-r--r--src/searchsup.c31
4 files changed, 82 insertions, 13 deletions
diff --git a/src/chess.h b/src/chess.h
index 9b6303d..3a78b2b 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -934,6 +934,10 @@ typedef struct _PLY_INFO
FLAG fInCheck;
FLAG fInQsearch;
FLAG fPvNode; // this node's own window was wide
+ FLAG fMovesRescoredByIID; // sMoveStack[uPly].iValue holds
+ // real search scores from
+ // RescoreMovesViaSearch, not
+ // generate.c's ordering encoding
MOVE mv;
MOVE mvBest;
@@ -2284,6 +2288,11 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx);
//
#define QPLIES_OF_NON_CAPTURE_CHECKS (2)
#define FUTILITY_BASE_MARGIN (50)
+// Measured: disabling this entirely (see lmr_testing/RESULTS.md) is a
+// clear net loss across ringers/confident_quick/hard_quick, so IID itself
+// is load-bearing. The "is the top move crappy" gate in search.c's DO_IID
+// block still misclassifies ordinary killer moves as crappy (see the fix
+// there) -- that's the next thing being tuned, not whether IID exists.
#define DO_IID
#define IID_R_FACTOR (TWO_PLY + HALF_PLY)
diff --git a/src/movesup.c b/src/movesup.c
index 8aa8ffa..dec090c 100755
--- a/src/movesup.c
+++ b/src/movesup.c
@@ -988,7 +988,7 @@ Return value:
}
-void FASTCALL
+void FASTCALL
SelectBestWithHistory(SEARCHER_THREAD_CONTEXT *ctx,
ULONG u)
/**
@@ -1017,11 +1017,11 @@ Return value:
SCORE iVal;
MOVE mv;
MOVE_STACK_MOVE_VALUE_FLAGS mvfTemp;
-
+
ASSERT(ctx->sMoveStack.uBegin[ctx->uPly] <= uEnd);
ASSERT(u >= ctx->sMoveStack.uBegin[ctx->uPly]);
ASSERT(u < uEnd);
-
+
//
// Linear search from u..ctx->sMoveStack.uEnd[ctx->uPly] for the
// move with the best value.
@@ -1033,7 +1033,7 @@ Return value:
iBestVal += g_HistoryCounters[mv.pMoved][mv.cTo];
}
uLoc = u;
-
+
for (v = u + 1; v < uEnd; v++)
{
iVal = ctx->sMoveStack.mvf[v].iValue;
@@ -1058,7 +1058,7 @@ Return value:
}
-void FASTCALL
+void FASTCALL
SelectBestNoHistory(SEARCHER_THREAD_CONTEXT *ctx,
ULONG u)
/**
diff --git a/src/search.c b/src/search.c
index eb931bc..6e596ab 100755
--- a/src/search.c
+++ b/src/search.c
@@ -200,6 +200,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
DTEnterNode(ctx, uDepth, FALSE, iAlpha, iBeta);
iInitialAlpha = iAlpha;
pi->fPvNode = (iBeta != iAlpha + 1);
+ pi->fMovesRescoredByIID = FALSE;
ASSERT((IS_CHECKING_MOVE(mvLast) && (TRUE == pi->fInCheck)) ||
(!IS_CHECKING_MOVE(mvLast) && (FALSE == pi->fInCheck)));
@@ -413,13 +414,19 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// EXPERIMENT: If we got no best move from the
// hash table and the best move we got from the
// generator looks crappy (i.e. is not a winning
- // or even capture/promotion) then rescore the
- // moves we generated at this ply using a
- // shallower search. "Internal Iterative
- // Deepening" or something like it.
+ // or even capture/promotion, AND not a killer --
+ // a killer move already proved itself elsewhere in
+ // the tree, unlike an untested quiet move, so it
+ // doesn't need IID's help) then rescore the moves
+ // we generated at this ply using a shallower
+ // search. "Internal Iterative Deepening" or
+ // something like it.
if ((iAlpha + 1 != iBeta) &&
(mvHash.uMove == 0) &&
(ctx->sMoveStack.mvf[x].iValue < SORT_THESE_FIRST) &&
+ (0 == (ctx->sMoveStack.mvf[x].iValue &
+ (FIRST_KILLER | SECOND_KILLER |
+ THIRD_KILLER | FOURTH_KILLER))) &&
(uDepth >= FOUR_PLY))
{
ASSERT(uDepth >= (IID_R_FACTOR + ONE_PLY));
@@ -457,7 +464,35 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
ASSERT(x >= ctx->sMoveStack.uBegin[ctx->uPly]);
if (uLegalMoves < SEARCH_SORT_LIMIT(ctx->uPly))
{
- SelectBestWithHistory(ctx, x);
+ // On an IID-rescored ply, mvf[].iValue holds a
+ // real, honest eval-axis score from an actual
+ // shallow search (see RescoreMovesViaSearch) --
+ // trust it outright, same principle as
+ // ComputeMoveScore's IID-trust branch. Two
+ // alternatives were measured and rejected (see
+ // RESULTS.md): blending history into the real
+ // score (scaled or capped) invents a new,
+ // leak-prone move-scoring axis on top of an
+ // already-crowded set (generate.c's ordering
+ // encoding, RescoreMovesViaSearch's real scores,
+ // root.c's own scheme) and measures no better
+ // than trusting the score outright; a pure
+ // tiebreak-on-exact-ties compromise still cost
+ // solves relative to full mixing, so it wasn't
+ // buying its complexity either. The branch lives
+ // here (once per selection call), not inside
+ // SelectBestWithHistory (once per candidate
+ // move in a hot per-node loop), to keep the
+ // overwhelmingly common non-rescored path at
+ // zero added cost.
+ if (TRUE == pi->fMovesRescoredByIID)
+ {
+ SelectBestNoHistory(ctx, x);
+ }
+ else
+ {
+ SelectBestWithHistory(ctx, x);
+ }
}
mv = ctx->sMoveStack.mvf[x].mv;
#ifdef DEBUG
diff --git a/src/searchsup.c b/src/searchsup.c
index 585bcc6..efdde67 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -239,7 +239,7 @@ Return value:
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));
@@ -247,8 +247,25 @@ ComputeMoveScore(IN SEARCHER_THREAD_CONTEXT *ctx,
{
ASSERT(uMoveNum < MAX_MOVE_STACK);
ASSERT(IS_SAME_MOVE(mv, ctx->sMoveStack.mvf[uMoveNum].mv));
+ ASSERT(ctx->uPly > 0);
iMoveScore = ctx->sMoveStack.mvf[uMoveNum].iValue;
- if ((iMoveScore >= SORT_THESE_FIRST) && IS_CAPTURE_OR_PROMOTION(mv))
+ // Every current caller (search.c's EFP check, and
+ // ComputeMoveExtension's two ComputeMoveScore() call sites) only
+ // reaches here after MakeMove(ctx, mv) has already succeeded, so
+ // ctx->uPly is always the *child's* ply here -- uMoveNum indexes
+ // the parent's move list, i.e. ctx->uPly - 1, not ctx->uPly. That's
+ // the ply RescoreMovesViaSearch (if it ran) would have rescored,
+ // so that's the flag to check.
+ if (TRUE == ctx->sPlyInfo[ctx->uPly - 1].fMovesRescoredByIID)
+ {
+ // RescoreMovesViaSearch already put a real, searched eval-axis
+ // score here -- better than SEE/MVV-LVA, since it reflects an
+ // entire subtree, not just the immediate exchange. Trust it
+ // exactly as-is; don't run it through the capture-bias
+ // subtraction or collapse it via MIN0, both of which assume
+ // generate.c's ordering-encoded format, which this isn't.
+ }
+ else if ((iMoveScore >= SORT_THESE_FIRST) && IS_CAPTURE_OR_PROMOTION(mv))
{
ASSERT(iMoveScore > 0);
iMoveScore &= STRIP_OFF_FLAGS;
@@ -668,7 +685,15 @@ Return value:
ctx->sMoveStack.mvf[x].iValue = -INFINITY;
x++;
}
- ctx->sMoveStack.mvf[uBest].iValue |= SORT_THESE_FIRST;
+ // uBest already holds the largest real score in the list (iBestScore
+ // tracked the running max as we went) -- SelectBest{With,No}History
+ // just compare raw magnitude, so it naturally sorts first without
+ // needing a flag. OR-ing in SORT_THESE_FIRST here used to corrupt
+ // that real score into looking like generate.c's biased-capture-
+ // ordering format to any later ComputeMoveScore() caller; removed.
+ // fMovesRescoredByIID (below) is the correct, non-destructive way to
+ // signal "trust this ply's iValue as a real eval-axis score."
+ ctx->sPlyInfo[ctx->uPly].fMovesRescoredByIID = TRUE;
ASSERT(IS_VALID_SCORE(iBestScore));
return(iBestScore);
}