@essrt/physics 4.9.0 → 6.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.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The composed lunisolar precession rate — ONE home (holisticuniverse plan 06,
3
+ * D6: the physical ψ̇(t) of falsification leg 1).
4
+ *
5
+ * Earth's axial precession rate is the two tiers composed: the spin ω(t) from
6
+ * the tidal chain (LOD history) carries BOTH torques, and the lunar torque
7
+ * additionally grows with the recession history as (a₀/a_M(t))³:
8
+ *
9
+ * ψ̇(t) = [ω(t)/ω₀] · p₀ · [ f_S + (1 − f_S) · (a₀/a_M(t))³ ]
10
+ *
11
+ * with p₀ the J2000 rate and f_S the solar fraction of the J2000 torque,
12
+ * derived from the shared constants (two-body point-mass torques with the
13
+ * (1 − e²)^(−3/2) eccentricity factors and the lunar-inclination factor
14
+ * 1 − 3/2·sin² i_M; ≈ 0.316, the textbook value). At J2000 every factor is 1.
15
+ *
16
+ * THE ANCHOR p₀ (plan 06 S5 — one J2000 precession reading): p₀ is the
17
+ * model's DERIVED J2000 rate, 1,296,000/axial0 with axial0 the certified
18
+ * year-length laws' beat at 2000 (sid/(sid − trop) = 25,771.4 yr = 50.2883
19
+ * ″/yr, IAU 50.2879 to 8×10⁻⁶) — the SAME anchor the hybrid obliquity
20
+ * self-anchors on. It is NOT 1,296,000/(H/13): H₀ = 335,317 was fitted on
21
+ * the 1246 AD perihelion–solstice alignment (the perihelion-of-date beat),
22
+ * so H₀/13 = 25,793.6 yr is the fit anchor's reading, 0.086 % slow, and
23
+ * is not a period of anything the model computes (nor a window mean: the
24
+ * published period averages 25,598 yr over ±26 kyr). The unit H(t) =
25
+ * H_era/[torque term] (deltat/deep-time.cjs hAtAge) scales WITH the
26
+ * composed period — H(t)/T_p(t) = H₀/axial0 = 13.011 at every epoch — but
27
+ * is not 13 of them; the 13 is a fit-era label that leaves the physics.
28
+ * The pre-Phase-3 structural clock 13·1,296,000/H_era(t) (pure ω-scaling,
29
+ * H_era = H₀·LOD/LOD₀) survives only as the FROZEN ERA CLOCK's named phase
30
+ * convention (eraClockHAtAge) and in docs/retired-record.md, with its
31
+ * record: it read 70.9 ″/yr against Lantink et al. 2022's 108.6 ± 8.5 at
32
+ * 2.46 Ga and 65.2 against Meyers & Malinverno 2018's 85.79 ± 2.72 at
33
+ * 1.4 Ga, where the composed rate reads 104.6 and 86.6.
34
+ *
35
+ * Pure factory: the tidal chain is injected (each runtime's own LOD and
36
+ * moon-distance evaluators), no artifact reads, no globals.
37
+ */
38
+
39
+ 'use strict';
40
+
41
+ const D2R = Math.PI / 180;
42
+
43
+ /**
44
+ * Solar fraction of the J2000 precession torque from the shared constants.
45
+ * @param {{
46
+ * gmSunKm3S2: number, auKm: number, earthEccentricity: number,
47
+ * gmMoonKm3S2: number, moonDistanceKm: number, moonEccentricity: number, moonInclinationDeg: number,
48
+ * }} c
49
+ * @returns {number} f_S in [0, 1]
50
+ */
51
+ function computeSolarTorqueShare(c) {
52
+ const sol = (c.gmSunKm3S2 / c.auKm ** 3) * Math.pow(1 - c.earthEccentricity * c.earthEccentricity, -1.5);
53
+ const lun = (c.gmMoonKm3S2 / c.moonDistanceKm ** 3) * Math.pow(1 - c.moonEccentricity * c.moonEccentricity, -1.5)
54
+ * (1 - 1.5 * Math.sin(c.moonInclinationDeg * D2R) ** 2);
55
+ return sol / (sol + lun);
56
+ }
57
+
58
+ /**
59
+ * @param {{
60
+ * p0ArcsecPerYr: number | (() => number),
61
+ * solarShare: number,
62
+ * lodSecondsAtAge: (tMa: number) => (number | null),
63
+ * lodJ2000Seconds: number,
64
+ * moonDistanceMetresAtAge: (tMa: number) => (number | null),
65
+ * moonDistanceJ2000Metres: number,
66
+ * yearToTMa: (year: number) => number,
67
+ * }} deps - p0 = the model's derived J2000 rate, 1,296,000/axial0 (S5; a
68
+ * number or a LAZY getter — the callers' certified year laws read the
69
+ * deep-time factory this is built inside, so the anchor is resolved on
70
+ * the first RATE use, never at construction; the torque term needs no
71
+ * anchor); lodJ2000Seconds the SAME day basis lodSecondsAtAge(0) returns.
72
+ * @returns {{
73
+ * composedRateArcsecPerYrAtAge: (tMa: number) => (number | null),
74
+ * composedPeriodYearsAtAge: (tMa: number) => (number | null),
75
+ * composedRateArcsecPerYrAtYear: (year: number) => (number | null),
76
+ * composedPeriodYearsAtYear: (year: number) => (number | null),
77
+ * composedRateRatioAtAge: (tMa: number) => (number | null),
78
+ * torqueTermAtAge: (tMa: number) => (number | null),
79
+ * lunarTorqueFactorAtAge: (tMa: number) => (number | null),
80
+ * solarShare: number,
81
+ * p0ArcsecPerYr: number,
82
+ * }}
83
+ */
84
+ function createComposedPrecession(deps) {
85
+ const { solarShare: fS } = deps;
86
+ /** @type {number|undefined} */
87
+ let p0Memo;
88
+ /** The J2000 anchor, resolved once on first use (lazy getter allowed). */
89
+ const p0 = () => {
90
+ if (p0Memo === undefined) {
91
+ const v = typeof deps.p0ArcsecPerYr === 'function' ? deps.p0ArcsecPerYr() : deps.p0ArcsecPerYr;
92
+ if (!(v > 0)) throw new Error(`createComposedPrecession: p0ArcsecPerYr must resolve to a positive rate, got ${v}`);
93
+ p0Memo = v;
94
+ }
95
+ return p0Memo;
96
+ };
97
+ /** @param {number} tMa */
98
+ const lunarTorqueFactorAtAge = (tMa) => {
99
+ const a = deps.moonDistanceMetresAtAge(tMa);
100
+ return a === null || !(a > 0) ? null : Math.pow(deps.moonDistanceJ2000Metres / a, 3);
101
+ };
102
+ /** The torque term f_S + (1 − f_S)·(a₀/a_M)³ — the factor the unit H(t)
103
+ * divides the spin-scaled H_era by (deltat/deep-time.cjs; layer0 spells the
104
+ * SAME operations, the layer0 gate holds them bit-identical).
105
+ * @param {number} tMa */
106
+ const torqueTermAtAge = (tMa) => {
107
+ const lf = lunarTorqueFactorAtAge(tMa);
108
+ return lf === null ? null : fS + (1 - fS) * lf;
109
+ };
110
+ /** @param {number} tMa */
111
+ const composedRateArcsecPerYrAtAge = (tMa) => {
112
+ const lod = deps.lodSecondsAtAge(tMa);
113
+ const term = torqueTermAtAge(tMa);
114
+ if (lod === null || !(lod > 0) || term === null) return null;
115
+ return (deps.lodJ2000Seconds / lod) * p0() * term;
116
+ };
117
+ /** @param {number} tMa */
118
+ const composedPeriodYearsAtAge = (tMa) => {
119
+ const r = composedRateArcsecPerYrAtAge(tMa);
120
+ return r === null ? null : 1296000 / r;
121
+ };
122
+ /** ψ̇(t)/ψ̇₀ — the factor a consumer scales ITS OWN certified J2000
123
+ * anchor by (since S5 p₀ and the hybrids' anchor are the same derived
124
+ * value, 25,771.4 yr; the ratio is what D6 changes). ≡ 1 at J2000; null
125
+ * out of the tidal chain's domain.
126
+ * @param {number} tMa */
127
+ const composedRateRatioAtAge = (tMa) => {
128
+ const r = composedRateArcsecPerYrAtAge(tMa);
129
+ return r === null ? null : r / p0();
130
+ };
131
+ return {
132
+ composedRateArcsecPerYrAtAge,
133
+ composedPeriodYearsAtAge,
134
+ composedRateArcsecPerYrAtYear: (year) => composedRateArcsecPerYrAtAge(deps.yearToTMa(year)),
135
+ composedPeriodYearsAtYear: (year) => composedPeriodYearsAtAge(deps.yearToTMa(year)),
136
+ composedRateRatioAtAge,
137
+ torqueTermAtAge,
138
+ lunarTorqueFactorAtAge,
139
+ solarShare: fS,
140
+ get p0ArcsecPerYr() { return p0(); },
141
+ };
142
+ }
143
+
144
+ module.exports = { computeSolarTorqueShare, createComposedPrecession };
@@ -113,4 +113,15 @@ function createYearLengths({ sampleAt, massLossSiderealSecondsAtYearFn, apsidalS
113
113
  });
