<feed xmlns='http://www.w3.org/2005/Atom'>
<title>typhoon.git, 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-09-09T03:25:26Z</updated>
<entry>
<title>Remove dead autoplay/autoplayer trees, land GNUmakefile/eval_tune companions</title>
<updated>2026-09-09T03:25:26Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-09T03:25:26Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=ac1db917dc50e372806780bc0ab2cc0570d8ca54'/>
<id>urn:sha1:ac1db917dc50e372806780bc0ab2cc0570d8ca54</id>
<content type='text'>
- Remove autoplay/ and autoplayer/ entirely: old opponent-automation
  scrapers/harnesses (child_process.cc test scaffolding, compiled a.out
  binaries and a .dSYM bundle, saved book/position files, macOS
  ._-prefixed resource-fork cruft) that predate this repo's current
  tooling and were never referenced by anything still in use.

- GNUmakefile: add DIAG_NO_QSEARCH_FUTILITY/CALIBRATE_QSEARCH_FUTILITY
  profile flags and testrecogn.o to the TEST=1 object list. Both are
  companions to already-committed work that never got their own build
  support committed: search.c's qsearch-futility calibration harness
  needs the two profile flags to be buildable at all, and testrecogn.c
  (added alongside the recogn.c bugfix, now committed here too) needs
  to be in TEST=1's OBJS to actually compile/link.

