Better understanding of stuff like KRKRB == drawish, Q's come off = !drawish Pawnhash size tunable from cmdline again Think about when to avoid nullmove pruning based on checks in the line Think about futility pruning when a >= +NMATE r2r4/p4pk1/2p3p1/1p1nPR2/5p1Q/2N5/PPPq4/1K4R1 w - - 0 0 -- check extension crazy 2026-09-17: search.c's extended futility pruning (VALUE_KNIGHT/VALUE_ROOK gate, PREPARE_TO_TRY_MOVES) now folds in iImprovement + iPositional[side] (the same per-tier p50 typical-value estimate QSearch's own futility gate uses), but still uses one flat margin regardless of which Eval() exit tier (PLY_INFO.uLastPositionalSource) produced iEval/iPositional at that node. iPositional corrects for a tier's mean bias, not its spread -- a coarse (super-lazy) read could carry more variance/tail risk than a fine one even after mean-centering. Rather than a categorical "never prune from super-lazy" rule (rejected as too blunt/magic-number-shaped -- iPositional already replaces trust-by-category with trust-by-measured-bias), the principled fix is a real EFP_BASE_MARGIN_BY_SOURCE[tier], calibrated the same way as QSearch's FUTILITY_BASE_MARGIN_BY_SOURCE (CALIBRATE_QSEARCH_ FUTILITY-style harness: search the would-be-pruned move anyway, bucket surprise rate by distance-short-of-bar AND uLastPositionalSource, check whether the tiers actually need different margins here too). Not yet built -- deferred behind self-play validation of today's iPositional/ futility-margin changes landing first. 2026-09-18: build two eval.c lazy-margin variants and compare all three (current/baseline, "messy but fast", "super conservative") head to head. "Messy but fast": tune iResidualP2/98ByOwnArmy + iSuperLazyResidualP2/98- ByOwnArmy to accept ~5% exceeded rate against tests/cross_sectional_200k.ep_ (search under each root position, not just eval in isolation -- want real search-tree exceeded rate, not a static-position read). "Super conservative": tune the same tables so the 200k set hardly ever misses. Then: (1) self-play match_play.py between baseline and each variant, and between the two variants directly; (2) full tests/ecm.ep_ run (not a curated subset) under each of the three binaries, same sd, compare solve counts. Interesting because it's a direct speed-vs-soundness tradeoff measurement on the exact margin mechanism this session spent a day mistakenly blaming for the 2026-09-17 regression (see recent git log/session notes) -- the real bug was searchsup.c's CommonSearchInit PV staleness, not the margins, so this is now safe to actually explore properly with a real experiment instead of blind percentile-guessing. Cheaper derivation than 11 rounds of guess-rebuild-rerun (see eval.c's guess #1-11 history comments): PersistMarginSafetyHistogramsToFile (calibrate marginsafety, CALIBRATE_MARGIN_SAFETY build) persists the raw per-bucket SWING distribution (real full-eval score minus the lazy/super-lazy estimate), not just a boolean exceeded count -- so one calibration run against the 200k pool lets a script sweep any candidate margin M against the persisted histogram and read "fraction of swings > M" directly, giving the M for ~5% exceeded and the M for ~0% exceeded from the SAME run. eval_tune/lazy_eval_margins/ aggregate_marginsafety_shards.py already sums bins across shards; needs a small "exceeded rate at threshold M" query added alongside hist_percentile.py's percentile query. Caveat (documented repeatedly in eval.c's guess history): the recorded swing distribution is itself shaped by whatever margin was actually compiled in during that calibration run -- a different final M reshapes the search tree, which changes which nodes even reach the swing-recording code -- so this gives a very good first estimate for both variants, not a substitute for the real match_play/ecm validation once each variant is actually built. 2026-09-18: sharded ASSERT-fuzzing harness -- the technique that found the CommonSearchInit PV-staleness bug (DEBUG binary + gdb, batch mode, `script .ep_` at a shallow sd, let it run until either an ASSERT/SIGSEGV or clean exit) generalizes well and should be run routinely, not just once ad hoc. Shard tests/cross_sectional_200k.ep_ (or any large stratified pool) into N pieces and run N concurrent DEBUG-build _typhoon processes under gdb, one per shard, each logging to its own file; investigate any hit (assert message + gdb backtrace) the same way this session did. Needs a full core budget (N cores) so it collides with self-play match runs -- schedule it for whenever the box isn't already saturated by match_play.py workers, not concurrently with them. 2026-09-18: blunders.pgn review (typhoon vs Crafty losses). 2026-09-18: consider adding cLikelyUnstoppablePasser to the POSITION structure and using it to extend searches. Just like we already do with cTrapped and cEnprise -- signals from eval.c to affect search. 2026-10-01: It seems like a Q vs RR in a pawnful endgame... Q dominates. In general, we should work on encouraging the engine to maintain a good pawn structure -- read more of that pawn power in chess book. Work on the pawn eval specifically. Maybe do eval learning on only the pawn hashed eval terms. 2026-10-04: We need to go faster. - Can we get rid of rgSquares[] completely and use bitboards everywher? - Profile and speed hunt again? - Eliminate SEE calls? - profile a super-lazy-always (material-only Eval() exit, forced unconditionally, throwaway diagnostic build a la the zero- selection-budget move-ordering trick -- never commit it) binary against Crafty at matched depth/positions to find out where our nps actually goes once eval is ~free. Scott's educated guess is SEE and the is-this-move-a-check / legal-reply-to-check generators dominate once eval is out of the way, not eval itself -- also suspect given the earlier `<=`-to-`<` SEE-skip experiment on line ~4178 (skip SEE on same-piece- for-same-piece captures) barely changed speed but made trees fatter (more nodes needed), i.e. that SEE call was doing real pruning work, not just costing time. Before assuming check-detection costs are irreducible, confirm "is this move a check" is answered via O(1) bitboard AND against the enemy king square from the post-move attack bitboard, not via a generate-and-filter pass -- Scott believes he already does this "cheaply" but isn't sure it's the fastest available method. Single-legal-reply-to- check detection is likely the genuinely unavoidable cost of the three (needs a real legal-move count in the child position) -- worth checking whether it early-exits as soon as a 2nd legal move is found rather than generating the full list. 2026-10-04: Reign in extensions the make the tree smaller. 2026-10-04: Modify self-play harness to record eval score / PVs per move in the PGN output.