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 +79 -0
- package/CITATION.cff +1 -1
- package/README.md +1 -1
- package/dist/legacy-BEa7tljT.js.map +1 -1
- package/dist/legacy-CV8oTnKM.cjs.map +1 -1
- package/package.json +9 -2
- package/src/0/3/6/9/1/2/4/8/7/5/1/a432.math.ts +14 -6
- package/src/0/3/6/9/1/2/4/8/7/5/1/a432.vbm.path.ts +6 -2
- package/src/0/index.ts +16 -0
- package/src/quantum/superposition-execution.ts +9 -4
- package/src/thermo/free-energy.ts +279 -0
- package/src/thermo/wastewater-energy.ts +204 -0
- package/src/verification/lean-bridge.ts +347 -0
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.
|
|
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.
|
|
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;
|
|
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;
|
|
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.
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
//
|
|
269
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
// ============================================================================
|