114
114
  }
115
115
 
116
- module.exports = { createYearLengths };
116
+ /**
117
+ * The published of-date window (plan 06 Phase 3 S2): inside |year − 2000| ≤
118
+ * this span the one-family route is THE published year-length / precession
119
+ * family (the banked tiers — 100-yr and 1000-yr grids, no unbounded sampler
120
+ * growth); beyond it the published surfaces return the unit's SECULAR mean
121
+ * (H(t)/13 for the precession period, the tidal-chain mean year), because the
122
+ * of-date wobble is unresolved there and the deep sampler grows ~0.1 s per Myr
123
+ * of span. A displayed rate must name its window — this is the window.
124
+ */
125
+ const ONE_FAMILY_WINDOW_YEARS = 2000000;
126
+
127
+ module.exports = { createYearLengths, ONE_FAMILY_WINDOW_YEARS };
package/src/index.js CHANGED
@@ -138,7 +138,11 @@ export { createDeepOrbitalHistory } from './earth/deep-orbital-history.cjs';
138
138
  // D6: the sidereal-year-of-date channel (banked λ̇ ratio × the caller's mass-loss law)
139
139
  export { createSiderealYearChannel } from './earth/sidereal-year-channel.cjs';
140
140
  // S2: THE ONE of-date year-length family (years + precession beats, SI seconds)
