zeropoint-node 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/CITATION.cff +1 -1
  3. package/README.md +50 -10
  4. package/dist/legacy-BEa7tljT.js.map +1 -1
  5. package/dist/legacy-CV8oTnKM.cjs.map +1 -1
  6. package/package.json +10 -2
  7. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.breathe.ts +15 -1
  8. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.cleanup.ts +15 -1
  9. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.cmyk.ts +7 -1
  10. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.generate.matrix.ts +15 -1
  11. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.browse.ts +15 -1
  12. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.click.ts +15 -1
  13. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.hear.ts +15 -1
  14. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.heat.ts +15 -1
  15. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.pulse.ts +15 -1
  16. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.speak.ts +15 -1
  17. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.i.tap.ts +15 -1
  18. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.math.ts +14 -6
  19. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.overlay.visual.controller.ts +0 -3
  20. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.rodin.ts +15 -1
  21. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.self.ts +15 -1
  22. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.service.worker.ts +30 -7
  23. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.terminal.ts +15 -1
  24. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.test.ts +15 -1
  25. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.trinity.ts +0 -3
  26. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.vbm.path.ts +6 -2
  27. package/src/0/3/6/9/1/2/4/8/7/5/1/a432.vbm.relations.ts +15 -1
  28. package/src/0/index.ts +16 -0
  29. package/src/index.ts +4 -4
  30. package/src/kernel/import-graph.ts +58 -0
  31. package/src/multidimensional-vortex-demo.ts +1 -1
  32. package/src/quantum/superposition-execution.ts +9 -4
  33. package/src/thermo/free-energy.ts +279 -0
  34. package/src/thermo/wastewater-energy.ts +204 -0
  35. package/src/vbm-math.ts +15 -1
  36. package/src/verification/lean-bridge.ts +384 -0
  37. package/src/zero-entropy-demo.ts +15 -1
@@ -30,6 +30,32 @@
30
30
  */
31
31
 
32
32
  import { createHash } from 'node:crypto'
