-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcengine.py
More file actions
2207 lines (2092 loc) · 129 KB
/
Copy pathcengine.py
File metadata and controls
2207 lines (2092 loc) · 129 KB
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
cengine.py -- Python root driver for the C search core (csearch.so).
====================================================================
A drop-in ``Engine`` for the project's battle/match harness, with the
ENTIRE per-node search loop in C (csearch.c): board, move ordering,
transposition table, pruning, quiescence and the full static eval
(bit-exact port of engine.py's ``_evaluate_static``, verified over 3M
positions). Born as phase-3 step 6 of the C-core plan; the shipped engine
since Old Engine/31. Its defaults ARE v42 -- v41 + CW-01 cannot-win eval
clamp (+3.27 +/-6.8 vs Old Engine/41, a null KEPT as correctness: the
eval no longer favors sides that cannot force mate; snapshotted Old
Engine/42). v43 = v42 MINUS CB-02's deep-null verification: NV-01
measured the removal at +5.18 +/-6.8 vs Old Engine/42 (pair ratio 1.08),
converging with CB-02's own -2.88 lean -- the insurance cost ~3-5 Elo of
nodes-to-depth and is DROPPED (modern-engine practice); snapshotted Old
Engine/43; FI-04 history-LMR read +2.15 null and is DORMANT -- the
finer-quiet-signal vein is 0-for-3). v44 = v43 + FI-26a, the unconditional
TT prefetch after apply_move (node-identical, +4.9% NPS): the timed A/B
priced it at +13.31 +/-6.8 vs Old Engine/43 @10k 50+0.20 (51.91%, pair
ratio 1.25, norm +27.85) -- P-45's null INVERTED by FI-01's free child
key, the biggest single NPS win of the C era in Elo terms; snapshotted
Old Engine/44 (a staged-quiet lazy pick was tried alongside and PARKED,
bench noise). v45 = v44 + FI-25, the TT-value pruning-eval sharpener:
+13.52 +/-6.8 vs Old Engine/44 @10k 50+0.20 (51.94%, pair ratio 1.22,
norm +28.34) -- sonnet5's top new idea confirmed at full value, back to
back with v44's +13.31; snapshotted Old Engine/45. FI-18 SEE pruning of
losing captures read -1.25 null and FI-06 root-move ordering read +2.26
null (both DORMANT, mechanisms kept) vs Old Engine/45. v46 = v45 with the
TT doubled to 22 bits (96 MB): +5.94 +/-6.8 vs Old Engine/45 @10k 50+0.20
(50.85%, pair ratio 1.10, norm +12.33) -- a borderline-positive (CI just
touches zero) shipped on the monotonic-low-risk rationale, motivated by a
hashfull capture showing a single deep search fills half the 48 MB table;
snapshotted Old Engine/46. v47 = v46 with the TT at 23 bits (192 MB):
+3.16 +/-6.8 vs Old Engine/46 @10k 50+0.20 (50.46%, norm +6.54) -- the
96->192 MB increment, net-positive at full load (same monotonic-low-risk
ship); the diminishing +5.94->+3.16 CLOSES memory-scaling (no 24 probe).
v47 also carries MultiPV (UCI spin 1..5, node-exact off). Snapshotted Old
Engine/47. Since v47: the time-policy vein closed on two more nulls --
soft_stop_frac 0.60 (+1.29 +/-6.8, nineteenth campaign) and the FI-09 bundle
(SINGLE_REPLY_INSTANT + EASY_MOVE, +0.69 +/-6.8, twentieth campaign vs Old
Engine/47) -- both reverted to their v47 defaults, dormant. FI-23
history-driven quiet pruning REJECTED 2026-07-16 (twenty-first campaign vs
Old Engine/47: -5.23 +/-7.1, SPRT ACCEPT H0 stopped early at 9,243 games --
a real negative; HIST_PRUNE reverted to 0, dormant, do-not-retry; the
shallow quiet/capture-prune vein is 0-for-2 with FI-18). v48 = v47 +
FI-30, the qsearch TT-quality batch: QS_TT_SHARPEN (FI-25's bound rule at
both qsearch stand-pat sites, raw_stand split keeping the FI-03 cache
exact) + QS_KEEP_MOVE (FB-22's keep-move rule for qs_tt_store move-0
stores). CONFIRMED 2026-07-16 over the longest campaign on the books
(twenty-second, vs Old Engine/47, four pooled tranches = 21,605 games @
50+0.20): +4.73 +/-3.19 (50.68%, pair ratio 1.08, norm +9.70), pooled
GSPRT[0,4] LLR +3.475 crossing the +2.944 accept -- the C era's first
sequential-test ACCEPT, reached after a premature 10k-cap revert was
walked back and the test ran to its own stopping rule. Snapshotted Old
Engine/48. v49 = v48 + FI-29, cuckoo upcoming-repetition (CYCLE_DETECT):
the side to move can force a repetition with one reversible move -> the
node takes the contempt draw a search earlier. KEPT-ON-NULL 2026-07-17
(+0.97 +/-6.8 @10k vs Old Engine/48, GSPRT LLR -0.19) -- the sixth
correctness release of its class; CYCLE_VERIFY differential 13,272/0,
paired matetrack noise-flat. Snapshotted Old Engine/49. Twenty-fourth
campaign (2026-07-18, vs Old Engine/49): the FI-50/51/52 qsearch-TT batch
(abi 14; QS_BETA_NARROW + QS_TTM_EXEMPT + QS_CHK_D1) read a dead NULL --
-0.28 +/-6.8 @10k, pair ratio 1.00, GSPRT LLR -0.797 flat -- all three
REVERTED to False (dormant, not correctness-class; matetrack had passed
907/778 vs 900/773). Defaults reproduce v49 node-exact. FI-48 flag-aware
TT replacement (TT_KEEP_EXACT, abi 15) built 2026-07-18 and CLOSED AS A
DEAD GATE pre-A/B: instrumented engagement ~0.001% of nodes at both
levels under the production config -- the probe-side EXACT cutoff
structurally prevents the overwrites the shield guards against, and the
192MB TT does not saturate at this TC (FI-08/FI-20 context). No slot
spent. Mechanism kept at 0 = v49 node-exact. Twenty-fifth campaign
(2026-07-18, vs Old Engine/49): FI-49 fail-high tightening REJECTED --
-3.65 +/-6.8 @10k, ratio 0.94, LLR -2.403 (reject-lean; the +28% node
cost never paid, as the matetrack dip predicted) -- reverted to dormant.
v50 = v49 + FI-53/54 (KEPT-ON-NULL 2026-07-18, twenty-sixth campaign vs
Old Engine/49 on rotated seed 50: +1.60 +/-6.8 @10k, LLR +0.117 flat --
seventh+eighth correctness-class releases; TT_R50/TERM_STORE/TT_MATE_CUT
= True are the shipped defaults, abi 17; matetrack had leaned positive
905/777 vs 893/768). Snapshotted Old Engine/50. v51 = v50 + FI-56
root-move LMR (ROOT_LMR=True, abi 18) -- the search/pruning lane's opening
statement and the C era's SECOND SPRT ACCEPT: twenty-seventh campaign vs
Old Engine/50 on seed 50, 2k screen +17.56 +/-15.3 (CI excluding zero, the
strongest screen on the books) then the offset-1000 main tranche ACCEPTED
H1 at 7,343 games (+9.37 +/-8.0, LLR +2.957, stopped early); pooled
verdict 9,343 games: **+11.12 +/-5.3** (51.60%, ptnml 220/996/1988/1173/
282, pair ratio 1.20, pooled GSPRT[0,4] LLR +4.549) -- the biggest
single-feature gain since FI-25. Matetrack had passed strongly positive
(924/794 vs 896/769). Snapshotted Old Engine/51; campaigns now run vs Old
Engine/51 on SUBSET_SEED 51. v52 = v51 + FI-24(a)+(b), the null-move
refinement batch (NULL_NODOUBLE: no null-after-null via the prev12
sentinel; NULL_EVALR: R += (prune_eval-beta)/200 capped +2 -- deep nulls
only at clearly-winning nodes): CONFIRMED 2026-07-21 (thirty-first
campaign vs Old Engine/51, nodes@1.75M): pooled 12,000 games **+6.63
+/-4.5**, pooled GSPRT[0,4] LLR +4.533 ACCEPT -- the third SPRT accept,
and the first verdict confirmed on the nodes instrument. Snapshotted Old
Engine/52; campaigns now run vs Old Engine/52 on SUBSET_SEED 52. Also in
this tree: real UCI pondering (go ponder/ponderhit, host layer).
v53 = v52 + the **Texel eval retune** -- NO change in this file or in
csearch.c: 44 eval scalars refitted in engine.py, which this module pushes
into csearch.so at construction (the eval-param oracle, _load_pyengine +
csearch_set_eval below). Fitted by tuning/texel.py on 4M quiet positions from
this project's own self-play logs, labelled with the GAME RESULT.
CONFIRMED 2026-07-22 (thirty-second campaign vs Old Engine/52,
nodes@1.75M): pooled 12,000 games **+37.52 +/-6.3** (55.38%, ptnml
245/1133/2264/1802/556, pair ratio 1.71, GSPRT[0,2] LLR +9.918 ACCEPT) --
the fourth SPRT accept, 2.8x the bound, and by a wide margin the largest
single gain in the C era (previous best +11.12). The eval lane's first
win, opened right after the search lane was declared exhausted. Full
detail in engine.py's version history. v54 = v53 + the **PST retune**
(tuning/texel.py --pst): the 736 piece-square entries fitted for the first time,
735 values moved, again NO change in this file. CONFIRMED 2026-07-23 vs Old
Engine/53 (nodes@1.75M): **+31.20 ±5.6 over 11,668 games** (54.48%, ptnml
312/1142/2185/1579/616, GSPRT[0,2] LLR +7.806 ACCEPT) -- the second-largest
release, both split halves positive. Snapshotted Old Engine/54; campaigns
now run vs Old Engine/54 on SUBSET_SEED 54.
v55 = v54 + **two node-identical SPEED changes** -- the first release in the
C era that buys no new moves at all, only more of them per second. FI-11
pin-aware legality (one pinned mask per node makes legality free for unpinned
ordinary movers out of check; king/ep/in-check keep the full scan) and FI-42
the (mg,eg,phase) accumulator on Board (apply_move maintains the tapered
material+PST sum on the squares FI-01's Zobrist update already touches, so
eval_white's 12 ctz loops over 32 pieces are gone). Both are bit-identical --
**bench signature 1,461,732 UNCHANGED**, ladder node-exact, perft --deep
1.49B clean, and a 117M-node accumulator differential -- so NO A/B slot was
spent and the ledger's Elo total is untouched. NPS, on the FI-84 instrument
with its new --repeat control: **+8.3% on x86** (Gold 6330, 48/48 rounds,
between-run spread 0.05) and **+13.5% on arm64**. FI-42 is ~+8 points of that
on BOTH machines; FI-11 is +5% on arm64 and a wash on x86, which is the
release's other lesson -- deleting work travels across architectures,
reorganising branches does not. CONFIRMED 2026-07-25 vs Old Engine/54 (TIMED 50+0.20 on
an Intel Gold 6330, 108 workers): **+9.66 +/-8.2 over 6,874 games** (51.39%,
ptnml 133/783/1460/866/188, pair ratio 1.15, normalized +14.98 (FB-54 scale; quoted as +21.18 pre-fix), GSPRT[0,4]
LLR +2.946 > +2.944 ACCEPT, stopped early) -- the C era's sixth SPRT accept.
TIMED on purpose: the --nodes instrument reads exactly zero for a
node-identical change (both sides search the same tree, and its NPS
calibration would cancel the speed being tested). THE CALIBRATION THIS BOUGHT:
+8.34% NPS -> +9.66 Elo = **~1.16 Elo per 1% NPS**, the LOW end of the
historical 1-2.7 band (v39 ~1, v44 ~2.7) -- so future bench items are worth
about a point per percent, which prices FI-83 (0-3%) at 0-3.5 Elo and makes
the NPS lane a minor one from here. Snapshotted Old Engine/55; campaigns now
run vs Old Engine/55 on SUBSET_SEED 55.
v56 = v55 + **FI-107 ProbCut** -- the fail-high half of forward pruning, which
this engine simply did not have. At a non-PV node past depth 5 a qsearch
filters each capture at beta + 200 and a real depth-4-reduced negamax CONFIRMS
before anything is cut; a deeper TT bound vetoes the probe. Nothing is ever
pruned on a static score, which is what sank FI-18 (-1.25) and FI-23 (-5.23) --
the two-stage verify is a different mechanism, not a wider-margin retry.
**Bench signature 1,461,732 -> 1,145,629 (-21.6%)** at a 2.4% NPS cost.
CONFIRMED 2026-07-30: **+4.11 +/-4.2 over 21,806 games** on --nodes 1.75M
(50.59%, ptnml 530/2606/4452/2706/609, GSPRT[0,4] LLR +2.971 ACCEPT), then the
pre-registered TIMED cross-check owed since FI-24: **+11.44 +/-6.9 over 5,940
games** at 50+0.20 vs Old Engine/55 (LLR +2.953 ACCEPT, nElo +17.07). The
timed figure is what the ledger banks (+294 -> +305), and the gap between the
two is the release's real lesson: **--nodes UNDER-CREDITS a node-saving
change**, because its NPS calibration charges the change for its own overhead
up front while a clock lets the saving become depth. Two more lessons banked:
the +15 screen gate is TRIAGE not a ship threshold (this read +2.90 on a 2k
screen and was wrongly closed as null), and match.py scores its pentanomial
from ENGINE 1 so the CANDIDATE GOES IN SLOT 1. Snapshotted Old Engine/56;
campaigns now run vs Old Engine/56 on SUBSET_SEED 56. Armed candidate: none
pinned. NNUE (FI-15 + FI-106) is CONFIRMED at +33.83 on a clock but is NOT
armed and NOT in this release -- it ships separately.
v57 = v56 + HOST-LAYER work only, and **the last pure-HCE release**: from here
the engine becomes an HCE/NNUE hybrid. NODE-IDENTICAL to v56 -- bench
signature 1,145,629 unchanged, ladder node-exact, every search toggle at its
v56 value -- so NO A/B slot was spent and the ledger stays at +305. What it
carries: the ponderhit soft-stop (a prediction hit used to spend the FULL
fresh budget re-confirming an already-stable move -- the documented v1
deviation from when ponder shipped; it now applies the same P-35/U-06
fractions the ID loop uses, measured 1.666s -> 0.686s, a ratio of 0.412
against the designed 0.40); the SoftStop/SoftStopStable/SoftStopUnstable/
SoftStopStableIters UCI options, which make the time-policy neighbourhood
sweepable with no rebuild; and a latent bug where cuci restored a HARDCODED
0.55 soft-stop fraction over whatever cengine.py had set -- harmless today
because the two agree, but it meant any future soft-stop tuning would work
under match.py and be silently discarded under UCI, i.e. in every real game.
Also dormant-but-present: FI-109 correction history (closed pre-screen).
FI-15 NNUE Phases 1-5
BUILT-DORMANT 2026-07-18 (abi 19): the full NN-eval infrastructure --
KA8T king-bucketed features + T16 threats, quantized int16/int8 net,
F49-31 accumulator stack, hybrid nn_eval-in-negamax/HCE-in-qsearch with
the F49-B02 depth-gated FI-03 cache -- behind USE_NNUE (default False =
v50+armed-defaults BYTE-EXACT; every gate in NNUE/README.md passed:
forward 100k/0 mismatches, increment 1.02M/0, NPS -37.8% with the toy
net on). Waits on Phases 6-8: real 50M dataset, bootstrap, screens.
v58 = v57 + **the NNUE net armed** -- the first HCE/NNUE hybrid release, and
the first net that pays. USE_NNUE flips True on nnue_v4_6f910e35bb1e.nnue:
GSPRT[0,4] ACCEPT H1 at 1,702 pairs, **+19.11 +/- 7.8 Elo** vs v57 on a clock
50s+0.20 (x86), ptnml 71/358/691/477/105, LLR +2.950 stopped early. Ledger
+305 -> +324. Bench signature 1,145,629 -> **1,074,820** and NPS drops ~30%
to the SIMD tail, which is the price the +19 is measured NET of.
The lesson is that the net was never the problem -- the TRAINING was. v3 read
+0.52 +/- 6.8 on this same instrument, i.e. nothing, and v4 changed neither
the dataset nor a single dimension: a cosine LR schedule in place of a flat
one took held-out val 0.074417 -> 0.066663, and that alone is the +19. Same
dims means same NPS, so none of it is speed. The 40-epoch run also settled
the next question by accident: val plateaued at epoch 4 while train fell
another 22%, so the net is DATA-limited, not epoch-limited, and more epochs
on this dataset are dead money. Labels are still 5,000-node searches while
the engine plays at 1.75M (FI-98 priced that at -0.580%), which is the
standing ceiling and the reason label DEPTH, not volume, is the next lever.
TWO CORRECTIONS TO THIS ENTRY, both found 2026-08-09.
(1) The +19.11 was NOT measured on this configuration. The candidate was
engine_nnue_v4.py, which sets LAZY_NNUE = True; this release ships
LAZY_NNUE = False. The number therefore prices NNUE WITH lazy evaluation
while the binary runs NNUE WITHOUT it. The gap is unmeasured: FI-106 has
never been isolated on either architecture, since its recorded +19.30 /
+5.91 come from engine_nnue_lazy.py against pre-NNUE HCE baselines and so
price the whole package. Re-measuring cengine.py (lazy off, as shipped)
against Old Engine/57 is the missing experiment.
(2) "val plateaued at epoch 4 ... the net is DATA-limited, not
epoch-limited" is wrong. The epoch sweep closed the other way: 8 is the
minimum, not a plateau at 4 (6 = 0.063989, 8 = 0.063676, 12 = 0.063858,
16 = 0.064264, 40 = 0.066663), so this net was trained for five times
longer than it should have been. The v5 net then took val down a further
4.5% and measured null, which is where "val is a weak predictor near the
floor" comes from. The label-DEPTH conclusion survives; the reasoning
that reached it did not.
OWED: an arm64 confirmation. v3 measured +5.70 +/- 4.6 on arm64 against
+0.52 on x86, so the architecture spread is real and v4 has only been
measured on x86. Shipped armed anyway because the risk direction is
favourable (arm64 read HIGHER for v3) and NNUE_REQUIRE_SIMD already refuses
to arm the net on a scalar build, where it would make the engine worse.
v59 = v58 + **FI-106 lazy NNUE eval armed** (LAZY_NNUE True): skip the net
where a cheap bound already decides the node. The FIRST release measured as
exactly the config that ships -- the candidate (engine_nnue_v4.py) was
byte-equivalent to this file plus the flip, vs Old Engine/58 on the
corrected (post-fc82cb7) harness: GSPRT[0,4] **ACCEPT** at 2,264 pooled
pairs, LLR +2.950, TIMED 50+0.5 x86, ptnml 69/509/975/595/116, pooled
51.99% -> **+13.84 +/- 6.4** (stopped early: magnitude bound-biased; the
verdict is the result). W/D/L 1,292/2,138/1,107 over 4,537 games. Ledger
+324 -> ~+338. Bench signature 1,074,820 -> **1,214,534** (+13% nodes for
+2.2% NPS on the d11 bench; on the clock the trade pays -- fewer net calls
per node buys more nodes than the extra tree costs). The v58-era package
readings (+19.30 arm64 / +5.91 x86, NNUE+lazy vs HCE) are superseded by
this isolated number.
Python keeps only what needs game/host state -- exactly the phase-3 plan:
* the iterative-deepening loop with v30's aspiration windows,
* v30's P-35/U-06 soft-stop time management (stability-scaled),
* v30's partial-iteration rule (an aborted depth's result is used iff at
least the first root move finished),
* the opening-book probe (delegated to an embedded engine.Engine, which is
also the single source of truth for every eval table/parameter synced
into the C core at construction),
* TT retention policy (the fixed-size C TT PERSISTS across game moves --
P-14, CONFIRMED +23.52 into v33; TT_KEEP_WARM=False restores v30's
wipe-after-irreversible-move rule, which only ever existed for the
Python engine's unbounded dict TT) and the game-history keys for
repetition detection.
API (battle_worker.py contract):
Engine().get_best_move(board, depth) -> Move | None
Engine().get_best_move_timed(board, seconds, max_depth) -> Move | None
attributes: nodes_searched / last_score (White POV) / last_depth /
last_pv, constants MATE_SCORE / MATE_THRESHOLD, settable use_book /
pv_uci.
Search-feature ledger -- each entry names its csearch.c setter and the
baseline its non-default setting restores node-exactly (the ladder pin).
Eval-side toggles (USE_KING_SHELTER / USE_OUTPOST / USE_SIMPLIFY) live on
the class attrs below with their own verdicts.
ON by default (A/B-confirmed, or free by construction):
* P-01 check extensions (set_check_ext; +6.81 +/-6.8 vs v33 ->
snapshotted Old Engine/34; OFF = v33 node-exact). P-47 made the
per-line budget runtime-settable (set_check_ext_budget; 5 = v36
node-exact); raise-to-8 REJECTED 2026-07-10 (-4.59 +/-6.8 @10k
50+0.20) -- the extensions vein is thin (P-01 +6.8, P-43 +3.5
marginal, P-47 -4.6), do not re-try at this TC.
* P-22 noisy-only qsearch generation (set_qgen; NODE-IDENTICAL by
construction -- same noisy subset, same order, stalemate semantics
preserved, verified over 8 FENs x 2 depths -- so it needs no ladder
pin; +32% NPS mixed bench / +55% startpos. Timed Elo measured
2026-07-10 as the P-22+P-44 bundle vs v34: ~+71.8 +/-8.5 @7k -- the
NPS converts at the classic ~2-3 Elo/1%).
* P-44 qsearch TT probe/store (set_qs_tt; isolation A/B vs the P-22 base
+8.06 +/-6.8 @10k, CI clear of zero -> CONFIRMED into v35, snapshotted
Old Engine/35; OFF = v34 node-exact): the node-majority qsearch probes
the warm TT before movegen/eval and stores depth-0 entries that never
displace negamax entries -- the persistent warm table across a game
delivered what the flat cold-ladder time-to-depth bench could not show.
* P-46 lazy qsearch generation (set_qs_lazy; node-identical, ~+1-3% NPS):
eval + stand-pat run BEFORE movegen, so stand-pat exits never pay for
generation.
* P-23 staged move ordering (set_staged; +24.67 +/-6.8 @10k vs v35 ->
CONFIRMED into v36, snapshotted Old Engine/36; set_staged(0) = v35
node-exact): TT-move/captures/killers/counter/quiets/bad-captures
generated lazily per stage -- ~+10-20% NPS AND a deliberate tree
change (later stages score quiets with FRESHER history than v35's
node-entry snapshot); stream equality under identical state proven by
verify mode over ~1M nodes.
* PV-01 triangular PV (cs_get_pv; NODE-EXACT, pure bookkeeping): the PV
is collected during the search instead of TT-walked afterwards;
_extract_pv emits the exact prefix in full, splicing the old TT walk
only past any truncation. Necessary but NOT sufficient alone: with the
warm TT, PV nodes hit exact entries almost immediately (check
extensions inflate stored depths along mate lines), so the exact
prefix was often 1 move and matetrack Bad-PVs stayed ~60%.
* FI-02/FI-03 NPS batch (2026-07-11, NODE-IDENTICAL -- ladder passes
bit-exactly, eval-cache differential clean over 15.9M nodes): mover PT
read from the move word in apply_move (was a 5-branch bitboard probe);
ordering's SEE verdict tagged into move-word bits 22-23 and reused by
qsearch's losing-capture skip (every consumer masks to 15 bits);
lazy pick_next ordering on the non-staged paths (stable shift-to-front,
emission order == the full sort's; most nodes cut by move 3 and never
sort the tail); static eval cached in the TT entry's spare 16 bits
(deterministic per position => EXACT, reused on TT hits in negamax AND
qsearch stand-pat -- the eval call is the most expensive per-node op).
Paired alternating bench vs v38: +3.94% median, 9/9 pairs positive.
Confirmed into v39 as the Phase-2 batch with FI-01 (+8.86 +/-6.8 vs Old
Engine/38). (-flto was probed and read null on Apple Silicon, not adopted.)
* FI-01 incremental Zobrist (2026-07-11, Phase-2 train part 2): the
position key lives ON the Board and is XOR-maintained through
apply_move/make_null (splitmix64 randoms, fixed seed) instead of the
old 9-MIX full-state hash recomputed at every node; make_board computes
it once per Python entry (key_from_scratch = the oracle). EP-01's FIDE
filter became an O(1) fixup in board_key (phantom ep XORed back out),
so set_ep_filter stays a runtime toggle at zero steady-state cost.
ZKEY differential clean over 52.4M nodes (castling/ep/promo trees);
d1-5 ladder bit-exact vs v38, deeper counts drift (different key
values -> different TT index-collision patterns -- NOT a logic change);
matetrack 896/767, zero Bad PVs. Paired bench: full Phase-2 train
+8.92% NPS median vs v38, 9/9 pairs positive (Zobrist's own share
~+4.8% on top of part 1's +3.94%). A/B vs Old Engine/38: +8.86 +/-6.8
@10k 50+0.20 (pair ratio 1.15, norm +18.89) -- CONFIRMED into v39.
* PV-02 exact PV (set_pv_exact; CONFIRMED into v37 2026-07-10,
snapshotted Old Engine/37; set_pv_exact(0) = v36's search): skip TT
cutoffs/narrowing at PV nodes so the collected PV is complete
end-to-end -- the same matetrack FEN goes 1-move -> full 13-ply mate
PV, Bad-PVs -> zero. Tree-changing (d12 ~-23% nodes) yet the A/B was a
clean null (+0.17 +/-6.8 @10k 50+0.20, pair ratio 1.02): for a
correctness feature, a null means FREE.
* CB-01 correctness batch (set_score_hygiene; CONFIRMED into v38
2026-07-10, snapshotted Old Engine/38; set_score_hygiene(0) = v37
node-exact): seven sub-resolution "score draws as draws, keep proven
bounds" fixes -- Texel-consistent delta-pruning values, qsearch
in-check repetition + insufficient-material detection (both draws
decided BEFORE the qsearch TT probe, repetition sees qsearch plies via
g_path logging), null-move fail-soft return + TT LOWER store (unproven
mates clamped to beta), qsearch TT lower-bound alpha narrowing,
mate-distance pruning (NON-PV nodes only: at a PV node the fastest-mate
score lands exactly on the clamped beta and starves PV-01's in-window
store -- matetrack caught it, 470 Bad PVs), deep-qsearch killers read
slot 63 not the root's. A/B vs v37: +1.36 +/-6.8 @10k 50+0.20 (pair
ratio 1.02) -- a clean null KEPT as correctness (PV-02 precedent);
matetrack @0.5s 692/600 -> 868/751, ZERO Bad PVs (MDP ~+25% found).
* EP-01 FIDE-exact ep hashing (set_ep_filter / EP_FILTER class attr;
CONFIRMED into v40 2026-07-11, snapshotted Old Engine/40;
EP_FILTER=False = v39 node-exact): the position key counts an
en-passant square only when a legal ep capture actually exists
(= python-chess's _transposition_key), so repetition detection agrees
with the FIDE arbiter -- a phantom ep after a double push no longer
splits one FIDE-identical position across two keys, missing
repetitions in either direction. Since FI-01 it is an O(1) fixup in
board_key that only runs when an ep square is set: near-zero cost,
and merging the phantom-ep TT entries even saves nodes (d12 ladder
713,014 -> 562,363). A/B vs Old Engine/39: +4.31 +/-6.8 @10k 50+0.20
(50.62%, ptnml 227/1203/2064/1231/275, pair ratio 1.05, norm +9.14)
-- a null KEPT as correctness (PV-02/CB-01 precedent).
* CB-02 correctness batch #4 (set_cb2 + the CB2 driver logic; CONFIRMED
into v41 2026-07-11, snapshotted Old Engine/41; CB2=False = v40
node-exact): null-move TT store obeys the replacement policy (deeper
entries and their moves survive), qsearch 50-move rule, verified deep
null cutoffs (depth >= 10, g_no_null suppresses nulls in the
verification subtree), root fail-high adoption/promotion across
aspiration calls. A/B vs Old Engine/40: -2.88 +/-6.8 @10k 50+0.20
(49.59%, ptnml 287/1198/2086/1169/260, pair ratio 0.96, norm -6.04)
-- a null KEPT as correctness, the fourth of its class.
* CW-01 cannot-win eval clamp (set_cantwin / CANTWIN class attr,
mirrored into the embedded engine's use_cantwin; CONFIRMED into v42
2026-07-11, snapshotted Old Engine/42; CANTWIN=False = v41 eval
exactly): the eval clamps to 0 when the favored side has no pawns, no
rooks/queens, and at most a lone minor (or two knights) -- it cannot
force mate, so the true upper bound is a draw. A/B vs Old Engine/41:
+3.27 +/-6.8 @10k 50+0.20 (50.47%, ptnml 257/1115/2159/1215/254, pair
ratio 1.07, norm +6.98) -- a null KEPT as correctness, the fifth of
its class.
* FI-26a TT prefetch (unconditional TT_PREFETCH(c.key) after apply_move
at the three child-recursion sites; CONFIRMED into v44 2026-07-12,
snapshotted Old Engine/44; node-identical, no toggle -- deleting the
macro line restores v43): FI-01's incremental child key made the
prefetch address free, inverting P-45's original null. +4.9% NPS
(median, 3/3 warmup-discarded pairs); A/B vs Old Engine/43: +13.31
+/-6.8 @10k 50+0.20 (51.91%, ptnml 250/1050/2073/1321/306, pair ratio
1.25, norm +27.85) -- the biggest single NPS win of the C era.
* FI-25 TT-value pruning-eval sharpener (set_tt_eval_sharpen /
TT_EVAL_SHARPEN class attr; CONFIRMED into v45 2026-07-12, snapshotted
Old Engine/45; False = v44 node-exact): the TT hit's SEARCH value
replaces the raw static eval in RFP / null-move / frontier futility
whenever its bound provably improves the estimate (LOWER above / UPPER
below / EXACT always; non-mate values, any entry depth); static_eval
stays RAW for the FI-03 cache and the P-04 stack. A/B vs Old
Engine/44: +13.52 +/-6.8 @10k 50+0.20 (51.94%, ptnml
225/1100/2056/1299/320, pair ratio 1.22, norm +28.34).
DORMANT (default OFF, mechanism kept for longer-TC re-tests):
* P-43 single-reply / forced-move extension (set_single_reply; +3.5
+/-4.8 over 20k pooled games vs v34 -- positive-leaning on every
signal but sub-significant, kept-marginal by user call; OFF = v34
node-exact).
* P-04 "improving" heuristic (set_improving; +0.38 +/-6.8 @10k vs v34 --
a dead null despite -56% nodes and +1 ply: at this TC the deeper tree
saw nothing new. v30's recipe: eval stack vs ply-2 feeding RFP depth /
frontier-futility margin / LMR+1; OFF = v34 node-exact).
* Q-01 continuation history (set_cont_hist; -0.87 +/-6.8 @10k 50+0.20 vs
v36, 2026-07-10 -- a dead NULL: the 1-ply/2-ply continuation scores
(v30's #1.6, piece-to keyed int16 tables) bought nothing at this depth
and their ~1.6MB of tables cost cache; OFF = v36 node-exact).
* (EP-01 graduated from this list to ON-by-default: CONFIRMED into v40,
see the ledger above.)
* FI-08 qsearch depth-0 eviction guard (set_qs_evict_max; +0.14 +/-6.8
@10k vs Old Engine/40 -- dead null, not correctness, so unlike
PV-02/CB-01/EP-01 it reverted: -1 = off = v40 rule, mechanism kept).
* (CW-01 graduated from this list to ON-by-default: CONFIRMED into
v42, see the ledger above.)
Deliberate deviations from v30 (documented, revisit if an A/B says so):
* no root random tiebreak (deterministic best move),
* no singular extensions / razoring (dormant or absent in v30 at match
depths anyway),
* repetition detection covers negamax nodes; quiescence only its
in-check nodes (CB-01, path-logged keys),
* (the raw-ep-hash deviation was FIXED by EP-01 in v40: the key now
counts an ep square only when a legal ep capture exists,)
* Lazy SMP exists in-process (csearch pthreads + lockless shared TT) but
is strictly OPT-IN (smp_workers / UCI Threads; default 1, Elo
unmeasured); tablebase probe exists but defaults off (use_tb=False,
v30 match).
"""
import ctypes
import os
import sys
import threading
import time
import chess
_DIR = os.path.dirname(os.path.abspath(__file__))
CS_INF = 30000
CS_MATE_THRESH = CS_INF - 1000
def _load_pyengine():
"""Import the sibling engine.py (param source + book probe)."""
if _DIR not in sys.path:
sys.path.insert(0, _DIR)
import engine as pyengine
return pyengine
# FB-04: csearch.so's eval params + toggles + TT are PROCESS-WIDE. Two Engine
# instances with different configs in one process silently share them (the
# second construction re-syncs the globals under the first). Refuse instead.
_SYNCED_FINGERPRINT = None
class Engine:
MATE_SCORE = 1_000_000
MATE_THRESHOLD = MATE_SCORE - 1_000
# P-20a king shelter: REJECTED at C-core depth (A/B vs v32, 2026-07-08:
# 10k games @ 45+0.1, 49.38% = -4.27 +/-6.8, norm -7.98). The depth-8
# signal (+10 +/-10 on the old engine) did not survive depth 14 --
# deep search sees king attacks concretely, subsuming the static term.
# False reproduces the v32 eval exactly (node-verified). Do not re-try
# at this TC; the mechanism stays for future eval-toggle A/Bs.
USE_KING_SHELTER = False
# Outpost re-test: NULL, OFF (A/B vs Old Engine/37 2026-07-10, fourth
# 50+0.20 campaign: -0.90 +/-6.8 @10k, 49.87%, ptnml 289/1230/1982/
# 1216/283, pair ratio 0.99 -- the Python-era +0 +/-10 depth-8 signal
# stayed a null at depth ~14, exactly P-20a's subsumption logic; unlike
# a correctness null this buys nothing and costs eval work, so OFF).
# C-era eval add-ons now 0-for-2 (shelter -4.27, outpost -0.90): no new
# static-eval term without a 2k-game screen first. Same sync mechanism
# as USE_KING_SHELTER; False = v37 eval exactly.
USE_OUTPOST = False
# FI-85 x-ray slider mobility REMOVED 2026-07-24: SCREEN-KILLED
# (-4.52 +/-15.3, do-not-retry) and its gating in all six slider loops
# cost NPS while dormant. See eval_c.c.
# CB-01 correctness batch (LIVE CANDIDATE, fifth 50+0.20-era campaign,
# A/B vs Old Engine/37 PENDING; selftest pins the ladder to off).
# One master toggle over seven sub-+/-6.8 "score draws as draws, keep
# proven bounds" fixes -- csearch.c set_score_hygiene: (a) delta pruning
# budgets Texel piece values (queen 1150 vs classic 900), (b) qsearch
# in-check repetition detection (perpetuals scored as eval before, and
# P-44 persisted the misscore into the warm TT), (c) qsearch
# insufficient-material draws, (d) null-move fail-soft return + TT
# LOWER store (unproven mates clamped), (e) qsearch TT lower-bound
# alpha narrowing, (f) mate-distance pruning, (g) deep-qsearch killers
# read slot 63, not the root's. KEEP-ON-NULL (PV-02 precedent:
# correctness nulls are free); False = v37 node-exact.
SCORE_HYGIENE = True
# EP-01 FIDE-exact ep hashing: CONFIRMED into v40 (seventh 50+0.20-era
# campaign, A/B vs Old Engine/39 2026-07-11: +4.31 +/-6.8 @10k, 50.62%,
# pair ratio 1.05 -- a null KEPT as correctness, PV-02/CB-01 precedent).
# The position key counts an en-passant square only when a legal ep
# capture actually exists (= python-chess's _transposition_key), so
# repetition detection agrees with the FIDE arbiter. Since FI-01 the
# filter is an O(1) fixup in board_key that only runs when an ep square
# is set -- near-zero cost. False = v39 node-exact.
EP_FILTER = True
# FI-08 / Q-03 qsearch depth-0 eviction guard: DORMANT (eighth 50+0.20
# campaign, A/B vs Old Engine/40 2026-07-11: +0.14 +/-6.8 @10k, 50.02%,
# pair ratio 1.01 -- a dead NULL; not a correctness fix, so the
# Q-01/P-04 rule applies: default OFF, mechanism kept). Verdict also
# prices the warm-TT-protection vein: at 48 MB / 50+0.20 the table is
# not saturation-bound, deprioritizing FI-20 (gen-touch/2-slot bucket).
# >= 0 = replace old-gen entries only up to that depth; -1 = v40 rule.
QS_EVICT_MAX = -1
# CB-02 correctness batch #4: CONFIRMED into v41 (ninth 50+0.20-era
# campaign, A/B vs Old Engine/40 2026-07-11: -2.88 +/-6.8 @10k, 49.59%,
# pair ratio 0.96 -- a null KEPT as correctness, the fourth of its
# class after PV-02/CB-01/EP-01). C side (set_cb2): (a) FB-22 null-move
# TT store obeys the replacement policy (never clobbers deeper entries,
# keeps a same-key entry's move); (b) FI-27.1 qsearch 50-move rule;
# (c) FI-24c deep null cutoffs (depth >= 10) verified with a reduced
# no-null re-search (zugzwang insurance). Driver side (this attr):
# FB-23 root fail-high moves adopted/promoted across aspiration calls
# (v30's _partial_root_move rule). False = v40 node-exact.
CB2 = True
# CW-01 cannot-win clamp: CONFIRMED into v42 (tenth 50+0.20-era
# campaign, A/B vs Old Engine/41 2026-07-11: +3.27 +/-6.8 @10k, 50.47%,
# pair ratio 1.07 -- a null KEPT as correctness, the fifth of its
# class). Eval clamps to 0 when the side it favors has no pawns and
# cannot force mate (lone minor / two knights) -- no more shuffling at
# "+2.6" to dodge a drawing capture (user-reported position goes
# +2.92/shuffles -> 0.00/plays Kxc4). Bit-exact twin of engine.py's
# use_cantwin (mirrored below: GUI eval bar and search always agree);
# oracle differential clean over 389 positions. False = v41 eval.
CANTWIN = True
# FI-76 wrong-bishop clamp REMOVED 2026-07-24: SCREEN-NULL (+0.17
# +/-15.3, ~9% pair engagement, 46 up / 44 down inside it) and it cost
# per-node work at both eval return sites while gated off. See csearch.c
# for the do-not-re-add condition.
# NV-01 verification isolation: RESOLVED into v43 (eleventh 50+0.20
# campaign, A/B vs Old Engine/42 2026-07-11: +5.18 +/-6.8 @10k for the
# REMOVAL, 50.74%, pair ratio 1.08, norm +10.82). Converging evidence
# (CB-02's own -2.88 lean + a recovered ply of nodes-to-depth) priced
# CB-02(c)'s zugzwang insurance at ~3-5 Elo -- v43 drops it, matching
# modern practice (Stockfish-family runs unverified null; has_non_pawn
# + the TT cover zugzwang). True = v42's verifying search.
NULL_VERIFY = False
# FI-04 history-based LMR: DORMANT (twelfth 50+0.20-era campaign, A/B
# vs Old Engine/43 2026-07-12: +2.15 +/-6.8 @10k, 50.31%, pair ratio
# 1.05 -- a null below the pre-registered +3 tune threshold, so no
# divisor tune; not correctness => the Q-01/P-04 rule: default 0,
# mechanism kept). The finer-quiet-signal vein is now 0-for-3 at this
# TC (Q-01 -0.87, P-42 -16.4, FI-04 +2.15) -- even the v39+ wave's
# 5/5-consensus form doesn't pay; do not re-try without a longer TC.
# divisor > 0 enables (adj = hist/div clamped +/-1); 0 = v43 exact.
#
# FI-105 (2026-07-29) ABANDONED PRE-SCREEN on the EBF gate, and found a
# defect in the verdict above while doing it. **At the documented armed
# divisor 8192 this is a DEAD GATE**: bench reads 1,461,732 -- byte
# identical to baseline -- so the mechanism never fires there. Same trap
# FI-23 recorded ("armed at 256, NOT the spec's 8192 -- that measured as a
# dead gate"): cs_search_begin zeroes g_history every move, so within one
# search it rarely passes a few hundred and hist/8192 clamps to 0. The
# +2.15 +/-6.8 campaign therefore measured a mechanism at the very edge of
# engagement, and the divisor was never tuned because the result fell
# below the tune threshold -- circular.
#
# Re-armed at LIVE divisors on top of FI-103+FI-104, EBF vs baseline:
# pair alone -7.49%
# + LMR_HIST 2048 -7.02% (worse)
# + LMR_HIST 512 -3.32% (much worse)
# It claims better reductions and does not deliver them, so the one-sided
# gate abandons it. This was R10's designated falsification point.
#
# SCREENED 2026-07-30 anyway, because the same gate produced a FALSE
# NEGATIVE on FI-107 (abandoned, then shipped at +4.11). Armed as
# CUTNODE_LMR + LMR_HIST 2048, without TTPV_LMR (+31.8% nodes on its own):
# -1.51 +/- 12.4 over 3,000 games, LLR -0.404 of -2.944. CLOSED here --
# a formal reject costs ~18,900 more games to prove a negative nobody
# would ship. The gate was right about this one; it is only discredited
# for constant-factor changes, which this is not.
# Finer-quiet-signal vein now 0-for-4 at this TC.
LMR_HIST = 0
# FI-25 TT-value pruning-eval sharpener: ARMED (fourteenth 50+0.20-era
# campaign, A/B vs Old Engine/44 PENDING -- sonnet5's top new idea).
# FI-03 reuses the cached STATIC eval; the TT entry's SEARCH value is
# strictly better information whenever its bound applies (LOWER above /
# UPPER below the static eval, EXACT always), so it replaces the raw
# eval in RFP / null-move / frontier futility -- prunes both more
# accurately and less wrongly at the same depth, Stockfish-family
# practice. Non-mate values only; the FI-03 TT cache and the P-04 eval
# stack keep the RAW static eval (exactness invariants). False = v44
# node-exact. CONFIRMED into v45 (fourteenth 50+0.20-era campaign, A/B
# vs Old Engine/44 2026-07-12: +13.52 +/-6.8 @10k, 51.94%, pair ratio
# 1.22 -- confirmed at full value, back to back with v44's +13.31).
TT_EVAL_SHARPEN = True
# FI-18 SEE pruning of losing captures: DORMANT (fifteenth 50+0.20-era
# campaign, A/B vs Old Engine/45 2026-07-13: -1.25 +/-6.8 @10k, 49.82%,
# pair ratio 0.98 -- a dead null with a negative lean; not correctness
# => the Q-01/P-04 rule: default False, mechanism kept). Even the
# standard-everywhere shallow losing-capture prune doesn't pay at this
# TC -- bad captures are already ordered last, so alpha-beta was
# getting most of the skip for free. matetrack stayed clean (913/783),
# the Elo just wasn't there. False = v45 node-exact.
SEE_PRUNE = False
# FI-06 root-move ordering: DORMANT (sixteenth 50+0.20-era campaign, A/B
# vs Old Engine/45 2026-07-13: +2.26 +/-6.8 @10k, 50.32%, pair ratio
# 1.02 -- a positive lean landing in the predicted +0-4 band but the CI
# covers zero; not correctness => the Q-01/P-04 rule: default False,
# mechanism kept). Same magnitude/verdict as FI-04's +2.15: a free-ish
# ordering tweak that can't clear the noise floor isn't banked. Three
# root-only refinements (subtree-node-count ordering + warm-TT
# iteration-1 seed, main thread only). False = v45 node-exact.
ROOT_ORDER = False
# FI-10: TT size in bits (2^bits x 24-byte entries; 21 = 48 MB, 22 =
# 96 MB, 23 = 192 MB). CONFIRMED into v46 at 22 (seventeenth 50+0.20-era
# campaign, A/B vs Old Engine/45 2026-07-13: +5.94 +/-6.8 @10k, 50.85%,
# pair ratio 1.10, norm +12.33 -- a BORDERLINE-positive, CI just touches
# zero, shipped on the monotonic-low-risk rationale: a bigger table
# cannot worsen decision quality at fixed nodes and its only downside
# (DRAM bandwidth) was exercised at the full 223-worker load = net +).
# Motivated by the user's hashfull capture (a single deep search fills
# ~half the 48 MB table). CONFIRMED into v47 at 23 (192 MB, eighteenth
# campaign, A/B vs Old Engine/46 2026-07-13: +3.16 +/-6.8 @10k, 50.46%,
# pair ratio 1.03, norm +6.54 -- the 96->192 MB increment; net-positive
# at full load = bandwidth hasn't bitten, so same monotonic-low-risk
# ship as v46). MEMORY-SCALING CLOSES HERE: +5.94 then +3.16 is halving
# each doubling, so 24 (384 MB) would gain ~+1.5 = sub-noise; not worth
# a campaign (RAM would still fit at ~85 GB). The UCI Hash option (cuci)
# maps MB onto this; a resize wipes the table. 22 = v46 exact.
#
# RE-EXAMINED 2026-07-30 and the verdict STANDS, but the note above lacked
# the counter-argument, so here it is measured. Warm table across a real
# game at 1.4s/move (the 50+0.20 operating point), hashfull in permille:
#
# ply 8 ply 16 ply 24 ply 32 ply 40
# 192 MB 833 974 995 1000 1000
# 768 MB 329 564 721 813 886
#
# 192 MB is FULLY SATURATED from move 16 and stays full for the rest of
# every game -- every store after that evicts something. A cold search
# hides this completely (bench hashfull reads 17 permille), which is why
# it had never been seen.
#
# It still does not justify a raise, for a reason that is about the
# HARNESS rather than the engine: match.py runs TWO engine processes per
# worker and each allocates its own table, so the default is multiplied by
# 2N. At 111 workers, 768 MB is 170 GB and 384 MB is 85 GB; on a 16 GB
# laptop 768 MB caps local runs at ~5 workers instead of ~20. Measuring a
# ~+1.5 item would cost a 4x slower campaign. Hash is exposed over UCI up
# to 20 GB -- serious long games set it there, which is the right place
# for this knob to live.
TT_BITS = 23
# Simplify-at-500 (v30's use_simplify ported: material-diff bonus past a
# >=500cp gate; v30's 200cp version A/B'd -14, traded into drawn endings).
# DROPPED FROM THE QUEUE 2026-07-13 -- not on the final_improvements plan
# (it survives only as one cheap screen inside FI-14, low-prio). Kept as a
# dormant off-by-default toggle: threshold 0 (off) = v36 eval exactly,
# node-exact, so it costs nothing to leave. Pushed via csearch_set_simplify.
USE_SIMPLIFY = False
SIMPLIFY_THRESHOLD = 500
# P-14 (CONFIRMED v33, +23.52 +/-6.8 vs v32): KEEP the C TT across
# irreversible root moves. v30's wipe-on-capture/pawn-move rule existed
# because its dict TT grew unbounded and dead entries wasted memory; the
# C table is fixed-size with generation-aware replacement and
# full-key-checked probes, and repetition/50-move draws are decided
# BEFORE the TT probe -- so the wipe only discarded still-reachable
# entries (the whole subtree behind the irreversible move) on a very
# frequent event. False = v32's exact behavior.
TT_KEEP_WARM = True
# P-47: per-line check-extension budget (v30's MAX_CHECK_EXT recipe).
# 5 = v36 node-exact. Raise-to-8 REJECTED 2026-07-10: -4.59 +/-6.8 @10k
# vs v36 (49.34%, pair ratio 0.96, norm -9.09) -- deeper check lines
# cost more than they find at this TC; extensions vein confirmed thin
# (P-01 +6.8, P-43 +3.5 marginal, P-47 -4.6). Do not re-try at this TC.
CHECK_EXT_BUDGET = 5
# PV-02 (CONFIRMED into v37, 2026-07-10): skip TT cutoffs/narrowing at
# PV nodes so the triangular PV (PV-01, always on) is complete
# end-to-end -- the standard strong-engine rule; the TT move still
# orders. A/B vs Old Engine/36 @ 50+0.20 10k: +0.17 +/-6.8 (pair ratio
# 1.02) -- a clean null, i.e. the exact PV is FREE; kept ON as a
# correctness feature (it fixed matetrack's ~60% Bad-PV rate).
# False restores v36's search.
PV_EXACT = True
# FI-09(a): a forced move (exactly one legal reply) is played instantly,
# banking the whole time budget -- no tree change, pure clock save.
# FI-09 BUNDLE RESOLVED 2026-07-14 (twentieth 50+0.20 campaign vs Old
# Engine/47, 10k games): +0.69 +/-6.8 (norm +1.49, SPRT LLR -0.314, no
# decision within budget) -- dead-null, single-reply/easy-move roots are too
# rare at this TC to move the needle. REVERTED to False (shipped v47 clock
# behavior; the CE_LADDER never saw a single-reply root either way). Kept as
# dormant infrastructure, not deleted -- do-not-retry at this TC.
SINGLE_REPLY_INSTANT = False
# FI-09(b): easy-move fast-out -- when the best root move leads the 2nd-best
# by >= EASY_MARGIN_CP for EASY_ITERS consecutive iterations (depth >=
# EASY_MIN_DEPTH), bank the clock by capping the soft-stop at EASY_FRAC.
# Scales INTO the U-06 machinery (min with the stability frac), never a new
# clock path. second-best = cs_search_root's out_second, an UPPER bound on
# the true 2nd-best (failing scouts fail soft), so the test is conservative
# -- it never over-claims dominance. NULL alongside FI-09(a) in the same
# bundle A/B (see above) -- REVERTED to False 2026-07-14 (shipped v47 clock;
# only affects TIMED search, the fixed-depth CE_LADDER is untouched).
EASY_MOVE = False # FI-09 BUNDLE NULL, do-not-retry
EASY_MARGIN_CP = 250
EASY_ITERS = 3
EASY_MIN_DEPTH = 8
EASY_FRAC = 0.35
# FI-23: history-driven quiet pruning -- LMP prunes by move-count only;
# this adds the signal sibling, skipping quiets the EXISTING butterfly
# history has consistently punished (same shallow/non-PV/not-in-check/
# non-check-giving gate as LMP/FI-18). Reuses g_history read-only, no new
# bookkeeping or ABI change. 0 = off = v47 node-exact; threshold is a
# magnitude on the +-HIST_MAX=16384 scale. Armed at 256 after an
# engagement sweep (8192 through 512 measured bit-identical to off --
# cs_search_begin zeroes g_history every move, so one search's history
# rarely swings a slot past a few hundred; see git history for the sweep).
# FI-23 REJECTED 2026-07-16 (twenty-first 50+0.20 campaign vs Old
# Engine/47): -5.23 +/-7.1 @9,243 games, pair ratio 0.92, norm -10.89,
# SPRT[0,4] LLR -2.955 ACCEPT H0 (stopped early) -- a real negative, not
# a null. With FI-18's -1.25 the shallow quiet/capture-prune vein is
# 0-for-2 in the C era: within-search history is too thin a signal at
# depth <= 3 to beat the ordering that already buried those moves.
# REVERTED to 0 (dormant, do-not-retry at this TC); mechanism kept.
HIST_PRUNE = 0
# FI-30: (a) QS_TT_SHARPEN -- FI-25's rule applied at qsearch's
# stand-pat: on a TT hit whose bound didn't cut, the entry's SEARCH
# value replaces the static eval as the stand-pat wherever the bound
# provably improves it (LOWER above / UPPER below / EXACT; non-mate);
# the FI-03 TT-eval cache keeps the RAW eval (raw_stand split).
# (b) QS_KEEP_MOVE -- a stand-pat (move-0) store keeps a same-key
# entry's best move (FB-22's rule applied to qs_tt_store).
# CONFIRMED 2026-07-16 => v48 (twenty-second campaign vs Old
# Engine/47, four pooled tranches = 21,605 games @ 50+0.20):
# +4.73 +/-3.19 (50.68%, ptnml 606/2518/4309/2714/655, pair ratio
# 1.08, norm +9.70), pooled GSPRT[0,4] LLR +3.475 crossing the
# +2.944 accept bound -- the C era's first sequential-test ACCEPT.
# A premature revert at the 10k cap was walked back: the SPRT said
# CONTINUE, and extensions ran until it decided (never cap a
# sequential test at a fixed budget again).
QS_TT_SHARPEN = True
QS_KEEP_MOVE = True
# FI-29: cuckoo upcoming-repetition (van Kervinck / SF has_game_cycle).
# is_repetition only sees repetitions already ON the path; the cuckoo
# table (8192 slots, one Zobrist delta per reversible non-pawn move)
# detects that the side to move can FORCE one with a single move, so
# the node scores the contempt draw a full search earlier -- pruning
# lost shuffle subtrees and banking perpetual half-points sooner.
# In-tree only, never in check, alpha-raise (not hard return); a match
# that would strip castling rights is rejected (key-soundness beyond
# SF's envelope). KEPT-ON-NULL => v49 2026-07-17 (twenty-third
# campaign vs Old Engine/48, 10,000 games @ 50+0.20): +0.97 +/-6.8
# (50.14%, pair ratio 1.02, norm +2.01, GSPRT[0,4] LLR -0.19) -- the
# pre-registered correctness-class rule ships the null, the sixth of
# its class (EP-01/CB-01/CB-02/PV-02/CW-01 precedent). Build gates:
# CYCLE_VERIFY 13,272 claims / 0 mismatches over 1.1M nodes; paired
# matetrack noise-flat (899/773 on vs 905/774 off); blocked-pawn
# fortress d16 11,893 -> 4,310 nodes, score snaps to 0.
CYCLE_DETECT = True
# FI-50/51/52: the qsearch-TT batch -- FI-30's direct descendant, three
# non-overlapping toggles ganged as one campaign (the grouped-toggle
# precedent FI-30 set):
# (50) QS_BETA_NARROW -- narrow beta from a TT_UPPER qsearch hit (the
# CB-01(e) alpha-narrow's mirror; negamax has done both all along).
# (51) QS_TTM_EXEMPT -- the qsearch TT move dodges the losing-SEE skip and
# delta pruning (a nonzero stored bm beat stand-pat at store time).
# (52) QS_CHK_D1 -- in-check RESOLVED qsearch stores tagged depth 1 so
# negamax's TT_DEPTH>=depth gate can cut directly from them.
# NULL 2026-07-18 (twenty-fourth campaign vs Old Engine/49, 10,000 games
# @ 50+0.20): -0.28 +/-6.8 (49.96%, ptnml 295/1177/2055/1187/286, pair
# ratio 1.00, norm -0.57, GSPRT[0,4] LLR -0.797 no decision, trend flat)
# -- a dead null, NOT correctness-class => all three REVERTED to False
# (dormant, mechanisms kept). The never-cap doctrine wasn't triggered:
# it protects tests trending toward a bound (FI-30's LLR climbed and
# never reversed); this one sat flat-to-negative. Not split for
# attribution (~$54 to price three likely-zeros against symmetric
# ptnml); FI-50 alone stays a cheap solo re-run candidate (its entry
# priced it keep-on-null-adjacent, unexercisable from a batch verdict).
# Paired matetrack had PASSED pre-verdict (ON 907/778 vs OFF 900/773
# found/best mates on mates2000 @0.5s -- noise-flat, no tactical or
# PV-integrity regression; the mechanisms are safe, just not worth Elo).
QS_BETA_NARROW = False
QS_TTM_EXEMPT = False
QS_CHK_D1 = False
# FI-48: flag-aware TT replacement -- shield same-key EXACT entries from
# equal-depth bound-only overwrites; level 2 adds a +2 cross-key
# effective-depth bonus for EXACT incumbents. CLOSED AS DEAD GATE
# 2026-07-18 pre-A/B (FI-23/P-33 doctrine: never spend a 10k on a config
# that barely runs). Instrumented count under the full production config
# (PV-02 on, warm TT): level 1 fired 26x over ~4M+ nodes of bench +
# timed search (577k same-key store checks -> 26 qualified); level 2
# added ~170 cross-key blocks per ~20M timed nodes -- ~0.001% either
# way. STRUCTURAL cause, not tuning: a node that would overwrite an
# equal/deeper same-key EXACT entry is cut off by that very entry at
# its own probe before it can store (only PV-02-skipped PV nodes
# escape), and cross-key pressure is starved because the 192MB table
# does not saturate at 50+0.2 (same reason FI-08 nulled / FI-20 is
# gated). Mechanism kept for a cheap re-measure if the TT shrinks, the
# TC lengthens, or FI-20's hashfull gate ever shows saturation.
TT_KEEP_EXACT = 0
# FI-49: TT fail-high depth tightening (SF-standard) -- an equal-depth
# TT_LOWER whose value would cut (v >= beta, non-mate) needs TT_DEPTH >=
# depth+1 before the negamax cutoff/narrowing block fires. REJECTED
# 2026-07-18 (twenty-fifth campaign vs Old Engine/49, 10,000 games @
# 50+0.20, seed 42): -3.65 +/-6.8 (49.48%, ptnml 310/1234/2002/1159/295,
# pair ratio 0.94, norm -7.44, GSPRT[0,4] LLR -2.403 -- 82% of the way
# to the H0 bound at full budget). A reject-lean null, not flat: the
# +28% fixed-depth node cost was not bought back, exactly as the paired
# matetrack's ~-1.5% best-mate dip predicted (pre-registered
# corroboration). The warm-cross-move-TT argument for SF's rule does
# not transfer to Pygin at this TC. REVERTED to False (dormant,
# do-not-retry at this TC); mechanism kept.
TT_FH_TIGHT = False
# FI-53 + FI-54: the store/probe-policy pair. KEPT-ON-NULL => v50
# 2026-07-18 (twenty-sixth campaign vs Old Engine/49, 10,000 games @
# 50+0.20, first on rotated SUBSET_SEED 50): +1.60 +/-6.8 (50.23%,
# ptnml 278/1155/2106/1165/296, pair ratio 1.02, norm +3.33, GSPRT[0,4]
# LLR +0.117 flat) -- the pre-registered correctness-class rule ships
# the null, the seventh and eighth releases of the class (EP-01/CB-01/
# CB-02/PV-02/CW-01/FI-29 precedent). Build gates had leaned positive:
# paired matetrack ON 905/777 vs OFF 893/768 (the mate machinery
# visibly helps mate-finding), KQvK@hmc95 correctly scored 0.
# FI-53 TT_R50 -- at hmc>=90 refuse TT cutoffs/narrowing for
# decisive-but-non-mate stored values (|v|>=500cp): the promised
# win may not be convertible before the rule draw. Mates and
# quiet values still cut (mate finds never lost by construction).
# FI-54 TERM_STORE -- terminal mate/stalemate returns write a
# permanent TT_EXACT entry at sentinel depth 200 (a forced mate
# is depth-invariant); provably safe half.
# FI-54 TT_MATE_CUT -- negamax probe cuts on mate-range TT values
# regardless of stored depth (GHI exposure = SF's accepted
# tradeoff; if matetrack shows wrong mates, arm TERM_STORE alone).
# All False = v49 node-exact.
TT_R50 = True
TERM_STORE = True
TT_MATE_CUT = True
# FI-56: root-move LMR -- late (i>=4) quiet
# non-promotion root moves that neither respond to nor give check are
# scouted at depth-1-R (R = g_lmr[d][i]/2, cap depth-2, depth>=3), with
# a full-depth zero-window verify before the full-window re-search --
# negamax's standard cascade, now at the root. Deliberately overturned
# the "no reductions at root" design stance; 3/4-convergent,
# SF-standard. CONFIRMED => v51 2026-07-18 (twenty-seventh campaign vs
# Old Engine/50, seed 50): 2k screen +17.56 +/-15.3 (CI > 0), main
# tranche (offset 1000) ACCEPT H1 at 7,343 games (+9.37 +/-8.0, LLR
# +2.957 > +2.944, stopped early) -- the C era's SECOND SPRT accept;
# pooled 9,343 games: +11.12 +/-5.3 (51.60%, ptnml 220/996/1988/1173/
# 282, ratio 1.20, pooled LLR +4.549). The -28% fixed-depth node cut
# converted to depth at fixed time (matetrack +28 mates presaged it).
# False = v50 node-exact.
ROOT_LMR = True
# FI-55: IIR weak-evidence trigger -- P-03 reduces on a missing TT move;
# this also reduces when the TT move exists but is weak ordering
# evidence: a TT_UPPER entry stored shallower than the current depth
# (the move is whatever the fail-low search last tried -- no cutoff
# evidence, ordering nearly as blind as a miss). Current-SF trigger
# form (!ttMove || bound == UPPER); IIR_MIN_DEPTH/!in_chk gates kept;
# the F1 depth-gap sub-variant is NOT built and now stays that way.
# SCREEN-KILLED 2026-07-19 (twenty-eighth candidate vs Old Engine/51,
# seed 51, 2k screen): -9.04 +/-15.2 (48.70%, ptnml 63/250/408/234/45,
# pair ratio 0.89, norm -18.90) -- a negative lean on a +0-2 prior
# fails the screen gate; no 10k spent. CALIBRATION LESSON recorded:
# the paired matetrack had read the STRONGEST result on the books
# (+100 mates, 1049/877 vs 949/811) and the screen still leaned
# negative -- matetrack magnitude does NOT predict Elo (it measures
# tactics-finding; the re-firing reduction mis-ranks quiet positions).
# REVERTED to False (dormant, do-not-retry at this TC); mechanism
# kept, abi 20 stays.
IIR_WEAK = False
# FI-64: LMR on SEE-losing captures -- badcaps (ordered dead last,
# almost never best) share the g_lmr reduction table instead of getting
# full-depth zero-window scouts. Reduction NOT pruning: a reduced
# badcap that fails high re-searches at full depth via the PVS ladder,
# so no move is ever lost (deep sacs seen one iteration later at
# worst) -- unlike the closed FI-18 pruning vein. The FI-04 history
# nudge is quiet-gated in the same edit (butterfly history is
# quiet-only). SCREEN-KILLED 2026-07-21 (twenty-ninth candidate vs Old
# Engine/51, seed 51 -- the first screen on the nodes instrument,
# 2k @ --nodes 2M NPS-calibrated on the cheap server): -10.95 +/-15.3
# (48.43%, ptnml 40/281/427/206/46, pair ratio 0.79, norm -24.07).
# The earlier GCloud TIMED screen had read +2.78 +/-15.2 -- the two
# reads straddle null within joint noise; combined evidence
# null-to-negative on a +0-2 prior = no 10k. The FI-18 diagnosis
# ("these subtrees already fail low fast -- alpha-beta gets the skip
# for free") stands as the likely story. REVERTED to False (dormant,
# do-not-retry at this TC); mechanism kept, abi 21 stays; the FI-04
# quiet-gate fix inside the widened block survives (latent-bug value).
LMR_BADCAP = False
# P-26 selectivity spins as VISIBLE class attrs (previously hardcoded in
# the _sync_c_params push below; exposing them is the sweep's precondition
# -- house rule: no hidden values, a candidate is an attr change here).
# SWEEP POINT 1 ARMED 2026-07-21 (thirtieth campaign vs Old Engine/51,
# the selectivity lane's zero-code opener): LMR_DIV 200 -> 170, i.e.
# every LMR reduction scales by ~1.18 (R = 0.75 + ln(d)ln(m)/1.70).
# Fixed-depth engagement is the depth-thesis direction: bench
# 1,083,772 -> 965,336 (-10.9%; 185 read -4.9%, 150 -12.8% and
# flattening -- 170 is the knee). Known exposure: over-reduction of
# late quiets (tactical misses) -- paired matetrack is the gate, and
# the interior-reduction 0-for-2 record (FI-55/64) is priced in: this
# scales the CONFIRMED-good existing reduction structure rather than
# adding new reduction sites. NULL_BASE 2->3 was measured and PARKED:
# +17.5% fixed-depth nodes (R+1 pushes shallow nulls below the child-
# depth floor, losing null pruning where the tree is widest) -- anti-
# thesis, screen it only if the sweep exhausts better points.
# (2, 6, 200) = v51 node-exact. NOT correctness-class: revert on null;
# a kept point re-pins and the next point sweeps from there.
# POINT 1 VERDICT: NULL 2026-07-21 (thirtieth campaign, split 2k screen
# @ nodes 1.75M on two cheap servers, pooled): +0.69 (50.10%, ptnml
# 56/230/412/258/44, ratio 1.06, LLR -0.063 dead flat) -- LMR
# aggressiveness is measurably FLAT near the default at this
# resolution; reverted to 200, sweep advances to the next lever.
NULL_BASE = 2
NULL_DIV = 6
LMR_DIV = 200
# FI-24(a)+(b): the null-move refinement batch, ARMED 2026-07-21 for
# the thirty-first campaign vs Old Engine/51 (nodes@1.75M standard).
# Two toggles, ONE campaign per the entry's pre-registration (same
# null-mechanism family -- the FI-30 batching precedent, not the
# FI-50/51/52 anti-pattern):
# (a) NULL_NODOUBLE -- no null-after-null (prev12 sentinel): two
# stand-pats in a row prove nothing and hide zugzwang 2 plies in.
# (b) NULL_EVALR -- R += (prune_eval-beta)/200 capped +2: deep nulls
# only at clearly-winning nodes; the shallow-null population is
# untouched, so the measured NULL_BASE cliff cannot recur.
# Both False = v51 node-exact. CONFIRMED => v52 2026-07-21 (thirty-first
# campaign vs Old Engine/51, nodes@1.75M NPS-calibrated, split across two
# cheap servers): split 2k screen +15.82 pooled (LLR +1.403), then two
# 5k tranche halves (+4.79 / +10.84); POOLED 12,000 games **+6.63 +/-4.5**
# (50.95%, ptnml 257/1376/2490/1558/319, pair ratio 1.15, pooled
# GSPRT[0,4] LLR **+4.533 ACCEPT**) -- the C era's THIRD SPRT accept and
# the first campaign confirmed on the nodes instrument. Owes the
# one-time timed cross-check (instrument validation, pre-registered).
NULL_NODOUBLE = True
NULL_EVALR = True
# FI-63: SF-style quietCheckEvasions -- in-check qsearch nodes are the
# last node population with ZERO pruning; after QS_EVASION_CAP
# fully-searched quiet evasions the rest are skipped (captures and
# promotion evasions ALWAYS searched), never while the node still reads
# mated (best <= -MATE_THRESH), so a mate can't be concluded from a
# pruned set. Known unsoundness, delta-pruning class: a capped fail-low
# node stores TT_UPPER at the searched best -- a wrong-way bound if a
# skipped evasion was better, which the TT-quality machinery (P-44/
# FI-30 sharpening) then reads; paired matetrack is the PRIMARY gate.
# CLOSED AS A DEAD GATE 2026-07-21, pre-A/B (FI-48 precedent, second
# of its class): the feature has NO useful operating point. Cap sweep
# at fixed depth -- 2: 1,163,657 nodes (+10.5%!), 3: +0.5%, 4: +0.3%,
# 6: ~0 (vacuous). At the spec's armed value it ENGAGES but costs a
# tenth of the tree (the named wrong-way TT_UPPER mis-bounding forcing
# re-search upstream); at cap>=3 it barely fires at all, because
# in-check qsearch nodes rarely hold more than two quiet evasions. And
# the PRIMARY gate failed: paired matetrack ON 930/802 vs OFF 948/811
# (-18 found, -9 best) -- the skipped-saving-evasion mode is real, not
# theoretical. Against an entry priced +0-1 Elo, no screen is
# warranted. Mechanism kept at 0 (v52 node-exact, abi 23); re-measure
# only if qsearch evasion ordering changes materially.
QS_EVASION_CAP = 0
# P-33 REVISIT: singular extensions. Rejected in the PYTHON era (null
# @depth 8, negative @depth 6) -- but that measured a depth-~8 engine,
# and singular is the classic technique whose value scales WITH depth;
# the C core now searches ~19. At a non-root node with a deep-enough