Repository navigation
refactor(eval): Move each leaf term into a module of its own under eval/ - #249
Conversation
|
Review round 1, folded into 87bab7d. A second agent rebuilt both sides in its own target directory: 3868987 nodes on both, all 18 bench rows identical in every column, 457 to 460 release tests, the three ignored pins passing, clippy and fmt clean. It compared the terms output over 787 positions (bench, tactics.epd, strategy.epd): byte identical apart from the layout line, which covers the whole 812 slot weight vector and every coefficient. The new tune.py agreed with the old on is_material for all 812 slots, on frozen_slots for all 32 hold combinations, and on bounds_hold over three vectors. Every new test was mutation tested and fired. Findings, none behavioural:
Three new rust tests and three new python ones. Airgap and trailers checked. |
eval.rs held three terms and everything they share, and board.rs held the counts all three are read from. So a term was four places: its counts and masks in board.rs, its weights and fold in eval.rs, its slot arithmetic in tune.rs and the same arithmetic again in scripts/tune.py. Adding one meant editing all four and getting them to agree, and nothing said when they did not. eval.rs becomes eval/, with a file per term. mobility.rs, shelter.rs and pawn_structure.rs each own their counts, their masks, their weights with the fit that produced them, their fold, their memo where they have one, and the tests that pin them. The doc blocks explaining each fit move whole. mod.rs keeps the material values, the phase weights, the accumulator and the sum. The slot layout follows from one table. TERMS in eval/mod.rs is a descriptor per term: its name, how many counts it is measured in per side and per half of the taper, its weights and its counts. tune.rs reads its slot arithmetic off that, so SLOTS is a const fn over the table, weight() walks it instead of a nine branch ladder, and the three loops in Terms::of become one. The run prints the result as a second header line, `layout midgame 384 endgame 384 material 6 mobility 4 shelter 7 pawn_structure 8`, and scripts/tune.py derives its slots from that line rather than from ten constants of its own. Its five guards against earlier layouts go with them: a run this file cannot fit is now refused by the name of the term it does not price, which is the case those lengths were standing in for. A header with no layout line is refused too. The search carries one eval::Caches rather than two fields, and the sum is written once over a private memo trait with two implementations, the caches and a unit that recomputes. eval and eval_cached are the two doors on to it. board.rs opens pub(crate) readers on the six piece boards and the king's square, gains sides(color) for the (ours, theirs) match that was written four times, and names no evaluation term at all. Four judgment calls. files_of lives in pawn_structure.rs beside spread, which is its inverse, and shelter.rs reads it from there; the alternative was a bitboard helper in mod.rs, which is not what mod.rs is for. shelter_key moved with the term as shelter::key, which is what leaves board.rs naming no term, and it needed ZOBRIST to go pub(crate). The mobility count reads the knight table through a new knight_attacks reader rather than the whole attack mask table being opened. And the pawn span test stayed in board.rs, since what it pins is a board helper against the generator's own masks rather than any count. Node counts are identical by construction: the arithmetic is the same arithmetic in a different place. The bench is 3868987 at depth 7, both pinned node count tables are unchanged, and so are EXPECTED_PASSES and EXPECTED_POINTS. The release run goes from 457 tests to 460 and the python ones from 210 to 213; the three new rust tests say that every term writes the counts its width claims, that the table names the terms the sum adds and that the run prints the layout its slots are laid out from. Two of the three new python ones refuse a header with no layout line and a term with no bound; the third keeps a row whose id opens with the layout line's own word. Bench: 3868987 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
87bab7d to
104f2ab
Compare
Speed against
|
A term was four places: its counts and masks in board.rs, its weights and fold in eval.rs, its slot arithmetic in tune.rs and the same arithmetic again in scripts/tune.py. Adding one meant editing all four and getting them to agree.
eval.rs becomes eval/, with a file per term. mobility.rs, shelter.rs and pawn_structure.rs each own their counts, masks, weights with the fit that produced them, fold, cache where they have one, and tests. mod.rs keeps the material values, the phase weights, the accumulator and the sum. A TERMS table in mod.rs describes each term once, tune.rs lays its slots out from it, and the terms run prints that layout as a second header line, which scripts/tune.py now reads instead of holding ten constants of its own. The search carries one eval::Caches, and eval and eval_cached share one body over a memo trait with two implementations. board.rs opens pub(crate) readers on the piece boards and the king's square and names no evaluation term.
Node counts are identical by construction. Bench 3868987 at depth 7, both pinned tables unchanged, EXPECTED_PASSES and EXPECTED_POINTS unchanged. Release tests 457 to 460, python 209 to 212. speed.sh read +0.3% at a 3.1% spread, inside the noise.
Four judgment calls, in the commit body: files_of lives beside spread in pawn_structure.rs; shelter_key moved with the term as shelter::key; the mobility count reads the knight table through a knight_attacks reader; the pawn span test stayed in board.rs.
Reviewed by a second agent that rebuilt both sides and compared the terms output over 787 positions (byte identical apart from the layout line). Its findings are folded in.
🤖 Generated with Claude Code