- eval_tune/match_play.py, eval_tune/test_vs_head.sh: real fixes found
  and applied earlier this session -- match_play.py's opening-book
  leak (games weren't actually book-free), missing --hash/--cpus
  (games ran on the 64k-entry/single-cpu memset-zero defaults instead
  of this project's normal 256m/1cpu), a shared-logfile race across
  concurrent match workers, and a report-parsing deadlock on engine
  resignation. test_vs_head.sh reverted to comparing against
  head_reference/typhoon + the live working-tree binary -- it had been
  pointed at a since-deleted, long-stale one-off comparison binary
  (typhoon_allbitboards) since 92fc412, silently invalidating every
  "vs head" self-play check run through it since Sep 4.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
</content>
</entry>
<entry>
<title>Fix BOOC opposite-bishop check, extract drawish-scaling, trim lazy margins</title>
<updated>2026-09-09T03:18:20Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-09T03:18:20Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=88a0787a7d19a5b4e19e540816f1d500e02dbfeb'/>
<id>urn:sha1:88a0787a7d19a5b4e19e540816f1d500e02dbfeb</id>
<content type='text'>
Three changes landed together, verified via the usual pipeline (release
+ DEBUG build, DEBUG smoke test, 40-game st1 match vs clean 434fa04,
score 0.600, llr +0.24):

1. New BOOC (bishops of opposite color) endgame drawish-scaling term
   had a real bug in its opposite-color check:

       (pos-&gt;uWhiteSqBishopCount[WHITE] &amp;&amp; !pos-&gt;uWhiteSqBishopCount[BLACK])

   only detects one of the two possible opposite-color configurations
   and silently misses the mirror case (White dark-squared / Black
   light-squared). Since each side's bishop-square-color flag is 0 or 1
   whenever uNonPawnCount[side][BISHOP] == 1 (already checked above),
   "opposite colors" is exactly the XOR of the two flags:

       (pos-&gt;uWhiteSqBishopCount[WHITE] != pos-&gt;uWhiteSqBishopCount[BLACK])

2. Extracted the winning-chances/BOOC/fifty-move drawish scaling out of
   Eval() into its own EvalLookForDrawishSituations(pos,
   &amp;iScoreForSideToMove) helper -- same semantics, cleaner separation.
   Fixed two small issues in the extraction: missing `static` (every
   other file-local eval.c helper is static; this had accidental
   external linkage with no prototype anywhere) and a typo in a new
   EVAL_DUMP trace string ("At of all pieces" -&gt; "After all pieces").
   Also reordered Eval()'s two king evaluations to go side-to-move
   first / enemy second (via the already-cached uColor/xColor) instead
   of always BLACK-then-WHITE -- confirmed safe, no dependency between
   the two _EvalKing calls (each only reads attack-bitboard data
   already populated by earlier phases).

3. Re-calibrated and trimmed SUPER_LAZY_MARGIN_BY_ARMY and
   iSwingFloorByArmy. Both were originally derived by measuring
   symmetric |real - lazy| swing, which conflates a swing *toward* the
   alpha/beta boundary (the only direction that can make an exit
   unsound) with a swing *away* from it (harmless). Re-ran
   CALIBRATE_MARGIN_SAFETY against the same 1500-position
   tests/twic_sample.ep_ (sd 8) with the harness fixed to measure only
   the dangerous-direction swing: true max ran 15-50% below the old
   symmetric measurement in most material buckets, several averaged in
   the single digits, and every bucket showed exceeded=0 even before
   adding any headroom back.

   iSwingFloorByArmy: {1069,1069,1069,974,821,876,796,754}
                    -&gt; {297,297,297,286,461,453,582,600}
   SUPER_LAZY_MARGIN_BY_ARMY: {2000,1800,1800,1750,1000,850,850,850}
                            -&gt; {300,1635,1800,1070,946,850,734,698}
   (buckets 2 and 5 unchanged -- already tighter than a fresh 15%
   headroom over the new directional max would give)

   Motivation: the board-representation-migration branch exists to
   close a measured 5x nps gap vs Crafty on the same CPU (profiled:
   typhoon spends more of its search time in Eval() than Crafty does
   in evaluate()); every lazy/super-lazy exit that fires is Eval()'s
   fast path, so trimming unnecessary margin headroom directly
   increases how often the cheap path is taken instead of a full
   evaluation.

Not yet done: splitting alpha-margin and beta-margin into independent
per-bucket values (currently symmetric per bucket, no principled reason
they need to be) -- would need another calibration pass tracking the
two separately.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
</content>
</entry>
<entry>
<title>Land super-lazy exit, material-based lazy floor, qsearch futility rework</title>
<updated>2026-09-09T01:47:22Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-09T01:47:22Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=379a03bbd993247c8de9c1f1b163fcfab2fa1d69'/>
<id>urn:sha1:379a03bbd993247c8de9c1f1b163fcfab2fa1d69</id>
<content type='text'>
Brings in the last remaining piece from stash@{0}: the super-lazy exit
point (material-only pre-check before the regular lazy gate), a
material-bucket floor under the regular lazy exit's margin
(iSwingFloorByArmy), and search.c's qsearch futility rework
(FUTILITY_BASE_MARGIN_BY_SOURCE, indexed by which Eval() exit tier
produced the score). Required Eval()'s signature change from a single
SCORE* to SCORE(*)[2] (positional estimate per side instead of one
munged magnitude) -- search.c's futility margin folds in
rgiPositional[pos-&gt;uToMove], which the earlier bad-trades investigation
found to be a meaningfully predictive signal.

search.c and chess.h brought in wholesale from the stash (both were
either completely untouched by prior commits or contained no
divergence worth preserving). eval.c required hand-merging on top of
this session's already-applied bad-trades fix, B-over-N removal, and
xColor/reorder cleanups -- ported the super-lazy exit block, the
regular-lazy material floor, the per-color piPositional writes (all
three exit sites: super-lazy, regular-lazy x2, full-eval), the
super-lazy calibration harness (RecordSuperLazyMarginSafetySwing,
dual-regime DumpMarginSafetyCalibration), and moved uArmyScaler/
uNumTrapped initialization to match the new ordering the super-lazy
exit depends on.

Verified: clean release + DEBUG build (only the previously-flagged
_EvalTrappedPieces warning), DEBUG smoke test pass, and a 40-game
st1 match against clean 434fa04 (score 0.487, llr -0.04) -- landing
this margin machinery as-is from the stash, before any retuning, does
not regress strength on its own. This confirms the original
regression (0.15-0.225 score seen early in this investigation) was
fully explained by the bad-trades unsigned-underflow bug, not by these
margins being unsound.

Values are the original, as-derived-from-calibration ones (see
inline comments: SUPER_LAZY_MARGIN_BY_ARMY from 100 positions/sd8
with ~15-20% headroom, iSwingFloorByArmy from 1500 positions/sd8
with ~25% headroom, FUTILITY_BASE_MARGIN_BY_SOURCE from a separate
1500-position/sd6 surprise-rate calibration). Not yet retuned --
suspected to carry more headroom than necessary, which costs real
search speed (speed=depth). Next step: re-run the margin-safety
calibration fresh against the current build and trim the super-lazy
and regular-lazy floor headroom down from the built-in database,
leaving the qsearch futility margins alone (different calibration
method, already risk-tolerant by construction).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
</content>
</entry>
<entry>
<title>eval.c: bad-trades fix, rook cache, xColor speedups, draw scaling, main-body reorder</title>
<updated>2026-09-09T01:15:36Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-09T01:15:36Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=85b71cf793a85074d8d4483b21a2a06c84553d1d'/>
<id>urn:sha1:85b71cf793a85074d8d4483b21a2a06c84553d1d</id>
<content type='text'>
Continues re-applying the eval.c overhaul from stash@{0} (see fbb138c),
each piece verified individually against clean 434fa04 (fast st1 matches,
~30-40 games each) before landing:

- _EvalBadTrades: replaced the TRADE_PIECES/DONT_TRADE_PAWNS lookup
  tables with direct arithmetic (part of a broader "eval.c is too slow,
  cut lookup tables where possible" effort). The stash's first attempt
  at this had a real bug: uInverseBehindPieceCount was computed by
  subtracting a raw material sum from 9 in unsigned arithmetic, which
  underflows to ~4 billion in any non-bare-endgame position with
  unequal material -- confirmed via match_play (score 0.15-0.225 over
  20 games vs clean 434fa04, a near-total wipeout). Fixed to use the
  existing uNonPawnCount piece-count field directly instead of
  reinventing it via material math, and restored a cheap zero-pawn
  guard (old DONT_TRADE_PAWNS punished "material lead with zero pawns
  left" specifically -- the classic KNB-vs-KRP false positive -- which
  the arithmetic replacement had dropped entirely). Verified back at
  parity (0.475/40 games) after the fix.
- _EvalRook: ROOK_FULL_HALF_OPEN_BONUS is now a startup-computed cache
  (InitEval(), refreshed on every DNA reload) instead of a per-call
  local array rebuild.
- xColor = FLIP(uColor) cached once instead of recomputed inline:
  _EvalBishop, _EvalKnight, CountKingSafetyDefects (also renamed its
  uSide/xSide params to uColor/xColor to match), and Eval()'s own
  per-piece-type loop.
- _EvalBishopPairs collapsed to one FOREACH_COLOR loop instead of
  duplicated per-side code.
- eval.c "diet" cleanup: removed pos-&gt;iTempScore (a scalar handoff
  between _EvalKing and Eval()'s per-color copy, now redundant --
  _EvalKing writes directly into ctx-&gt;sPlyInfo[ply].iKingScore[uColor]);
  removed several stale comments documenting already-historical
  removals; _EvalPassers renamed to _ReEvalPassers; re-enabled a
  previously-disabled ASSERT in _EvaluateCandidatePasser (confirmed
  live via DEBUG smoke test, does not fire under current DNA).
- Added _SideHasWinningChances (Crafty-style material-only "can this
  side force a win at all" classifier) plus the fifty-move-rule
  dampening in Eval() -- both scale the score toward g_iDrawScore when
  material alone rules out real winning chances, without touching
  Eval()'s signature (kept the existing scalar piPositional convention
  rather than pulling in the super-lazy exit's per-color rework).
- Removed the "B over N in the endgame with 2 pawn wings" term
  entirely, by direct instruction -- BISHOP_OVER_KNIGHT_IN_ENDGAME is
  now an orphaned DNA constant (matches the stash's own choice, which
  left it similarly unused).
- Eval()'s main per-piece-type loop reordered from "all of our minors,
  then all of the enemy's minors, then rooks both colors, then queens
  both colors" to "ours then enemy's, interleaved per type" (knights,
  then bishops, then rooks, then queens). Verified the real dependency
  this ordering has to preserve -- _EvalRook/_EvalQueen read the
  enemy's bbMinorAttacks, _EvalKing reads both colors' bbMinorAttacks/
  bbQueenAttacks, _ReEvalPassers needs the king scores -- and confirmed
  the new interleaving still satisfies it (all minors both colors done
  before any rook/queen; both colors done before king).

Verification methodology throughout: gmake clean release build, DEBUG
smoke test (10 random ECM positions at sd 5, asserts compiled in), then
a fast (st 1, 40-game) match_play.py run against a clean 434fa04
reference binary before landing each piece. A same-binary-vs-itself
control match (score 0.500/20 games) confirms the harness itself has no
structural bias, so per-checkpoint scores in the 0.44-0.58 band across
this session reflect real (if noisy) parity, not measurement artifacts.

Still not re-applied (remains in stash@{0}): the super-lazy exit, the
material-based normal-lazy floor, and search.c's qsearch futility
rework -- these three are one coupled unit (the futility margins index
by which Eval() exit tier fired) and go in as the next, more carefully
scrutinized step, since the original regression that started this
whole investigation traced to exactly this area.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
</content>
</entry>
<entry>
<title>Retire asm GetAttacks, recogn.c/fen.c bugfixes, misc bugfixes verified at parity</title>
<updated>2026-09-08T23:50:55Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-08T23:50:55Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=fbb138cc1dd13da2f30129206cdcd3d128344f54'/>
<id>urn:sha1:fbb138cc1dd13da2f30129206cdcd3d128344f54</id>
<content type='text'>
Confirmed self-play regression traced to a stale test_vs_head.sh reference
binary (typhoon_allbitboards/550ea81, deleted): every "vs head" comparison
since 92fc412 (Sep 4) was checking new work against that fixed Sep-4
snapshot, never against real HEAD or the working tree. Rebuilt clean
reference binaries directly from git and re-verified everything from
scratch.

This commit lands only the pieces confirmed safe against clean 434fa04
(fast st1 match, ~30-40 games, score ~0.44-0.55, consistent with parity;
plus a DEBUG-build smoke test pass):

- recogn.c, fen.c: real bugfixes
- data.c, draw.c, ics.c: whitespace only
- x64.asm: retires the asm GetAttacks implementation now that chess.h's
  GetAttacks macro unconditionally selects the already-verified-faster
  _GetAttacksBB bitboard version instead of a three-way build-flag
  toggle (GETATTACKS_BITBOARD/CROUTINES/asm default)
- see.c, testsee.c: SEE/test-harness updates supporting that default
- root.c: per-tier eval-exit reporting (super-lazy counters currently
  always read 0 -- accurate, since no super-lazy exit exists yet)
- main.c: startup banner update, InitEval() call, TestRecogn() added to
  the #ifdef TEST self-test sequence
- command.c: InitEval() DNA-reload hook, new qsearchfutility diagnostic
- dynamic.c: minor changes
- chess.h: the GetAttacks default change above, three
  FUTILITY_BASE_MARGIN_* compatibility aliases (all still equal to the
  original flat FUTILITY_BASE_MARGIN -- search.c has not been split into
  per-tier margins here), placeholder super-lazy counters, and an
  EvalPasserRaces -&gt; _EvalPasserRacesAgainstLoneKings rename (confirmed
  byte-identical body) to match recogn.c's call site
- eval.c: the same rename, plus a no-op InitEval() stub (nothing to
  initialize until the ROOK_FULL_HALF_OPEN_BONUS cache below exists)

Deliberately NOT included: the full eval.c overhaul (~1770 lines) and
search.c's qsearch-futility rework (~650 lines), including yesterday's
loosened SUPER_LAZY_MARGIN_BY_ARMY/FUTILITY_BASE_MARGIN_BY_SOURCE tables.
Reverting just those two tables while keeping the rest of the eval.c
overhaul still lost badly to 434fa04 (0.20 over 10 games), so the
regression isn't fully explained by the margins alone -- the eval.c
overhaul needs careful, incremental re-verification against this commit
as the new baseline, not a bulk re-apply. Full original work preserved in
git stash (stash@{0} as of this commit) for that follow-up.

Note: two pre-existing, position/state-dependent assertion crashes were
found during this verification (util.c:1093 WalkPV, recogn.c:1359
_SanityCheckRecognizers), both reproducing on unmodified 434fa04 -- not
introduced by anything here, not yet root-caused.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
</content>
</entry>
<entry>
<title>Harden _WhoControlsSquareFast against g_SwapTable out-of-bounds indexing</title>
<updated>2026-09-06T04:53:47Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-06T04:53:47Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=434fa0406e1b01395a2b7f0aa481ca5dc367fa09'/>
<id>urn:sha1:434fa0406e1b01395a2b7f0aa481ca5dc367fa09</id>
<content type='text'>
Add ASSERT(uWhite &lt; 32)/ASSERT(uBlack &lt; 32), the bound that actually
matters for g_SwapTable[14][32][32] -- the existing (&amp; 0xFFFFFF00)
checks only caught garbage above bit 7 and would have passed silently
through the exact out-of-bounds indexing fixed in the previous commit,
had a similar bit-layout mistake been made again in the future.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
</content>
</entry>
<entry>
<title>King-safety recalibration, lazy-eval material floor, eval hot-path trimming</title>
<updated>2026-09-06T04:52:40Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-06T04:52:40Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=9e995e7c39a83ae9b5ba86f3346e0281744bf773'/>
<id>urn:sha1:9e995e7c39a83ae9b5ba86f3346e0281744bf773</id>
<content type='text'>
Recalibrate iKingSwingP90 against the bitboard-rewritten
CountKingSafetyDefects (~1.28B samples via new CALIBRATE_POSITIONAL/
CALIBRATE_BASE_MARGIN/CALIBRATE_MARGIN_SAFETY diagnostic build flags,
board_representation/EVAL.md section 9). Add LAZY_EVAL_MIN_MATERIAL:
measured the regular lazy exit's real swing exceeding its own assumed
margin 20.6% of the time in near-bare-king endgames (vs &lt;=0.36%
elsewhere) -- skip lazy eval entirely below that material floor.
Double the stale search.c/searchsup.c CountKingSafetyDefects extension
thresholds as a stopgap pending their own recalibration.

Eval hot-path trimming (measured via EVAL_TIME, ~1759 -&gt; ~1386 avg
cycles/eval on a representative middlegame position):
  - Pull _GetFileStormDefects out of EstimatePositionalScore's hot path
    (cost more than the "cheap cached lookup" it was assumed to be,
    running on ~90% of all Eval() calls).
  - Add pos-&gt;bbOccupiedSide[2], incrementally maintained alongside
    bbOccupied, so _BuildFriendlySideBB is a field read instead of a
    6-term OR.
  - Switch CoorFromBitBoardRank8ToRank1/Rank1ToRank8 to the existing
    static-inline FastFirstBit/FastLastBit (same bsf/bsr instruction,
    no call/ret overhead).
  - Defer EvalPasserRaces' uRacerDist/fDontCountMeOut past its
    no-passer early return.
  - Remove the mailbox-era "max mobility in a row" term from
    _EvalBishop/_EvalRook (no bitboard-mobility equivalent need for it).
  - Simplify _EvalBishopPairs and rook file-openness/passer bonuses to
    flat DNA-tunable constants instead of distance/pawn-count-scaled
    tables, rook file-openness now a branchless bitboard-indexed lookup.
  - Remove pos-&gt;cPiece (write-only, no reader anywhere).
  - Collapse WHITE/BLACK mirror-branches (castle-rights block,
    rook-trapped-in-corner) to color-indexed constants.
  - Close the PAWN_BIT..KING_BIT gap (bits 7-3 -&gt; bits 4-0), removing
    the bvPattern &gt;&gt;= 3 before its KING_COUNTER_BY_ATTACK_PATTERN
    lookup. This also fixes a real bug introduced earlier this session
    when _WhoControlsSquareFast was converted to read these constants
    directly: g_SwapTable is only [32][32], but the old bit values
    (up to 0xF8) indexed far out of bounds on any attacked square --
    data.c's InitializeSwapTable was always built assuming the bits
    0-4 range this change now actually produces.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
</content>
</entry>
<entry>
<title>Fix _EvaluateCandidatePasser's helper-pawn safety gate (real no-op since 57502d6)</title>
<updated>2026-09-05T18:11:58Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-05T18:11:58Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=53694883d63674a97a3960514dd4e0a4e1b67f01'/>
<id>urn:sha1:53694883d63674a97a3960514dd4e0a4e1b67f01</id>
<content type='text'>
Flagged during the ATTACK_BITV/bvAttacks cleanup (5405191) but left
unfixed there deliberately, per direct instruction, since a real fix
changes eval scoring and deserves its own before/after check rather
than being buried in a mechanical rename commit.

The gate ("is this square safe to advance a helper pawn into, i.e.
not enemy-attacked or already friend-defended") used to read the old
combined bvAttacks word for both colors at the target square. This
function runs from _EvalPawns, the first piece type Eval() evaluates
each call -- no non-pawn piece (and, since commit 57502d6, not even
pawns themselves) has written any attack data yet at this point. That
word has therefore been unconditionally zero, and the gate
unconditionally true (silently disabled), since 57502d6 landed.

Fixed using pos-&gt;bbPawnAttacks -- the one piece-type bitboard that
actually is valid this early in Eval()'s sequence (populated at the
top of _EvalPawns, before this function runs). Narrower than whatever
the original gate covered (pawns only, not every piece type), but a
real, correct check instead of a fake one, and pawns are the dominant
real-world case for contesting a helper-pawn's advance square anyway.
Applied to both the right- and left-side helper searches (the initial
target-square check and the backward walk-and-search loop each need
their own copy, since the loop's own square changes every iteration).

Verified via precommit_check.sh, then all three curated suites at
sd10 (not just ringers, since this is a real eval-scoring change, not
a mechanical one): net +2/191 (113-&gt;115), concentrated in
ecm_hard_quick (18-&gt;22) with a small ecm_confident_quick give-back
(85-&gt;83) and ringers unchanged (10/11) -- the same "gains lopsided
toward hard_quick" shape this session's other real eval changes have
shown, consistent with a genuine (if modest) positional improvement
rather than suite-specific noise.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
</content>
</entry>
<entry>
<title>Retire ATTACK_BITV/bvAttacks and the c|8 shadow-index mechanism entirely</title>
<updated>2026-09-05T17:37:55Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-05T17:37:55Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=5405191f8c519333006417a5a3c31bc3e186e42e'/>
<id>urn:sha1:5405191f8c519333006417a5a3c31bc3e186e42e</id>
<content type='text'>
Now that king (the last piece writing it) has converted to a bitboard
accumulator, nothing writes rgSquare[c|8].bvAttacks any more -- deletes
the whole mechanism rather than leaving a known-dead struct around:

- chess.h: ATTACK_BITV union gone. SQUARE collapses from a
  union-with-ATTACK_BITV to a plain {pPiece, uIndex} struct (the #pragma
  pack(1) that only existed for ATTACK_BITV's bitfield layout goes too).
  UNSAFE_FOR_ROOK/UNSAFE_FOR_QUEEN and the whole-word MINOR_XRAY_BIT/
  ROOK_XRAY_BIT/QUEEN_XRAY_BIT constants deleted -- confirmed unused
  (only ever referenced in stale comments, not code) now that every
  consumer reads bbXAttacks bitboards directly. PAWN_BIT/MINOR_BIT/
  ROOK_BIT/QUEEN_BIT/KING_BIT stay: they're a separate, still-live
  local bit-packing scheme _EvalKing/_WhoControlsSquareFast use to
  build a per-square attack-pattern index into KING_COUNTER_BY_
  ATTACK_PATTERN/g_SwapTable, unrelated to the retired storage struct.

- eval.c: _ClearAttackTables drops its entire macro-unrolled,
  128-square clearing loop (CLEAR_A_SQ/CLEAR_A_RANK/CLEAR_SHORT_RANK,
  all deleted) -- it only ever existed to zero the old per-square
  struct; clearing the 7 bbXAttacks accumulators is the whole function
  now. The transitional _IsSquareAttackedByX/_IsSquareXrayedByX helpers
  (minor/rook/queen/king, 8 functions total) are deleted outright, not
  just simplified -- their only remaining purpose was bridging to the
  now-gone struct, and their DEBUG cross-checks were explicitly
  migration-only scaffolding, not a permanent invariant. Call sites
  (_WhoControlsSquareFast, _EvalKing's bvAttack/bvXray/bvDefend) read
  the bbXAttacks bitboards directly instead. _WhoControlsSquareFast
  simplifies to a flat OR of 8 bitboard membership tests per color,
  down from raw struct reads plus 7 helper calls each.

Found and fixed one real, pre-existing bug while doing this (flagged
and confirmed with the user before touching it, kept as its own
documented change rather than silently folded into the mechanical
rename): _EvaluateCandidatePasser's helper-pawn-safety gate read
rgSquare[c1+8].bvAttacks[...].uWholeThing, but this function runs from
_EvalPawns -- the first piece type Eval() evaluates each call, before
any non-pawn piece (or, since commit 57502d6 retired pawns' own
bvAttacks write, even pawns) has written anything there. That word has
therefore been unconditionally zero, and the gate unconditionally true
(a silent no-op), since 57502d6 landed -- not something today's cleanup
introduced. Left exactly as dead/unconditional (deleted the
now-meaningless condition, kept the body it always ran anyway) rather
than fixed, since a real fix changes eval scoring and deserves its own
before/after check, documented inline for a future session.

Verification: precommit_check.sh (self-test + DEBUG smoke test) passes.
tests/ecm_ringers.ep_ at sd10 vs. the immediately preceding commit:
solve parity holds exactly (10/11 both), and final (depth-10) node
counts are byte-identical for all 11 positions -- the bar for a change
meant to be purely mechanical, unlike king's own conversion. One
harmless artifact noted: ECM.750 has a different depth-6 *intermediate*
best move (a shallow tie-break flip) that already resolves to the
identical PV and node count by depth 7 and holds through depth 10 --
not chased further since the actual (depth-10) result matches exactly
and search is deterministic at --cpus 1.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
</content>
</entry>
<entry>
<title>King attack-table population: bitboard rewrite, fixes a real bug and a real asymmetry</title>
<updated>2026-09-05T17:23:28Z</updated>
<author>
<name>Scott Gasch</name>
<email>scott@gasch.org</email>
</author>
<published>2026-09-05T17:23:28Z</published>
<link rel='alternate' type='text/html' href='https://git.acknak.org/cgit/typhoon.git/commit/?id=aad154a5a07d2793e52a71415cc5c59276a8f629'/>
<id>urn:sha1:aad154a5a07d2793e52a71415cc5c59276a8f629</id>
<content type='text'>
_EvalKing was the last piece type contributing to the old bvAttacks/
ATTACK_BITV mechanism -- both write sites (the low-enemy-material
early-exit path and the main king-safety loop) replaced with a single
pos-&gt;bbKingAttacks[uColor] |= g_KingAttacksBB[c], plus a new
_IsSquareAttackedByKing transitional helper (DEBUG-cross-checked, no
x-ray companion needed since a king can't move through a blocker) for
_WhoControlsSquareFast and _EvalKing's own bvDefend to read.

Two real, intentional behavior changes land with this conversion,
both discussed and confirmed before coding rather than assumed:

1. The main king-safety loop's old per-square write used
   KingSafetyDeltas (11 entries: the 8 real king-move squares plus -2/
   +2, two squares away on the same rank, present only for that loop's
   own file-distance bookkeeping) instead of the real 8-square king
   move pattern -- a bug, confirmed by direct instruction, not a
   design choice worth preserving. Fixed by writing the real
   g_KingAttacksBB[c] pattern once, before the loop, instead of
   whatever KingSafetyDeltas happened to visit per-square.

2. _EvalKing's own bvAttack computation (does the *enemy* king attack
   a square near this king?) is now symmetric where it used to be an
   accidental artifact of evaluation order: kings are evaluated
   black-then-white, so the old mailbox code let white's computation
   see black's already-written king bit while black's could never see
   white's (white hadn't run yet). Rather than preserve or upgrade
   that asymmetry now that both colors go through an explicit helper
   either way, neither side sees the enemy king as a threat here,
   matching the side that already couldn't -- by direct instruction.
   This is narrow in practice (two kings can never be legally
   adjacent, so it only ever fires at king-vs-king distance 2) but
   real.

_WhoControlsSquareFast's own king contribution is unaffected by either
change and stays fully symmetric: it's only ever called after *both*
kings finish evaluating (the passed-pawn re-check and trapped-piece/
danger passes all run after Eval()'s king-eval block), so
pos-&gt;bbKingAttacks is always fully populated for both colors by the
time it runs -- confirmed by checking call-site ordering directly, not
assumed from bvAttack's own (different) situation.

CountKingSafetyDefects is untouched by this change (confirmed by
reading it again): it's pure CHECK_VECTOR geometry over piece
locations, computed before any bbXAttacks accumulator exists this
Eval() call, and reads none of them. Its correlation with _EvalKing's
score is therefore preserved by construction, not something requiring
separate re-tuning.

Verification: since this bundles three attributable behavior changes
(the bitboard rewrite itself, the KingSafetyDeltas bug fix, and the
dropped bvAttack asymmetry), used the ringers-suite-plus-bounded-delta
bar from bishop's conversion rather than expecting exact node-count
equality: precommit_check.sh (self-test + DEBUG smoke test, plus
several hand-built king-adjacency/king-proximity positions run
directly against a DEBUG binary to exercise the new cross-check) all
pass; whole-engine tests/ecm_ringers.ep_ at sd10 vs. the immediately
preceding commit holds exact solve parity (10/11 both), with
per-position node-count deltas (-50% to +76%) all attributable to the
three documented changes above, no unexplained outliers.

ATTACK_BITV/bvAttacks itself is intentionally left in place -- nothing
writes it any more (king was the last writer), but the struct deletion
and the resulting simplification of the transitional
_IsSquareAttackedByX/_IsSquareXrayedByX helpers (which collapse to
plain bitboard reads once nothing can ever populate the old structure)
is staged as a deliberate follow-up commit, not bundled here.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XxmVi2sTMwpPp4i6WYFjan
</content>
</entry>
</feed>
