@essrt/physics 22.1.0 → 23.2.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 (45) hide show
  1. package/README.md +2 -1
  2. package/package.json +9 -3
  3. package/src/constants/coefficients.d.ts +11 -11
  4. package/src/constants/coefficients.js +4 -4
  5. package/src/constants/generated.d.ts +4 -16
  6. package/src/constants/generated.js +8 -32
  7. package/src/constants/units.cjs +20 -0
  8. package/src/deltat/deep-time-chain.js +457 -0
  9. package/src/deltat/deep-time.cjs +68 -25
  10. package/src/deltat/moon-distance-artifact.cjs +23 -0
  11. package/src/deltat/moon-distance-history.cjs +115 -0
  12. package/src/earth/deep-orbital-history.cjs +69 -19
  13. package/src/earth/mass-chain.cjs +95 -0
  14. package/src/earth/mean-obliquity-history.cjs +94 -0
  15. package/src/earth/precession-composed.cjs +49 -3
  16. package/src/index.js +23 -1
  17. package/src/layer0/derive-params.js +1 -5
  18. package/src/layer0/index.js +30 -17
  19. package/src/math/angles.cjs +38 -0
  20. package/src/model.js +100 -266
  21. package/src/moon/arguments.cjs +5 -31
  22. package/src/moon/month-chain.cjs +12 -19
  23. package/src/planets/chain-artifact.js +2 -2
  24. package/src/time/calendar.cjs +165 -0
  25. package/types/constants/coefficients.d.ts +11 -11
  26. package/types/constants/generated.d.ts +4 -16
  27. package/types/constants/units.d.cts +8 -0
  28. package/types/deltat/deep-time-chain.d.ts +266 -0
  29. package/types/deltat/deep-time.d.cts +42 -12
  30. package/types/deltat/moon-distance-artifact.d.cts +22 -0
  31. package/types/deltat/moon-distance-history.d.cts +36 -0
  32. package/types/earth/deep-orbital-history.d.cts +5 -3
  33. package/types/earth/mass-chain.d.cts +78 -0
  34. package/types/earth/mean-obliquity-history.d.cts +32 -0
  35. package/types/earth/precession-composed.d.cts +11 -1
  36. package/types/index.d.ts +8 -1
  37. package/types/layer0/derive-params.d.ts +3 -2
  38. package/types/layer0/index.d.ts +9 -6
  39. package/types/math/angles.d.cts +18 -0
  40. package/types/model.d.ts +21 -3
  41. package/types/moon/arguments.d.cts +2 -6
  42. package/types/moon/month-chain.d.cts +7 -13
  43. package/types/time/calendar.d.cts +78 -0
  44. package/src/deltat/recession-history.cjs +0 -169
  45. package/types/deltat/recession-history.d.cts +0 -80