33
+ import { digitalRoot, throughVoid, bearingForDigit, VORTEX_SEQUENCE, VORTEX_ORBIT, VORTEX_AXIS } from '../0/index.ts'
34
+ import { angleForDigit } from '../0/3/6/9/1/2/4/8/7/5/1/a432.math.ts'
35
+ import { foldToLean, leanIsFixed } from '../kernel/import-graph.ts'
36
+ import {
37
+ GIBBS_FORMATION,
38
+ GIBBS_SPLITTING,
39
+ ENTHALPY_FORMATION,
40
+ ENTHALPY_SPLITTING,
41
+ reversiblePotentialMicrovolts,
42
+ thermoneutralPotentialMicrovolts,
43
+ roundTrip,
44
+ selfTest as thermoSelfTest,
45
+ SCALE,
46
+ ELECTRONS,
47
+ FARADAY,
48
+ reversiblePotentialExact,
49
+ thermoneutralPotentialExact,
50
+ energyFromPotential,
51
+ } from '../thermo/free-energy.ts'
52
+ import {
53
+ balanceFor,
54
+ breakEvenCod,
55
+ TYPICAL_LOADS,
56
+ TREATMENT_DEMAND_DECIJOULES_PER_LITRE,
57
+ selfTest as wastewaterSelfTest,
58
+ } from '../thermo/wastewater-energy.ts'
33
59
  import {
34
60
  zeroState,
35
61
  applyGate1,
@@ -43,6 +69,9 @@ import {
43
69
  STEANE_CODE,
44
70
  symplecticProduct,
45
71
  estimateSurfaceCodeThreshold,
72
+ executeInSuperposition,
73
+ computeInterferencePattern,
74
+ describeQuantumExecution,
46
75
  } from '../quantum/index.ts'
47
76
  import { ML_KEM_768 } from '../crypto/ml-kem.ts'
48
77
  import { sqrt } from '../0/algebra.ts'
@@ -390,6 +419,361 @@ export const SEALS: Record<string, Seal> = {
390
419
  ML_KEM_768.decapsulationKeyBytes === 2400 &&
391
420
  ML_KEM_768.ciphertextBytes === 1088,
392
421
  },
422
+ doubling_avoids_the_triad: {
423
+ basis: 'VORTEX_ORBIT and VORTEX_AXIS are pinned to computation, not trusted. gcd(2,9) = 1, so every power of 2 is a unit mod 9 and can never be a multiple of 3; 2 is a primitive root, so its orbit is all six units {1,2,4,5,7,8}; 2^6 = 64 = 1 mod 9 gives period 6, which makes six cases exhaustive rather than sampled. Reflection through the void carries {1,4,7} onto {9,6,3}, so the triad is reachable only by reflecting.',
424
+ decide: () => {
425
+ // Bound to the kernel's own constants, not retyped here. VORTEX_AXIS had
426
+ // no reader anywhere in the repository before this seal, so nothing
427
+ // checked it was the triad at all.
428
+ const TRIAD: readonly number[] = VORTEX_AXIS
429
+
430
+ // Period 6, from 2^6 = 64 = 1 (mod 9). Without this the six cases below
431
+ // would be a sample; with it they are the whole sequence.
432
+ if (digitalRoot(64) !== digitalRoot(1)) return false
433
+
434
+ // The orbit of 2, generated by doubling and folded each step.
435
+ const orbit: number[] = []
436
+ let d = 1
437
+ for (let i = 0; i < 6; i++) {
438
+ orbit.push(d)
439
+ if (TRIAD.includes(d)) return false // a power of two landed on 3, 6 or 9
440
+ d = digitalRoot(d * 2)
441
+ }
442
+ // Six distinct values, and doubling returns to the start: a full cycle.
443
+ if (new Set(orbit).size !== 6 || d !== 1) return false
444
+
445
+ // Those six are exactly the units mod 9 — the residues coprime to 9.
446
+ // Everything else mod 9 is a multiple of 3, which is the triad.
447
+ const units = [1, 2, 4, 5, 7, 8]
448
+ if (orbit.slice().sort((a, b) => a - b).join() !== units.join()) return false
449
+
450
+ // VORTEX_ORBIT must BE the computed orbit, in doubling order — the
451
+ // constant is pinned to the computation rather than trusted.
452
+ if (VORTEX_ORBIT.join() !== orbit.join()) return false
453
+
454
+ // And VORTEX_AXIS must be exactly what the orbit cannot reach: the
455
+ // complement of the orbit in 1..9, in ascending order.
456
+ const complement = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter((n) => !orbit.includes(n))
457
+ if (TRIAD.join() !== complement.join()) return false
458
+
459
+ // VORTEX_SEQUENCE reads orbit then axis, so it is not an arbitrary order.
460
+ if (VORTEX_SEQUENCE.slice(0, orbit.length).join() !== orbit.join()) return false
461
+ if (VORTEX_SEQUENCE.slice(orbit.length).join() !== TRIAD.join()) return false
462
+
463
+ // Reflection is where the triad enters: 1 -> 9, 4 -> 6, 7 -> 3.
464
+ if (throughVoid(1) !== 9 || throughVoid(4) !== 6 || throughVoid(7) !== 3) return false
465
+
466
+ // And it is an involution, so the reading is reversible, not a relabel.
467
+ for (let n = 0; n <= 9; n++) if (throughVoid(throughVoid(n)) !== n) return false
468
+
469
+ // Reflecting the orbit reaches every member of the triad.
470
+ const reflected = orbit.map(throughVoid)
471
+ return TRIAD.every((t) => reflected.includes(t))
472
+ },
473
+ },
474
+ superposition_reports_its_own_state: {
475
+ basis: "the superposition model's prose must follow its measurement, not assert simultaneity regardless. Collapse is decided by one comparison — interference against the threshold — and the description must claim 'all at once' exactly when that comparison says so. While anything is still open the sequence has not computed all at once, and the text must say that.",
476
+ decide: () => {
477
+ const execution = executeInSuperposition()
478
+ const interference = computeInterferencePattern()
479
+ const text = describeQuantumExecution()
480
+
481
+ // The reported verdict must be the comparison, not a stored string.
482
+ const collapsedByNumbers = interference.working_solution_probability > 85 / 100
483
+ const collapsedByReport = execution.system_correctness === 'collapsed_to_valid'
484
+ if (collapsedByReport !== collapsedByNumbers) return false
485
+
486
+ // The prose takes exactly one of the two branches — never both, never
487
+ // neither, which is what an unconditional closing slogan would do.
488
+ const claimsCollapse = text.includes('COLLAPSED.')
489
+ const admitsOpen = text.includes('STILL SUPERPOSED')
490
+ if (claimsCollapse === admitsOpen) return false
491
+
492
+ // And the branch it takes is the one the measurement licenses.
493
+ if (claimsCollapse !== collapsedByNumbers) return false
494
+
495
+ // When open, it must publish the shortfall rather than only naming it.
496
+ if (admitsOpen && !text.includes('NOT ALL AT ONCE')) return false
497
+ return true
498
+ },
499
+ },
500
+ merkaba_is_two_mirrored_tetrahedra: {
501
+ basis: "the residues mod 3 cut 1..9 into three triangles, each equilateral on the enneagram (120 degrees apart). Reflection through the void SWAPS {1,4,7} with {3,6,9} and fixes {2,5,8} setwise, with the void root 0 fixed throughout. So {3,6,9,0} and {1,4,7,0} are two tetrahedra sharing exactly the void and carried onto each other by the mirror — a merkaba, of which one tetrahedron is half. From 3 the four vertices are 3 with its two triangle partners and the void.",
502
+ decide: () => {
503
+ const RING = [1, 2, 3, 4, 5, 6, 7, 8, 9]
504
+ const cls = (r: number) => RING.filter((d) => d % 3 === r)
505
+ const up = cls(1) // 1,4,7
506
+ const equator = cls(2) // 2,5,8
507
+ const down = cls(0) // 3,6,9
508
+ const key = (a: readonly number[]) => [...a].sort((x, y) => x - y).join(',')
509
+
510
+ // Three classes of three, partitioning the ring.
511
+ if (up.length !== 3 || equator.length !== 3 || down.length !== 3) return false
512
+ if (key([...up, ...equator, ...down]) !== key(RING)) return false
513
+
514
+ // Each is equilateral on the enneagram: three gaps of 120 degrees.
515
+ for (const t of [up, equator, down]) {
516
+ const b = t.map(bearingForDigit).sort((x, y) => x - y)
517
+ const gaps = [b[1]! - b[0]!, b[2]! - b[1]!, 360 - (b[2]! - b[0]!)]
518
+ if (!gaps.every((g) => g === 120)) return false
519
+ }
520
+
521
+ // The mirror swaps the two tetrahedral bases and fixes the equator.
522
+ if (key(up.map(throughVoid)) !== key(down)) return false
523
+ if (key(down.map(throughVoid)) !== key(up)) return false
524
+ if (key(equator.map(throughVoid)) !== key(equator)) return false
525
+
526
+ // The void is the shared apex, and it is the mirror's fixed point.
527
+ if (throughVoid(0) !== 0) return false
528
+ const tetraDown = [...down, 0]
529
+ const tetraUp = [...up, 0]
530
+ if (tetraDown.length !== 4 || tetraUp.length !== 4) return false
531
+ const shared = tetraDown.filter((v) => tetraUp.includes(v))
532
+ if (key(shared) !== '0') return false
533
+
534
+ // The gateway triangle is the axis the kernel already declares, so this
535
+ // is the same structure the vortex constants describe, not a new one.
536
+ if (key(down) !== key(VORTEX_AXIS)) return false
537
+
538
+ // From 3: itself, its two triangle partners, and the void. Four keys.
539
+ const fromThree = [3, ...down.filter((d) => d !== 3), 0]
540
+ return fromThree.length === 4 && key(fromThree) === key(tetraDown)
541
+ },
542
+ },
543
+ agl_acts_on_the_three_triangles: {
544
+ basis: "AGL(1,Z/9) has order 54 and acts on the three triangles — the cosets of {0,3,6} in Z/9, which are the residue classes the merkaba seal uses. The action is transitive with stabiliser 18 and kernel 9, and the induced permutation group is AGL(1,Z/3) of order 6. So the count three is the index of {0,3,6}, derived from the group rather than assumed: orbit x stabiliser = 3 x 18 = 54.",
545
+ decide: () => {
546
+ const units = [1, 2, 4, 5, 7, 8] // the residues coprime to 9
547
+ const maps: ((x: number) => number)[] = []
548
+ for (const a of units) for (let b = 0; b < 9; b++) maps.push((x) => ((a * x + b) % 9 + 9) % 9)
549
+ if (maps.length !== 54) return false
550
+
551
+ // The three cosets of {0,3,6}. Written as residues, so 9 appears as 0.
552
+ const T = [[1, 4, 7], [2, 5, 8], [3, 6, 0]]
553
+ const key = (xs: readonly number[]) => [...new Set(xs)].sort((x, y) => x - y).join(',')
554
+ if (key([...T[0]!, ...T[1]!, ...T[2]!]) !== '0,1,2,3,4,5,6,7,8') return false
555
+
556
+ // The gateway triangle must be the axis the kernel declares, reduced mod 9.
557
+ if (key(T[2]!) !== key(VORTEX_AXIS.map((d) => d % 9))) return false
558
+
559
+ const index = new Map(T.map((t, i) => [key(t), i]))
560
+ const induced = new Set<string>()
561
+ let fixesAll = 0
562
+ let stabilisesFirst = 0
563
+ for (const f of maps) {
564
+ const image = T.map((t) => index.get(key(t.map(f))))
565
+ // Closure: an affine map must send a coset to a coset.
566
+ if (image.some((v) => v === undefined)) return false
567
+ const word = image.join('')
568
+ induced.add(word)
569
+ if (word === '012') fixesAll++
570
+ if (image[0] === 0) stabilisesFirst++
571
+ }
572
+
573
+ // Induced group is AGL(1,Z/3); kernel and stabiliser follow from it.
574
+ if (induced.size !== 6) return false
575
+ if (fixesAll !== 9) return false
576
+ if (stabilisesFirst !== 18) return false
577
+ // Transitive on three, so orbit x stabiliser recovers the whole group.
578
+ return 3 * stabilisesFirst === maps.length
579
+ },
580
+ },
581
+ digit_geometry_is_single_valued: {
582
+ basis: "the two independent digit geometries agree and are injective. a432.math.ts angleForDigit and the kernel's bearingForDigit are written separately — the a432 tree does not import the kernel — so this checks they place all nine digits identically and never put two digits at one bearing. angleForDigit used to map nine digits onto six angles, colliding 3 with 5, 2 with 6 and 8 with 9.",
583
+ decide: () => {
584
+ const RING = [1, 2, 3, 4, 5, 6, 7, 8, 9]
585
+
586
+ // Injective: nine digits, nine distinct bearings, all whole degrees.
587
+ const kernel = RING.map(bearingForDigit)
588
+ if (new Set(kernel).size !== RING.length) return false
589
+ if (!kernel.every((b) => Number.isInteger(b) && b >= 0 && b < 360)) return false
590
+
591
+ // The two definitions must agree digit for digit.
592
+ if (!RING.every((d) => angleForDigit(d) === bearingForDigit(d))) return false
593
+
594
+ // Evenly spaced: consecutive bearings differ by one ninth of a turn.
595
+ const sorted = [...kernel].sort((a, b) => a - b)
596
+ for (let i = 1; i < sorted.length; i++) {
597
+ if (sorted[i]! - sorted[i - 1]! !== 40) return false
598
+ }
599
+
600
+ // 9 at the top is what fixes the ring's phase; without it any rotation
601
+ // would satisfy the spacing check above.
602
+ return bearingForDigit(9) === 270 && bearingForDigit(3) === 30
603
+ },
604
+ },
605
+ free_energy_of_splitting_is_positive: {
606
+ basis: "ΔG = ΔH − TΔS for water, from tabulated standard-state values. Formation is −237 kJ/mol and splitting is +237, so splitting must be paid for; the reversible cell potential ΔG/(nF) is 1229 mV and the thermoneutral ΔH/(nF) is 1481 mV, the gap being TΔS. A split-then-burn cycle breaks even at perfect efficiency and loses otherwise — checked exhaustively over the efficiency grid, not sampled. This is the sign of ΔG, not an engineering limit.",
607
+ decide: () => {
608
+ if (thermoSelfTest().length > 0) return false
609
+
610
+ // The sign is the claim. Formation releases work; splitting costs it.
611
+ if (!(GIBBS_FORMATION < 0)) return false
612
+ if (!(GIBBS_SPLITTING > 0)) return false
613
+
614
+ // One ledger, read in two directions — so what splitting costs is
615
+ // exactly what burning returns, before any device takes its cut.
616
+ if (ENTHALPY_SPLITTING !== -ENTHALPY_FORMATION) return false
617
+ if (GIBBS_SPLITTING !== -GIBBS_FORMATION) return false
618
+
619
+ // Free energy is strictly less than enthalpy: TΔS is real and is owed.
620
+ if (!(GIBBS_SPLITTING < ENTHALPY_SPLITTING)) return false
621
+ const rev = reversiblePotentialMicrovolts()
622
+ const thermo = thermoneutralPotentialMicrovolts()
623
+ if (!(thermo > rev)) return false
624
+
625
+ // The perfect cycle breaks even EXACTLY — the boundary case, and the one
626
+ // most favourable to a closed loop.
627
+ const ideal = roundTrip(100, 100, 100)
628
+ if (ideal.net !== 0) return false
629
+
630
+ // And no cycle anywhere on the grid gains. Exhaustive over 5% steps in
631
+ // all three efficiencies: 8000 combinations, none of them sampled.
632
+ //
633
+ // The verdict is recomputed from the two energies rather than read off
634
+ // `gainsEnergy`. Trusting that flag let a mutant hardcode it to false and
635
+ // still pass — a seal must not accept the answer from the thing it is
636
+ // judging. `net` is cross-checked against its own definition for the same
637
+ // reason.
638
+ let checked = 0
639
+ for (let e = 5; e <= 100; e += 5) {
640
+ for (let m = 5; m <= 100; m += 5) {
641
+ for (let g = 5; g <= 100; g += 5) {
642
+ checked++
643
+ const r = roundTrip(e, m, g)
644
+ if (r.net !== r.outputRecovered - r.inputRequired) return false
645
+ if (r.outputRecovered > r.inputRequired) return false
646
+ if (r.gainsEnergy !== r.net > 0) return false
647
+ }
648
+ }
649
+ }
650
+ return checked === 8000
651
+ },
652
+ },
653
+ polluted_water_powers_its_own_cleaning_above_a_threshold: {
654
+ basis: "the described machine works, and the fuel is the pollution rather than the water. Clean water carries zero recoverable energy, so it can only owe the treatment demand; organic load carries 13.9 J per mg COD, and above a threshold of about 4200 mg/L the electricity generated exceeds what membrane treatment consumes. Municipal sewage sits below that line and dairy or manure effluent well above it. The threshold is found by scanning, so it is the same quantity the balance reports.",
655
+ decide: () => {
656
+ if (wastewaterSelfTest().length > 0) return false
657
+
658
+ // The water itself is not the fuel. At zero load there is nothing to
659
+ // burn, and the plant owes exactly the cost of cleaning.
660
+ const clean = balanceFor(0)
661
+ if (clean.energyInLoad !== 0) return false
662
+ if (clean.electricityGenerated !== 0) return false
663
+ if (clean.netElectricity !== -TREATMENT_DEMAND_DECIJOULES_PER_LITRE) return false
664
+
665
+ // A threshold exists and is a genuine boundary: it exports, and one
666
+ // milligram less does not.
667
+ const t = breakEvenCod()
668
+ if (!(t > 0)) return false
669
+ if (!(balanceFor(t).netElectricity > 0)) return false
670
+ if (balanceFor(t - 1).netElectricity > 0) return false
671
+
672
+ // Monotone in the load — more pollution never yields less electricity.
673
+ let previous = -1
674
+ for (let cod = 0; cod <= 20000; cod += 100) {
675
+ const e = balanceFor(cod).electricityGenerated
676
+ if (e < previous) return false
677
+ previous = e
678
+ }
679
+
680
+ // Conservation still binds: recovery is strictly below the energy the
681
+ // load contains, because capture and engine efficiency are each < 100%.
682
+ for (const { cod } of TYPICAL_LOADS) {
683
+ if (cod === 0) continue
684
+ const b = balanceFor(cod)
685
+ if (b.electricityGenerated >= b.energyInLoad) return false
686
+ }
687
+
688
+ // And the model must still place real streams where they actually fall.
689
+ const strongSewage = TYPICAL_LOADS.find((l) => l.name === 'municipal sewage, strong')
690
+ const dairy = TYPICAL_LOADS.find((l) => l.name === 'dairy processing')
691
+ if (!strongSewage || !dairy) return false
692
+ if (balanceFor(strongSewage.cod).selfPowering) return false
693
+ return balanceFor(dairy.cod).selfPowering
694
+ },
695
+ },
696
+ every_model_inverts: {
697
+ basis: "each quantitative result recomputes a second, independent way and the two agree. Entropy recovered from (ΔH − ΔG)/T must equal the tabulated ΔS exactly; ΔG recovered from the EXACT rational potential must return with zero drift, while the rounded microvolt form is separately held to half a microvolt's worth of energy; and the break-even COD found by SCANNING must equal the one obtained by INVERTING the arithmetic, which is a different computation reaching the same integer. Reflection through the void inverts too, being its own inverse.",
698
+ decide: () => {
699
+ // --- free energy: invert ΔG = ΔH − TΔS to recover the entropy --------
700
+ // The forward pass multiplied T by ΔS; dividing it back out must land on
701
+ // the tabulated figure exactly, with no rounding introduced anywhere.
702
+ const T_CENTIKELVIN = 29815
703
+ const TABULATED_ENTROPY_MILLI = -163305
704
+ const recovered =
705
+ ((ENTHALPY_FORMATION - GIBBS_FORMATION) * 1000 * 100) / (T_CENTIKELVIN * SCALE)
706
+ if (recovered !== TABULATED_ENTROPY_MILLI) return false
707
+
708
+ // --- cell potential: invert E = ΔG/(nF) to recover ΔG ----------------
709
+ // Exactly, from the rational form. This used to be a bounded check
710
+ // because the microvolt rounding made an exact inverse impossible; the
711
+ // exact potentials exist so that the demand can be equality.
712
+ if (energyFromPotential(reversiblePotentialExact()) !== GIBBS_SPLITTING) return false
713
+ if (energyFromPotential(thermoneutralPotentialExact()) !== ENTHALPY_SPLITTING) return false
714
+
715
+ // The ROUNDED form still cannot invert exactly, and must not pretend to.
716
+ // Its drift is held to half a microvolt's worth of energy — nF/20 in
717
+ // these units, a derived bound rather than an eyeballed tolerance.
718
+ const microvolts = reversiblePotentialMicrovolts()
719
+ const backToGibbs = (microvolts * ELECTRONS * FARADAY) / 10
720
+ const drift = backToGibbs - GIBBS_SPLITTING
721
+ const magnitude = drift < 0 ? -drift : drift
722
+ if (!(2 * magnitude <= (ELECTRONS * FARADAY) / 10)) return false
723
+ if (!(magnitude * 1000000 < GIBBS_SPLITTING)) return false
724
+
725
+ // --- break-even COD: scanning versus inverting ------------------------
726
+ // balanceFor rounds twice on the way up, so a closed form agreeing with
727
+ // the scan is a real check on both, not a restatement of one.
728
+ const scanned = breakEvenCod()
729
+ const numerator = TREATMENT_DEMAND_DECIJOULES_PER_LITRE * 10000
730
+ const denominator = 139 * 65 * 38
731
+ const exact = numerator / denominator
732
+ const inverted = exact % 1 === 0 ? exact + 1 : exact - (exact % 1) + 1
733
+ if (scanned !== inverted) return false
734
+
735
+ // --- reflection is its own inverse ------------------------------------
736
+ for (let d = 0; d <= 9; d++) if (throughVoid(throughVoid(d)) !== d) return false
737
+
738
+ return true
739
+ },
740
+ },
741
+ lean_is_a_fixed_point: {
742
+ basis: "LEAN is defined as the fixed point of the reachability fold, not as a number anyone chose: fold(fold(S)) = fold(S). This checks the fold on graphs whose answers are known by hand — a chain, a cycle, a disconnected node, an entry outside the graph — and checks idempotence on each. Cycles must terminate rather than reaching the answer by luck, and an unreachable node must stay out however many times the fold is applied.",
743
+ decide: () => {
744
+ const key = (s: Set<string>) => [...s].sort().join(',')
745
+
746
+ // A chain, a 2-cycle, and an isolated node, all in one graph.
747
+ const g = new Map<string, readonly string[]>([
748
+ ['a', ['b']], ['b', ['c']], ['c', []],
749
+ ['x', ['y']], ['y', ['x']],
750
+ ['z', []],
751
+ ])
752
+
753
+ // Reachability, by hand.
754
+ if (key(foldToLean(g, ['a'])) !== 'a,b,c') return false
755
+ // A cycle must terminate and include both nodes exactly once.
756
+ if (key(foldToLean(g, ['x'])) !== 'x,y') return false
757
+ // An isolated node is reachable only from itself.
758
+ if (key(foldToLean(g, ['z'])) !== 'z') return false
759
+ // Nothing is reachable from nothing.
760
+ if (foldToLean(g, []).size !== 0) return false
761
+ // An entry that is not in the graph contributes nothing rather than throwing.
762
+ if (foldToLean(g, ['absent']).size !== 0) return false
763
+ // z is outside the fold from a, and stays outside however often we fold.
764
+ if (foldToLean(g, ['a']).has('z')) return false
765
+
766
+ // The definition must be well founded on every one of these.
767
+ for (const entries of [['a'], ['x'], ['z'], [], ['a', 'x'], ['absent']]) {
768
+ if (!leanIsFixed(g, entries)) return false
769
+ }
770
+
771
+ // And on a graph where an entry reaches everything, lean is everything.
772
+ const full = new Map<string, readonly string[]>([['r', ['a', 'x', 'z']], ['a', ['b']], ['b', []], ['x', ['x']], ['z', []]])
773
+ if (key(foldToLean(full, ['r'])) !== 'a,b,r,x,z') return false
774
+ return leanIsFixed(full, ['r'])
775
+ },
776
+ },
393
777
  }
