On this page

Summary

A Drop7 search spends almost all of its time doing two things: applying a candidate move to a board (place a disc, pop matching discs, crack and reveal gray discs, let everything fall, repeat) and then scoring the resulting board with a leaf evaluator. The frozen reference does both correctly but wastefully — rescanning the board dozens of times per wave, allocating millions of tiny heap objects per decision, and calling pow and ldexp in the innermost loop. The fast engine keeps every rule, every random draw, every floating-point operation in the same order, and removes only the waste. It was then replayed against the original on hundreds of thousands of moves and required to agree on every byte.

whole decision, depth 4 / 5 strata3.08×interleaved A/B, one thread
heap allocations per decision0reference: ≈15–30 million
moves replayed, both engines438,0200 mismatches
leaf values compared bit-for-bit225,1830 mismatches

Runtime profile

Before anything was optimised, the search was instrumented to count what one depth-4, five-strata decision really does. The shape of the tree is the whole story: the last ply is always the overwhelming majority of nodes, so the leaf evaluator is called on 96 % of everything the search touches.

root: one real positionply 1: interior nodes, transposition key builtply 2: interior nodes, transposition key builtply 3: interior nodes, transposition key builtply 4: leaf evaluations — 96.1 % of nodesone depth-4 decision: 796,058 nodes · 764,899 leaf calls · 31,159 interior keys
Census of one real depth-4 five-strata decision (24 decisions from real games). Only 3.9 % of nodes ever build a transposition key, which caps what any cache optimisation can be worth before it starts.

Multiplying those counts by the measured cost of each primitive attributes the decision's wall time. The transposition table, which the brief had ranked as "very likely the single largest win" — is 0.6 %.

Attributed share of one depth-4 / 5-strata decision, original engine
leaf evaluation (fairLeaf): 79.1% — 764,899 calls × 970.3 ns79.1%move application (playMoveSampled): 20.2% — 796,081 calls × 238.2 ns20.2%transposition probe + insert: 0.6% — 31,159 calls × 187.9 ns
leaf evaluation (fairLeaf) · 79.1%move application (playMoveSampled) · 20.2%transposition probe + insert · 0.6%
table view
componentsharedetail
leaf evaluation (fairLeaf)79.1%764,899 calls × 970.3 ns
move application (playMoveSampled)20.2%796,081 calls × 238.2 ns
transposition probe + insert0.6%31,159 calls × 187.9 ns
Attributed components sum to 937.7 ms against 1,081.8 ms measured; the 13.3 % residual is the search driver itself and is reported rather than distributed. Load average ≈15–22 during these measurements.

Move pipeline

Watch one real move resolve. The frames below were generated by the repository's TypeScript rules engine with a predetermined latent board, so the reveals are exact, not illustrative. A is dropped into column 2.

333step 1 of 6Place the discThe 3 falls to the lowest empty cell of column 2.Nothing else has changed yet.333step 2 of 6Wave 1 — find the poppersColumns 2–4 of the bottom row form a run of exactlythree occupied cells, and all three discs are 3s.1step 3 of 6Resolve the coversThe solid gray above column 3 takes one hit and cracks.The already-cracked gray above column 4 takes itssecond hit and reveals a 1. The 3s are then cleared.3 discs × 7 points = 21.1step 4 of 6Gravity, affected columns onlyOnly columns 3 and 4 lost a disc, so only they arecompacted. The other five columns are provablyunchanged and are not touched.1step 5 of 6Wave 2 — scan againThe revealed 1 now stands alone vertically: a run oflength one equals its value, so it pops. The crackedgray beside it takes a second hit.4step 6 of 6Wave 2 resolves; cascade endsThe second hit reveals a 4. 1 disc × 39 points = 39.The next scan finds no poppers, so the move is overwith a score delta of 60 and two chain waves.
333
1. Place the disc
333
2. Wave 1 — find the poppers
1
3. Resolve the covers
1
4. Gravity, affected columns only
1
5. Wave 2 — scan again
4
6. Wave 2 resolves; cascade ends
The engine resolves every popper in a wave simultaneously, reads cover hits from the pre-clear board, reveals in row-major order, then applies gravity — in exactly this order in both the reference and the fast engine. Each step is one pass through the loop in resolveCascadeFast.

Both engines perform these same steps. What changed is how much work each step costs:

1. Placereferencecopy board, scan columnfast enginesame (already cheap)2. Scanreference~1,300 reads per wavefast engine1 pass → 14 masks + bitboard3. PoppersreferencelineLength ×2 per cellfast engine128-entry run-length table4. Cover hitsreferenceall 49 cells checkedfast engineonly cover bits, built lazily5. Clear + revealreferenceseparate board copyfast enginein place, same order6. Gravityreference49-byte board by valuefast enginein place, popped columns only7. Score wavereferencepow(d, 2.5) each wavefast enginetable, verified bit-exact8. Wavesreferenceheap vector per movefast engineinline sink, no allocation
The reference is correct at every step; it simply recomputes from scratch. The fast engine caches what a 7×7 board makes cacheable and never allocates.

Packed board state

A row is seven cells, so its occupancy is one of 128 patterns. One pass over the 49 bytes produces seven row masks, seven column masks, and a 49-bit bitboard of numbered cells packed into a single 64-bit word. Everything the cascade needs afterwards is a table lookup or a bit operation.

board (49 bytes)333row occupancy mask (7 bits each)0000000= 00000000= 00000000= 00000000= 00000000= 00011000= 240111010= 58column occupancy masks (top → bottom bit)numbered bitboard (49 bits in one 64-bit word)iterate set bits with ctz; skip every empty or gray cell for free
scanBoard: one sweep fills row_mask[7], column_mask[7] and the numbered bitboard. The cover bitboard is deliberately not built here — more than half of applied moves produce no wave and never need it.

Run-length lookup

The reference asks "how long is the contiguous run through this cell?" by walking left, right, up and down from every numbered cell, on every wave. With the row mask in hand, the run length of every position in that row is a single precomputed entry: kRunLengthTable[mask].length[column]. The popper test is then one comparison per numbered cell, visited in the same row-major order as the reference so the popper list is byte-identical.

one row of the board333occupancy mask → table index 58 of 1280111010kRunLengthTable[58].length[0..6]0333010popper test per numbered cell: length[column] == disc valueemptyskippeddisc 3, run 3POPSdisc 3, run 3POPSdisc 3, run 3POPSemptyskippedgrayskippedemptyskipped
The bottom row from the animation after the drop. The mask 0111010 indexes the table; positions 1–3 have run length 3, so the three 3s pop. Position 5 is a gray disc and is never a popper. 98 reads replace ~1,300.

Incremental gravity and cover updates

Reveals overwrite a cover in place and never create a hole, so a column with no popper is unchanged by compaction. Gravity therefore runs only on the columns that lost a disc, and it runs in the same buffer: the destination index never trails the source, so no second board is needed. Cover resolution iterates the cover bitboard (typically a dozen cells) instead of all 49, and reads the pre-clear board exactly as the reference does, which is why the crack-versus- reveal decision in the animation above is unchanged.

Allocation-free hot path

The allocation census was the surprise. Per depth-4 decision, the original engine performed:

Siteallocations per decision
transposition key (52-byte string, over the 15-byte small-string buffer)31,159
cache value (list node plus a copy of the key)45,486
MoveResult::waves vector on every applied move that produced a wave≈ 358,000
two std::vector<double> per numbered disc per leaf, in the leaf's release inventory≈ 15–30 million

The fast engine performs zero. Wave lists go to an inline sink (the search only ever asks "was there a wave, and how deep was the last one?"), the leaf's scratch is one reused member, and the transposition key is a fixed 32-byte packed struct.

byte 0byte 31
49 cells × 4 bits = 196 bitsnext discmoves leftdepthunused / zeroed
Packing is injective on the reachable domain (cells 0–9, next disc 1–7, moves remaining 1–5, depth 1–8), so two states collide in the fast table exactly when their strings collided in the reference — hit, miss, insert and strict-LRU eviction sequences are identical, which is why logical work matches even at depth 5 where the cache evicts on almost every store.

Math lookup tables

scoreForWave(d) is floor(7 · d^2.5) and was a pow call per wave; the leaf's readiness(cost) is ldexp(1, 1 − cost). Both became namespace-scope tables: a plain indexed load, no thread-safe-static guard. Each table entry was verified against the original expression over its whole reachable range (wave depths 1–1,087; readiness costs −64–143), and anything beyond the table falls back to the original call.

Optimized leaf evaluator

The leaf rewrite is seven changes, each value-preserving by construction: dead features removed (13 of 24 were computed and discarded, three pow calls among them); the libm call tabled; the per-disc vectors replaced by stack arrays, with a sort replaced by an insertion sort that yields the same order statistic; ~5.5 kB of per-call zero-initialisation replaced by one reused buffer; line analysis collapsed onto the same 128-pattern run-length idea; six full-board passes fused into two while preserving the order within each sum; and a term multiplied by a weight of exactly 0.0 removed, with a finiteness argument that +0.0 is the additive identity here. Everything that would have changed a floating-point result — vectorising the dot product, reciprocal multiplication, split accumulators, float, -ffast-math — was rejected.