@@ -0,0 +1,457 @@
1
+ /**
2
+ * THE DEEP-TIME CHAIN — ONE assembly (holisticuniverse plan 13 B2).
3
+ *
4
+ * Before B2 three runtimes assembled the same chain by hand from the same
5
+ * factories — `packages/physics/src/model.js`, `tools/lib/deep-time.js`
6
+ * ("copy, keep in sync") and the `src/script.js` block — each deriving the
7
+ * GIA α channel, the Layer-0 parameter bundle, the cited Moon-distance
8
+ * history, the ΔT cycle stack, |s₃|, the LOD/ΔT core and the lunar month
9
+ * chain, and each carrying its own `mean*AtAge` wrappers. This module is
10
+ * that assembly, once, from the generated constants and fitted
11
+ * coefficients; the runtimes destructure it and keep only what is genuinely
12
+ * theirs (the browser's research toggles and snapshot mode, the Node
13
+ * engine's env gates and scene grid, the model's one-source movement).
14
+ *
15
+ * Bit-identity rule (plan 13 §2.6): every operation here is the twins'
16
+ * operation in the twins' order — floating point is not associative, and
17
+ * the goldens pin the values.
18
+ *
19
+ * Twin differences that are CONVENTIONS, kept as parameters:
20
+ * - `ageAnchorYear`: the year the twins' `h5Correction`/`frameworkDeltaT`
21
+ * convert to t_Ma from (the scene epoch 2000.5 in both twins; the model's
22
+ * own surfaces use 2000 — `yearToTMa` here is always 2000-anchored, the
23
+ * Layer-0 rule, and the scene-anchored readers take `ageAnchorYear`).
24
+ * - `flagsAt()`: the ΔT stack's five post-integration terms, each
25
+ * switchable (the browser's research toggles; the Node engine maps its
26
+ * two env gates onto them; the model runs all ON).
27
+ * - the stellar day: the Node twin projects the precession offset onto the
28
+ * equator (`stellarDayRaProjection`), the browser does not — two named
29
+ * functions, `meanStellarDayAtAge` (unprojected) and
30
+ * `meanStellarDayProjectedAtAge`.
31
+ */
32
+
33
+ import { deriveEpochParams } from '../layer0/derive-params.js';
34
+ import { createEpochPrimitives } from '../layer0/index.js';
35
+ import { computeMeanTropicalYearJ2000Days } from '../earth/year-lengths.cjs';
36
+ import { createAlphaGiaChannel } from '../climate/l1-orbital.cjs';
37
+ import { createMoonDistanceHistory } from './moon-distance-history.cjs';
38
+ import { createMeanObliquityHistory } from '../earth/mean-obliquity-history.cjs';
39
+ import { createDeltaTCycles } from './cycles.cjs';
40
+ import { createDeepTimeLod } from './deep-time.cjs';
41
+ import { createDeepEccChannel } from '../moon/deep-ecc-channel.cjs';
42
+ import { createMoonMonthChain } from '../moon/month-chain.cjs';
43
+ import { massLossScaledLinearAtAge, driver2PeriodSecondsAtAge, synodicPeriodSeconds } from '../planets/orbit-chain.cjs';
44
+ import { computeMassChainJ2000, computeMoonPeriodsJ2000 } from '../earth/mass-chain.cjs';
45
+
46
+ /**
47
+ * Moon-channel eccentricity sensitivities (perigee/node), the [g/g₀]^s
48
+ * exponents of the factored deep-time law — Meeus-effective (S_N moved
49
+ * 1.0 → 1.018 with the v4 frame-attribution batch). ONE home since B2 (they
50
+ * were literals in model.js, tools/lib/deep-time.js and script.js `_FW_MOON`).
51
+ */
52
+ export const MOON_ECC_SENSITIVITY_PERIGEE = 2.407;
53
+ export const MOON_ECC_SENSITIVITY_NODE = 1.018;
54
+
55
+ /**
56
+ * The ΔT stack's switches. `resonator` gates the swing's δLOD terms; the
57
+ * swing's ΔT term is gated by `resonatorDeltaT` when given (the Node twin
58
+ * gates it on BOTH env vars, DT_CORRECTIONS and DT_RESONATOR), else by
59
+ * `resonator`. The four cycles' δLOD terms follow their flags only when
60
+ * `cycleLodFollowsFlags` is true (the browser's research toggles switch a
61
+ * cycle's ΔT and δLOD together); the Node twin sums them unconditionally.
62
+ * Their ΔT terms always follow their flags. Identical with every flag ON.
63
+ * @typedef {{bond: boolean, hallstatt: boolean, jose5: boolean, jose4: boolean, resonator: boolean, resonatorDeltaT?: boolean, cycleLodFollowsFlags?: boolean}} DeltaTFlags
64
+ */
65
+
66
+ /**
67
+ * @param {{
68
+ * constants: any,
69
+ * fitted: any,
70
+ * deepModes: any,
71
+ * moonDistanceArtifact: any,
72
+ * ageAnchorYear?: number,
73
+ * flagsAt?: () => DeltaTFlags,
74
+ * planetAuRatios?: Record<string, number>,
75
+ * stellarDayRaProjection?: number,
76
+ * }} deps — `constants` the generated DEFAULT_CONSTANTS, `fitted` the
77
+ * generated FITTED_COEFFICIENTS, `deepModes` the DEEP_MODES_ARTIFACT,
78
+ * `moonDistanceArtifact` the MOON_DISTANCE_ARTIFACT — the cited distance
79
+ * AND mean-obliquity columns, plan 12 option A / §8 (a) (injected so a
80
+ * counterfactual context can replace any of them); `ageAnchorYear` the
81
+ * scene epoch (default constants.timeReference.sceneEpochYear);
82
+ * `flagsAt` the ΔT stack's switches (default all ON); `planetAuRatios`
83
+ * the planets' J2000 semi-major axes in AU (for the per-planet family);
84
+ * `stellarDayRaProjection` cos ε₀-class factor for the projected stellar day.
85
+ */
86
+ export function createDeepTimeChain(deps) {
87
+ const C = deps.constants, F = deps.fitted;
88
+ /** @type {() => DeltaTFlags} */
89
+ const flagsAt = deps.flagsAt ?? (() => ({ bond: true, hallstatt: true, jose5: true, jose4: true, resonator: true }));
90
+ const ageAnchorYear = deps.ageAnchorYear ?? C.timeReference.sceneEpochYear;
91
+
92
+ // ── Derived constants (constants.js §9 order — the twins' operations) ─────
93
+ const meanSolarYearDays = computeMeanTropicalYearJ2000Days(C.yearLengthRef.siderealYear, C.earthOrbital.generalPrecessionJ2000_arcsecPerYr);
94
+ const meanSiderealYearDays = C.yearLengthRef.siderealYear;
95
+ const meanSiderealYearSeconds = meanSiderealYearDays * 86400;
96
+ const meanLengthOfDay = C.yearLengthRef.solarDay;
97
+ const meanTropicalYearJ2000Seconds = meanSolarYearDays * meanLengthOfDay;
98
+ const siTropicalYearDays = meanTropicalYearJ2000Seconds / 86400;
99
+ const axialPrecessionJ2000Years = 1296000 / C.earthOrbital.generalPrecessionJ2000_arcsecPerYr;
100
+ const axialPrecessionJ2000Days = axialPrecessionJ2000Years * 365.25;
101
+
102
+ const G_CONSTANT = C.physicalConstants.G_CONSTANT;
103
+ const MASS_RATIO_EARTH_MOON = C.physicalConstants.MASS_RATIO_EARTH_MOON;
104
+ const currentAUDistance = C.physicalConstants.currentAUDistance;
105
+ const earthMoiFactorJ2000 = C.physicalConstants.earthMoiFactorJ2000;
106
+ const moonDistanceKm = C.moonReference.moonDistance;
107
+ const moonSiderealMonthInput = C.moonReference.moonSiderealMonthInput;
108
+ const moonSiderealMonth = moonSiderealMonthInput;
109
+
110
+ // The mass chain (Moon Kepler → GM_EM → the Earth/Moon split → the Sun) — ONE home (B3)
111
+ const mass = computeMassChainJ2000({
112
+ moonDistanceKm, moonSiderealMonthDays: moonSiderealMonth, meanSiderealYearDays,
113
+ meanLengthOfDaySeconds: meanLengthOfDay, massRatioEarthMoon: MASS_RATIO_EARTH_MOON,
114
+ currentAUDistanceKm: currentAUDistance, gravitationalConstantKm3KgS2: G_CONSTANT,
115
+ });
116
+ const moonOrbitalShift = mass.moonOrbitalShiftKm;
117
+ const moonDistanceCorrected = mass.moonDistanceCorrectedKm;
118
+ const GM_EARTH_MOON_SYSTEM = mass.gmEarthMoonSystemKm3S2;
119
+ const GM_EARTH_ALONE = mass.gmEarthAloneKm3S2;
120
+ const GM_MOON_ALONE = mass.gmMoonAloneKm3S2;
121
+ const GM_SUN_PLUS_EARTH = mass.gmSunPlusEarthKm3S2;
122
+ const GM_SUN = mass.gmSunKm3S2;
123
+ const M_SUN = mass.sunMassKg;
124
+
125
+ /** THE J2000 precession anchor p₀ as a period (plan 09: the IAU 2006 general precession). @returns {number} */
126
+ const certifiedAxialPrecessionJ2000Years = () => axialPrecessionJ2000Years;
127
+ /** p₀, ″/yr. @returns {number} */
128
+ const precessionRateJ2000ArcsecPerYr = () => 1296000 / certifiedAxialPrecessionJ2000Years();
129
+
130
+ // ── α(t): the GIA channel (lagged L1 response), the memo and the lattice pin ──
131
+ const CLIMATE_REGIME = F.CLIMATE_FORMULA_COEFFS.regimes['lr04-post-mpt'];
132
+ const alphaGia = createAlphaGiaChannel({
133
+ l1Terms: CLIMATE_REGIME.L1,
134
+ yStdDenormalization: CLIMATE_REGIME.denormalization.y_std,
135
+ relaxationKyr: C.deepTime.alphaGiaRelaxationKyr,
136
+ alphaGiaRateJ2000PerYr: C.deepTime.alphaGiaRateJ2000PerYr,
137
+ alphaJ2000: earthMoiFactorJ2000,
138
+ j2RateJ2000PerYr: C.deepTime.j2RateJ2000PerYr,
139
+ j2J2000: C.physicalConstants.earthJ2,
140
+ });
141
+ // R2 — the α lattice reference: while a lattice table is being BUILT α is
142
+ // held at its J2000 reference, or the lattice defines itself in terms of
143
+ // its own output. Save/restore under try/finally.
144
+ let latticeAlphaRef = false;
145
+ /** @param {() => void} build */
146
+ const withLatticeAlpha = (build) => { const prev = latticeAlphaRef; latticeAlphaRef = true; try { build(); } finally { latticeAlphaRef = prev; } };
147
+ /** @type {Map<number, number>} */
148
+ const alphaMemo = new Map();
149
+ /** Exact-argument memo (pure dedupe, not a grid); the pin bypasses it. @param {number} tMa @returns {number} */
150
+ const earthMoiFactorAtAge = (tMa) => {
151
+ if (latticeAlphaRef) return earthMoiFactorJ2000;
152
+ let v = alphaMemo.get(tMa);
153
+ if (v === undefined) {
154
+ v = alphaGia.alphaAt(2000 - tMa * 1e6);
155
+ if (alphaMemo.size >= 8192) alphaMemo.clear();
156
+ alphaMemo.set(tMa, v);
157
+ }
158
+ return v;
159
+ };
160
+ /** J₂(t)/J₂₀ of the GIA channel at a calendar year — PURE in year. @param {number} year @returns {number} */
161
+ const j2RatioAtYear = (year) => alphaGia.j2RatioAt(year);
162
+
163
+ // ── Layer 0 bundle ────────────────────────────────────────────────────────
164
+ const EPOCH_PARAMS = deriveEpochParams({
165
+ solarLuminosityW: C.physicalConstants.solarLuminosityW,
166
+ solarWindKgPerS: C.physicalConstants.solarWindMassLossKgPerS,
167
+ speedOfLightKmPerS: C.physicalConstants.speedOfLight,
168
+ alpha1PerMa: C.deepTime.alpha1PerMa,
169
+ meanSiderealYearSeconds,
170
+ lodJ2000Seconds: meanLengthOfDay,
171
+ sunMassKg: M_SUN,
172
+ gmEarthAloneKm3S2: GM_EARTH_ALONE,
173
+ gmMoonAloneKm3S2: GM_MOON_ALONE,
174
+ gravitationalConstantKm3KgS2: G_CONSTANT,
175
+ earthMoiFactorJ2000,
176
+ earthDiameterKm: C.bodyDiametersKm.earth,
177
+ moonDistanceKm,
178
+ moonOrbitalEccentricity: C.moonReference.moonOrbitalEccentricityBase,
179
+ gmEarthMoonSystemKm3S2: GM_EARTH_MOON_SYSTEM,
180
+ gmSunKm3S2: GM_SUN,
181
+ astronomicalUnitKm: currentAUDistance,
182
+ earthOrbitalEccentricityJ2000: C.earthOrbital.earthEccentricityJ2000,
183
+ moonEclipticInclinationJ2000Deg: C.moonReference.moonEclipticInclinationJ2000,
184
+ axialPrecessionJ2000Years: certifiedAxialPrecessionJ2000Years(),
185
+ });
186
+ /** @param {number} tMa @returns {number} */
187
+ const iEarthAtAge = (tMa) => earthMoiFactorAtAge(tMa) * EPOCH_PARAMS.earthMassKg * EPOCH_PARAMS.earthRadiusM * EPOCH_PARAMS.earthRadiusM;
188
+
189
+ // ── The Earth–Moon distance history — the CITED INPUT (plan 12 option A) ──
190
+ const recession = createMoonDistanceHistory({
191
+ table: deps.moonDistanceArtifact,
192
+ aMoonNowMetres: EPOCH_PARAMS.moonDistanceNowM,
193
+ alpha1PerMa: EPOCH_PARAMS.alpha1PerMa,
194
+ });
195
+ // The cited mean obliquity ε̄(t) — the second column of the same history
196
+ // (plan 12 §8 option (a)): the precession RATE of date is the constant's
197
+ // scaling × cos ε̄(t)/cos ε̄(0); the hybrid keeps integrating on the constant.
198
+ const meanObliquity = createMeanObliquityHistory({ table: deps.moonDistanceArtifact });
199
+
200
+ // ── The ΔT cycle stack (the fitted terms, periods stated in years) ────────
201
+ const DT = F.DT_STACK, RES = F.DT_RESONATOR;
202
+ const dtCycles = createDeltaTCycles({
203
+ taperFullHalfwidthYears: C.deepTime.dtStackTaperFullHalfwidthYr,
204
+ taperTotalHalfwidthYears: C.deepTime.dtStackTaperTotalHalfwidthYr,
205
+ tropicalYearSecondsJ2000: meanTropicalYearJ2000Seconds,
206
+ cycles: {
207
+ bond: { periodYears: DT.bond.period_yr, cosCoeffSeconds: DT.bond.cos_coeff_s, sinCoeffSeconds: DT.bond.sin_coeff_s },
208
+ hallstatt: { periodYears: DT.hallstatt.period_yr, cosCoeffSeconds: DT.hallstatt.cos_coeff_s, sinCoeffSeconds: DT.hallstatt.sin_coeff_s },
209
+ jose5: { periodYears: DT.jose5.period_yr, cosCoeffSeconds: DT.jose5.cos_coeff_s, sinCoeffSeconds: DT.jose5.sin_coeff_s },
210
+ jose4: { periodYears: DT.jose4.period_yr, cosCoeffSeconds: DT.jose4.cos_coeff_s, sinCoeffSeconds: DT.jose4.sin_coeff_s },
211
+ },
212
+ resonator: {
213
+ t0Years: RES.T0_yr,
214
+ q: RES.Q,
215
+ kicks: RES.kick_epochs_year.map(/** @param {number} t @param {number} i */ (t, i) => ({ tYear: t, cosSeconds: RES.kick_coefficients_s[i].cos, sinSeconds: RES.kick_coefficients_s[i].sin })),
216
+ tones: RES.drive_tones.map(/** @param {{period_yr: number, phi_locked_rad: number, amp_s: number}} t */ (t) => ({ periodYears: t.period_yr, phiLockedRad: t.phi_locked_rad, ampSeconds: t.amp_s })),
217
+ },
218
+ });
219
+ /** @param {string} name @param {number} year @returns {number} */
220
+ const cycleDeltaT = (name, year) => dtCycles.cycleDeltaTSecondsAt(name, year);
221
+ /** @param {string} name @param {number} year @returns {number} */
222
+ const cycleLod = (name, year) => dtCycles.cycleLodSecondsAt(name, year);
223
+ /** The resonator episode's ΔT term, 0 when its flag is off. @param {number} year @returns {number} */
224
+ const resonatorSwingDeltaTCorrection = (year) => (flagsAt().resonator ? dtCycles.swingDeltaTSecondsAt(year) : 0);
225
+ /** @param {number} year @returns {number} */
226
+ const resonatorSwingLodCorrection = (year) => (flagsAt().resonator ? dtCycles.swingLodSecondsAt(year) : 0);
227
+ /** @param {number} year @returns {number} */
228
+ const resonatorSwingLodRate = (year) => (flagsAt().resonator ? dtCycles.swingLodRateAt(year) : 0);
229
+ /** Sum of the cyclic δLOD contributions at a calendar year (s): the four flags plus the swing when ON. @param {number} year @returns {number} */
230
+ const dtCycleLodCorrectionSum = (year) => {
231
+ const f = flagsAt();
232
+ const g = f.cycleLodFollowsFlags === true;
233
+ return (g && !f.bond ? 0 : cycleLod('bond', year))
234
+ + (g && !f.hallstatt ? 0 : cycleLod('hallstatt', year))
235
+ + (g && !f.jose5 ? 0 : cycleLod('jose5', year))
236
+ + (g && !f.jose4 ? 0 : cycleLod('jose4', year))
237
+ + resonatorSwingLodCorrection(year);
238
+ };
239
+
240
+ // ── |s₃| — the dominant nodal mode of Earth's orbit ──────────────────────
241
+ const s3ArcsecPerYr = (() => {
242
+ const z = deps.deepModes.earthZeta
243
+ .filter(/** @param {{omegaRadPerYr: number}} m */ (m) => Math.abs(m.omegaRadPerYr) > 1e-9)
244
+ .sort(/** @param {{re: number, im: number}} a @param {{re: number, im: number}} b */ (a, b) => Math.hypot(b.re, b.im) - Math.hypot(a.re, a.im))[0];
245
+ return Math.abs((z.omegaRadPerYr * 180) / Math.PI) * 3600;
246
+ })();
247
+
248
+ // ── The LOD/ΔT core ───────────────────────────────────────────────────────
249
+ const deepLod = createDeepTimeLod({
250
+ constants: {
251
+ lTotalEmKgm2S: EPOCH_PARAMS.totalAngularMomentumKgM2S,
252
+ mMoonAloneKg: EPOCH_PARAMS.moonMassKg,
253
+ mEarthAloneKg: EPOCH_PARAMS.earthMassKg,
254
+ rEarthMetres: EPOCH_PARAMS.earthRadiusM,
255
+ gmEmM3PerS2: EPOCH_PARAMS.gmEarthMoonM3S2,
256
+ eFactorMoon: EPOCH_PARAMS.moonEccentricityFactor,
257
+ aLockMetres: EPOCH_PARAMS.moonLockDistanceM,
258
+ aMoonNowMetres: EPOCH_PARAMS.moonDistanceNowM,
259
+ alpha1PerMa: EPOCH_PARAMS.alpha1PerMa,
260
+ lodNowH13Seconds: EPOCH_PARAMS.lodNowH13Seconds,
261
+ meanSiderealYearJ2000Seconds: meanSiderealYearSeconds,
262
+ solarMassLossFracPerYear: EPOCH_PARAMS.solarMassLossFracPerYear,
263
+ precessionSolarShareJ2000: EPOCH_PARAMS.precessionSolarShareJ2000,
264
+ },
265
+ moonDistanceMetresAtAge: recession.distanceMetresAtAge,
266
+ meanObliquityCosRatioAtAge: meanObliquity.cosRatioAtAge,
267
+ moiFactorAtAge: earthMoiFactorAtAge,
268
+ precessionPeriodJ2000YearsFn: certifiedAxialPrecessionJ2000Years,
269
+ nodalPeriodYearsFn: () => 1296000 / s3ArcsecPerYr,
270
+ cycleLodSumAt: dtCycleLodCorrectionSum,
271
+ swingLodAt: resonatorSwingLodCorrection,
272
+ swingLodRateAt: resonatorSwingLodRate,
273
+ moonDistanceSlopeMetresPerMaAtAge: recession.slopeMetresPerMaAtAge,
274
+ });
275
+
276
+ // ── Layer 0 primitives ────────────────────────────────────────────────────
277
+ const l0 = createEpochPrimitives({ params: EPOCH_PARAMS, alphaAtAgeMa: earthMoiFactorAtAge, moonDistanceMetresAtAge: recession.distanceMetresAtAge, meanObliquityCosRatioAtAge: meanObliquity.cosRatioAtAge });
278
+
279
+ // ── The ONE deep e and the lunar month chain ──────────────────────────────
280
+ const deepEcc = createDeepEccChannel(deps.deepModes);
281
+ /** [g(t)/g₀]^s — the e_E-channel modulation; ≡ 1 at J2000. @param {number} tMa @param {number} s @returns {number} */
282
+ const eCompModulation = (tMa, s) => deepEcc.modulation(tMa, s);
283
+
284
+ // The Moon's J2000 periods on the IAU inputs — ONE home (B3): the star-referenced
285
+ // apsidal/nodal anchors the month chain rides, and the days forms the twins publish.
286
+ const moonPeriods = computeMoonPeriodsJ2000({
287
+ moonSiderealMonthDays: moonSiderealMonth,
288
+ moonApsidalPrecessionDaysInput: C.moonReference.moonApsidalPrecessionDaysInputICRF,
289
+ moonNodalPrecessionDaysInput: C.moonReference.moonNodalPrecessionDaysInputICRF,
290
+ axialPrecessionJ2000Days, meanSiderealYearDays, meanSolarYearDays,
291
+ });
292
+ const { moonApsidalJ2000Seconds, moonNodalJ2000Seconds, moonSiderealMonthJ2000Seconds } = moonPeriods;
293
+
294
+ /** @param {number} tMa @returns {number|null} */
295
+ const meanLodSecondsAtAge = (tMa) => deepLod.lodSecondsAtAge(tMa);
296
+ /** @param {number} tMa @returns {number} */
297
+ const meanSiderealYearSecondsAtAge = (tMa) => deepLod.siderealYearSecondsAtAge(tMa);
298
+ /** @param {number} tMa @returns {number|null} */
299
+ const meanLunisolarPrecessionPeriodYearsAtAge = (tMa) => deepLod.lunisolarPrecessionPeriodYearsAtAge(tMa);
300
+
301
+ const moonChain = createMoonMonthChain({
302
+ constants: {
303
+ aMoonNowMetres: EPOCH_PARAMS.moonDistanceNowM,
304
+ gmEarthMoonM3PerS2: EPOCH_PARAMS.gmEarthMoonM3S2,
305
+ massRatioEarthMoon: MASS_RATIO_EARTH_MOON,
306
+ moonSiderealMonthInputDays: moonSiderealMonthInput,
307
+ meanSiderealYearJ2000Seconds: meanSiderealYearSeconds,
308
+ moonApsidalJ2000Seconds,
309
+ moonNodalJ2000Seconds,
310
+ moonSiderealMonthJ2000Seconds,
311
+ sPerigee: MOON_ECC_SENSITIVITY_PERIGEE, sNode: MOON_ECC_SENSITIVITY_NODE,
312
+ },
313
+ fns: {
314
+ meanLodSecondsAtAge,
315
+ meanSiderealYearSecondsAtAge,
316
+ precessionPeriodYearsAtAge: meanLunisolarPrecessionPeriodYearsAtAge,
317
+ modulation: eCompModulation,
318
+ distanceMetresAtAge: recession.distanceMetresAtAge,
319
+ },
320
+ });
321
+
322
+ // ── The ΔT integrator with the flag-keyed cache and the SEQUENTIAL adds ───
323
+ /** @type {Map<string, number>} */
324
+ const deltaTCache = new Map();
325
+ /** ΔT in SI seconds at age tMa, RELATIVE TO J2000 (0 at tMa = 0); the raw Simpson
326
+ * integral plus the post-integration terms the flags switch, added one by one in
327
+ * the twins' order (pre-summing would change the FP association). @param {number} tMa @returns {number} */
328
+ const meanDeltaTSecondsAtAge = (tMa) => {
329
+ if (tMa === 0) return 0;
330
+ const f = flagsAt();
331
+ const resonatorDeltaT = f.resonatorDeltaT ?? f.resonator;
332
+ const cacheKey = `${f.bond ? 'B' : '_'}${f.hallstatt ? 'H' : '_'}${f.jose5 ? 'J' : '_'}${f.jose4 ? 'W' : '_'}${resonatorDeltaT ? 'R' : '_'}:${tMa}`;
333
+ const hit = deltaTCache.get(cacheKey);
334
+ if (hit !== undefined) return hit;
335
+ let result = deepLod.deltaTRawSecondsAtAge(tMa);
336
+ const yearY = 2000 - tMa * 1e6;
337
+ if (f.bond) result += cycleDeltaT('bond', yearY);
338
+ if (f.hallstatt) result += cycleDeltaT('hallstatt', yearY);
339
+ if (f.jose5) result += cycleDeltaT('jose5', yearY);
340
+ if (f.jose4) result += cycleDeltaT('jose4', yearY);
341
+ if (resonatorDeltaT) result += dtCycles.swingDeltaTSecondsAt(yearY);
342
+ if (deltaTCache.size >= 512) deltaTCache.delete(/** @type {string} */ (deltaTCache.keys().next().value));
343
+ deltaTCache.set(cacheKey, result);
344
+ return result;
345
+ };
346
+
347
+ // ── The scene-anchored readers (t_Ma from the scene epoch) ───────────────
348
+ /** @param {number} year @returns {number} */
349
+ const ageFromSceneYear = (year) => (ageAnchorYear - year) / 1e6;
350
+ /** The solar day's ecliptic missing-motion term on the nodal period (the identifier is historical). @param {number} year @returns {number} */
351
+ const h5Correction = (year) => deepLod.eclipticLodCorrectionSecondsAtAge(ageFromSceneYear(year)) ?? 0;
352
+ /** The kinematic day of date: the Layer-0 tidal mean LOD, the J2000 day past tidal lock. @param {number} year @returns {number} */
353
+ const computeLodKinematicSecondsAtEpoch = (year) => l0.lodSeconds(year) ?? meanLengthOfDay;
354
+ /** The Layer-4 solar day: kinematic + ecliptic term + the cycle stack. @param {number} year @returns {number} */
355
+ const computeLodRealSecondsAtEpoch = (year) => computeLodKinematicSecondsAtEpoch(year) + h5Correction(year) + dtCycleLodCorrectionSum(year);
356
+
357
+ // ── Driver 2 and the per-planet family ────────────────────────────────────
358
+ /** @param {number} tMa @returns {number} */
359
+ const meanAuAtAge = (tMa) => massLossScaledLinearAtAge(tMa, currentAUDistance, EPOCH_PARAMS.solarMassLossFracPerYear);
360
+ const planetAuRatios = deps.planetAuRatios ?? {};
361
+ /** @param {string} planetName @param {number} tMa @returns {number|null} */
362
+ const meanPlanetSemiMajorAxisAtAge = (planetName, tMa) => {
363
+ if (planetName === 'earth') return meanAuAtAge(tMa);
364
+ const ratio = planetAuRatios[planetName];
365
+ if (ratio === undefined) return null;
366
+ return massLossScaledLinearAtAge(tMa, ratio * currentAUDistance, EPOCH_PARAMS.solarMassLossFracPerYear);
367
+ };
368
+
369
+ // ── The stellar and sidereal days ─────────────────────────────────────────
370
+ /** @param {number} tMa @returns {number|null} */
371
+ const meanSiderealDayAtAge = (tMa) => {
372
+ const T_sid_s = meanSiderealYearSecondsAtAge(tMa);
373
+ const LOD_s = meanLodSecondsAtAge(tMa);
374
+ if (LOD_s === null) return null;
375
+ const Tp = meanLunisolarPrecessionPeriodYearsAtAge(tMa) ?? certifiedAxialPrecessionJ2000Years();
376
+ return T_sid_s / (T_sid_s / LOD_s + 1 + 1 / Tp);
377
+ };
378
+ /** The browser's form: no projection of the precession offset. @param {number} tMa @returns {number|null} */
379
+ const meanStellarDayAtAge = (tMa) => {
380
+ const T_sid_s = meanSiderealYearSecondsAtAge(tMa);
381
+ const LOD_s = meanLodSecondsAtAge(tMa);
382
+ if (LOD_s === null) return null;
383
+ return T_sid_s / (T_sid_s / LOD_s + 1);
384
+ };
385
+ /** The Node twin's form: the precession offset projected onto the equator. @param {number} tMa @returns {number|null} */
386
+ const meanStellarDayProjectedAtAge = (tMa) => {
387
+ const T_sid_s = meanSiderealYearSecondsAtAge(tMa);
388
+ const LOD_s = meanLodSecondsAtAge(tMa);
389
+ if (LOD_s === null) return null;
390
+ const sid = meanSiderealDayAtAge(tMa);
391
+ const Tp = meanLunisolarPrecessionPeriodYearsAtAge(tMa) ?? certifiedAxialPrecessionJ2000Years();
392
+ const unproj = T_sid_s / (T_sid_s / LOD_s + 1);
393
+ const sidTp = T_sid_s / (T_sid_s / LOD_s + 1 + 1 / Tp);
394
+ return /** @type {number} */ (sid) + (unproj - sidTp) * (deps.stellarDayRaProjection ?? 1);
395
+ };
396
+
397
+ return Object.freeze({
398
+ // the derived scalars
399
+ meanSolarYearDays, meanSiderealYearDays, meanSiderealYearSeconds, meanLengthOfDay,
400
+ meanTropicalYearJ2000Seconds, siTropicalYearDays, axialPrecessionJ2000Years, axialPrecessionJ2000Days,
401
+ G_CONSTANT, MASS_RATIO_EARTH_MOON, currentAUDistance, earthMoiFactorJ2000, moonDistanceKm, moonSiderealMonthInput,
402
+ moonOrbitalShift, moonDistanceCorrected, GM_EARTH_MOON_SYSTEM, GM_EARTH_ALONE, GM_MOON_ALONE, GM_SUN_PLUS_EARTH, GM_SUN, M_SUN,
403
+ moonApsidalJ2000Seconds, moonNodalJ2000Seconds, moonSiderealMonthJ2000Seconds, moonPeriods,
404
+ certifiedAxialPrecessionJ2000Years, precessionRateJ2000ArcsecPerYr,
405
+ EPOCH_PARAMS, ageAnchorYear,
406
+ // the pieces
407
+ alphaGia, earthMoiFactorAtAge, withLatticeAlpha, j2RatioAtYear, iEarthAtAge,
408
+ recession, meanObliquity, dtCycles, s3ArcsecPerYr, deepLod, l0, deepEcc, eCompModulation, moonChain,
409
+ // the family
410
+ meanMoonDistanceMetresAtAge: recession.distanceMetresAtAge,
411
+ meanMoonDistanceAtAge: moonChain.distanceKmAtAge,
412
+ meanLodSecondsAtAge,
413
+ meanLodHoursAtAge: deepLod.lodHoursAtAge,
414
+ meanLunisolarPrecessionRateArcsecPerYrAtAge: deepLod.lunisolarPrecessionRateArcsecPerYrAtAge,
415
+ meanLunisolarPrecessionPeriodYearsAtAge,
416
+ // the constant's scaling alone (the hybrid's integrand), the rate's obliquity factor and the cited mean (plan 12 §8)
417
+ meanLunisolarTorqueRateArcsecPerYrAtAge: deepLod.lunisolarTorqueRateArcsecPerYrAtAge,
418
+ meanLunisolarTorquePeriodYearsAtAge: deepLod.lunisolarTorquePeriodYearsAtAge,
419
+ precessionObliquityFactorAtAge: deepLod.precessionObliquityFactorAtAge,
420
+ meanObliquityCitedDegAtAge: meanObliquity.meanObliquityDegAtAge,
421
+ meanAuAtAge, meanSiderealYearSecondsAtAge,
422
+ meanTropicalYearSecondsAtAge: deepLod.tropicalYearSecondsAtAge,
423
+ meanTropicalYearDaysAtAge: deepLod.tropicalYearDaysAtAge,
424
+ meanYearInDaysAtAge: deepLod.yearInDaysAtAge,
425
+ meanDeltaTSecondsAtAge,
426
+ bondCycleDeltaTCorrection: (/** @type {number} */ year) => cycleDeltaT('bond', year),
427
+ hallstattCycleDeltaTCorrection: (/** @type {number} */ year) => cycleDeltaT('hallstatt', year),
428
+ jose5CycleDeltaTCorrection: (/** @type {number} */ year) => cycleDeltaT('jose5', year),
429
+ jose4CycleDeltaTCorrection: (/** @type {number} */ year) => cycleDeltaT('jose4', year),
430
+ bondCycleLodCorrection: (/** @type {number} */ year) => cycleLod('bond', year),
431
+ hallstattCycleLodCorrection: (/** @type {number} */ year) => cycleLod('hallstatt', year),
432
+ jose5CycleLodCorrection: (/** @type {number} */ year) => cycleLod('jose5', year),
433
+ jose4CycleLodCorrection: (/** @type {number} */ year) => cycleLod('jose4', year),
434
+ dtCycleLodCorrectionSum,
435
+ resonatorSwingDeltaTCorrection, resonatorSwingLodCorrection, resonatorSwingLodRate,
436
+ meanLodSecondsWithCorrectionsAtAge: deepLod.lodSecondsWithCorrectionsAtAge,
437
+ dLodDtDecompositionAtAge: deepLod.dLodDtDecompositionAtAge,
438
+ h5Correction, computeLodKinematicSecondsAtEpoch, computeLodRealSecondsAtEpoch,
439
+ meanSolarDeltaAAtAge: moonChain.solarDeltaAKmAtAge,
440
+ meanMoonDistanceCorrectedAtAge: moonChain.distanceCorrectedKmAtAge,
441
+ meanMoonSiderealMonthAtAge: moonChain.siderealMonthSecondsAtAge,
442
+ meanSynodicMonthAtAge: moonChain.synodicMonthSecondsAtAge,
443
+ meanTropicalMonthAtAge: moonChain.tropicalMonthSecondsAtAge,
444
+ meanLunarPerigeePrecessionAtAge: moonChain.perigeePrecessionSecondsAtAge,
445
+ meanLunarNodePrecessionAtAge: moonChain.nodePrecessionSecondsAtAge,
446
+ meanAnomalisticMonthAtAge: moonChain.anomalisticMonthSecondsAtAge,
447
+ meanNodalMonthAtAge: moonChain.nodalMonthSecondsAtAge,
448
+ meanApsidalMeetsNodalAtAge: moonChain.apsidalMeetsNodalSecondsAtAge,
449
+ meanLunarLevelingCycleAtAge: moonChain.lunarLevelingSecondsAtAge,
450
+ meanStellarDayAtAge, meanStellarDayProjectedAtAge, meanSiderealDayAtAge,
451
+ meanPlanetSemiMajorAxisAtAge,
452
+ /** @param {number} tMa @param {number} periodJ2000Seconds @returns {number} */
453
+ meanPlanetOrbitalPeriodAtAge: (tMa, periodJ2000Seconds) => driver2PeriodSecondsAtAge(tMa, periodJ2000Seconds, EPOCH_PARAMS.solarMassLossFracPerYear),
454
+ /** @param {number} tMa @param {number} periodJ2000Seconds @returns {number} */
455
+ meanEarthPlanetSynodicPeriodAtAge: (tMa, periodJ2000Seconds) => synodicPeriodSeconds(driver2PeriodSecondsAtAge(tMa, periodJ2000Seconds, EPOCH_PARAMS.solarMassLossFracPerYear), meanSiderealYearSecondsAtAge(tMa)),
456
+ });
457
+ }
@@ -43,8 +43,7 @@ const { createComposedPrecession } = require('../earth/precession-composed.cjs')
43
43
  * @property {number} eFactorMoon - √(1−e²) factor of the lunar orbit
