/machines/1RB0LF_0RC---_1RD0RA_1LE0RC_0LA0LE_1LE1LB/check_proof.py · raw
  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
#!/usr/bin/env python3
"""check_proof.py -- simulation evidence for proof.md of 1RB0LF_0RC---_1RD0RA_1LE0RC_0LA0LE_1LE1LB (machine #8).

Self-contained direct simulator on THIS table (pure Python; check 10 also builds the plain C simulator
scripts/check_direct_sim.c with cc).  Only check 9a/9d import the event-level macro model
scripts/deepen3_macro.py (round 3); the path algebra of proof.md section 5 is re-implemented here from the
closed forms, and check 10 tests it against direct simulation without any model in between.

  python3 -B check_proof.py [--seed S] [--quick] [--only 1,2,...] [--jobs 2]

Checks (each prints PASS/FAIL with counts):
  1 table      the only halting transition is B1; B is entered only by A0 (1RB) and F1 (1LB)
  2 windows    every window lemma of proof.md 2.1, PEN and PFN included, by an exact finite run that never reads
               outside its window (so it holds in every context); exact output word, exit state and step count.
               PEN/PFN are run on the bare 73-cell window P1 (and, as a sanity check, with each 2-cell right context)
  3 Inc_L      rule Inc_L: exhaustive small (N<=4 digits, all v<2^N-1, gaps 1..3, a 0..3, 6 left contexts)
               + random large; exact tape, the head never enters L before the end, steps 6a+6S+24j+7
  4 pause      Inc, P100, WX, P010, ZX, ZXN, ZXP1, ZXP2: inner record -> next inner record, exact whole tape and
               exact step formula (the step formula is the sum of the chain Inc_L [B1 Inc_L] END_X of proof.md 3)
  5 T(f)       all 16,384 L* fixed parts: the window run 'C> F(f)' (73 cells) exits LEFT (state E or F) without
               reading the end unit, except f = 1^15, which exits RIGHT in state C after 73 steps; and the finite
               facts (T1)-(T4) of proof.md 4.3 that lemma RB turns into TAILFRONT and L* closure: an F-exit leaves 00
               first, y = 0 forces sigma = F, the exit word (first token set to 01 after an F-exit) is F(f') for an
               f' with x' = 1, y' = 1 - y (N) or y' = y (S), and (y,z) -> (y',z') is a TAILFRONT arrow
  6 overflow   one overflow by direct simulation for every L* fixed part (m=3, random end bits), in normal context
               at (r,a,G) = (0,2,()) and (3,3,(9,)) and in pause context at (r,p,wall,G) = (0,1,1,()) and (2,4,2,(8,)):
               NO/PO or SILENT/SILENTP with the exact wall / lead form; the class equals the parity prediction of
               proof.md 4.3; the new RC parses as U^(2r'+H') W(f',e) with the r', H' of lemma RB; L* closed; end
               unit untouched (f != 1^15); TAILFRONT; plus 200 random pairs: PO/SILENTP give the same new RC as
               NO/SILENT
  7 endcarry   ENDCARRY exhaustive for every end unit with m <= 10 (normal and pause context) and random m <= 40
               (all j, incl. growth j = m) by direct simulation
  8 init       blank tape -> first configuration in family X that satisfies INV (step 92,299): exact parse,
               a = 121, G = (28,), r + H = 6, L*, m = 3, f = 11, M = 256, s = 55, kappa = 2; lists the earlier
               all-ones normal-mode records (outside X, or in X but outside INV)
  9 macro      (a) path closed forms S/P1/P1S/P3 vs the event-level macro model on random invariant states;
               (b) interval closure, exact integers, N = 8..400; (c) side conditions along every path, with the
               three-way slot rule (PO iff c <= w-1, WX iff c >= w+1, c = w never); (d) replay of deepen3_macro
               random runs and a DFS over all front-unit sequences (depth 48)
  10 direct    whole paths by plain direct simulation (C core scripts/check_direct_sim.c, no acceleration): build a
               configuration in X with INV from (N, k, kappa, G, f, s, tail), run the real machine to the next
               normal-mode all-ones inner record, parse it, and require INV there with exactly one predicted
               (path, M', s', kappa', f'). N = 8 exhaustive (all G, both kappa, every s of all three intervals);
               random N = 9..12 incl. interval end points, f = 1^15 and the tail 110111111111111 that forces
               ENDCARRY at PO#2; targeted ENDCARRY-at-PO#2; continuous chains; and the real orbit from the blank
               tape (records 92,299 / 1,006,516 / 14,514,329 / 114,113,163: paths P1, P1, S)

Checks 6, 7 and 10 use a pool of 2 worker processes (--jobs).
"""
import sys, os, random, re, itertools, argparse, json
HERE = os.path.dirname(os.path.abspath(__file__))
TM = "1RB0LF_0RC---_1RD0RA_1LE0RC_0LA0LE_1LE1LB"
ST = "ABCDEF"
TT = {}
for i, part in enumerate(TM.split("_")):
    for s in range(2):
        t = part[3 * s:3 * s + 3]
        TT[(ST[i], s)] = None if t == "---" else (int(t[0]), 1 if t[1] == "R" else -1, t[2])
# int-coded table for the fast simulator
NX = [[None, None] for _ in range(6)]
for (q, s), v in TT.items():
    NX[ST.index(q)][s] = None if v is None else (v[0], v[1], ST.index(v[2]))

RESULTS = []
def report(name, ok, detail):
    RESULTS.append((name, ok))
    print("%-10s %s  %s" % (name, "PASS" if ok else "FAIL", detail), flush=True)

POOL = None            # set in main: a fork pool of --jobs workers (at most 2 heavy processes, see the repo rules)
def pmap(fn, jobs, chunksize=16):
    """map fn over jobs (in order), with the worker pool when there is one"""
    if POOL is None: return [fn(j) for j in jobs]
    return POOL.map(fn, jobs, chunksize)

# ----------------------------------------------------------------------------------------------- simulator
class Sim:
    """Bytearray tape. pos = head index, st = state index, last = last move (+1/-1/0)."""
    def __init__(self, left, right, state="A", head_on="right", pad=1 << 16):
        self.off = pad + len(left)
        self.T = bytearray(2 * pad + len(left) + len(right) + 16)
        for i, c in enumerate(left + right): self.T[pad + i] = 1 if c == "1" else 0
        self.pos = self.off if head_on == "right" else self.off - 1
        self.st = ST.index(state); self.steps = 0; self.last = 0
    def step(self):
        tr = NX[self.st][self.T[self.pos]]
        if tr is None: return False
        w, mv, ns = tr
        self.T[self.pos] = w; self.pos += mv; self.st = ns; self.steps += 1; self.last = mv
        return True
    def cells(self, a, b):  # relative to off
        return "".join("1" if self.T[self.off + i] else "0" for i in range(a, b))
    def span(self):
        nz = [i for i in range(len(self.T)) if self.T[i]]
        if not nz: return self.pos - self.off, self.pos - self.off
        return min(nz[0], self.pos) - self.off, max(nz[-1], self.pos) - self.off
    def tape_str(self):
        lo, hi = self.span()
        return self.cells(lo, self.pos - self.off) + "|" + self.cells(self.pos - self.off, hi + 1)

def lead_at(T, k):
    q = 0
    while T[k] == 0 and T[k + 1] == 0 and T[k + 2] == 1: k += 3; q += 1
    return q if (T[k] == 0 and T[k + 1] == 0 and T[k + 2] == 0 and T[k + 4] == 1 and T[k + 5] == 0
                 and T[k + 6] == 0 and T[k + 7] == 0 and T[k + 8] == 1) else None

def is_ir(sim):
    """inner record: A reads a 0 it reached moving left, right side (001)^p 00 0b10001"""
    return sim.st == 0 and sim.last == -1 and sim.T[sim.pos] == 0 and lead_at(sim.T, sim.pos) is not None

def run_to_ir(left, right, cap=10 ** 7):
    s = Sim(left, right)
    while s.steps < cap:
        if s.steps > 0 and is_ir(s): return s
        if not s.step(): return None
    return None

