approaches/ntuple-rl/ntuple-scale/README.mdxMDX707 lines · 46.4 KB
---
title: Row and column lookup tables, learned from a billion moves
family: ntuple-rl
summary: A lookup-table evaluator that reads every full row and full column of the board as one pattern, learns its numbers by temporal-difference play on the Rust engine, and replaces the hand-written leaf inside the depth-3 fair search.
status: completed
evidence: repository-verified
reads: public
kind: strategy
technique: n-tuple
---

## The problem

Every game tree search ends the same way: after a few imagined moves it
stops and asks a [leaf evaluator](/learn/glossary) how good the board is. The
*frozen fair leaf* is a sum of eighteen hand-written terms, and a diagnostic on
the [evolved NNUE page](/approach/lifetime-objective/nnue-evolution) measured
that term carrying about 95% of the spread between columns at
every search depth. 

> The leaf evaluator is where the decisions are made.

This family's earlier [n-tuple networks](/learn/glossary) looked at the board 
through four-to-six-cell windows trained on tens of thousand games, after which
they were played on their own under a search that followed one guessed outcome
instead of averaging over chance. After establishing it as a promising research
direction, a much larger network was trained on millions of games with a full
set of seven-cell row and column windows.

## Proposed solution

Read every full row and every full column as one seven-cell pattern. Each cell
has ten possible states (empty, a number from 1 to 7, an untouched gray disc, or a
cracked one), so a line has 10<sup>7</sup> (ten million) possible patterns and each pattern gets
its own learned number. Thirty two-by-three windows and thirty three-by-two
windows add local structure between lines. The column tables are also split by
the number of drops left before the next [rise](/learn/glossary). The whole
evaluator is the sum of 74 looked-up numbers, from about 480 million table
entries.

The numbers are learned the way 2048 evaluators were: the program plays
complete games with a one-move-ahead policy over the tables, and after each
move, it nudges its entries toward the points the move scored plus its
own estimate of the next board. Temporal coherence gives every entry its own
step size, which shrinks once that entry's errors stop pointing the same way.
The policy reads the visible board and the moves until the next rise, and
nothing else; the visible next disc is deliberately not an input, so one
learned number serves every column that leads to the same board.

## Experiment design

1. The [Rust bitboard engine](/approach/fair-expectimax/rust-engine) keeps the
   board as seven packed column words, four bits per cell. A column's pattern
   number comes from its word through two small lookup tables, without
   unpacking a single cell. Row patterns come from a nibble gather across the
   seven words, and windows from two or three adjacent nibbles of neighbouring
   words. **Feature extraction costs about forty nanoseconds per board.**
2. **Training:** Thirty-two threads play games at once on the shared tables, each
   thread picking the column whose expected value, averaged over the same
   seven covered-disc reveal samples the fair search uses, is highest. After
   every move the previous board's entries move toward the observed reward
   plus the new board's value. Tables start optimistic, at twenty row-rises of
   value for any board, so unexplored boards look attractive.
3. **Validation:** At fixed move counts the tables are dropped into the
   real depth-3, seven-stratum fair search and played on 64 fixed training-role
   games against the same search with the frozen fair leaf, and also played
   directly one ply. The point with the largest paired margin is frozen as the
   candidate.
4. **Tuning parameters:** Six configurations train for two hundred
   million moves each on the same seeds: with and without rise-phase tables,
   with and without the windows, windows only (which tests whether the full
   lines are what matters), and a smaller step size. The one with the largest
   final validation margin is trained again from scratch for four billion
   moves.