141
- export { createYearLengths } from './earth/year-lengths.cjs';
141
+ export { createYearLengths, ONE_FAMILY_WINDOW_YEARS } from './earth/year-lengths.cjs';
142
+ // Plan 06 D6: THE ONE home of the composed lunisolar precession rate ψ̇(t)
143
+ // (spin ω(t) × [solar torque + lunar torque on the recession history]) —
144
+ // leg-1's physical rate; the structural H(t)/13 clock is its named diagnostic.
145
+ export { computeSolarTorqueShare, createComposedPrecession } from './earth/precession-composed.cjs';
142
146
  export { createPredictivePrecession, calcPlanetPerihelionLongDeg } from './planets/predict.cjs';
143
147
  // L10 — the composition front door: one law set, N body records. Thin by
144
148
  // design; engines keep their direct call sites (see planets/model.cjs).
@@ -148,7 +152,7 @@ export { createDeltaTCycles } from './deltat/cycles.cjs';
148
152
  export { createDeepTimeLod } from './deltat/deep-time.cjs';
149
153
  export { createMoonRecessionHistory, createSolarChannelBudget } from './deltat/recession-history.cjs';
150
154
  export { deltaTEspenakMeeusCanonSeconds } from './deltat/historical.cjs';
151
- export { evalClimateL1OrbitalPermil } from './climate/l1-orbital.cjs';
155
+ export { evalClimateL1OrbitalPermil, laggedL1Terms, createAlphaGiaChannel } from './climate/l1-orbital.cjs';
152
156
  // Phase 8.5 — eclipse geometry (single-copy: the browser had no Node twin).