def norm(l, r):
    return l.lstrip("0"), r.rstrip("0")

def ir_split(s):
    lo, hi = s.span()
    return s.cells(lo, s.pos - s.off).lstrip("0"), s.cells(s.pos - s.off, hi + 1).rstrip("0")

# ----------------------------------------------------------------------------------------------- notation
def digits(bits, gaps):
    out = "0%d10001" % bits[0]
    for b, g in zip(bits[1:], gaps): out += "100" * g + "0%d10001" % b
    return out
def bits_of(v, N): return [(v >> i) & 1 for i in range(N)]
FIXED = "0?0?0?10001" "0?0?10001" "0?1000?10001" "0?0?1000?10001" "0?1000?1000?1000?1000?10001"
def fill(t, bits):
    it = iter(bits); return "".join(next(it) if c == "?" else c for c in t)
def Wword(fb, eb): return fill(FIXED + "0?" * len(eb) + "01", fb + eb)
def RC(r, fb, eb):
    return "0110001100" * r + ("01100" if fb[1] == "1" else "") + Wword(fb, eb)
GAP2 = "0110001100100"          # the gap-2 digit with bit 1, followed by its gap (100)^2
def parse_rc(rc):
    """rc with its digit bits set (set_bits, as at the next overflow) -> (fixed bits, end bits, H, r) or None"""
    for m in range(0, 400):
        t = FIXED + "0?" * m + "01"; L = len(t)
        if len(rc) < L: return None
        w = rc[-L:]
        if not all(c == "?" or c == d for c, d in zip(t, w)): continue
        b = "".join(d for c, d in zip(t, w) if c == "?")
        mm = re.fullmatch(r"((?:0110001100)*)(01100)?", rc[:-L])
        if mm: return b[:15], b[15:], 1 if mm.group(2) else 0, len(mm.group(1)) // 10
    return None
def set_bits(s):
    """set every digit bit of the leading run of digits 0b10001 (100)^g (counter at its next overflow)"""
    s = list(s); pos = 0; n = len(s)
    while pos + 7 <= n and "".join(s[pos:pos + 7]) in ("0010001", "0110001"):
        s[pos + 1] = "1"; pos += 7
        while pos + 3 <= n and "".join(s[pos:pos + 3]) == "100": pos += 3
    return "".join(s)

# ----------------------------------------------------------------------------------------------- 1 table
def check_table():
    halts = [k for k, v in TT.items() if v is None]
    into_B = sorted(k[0] + str(k[1]) for k, v in TT.items() if v is not None and v[2] == "B")
    ok = halts == [("B", 1)] and into_B == ["A0", "F1"]
    report("1 table", ok, "halting transitions %s; B entered by %s" % (halts, into_B))

# ----------------------------------------------------------------------------------------------- 2 windows
def win(u, X, v, cap=100000):
    """Run u <X v (head on last cell of u) or u X> v (head on first cell of v) until the head leaves the window."""
    w = [int(c) for c in u + v]
    if X.startswith("<"): pos = len(u) - 1; st = X[1]
    else: pos = len(u); st = X[0]
    steps = 0
    while 0 <= pos < len(w) and steps < cap:
        tr = TT[(st, w[pos])]
        if tr is None: return ("HALT", steps)
        a, mv, st = tr; w[pos] = a; pos += mv; steps += 1
    s = "".join(map(str, w))
    if pos < 0: return ("<%s %s" % (st, s), steps)
    if pos >= len(w): return ("%s %s>" % (s, st), steps)
    return ("CAP", steps)

F1 = fill(FIXED, "1" * 15)
F1P = "1010100101010100101010010100101010100101001010100101001010010100101001010"   # C-image of F(1^15)
WINDOWS = [
    # name, u, X, v, expected result, steps                                   (proof.md 2.1)
    ("R2", "", "A>", "00", "10 C>", 2),
    ("R4", "", "C>", "100", "010 C>", 3),
    ("R6", "", "C>", "01", "10 C>", 2),
    ("R7", "", "C>", "00", "<E 01", 3),
    ("R3", "", "C>", "0110001", "1001010 C>", 7),
    ("R5", "", "C>", "0010001", "<E 0110001", 3),
    ("L1", "010", "<E", "", "<E 100", 3),
    ("L2", "1001010", "<E", "", "<E 0010001", 17),
    ("L3", "10", "<E", "", "<F 00", 2),
    ("L3F", "10", "<F", "", "<E 01", 2),
    ("L4", "100", "<F", "", "<F 001", 3),
    ("UE", "10010", "<E", "", "<F 00100", 5),
    ("UF", "10010", "<F", "00", "<E 0010001", 15),
    ("PE", "1001010010", "<E", "", "<E 0010001100", 20),
    ("PF", "1001010010", "<F", "00", "<F 001000010001", 20),
    # left ends (proof.md 3.2)
    ("END", "00", "<F", "", "<A 01", 2),
    ("B1", "01", "<F", "", "00 A>", 3),
    ("ENDZX", "010010100100", "<F", "", "<A 000100011001", 22),
    # overflow boundaries (proof.md 4.2)
    ("BN", "010010", "<F", "00", "<A 00010001", 16),
    ("BS", "1001010010010", "<E", "", "<E 0010001100100", 23),
    # ENDCARRY pieces (proof.md 4.4): CF1, and the two returns over the processed F(1^15) = P1 (73-cell windows:
    # the run never reads the cell right of P1, so the end unit to the right is untouched)
    ("CF1", "", "C>", F1, F1P + " C>", 73),
    ("PEN", F1P, "<E", "", "<E " + fill(FIXED, "100100010001010"), 133),
    ("PFN", F1P, "<F", "", "<F " + fill(FIXED, "010000100100101"), 123),
]
def check_windows():
    bad = []; n = 0
    for name, u, X, v, exp, k in WINDOWS:
        r, kk = win(u, X, v); n += 1
        if r != exp or kk != k: bad.append((name, r, kk, exp, k))
    # sanity: PEN / PFN with each 2-cell right context -- same run, context unchanged
    nctx = 0
    for ctx in ("00", "01", "10", "11"):
        for name, X, f in (("PEN", "<E", "100100010001010"), ("PFN", "<F", "010000100100101")):
            r, kk = win(F1P, X, ctx); nctx += 1
            if r != X + " " + fill(FIXED, f) + ctx: bad.append((name + "+" + ctx, r, kk))
    report("2 windows", not bad, "%d window lemmas exact (full output word, exit side and state, steps), incl. "
           "PEN P1 <E -> <E F(100100010001010) in 133 and PFN P1 <F -> <F F(010000100100101) in 123 on the bare "
           "73-cell window; %d PEN/PFN runs with a 2-cell right context leave it unchanged%s"
           % (n, nctx, "" if not bad else "  BAD %s" % bad[:3]))

# ----------------------------------------------------------------------------------------------- 3 Inc_L
LEFTS = ["", "1", "01", "1001", "100100101001", "0101001001001"]
def inc_l_case(L, a, bits, gaps, tail):
    N = len(bits); v = sum(b << i for i, b in enumerate(bits))
    if v + 1 >= 1 << N: return None
    j = 0
    while bits[j]: j += 1
    S = sum(gaps[:j])
    right = "001" * a + "00" + digits(bits, gaps) + tail
    s = Sim("1" + L, right)                 # a guard cell '1' left of L makes 'never enters L' observable
    target = s.off - 1
    while s.steps < 10 ** 6:
        if s.pos < s.off: break
        if not s.step(): return False
    want = "001" * a + "00" + digits(bits_of(v + 1, N), gaps) + tail
    got = s.cells(0, len(want))
    return (s.pos == target and ST[s.st] == "F" and got == want and s.cells(-len(L) - 1, 0) == "1" + L
            and s.steps == 6 * a + 6 * S + 24 * j + 7)
