<feed xmlns='http://www.w3.org/2005/Atom'>
<title>typhoon.git/src/GNUmakefile, 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-10-25T18:20:41Z</updated>
<entry>
<title>Remove the x86/x64 assembly backend; use compiler intrinsics instead</title>
<updated>2026-10-25T18:20:41Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-25T18:20:24Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=c1e8cefc7bf0c4017dda24b0486806ce3304a7ae'/>
<id>urn:sha1:c1e8cefc7bf0c4017dda24b0486806ce3304a7ae</id>
<content type='text'>
x86.asm/x64.asm were already mostly dead weight -- GetAttacks's real
asm body was retired 2026-09-06 (section 7 of
board_representation/MIGRATION.md), and LockDecrement/
CanUseParallelOpcodes/ParallelCompareUlong/ParallelCompareVector had
zero remaining callers (confirmed by grep before deleting anything).
What was still real and load-bearing:

- CountBits/FirstBit/LastBit: popcount/bsf/bsr CDECL calls. Already
  had faster, compiler-intrinsic replacements sitting right next to
  them in chess.h (FastCountBits/FastFirstBit/FastLastBit, via
  __builtin_popcountll/__builtin_ctzll/__builtin_clzll with -mpopcnt)
  that per-file #ifndef CROUTINES blocks in board.c/eval.c/generate.c/
  probe.c/recogn.c were already redirecting individual call sites to.
  Promoted that redirect to the plain names unconditionally
  (#define CountBits FastCountBits etc. in chess.h) and dropped the
  now-always-true CROUTINES guards in those five files -- CountBits'
  real asm body was a Kernighan bit-clearing loop (~23 cycles/op per
  gmake TEST=1's own benchmark), not even O(1), so this is strictly
  faster as well as removing the toolchain dependency.
- LockCompareExchange/LockIncrement: real atomics backing
  AcquireSpinLock/TryAcquireSpinLock/ReleaseSpinLock (util.c) and the
  move-pool allocator (gamelist.c). Replaced with static inline
  wrappers over __sync_val_compare_and_swap/__sync_add_and_fetch --
  same CAS/atomic-add semantics as the retired `lock cmpxchg`/
  `lock xadd` instructions.

Removes the yasm/nasm build dependency entirely: GNUmakefile's
NASM/NASMFLAGS variables, the CROUTINES flag, the .asm-&gt;.o build rule,
and x86.o/x64.o from every OBJS list. x86.asm/x64.asm deleted outright
(git history keeps them if ever needed again). The ASM=1 debug mode
(-S -fverbose-asm, a C-compiler assembly-listing dump, unrelated to
the deleted files despite the similar name) is untouched.

Verified: clean build with no yasm/nasm invocation anywhere in the
log, a clean DEBUG=1 TEST=1 self-test run, and node-count parity
against the previous commit on all three curated suites at sd10
(4,003,856 / 22,308,015 / 26,868,016 on both sides) -- confirms this
is a pure substitution with zero behavioral change, not just "doesn't
crash."

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Enable readline by default for command-line history/editing/completion</title>
<updated>2026-10-25T17:54:23Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-25T17:54:23Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=fc873933e21d9188f2b5a9d13764f00b6ce68ec9'/>
<id>urn:sha1:fc873933e21d9188f2b5a9d13764f00b6ce68ec9</id>
<content type='text'>
USE_READLINE was a complete, working, opt-in-only feature -- input.c's
readline()/add_history() integration had no known reason to still be
gated behind a flag nobody was passing. Flips it to default-on
(renamed to a DISABLE_READLINE opt-out, matching the project's own
ifndef-DISABLE_* convention) and fixes the build: readline/history
live under the FreeBSD ports prefix (/usr/local), which this box's
clang/ld don't search by default, so linking bare -lreadline failed
until -L/usr/local/lib (LIBRARIES) and -I/usr/local/include (PROFILE)
were added alongside it.

Unlike the GENERATE_*_BITBOARD cleanup, kept DISABLE_READLINE as a
real escape hatch rather than removing the toggle outright -- this
introduces an actual external library dependency (readline-devel),
not just dead source-level conditionality, so a box without it
installed still needs a way to build.

Verified: both the new default build and `DISABLE_READLINE=1` build
clean (confirmed via ldd that only the latter has no libreadline.so
dependency), and a clean DEBUG=1 TEST=1 self-test run under the new
default with no assertion failures. Batch mode (used by all of this
project's scripted testing) never touches the input thread/readline
path at all, so this has no effect there.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Make bitboard move generation unconditional; delete the mailbox fallback</title>
<updated>2026-10-25T17:39:58Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-25T17:39:58Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=f97b2710981e936cfb500ad0cdf1ceab233efad8'/>
<id>urn:sha1:f97b2710981e936cfb500ad0cdf1ceab233efad8</id>
<content type='text'>
All GENERATE_*_BITBOARD/EXPOSESCHECK_BITBOARD/ISATTACKED_BITBOARD/
UNBLOCKED_BITBOARD flags were already default-on (ifndef
DISABLE_BITBOARD_MOVEGEN); this removes that opt-out entirely (GNUmakefile)
and deletes the mailbox-era code the flags used to toggle between, since
there's no longer a build configuration that still needs it:

- generate.c: the mailbox per-piece generators and escape-generators
  (GenerateKnight/GenerateWhiteKnight/GenerateBishop/GenerateRook/
  GenerateQueen/GenerateBlackKing/GenerateWhiteKing/GenerateWhitePawn/
  GenerateBlackPawn and their SaveMe* escape-generator counterparts),
  _FindUnblockedSquares and its DEBUG-build cross-check inside
  _FindUnblockedSquaresBB, and the dispatch-table #if/#else pairs that
  chose between mailbox and bitboard functions (now unconditionally
  bitboard). WouldGiveCheck simplified to its UNBLOCKED_BITBOARD-only
  body. g_iBDeltas/g_iRDeltas (orphaned once their only consumers were
  deleted) removed; g_iNDeltas (still used by data.c's InitMagic) moved
  there, next to its real consumer.
- movesup.c: mailbox FasterExposesCheck/ExposesCheck/ExposesCheckEp/
  IsAttacked/InCheck deleted; chess.h's three-way macro choice
  simplified to a direct #define onto the BB versions (ExposesCheckBB/
  IsAttackedBB/etc. are simply "the" implementation now).
- testsee.c/testgenerate.c: deleted the mailbox-vs-BB comparison/speed
  harnesses that only existed to validate code just removed
  (TestIsAttackedBB, TestGenerateKnightSpeed/KingSpeed/RookSpeed/
  BishopSpeed/QueenSpeed, TestFindUnblockedSquaresSpeed's mailbox arm).
  TestGetAttacks and the _GenerateAllMoves-vs-_GenerateAllMovesBB
  dispatch-overhead benchmark are unaffected -- GetAttacks' mailbox/asm
  predecessors were already retired independently, and that pair
  compares two still-live BB-only dispatch strategies, not mailbox vs
  BB.

Verified: clean release build and a clean DEBUG=1 TEST=1 self-test run
(move generation, perft-style legal-move checks, SAN/ICS parsing,
search smoke test) with no assertion failures.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Make the FH% regression model the default; remove data.c's retired tables</title>
<updated>2026-10-25T17:16:16Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-25T17:16:16Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=3927a4cac6ec254a8e34a5e864300f75c3efc9f3'/>
<id>urn:sha1:3927a4cac6ec254a8e34a5e864300f75c3efc9f3</id>
<content type='text'>
Flips FH_USE_REGRESSION_MODEL from an opt-in safety toggle to the only
behavior: fhmodel.c's quantized logistic regressions are now always
used, with no build-time choice left between them and the old
histogram tables. Removes g_uJointFHThousandths/g_uBetaFHThousandths
and their _NNUE counterparts from data.c (4085 lines), their extern
declarations from chess.h, and the now-dead
_JointTablePredictedFHThousandths/_BetaTablePredictedFHThousandths
lookup functions and their supporting bucket helpers
(HangCpBucket/GapCpBucket/MoveNumBucket/DepthBucket/ImproveBucket/
GapBetaBucket/_ShipDepthBucket/_ShipLateBucket) from searchsup.c, along
with the uRawDepthB/uRawImproveB plumbing that only existed to feed
them (search.c, split.c, SearchOneMove's parameter list).

Also drops the beta-side static-nullmove skip threshold's provisional-
96000 #else branch -- 99000 is now simply the default, confirmed (not
provisional) by docs/REGRESSION.md's trust-audit sweep (+291 Elo over
96000 at 200 games, LOWER95=0.79).

Verified: clean release build, clean DEBUG=1 TEST=1 self-test run (no
asserts), and a fresh head_reference/ checkpoint
(head-reference-20261025 -&gt; 874ef04's regression-model binary) with
sd10/sn5000000 solve counts on the three curated suites essentially
matching the previous table-based checkpoint (70/191 vs 71/191 sd10,
93/191 vs 96/191 sn5M).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Learned FH% logistic-regression model (alpha full, beta whole-node-only)</title>
<updated>2026-10-25T16:55:30Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-25T16:55:30Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=874ef0478078ef4eb436af7d2b68a353f61f4d70'/>
<id>urn:sha1:874ef0478078ef4eb436af7d2b68a353f61f4d70</id>
<content type='text'>
Replaces the data.c histogram tables (g_uJointFHThousandths/
g_uBetaFHThousandths and their _NNUE counterparts) with two quantized
logistic regressions as the default FH%-prediction mechanism under
FH_USE_REGRESSION_MODEL=1, trained against real collected search data
(fhcollect.c) per docs/REGRESSION.md.

Alpha side is a full per-move regression, validated against the old
table (0.119pp weighted mean deviation across 10,684 matched cells).
Beta side is whole-node-only (matching the old table's own scope --
no functionality lost), refit on the NO_MOVE population after an
earlier mixed-tier fit saturated and over-pruned; its skip threshold
(FH_BETA_SKIP_THRESHOLD=99000) was confirmed by direct self-play
sweeps, not just ECM solve-count intuition (see docs/REGRESSION.md's
"Category A vs B" discussion).

Gated by an overnight SPRT match (regression vs HEAD tables, same
NNUE eval, sd8): at 4120 games, llr=+2.53 (never dipped negative,
peaked +2.8), score=0.517, candidate reaching comparable depth at
higher nps/ebf than head. Did not cross the H1=+2.94 SPRT bound, but
the trend across the full run is unambiguous and the marginal value of
waiting out the remaining games was judged not worth it.

The data.c tables are left in place as the inactive #else branch of
FH_USE_REGRESSION_MODEL for now; a follow-up commit will flip the
build default and remove them once this commit's head_reference
baseline and ECM numbers are recorded.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>More aggressive move reductions; Wilson bound overrides static signal when available</title>
<updated>2026-10-24T16:07:03Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-24T16:07:03Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=5f901d737b17c5f99b6e456d6c0c7d5e3c093dc4'/>
<id>urn:sha1:5f901d737b17c5f99b6e456d6c0c7d5e3c093dc4</id>
<content type='text'>
SPRT playtest (2000 games, sd/depth matched): -535 =875 +590, score=0.514,
llr=+1.04 (floated around +1.0 the whole run, never went negative) toward
H1=+2.94. Not a confirmed win, but searches meaningfully deeper (avg depth
14.35 vs 13.50, EBF 3.131 vs 3.305) at no measurable strength cost -- worth
banking and continuing to accumulate games against.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>NNUE search integration: FH% tables, NODE_TYPE refinement, TREEDUMP rewrite</title>
<updated>2026-10-23T20:19:17Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-23T20:19:17Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=69525eb2f42ea9cca8255f6cc3b85e6b1312fd05'/>
<id>urn:sha1:69525eb2f42ea9cca8255f6cc3b85e6b1312fd05</id>
<content type='text'>
- data.c/chess.h: widen the joint FH% table's late axis 16-&gt;17, dedicating
  the new bucket 0 to a whole-node ("will something here fail high at
  all") sample rather than sharing it with the first-leftover-move curve.
  Add an NNUE-eval counterpart table (g_uJointFHThousandths_NNUE) and an
  NNUE counterpart of the beta-side table, selected at runtime based on
  whether NNUE eval is active.
- search.c/searchsup.c: add _RefineNodeTypeGuess, which uses the FH%
  tables (via g_NodeTypeGuessTable) to refine a node's NODE_TYPE guess
  instead of the old heuristic; thread uParentMoveFHEstimate through
  PLY_INFO so a node can see its parent's prediction for the move that
  led to it.
- generate.c: initialize sKillers[2]/[3].uBonus unconditionally (an
  uninitialized-stack read caught by valgrind under LTO; harmless today
  but a real bug).
- dumptree.c/chess.h/GNUmakefile: replace the long-dead DUMP_TREE XML
  tree dump (written for a GUI this project no longer has) with TREEDUMP,
  a flat, path-keyed move/result trace meant to diff against an
  identically-instrumented Stockfish build -- see docs/TREEDUMP_FORMAT.md.
- docs/DEEPER_PRUNING.md: notes on closing the search-efficiency gap vs
  Stockfish.
- crafty_egtb/stockfish_xboard/typhoon_nnue: wrapper scripts so
  match_play.py's Engine class can drive Crafty, Stockfish (via
  PolyGlot), and an NNUE-enabled typhoon the same way it drives HCE
  typhoon.
- tests/lop: rename lop.txt -&gt; lop.ep_; update compare_lop.cpp.
- annotate_pgns: add aggregate_marginsafety.py, dedup_pgn.py,
  fit_lazy_probe.py; update annotate.py/run_nnue_pipeline.py for the NNUE
  training pipeline.
- .gitignore: ignore compile_commands.json (clangd build DB) and
  eval.nnue (a symlink into the untracked head_reference/ comparison
  baseline), neither of which belongs in the repo.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Fix FH_TABLE_COLLECTION makefile wiring; disable LMP censoring during collection builds</title>
<updated>2026-10-21T01:37:45Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-21T01:37:45Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=4e6e78fc767985b9a06ce0fc5dda392027bcc25a'/>
<id>urn:sha1:4e6e78fc767985b9a06ce0fc5dda392027bcc25a</id>
<content type='text'>
FH_TABLE_COLLECTION was referenced in search.c/searchsup.c/chess.h but
had no GNUmakefile wiring to a compiler define -- collateral damage
from the 2026-10-14 PRUNE_VERIFY/PRUNE_LIVE dead-flag cleanup, going by
GNUmakefile.orig's surviving pre-cleanup copy, which still had it.
Every "collection" build in between compiled clean but silently never
included the instrumentation. Restored.

Also: under FH_TABLE_COLLECTION, force fThisMoveLMPPruned = FALSE
instead of calling ShouldLatePrune. ShouldLatePrune is itself partly
driven by uPredictedFH -- a lookup into the CURRENTLY LOADED data.c
table -- so leaving LMP live during a collection run meant the old
(soon-to-be-replaced) table decided which moves were "safe enough" to
go unobserved by the new collection, silently censoring the very
population the new table is supposed to measure. Collection builds now
always try every eligible leftover move.

Found while preparing the NNUE-eval dual-table (alpha+beta FH%)
re-collection run (docs/NNUE.md's eval-swap calibration checklist,
section 3) -- the current tree's collection instrumentation also turns
out to be missing the 5D alpha table's `improve` axis and the entire
beta-side table relative to the original dual_table_run collection;
that restoration is tracked as separate, larger follow-up work.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Wire NNUE eval into search, build the training pipeline, and add a fast</title>
<updated>2026-10-20T16:26:44Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-20T16:26:44Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=c8c6bbc2c1a103e5b07b079a0f8bdf66c7724d80'/>
<id>urn:sha1:c8c6bbc2c1a103e5b07b079a0f8bdf66c7724d80</id>
<content type='text'>
move/eval-agreement benchmark against Stockfish (tests/lop)

Implements the architecture decisions in src/docs/NNUE.md: HalfKAv2_hm
feature set, int16/int8 quantization, per-ply accumulator backup for
king moves, and a lazy-eval-ready integration into search.c/eval.c.
Adds the full annotate_pgns/ training pipeline (extraction, K-fit/
labeling, quiet filtering, tensor building, training, export) plus an
end-to-end orchestrator (run_nnue_pipeline.py) and validation tooling
(sanity_check_net.py, compare_vs_stockfish_net.py). Adds tests/lop:
a 10k-position list-of-positions benchmark comparing engine eval rank
and move choice against pre-computed Stockfish/lc0 references, much
cheaper than a full self-play match for day-to-day progress checks.

First sd10-matched self-play result against classic HCE: LLR +2.28
(H1=+2.94), -72=119+169 over 360 games, confirming the net is
"smarter per node" at matched depth. A real-time (--st 4) match is
currently weaker (candidate loses ~3 plies of depth to a ~3.3-3.7x
nps gap against an unrecalibrated search), consistent with
NNUE.md's adoption-criterion section -- pruning/reduction constants
still need retuning for the new eval's score scale before a fair
real-time comparison is possible.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Wire NNUE eval into real search path; fix critical perf regression</title>
<updated>2026-10-15T23:26:09Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-10-15T23:26:09Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=1ea81bae240912efb8cebabae35fe58bc5d12afb'/>
<id>urn:sha1:1ea81bae240912efb8cebabae35fe58bc5d12afb</id>
<content type='text'>
Load-and-call infra: export_nnue_weights.py-produced weight files can
now be loaded at runtime (nnueeval.c's NnueLoadWeights, "nnue load"
command, --nnueweights CLI flag for scripted match-play), and the FC
head (NnueForward) runs the real forward pass against the live
accumulator.

Real integration: Eval() now short-circuits to NnueForward when
g_fUseNnueEval &amp;&amp; g_fNnueWeightsLoaded, so ./typhoon can actually play
on the NNUE eval -- ./eval_tune/test_vs_head.sh works unmodified to
compare it against the old HCE. ReInitializeSearcherContext (root.c)
gained a defensive accumulator recompute hook covering setboard's
downstream effect on the next go/ponder; it only fires when both
kings are validly placed, since testrecogn.c's synthetic positions
don't always have one at the moment this is called.

Critical fix: the NNUE accumulator maintenance added to move.c's 8
primitives (and MakeMove/UnmakeMove's king-move backup/restore) was
running unconditionally, on the mistaken assumption this was cheap.
It cost ~50% of engine throughput even with NNUE off (673k nps
measured vs the expected 1.4-1.5M baseline). Gated all of it behind
g_fUseNnueEval; classic HCE is back to ~1.52M nps, NNUE eval runs at
a fair ~409k nps (expected for an unquantized float32 forward pass).

This gating also exposed that testnnue.c's TestNnueAccumulator never
set g_fUseNnueEval, so its own direct NnueAccumRecompute calls raced
ahead of now-gated-off incremental maintenance; fixed by having the
test set/restore the flag around its run.

board.c: added en passant legality check to IsBoardLegal (rank vs
side-to-move, origin-square occupancy) -- a real, previously-latent
bug surfaced while building the NNUE test fixtures, unrelated to
NNUE itself.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