Ablation — whole depth-4 / 5-strata decision, variants interleaved within each repeat
0×1×2×3×4×1× = frozen referencetransposition table only: 1.01× (inside the noise; 5.8× per op on 0.6 % of runtime)transposition table only1.01×fast engine only: 1.08× (1.26× on the 20 % that is move application)fast engine only1.08×fast leaf only: 2.61× (carries the result)fast leaf only2.61×all three: 3.08× (slightly super-additive: less allocator contention)all three3.08×
table view
configurationspeedupnote
transposition table only1.01×inside the noise; 5.8× per op on 0.6 % of runtime
fast engine only1.08×1.26× on the 20 % that is move application
fast leaf only2.61×carries the result
all three3.08×slightly super-additive: less allocator contention
24 real decisions, 3 repeats, load ≈22. Worst/best spread within a variant is 1.35–1.74×; nothing finer than that is claimed.

Parity validation

"Faster and the answers still look right" is not a standard this repository accepts. Five gates replay the same inputs through the frozen reference and the fast engine and fail on any difference at all.

gate 1 · anchor
the parameterised slow search vs the frozen depth-4 binary: action, logical work, completed depth, node count, cache hits, cache size
60 moves
0 mismatches
gate 2 · search parity
fast search vs slow search: selected column AND logical work AND completed depth, at nine (depth, strata) configurations up to depth 5 / 7 strata
306 moves, including configurations where the cache evicts on almost every store
0 action, 0 work, 0 depth mismatches
gate 3 · determinism + reflection
repeat call identity; mirrored position gives the mirrored action with identical work
43 moves, 38 asymmetric boards (symmetric boards excluded and counted)
0 mismatches
gate 4 · trajectory
board, next disc, score, score delta, level, moves, terminal flag, board-clear and level-advance flags, and the complete wave list entry by entry
8,288 games · 438,020 moves · 548,263 waves, across a deterministic policy, a depth-3 search and the frozen depth-4 search
0 mismatches
gate 5 · bit-exact leaf
fairLeaf return values as raw 64-bit patterns, not approximately-equal doubles, on states harvested exactly as the search expands them
225,183 real leaf states, plus every table entry
0 mismatches
The column played is always chosen once from the reference state and handed to both engines, so the trajectory gate isolates engine semantics from policy semantics. The one real defect found during the work (an out-of-bounds read in an early isBoardEmpty) was caught by writing the gate first, before it produced a number.

The end-to-end benchmark asserts, on every configuration, that both arms finish with the same score, move count and total logical work, and aborts otherwise; a silent divergence cannot be reported as a speedup.

Measured speedups, both arms in one process, best of three
0×1×2×3×4×1× = frozen referencewhole games, depth 3 / 5 strata: 3.01× (435 moves, work/move 54,826)whole games, depth 3 / 5 strata3.01×whole games, depth 3 / 7 strata: 3.09× (345 moves, work/move 153,759)whole games, depth 3 / 7 strata3.09×whole games, depth 4 / 5 strata: 2.88× (60 moves)whole games, depth 4 / 5 strata2.88×whole games, depth 4 / 7 strata: 2.93× (12 moves)whole games, depth 4 / 7 strata2.93×per decision, depth 4 / 5 strata: 3.1× (3 fixed real roots)per decision, depth 4 / 5 strata3.10×per decision, depth 4 / 7 strata: 3.19× (3 fixed real roots)per decision, depth 4 / 7 strata3.19×per decision, depth 5 / 5 strata: 3.15× (3 fixed real roots)per decision, depth 5 / 5 strata3.15×per decision, depth 5 / 7 strata: 3.23× (3 fixed real roots)per decision, depth 5 / 7 strata3.23×
table view
configurationspeedupnote
whole games, depth 3 / 5 strata3.01×435 moves, work/move 54,826
whole games, depth 3 / 7 strata3.09×345 moves, work/move 153,759
whole games, depth 4 / 5 strata2.88×60 moves
whole games, depth 4 / 7 strata2.93×12 moves
per decision, depth 4 / 5 strata3.10×3 fixed real roots
per decision, depth 4 / 7 strata3.19×3 fixed real roots
per decision, depth 5 / 5 strata3.15×3 fixed real roots
per decision, depth 5 / 7 strata3.23×3 fixed real roots
Load average 22–32 throughout. The speedup is flat to slightly rising with depth and strata, as the decomposition predicts: the last ply is always ≈96 % of nodes, so the leaf fraction does not fall as the tree grows.

