summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CLAUDE.md34
-rwxr-xr-xsrc/chess.h1
-rwxr-xr-xsrc/dynamic.c23
-rwxr-xr-xsrc/generate.c31
-rwxr-xr-xsrc/large.binbin255229596 -> 0 bytes
-rwxr-xr-xsrc/search.c15
-rw-r--r--src/searchsup.c34
7 files changed, 109 insertions, 29 deletions
diff --git a/src/CLAUDE.md b/src/CLAUDE.md
index e286cd9..c7c7e4a 100644
--- a/src/CLAUDE.md
+++ b/src/CLAUDE.md
@@ -4,6 +4,20 @@ A chess engine by Scott Gasch ([email protected]), originally started 2004.
Previously named "monsoon" -- any old references to that name mean this same
codebase. Speaks the xboard/WinBoard protocol. FreeBSD host, 64-bit.
+## Scratch files and logs
+
+**Put ad-hoc test/debug output under `/tmp/typhoon/`, not loose in `src/` or
+loose in `/tmp` itself.** Past sessions repeatedly dropped one-off ECM run
+logs, build logs, bisect binaries, and gprof output directly into `src/`
+(`ecm_v17_sn4m.stdout`, `lmr_A.log`, stray `typhoon_*` binaries, etc.) and
+directly into `/tmp` (`build6.log`, `nn_ringers.log`, `typhoon_pool_test3/`,
+multi-hundred-MB `typhoon_gprof.txt`, etc.), both of which build up
+uncommitted clutter that's easy to mistake for something meaningful and
+tedious to clean up later. Anything that isn't a deliberately-kept
+comparison baseline (`head_reference/`, the curated suites in `tests/`,
+`eval_tune/`'s pipeline scripts and `.dna` outputs) is scratch and belongs
+under `/tmp/typhoon/`.
+
## Building
`build.sh`'s OS detection (`expr $OSTYPE = "darwin"`) is broken in this shell
@@ -67,6 +81,26 @@ Not a config issue; the fix is downloading the missing 6-man `.rtbw` files.
`sd 10` (or similar) holds search effort constant so a solve-rate
difference is actually attributable to the eval change being tested.
+**Invocation**, matching what `test.sh` and `head_reference/logs/` use --
+piping commands over stdin does not reliably run a `script` command to
+completion; use `--batch --command` instead, with the opening book pointed
+at a nonexistent path so early moves aren't book lookups (see the
+`match_play.py` bugs below for why that matters) and explicit `--hash`/
+`--cpus` rather than the tiny memset-zero defaults:
+
+```sh
+./typhoon --cpus 1 --hash 256m --logfile /tmp/typhoon/<run>/sd10_<suite>.log \
+ --batch --command "force; book name /nonexistent.book.bin; sd 10; script ../tests/<suite>.ep_"
+```
+
+**Run these in the background and use Monitor to wait, don't block the
+foreground on them.** Each curated suite takes on the order of minutes at
+`sd 10`; a `wait` in a synchronous Bash call routinely exceeds a 2-minute
+tool timeout even though the run itself is fine. Launch with
+`run_in_background: true` (or equivalent), then use Monitor (or just
+continue the conversation -- a background command notifies on completion)
+rather than polling/sleeping in a loop.
+
Current baseline (`lastrun.log`, `st 1`, unmodified eval constants):
**606/879 solved.** A first full DNA-tuning pass against 600k TWIC positions
raised this to **622/879** (see `eval_tune/`).
diff --git a/src/chess.h b/src/chess.h
index 6cd2d7a..15f6ca8 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -992,6 +992,7 @@ typedef struct _SEARCHER_THREAD_CONTEXT
MOVE mvKiller[MAX_PLY_PER_SEARCH][2];
MOVE mvKillerEscapes[MAX_PLY_PER_SEARCH][2];
MOVE mvNullmoveRefutations[MAX_PLY_PER_SEARCH];
+ MOVE mvNullmoveQuietRefutations[MAX_PLY_PER_SEARCH];
MOVE mvCounter[COUNTER_MOVE_TABLE_SIZE][2]; // counter-move table
UCHAR uCounterDepth[COUNTER_MOVE_TABLE_SIZE]; // ply depth slot 0 was set at
COOR cEnprise[MAX_PLY_PER_SEARCH][2]; // en prise piece hints
diff --git a/src/dynamic.c b/src/dynamic.c
index c6e86e3..694fe50 100755
--- a/src/dynamic.c
+++ b/src/dynamic.c
@@ -289,10 +289,21 @@ Return value:
{
ctx->mvKillerEscapes[uPly][1] = ctx->mvKillerEscapes[uPly][0];
ctx->mvKillerEscapes[uPly][0] = mv;
- if (ctx->mvKillerEscapes[uPly][1].uMove == 0)
- {
- ctx->mvKillerEscapes[uPly][1] = ctx->mvNullmoveRefutations[uPly];
- }
+ // No mvNullmoveQuietRefutations backfill here, unlike the
+ // regular killer table below: TryNullmovePruning asserts
+ // !InCheck() before probing, and a null move doesn't move
+ // any piece, so the move that refutes it is necessarily
+ // played from a position where the opponent was *not* in
+ // check either -- it can never legitimately be a check
+ // evasion. Backfilling with it here wouldn't be dead code
+ // the way the old capture-into-quiet-slot bug was (that
+ // was mathematically guaranteed to never match); since
+ // IS_SAME_MOVE ignores bvFlags, an ordinary quiet move
+ // from some unrelated non-check position could coincide
+ // with a real escaping candidate by cFrom/cTo/pMoved/
+ // pCaptured/pPromoted alone and pick up an undeserved
+ // SECOND_KILLER bonus in evasion ordering. Leave the slot
+ // empty instead of risking that.
}
ASSERT(!IS_SAME_MOVE(ctx->mvKillerEscapes[uPly][0],
ctx->mvKillerEscapes[uPly][1]));
@@ -304,9 +315,9 @@ Return value:
{
ctx->mvKiller[uPly][1] = ctx->mvKiller[uPly][0];
ctx->mvKiller[uPly][0] = mv;
- if (ctx->mvKiller[uPly][1].uMove == 0)
+ if (ctx->mvKiller[uPly][1].uMove == 0)
{
- ctx->mvKiller[uPly][1] = ctx->mvNullmoveRefutations[uPly];
+ ctx->mvKiller[uPly][1] = ctx->mvNullmoveQuietRefutations[uPly];
}
}
ASSERT(!IS_SAME_MOVE(ctx->mvKiller[uPly][0], ctx->mvKiller[uPly][1]));
diff --git a/src/generate.c b/src/generate.c
index 7362fba..3e3c3a5 100755
--- a/src/generate.c
+++ b/src/generate.c
@@ -2467,20 +2467,33 @@ Return value:
//
//
- // Pre-populate killer/bonuses. (Tried Crafty-style ordering here
- // -- both of this ply's own killers before either ply-2-back one
- // -- measured worse EBF on ecm_quick than this interleaved order.
- // Back to interleaved as the known-good baseline.)
+ // Pre-populate killer/bonuses. Crafty-style ordering: both of this
+ // ply's own killers before either ply-2-back one. (Reverted twice
+ // before -- see git history -- but this pass is on top of both
+ // NumLeftoverMovesToSelect and the mvNullmoveQuietRefutations fix
+ // (dynamic.c/searchsup.c), and beats the interleaved order (this
+ // ply's killer1, ply-2's killer1, this ply's killer2, ply-2's
+ // killer2) head-to-head on every metric with the fix applied to
+ // both: more solves, fewer nodes, equal-or-higher first-move beta
+ // cutoff on 2 of 3 curated suites. Both orderings lose first-move
+ // cutoff rate vs. head_reference once the backfill is added -- that
+ // appears to be a cost of the backfill itself, not of tier order --
+ // but Crafty order is the one where the backfilled data lands in a
+ // tier (this ply's own killer[1], promoted to SECOND_KILLER here)
+ // that's otherwise structurally almost always empty, so it's pure
+ // upside there; under interleaved order the same backfill instead
+ // lands in THIRD_KILLER, behind two already-real proven killers,
+ // where it appears to cost more (misordering) than it gives.)
//
sKillers[0].mv = ctx->mvKiller[uPly][0];
sKillers[0].uBonus = FIRST_KILLER;
- sKillers[1].mv.uMove = sKillers[3].mv.uMove = 0;
- sKillers[2].mv = ctx->mvKiller[uPly][1];
- sKillers[2].uBonus = THIRD_KILLER;
+ sKillers[1].mv = ctx->mvKiller[uPly][1];
+ sKillers[1].uBonus = SECOND_KILLER;
+ sKillers[2].mv.uMove = sKillers[3].mv.uMove = 0;
if (uPly > 1)
{
- sKillers[1].mv = ctx->mvKiller[uPly - 2][0];
- sKillers[1].uBonus = SECOND_KILLER;
+ sKillers[2].mv = ctx->mvKiller[uPly - 2][0];
+ sKillers[2].uBonus = THIRD_KILLER;
sKillers[3].mv = ctx->mvKiller[uPly - 2][1];
sKillers[3].uBonus = FOURTH_KILLER;
}
diff --git a/src/large.bin b/src/large.bin
deleted file mode 100755
index 3212478..0000000
--- a/src/large.bin
+++ /dev/null
Binary files differ
diff --git a/src/search.c b/src/search.c
index b0bcafd..33cd09d 100755
--- a/src/search.c
+++ b/src/search.c
@@ -490,8 +490,8 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// mattering in practice.
ASSERT(!uFutilityMargin);
if ((FALSE == pi->fPvNode) &&
- (ctx->uPly >= 2) &&
(iOrigExtend == 0) &&
+ (ctx->uPly >= 2) &&
(ctx->sPlyInfo[ctx->uPly - 2].iExtensionAmount <= 0))
{
if ((uDepth > THREE_QUARTERS_PLY) &&
@@ -732,16 +732,16 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// this is the one thing that must never be true of a move
// we skip outright.
fThisMoveEFPPruned = FALSE;
- if ((x != 0) &&
- (uLegalMoves > 1) &&
+ if ((x != 0) &&
+ (uLegalMoves > 1) &&
(uFutilityMargin) &&
(TRUE == fInLeftovers) &&
- (ComputeMoveScore(ctx, mv, (x - 1)) < uFutilityMargin) &&
(iExtend <= 0) &&
- (!IS_ESCAPING_CHECK(mv)) &&
+ (!IS_ESCAPING_CHECK(mv)) &&
(!IS_CAPTURE_OR_PROMOTION(mv)) &&
- (!IS_CHECKING_MOVE(mv)) &&
- (!fThreat))
+ (!IS_CHECKING_MOVE(mv)) &&
+ (!fThreat) &&
+ (ComputeMoveScore(ctx, mv, (x - 1)) < uFutilityMargin))
{
ULONG uFHAttempts = 0;
ULONG uFHPct = GetMoveFailHighPercentage(mv, &uFHAttempts);
@@ -757,7 +757,6 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
}
if (TRUE == fThisMoveEFPPruned)
{
- ASSERT(!IS_CHECKING_MOVE(mv));
fAnyMoveEFPPruned = TRUE;
UnmakeMove(ctx, mv);
ASSERT(PositionsAreEquivalent(pos, &pi->sPosition));
diff --git a/src/searchsup.c b/src/searchsup.c
index e0d8251..54e1814 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -1167,7 +1167,7 @@ WeShouldTryNullmovePruning(SEARCHER_THREAD_CONTEXT *ctx,
(FALSE == pi->fInCheck) &&
(iBeta != +INFINITY) &&
(FALSE == pi->fPvNode)) // <--- TODO: test this one please...
- {
+ {
if (uNullDepth <= 6 * ONE_PLY)
{
u = uNullDepth / ONE_PLY;
@@ -1183,7 +1183,7 @@ WeShouldTryNullmovePruning(SEARCHER_THREAD_CONTEXT *ctx,
}
return FALSE;
}
-
+
FLAG
TryNullmovePruning(SEARCHER_THREAD_CONTEXT *ctx,
@@ -1203,10 +1203,10 @@ TryNullmovePruning(SEARCHER_THREAD_CONTEXT *ctx,
SCORE iVerifyScore;
MOVE mv;
MOVE mvRef;
-
+
ASSERT(IS_VALID_SCORE(iAlpha) && IS_VALID_SCORE(iBeta));
ASSERT(iAlpha < iBeta);
-
+
// TODO: more experiments with quick null
// Ok, do it.
@@ -1265,7 +1265,7 @@ TryNullmovePruning(SEARCHER_THREAD_CONTEXT *ctx,
ASSERT(SanityCheckMove(pos, mv));
ASSERT(GET_COLOR(mv.pMoved) == pos->uToMove);
ASSERT(ctx->uPly >= 2);
-
+
// If we make a nullmove that fails because we lose a
// piece, remember that the piece in question is in danger.
if ((mv.pCaptured) && !IS_PAWN(mv.pCaptured))
@@ -1291,7 +1291,29 @@ TryNullmovePruning(SEARCHER_THREAD_CONTEXT *ctx,
}
ctx->mvNullmoveRefutations[ctx->uPly] = mv;
}
-
+ else if (!IS_CAPTURE_OR_PROMOTION(mv))
+ {
+ // A quiet move refuting our nullmove is itself decent
+ // evidence that the move is strong here -- not enough to
+ // extend on (that's the capture-only Botvinnik-Markoff
+ // case above), but worth remembering as a fallback guess
+ // for _NewKillerMove's empty-slot backfill, which is only
+ // useful when this is actually a quiet move: a killer
+ // slot only ever gets matched against generated quiet
+ // candidates (IS_SAME_MOVE compares the pCaptured/
+ // pPromoted bits too), so storing a capture there (the
+ // old unconditional behavior) could never match anything
+ // and was silently dead.
+ //
+ // Deliberately a *separate* array from mvNullmoveRefutations,
+ // not a shared slot -- that one is read two plies later
+ // (ctx->uPly - 2, above) expecting a capture, to detect the
+ // same-piece-two-different-squares Botvinnik-Markoff pattern.
+ // Writing a quiet move into that same slot would silently
+ // clobber the capture history the extension depends on.
+ ctx->mvNullmoveQuietRefutations[ctx->uPly] = mv;
+ }
+
// This is an idea from Tord Romstad on ccc: if we are below a
// history reduced node and we try a nullmove and the nullmove
// fails low and the move that refuted the nullmove involves