@essrt/physics 20.0.1 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +4 -4
  2. package/package.json +3 -8
  3. package/src/cardinal/one-source-structure.cjs +3 -2
  4. package/src/chain-cycles/index.cjs +12 -30
  5. package/src/constants/coefficients.d.ts +16 -92
  6. package/src/constants/coefficients.js +5 -21
  7. package/src/constants/generated.d.ts +12 -16
  8. package/src/constants/generated.js +21 -25
  9. package/src/deltat/deep-time.cjs +36 -92
  10. package/src/deltat/recession-history.cjs +2 -1
  11. package/src/earth/deep-orbital-history.cjs +4 -4
  12. package/src/earth/precession-composed.cjs +3 -3
  13. package/src/earth/year-lengths.cjs +16 -1
  14. package/src/eclipse/sun-planetary-completion.cjs +11 -12
  15. package/src/index.js +22 -46
  16. package/src/layer0/derive-params.js +10 -5
  17. package/src/layer0/index.js +27 -73
  18. package/src/layer1/index.js +6 -37
  19. package/src/model.js +182 -210
  20. package/src/moon/arguments.cjs +57 -83
  21. package/src/moon/deep-modes-artifact.cjs +2 -2
  22. package/src/moon/month-chain.cjs +25 -59
  23. package/src/moon/series.cjs +9 -5
  24. package/src/phase/deep-calendar.cjs +35 -80
  25. package/src/planets/chain-artifact.js +2 -2
  26. package/src/planets/geometry.cjs +52 -23
  27. package/src/planets/model.cjs +5 -5
  28. package/types/chain-cycles/index.d.cts +9 -4
  29. package/types/constants/coefficients.d.ts +16 -92
  30. package/types/constants/generated.d.ts +12 -16
  31. package/types/deltat/deep-time.d.cts +3 -16
  32. package/types/earth/year-lengths.d.cts +12 -0
  33. package/types/eclipse/sun-planetary-completion.d.cts +0 -9
  34. package/types/index.d.ts +2 -12
  35. package/types/layer0/derive-params.d.ts +7 -4
  36. package/types/layer0/index.d.ts +6 -13
  37. package/types/layer1/index.d.ts +0 -2
  38. package/types/model.d.ts +6 -6
  39. package/types/moon/arguments.d.cts +23 -22
  40. package/types/moon/deep-modes-artifact.d.cts +3 -3
  41. package/types/moon/month-chain.d.cts +7 -16
  42. package/types/moon/series.d.cts +2 -2
  43. package/types/phase/deep-calendar.d.cts +14 -50
  44. package/types/planets/geometry.d.cts +27 -11
  45. package/types/planets/model.d.cts +9 -3
  46. package/src/cardinal/index.cjs +0 -324
  47. package/src/moon/ecc-channel.cjs +0 -141
  48. package/src/phase/index.cjs +0 -186
  49. package/src/planets/ecc-channel.cjs +0 -59
  50. package/src/sun/longitude-correction.cjs +0 -68
  51. package/types/cardinal/index.d.cts +0 -80
  52. package/types/moon/ecc-channel.d.cts +0 -30
  53. package/types/phase/index.d.cts +0 -38
  54. package/types/planets/ecc-channel.d.cts +0 -20
  55. package/types/sun/longitude-correction.d.cts +0 -41
