@essrt/physics 23.3.3 → 24.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 (65) hide show
  1. package/README.md +4 -1
  2. package/package.json +2 -2
  3. package/src/cardinal/one-source-structure.cjs +3 -1
  4. package/src/constants/coefficients.d.ts +5 -1
  5. package/src/constants/coefficients.js +3 -2
  6. package/src/constants/generated.d.ts +13 -14
  7. package/src/constants/generated.js +14 -15
  8. package/src/constants/units.cjs +4 -1
  9. package/src/deltat/deep-time.cjs +37 -72
  10. package/src/earth/deep-orbital-history.cjs +48 -34
  11. package/src/earth/year-lengths.cjs +2 -1
  12. package/src/eclipse/besselian.cjs +24 -11
  13. package/src/index.js +30 -48
  14. package/src/model/cardinal-instants.js +101 -0
  15. package/src/model/context.js +54 -0
  16. package/src/model/deep-time-chain.js +79 -0
  17. package/src/model/delta-t.js +38 -0
  18. package/src/model/derived-constants.js +58 -0
  19. package/src/model/earth-scalars.js +47 -0
  20. package/src/model/inv-plane-elements.js +56 -0
  21. package/src/model/lunar-theory.js +216 -0
  22. package/src/model/moon-epoch.js +28 -0
  23. package/src/model/one-source-movement.js +192 -0
  24. package/src/model/planet-elements-of-date.js +61 -0
  25. package/src/model/planet-records.js +63 -0
  26. package/src/model/planet-spin-channel.js +41 -0
  27. package/src/model/published-periods.js +38 -0
  28. package/src/model/sun-and-eclipses.js +199 -0
  29. package/src/model/surface.js +332 -0
  30. package/src/model/time-axis.js +24 -0
  31. package/src/model/year-day-lengths.js +42 -0
  32. package/src/model.js +62 -1612
  33. package/src/moon/arguments.cjs +27 -43
  34. package/src/planets/chain-artifact.js +2 -2
  35. package/src/planets/keplerian-chain.cjs +3 -3
  36. package/src/planets/model.cjs +2 -2
  37. package/types/constants/coefficients.d.ts +5 -1
  38. package/types/constants/generated.d.ts +13 -14
  39. package/types/constants/units.d.cts +3 -0
  40. package/types/deltat/deep-time.d.cts +18 -24
  41. package/types/earth/deep-orbital-history.d.cts +23 -0
  42. package/types/eclipse/besselian.d.cts +37 -18
  43. package/types/index.d.ts +1 -1
  44. package/types/model/cardinal-instants.d.ts +14 -0
  45. package/types/model/context.d.ts +87 -0
  46. package/types/model/deep-time-chain.d.ts +288 -0
  47. package/types/model/delta-t.d.ts +11 -0
  48. package/types/model/derived-constants.d.ts +25 -0
  49. package/types/model/earth-scalars.d.ts +14 -0
  50. package/types/model/inv-plane-elements.d.ts +6 -0
  51. package/types/model/lunar-theory.d.ts +72 -0
  52. package/types/model/moon-epoch.d.ts +10 -0
  53. package/types/model/one-source-movement.d.ts +74 -0
  54. package/types/model/planet-elements-of-date.d.ts +9 -0
  55. package/types/model/planet-records.d.ts +7 -0
  56. package/types/model/planet-spin-channel.d.ts +29 -0
  57. package/types/model/published-periods.d.ts +7 -0
  58. package/types/model/sun-and-eclipses.d.ts +53 -0
  59. package/types/model/surface.d.ts +281 -0
  60. package/types/model/time-axis.d.ts +12 -0
  61. package/types/model/year-day-lengths.d.ts +8 -0
  62. package/types/model.d.ts +10 -81
  63. package/types/moon/arguments.d.cts +5 -11
  64. package/types/planets/keplerian-chain.d.cts +4 -2
  65. package/types/planets/model.d.cts +3 -3
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Lunar theory — the shared-chain assembly: the derived months, the chain-cycle counters, the arguments, UT→TT, the Meeus series.
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+ import { createChainCycleIntegrator } from '../chain-cycles/index.cjs';
7
+ import { J2000_JD, JULIAN_CENTURY_DAYS } from '../constants/units.cjs';
8
+ import { createMoonArguments, jdToDecimalYear } from '../moon/arguments.cjs';
9
+ import { createMoonSeries } from '../moon/series.cjs';
10
+ import { driver2PeriodSecondsAtAge } from '../planets/orbit-chain.cjs';
11
+
12
+ /** @param {import('./time-axis.js').Ctx} ctx @returns the bindings the later slices read */
13
+ export function assembleLunarTheory(ctx) {
14
+ const {
15
+ C, F, late, EPOCH_PARAMS, axialPrecessionJ2000Days, chain, deepEcc, deepLod, meanDeltaTSecondsAtAge,
16
+ meanSiderealYearDays, meanSolarYearDays, meanTropicalYearJ2000Seconds, moonChain, moonDistanceKm,
17
+ moonDistanceMetresAtAge, moonSiderealMonth, oneSourceM, startmodelJD, startmodelYear, sunPerihelionDegAt,
18
+ yearFromJD,
19
+ } = ctx;
20
+ // Wiring mirrors the engine call sites exactly — tools/lib/deep-time.js
21
+ // (ecc channel, month chain, J2000 precession anchors),
22
+ // tools/lib/scene-graph.js (chain-cycles, arguments, series) and
23
+ // tools/verify/eclipse-audit.js (finders). Deep-time and framework-native
24
+ // are hardwired ON here: they are the shipped defaults; the A/B env
25
+ // toggles (SG_DEEP_TIME/MOON_ARGS_PURE_MEEUS) stay an engine concern.
26
+ const j2000JD = J2000_JD; // ONE home: constants/units (plan 14 step 2)
27
+ const julianCenturyDays = JULIAN_CENTURY_DAYS;
28
+
29
+ // J2000 Moon precession anchors (the IAU inputs themselves). The
30
+ // legacy-'ICRF'-named inputs hold the EQUINOX-OF-DATE observables (Meeus/
31
+ // IERS 3231.50 d / 6798.38 d); the star-referenced (inertial) periods
32
+ // follow by removing the equinox's own precession, 1/T_E = 1/T_I ∓ 1/T_p
33
+ // (perigee advances, node regresses), T_p the J2000 precession anchor.
34
+ const { moonApsidalJ2000Seconds } = chain;
35
+ // (The of-date perigee/node are the chain's count compositions —
36
+ // mcApsidalOfDate / mcNodalOfDate below.)
37
+
38
+ // Derived months on the IAU inputs (constants.js §Moon derived months)
39
+ const moonTropicalMonthDays = 1 / (1 / moonSiderealMonth + 1 / axialPrecessionJ2000Days);
40
+ const moonAnomalisticMonthDays = 1 / (1 / moonSiderealMonth - 86400 / moonApsidalJ2000Seconds);
41
+ const moonSynodicMonthDays = moonSiderealMonth * meanSiderealYearDays / (meanSiderealYearDays - moonSiderealMonth);
42
+
43
+ // The Layer-2 month/precession chain (Brouwer-Clemence m² scaling × the
44
+ // e_E-line modulation on the ONE deep e — decision (ii), plan 02 §8) is
45
+ // the deep-time chain's `moonChain` (deep-time-chain.js).
46
+
47
+ // Chain-cycle integrator. S5/S12 conventions: age anchor = startmodelYear
48
+ // (the scene's t_Ma convention), grid anchor C(2000) = 0 — grid anchor ≠
49
+ // age anchor, deliberately. One stable period fn per chain so the shared
50
+ // Float64Array tables key correctly and persist.
51
+ const chainCycles = createChainCycleIntegrator({
52
+ ageAnchorYear: startmodelYear,
53
+ tropicalYearJ2000Seconds: meanTropicalYearJ2000Seconds, // the SI tick (plan 09 step 7c′)
54
+ isDeepTime: () => true,
55
+ });
56
+ /** @param {number} tMa @returns {number|null} */
57
+ const nodalMonthPeriodFn = (tMa) => moonChain.nodalMonthSecondsAtAge(tMa);
58
+ /** @param {number} tMa @returns {number|null} */
59
+ const tropicalMonthPeriodFn = (tMa) => moonChain.tropicalMonthSecondsAtAge(tMa);
60
+ /** @param {number} tMa @returns {number|null} */
61
+ const anomalisticMonthPeriodFn = (tMa) => moonChain.anomalisticMonthSecondsAtAge(tMa);
62
+ /** @param {number} tMa @returns {number|null} */
63
+ const siderealMonthPeriodFn = (tMa) => moonChain.siderealMonthSecondsAtAge(tMa);
64
+ const jupiterT0Seconds = C.planetOrbitalElements.jupiter.orbitalPeriodInputDays * 86400;
65
+ /** @param {number} tMa @returns {number} */
66
+ const jupiterPeriodFn = (tMa) => driver2PeriodSecondsAtAge(tMa, jupiterT0Seconds, EPOCH_PARAMS.solarMassLossFracPerYear);
67
+ /** @param {number} a @param {number} b @returns {number|null} */
68
+ const mcDraconic = (a, b) => chainCycles.cyclesBetween(nodalMonthPeriodFn, a, b);
69
+ /** @param {number} a @param {number} b @returns {number|null} */
70
+ const mcTropical = (a, b) => chainCycles.cyclesBetween(tropicalMonthPeriodFn, a, b);
71
+ /** @param {number} a @param {number} b @returns {number|null} */
72
+ const mcAnomalistic = (a, b) => chainCycles.cyclesBetween(anomalisticMonthPeriodFn, a, b);
73
+ /** @param {number} a @param {number} b @returns {number|null} */
74
+ const mcJupiter = (a, b) => chainCycles.cyclesBetween(jupiterPeriodFn, a, b);
75
+ /** The Moon's sidereal (star-referenced) orbits — the series' A3 argument (plan 09 step 5c). @param {number} a @param {number} b @returns {number|null} */
76
+ const mcSidereal = (a, b) => chainCycles.cyclesBetween(siderealMonthPeriodFn, a, b);
77
+ /** @param {number} a @param {number} b @returns {number|null} */
78
+ const mcApsidalOfDate = (a, b) => {
79
+ const t = mcTropical(a, b), n = mcAnomalistic(a, b);
80
+ return (t === null || n === null) ? null : t - n;
81
+ };
82
+ /** @param {number} a @param {number} b @returns {number|null} */
83
+ const mcNodalOfDate = (a, b) => {
84
+ const dr = mcDraconic(a, b), t = mcTropical(a, b);
85
+ return (dr === null || t === null) ? null : dr - t;
86
+ };
87
+
88
+ // The argument skeleton (the _FW_MOON bundle; Sun secular deviations on
89
+ // the CALENDAR year coordinate — S3)
90
+ const moonArgs = createMoonArguments({
91
+ constants: {
92
+ j2000JD,
93
+ julianCenturyDays,
94
+ eccentricityJ2000: C.earthOrbital.earthEccentricityJ2000,
95
+ eccentricityDotJ2000: C.earthOrbital.earthEccentricityDotJ2000,
96
+ eccentricityDotDotJ2000: C.earthOrbital.earthEccentricityDotDotJ2000,
97
+ elpEarthFigureJ2ArcsecPerCy2: C.moonMeeus.elpW1T2Decomposition_arcsecPerCy2.earthFigureJ2,
98
+ elpGeneralPrecessionPA_T2ArcsecPerCy2: C.moonMeeus.elpW1T2Decomposition_arcsecPerCy2.generalPrecessionPA_T2_Lieske1976,
99
+ // Decision (ii): the lunar chain reads ONE deep e end to end
100
+ eccE0: deepEcc.e0,
101
+ },
102
+ fns: {
103
+ eccAt: /** @param {number} tYr */ (tYr) => deepEcc.eccAt(tYr),
104
+ channelIntegral: /** @param {number} T @param {number} s */ (T, s) => deepEcc.channelIntegral(T, s),
105
+ // The arguments' obliquity carrier reads the PUBLISHED ε (the one-source
106
+ // hybrid) — one ε everywhere. Twins: script.js (_sceneEpsTargetDeg), scene-graph.js.
107
+ computeObliquityEarth: /** @param {number} year @returns {number} */ (year) => oneSourceM.epsAt(year),
108
+ // The arguments' Sun IS the one certified Sun — the mean longitude that
109
+ // integrates the one-family tropical year (the eclipse finder's Sun,
110
+ // sun-and-eclipses.js) and the series' mean perihelion of date
111
+ // (unwrapped), both on the TT Julian-year axis. Twins: scene-graph.js,
112
+ // script.js.
113
+ sunMeanLonDegAtJdTT: /** @param {number} jdTT @returns {number} */ (jdTT) => late.sunMeanLongitudeDegAt(2000 + (jdTT - j2000JD) / 365.25),
114
+ sunPerihelionLonDegAtJdTT: /** @param {number} jdTT @returns {number} */ (jdTT) => sunPerihelionDegAt(2000 + (jdTT - j2000JD) / 365.25),
115
+ jdToSIyear: yearFromJD,
116
+ tropicalOrbitsBetween: mcTropical,
117
+ apsidalOfDateCyclesBetween: mcApsidalOfDate,
118
+ nodalOfDateCyclesBetween: mcNodalOfDate,
119
+ isDeepTime: () => true,
120
+ isFrameworkNative: () => true,
121
+ // (d′) of-date rate completion: the DYNAMICAL axial precession and the
122
+ // KINEMATIC pair's beat the chains embed. MATCHED TRIPLE with the
123
+ // tools-lib and browser wirings. The dynamical rate of date is the
124
+ // published precession period's (the one-family beat inside its window,
125
+ // the composed clock beyond — published-periods.js). Degrees per Julian year.
126
+ pDynDegPerYearAt: /** @param {number} year */ (year) => 360 / late.publishedAxialPrecessionYearsAtYear(year),
127
+ pKinDegPerYearAt: /** @param {number} year */ (year) => {
128
+ // MODEL-START-anchored age, matching the tools-lib/browser wirings
129
+ // bit-exactly — NOT yearToTMa's 2000.0 convention (the recorded
130
+ // 0.4977-yr parity trap, same class as getMoonDistanceKm in the series wiring below).
131
+ const t = (startmodelYear - year) / 1e6;
132
+ const sid = deepLod.siderealYearSecondsAtAge(t);
133
+ const trop = deepLod.tropicalYearSecondsAtAge(t);
134
+ return (sid === null || trop === null) ? 0 : 360 * (sid - trop) / sid;
135
+ },
136
+ },
137
+ });
138
+
139
+ // UT→TT on the CALENDAR decimal-year coordinate (script.js Phase 9.16 —
140
+ // a linear-year approximation here once cost ~5–6 s of ΔT and ~1e-3° of
141
+ // Moon longitude at the Babylonian epochs)
142
+ /** @param {number} jd @returns {number} */
143
+ const jdTTFromUT = (jd) => {
144
+ const tMa = (startmodelYear - jdToDecimalYear(jd)) / 1e6;
145
+ const dT = meanDeltaTSecondsAtAge(tMa);
146
+ return Number.isFinite(dT) ? jd + dT / 86400 : jd;
147
+ };
148
+ // ΔT at a JD on the CALENDAR-year axis (mirrors deep-time frameworkDeltaT;
149
+ // NOT deltaTSeconds of delta-t.js, which adds the deltaTStart anchor)
150
+ /** @param {number} jd @returns {number} */
151
+ const frameworkDeltaTSecondsAtJD = (jd) => {
152
+ const decYear = startmodelYear + (jd - startmodelJD) / meanSolarYearDays;
153
+ const dT = meanDeltaTSecondsAtAge((startmodelYear - decYear) / 1e6);
154
+ return Number.isFinite(dT) ? dT : 0;
155
+ };
156
+
157
+ // Bounded Meeus E-factor from the ONE deep e (decision (ii); the
158
+ // pure-Meeus polynomial A/B branch stays engine-local)
159
+ /** @param {number} dDays @returns {number} */
160
+ // (on the channel's Julian-year axis — the N-body series' time)
161
+ const fwEFactor = (dDays) => deepEcc.eFactorAt(dDays / 365.25);
162
+
163
+ // D2 derived additional-argument rates (deg/cy, from the J2000 month inputs —
164
+ // docs/109 §22 for the derivation)
165
+ const fwA2RateDegPerCy = 2 * ((360 * 36525) / moonTropicalMonthDays)
166
+ - (360 * 36525) / moonAnomalisticMonthDays
167
+ - 2 * ((360 * 36525) / C.planetOrbitalElements.jupiter.orbitalPeriodInputDays);
168
+ const fwA3RateDegPerCy = (360 * 36525) / moonSiderealMonth;
169
+
170
+ // Meeus Ch. 47 truncated series (framework-native arguments + E-factor)
171
+ const moonSeries = createMoonSeries({
172
+ constants: {
173
+ moonL: F.MEEUS_LONGITUDE_TERMS,
174
+ moonB: F.MEEUS_LATITUDE_TERMS,
175
+ moonR: F.MEEUS_DISTANCE_TERMS.terms,
176
+ moonRMeanKm: F.MEEUS_DISTANCE_TERMS.meanKm,
177
+ moonDistanceJ2000Km: C.moonReference.moonDistance,
178
+ j2000JD,
179
+ julianCenturyDays,
180
+ fwA2RateDegPerCy,
181
+ fwA3RateDegPerCy,
182
+ },
183
+ fns: {
184
+ argsAt: /** @param {number} jdTT */ (jdTT) => moonArgs.argsAt(jdTT),
185
+ eFactorForD: fwEFactor,
186
+ eFactorAtJdTT: /** @param {number} jdTT */ (jdTT) => fwEFactor(jdTT - j2000JD),
187
+ // 20.3d(i): the Driver-1 ratio at the EVALUATED epoch (per-jd pure
188
+ // evaluator). MATCHED TRIPLE with the tools-lib and browser getters:
189
+ // identical age arithmetic — the SI-linear year (yearFromJD, never
190
+ // the calendar year: the recorded linear-vs-calendar mirror trap)
191
+ // against the MODEL-START anchor (startmodelYear, the browser's
192
+ // J2000_CALENDAR_YEAR — NOT this model's 2000.0 yearToTMa convention:
193
+ // the 0.5-yr difference is 4.95e-11 of distance and failed the
194
+ // bit-exact parity gate; plan 11 step 5 dropped the −0.83-d correction
195
+ // from the anchor, both twins alike). Falls back to the J2000 constant
196
+ // without a jd (legacy call shape) or past the domain.
197
+ getMoonDistanceKm: /** @param {number} [jdTT] */ (jdTT) => {
198
+ if (jdTT === undefined) return moonDistanceKm;
199
+ const d = moonDistanceMetresAtAge((startmodelYear - yearFromJD(jdTT)) / 1e6);
200
+ return d === null ? moonDistanceKm : d / 1000;
201
+ },
202
+ getEccentricityBase: () => C.moonReference.moonOrbitalEccentricityBase,
203
+ deltaTSeconds: /** @param {number} jd */ (jd) => (jdTTFromUT(jd) - jd) * 86400,
204
+ jdToSIyear: yearFromJD,
205
+ tropicalOrbitsBetween: mcTropical,
206
+ apsidalOfDateCyclesBetween: mcApsidalOfDate,
207
+ siderealOrbitsBetween: mcSidereal,
208
+ jupiterOrbitsBetween: mcJupiter,
209
+ isDeepTime: () => true,
210
+ isFrameworkNative: () => true,
211
+ },
212
+ });
213
+
214
+ return { frameworkDeltaTSecondsAtJD, j2000JD, jdTTFromUT, julianCenturyDays, moonArgs, moonSeries, moonSynodicMonthDays };
215
+ }
216
+ /** @typedef {import('./time-axis.js').Ctx & ReturnType<typeof assembleLunarTheory>} Ctx */
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The Moon at epoch — the solar-Δa-corrected Kepler month.
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+
7
+ /** @param {import('./one-source-movement.js').Ctx} ctx @returns the bindings the later slices read */
8
+ export function assembleMoonEpoch(ctx) {
9
+ const {
10
+ EPOCH_PARAMS, MASS_RATIO_EARTH_MOON, deepLod, moonDistanceMetresAtAge, moonSiderealMonthInput, yearToTMa,
11
+ } = ctx;
12
+ /** Solar-Δa-corrected Kepler month. @param {number} year @returns {number} */
13
+ const moonSiderealMonthDaysAt = (year) => {
14
+ const tMa = yearToTMa(year);
15
+ const lod = deepLod.lodSecondsAtAge(tMa);
16
+ const aAppM = moonDistanceMetresAtAge(tMa);
17
+ if (lod === null || aAppM === null) return NaN;
18
+ const aAppKm = aAppM / 1000;
19
+ const sidYrDays = deepLod.siderealYearSecondsAtAge(tMa) / lod;
20
+ const deltaA = aAppKm * (1 / (MASS_RATIO_EARTH_MOON + 1)) * (moonSiderealMonthInput / sidYrDays);
21
+ const aCorrM = (aAppKm + deltaA) * 1000;
22
+ const monthSeconds = 2 * Math.PI * Math.sqrt(Math.pow(aCorrM, 3) / EPOCH_PARAMS.gmEarthMoonM3S2);
23
+ return monthSeconds / lod;
24
+ };
25
+
26
+ return { moonSiderealMonthDaysAt };
27
+ }
28
+ /** @typedef {import('./one-source-movement.js').Ctx & ReturnType<typeof assembleMoonEpoch>} Ctx */
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The one-source movement — the hybrid's ε(t), e(t), ϖ(t) on the per-tier sampler, the ONE year-length family, and the days of date on them.
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+ import { createDeepOrbitalHistory } from '../earth/deep-orbital-history.cjs';
7
+ import { ONE_FAMILY_WINDOW_YEARS, createYearLengths } from '../earth/year-lengths.cjs';
8
+ import { DEEP_MODES_ARTIFACT } from '../moon/deep-modes-artifact.cjs';
9
+ import { CHAIN_ARTIFACT } from '../planets/chain-artifact.js';
10
+
11
+ /** @param {import('./year-day-lengths.js').Ctx} ctx @returns the bindings the later slices read */
12
+ export function assembleOneSourceMovement(ctx) {
13
+ const {
14
+ C, options, secularSeriesArtifact, EPOCH_PARAMS, alphaGia, certifiedAxialPrecessionJ2000Years, dayLengthSeconds,
15
+ deepLod, dtCycleLodCorrectionSum, meanSolarYearDays, startmodelYear, yearToTMa,
16
+ } = ctx;
17
+ // The cardinal INSTANTS are the crossings of the one Sun the scene renders
18
+ // and the eclipse chain certifies (cardinal-instants.js); the cardinal
19
+ // STRUCTURE here is the EoC layer (year lengths, crossing offsets, the
20
+ // e(t)-proportional spread) on the one-source movement's own e(t)/ϖ(t) —
21
+ // the MODE tier (the package's embedded deep-modes artifact; the series
22
+ // tier when the governed artifact is injected). Absolute dates deliberately
23
+ // absent — see cardinal/one-source-structure.cjs. The mean year is SI
24
+ // SECONDS: the sidereal year of date reduced by the SECULAR equinox
25
+ // precession on the composed T_p(t).
26
+ // THE ONE of-date year-length family — createYearLengths owns tropical
27
+ // (equinox-rate mean, wobble included), sidereal (the λ̇ channel),
28
+ // anomalistic (the cardinal structure on the SAME mean), all λ̇-corrected
29
+ // coherently, SI seconds, and the beats of that pair.
30
+ // S3 tier unification: when the caller injects the governed secular-
31
+ // series artifact (createModel opts.secularSeriesArtifact — the API/Node
32
+ // path; the npm package alone has no 8 MB series), the movement runs the
33
+ // SERIES tier exactly like the browser — killing the mode-vs-series
34
+ // value split (measured: 6 s on the anomalistic year at J2000).
35
+ // The ONE-SOURCE movement: the hybrid's ε(t) (plan 06 Phase 3 S3b — THE
36
+ // published obliquity) and the one-family year lengths, on the same
37
+ // per-tier sampler.
38
+ const oneSourceM = (() => {
39
+ const AEarth = /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements.earth;
40
+ // The ψ̇ anchor: the IAU 2006 general precession p_A (25,771.575 yr — the
41
+ // ONE home certifiedAxialPrecessionJ2000Years, twins in tools-lib and the
42
+ // browser).
43
+ const axial0 = certifiedAxialPrecessionJ2000Years(); // the ONE home
44
+ // The deep-time ψ̇(t) the hybrid precesses on is the COMPOSED lunisolar
45
+ // rate — its period of date T_p(t) read directly; the composition carries
46
+ // the lunar 1/a³ growth (104.5 ″/yr at 2.46 Ga against Lantink 2022's
47
+ // 108.6 ± 8.5).
48
+ const seriesArt = /** @type {any} */ (secularSeriesArtifact);
49
+ const sb = seriesArt && seriesArt.bodies && seriesArt.bodies.earth;
50
+ // Plan 12 §8.5 (owner): the hybrid's own domain is the banked ζ series' span (years; null
51
+ // without a series). Beyond it the ζ-mode tail carries the Quaternary mean with no tidal
52
+ // evolution — the API refuses the obliquity there and serves the cited mean beside it.
53
+ const obliquityHybridDomainYears = sb
54
+ ? /** @type {[number, number]} */ ([2000 + seriesArt.t0Yr, 2000 + seriesArt.t0Yr + (sb.zetaQ.length - 1) * sb.stepYr])
55
+ : null;
56
+ const hist = createDeepOrbitalHistory({
57
+ zModes: DEEP_MODES_ARTIFACT.earthZ,
58
+ zetaModes: DEEP_MODES_ARTIFACT.earthZeta,
59
+ ...(sb ? {
60
+ zetaSeries: { t0Yr: seriesArt.t0Yr, stepYr: sb.stepYr, q: sb.zetaQ, p: sb.zetaP },
61
+ zSeries: { t0Yr: seriesArt.t0Yr, stepYr: sb.stepYr, q: sb.zQ, p: sb.zP },
62
+ } : {}),
63
+ anchorE: AEarth.e,
64
+ anchorPeriEclipticDeg: AEarth.lonPeriEclipticDeg,
65
+ anchorInclEclipticDeg: AEarth.inclEclipticDeg,
66
+ anchorAscNodeEclipticDeg: AEarth.ascNodeEclipticDeg,
67
+ axialPrecessionYearsJ2000: axial0,
68
+ // The hybrid's initial condition is the OBSERVED J2000 mean obliquity —
69
+ // the IAU 2006 input constant (84381.406″), the same anchor the
70
+ // browser's _deepHistSeries and the Node one-source movement use.
71
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
72
+ // The CONSTANT's scaling (the torque period), not the published rate of date: the
73
+ // integration's own ŝ·n̂ carries the obliquity's cosine, so the cited mean obliquity's
74
+ // factor (plan 12 §8 option (a)) must not enter twice. Bit-identical to the pre-§8 form.
75
+ axialPrecessionYearsAtYearFn: (yr) => deepLod.lunisolarTorquePeriodYearsAtAge((startmodelYear - yr) / 1e6) ?? axial0,
76
+ // THE PRECESSION OF DATE: on top of the secular law above, the solar
77
+ // torque at the eccentricity of date and the oblateness of date from
78
+ // the GIA channel — both exactly 1 at J2000, so the J2000 anchor is
79
+ // untouched. The channel is
80
+ // read PURE (alphaGia, not the lattice-pin-aware earthMoiFactorAtAge):
81
+ // the samplers are cached, their values must be pure in year.
82
+ solarTorqueShareJ2000: EPOCH_PARAMS.precessionSolarShareJ2000,
83
+ // options.giaEllipticityOfDate === false: the orbital forcing alone (the T1 line derivation).
84
+ dynamicalEllipticityRatioAtYearFn: options.giaEllipticityOfDate === false ? undefined : alphaGia.j2RatioAt,
85
+ });
86
+ // PER-TIER ROUTING (the anomalistic-contamination fix): one sampler PER
87
+ // grid tier, built on first entry and KEPT — a query always reads the
88
+ // tier its own span selects. The former single grown sampler replaced
89
+ // the 100-yr grid with the 1000/5000-yr one after any deep-time probe,
90
+ // and the year-length rates (±0.5-yr central differences through the
91
+ // grid) then returned grid-segment averages instead of local rates
92
+ // (anomalistic of date +2.63 s / +19 s, visit-order dependent).
93
+ const samplers = /** @type {Map<number, any>} */ (new Map());
94
+ const gridStep = (/** @type {number} */ n) => (n <= 50000 ? 100 : n <= 2000000 ? 1000 : 5000);
95
+ const tierSpan = (/** @type {number} */ n) => (n <= 50000 ? 50000 : 2000000);
96
+ const sampleAt = (/** @type {number} */ year) => {
97
+ const t = year - 2000, need = Math.max(20000, Math.abs(t) * 1.25);
98
+ if (need > 2000000) {
99
+ // The deep tier (5000-yr grid): walked in the queried DIRECTION only,
100
+ // to two nodes past the query, and EXTENDED by the factory's shared
101
+ // walk when a deeper epoch arrives (plan 13 A1). It was built to
102
+ // ±1.25·|t| in both directions and rebuilt from J2000 on every deeper
103
+ // probe — bit-identical values, 4× the RK4 steps of one walk.
104
+ const span = Math.ceil((Math.abs(t) + 2 * 5000) / 5000) * 5000;
105
+ return hist.build(t < 0 ? -span : span, 0, 5000).at(t);
106
+ }
107
+ const span = tierSpan(need);
108
+ if (!samplers.has(span)) samplers.set(span, hist.build(span, -span, gridStep(span)));
109
+ return samplers.get(span).at(t);
110
+ };
111
+ // The anomalistic year rides THIS movement's own apsidal line (the
112
+ // scene's perihelion) — the factory owns the construction; the planet
113
+ // chain's secular tangent is no longer a member (year-lengths.cjs).
114
+ const yearLengths = createYearLengths({
115
+ sampleAt,
116
+ massLossSiderealSecondsAtYearFn: (year) => deepLod.siderealYearSecondsAtAge(yearToTMa(year)),
117
+ });
118
+ return {
119
+ yearLengths,
120
+ /** The one-source sample at a decimal year (the engine year) — ε, e, ϖ of date, the Earth-orbit normal (the fields the frame of date and the invariable-plane elements read). @param {number} year */
121
+ sampleAt,
122
+ /** EXPERIMENT accessors: the series' e and equinox-referenced ϖ of date. @param {number} year @returns {number} */
123
+ eAt: (year) => sampleAt(year).e,
124
+ /** @param {number} year @returns {number} */
125
+ periOfDateDegAt: (year) => sampleAt(year).periOfDateDeg,
126
+ /** The hybrid's obliquity at a decimal year, degrees — the banked series inside its span, the α(t)-coupled ζ-tail integration beyond (the deep sampler grows ~0.1 s/Myr). @param {number} year @returns {number} */
127
+ epsAt: (year) => sampleAt(year).epsDeg,
128
+ /** The hybrid's own domain, [yearFrom, yearTo] — the banked ζ series' span (plan 12 §8.5); null without a series. */
129
+ obliquityHybridDomainYears,
130
+ /** The precession of date's two factors at a decimal year (the factory's own — ONE home). @param {number} year @returns {{solarTorque: number, ellipticity: number}} */
131
+ precessionOfDateFactorsAt: (year) => hist.precessionOfDateFactorsAt(year - 2000),
132
+ /** The spin axis's INERTIAL precession period at J2000, years — the self-anchored
133
+ * lunisolar rate the hybrid integrates ŝ on (deep-orbital-history D4e): the general
134
+ * precession anchor plus the planetary mean the ecliptic's own motion carries. */
135
+ axialPrecessionYearsLunisolarJ2000: hist.axialPrecessionYearsLunisolarJ2000,
136
+ };
137
+ })();
138
+ const yearLengthsM = oneSourceM.yearLengths;
139
+ /** THE one-family tropical year of date in SI seconds: route B inside the
140
+ * one-family window, the tidal-chain year beyond (plan 06 R1 — the Sun's
141
+ * mean longitude, the cardinal instants and the published year lengths
142
+ * all ride this one function). @param {number} year @returns {number} */
143
+ const oneFamilyTropicalYearSeconds = (year) => (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS
144
+ ? yearLengthsM.tropicalYearSecondsAtYear(year)
145
+ : (deepLod.tropicalYearSecondsAtAge(yearToTMa(year)) ?? meanSolarYearDays * 86400));
146
+
147
+ /** Tropical year of date in DAYS OF THE EPOCH (the kinematic day,
148
+ * year-day-lengths.js) — the one-family SI year over the epoch's day
149
+ * length, so solarYearSeconds ≡ the one-family year. @param {number} year @returns {number} */
150
+ const tropicalYearDays = (year) => oneFamilyTropicalYearSeconds(year) / dayLengthSeconds(year);
151
+
152
+ /** Layer-4 solar day of date in SI seconds — LOD_real, the shipped
153
+ * observable (the tweakpane Solar Day row, the registry's lodRealPhysical,
154
+ * the Framework Verification panel's day): the kinematic day of date + the
155
+ * ecliptic missing-motion term on the nodal period + the calibrated ΔT
156
+ * cycle stack incl. the core-mantle swing. Twin of tools/lib
157
+ * computeLodRealSecondsAtEpoch and the browser's _vfpSolarDaySecondsOfDate
158
+ * (86,400.001780 s at 2000). @param {number} year @returns {number} */
159
+ const lodRealSeconds = (year) =>
160
+ dayLengthSeconds(year)
161
+ + (deepLod.eclipticLodCorrectionSecondsAtAge(yearToTMa(year)) ?? 0)
162
+ + dtCycleLodCorrectionSum(year);
163
+ /** Mean sidereal day of date in SI seconds — on LOD_real (plan 08 storyline
164
+ * sweep, 2026-10-09: the published day bases are the SI day of exactly
165
+ * 86,400 s, the IAU constants, and LOD_real; this rode the KINEMATIC day
166
+ * before, 1.7 ms below the simulator's readout at J2000). One rotation
167
+ * more against the equinox than there are solar days in a year.
168
+ * @param {number} year @returns {number} */
169
+ const siderealDaySeconds = (year) => {
170
+ const Y = tropicalYearDays(year);
171
+ return (lodRealSeconds(year) * Y) / (Y + 1);
172
+ };
173
+ /** Mean stellar day of date in SI seconds — the sidereal day plus the
174
+ * equinox's daily regression projected onto the equator (on LOD_real, like
175
+ * the sidereal day). @param {number} year @returns {number} */
176
+ const stellarDaySeconds = (year) => {
177
+ const tMa = yearToTMa(year);
178
+ // One equinox turn per T_p(t) — the composed lunisolar period (its
179
+ // certified J2000 anchor past the chain's domain).
180
+ const TpRaw = deepLod.lunisolarPrecessionPeriodYearsAtAge(tMa);
181
+ const Tp = TpRaw === null ? certifiedAxialPrecessionJ2000Years() : TpRaw;
182
+ const syD = tropicalYearDays(year);
183
+ const sidDay = siderealDaySeconds(year);
184
+ // The RA projection reads the published ε (the one-source hybrid) — the
185
+ // browser's _sceneEpsTargetDeg twin; 1e-8 s.
186
+ const raProjection = Math.cos((oneSourceM.epsAt(year) * Math.PI) / 180);
187
+ return (sidDay / Tp / (syD + 1)) * raProjection + sidDay;
188
+ };
189
+
190
+ return { lodRealSeconds, oneFamilyTropicalYearSeconds, oneSourceM, siderealDaySeconds, stellarDaySeconds, tropicalYearDays, yearLengthsM };
191
+ }
192
+ /** @typedef {import('./year-day-lengths.js').Ctx & ReturnType<typeof assembleOneSourceMovement>} Ctx */
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The planets' elements of date — the chain with the series handover: ϖ, Ω (S&S origin), i, e.
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+ import { DEEP_MODES_ARTIFACT } from '../moon/deep-modes-artifact.cjs';
7
+ import { CHAIN_ARTIFACT } from '../planets/chain-artifact.js';
8
+ import { computeEquatorNodeOriginSFrameDeg, convertNodeSFrameToEquatorOriginDeg } from '../planets/inv-plane-frame.cjs';
9
+ import { buildPlanetChainsFromArtifactData, computePlanetElementsAtYear } from '../planets/keplerian-chain.cjs';
10
+ import { createSecularSeriesOverride } from '../planets/secular-series.cjs';
11
+
12
+ /** @param {import('./planet-spin-channel.js').Ctx} ctx @returns the bindings the later slices read */
13
+ export function assemblePlanetElementsOfDate(ctx) {
14
+ const { C, secularSeriesArtifact } = ctx;
15
+ // The planets' elements of date ride THE CHAIN — the only planet path the
16
+ // simulator renders — with the series handover beyond each planet's
17
+ // measured boundary when the series artifact is supplied: the mirror of the
18
+ // browser's _chainElementsOfDate → _chainSeriesSecularEl. The chain's
19
+ // argument is dynamical time; the API's decimal year is taken as that
20
+ // argument (ΔT on a secular element is far below its stated accuracy).
21
+ let planetChains = /** @type {any} */ (null);
22
+ let planetSeriesOverride = /** @type {any} */ (null);
23
+ let planetNodeOriginSSDeg = /** @type {number|null} */ (null);
24
+ /** @param {string} k @param {number} year */
25
+ const planetChainElementsAt = (k, year) => {
26
+ if (!planetChains) planetChains = buildPlanetChainsFromArtifactData(/** @type {any} */ (CHAIN_ARTIFACT));
27
+ const el = computePlanetElementsAtYear(year, planetChains[k], planetChains);
28
+ const seriesArt = /** @type {any} */ (secularSeriesArtifact);
29
+ if (!seriesArt) return el;
30
+ if (!planetSeriesOverride) {
31
+ planetSeriesOverride = createSecularSeriesOverride({
32
+ series: seriesArt,
33
+ anchorElements: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements,
34
+ invariablePlane: /** @type {any} */ (CHAIN_ARTIFACT).invariablePlane,
35
+ planetZModes: /** @type {any} */ (DEEP_MODES_ARTIFACT).planetZ,
36
+ planetZetaModes: /** @type {any} */ (DEEP_MODES_ARTIFACT).planetZeta,
37
+ });
38
+ }
39
+ return planetSeriesOverride.applyToElements(k, year, el);
40
+ };
41
+ /** Ecliptic longitude of perihelion, J2000 ecliptic and equinox (the chain's ϖ). @param {string} k @param {number} year @returns {number} */
42
+ const planetPerihelionDeg = (k, year) => planetChainElementsAt(k, year).lonPeriEclipticDeg;
43
+ /** The S&S longitude origin as an s-frame longitude (J2000-fixed, derived). @returns {number} */
44
+ const nodeOriginSSDeg = () => {
45
+ if (planetNodeOriginSSDeg === null) {
46
+ planetNodeOriginSSDeg = computeEquatorNodeOriginSFrameDeg(/** @type {any} */ (CHAIN_ARTIFACT).invariablePlane, C.earthOrbital.obliquityJ2000_deg);
47
+ }
48
+ return planetNodeOriginSSDeg;
49
+ };
50
+ /** Ascending node on the model's invariable plane, from the plane's ascending node on the ICRF equator (the Souami & Souchay 2012 origin; derived conversion, inv-plane-frame). @param {string} k @param {number} year @returns {number} */
51
+ const planetAscNodeDeg = (k, year) => convertNodeSFrameToEquatorOriginDeg(planetChainElementsAt(k, year).ascNodeInvPlaneDeg, nodeOriginSSDeg());
52
+ /** Inclination to the model's invariable plane (exact orbit-normal rotation). @param {string} k @param {number} year @returns {number} */
53
+ const planetInclinationDeg = (k, year) => planetChainElementsAt(k, year).inclInvPlaneDeg;
54
+ /** Orbital eccentricity of date, from the same chain elements — the chain's
55
+ * multi-mode e-vector has no single base + amplitude + phase triple, so the
56
+ * value of date is the published form. @param {string} k @param {number} year @returns {number} */
57
+ const planetEccentricity = (k, year) => planetChainElementsAt(k, year).e;
58
+
59
+ return { nodeOriginSSDeg, planetAscNodeDeg, planetEccentricity, planetInclinationDeg, planetPerihelionDeg };
60
+ }
61
+ /** @typedef {import('./planet-spin-channel.js').Ctx & ReturnType<typeof assemblePlanetElementsOfDate>} Ctx */
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The planets' structural records — keys, mass fractions, the published J2000 record.
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+ import { CHAIN_ARTIFACT } from '../planets/chain-artifact.js';
7
+ import { PLANET_DISPLAY_NAMES } from '../planets/display-names.cjs';
8
+ import { computeObliquityJ2000Deg } from '../planets/spin-channel.cjs';
9
+
10
+ /** @param {import('./delta-t.js').Ctx} ctx @returns the bindings the later slices read */
11
+ export function assemblePlanetRecords(ctx) {
12
+ const { C, GM_EARTH_ALONE, G_CONSTANT, M_SUN } = ctx;
13
+ const PLANET_KEYS = ['mercury', 'venus', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune'];
14
+ const massEarthAlone = GM_EARTH_ALONE / G_CONSTANT;
15
+ /** @type {Record<string, number>} */
16
+ const massFraction = {};
17
+ for (const k of PLANET_KEYS) massFraction[k] = 1 / C.physicalConstants.massRatioDE440[k];
18
+ massFraction.earth = massEarthAlone / M_SUN;
19
+
20
+ /** @type {Record<string, Record<string, any>>} */
21
+ const PUBLISHED_PLANET_RECORDS = {}; // what model.planets.record() serves
22
+ for (const k of PLANET_KEYS) {
23
+ const ar = C.planetOrbitalElements[k];
24
+ // The obliquity is the DERIVED J2000 obliquity of the spin channel (the
25
+ // IAU pole against the chain's J2000 plane, acute form) — from the
26
+ // governed artifacts.
27
+ const obliquityDerived = computeObliquityJ2000Deg({
28
+ spin: C.planetSpinPhysical[k],
29
+ anchorInclEclipticDeg: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements[k].inclEclipticDeg,
30
+ anchorAscNodeEclipticDeg: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements[k].ascNodeEclipticDeg,
31
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
32
+ });
33
+ const obliquityMean = Math.min(obliquityDerived, 180 - obliquityDerived); // acute form of the derived J2000 obliquity
34
+ // What `model.planets.record()` PUBLISHES is the structural record: the
35
+ // REFERENCE inputs (IAU J2000 ϖ, the Souami & Souchay node and
36
+ // inclination, DE440 e, the period input, the axial tilt), the DERIVED
37
+ // `obliquityMean` (the spin channel against the chain's J2000 plane), and
38
+ // the two PERIODS that are chain eigenmodes — 1,296,000/g and 1,296,000/s
39
+ // (apsidal prograde positive, nodal regression negative). Quantities that
40
+ // vary with the epoch (e, i, Ω, ϖ of date) are served AT AN EPOCH by
41
+ // planet-elements-of-date.js and `/v1/bodies`' `at[]` rows, never as a
42
+ // single oscillation's mean + amplitude + phase — the chain's multi-mode
43
+ // vectors have none.
44
+ const chainModes = /** @type {any} */ (CHAIN_ARTIFACT);
45
+ const gArc = chainModes.g[k] && chainModes.g[k].arcsecPerYr;
46
+ const sArc = chainModes.s[k] && chainModes.s[k].arcsecPerYr;
47
+ PUBLISHED_PLANET_RECORDS[k] = Object.freeze({
48
+ name: PLANET_DISPLAY_NAMES[k], // presentation — planets/display-names, the deleted JSON's last leaf (plan 11 step 6)
49
+ orbitalPeriodInputDays: ar.orbitalPeriodInputDays,
50
+ longitudePerihelion: ar.longitudePerihelion,
51
+ ascendingNodeInvPlane: ar.ascendingNodeInvPlaneClosedForm, // the closed-form verified J2000 node — astro-reference since plan 11 step 2
52
+ invPlaneInclinationJ2000: ar.invPlaneInclinationJ2000,
53
+ orbitalEccentricityJ2000: ar.orbitalEccentricityJ2000,
54
+ axialTiltJ2000: ar.axialTiltJ2000,
55
+ obliquityMean,
56
+ perihelionEclipticYears: gArc ? 1296000 / gArc : undefined,
57
+ ascendingNodePeriod: sArc ? 1296000 / sArc : undefined,
58
+ });
59
+ }
60
+
61
+ return { PLANET_KEYS, PUBLISHED_PLANET_RECORDS, massFraction };
62
+ }
63
+ /** @typedef {import('./delta-t.js').Ctx & ReturnType<typeof assemblePlanetRecords>} Ctx */
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The planets' spin channel — each planet's precession constant from its own torques on the chain's orbit.
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+ import { DEEP_MODES_ARTIFACT } from '../moon/deep-modes-artifact.cjs';
7
+ import { CHAIN_ARTIFACT } from '../planets/chain-artifact.js';
8
+ import { createPlanetSpinChannelFromArtifacts } from '../planets/spin-channel.cjs';
9
+
10
+ /** @param {import('./planet-records.js').Ctx} ctx @returns the bindings the later slices read */
11
+ export function assemblePlanetSpinChannel(ctx) {
12
+ const { C, GM_SUN, PLANET_KEYS, massFraction } = ctx;
13
+ // The planets' spin channel: the precession constant
14
+ // from each planet's OWN torques (astro-reference planetSpinPhysical) on the
15
+ // model's OWN orbit — the chain's J2000 a/e/plane and the deep ζ table —
16
+ // integrated as dŝ/dt = α(ŝ·n̂)(ŝ×n̂) from the IAU J2000 pole. ONE home:
17
+ // planets/spin-channel.cjs; built lazily per planet, pure in `year`.
18
+ /** @type {Map<string, ReturnType<typeof createPlanetSpinChannelFromArtifacts>>} */
19
+ const spinChannels = new Map();
20
+ /** @param {string} k */
21
+ const planetSpin = (k) => {
22
+ if (!PLANET_KEYS.includes(k)) throw new Error(`planets.spin: unknown planet '${k}'`);
23
+ let ch = spinChannels.get(k);
24
+ if (!ch) {
25
+ ch = createPlanetSpinChannelFromArtifacts({
26
+ key: k,
27
+ planetSpinPhysical: C.planetSpinPhysical,
28
+ chainAnchorElements: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements,
29
+ planetZeta: /** @type {any} */ (DEEP_MODES_ARTIFACT).planetZeta,
30
+ massFractionOfSun: massFraction[k],
31
+ gmSunKm3S2: GM_SUN,
32
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
33
+ });
34
+ spinChannels.set(k, ch);
35
+ }
36
+ return ch;
37
+ };
38
+
39
+ return { planetSpin };
40
+ }
41
+ /** @typedef {import('./planet-records.js').Ctx & ReturnType<typeof assemblePlanetSpinChannel>} Ctx */
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The published periods of date — the seamed precession period, tropical year and sidereal year (one-family inside the window, the deep chain beyond).
3
+ *
4
+ * One slice of `assembleModel` (../model.js): receives the context the earlier slices built, returns the bindings the later ones read.
5
+ */
6
+ import { ONE_FAMILY_WINDOW_YEARS } from '../earth/year-lengths.cjs';
7
+
8
+ /** @param {import('./sun-and-eclipses.js').Ctx} ctx @returns the bindings the later slices read */
9
+ export function assemblePublishedPeriods(ctx) {
10
+ const { certifiedAxialPrecessionJ2000Years, deepLod, yearLengthsM, yearToTMa } = ctx;
11
+ // Plan 06 Phase 3 S2 — THE published of-date precession period and mean
12
+ // tropical year: the one-family route (B) inside the banked tiers
13
+ // (|Δyear| ≤ ONE_FAMILY_WINDOW_YEARS), the composed lunisolar period
14
+ // beyond (S5: on the derived J2000 anchor, so the two faces agree at
15
+ // 25,771.4 at J2000 and the deep tail is the same clock scaled; the
16
+ // tidal-chain year). Shared by `epoch` and `lunisolar`.
17
+ /** @param {number} year @returns {number} */
18
+ const publishedAxialPrecessionYearsAtYear = (year) => {
19
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.axialPrecessionYearsAtYear(year);
20
+ const p = deepLod.lunisolarPrecessionPeriodYearsAtAge(yearToTMa(year));
21
+ return p === null ? certifiedAxialPrecessionJ2000Years() : p;
22
+ };
23
+ /** @param {number} year @returns {number} */
24
+ const publishedTropicalYearSecondsAtYear = (year) => {
25
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.tropicalYearSecondsAtYear(year);
26
+ return deepLod.tropicalYearSecondsAtAge(yearToTMa(year));
27
+ };
28
+ /** The published sidereal year of date, SI seconds — the one-family λ̇
29
+ * channel inside the window, the mass-loss law beyond (plan 09: the
30
+ * `lengths` surface's days ride it). @param {number} year @returns {number} */
31
+ const publishedSiderealYearSecondsAtYear = (year) => {
32
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.siderealYearSecondsAtYear(year);
33
+ return deepLod.siderealYearSecondsAtAge(yearToTMa(year));
34
+ };
35
+
36
+ return { publishedAxialPrecessionYearsAtYear, publishedSiderealYearSecondsAtYear, publishedTropicalYearSecondsAtYear };
37
+ }
38
+ /** @typedef {import('./sun-and-eclipses.js').Ctx & ReturnType<typeof assemblePublishedPeriods>} Ctx */