Search impact

A 64-game depth-5 / seven-strata cohort was projected at "roughly 75 hours". Measured work at that configuration is 55,765,609 per move against a worst case of 582,727,796 — 10.45× lower, because deeper trees are mostly transpositions — and that correction applies to the unoptimised engine too. On top of it, the fast engine turns the cohort from about 61.8 CPU-hours into about 19.1: roughly 38 minutes of wall time on 30 threads instead of two hours. The thread count is arithmetic by analogy, not a measured scaling preflight.


Further optimization

Everything below is proposed. None of it has an implementation, a gate, or a measurement in this repository unless the entry says otherwise, and each one would need its own registered experiment before a number could be quoted.

Current bottleneck

The decomposition above is blunt about its own limit: with the leaf at ~96 % of nodes, the bound on this decomposition with an infinitely fast leaf is about 5×. After the rewrite, the leaf is still 58 % of the remaining time and move application 41 %. Getting another order of magnitude therefore cannot come from polishing the same serial loop; it has to come from doing less of it, doing it wider, or doing it on different hardware.

1
Leaf memoisationmeasured headroom, deliberately not shipped
264,655 of 764,899 leaf calls in a decision (34.6 %) re-evaluate a state already seen in that decision. fairLeaf is a pure function of (board, next disc, moves remaining), so a memo cannot change its value at any capacity and leaves logical work untouched. It is a cache-semantics change, which the benchmark contract says must be declared, memory-accounted and gated on its own, and its payoff is not obviously positive: a table large enough for the distinct leaf states per decision is ~20 MB per thread, probed 765,000 times, against a 64 MB L3 shared by 30 threads. 34.6 % is the headroom, not the gain.
finding-13 §8.4
2
First-wave restricted popper scanrejected pending an enforced invariant
A board handed to playMove has no poppers (the previous cascade ran to completion), so after a placement only that disc's row and column can pop — 13 candidate cells instead of 49. Exactly equivalent given the invariant, and silently wrong the first time a caller hands the engine an unresolved board. Shippable only with a debug-build cross-check against the full scan, exercised by the gates.
finding-13 §8.3
3
Batched simulation: thousands of boards in lockstepproposed: no implementation
Not for the expectimax hot path, whose tree is irregular and sequential, but for the work search cannot do serially: mass rollouts, corpus generation and training-data throughput. Lay boards out structure-of-arrays (one array per cell, or per bit-plane) so lane b owns board b, and make each cascade wave one vectorised step over every lane — AVX-512 on the CPU (16 cores, 32 threads) or one thread per board on the GPU. Every step in the pipeline above is a bit operation or a table lookup, which is exactly what vectorises. The open costs are divergence (lanes whose cascade has ended idle until the batch settles) and reveal RNG, which must remain bit-compatible with the reference's Mulberry32/stratified draws if the batch engine is to pass the trajectory gate.
docs/hardware/amd-ryzen-halo.md places a full simulator GPU port as a research prototype that must pass transition/RNG parity and beat the CPU end to end
4
Batched leaf evaluation on the GPUproposed — designated GPU candidate in the hardware plan
The leaf is the bottleneck, and a leaf is a fixed function of a 49-cell board: the kind of uniform, data-parallel work a GPU amortises. A search that collects its frontier of leaf states and evaluates them in one batch trades the serial 279 ns per leaf for launch latency plus a per-board kernel. With the current hand-written leaf this would demand a bit-exact floating-point port, which changes summation order on most GPU reductions; it is far more natural once the leaf is a learned evaluator whose semantics the new candidate defines for itself.
gpu-01: PyTorch on ROCm works on this machine's gfx1151 after two documented workarounds, and wins 1.25–5× over the 32-thread CPU for the Drop7-sized policy net depending on batch size (lower bounds, contended host)
5
A learned leaf (NNUE-style) instead of the hand-written onealgorithmic candidate, not a speedup
Replacing fairLeaf with a small network is a new policy, not an optimisation. It is judged by whole-game score, not by agreeing with the old leaf. The attraction is that inference is dense linear algebra, which batches on the GPU and would run in a browser via WebGL or WebGPU shaders. The evidence in status.md is the caution: several learned evaluators already found predictive signal but failed to rank sibling moves well enough to beat fair D4. The hard, unsolved part is training, not running.
docs/research/status.md §4–§7
6
Batched board representation in the browserfar horizon — depends on the two entries above
The same packed representation (49 cells × 4 bits, masks, run-length table) is small enough to live in a texture or a storage buffer, and the cascade steps are shader-friendly integer ops. A playable browser engine that does a shallow lookahead with a learned leaf is plausible engineering once a leaf worth running exists; it would still be gated the same way: the WebGL port replayed against the TypeScript engine, move for move.
no artifact; the TypeScript engine's latent-board mode already provides the deterministic reveals such a port would be tested against
7
Things that would change the game, and are therefore not speedupsallowed only as registered new candidates
Alpha-beta or move ordering (full width at every node is part of what fair D4 is), sharing chance subtrees between sibling columns, skipping the shallow iterative-deepening passes, or raising the transposition capacity so depth 5 never evicts. Each changes logical work or the selected column. They may well be good ideas; they must be tested as policies, with gates that measure score, not equivalence.
finding-13 §8.2; docs/benchmarks.md required correctness gate
today: one board at a time49 bytes per board; one thread walks one cascadebatched: thousands of boards in lockstepone array per cell (or per bit-plane); lane b owns board bcell 0cell 48a “wave” is then one vectorised step over every lane:scan masksfind poppershit coversclear + revealgravitylanes whose cascade has ended idle until the wholebatch settles — this divergence is the cost to measureirregular: each board's cascade has its ownnumber of waves, reveals and moved columns
Today the engine walks one cascade on one board. A batched engine would hold thousands of boards column-wise and advance every cascade one step at a time across all lanes; the per-step logic is unchanged, only the loop nesting flips. Divergence (boards that finish early) is the cost to measure.