@@ -1,324 +0,0 @@
1
- /**
2
- * Cardinal-point model — THE shared implementation (Phase 7.2).
3
- *
4
- * The §10 derived form and everything on it, extracted verbatim from
5
- * tools/lib/orbital-engine.js (which the browser mirrored function-for-
6
- * function — this file replaces both copies):
7
- *
8
- * JD_X(Y) = anchor_X + ΣT_trop(Y) + δ_X(Y) − δ_X(2000)
9
- * ΣT_trop = lincoef·(Y−2000) + driftTerm(Y) + Ih(Y)
10
- * δ_X = sinusoids + equation-of-centre orders (e(t)ⁿ·sin(nM), e(t)
11
- * the LAW OF COSINES) + §10g quadrature-locked joint sidebands
12
- * T_X(Y) = the EXACT term-by-term derivative of JD_X
13
- *
14
- * LOAD-BEARING, do not "improve":
15
- * - lincoef/h0/h1 come from the fit and are used VERBATIM — recomputing
16
- * lincoef from the 1-year anchor injects a −12,276 s ramp; holding H
17
- * constant inside Ih costs up to 5.2 s (§10c/§10e-quinquies).
18
- * - The drift term is Simpson on 2000-yr NODE SPACING (not a fixed node
19
- * count: that was exact in-window and −33,758 s at −380 Ma) plus the
20
- * Euler–Maclaurin endpoint term (omitting it costs 6–13 s).
21
- * - The equation-of-centre terms are {order,sin,cos}, NOT sinusoids — read
22
- * as H/16+H/32 harmonics they are wrong by the whole ~1.78 d braid.
23
- * - §10g joint sidebands are SHARED across the four points with phase
24
- * order·λ_X − 2π·div·c — COUNTER-rotating. The minus sign is load-bearing:
25
- * the co-rotating sense captures NOTHING (measured — the sign experiment,
26
- * doc 99 "the braid law").
27
- * - The year-length derivative keeps ONE deliberate divergence from the
28
- * exact derivative: its drift part uses the real-LOD convention
29
- * (`meanYearRealLodDays`) — the tweakpane's epoch-local days ("~400 days
30
- * at the Devonian") — where the JD form integrates the SI form. Equal at
31
- * J2000 up to the known 118 ms fit-basis gap.
32
- *
33
- * The `cyclesBetween` the caller injects is the ENGINE'S OWN function, so
34
- * each engine's deep-time/snapshot toggle semantics are preserved exactly;
35
- * under deep time both engines now route it through @essrt/physics/phase.
36
- */
37
-
38
- 'use strict';
39
-
40
- /** @type {Record<string, number>} */
41
- const JOINT_LAMBDA = { SS: 0, AE: Math.PI / 2, WS: Math.PI, VE: 1.5 * Math.PI };
42
-
43
- const DRIFT_NODE_SPACING_YEARS = 2000;
44
- const DRIFT_SIMPSON_N_MIN = 64;
45
-
46
- /** @typedef {{ lincoef: number, h0: number, h1: number }} DerivedCoefs */
47
- /** @typedef {{ order: number, sin: number, cos: number }} EccTerm */
48
- /** @typedef {{ order: number, div: number, sin: number, cos: number }} JointTerm */
49
-
50
- /**
51
- * @param {{
52
- * isDeepTime: () => boolean,
53
- * constants: {
54
- * anchors: Record<string, number>,
55
- * harmonics: Record<string, Array<[number, number, number]>>,
56
- * eccTerms: (Record<string, EccTerm[]> | null),
57
- * jointTerms: ({ terms: JointTerm[] } | null),
58
- * derived: (DerivedCoefs | null),
59
- * tropicalHarmonics: Array<[number, number, number]>,
60
- * balancedYear: number,
61
- * meanSolarYearDays: number,
62
- * hJ2000: number,
63
- * tiltMeanDeg: number,
64
- * raAngleDeg: number,
65
- * inclAmplitudeDeg: number,
66
- * },
67
- * fns: {
68
- * cyclesBetween: (yearA: number, yearB: number, divisorN: number) => (number | null),
69
- * analyticTropicalDays: (year: number) => (number | null),
70
- * meanHAtAgeMa: (tMa: number) => (number | null),
71
- * meanYearRealLodDays: (tMa: number) => (number | null),
72
- * eccentricityAt: (year: number) => number,
73
- * eccentricityRateAt: (year: number) => number,
74
- * },
75
- * }} deps
76
- */
77
- function createCardinalModel({ isDeepTime, constants, fns }) {
78
- const {
79
- anchors, // { SS, WS, VE, AE } — J2000 event JDs
80
- harmonics, // { type: [[div, sin, cos], …] } — δ sinusoids
81
- eccTerms, // { type: [{order, sin, cos}, …] } | null
82
- jointTerms, // { terms: [{order, div, sin, cos}, …] } | null
83
- derived, // { lincoef, h0, h1 } — fit-calibrated, VERBATIM
84
- tropicalHarmonics, // TROPICAL_YEAR_HARMONICS [[div, sin, cos], …]
85
- balancedYear,
86
- meanSolarYearDays,
87
- hJ2000,
88
- tiltMeanDeg, // for RA
89
- raAngleDeg,
90
- inclAmplitudeDeg,
91
- } = constants;
92
- const {
93
- cyclesBetween, // (yearA, yearB, divisorN) => cycles | null
94
- analyticTropicalDays, // (year) => SI days | null — the drift integrand base
95
- meanHAtAgeMa, // (t_Ma) => H | null — the derivative's dc/dY
96
- meanYearRealLodDays, // (t_Ma) => days | null — real-LOD drift convention
97
- eccentricityAt, // (year) => e(t) — the model's ONE eccentricity law (H/3 vector sum, plan IP-eccentricity-unification)
98
- eccentricityRateAt, // (year) => de/dyear of the same law — the braid's EoC derivative rides it
99
- } = fns;
100
-
101
- /** @param {number} year */
102
- const cycleOf = (year) => {
103
- const c = cyclesBetween(balancedYear, year, 1);
104
- return c === null ? 0 : c;
105
- };
106
-
107
- /** @param {number} year */
108
- const driftIntegrand = (year) => {
109
- const a = analyticTropicalDays(year);
110
- return a === null ? 0 : (a - meanSolarYearDays);
111
- };
112
-
113
- /** @param {number} year */
114
- function driftTerm(year) {
115
- const span = year - 2000;
116
- if (span === 0) return 0;
117
- let n = Math.ceil(Math.abs(span) / DRIFT_NODE_SPACING_YEARS);
118
- if (n % 2) n++;
119
- if (n < DRIFT_SIMPSON_N_MIN) n = DRIFT_SIMPSON_N_MIN;
120
- const h = span / n;
121
- const f0 = driftIntegrand(2000), fN = driftIntegrand(year);
122
- let acc = f0 + fN;
123
- for (let i = 1; i < n; i++) acc += driftIntegrand(2000 + i * h) * ((i % 2) ? 4 : 2);
124
- return acc * h / 3 - (fN - f0) / 2;
125
- }
126
-
127
- /** Step 6d's self-corrected tropical harmonic series, as a length deviation.
128
- * @param {number} year */
129
- function tropHarmonicsAt(year) {
130
- const c = cycleOf(year), c0 = cycleOf(2000);
131
- let s = 0;
132
- for (const [div, sinC, cosC] of tropicalHarmonics) {
133
- const th = 2 * Math.PI * div * c, th0 = 2 * Math.PI * div * c0;
134
- s += sinC * (Math.sin(th) - Math.sin(th0)) + cosC * (Math.cos(th) - Math.cos(th0));
135
- }
136
- return s;
137
- }
138
-
139
- // Ih(Y) = Σ_{2000→Y} of the tropical harmonics, closed form. H stays INSIDE
140
- // the integral (h0 + h1·c) — it moves 27.5 yr across the window and
141
- // multiplies the amplitude. Deep-time only, so `derived` is present here.
142
- /** @param {number} year */
143
- function integratedTropHarmonics(year) {
144
- const D = /** @type {DerivedCoefs} */ (derived);
145
- const cY = cycleOf(year), c0 = cycleOf(2000);
146
- let tot = 0, k0 = 0;
147
- for (const [div, sinC, cosC] of tropicalHarmonics) {
148
- const k = 2 * Math.PI * div;
149
- /** @param {number} c */
150
- const F = (c) => {
151
- const sn = Math.sin(k * c), cs = Math.cos(k * c), Hc = D.h0 + D.h1 * c;
152
- return [-Hc * cs / k + D.h1 * sn / (k * k), Hc * sn / k + D.h1 * cs / (k * k)];
153
- };
154
- const a = F(c0), b = F(cY);
155
- tot += sinC * (b[0] - a[0]) + cosC * (b[1] - a[1]);
156
- k0 += sinC * Math.sin(k * c0) + cosC * Math.cos(k * c0);
157
- }
158
- return tot - k0 * (year - 2000)
159
- - (tropHarmonicsAt(year) - tropHarmonicsAt(2000)) / 2;
160
- }
161
-
162
- /** @param {number} year */
163
- function sigmaTropical(year) {
164
- const D = /** @type {DerivedCoefs} */ (derived);
165
- return D.lincoef * (year - 2000) + driftTerm(year) + integratedTropHarmonics(year);
166
- }
167
-
168
- // δ_X(2000) — the self-correction that pins JD(2000) to the anchor exactly
169
- // (sinusoids + ecc orders; the §10g joint terms self-correct inline).
170
- // Computed lazily so the phase table builds on the caller's schedule.
171
- /** @type {Record<string, number> | null} */
172
- let _selfCorr = null;
173
- function selfCorr() {
174
- if (_selfCorr !== null) return _selfCorr;
175
- _selfCorr = {};
176
- const c2000 = cycleOf(2000);
177
- for (const type of ['SS', 'WS', 'VE', 'AE']) {
178
- let h2000 = 0;
179
- for (const [div, sinC, cosC] of harmonics[type]) {
180
- const th0 = 2 * Math.PI * div * c2000;
181
- h2000 += sinC * Math.sin(th0) + cosC * Math.cos(th0);
182
- }
183
- const ecc = eccTerms && eccTerms[type];
184
- if (ecc) {
185
- const e0 = eccentricityAt(2000);
186
- const th0 = 2 * Math.PI * 16 * c2000;
187
- for (const t of ecc) {
188
- const eN0 = Math.pow(e0, t.order);
189
- h2000 += t.sin * eN0 * Math.sin(t.order * th0) + t.cos * eN0 * Math.cos(t.order * th0);
190
- }
191
- }
192
- _selfCorr[type] = h2000;
193
- }
194
- return _selfCorr;
195
- }
196
-
197
- /** @param {number} year @param {string} [type] */
198
- function computeSolsticeJD(year, type) {
199
- const cp = type || 'SS';
200
- const anchor = anchors[cp];
201
- const deep = isDeepTime();
202
-
203
- let jd = deep
204
- ? anchor + sigmaTropical(year)
205
- : anchor + meanSolarYearDays * (year - 2000);
206
-
207
- const cY = cycleOf(year);
208
- for (const [div, sinC, cosC] of harmonics[cp]) {
209
- jd += sinC * Math.sin(2 * Math.PI * div * cY) + cosC * Math.cos(2 * Math.PI * div * cY);
210
- }
211
- const ecc = eccTerms && eccTerms[cp];
212
- if (ecc) {
213
- const e = eccentricityAt(year);
214
- const th = 2 * Math.PI * 16 * cY;
215
- for (const t of ecc) {
216
- const eN = Math.pow(e, t.order);
217
- jd += t.sin * eN * Math.sin(t.order * th) + t.cos * eN * Math.cos(t.order * th);
218
- }
219
- }
220
- if (jointTerms) {
221
- const lam = JOINT_LAMBDA[cp];
222
- const c2000 = cycleOf(2000);
223
- for (const t of jointTerms.terms) {
224
- const th = t.order * lam - 2 * Math.PI * t.div * cY;
225
- const th0 = t.order * lam - 2 * Math.PI * t.div * c2000;
226
- jd += t.sin * (Math.sin(th) - Math.sin(th0)) + t.cos * (Math.cos(th) - Math.cos(th0));
227
- }
228
- }
229
- jd -= selfCorr()[cp];
230
- return jd;
231
- }
232
-
233
- /** The EXACT term-by-term derivative of computeSolsticeJD — except the
234
- * drift part, which deliberately stays in the real-LOD convention (see
235
- * the file header). Neglected: the drift Euler–Maclaurin half-sample
236
- * term's own derivative (f′/2, sub-µs).
237
- * @param {number} year @param {string} [type] */
238
- function computeSolsticeYearLength(year, type) {
239
- const cp = type || 'SS';
240
- const deep = isDeepTime();
241
- const t_Ma = (2000 - year) / 1e6;
242
-
243
- const H_at = deep ? (meanHAtAgeMa(t_Ma) ?? hJ2000) : hJ2000;
244
- const dcdY = 1 / H_at;
245
- const cY = cycleOf(year);
246
-
247
- let length;
248
- if (deep && derived) {
249
- length = derived.lincoef;
250
- const mSY_at = meanYearRealLodDays(t_Ma);
251
- if (mSY_at !== null) length += (mSY_at - meanSolarYearDays);
252
- length += tropHarmonicsAt(year);
253
- let dTrop = 0;
254
- for (const [div, sinC, cosC] of tropicalHarmonics) {
255
- const k = 2 * Math.PI * div, th = k * cY;
256
- dTrop += k * dcdY * (sinC * Math.cos(th) - cosC * Math.sin(th));
257
- }
258
- length -= dTrop / 2;
259
- } else {
260
- length = meanSolarYearDays;
261
- }
262
-
263
- for (const [div, sinC, cosC] of harmonics[cp]) {
264
- const k = 2 * Math.PI * div, th = k * cY;
265
- length += k * dcdY * (sinC * Math.cos(th) - cosC * Math.sin(th));
266
- }
267
-
268
- const ecc = eccTerms && eccTerms[cp];
269
- if (ecc) {
270
- const th16 = 2 * Math.PI * 16 * cY;
271
- const thp = 2 * Math.PI * 16 * dcdY;
272
- const e = eccentricityAt(year);
273
- // de/dyear from the one law (formerly the H/16 law-of-cosines derivative).
274
- // The braid's own phase θ16 stays: it is the perihelion-OF-DATE phase the
275
- // equation of centre is seen through — an H/16 quantity by nature.
276
- const de = eccentricityRateAt(year);
277
- for (const t of ecc) {
278
- const n = t.order, nth = n * th16;
279
- const eN = Math.pow(e, n), eN1 = Math.pow(e, n - 1);
280
- length += t.sin * (n * eN1 * de * Math.sin(nth) + eN * n * thp * Math.cos(nth))
281
- + t.cos * (n * eN1 * de * Math.cos(nth) - eN * n * thp * Math.sin(nth));
282
- }
283
- }
284
-
285
- if (jointTerms) {
286
- const lam = JOINT_LAMBDA[cp];
287
- for (const t of jointTerms.terms) {
288
- const k = 2 * Math.PI * t.div;
289
- const th = t.order * lam - k * cY;
290
- length += k * dcdY * (-t.sin * Math.cos(th) + t.cos * Math.sin(th));
291
- }
292
- }
293
- return length;
294
- }
295
-
296
- /** RA where a cardinal point occurs — fully derived, zero fitted constants.
297
- * INTEGRATED phase: the formula describes where the SCENE puts the point,
298
- * and the scene's H/3 and H/8 objects rotate on integrated phase.
299
- * @param {number} year @param {string} [type] */
300
- function computeSolsticeRA(year, type) {
301
- const sinE = Math.sin(tiltMeanDeg * Math.PI / 180);
302
- const baseRA = /** @type {Record<string, number>} */ ({ SS: 90, WS: 270, VE: 0, AE: 180 })[type || 'SS'];
303
- const raMean = baseRA - raAngleDeg / sinE;
304
- const amp = inclAmplitudeDeg / sinE;
305
- const cY = cycleOf(year);
306
- const phase3 = 2 * Math.PI * 3 * cY;
307
- const phase8 = 2 * Math.PI * 8 * cY;
308
- return raMean + amp * (-Math.sin(phase3) + Math.sin(phase8));
309
- }
310
-
311
- /** Tropical year as the mean of the four cardinal intervals — the
312
- * physically correct definition (Σδ_X ≡ 0 makes the braid cancel).
313
- * @param {number} year */
314
- function computeTropicalYearLength(year) {
315
- return (computeSolsticeYearLength(year, 'SS') +
316
- computeSolsticeYearLength(year, 'WS') +
317
- computeSolsticeYearLength(year, 'VE') +
318
- computeSolsticeYearLength(year, 'AE')) / 4;
319
- }
320
-
321
- return { computeSolsticeJD, computeSolsticeYearLength, computeSolsticeRA, computeTropicalYearLength };
322
- }
323
-
324
- module.exports = { createCardinalModel, JOINT_LAMBDA };
@@ -1,141 +0,0 @@
1
- /**
2
- * The Moon-channel eccentricity line — THE shared implementation (Phase 8.2-2).
3
- *
4
- * ONE movement, FULLY DERIVED, zero solved values (doc 66 §"Framework-native
5
- * e_E"): e(t) = eccentricityBase · (1 + cos θ(t) / 2), where θ rides the H/3
6
- * wobble cycle that drives Earth's inclination. Mean = base (Law 5),
7
- * amplitude = base/2, phase anchor θ₀ = ϖ_ICRF(J2000) − 21.77° = 81.18° past
8
- * max. The observed J2000 e (−0.86%), ė (+1.7%) and the sign of ë are
9
- * PREDICTIONS of this line, not inputs.
10
- *
11
- * DOMAIN (engine-switch decision (ii) restatement, plan 02 §8): this H/3
12
- * line is the CLOCK-side e — the eclipse Sun's equation of centre (FQ-7-Sun
13
- * option C-small, doc 66 §5), the besselian Sun distance, the cardinal
14
- * braid and the scene machinery ride it. The LUNAR chain (E-factor,
15
- * modulation, channelIntegral, argument Δe²) rides the ONE deep e
16
- * (deep-ecc-channel.cjs — the engine's own ±10-Myr z-vector); the two
17
- * agree within 4.2e-5 across the historical era, and this line keeps its
18
- * E18 role as the epoch-local tangent. Eccentricity remains
19
- * frame-invariant (fixed-frame periods only); the H/16 perihelion cycle
20
- * (the H/3 rotation seen from the H/13 equinox, 13 + 3 = 16) belongs to
21
- * ϖ, not e (doc 108).
22
- *
23
- * Extracted VERBATIM from src/script.js (_FW_ECC, _fwEarthEcc,
24
- * _eCompModulation, _fwChannelIntegral, the framework branch of _fwEFactor),
25
- * which tools/lib mirrored; this file replaces both copies. The 8.2-1
26
- * alignment made the two engines bit-exact first, so this move is provably
27
- * behaviour-preserving against the lunar golden masters.
28
- *
29
- * LOAD-BEARING conventions:
30
- * - The injected `cyclesBetween` is the ENGINE'S OWN integrated-phase
31
- * function (divisor 3 on the H lattice). Each engine's deep-time/snapshot
32
- * toggle semantics ride along with it.
33
- * - `e0`/`g0` are ANCHOR CONSTS by construction, never eccAt(0): under
34
- * integrated phase, cycles(2000→2000) carries the R3 drift correction and
35
- * is not exactly zero. The tools mirror once recomputed g0 from
36
- * _fwEarthEcc(0) — unified here to the const.
37
- * - channelIntegral uses composite Simpson with step ≤ ~4,000 yr (N ≥ 2,
38
- * even) — the discretization the certified numbers were produced with.
39
- * - Past the tidal-lock asymptote (cyclesBetween → null) eccAt returns the
40
- * mean — the bounded continuation, not an error.
41
- */
42
-
43
- 'use strict';
44
-
45
- /**
46
- * @param {{
47
- * cyclesBetween: (yearA: number, yearB: number, divisorN: number) => (number | null),
48
- * eccentricityBase: number,
49
- * eccentricityJ2000?: number,
50
- * perihelionLongitudeJ2000Deg: number,
51
- * inclinationCycleAnchorDeg: number,
52
- * }} deps — cyclesBetween MUST be the engine's own H-lattice phase counter
53
- * (integrated under deep time); perihelionLongitudeJ2000Deg is ϖ_ICRF at
54
- * J2000 (102.94719°, the FULL value — a truncated copy once cost 0.684″);
55
- * inclinationCycleAnchorDeg is the H/3 inclination anchor (21.77°).
56
- */
57
- function createMoonEccChannel({
58
- cyclesBetween,
59
- eccentricityBase,
60
- perihelionLongitudeJ2000Deg,
61
- inclinationCycleAnchorDeg,
62
- eccentricityJ2000,
63
- }) {
64
- const th0 = (perihelionLongitudeJ2000Deg - inclinationCycleAnchorDeg) * Math.PI / 180;
65
- // ECCENTRICITY UNIFICATION (plan IP-eccentricity-unification, D1 = form
66
- // (e)): this line is the model's ONE eccentricity law for every consumer
67
- // (eclipse Sun, Sun distance, Moon E-factor and T² channel, cardinal
68
- // braid, scene). When the observed J2000 eccentricity is supplied the
69
- // mean is DERIVED from it and the shared anchor — base' = e0 / (1 +
70
- // cos θ₀ / 2) = 0.015520 — so e(J2000) is exact by construction; the
71
- // legacy form (mean = the Law-5 base, e(J2000) a −0.86% prediction) is
72
- // kept for callers that do not supply it.
73
- const mean = eccentricityJ2000 !== undefined
74
- ? eccentricityJ2000 / (1 + Math.cos(th0) / 2)
75
- : eccentricityBase;
76
- const amplitude = mean / 2;
77
- const e0 = mean + amplitude * Math.cos(th0); // J2000 anchor, exact by construction
78
- const g0 = Math.pow(1 - e0 * e0, -1.5); // (1−e²)^(−3/2) at the anchor
79
-
80
- /** e_E on the H/3 line at tYr years from J2000 (negative = past).
81
- * Returns the mean past the tidal-lock asymptote.
82
- * @param {number} tYr @returns {number} */
83
- function eccAt(tYr) {
84
- const cycles = cyclesBetween(2000, 2000 + tYr, 3);
85
- if (cycles === null) return mean;
86
- return mean + amplitude * Math.cos(th0 + 2 * Math.PI * cycles);
87
- }
88
-
89
- /** [g(e(t))/g₀]^s — the perigee/node rate modulation, ≡ 1 at J2000.
90
- * tMa is age in Myr (positive = past, the deep-time chain convention).
91
- * @param {number} tMa @param {number} s @returns {number} */
92
- function modulation(tMa, s) {
93
- if (tMa === 0) return 1;
94
- const e = eccAt(-tMa * 1e6);
95
- return Math.pow(Math.pow(1 - e * e, -1.5) / g0, s);
96
- }
97
-
98
- /** ∫₀ᵀ [(g(e(t′))/g₀)^s − 1] dt′ in Julian centuries — the phase-aware
99
- * channel-rate integral (the old frozen-κ T²/T³ Taylor coefficients were
100
- * this integral's J2000 truncation). Composite Simpson, step ≤ ~4 kyr.
101
- * @param {number} T @param {number} s @returns {number} */
102
- function channelIntegral(T, s) {
103
- if (T === 0) return 0;
104
- /** @param {number} t */
105
- const f = (t) => {
106
- const e = eccAt(t * 100); // t in cy → years
107
- return Math.pow(Math.pow(1 - e * e, -1.5) / g0, s) - 1;
108
- };
109
- const N = Math.max(2, 2 * Math.ceil(Math.abs(T) * 100 / 8000));
110
- const h = T / N;
111
- let sum = f(0) + f(T);
112
- for (let i = 1; i < N; i++) sum += f(i * h) * (i % 2 ? 4 : 2);
113
- return sum * h / 3;
114
- }
115
-
116
- /** Bounded Meeus E-factor: E ≡ e_E(t)/e_E(J2000), value 1 at J2000 by
117
- * construction. The caller supplies tYr = days-from-J2000 / mean solar
118
- * year; the pure-Meeus A/B polynomial branch stays ENGINE-LOCAL with its
119
- * mode toggle. @param {number} tYr @returns {number} */
120
- function eFactorAt(tYr) {
121
- return eccAt(tYr) / e0;
122
- }
123
-
124
- /** de/dyear of the line at tYr years from J2000 (the cardinal braid's
125
- * equation-of-centre derivative): −amplitude·sin(θ)·dθ/dt, with dθ/dt
126
- * from the phase counter's two-point difference over ±1 yr (exact for a
127
- * linear phase, ppm-class under the integrated one). Returns 0 past the
128
- * tidal-lock asymptote, where eccAt returns the mean.
129
- * @param {number} tYr @returns {number} */
130
- function eccRateAt(tYr) {
131
- const c = cyclesBetween(2000, 2000 + tYr, 3);
132
- if (c === null) return 0;
133
- const cp = cyclesBetween(2000, 2000 + tYr + 1, 3), cm = cyclesBetween(2000, 2000 + tYr - 1, 3);
134
- const dth = 2 * Math.PI * ((cp === null || cm === null) ? 0 : (cp - cm) / 2);
135
- return -amplitude * Math.sin(th0 + 2 * Math.PI * c) * dth;
136
- }
137
-
138
- return { eccAt, eccRateAt, modulation, channelIntegral, eFactorAt, th0, amplitude, mean, e0, g0 };
139
- }
140
-
141
- module.exports = { createMoonEccChannel };
@@ -1,186 +0,0 @@
1
- /**
2
- * Integrated-phase machinery — THE shared implementation (Phase 7.1).
3
- *
4
- * The cumulative ∫1/H(t)dt table and everything derived from it: cycle counts,
5
- * the J2000 drift correction, and the inverse lookup. Extracted VERBATIM from
6
- * tools/lib/deep-time.js, which was itself the port of src/script.js — this
7
- * file replaces both copies (and is the reference the website's deepTime.ts
8
- * must match; Phase E measured what happens when it doesn't).
9
- *
10
- * CommonJS on purpose: `tools/lib` is CJS and cannot require an ESM module
11
- * synchronously; ESM (`src/script.js`, bundlers) imports CJS natively. One
12
- * file, every engine.
13
- *
14
- * THE CONVENTIONS ARE LOAD-BEARING — the fitted coefficients were produced
15
- * against THIS discretization, so the numerics ship with the coefficients:
16
- *
17
- * - TRAPEZOID rule on 10-kyr cells with LINEAR interpolation between cells.
18
- * An analytically better integrator is WRONG here: the website's adaptive
19
- * Simpson disagreed by ~1e-6 cycles at −280 kyr = ~18 s of cardinal-point
20
- * JD through the ×16 braid phase (Gate E measured 24.7 s before the fix).
21
- * - R2: the table must be built under the LATTICE α — the CONSTANT moment of
22
- * inertia, not the climate-modulated α(t). The `holisticHAtAgeMa` the
23
- * caller injects must already be pinned (see each engine's construction
24
- * site). The always-on climate modulation integrates into a linear phase
25
- * drift (the same ~18 s class — the dominant Phase E bug).
26
- * - R3: the drift-correction anchor is identified by CALL SHAPE, never by
27
- * distance from J2000 — the old distance heuristic put a 2·drift
28
- * = 6.80e-5-cycle STEP DISCONTINUITY at yearB == yearA, worth 0.393 d of
29
- * apsis displacement and 100% of 6d's anomalistic residual.
30
- * - The table zero sits at the cell nearest `tableAnchorYear` (2000.5,
31
- * startmodelYear); the drift reference is `driftRefYear`
32
- * (startModelYearWithCorrection ≈ 2000.4977) — TWO different constants,
33
- * half a day apart. Do not "unify" them.
34
- */
35
-
36
- 'use strict';
37
-
38
- /**
39
- * @param {{
40
- * holisticHAtAgeMa: (tMa: number) => (number | null),
41
- * tableAnchorYear: number,
42
- * driftRefYear: number,
43
- * hJ2000: number,
44
- * yearMin?: number,
45
- * yearMax?: number,
46
- * stepYears?: number,
47
- * }} cfg — holisticHAtAgeMa MUST be the lattice-α form; tableAnchorYear is
48
- * startmodelYear (2000.5, table zero + t_Ma convention); driftRefYear is
49
- * startModelYearWithCorrection (the R3 drift anchor); hJ2000 the snapshot
50
- * rate the drift compares against; 10-kyr cells match both engines'
51
- * historic tables.
52
- */
53
- function createPhaseMachinery({
54
- holisticHAtAgeMa,
55
- tableAnchorYear,
56
- driftRefYear,
57
- hJ2000,
58
- yearMin = -500e6,
59
- yearMax = 500e6,
60
- stepYears = 10000,
61
- }) {
62
- /** @type {Float64Array | null} */
63
- let table = null;
64
- let j2000Idx = -1;
65
-
66
- function ensureTable() {
67
- if (table !== null) return;
68
- const N = Math.ceil((yearMax - yearMin) / stepYears) + 1;
69
- const t = new Float64Array(N);
70
- j2000Idx = Math.round((tableAnchorYear - yearMin) / stepYears);
71
-
72
- /** @param {number} year */
73
- const invH = (year) => {
74
- const t_Ma = (tableAnchorYear - year) / 1e6;
75
- const H = holisticHAtAgeMa(t_Ma);
76
- return H === null ? null : 1 / H;
77
- };
78
-
79
- t[j2000Idx] = 0;
80
- let prev = invH(yearMin + j2000Idx * stepYears);
81
- for (let i = j2000Idx + 1; i < N; i++) {
82
- const year_i = yearMin + i * stepYears;
83
- const curr = invH(year_i);
84
- t[i] = (prev !== null && curr !== null)
85
- ? t[i - 1] + 0.5 * (prev + curr) * stepYears
86
- : NaN;
87
- prev = curr;
88
- }
89
- prev = invH(yearMin + j2000Idx * stepYears);
90
- for (let i = j2000Idx - 1; i >= 0; i--) {
91
- const year_i = yearMin + i * stepYears;
92
- const curr = invH(year_i);
93
- t[i] = (prev !== null && curr !== null)
94
- ? t[i + 1] - 0.5 * (prev + curr) * stepYears
95
- : NaN;
96
- prev = curr;
97
- }
98
- table = t;
99
- }
100
-
101
- /** Cumulative ∫1/H at a year (linear interpolation). null outside the range
102
- * or where the physics is undefined (past the tidal-lock asymptote).
103
- * @param {number} year @returns {number | null} */
104
- function cumulAtYear(year) {
105
- ensureTable();
106
- if (year < yearMin || year > yearMax) return null;
107
- const t = /** @type {Float64Array} */ (table);
108
- const idx_f = (year - yearMin) / stepYears;
109
- const idx_lo = Math.floor(idx_f);
110
- const idx_hi = Math.min(idx_lo + 1, t.length - 1);
111
- const v_lo = t[idx_lo];
112
- const v_hi = t[idx_hi];
113
- if (Number.isNaN(v_lo) || Number.isNaN(v_hi)) return null;
114
- return v_lo + (idx_f - idx_lo) * (v_hi - v_lo);
115
- }
116
-
117
- /** ∫_{yearA}^{yearB} 1/H(t') dt'. null if either endpoint is out of domain.
118
- * @param {number} yearA @param {number} yearB @returns {number | null} */
119
- function integralBetween(yearA, yearB) {
120
- if (yearA === yearB) return 0;
121
- const cA = cumulAtYear(yearA);
122
- const cB = cumulAtYear(yearB);
123
- if (cA === null || cB === null) return null;
124
- return cB - cA;
125
- }
126
-
127
- /** How far the integrated form has drifted from the J2000-snapshot form over
128
- * (yearA → driftRefYear). Subtracting it at the anchor restores the
129
- * snapshot-fitted harmonic calibration at J2000 without changing the
130
- * integrated form's shape at deep time.
131
- * @param {number} yearA @returns {number} */
132
- function j2000Drift(yearA) {
133
- const integral = integralBetween(yearA, driftRefYear);
134
- if (integral === null) return 0;
135
- const snapshot = (driftRefYear - yearA) / hJ2000;
136
- return integral - snapshot;
137
- }
138
-
139
- /** Total cycles between two years for a cycle of period H/divisorN.
140
- * R3: the anchor is identified from the CALL SHAPE — two shapes exist,
141
- * (anchor, movingYear, N) and (J2000, anchor, N) where yearA IS the J2000
142
- * reference itself. The correction depends only on a FIXED endpoint, so it
143
- * is constant across any scan. Returns null past the tidal-lock asymptote.
144
- * @param {number} yearA @param {number} yearB @param {number} divisorN
145
- * @returns {number | null} */
146
- function cyclesBetween(yearA, yearB, divisorN) {
147
- const integral = integralBetween(yearA, yearB);
148
- if (integral === null) return null;
149
- const anchorIsA = (yearA !== driftRefYear);
150
- const correction = anchorIsA ? j2000Drift(yearA) : -j2000Drift(yearB);
151
- return divisorN * (integral - correction);
152
- }
153
-
154
- /** Inverse of cumulAtYear — the year at a given cumulative ∫1/H (binary
155
- * search over the monotone table). null outside the table domain.
156
- * @param {number} targetCumul @returns {number | null} */
157
- function yearAtCumul(targetCumul) {
158
- ensureTable();
159
- const t = /** @type {Float64Array} */ (table);
160
- const N = t.length;
161
- let lo = 0, hi = N - 1;
162
- while (lo < N && Number.isNaN(t[lo])) lo++;
163
- while (hi >= 0 && Number.isNaN(t[hi])) hi--;
164
- if (lo >= hi) return null;
165
- if (targetCumul < t[lo] || targetCumul > t[hi]) return null;
166
- while (lo < hi - 1) {
167
- const mid = (lo + hi) >> 1;
168
- if (t[mid] <= targetCumul) lo = mid; else hi = mid;
169
- }
170
- const v_lo = t[lo], v_hi = t[hi];
171
- const frac = (v_lo === v_hi) ? 0 : (targetCumul - v_lo) / (v_hi - v_lo);
172
- return yearMin + (lo + frac) * stepYears;
173
- }
174
-
175
- /** Grid geometry — consumed by the engines' days/pos tables, which share
176
- * this grid but keep their own integrands (they move at Phase 8). */
177
- function grid() {
178
- ensureTable();
179
- const t = /** @type {Float64Array} */ (table);
180
- return { yearMin, yearMax, stepYears, j2000Idx, length: t.length };
181
- }
182
-
183
- return { ensureTable, cumulAtYear, integralBetween, j2000Drift, cyclesBetween, yearAtCumul, grid };
184
- }
185
-
186
- module.exports = { createPhaseMachinery };