def check_inc(R, quick):
    n = 0; bad = []
    for N in range(1, 5):
        for gaps in itertools.product(range(1, 4), repeat=N - 1):
            for v in range(0, (1 << N) - 1):
                for a in range(0, 4):
                    for L in LEFTS:
                        r = inc_l_case(L, a, bits_of(v, N), list(gaps), "0110001100")
                        n += 1
                        if not r: bad.append((L, a, v, gaps))
    nr = 0
    for _ in range(100 if quick else 400):
        N = R.randint(1, 8); v = R.randrange(0, (1 << N) - 1); gaps = [R.randint(1, R.choice([5, 60, 300])) for _ in range(N - 1)]
        a = R.randint(0, R.choice([5, 60, 300])); L = "".join(R.choice("01") for _ in range(R.randint(0, 12)))
        r = inc_l_case(L, a, bits_of(v, N), gaps, "".join(R.choice("01") for _ in range(9)))
        nr += 1
        if not r: bad.append(("rnd", L, a, v, gaps))
    report("3 Inc_L", not bad, "exhaustive %d + random %d instances: exact tape, F on the last cell of L, L untouched, "
           "steps 6a+6S+24j+7%s" % (n, nr, "" if not bad else "  BAD %s" % bad[:3]))

# ----------------------------------------------------------------------------------------------- 4 pause rules
def rnd_Y(R):
    return R.choice(["", "100" * R.randint(1, 30) + "101001" + "010" * R.randint(3, 30) + "1001",
                     "100" * R.randint(1, 30) + "010" * R.randint(3, 30) + "1001", "100" * R.randint(1, 30)])
