TYCHO Search

Search documentation

    ↑ ↓ to move · Enter to open · Esc to close

    What the next program says the language needs

    Status: DONE 2026-08-04. Program: chess engine (tools/tycho-chess/, ~740 lines). Outcome: perft against published ground truth, alpha-beta + parallel root search shipped, make chess-check green; no language gap — third zero-defect program in a row.

    This plan is a fresh clone, 2026-08-03: the completed tycho-kv plan lives at docs/internals/plan-tycho-kv-DONE.md. The rule from that plan holds here: *does anything that is not the program written to want it need this?* A finding becomes a phase only when a second, independent caller exists.

    The program

    A chess engine -- tools/tycho-chess/, bitboards (64-bit ints are the natural piece map), move generation, make/unmake, perft, and alpha-beta search with a transposition table and a PARALLEL root search via parallel for.

    Phase 1 is DONE (2026-08-04): the engine and its gate. main.ty is ~740 lines: bitboard Board (6×2 piece maps + side/castling/ep), attack tables, FEN parser, pseudo-legal movegen, make_move returning a NEW board (value semantics, no unmake), legality via make + in_check, perft with per-root divide output, CLI perft <depth> [fen]. make chess-check (in make ci as step [3j/16]) asserts perft totals against PUBLISHED ground truth, and the divide transcript is golden-locked.

    The differential is GROUND TRUTH: perft (legal move counting) has exact published values for standard positions. Verified totals (GoBit's perft_tests.txt):

    perft(5) on the start (4,865,609), Kiwipete(4), and Position 3(6) (11,030,083) are the deep checks; the divide goldens lock the per-root counts, which move if any subtree's movegen changes even when the total doesn't. The gate also asserts five edge positions (bare castling, king endgames, an en-passant capture, both-side promotions) against the python-chess oracle -- the standard positions' trees never reach a promotion or an ep capture, so the published totals alone cannot see those code paths. ~3M nodes/sec; the gate runs ~10s.

    Phase 2 (search) is DONE (2026-08-04): negamax alpha-beta, quiescence, PST eval, exact-only transposition table, search <depth> [fen] CLI, and the search assertions in make chess-check (determinism, TT-invariance, three exact tactical probes). Parallel root search via parallel for shipped — see below.

    Findings

    Phases

    (none -- the program's own milestones below are next; a finding becomes a phase only when a second, independent caller needs it)

    Next program milestone -- alpha-beta search + transposition table [DONE 2026-08-04]

    The search shipped in the same commit as the engine: negamax alpha-beta with an exact-only transposition table (below), a PST-based eval (the classic simplified-evaluation tables), and a quiescence search. search <depth> [fen] reports per-root scores and the best line; make chess-check asserts the search properties. Three bugs were caught on the way, all by the differential:

    1. The negamax leaf sign. evaluate() is white-positive; the depth-0 leaf must return the value from the SIDE-TO-MOVE's perspective. Returning the unnegated eval inverted every odd-depth-from-white search -- caught by the Rxc2 probe (depth 1 said -500 for winning a free queen, depth 2 said +500).
    2. The horizon effect (not a search bug, a missing quiescence). At depth 6 from the start every move scored -100: the last ply's eval "won" a pawn (3...Nxf7) one ply short of the recapture. Quiescence (captures + checks, stand-pat cutoff, delta pruning, depth caps) fixed it.
    3. No quiet-move ordering = no pruning. order_captures only lifted captures; in capture-free positions the alpha-beta degenerated to ~full search -- measured 16x per ply (d3 116ms -> d4 1.9s -> d5 26s -> d6 3min). PST-aware move ordering collapsed it to the sqrt-shape (d6 3.1s).

    The TT is deliberately EXACT-ONLY and same-depth: bound entries are never stored, so a probe returns only the true minimax value at that depth and the TT provably cannot change a result -- it cuts transpositions and reorders moves. The gate asserts exactly that (search twice is byte-identical and search-nott reports the same best line) plus three exact tactical probes: the rook taking a free queen (+510), a hanging queen (+510), and the scholar's mate (Qxf7# = 100000). The PST-only eval keeps quiet-position values approximate (depth-5 from the start ties many moves at 0); the probes pin the parts that must be exact.

    Next program milestone -- parallel root search [DONE 2026-08-04]

    search_root_par fans the root moves out over parallel for, one full-window search per task, results sent on a Channel and merged by (score, index). Result: parallel for handles the dynamic work set (the root move count), per-item results leave the loop through a channel (the only legal outer write beside +/* reductions), and the merged best line equals the serial search's on every gated position, with byte-identical repeats. The TT needed no sharing decision: the loop deep-copies its captures per chunk, so each task searches with its OWN exact-only table from cold -- correct by construction (the TT provably never changes a result) and no race. Cost: with no shared best-so-far to prune against, every task runs a full-window search, so the parallel root is a correctness probe, not a speedup -- measured 13s vs 2.8s on kiwipete d4; the gate runs it on the fast positions (start d5, pos3 d5, scholar d3).