@essrt/physics 23.3.2 → 23.6.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 (57) hide show
  1. package/README.md +6 -1
  2. package/package.json +2 -2
  3. package/src/constants/coefficients.d.ts +5 -1
  4. package/src/constants/coefficients.js +3 -2
  5. package/src/constants/generated.d.ts +2 -3
  6. package/src/constants/generated.js +3 -4
  7. package/src/constants/units.cjs +4 -1
  8. package/src/deltat/deep-time.cjs +37 -72
  9. package/src/index.js +35 -48
  10. package/src/model/cardinal-instants.js +101 -0
  11. package/src/model/context.js +54 -0
  12. package/src/model/deep-time-chain.js +79 -0
  13. package/src/model/delta-t.js +38 -0
  14. package/src/model/derived-constants.js +58 -0
  15. package/src/model/earth-scalars.js +47 -0
  16. package/src/model/inv-plane-elements.js +56 -0
  17. package/src/model/lunar-theory.js +216 -0
  18. package/src/model/moon-epoch.js +28 -0
  19. package/src/model/one-source-movement.js +192 -0
  20. package/src/model/planet-elements-of-date.js +61 -0
  21. package/src/model/planet-records.js +63 -0
  22. package/src/model/planet-spin-channel.js +41 -0
  23. package/src/model/published-periods.js +38 -0
  24. package/src/model/sun-and-eclipses.js +199 -0
  25. package/src/model/surface.js +330 -0
  26. package/src/model/time-axis.js +24 -0
  27. package/src/model/year-day-lengths.js +42 -0
  28. package/src/model.js +62 -1612
  29. package/src/moon/arguments.cjs +27 -43
  30. package/src/planets/chain-artifact.js +2 -2
  31. package/src/planets/keplerian-chain.cjs +1 -1
  32. package/types/constants/coefficients.d.ts +5 -1
  33. package/types/constants/generated.d.ts +2 -3
  34. package/types/constants/units.d.cts +3 -0
  35. package/types/deltat/deep-time.d.cts +18 -24
  36. package/types/index.d.ts +1 -1
  37. package/types/model/cardinal-instants.d.ts +14 -0
  38. package/types/model/context.d.ts +87 -0
  39. package/types/model/deep-time-chain.d.ts +288 -0
  40. package/types/model/delta-t.d.ts +11 -0
  41. package/types/model/derived-constants.d.ts +25 -0
  42. package/types/model/earth-scalars.d.ts +14 -0
  43. package/types/model/inv-plane-elements.d.ts +6 -0
  44. package/types/model/lunar-theory.d.ts +72 -0
  45. package/types/model/moon-epoch.d.ts +10 -0
  46. package/types/model/one-source-movement.d.ts +74 -0
  47. package/types/model/planet-elements-of-date.d.ts +9 -0
  48. package/types/model/planet-records.d.ts +7 -0
  49. package/types/model/planet-spin-channel.d.ts +29 -0
  50. package/types/model/published-periods.d.ts +7 -0
  51. package/types/model/sun-and-eclipses.d.ts +52 -0
  52. package/types/model/surface.d.ts +279 -0
  53. package/types/model/time-axis.d.ts +12 -0
  54. package/types/model/year-day-lengths.d.ts +8 -0
  55. package/types/model.d.ts +9 -81
  56. package/types/moon/arguments.d.cts +5 -11
  57. package/types/planets/keplerian-chain.d.cts +3 -1
