<feed xmlns='http://www.w3.org/2005/Atom'>
<title>typhoon.git/src/CLAUDE.md, branch master</title>
<subtitle>The typhoon chess playing engine.
</subtitle>
<id>https://git.acknak.org/cgit/typhoon.git/atom?h=master</id>
<link rel='self' href='https://git.acknak.org/cgit/typhoon.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/'/>
<updated>2026-08-30T02:26:49Z</updated>
<entry>
<title>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.</title>
<updated>2026-08-30T02:26:49Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-08-30T02:26:49Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=9632634b648109765955e89ec07f788388d8170f'/>
<id>urn:sha1:9632634b648109765955e89ec07f788388d8170f</id>
<content type='text'>
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 &gt; -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 &lt;= -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 "&lt;#&gt;" 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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01M9ZDiJhiUajUxh95mTXCFJ
</content>
</entry>
<entry>
<title>Dynamic move ordering overhaul: continuation-history, evidence-gated</title>
<updated>2026-08-29T22:03:54Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-08-29T22:03:54Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=0b12137376929d96da81cd088d3d288cb1eec32d'/>
<id>urn:sha1:0b12137376929d96da81cd088d3d288cb1eec32d</id>
<content type='text'>
countermove promotion, retired hung-piece-escape and NumLeftoverMovesToSelect.

Full session was built on a "measure the pick, not the game" methodology:
aggregate solve counts on curated suites are too noisy to tune move-ordering
knobs against, so most decisions here came from per-move fail-high/alpha-raise
rates at much larger sample sizes (leftover FH% instrumentation, a zero-
selection-budget diagnostic that isolates a single best-of-remaining pick,
and evidence-bucket calibration), not solve-count deltas alone. See
CLAUDE.md's "Dynamic move ordering experiments" section for the reusable
methodology and generate.c's _ScoreAllMoves comment for the resulting
ordering hierarchy.

Changes:
- Added g_ContinuationHistory: same growth/decay math as the existing
  g_HistoryCounters butterfly table, additionally keyed by the previous
  move, so its magnitude is self-calibrated rather than a hand-picked
  constant. Flat, sufficient response across a 256x scale sweep.
- Countermove-table matches now get a real GOOD_MOVE-tier promotion
  (previously the table was write-only, tracked for stats but never read
  for ordering), but only when the match's own accumulated
  history+continuation evidence clears COUNTERMOVE_EVIDENCE_THRESHOLD
  (10,000) -- a raw match with no track record was shown to perform
  identically to an ordinary leftover (~0.6-0.85% FH), so promoting on
  match alone would have repeated hung-piece-escape's mistake below.