44
44
  * @property {number} aLockMetres - tidal-lock distance (LOD denominator → 0)
45
45
  * @property {number} aMoonNowMetres - Farhat polynomial anchor
46
- * @property {number} alpha1PerMa @property {number} alpha3PerMa3
47
- * @property {number} alpha4PerMa4
46
+ * @property {number} alpha1PerMa - the LLR recession rate (the J2000 slope of the cited history)
48
47
  * @property {number} lodNowH13Seconds - ω₀, the J2000 day length (the SI day since plan 09; identifier kept)
49
48
  * @property {number} meanSiderealYearJ2000Seconds
50
49
  * @property {number} solarMassLossFracPerYear
@@ -55,7 +54,7 @@ const { createComposedPrecession } = require('../earth/precession-composed.cjs')
55
54
  /**
56
55
  * @typedef {Object} DeepTimeLodDeps
57
56
  * @property {DeepTimeLodConstants} constants
58
- * @property {(tMa: number) => number} moonDistanceMetresAtAge
57
+ * @property {(tMa: number) => number|null} moonDistanceMetresAtAge - null beyond the cited history's span
59
58
  * @property {(tMa: number) => number} moiFactorAtAge - GIA α(t)
60
59
  * @property {(year: number) => number} cycleLodSumAt - gated δLOD sum (incl. swing)
61
60
  * @property {(year: number) => number} swingLodAt - gated swing δLOD alone
@@ -74,9 +73,18 @@ const { createComposedPrecession } = require('../earth/precession-composed.cjs')
74
73
  * formerly the spin unit's H/5, 67,063 yr, 2.5 % off and scaling with the
75
74
  * spin at deep time). Lazy, like the precession anchor.
76
75
  * @property {(tMa: number) => number} [lEmAtAgeKgm2S] - OPTIONAL time-dependent
77
- * Earth-Moon angular momentum (the Driver-1½ solar channels,
78
- * recession-history.cjs). Absent → the J2000 constant, which the budget
79
- * module equals exactly for t ≤ jointMa — the pure-twin convention.
76
+ * Earth-Moon angular momentum. Absent → the J2000 constant: conservation
77
+ * (plan 12 option A — the Driver-1½ solar-channel budget is deleted;
78
+ * measured: conservation on the cited history reproduces Farhat's own LOD
79
+ * to 0.01 h at 100 Ma, 0.14 h at 2.46 Ga).
80
+ * @property {(tMa: number) => number|null} [moonDistanceSlopeMetresPerMaAtAge] -
81
+ * OPTIONAL analytic slope of the distance history (metres per Ma, positive
82
+ * into the past means a growing a... sign as da/dt_Ma); absent → a central
83
+ * difference on moonDistanceMetresAtAge (the LOD-rate decomposition's tidal term).
84
+ * @property {(tMa: number) => number|null} [meanObliquityCosRatioAtAge] -
85
+ * OPTIONAL cos ε̄(t)/cos ε̄(0) of the cited mean obliquity (earth/mean-
86
+ * obliquity-history) — the factor the precession RATE of date carries on the
87
+ * constant (plan 12 §8 option (a)); absent → 1, the constant alone, bit-identical.
80
88
  */
