-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtests.py
More file actions
5106 lines (4569 loc) · 270 KB
/
Copy pathtests.py
File metadata and controls
5106 lines (4569 loc) · 270 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
#!/usr/bin/env python3
# https://github.com/petersm3/roae
# Developed with AI assistance (Claude, Anthropic)
"""Regression harness for the Python instrument layer (solve.py, roae.py, sat.py,
and the records path of the independent verifier verify.py).
One command: python3 tests.py
Covers the invariants that protect the two-language ground truth against future
edits — complementing solve.c's --selftest (which anchors the enumerator) and the
per-tool gates (--registry-verify, --f4p-verify) by running them all plus
helper-level checks in a single pass. Stdlib only."""
import subprocess, sys, unittest, importlib.util, itertools
import gzip # gz-framing equivalence fixture (V2-F48 #4)
import os, random, re, shutil, struct, tempfile, hashlib
def _load(name):
spec = importlib.util.spec_from_file_location(name, name + ".py")
m = importlib.util.module_from_spec(spec)
argv, sys.argv = sys.argv, [name + ".py"]
try:
spec.loader.exec_module(m)
finally:
sys.argv = argv
return m
def _emit_token(key, value):
"""Print one `KEY=value` verdict line that OWNS its line.
C4 (2026-09-02), MEASURED not reasoned: unittest writes each test's name to
stderr WITHOUT a trailing newline, so under the harness's own
`python3 tests.py 2>&1` a bare `print("KEY=1")` can be appended to that
progress line — `PERM_NCYC_P2=0.30386238` was observed glued to the end of
`test_c3_and_c5_contribute_no_clauses_and_the_header_says_so ... `. The line
still CONTAINS the token, so every substring grep stays green while
`grep -qx` — the whole-line form this project requires — silently never
matches. The leading newline is what makes the whole-line assertion true;
the flushes keep the two streams from re-interleaving on the next write."""
sys.stderr.flush()
sys.stdout.write(f"\n{key}={value}\n")
sys.stdout.flush()
solve = _load("solve")
roae = _load("roae")
sat = _load("sat")
KW = list(solve.binary_hexagrams)
class TestSequenceGround(unittest.TestCase):
def test_kw_is_permutation(self):
self.assertEqual(sorted(KW), list(range(64)))
def test_kw_satisfies_c1(self):
rev6 = lambda h: int(format(h, "06b")[::-1], 2)
for i in range(32):
a, b = KW[2 * i], KW[2 * i + 1]
self.assertTrue(b == rev6(a) or (rev6(a) == a and b == a ^ 63),
f"pair {i + 1}: {a},{b}")
def test_kw_c2_no_five(self):
d = [bin(KW[i] ^ KW[i + 1]).count("1") for i in range(63)]
self.assertNotIn(5, d)
def test_kw_c4_start(self):
self.assertEqual(KW[:2], [0b111111, 0b000000])
def test_kw_c5_multiset(self):
d = [bin(KW[i] ^ KW[i + 1]).count("1") for i in range(63)]
self.assertEqual({k: d.count(k) for k in sorted(set(d))},
{1: 2, 2: 20, 3: 13, 4: 19, 6: 9})
def test_kw_c3_total_776(self):
pos = {h: i for i, h in enumerate(KW)}
self.assertEqual(sum(abs(pos[h] - pos[h ^ 63]) for h in range(64)), 776)
def test_pair_null_gender_le2_exact(self):
# TR-8 §2 null (b): exact P(rc4_violations <= 2) over the pair-only (C1) null.
from fractions import Fraction
self.assertEqual(solve.pair_null_gender_le2_exact(), Fraction(47, 445740))
dist = solve.pair_null_gender_distribution_exact()
self.assertEqual(sum(dist.values()), 1)
self.assertEqual(solve.rc4_violations(KW)[0], 2) # KW sits at the <=2 boundary
class TestTr8DofSampler(unittest.TestCase):
"""The TR-8 dof-matched KW-fitting-predicate sampler (solve.py --tr8-dof-sampler).
These are the pre-registration's §4.4 self-test obligations as standing regressions. They
test the INSTRUMENT; none of them is a measurement of anything, and none of the numbers
here is citable. The full instrument gate — including determinism and shard/merge
equivalence at a slightly larger scale — is `python3 solve.py --tr8-dof-selftest`."""
SEED = "TR8-TESTS-THROWAWAY"
def test_bank_integrity(self):
# B_raw is fully determined by the frozen family table; a drift in either direction is
# a bug, not a new bank. 36+64+32+63+32+15+8+64+5 = 319.
bank = solve.tr8_clause_bank()
self.assertEqual(len(bank), 319)
self.assertEqual(solve.TR8_B_RAW, 319)
for fam, n in solve._TR8_FAMILY_SIZES:
self.assertEqual(sum(1 for e in bank if e[0] == fam), n, fam)
# Bank order is the evaluation order — if these ever diverge every marginal is
# attributed to the wrong template and nothing downstream would notice.
self.assertEqual(len(solve.tr8_clause_values(KW)), len(bank))
def test_h_a_king_wen_satisfies_every_template(self):
# H-a: every clause is instantiated at King Wen's own value, so King Wen satisfies
# every predicate drawn from any subset of the bank BY CONSTRUCTION. A single failure
# is a first-order implementation finding, not a result.
v = solve.tr8_clause_values(KW)
self.assertTrue(all(v), [i for i, x in enumerate(v) if not x][:8])
def test_pair_null_draw_is_c1_preserving(self):
import random
pairs = solve.king_wen_pairs()
rng = random.Random(20260811)
for _ in range(300):
s = solve.pair_null_draw(rng, pairs)
self.assertEqual(sorted(s), list(range(64)))
for i in range(32):
a, b = s[2 * i], s[2 * i + 1]
self.assertTrue((a, b) in pairs or (b, a) in pairs)
def test_h_b_null_calibration_tail(self):
# H-b, the pre-registration's named tests.py regression: the sampler's own pair-only
# null draw generator must reproduce pair_null_gender_le2_exact() = 47/445740 within
# Poisson error, scored by the UNMODIFIED rc4_violations. At 1e5 draws the expectation
# is ~10.5 hits, so this tail check is weak on its own — which is exactly why the
# distribution check below exists beside it.
import random
rng = random.Random(20260811)
pairs = solve.king_wen_pairs()
n = 100000
hits = sum(1 for _ in range(n)
if solve.rc4_violations(solve.pair_null_draw(rng, pairs))[0] <= 2)
exp = float(solve.pair_null_gender_le2_exact()) * n
self.assertLess(abs(hits - exp), 5.0 * exp ** 0.5 + 3.0,
"observed %d, expected %.2f" % (hits, exp))
def test_h_b_violation_distribution_matches_closed_form(self):
# The strong form of H-b: the whole violation-count distribution, not just its tail.
# This is what actually proves the pool is the same null the exact DP models.
import random
rng = random.Random(7)
pairs = solve.king_wen_pairs()
n = 20000
obs = {}
for _ in range(n):
v = solve.rc4_violations(solve.pair_null_draw(rng, pairs))[0]
obs[v] = obs.get(v, 0) + 1
worst = 0.0
checked = 0
for v, p in solve.pair_null_gender_distribution_exact().items():
e = float(p) * n
if e < 25: # normal approximation is not trustworthy below this
continue
checked += 1
worst = max(worst, abs(obs.get(v, 0) - e) / e ** 0.5)
self.assertGreater(checked, 5) # vacuous if the closed form ever returns nothing
self.assertLess(worst, 5.0, "worst |z| = %.2f" % worst)
def test_clopper_pearson_matches_closed_form(self):
# x = 0 and x = n have closed forms: 1 - (alpha/2)^(1/n) and (alpha/2)^(1/n). The
# interior values are the standard published Clopper-Pearson intervals.
lo, hi = solve.tr8_clopper_pearson(0, 10)
self.assertEqual(lo, 0.0)
self.assertAlmostEqual(hi, 1 - 0.025 ** 0.1, places=9)
lo, hi = solve.tr8_clopper_pearson(10, 10)
self.assertAlmostEqual(lo, 0.025 ** 0.1, places=9)
self.assertEqual(hi, 1.0)
self.assertEqual([round(x, 4) for x in solve.tr8_clopper_pearson(5, 10)],
[0.1871, 0.8129])
self.assertEqual([round(x, 4) for x in solve.tr8_clopper_pearson(2, 20)],
[0.0123, 0.3170])
def test_median_ci_ranks_are_computed_not_hardcoded(self):
# n = 10: P(Bin<=1) = 11/1024 = 0.0107 <= 0.025 and P(Bin<=2) = 56/1024 > 0.025, so
# L = 2; P(Bin<=8) = 1013/1024 = 0.9893 >= 0.975 so U = 9 — the textbook sign-test
# interval [x(2), x(9)]. n = 1000 is the pre-registered N_pred.
self.assertEqual(solve.tr8_median_ci_ranks(10), (2, 9))
self.assertEqual(solve.tr8_median_ci_ranks(1000), (469, 532))
def test_determinism_and_shard_merge_equivalence(self):
# Identical seed root => byte-identical header and identical statistics; and running
# the pool as separate shards then merging must equal the single-process run exactly
# (hits are additive across shards because every shard scores the same ensemble).
import json, os, tempfile
kl = (4, 8)
with tempfile.TemporaryDirectory() as td:
a, b, c = (os.path.join(td, x) for x in "abc")
for d in (a, b):
solve.tr8_dof_sampler(d, seed_root=self.SEED, n_pool=1024, n_pred=25,
klist=kl, n_shards=2, calib_draws=1500, quiet=True)
self.assertEqual(open(os.path.join(a, "header.json"), "rb").read(),
open(os.path.join(b, "header.json"), "rb").read())
ra = json.load(open(os.path.join(a, "results.json"), encoding="utf-8"))
rb = json.load(open(os.path.join(b, "results.json"), encoding="utf-8"))
self.assertEqual(ra["statistics"], rb["statistics"])
for i in range(2):
solve.tr8_dof_sampler(c, seed_root=self.SEED, n_pool=1024, n_pred=25,
klist=kl, n_shards=2, shard=i, calib_draws=1500,
quiet=True)
solve.tr8_dof_merge(c, quiet=True)
rc = json.load(open(os.path.join(c, "results.json"), encoding="utf-8"))
self.assertEqual(rc["statistics"], ra["statistics"])
# Every admitted marginal lies inside the frozen band, and the admitted set is a
# subset of the raw bank.
bank = json.load(open(os.path.join(a, "bank.json"), encoding="utf-8"))
self.assertEqual(bank["b_raw"], 319)
adm = [e for e in bank["bank"] if e["admitted"]]
self.assertEqual(len(adm), bank["b_admitted"])
self.assertLessEqual(bank["b_admitted"], bank["b_raw"])
for e in adm:
self.assertTrue(0.25 <= e["marginal"] <= 0.75, e)
def test_merge_refuses_a_partial_pool(self):
# A partial pool is a different pool. Silently reporting one would be the exact
# failure mode the canonical-sha gates exist to prevent, so the merge must refuse.
import os, tempfile
with tempfile.TemporaryDirectory() as td:
d = os.path.join(td, "p")
solve.tr8_dof_sampler(d, seed_root=self.SEED, n_pool=1024, n_pred=10,
klist=(4,), n_shards=2, shard=0, calib_draws=1500,
quiet=True)
with self.assertRaises(SystemExit):
solve.tr8_dof_merge(d, quiet=True)
class TestMawangdui(unittest.TestCase):
"""Primary-source anchors for the Mawangdui corpus-control array.
Added 2026-07-05 after the array was found wrong (see incident notes /
TR errata): the original 2026-04-06 array had correct octet membership
but wrong octet order and wrong within-octet order, and no test asserted
anything beyond permutation validity. Anchors below are from Shaughnessy,
*The Origin and Early Development of the Zhou Changes* (Brill, 2022),
p. 50 + Table 11.2; concordant with Cook 2006 and Shaughnessy 1996.
RULE: any hardcoded sequence imported from a source gets anchor tests
asserting positions stated by a PRIMARY source."""
MD = list(roae.mawangdui_kw_indices)
def test_md_is_permutation(self):
self.assertEqual(sorted(self.MD), list(range(64)))
def test_md_prose_anchors(self):
# Qian 1st, Kun 33rd, Jiji (#63) 22nd, Weiji (#64) 54th (1-based)
self.assertEqual(self.MD[0], 0)
self.assertEqual(self.MD[32], 1)
self.assertEqual(self.MD[21], 62)
self.assertEqual(self.MD[53], 63)
def test_md_generation_rule(self):
# Octets by upper trigram Qian,Gen,Kan,Zhen,Kun,Dui,Li,Xun; lower
# cycles Qian,Kun,Gen,Dui,Kan,Li,Zhen,Xun with own trigram promoted
# to first (each octet opens with the pure doubled hexagram).
val = {b: i for i, b in enumerate(KW)}
upper = [0b111, 0b100, 0b010, 0b001, 0b000, 0b011, 0b101, 0b110]
lower = [0b111, 0b000, 0b100, 0b011, 0b010, 0b101, 0b001, 0b110]
gen = [val[(u << 3) | l] for u in upper
for l in [u] + [t for t in lower if t != u]]
self.assertEqual(self.MD, gen)
def test_md_single_five_line_transition_at_octet_seam(self):
# Authentic Mawangdui FAILS C2: exactly one 5-line transition,
# positions 24->25 (#48 Jing -> #51 Zhen), an octet boundary.
seq = [KW[i] for i in self.MD]
fives = [i for i in range(63)
if bin(seq[i] ^ seq[i + 1]).count("1") == 5]
self.assertEqual(fives, [23])
self.assertEqual((self.MD[23], self.MD[24]), (47, 50))
def test_md_transition_histogram(self):
# Linear (63-step) histogram per Shaughnessy-derived sequence.
seq = [KW[i] for i in self.MD]
d = [bin(seq[i] ^ seq[i + 1]).count("1") for i in range(63)]
self.assertEqual({k: d.count(k) for k in sorted(set(d))},
{1: 21, 2: 10, 3: 29, 4: 2, 5: 1})
class TestJingFang(unittest.TestCase):
"""Primary-source anchors for the Jing Fang eight-palace ORDER.
Added 2026-08-01. The palace order Qian, Zhen, Kan, Gen, Kun, Xun, Li, Dui
is hardcoded as the trigram literal at five sites — solve.py
`_f4p_jf_palace`, `books_jf1` (`heads`), `_r7_jingfang`; roae.py
`--trigrams`; solve.c `--null-historical` — and restated in decimal at two
more (`_r7_J3`, and the `--r7-verify` anchor). Nothing compared any of them
to a statement of the order outside the generators. In particular
`books_jf1`'s "64/64 cells match Nielsen Table 2" subscripts
`_BOOKS_NIELSEN_T2` **by key**, so it attests palace MEMBERSHIP and
within-palace world-stage order and is silent on the order of the palaces
themselves.
The order is load-bearing for what crosses the seven inter-palace seams:
only 1,152 of the 8! = 40,320 palace orders reproduce Jing Fang's diff-wave
multiset {1: 48, 3: 15} (`_r7_J5`), asserted below. It is NOT load-bearing
everywhere — `f4p_housedisp` is 56 for all 40,320, since the palaces are
contiguous blocks of 8 in any order. How far the Jing Fang leg of the FC-1
broken-instrument gate (`solve.py --r7-verify`) would move under a
different order is UNMEASURED: CRITIQUE §Corpus Control II prices the order
exactly at P(J2 ∧ J3 | J1) = 1/40,320 and reports Jing Fang EXTREME on 0 of
11 under the J1-conditioned null, which bounds that exposure without
settling it.
Two anchors, both external to the generators:
(1) Nielsen 2003 Table 2 (p. 3, after Hui Dong 1697-1758) prints the
palaces as four "Yang Palaces" columns Qian | Zhen | Kan | Gen then
four "Yin Palaces" columns Kun | Xun | Li | Dui. Transcribed from the
page image on 2026-07-05 in roae-private/books/nielsen_companion/
VISION_TRANSCRIPTIONS_2026_07_05.md (page_0591) — the same
primary-data record `_BOOKS_NIELSEN_T2`'s cell values come from.
(2) Within each half the order is exactly the Shuogua trigram-family
scheme: father, then three sons ranked by the position of their
single yang line; mother, then three daughters ranked by the
position of their single yin line.
`test_jf_order_from_trigram_family` derives both halves from the bit
patterns alone. The yang-half-before-yin-half grouping is NOT from
Shuogua — whose own enumeration alternates son/daughter — it is the
table's own "Yang Palaces" / "Yin Palaces" column split, i.e. anchor
(1), and is what solve.py `_r7_J2` states as a predicate.
SCOPE: this pins ROAE's order to the order Nielsen prints. It does not
settle the historical question CITATIONS.md#jingfang leaves open
("alternative orderings within the same palaces exist ... historical
certainty of the full ordering is debated").
RULE (see TestMawangdui): any hardcoded sequence imported from a source
gets anchor tests asserting positions stated by a PRIMARY source."""
# bit0 = bottom line, 1 = yang (solid); see solve.py `_r7_W` header.
TRIGRAM = {"Qian": 0b111, "Zhen": 0b001, "Kan": 0b010, "Gen": 0b100,
"Kun": 0b000, "Xun": 0b110, "Li": 0b101, "Dui": 0b011}
NIELSEN_T2_COLUMNS = ["Qian", "Zhen", "Kan", "Gen", # "Yang Palaces"
"Kun", "Xun", "Li", "Dui"] # "Yin Palaces"
@property
def order(self):
return [self.TRIGRAM[n] for n in self.NIELSEN_T2_COLUMNS]
def test_jf_order_from_trigram_family(self):
# Shuogua family scheme, derived from the bit patterns alone.
sons = sorted((t for t in range(8) if bin(t).count("1") == 1),
key=lambda t: t.bit_length())
daughters = sorted((t for t in range(8) if bin(t ^ 7).count("1") == 1),
key=lambda t: (t ^ 7).bit_length())
self.assertEqual([0b111] + sons + [0b000] + daughters, self.order)
def test_jf_generators_use_the_printed_palace_order(self):
jf = solve._r7_jingfang()
self.assertEqual(sorted(jf), list(range(64)))
# Block b of the linear sequence is palace order[b]'s world-stage orbit.
self.assertEqual(solve._r7_J1(jf), self.order)
# The F4' palace index and the R7 seniority predicate agree with it.
self.assertEqual([solve._F4P_PAL[(t << 3) | t] for t in self.order],
list(range(8)))
self.assertTrue(solve._r7_J3(jf))
def test_palace_order_is_load_bearing_for_the_diff_wave(self):
# Exhaustive over all 8! palace orders (0.5 s): the order is not free
# decoration. If this ever prints a different count, the world-stage
# orbit _r7_W changed, not the order.
W = {t: solve._r7_W(t) for t in range(8)}
n = 0
for p in itertools.permutations(self.order):
s = []
for t in p:
s += W[t]
if solve._r7_J5(s):
n += 1
self.assertEqual(n, 1152)
self.assertTrue(solve._r7_J5(solve._r7_jingfang()))
def test_nielsen_table2_key_order_is_the_printed_column_order(self):
# _BOOKS_NIELSEN_T2 is insertion-ordered (py>=3.7) and its key order
# already recorded the printed column order — but books_jf1 subscripts
# the dict and never reads that order, so nothing checked it.
self.assertEqual(list(solve._BOOKS_NIELSEN_T2), self.order)
def test_other_language_generators_carry_the_same_literal(self):
# solve.c --null-historical and roae.py --trigrams each hardcode the
# order as their own literal; their headers called this a "cross-check"
# while nothing compared them. Whitespace-insensitive fixed-string
# match, no regex. If a count below changes, a copy of the palace order
# was added or removed — anchor it here rather than relaxing the test.
lit = ",".join("0b{:03b}".format(t) for t in self.order)
for path, wrapped, n in (("solve.py", "(" + lit + ")", 3),
("roae.py", "(" + lit + ")", 1),
("solve.c", "{" + lit + "}", 1)):
with open(path) as f:
src = "".join(f.read().split())
self.assertEqual(src.count(wrapped), n, path)
class TestKnownValues(unittest.TestCase):
def test_rc4_violations(self):
n, slots = solve.rc4_violations(KW)
self.assertEqual((n, slots), (2, [25, 26]))
def test_wrap_distance_is_3(self):
self.assertEqual(bin(KW[63] ^ KW[0]).count("1"), 3)
def test_parity_switches_30(self):
p = [bin(KW[i] ^ KW[i + 1]).count("1") & 1 for i in range(63)]
self.assertEqual(sum(1 for i in range(62) if p[i] != p[i + 1]), 30)
def test_alternations_15(self):
pc = [bin(KW[2 * i]).count("1") % 2 for i in range(32)]
self.assertEqual(sum(1 for i in range(31) if pc[i] != pc[i + 1]), 15)
class TestHelpers(unittest.TestCase):
def test_trigram_split(self):
self.assertEqual(roae.lower_trigram(0b111000), 0b000)
# Unconditional by design: the previous form was guarded by
# `if hasattr(roae, "upper_trigram") else None`, so renaming or deleting
# the function would have turned a real check into a silent no-op rather
# than a failure. A test that cannot fail when its subject disappears is
# not a test. If this line ever errors on AttributeError, that is the
# correct signal.
self.assertEqual(roae.upper_trigram(0b111000), 0b111)
def test_nuclear(self):
h = 0b010111
self.assertEqual(roae.nuclear_hexagram(h) & 7, (h >> 1) & 7)
class TestGates(unittest.TestCase):
def test_roae_verify(self):
# roae.py had 29 analysis sections and NO self-verify gate, while solve.py has five.
# (29 per main()'s all_sections list / its "29 sections" banner / ROAE_PY_CLI.md;
# this comment said 37 when written 2026-08-01 — corrected on same-day re-review.)
# The load-bearing check inside is that roae.py's own King Wen table is identical to
# solve.py's — they agree, but nothing enforced it, so a drift would have silently
# diverged every roae analysis from every solve.py analysis.
r = subprocess.run([sys.executable, "roae.py", "--verify"],
capture_output=True, text=True)
self.assertIn("ROAE VERIFY: ALL", r.stdout)
self.assertEqual(r.returncode, 0)
def test_registry_verify(self):
r = subprocess.run([sys.executable, "solve.py", "--registry-verify"],
capture_output=True, text=True)
self.assertIn("ALL 31 REGISTRY CHECKS PASS", r.stdout)
# The banner and the exit contract are two conjuncts; assert both
# (solve.py documents "Returns 0 on full PASS, 1 on any mismatch").
self.assertEqual(r.returncode, 0)
def test_f4p_verify(self):
r = subprocess.run([sys.executable, "solve.py", "--f4p-verify"],
capture_output=True, text=True)
self.assertIn("F4P VERIFY: PASS", r.stdout)
self.assertEqual(r.returncode, 0)
def test_books_verify(self):
r = subprocess.run([sys.executable, "solve.py", "--books-verify"],
capture_output=True, text=True)
self.assertIn("BOOKS VERIFY: ALL 14 CLAIMS PASS", r.stdout)
self.assertEqual(r.returncode, 0)
def test_trigram_verify(self):
# Two-language check of lean/TrigramTheorems.lean (finite facts +
# KW instances); see documentation/TRIGRAM_STRUCTURE.md.
r = subprocess.run([sys.executable, "solve.py", "--trigram-verify"],
capture_output=True, text=True)
self.assertIn("TRIGRAM VERIFY: ALL 18 CLAIMS PASS", r.stdout)
self.assertEqual(r.returncode, 0)
def test_perm_verify(self):
# R3 permutation-cycle family: KW gate (13 frozen functionals) +
# Fu Xi natural-order identity free-correctness check (prereg §6c).
r = subprocess.run([sys.executable, "solve.py", "--perm-verify"],
capture_output=True, text=True)
self.assertIn("PERM VERIFY: PASS", r.stdout)
seq = ",".join(str(i) for i in range(64))
r2 = subprocess.run([sys.executable, "solve.py", "--perm-verify", seq],
capture_output=True, text=True)
# bit0=bottom identity -> pi_bot=id: ncyc=64,lcyc=1,fix=64,c2=0,ord=1,
# desc=0,sign=0 (top convention non-trivial); template indicators 0,0.
self.assertEqual(r2.stdout.strip().split(",")[:7],
["64", "1", "64", "0", "1", "0", "0"])
def test_r7_verify(self):
# R7 cross-tradition corpus-control: frozen anchors (FC-2 construction
# cross-validation; J1-J5 reproduce Jing Fang; M1-M5 + exact Mawangdui
# reconstruction; cross-application matrix a-priori cells; FC-1
# positive-control expectation at pilot N=10^4). See roae-private/
# R7_CORPUS_CONTROL_DESIGN_FROZEN_2026_07_11.md.
r = subprocess.run([sys.executable, "solve.py", "--r7-verify"],
capture_output=True, text=True)
self.assertIn("R7 VERIFY: ALL ANCHORS PASS", r.stdout)
self.assertEqual(r.returncode, 0)
def test_sat_import_assertions(self):
r = subprocess.run([sys.executable, "-c", "import sat"], capture_output=True, text=True)
self.assertEqual(r.returncode, 0, r.stderr[-300:])
def test_certify_count_absent_tools(self):
# sat.py --certify-count depends on OPTIONAL external binaries
# (d4/cpog-gen/cpog-check). With them absent it must exit gracefully
# with the clear install message (roae.py Graphviz `dot` idiom), never a
# traceback. PATH is scrubbed to an empty dir so this gate holds even
# on hosts that DO have the tools installed. The present-tools path is
# RUN-validated during the R2-c cross-check campaign (see sat.py's
# certify-count section header).
import os, tempfile
with tempfile.TemporaryDirectory() as empty:
env = dict(os.environ, PATH=empty)
r = subprocess.run([sys.executable, "sat.py", "--certify-count", "f1c5",
"--f1-pairs", "9", "--expect", "26112"],
capture_output=True, text=True, env=env)
self.assertNotEqual(r.returncode, 0)
self.assertIn("required to run --certify-count", r.stderr)
self.assertIn("The rest of sat.py works without them.", r.stderr)
self.assertNotIn("Traceback", r.stderr)
def test_witness_absent_kissat(self):
# same graceful-absence contract for --witness's kissat dependency
import os, tempfile
with tempfile.TemporaryDirectory() as empty:
env = dict(os.environ, PATH=empty)
r = subprocess.run([sys.executable, "sat.py", "--witness", "plain"],
capture_output=True, text=True, env=env)
self.assertNotEqual(r.returncode, 0)
self.assertIn("kissat is required to run --witness", r.stderr)
self.assertNotIn("Traceback", r.stderr)
def test_sat_c5_tables_derived_and_guard_rejects_common_mode(self):
# T6 (2026-09-02): sat.py's two C5 tables were hand-written literals, in breach of its own
# header rule, and the guard between them passed a common-mode +1 (Codex V2 A08 row 13 /
# A09 row 17). Pinned by verdict TOKENS (grep -qx semantics), never by output shape.
r = subprocess.run([sys.executable, "sat.py", "--c5-selfcheck"], capture_output=True, text=True)
lines = r.stdout.splitlines()
self.assertIn("C5_LITERALS_DERIVED=1", lines, r.stdout)
self.assertIn("GUARD_REJECTS_COMMON_MODE=1", lines, r.stdout)
self.assertIn("GUARD_REJECTS_NON_KW=1", lines, r.stdout)
self.assertEqual(r.returncode, 0, r.stdout + r.stderr[-300:])
# The red test again, in-process and with the reference recomputed HERE from solve primitives
# (no sat.py code path reused), so a common-mode edit to both module tables goes red even if
# the subcommand's own accounting were wrong.
from collections import Counter
tot = dict(Counter(solve.bit_diff(KW[i], KW[i + 1]) for i in range(63)))
between = dict(Counter(solve.bit_diff(KW[2 * i + 1], KW[2 * i + 2]) for i in range(31)))
self.assertEqual(sat._tot, tot)
self.assertEqual(sat.BETWEEN_MULTISET, between)
sat.c5_tables_guard(sat._tot, sat._wp, sat.BETWEEN_MULTISET) # the true tables pass
bad_tot, bad_between = dict(tot), dict(between)
bad_tot[2] += 1; bad_between[2] += 1 # +1 on BOTH at d=2
with self.assertRaises(AssertionError):
sat.c5_tables_guard(bad_tot, sat._wp, bad_between)
# and the round-trip verifier no longer shares the encoder's table (A09 row 17)
self.assertTrue(sat.verify_seq(KW)[0])
self.assertFalse(sat.verify_seq(KW[:2] + KW[4:6] + KW[2:4] + KW[6:])[0])
def test_rigidity_run_reachable_and_subcommand_token_validated(self):
# Codex V2 A08 row 18 / A09 row 20: from 2026-08-28 to 2026-09-02 the documented
# `--rigidity-cnf OUT --run` exited 1 on the stray-flag guard with nothing written, leaving a
# complete kissat + DRAT + drat-trim implementation unreachable. Now the CNF is written and,
# with kissat absent, the run leg exits with the install message -- the same graceful-absence
# contract as --witness. The kissat leg itself is not exercised here (no solver on PATH).
import os, tempfile
with tempfile.TemporaryDirectory() as empty:
out = os.path.join(empty, "rig.cnf")
env = dict(os.environ, PATH=empty)
r = subprocess.run([sys.executable, "sat.py", "--rigidity-cnf", out, "--run"],
capture_output=True, text=True, env=env)
self.assertTrue(os.path.exists(out), r.stderr[-300:])
self.assertNotIn("unrecognised flag", r.stderr)
self.assertIn("kissat is required for --rigidity-cnf --run", r.stderr)
self.assertNotIn("Traceback", r.stderr)
# --run outside --rigidity-cnf is refused, not silently dropped (the Q-309 class)
r = subprocess.run([sys.executable, "sat.py", "--emit-cnf", "plain",
os.path.join(empty, "x.cnf"), "--run"], capture_output=True, text=True)
self.assertNotEqual(r.returncode, 0)
self.assertIn("--run applies to --rigidity-cnf only", r.stderr)
self.assertFalse(os.path.exists(os.path.join(empty, "x.cnf")))
# sibling (A09 row 20, limb 2): a mistyped SUBCOMMAND is an error, not help banner + rc 0
r = subprocess.run([sys.executable, "sat.py", "--wittness", "plain"], capture_output=True, text=True)
self.assertNotEqual(r.returncode, 0)
self.assertIn("unrecognised flag(s): --wittness", r.stderr)
class TestSatC5Subset(unittest.TestCase):
"""Gate for the C5 cardinality/budget encoding + the reduced-subset
(small-n certified-count probe) instances in sat.py (TASK #225 §6.4).
Cross-checks sat.py's CNF against an INDEPENDENT reference count computed
here from solve.py primitives only (no sat.py code path reused):
* decisive: at tiny N the set of Y-assignments the CNF accepts (decided
by unit propagation over the emitted clauses — a genuine SAT decision,
Sinz counters being UP-complete once the Y/T inputs are fixed) equals
exactly the valid C1&C2&C4&C5 sequences and the reference DP count;
* pinned: the B0 budget and CNF construction at the group-closed
N in {9,13,16}. The exact |C1&C2&C4&C5| is asserted LIVE AT N=9 ONLY
(26,112, recomputed here every run). The N=13 and N=16 counts are
carried as DOCUMENTATION of the values `verify.py --recount` gates —
this class does not check them, and a reader should not infer from a
`"count"` field that it does. `verify.py --recount` reproduces
2,063,395,607,040 and 267,765,117,419,520 independently with B0
re-derived (RECOUNT_RESULT=PASS); VERIFY.md tabulates both. Their
reference DP has a ~10^7-10^9 state space, too heavy for a per-run
gate, which is why they are gated there and not here.
Python-only, stdlib-only, <1 s."""
DVAL = (1, 2, 3, 4, 6)
CLS = {1: 0, 2: 1, 3: 2, 4: 3, 6: 4}
@classmethod
def _pairs(cls):
return [(KW[2 * i], KW[2 * i + 1]) for i in range(32)]
@classmethod
def _ref_b0(cls, pl, start):
# independent port of solve.c f1c5_b0_dfs (deterministic first completion)
P = cls._pairs(); n = len(pl)
pa = [P[p][0] for p in pl]; pb = [P[p][1] for p in pl]; out = [None] * n
def dfs(mask, last, dep):
if mask == (1 << n) - 1:
return True
for i in range(n):
if (mask >> i) & 1:
continue
for o in (0, 1): # o=0: enter pair_b / exit pair_a (solve.c f1c5_b0_dfs)
f = pa[i] if o else pb[i]; s = pb[i] if o else pa[i]
if bin(last ^ f).count("1") == 5:
continue
out[dep] = cls.CLS[bin(last ^ f).count("1")]
if dfs(mask | (1 << i), s, dep + 1):
return True
return False
if not (dfs(0, start, 0)):
raise AssertionError('guard failed: dfs(0, start, 0)')
b = {d: 0 for d in cls.DVAL}
for c in out:
b[cls.DVAL[c]] += 1
return b
@classmethod
def _ref_count(cls, pl, start, b0):
from functools import lru_cache
P = cls._pairs(); n = len(pl)
trans = [[(P[p][o ^ 1], P[p][o]) for o in (0, 1)] for p in pl]
b0t = tuple(b0[d] for d in cls.DVAL)
@lru_cache(maxsize=None)
def rec(mask, last, res):
if mask == (1 << n) - 1:
return 1 if res == b0t else 0
t = 0
for i in range(n):
if (mask >> i) & 1:
continue
for f, s in trans[i]:
dd = bin(last ^ f).count("1")
if dd == 5:
continue
c = cls.CLS[dd]
if res[c] >= b0t[c]:
continue
nr = list(res); nr[c] += 1
t += rec(mask | (1 << i), s, tuple(nr))
return t
return rec(0, start, (0, 0, 0, 0, 0))
@staticmethod
def _up_ok(clauses, units):
"""Unit-propagation SAT decision: False iff the units force a conflict."""
val = {}
for l in units:
v = abs(l); s = l > 0
if val.get(v, s) != s:
return False
val[v] = s
changed = True
while changed:
changed = False
for cl in clauses:
un = []; done = False
for l in cl:
v = abs(l); w = l > 0
if v in val:
if val[v] == w:
done = True; break
else:
un.append(l)
if done:
continue
if not un:
return False
if len(un) == 1:
l = un[0]; v = abs(l); s = l > 0
if val.get(v, s) != s:
return False
if v not in val:
val[v] = s; changed = True
return True
def test_tiny_encoding_equivalence(self):
# exhaustive: CNF-accepts(arrangement) == valid(arrangement) == ref count
import itertools
P = self._pairs()
for N in (2, 3, 4):
for start in (0, 63):
pl = list(range(1, N + 1))
b0 = self._ref_b0(pl, start)
self.assertEqual(b0, sat.derive_b0(pl, start)) # port agrees with sat.py
cnf, ctx = sat.build_subset_pl(pl, start, b0)
Y = ctx["Y"]; nj = ctx["nj"]; slots = ctx["slots"]; ors = ctx["orients"]
accepted = 0; valid = 0
for perm in itertools.permutations(range(N)):
for oc in itertools.product((0, 1), repeat=N):
units = []; seq = []
for si, s in enumerate(slots):
j = perm[si] * 2 + oc[si]
for jj in range(nj):
units.append(Y[(s, jj)] if jj == j else -Y[(s, jj)])
seq += [ors[j][2], ors[j][3]]
bnd = [bin(start ^ seq[0]).count("1")] + \
[bin(seq[2 * i + 1] ^ seq[2 * i + 2]).count("1") for i in range(N - 1)]
got = {d: 0 for d in self.DVAL}; ok = len(set(seq)) == 2 * N
for bd in bnd:
if bd in got:
got[bd] += 1
else:
ok = False
is_valid = ok and got == b0
is_acc = self._up_ok(cnf.cl, units)
self.assertEqual(is_acc, is_valid,
f"N={N} start={start} perm={perm} oc={oc}")
accepted += is_acc; valid += is_valid
self.assertEqual(accepted, self._ref_count(pl, start, b0))
self.assertEqual(accepted, valid)
@staticmethod
def _count_models(clauses, nvars):
"""Exhaustive DPLL TOTAL-model counter (all variables, no projection).
Unassigned-anywhere variables contribute 2^free once the clause set
is satisfied, so this is the true #SAT count over nvars variables."""
def simplify(cls, lit):
out = []
for c in cls:
if lit in c:
continue
if -lit in c:
nc = [l for l in c if l != -lit]
if not nc:
return None # empty clause: conflict
out.append(nc)
else:
out.append(c)
return out
def rec(cls, nfree):
while True: # unit propagation
units = [c[0] for c in cls if len(c) == 1]
if not units:
break
cls = simplify(cls, units[0])
if cls is None:
return 0
nfree -= 1
if not cls:
return 1 << nfree
v = abs(cls[0][0])
pos, neg = simplify(cls, v), simplify(cls, -v)
return ((rec(pos, nfree - 1) if pos is not None else 0) +
(rec(neg, nfree - 1) if neg is not None else 0))
return rec([list(c) for c in clauses], nvars)
def test_tiny_total_model_count(self):
# #SAT-safety gate (R2 review §1e): the certified-count cross-check
# (D4/CPOG) counts TOTAL models over ALL variables — Y, T indicators,
# AND Sinz counter registers — not projections onto Y. That is safe
# only because in an exactly_k context the auxiliary variables are
# functionally determined in every model. Pin the property: exhaustive
# DPLL total-model count == walk count at N in {2,3}, both start
# values, so a future encoding change (e.g. swapping the cardinality
# encoding for a non-count-safe one) cannot silently break #SAT-safety
# before a model-counter run. NOTE the standing caveat: at_most_k
# ALONE (as used by --with-c3 / alt-le-14 / -near-) is NOT
# model-count-safe; this gate covers the exactly_k subset targets.
for N in (2, 3):
for start in (0, 63):
pl = list(range(1, N + 1))
b0 = self._ref_b0(pl, start)
cnf, _ = sat.build_subset_pl(pl, start, b0)
walks = self._ref_count(pl, start, b0)
self.assertGreater(walks, 0)
self.assertEqual(self._count_models(cnf.cl, cnf.n), walks,
f"total-model count != walk count at N={N} start={start}")
def test_subset_probe_pins(self):
# group-closed certified-count-probe instances: B0 + exact |C1&C2&C4&C5|.
# Pinned oracle values; a proof-emitting #SAT / C-binary model-count
# cross-check at these N is the intended follow-up (see R2 private note).
# N=9's exact count is recomputed live here (cheap); N=13/16 counts are
# pinned literals (their reference DP has a ~10^7-10^9 state space — too
# heavy for a per-run gate; verified once out-of-band, see the R2 note).
EXPECT = {
9: {"b0": {1: 2, 2: 5, 3: 0, 4: 2, 6: 0}, "count": 26_112, "live": True},
13: {"b0": {1: 1, 2: 6, 3: 0, 4: 6, 6: 0}, "count": 2_063_395_607_040, "live": False, "gated_by": "verify.py --recount"},
16: {"b0": {1: 1, 2: 8, 3: 1, 4: 6, 6: 0}, "count": 267_765_117_419_520, "live": False, "gated_by": "verify.py --recount"},
}
# 🔴 A DEAD LITERAL MUST BE IMPOSSIBLE TO ADD SILENTLY. A `"count"` guarded by
# `"live": False` asserts nothing, but reads exactly like a pinned oracle — this class
# carried two such values while its own docstring claimed they were matched. So a
# non-live count is only allowed if it NAMES the instrument that does gate it.
for N, exp in EXPECT.items():
if not exp["live"]:
self.assertIn("gated_by", exp,
f"N={N}: a non-live 'count' is a dead literal unless 'gated_by' "
f"names the instrument that checks it")
self.assertIsInstance(exp["gated_by"], str)
self.assertTrue(exp["gated_by"].strip(), f"N={N}: 'gated_by' must not be empty")
pl, start = sat.subset_pairlist(N)
self.assertEqual(len(pl), N)
b0 = sat.derive_b0(pl, start)
self.assertEqual(b0, exp["b0"], f"B0 mismatch at N={N}")
if exp["live"]:
self.assertEqual(self._ref_count(pl, start, b0), exp["count"], f"count N={N}")
# sanity: the emitted CNF builds and its recorded budget matches
cnf, ctx = sat.build_subset(N)
self.assertGreater(len(cnf.cl), 0)
self.assertEqual(ctx["b0"], exp["b0"])
class TestAtlasRatioPrecision(unittest.TestCase):
"""Every digit solve.py prints for a derived ratio must be a digit of the RATIONAL.
The oracle here does pure integer long division: no float, no Fraction, no Decimal. It
therefore shares no rounding code with the implementation, which is the only reason it is
an oracle rather than a second opinion. Before 2026-08-23 _atlas_ratio returned
float(Fraction(...)) and _atlas_f printed "%.17g" of it; binary64 carries ~15.95 significant
decimal digits, so the last one or two digits reconstructed the rounded double rather than
the rational. test_old_float_path_would_fail keeps that failure demonstrable -- a check that
has never been shown able to fail proves nothing.
"""
SIG = 17
@staticmethod
def _oracle(num, den, sig):
"""(mantissa_digits, exponent) of num/den, correctly rounded, integers only."""
if num == 0:
return (0, 0)
e = 0
while num >= den * 10:
den *= 10
e += 1
while num < den:
num *= 10
e -= 1
q, r = divmod(num * 10 ** (sig - 1), den)
if 2 * r > den or (2 * r == den and q & 1):
q += 1
if q >= 10 ** sig:
q //= 10
e += 1
return (q, e)
@classmethod
def _digits(cls, s, sig):
s = s.strip().lstrip("+-")
if "e" in s or "E" in s:
mant, _, ex = s.replace("E", "e").partition("e")
ex = int(ex)
else:
mant, ex = s, 0
ip, _, fp = mant.partition(".")
if ip.strip("0"):
e = len(ip.lstrip("0")) - 1 + ex
else:
e = ex - (len(fp) - len(fp.lstrip("0"))) - 1
digs = (ip + fp).lstrip("0")
return (int((digs + "0" * sig)[:sig]), e)
# (numerator, denominator) pairs. The n=31-scale entry is the project's own
# 1.3287e38 / 1.097051e39 conditional, at the magnitude it is actually published at.
CASES = [
(26112, 2 ** 20),
(1234567, 26112),
(1328700000000000000000000000000000000000,
1097051000000000000000000000000000000000),
(2 ** 130 + 1, 3 * (2 ** 130)),
(1, 3),
(7, 11),
(10 ** 38 + 7, 3 * 10 ** 38 + 11),
(1, 10 ** 25 + 3),
]
def test_published_digits_are_exact(self):
for a, b in self.CASES:
got = solve._atlas_f(solve._atlas_ratio(a, b))
self.assertEqual(self._digits(got, self.SIG),
self._oracle(a, b, self.SIG),
"%d/%d printed as %s" % (a, b, got))
def test_old_float_path_would_fail(self):
"""The regression this guards must be reachable, or the test above is decorative."""
from fractions import Fraction
bad = sum(1 for a, b in self.CASES
if self._digits("%.17g" % float(Fraction(a, b)), self.SIG)
!= self._oracle(a, b, self.SIG))
self.assertGreater(bad, 0, "the float path no longer fails; this gate is now vacuous")
def test_zero_denominator_still_nan(self):
self.assertEqual(solve._atlas_f(solve._atlas_ratio(1, 0)), "nan")
class TestMooreKwGates(unittest.TestCase):
"""F-1 (TR-2 review) KW-forced regression gates for the Moore parity and
rhythm encodings — the two rules carrying the grand-ccn4 UNSAT conflict
theorem and its minimal cores ({parity,ccn4}, {rhythm,ccn4}). UNSAT has no
witness to round-trip, so these gates pin the encodings to solve.py the
other way around: with KW pinned by unit clauses, the strict Moore clauses
must conflict at EXACTLY the solve.r11_axes-scored loci (g1 = 2 parity
violations, g2 = 2 rhythm breaks, per R11_KW_EXPECTED), and unit
propagation alone then decides UNSAT — solver-free, the rc4-kwtest /
ccn4-kwtest analogue for the Moore axes (DRAT-certified kissat runs remain
the archive-grade check when a solver is present)."""
@staticmethod
def _kw_j(s):
return next(j for j in range(sat.NJ)
if sat.ORIENTS[j][0] == s and sat.ORIENTS[j][1] == 0)
def test_kw_moore_scores_are_2_2(self):
# ground truth + the sat.py scorer wrapper agree: KW = 2 parity
# violations, 2 rhythm breaks (the values the reports claim)
self.assertEqual(solve.r11_axes(KW)[:2], [2, 2])
self.assertEqual(sat._moore_scores(KW), (2, 2))
def test_moore_kwtest_conflicts_at_exactly_2_loci_and_up_unsat(self):
g1 = solve.r11_axes(KW)[0]
cnf, Y = sat.build("moore-kwtest")
cl = set(map(tuple, cnf.cl))
loci = [s for s in sat.SLOTS
if (Y[(s, self._kw_j(s))],) in cl # KW pin unit
and (-Y[(s, self._kw_j(s))],) in cl] # parity forbid unit
self.assertEqual(len(loci), g1, f"parity conflict loci {loci}")
self.assertFalse(TestSatC5Subset._up_ok(cnf.cl, [])) # UNSAT by UP
def test_rhythm_kwtest_conflicts_at_exactly_2_loci_and_up_unsat(self):
g2 = solve.r11_axes(KW)[1]
cnf, Y = sat.build("rhythm-kwtest")
cl = set(map(tuple, cnf.cl))
loci = [s for s in range(1, 31) # KW adjacency forbidden
if (-Y[(s, self._kw_j(s))], -Y[(s + 1, self._kw_j(s + 1))]) in cl]
self.assertEqual(len(loci), g2, f"rhythm conflict loci {loci}")
self.assertFalse(TestSatC5Subset._up_ok(cnf.cl, [])) # UNSAT by UP
def test_derived_tables_match_solve_on_kw(self):
# encoder-table replica of the clause semantics reproduces solve.r11_axes
# on the KW arrangement (the tables themselves are probed out of
# solve.r11_axes at sat import, with 300 randomized endorsements there)
kw_arrangement = [(p, 0) for p in range(1, 32)]
self.assertEqual(sat._moore_predict(kw_arrangement), (2, 2))
self.assertEqual(sum(sat.MOORE_COUNTED.values()), 18)
def test_sat_c4_pins_the_oriented_form(self):
# 2026-08-01: --sat-c4 pinned hexagram 0 (Kun) at position 0 — the COMPLEMENT of
# SPECIFICATION.md C4 (s0 = 63 Qian, s1 = 0 Kun). The decisive test is that the
# pinned orientation must be one KING WEN ITSELF satisfies; the old pin excluded it.
partner = solve._sat_partner_map()
self.assertEqual(partner[63], 0) # Qian's partner is Kun
self.assertEqual((KW[0], KW[1]), (63, 0)) # C4's oriented form, from the sequence
# the unit clauses the encoder emits must be satisfied by KW's own opening
self.assertEqual(solve._sat_var(0, KW[0]), solve._sat_var(0, 63))
self.assertEqual(solve._sat_var(1, partner[63]), solve._sat_var(1, KW[1]))
def test_verify_seq_rescores_literature_rules(self):
# F-1: the decoded-witness round-trip re-scores Moore parity, Moore
# rhythm AND Schulz gender via solve.py scorers (not just C1/C2/C3/C5)
ok, c3, scores = sat.verify_seq(KW)
self.assertTrue(ok)
self.assertEqual(c3, 776)
self.assertEqual(scores, (2, 2, 2))
class TestVerifyRecordsPath(unittest.TestCase):
"""A3 (2026-08-01): guard the independent records verifier against the three
drift defects an adversarial audit found in it. verify.py is deliberately
independent of solve.py/roae.py/sat.py, so it is loaded here on its own.
The load itself exercises the new import-time table gate: verify.py refuses
to import unless PAIRS equals the partner()-derived canonical pairing, KW is
a permutation, the difference-wave multiset equals SPECIFICATION.md C5's
literal, and cd(KW) = 776. Without that gate the reference tables would be
self-verifying (all derived from the same KW literal they check against)."""
@classmethod
def setUpClass(cls):
cls.V = _load("verify")
cls.PIDX = {frozenset(p): i for i, p in enumerate(cls.V.PAIRS)}
def _encode(self, seq):
out = bytearray()
for i in range(32):
a, b = seq[2 * i], seq[2 * i + 1]
p = self.PIDX[frozenset((a, b))]
out.append((p << 2) | ((0 if self.V.PAIRS[p] == (a, b) else 1) << 1))
return bytes(out)
def _counts(self, rec):
import struct, tempfile, os
blob = b"ROAE" + struct.pack("<I", 1) + struct.pack("<Q", 1) + b"\0" * 16 + rec
fd, path = tempfile.mkstemp(suffix=".bin")
try:
os.write(fd, blob); os.close(fd)
return self.V.verify_chunk((path, 0, 1))