1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
# Re-imagining the baseline LMR, 2026-10-03
Question: the dumb flat `-1 ply on every leftover move` is load-bearing to
~40-50 Elo and +1.17 ply (`GET_LMR_REDUCTION.md` 10.12a). Five attempts to
improve on it have all measured as noise. If we rebuilt the baseline from
scratch with what we now know, what would it be?
Not looking for a miracle. Looking for ~5 Elo and some sound depth.
---
## The reframing that matters
**Every one of the five attempts pushed in the same direction: trade accuracy
for depth.** Graded magnitude, wider admission, bigger cuts, LMP, deep
pruning -- all of them reduce or remove more search in exchange for a higher
root iteration number.
We now have that direction's exchange rate: **~8 Elo per marginal ply**
(10.12b). It is a bad rate, and it is bad for a structural reason -- a
reduction is a *transfer*, not new resource. It lowers the depth of the
refutation lines (where the move choice is actually decided) to raise the
depth of the PV. Time-bought plies, which raise the whole distribution, are
worth ~95 Elo/ply.
**Nobody has ever tried the other direction: trade depth for accuracy.**
That is the gap. If accuracy converts better than depth -- and there is no
reason to assume it converts at 8 Elo/ply, because it is not buying plies at
all -- then the remaining few Elo are on that side of the ledger, not this one.
---
## What the five attempts actually taught us
| lesson | evidence |
|---|---|
| **Coverage beats magnitude.** Breadth of admission moves total tree size far more than depth-of-cut per move. | section 3 (HEAD-style reduces 76% of decisions and beat the "honest" 62-66% design on node count); section 6 |
| **Magnitude is capped by the depth DISTRIBUTION, not the formula.** 76% of the LMR population sits at remaining depth buckets 2-3, where `iMaxExtra = depth - 3` permits zero extra plies -- and where the `log(d)*log(m)` curve prescribes ~1 ply anyway, i.e. exactly flat. The curve's whole contribution is **mean 0.164 extra plies ~= +0.05 root ply**. | 10.8, 10.12c |
| **Learned per-situation grading loses to a flat formula.** Table-driven tiers vs flat `log(d)*log(m)`: 0.224 vs 0.244 mean extra plies -- a wash, and the tier version broke with a re-search-rate inversion when widened. | section 8, 10.8 |
| **Per-move dynamic signals cannot identify the dangerous moves.** 11 of 23 verified low-band prune misses sit at own-FH-rate **exactly 0** -- the per-move history agreed with the static table and both were wrong. A veto catching the rest would refuse ~49,000 sound prunes per miss prevented. | 10.12h |
| **`eNodeType` is uninformative here.** Full-width ALL nodes fail HIGH 69-71% of the time. The structural label is close to meaningless. | 10.12f, 10.12d |
| **Move ordering is the real sophistication, and it is already good.** FMBC 90-95%; `r ~= 2.17`. By the time a move is in the leftover pool the generator has already triaged it -- the table is re-triaging an already-triaged population. | 10.11a, 10.12c |
| **Only fail-HIGHS are verified.** The re-search path fires on `iScore >= iBeta`. A reduced search that fails low is accepted silently, forever. | search.c move loop |
That last row is the one nobody has acted on.
---
## The redesign
### A. Keep (this is where the 40 Elo lives)
- **Broad admission.** ~93% of eligible decisions get reduced. Five attempts
to be cleverer about *which* moves all failed.
- **One magnitude formula, `log(depth)*log(moves)`.** Already present
(`g_iLMRQuietReduction`, dynamic.c, built by `InitLMRTable`). Keep it as the
single source of magnitude. No table, no tiers.
- **The one-full-width-ply floor.** `JOINT_EXTRA_MIN_REMAINING_DEPTH = ONE_PLY`
guarantees a reduced move still gets a real node before qsearch. Stockfish
keeps the identical invariant (`search.cpp:1370`, `std::max(1, ...)`) and
gets its leanness from separate explicit pruning. Twice falsified as a
target (10.9a).
- **Killer-adjacency vetoes** (this ply's killers, ply-3's). Cheap, exact,
principled -- a specific move at a specific ply, not an aggregate rate.
### B. ADD: verify fail-lows near the margin <-- the actual proposal
The only unverified error direction, attacked with the search's own evidence
rather than a static prior.
Today, in `search.c`'s move loop:
```c
iScore = -Search(ctx, -iAlpha - 1, -iAlpha, uNextDepth); /* reduced */
if ((iExtend < 0) && (iScore >= iBeta)) { ...re-search full depth... }
```
At a null window `iBeta == iAlpha + 1`, so that branch means "the reduced
search beat alpha". A reduced search that returns `iScore <= iAlpha` is taken
at face value. Proposed addition:
```c
else if ((iExtend < 0) && (iScore > iAlpha - LMR_NEAR_MISS_MARGIN))
{
/* Reduced search failed low, but only just. The reduced search is the
weakest evidence in the tree and this is the one direction the
existing machinery never checks. Re-search at full depth. */
uNextDepth -= iExtend;
pi->iExtensionAmount = 0; /* defuse the null-move bailout */
iScore = -Search(ctx, -iAlpha - 1, -iAlpha, uNextDepth);
}
```
Why this is different from everything tried:
- **Sound by construction.** It only ever ADDS search. It cannot introduce a
wrong answer; the worst case is wasted nodes. Every previous attempt risked
correctness to buy depth.
- **It trades depth for accuracy** -- the untried direction. It will cost
nominal plies (marginal plies are worth ~8 Elo, so that is a cheap thing to
spend) in exchange for catching refutations the reduction hid.
- **Self-measuring, and falsifiable in one suite run.** Instrument
`near_miss_researches` and `near_miss_flips` (how many came back
`>= iBeta`). If the flip rate is ~0, the idea is dead immediately and
cost nothing. If it is a few percent, those are refutations currently being
missed, and the margin can be swept.
- **It is targeted.** A move returning `alpha - 5` nearly made it; one
returning `alpha - 300` did not. Score proximity is real evidence, unlike
the cell averages the 4D table offers.
Sweep `LMR_NEAR_MISS_MARGIN` over 0 / 10 / 25 / 50 / 100 cp. Measure flip rate
and node cost per margin before any self-play.
### C. DELETE (each is unmeasured or measured-as-noise, and each costs real
### work per decision in a SHIPPING build)
- **The 4D table admission gate.** `LMR_STATIC_ADMIT_PCT_THOUSANDTHS` vetoes
509 of 21,780 cells -- 2.3% of cell space, 7.4% of traffic -- and the
threshold was chosen in section 6 on solve-count deltas 10.6 showed were
noise. Removing it also removes `EstimateHangingMaterialCp` plus four bucket
functions plus a table lookup *per eligible move*, which is the same class of
per-move cost the PERF_COUNTERS cleanup just went after. (Hold if EFP ends up
needing the table -- see 10.13 item 1.)
- **`fDynamicDanger`'s extras gate.** Fires on 27.4% of decisions and only
zeroes the extra plies -- which 85% of reduced moves never get anyway. The
section 1 "honesty fix" that motivated the whole 2026-10-02 session is
therefore nearly inert in shipped behaviour.
- **The bare countermove-match veto.** Unconditional, unmeasured, and
generate.c's own use of a countermove match is evidence-GATED precisely
because a bare match was shown not to carry signal.
- **`JOINT_EXTRA_TIER1/2/3_PCT_THOUSANDTHS`** -- vestigial (TIER1 in comments
only, TIER3 only in the disabled LMP path).
### D. CONSIDER: widen coverage past the leftover pool
`fPruneReduceEligible` requires `fIsLeftoverMove` (sub-`GOOD_MOVE`). Crafty and
Stockfish reduce **all** quiet moves past a move-count threshold, including ones
the generator scored into a higher tier. Coverage is the single lever with
replicated evidence behind it (section 3, section 6, 10.2), and this is the
largest untapped slice.
But: it buys marginal plies, so expect ~8 Elo/ply and bound expectations
accordingly. Do it *after* B, because B makes additional coverage safer -- the
verification catches the extra mistakes wider coverage would make.
### E. DO NOT (all falsified; reasons in 10.13 item 8)
Per-situation magnitude grading; `eNodeType` modulation; clamp removal /
reducing into qsearch; dynamic per-move vetoes; chasing bigger cuts.
---
## What this adds up to
A baseline LMR that is **simpler** than today's (no 4D table, no dynamic
extras gate, no countermove veto, one magnitude formula) and has **one new
mechanism** that is sound by construction and pushes in the only untried
direction.
Honest expectation: the deletions are roughly Elo-neutral and buy a little
nps; the near-miss verification is the only part with real upside, and its
upside is unknown rather than bounded at 8 Elo/ply, because it is not buying
plies. If its flip rate comes back near zero it dies in one suite run, which
is the cheapest possible failure mode.
That is the whole pitch: stop buying depth at 8 Elo/ply, spend some of it back
on the one error the search never checks.
## Order of work
1. Instrument the near-miss flip rate ONLY (no behaviour change): count how
many reduced fail-lows land within each margin band, and -- behind a
verify flag -- how many of those flip to `>= iBeta` at full depth. One
suite run answers whether B is worth building.
2. If the flip rate is material, implement B and sweep the margin on node
count + `sn` suites, then self-play with `elo0=0 elo1=15`.
3. Do the deletions in C as a separate commit, measured on nps (idle box,
round-robin arms) rather than on Elo -- they are a complexity/speed play,
not a strength play.
4. D last, and only if B worked.
|