153
157
  export { createEclipseFinders } from './eclipse/finders.cjs';
154
158
  // Phase 8.6 — the published reference curves (external comparison formulas
@@ -223,19 +227,22 @@ export const createModel = (constants = GENERATED, opts = {}) => {
223
227
  hash,
224
228
 
225
229
  /**
226
- * H-lattice periods — the structural identities, in years.
230
+ * The fit-era lattice identities H/13, H/3, H/16, in years — DEVICE tier.
227
231
  *
228
- * PURE ALGEBRA OVER THE CONTEXT. No epoch, no formula, no fit. These are
229
- * the divisor relationships H/13, H/3, H/16 that define the lattice, and
230
- * they exist here in Phase 5 for one reason: a hash-only counterfactual
231
- * test would still pass if `createModel` ignored its argument entirely.
232
- * Something has to READ the context and return a number for injection to be
233
- * demonstrated end to end.
232
+ * PURE ALGEBRA OVER THE CONTEXT. No epoch, no formula, no fit. They exist
233
+ * here for one reason: a hash-only counterfactual test would still pass if
234
+ * `createModel` ignored its argument entirely. Something has to READ the
235
+ * context and return a number for injection to be demonstrated end to end.
234
236
  *
235
- * The motion model is Phase 6. This is not it.
237
+ * These are NOT the published periods (plan 06 S5): the axial precession
238
+ * period is `epoch.axialPrecessionYearsAtYear` (25,771.4 yr at J2000, the
239
+ * of-date year laws' beat); H/13 = 25,793.6 is the fit anchor's reading,
240
+ * 0.086 % slow — H₀ was fitted on the perihelion-of-date beat. Likewise
241
+ * H/3 and H/16 against the chain's apsidal and perihelion-of-date periods.
242
+ * The property names keep their historical form (plan 06 P4).
236
243
  *
237
244
  * `divisor` and `period` are never interchangeable (CLAUDE.md): 13 is the
238
- * divisor, H/13 years is the period. The names say which.
245
+ * divisor, H/13 years is the identity's value. The names say which.
239
246
  *
240
247
  * @returns {{axialPrecessionPeriodYears: number, inclinationPrecessionPeriodYears: number, perihelionPrecessionPeriodYears: number}}
241
248
  */
@@ -9,12 +9,16 @@
9
9
  * If the two ever disagree, one of them changed and the other did not — which
10
10
  * is the exact drift mode that produced five diverging implementations.
11
11
  *
12
- * Pure function, imports nothing. Lives in physics so the browser and the Node
13
- * engine can both build Layer 0 from ONE derivation (deep-time.js keeps its
14
- * own inline copy only until Phase C rewrites it; until then the test is the
15
- * enforcement).
12
+ * Pure function; its only import is the torque-share derivation (plan 06
13
+ * Phase 3: the unit H(t) = 13·T_p,composed needs the solar fraction of the
14
+ * J2000 precession torque, derived ONCE here from the shared constants).
15
+ * Lives in physics so the browser and the Node engine can both build Layer 0
16
+ * from ONE derivation (deep-time.js keeps its own inline copy only until
17
+ * Phase C rewrites it; until then the test is the enforcement).
16
18
  */
17
19
 
20
+ import { computeSolarTorqueShare } from '../earth/precession-composed.cjs';
21
+
18
22
  /**
19
23
  * @typedef {Object} RawEpochConstants
20
24
  * @property {number} solarLuminosityW IAU 2015 nominal (W)
@@ -35,6 +39,10 @@
35
39
  * @property {number} moonDistanceKm
36
40
  * @property {number} moonOrbitalEccentricity
37
41
  * @property {number} gmEarthMoonSystemKm3S2
42
+ * @property {number} gmSunKm3S2 GM☉ (km³/s²) — the solar precession torque
43
+ * @property {number} astronomicalUnitKm 1 AU (km)
44
+ * @property {number} earthOrbitalEccentricityJ2000
45
+ * @property {number} moonEclipticInclinationJ2000Deg
38
46
  */
39
47
 
40
48
  /**
@@ -64,6 +72,14 @@ export const deriveEpochParams = (raw) => {
64
72
  + (moonMassKg * Math.sqrt(gmEarthMoonM3S2 * moonDistanceNowM) * moonEccentricityFactor);
65
73
  const moonLockDistanceM = (totalAngularMomentumKgM2S / (moonMassKg * Math.sqrt(gmEarthMoonM3S2) * moonEccentricityFactor)) ** 2;
66
74
 
75
+ // The solar fraction of the J2000 precession torque (plan 06 Phase 3): the
76
+ // unit H(t) ≡ 13·T_p,composed = H_era(t) / [f_S + (1 − f_S)(a₀/a_M(t))³].
77
+ const precessionSolarShareJ2000 = computeSolarTorqueShare({
78
+ gmSunKm3S2: raw.gmSunKm3S2, auKm: raw.astronomicalUnitKm, earthEccentricity: raw.earthOrbitalEccentricityJ2000,
79
+ gmMoonKm3S2: raw.gmMoonAloneKm3S2, moonDistanceKm: raw.moonDistanceKm, moonEccentricity: raw.moonOrbitalEccentricity,
80
+ moonInclinationDeg: raw.moonEclipticInclinationJ2000Deg,
81
+ });
82
+
67
83
  return Object.freeze({
68
84
  epochYear: 2000,
69
85
  alpha1PerMa: raw.alpha1PerMa,
@@ -81,5 +97,6 @@ export const deriveEpochParams = (raw) => {
81
97
  lodNowH13Seconds,
82
98
  siderealYearJ2000Seconds: raw.meanSiderealYearSeconds,
83
99
  solarMassLossFracPerYear,
100
+ precessionSolarShareJ2000,
84
101
  });
85
102
  };
@@ -47,6 +47,7 @@
47
47
  * @property {number} lodNowH13Seconds
48
48
  * @property {number} siderealYearJ2000Seconds
49
49
  * @property {number} solarMassLossFracPerYear
50
+ * @property {number} precessionSolarShareJ2000 f_S, the solar fraction of the J2000 precession torque
50
51
  */
51
52
 
52
53
  /**
@@ -54,10 +55,12 @@
54
55
  * @property {(year: number) => number} tMa
55
56
  * @property {(year: number) => number} moonDistanceMetres
56
57
  * @property {(year: number) => number|null} lodSeconds
57
- * @property {(year: number) => number|null} holisticH
58
+ * @property {(year: number) => number|null} holisticH the UNIT: 13 × the composed lunisolar precession period
58
59
  * @property {(year: number) => number} siderealYearSeconds
59
60
  * @property {(year: number) => number} tropicalYearSeconds
60
61
  * @property {(year: number) => number|null} anomalisticYearSeconds
62
+ * @property {(year: number) => number|null} eraClockH the frozen era clock's phase convention H₀·LOD/LOD₀ (device tier)
63
+ * @property {(year: number) => number} eraClockTropicalYearSeconds T_sid·(1 − 13/H_era) — the frozen comb family's base
61
64
  */
62
65
 
63
66
  /**
@@ -98,16 +101,35 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
98
101
  };
99
102
 
100
103
  /**
101
- * The Earth Fundamental Cycle. H scales with LOD — the lattice is defined by
102
- * the rotation rate, so a longer day is a longer H.
104
+ * The frozen era clock's phase convention: H_era(t) = H₀·LOD(t)/LOD₀ —
105
+ * pure spin scaling. This is the counter the FROZEN devices (the cardinal
106
+ * era clock, the year-length comb family) were fitted against; it ships
107
+ * with their coefficients as a named device constant (plan 06 Phase 3,
108
+ * D8), never as "H(t)".
103
109
  * @param {number} t age in Ma
104
110
  * @returns {number|null} years
105
111
  */
106
- const holisticHCore = (t) => {
112
+ const eraClockHCore = (t) => {
107
113
  const lod = lodSecondsCore(t);
108
114
  return lod === null ? null : p.holisticYearJ2000 * lod / p.lodNowH13Seconds;
109
115
  };
110
116
 
117
+ /**
118
+ * The unit H(t) ≡ 13 × the composed lunisolar precession period:
119
+ * H_era(t) / [f_S + (1 − f_S)·(a₀/a_M(t))³] — the spin scaling AND the
120
+ * lunar torque growing on the recession history (plan 06 D6/Phase 3; the
121
+ * formula's home is earth/precession-composed, this is its Layer-0 twin,
122
+ * held bit-identical to deltat/deep-time.cjs by the layer0 gate).
123
+ * @param {number} t age in Ma
124
+ * @returns {number|null} years
125
+ */
126
+ const holisticHCore = (t) => {
127
+ const hEra = eraClockHCore(t);
128
+ if (hEra === null) return null;
129
+ const lf = Math.pow(p.moonDistanceNowM / moonDistanceMetresCore(t), 3);
130
+ return hEra / (p.precessionSolarShareJ2000 + (1 - p.precessionSolarShareJ2000) * lf);
131
+ };
132
+
111
133
  /**
112
134
  * Sidereal year. Driver 2 alone — solar mass loss via Kepler, `dT/T = -2 dM/M`
113
135
  * under the adiabatic `a*M = const` coupling. Carries NO alpha and NO LOD,
@@ -127,7 +149,10 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
127
149
  };
128
150
 
129
151
  /**
130
- * Tropical year — sidereal less one axial-precession turn, H/13.
152
+ * Tropical year — sidereal less one calendar turn of the unit, 13/H(t): the
153
+ * unit's CALENDAR convention (plan 06 S5 — a device identity, 0.086 % from
154
+ * one turn per the published precession period; D2 decided: it stays a
155
+ * named device, never re-based).
131
156
  * Falls back to the J2000 H past the asymptote, matching the shipped chain.
132
157
  * @param {number} t age in Ma
133
158
  * @returns {number} seconds
@@ -159,6 +184,13 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
159
184
  return sid * (H - 13) / H * H / (H - 16);
160
185
  };
161
186
 
187
+ /** The frozen comb family's tropical base: T_sid·(1 − 13/H_era). @param {number} t */
188
+ const eraClockTropicalYearSecondsCore = (t) => {
189
+ const sid = siderealYearSecondsCore(t);
190
+ const H = eraClockHCore(t);
191
+ return H === null ? sid * (1 - 13 / p.holisticYearJ2000) : sid * (1 - 13 / H);
192
+ };
193
+
162
194
  return Object.freeze({
163
195
  tMa,
164
196
  moonDistanceMetres: (year) => moonDistanceMetresCore(tMa(year)),
@@ -167,5 +199,7 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
167
199
  siderealYearSeconds: (year) => siderealYearSecondsCore(tMa(year)),
168
200
  tropicalYearSeconds: (year) => tropicalYearSecondsCore(tMa(year)),
169
201
  anomalisticYearSeconds: (year) => anomalisticYearSecondsCore(tMa(year)),
202
+ eraClockH: (year) => eraClockHCore(tMa(year)),
203
+ eraClockTropicalYearSeconds: (year) => eraClockTropicalYearSecondsCore(tMa(year)),
170
204
  });
171
205
  };