Repository navigation
Fit the king attack zone weights, and take its counts in the mobility walk - #264
Conversation
The king attack zone landed at zero weight, counted but not scored. These are
its eight weights, fitted on the whole archive with every other term held:
knight 11 / 3 bishop 20 / -2
rook 27 / -3 queen 17 / 9
310 artifacts and 50,677 games, whose 6,623,970 post-book plies gave
6,227,777 positions and 2,890,773 quiet rows at the 820 slot layout. The
corpus is sha256 a0798d51, built with the sealed pairs named, and those are
sha256 a723887f. The rows are sha256 6001063b and the fitted vector sha256
f65aa57e. K was held at 1.0821. The games split by pair: 16,300 pairs and
32,516 games trained, 5,528 pairs and 11,030 games chose the ridge, and 3,498
pairs and 6,985 games were sealed. The seal file names 3,501 pairs over 6,995
games. Three of those pairs reach no row, which accounts for at most six of
the ten missing games, and where the other four went is not established. The
768 table entries, the six material values and the mobility, shelter and pawn
structure weights were all held, so these eight are the only thing that moved.
The fit's figure is the sealed group's. It was opened once, at
2026-09-16T10:28:31Z, after the vector was frozen. Over 417,778 positions it
reads -0.000143 against a standard error of 0.000059 at a design factor of
3.2. That is 2.42 standard errors from zero, outside its interval.
The selection group reads -0.000395 against a standard error of 0.000052 at
the same design factor: 0.085901 at zero, 0.085506 at the fit before rounding
and 0.085496 after. By phase it is -0.000261 at six pieces or fewer, -0.000811
from seven to twelve and +0.000008 at thirteen or more. The two readings are
about 3.2 standard errors apart. By the rule the fit was registered with, that
a disagreement wider than the mobility refit's 1.85 standard errors means the
sealed figure is the one quoted, the selection group's -0.000395 ±0.000052
overstated the fit.
Two findings in the sealed reading were written down before any game was
played. The first is its size: about a third of the selection group's, and
about 3.2 standard errors from it, where the mobility refit's two readings
were 1.85 standard errors apart and the king safety fit's 0.68. Most of the
sealed games are the lazy mobility arms, whose candidates skipped the mobility
term at most quiescence stand pats and so reached positions a different
evaluation chose. The second is where it pays. By phase the sealed reading is
-0.000088 at six pieces or fewer, -0.000414 from seven to twelve and +0.000366
at thirteen or more, so on those games the fullest boards read slightly worse
at these weights. No standard error is printed per bucket.
The ridge is zero, which the grid ranked first. It would have been overruled
had the largest weight sat on the column with the least support. It did not:
the largest is the rook's 27, and the rook column is the best supported of the
four. A midgame coefficient is written in 16.2% of the training rows for the
knight, 27.0% for the bishop, 30.5% for the rook and 17.4% for the queen, and
little of that is on a full board. A sample of every twentieth of all
2,890,773 rows, across all three groups and reading positions only, finds a
rook bearing on the enemy ring from 0.6% of the sides with thirteen or more
pieces left, against 21.7% over the sampled rows. Because the sample spans
every group, it is a different population from the training row shares.
The learning curve is flat from an eighth of the training pairs. Five draws of
2,038 pairs read -0.000346 to -0.000397 against the whole set's -0.000395, so
the corpus is not what limits eight weights.
The match, played on this commit with the shared walk of the one after it,
passed [0, 10] at 10+0.1 on 8moves_v3 over 1,000 games in two batches: +35
±16 elo pooled against 4b1b80f, which plays the zero weight tree. The
batches read +36 ±23 and +33 ±24, and the likelihood ratio reached 4.21
against a bound of 2.94. Under +40, so it is not re-measured.
## What moved
The bench goes from 3,868,987 to 4,102,609, which is 6.04% more. Sixteen of
the eighteen default pins and sixteen of the eighteen reference ones moved.
`king and pawn` and `trebuchet` are unmoved in both sets.
default before after
start 68,341 73,075
italian 150,035 132,136
ruy lopez 161,892 127,220
kiwipete 243,106 275,925
perft 4 110,373 122,259
promotions 54,363 46,622
middlegame 100,507 167,457
sharp middlegame 180,608 184,701
bratko kopec 1 571,361 671,225
wac 4 1,944,432 1,946,406
rook and pawns 29,021 29,112
tarrasch 89,870 53,975
lucena 15,468 16,701
philidor 61,191 60,589
minor endgame 25,367 30,918
queen endgame 56,696 157,932
king and pawn 3,522 3,522
trebuchet 2,834 2,834
reference before after
start 28,328 33,729
italian 227,641 120,260
ruy lopez 120,190 118,515
kiwipete 178,402 224,244
perft 4 232,961 255,435
promotions 95,254 95,210
middlegame 167,371 164,325
sharp middlegame 277,951 241,920
bratko kopec 1 44,544 52,545
wac 4 89,394 89,428
rook and pawns 24,020 22,615
tarrasch 33,182 48,576
lucena 21,127 24,290
philidor 51,857 29,763
minor endgame 34,141 30,627
queen endgame 96,523 175,117
king and pawn 1,601 1,601
trebuchet 968 968
The tactical suite goes from 224 to 237, which puts back more than the mobility
refit took. The floor's comment now counts the history with 237 in it and the
empty list of accepted losses counts sixty three misses.
The strategic suite goes from 99,938 to 100,176. Eight themes rise and seven
fall. AKPC rises most, by 432, then Undermine by 275 and Square Vacancy by 235.
Pawn Play in the Center falls 322, Offer of Simplification 311 and Advancement
of a/b/c pawns 222.
The uci session's node budget moves with the tree, as its comment says it will.
The swap is still at depth ten, which now ends at 2,114,707 and reports the new
move from about 1,995,000, so the budget goes from 2,800,000 to 2,050,000. No
other test moved: the pins in `engine.rs`, `reduction.rs`, `residual.rs` and
the reductions session all pass as they stand.
## What it costs
Callgrind over `arche bench` at depth 7, on a build with `king_attack::fold`,
`king_attack::counts_of` and `mobility::fold` forced out of line:
king_attack::fold, inclusive 558,628,090 11.51% of the run
of which counts_of 512,071,008 10.55%
mobility::fold, inclusive 553,880,961 11.41%
the run 4,854,634,752
Those three attributes cost 33,558,174 instructions of call overhead the
shipped build does not pay: the shipped build is 4,821,016,788. What the
figure prices is this arrangement, a second walk over both sides' pieces at
every leaf beside mobility's, and it costs about what mobility's walk costs,
136 instructions for each node the bench counts against mobility's 135. The
run is 4,024,717,460 instructions over 3,868,987 nodes before this and
4,821,016,788 over 4,102,609 after, which is 1,040 a node against 1,175, or
13.0% more.
That is over the 5% rule of thumb. The figure is recorded here and the counts
are left as their own walk. Taking both readings in the mobility walk is a
separate change and is not made here.
On the clock, `scripts/speed.sh` against 4b1b80f over 21 interleaved rounds
reads -7.6% on the median nodes per second and -8.8% on the fastest, against a
spread of 8.9%. The tree is 6.0% larger, so the bench takes 14.8% longer to
reach depth 7. The box was shared during the run, so the instruction count is
the claim.
## What went
The scaffolding the zero weights needed. The equality on the term's name in
`every_term_writes_the_counts_its_width_claims` is back to asserting every
term is priced. `the_table_names_the_terms_the_sum_adds` reads the shipped
fold again rather than trial weights, on the same position, where all four
folds are non-zero. `fold_with` is private again, and the fold test no longer
asserts that the shipped fold is zero. The doc blocks and comments that said
the term was unfitted, in `king_attack.rs`, `eval/mod.rs`, `tune.rs`, the
architecture overview and the instruments guide, say what it is now. The
readme counts four fitted leaf terms. The roadmap says what the term counts
and what the fit read, and gives its cost.
Bench: 4102609
Elo: +35 ±16 (sprt [0, 10] passed, 1000 games, 10+0.1, vs 4b1b80f)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mobility and the king attack zone each walked both sides' knights, bishops, rooks and queens at every leaf, and each probed the same attack sets: the knight table and the two magic lookups over the whole occupancy. Now one walk per side takes each piece's attack set once and reads it twice, against mobility's scope for the mobility counts and against the enemy king's ring for the king attack counts. The probes and the loops over the pieces are what is shared. The masks, the columns, the weights and the folds are not: each term folds its own counts through a new `fold_counts`. The skips still hold. The walk takes mobility's `SCORED_KINDS` and the king attack `SCORED` as compile time parameters, so a count that is not asked for is not compiled, down to the loop over a kind neither term wants. Read off the disassembly of the walk forced out of line: all four kinds with the ring is 227 instructions with 8 popcounts, without the ring 161 with 4, the knight alone without the ring 61 with 1 and no magic multiply, the knight with the ring 188 with 5, and nothing at all 9. The walk lives in `eval/mod.rs`, beside the sum, which is its one caller. It is the sum's arrangement of two terms rather than a part of either, and putting it in `mobility.rs` would have that file compute the other term's counts (and import from a file that already imports from it). A separate `eval/attacks.rs` was the other choice and was declined: it would be a file for one function with one caller and no counts, weights or fold of its own. `mobility::counts_of` and `king_attack::counts_of` are unchanged. The tuner still reads them through `TERMS`, the hand counts still pin them, and they are now what the walk is tested against. `fold` in each term file is kept for the tests, as the fold over the standalone counts the sum used to answer. ## What it costs Callgrind over `arche bench` at depth 7. The attributed builds force the measured functions out of line: at 02432c9 the two folds and `king_attack::counts_of`, here the walk and the two `fold_counts`. A third build is this commit with the walk's ring turned off and the king attack term read by its own walk again, which gives the walk without the ring over the same tree. attributed 02432c9 this commit the run 4,854,574,730 4,600,409,835 per node 1,183.3 1,121.3 king_attack::fold 558,628,090 11.51% of which counts_of 512,071,008 10.55% mobility::fold 553,880,961 11.41% the shared walk 777,417,417 16.90% the walk without the ring 576,199,267 the ring 201,218,150 4.37% king_attack::fold_counts 21,162,310 0.46% mobility::fold_counts 21,162,310 0.46% shipped 02432c9 this commit the run 4,821,016,788 4,452,853,888 per node 1,175.1 1,085.4 The node count is 4,102,609 in every build. The shipped run is 368,162,900 instructions shorter, 7.64%. What the ring adds to the walk is 49 instructions a node, and 54 with its fold, against the 125 the standalone counts cost. The walk without the ring costs more than 02432c9's `mobility::fold` because it is called once per side rather than once per leaf in that build and returns both arrays, so the attributed builds overstate the walk a little and the shipped figures are the ones to compare. Against 4b1b80f, before the king attack zone, measured again here on its shipped build at 4,024,751,355 instructions over 3,868,987 nodes, 1,040.3 a node, this commit is 4.34% more a node. The fit alone was 12.96% more. The ring's share of the attributed run is 4.37%, and 4.83% with its fold. Both are under the 5% rule of thumb. These price instructions over one tree, so they are what the arrangement saves and nothing about strength. The fit commit's attributed build reproduces its own figures to within 60,022 instructions of the run. It was measured as 4379b1b, whose code 02432c9 carries unchanged: the two differ in comments and docs alone. On the clock, `scripts/speed.sh` against 02432c9 over 21 interleaved rounds reads +5.7% on the median nodes per second and +5.3% on the fastest, against a spread of 12.2%, which is two slow candidate rounds (7 and 18). The box was shared, so the instruction count is the claim, and the clock agrees with it in sign. ## Tests `the_shared_walk_counts_what_each_term_counts_alone` holds the walk to the two standalone counts for both colours on every position of the bench, tactical and strategic suites and the shared fens. It asks five instantiations: all four kinds with the ring, the live constants, two that leave out different kinds with the ring and without it, and no kind without the ring. A skipped count has to answer zero. The hand count tests of both terms run the same check on their positions. Two mutations were run against it. Reading the ring through mobility's scope fails it, and so does skipping the queens. Each also fails both hand count tests and `the_table_names_the_terms_the_sum_adds`. The ARCHITECTURE entries say the two terms share one walk at the leaf and keep their own counts for the tuner. The doc comments that said `eval` and the tuner read one function for these counts now name the shared walk and the test that holds it to them. The roadmap's budget bullet carries the fused figures, with the standalone 11.5% kept as history. Bench: 4102609 Speed: +5.7% (bench nps, 21 interleaved rounds vs 02432c9, spread 12.2%) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Review rounds, all folded before the match was played. The fit commit was read by one reviewer on its own, then both commits by a second that rebuilt 4b1b80f, the fit and the shared walk in its own clone. Neither found a defect in the code. What it established: 4,102,609 nodes on both commits with every bench row identical but the times; the shipped instruction counts reproduced to within 35,000 of about four and a half billion on all three builds; every provenance figure matched the raw fit outputs; seven mutations of the shared walk (an x-ray through the blocker, the knight's ring through the scope mask, the wrong king, the queen read as a bishop, the skip parameters swapped, the sides' arrays swapped both ways) were each caught or did not compile. Findings, folded into the commit each belonged to:
One test was added: the shared walk with no kind and no ring, where every count must answer zero. No production code changed in the fold. Airgap and trailers checked on both commits. |
Speed against
|
The second half of the king attack zone, whose counts landed at zero weight in #258. Two commits.
Fit the king attack zone weights on the archive. Knight
pack(11, 3), bishoppack(20, -2), rookpack(27, -3), queenpack(17, 9), fitted on 50,677 games with the tables, material, mobility, shelter and pawn structure held. The midgame half is positive for all four and the endgame half near zero except the queen, so the taper came out of the fit. The held-out games read -0.000143 ±0.000059 (2.42 standard errors), which is the figure quoted: the selection group read -0.000395 and sat 3.2 standard errors away, so it overstated the fit. The match passed [0, 10] at 10+0.1 on 8moves_v3: +35 ±16 over 1,000 games, LLR 4.21, against 4b1b80f.Take the king attack counts in the mobility walk. Standalone the term was 11.51% of the bench run's instructions. Now one walk per side reads each attack set twice, against mobility's scope and against the enemy king's ring. The shipped build is 7.64% fewer instructions than the first commit over the same tree, and 4.34% more a node than master before the term. Each term keeps its own counts for the tuner, and a test holds the shared walk to both over the bench, tactical and strategic suites.
Bench 3868987 to 4102609. Tactical 224 to 237 and strategic 99,938 to 100,176, which the suites cannot read as strength. The match was played on the tip of this branch; the commits were reworded afterwards to carry their trailers, with the trees unchanged.
Each commit builds, tests and benches on its own, and carries its own
Bench:,Elo:andSpeed:trailers, so a squash merge would fold them into one message.🤖 Generated with Claude Code