394
778
 
395
779
  // ============================================================================
@@ -9,6 +9,7 @@
9
9
  * 3. Pure mathematical relationships (no randomness or entropy)
10
10
  */
11
11
 
12
+ import { pathToFileURL } from 'node:url'
12
13
  import { min, sqrt } from './0/algebra.ts'
13
14
  import {
14
15
  ZERO_ENTROPY_CONSTANTS,
@@ -297,7 +298,20 @@ export class ZeroEntropyDemo {
297
298
  }
298
299
 
299
300
  // Run demo if this file is executed directly
300
- if (typeof require !== 'undefined' && require.main === module) {
301
+ /**
302
+ * True when this file is the entry point Node was started with.
303
+ *
304
+ * ESM has no `require.main`. The CommonJS idiom did not merely fail to detect
305
+ * direct execution here — `require` is undefined in an ES module, so the guard
306
+ * THREW on import and made the whole module unloadable. Nobody importing this
307
+ * ever got far enough to notice the guard was wrong.
308
+ */
309
+ function isMainModule(): boolean {
310
+ const entry = process.argv[1]
311
+ return entry !== undefined && import.meta.url === pathToFileURL(entry).href
312
+ }
313
+
314
+ if (isMainModule()) {
301
315
  ZeroEntropyDemo.runCompleteDemo();
302
316
  }
303
317