Reading further

RecordsTheories, experiments and results that reference this directory

Claim: With seven chance strata (one stratum per disc value, so every individual chance marginal is exact), adding a fifth ply of look-ahead to the fair expectimax search raises mean score on the shared 64-game development cohort by a paired margin whose one-sided 95% whole-game bootstrap lower bound is above zero, as the third-to-fourth ply does (+86,172 with a lower bound of +26,468, finding-05). With five strata, where the third-to-fourth ply gradient is absent (-7,723, not significant), no fourth-to-fifth ply gradient is expected either. Depth and chance-estimator exactness are therefore complements, and the fourth ply is not a special stopping point.

This theory is currently mixed at the public-development (a cohort for deciding what to try next, not confirmation) level.

Claim: A 28-byte column-major board (7 x u32, 4 bits per cell) makes gravity, disc placement and legality constant-time bit operations (PEXT compaction on Zen 5, portable nibble loop elsewhere); combined with a packed-key open-addressed transposition table and per-thread searchers, the fair expectimax search runs bit-identical to the frozen C++ reference at higher throughput and lower memory, scaling near-linearly to all available cores.

This theory is currently untested at the proposal (no games played) level.

It compares fast-engine-parameterized-fair-search, depth 5 (arms d5s7 and d5s5) against the recorded depth-4 arms on the same seeds (d4s7 = runs/RUN-A51D-s7confirm/fresh-s7.json, d4s5 = runs/RUN-A51D-s7confirm/fresh-s5.json), plus a same-cohort fast-engine reproduction of d4s7 as the control at the STANDARD (a 64-game paired development cohort) level, using previously-evaluated-development data.

partial run outcome: fail The run was partial and the outcome was fail (public-development (a cohort for deciding what to try next, not confirmation)). Read the result.

partial run outcome: inconclusive The run was partial and the outcome was inconclusive (public-development (a cohort for deciding what to try next, not confirmation)). Read the result.

It compares rust-engine (drop7-rs cargo crate, std-only) against C++ fast engine + frozen native reference + TypeScript engine at the CHECK (mechanics checks only, no games played) level, using no-gameplay data.

valid run outcome: pass The run was valid and the outcome was pass (mechanics-only (checks only, no games played)). Read the result.

resultpartial runoutcome: failtier: public-developmentRS-20260821T181917Z-9a34ba02

The run was partial; the outcome was fail, at the public-development (a cohort for deciding what to try next, not confirmation) level. Of 6 preregistered checks, 3 passed and 2 failed.