@@ -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
+ solarYearInput: ar.solarYearInput,
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 */
@@ -0,0 +1,199 @@
1
+ /**
2
+ * The certified Sun and the eclipse tier — the mean longitude of date, the finders, the planetary completion, the besselian location tier.
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
+ import { createBesselian } from '../eclipse/besselian.cjs';
8
+ import { createEclipseFinders } from '../eclipse/finders.cjs';
9
+ import { computeCarrierRatesDegPerCy, createSunPlanetaryCompletion } from '../eclipse/sun-planetary-completion.cjs';
10
+
11
+ /** @param {import('./lunar-theory.js').Ctx} ctx @returns the bindings the later slices read */
12
+ export function assembleSunAndEclipses(ctx) {
13
+ const {
14
+ C, F, late, MASS_RATIO_EARTH_MOON, currentAUDistance, frameworkDeltaTSecondsAtJD, j2000JD, jdTTFromUT,
15
+ julianCenturyDays, meanSiderealYearDays, meanSolarYearDays, moonArgs, moonDistanceKm, moonSeries,
16
+ moonSiderealMonthInput, moonSynodicMonthDays, obliquityDeg, oneFamilyTropicalYearSeconds, sunEccentricityAt,
17
+ sunPerihelionDegAt, yearFromJD,
18
+ } = ctx;
19
+ // The certified Sun — assembled from the model's own laws, ZERO fitted Sun
20
+ // constants: e(t) and ϖ(t) are the eclipse Sun's mean elements of date
21
+ // (earth-scalars.js: the one-source series plus the derived mean offset);
22
+ // L(t) is the integral of the one-family tropical year (below). The
23
+ // finders, the derived planetary completion and the besselian location
24
+ // tier are wired on it; the sun-vs-horizons gate banks its modern-window
25
+ // residual, the eclipse audit the finders' canon statistics.
26
+ const sunL0Deg = C.earthOrbital.sunMeanLongitudeJ2000_deg;
27
+ const sunTropicalRateDegPerCy = 360 * julianCenturyDays / meanSolarYearDays;
28
+ const sunMeanLongitudeDegAt = (() => {
29
+ // The Sun's mean longitude of date is the INTEGRAL of the one-source
30
+ // tropical year: L(t) = L0 + 360·∫_{2000}^{t} dt′/T_trop(t′), T_trop the
31
+ // one-family tropical year of date in SI seconds (one-source-movement.js).
32
+ // MEASURED: it equals the IAU/Laskar drift expression to 0.1 s over
33
+ // −1000..+3000. The smooth year cannot carry the Earth–Mars–Jupiter long
34
+ // inequality (1783 yr, 6.3″); derived on the model's own N-body and
35
+ // shipped in the planetary completion, the JPL instrument reads 1.03″
36
+ // all-phase sd (Horizons ±3000 yr: modern-window mean +0.8″).
37
+ // NUMERICS (rate vs point value): a cumulative trapezoid table of
38
+ // cycles, yearly inside ±20,000 yr and per century beyond, grown on
39
+ // demand from 2000 in both directions and interpolated inside a cell —
40
+ // never a rate multiplied by a span. Beyond the one-family window the
41
+ // tidal-chain year continues the integrand (the scene overlay ends at
42
+ // 20,000 yr; the finders refuse out-of-domain epochs upstream).
43
+ const JULIAN_YEAR_S = 365.25 * 86400;
44
+ const tropSec = oneFamilyTropicalYearSeconds;
45
+ const FINE_SPAN = 20000, COARSE_STEP = 100;
46
+ /** cycles from 2000 to 2000 ± i·step (fwd/bwd tiers) */
47
+ /** @type {number[]} */ const fwdFine = [0];
48
+ /** @type {number[]} */ const bwdFine = [0];
49
+ /** @type {number[]} */ const fwdCoarse = [];
50
+ /** @type {number[]} */ const bwdCoarse = [];
51
+ /** UNSIGNED trapezoid cycles over the span between two years (Julian
52
+ * years → SI seconds over the one-family year); the caller applies the
53
+ * direction sign. @param {number} ya @param {number} yb @returns {number} */
54
+ const segAbs = (ya, yb) => 0.5 * (Math.abs(yb - ya) * JULIAN_YEAR_S) * (1 / tropSec(ya) + 1 / tropSec(yb));
55
+ /** signed cycles from 2000 to `year` @param {number} year @returns {number} */
56
+ const cyclesTo = (year) => {
57
+ const dy = year - 2000;
58
+ const s = dy >= 0 ? 1 : -1, a = Math.abs(dy);
59
+ // WARM THE SAMPLER TO THE TARGET FIRST (plan 06 R4, measured): the
60
+ // table below grows outward in 1-yr / 100-yr steps, and every step
61
+ // beyond ~1.6 Myr enlarged the one-family sampler's >2-Myr tier just
62
+ // past its span, rebuilding the whole deep integration about every 32
63
+ // steps — ~125 rebuilds and 22 s for the first deep-epoch call (the
64
+ // scene's first jump to −5 Myr), 0.4 ms once built. One read at the
65
+ // clamped target builds the tier at its final span; the grid stores
66
+ // are endpoint-independent, so every value is bit-identical.
67
+ tropSec(2000 + s * Math.min(a, ONE_FAMILY_WINDOW_YEARS));
68
+ const fine = s > 0 ? fwdFine : bwdFine;
69
+ const growFine = (/** @type {number} */ upto) => { while (fine.length <= upto) { const k = fine.length; fine.push(fine[k - 1] + s * segAbs(2000 + s * (k - 1), 2000 + s * k)); } };
70
+ if (a <= FINE_SPAN) {
71
+ const i = Math.floor(a), f = a - i;
72
+ growFine(i + 1);
73
+ return fine[i] + f * (fine[i + 1] - fine[i]);
74
+ }
75
+ growFine(FINE_SPAN);
76
+ const coarse = s > 0 ? fwdCoarse : bwdCoarse;
77
+ if (coarse.length === 0) coarse.push(fine[FINE_SPAN]);
78
+ const c = (a - FINE_SPAN) / COARSE_STEP, ci = Math.floor(c), cf = c - ci;
79
+ while (coarse.length <= ci + 1) { const k = coarse.length; const y0 = 2000 + s * (FINE_SPAN + (k - 1) * COARSE_STEP); coarse.push(coarse[k - 1] + s * segAbs(y0, y0 + s * COARSE_STEP)); }
80
+ return coarse[ci] + cf * (coarse[ci + 1] - coarse[ci]);
81
+ };
82
+ return /** @param {number} year @returns {number} */ (year) => sunL0Deg + 360 * cyclesTo(year);
83
+ })();
84
+ // The lunar arguments' Sun perihelion of date is the eclipse Sun's mean
85
+ // perihelion (`sunPerihelionDegAt`) read DIRECTLY, wrapped.
86
+ // Every consumer wraps the argument it enters (M = Lsun − ϖ − 180) or
87
+ // differences it over ±25 days (the rate anchors, wrapped to ±180°), so
88
+ // no continuous angle is needed. MEASURED: a 100-yr unwrapping ladder
89
+ // (two series reads per rung, 53,400 rungs at −5.34 Myr) cost the cold
90
+ // deep jump 1,062 s — 343× the in-table jump against the perf gate's 8× —
91
+ // for a value the consumers reduced mod 360 anyway.
92
+
93
+ // Eclipse finders — wired like the engine probe (tools/verify/
94
+ // eclipse-audit.js). The finder axis is JD(UT): the series wrapper applies
95
+ // UT→TT internally. Ground-track/umbra paths deliberately absent: the
96
+ // scene-umbra projection navigates the Tychosium-derived scaffold, which
97
+ // never enters this package (§2h).
98
+ const eclipseFinders = createEclipseFinders({
99
+ moonLonDegAt: /** @param {number} jd */ (jd) => moonSeries.truncatedLonDeg(jd),
100
+ moonBetaDegAt: /** @param {number} jd */ (jd) => moonSeries.truncatedBetaDeg(jd),
101
+ moonDistanceKmAt: /** @param {number} jd */ (jd) => moonSeries.truncatedDistanceKm(jd),
102
+ deltaTSecondsAt: frameworkDeltaTSecondsAtJD,
103
+ getSynodicMonthDays: () => moonSynodicMonthDays,
104
+ getSunDistanceKm: () => currentAUDistance,
105
+ frameworkSun: {
106
+ sunMeanLongitudeJ2000Deg: sunL0Deg,
107
+ tropicalRateDegPerCy: sunTropicalRateDegPerCy,
108
+ eccentricityAt: sunEccentricityAt,
109
+ perihelionLongitudeDegAt: sunPerihelionDegAt,
110
+ meanLongitudeDegAt: sunMeanLongitudeDegAt,
111
+ },
112
+ constants: {
113
+ rEarthMetres: (C.bodyDiametersKm.earth / 2) * 1000,
114
+ moonDiameterKm: C.bodyDiametersKm.moon,
115
+ sunDiameterKm: C.bodyDiametersKm.sun,
116
+ j2000JD,
117
+ julianCenturyDays,
118
+ },
119
+ });
120
+
121
+ // 20.3g — the solar-eclipse LOCATION tier: shadow geometry on the FULL
122
+ // series (sceneEvalAt — the truncated finder forms omit the fitted Lp
123
+ // anchor and the −2235·sin(Lp) β family, deliberate for finder
124
+ // statistics, required here) at the model's ABSOLUTE TT (deltaTStart +
125
+ // curve, the same convention every other ΔT consumer reads). Both axis
126
+ // conventions are measured against the NASA path-table centerlines —
127
+ // see eclipse/besselian.cjs.
128
+ // Derived Earth-around-EMB wobble for the Sun completion: a_M·μ/AU,
129
+ // μ = 1/(1+M_E/M_M) — 6.4399″ at current constants, tracks them live.
130
+ const embWobbleArcsec = (moonDistanceKm / (MASS_RATIO_EARTH_MOON + 1) / currentAUDistance)
131
+ * (648000 / Math.PI);
132
+ // FQ-5 N3 — the completion's carriers are FRAMEWORK-derived from the
133
+ // model's own planet period records (the Moon-elongation rate from the
134
+ // sidereal month/year identity). Plan 06 I2 — the carriers are SIDEREAL
135
+ // (measured): the records' solarYearInput are OF-DATE periods (their
136
+ // rates sit +1.39..1.62°/cy above the VSOP sidereal mean motions — the
137
+ // framework precession plus input rounding), and a perturbation argument
138
+ // is inertial (D'Alembert: only Σk = 0 arguments are frame-free), so on
139
+ // of-date carriers every Σk ≠ 0 argument drifted by Σk·ψ(t) — 42..52° at
140
+ // −3000 for the table's Σk = −1 terms (their ancient scatter against
141
+ // Horizons 6.4 → 4.4″ on sidereal carriers), and 6 % of frequency for the
142
+ // 1783-yr long inequality (1683 yr on the of-date carriers). Constant-free:
143
+ // the record rate minus the model's own J2000 precession p₀ =
144
+ // 360·36525·(1/T_trop − 1/T_sid) for the planets, the framework sidereal
145
+ // year for Earth; identical to the N3 carriers at J2000 (same phase
146
+ // anchors), ≤ 1.4°·|Σk| apart at the 200-yr extraction window's edges
147
+ // (the composed table reproduces the N3 one there: JPL 1900–2100
148
+ // all-phase sd unchanged to 0.01″). The parity gate's fingerprint mirrors
149
+ // this arithmetic (test/create-model-parity.test.mjs).
150
+ // PLAN 06 I3 — the carriers are the model's own banked J2000 sidereal mean
151
+ // motions (the embedded sun-completion artifact; ONE home,
152
+ // computeCarrierRatesDegPerCy — the former record-based arithmetic sat
153
+ // 4.1″/yr off for Venus, 1.7″/yr for Jupiter: 83° of the Venus–Earth
154
+ // argument at ±9000 yr), Earth the framework sidereal year, the Moon
155
+ // elongation from the sidereal month/year identity. The long-period rows
156
+ // ride the embedded e-vectors of date, and the lunar equation rides the
157
+ // framework's own Moon elongation of date (ṅ included) — the constant-rate
158
+ // carrier ran ~29° off it at ±9000 yr.
159
+ const carrierRatesDegPerCy = computeCarrierRatesDegPerCy({ meanSiderealYearDays, moonSiderealMonthDays: moonSiderealMonthInput });
160
+ const { sunPlanetaryCompletionDeg } = createSunPlanetaryCompletion({
161
+ embWobbleArcsec,
162
+ carrierRatesDegPerCy,
163
+ moonElongationDegAtT: /** @param {number} T */ (T) => moonArgs.argsAt(j2000JD + T * julianCenturyDays).D,
164
+ });
165
+ const besselian = createBesselian({
166
+ moonFullAtDaysTT: /** @param {number} dDaysTT */ (dDaysTT) => {
167
+ const ev = moonSeries.sceneEvalAt(dDaysTT);
168
+ return { lonDeg: ev.lonDeg, latDeg: ev.latDeg, distKm: ev.distKm };
169
+ },
170
+ sunLonDegAt: /** @param {number} jdUT */ (jdUT) => eclipseFinders.sunLonDegAt(jdUT),
171
+ sunCompletionDeg: sunPlanetaryCompletionDeg,
172
+ // plan 06 R3 item 1: the location tier on APPARENT places — the Sun's derived κ
173
+ // (the same helper the cardinal instants use), the Moon's relative light-time
174
+ // inside the besselian; the series-extension tail now lives in sceneEvalAt.
175
+ sunAberrationDegAt: /** @param {number} year */ (year) => late.sunAberrationArcsecAt(year) / 3600,
176
+ deltaTSecondsAt: /** @param {number} jd */ (jd) => (jdTTFromUT(jd) - jd) * 86400,
177
+ obliquityDegAt: obliquityDeg,
178
+ eccentricityAt: sunEccentricityAt,
179
+ perihelionLongitudeDegAt: sunPerihelionDegAt,
180
+ yearFromJD,
181
+ constants: {
182
+ j2000JD,
183
+ julianCenturyDays,
184
+ earthDiameterKm: C.bodyDiametersKm.earth,
185
+ moonDiameterKm: C.bodyDiametersKm.moon,
186
+ sunDiameterKm: C.bodyDiametersKm.sun,
187
+ sunDistanceKm: currentAUDistance,
188
+ earthFlatteningInverse: C.physicalConstants.earthFlatteningInverseWGS84,
189
+ ttBridgeSeconds: F.DT_ANCHOR.deltaTStartSeconds,
190
+ gmstMeanSiderealT0Deg: C.physicalConstants.gmstMeanSiderealT0Deg,
191
+ gmstMeanSiderealRateDegPerDay: C.physicalConstants.gmstMeanSiderealRateDegPerDay,
192
+ gmstMeanSiderealT2Deg: C.physicalConstants.gmstMeanSiderealT2Deg,
193
+ speedOfLightKmS: C.physicalConstants.speedOfLight,
194
+ },
195
+ });
196
+
197
+ return { besselian, eclipseFinders, sunL0Deg, sunMeanLongitudeDegAt, sunPlanetaryCompletionDeg, sunTropicalRateDegPerCy };
198
+ }
199
+ /** @typedef {import('./lunar-theory.js').Ctx & ReturnType<typeof assembleSunAndEclipses>} Ctx */