summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-08-27 13:13:08 -0700
committerScott Gasch <[email protected]>2026-08-27 13:13:08 -0700
commit2c44820a5cd952f3596dc34095a653cf461daef7 (patch)
treeabed39ed47f1c7be4aae3689bec7dbb6b7bd55b9 /src
parent489915f4c98c7aca3a63c0f35b83929e8331a597 (diff)
Remove ctx->uPositional and the EVAL_HASH subsystem; fix GetRoughEvalScore.
Finishes work left half-done in 7857096 ("Replace ctx->uPositional with a data-calibrated Eval() return value"): that commit added Eval()'s new piPositional out-param but never migrated GetRoughEvalScore onto it, so GetRoughEvalScore's mid/deep-tree fallback kept reading the old ctx->uPositional field -- a per-thread EWMA written only on full-eval calls and never touched by the (far more common) lazy-eval path, so it carried a stale value from whatever unrelated position last triggered a full eval, potentially many nodes/plies away. Combined with EVAL_HASH being long since disabled (its probe branch already dead), every GetRoughEvalScore call past ply 4 was effectively "material + garbage." Fixed by having GetRoughEvalScore just call Eval() directly -- its own lazy-exit machinery already is the cheap, calibrated estimate this function exists to provide, so there's no separate estimator to maintain. Removed ctx->uPositional entirely (struct field, its EWMA update in eval.c, both root.c init sites, split.c's cross-split propagation, testeval.c's reset) along with the entire EVAL_HASH subsystem (struct, table, Probe/StoreEvalHash, main.c's now-dead reporting branch, the GNUmakefile flag) -- confirmed unused elsewhere and explicitly being cut for good, not coming back in this form. Also fixed GetRoughEvalScore's prototype being wrongly declared inside #ifdef EVAL_HASH in chess.h even though the function itself is defined and called unconditionally -- this was the source of the recurring "call to undeclared function 'GetRoughEvalScore'" implicit-declaration warning seen throughout this session's builds. Separately, fixed QSearch to match its own documented intent: the en-prise/trapped-piece "don't let this side stand pat" check now only fires if the side hasn't already been allowed to stand pat earlier in this qsearch line (matching the comment above it, which already said this but the code never implemented it). Verified against baseline/typhoon_baseline (pristine, pre-session) on ecm_ringers.ep_ (4), ecm_hard_quick.ep_ (50-sample), and ecm_confident_quick.ep_ (40) at sn=5M, --cpus 1, book disabled: pristine baseline solves 3/50 on the hard sample; this commit solves 6/50, with the stand-pat fix and GetRoughEvalScore fix each contributing +1 independently confirmed. No regressions on the other two suites (4/4 and 40/40 unchanged throughout). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YGSMkwjqiCk4XhbfN7ugD2
Diffstat (limited to 'src')
-rw-r--r--src/GNUmakefile6
-rwxr-xr-xsrc/chess.h55
-rwxr-xr-xsrc/eval.c44
-rw-r--r--src/evalhash.c173
-rwxr-xr-xsrc/main.c7
-rwxr-xr-xsrc/root.c2
-rwxr-xr-xsrc/search.c3
-rwxr-xr-xsrc/split.c4
-rwxr-xr-xsrc/testeval.c1
9 files changed, 21 insertions, 274 deletions
diff --git a/src/GNUmakefile b/src/GNUmakefile
index dbb74cf..ef80e95 100644
--- a/src/GNUmakefile
+++ b/src/GNUmakefile
@@ -4,7 +4,6 @@
# TEST=1: include self-test code in the binary produced
# ASM=1: create assembly code, does not link final binary
# EVAL_DUMP=1: make a version that can dump eval breakdowns
-# EVAL_HASH=1: hash eval scores
# EVAL_TIME=1: make a version that counts cycles spent in eval
# PERF_COUNTERS=1: make version with perf counters enabled
# BOUNDS_CHECKING=1: make version with bounds checking enabled
@@ -91,14 +90,11 @@ ifdef CROUTINES
endif
ifdef EVERYTHING
-PROFILE += -DEVAL_DUMP -DEVAL_HASH -DEVAL_TIME -DPERF_COUNTERS -DMP -DSMP -DTEST_NULL -DDUMP_TREE -fbounds-checking
+PROFILE += -DEVAL_DUMP -DEVAL_TIME -DPERF_COUNTERS -DMP -DSMP -DTEST_NULL -DDUMP_TREE -fbounds-checking
else
ifdef EVAL_DUMP
PROFILE += -DEVAL_DUMP
endif
-ifdef EVAL_HASH
-PROFILE += -DEVAL_HASH
-endif
ifdef EVAL_TIME
PROFILE += -DEVAL_TIME
endif
diff --git a/src/chess.h b/src/chess.h
index 9689189..630f090 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -907,7 +907,6 @@ typedef struct _SPLIT_INFO
INT iPositionExtend; // positional extension
SCORE iAlpha; // original alpha at split
SCORE iBeta; // beta at split
- ULONG uSplitPositional; // pos->uPositional at split
CUMULATIVE_SEARCH_FLAGS sSearchFlags; // flags at split time
// output from the split node
@@ -953,38 +952,6 @@ typedef struct _PLY_INFO
}
PLY_INFO;
-// #define EVAL_HASH
-#ifdef EVAL_HASH
-#define EVAL_HASH_TABLE_SIZE (2097152) // 32Mb (per thread)
-
-//
-// bvFlags values for EVAL_HASH_ENTRY. EXACT means iEval is the real,
-// fully-computed static eval. UPPER/LOWER mean the entry came from a
-// lazy-eval early exit: iEval is the (uncorrected) partial score that
-// would be returned and iBound is the proven bound on the true, fully
-// computed eval (true score <= iBound for UPPER, true score >= iBound
-// for LOWER). Because the bound is a fact about the position -- not
-// about whatever alpha/beta window triggered the lazy exit -- it can
-// be reused by a later probe with a different window, as long as that
-// window is still resolved by the bound (mirrors HASH_FLAG_UPPER /
-// HASH_FLAG_LOWER in the main hash table).
-//
-#define EVAL_HASH_EXACT 0x1
-#define EVAL_HASH_UPPER 0x2
-#define EVAL_HASH_LOWER 0x4
-
-typedef struct _EVAL_HASH_ENTRY
-{
- UINT64 u64Key;
- SCORE iEval;
- SCORE iBound;
- UCHAR bvFlags;
- ULONG uPositional;
- COOR cTrapped[2];
-
-} EVAL_HASH_ENTRY;
-#endif
-
#define PAWN_HASH_TABLE_SIZE (131072) // 5.5Mb (per thread)
typedef struct _PAWN_HASH_ENTRY
{
@@ -1014,7 +981,6 @@ PAWN_HASH_ENTRY;
typedef struct _SEARCHER_THREAD_CONTEXT
{
ULONG uPly; // its distance from root
- ULONG uPositional; // positional component of score
POSITION sPosition; // the board
MOVE_STACK sMoveStack; // the move stack
CUMULATIVE_SEARCH_FLAGS sSearchFlags;
@@ -1035,9 +1001,6 @@ typedef struct _SEARCHER_THREAD_CONTEXT
SCORE iRootScore;
ULONG uRootDepth;
PAWN_HASH_ENTRY rgPawnHash[PAWN_HASH_TABLE_SIZE];
-#ifdef EVAL_HASH
- EVAL_HASH_ENTRY rgEvalHash[EVAL_HASH_TABLE_SIZE];
-#endif
CHAR szLastPV[SMALL_STRING_LEN_CHAR];
}
SEARCHER_THREAD_CONTEXT;
@@ -1060,7 +1023,6 @@ SEARCHER_THREAD_CONTEXT;
typedef struct _LIGHTWEIGHT_SEARCHER_CONTEXT
{
ULONG uPly;
- ULONG uPositional;
POSITION sPosition;
MOVE_STACK sMoveStack;
CUMULATIVE_SEARCH_FLAGS sSearchFlags;
@@ -2309,7 +2271,7 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx);
// search.c
//
#define QPLIES_OF_NON_CAPTURE_CHECKS (2)
-#define FUTILITY_BASE_MARGIN (50) // + ctx->uPositional (min 100)
+#define FUTILITY_BASE_MARGIN (50)
#define DO_IID
#define IID_R_FACTOR (TWO_PLY + HALF_PLY)
@@ -3029,25 +2991,10 @@ RecognLookup(SEARCHER_THREAD_CONTEXT *ctx,
SCORE *piScore,
FLAG fProbeEGTB);
-#ifdef EVAL_HASH
-void
-ClearEvalHashStats(void);
-
-void
-ReportEvalHashStats(void);
-
-SCORE
-ProbeEvalHash(SEARCHER_THREAD_CONTEXT *ctx, SCORE iAlpha, SCORE iBeta);
-
SCORE
GetRoughEvalScore(IN SEARCHER_THREAD_CONTEXT *ctx,
IN SCORE iAlpha,
IN SCORE iBeta,
IN FLAG fUseHash);
-void
-StoreEvalHash(SEARCHER_THREAD_CONTEXT *ctx, SCORE iScore, SCORE iBound,
- UCHAR bvFlags);
-#endif // EVAL_HASH
-
#endif // CHESS
diff --git a/src/eval.c b/src/eval.c
index 99baf8a..cf9527b 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -5561,18 +5561,6 @@ Return value:
pos->uMinMobility[BLACK] = pos->uMinMobility[WHITE] = 100;
pos->cTrapped[BLACK] = pos->cTrapped[WHITE] = ILLEGAL_COOR;
-#ifdef EVAL_HASH
- //
- // Check the eval hash
- //
- iScoreForSideToMove = ProbeEvalHash(ctx, iAlpha, iBeta);
- if (iScoreForSideToMove != INVALID_SCORE)
- {
- INC(ctx->sCounters.tree.u64EvalHashHits);
- goto end;
- }
-#endif
-
pos->iScore[BLACK] =
(pos->uPawnMaterial[BLACK] + pos->uNonPawnMaterial[BLACK]);
pos->iScore[WHITE] =
@@ -5690,16 +5678,6 @@ Return value:
{
*piPositional = iAlphaMargin;
}
-#ifdef EVAL_HASH
- //
- // The true eval can't reach iAlpha; that's a fact about
- // the position (not this particular window), so remember
- // it as a proven upper bound for reuse by later probes.
- //
- StoreEvalHash(ctx, iScoreForSideToMove,
- (SCORE)(iScoreForSideToMove + iAlphaMargin),
- EVAL_HASH_UPPER);
-#endif
goto end;
}
else if (iScoreForSideToMove - iBetaMargin >= iBeta)
@@ -5709,11 +5687,6 @@ Return value:
{
*piPositional = iBetaMargin;
}
-#ifdef EVAL_HASH
- StoreEvalHash(ctx, iScoreForSideToMove,
- (SCORE)(iScoreForSideToMove - iBetaMargin),
- EVAL_HASH_LOWER);
-#endif
goto end;
}
}
@@ -6090,15 +6063,6 @@ Return value:
{
*piPositional = iAlphaMargin;
}
- if ((ULONG)iAlphaMargin > ctx->uPositional)
- {
- ctx->uPositional = (ULONG)iAlphaMargin;
- }
- else
- {
- ctx->uPositional -= (ctx->uPositional - (ULONG)iAlphaMargin) / 4;
- ctx->uPositional = MAXU(ctx->uPositional, VALUE_PAWN);
- }
g_Options.iLastEvalScore = iScoreForSideToMove;
#ifdef EVAL_TIME
@@ -6107,13 +6071,6 @@ Return value:
#endif
INC(ctx->sCounters.tree.u64FullEvals);
-#ifdef EVAL_HASH
- //
- // Store in eval hash.
- //
- StoreEvalHash(ctx, iScoreForSideToMove, 0, EVAL_HASH_EXACT);
-#endif
-
end:
ASSERT(IS_VALID_SCORE(iScoreForSideToMove));
ASSERT((iScoreForSideToMove > -NMATE) &&
@@ -6122,6 +6079,5 @@ Return value:
ASSERT(abs(ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE]) < 700);
ASSERT(ctx->sPlyInfo[ctx->uPly].uMinMobility[BLACK] <= 100);
ASSERT(ctx->sPlyInfo[ctx->uPly].uMinMobility[WHITE] <= 100);
- ASSERT((ctx->uPositional < 2500) && (ctx->uPositional >= VALUE_PAWN));
return(iScoreForSideToMove);
}
diff --git a/src/evalhash.c b/src/evalhash.c
index fc29b88..4df6e23 100644
--- a/src/evalhash.c
+++ b/src/evalhash.c
@@ -8,7 +8,11 @@ Module Name:
Abstract:
- Evaluation score hashing code.
+ Rough (mid-tree) eval estimation. Used to hold the eval-score
+ hashing code (EVAL_HASH) too; that subsystem was cut for good
+ (measured as slow as or slower than just computing eval) and is
+ not coming back in this form, so this file is now just
+ GetRoughEvalScore().
Author:
@@ -20,8 +24,6 @@ Revision History:
#include "chess.h"
-extern ULONG g_uIterateDepth;
-
SCORE
GetRoughEvalScore(IN OUT SEARCHER_THREAD_CONTEXT *ctx,
IN SCORE iAlpha,
@@ -34,23 +36,22 @@ Routine description:
This is meant to be the "one place" that code looks when it needs to
get an idea of the estimated evaluation of a position (in mid tree,
not at the leaves... at the leaves just call Eval directly).
-
- The thought is that:
-
- 1. The top of the tree (near the root) represents very few nodes
- which have big subtrees. Use the real Eval here.
-
- 2. Otherwise possibly probe the eval hash -- if there's a score in
- it then we can return quickly.
-
- 3. Otherwise do a (very) rough material estimate.
+
+ Just calls Eval() -- its own lazy-exit machinery already is the
+ cheap, calibrated estimate this function exists to provide, so
+ there's no separate "rough" estimator to maintain here. Used to
+ fall back to material + a stale ctx->uPositional EWMA left over
+ from whatever full eval last ran anywhere in the tree, fed by an
+ eval-hash probe (fUseHash); both are gone now (see the commit
+ removing ctx->uPositional and EVAL_HASH), and this is the direct
+ replacement.
Parameters:
IN OUT SEARCHER_THREAD_CONTEXT *ctx,
IN SCORE iAlpha,
IN SCORE iBeta,
- IN FLAG fUseHash
+ IN FLAG fUseHash : unused, kept for call-site compatibility
Return value:
@@ -58,148 +59,6 @@ Return value:
--*/
{
- POSITION *pos = &(ctx->sPosition);
- UINT64 u64Key;
- ULONG u;
-
- if (ctx->uPly <= 4)
- {
- return Eval(ctx, iAlpha, iBeta, NULL);
- }
-#ifdef EVAL_HASH
- else if ((fUseHash) || (ctx->uPly <= (g_uIterateDepth / 2)))
- {
- u64Key = (pos->u64PawnSig ^ pos->u64NonPawnSig);
- u = (ULONG)u64Key & (EVAL_HASH_TABLE_SIZE - 1);
- if (ctx->rgEvalHash[u].u64Key == u64Key)
- {
- switch(ctx->rgEvalHash[u].bvFlags)
- {
- case EVAL_HASH_EXACT:
- ctx->uPositional = ctx->rgEvalHash[u].uPositional;
- return(ctx->rgEvalHash[u].iEval);
- case EVAL_HASH_UPPER:
- if (ctx->rgEvalHash[u].iBound <= iAlpha)
- {
- return(ctx->rgEvalHash[u].iEval);
- }
- break;
- case EVAL_HASH_LOWER:
- if (ctx->rgEvalHash[u].iBound >= iBeta)
- {
- return(ctx->rgEvalHash[u].iEval);
- }
- break;
- }
- }
- }
-#else
- (void)u64Key;
- (void)u;
(void)fUseHash;
-#endif
- return(pos->iMaterialBalance[pos->uToMove] + ctx->uPositional);
-}
-
-#ifdef EVAL_HASH
-
-
-SCORE
-ProbeEvalHash(IN OUT SEARCHER_THREAD_CONTEXT *ctx, IN SCORE iAlpha, IN SCORE iBeta)
-/*++
-
-Routine description:
-
- Probe the eval hash; return a real score if there's a hit
- otherwise return INVALID_SCORE. An EXACT entry is always usable.
- An UPPER/LOWER entry (recorded from a lazy-eval early exit) is
- only usable if the bound it proved still resolves the caller's
- current alpha/beta window; otherwise it's treated as a miss and
- the caller must actually compute something.
-
-Parameters:
-
- IN OUT SEARCHER_THREAD_CONTEXT *ctx : note that in the case
- of a hit, *ctx is modified also.
- IN SCORE iAlpha, IN SCORE iBeta : caller's current window
-
-Return value:
-
- SCORE
-
---*/
-{
- POSITION *pos = &(ctx->sPosition);
- UINT64 u64Key = (pos->u64PawnSig ^ pos->u64NonPawnSig);
- ULONG u = (ULONG)u64Key & (EVAL_HASH_TABLE_SIZE - 1);
-
- if (ctx->rgEvalHash[u].u64Key == u64Key)
- {
- switch(ctx->rgEvalHash[u].bvFlags)
- {
- case EVAL_HASH_EXACT:
- ctx->uPositional = ctx->rgEvalHash[u].uPositional;
- pos->cTrapped[WHITE] = ctx->rgEvalHash[u].cTrapped[WHITE];
- pos->cTrapped[BLACK] = ctx->rgEvalHash[u].cTrapped[BLACK];
- return(ctx->rgEvalHash[u].iEval);
- case EVAL_HASH_UPPER:
- if (ctx->rgEvalHash[u].iBound <= iAlpha)
- {
- return(ctx->rgEvalHash[u].iEval);
- }
- break;
- case EVAL_HASH_LOWER:
- if (ctx->rgEvalHash[u].iBound >= iBeta)
- {
- return(ctx->rgEvalHash[u].iEval);
- }
- break;
- }
- }
- return(INVALID_SCORE);
-}
-
-void
-StoreEvalHash(IN OUT SEARCHER_THREAD_CONTEXT *ctx,
- IN SCORE iScore,
- IN SCORE iBound,
- IN UCHAR bvFlags)
-/*++
-
-Routine description:
-
- Store a score in the eval hash table (which is pointed to
- indirectly via ctx). bvFlags is EVAL_HASH_EXACT for a fully
- computed eval (iBound ignored) or EVAL_HASH_UPPER/EVAL_HASH_LOWER
- for a bound proven by a lazy-eval early exit (iScore is the
- uncorrected partial score that would be returned, iBound is the
- proven bound on the true eval).
-
-Parameters:
-
- SEARCHER_THREAD_CONTEXT *ctx,
- SCORE iScore,
- SCORE iBound,
- UCHAR bvFlags
-
-Return value:
-
- void
-
---*/
-{
- POSITION *pos = &(ctx->sPosition);
- UINT64 u64Key = (pos->u64PawnSig ^ pos->u64NonPawnSig);
- ULONG u = (ULONG)u64Key & (EVAL_HASH_TABLE_SIZE - 1);
- ctx->rgEvalHash[u].u64Key = u64Key;
- ctx->rgEvalHash[u].iEval = iScore;
- ctx->rgEvalHash[u].iBound = iBound;
- ctx->rgEvalHash[u].bvFlags = bvFlags;
- if (bvFlags == EVAL_HASH_EXACT)
- {
- ctx->rgEvalHash[u].uPositional = ctx->uPositional;
- ctx->rgEvalHash[u].cTrapped[WHITE] = pos->cTrapped[WHITE];
- ctx->rgEvalHash[u].cTrapped[BLACK] = pos->cTrapped[BLACK];
- }
+ return Eval(ctx, iAlpha, iBeta, NULL);
}
-#endif // EVAL_HASH
diff --git a/src/main.c b/src/main.c
index 52e1bb6..3267da6 100755
--- a/src/main.c
+++ b/src/main.c
@@ -79,16 +79,9 @@ Return value:
#ifdef DUMP_TREE
Trace(" Search tree dumpfile generation enabled\n");
#endif
-#ifdef EVAL_HASH
- Trace(" Hash sizes: %u Mb (main), %u Mb / thread (pawn), %u Mb / thread (eval)\n",
- (g_uHashTableSizeEntries * sizeof(HASH_ENTRY)) / MB,
- PAWN_HASH_TABLE_SIZE * sizeof(PAWN_HASH_ENTRY) / MB,
- EVAL_HASH_TABLE_SIZE * sizeof(EVAL_HASH_ENTRY) / MB);
-#else
Trace(" Hash sizes: %u Mb (main), %u Mb / thread (pawn)\n",
(g_uHashTableSizeEntries * sizeof(HASH_ENTRY)) / MB,
PAWN_HASH_TABLE_SIZE * sizeof(PAWN_HASH_ENTRY) / MB);
-#endif
Trace(" QCheckPlies: %u\n", QPLIES_OF_NON_CAPTURE_CHECKS);
Trace(" FutilityBase: %u\n", FUTILITY_BASE_MARGIN);
p = ExportEvalDNA();
diff --git a/src/root.c b/src/root.c
index a83249c..a651c71 100755
--- a/src/root.c
+++ b/src/root.c
@@ -296,7 +296,6 @@ Return value:
memmove(&(ctx->sPosition), pos, sizeof(POSITION));
}
ctx->uPly = 0;
- ctx->uPositional = 133;
memset(&(ctx->sCounters), 0, sizeof(COUNTERS));
}
@@ -375,7 +374,6 @@ Return value:
memmove(&(ctx->sPosition), pos, sizeof(POSITION));
}
ctx->uPly = 0;
- ctx->uPositional = 133;
ctx->sMoveStack.uUnblockedKeyValue[0] = 1;
for (u = 1;
u < MAX_PLY_PER_SEARCH;
diff --git a/src/search.c b/src/search.c
index ce867b6..6c8a945 100755
--- a/src/search.c
+++ b/src/search.c
@@ -1147,7 +1147,8 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
// 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))
+ if (0 != ValueOfMaterialInTroubleDespiteMove(ctx, pos->uToMove) &&
+ FALSE == ctx->sSearchFlags.fCouldStandPat[pos->uToMove])
{
iBestScore = iAlpha;
}
diff --git a/src/split.c b/src/split.c
index 2672c0c..8558e3c 100755
--- a/src/split.c
+++ b/src/split.c
@@ -182,8 +182,7 @@ Return value:
uIdleLoops = 0;
ReInitializeSearcherContext(&(g_SplitInfo[u].sRootPosition), ctx);
ctx->pSplitInfo[0] = &(g_SplitInfo[u]);
- ctx->uPositional = g_SplitInfo[u].uSplitPositional;
-
+
//
// Note: the main thread could have already exhausted the
// split and decremented it from 3->2. When we leave it
@@ -597,7 +596,6 @@ Return value:
g_SplitInfo[u].iPositionExtend = iPositionExtend;
g_SplitInfo[u].iAlpha = *piAlpha;
g_SplitInfo[u].iBeta = iBeta;
- g_SplitInfo[u].uSplitPositional = ctx->uPositional;
g_SplitInfo[u].sSearchFlags = ctx->sSearchFlags;
ASSERT(FALSE == ctx->sSearchFlags.fAvoidNullmove);
g_SplitInfo[u].mvBest = *pmvBest;
diff --git a/src/testeval.c b/src/testeval.c
index 0c4b1cf..ee9c6a2 100755
--- a/src/testeval.c
+++ b/src/testeval.c
@@ -422,7 +422,6 @@ Return value:
u < ARRAY_LENGTH(x);
u++)
{
- ctx.uPositional = 0;
if (FALSE == FenToPosition(&(ctx.sPosition), x[u].szFen))
{
UtilPanic(INCONSISTENT_STATE,