The fifth ply buys nothing at either chance resolution, and the earlier interim reading that it was actively harmful is withdrawn. Complete leg, 64 of 64 games: depth 5 at five strata scores 288,704 against depth 4 at five strata's 297,327, a paired -8,624 with a one-sided 95% whole-game bootstrap lower bound of -55,134 and W-T-L 33-0-31, for 23.29x the logical work per move. That is a wash, not a reversal. Partial leg, 16 of 64 games and still running: depth 5 at seven strata is -1,581 against the depth-4 seven-stratum control (95% lower bound -173,154, W-T-L 7-0-9) at 34.32x the work, and +16,622 against depth 3 at seven strata (95% lower bound -130,027, W-T-L 8-0-8) at 1,084.78x the work. At a fixed stratum count, depth 3 -> 4 -> 5 does not separate. READ THIS BEFORE QUOTING THE MEANS: the eye-catching gap between the 398,498 of d4s7 and the 288,704 of d5s5 is a chance-samples effect and not a depth effect, because those two arms differ in both factors; the correct paired depth contrasts at fixed chance resolution are d5s5 - d4s5 = -8,624 and d5s7 - d4s7 = -1,581, both indistinguishable from zero, and the correct paired stratum contrast at fixed depth is finding-05's d4s7 - d4s5 = +101,171. The interim slice reported in finding-15 section 2.2 (-268,611 over 8 paired games) was completion-order biased against depth 5 exactly as that section warned; at 16 games the bias is gone and the delta is -1,581. The engine control is clean and is the other retained result here: the fast engine's depth-4 seven-stratum arm reproduces the recorded unoptimised arm over 64 paired games x 11 fields with 0 mismatches, and the depth-5 five-stratum arm reproduces the recorded 32-game unoptimised arm over 32 paired games x 11 fields with 0 mismatches across two binaries and two different cache capacities. Every arm audited 0 incomplete decisions at its requested depth, 0 censored games and 0 score-decomposition identity failures. Flow rates fall with depth at five strata (1.9387 clears and 1.0651 reveals per move against depth 4's 1.9489 and 1.0697, and against the 2.400 and 1.400 indefinite survival needs), so nothing here moves toward the target.

Technical recordLimitations recorded with the resultRS-20260821T181917Z-9a34ba02
  • PARTIAL ARM: the depth-5 seven-stratum arm holds 16 of 64 games and was still executing (process 127323) when this result was written. Every seven-stratum number here is over those 16 paired games and none of them can decide the primary gate. The frozen snapshot assessed is runs/RUN-20260821T060358Z-895d0a79/d5s7-partial-16games.json; the live artifact will be rewritten to 32 games and beyond and will no longer match this record's hash.
  • The 16 finished games are the cohort's first 16 seeds (0xa51d1000-0xa51d100f) because the runner completes whole 16-game chunks, so they are a fixed block rather than a random subset; they are paired seed-for-seed against comparators recorded on exactly those seeds, which is what makes the paired delta fair even though the 16-game mean is not an estimate of the arm's 64-game mean.
  • 64 paired games with a score standard deviation of 38-64% of the mean; at this sample size a true depth effect smaller than roughly 55,000 points cannot be resolved, so 'no gradient' means 'no gradient this cohort can see', not 'exactly zero'.
  • The cohort 0xa51d1000-0xa51d103f was opened by finding-05 and is permanently development data. This is a diagnostic comparison at STANDARD tier; it is not a freeze gate and can never become confirmation evidence.
  • The depth-5 arms declare a 200,000-entry transposition cache and the depth-4 comparators 60,000. Capacity provably cannot change play (verified three ways in finding-15 section 3, including whole-game agreement across two binaries) but work per move is not comparable across capacities and each figure must be read with its capacity.
  • The experiment record was written after the runs, by a different agent than the one that executed them. The comparison rule predates the arms and is implemented in analyze.py, but this is retroactive registration and not a preregistration; the amendment on the experiment record says so.
  • Wall-clock figures are not timing-grade: the machine carried a one-minute load average of 12-63 from other agents' jobs throughout. Scores, moves and logical work are deterministic and unaffected.
  • This rejects the exact configurations tested - depth 5 at five and seven strata, this leaf, this terminal utility, these work bounds. It does not show that no five-ply search can help. finding-15 section 5 names two mechanisms that would predict exactly this outcome (the terminal utility has no death-depth shaping, and the leaf is an uncalibrated potential); neither was tested here.
  • Cost model correction, recorded because it changes what is affordable rather than what is true: the 78-wall-hour projection that once cancelled this experiment used worst-case iterative-deepening work. Measured work at depth 5 with seven strata is about 10x below that bound, and the arms ran in hours.

Full record →

resultpartial runoutcome: inconclusivetier: public-developmentRS-20260821T205102Z-d89df4b5

The run was partial; the outcome was inconclusive, at the public-development (a cohort for deciding what to try next, not confirmation) level. Of 6 preregistered checks, 3 passed and 2 failed.