5. **Screen:** The frozen candidate plays 256 games on a seed block nothing
   has ever opened, as the leaf of the depth-3 search, against three arms on
   the identical seeds: the frozen fair leaf in the same search (the
   comparator), the same tables played directly one ply, and the fair leaf at
   depth 4 (the program's standing reference).

## What happened

<NTupleStatus run="RUN-20260905T193006Z-4fbeb4e5" />

On 256 never-read paired games, the tables as the leaf of the depth-3 search
averaged 484,577 points and 140 moves. The same search with the frozen fair
leaf averaged 314,438 points and 93 moves on the identical games. The paired
difference is 170,139 points with a bootstrap 95% lower bound of 130,499,
both halves of the block positive, 167 wins to 89, and a higher lower
quartile, so every criterion of the preregistered gate passes
([RS-20260905T215332Z-95d18a5a](/results/RS-20260905T215332Z-95d18a5a)). 

> This is the first learned evaluator produced by this research project
which consistently outperforms the hand-written fair leaf evaluator.

The fair leaf at depth 4, which is the current standing reference, averaged 377,803 on the
same games, and the tables at depth 3 beat it by 106,775 paired with a lower
bound of 62,574. The tables played directly one ply averaged 281,441 and lost
to the fair leaf at depth 3 by 32,997, so the search over the tables is worth
about 200,000 points on these games.

<NTuplePilotFigure run="RUN-20260905T193006Z-4fbeb4e5" caption="Stage A. Each bar is one configuration's paired margin over the fair leaf on the 64-game training-role validation block after two hundred million training moves, with its bootstrap bounds and detection floor. Arm E, windows only, is the mechanism ablation: without the full-line tuples the tables lose to the fair leaf. These games chose the configuration, so they cannot confirm it." />

The pilot picked arm C, every table family conditioned on the rise clock, at
a final validation margin of 123,950. The two arms without rise-phase column
tables, and the windows-only arm, all lost to the fair leaf on the validation
block; the arms with full rows and columns all beat it.

<NTupleTrainingFigure run="RUN-20260905T193006Z-4fbeb4e5" caption="Stage B. Arm C trained from scratch for four billion moves. The solid line is the tables as the leaf of the depth-3 search on the 64 validation games; the dashed line is the frozen fair leaf in the same search on the same games; the thin lines are the tables played directly and the training games themselves. Every one of the twenty validation points is above the fair leaf; the point at 1.4 billion moves, the largest margin, was frozen as the candidate." />

The main run trained for four billion moves in 42 minutes, about 46 million
one-ply games, and all twenty validation points beat the fair leaf. The
margin on the 64-game block was noisy from point to point, between 116,000
and 270,000, and the largest, at 1.4 billion moves, became the candidate.
About 99 million of the billion table entries had been updated at that point.

<NTupleScreenFigure run="RUN-20260905T193006Z-4fbeb4e5" caption="Stage D, the primary contrast. One bar per held-out game: the candidate as the depth-3 leaf minus the frozen fair leaf in the same search on the same seed, in seed order. The dashed line is the recorded paired mean and the divider separates the two halves the gate checks separately." />

<NTupleScreenFigure run="RUN-20260905T193006Z-4fbeb4e5" contrast="candidate-d3s7-vs-fair-d4s7" caption="For context: the same candidate against the fair leaf at depth 4, the program's standing reference, on the same 256 games." />

<NTupleGateTable run="RUN-20260905T193006Z-4fbeb4e5" />

### The replication at larger scale

The first run left two questions open: whether the margin holds on seeds
nothing has ever read, and whether more scale helps. A second experiment,
[EX-20260906-ntuple-scale-replication-wide-plateau-f627f07a](/experiments/EX-20260906-ntuple-scale-replication-wide-plateau-f627f07a),
asks both on the same screen. It trains a wider evaluator from scratch on a
fresh training block: the same rows, columns and small windows as before,
plus 2-by-4 and 4-by-2 windows with a hundred million patterns each, 5.8
billion table entries in all. There is no fixed move budget. Every five
hundred million moves the tables are dropped into the depth-3 search and
played on a fresh block of 256 training-role games against the fair leaf,
and training stops once the mean margin of the last four validation points
is no longer above the mean of the four before them. The frozen candidate
then plays 512 held-out games beside the first run's frozen tables on the
identical seeds, so one screen reads the replication of the first result
and the effect of the extra scale at the same time.

<NTupleStatus run="RUN-20260906T013222Z-ba0ee34f" />

The result came in two parts. On the 512 fresh games, the first run's frozen
tables, unchanged, scored 487,066 as the depth-3 leaf against 326,717 for the
fair leaf in the same search: a paired margin of 160,349 with a bootstrap 95%
lower bound of 129,753, both halves positive, 330 wins to 182 and a higher
lower quartile
([RS-20260906T040113Z-6ba93171](/results/RS-20260906T040113Z-6ba93171), run
valid, outcome pass, screen tier on public development seeds). The first
result replicates on seeds nothing had read, within a few thousand points of
its own screen.

The wider tables passed the same gate on the same games, 481,869 against
326,717, a margin of 155,153 with a lower bound of 126,819. Against the first
run's tables they came out 5,196 behind, with bootstrap bounds from minus
40,535 to plus 29,158 and 267 wins to 244, so the preregistered scale verdict
is inconclusive. Six times the entries, and a run that stopped on its own,
bought nothing this screen could see. Both sets of tables beat the fair leaf
at depth 4 on these games by more than 84,000.

One thing did move with scale. Played directly, one move ahead and with no
search at all, the wide tables averaged 328,039, level with the fair leaf's
depth-3 search (a margin of 1,323 with bounds either side of zero), where the
first run's tables lost to that search by 32,394 on the same games. The extra
windows made a better one-ply player and the same leaf.

<NTupleTrainingFigure run="RUN-20260906T013222Z-ba0ee34f" caption="Stage B of the replication. The wide layout trained from scratch on the fresh training block. The solid line is the tables as the leaf of the depth-3 search on the 256 fresh validation games; the dashed line is the frozen fair leaf in the same search on the same games; the thin lines are the tables played directly and the training games themselves. The plateau rule reads the solid line's margin over the dashed one from the eighth point on." />

The main run stopped itself at the ninth validation point, 4.5 billion
moves, when the last four margins averaged 165,666 against 173,783 for the
four before them. The candidate is the fourth point, 2 billion moves, where
the margin on the 256 fresh validation games was 187,500. All nine points
beat the fair leaf; the curve rose for the first two billion moves and then
wandered between 142,000 and 188,000. About 231 million of the 5.8 billion
entries had been updated at the candidate's point.

<NTupleScreenFigure run="RUN-20260906T013222Z-ba0ee34f" caption="Stage D of the replication, the primary contrast. One bar per held-out game: the wide candidate as the depth-3 leaf minus the frozen fair leaf in the same search on the same seed, in seed order, 512 games." />

<NTupleScreenFigure run="RUN-20260906T013222Z-ba0ee34f" contrast="prior-d3s7-vs-fair-d3s7" caption="The replication reading. The first run's frozen tables, unchanged, as the depth-3 leaf minus the fair leaf in the same search, on the same 512 fresh games." />

<NTupleScreenFigure run="RUN-20260906T013222Z-ba0ee34f" contrast="candidate-d3s7-vs-prior-d3s7" caption="The scale reading. The wide candidate minus the first run's tables, both as the depth-3 leaf, on the same 512 games." />

<NTupleGateTable run="RUN-20260906T013222Z-ba0ee34f" />

### The fourth ply

Both screens played the tables inside the depth-3 search. A third
experiment,
[EX-20260906-ntuple-scale-depth4-frozen-tables-54aed6a3](/experiments/EX-20260906-ntuple-scale-depth4-frozen-tables-54aed6a3),
asks what one more completed ply is worth to this leaf. Nothing is trained:
the first run's frozen tables, hash-verified, play as the leaf of the
depth-4 search with seven chance strata (the reference search's exact
configuration) and as the depth-3 leaf on 512 games from a fourth block
nothing had read, beside the fair leaf in both searches on the same games.
The gate reads the tables' own depth step; two readings sit beside it, fixed
in advance: whether the tables still beat the fair leaf at depth 4, and
whether the fourth ply is worth more, less or the same on the tables as on
the fair leaf, from the per-game difference of the two depth steps.

<NTupleStatus run="RUN-20260906T081306Z-e62d9837" />

The fourth ply lifted the tables from 465,675 to 516,155 points and from
135 to 149 moves on the 512 games, a paired gain of 50,479 with a bootstrap
95% lower bound of 14,707, both halves positive and a higher lower quartile
([RS-20260906T171746Z-1623f833](/results/RS-20260906T171746Z-1623f833),
run valid, outcome pass, screen tier on public development seeds). The gate
passes. On the same games the fair leaf's fourth ply was worth 49,599, and
the per-game difference between the two depth steps is 881 points with
bounds from minus 40,932 to plus 42,867: the preregistered verdict is
inconclusive, so the tables pay for depth about as well as the hand-written
leaf does, and no better or worse than this screen can see. At depth 4 the
tables beat the fair leaf in the same search by 133,588 with a lower bound
of 102,051, so the margin survives the extra ply intact. The depth-4 search
costs about 36 times the depth-3 search's logical work on these games.

<NTupleScreenFigure run="RUN-20260906T081306Z-e62d9837" caption="The primary contrast of the depth-4 screen. One bar per held-out game: the frozen tables as the depth-4 leaf minus the same tables as the depth-3 leaf on the same seed, in seed order, 512 games. The dashed line is the recorded paired mean and the divider separates the two halves the gate checks separately." />

<NTupleScreenFigure run="RUN-20260906T081306Z-e62d9837" contrast="fair-d4s7-vs-fair-d3s7" caption="The same depth step for the fair leaf on the same 512 games: the fair leaf at depth 4 minus the fair leaf at depth 3." />

<NTupleScreenFigure run="RUN-20260906T081306Z-e62d9837" contrast="prior-d4s7-vs-fair-d4s7" caption="Persistence: the tables as the depth-4 leaf minus the fair leaf in the same depth-4 search, on the same 512 games." />

<NTupleGateTable run="RUN-20260906T081306Z-e62d9837" />

### Tables keyed to how full the board is

The three screens above share one leaf, and its training record shows
where it is weakest. The first run saved, beside its tables, the
accumulators that temporal coherence keeps for every entry, and a seed-free
pass over them counts how many patterns of each shape were ever updated. Of
the legal seven-high column patterns, 0.98% were touched; of top-row
patterns holding five or more discs, 1.06%; of column patterns with three or
fewer discs, 68% to 100%. The touched entries of full lines still average
the starting value, 20/74 of a rise, about 4,600 points each. A search that
looks three plies ahead from a crowded board reads many entries that
training never reached, each carrying the value of a fresh board, so the
leaf is most optimistic where the next few moves decide whether the game
goes on. And because the leaf is a sum of 74 lookups, a pattern cannot be
worth one amount on an empty board and another under a rise on a full one.

A fourth experiment,
[EX-20260906-ntuple-fill-conditioned-continuation-a9e5cbd3](/experiments/EX-20260906-ntuple-fill-conditioned-continuation-a9e5cbd3),
tests two repairs on one fresh block of 512 games, with the frozen tables
as the comparator. The cheap one trains nothing. Every entry of the frozen
file still sitting at the starting value bit for bit, 901,259,321 of the
billion, is replaced: by zero, so an unfamiliar board is priced below a
familiar one, or by the mean of the touched entries of the same table, rise
phase and pattern occupancy, so it is priced like a typical familiar board
of the same shape. The second repair gives every table five copies keyed to
a global measure of fill, either the number of occupied cells (0 to 13, 14
to 20, 21 to 27, 28 to 34, 35 to 49) or the tallest column (up to 3, 4, 5,
6, 7). The copies start as exact copies of the frozen tables, so the
conditioned evaluator agrees with the frozen one on every board until
training pulls the copies apart. The same temporal-difference play then
continues from that start with fresh step sizes, on a training block
nothing had read, validated every two hundred million moves on a fresh
256-game block and stopped by a plateau rule. The 2048 literature calls
this multi-stage weight promotion. A third arm continues the frozen tables
the same way without any copies, so the screen can tell continued training
from conditioning. The fill arm with the larger best validation margin is
the candidate; the control arm's best point is the control.

The screen plays eleven arms on the 512 games: the frozen tables, the fill
candidate, the control and both edits as the depth-3 leaf; the frozen
tables, the fill candidate and the zeroed edit as the depth-4 leaf; the
frozen and the fill tables played directly; and the fair leaf at depth 3.
The gate reads the fill candidate against the frozen tables at depth 3, and
six readings beside it each carry a verdict fixed in advance: the fill
candidate against the control, the control against the frozen tables, each
edit against the frozen tables, and the fill candidate and the zeroed edit
against the frozen tables at depth 4.

<NTupleStatus run="RUN-20260906T201104Z-a96ea6c8" />

<NTuplePilotFigure run="RUN-20260906T201104Z-a96ea6c8" caption="The three warm-started arms: each bar is an arm's paired margin over the fair leaf on the 256-game training-role block at its final validation point, with bootstrap bounds and detection floor. The candidate is the fill arm whose best point, not its final one, was larger; these games chose it, so they cannot confirm it." />

All three arms started from the frozen tables and kept their margin over
the fair leaf from the first validation point. The control arm, the frozen
tables reloaded with fresh step sizes and no buckets, stopped by the
plateau rule at 1.2 billion moves after six points; its best point, at 600
million moves, was 210,990 ahead of the fair leaf on the 256 validation
games, more than either fill arm ever reached. The occupied-cells arm
stopped at 1.6 billion moves after eight points with its best at 1.4
billion (203,193), and the tallest-column arm ran to the two-billion cap
with its best at 1.6 billion (179,760). By the fixed rule the occupied-cells
arm became the candidate, and the pilot-tier training-signal check reads
not passed: neither fill arm's best validation margin exceeded the control
arm's. Each arm trained at about two million moves per second, the
five-billion-entry tables no slower than the one-billion-entry control.

<NTupleTrainingFigure run="RUN-20260906T201104Z-a96ea6c8" arm="occ5" caption="The occupied-cells arm. The solid line is the tables as the depth-3 leaf on the 256 fresh validation games; the dashed line is the fair leaf in the same search on the same games; the thin lines are the tables played directly and the training games themselves. The point at 1.4 billion moves was frozen as the candidate." />

<NTupleTrainingFigure run="RUN-20260906T201104Z-a96ea6c8" arm="control" caption="The control arm: the same warm start continued without buckets. Its best point, at 600 million moves, became the control candidate." />

The screen opened the 512-game block once, after the CHECK gates had
passed again on each of the four new table files. Nothing broke: no arm
made an illegal or incomplete decision and no game hit the move cap.

<NTupleScreenFigure run="RUN-20260906T201104Z-a96ea6c8" caption="The primary contrast. One bar per held-out game: the fill candidate as the depth-3 leaf minus the frozen tables in the same search on the same seed, in seed order, 512 games. The dashed line is the recorded paired mean and the divider separates the two halves the gate checks separately." />

The fill candidate as the depth-3 leaf averaged 506,494 points and 146
moves against 485,455 and 140 for the frozen tables on the same games: a
paired difference of 21,039 with bootstrap bounds from minus 16,864 to
plus 59,230, 271 wins to 241, both halves positive, and a lower quartile
490 points lower. Three of the gate's five criteria fail, so the
preregistered gate fails
([RS-20260906T234914Z-a3fae1a9](/results/RS-20260906T234914Z-a3fae1a9), run
valid, outcome fail, screen tier on public development seeds). The reading
is a non-measurement rather than a refutation: a gain or loss larger than
about 38,000 would have shown, and neither did. The two readings that were
meant to attribute a gain say the same thing at smaller size. The candidate
against the control continuation is +15,093 with bounds from minus 22,431
to plus 52,817, and the control against the frozen tables is +5,946 with
bounds from minus 29,891 to plus 41,989. Continued training and five fill
buckets together moved the depth-3 leaf by less than a 512-game screen can
see.

<NTupleScreenFigure run="RUN-20260906T201104Z-a96ea6c8" contrast="zeroed-d3s7-vs-prior-d3s7" caption="The zeroed edit. One bar per held-out game: the frozen tables with every never-updated entry set to zero, minus the unchanged frozen tables, both as the depth-3 leaf. In 318 of the 512 games the two leaves played the identical game." />

The cheap repair did better. The zeroed edit, the frozen file with its
901,259,321 never-updated entries set to zero and nothing trained, averaged
514,897 as the depth-3 leaf: 29,442 ahead of the frozen tables with a
bootstrap lower bound of 5,813, an upper bound of 54,179 and a detection
floor of 24,154. The verdict fixed for this reading is supported. The edit
only matters when the search reads a never-updated entry at a decision
that is close, and in 318 of the 512 games it never did; across the 194
games it touched, the mean gain was about 77,700 points. Its lower quartile
sits 490 points under the frozen tables', so under the gate's four criteria
this reading would have failed on that line alone. The class-mean edit,
which replaces the same entries with the mean of the touched entries of the
same shape, changed six games and scored 931 more: the touched entries of
full lines average the starting value, so the swap is nearly a no-op.

<NTupleScreenFigure run="RUN-20260906T201104Z-a96ea6c8" contrast="fill-d4s7-vs-prior-d4s7" caption="At depth 4. One bar per held-out game: the fill candidate minus the frozen tables, both as the leaf of the reference depth-4 search on the same seed." />

One ply deeper, both repairs gave their gains back. As the depth-4 leaf the
fill candidate averaged 499,324 against 521,956 for the frozen tables,
22,631 behind with bounds from minus 57,779 to plus 12,806, and below its
own depth-3 score by 7,170. The zeroed edit at depth 4 averaged 511,364,
10,592 behind the frozen tables with bounds from minus 32,045 to plus
11,133, and 3,534 below its own depth-3 score. Both depth-4 verdicts are
inconclusive, but the sign is the same for both repairs and for both depth
steps, while the frozen tables' own fourth ply on these games was worth
36,500 (bounds minus 249 to plus 72,651). A deeper search reads far more
boards that training never visited, and pricing them at zero, or at the
value of a bucket that had 1.4 billion moves to relearn its share, served
the depth-4 search worse than the fresh-board value did. The frozen tables
at depth 4 stay the candidate to carry forward.

<NTupleGateTable run="RUN-20260906T201104Z-a96ea6c8" />

## What we learned

The evaluator was the bottleneck, and a lookup table can be a better one than
eighteen hand-written terms once it can see whole lines. Each part of the
recipe was needed: the windows-only arm lost, the arms without rise-phase
column tables lost, and the tables played without any search lost too. What
worked was full rows and columns, conditioned on the rise clock, learned from
the engine's own returns at a scale of billions of moves, and then placed
inside the fair chance-averaging search, which turned a one-ply policy that
trails the fair leaf into a leaf that beats the depth-4 reference.

The result is a screen-tier finding, now on two blocks of public development
games, 256 and then 512, with the same frozen tables ahead of the fair leaf
by 170,139 and then 160,349. It is not a qualification, and a mean near
485,000 is still less than half the million-point target. Both candidates
won on their training-role validation blocks by more than they won on the
screens, which is the selection bias the screens exist to measure.

The replication also drew a line under scale, at least in this direction.
Six times the entries, trained until a 256-game validation curve stopped
rising, produced a leaf the 512-game screen could not tell apart from the
first one, while the same tables played without any search improved by
about 34,000 on the same games. The value function got better at the thing
the search does for it and no better at the thing the search needs from it.
The fourth ply answered one of the two questions the replication left. The
same frozen tables one ply deeper gained 50,479 points on 512 fresh games,
the fair leaf gained 49,599 on the same games, and the difference between
those two steps is inside its detection floor. The learned leaf pays for
search depth the way the hand-written one does, and it keeps its whole
margin over the fair leaf at depth 4. Search depth is a lever for this
evaluator, at the usual price: about 36 times the logical work per game for
the fourth ply. The tables at depth 4, averaging 516,155 on that block, are
now the candidate to carry forward.

The fourth experiment asked the leaf to know how full the board is, and
the answer at this budget is that it did not help where it could be
measured. Five fill buckets warm-started from the frozen tables and trained
for another 1.4 billion moves were 21,039 ahead of the frozen tables at
depth 3, inside a 38,000-point floor, and 22,631 behind at depth 4; the
same warm start continued without buckets landed in the same place. The
mechanism the experiment was built on was half right. The frozen leaf does
price never-updated entries at a fresh board's value, and setting those
entries to zero, with no training at all, was worth 29,442 points at depth
3 with a lower bound above zero, by changing only 194 of 512 games. But the
same edit gave that back at depth 4, and so did the buckets: the deeper
search reads far more unfamiliar boards, and a pessimistic price for them
served it worse than the optimistic one. What that leaves open is a leaf
trained on the boards the search evaluates rather than the ones one-ply
play visits, which is the other half of the question the replication left,
and a depth-aware version of the edit that prices unfamiliar boards
between zero and a fresh board's value.

<AgentContext summary="Records and provenance">

Theory
[TH-20260905-ntuple-line-tuples-tc-td-leaf-bcb25133](/theories/TH-20260905-ntuple-line-tuples-tc-td-leaf-bcb25133),
experiment
[EX-20260905-ntuple-scale-tc-td-leaf-d3-535b2620](/experiments/EX-20260905-ntuple-scale-tc-td-leaf-d3-535b2620)
(frozen protocol SHA-256 `ee8eabb0512f459bb0acbb717e335d5e6953d2ac7ec57400f2a35b9f1c76b65d`),
run `RUN-20260905T193006Z-4fbeb4e5`, machine profile
`MACH-20260905T192901Z-83559f62`, contribution `CT-20260905T192505Z-c487efca`.

Replication at larger scale: experiment
[EX-20260906-ntuple-scale-replication-wide-plateau-f627f07a](/experiments/EX-20260906-ntuple-scale-replication-wide-plateau-f627f07a)
(frozen protocol SHA-256 `0284abf064ef24fe47ae1ee1b3a7d63245b9f470bdcdcbf00976576bb1d67df8`),
run `RUN-20260906T013222Z-ba0ee34f` on branch
`claude/n-tuple-scale-replication`, machine profile
`MACH-20260906T014136Z-58c2ebe0`, contribution `CT-20260906T014308Z-21df88ae`.
Its seed leases: training `SL-20260906T013222Z-ce319097` (`0xa5500000` to
`0xa56f0000`, read in order and wrapped), validation
`SL-20260906T013222Z-6cce6192` (`0xa52f2280`, 256 games, training role,
re-read at every validation point), screen `SL-20260906T013222Z-f34b23c0`
(`0xa52f2380`, 512 games, public development, opened once). All three are
disjoint from every block the first experiment read. Stage driver
`approaches/ntuple-rl/ntuple-scale/scripts/pipeline2.sh`; artifacts under
`runs/RUN-20260906T013222Z-ba0ee34f/ntuple-scale/` (gitignored); web snapshot
`web/content/figures/ntuple-scale/RUN-20260906T013222Z-ba0ee34f.json`. The
wide layout is `rows,cols,win23,win32,win24,win42,phase=all`: 5.8 x 10^9
entries, 122 active per state, 23.2 GB of weights and 69.6 GB with the
coherence accumulators; table files stay on the workstation with their
SHA-256 in the result record.

The fourth ply: theory
[TH-20260906-ntuple-leaf-fourth-ply-search-compatible-ace4fe2a](/theories/TH-20260906-ntuple-leaf-fourth-ply-search-compatible-ace4fe2a),
experiment
[EX-20260906-ntuple-scale-depth4-frozen-tables-54aed6a3](/experiments/EX-20260906-ntuple-scale-depth4-frozen-tables-54aed6a3)
(frozen protocol SHA-256 `18733b1875bd5fe93f8625a22d617e6aa77a611b88b306d62c64e5d9196091bb`),
run `RUN-20260906T081306Z-e62d9837` on branch
`claude/n-tuple-continuation-4-ply`, machine profile
`MACH-20260906T081306Z-e87b3f5a`, contribution `CT-20260906T081306Z-c70f4648`,
result
[RS-20260906T171746Z-1623f833](/results/RS-20260906T171746Z-1623f833).
One lease, the screen `SL-20260906T081306Z-1aaec419` (`0xa52f2580`, 512
games, public development, opened once), disjoint from every block the two
earlier experiments read. No training: the screened tables are the first
experiment's frozen candidate (SHA-256 verified before the lease opened),
played through `Arm::NTupleD4` (the reference `d4s7` parameters, 1M-entry
table) and `Arm::NTupleD3`. Stage driver
`approaches/ntuple-rl/ntuple-scale/scripts/pipeline3.sh` (gates, freeze,
screen, compare, analyze); artifacts under
`runs/RUN-20260906T081306Z-e62d9837/ntuple-scale/` and promoted under
`artifacts/results/EX-20260906-ntuple-scale-depth4-frozen-tables-54aed6a3/RUN-20260906T081306Z-e62d9837/`;
web snapshot
`web/content/figures/ntuple-scale/RUN-20260906T081306Z-e62d9837.json`.

The fill-conditioned tables: theory
[TH-20260906-ntuple-fill-conditioned-leaf-28cb0ae2](/theories/TH-20260906-ntuple-fill-conditioned-leaf-28cb0ae2),
experiment
[EX-20260906-ntuple-fill-conditioned-continuation-a9e5cbd3](/experiments/EX-20260906-ntuple-fill-conditioned-continuation-a9e5cbd3)
(frozen protocol SHA-256 `974bdae371b7dab5835003c8b62e26b4ab303f01761e5846880c6b10eee1aa14`),
run `RUN-20260906T201104Z-a96ea6c8` on branch
`claude/n-tuple-fill-conditioned` (preregistration commit `7aa1db7`), machine
profile `MACH-20260906T201104Z-c51a782f`, contribution
`CT-20260906T201547Z-705074a5`, result
[RS-20260906T234914Z-a3fae1a9](/results/RS-20260906T234914Z-a3fae1a9).
Leases: training `SL-20260906T201104Z-88b984ce` (`0xa5800000` to
`0xa59f0000`, read in order and wrapped by all three arms), validation
`SL-20260906T201104Z-26371f8b` (`0xa52f2780`, 256 games, training role,
re-read at every validation point of every arm), screen
`SL-20260906T201104Z-53350936` (`0xa52f2880`, 512 games, public
development, opened once). All three are disjoint from every block the
three earlier experiments read. Layouts `rows,cols,win23,win32,phase=all,fill=occ5`
and `...,fill=hgt5` (5 x 10^9 entries, 74 active per state, 20 GB frozen, 60
GB trainable; `Model::promote` copies the frozen tables into every bucket;
the trainer's `--init-from` loads or promotes a frozen file with fresh
accumulators). Table files: the candidate (occupied-cells arm, best point,
SHA-256 `4f2e7ccf5c14fed8dd19563965e3937e8784b487f2de9eb70fe0e86830edae24`)
and the control (SHA-256
`92dd1cb2d2a74b026270606c18c5f0d6e4f4ccc74e64c3c4e0f0d2043cdddd90`) are
published compressed under the run's archive prefix
(`ntuple-scale/pilot/occ5/best-weights.bin.zst`,
`ntuple-scale/pilot/control/best-weights.bin.zst`; dataset record
`DS-20260906-ntuple-fill-conditioned-candidate-tables-860c310b`); the zeroed
(`e9248b1a...`) and class-mean (`c3f05eee...`) edits are reproduced from the
frozen tables by `scripts/edit-tables.py` and are not published; the
tallest-column arm's tables stay on the workstation. Stage driver
`scripts/pipeline4.sh` (gates, edit, train, select, freeze, screen, compare,
analyze); artifacts under `runs/RUN-20260906T201104Z-a96ea6c8/ntuple-scale/`
(gitignored), the compact ones promoted under
`artifacts/results/EX-20260906-ntuple-fill-conditioned-continuation-a9e5cbd3/RUN-20260906T201104Z-a96ea6c8/`
and published under the run's archive prefix
(`runs/RUN-20260906T201104Z-a96ea6c8/published.jsonl`); web snapshot
`web/content/figures/ntuple-scale/RUN-20260906T201104Z-a96ea6c8.json`. The
seed-free diagnostic behind the theory is `scripts/touched-by-fill.py`, its
output retained as `main/touched-by-fill.txt` of the run.

Published artifacts (the public research archive, immutable run-scoped keys,
SHA-256 in each reference's fragment; `runs/<run-id>/published.jsonl` lists
them): the first run's frozen tables as
`https://data.drop7.dev/runs/RUN-20260905T193006Z-4fbeb4e5/ntuple-scale/main/best-weights.bin.zst`
(zstd; the decompressed file's SHA-256 is `0ade9d4e...`, dataset record
`DS-20260906-ntuple-scale-frozen-tables-ff977178`), the wide tables as
`https://data.drop7.dev/runs/RUN-20260906T013222Z-ba0ee34f/ntuple-scale/main/best-weights.bin.zst`
(decompressed SHA-256 `824b0a39...`, dataset record
`DS-20260906-ntuple-scale-wide-frozen-tables-c5eb52a6`), and the three
screens' per-game artifacts (`screen/heldout.json`) and analyses under the
same run prefixes. Download with plain HTTPS, check the digest, `zstd -d`,
check the decompressed digest, then `Model::load`.

Seed leases: training `SL-20260905T191317Z-09895ea2` (`0xa5300000`
to `0xa54f0000`, read in order and wrapped; wrap counts in each
`progress.jsonl`), validation `SL-20260905T191317Z-549265de` (`0xa52f2240`,
64 games, training role, re-read at every validation point), screen
`SL-20260905T191317Z-c25f58cd` (`0xa52f2140`, 256 games, public development,
opened once). CHECK gates and the throughput smoke run read only the
already-opened probe block `0xa5277000`.

Run artifacts live under `runs/RUN-20260905T193006Z-4fbeb4e5/ntuple-scale/`
(gitignored): `gates.log`, `pilot/<arm>/`, `main/`, `screen/`, `rusage.jsonl`,
`analysis.json`, `analysis.md`. The web snapshot is
`web/content/figures/ntuple-scale/RUN-20260905T193006Z-4fbeb4e5.json`, written
by `web/scripts/extract-ntuple-scale.ts`. Table files are 1.9 GB (weights) and
5.8 GB (with coherence accumulators) and are retained on the workstation with
their SHA-256 in the result record; they are not committed.

Reproduce:

```sh
approaches/ntuple-rl/ntuple-scale/build.sh
cd approaches/ntuple-rl/ntuple-scale && cargo test --release
RUN_ID=RUN-... THREADS=32 scripts/pipeline.sh chain
# the replication (fresh blocks, wide layout, plateau rule):
RUN_ID=RUN-... EXPERIMENT_ID=EX-20260906-ntuple-scale-replication-wide-plateau-f627f07a \
  SCREEN_LEASE=research/seeds/leases/SL-20260906T013222Z-f34b23c0.json THREADS=32 \
  scripts/pipeline2.sh gates && scripts/pipeline2.sh smoke && scripts/pipeline2.sh chain
```

</AgentContext>

<AgentContext summary="Full results table">

Held-out screen, 256 paired games on `0xa52f2140`, 2,000-move cap, from
`screen/heldout.json` and the compare.py reports
([RS-20260905T215332Z-95d18a5a](/results/RS-20260905T215332Z-95d18a5a)):

| arm | mean | median | Q25 | max | moves | clears/move | reveals/move |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| candidate-d3s7 | 484,577 | 407,474 | 212,820 | 2,295,869 | 140.21 | 2.1074 | 1.1899 |
| candidate-1ply | 281,441 | 230,850 | 157,750 | 815,564 | 84.30 | 1.9217 | 1.0608 |
| fair-d3s7 | 314,438 | 250,821 | 175,832 | 1,221,000 | 92.58 | 1.9848 | 1.1026 |
| fair-d4s7 | 377,803 | 282,076 | 192,562 | 1,794,652 | 109.34 | 2.0492 | 1.1499 |

| contrast | paired delta | bootstrap LB95 | t LB95 | UB95 | W-T-L | halves | floor |
| --- | ---: | ---: | ---: | ---: | --- | --- | ---: |
| candidate-d3s7 minus fair-d3s7 (gate) | +170,139 | +130,499 | +130,560 | +209,881 | 167-0-89 | +198,575 / +141,704 | 39,440 |
| candidate-d3s7 minus fair-d4s7 | +106,775 | +62,574 | +61,891 | +151,706 | 160-0-96 | +109,211 / +104,338 | 44,724 |
| candidate-1ply minus fair-d3s7 | -32,997 | -58,536 | -58,451 | -7,597 | 114-0-142 | -11,749 / -54,246 | 25,363 |
| fair-d4s7 minus fair-d3s7 | +63,365 | +31,538 | +31,238 | +95,449 | 138-0-118 | +89,363 / +37,366 | 32,013 |
| candidate-d3s7 minus candidate-1ply | +203,137 | +167,199 | +166,028 | +240,598 | 185-0-71 | +210,323 / +195,950 | 36,977 |

Pilot arms, 2 x 10^8 training moves each, paired margin of the tables in
d3s7 over fair-d3s7 on the 64-game training-role validation block
(`pilot/<arm>/val-*.json`; the block's fair-d3s7 mean is 337,611):

| arm | layout | alpha | entries | final margin | best margin |
| --- | --- | ---: | ---: | ---: | ---: |
| A | rows,cols,win23,win32,phase=cols | 1.0 | 480,000,000 | +95,051 | +170,874 |
| B | rows,cols,win23,win32,phase=none | 1.0 | 200,000,000 | -70,587 | -35,091 |
| C (selected) | rows,cols,win23,win32,phase=all | 1.0 | 1,000,000,000 | +123,950 | +123,950 |
| D | rows,cols,phase=cols | 1.0 | 420,000,000 | +26,951 | +94,015 |
| E (ablation) | win23,win32,phase=none | 1.0 | 60,000,000 | -83,261 | -80,696 |
| F | rows,cols,win23,win32,phase=cols | 0.25 | 480,000,000 | +73,116 | +112,886 |

Main run (arm C layout): 4,000,004,271 moves, 45,852,198 games, 2,497 s wall
at a mean 1,956,636 moves per second, 20 validation points every 2 x 10^8
moves, all with a positive paired margin (116,000 to 270,023); the candidate
is the point at 1,400,240,319 moves (607,633 vs 337,611 on the block, 47-17),
SHA-256 `0ade9d4e4080ebdd52a1474b1a13410dc8dfb77f5eba24b078aa7703c92ace0b`.

Replication at larger scale, held-out screen, 512 paired games on
`0xa52f2380`, 2,000-move cap, from `screen/heldout.json` and the compare.py
reports of `RUN-20260906T013222Z-ba0ee34f`
([RS-20260906T040113Z-6ba93171](/results/RS-20260906T040113Z-6ba93171)):

| arm | mean | median | Q25 | max | moves | clears/move | reveals/move |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| candidate-d3s7 (wide) | 481,869 | 393,375 | 241,610 | 2,487,485 | 139.39 | 2.1088 | 1.1927 |
| candidate-1ply (wide) | 328,039 | 282,096 | 178,990 | 1,224,664 | 97.27 | 1.9762 | 1.0946 |
| prior-d3s7 (first run's tables) | 487,066 | 385,960 | 232,825 | 3,236,265 | 140.83 | 2.1123 | 1.1949 |
| prior-1ply (first run's tables) | 294,323 | 247,261 | 173,121 | 1,270,003 | 87.91 | 1.9353 | 1.0665 |
| fair-d3s7 | 326,717 | 269,648 | 192,040 | 1,714,793 | 95.87 | 2.0033 | 1.1138 |
| fair-d4s7 | 397,154 | 320,815 | 202,360 | 1,707,841 | 114.55 | 2.0598 | 1.1550 |

| contrast | paired delta | bootstrap LB95 | t LB95 | UB95 | W-T-L | halves | floor |
| --- | ---: | ---: | ---: | ---: | ---: | --- | --- | ---: |
| candidate-d3s7 minus fair-d3s7 (gate) | +155,153 | +126,819 | +126,919 | +183,307 | 333-0-179 | +159,105 / +151,201 | 28,185 |
| prior-d3s7 minus fair-d3s7 (replication) | +160,349 | +129,753 | +129,626 | +191,264 | 330-0-182 | +133,349 / +187,349 | 30,670 |
| candidate-d3s7 minus prior-d3s7 (scale, inconclusive) | -5,196 | -40,535 | -40,060 | +29,158 | 267-1-244 | +25,756 / -36,148 | 34,803 |
| candidate-d3s7 minus fair-d4s7 | +84,716 | +54,794 | +54,672 | +114,355 | 298-0-214 | +92,759 / +76,672 | 29,992 |
| prior-d3s7 minus fair-d4s7 | +89,912 | +58,243 | +58,255 | +121,567 | 290-0-222 | +67,003 / +112,821 | 31,602 |
| fair-d4s7 minus fair-d3s7 | +70,437 | +46,917 | +47,196 | +93,486 | 302-0-210 | +66,346 / +74,528 | 23,201 |
| candidate-1ply minus fair-d3s7 | +1,323 | -17,545 | -17,456 | +20,096 | 268-0-244 | -21,521 / +24,166 | 18,746 |
| prior-1ply minus fair-d3s7 | -32,394 | -51,190 | -51,084 | -13,948 | 219-0-293 | -50,576 / -14,211 | 18,658 |
| candidate-d3s7 minus candidate-1ply | +153,830 | +127,665 | +127,508 | +180,357 | 333-0-179 | +180,625 / +127,035 | 26,277 |

The fourth ply, held-out screen, 512 paired games on `0xa52f2580`,
2,000-move cap, four arms on identical seeds, from `screen/heldout.json` and
the compare.py reports of `RUN-20260906T081306Z-e62d9837`
([RS-20260906T171746Z-1623f833](/results/RS-20260906T171746Z-1623f833)):

| arm | mean | median | Q25 | max | moves | clears/move | reveals/move | work/game | wall s/game |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| prior-d4s7 (candidate) | 516,155 | 392,496 | 233,182 | 2,716,683 | 148.75 | 2.1255 | 1.2033 | 7.96e8 | 152.3 |
| prior-d3s7 (comparator) | 465,675 | 352,446 | 212,969 | 2,674,728 | 134.82 | 2.0987 | 1.1851 | 2.22e7 | 4.1 |
| fair-d4s7 | 382,567 | 303,015 | 208,481 | 2,634,604 | 110.45 | 2.0459 | 1.1460 | 5.59e8 | 175.7 |
| fair-d3s7 | 332,968 | 262,338 | 174,599 | 1,745,301 | 97.72 | 2.0115 | 1.1225 | 1.56e7 | 4.5 |

| contrast | paired delta | bootstrap LB95 | t LB95 | UB95 | W-T-L | halves | floor |
| --- | ---: | ---: | ---: | ---: | --- | --- | ---: |
| prior-d4s7 minus prior-d3s7 (gate) | +50,479 | +14,707 | +14,086 | +86,224 | 284-0-228 | +67,335 / +33,623 | 36,331 |
| fair-d4s7 minus fair-d3s7 | +49,599 | +25,751 | +25,578 | +73,244 | 289-0-223 | +45,189 / +54,009 | 23,979 |
| (tables d4 minus d3) minus (fair d4 minus d3), interaction | +881 | -40,932 | -41,742 | +42,867 | 250-0-262 | +22,146 / -20,385 | 42,550 |
| prior-d4s7 minus fair-d4s7 (persistence) | +133,588 | +102,051 | +101,161 | +165,801 | 314-0-198 | +137,477 / +129,699 | 32,371 |
| prior-d4s7 minus fair-d3s7 | +183,187 | +150,976 | +150,210 | +216,400 | 345-0-167 | +182,666 / +183,708 | 32,920 |
| prior-d3s7 minus fair-d3s7 | +132,707 | +102,193 | +101,741 | +163,903 | 309-0-203 | +115,331 / +150,084 | 30,913 |
| prior-d3s7 minus fair-d4s7 | +83,109 | +51,349 | +51,365 | +114,439 | 282-0-230 | +70,142 / +96,076 | 31,689 |

Wall seconds are observations on a shared workstation (32 threads, the four
arms in sequence); logical work is the cost quantity.

Replication main run (wide layout, 5.8 x 10^9 entries): 4,500,370,590 moves,
47,477,538 games, 4,462 s wall at a mean 1,184,973 moves per second, nine
validation points every 5 x 10^8 moves on the 256-game block (fair-d3s7 mean
337,539): paired margins +154,572, +145,989, +177,860, +187,500, +183,785,
+186,631, +172,106, +142,514, +161,412; the plateau rule fired at the ninth
point (last four +165,666, previous four +173,783); the candidate is the
fourth point at 2,000,153,332 moves (525,039 vs 337,539, 168-88), SHA-256
`824b0a39a90d8a5aae63438c1538d4c5022f0e0f09c75fb7e2d758a1d6c6fb8a`.

</AgentContext>

<AgentContext summary="Validity, gates and limitations">

CHECK gates (all pass, `gates.log`): the two-table base-10 codec against a
Horner reference on two million random line words; the row gather against the
engine's cell accessor; feature indices against an independent reference for
every layout, with distinctness and range; information-boundary blindness
(score, level, moves played and the visible next disc leave the leaf value
unchanged; score, level and moves played leave the direct decision unchanged);
reflection (mirrored boards share the value and the direct decision mirrors);
direct-policy legality; the tables inside the depth-3 search play legal,
complete, deterministic games that are identical at one and four workers; two
single-thread training runs on the same games produce identical tables; values
finite.

Known limitations, fixed before any leased seed was read: training uses
lock-free asynchronous updates from 32 threads, so a training run is not
bit-reproducible (the serial update rule is); the validation block is
training-role data read many times, so validation margins select and cannot
confirm; the pilot's configuration choice is made on the same 64 games; the
one-shot screen is 256 games, whose detection floor for this contrast is
recorded in the analysis; the candidate is chosen at the validation point with
the best margin, which is an upward-biased estimate the screen exists to
correct.

Replication gates (19, all pass, `gates.log` of the second run): the same
list with feature-index references for the 2x4 and 4x2 families and the wide
layout. The first invocation of that gate binary panicked: the
information-boundary gate values terminal states, a state whose rise ended
the game carries a rise phase of 0, and the wide layout's 64-bit index then
left the table. The phase is now clamped inside the feature function; the
search never values a terminal state (it returns the terminal utility
first), training and the direct policy guard the same way, so no recorded
game is affected. In the first experiment the same gate read an in-range
entry of the phase-conditioned column tables for those terminal states
because its 32-bit index wrapped; the gate's conclusion (hidden fields leave
the value unchanged) held because the wrong entry was the same wrong entry
for every perturbation. The replication's validation block is 256 games
(the rule that stops training reads it, so it selects and cannot confirm);
its screen is 512 games opened once; the replication arm is the first
experiment's exact frozen tables, verified by SHA-256 before the screen
lease opened.

The fourth ply's gates (20, all pass, `gates.log` of the third run, on the
frozen tables through `gate --weights`): the same list plus
`leaf-in-d4-search-determinism`, two probe games over 40-move caps at one
and two workers, identical scores, moves and work, no illegal or incomplete
decision, and more work than the depth-3 arm on every game. Limitations,
fixed before the lease opened: no table was trained or changed, so the
screen inherits the first experiment's candidate selection and adds none of
its own; the depth-4 search is the standing reference configuration (1M-entry
table, seven strata, terminal utility -1,000,000), and a different terminal
utility, table size or stratum count is a different configuration; the
fair-leaf arms are context for the interaction reading, whose detection
floor (42,550) is larger than the depth steps' own floors because it is a
difference of differences; the screen is 512 games opened once.

</AgentContext>

<AgentContext summary="Scoring mode">

Corrected five-move Hardcore scoring, 17,000 points per row rise and 70,000 per
board clear, 2,000-move cap, on the Rust bitboard engine that is
trace-identical to the frozen C++ reference. All scores on this page are on
that scale. Table values are learned in units of one row rise and multiplied
by 17,000 inside the search.

</AgentContext>