81
89
 
82
90
  /** @param {DeepTimeLodDeps} deps */
@@ -88,7 +96,7 @@ function createDeepTimeLod(deps) {
88
96
  * @param {number} t_Ma @returns {number|null} seconds, null past tidal lock */
89
97
  function lodSecondsAtAge(t_Ma) {
90
98
  const a = deps.moonDistanceMetresAtAge(t_Ma);
91
- if (a <= 0 || a >= K.aLockMetres) return null;
99
+ if (a === null || a <= 0 || a >= K.aLockMetres) return null;
92
100
  return (2 * Math.PI * (deps.moiFactorAtAge(t_Ma) * K.mEarthAloneKg * K.rEarthMetres * K.rEarthMetres)) /
93
101
  (lEmAt(t_Ma) - K.mMoonAloneKg * Math.sqrt(K.gmEmM3PerS2 * a) * K.eFactorMoon);
94
102
  }
@@ -97,7 +105,7 @@ function createDeepTimeLod(deps) {
97
105
  * @param {number} t_Ma @param {number} alpha @returns {number|null} */
98
106
  function lodSecondsAtAgeWithAlpha(t_Ma, alpha) {
99
107
  const a = deps.moonDistanceMetresAtAge(t_Ma);
100
- if (a <= 0 || a >= K.aLockMetres) return null;
108
+ if (a === null || a <= 0 || a >= K.aLockMetres) return null;
101
109
  const iEarth = alpha * K.mEarthAloneKg * K.rEarthMetres * K.rEarthMetres;
102
110
  return (2 * Math.PI * iEarth) /
103
111
  (lEmAt(t_Ma) - K.mMoonAloneKg * Math.sqrt(K.gmEmM3PerS2 * a) * K.eFactorMoon);
@@ -122,13 +130,13 @@ function createDeepTimeLod(deps) {
122
130
  // year with the model's ΔT memo). Same operations, same inputs — every
123
131
  // value bit-identical (11,958-value sweep, 0 differences).
124
132
 
125
- // The composed lunisolar precession rate (plan 06 D6 → Phase 3 → S5):
126
- // ψ̇(t) = [ω(t)/ω₀]·p₀·[f_S + (1 − f_S)(a₀/a_M(t))³], p₀ the model's
127
- // DERIVED J2000 rate 1,296,000/axial0 (the certified year-length laws'
128
- // beat at 2000 ≈ 25,771.4 yr — NOT 1,296,000/(H₀/13) = 50.245 ″/yr, the
129
- // fit anchor's reading, 0.086 % slow; S5). ONE formula home:
130
- // earth/precession-composed, built here on this factory's own LOD and
131
- // recession history; the anchor resolves lazily on the first rate use.
133
+ // The composed lunisolar precession (plan 06 D6 → Phase 3 → S5; plan 12 §8):
134
+ // the CONSTANT's scaling [ω(t)/ω₀]·p₀·[f_S + (1 − f_S)(a₀/a_M(t))³] and the
135
+ // RATE of date, that × cos ε̄(t)/cos ε̄(0) on the cited mean obliquity; p₀ the
136
+ // model's J2000 rate 1,296,000/axial0 (the IAU 2006 general precession since
137
+ // plan 09). ONE formula home: earth/precession-composed, built here on this
138
+ // factory's own LOD, recession and obliquity histories; the anchor resolves
139
+ // lazily on the first rate use.
132
140
  const composed = createComposedPrecession({
133
141
  p0ArcsecPerYr: () => 1296000 / deps.precessionPeriodJ2000YearsFn(),
134
142
  solarShare: K.precessionSolarShareJ2000,
@@ -137,23 +145,44 @@ function createDeepTimeLod(deps) {
137
145
  moonDistanceMetresAtAge: deps.moonDistanceMetresAtAge,
138
146
  moonDistanceJ2000Metres: K.aMoonNowMetres,
139
147
  yearToTMa: /** @param {number} year */ (year) => (2000 - year) / 1e6,
148
+ meanObliquityCosRatioAtAge: deps.meanObliquityCosRatioAtAge,
140
149
  });
141
150
 
142
151
 
143
- /** The composed lunisolar precession rate, ″/yr — p₀·[ω/ω₀]·[f_S + (1 − f_S)
144
- * (a₀/a_M)³] on the derived J2000 anchor (S5). ≡ 1,296,000·(H₀/axial0)/H(t).
152
+ /** THE PRECESSION RATE OF DATE ψ̇(t), ″/yr — the constant's scaling
153
+ * p₀·[ω/ω₀]·[f_S + (1 − f_S)(a₀/a_M)³] × cos ε̄(t)/cos ε̄(0), ε̄ the cited mean
154
+ * obliquity (plan 12 §8 option (a); the chain held cos ε̄ at J2000 before —
155
+ * 2.5 % of rate at 2.46 Ga, 2e-7 at 10 kyr). p₀ the J2000 anchor (S5).
145
156
  * @param {number} t_Ma @returns {number|null} */
146
157
  function lunisolarPrecessionRateArcsecPerYrAtAge(t_Ma) {
147
158
  return composed.composedRateArcsecPerYrAtAge(t_Ma);
148
159
  }
149
160
 
150
- /** The mean lunisolar precession period T_p(t), years — 1,296,000/ψ̇(t);
151
- * 25,771.4 at J2000 (the model's one J2000 precession reading, S5).
161
+ /** The mean lunisolar precession period T_p(t), years — 1,296,000/ψ̇(t) on
162
+ * the rate of date; 25,771.6 at J2000 (the model's one J2000 precession
163
+ * reading, S5). THE published deep-time clock.
152
164
  * @param {number} t_Ma @returns {number|null} */
153
165
  function lunisolarPrecessionPeriodYearsAtAge(t_Ma) {
154
166
  return composed.composedPeriodYearsAtAge(t_Ma);
155
167
  }
156
168
 
169
+ /** THE PRECESSION CONSTANT OF DATE as a rate on the J2000 obliquity, ″/yr —
170
+ * the spin and torque scaling alone, α(t)·cos ε̄₀. The hybrid obliquity
171
+ * integration takes this one: its own ŝ·n̂ carries the cosine.
172
+ * @param {number} t_Ma @returns {number|null} */
173
+ function lunisolarTorqueRateArcsecPerYrAtAge(t_Ma) {
174
+ return composed.torqueRateArcsecPerYrAtAge(t_Ma);
175
+ }
176
+ /** 1,296,000/the torque rate, years. @param {number} t_Ma @returns {number|null} */
177
+ function lunisolarTorquePeriodYearsAtAge(t_Ma) {
178
+ return composed.torquePeriodYearsAtAge(t_Ma);
179
+ }
180
+ /** cos ε̄(t)/cos ε̄(0) — the rate's obliquity factor (1 at J2000; 1 when no
181
+ * history is injected). @param {number} t_Ma @returns {number|null} */
182
+ function precessionObliquityFactorAtAge(t_Ma) {
183
+ return composed.obliquityFactorAtAge(t_Ma);
184
+ }
185
+
157
186
  /** Sidereal year seconds (Kepler under linear mass loss, dT/T = −2 dM/M).
158
187
  * @param {number} t_Ma @returns {number} */
159
188
  function siderealYearSecondsAtAge(t_Ma) {
@@ -172,7 +201,8 @@ function createDeepTimeLod(deps) {
172
201
 
173
202
  /** Tropical year of date, seconds — the sidereal year less the equinox's
174
203
  * own motion over one year: one turn per the composed lunisolar precession
175
- * period T_p(t), 1/T_trop = 1/T_sid + ψ̇(t)/(1,296,000″ · Julian-year s).
204
+ * period T_p(t), 1/T_trop = 1/T_sid + ψ̇(t)/(1,296,000″ · Julian-year s),
205
+ * ψ̇(t) the RATE of date (with the cited mean obliquity's cosine, plan 12 §8).
176
206
  * Plan 09 step 5d: it was the unit's calendar convention sid·(1 − 13/H(t))
177
207
  * — plan 06 D2 had kept it as a named device — 0.086 % slow on the equinox
178
208
  * term (1.0 s of year at J2000), and the deep JD↔year calendar integrates
@@ -187,7 +217,8 @@ function createDeepTimeLod(deps) {
187
217
  const sidSec = siderealYearSecondsAtAge(t_Ma);
188
218
  const term = composed.torqueTermAtAge(t_Ma);
189
219
  const p0 = composed.p0ArcsecPerYr;
190
- const rate = (LOD_s === null || !(LOD_s > 0) || term === null) ? p0 : (K.lodNowH13Seconds / LOD_s) * p0 * term;
220
+ const f = composed.obliquityFactorAtAge(t_Ma);
221
+ const rate = (LOD_s === null || !(LOD_s > 0) || term === null || f === null) ? p0 : (K.lodNowH13Seconds / LOD_s) * p0 * term * f;
191
222
  return 1 / (1 / sidSec + rate / (1296000 * 365.25 * 86400));
192
223
  }
193
224
 
@@ -300,10 +331,20 @@ function createDeepTimeLod(deps) {
300
331
  const I_E = alpha * K.mEarthAloneKg * K.rEarthMetres * K.rEarthMetres;
301
332
  const year = 2000 - t_Ma * 1e6;
302
333
 
303
- // Farhat polynomial derivative: da/dyear = −A_now·(α₁ + 3α₃t² + 4α₄t³)/1e6
304
- // (t_Ma > 0 = past; forward-time sign flip). At J2000: +3.82 cm/yr (LLR) ✓
305
- const da_dt_yr = -K.aMoonNowMetres * (K.alpha1PerMa + 3 * K.alpha3PerMa3 * t_Ma * t_Ma
306
- + 4 * K.alpha4PerMa4 * t_Ma * t_Ma * t_Ma) / 1e6;
334
+ // The cited history's slope (plan 12 option A): the engine's
335
+ // moonDistanceSlopeMetresPerMaAtAge when it provides one (the Hermite's
336
+ // analytic derivative — C1, smooth), else a ±1-kyr central difference on
337
+ // the distance itself. t_Ma > 0 = past; forward-time sign flip. At J2000
338
+ // the slope IS the LLR observation (the history's boundary condition):
339
+ // +3.82 cm/yr ✓
340
+ let slopePerMa = deps.moonDistanceSlopeMetresPerMaAtAge ? deps.moonDistanceSlopeMetresPerMaAtAge(t_Ma) : null;
341
+ if (slopePerMa === null || slopePerMa === undefined) {
342
+ const H_MA = 1e-3;
343
+ const aPlus = deps.moonDistanceMetresAtAge(t_Ma + H_MA), aMinus = deps.moonDistanceMetresAtAge(t_Ma - H_MA);
344
+ if (aPlus === null || aMinus === null) return nullResult;
345
+ slopePerMa = (aPlus - aMinus) / (2 * H_MA);
346
+ }
347
+ const da_dt_yr = -slopePerMa / 1e6;
307
348
  const SEC_PER_YR = 365.25 * 86400;
308
349
  const da_dt_s = da_dt_yr / SEC_PER_YR;
309
350
 
@@ -346,6 +387,8 @@ function createDeepTimeLod(deps) {
346
387
  return {
347
388
  lodSecondsAtAge, lodSecondsAtAgeWithAlpha, lodHoursAtAge,
348
389
  lunisolarPrecessionRateArcsecPerYrAtAge, lunisolarPrecessionPeriodYearsAtAge,
390
+ // the constant's scaling alone (the hybrid's integrand) and the rate's obliquity factor (plan 12 §8)
391
+ lunisolarTorqueRateArcsecPerYrAtAge, lunisolarTorquePeriodYearsAtAge, precessionObliquityFactorAtAge,
349
392
  // the composition's terms (plan 06 Phase 3 S3, the lunisolar surface): (a₀/a_M)³ and f_S + (1 − f_S)(a₀/a_M)³
350
393
  lunarTorqueFactorAtAge: composed.lunarTorqueFactorAtAge,
351
394
  precessionTorqueTermAtAge: composed.torqueTermAtAge,