SUPERSEDES RS-20260821T181917Z-9a34ba02, which assessed this experiment when the depth-5 seven-stratum arm held 16 games. The arm was stopped by the repository owner's decision at the 32-game chunk boundary and will not be resumed, so its analysis is now FINAL even though the cohort is partial: 32 of 64 planned games, every one of them a whole game, 0 censored, 0 score-decomposition identity failures, 0 incomplete decisions, minimum completed depth 5. The old record remains committed history and is not edited. THE HEADLINE IS A CORRECTION, NOT AN UPDATE. The previous record read the fifth ply as 'does not separate'. That reading was a NON-MEASUREMENT REPORTED AS A NULL. Doubling the sample from 16 to 32 games moved the depth-5-minus-depth-4 seven-stratum contrast from -1,581 to +23,367 and its median from -39,660 to +18,820 - THE SIGN FLIPPED - which is what a quantity being estimated far below its detection floor looks like. By chunk the paired mean is -1,581 on the first 16 seeds and +48,315 on the second 16. Do NOT replace the old reading with 'depth 5 helps': +23,367 is equally unsupported. The one-sided 95% bootstrap lower bound is -83,046 and the contrast's detection floor at n=32 is 107,988, so the estimate sits at 22% of the smallest effect this cohort could have resolved. The correct statement is that THE FOURTH-TO-FIFTH PLY CONTRAST AT SEVEN STRATA WAS NEVER MEASURED, in either record. THE POWER ANALYSIS IS THE MOST USEFUL THING THIS EXPERIMENT PRODUCED. Detection floor, taken as 1.645 x sd / sqrt(n), the smallest true effect whose one-sided 95% bound would clear zero: d4s7-d4s5 +101,171 against a floor of 55,192 (n=64); d4s7-d3s7 +86,172 against 61,457 (n=64); d5s5-d4s5 -8,624 against 47,052 (n=64); d5s7-d4s7 +23,367 against 107,988 (n=32). EVERY SIGNIFICANT RESULT IN THIS FACTORIAL IS ABOVE ITS FLOOR AND EVERY NULL IS BELOW IT - the factorial separated the contrasts it had the power to separate and nothing else. Resolving the observed +23,367 needs about 684 paired games; finishing to the planned 64 would have left a standard error near 46,400 against a 23,367 estimate, still a non-measurement. That is the justification for the stop: the contrast is not answerable at any affordable cohort size, so the marginal machine-day buys no information. The variance is structural, not fixable by tidier running: the five largest single-seed paired deltas are -1,002,862, +958,985, -678,455, +592,546 and -577,069, so individual games swing by more than twice the cohort mean. WHAT IS ACTUALLY MEASURED HERE, and it is the same lesson from the other side: at depth 5, going from five to seven strata is worth +123,613 with a lower bound of +32,575, W-T-L 19-0-13 - SIGNIFICANT, and comfortably above its 95,207 floor - for 5.85x the work. The chance-exactness axis pays at depth 5 exactly as it pays at depth 4 (+101,171 [+47,447] there). The previous record's warning therefore survives and is strengthened: the eye-catching gap between d4s7's 398,498 and d5s5's 288,704 is a CHANCE-SAMPLES effect, not a depth effect, and both stratum contrasts are now significant while no depth contrast is. The engine controls are unchanged and clean: the fast engine's depth-4 arm reproduces the recorded unoptimised arm over 704 field comparisons with 0 mismatches, and the depth-5 five-stratum arm reproduces its recorded 32-game predecessor over 352 comparisons with 0 mismatches across two binaries and two cache capacities.

