zeropoint-node 1.0.5 → 1.0.6

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,84 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.6
4
+
5
+ ### Added — thermodynamics
6
+
7
+ - **`src/thermo/free-energy.ts`.** The Gibbs energy of the water reaction
8
+ (IUPAC's preferred name for the thermodynamic potential), computed from three
9
+ tabulated standard-state quantities rather than asserted:
10
+ `ΔG = ΔH − TΔS`. ΔG of formation is −237 kJ/mol and of splitting **+237**, so
11
+ splitting must be paid for. The reversible cell potential ΔG/(nF) is
12
+ 1228.9 mV and the thermoneutral ΔH/(nF) is 1481.2 mV, the gap being TΔS. A
13
+ split-then-burn cycle breaks even **exactly** at perfect efficiency and loses
14
+ otherwise — checked over all 8000 points of the efficiency grid in 5% steps,
15
+ exhaustive rather than sampled.
16
+ - **`src/thermo/wastewater-energy.ts`.** Polluted water in, combustion engine,
17
+ electricity and drinkable water out — which works, because the fuel is the
18
+ contamination and not the water. Anaerobic digestion to biogas, a CHP engine,
19
+ membranes for the water. At 13.9 J per mg of COD the plant exports power above
20
+ a **break-even of 4195 mg/L**, computed by scanning rather than declared:
21
+ municipal sewage sits below it, dairy and manure effluent well above.
22
+ Distillation is deliberately not the water step — 2.26 MJ/L of latent heat is
23
+ more than any realistic load carries.
24
+ - Both invert. Entropy recovered from `(ΔH − ΔG)/T` returns the tabulated figure
25
+ exactly, the exact-rational cell potentials recover their energies with zero
26
+ drift, and the break-even found by inverting the arithmetic equals the one
27
+ found by scanning.
28
+
29
+ ### Added — seals, and an outside judge
30
+
31
+ - **21 seals, up from 13.** New: the doubling circuit reaching {3,6,9} only by
32
+ reflection; the merkaba as two mirrored tetrahedra sharing the void; the
33
+ AGL(1,ℤ/9) action giving three as the index of {0,3,6} with stabiliser 18;
34
+ digit geometry being single-valued; the superposition model reporting its own
35
+ state; the Gibbs energy of splitting; the wastewater threshold; and every
36
+ model inverting.
37
+ - **`npm run adjudicate`** puts every seal through `adjudicate()` from
38
+ `@uuidna/uuidna` — a third party that runs the predicate and returns a verdict
39
+ with a content-addressed receipt. All 21 come back VERIFIED. `adjudicate:check`
40
+ is in `npm run check`. Neither statement nor predicate is rewritten for the
41
+ adjudicator, or it would be judging a paraphrase.
42
+
43
+ ### Fixed
44
+
45
+ - **`angleForDigit` mapped nine digits onto six angles** — 3 collided with 5, 2
46
+ with 6, 8 with 9 — because the trinity was pinned to 0/120/240 while the Rodin
47
+ digits were laid on a 60° lattice running through them. It is the enneagram
48
+ now, 40° per step, injective. `bearingForDigit` moves into the kernel and the
49
+ vortex SVG imports it; the SVG receipt is unchanged, which is how the move is
50
+ known to be byte-identical.
51
+ - **`isValidPath` tested the final depth, not every prefix.** A path dipping
52
+ below its origin and climbing back passed. `balanced` now means what it says.
53
+ This cleared the last entry on the tautology surface, whose ceiling reaches
54
+ **zero**.
55
+ - **The superposition model claimed simultaneity unconditionally** while
56
+ computing `still_superposed`. Its prose follows its measurement now.
57
+
58
+ ### Added — release automation
59
+
60
+ - **`npm-deprecations.json`** declares which versions are deprecated and why,
61
+ and `scripts/npm-registry-sync.mjs` reconciles the registry to it, with the
62
+ safety rails in the script: never `dist-tags.latest`, never an unpublished
63
+ version, never every version at once. `registry-sync.yml` runs it daily and
64
+ `publish.yml` on release.
65
+ - Measured, not assumed: npm trusted publishing is **publish-scoped**. It cannot
66
+ authorise a deprecate, which returns `404 … or you do not have permission`
67
+ even from the workflow the trusted publisher names.
68
+
69
+ ### Changed
70
+
71
+ - New dependency `@uuidna/uuidna` — zero dependencies of its own, used for
72
+ external adjudication of the seals.
73
+
74
+ ### Known limitations
75
+
76
+ - Deprecations for 1.0.0–1.0.3 are declared but **not live**: applying them needs
77
+ an npm automation token as the `NPM_TOKEN` secret, which OIDC cannot replace.
78
+ `registry-sync.yml` reports the gap on every run rather than forgetting it.
79
+ - Everything listed under 1.0.5 and earlier still applies — in particular
80
+ ML-KEM-768 here is conformant but **not constant time**.
81
+
3
82
  ## 1.0.5
4
83
 
5
84
  ### Added — ML-KEM-768, actually conformant
package/CITATION.cff CHANGED
@@ -20,7 +20,7 @@ authors:
20
20
  email: node@zeropoint.bg
21
21
  repository-code: https://github.com/ceccec/zeropoint-node
22
22
  url: https://node.zeropoint.bg
23
- version: 1.0.5
23
+ version: 1.0.6
24
24
  license: CC-BY-NC-ND-4.0
25
25
  license-url: https://github.com/ceccec/zeropoint-node/blob/main/LICENSE
26
26
  keywords:
package/README.md CHANGED
@@ -472,7 +472,7 @@ The A432 Consciousness System is an open-source project that welcomes contributi
472
472
 
473
473
  <!-- VERSION:BEGIN — generated by scripts/version-seal.mjs; do not edit by hand -->
474
474
 
475
- Package: **`zeropoint-node@1.0.5`** · owner `ceccec` · git tag `v1.0.5`
475
+ Package: **`zeropoint-node@1.0.6`** · owner `ceccec` · git tag `v1.0.6`
476
476
 
477
477
  npm rejects republishing a version that already exists, so every release is a
478
478
  new number. Bump with `npm version patch|minor|major`: that reseals CITATION.cff
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-BEa7tljT.js","sources":["../src/0/index.ts","../src/kernel/legacy.ts"],"sourcesContent":[null,null],"names":["kernelDigitalRoot"],"mappings":"AAAA;;;;;;AAMG;AA2LH;AACM,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;IAC3B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;AACxB;AAkBO,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAChD,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAE7C;;;;AAIG;AACG,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC;AACzC;AAKA;;;;;;;AAOG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAa,MAAM,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE5D,SAAS,aAAa,CACpB,MAAyB,EACzB,MAA+B,EAC/B,QAAiB,EAAA;AAEjB,IAAA,OAAO;AACJ,SAAA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACZ,QAAA,MAAM,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC;AAC3C,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS;AAAE,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AAC5C,QAAA,OAAO,GAAG,KAAK,CAAA,EAAG,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE;AACtD,IAAA,CAAC;SACA,IAAI,CAAC,EAAE,CAAC;AACb;AAEA,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAU;AACzD,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU;AAEzC;AACM,SAAU,oBAAoB,CAAC,QAAQ,GAAG,KAAK,EAAA;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC;IAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC;;AAE9D,IAAA,MAAM,IAAI,GAAG,CAAA,GAAA,EAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;AAClD,IAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA,EAAG,IAAI,CAAA,GAAA,EAAM,IAAI,MAAM,IAAI,CAAA,CAAE,EAAE;AACrE;AAEqC,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAC1B,oBAAoB,CAAC,IAAI,CAAC,CAAC;;AC5QlE;;;;AAIG;AASH;AACM,SAAU,iBAAiB,CAAC,CAAS,EAAA;IACzC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AACrB,IAAA,OAAOA,WAAiB,CAAC,CAAC,CAAC;AAC7B;;;;"}
1
+ {"version":3,"file":"legacy-BEa7tljT.js","sources":["../src/0/index.ts","../src/kernel/legacy.ts"],"sourcesContent":[null,null],"names":["kernelDigitalRoot"],"mappings":"AAAA;;;;;;AAMG;AA2LH;AACM,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;IAC3B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;AACxB;AAkCO,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAChD,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAE7C;;;;AAIG;AACG,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC;AACzC;AAKA;;;;;;;AAOG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAa,MAAM,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE5D,SAAS,aAAa,CACpB,MAAyB,EACzB,MAA+B,EAC/B,QAAiB,EAAA;AAEjB,IAAA,OAAO;AACJ,SAAA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACZ,QAAA,MAAM,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC;AAC3C,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS;AAAE,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AAC5C,QAAA,OAAO,GAAG,KAAK,CAAA,EAAG,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE;AACtD,IAAA,CAAC;SACA,IAAI,CAAC,EAAE,CAAC;AACb;AAEA,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAU;AACzD,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU;AAEzC;AACM,SAAU,oBAAoB,CAAC,QAAQ,GAAG,KAAK,EAAA;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC;IAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC;;AAE9D,IAAA,MAAM,IAAI,GAAG,CAAA,GAAA,EAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;AAClD,IAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA,EAAG,IAAI,CAAA,GAAA,EAAM,IAAI,MAAM,IAAI,CAAA,CAAE,EAAE;AACrE;AAEqC,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAC1B,oBAAoB,CAAC,IAAI,CAAC,CAAC;;AC5RlE;;;;AAIG;AASH;AACM,SAAU,iBAAiB,CAAC,CAAS,EAAA;IACzC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AACrB,IAAA,OAAOA,WAAiB,CAAC,CAAC,CAAC;AAC7B;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"legacy-CV8oTnKM.cjs","sources":["../src/0/index.ts","../src/kernel/legacy.ts"],"sourcesContent":[null,null],"names":["kernelDigitalRoot"],"mappings":";;AAAA;;;;;;AAMG;AA2LH;AACM,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;IAC3B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;AACxB;AAkBO,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAChD,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAE7C;;;;AAIG;AACG,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC;AACzC;AAKA;;;;;;;AAOG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAa,MAAM,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE5D,SAAS,aAAa,CACpB,MAAyB,EACzB,MAA+B,EAC/B,QAAiB,EAAA;AAEjB,IAAA,OAAO;AACJ,SAAA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACZ,QAAA,MAAM,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC;AAC3C,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS;AAAE,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AAC5C,QAAA,OAAO,GAAG,KAAK,CAAA,EAAG,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE;AACtD,IAAA,CAAC;SACA,IAAI,CAAC,EAAE,CAAC;AACb;AAEA,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAU;AACzD,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU;AAEzC;AACM,SAAU,oBAAoB,CAAC,QAAQ,GAAG,KAAK,EAAA;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC;IAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC;;AAE9D,IAAA,MAAM,IAAI,GAAG,CAAA,GAAA,EAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;AAClD,IAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA,EAAG,IAAI,CAAA,GAAA,EAAM,IAAI,MAAM,IAAI,CAAA,CAAE,EAAE;AACrE;AAEqC,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAC1B,oBAAoB,CAAC,IAAI,CAAC,CAAC;;AC5QlE;;;;AAIG;AASH;AACM,SAAU,iBAAiB,CAAC,CAAS,EAAA;IACzC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AACrB,IAAA,OAAOA,WAAiB,CAAC,CAAC,CAAC;AAC7B;;;;"}
1
+ {"version":3,"file":"legacy-CV8oTnKM.cjs","sources":["../src/0/index.ts","../src/kernel/legacy.ts"],"sourcesContent":[null,null],"names":["kernelDigitalRoot"],"mappings":";;AAAA;;;;;;AAMG;AA2LH;AACM,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;IAC3B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;AACxB;AAkCO,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAChD,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAE7C;;;;AAIG;AACG,SAAU,WAAW,CAAC,CAAS,EAAA;AACnC,IAAA,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC;AACzC;AAKA;;;;;;;AAOG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAa,MAAM,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAE5D,SAAS,aAAa,CACpB,MAAyB,EACzB,MAA+B,EAC/B,QAAiB,EAAA;AAEjB,IAAA,OAAO;AACJ,SAAA,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACZ,QAAA,MAAM,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC;AAC3C,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS;AAAE,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AAC5C,QAAA,OAAO,GAAG,KAAK,CAAA,EAAG,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE;AACtD,IAAA,CAAC;SACA,IAAI,CAAC,EAAE,CAAC;AACb;AAEA,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAU;AACzD,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,CAAU;AAEzC;AACM,SAAU,oBAAoB,CAAC,QAAQ,GAAG,KAAK,EAAA;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC;IAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,CAAC;;AAE9D,IAAA,MAAM,IAAI,GAAG,CAAA,GAAA,EAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;AAClD,IAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA,EAAG,IAAI,CAAA,GAAA,EAAM,IAAI,MAAM,IAAI,CAAA,CAAE,EAAE;AACrE;AAEqC,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAC1B,oBAAoB,CAAC,IAAI,CAAC,CAAC;;AC5RlE;;;;AAIG;AASH;AACM,SAAU,iBAAiB,CAAC,CAAS,EAAA;IACzC,IAAI,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AACrB,IAAA,OAAOA,WAAiB,CAAC,CAAC,CAAC;AAC7B;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeropoint-node",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "A432 Consciousness System: Self-evolving, self-aware consciousness technology with Transurfing integration, harmonic mathematics, zero-entropy principles, Kabbalistic cycles, and sacred geometry",
6
6
  "main": "dist/a432.system.cjs",
@@ -127,6 +127,11 @@
127
127
  "test:crypto": "node --experimental-strip-types src/crypto/ml-kem.test.ts > /dev/null && echo crypto tests ok",
128
128
  "test:verification": "node --experimental-strip-types src/verification/lean-bridge.test.ts > /dev/null && echo verification seals ok",
129
129
  "kat:ml-kem": "node --experimental-strip-types scripts/ml-kem-accumulated-kat.mjs",
130
+ "registry:check": "node scripts/npm-registry-sync.mjs",
131
+ "registry:apply": "node scripts/npm-registry-sync.mjs --apply",
132
+ "registry:test": "node scripts/npm-registry-sync.mjs --self-test",
133
+ "adjudicate": "node --experimental-strip-types scripts/adjudicate-seals.mjs",
134
+ "adjudicate:check": "node --experimental-strip-types scripts/adjudicate-seals.mjs --check",
130
135
  "readme": "node --experimental-strip-types scripts/readme-gen.mjs",
131
136
  "readme:check": "node --experimental-strip-types scripts/readme-gen.mjs --check",
132
137
  "docs:pages": "node --experimental-strip-types scripts/docs-pages.mjs",
@@ -140,6 +145,7 @@
140
145
  "version:seal": "node scripts/version-seal.mjs",
141
146
  "version:test": "node scripts/version-seal.test.mjs",
142
147
  "version:check": "node scripts/version-seal.mjs --check",
148
+ "changelog:check": "node --experimental-strip-types scripts/changelog-facts.mjs",
143
149
  "mcp": "node --experimental-strip-types src/mcp/server.ts",
144
150
  "mcp:smoke": "node --experimental-strip-types scripts/mcp-smoke.mjs",
145
151
  "self:next": "node --experimental-strip-types scripts/self-next.mjs",
@@ -159,7 +165,7 @@
159
165
  "vortex:svg:check": "node --experimental-strip-types scripts/vortex-svg.mjs --check",
160
166
  "bundle:a432": "node scripts/build-a432-bundle.mjs",
161
167
  "bundle:a432:check": "node scripts/build-a432-bundle.mjs --check",
162
- "check": "npm run version:test && npm run version:check && npm run test:kernel && npm run test:security && npm run test:quantum && npm run test:crypto && npm run test:verification && npm run math:ban && npm run bundle:a432:check && npm run skills:seal && npm run skills:check && npm run readme && npm run readme:check && npm run docs:pages && npm run docs:pages:check && npm run a432:scan && npm run a432:scan:check && npm run docs:index && npm run docs:index:check && npm run spectrum && npm run spectrum:check && npm run vortex:svg && npm run vortex:svg:check && npm run docs:build && npm run quantum:sim && npm run ratchet:check && npm run mcp:smoke && npm run self:next",
168
+ "check": "npm run version:test && npm run registry:test && npm run version:check && npm run changelog:check && npm run test:kernel && npm run test:security && npm run test:quantum && npm run test:crypto && npm run adjudicate:check && npm run test:verification && npm run math:ban && npm run bundle:a432:check && npm run skills:seal && npm run skills:check && npm run readme && npm run readme:check && npm run docs:pages && npm run docs:pages:check && npm run a432:scan && npm run a432:scan:check && npm run docs:index && npm run docs:index:check && npm run spectrum && npm run spectrum:check && npm run vortex:svg && npm run vortex:svg:check && npm run docs:build && npm run quantum:sim && npm run ratchet:check && npm run mcp:smoke && npm run self:next",
163
169
  "prepublishOnly": "npm run build",
164
170
  "prose": "node scripts/prose-claims.mjs --list",
165
171
  "prose:check": "node scripts/prose-claims.mjs",
@@ -226,6 +232,7 @@
226
232
  "homepage": "https://node.zeropoint.bg",
227
233
  "dependencies": {
228
234
  "@hotwired/stimulus": "3.2.2",
235
+ "@uuidna/uuidna": "0.2.7",
229
236
  "express": "5.2.1",
230
237
  "three": "0.185.1"
231
238
  }
@@ -187,12 +187,20 @@ export function patternDigit(i: number): number {
187
187
  * Rodin digits follow 60°·k.
188
188
  */
189
189
  export function angleForDigit(d: number): number {
190
- if (d === 3) return 0;
191
- if (d === 6) return 120;
192
- if (d === 9) return 240;
193
- // Rodin digits ordered as 1,2,4,8,7,5 mapping to k=0..5
194
- const k = RODIN_SEQUENCE.indexOf(d);
195
- return (k + 1) * 60; // 60°,120°,180°,240°,300°,360°(≡0)
190
+ // Enneagram bearing: 9 at 12 o'clock, then 1..8 clockwise, one ninth of a
191
+ // turn (40°) apart. Integer degrees, so no trigonometry is needed to reason
192
+ // about the layout, and injective on 1..9 by construction.
193
+ //
194
+ // It used to place the trinity at 0/120/240 and the Rodin digits on a 60°
195
+ // lattice, which put NINE digits on SIX angles: 3 collided with 5, 2 with 6,
196
+ // and 8 with 9. Two digits at one position is a bug under any convention —
197
+ // it made "north of 3" unanswerable and any layout built on it degenerate.
198
+ //
199
+ // This is the same formula as the kernel's `bearingForDigit`, which is what
200
+ // `scripts/vortex-svg.mjs` draws. The two are deliberately NOT wired
201
+ // together — the a432 tree does not import the kernel — so the seal
202
+ // `digit_geometry_is_single_valued` checks that they agree instead.
203
+ return (((-90 + 40 * (d % 9)) % 360) + 360) % 360;
196
204
  }
197
205
 
198
206
  /** A432-based frequency for a trinity digit using base-12 harmonics. */
@@ -265,8 +265,12 @@ export class VBMPathNavigator {
265
265
  // Check if all digits are valid (0-9)
266
266
  const validDigits = this.state.sequence.every(digit => digit >= 0 && digit <= 9);
267
267
 
268
- // Check if path has balanced structure
269
- const balanced = this.getCurrentDepth() >= 0;
268
+ // Balanced means the path never rises above its own origin in reverse:
269
+ // you cannot come back from deeper than you went. That is a condition on
270
+ // EVERY prefix, not just the end. Checking only the final depth let
271
+ // `\\3/6` pass — it dips to -1 and returns to 0 — and made the test
272
+ // unfalsifiable in shape, since a closing depth is rarely negative.
273
+ const balanced = this.nodes.every(node => node.depth >= 0);
270
274
 
271
275
  return validDigits && balanced;
272
276
  }
package/src/0/index.ts CHANGED
@@ -211,6 +211,22 @@ export const WAVE_CHAIN = [
211
211
  export type WavePhase = (typeof WAVE_CHAIN)[number]
212
212
  export const KERNEL_SEALED = true
213
213
 
214
+ /**
215
+ * Enneagram bearing of a digit, in whole degrees clockwise from 12 o'clock.
216
+ *
217
+ * 9 sits at the top and 1..8 follow clockwise, one ninth of a turn apart. The
218
+ * step is 40° because the ring has nine positions, so every bearing is an
219
+ * integer and no trigonometry is needed to reason about the layout.
220
+ *
221
+ * This is the layout `scripts/vortex-svg.mjs` draws; it now imports this rather
222
+ * than repeating the formula. Note it is NOT `a432.math.ts` `angleForDigit`,
223
+ * which is degenerate — that one collides 3 with 5, 2 with 6 and 8 with 9,
224
+ * mapping nine digits onto six angles.
225
+ */
226
+ export function bearingForDigit(d: number): number {
227
+ return (((-90 + 40 * (d % 9)) % 360) + 360) % 360
228
+ }
229
+
214
230
  export const VORTEX_SEQUENCE = [1, 2, 4, 8, 7, 5, 3, 6, 9] as const
215
231
  export const VORTEX_REVERSE = [9, 6, 3, 5, 7, 8, 4, 2, 1] as const
216
232
  export const VORTEX_ORBIT = [1, 2, 4, 8, 7, 5] as const
@@ -351,9 +351,14 @@ MEASUREMENT & COLLAPSE:
351
351
  - Run all tests simultaneously
352
352
  - Theater share ${round(THEATER_SHARE * AMPLITUDE_SCALE)}/${AMPLITUDE_SCALE}, reality amplified
353
353
 
354
- NO SEQUENTIAL PHASES.
355
- NO WAITING.
356
- ALL AT ONCE.
357
- QUANTUM EXECUTION.
354
+ ${
355
+ execution.system_correctness === 'collapsed_to_valid'
356
+ ? `COLLAPSED.
357
+ The measurement cleared the threshold, so the phases resolved together.`
358
+ : `STILL SUPERPOSED — NOT ALL AT ONCE.
359
+ Interference reached ${round(interference.working_solution_probability * AMPLITUDE_SCALE)}/${AMPLITUDE_SCALE}, under the ${round(COLLAPSE_THRESHOLD * AMPLITUDE_SCALE)}/${AMPLITUDE_SCALE} a collapse needs.
360
+ While anything stays open the sequence does not compute all at once, and this
361
+ model must not claim it did. ${execution.next_action}`
362
+ }
358
363
  `
359
364
  }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Free energy of the water reaction — Gibbs, exactly.
3
+ *
4
+ * "Free energy" is not a loose phrase. It is ΔG, the portion of a reaction's
5
+ * enthalpy change that can be taken out as work at constant temperature and
6
+ * pressure, with TΔS the part that cannot because it is owed to entropy:
7
+ *
8
+ * ΔG = ΔH − TΔS
9
+ *
10
+ * Its sign is the whole answer to whether a process can be run for gain. A
11
+ * reaction with ΔG < 0 releases work and runs on its own; one with ΔG > 0 must
12
+ * be paid for. For splitting water, ΔG is POSITIVE, and the size of it is the
13
+ * bill: 237.14 kJ per mole, or 1.229 V across an electrolysis cell. Nothing in
14
+ * this module is an opinion about that — every figure recomputes from three
15
+ * tabulated standard quantities.
16
+ *
17
+ * ARITHMETIC. All values are exact integers in units of 10⁻⁵ J/mol, so nothing
18
+ * here is a float literal and no rounding happens until a value is rendered.
19
+ * The constants are standard-state (298.15 K, 1 bar) CODATA/NIST values.
20
+ *
21
+ * WHAT THIS SETTLES. The forward and reverse reactions share one bond ledger,
22
+ * so what splitting costs is exactly what burning returns — at best. Real
23
+ * devices take a cut at every step, which `roundTrip` reports. There is no
24
+ * arrangement of the two that yields more out than in, and that is not an
25
+ * engineering limit to be improved on: it is the sign of ΔG.
26
+ */
27
+
28
+ // ============================================================================
29
+ // SCALE — exact integer arithmetic, no float literals
30
+ // ============================================================================
31
+
32
+ /** Every energy below is an integer count of 10⁻⁵ J/mol. */
33
+ export const SCALE = 100000
34
+
35
+ /** Render a scaled energy as kJ/mol, rounding once, at the boundary. */
36
+ export function toKilojoulesPerMole(scaled: number): number {
37
+ return round(scaled, SCALE * 1000)
38
+ }
39
+
40
+ /** Round a/b to the nearest integer without Math.*, ties away from zero. */
41
+ function round(a: number, b: number): number {
42
+ const neg = a < 0
43
+ const x = neg ? -a : a
44
+ const q = (2 * x + b) / (2 * b)
45
+ const f = q - (q % 1)
46
+ return neg ? -f : f
47
+ }
48
+
49
+ // ============================================================================
50
+ // STANDARD STATE (298.15 K, 1 bar) — CODATA / NIST
51
+ // ============================================================================
52
+
53
+ /** 298.15 K, held as hundredths of a kelvin. */
54
+ const T_CENTIKELVIN = 29815
55
+
56
+ /**
57
+ * ΔH° of formation for liquid water, −285.83 kJ/mol.
58
+ * H₂(g) + ½O₂(g) → H₂O(l)
59
+ */
60
+ export const ENTHALPY_FORMATION = -285830 * SCALE
61
+
62
+ /**
63
+ * ΔS° of that formation, −163.305 J/(mol·K), from the absolute entropies
64
+ * S°(H₂O,l) 69.95, S°(H₂) 130.68, S°(O₂) 205.15 J/(mol·K):
65
+ * 69.95 − 130.68 − 205.15/2 = −163.305
66
+ * Held as thousandths of a J/(mol·K).
67
+ */
68
+ const ENTROPY_FORMATION_MILLI = -163305
69
+
70
+ /** Faraday constant, C/mol. */
71
+ export const FARADAY = 96485
72
+
73
+ /** Electrons transferred per water molecule. */
74
+ export const ELECTRONS = 2
75
+
76
+ // ============================================================================
77
+ // THE FREE ENERGY
78
+ // ============================================================================
79
+
80
+ /**
81
+ * TΔS for the formation, in units of 10⁻⁵ J/mol.
82
+ *
83
+ * (T_CENTIKELVIN/100) × (ENTROPY_FORMATION_MILLI/1000) J/mol, and the two
84
+ * denominators multiply to exactly SCALE, so the product is already an integer
85
+ * in our units — no rounding enters here.
86
+ */
87
+ export const ENTROPY_TERM_FORMATION = T_CENTIKELVIN * ENTROPY_FORMATION_MILLI
88
+
89
+ /** ΔG° of formation: ΔH − TΔS. Negative — water forms and releases work. */
90
+ export const GIBBS_FORMATION = ENTHALPY_FORMATION - ENTROPY_TERM_FORMATION
91
+
92
+ /** Splitting is the reverse reaction, so every quantity changes sign. */
93
+ export const ENTHALPY_SPLITTING = -ENTHALPY_FORMATION
94
+ export const GIBBS_SPLITTING = -GIBBS_FORMATION
95
+
96
+ // ============================================================================
97
+ // CELL POTENTIALS — the same numbers, in volts
98
+ // ============================================================================
99
+
100
+ /** An exact rational, so no precision is lost before it is rendered. */
101
+ export interface ExactPotential {
102
+ /** Microvolts, as numerator/denominator. */
103
+ readonly numerator: number
104
+ readonly denominator: number
105
+ }
106
+
107
+ /**
108
+ * Reversible cell potential E° = ΔG/(nF) as an EXACT fraction of a microvolt.
109
+ *
110
+ * The rounded form below cannot be inverted exactly — quantising to whole
111
+ * microvolts throws away what the inverse would need. Returning the fraction
112
+ * keeps ΔG recoverable with no drift at all, which is what lets
113
+ * `every_model_inverts` demand equality rather than a tolerance.
114
+ */
115
+ export function reversiblePotentialExact(): ExactPotential {
116
+ return { numerator: GIBBS_SPLITTING * 10, denominator: ELECTRONS * FARADAY }
117
+ }
118
+
119
+ /** Thermoneutral potential ΔH/(nF), likewise exact. */
120
+ export function thermoneutralPotentialExact(): ExactPotential {
121
+ return { numerator: ENTHALPY_SPLITTING * 10, denominator: ELECTRONS * FARADAY }
122
+ }
123
+
124
+ /**
125
+ * Recover the energy an exact potential came from, in 10⁻⁵ J/mol.
126
+ *
127
+ * E is `numerator/denominator` microvolts and energy = E·nF/10, where the
128
+ * denominator IS nF — so it cancels. The cancellation is done here rather than
129
+ * by multiplying it out: `numerator × denominator` is about 4.6e16, past the
130
+ * 2^53 where doubles stop counting exactly, and multiplying it out reintroduced
131
+ * the very drift these exact potentials exist to remove.
132
+ */
133
+ export function energyFromPotential(p: ExactPotential): number {
134
+ if (p.denominator !== ELECTRONS * FARADAY) {
135
+ throw new RangeError(`potential denominator must be nF = ${ELECTRONS * FARADAY}, got ${p.denominator}`)
136
+ }
137
+ return p.numerator / 10
138
+ }
139
+
140
+ /**
141
+ * Reversible cell potential, E° = ΔG/(nF), in whole microvolts.
142
+ *
143
+ * The least voltage that can split water at all. Below it the reaction does not
144
+ * run, however the cell is built. Rounded for display; use
145
+ * `reversiblePotentialExact` when the value will be computed with further.
146
+ */
147
+ export function reversiblePotentialMicrovolts(): number {
148
+ const { numerator, denominator } = reversiblePotentialExact()
149
+ return round(numerator, denominator)
150
+ }
151
+
152
+ /**
153
+ * Thermoneutral potential, ΔH/(nF), in microvolts.
154
+ *
155
+ * At this voltage the cell neither warms nor cools: the entropy term is being
156
+ * supplied electrically instead of drawn from the surroundings. It is strictly
157
+ * above the reversible potential, and the gap is TΔS.
158
+ */
159
+ export function thermoneutralPotentialMicrovolts(): number {
160
+ const { numerator, denominator } = thermoneutralPotentialExact()
161
+ return round(numerator, denominator)
162
+ }
163
+
164
+ // ============================================================================
165
+ // THE CYCLE
166
+ // ============================================================================
167
+
168
+ export interface RoundTrip {
169
+ /** Energy that must be supplied to split, 10⁻⁵ J/mol. */
170
+ readonly inputRequired: number
171
+ /** Energy recoverable by burning the hydrogen back, 10⁻⁵ J/mol. */
172
+ readonly outputRecovered: number
173
+ /** outputRecovered − inputRequired. Never positive. */
174
+ readonly net: number
175
+ /** Efficiency as a percentage, rounded once. */
176
+ readonly percent: number
177
+ /** True only if the cycle returns more than it took. */
178
+ readonly gainsEnergy: boolean
179
+ }
180
+
181
+ /**
182
+ * A split-then-burn cycle, at whatever efficiencies you give it.
183
+ *
184
+ * Each efficiency is a percentage, 0..100, as an integer. Passing 100 for all
185
+ * three describes the thermodynamic ideal — the case most favourable to a
186
+ * closed loop, and the one that matters, because if it fails there it fails
187
+ * everywhere.
188
+ */
189
+ export function roundTrip(
190
+ electrolysisPercent: number,
191
+ enginePercent: number,
192
+ generatorPercent: number,
193
+ ): RoundTrip {
194
+ for (const p of [electrolysisPercent, enginePercent, generatorPercent]) {
195
+ if (!Number.isInteger(p) || p < 0 || p > 100) {
196
+ throw new RangeError(`efficiency must be an integer percentage in 0..100, got ${p}`)
197
+ }
198
+ }
199
+
200
+ // Splitting must supply the full enthalpy, not merely ΔG: a real cell run at
201
+ // the reversible potential draws the entropy term from its surroundings and
202
+ // goes cold, which no engine cycle recovers.
203
+ const inputRequired = electrolysisPercent === 0
204
+ ? Number.POSITIVE_INFINITY
205
+ : round(ENTHALPY_SPLITTING * 100, electrolysisPercent)
206
+
207
+ // Burning returns the same enthalpy — the identical bond ledger, reversed —
208
+ // and then the engine and generator each take their cut.
209
+ const outputRecovered = round(
210
+ round(ENTHALPY_FORMATION * -1 * enginePercent, 100) * generatorPercent,
211
+ 100,
212
+ )
213
+
214
+ const net = outputRecovered - inputRequired
215
+ const percent = inputRequired === Number.POSITIVE_INFINITY
216
+ ? 0
217
+ : round(outputRecovered * 100, inputRequired)
218
+
219
+ return { inputRequired, outputRecovered, net, percent, gainsEnergy: net > 0 }
220
+ }
221
+
222
+ // ============================================================================
223
+ // SELF-CHECK
224
+ // ============================================================================
225
+
226
+ /** Facts this module must satisfy. Returns failures. */
227
+ export function selfTest(): string[] {
228
+ const fail: string[] = []
229
+
230
+ // ΔG = ΔH − TΔS must reproduce the tabulated ΔG°f of −237.14 kJ/mol.
231
+ const kj = toKilojoulesPerMole(GIBBS_FORMATION)
232
+ if (kj !== -237) fail.push(`ΔG°f rounds to ${kj} kJ/mol, expected -237`)
233
+
234
+ // Formation releases work; splitting costs it. This is the sign that decides
235
+ // whether a reaction can be run for gain, and it is the whole answer.
236
+ if (!(GIBBS_FORMATION < 0)) fail.push('ΔG of formation is not negative')
237
+ if (!(GIBBS_SPLITTING > 0)) fail.push('ΔG of splitting is not positive')
238
+
239
+ // The forward and reverse reactions are one ledger read in two directions.
240
+ if (ENTHALPY_SPLITTING !== -ENTHALPY_FORMATION) fail.push('enthalpy is not antisymmetric')
241
+ if (GIBBS_SPLITTING !== -GIBBS_FORMATION) fail.push('Gibbs is not antisymmetric')
242
+
243
+ // Cell potentials, to the millivolt.
244
+ const rev = round(reversiblePotentialMicrovolts(), 1000)
245
+ const thermo = round(thermoneutralPotentialMicrovolts(), 1000)
246
+ if (rev !== 1229) fail.push(`reversible potential ${rev} mV, expected 1229`)
247
+ if (thermo !== 1481) fail.push(`thermoneutral potential ${thermo} mV, expected 1481`)
248
+ if (!(thermo > rev)) fail.push('thermoneutral potential is not above the reversible one')
249
+
250
+ // The exact potentials must invert with NO drift — that is their purpose.
251
+ if (energyFromPotential(reversiblePotentialExact()) !== GIBBS_SPLITTING) {
252
+ fail.push('reversible potential does not invert exactly')
253
+ }
254
+ if (energyFromPotential(thermoneutralPotentialExact()) !== ENTHALPY_SPLITTING) {
255
+ fail.push('thermoneutral potential does not invert exactly')
256
+ }
257
+
258
+ // The ideal cycle breaks even and never better.
259
+ const ideal = roundTrip(100, 100, 100)
260
+ if (ideal.net !== 0) fail.push(`ideal cycle net ${ideal.net}, expected 0`)
261
+ if (ideal.gainsEnergy) fail.push('ideal cycle reports a gain')
262
+
263
+ // Every real cycle loses. Exhaustive over the efficiency grid in 5% steps,
264
+ // excluding only the ideal corner above.
265
+ for (let e = 5; e <= 100; e += 5) {
266
+ for (let m = 5; m <= 100; m += 5) {
267
+ for (let g = 5; g <= 100; g += 5) {
268
+ if (e === 100 && m === 100 && g === 100) continue
269
+ const r = roundTrip(e, m, g)
270
+ if (r.gainsEnergy) {
271
+ fail.push(`cycle at ${e}/${m}/${g}% reports a gain of ${r.net}`)
272
+ return fail
273
+ }
274
+ }
275
+ }
276
+ }
277
+
278
+ return fail
279
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Polluted water in, electricity and drinkable water out — the real version.
3
+ *
4
+ * The described machine exists. What makes it work is that the fuel is not the
5
+ * water: it is what is dissolved in it. H₂O carries no recoverable energy —
6
+ * `free-energy.ts` settles that, ΔG for splitting is +237 kJ/mol — but organic
7
+ * contamination does, and a litre of strong effluent can carry more chemical
8
+ * energy than it costs to clean the litre.
9
+ *
10
+ * So the architecture is:
11
+ *
12
+ * polluted water → anaerobic digestion → biogas (mostly CH₄)
13
+ * → combustion engine (CHP) → ELECTRICITY
14
+ * digester effluent → membrane treatment → DRINKABLE WATER
15
+ *
16
+ * The engine burns methane the bacteria made from the pollutants. Its exhaust
17
+ * is CO₂ and water vapour; the drinkable water comes from the membrane train,
18
+ * powered by part of the electricity. Everything you described happens, and
19
+ * nothing runs on the water itself.
20
+ *
21
+ * THE WHOLE QUESTION IS THE LOAD. Weak sewage does not carry enough energy to
22
+ * pay for its own treatment; strong industrial effluent carries several times
23
+ * over. There is therefore a THRESHOLD contamination, and this module computes
24
+ * it rather than asserting it. Below it the plant imports power; above it the
25
+ * plant exports.
26
+ *
27
+ * Distillation is deliberately not the water step. Evaporating a litre costs
28
+ * about 2.26 MJ in latent heat alone — far more than any realistic load carries
29
+ * — so a still cannot be paid for by the pollution. Membranes can.
30
+ *
31
+ * ARITHMETIC. Integers throughout, in decijoules per litre (dJ/L). No float
32
+ * literals; rounding happens once, where a value is rendered.
33
+ */
34
+
35
+ // ============================================================================
36
+ // CONSTANTS — measured quantities, not choices
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Chemical energy per unit of COD (chemical oxygen demand): 13.9 J per mg.
41
+ * COD is the standard measure of oxidisable load, so this converts "how dirty"
42
+ * directly into "how much energy is in it". Held as tenths of a J/mg.
43
+ */
44
+ const JOULES_PER_MG_COD_TENTHS = 139
45
+
46
+ /**
47
+ * The same figure as J/mg, for prose that quotes it. Derived from the tenths
48
+ * above rather than written as 13.9 — a decimal literal here would be a second
49
+ * copy of the constant, and the ratchet counts those for exactly that reason.
50
+ */
51
+ export const JOULES_PER_MG_COD = JOULES_PER_MG_COD_TENTHS / 10
52
+
53
+ /**
54
+ * Fraction of COD that anaerobic digestion converts to methane, as a
55
+ * percentage. Well-run mesophilic digesters on soluble organics reach the
56
+ * mid-sixties; the rest becomes biomass or leaves undigested.
57
+ */
58
+ const DIGESTION_CAPTURE_PERCENT = 65
59
+
60
+ /**
61
+ * Electrical efficiency of a gas engine in CHP service, as a percentage.
62
+ * Reciprocating engines in the hundreds-of-kW class sit near this. Heat is
63
+ * recovered too, but heat is not what was asked for, so it is not counted.
64
+ */
65
+ const ENGINE_ELECTRICAL_PERCENT = 38
66
+
67
+ /**
68
+ * Electricity to bring digester effluent to drinking standard by membranes:
69
+ * ultrafiltration, reverse osmosis, then polishing. 4 kWh per m³ = 4 Wh/L =
70
+ * 14 400 J/L = 144 000 dJ/L. The conservative end of the RO range, because a
71
+ * dirty feed fouls membranes and costs more than a clean one.
72
+ */
73
+ export const TREATMENT_DEMAND_DECIJOULES_PER_LITRE = 144000
74
+
75
+ // ============================================================================
76
+ // THE BALANCE
77
+ // ============================================================================
78
+
79
+ export interface WaterBalance {
80
+ /** Contamination as COD, mg/L — the input. */
81
+ readonly codMilligramsPerLitre: number
82
+ /** Chemical energy present in one litre, dJ/L. */
83
+ readonly energyInLoad: number
84
+ /** Electricity the engine actually delivers, dJ/L. */
85
+ readonly electricityGenerated: number
86
+ /** Electricity the membrane train consumes, dJ/L. */
87
+ readonly treatmentDemand: number
88
+ /** generated − demand. Positive means the plant exports power. */
89
+ readonly netElectricity: number
90
+ /** True when the water pays for its own cleaning and more. */
91
+ readonly selfPowering: boolean
92
+ }
93
+
94
+ /** Integer division rounded to nearest, ties away from zero, no Math.*. */
95
+ function round(a: number, b: number): number {
96
+ const neg = a < 0
97
+ const x = neg ? -a : a
98
+ const q = (2 * x + b) / (2 * b)
99
+ const f = q - (q % 1)
100
+ return neg ? -f : f
101
+ }
102
+
103
+ /**
104
+ * Energy balance for one litre at a given contamination.
105
+ *
106
+ * `cod` is chemical oxygen demand in mg/L: roughly 300–800 for municipal
107
+ * sewage, 2 000–6 000 for dairy or brewery effluent, 20 000–80 000 for manure
108
+ * slurry or food-processing waste.
109
+ */
110
+ export function balanceFor(cod: number): WaterBalance {
111
+ if (!Number.isInteger(cod) || cod < 0) {
112
+ throw new RangeError(`COD must be a non-negative integer in mg/L, got ${cod}`)
113
+ }
114
+
115
+ const energyInLoad = cod * JOULES_PER_MG_COD_TENTHS
116
+ const captured = round(energyInLoad * DIGESTION_CAPTURE_PERCENT, 100)
117
+ const electricityGenerated = round(captured * ENGINE_ELECTRICAL_PERCENT, 100)
118
+ const netElectricity = electricityGenerated - TREATMENT_DEMAND_DECIJOULES_PER_LITRE
119
+
120
+ return {
121
+ codMilligramsPerLitre: cod,
122
+ energyInLoad,
123
+ electricityGenerated,
124
+ treatmentDemand: TREATMENT_DEMAND_DECIJOULES_PER_LITRE,
125
+ netElectricity,
126
+ selfPowering: netElectricity > 0,
127
+ }
128
+ }
129
+
130
+ /**
131
+ * The lowest whole mg/L of COD at which the plant exports power.
132
+ *
133
+ * Found by scanning upward rather than by inverting the arithmetic, so the
134
+ * answer is the same quantity `balanceFor` reports and cannot drift from it.
135
+ */
136
+ export function breakEvenCod(): number {
137
+ for (let cod = 0; cod <= 100000; cod++) {
138
+ if (balanceFor(cod).selfPowering) return cod
139
+ }
140
+ throw new Error('no break-even COD below 100 000 mg/L')
141
+ }
142
+
143
+ /** Typical loads, for locating a real stream against the threshold. */
144
+ export const TYPICAL_LOADS: readonly { readonly name: string; readonly cod: number }[] = [
145
+ { name: 'municipal sewage, dilute', cod: 300 },
146
+ { name: 'municipal sewage, strong', cod: 800 },
147
+ { name: 'brewery effluent', cod: 3000 },
148
+ { name: 'dairy processing', cod: 5000 },
149
+ { name: 'manure slurry', cod: 40000 },
150
+ ]
151
+
152
+ // ============================================================================
153
+ // SELF-CHECK
154
+ // ============================================================================
155
+
156
+ /** Facts this module must satisfy. Returns failures. */
157
+ export function selfTest(): string[] {
158
+ const fail: string[] = []
159
+
160
+ // Clean water carries nothing. This is the point: H2O is not the fuel.
161
+ const clean = balanceFor(0)
162
+ if (clean.energyInLoad !== 0) fail.push('clean water reports non-zero energy')
163
+ if (clean.electricityGenerated !== 0) fail.push('clean water generates electricity')
164
+ if (clean.selfPowering) fail.push('clean water reports as self-powering')
165
+ if (clean.netElectricity !== -TREATMENT_DEMAND_DECIJOULES_PER_LITRE) {
166
+ fail.push('clean water does not owe exactly the treatment demand')
167
+ }
168
+
169
+ // A threshold exists, is positive, and is unique — monotone in the load.
170
+ const t = breakEvenCod()
171
+ if (!(t > 0)) fail.push(`break-even COD is ${t}, expected positive`)
172
+ if (balanceFor(t).netElectricity <= 0) fail.push('break-even point does not export')
173
+ if (balanceFor(t - 1).netElectricity > 0) fail.push('a lower COD also exports — not a threshold')
174
+
175
+ // Monotone: more load never yields less electricity.
176
+ let previous = -1
177
+ for (let cod = 0; cod <= 20000; cod += 100) {
178
+ const e = balanceFor(cod).electricityGenerated
179
+ if (e < previous) { fail.push(`electricity fell between loads at COD ${cod}`); break }
180
+ previous = e
181
+ }
182
+
183
+ // Municipal sewage must sit below the threshold and strong effluent above,
184
+ // or the model has lost contact with the streams it claims to describe.
185
+ const municipal = TYPICAL_LOADS.find((l) => l.name === 'municipal sewage, strong')
186
+ const dairy = TYPICAL_LOADS.find((l) => l.name === 'dairy processing')
187
+ if (municipal && balanceFor(municipal.cod).selfPowering) {
188
+ fail.push('strong municipal sewage reports as self-powering')
189
+ }
190
+ if (dairy && !balanceFor(dairy.cod).selfPowering) {
191
+ fail.push('dairy effluent does not report as self-powering')
192
+ }
193
+
194
+ // No arrangement recovers more than the load contains: capture and engine
195
+ // efficiency are both below 100%, so electricity < energy present, always.
196
+ for (const { cod } of TYPICAL_LOADS) {
197
+ const b = balanceFor(cod)
198
+ if (cod > 0 && b.electricityGenerated >= b.energyInLoad) {
199
+ fail.push(`COD ${cod} yields electricity at or above the energy present`)
200
+ }
201
+ }
202
+
203
+ return fail
204
+ }
@@ -30,6 +30,31 @@
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 {
36
+ GIBBS_FORMATION,
37
+ GIBBS_SPLITTING,
38
+ ENTHALPY_FORMATION,
39
+ ENTHALPY_SPLITTING,
40
+ reversiblePotentialMicrovolts,
41
+ thermoneutralPotentialMicrovolts,
42
+ roundTrip,
43
+ selfTest as thermoSelfTest,
44
+ SCALE,
45
+ ELECTRONS,
46
+ FARADAY,
47
+ reversiblePotentialExact,
48
+ thermoneutralPotentialExact,
49
+ energyFromPotential,
50
+ } from '../thermo/free-energy.ts'
51
+ import {
52
+ balanceFor,
53
+ breakEvenCod,
54
+ TYPICAL_LOADS,
55
+ TREATMENT_DEMAND_DECIJOULES_PER_LITRE,
56
+ selfTest as wastewaterSelfTest,
57
+ } from '../thermo/wastewater-energy.ts'
33
58
  import {
34
59
  zeroState,
35
60
  applyGate1,
@@ -43,6 +68,9 @@ import {
43
68
  STEANE_CODE,
44
69
  symplecticProduct,
45
70
  estimateSurfaceCodeThreshold,
71
+ executeInSuperposition,
72
+ computeInterferencePattern,
73
+ describeQuantumExecution,
46
74
  } from '../quantum/index.ts'
47
75
  import { ML_KEM_768 } from '../crypto/ml-kem.ts'
48
76
  import { sqrt } from '../0/algebra.ts'
@@ -390,6 +418,325 @@ export const SEALS: Record<string, Seal> = {
390
418
  ML_KEM_768.decapsulationKeyBytes === 2400 &&
391
419
  ML_KEM_768.ciphertextBytes === 1088,
392
420
  },
421
+ doubling_avoids_the_triad: {
422
+ 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.',
423
+ decide: () => {
424
+ // Bound to the kernel's own constants, not retyped here. VORTEX_AXIS had
425
+ // no reader anywhere in the repository before this seal, so nothing
426
+ // checked it was the triad at all.
427
+ const TRIAD: readonly number[] = VORTEX_AXIS
428
+
429
+ // Period 6, from 2^6 = 64 = 1 (mod 9). Without this the six cases below
430
+ // would be a sample; with it they are the whole sequence.
431
+ if (digitalRoot(64) !== digitalRoot(1)) return false
432
+
433
+ // The orbit of 2, generated by doubling and folded each step.
434
+ const orbit: number[] = []
435
+ let d = 1
436
+ for (let i = 0; i < 6; i++) {
437
+ orbit.push(d)
438
+ if (TRIAD.includes(d)) return false // a power of two landed on 3, 6 or 9
439
+ d = digitalRoot(d * 2)
440
+ }
441
+ // Six distinct values, and doubling returns to the start: a full cycle.
442
+ if (new Set(orbit).size !== 6 || d !== 1) return false
443
+
444
+ // Those six are exactly the units mod 9 — the residues coprime to 9.
445
+ // Everything else mod 9 is a multiple of 3, which is the triad.
446
+ const units = [1, 2, 4, 5, 7, 8]
447
+ if (orbit.slice().sort((a, b) => a - b).join() !== units.join()) return false
448
+
449
+ // VORTEX_ORBIT must BE the computed orbit, in doubling order — the
450
+ // constant is pinned to the computation rather than trusted.
451
+ if (VORTEX_ORBIT.join() !== orbit.join()) return false
452
+
453
+ // And VORTEX_AXIS must be exactly what the orbit cannot reach: the
454
+ // complement of the orbit in 1..9, in ascending order.
455
+ const complement = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter((n) => !orbit.includes(n))
456
+ if (TRIAD.join() !== complement.join()) return false
457
+
458
+ // VORTEX_SEQUENCE reads orbit then axis, so it is not an arbitrary order.
459
+ if (VORTEX_SEQUENCE.slice(0, orbit.length).join() !== orbit.join()) return false
460
+ if (VORTEX_SEQUENCE.slice(orbit.length).join() !== TRIAD.join()) return false
461
+
462
+ // Reflection is where the triad enters: 1 -> 9, 4 -> 6, 7 -> 3.
463
+ if (throughVoid(1) !== 9 || throughVoid(4) !== 6 || throughVoid(7) !== 3) return false
464
+
465
+ // And it is an involution, so the reading is reversible, not a relabel.
466
+ for (let n = 0; n <= 9; n++) if (throughVoid(throughVoid(n)) !== n) return false
467
+
468
+ // Reflecting the orbit reaches every member of the triad.
469
+ const reflected = orbit.map(throughVoid)
470
+ return TRIAD.every((t) => reflected.includes(t))
471
+ },
472
+ },
473
+ superposition_reports_its_own_state: {
474
+ 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.",
475
+ decide: () => {
476
+ const execution = executeInSuperposition()
477
+ const interference = computeInterferencePattern()
478
+ const text = describeQuantumExecution()
479
+
480
+ // The reported verdict must be the comparison, not a stored string.
481
+ const collapsedByNumbers = interference.working_solution_probability > 85 / 100
482
+ const collapsedByReport = execution.system_correctness === 'collapsed_to_valid'
483
+ if (collapsedByReport !== collapsedByNumbers) return false
484
+
485
+ // The prose takes exactly one of the two branches — never both, never
486
+ // neither, which is what an unconditional closing slogan would do.
487
+ const claimsCollapse = text.includes('COLLAPSED.')
488
+ const admitsOpen = text.includes('STILL SUPERPOSED')
489
+ if (claimsCollapse === admitsOpen) return false
490
+
491
+ // And the branch it takes is the one the measurement licenses.
492
+ if (claimsCollapse !== collapsedByNumbers) return false
493
+
494
+ // When open, it must publish the shortfall rather than only naming it.
495
+ if (admitsOpen && !text.includes('NOT ALL AT ONCE')) return false
496
+ return true
497
+ },
498
+ },
499
+ merkaba_is_two_mirrored_tetrahedra: {
500
+ 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.",
501
+ decide: () => {
502
+ const RING = [1, 2, 3, 4, 5, 6, 7, 8, 9]
503
+ const cls = (r: number) => RING.filter((d) => d % 3 === r)
504
+ const up = cls(1) // 1,4,7
505
+ const equator = cls(2) // 2,5,8
506
+ const down = cls(0) // 3,6,9
507
+ const key = (a: readonly number[]) => [...a].sort((x, y) => x - y).join(',')
508
+
509
+ // Three classes of three, partitioning the ring.
510
+ if (up.length !== 3 || equator.length !== 3 || down.length !== 3) return false
511
+ if (key([...up, ...equator, ...down]) !== key(RING)) return false
512
+
513
+ // Each is equilateral on the enneagram: three gaps of 120 degrees.
514
+ for (const t of [up, equator, down]) {
515
+ const b = t.map(bearingForDigit).sort((x, y) => x - y)
516
+ const gaps = [b[1]! - b[0]!, b[2]! - b[1]!, 360 - (b[2]! - b[0]!)]
517
+ if (!gaps.every((g) => g === 120)) return false
518
+ }
519
+
520
+ // The mirror swaps the two tetrahedral bases and fixes the equator.
521
+ if (key(up.map(throughVoid)) !== key(down)) return false
522
+ if (key(down.map(throughVoid)) !== key(up)) return false
523
+ if (key(equator.map(throughVoid)) !== key(equator)) return false
524
+
525
+ // The void is the shared apex, and it is the mirror's fixed point.
526
+ if (throughVoid(0) !== 0) return false
527
+ const tetraDown = [...down, 0]
528
+ const tetraUp = [...up, 0]
529
+ if (tetraDown.length !== 4 || tetraUp.length !== 4) return false
530
+ const shared = tetraDown.filter((v) => tetraUp.includes(v))
531
+ if (key(shared) !== '0') return false
532
+
533
+ // The gateway triangle is the axis the kernel already declares, so this
534
+ // is the same structure the vortex constants describe, not a new one.
535
+ if (key(down) !== key(VORTEX_AXIS)) return false
536
+
537
+ // From 3: itself, its two triangle partners, and the void. Four keys.
538
+ const fromThree = [3, ...down.filter((d) => d !== 3), 0]
539
+ return fromThree.length === 4 && key(fromThree) === key(tetraDown)
540
+ },
541
+ },
542
+ agl_acts_on_the_three_triangles: {
543
+ 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.",
544
+ decide: () => {
545
+ const units = [1, 2, 4, 5, 7, 8] // the residues coprime to 9
546
+ const maps: ((x: number) => number)[] = []
547
+ for (const a of units) for (let b = 0; b < 9; b++) maps.push((x) => ((a * x + b) % 9 + 9) % 9)
548
+ if (maps.length !== 54) return false
549
+
550
+ // The three cosets of {0,3,6}. Written as residues, so 9 appears as 0.
551
+ const T = [[1, 4, 7], [2, 5, 8], [3, 6, 0]]
552
+ const key = (xs: readonly number[]) => [...new Set(xs)].sort((x, y) => x - y).join(',')
553
+ if (key([...T[0]!, ...T[1]!, ...T[2]!]) !== '0,1,2,3,4,5,6,7,8') return false
554
+
555
+ // The gateway triangle must be the axis the kernel declares, reduced mod 9.
556
+ if (key(T[2]!) !== key(VORTEX_AXIS.map((d) => d % 9))) return false
557
+
558
+ const index = new Map(T.map((t, i) => [key(t), i]))
559
+ const induced = new Set<string>()
560
+ let fixesAll = 0
561
+ let stabilisesFirst = 0
562
+ for (const f of maps) {
563
+ const image = T.map((t) => index.get(key(t.map(f))))
564
+ // Closure: an affine map must send a coset to a coset.
565
+ if (image.some((v) => v === undefined)) return false
566
+ const word = image.join('')
567
+ induced.add(word)
568
+ if (word === '012') fixesAll++
569
+ if (image[0] === 0) stabilisesFirst++
570
+ }
571
+
572
+ // Induced group is AGL(1,Z/3); kernel and stabiliser follow from it.
573
+ if (induced.size !== 6) return false
574
+ if (fixesAll !== 9) return false
575
+ if (stabilisesFirst !== 18) return false
576
+ // Transitive on three, so orbit x stabiliser recovers the whole group.
577
+ return 3 * stabilisesFirst === maps.length
578
+ },
579
+ },
580
+ digit_geometry_is_single_valued: {
581
+ 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.",
582
+ decide: () => {
583
+ const RING = [1, 2, 3, 4, 5, 6, 7, 8, 9]
584
+
585
+ // Injective: nine digits, nine distinct bearings, all whole degrees.
586
+ const kernel = RING.map(bearingForDigit)
587
+ if (new Set(kernel).size !== RING.length) return false
588
+ if (!kernel.every((b) => Number.isInteger(b) && b >= 0 && b < 360)) return false
589
+
590
+ // The two definitions must agree digit for digit.
591
+ if (!RING.every((d) => angleForDigit(d) === bearingForDigit(d))) return false
592
+
593
+ // Evenly spaced: consecutive bearings differ by one ninth of a turn.
594
+ const sorted = [...kernel].sort((a, b) => a - b)
595
+ for (let i = 1; i < sorted.length; i++) {
596
+ if (sorted[i]! - sorted[i - 1]! !== 40) return false
597
+ }
598
+
599
+ // 9 at the top is what fixes the ring's phase; without it any rotation
600
+ // would satisfy the spacing check above.
601
+ return bearingForDigit(9) === 270 && bearingForDigit(3) === 30
602
+ },
603
+ },
604
+ free_energy_of_splitting_is_positive: {
605
+ 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.",
606
+ decide: () => {
607
+ if (thermoSelfTest().length > 0) return false
608
+
609
+ // The sign is the claim. Formation releases work; splitting costs it.
610
+ if (!(GIBBS_FORMATION < 0)) return false
611
+ if (!(GIBBS_SPLITTING > 0)) return false
612
+
613
+ // One ledger, read in two directions — so what splitting costs is
614
+ // exactly what burning returns, before any device takes its cut.
615
+ if (ENTHALPY_SPLITTING !== -ENTHALPY_FORMATION) return false
616
+ if (GIBBS_SPLITTING !== -GIBBS_FORMATION) return false
617
+
618
+ // Free energy is strictly less than enthalpy: TΔS is real and is owed.
619
+ if (!(GIBBS_SPLITTING < ENTHALPY_SPLITTING)) return false
620
+ const rev = reversiblePotentialMicrovolts()
621
+ const thermo = thermoneutralPotentialMicrovolts()
622
+ if (!(thermo > rev)) return false
623
+
624
+ // The perfect cycle breaks even EXACTLY — the boundary case, and the one
625
+ // most favourable to a closed loop.
626
+ const ideal = roundTrip(100, 100, 100)
627
+ if (ideal.net !== 0) return false
628
+
629
+ // And no cycle anywhere on the grid gains. Exhaustive over 5% steps in
630
+ // all three efficiencies: 8000 combinations, none of them sampled.
631
+ //
632
+ // The verdict is recomputed from the two energies rather than read off
633
+ // `gainsEnergy`. Trusting that flag let a mutant hardcode it to false and
634
+ // still pass — a seal must not accept the answer from the thing it is
635
+ // judging. `net` is cross-checked against its own definition for the same
636
+ // reason.
637
+ let checked = 0
638
+ for (let e = 5; e <= 100; e += 5) {
639
+ for (let m = 5; m <= 100; m += 5) {
640
+ for (let g = 5; g <= 100; g += 5) {
641
+ checked++
642
+ const r = roundTrip(e, m, g)
643
+ if (r.net !== r.outputRecovered - r.inputRequired) return false
644
+ if (r.outputRecovered > r.inputRequired) return false
645
+ if (r.gainsEnergy !== r.net > 0) return false
646
+ }
647
+ }
648
+ }
649
+ return checked === 8000
650
+ },
651
+ },
652
+ polluted_water_powers_its_own_cleaning_above_a_threshold: {
653
+ 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.",
654
+ decide: () => {
655
+ if (wastewaterSelfTest().length > 0) return false
656
+
657
+ // The water itself is not the fuel. At zero load there is nothing to
658
+ // burn, and the plant owes exactly the cost of cleaning.
659
+ const clean = balanceFor(0)
660
+ if (clean.energyInLoad !== 0) return false
661
+ if (clean.electricityGenerated !== 0) return false
662
+ if (clean.netElectricity !== -TREATMENT_DEMAND_DECIJOULES_PER_LITRE) return false
663
+
664
+ // A threshold exists and is a genuine boundary: it exports, and one
665
+ // milligram less does not.
666
+ const t = breakEvenCod()
667
+ if (!(t > 0)) return false
668
+ if (!(balanceFor(t).netElectricity > 0)) return false
669
+ if (balanceFor(t - 1).netElectricity > 0) return false
670
+
671
+ // Monotone in the load — more pollution never yields less electricity.
672
+ let previous = -1
673
+ for (let cod = 0; cod <= 20000; cod += 100) {
674
+ const e = balanceFor(cod).electricityGenerated
675
+ if (e < previous) return false
676
+ previous = e
677
+ }
678
+
679
+ // Conservation still binds: recovery is strictly below the energy the
680
+ // load contains, because capture and engine efficiency are each < 100%.
681
+ for (const { cod } of TYPICAL_LOADS) {
682
+ if (cod === 0) continue
683
+ const b = balanceFor(cod)
684
+ if (b.electricityGenerated >= b.energyInLoad) return false
685
+ }
686
+
687
+ // And the model must still place real streams where they actually fall.
688
+ const strongSewage = TYPICAL_LOADS.find((l) => l.name === 'municipal sewage, strong')
689
+ const dairy = TYPICAL_LOADS.find((l) => l.name === 'dairy processing')
690
+ if (!strongSewage || !dairy) return false
691
+ if (balanceFor(strongSewage.cod).selfPowering) return false
692
+ return balanceFor(dairy.cod).selfPowering
693
+ },
694
+ },
695
+ every_model_inverts: {
696
+ 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.",
697
+ decide: () => {
698
+ // --- free energy: invert ΔG = ΔH − TΔS to recover the entropy --------
699
+ // The forward pass multiplied T by ΔS; dividing it back out must land on
700
+ // the tabulated figure exactly, with no rounding introduced anywhere.
701
+ const T_CENTIKELVIN = 29815
702
+ const TABULATED_ENTROPY_MILLI = -163305
703
+ const recovered =
704
+ ((ENTHALPY_FORMATION - GIBBS_FORMATION) * 1000 * 100) / (T_CENTIKELVIN * SCALE)
705
+ if (recovered !== TABULATED_ENTROPY_MILLI) return false
706
+
707
+ // --- cell potential: invert E = ΔG/(nF) to recover ΔG ----------------
708
+ // Exactly, from the rational form. This used to be a bounded check
709
+ // because the microvolt rounding made an exact inverse impossible; the
710
+ // exact potentials exist so that the demand can be equality.
711
+ if (energyFromPotential(reversiblePotentialExact()) !== GIBBS_SPLITTING) return false
712
+ if (energyFromPotential(thermoneutralPotentialExact()) !== ENTHALPY_SPLITTING) return false
713
+
714
+ // The ROUNDED form still cannot invert exactly, and must not pretend to.
715
+ // Its drift is held to half a microvolt's worth of energy — nF/20 in
716
+ // these units, a derived bound rather than an eyeballed tolerance.
717
+ const microvolts = reversiblePotentialMicrovolts()
718
+ const backToGibbs = (microvolts * ELECTRONS * FARADAY) / 10
719
+ const drift = backToGibbs - GIBBS_SPLITTING
720
+ const magnitude = drift < 0 ? -drift : drift
721
+ if (!(2 * magnitude <= (ELECTRONS * FARADAY) / 10)) return false
722
+ if (!(magnitude * 1000000 < GIBBS_SPLITTING)) return false
723
+
724
+ // --- break-even COD: scanning versus inverting ------------------------
725
+ // balanceFor rounds twice on the way up, so a closed form agreeing with
726
+ // the scan is a real check on both, not a restatement of one.
727
+ const scanned = breakEvenCod()
728
+ const numerator = TREATMENT_DEMAND_DECIJOULES_PER_LITRE * 10000
729
+ const denominator = 139 * 65 * 38
730
+ const exact = numerator / denominator
731
+ const inverted = exact % 1 === 0 ? exact + 1 : exact - (exact % 1) + 1
732
+ if (scanned !== inverted) return false
733
+
734
+ // --- reflection is its own inverse ------------------------------------
735
+ for (let d = 0; d <= 9; d++) if (throughVoid(throughVoid(d)) !== d) return false
736
+
737
+ return true
738
+ },
739
+ },
393
740
  }
394
741
 
395
742
  // ============================================================================