def pause_instances(R, n):
    """yield (name, wall, p, bits, gaps, expected (wall', p', bits', gaps'), expected steps or None)"""
    for _ in range(n):
        Nd = R.randint(1, 6); gaps = [R.randint(1, R.choice([3, 20, 60])) for _ in range(Nd - 1)]
        v = 2 * R.randrange(0, max(1, (1 << Nd) // 2 - 1)); bits = bits_of(v, Nd)
        if v + 2 >= 1 << Nd: continue
        p = R.randint(0, R.choice([4, 40, 200])); b2 = bits_of(v + 2, Nd)
        j2 = 0
        while bits_of(v + 1, Nd)[j2]: j2 += 1
        S2 = sum(gaps[:j2])
        nn = R.randint(0, R.choice([3, 30, 200]))
        st2 = 12 * p + 6 * S2 + 24 * j2 + 19; st3 = 12 * p + 6 * S2 + 24 * j2 + 39
        yield ("Inc", "", p, bits, gaps, ("", p + 1, bits_of(v + 1, Nd), gaps), 6 * p + 9)
        yield ("P100", "100" * (nn + 1) + "1", p, bits, gaps, ("100" * nn + "1", p + 1, b2, gaps), st2)
        yield ("WX", "1", p, bits, gaps, ("", p + 1, b2, gaps), st2)
        Y = rnd_Y(R); m = R.randint(0, 40)
        yield ("P010", Y + "010" * (m + 1) + "01", p, bits, gaps, (Y + "010" * m + "01", p + 1, b2, gaps), st2)
        h = R.randint(3, 40); Y = R.choice(["", "100" * R.randint(1, 30) + "101001", "100" * R.randint(1, 30)])
        yield ("ZX", Y + "010" * h + "100101", p, bits, gaps, (Y + "010" * (h - 3) + "01", 0, [0] + b2, [p + 2] + gaps), st3)
        nz = R.randint(2, 40)
        yield ("ZXN", "100" * nz + "10100101", p, bits, gaps, ("100" * (nz - 2) + "1", 0, [0] + b2, [p + 2] + gaps), st3)
        n3 = R.randint(3, 40)
        if v + 3 < 1 << Nd:
            yield ("ZXP1", "100" * n3 + "01", p, bits, gaps, ("100" * (n3 - 1) + "10", p + 1, b2, gaps), st2)
            yield ("ZXP2", "100" * (n3 - 1) + "10", p + 1, b2, gaps,
                   ("100" * (n3 - 3) + "1", 0, [0] + bits_of(v + 3, Nd), [p + 1] + gaps), 6 * (p + 1) + 23)
def check_pause(R, quick):
    tally = {}; bad = []
    tail = "0110001100" + "01100" + Wword("110000100100101", "000")    # a real-shaped RC (never reached here)
    insts = list(pause_instances(R, 40 if quick else 150))
    # exhaustive small: all walls of the listed shapes with tiny parameters and all v up to 2^3
    for Nd in (1, 2, 3):
        for gaps in itertools.product((1, 2), repeat=Nd - 1):
            for v in range(0, 1 << Nd, 2):
                for p in range(0, 3):
                    R2 = random.Random(v * 100 + p * 10 + Nd)
                    for x in pause_instances(R2, 1):
                        if x[3] == bits_of(v, Nd) or True: insts.append(x)
    for name, wall, p, bits, gaps, exp, st in insts:
        Nv = len(bits)
        if sum(b << i for i, b in enumerate(bits)) + (1 if name in ("Inc", "ZXP2") else 2) >= 1 << Nv: continue
        s = run_to_ir(wall, "001" * p + "00" + digits(bits, gaps) + tail)
        ok = s is not None
        if ok:
            got = ir_split(s); w2, p2, b2, g2 = exp
            want = norm(w2, "001" * p2 + "00" + digits(b2, g2) + tail)
            ok = got == want and (st is None or s.steps == st)
        t = tally.setdefault(name, [0, 0]); t[0] += 1; t[1] += ok
        if not ok and len(bad) < 4: bad.append((name, wall[-20:], p, bits, gaps, s and s.steps, st))
    allok = all(a == b for a, b in tally.values()) and len(tally) == 8
    report("4 pause", allok, " ".join("%s %d/%d" % (k, v[1], v[0]) for k, v in tally.items()) +
           "  (exact whole tape at the next inner record + exact steps)" + ("" if not bad else "  BAD %s" % bad))

# ----------------------------------------------------------------------------------------------- 5 T(f)
LSTAR = [b for b in range(1 << 15) if b & 1]          # x = 1
def fb_str(b): return "".join(str((b >> i) & 1) for i in range(15))
FU_NEXT = {("10", "e0"): "01", ("01", "e1"): "11", ("11", "e0"): "00", ("00", "e1"): "10", ("11", "S"): "10"}
def tf_facts(fb, sig, Fp):
    """The finite facts (T1)-(T4) of proof.md 4.3 for one fixed part f != 1^15 with x = 1, from its T(f) exit
    (state sig, exit word Fp).  Returns (class, f') or (None, reason)."""
    y = fb[1]
    if sig == "F" and not Fp.startswith("00"): return None, "T1: F-exit does not leave 00"
    if y == "0" and sig != "F": return None, "T2: y = 0 but sigma = E"
    Fq = "01" + Fp[2:] if sig == "F" else Fp            # lemma RB: after an F-exit the next UF / BN sets x to 1
    if len(Fq) != len(FIXED) or not all(c == "?" or c == d for c, d in zip(FIXED, Fq)): return None, "T3: not F(f')"
    f2 = "".join(d for c, d in zip(FIXED, Fq) if c == "?")
    cls = "S" if (y == "1" and sig == "F") else "N"     # lemma RB: N iff mu = F, mu = sigma flipped q = 2r + y times
    act = "S" if cls == "S" else "e%d" % (1 - int(y))   # lemma RB: emission 1 - H = 1 - y at N, 0 at S
    yy = y if cls == "S" else str(1 - int(y))           # lemma RB: H' = H at S, 1 - H at N; L* needs y' = H'
    if f2[0] != "1" or f2[1] != yy: return None, "T4: L* not closed"
    if FU_NEXT.get((fb[1:3], act)) != f2[1:3]: return None, "T4: not a TAILFRONT arrow"
    return cls, f2
def check_tf():
    TF = {}; cnt = {}; facts = {}; badf = []
    for b in LSTAR:
        fb = fb_str(b); r, k = win("", "C>", fill(FIXED, fb))
        if r.startswith("<"):
            key = (r[1], fb[1]); TF[fb] = (r[1], r.split()[1], k)
            cls, f2 = tf_facts(fb, r[1], r.split()[1])
            if cls is None: badf.append((fb, f2))
            else:
                kk = "%s-%s->%s" % (fb[1:3], cls if cls == "S" else "N,e%d" % (1 - int(fb[1])), f2[1:3])
                facts[kk] = facts.get(kk, 0) + 1
        else: key = ("RIGHT-" + r.split()[-1], fb[1]); TF[fb] = ("RIGHT", r.split()[0], k)
        cnt[key] = cnt.get(key, 0) + 1
    ok = (not badf and cnt.get(("RIGHT-C>", "1")) == 1 and TF["1" * 15] == ("RIGHT", F1P, 73)
          and sum(v for k, v in cnt.items() if not k[0].startswith("RIGHT")) == len(LSTAR) - 1)
    report("5 T(f)", ok, "16,384 L* fixed parts: window exits %s (window = the 73-cell fixed part only: the end "
           "unit is never read unless f = 1^15); facts (T1)-(T4) hold for all %d f != 1^15, giving %s%s"
           % (json.dumps({"%s,y=%s" % k: v for k, v in sorted(cnt.items())}), sum(facts.values()),
              json.dumps(dict(sorted(facts.items()))), "" if not badf else "  BAD %s" % badf[:3]))
    return TF

# ----------------------------------------------------------------------------------------------- 6 overflow
def overflow(left_wall, lead, G, r, fb, eb, pause):
    """One overflow from a normal-mode (pause=False: 0^inf A>(001)^lead 00 D(all 1) RC) or a pause-mode slot
    (pause=True: 0^inf (100)^w 1 A>(001)^lead 00 D(2^N-2) RC, wall given as left_wall = (100)^w 1).
    Returns (kind, new RC as found, H, steps) or (error,...)."""
    big = "".join("0110001" + "100" * g for g in G)
    D1 = big + GAP2 + "0110001100" * r + ("01100" if fb[1] == "1" else "")
    if pause: D1 = "0010001" + D1[7:] if D1 else D1      # b0 = 0 (v = 2^N - 2)
    rc = Wword(fb, eb)
    s = run_to_ir(left_wall, "001" * lead + "00" + D1 + rc)
    if s is None: return ("NOIR",)
    l, rr = ir_split(s)
    m = re.match(r"((?:001)*)00", rr); p = len(m.group(1)) // 3; rest = rr[m.end():]
    Gz = "".join("0010001" + "100" * g for g in G)
    if not pause:
        wallN = "100" * lead + "101001" + "".join("010" * (g + 1) + "1001" for g in G) + "01"
        if l == "" and p == lead + 1 and rest.startswith(Gz + "0010001100100"):
            return ("S", rest[len(Gz) + 13:], s.steps)
        if l == wallN and p == 0: return ("N", rest, s.steps)
        return ("X", l[-40:], p)
    w = left_wall
    n = (len(w) - 1) // 3
    wallN = "100" * n + "010" * (lead + 1) + "1001" + "".join("010" * (g + 1) + "1001" for g in G) + "01"
    if l == ("100" * (n - 1) + "1").lstrip("0") and p == lead + 1 and rest.startswith(Gz + "0010001100100"):
        return ("S", rest[len(Gz) + 13:], s.steps)
    if l == wallN and p == 0: return ("N", rest, s.steps)
    return ("X", l[-40:], p)
def _ovf_job(job):
    """worker: one overflow; returns (kind, parsed new RC or None)"""
    pause, r, lead, G, n, fb, eb = job
    res = overflow("100" * n + "1" if pause else "", lead, G, r, fb, eb, pause)
    if res[0] not in ("N", "S"): return (res[0], str(res[1:])[:120])
    return (res[0], parse_rc(set_bits(res[1])), res[1])
def check_overflow(R, TF, quick):
    stats = {"inputs": 0, "N": 0, "S": 0, "pred_ok": 0, "rb_ok": 0, "closed": 0, "endunit": 0, "tailfront": 0, "bad": []}
    # (pause, r, lead, G, wall n)
    sets = [(False, 0, 2, (), 0), (True, 0, 1, (), 1), (False, 3, 3, (9,), 0), (True, 2, 4, (8,), 2)]
    if quick: sets = sets[:2]
    jobs = []
    for pause, r, lead, G, n in sets:
        for b in LSTAR:
            fb = fb_str(b); eb = "".join(R.choice("01") for _ in range(3))
            if b == (1 << 15) - 1: eb = "010"
            jobs.append((pause, r, lead, G, n, fb, eb))
    ctx = {}
    for job, res in zip(jobs, pmap(_ovf_job, jobs, 64)):
        pause, r, lead, G, n, fb, eb = job; stats["inputs"] += 1
        c = ctx.setdefault("%s r=%d" % ("pause" if pause else "normal", r), [0, 0])
        if res[0] not in ("N", "S"):
            if len(stats["bad"]) < 3: stats["bad"].append(("kind", job, res))
            continue
        stats[res[0]] += 1; c[0 if res[0] == "N" else 1] += 1
        # parity prediction (proof.md 4.3): mu = sigma(f) flipped q = 2r + y times; N iff mu = F
        sig, _, _ = TF[fb]; q = 2 * r + int(fb[1])
        if sig != "RIGHT":
            mode = sig if q % 2 == 0 else ("E" if sig == "F" else "F")
            stats["pred_ok"] += ("N" if mode == "F" else "S") == res[0]
        else: stats["pred_ok"] += 1          # f = 1^15: ENDCARRY (check 7)
        pr = res[1]
        if pr is None:
            if len(stats["bad"]) < 3: stats["bad"].append(("shape", job, res[2][:60]))
            continue
        f2, e2, H2, r2 = pr
        # lemma RB: the restored block has q + 1 units after N (BN adds one), q units after S
        stats["rb_ok"] += 2 * r2 + H2 == (q + 1 if res[0] == "N" else q)
        stats["closed"] += f2[0] == "1" and H2 == int(f2[1])
        stats["endunit"] += e2 == eb or fb == "1" * 15
        e = (r2 + H2) - (r + int(fb[1]))
        fu, fu2 = fb[1:3], f2[1:3]; act = "S" if res[0] == "S" else "e%d" % e
        stats["tailfront"] += FU_NEXT.get((fu, act)) == fu2 and (res[0] == "N" or e == 0)
    # pause context: PO / SILENTP give the same new RC as NO / SILENT (random sample, random r, gaps, lead, wall)
    pjobs = []
    for _ in range(40 if quick else 200):
        fb = "1" + "".join(R.choice("01") for _ in range(14)); eb = "".join(R.choice("01") for _ in range(R.randint(0, 6)))
        r = R.randint(0, 5); G = tuple(R.randint(7, 40) for _ in range(R.choice([0, 1, 2]))); lead = R.randint(3, 40)
        n = R.randint(1, 30)
        pjobs.append((False, r, R.randint(2, 30), G, 0, fb, eb)); pjobs.append((True, r, lead, G, n, fb, eb))
    pres = pmap(_ovf_job, pjobs, 8); pc = [0, 0]
    for i in range(0, len(pjobs), 2):
        A, B = pres[i], pres[i + 1]
        pc[0] += 1; pc[1] += A[0] == B[0] and A[0] in ("N", "S") and A[2] == B[2]
    n = stats["inputs"]
    ok = (stats["pred_ok"] == n and stats["rb_ok"] == n and stats["closed"] == n and stats["endunit"] == n
          and stats["tailfront"] == n and not stats["bad"] and pc[0] == pc[1])
    report("6 overflow", ok, "%d overflows (%d N, %d S; N/S by context %s): class = parity prediction %d, "
           "2r'+H' as lemma RB %d, L* closed %d, end unit untouched %d, TAILFRONT %d; pause context (PO/SILENTP) "
           "same new RC as NO/SILENT %d/%d%s" % (n, stats["N"], stats["S"], json.dumps(ctx), stats["pred_ok"],
           stats["rb_ok"], stats["closed"], stats["endunit"], stats["tailfront"], pc[1], pc[0],
           "" if not stats["bad"] else "  BAD %s" % stats["bad"]))

# ----------------------------------------------------------------------------------------------- 7 ENDCARRY
def endcarry_want(e):
    """proof.md 4.4: (class, new end bits, new fixed bits, H') for the end bits e when f = 1^15"""
    m = len(e); j = 0
    while j < m and e[j] == "1": j += 1
    jj = j if j < m else m + 1; rest = e[j + 1:] if j < m else ""
    if jj % 2 == 0: want = ("N", "10" * (jj // 2) + "1" + rest, "100100010001010", 0)
    else: want = ("S", "01" * ((jj + 1) // 2) + rest, "110000100100101", 1)
    return want
def check_endcarry(R, quick):
    jobs = []
    # exhaustive: every end unit with m <= 10 (quick: m <= 7), normal context and pause context
    for pause in (False, True):
        for m in range(0, 8 if quick else 11):
            for b in range(1 << m):
                e = format(b, "0%db" % m) if m else ""
                jobs.append((pause, 1, 3, (9,), 2 if pause else 0, "1" * 15, e))
    nex = len(jobs)
    for _ in range(60 if quick else 300):
        m = R.randint(0, 40); r = R.randint(0, 8); a = R.randint(2, 30); pause = R.random() < 0.3
        G = tuple(R.randint(7, 30) for _ in range(R.choice([0, 1, 2])))
        j = R.choice([R.randint(0, m), m, max(0, m - 1), min(m, R.randint(0, 5))])
        e = "1" * j + ("0" + "".join(R.choice("01") for _ in range(m - j - 1)) if j < m else "")
        jobs.append((pause, r, a, G, R.randint(1, 30) if pause else 0, "1" * 15, e))
    t = [0, 0]; bad = []; tal = {}
    for job, res in zip(jobs, pmap(_ovf_job, jobs, 16)):
        pause, r, a, G, n, fb, e = job
        cls, we, wf, wH = endcarry_want(e)
        if len(we) == len(e) + 2: we = we[:-1]        # growth: the old end marker became a bit, a new 01 marker
        ok = res[0] == cls
        if ok:
            pr = res[1]
            ok = (pr is not None and pr[0] == wf and pr[1] == we and pr[2] == wH and (pr[3] + pr[2]) - (r + 1) == 0)
        t[0] += 1; t[1] += ok; k = "%s:%s" % ("pause" if pause else "normal", res[0]); tal[k] = tal.get(k, 0) + 1
        if not ok and len(bad) < 3: bad.append((job, res[:1]))
    report("7 endcarry", t[0] == t[1], "ENDCARRY %d/%d (%d exhaustive: every end unit with m <= %d, normal and pause "
           "context; %d random m <= 40, j incl. growth j = m) %s: j even -> N, end (10)^(j/2) 1 rest; j odd -> S, end "
           "(01)^((j+1)/2) rest; fixed bits 100100010001010/H'=0 or 110000100100101/H'=1; e0%s"
           % (t[1], t[0], nex, 7 if quick else 10, t[0] - nex, json.dumps(tal), "" if not bad else "  BAD %s" % bad))

# ----------------------------------------------------------------------------------------------- 8 init + real run
def macro_inv(a, G, R_):
    k = len(G); N = k + 1 + R_; M = 2 ** N
    Gam = sum((g - 1) << i for i, g in enumerate(G)); c = 2 ** (k + R_) - 2 ** (k + 1) - 2 * Gam - 4
    return M, a - c, c - M // 4
def body_digits(body):
    """leading digits 0b10001 (100)^g of a normal-mode body -> list of (bit, gap), end position"""
    out = []; pos = 0
    while True:
        mm = re.match(r"0([01])10001((?:100)*)", body[pos:])
        if not mm: break
        out.append((int(mm.group(1)), len(mm.group(2)) // 3)); pos += mm.end()
    return out, pos
def parse_family(a, body):
    """body right of the lead of an all-ones normal-mode configuration -> (a, G, r, H, (m, bits)) or None"""
    mm = re.fullmatch(r"((?:0110001(?:100){3,})*)0110001100100((?:0110001100)*)(01100)?(.*)", body)
    if not mm: return None
    G = tuple(len(x) // 3 for x in re.findall(r"0110001((?:100)+)", mm.group(1)))
    r = len(mm.group(2)) // 10; H = 1 if mm.group(3) else 0; W = mm.group(4)
    for m_ in range(0, 400):
        tt = FIXED + "0?" * m_ + "01"
        if len(tt) > len(W): return None
        if len(tt) == len(W) and all(c == "?" or c == d for c, d in zip(tt, W)):
            return a, G, r, H, (m_, "".join(d for c, d in zip(tt, W) if c == "?"))
    return None
def check_init(quick):
    """Run from the blank tape to the first normal-mode inner record that is a configuration of the family X
    (proof.md 1.3): all counter bits 1, large gaps >= 3, gap-2 digit, R block, W(f, e) in the template."""
    s = Sim("", ""); first = None; n_ir = 0; early = []
    while s.steps < 3_000_000:
        if s.steps > 0 and is_ir(s):
            lo, hi = s.span()
            if lo == s.pos - s.off:                         # normal mode: nothing left of the head
                n_ir += 1
                rr = s.cells(lo, hi + 1); m = re.match(r"((?:001)*)00", rr)
                body = rr[m.end():].rstrip("0") if m else ""
                dg, _ = body_digits(body)
                if m and len(dg) >= 2 and all(b for b, g in dg):
                    fam = parse_family(len(m.group(1)) // 3, body)
                    if fam is not None:
                        M_, s_, k_ = macro_inv(fam[0], fam[1], fam[2] + fam[3])
                        if fam[1] and k_ in (2, 8) and fam[4][1][1:3] == "11" and 24 <= s_ <= M_ // 4 + 2:
                            first = (s.steps, fam); break
                    early.append((s.steps, "X" if fam else "-"))
        if not s.step(): report("8 init", False, "HALT at %d" % s.steps); return
    if first is None: report("8 init", False, "no family configuration"); return
    steps, (a, G, r, H, t) = first
    M, sv, kap = macro_inv(a, G, r + H)
    ok = (t is not None and a == 121 and G == (28,) and r + H == 6 and t[0] == 3 and t[1][0] == "1"
          and H == int(t[1][1]) and t[1][1:3] == "11" and M == 256 and sv == 55 and kap == 2)
    report("8 init", ok, "blank -> first configuration in X and INV at step %d (normal-mode inner record no. %d; earlier "
           "all-ones normal-mode records (X = in family X but outside the invariant, - = not in X): %s): "
           "a=%d G=%s r=%d H=%d m=%s fixed=%s end=%s; M=%d s=%d kappa=%d (expected a=121, G=(28,), r+H=6, m=3, x=1, "
           "H=y, f=11, M=256, s=55, kappa=2)" % (steps, n_ir, early, a, G, r, H, t and t[0], t and t[1][:15], t and t[1][15:],
           M, sv, kap))

# ----------------------------------------------------------------------------------------------- 9 macro
def check_macro(R, quick):
    sys.path.insert(0, os.path.join(HERE, "scripts"))
    import deepen3_macro as DM
    PRED = {("S",): lambda M, s: (M, s + M, 2), ("e0", "e1"): lambda M, s: (4 * M, M + 10 - s, 2),
            ("e0", "e1", "S"): lambda M, s: (4 * M, 5 * M + 10 - s, 2),
            ("e0", "e1", "e0", "e1"): lambda M, s: (32 * M, 9 * M + 32 - s, 8)}
    def inv(f, M, s, kap):
        if kap not in (2, 8) or M < 256: return False
        if f == "11": return 24 <= s <= M // 4 + 2
        if f == "10": return 8 <= s <= M - 14 or M + 24 <= s <= 2 * M
        return False
    # (a) path closed forms vs event-level model, random runs (incl. S-heavy)
    tally = {"nslots": 0, "inv_ok": 0, "pred_ok": 0, "pred_bad": 0, "other": 0, "flags": 0, "trans": {}}
    for seed in range(20 if quick else 80):
        RR = random.Random(seed); q = RR.choice([0.1, 0.5, 0.9]); st = DM.START; fu = DM.START_FU; cur = None; path = []
        for i in range(300 if quick else 700):
            if st[0] == "N":
                _, a, G, NR = st; M, s, kap = macro_inv(a, G, NR)
                tally["nslots"] += 1; tally["inv_ok"] += inv(fu, M, s, kap)
                if cur is not None:
                    key = tuple(path); p = PRED.get(key); nm = "%s:%s" % (cur[0], "-".join(key))
                    tally["trans"][nm] = tally["trans"].get(nm, 0) + 1
                    if p is None: tally["other"] += 1
                    elif p(cur[1], cur[2]) == (M, s, kap): tally["pred_ok"] += 1
                    else: tally["pred_bad"] += 1
                cur = (fu, M, s); path = []
            opts = DM.FU_ACTS[fu]; act = opts[0] if len(opts) == 1 else ("S" if RR.random() < q else "e0")
            st2, f = DM.apply(st, act); fu = DM.FU_NEXT[(fu, act)]; path.append(act)
            if st2 is None or f: tally["flags"] += 1; break
            st = st2
    oka = tally["inv_ok"] == tally["nslots"] and tally["pred_bad"] == 0 and tally["other"] == 0 and tally["flags"] == 0
    # (b) interval closure, exact: images of the interval end points (all maps have slope +-1)
    badb = []
    for N in range(8, 401):
        M = 2 ** N
        cases = [("11", 24, M // 4 + 2, "S", lambda s: (M, s + M), "10"),
                 ("11", 24, M // 4 + 2, "P1", lambda s: (4 * M, M + 10 - s), "10"),
                 ("10", 8, M - 14, "P1", lambda s: (4 * M, M + 10 - s), "11"),
                 ("10", M + 24, 2 * M, "P1S", lambda s: (4 * M, 5 * M + 10 - s), "10"),
                 ("10", M + 24, 2 * M, "P3", lambda s: (32 * M, 9 * M + 32 - s), "10")]
        for f, lo, hi, nm, fn, f2 in cases:
            for s in (lo, hi):
                M2, s2 = fn(s)
                if f2 == "11": okk = 24 <= s2 <= M2 // 4 + 2
                else: okk = 8 <= s2 <= M2 - 14 or M2 + 24 <= s2 <= 2 * M2
                if not okk: badb.append((N, nm, s))
            # interval stays inside ONE target interval (images of both ends in the same piece)
            M2, a2 = fn(lo); _, b2 = fn(hi); l2, h2 = min(a2, b2), max(a2, b2)
            if f2 == "11": okk = 24 <= l2 and h2 <= M2 // 4 + 2
            else: okk = (8 <= l2 and h2 <= M2 - 14) or (M2 + 24 <= l2 and h2 <= 2 * M2)
            if not okk: badb.append((N, nm, "interval"))
    # (c) side conditions along each path, from the closed forms of proof.md 5.2, at both interval ends, N = 8..400
    #     (every quantity is linear in s, so the interval end points suffice).  slot(cyc, w): the pause has outer wall
    #     (100)^w 1 and the next overflow slot is cyc P100 cycles away: PO iff cyc <= w - 1, WX iff cyc >= w + 1,
    #     and cyc = w (bare wall '1' at the slot) is covered by no rule, so it must never occur.
    def slot(cyc, w): return "PO" if cyc <= w - 1 else ("WX" if cyc >= w + 1 else "NONE")
    badc = []
    def need(cond, tag):
        if not cond: badc.append(tag)
    for N in range(8, 401):
        M = 2 ** N
        for kap in (2, 8):
            c = M // 4 + kap
            for f, lo, hi in (("11", 24, M // 4 + 2), ("10", 8, M - 14), ("10", M + 24, 2 * M)):
                for s in (lo, hi):
                    t = (N, kap, f, s); a = s + c
                    need(a >= 2, ("NO a>=2",) + t)                                  # ZXN at the end of the NO rebuild
                    need(slot(c + 3, a - 2) == "PO", ("PO#1 slot",) + t)           # wall a-2 after ZXN, c' = c+3
                    w1 = a - 2 - (c + 3); need(w1 == s - 5 and w1 >= 3, ("PO#1 wall",) + t)
                    need(c + 3 >= 8, ("PO#1 p>=8 (new gap >= 7)",) + t)
                    w2 = w1 - 3                                                     # ZXP1 + ZXP2
                    if f == "11" or s <= M - 14:                                    # P1 (and S from 11)
                        need(slot(M + 2, w2) == "WX", ("P1 WX",) + t)
                        if f == "11": need(s + M >= M + 24, ("S",) + t)
                        continue
                    need(slot(M + 2, w2) == "PO", ("PO#2 slot",) + t)              # P1S / P3
                    w3 = w2 - (M + 2); need(w3 == s - M - 10 and w3 >= 14, ("PO#2 wall",) + t)
                    need(slot(2 * M - 1, w3 - 1) == "WX", ("P1S WX after SILENTP",) + t)   # SILENTP: wall -1, value 0
                    w4 = w3 - 3; need(slot(8, w4) == "PO", ("PO#3 slot",) + t)     # e0 at PO#2: new gap M+1
                    w5 = w4 - 8; need(w5 == s - M - 21 and w5 >= 3, ("PO#3 wall",) + t)
                    need(slot(8 * M + 8, w5 - 3) == "WX", ("P3 WX",) + t)          # PO#3: new gap 7
    # (d) deepen3_macro random runs (front-unit language) + DFS
    agg = {"seeds": 0, "clean": 0}
    for seed in range(40 if quick else 200):
        RR = random.Random(10_000 + seed); st = DM.START; fu = DM.START_FU; clean = True
        for i in range(400 if quick else 1500):
            opts = DM.FU_ACTS[fu]; act = opts[0] if len(opts) == 1 else ("S" if RR.random() < 0.3 else "e0")
            st2, f = DM.apply(st, act); fu = DM.FU_NEXT[(fu, act)]
            if f: clean = False; break
            st = st2
        agg["seeds"] += 1; agg["clean"] += clean
    depth = 24 if quick else 48
    stack = [(DM.START, DM.START_FU, 0)]; nodes = 0; flags = 0; leaves = 0
    while stack:
        st, fu, d = stack.pop(); nodes += 1
        if d == depth: leaves += 1; continue
        for act in DM.FU_ACTS[fu]:
            st2, f = DM.apply(st, act)
            if f: flags += 1
            if st2 is None: continue
            stack.append((st2, DM.FU_NEXT[(fu, act)], d + 1))
    ok = oka and not badb and not badc and agg["seeds"] == agg["clean"] and flags == 0
    report("9 macro", ok, "(a) %d normal-mode overflows, invariant %d, path closed forms %d ok / %d bad / %d other, "
           "transitions %s; (b) interval closure N=8..400 %s; (c) side conditions %s; (d) %d/%d random runs clean, "
           "DFS depth %d: %d leaves, %d nodes, %d flags" % (tally["nslots"], tally["inv_ok"], tally["pred_ok"],
           tally["pred_bad"], tally["other"], json.dumps(tally["trans"]), "ok" if not badb else badb[:3],
           "ok" if not badc else badc[:3], agg["clean"], agg["seeds"], depth, leaves, nodes, flags))

# ----------------------------------------------------------------------------------------------- 10 direct paths
_DLIB = None
def dlib():
    """build (if needed) and load the plain C simulator scripts/check_direct_sim.c"""
    global _DLIB
    if _DLIB is None:
        import ctypes, subprocess
        src = os.path.join(HERE, "scripts", "check_direct_sim.c"); so = os.path.join(HERE, "work", "check_direct_sim.so")
        if not os.path.exists(so) or os.path.getmtime(so) < os.path.getmtime(src):
            tmp = so + ".%d" % os.getpid()
            subprocess.check_call(["cc", "-O2", "-shared", "-fPIC", "-o", tmp, src]); os.replace(tmp, so)
        L = ctypes.CDLL(so); L.run.restype = ctypes.c_int
        P = ctypes.POINTER
        L.run.argtypes = [ctypes.c_void_p, ctypes.c_long, P(ctypes.c_long), P(ctypes.c_int), P(ctypes.c_int),
                          ctypes.c_longlong, ctypes.c_int, P(ctypes.c_longlong)]
        _DLIB = L
    return _DLIB
class CTape:
    """tape for the C core; grows (re-centres) when the head reaches a border"""
    def __init__(self, right):
        n = len(right); pad = max(1 << 14, 4 * n)
        self.T = bytearray(2 * pad + n); self.T[pad:pad + n] = right.encode().translate(bytes.maketrans(b"01", b"\x00\x01"))
        self.pos = pad; self.st = 0; self.lastmv = 0; self.steps = 0
    def regrow(self):
        lo = self.T.find(1); hi = self.T.rfind(1)
        lo = self.pos if lo < 0 else min(lo, self.pos); hi = self.pos if hi < 0 else max(hi, self.pos)
        core = self.T[lo:hi + 1]; pad = max(1 << 14, 2 * len(core))
        self.T = bytearray(pad) + core + bytearray(pad); self.pos = pad + (self.pos - lo)
    def run(self, cap, mode):
        import ctypes
        L = dlib()
        while True:
            buf = (ctypes.c_uint8 * len(self.T)).from_buffer(self.T)
            p = ctypes.c_long(self.pos); st = ctypes.c_int(self.st); lm = ctypes.c_int(self.lastmv); n = ctypes.c_longlong(0)
            rc = L.run(ctypes.addressof(buf), len(self.T), ctypes.byref(p), ctypes.byref(st), ctypes.byref(lm),
                       cap - self.steps, mode, ctypes.byref(n))
            del buf
            self.pos = p.value; self.st = st.value; self.lastmv = lm.value; self.steps += n.value
            if rc == 3: self.regrow(); continue
            return rc
    def split(self):
        lo = self.T.find(1); hi = self.T.rfind(1)
        lo = min(lo, self.pos); hi = max(hi, self.pos); tr = bytes.maketrans(b"\x00\x01", b"01")
        return (bytes(self.T[lo:self.pos]).translate(tr).decode().lstrip("0"),
                bytes(self.T[self.pos:hi + 1]).translate(tr).decode().rstrip("0"))

def X_word(a, G, r, H, f, e):
    """proof.md 1.3: (001)^a 00 [0110001 Z^g]_i 0110001 Z^2 U^(2r+H) W(f, e)"""
    return "001" * a + "00" + "".join("0110001" + "100" * g for g in G) + GAP2 + "01100" * (2 * r + H) + Wword(f, e)
def quantities(a, G, R_, f):
    M, s_, kap = macro_inv(a, G, R_)
    return M, s_, kap, f[1:3]
def INV_ok(a, G, r, H, f):
    """INV of proof.md 5.3 (with M >= 256) at a configuration X(a; G; r, H; f, e)"""
    M, s_, kap, fu = quantities(a, G, r + H, f)
    if kap not in (2, 8) or M < 256 or any(g < 7 for g in G) or f[0] != "1" or int(f[1]) != H: return False
    if fu == "11": return 24 <= s_ <= M // 4 + 2
    if fu == "10": return 8 <= s_ <= M - 14 or M + 24 <= s_ <= 2 * M
    return False
def predict_paths(fu, M, s_, kap):
    """proof.md 5.2 / 5.3: the possible (path, M', s', kappa', f') from a configuration in INV"""
    out = []
    if fu == "11": out += [("S", M, s_ + M, kap, "10"), ("P1", 4 * M, M + 10 - s_, 2, "10")]
    if fu == "10":
        if 8 <= s_ <= M - 14: out.append(("P1", 4 * M, M + 10 - s_, 2, "11"))
        if M + 24 <= s_ <= 2 * M: out += [("P1S", 4 * M, 5 * M + 10 - s_, 2, "10"), ("P3", 32 * M, 9 * M + 32 - s_, 8, "10")]
    return out
def G_options(N, k, kap):
    """all large-gap tuples G (g_i >= 7, k of them) with c = M/4 + kappa, i.e. Gamma = 2^(N-3) - 2^k - 2 - kappa/2"""
    R_ = N - k - 1
    if R_ < 1: return []
    Gam = 2 ** (N - 3) - 2 ** k - 2 - kap // 2; E = Gam - 6 * (2 ** k - 1)
    if E < 0: return []
    if k == 0: return [()] if E == 0 else []
    out = []
    def rec(i, E, acc):          # choose g_i - 7 for i = k..2 (weight 2^(i-1)); g_1 takes the rest
        if i == 1: out.append(tuple([7 + E] + acc)); return
        w = 2 ** (i - 1)
        for x in range(E // w + 1): rec(i - 1, E - x * w, [7 + x] + acc)
    rec(k, E, []); return out
def parse_record(R_side):
    """right side at a normal-mode all-ones record -> (a, G, r, H, f, e) or None (family X, proof.md 1.3)"""
    m = re.match(r"((?:001)*)00", R_side)
    if not m: return None
    fam = parse_family(len(m.group(1)) // 3, R_side[m.end():])
    if fam is None: return None
    a, G, r, H, (mm, bits) = fam
    return a, G, r, H, bits[:15], bits[15:]
def _path_job(job):
    """worker: one path by direct simulation from X(a; G; r, H; f, e) in INV -> (ok, path or reason, steps)"""
    a, G, r, H, f, e = job
    M, s_, kap, fu = quantities(a, G, r + H, f)
    t = CTape(X_word(a, G, r, H, f, e)); rc = t.run(1 << 62, 1)
    if rc != 2: return (False, "rc%d" % rc, t.steps)
    L, Rs = t.split()
    if L != "": return (False, "not normal mode", t.steps)
    y = parse_record(Rs)
    if y is None: return (False, "not in X", t.steps)
    if not INV_ok(*y[:5]): return (False, "not in INV", t.steps)
    M2, s2, k2, fu2 = quantities(y[0], y[1], y[2] + y[3], y[4])
    hit = [p[0] for p in predict_paths(fu, M, s_, kap) if p[1:] == (M2, s2, k2, fu2)]
    return (len(hit) == 1, hit[0] if len(hit) == 1 else "no predicted path %s" % ((M2, s2, k2, fu2),), t.steps)
def make_start(N, k, kap, G, fu, s_, R, tail=None, e=None):
    """X configuration in INV with the given parameters (H = y = 1 since both front units 10 and 11 have y = 1)"""
    M = 2 ** N; c = M // 4 + kap; a = s_ + c; r = N - k - 2
    f = "1" + fu + (tail if tail is not None else "".join(R.choice("01") for _ in range(12)))
    if e is None: e = "".join(R.choice("01") for _ in range(R.randint(0, 10)))
    x = (a, tuple(G), r, 1, f, e)
    assert quantities(a, G, r + 1, f)[:3] == (M, s_, kap) and INV_ok(*x[:5]), x
    return x
def _chain_job(job):
    """worker: continuous direct simulation through successive normal-mode overflows while N <= Nmax"""
    x, Nmax = job
    a, G, r, H, f, e = x
    t = CTape(X_word(*x)); seq = []
    while len(G) + 1 + r + H <= Nmax:
        M, s_, kap, fu = quantities(a, G, r + H, f)
        t.lastmv = 0; rc = t.run(1 << 62, 1)                 # lastmv = 0: the current record does not count
        if rc != 2: return (False, seq + ["rc%d" % rc], t.steps)
        L, Rs = t.split(); y = parse_record(Rs)
        if L != "" or y is None or not INV_ok(*y[:5]): return (False, seq + ["left INV"], t.steps)
        M2, s2, k2, fu2 = quantities(y[0], y[1], y[2] + y[3], y[4])
        hit = [p[0] for p in predict_paths(fu, M, s_, kap) if p[1:] == (M2, s2, k2, fu2)]
        if len(hit) != 1: return (False, seq + ["unpredicted"], t.steps)
        seq.append(hit[0]); a, G, r, H, f, e = y
    return (True, seq, t.steps)
def check_direct(R, quick):
    out = {}; bad = []
    def run(tag, jobs, fn=_path_job, chunk=4):
        res = pmap(fn, jobs, chunk); tal = {}; ok = 0; steps = 0
        for job, rr in zip(jobs, res):
            ok += rr[0]; steps += rr[2]
            if fn is _path_job: key = "%s:%s" % (job[4][1:3], rr[1] if rr[0] else "BAD")
            else: key = " ".join(rr[1]) if rr[0] else "BAD"
            tal[key] = tal.get(key, 0) + 1
            if not rr[0] and len(bad) < 4: bad.append((tag, job if fn is _path_job else job[0], rr[1]))
        out[tag] = (ok, len(jobs), tal, steps)
        return res
    # (a) N = 8 exhaustive: all (k, kappa, G) and every s of the three intervals (quick: every 8th s)
    jobs = []
    N = 8; M = 2 ** N
    for k in range(N):
        for kap in (2, 8):
            for G in G_options(N, k, kap):
                for fu, lo, hi in (("11", 24, M // 4 + 2), ("10", 8, M - 14), ("10", M + 24, 2 * M)):
                    for s_ in range(lo, hi + 1, 8 if quick else 1):
                        tail = "1" * 12 if (fu == "11" and R.random() < 0.05) else None
                        jobs.append(make_start(N, k, kap, G, fu, s_, R, tail))
    run("N=8 exhaustive" if not quick else "N=8 every 8th s", jobs)
    # (b) random N = 9..12: end points, random G (random / max / min spread), special tails
    SPECIAL = {"11": "1" * 12, "10": "1" * 12}      # f = 1^15 (ENDCARRY at the NO), 110111111111111 (ENDCARRY at PO#2)
    plan = [(9, 30), (10, 16), (11, 8), (12, 4)] if quick else [(9, 100), (10, 50), (11, 16), (12, 6)]
    jobs = []
    for N, cnt in plan:
        M = 2 ** N; feas = [(k, kap) for k in range(N) for kap in (2, 8) if G_options(N, k, kap)]
        for _ in range(cnt):
            k, kap = R.choice(feas); Gs = G_options(N, k, kap)
            G = R.choice([Gs[0], Gs[-1], R.choice(Gs)])
            fu, lo, hi = R.choice([("11", 24, M // 4 + 2), ("10", 8, M - 14), ("10", M + 24, 2 * M)])
            s_ = R.choice([lo, hi, lo + 1, hi - 1, R.randint(lo, hi), R.randint(lo, hi)])
            tail = e = None
            if R.random() < 0.35:
                tail = SPECIAL[fu]; m = R.randint(0, 12); e = "".join(R.choice("01") for _ in range(m))
                if R.random() < 0.4:
                    j = R.randint(0, m); e = "1" * j + ("0" + e[j + 1:] if j < m else "")
            jobs.append(make_start(N, k, kap, G, fu, s_, R, tail, e))
    run("random N=9..12", jobs)
    # (c) ENDCARRY at PO#2: front unit 10 with the preimage tail 110111111111111 (after e0, e1 it is 1^15), s in the
    #     upper interval, every j parity incl. growth
    f = "110111111111111"                                    # T(f) table: two N overflows take it to 1^15
    for _ in range(2):
        r_, _k = win("", "C>", fill(FIXED, f)); f = tf_facts(f, r_[1], r_.split()[1])[1]
    if f != "1" * 15: bad.append(("ENDCARRY preimage", f))
    jobs = []
    for N in ((8,) if quick else (8, 9)):
        M = 2 ** N; feas = [(k, kap) for k in range(N) for kap in (2, 8) if G_options(N, k, kap)]
        for e in ["", "0", "1", "11", "111", "10", "110", "1110", "0101", "11111", "111110", "1111111"]:
            for s_ in (M + 24, 2 * M, R.randint(M + 24, 2 * M)):
                k, kap = R.choice(feas); G = R.choice(G_options(N, k, kap))
                jobs.append(make_start(N, k, kap, G, "10", s_, R, "111111111111", e))
    run("ENDCARRY at PO#2", jobs)
    # (d) continuous chains from N = 8 (one simulation through several paths, N <= Nmax)
    cj = []
    for _ in range(2 if quick else 6):
        N = 8; M = 2 ** N; feas = [(k, kap) for k in range(N) for kap in (2, 8) if G_options(N, k, kap)]
        k, kap = R.choice(feas); G = R.choice(G_options(N, k, kap))
        fu, lo, hi = R.choice([("11", 24, M // 4 + 2), ("10", 8, M - 14), ("10", M + 24, 2 * M)])
        cj.append((make_start(N, k, kap, G, fu, R.randint(lo, hi), R), 11 if quick else 12))
    run("chains", cj, _chain_job, 1)
    # (e) the real orbit: blank tape, the first normal-mode all-ones records in X with INV and the paths between them
    t = CTape(""); recs = []; okr = True
    while len(recs) < 4 and t.steps < 3 * 10 ** 8:
        rc = t.run(3 * 10 ** 8, 1)
        if rc != 2: okr = False; break
        L, Rs = t.split(); y = parse_record(Rs)
        if L == "" and y is not None and INV_ok(*y[:5]): recs.append((t.steps, y))
        elif recs: okr = False; break                      # after the first INV record every record must be in INV
        t.lastmv = 0                                         # move on past this record
    paths = []
    for (s1, x), (s2, y) in zip(recs, recs[1:]):
        M, s_, kap, fu = quantities(x[0], x[1], x[2] + x[3], x[4]); M2, s2_, k2, fu2 = quantities(y[0], y[1], y[2] + y[3], y[4])
        hit = [p[0] for p in predict_paths(fu, M, s_, kap) if p[1:] == (M2, s2_, k2, fu2)]
        paths.append(hit[0] if len(hit) == 1 else "BAD")
    okr = okr and [s for s, _ in recs] == [92299, 1006516, 14514329, 114113163] and paths == ["P1", "P1", "S"]
    out["real orbit"] = (int(okr), 1, {"records": [s for s, _ in recs], "paths": paths}, recs[-1][0] if recs else 0)
    ok = all(v[0] == v[1] for v in out.values()) and not bad
    report("10 direct", ok, "; ".join("%s %d/%d %s (%.3g steps)" % (k, v[0], v[1], json.dumps(v[2]), v[3])
                                      for k, v in out.items()) + ("" if not bad else "  BAD %s" % bad))

# ----------------------------------------------------------------------------------------------- main
if __name__ == "__main__":
    ap = argparse.ArgumentParser(); ap.add_argument("--seed", type=int, default=8); ap.add_argument("--quick", action="store_true")
    ap.add_argument("--only", default="")
    ap.add_argument("--jobs", type=int, default=2)
    A = ap.parse_args(); R = random.Random(A.seed)
    if A.jobs > 1:
        import multiprocessing
        POOL = multiprocessing.get_context("fork").Pool(min(A.jobs, 2))
    only = set(A.only.split(",")) if A.only else None
    def want(k): return only is None or k in only
    if want("1"): check_table()
    if want("2"): check_windows()
    if want("3"): check_inc(R, A.quick)
    if want("4"): check_pause(R, A.quick)
    TF = check_tf() if (want("5") or want("6")) else None
    if want("6"): check_overflow(R, TF, A.quick)
    if want("7"): check_endcarry(R, A.quick)
    if want("8"): check_init(A.quick)
    if want("9"): check_macro(R, A.quick)
    if want("10"): check_direct(R, A.quick)
    nf = sum(1 for _, ok in RESULTS if not ok)
    print("ALL PASS" if nf == 0 else "%d FAIL" % nf)
    sys.exit(0 if nf == 0 else 1)