diff options
| author | Scott Gasch <[email protected]> | 2026-08-29 19:26:49 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-08-29 19:26:49 -0700 |
| commit | 9632634b648109765955e89ec07f788388d8170f (patch) | |
| tree | 7854105dd63ae6eb95f776700048e824f9650575 /src/search.c | |
| parent | 4b99e893a2b423247fd20ab69c0346fcbe6bca81 (diff) | |
Fix batch-mode exit code, killer-table backfill collision, impossible QSearch mate-magnitude asserts, MATEMOVE PV display, --command truncation, and test.sh's stale egtbpath.
Several small, independent correctness fixes bundled together since
they were all exercised together through today's precommit_check.sh
and curated-suite runs:
- command.c: batch-mode's "Exhausted input" exit was exit(-1), which
truncates to 255 (an 8-bit status) and is indistinguishable from a
real crash's nonzero exit. Changed to exit(0) so debug_smoke_test.sh
can reliably tell a clean batch run apart from a crash by exit status
alone.
- dynamic.c: _NewKillerMove's slot[1] backfill from
mvNullmoveQuietRefutations[uPly] had no check that the backfilled
move differed from the move just placed in slot[0]. When they
coincided, both slots held the identical move, silently wasting a
killer slot in release builds (ASSERT is a no-op there) and tripping
_NewKillerMove's own IS_SAME_MOVE invariant in DEBUG builds. Fixed by
skipping the backfill on collision.
- search.c: removed two ASSERT(iBestScore > -NMATE) calls in QSearch
that encoded an invariant that isn't actually guaranteed -- at an
early full-width root iteration, or after aspiration-window widening
following repeated fail-highs, an ancestor frame's iAlpha/iBeta can
itself already be more extreme than -NMATE with no mate anywhere in
the line, so a legitimate fail-low placeholder or fail-high score can
land in mate-magnitude territory purely as a window artifact.
hash.c's storage path already treats any value <= -NMATE as a sound
upper bound regardless of origin, so this was a false invariant, not
a caught bug. Also: minor whitespace cleanup, an added ASSERT
documenting the futility-margin depth precondition it replaced a
redundant runtime check for, and PV/leaf-count bookkeeping on the
mate/draw-at-root leaf paths that was previously skipped.
- util.c: MATEMOVE sentinel moves weren't handled in PV-to-string
conversion, so a PV ending in a detected mate would either display
garbage or hit the same-move assert. Added an explicit "<#>" marker.
- test.sh: --egtbpath pointed at a nonexistent /egtb/three;/egtb/four;
/egtb/five; corrected to /zscratch/egtb, this box's actual EGTB
location.
- main.c/input.c: --command's initial-command buffer (g_szInitialCommand)
was a fixed 256-byte array; strncpy(..., SMALL_STRING_LEN_CHAR - 2)
silently truncated any longer --command string, and -- worse -- when
the source was long enough not to fit, strncpy doesn't null-terminate
the destination, so the immediately-following strcat(..., "\r\n") could
read/write past the buffer. Long move-replay command strings used
during this session's debugging hit the truncation directly (a ~600
char move list silently cut off mid-token, desyncing the input queue).
Changed g_szInitialCommand to a heap allocation sized to the actual
input length instead of a fixed cap.
- CLAUDE.md: documents the above (this file's own diff is prior
session's writeup of these same fixes, committed now alongside the
code).
All exercised together via precommit_check.sh (self-test suite + DEBUG
smoke test against random ecm.ep_ samples) and the sd10/sn5m curated
suite sweep run for the eval.c hand-tuning commit just before this one.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01M9ZDiJhiUajUxh95mTXCFJ
Diffstat (limited to 'src/search.c')
| -rwxr-xr-x | src/search.c | 26 |
1 files changed, 17 insertions, 9 deletions
diff --git a/src/search.c b/src/search.c index 61fa428..ab7d122 100755 --- a/src/search.c +++ b/src/search.c @@ -358,7 +358,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx, if (iScore > iBeta) { StoreLowerBound(mvHash, pos, iScore, uDepth, FALSE); } - iBestScore = iScore; // TODO: try just beta here + iBestScore = iScore; goto end; } } @@ -516,8 +516,8 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx, (ctx->uPly >= 2) && (ctx->sPlyInfo[ctx->uPly - 2].iExtensionAmount <= 0)) { - if ((uDepth > THREE_QUARTERS_PLY) && - (uDepth <= ONE_PLY + THREE_QUARTERS_PLY) && + ASSERT(uDepth >= THREE_QUARTERS_PLY); + if ((uDepth <= ONE_PLY + THREE_QUARTERS_PLY) && (iRoughEval + VALUE_KNIGHT <= iAlpha)) { uFutilityMargin = (iAlpha - iRoughEval) / 2; @@ -762,9 +762,9 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx, (uFutilityMargin) && (TRUE == fIsLeftoverMove) && (iExtend <= 0) && - (!IS_ESCAPING_CHECK(mv)) && + (!IS_ESCAPING_CHECK(mv)) && (!IS_CAPTURE_OR_PROMOTION(mv)) && - (!IS_CHECKING_MOVE(mv)) && + (!IS_CHECKING_MOVE(mv)) && (!fThreat) && (ComputeMoveScore(ctx, mv, (x - 1)) < uFutilityMargin)) { @@ -843,7 +843,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx, iBestScore = iScore; mvBest = mv; pi->mvBest = mv; - + if (iScore > iAlpha) { if (iScore >= iBeta) @@ -919,6 +919,11 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx, ASSERT(IS_CHECKING_MOVE(mvLast)); ASSERT(InCheck(pos, pos->uToMove)); iBestScore = MATED_SCORE(ctx->uPly); + if ((iAlpha < iBestScore) && (iBestScore < iBeta)) + { + INC(ctx->sCounters.tree.u64LeafCount); + UpdatePV(ctx, MATEMOVE); + } ASSERT(iBestScore <= -NMATE); goto end; } @@ -927,6 +932,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx, iBestScore = 0; if ((iAlpha < iBestScore) && (iBestScore < iBeta)) { + INC(ctx->sCounters.tree.u64LeafCount); UpdatePV(ctx, DRAWMOVE); } goto end; @@ -1460,7 +1466,6 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, // If we see a move whose value is zero, the rest of the moves // in this ply can be tossed. ASSERT(SanityCheckMoves(ctx, x, VERIFY_BEFORE | VERIFY_AFTER)); - ASSERT(iBestScore > -NMATE); goto end; } mv = ctx->sMoveStack.mvf[x].mv; @@ -1522,7 +1527,6 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, mv.cTo); } KEEP_TRACK_OF_FIRST_MOVE_FHs(uLegalMoves == 1); - ASSERT(iBestScore > -NMATE); ASSERT(SanityCheckMoves(ctx, x, VERIFY_BEFORE)); goto end; } @@ -1546,7 +1550,6 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, } } } - ASSERT(iBestScore > -NMATE); ASSERT(SanityCheckMoves(ctx, x, VERIFY_BEFORE)); end: @@ -1554,5 +1557,10 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx, ASSERT(PositionsAreEquivalent(pos, &pi->sPosition)); ASSERT(IS_VALID_SCORE(iBestScore) || WE_SHOULD_STOP_SEARCHING); DTLeaveNode(ctx, TRUE, iBestScore, pi->mvBest); + + // Note: iBestScore can be +INFINITY or -INFINITY here even in the + // absence of a legitimate mate detected if we disallowed stand + // pat due to perceived danger early on, when the a..b window had + // an extreme bound. This is "legitimate" but weird. return(iBestScore); } |