- Retired hung-piece-escape's unconditional GOOD_MOVE-tier promotion.
  Evidence-calibration showed the overwhelming majority of triggers (a
  zero-evidence population 250-1000x larger than countermove's) performed
  at the plain-leftover baseline -- the promotion was mostly free tier-
  escape treatment for moves that hadn't earned it. Replaced with
  FLEE_BONUS, a flat same-tier nudge inside SelectBestWithHistory (never
  escapes GOOD_MOVE/leftover classification, unlike a generation-time
  promotion) at the magnitude found to plateau a same-tier-nudge sweep.
- Retired NumLeftoverMovesToSelect (the depth-indexed budget on how many
  leftover moves got a full selection scan before falling back to
  unsorted order). search.c's main move loop now always fully selects --
  the leftover pool was shown to contain real, findable signal a bailout
  budget was discarding for a node-count savings that didn't hold up net-
  net once measured by solve counts and fail-high rates rather than raw
  node counts (noisy on small suites independent of this change).
- Collapsed leftover-move instrumentation from sorted/raw pairs down to a
  single set now that "raw" (unsorted fallback) is structurally
  impossible; kept the countermove evidence-bucket calibration counters
  (ongoing check that COUNTERMOVE_EVIDENCE_THRESHOLD stays well-
  calibrated); removed the contested-node A/B harness and hung-piece
  evidence calibration now that the decisions they were built to inform
  are made.

Net effect on the three curated suites (sd 10): solve counts wash (tied,
+1, -1 across ringers/confident/hard), leftover fail-high rate improved
consistently on all three (the intended, directly-measured target of this
work). Not yet validated beyond sd 10 -- an sn-based run or
eval_tune/match_play.py head-to-head gate is the natural next check before
leaning on this as a proven strength gain rather than a directionally-
sound, sd-10-clean change.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_014XePz6Sk4qQsTaP2jVJWJu
</content>
</entry>
<entry>
<title>Switch to Crafty-style killer ordering; fix mvNullmoveRefutations type-mixing bug and add a quiet-refutation killer backfill.</title>
<updated>2026-08-29T07:30:53Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-08-29T07:30:53Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=917bf1260ae217bdcbdbc11c2c37cdb5d3a8eae9'/>
<id>urn:sha1:917bf1260ae217bdcbdbc11c2c37cdb5d3a8eae9</id>
<content type='text'>
Killer tiers now try both of this ply's own killers before either
ply-2-back one, matching Crafty's ordering. Two earlier attempts at
this same swap were reverted for regressing; this pass lands on top of
NumLeftoverMovesToSelect (more SelectBestWithHistory budget to reach
these lower-tier slots) and a real bug fix below, and beats interleaved
order head-to-head on solves, node count, and first-move beta cutoff
across the three curated suites.

The bug: mvNullmoveRefutations's empty-killer-slot backfill could only
ever contain a capturing move (TryNullmovePruning only wrote it inside
the capture-refutation branch), but IS_SAME_MOVE's mask includes the
pCaptured bits, so that backfilled value could never match a real
quiet candidate -- the backfill was silently dead code. Fixed by
recording genuinely quiet null-move refutations into a new, separate
mvNullmoveQuietRefutations array (kept separate so it can't clobber the
capture history mvNullmoveRefutations still needs for the
Botvinnik-Markoff same-piece-two-squares extension check) and
backfilling the regular killer table from that instead. The
check-evasion killer table intentionally does *not* get this backfill:
a null-move refutation can never legitimately be an escaping-check
move (null moves can't deliver check), so backfilling there risks
IS_SAME_MOVE cross-context false positives instead of the old
guaranteed-inert no-op.

Measured at sd10 across ecm_ringers/ecm_confident_quick/ecm_hard_quick
against head_reference (commit d11e973): 115/191 solves (vs. 116
baseline), 924.36M total nodes (vs. 933.23M), first-move beta cutoff
within 0.1-0.9 points of baseline on all three suites -- and clearly
better than the same fix under interleaved order (113/191 solves,
963.10M nodes), which loses to head_reference on every metric.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EortUUkDVpsfrbqshBJYJg
</content>
</entry>
<entry>
<title>Document the three-curated-suites testing methodology and the</title>
<updated>2026-08-29T05:22:23Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-08-29T05:22:23Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=366eebaf6b72fc6b56ea06a71f0276069a501dc7'/>
<id>urn:sha1:366eebaf6b72fc6b56ea06a71f0276069a501dc7</id>
<content type='text'>
head_reference protocol.

Realized while asking "where is this documented" that it wasn't --
scattered implicitly across lmr_testing/RESULTS.md and
head_reference/README.md but never stated as a methodology anywhere
durable. Also records the sd-vs-sn guidance (pick based on what the
change is expected to affect) and flags the specific gap that cost
real time this session: assuming a working tree's disabled-mechanism
state matched HEAD's actual committed behavior, when it didn't.
</content>
</entry>
<entry>
<title>Started doing texel eval tuning.</title>
<updated>2026-08-25T02:31:36Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-08-25T02:31:36Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=29d73f4dd59554a349aa8e86e5ea65f28c912ec9'/>
<id>urn:sha1:29d73f4dd59554a349aa8e86e5ea65f28c912ec9</id>
<content type='text'>
</content>
</entry>
</feed>
