@essrt/physics 22.1.0 → 23.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.
package/types/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { createPlanetModel } from "./planets/model.cjs";
18
18
  export { PLANET_DISPLAY_NAMES } from "./planets/display-names.cjs";
19
19
  export { createDeltaTCycles } from "./deltat/cycles.cjs";
20
20
  export { createDeepTimeLod } from "./deltat/deep-time.cjs";
21
+ export { createMoonDistanceHistory } from "./deltat/moon-distance-history.cjs";
21
22
  export { deltaTEspenakMeeusCanonSeconds } from "./deltat/historical.cjs";
22
23
  export { createEclipseFinders } from "./eclipse/finders.cjs";
23
24
  export function createModel(constants?: Constants, opts?: {
@@ -66,5 +67,5 @@ export { CHAIN_ARTIFACT, CHAIN_ARTIFACT_HASH } from "./planets/chain-artifact.js
66
67
  export { DEEP_MODES_ARTIFACT, DEEP_MODES_ARTIFACT_HASH } from "./moon/deep-modes-artifact.cjs";
67
68
  export { createYearLengths, computeMeanTropicalYearJ2000Days, ONE_FAMILY_WINDOW_YEARS } from "./earth/year-lengths.cjs";
68
69
  export { computeSolarTorqueShare, createComposedPrecession } from "./earth/precession-composed.cjs";
69
- export { createMoonRecessionHistory, createSolarChannelBudget } from "./deltat/recession-history.cjs";
70
+ export { MOON_DISTANCE_ARTIFACT, MOON_DISTANCE_ARTIFACT_HASH } from "./deltat/moon-distance-artifact.cjs";
70
71
  export { evalClimateL1OrbitalPermil, laggedL1Terms, createAlphaGiaChannel } from "./climate/l1-orbital.cjs";
@@ -12,9 +12,10 @@ export type RawEpochConstants = {
12
12
  * km/s (fallback 299792458 m/s if falsy)
13
13
  */
14
14
  speedOfLightKmPerS: number;
15
+ /**
16
+ * the LLR recession rate — the cited distance history's J2000 slope
17
+ */
15
18
  alpha1PerMa: number;
16
- alpha3PerMa3: number;
17
- alpha4PerMa4: number;
18
19
  meanSiderealYearSeconds: number;
19
20
  /**
20
21
  * ω₀: the J2000 day length, SI seconds (the SI day since plan 09)
@@ -1,6 +1,7 @@
1
- export function createEpochPrimitives({ params: p, alphaAtAgeMa }: {
1
+ export function createEpochPrimitives({ params: p, alphaAtAgeMa, moonDistanceMetresAtAge }: {
2
2
  params: EpochParams;
3
3
  alphaAtAgeMa: (tMa: number) => number;
4
+ moonDistanceMetresAtAge: (tMa: number) => number | null;
4
5
  }): EpochPrimitives;
5
6
  export type EpochParams = {
6
7
  /**
@@ -8,11 +9,9 @@ export type EpochParams = {
8
9
  */
9
10
  epochYear: number;
10
11
  /**
11
- * Moon recession, LLR-anchored
12
+ * Moon recession, LLR-anchored (the J2000 slope of the cited history)
12
13
  */
13
14
  alpha1PerMa: number;
14
- alpha3PerMa3: number;
15
- alpha4PerMa4: number;
16
15
  /**
17
16
  * a_Moon at J2000, metres
18
17
  */
@@ -47,7 +46,10 @@ export type EpochParams = {
47
46
  };
48
47
  export type EpochPrimitives = {
49
48
  tMa: (year: number) => number;
50
- moonDistanceMetres: (year: number) => number;
49
+ /**
50
+ * - null beyond the cited history's span
51
+ */
52
+ moonDistanceMetres: (year: number) => number | null;
51
53
  lodSeconds: (year: number) => number | null;
52
54
  siderealYearSeconds: (year: number) => number;
53
55
  /**
@@ -2,9 +2,6 @@
2
2
  * @param {{
3
3
  * constants: {
4
4
  * aMoonNowMetres: number,
5
- * alpha1PerMa: number,
6
- * alpha3PerMa3: number,
7
- * alpha4PerMa4: number,
8
5
  * gmEarthMoonM3PerS2: number,
9
6
  * massRatioEarthMoon: number,
10
7
  * moonSiderealMonthInputDays: number,
@@ -20,7 +17,7 @@
20
17
  * meanSiderealYearSecondsAtAge: (tMa: number) => number,
21
18
  * precessionPeriodYearsAtAge: (tMa: number) => (number | null),
22
19
  * modulation: (tMa: number, s: number) => number,
23
- * distanceMetresAtAge?: (tMa: number) => number,
20
+ * distanceMetresAtAge: (tMa: number) => number|null,
24
21
  * },
25
22
  * }} deps — fns are the ENGINE'S OWN layer-0/1 evaluators and the shared
26
23
  * eccentricity channel's modulation; constants are the engine's derived
@@ -30,16 +27,13 @@
30
27
  * 5c — the tropical month's equinox turn, formerly the counter's 13/H(t),
31
28
  * 0.086 % slow: the chain's J2000 tropical month sat 5.85 ms above the IAU
32
29
  * input, the 1/T_p form is within 0.1 ms of it).
33
- * fns.distanceMetresAtAge, when present, OVERRIDES the internal quartic —
34
- * the Driver-1½ regime-aware history (recession-history.cjs); absent, the
35
- * internal quartic is the pure twin (bit-identical for t ≤ jointMa).
30
+ * fns.distanceMetresAtAge is the engine's CITED distance history
31
+ * (deltat/moon-distance-history; plan 12 option A — the internal quartic
32
+ * twin this chain carried until then is deleted; null beyond the span).
36
33
  */
37
34
  export function createMoonMonthChain({ constants, fns }: {
38
35
  constants: {
39
36
  aMoonNowMetres: number;
40
- alpha1PerMa: number;
41
- alpha3PerMa3: number;
42
- alpha4PerMa4: number;
43
37
  gmEarthMoonM3PerS2: number;
44
38
  massRatioEarthMoon: number;
45
39
  moonSiderealMonthInputDays: number;
@@ -55,11 +49,11 @@ export function createMoonMonthChain({ constants, fns }: {
55
49
  meanSiderealYearSecondsAtAge: (tMa: number) => number;
56
50
  precessionPeriodYearsAtAge: (tMa: number) => (number | null);
57
51
  modulation: (tMa: number, s: number) => number;
58
- distanceMetresAtAge?: (tMa: number) => number;
52
+ distanceMetresAtAge: (tMa: number) => number | null;
59
53
  };
60
54
  }): {
61
- distanceMetresAtAge: (tMa: number) => number;
62
- distanceKmAtAge: (tMa: number) => number;
55
+ distanceMetresAtAge: (tMa: number) => number | null;
56
+ distanceKmAtAge: (tMa: number) => number | null;
63
57
  solarDeltaAKmAtAge: (tMa: number, aApparentKm: number) => number | null;
64
58
  distanceCorrectedKmAtAge: (tMa: number) => number | null;
65
59
  siderealMonthSecondsAtAge: (tMa: number) => number | null;
@@ -1,169 +0,0 @@
1
- /**
2
- * Regime-aware lunar recession history + the solar angular-momentum
3
- * channels (Driver 1½) — THE shared implementation.
4
- *
5
- * Background (docs/99 §"The ungated mid-Precambrian window", docs/106 §D,
6
- * and the research record in tools/explore/{farhat-divergence-probe,
7
- * fit-regime-recession}.js — the hand-shaped regime-aware-recession-study.js
8
- * was deleted at plan 09 step 7e): Farhat 2022's
9
- * central result is that lunar recession followed a resonant STAIRCASE —
10
- * a single smooth quartic anchored at the modern LLR rate, the gated
11
- * 0–650 Ma era and the Roche crossing cannot follow it through the
12
- * 1–3.5 Ga window (−8.9σ Joffre, −11.4σ Moodies). This module carries
13
- * the regime-aware history:
14
- *
15
- * t ≤ jointMa — the calibrated quartic, BIT-IDENTICAL (the
16
- * Wells/Wu-gated era; its α set is the calibrated
17
- * effective closed-budget pair and is not touched).
18
- * jointMa..genesisMa — monotone cubic Hermite (Fritsch–Carlson) through
19
- * the fitted knots, value- AND slope-continuous at
20
- * the joint (analytic polynomial derivative), ending
21
- * at the rigid Roche limit at genesisMa.
22
- * t ≥ genesisMa — clamped at the Roche value (the Moon does not
23
- * exist beyond its genesis; boundary value, not an
24
- * extrapolation).
25
- *
26
- * The solar channels (explicit ONLY beyond jointMa — inside the gated era
27
- * they are absorbed by the α calibration, and applying them there without
28
- * recalibration measurably breaks Wells; see the study's variant 3):
29
- * leak τ_s = β₀ (a/a₀)⁶ · τ_moon — ocean solar tide (M²/AU⁶ ratio
30
- * scaling of the semidiurnal torque)
31
- * pump τ_t = f_p · (τ_moon + τ_s) inside [pumpStartMa, pumpEndMa] —
32
- * the insolation-driven atmospheric thermal tide
33
- * (Zahnle–Walker 1987; Bartlett–Stevenson 2016;
34
- * Mitchell–Kirscher 2023). The FITTED f_p ≈ 0.45 over a ~300 Myr
35
- * window (degenerate with 0.15 × 900 Myr — the data constrain
36
- * the INTEGRATED pump, ~2σ preferred over zero, Zhou 2024's
37
- * contrary reading honestly noted in the fit record).
38
- *
39
- * Fit provenance: tools/explore/fit-regime-recession.js — 11 anchors
40
- * (Lantink/Joffre 2022 · Weeli Wolli · Zhou 2024 ×3 paired a+LOD ·
41
- * Meyers–Malinverno 2018 · Nanfen 2023 · Moodies), all ≤1.3σ; genesis
42
- * pinned at the shipped 4498 Ma Roche crossing (the genesis profile is
43
- * flat — the data do not constrain it). The knot values ARE the shipped
44
- * definition (metre-rounded at fit time, not truncated from a more
45
- * precise source).
46
- */
47
-
48
- 'use strict';
49
-
50
- /**
51
- * @typedef {Object} RecessionRegime
52
- * @property {number} jointMa - regime boundary; quartic below, spline above
53
- * @property {number[]} knotAgesMa - interior knot ages (ascending)
54
- * @property {number[]} knotDistancesKm - fitted knot distances
55
- * @property {number} genesisMa - the Roche-crossing epoch
56
- * @property {number} rocheLimitKm - the rigid Roche endpoint
57
- */
58
-
59
- /**
60
- * @param {{
61
- * aMoonNowMetres: number,
62
- * alpha1PerMa: number, alpha3PerMa3: number, alpha4PerMa4: number,
63
- * regime: RecessionRegime,
64
- * }} deps
65
- * @returns {{ distanceMetresAtAge: (tMa: number) => number }}
66
- */
67
- function createMoonRecessionHistory(deps) {
68
- const { aMoonNowMetres: aNow, alpha1PerMa: a1, alpha3PerMa3: a3, alpha4PerMa4: a4 } = deps;
69
- const R = deps.regime;
70
-
71
- /** The calibrated quartic (identical expression to every consumer).
72
- * @param {number} tMa @returns {number} */
73
- const polyM = (tMa) => aNow * (1 + a1 * tMa + a3 * tMa * tMa * tMa + a4 * tMa * tMa * tMa * tMa);
74
- /** Analytic slope of the quartic, m/Ma.
75
- * @param {number} tMa @returns {number} */
76
- const polySlopeM = (tMa) => aNow * (a1 + 3 * a3 * tMa * tMa + 4 * a4 * tMa * tMa * tMa);
77
-
78
- // Knot chain: joint (value+slope from the quartic) → fitted knots → Roche.
79
- const ts = [R.jointMa, ...R.knotAgesMa, R.genesisMa];
80
- const vs = [polyM(R.jointMa), ...R.knotDistancesKm.map((km) => km * 1000), R.rocheLimitKm * 1000];
81
- const n = ts.length;
82
- const d = [];
83
- for (let i = 0; i < n - 1; i++) d.push((vs[i + 1] - vs[i]) / (ts[i + 1] - ts[i]));
84
- const m = new Array(n);
85
- m[0] = polySlopeM(R.jointMa);
86
- for (let i = 1; i < n - 1; i++) m[i] = (d[i - 1] * d[i] <= 0) ? 0 : (d[i - 1] + d[i]) / 2;
87
- m[n - 1] = d[n - 2];
88
- // Fritsch–Carlson monotonicity limiter — the history must be monotone
89
- // into the past (the Moon only recedes on the mean).
90
- for (let i = 0; i < n - 1; i++) {
91
- if (d[i] === 0) { m[i] = 0; m[i + 1] = 0; continue; }
92
- const a = m[i] / d[i], b = m[i + 1] / d[i];
93
- const s = a * a + b * b;
94
- if (s > 9) { const tau = 3 / Math.sqrt(s); m[i] = tau * a * d[i]; m[i + 1] = tau * b * d[i]; }
95
- }
96
-
97
- /** @param {number} tMa @returns {number} metres */
98
- function distanceMetresAtAge(tMa) {
99
- if (tMa <= R.jointMa) return polyM(tMa);
100
- if (tMa >= R.genesisMa) return R.rocheLimitKm * 1000;
101
- let i = 0;
102
- while (i < n - 2 && tMa > ts[i + 1]) i++;
103
- const h = ts[i + 1] - ts[i], s = (tMa - ts[i]) / h, s2 = s * s, s3 = s2 * s;
104
- return (2 * s3 - 3 * s2 + 1) * vs[i] + (s3 - 2 * s2 + s) * h * m[i]
105
- + (-2 * s3 + 3 * s2) * vs[i + 1] + (s3 - s2) * h * m[i + 1];
106
- }
107
-
108
- return { distanceMetresAtAge };
109
- }
110
-
111
- /**
112
- * The solar angular-momentum channels: L_EM(t) integrated backward from
113
- * J2000 with the ocean leak and the thermal-tide pump, explicit only
114
- * beyond jointMa. Absent this module, every consumer's L_EM is the J2000
115
- * constant — which remains the exact behaviour for t ≤ jointMa.
116
- *
117
- * @param {{
118
- * lTotalJ2000KgM2S: number,
119
- * mMoonAloneKg: number, gmEmM3PerS2: number, eFactorMoon: number,
120
- * beta0: number,
121
- * pumpStartMa: number, pumpEndMa: number, pumpFactor: number,
122
- * jointMa: number, genesisMa: number,
123
- * distanceMetresAtAge: (tMa: number) => number,
124
- * stepMa?: number,
125
- * }} deps
126
- * @returns {{ lEmAtAgeKgm2S: (tMa: number) => number }}
127
- */
128
- function createSolarChannelBudget(deps) {
129
- const STEP = deps.stepMa ?? 5;
130
- /** @param {number} aM @returns {number} */
131
- const lOrb = (aM) => deps.mMoonAloneKg * Math.sqrt(deps.gmEmM3PerS2 * aM) * deps.eFactorMoon;
132
- const a0 = deps.distanceMetresAtAge(0);
133
-
134
- /** @type {Float64Array|null} */
135
- let table = null;
136
- const build = () => {
137
- const nSteps = Math.ceil(deps.genesisMa / STEP) + 1;
138
- const t = new Float64Array(nSteps);
139
- let L = deps.lTotalJ2000KgM2S;
140
- for (let i = 0; i < nSteps; i++) {
141
- t[i] = L;
142
- const tMa = i * STEP;
143
- const dLorb = lOrb(deps.distanceMetresAtAge(tMa)) - lOrb(deps.distanceMetresAtAge(tMa + STEP));
144
- const mid = tMa + STEP / 2;
145
- const leakDt = mid > deps.jointMa ? deps.beta0 * Math.pow(deps.distanceMetresAtAge(tMa) / a0, 6) * dLorb : 0;
146
- const pumpDt = (mid > deps.pumpStartMa && mid < deps.pumpEndMa) ? deps.pumpFactor * (dLorb + leakDt) : 0;
147
- L = L - (pumpDt - leakDt);
148
- }
149
- return t;
150
- };
151
-
152
- /** @param {number} tMa @returns {number} */
153
- function lEmAtAgeKgm2S(tMa) {
154
- // The channels are zero below jointMa by construction — return the
155
- // constant EXACTLY there (no table interpolation), so the gated era
156
- // stays bit-identical to the closed-budget engine (the 1-ULP fixture
157
- // gates depend on this).
158
- if (tMa <= deps.jointMa) return deps.lTotalJ2000KgM2S;
159
- if (table === null) table = build();
160
- const x = tMa / STEP;
161
- const i = Math.min(Math.floor(x), table.length - 2);
162
- const f = Math.min(x - i, 1);
163
- return table[i] * (1 - f) + table[i + 1] * f;
164
- }
165
-
166
- return { lEmAtAgeKgm2S };
167
- }
168
-
169
- module.exports = { createMoonRecessionHistory, createSolarChannelBudget };
@@ -1,80 +0,0 @@
1
- export type RecessionRegime = {
2
- /**
3
- * - regime boundary; quartic below, spline above
4
- */
5
- jointMa: number;
6
- /**
7
- * - interior knot ages (ascending)
8
- */
9
- knotAgesMa: number[];
10
- /**
11
- * - fitted knot distances
12
- */
13
- knotDistancesKm: number[];
14
- /**
15
- * - the Roche-crossing epoch
16
- */
17
- genesisMa: number;
18
- /**
19
- * - the rigid Roche endpoint
20
- */
21
- rocheLimitKm: number;
22
- };
23
- /**
24
- * @typedef {Object} RecessionRegime
25
- * @property {number} jointMa - regime boundary; quartic below, spline above
26
- * @property {number[]} knotAgesMa - interior knot ages (ascending)
27
- * @property {number[]} knotDistancesKm - fitted knot distances
28
- * @property {number} genesisMa - the Roche-crossing epoch
29
- * @property {number} rocheLimitKm - the rigid Roche endpoint
30
- */
31
- /**
32
- * @param {{
33
- * aMoonNowMetres: number,
34
- * alpha1PerMa: number, alpha3PerMa3: number, alpha4PerMa4: number,
35
- * regime: RecessionRegime,
36
- * }} deps
37
- * @returns {{ distanceMetresAtAge: (tMa: number) => number }}
38
- */
39
- export function createMoonRecessionHistory(deps: {
40
- aMoonNowMetres: number;
41
- alpha1PerMa: number;
42
- alpha3PerMa3: number;
43
- alpha4PerMa4: number;
44
- regime: RecessionRegime;
45
- }): {
46
- distanceMetresAtAge: (tMa: number) => number;
47
- };
48
- /**
49
- * The solar angular-momentum channels: L_EM(t) integrated backward from
50
- * J2000 with the ocean leak and the thermal-tide pump, explicit only
51
- * beyond jointMa. Absent this module, every consumer's L_EM is the J2000
52
- * constant — which remains the exact behaviour for t ≤ jointMa.
53
- *
54
- * @param {{
55
- * lTotalJ2000KgM2S: number,
56
- * mMoonAloneKg: number, gmEmM3PerS2: number, eFactorMoon: number,
57
- * beta0: number,
58
- * pumpStartMa: number, pumpEndMa: number, pumpFactor: number,
59
- * jointMa: number, genesisMa: number,
60
- * distanceMetresAtAge: (tMa: number) => number,
61
- * stepMa?: number,
62
- * }} deps
63
- * @returns {{ lEmAtAgeKgm2S: (tMa: number) => number }}
64
- */
65
- export function createSolarChannelBudget(deps: {
66
- lTotalJ2000KgM2S: number;
67
- mMoonAloneKg: number;
68
- gmEmM3PerS2: number;
69
- eFactorMoon: number;
70
- beta0: number;
71
- pumpStartMa: number;
72
- pumpEndMa: number;
73
- pumpFactor: number;
74
- jointMa: number;
75
- genesisMa: number;
76
- distanceMetresAtAge: (tMa: number) => number;
77
- stepMa?: number;
78
- }): {
79
- lEmAtAgeKgm2S: (tMa: number) => number;
80
- };