Technical recordLimitations recorded with the resultRS-20260821T205102Z-d89df4b5
  • SUPERSESSION: this record replaces RS-20260821T181917Z-9a34ba02 for the same experiment. That record was written and committed when the depth-5 seven-stratum arm held 16 games, and its central depth claim reversed sign when the sample doubled. It is left byte-unchanged, because this repository has no precedent for annotating a committed result in place; the relationship is carried here and by the theory record's evidenceRefs. Quote this record, not its predecessor.
  • PARTIAL BUT FINAL: 32 of 64 planned games. The run validity stays partial because the planned cohort was not completed; the analysis is nevertheless final, because the stop was a decision and the arm will not be resumed. These are different things and the record keeps them apart deliberately.
  • THE PRIMARY CONTRAST IS UNMEASURED, not null. Neither -1,581 at n=16 nor +23,367 at n=32 is evidence about the fifth ply at seven strata. Anyone quoting either number as a finding is quoting noise.
  • The 32 games are the cohort's first two 16-seed blocks, a fixed prefix rather than a random subset, because the runner completes whole chunks. They are paired seed-for-seed against comparators recorded on exactly those seeds, which is what keeps the paired delta fair; the arm's 32-game mean is not an estimate of a 64-game mean.
  • The detection floors are a normal approximation (1.645*sd/sqrt(n)) applied to heavy-tailed paired deltas with sample skewness between -0.30 and +0.92. They are planning quantities, accurate to roughly the 1-5% by which they differ from the percentile bootstrap the tooling reports, not exact power guarantees.
  • The 684-game and 13-wall-day figures assume the observed effect size is the true one and that throughput matches this run's 1,647 s per game at 14 threads. Throughput varied 2.3x between the two chunks under other agents' load, so the wall estimate spans roughly 8-18 days. If the true effect is smaller than +23,367 the required cohort grows quadratically.
  • The one significant new result, d5s7 - d5s5 at +123,613 [+32,575], rests on 32 games rather than 64 and on an arm that was stopped; it is a stratum contrast at fixed depth 5 and should be replicated on a fresh block before it is leaned on.
  • The cohort 0xa51d1000-0xa51d103f was opened by finding-05 and is permanently development data. STANDARD tier, diagnostic only, never a freeze gate and never confirmation evidence.
  • The depth-5 arms declare a 200,000-entry transposition cache and the depth-4 comparators 60,000. Capacity provably cannot change play but work per move is not comparable across capacities.
  • The experiment record was written after the runs, by a different agent than the one that executed them; its amendment says so. The comparison rule predates the arms.
  • This still rejects nothing about five-ply search in general - only these depths, this leaf, this terminal utility, these work bounds. The two mechanisms named in finding-15 section 5 remain untested, and a flat-and-unmeasurable depth axis is consistent with both.
  • Nothing here moves the ceiling. The best arm on this cohort is 411,874 on 32 games against the 1,050,000 the frozen qualification protocol requires, and every game ended.

Full record →

resultvalid runoutcome: passtier: mechanics-onlyRS-20260824T075451Z-e89ea128

The run was valid; the outcome was pass, at the mechanics-only (checks only, no games played) level. Of 6 preregistered checks, 6 passed and 0 failed.

The Rust bitboard engine is trace-equivalent to the frozen C++ reference, the proven C++ fast engine, and the TypeScript engine on every observable, and is the fastest of the three. Board representation is seven u32 column words at 4 bits per cell (28 bytes): gravity is a single PEXT bit-gather per column, a row rise is (word << 4) | SOLID, and cover hits are counted board-wide with a 4-way bitboard parallel counter. All parity gates pass with zero mismatches: 3 trajectory arms (512 center + 256 search-policy games vs C++ playHeadlessMove; 256 games vs the TypeScript seededRandom driver) totalling 36,427 moves and 40,286 waves; 150,854 leaf states bit-identical as uint64 patterns; 105 d4s7 and 10 d5s7 roots with bit-identical per-column values and identical actions; the values gate re-run with the transposition table enabled proves cache-independence. Measured on the shared AMD Ryzen AI MAX+ 395 workstation (best-of-N, load 1.1-1.7): single-core engine throughput 12.8M moves/s vs C++ fast 6.5M (1.97x) and TypeScript 0.65M (19.8x); leaf 155.6 ns vs C++ fast 187.5 ns (1.20x); fair search at d4s7 908 ms/decision vs C++ fast 1,071 ms (1.18x) with a 3.1 MB direct-mapped table vs the C++ 16.2 MB LRU; d5s7 7,047 ms at 1M entries vs 7,817 ms (1.11x). Game-level scaling is shared-nothing and near-linear (10.3x on 16 physical cores on the shared machine; 14.1x in a clean run), with identical results at every worker count. A key recorded finding: the transposition table's 1.3% node hit rate is misleading — each hit prunes a whole subtree, so the table eliminates ~47% of work at d4s7 and ~90% at d5s7, and a cheap direct-mapped depth-preferred table captures nearly all of the strict-LRU table's payoff at a fifth of the memory. No strength claim; no new seeds opened.

Technical recordLimitations recorded with the resultRS-20260824T075451Z-e89ea128
  • CHECK-tier engineering result: proves equivalence and measures speed/memory, but makes no policy-strength claim and advances no benchmark tier.
  • Timing measured on a shared workstation (load average 1.1-1.7); ratios between back-to-back arms are the trustworthy quantity, absolute nanoseconds are not.
  • The d5s7 arms ran 3 decisions each (1 repeat) because a single d5s7 decision costs 7-63 s; the d4s7 arms ran 21 decisions, best of 3.
  • The direct-mapped table's hit rate at d5s7 (748k-825k hits/decision) trails the C++ strict-LRU table (911k); a set-associative table is the recorded reopening direction.
  • No GPU, latent-mode, or native-scenario variant: scripted-round and scenario duties stay with the existing engines.

Full record →

Agent contextSource files, operational notes and how to reproduce