@essrt/physics 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/package.json +58 -0
- package/src/cardinal/index.cjs +323 -0
- package/src/chain-cycles/index.cjs +189 -0
- package/src/climate/l1-orbital.cjs +46 -0
- package/src/constants/coefficients.js +52 -0
- package/src/constants/generated.d.ts +710 -0
- package/src/constants/generated.js +868 -0
- package/src/constants/index.js +30 -0
- package/src/deltat/cycles.cjs +256 -0
- package/src/deltat/deep-time.cjs +266 -0
- package/src/deltat/historical.cjs +84 -0
- package/src/eclipse/finders.cjs +311 -0
- package/src/index.js +252 -0
- package/src/layer0/derive-params.js +85 -0
- package/src/layer0/index.js +166 -0
- package/src/layer1/index.js +86 -0
- package/src/layer2/index.js +6 -0
- package/src/moon/apparent.cjs +153 -0
- package/src/moon/arguments.cjs +320 -0
- package/src/moon/ecc-channel.cjs +108 -0
- package/src/moon/month-chain.cjs +251 -0
- package/src/moon/series.cjs +211 -0
- package/src/phase/index.cjs +186 -0
- package/src/planets/asc-node-integrator.cjs +120 -0
- package/src/planets/corrections.cjs +167 -0
- package/src/planets/ecc-channel.cjs +59 -0
- package/src/planets/fibonacci-laws.cjs +136 -0
- package/src/planets/geometry.cjs +145 -0
- package/src/planets/model.cjs +185 -0
- package/src/planets/orbit-chain.cjs +46 -0
- package/src/planets/orientation.cjs +133 -0
- package/src/planets/predict.cjs +324 -0
- package/src/reference/published-curves.cjs +247 -0
- package/src/sun/longitude-correction.cjs +68 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 0 — epoch primitives. Pure `f(year)` → SI scalar.
|
|
3
|
+
*
|
|
4
|
+
* Imports NOTHING: not a sibling layer, not the constants module, not a Node
|
|
5
|
+
* builtin. Everything arrives through `createEpochPrimitives`. That is §2b and
|
|
6
|
+
* §2d of IP-unified-architecture.md, and it is what makes a counterfactual
|
|
7
|
+
* expressible — the one thing DE440 and Laskar cannot offer.
|
|
8
|
+
*
|
|
9
|
+
* ── ONE AXIS, BY MEASUREMENT ──────────────────────────────────────────────
|
|
10
|
+
* The API is `f(year)` (naming rule: the epoch parameter is a year). The
|
|
11
|
+
* internal cores work in t_Ma, converted once by `tMa()`. A second `t_Ma`
|
|
12
|
+
* entry axis was built and then REMOVED: the worry was that pre-migration
|
|
13
|
+
* callers holding a t_Ma would suffer `t → year → t` float wobble (1.9% of the
|
|
14
|
+
* domain fails that round-trip), but a 200k-point sweep found ZERO output-level
|
|
15
|
+
* differences — the chain's sensitivity (~20 s/Ma on LOD) times a 1-ULP wobble
|
|
16
|
+
* in t sits orders below the output ULP everywhere in ±495 Ma. The layer0
|
|
17
|
+
* identity gate pins that conclusion with harvested non-round-tripping t
|
|
18
|
+
* values, so a future chain steep enough to break it fails loudly.
|
|
19
|
+
*
|
|
20
|
+
* ── α IS INJECTED, AND THAT IS R2 ─────────────────────────────────────────
|
|
21
|
+
* `alphaAtAgeMa` is a parameter, not an import. The Earth moment-of-inertia
|
|
22
|
+
* factor rides on the climate L1 series, so hard-wiring it would drag the
|
|
23
|
+
* climate formula into Layer 0 — but the deeper reason is R2: when BUILDING an
|
|
24
|
+
* H-lattice table α must be held at its J2000 reference, or the lattice
|
|
25
|
+
* defines itself in terms of its own output. The browser gets this right today
|
|
26
|
+
* only by accident (a TDZ throw during table build); Node integrates the live
|
|
27
|
+
* α(t) and lands 141.6 d away at cycle 0. Making α an argument turns that
|
|
28
|
+
* accident into a decision the caller states out loud. R2 itself is a Phase C
|
|
29
|
+
* change — nothing here applies it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} EpochParams
|
|
34
|
+
* @property {number} epochYear year at t_Ma = 0 (2000, NOT startmodelYear)
|
|
35
|
+
* @property {number} alpha1PerMa Moon recession, LLR-anchored
|
|
36
|
+
* @property {number} alpha3PerMa3
|
|
37
|
+
* @property {number} alpha4PerMa4
|
|
38
|
+
* @property {number} moonDistanceNowM a_Moon at J2000, metres
|
|
39
|
+
* @property {number} moonLockDistanceM tidal-lock asymptote, metres
|
|
40
|
+
* @property {number} totalAngularMomentumKgM2S L_tot of the Earth-Moon system
|
|
41
|
+
* @property {number} moonMassKg
|
|
42
|
+
* @property {number} gmEarthMoonM3S2
|
|
43
|
+
* @property {number} moonEccentricityFactor sqrt(1 - e^2)
|
|
44
|
+
* @property {number} earthMassKg
|
|
45
|
+
* @property {number} earthRadiusM
|
|
46
|
+
* @property {number} holisticYearJ2000
|
|
47
|
+
* @property {number} lodNowH13Seconds
|
|
48
|
+
* @property {number} siderealYearJ2000Seconds
|
|
49
|
+
* @property {number} solarMassLossFracPerYear
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} EpochPrimitives
|
|
54
|
+
* @property {(year: number) => number} tMa
|
|
55
|
+
* @property {(year: number) => number} moonDistanceMetres
|
|
56
|
+
* @property {(year: number) => number|null} lodSeconds
|
|
57
|
+
* @property {(year: number) => number|null} holisticH
|
|
58
|
+
* @property {(year: number) => number} siderealYearSeconds
|
|
59
|
+
* @property {(year: number) => number} tropicalYearSeconds
|
|
60
|
+
* @property {(year: number) => number|null} anomalisticYearSeconds
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {{params: EpochParams, alphaAtAgeMa: (tMa: number) => number}} deps
|
|
65
|
+
* @returns {EpochPrimitives}
|
|
66
|
+
*/
|
|
67
|
+
export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
|
|
68
|
+
/**
|
|
69
|
+
* Years before the epoch, in Ma. The ONE place this conversion lives.
|
|
70
|
+
* @param {number} year
|
|
71
|
+
* @returns {number}
|
|
72
|
+
*/
|
|
73
|
+
const tMa = (year) => (p.epochYear - year) / 1e6;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Moon semi-major axis. Cubic in t_Ma: alpha1 is LLR-anchored, alpha3/alpha4
|
|
77
|
+
* are the Farhat 2022 deep-time fit.
|
|
78
|
+
* @param {number} t age in Ma
|
|
79
|
+
* @returns {number} metres
|
|
80
|
+
*/
|
|
81
|
+
const moonDistanceMetresCore = (t) =>
|
|
82
|
+
p.moonDistanceNowM
|
|
83
|
+
* (1 + p.alpha1PerMa * t + p.alpha3PerMa3 * t * t * t + p.alpha4PerMa4 * t * t * t * t);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Length of day from angular-momentum conservation: as the Moon recedes it
|
|
87
|
+
* takes orbital angular momentum and Earth's spin slows to compensate.
|
|
88
|
+
* @param {number} t age in Ma
|
|
89
|
+
* @returns {number|null} seconds; null past the tidal-lock asymptote
|
|
90
|
+
*/
|
|
91
|
+
const lodSecondsCore = (t) => {
|
|
92
|
+
const a = moonDistanceMetresCore(t);
|
|
93
|
+
if (a <= 0 || a >= p.moonLockDistanceM) return null;
|
|
94
|
+
const iEarth = alphaAtAgeMa(t) * p.earthMassKg * p.earthRadiusM * p.earthRadiusM;
|
|
95
|
+
return (2 * Math.PI * iEarth)
|
|
96
|
+
/ (p.totalAngularMomentumKgM2S
|
|
97
|
+
- p.moonMassKg * Math.sqrt(p.gmEarthMoonM3S2 * a) * p.moonEccentricityFactor);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The Earth Fundamental Cycle. H scales with LOD — the lattice is defined by
|
|
102
|
+
* the rotation rate, so a longer day is a longer H.
|
|
103
|
+
* @param {number} t age in Ma
|
|
104
|
+
* @returns {number|null} years
|
|
105
|
+
*/
|
|
106
|
+
const holisticHCore = (t) => {
|
|
107
|
+
const lod = lodSecondsCore(t);
|
|
108
|
+
return lod === null ? null : p.holisticYearJ2000 * lod / p.lodNowH13Seconds;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sidereal year. Driver 2 alone — solar mass loss via Kepler, `dT/T = -2 dM/M`
|
|
113
|
+
* under the adiabatic `a*M = const` coupling. Carries NO alpha and NO LOD,
|
|
114
|
+
* which is why attempt 1 used it as the clean probe for the mass-loss
|
|
115
|
+
* amplitude (measured 0.9964, i.e. 1.00).
|
|
116
|
+
* @param {number} t age in Ma
|
|
117
|
+
* @returns {number} seconds
|
|
118
|
+
*/
|
|
119
|
+
const siderealYearSecondsCore = (t) => {
|
|
120
|
+
if (t === 0) return p.siderealYearJ2000Seconds;
|
|
121
|
+
return p.siderealYearJ2000Seconds * (1 - 2 * p.solarMassLossFracPerYear * t * 1e6);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Tropical year — sidereal less one axial-precession turn, H/13.
|
|
126
|
+
* Falls back to the J2000 H past the asymptote, matching the shipped chain.
|
|
127
|
+
* @param {number} t age in Ma
|
|
128
|
+
* @returns {number} seconds
|
|
129
|
+
*/
|
|
130
|
+
const tropicalYearSecondsCore = (t) => {
|
|
131
|
+
const sid = siderealYearSecondsCore(t);
|
|
132
|
+
const H = holisticHCore(t);
|
|
133
|
+
return H === null ? sid * (1 - 13 / p.holisticYearJ2000) : sid * (1 - 13 / H);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Anomalistic year.
|
|
138
|
+
*
|
|
139
|
+
* NOT `T_sid * (1 + 3/H)`. That is the FIRST-ORDER form and the plan quotes
|
|
140
|
+
* it, but the shipped chain composes the lattice steps exactly: tropical is
|
|
141
|
+
* `(H-13)/H` of sidereal, then anomalistic is `H/(H-16)` of tropical, giving
|
|
142
|
+
* `T_sid * (H-13)/(H-16)`. The two differ by `48/H^2 * T_sid` ~ 13.5 ms —
|
|
143
|
+
* comparable to 6d's own 0.0178 s anomalistic RMSE, so adopting the
|
|
144
|
+
* first-order form would move a shipped value for no reason. Exact
|
|
145
|
+
* composition it is; the identity 13 + 3 = 16 still holds to first order.
|
|
146
|
+
*
|
|
147
|
+
* @param {number} t age in Ma
|
|
148
|
+
* @returns {number|null} seconds
|
|
149
|
+
*/
|
|
150
|
+
const anomalisticYearSecondsCore = (t) => {
|
|
151
|
+
const H = holisticHCore(t);
|
|
152
|
+
if (H === null) return null;
|
|
153
|
+
const sid = siderealYearSecondsCore(t);
|
|
154
|
+
return sid * (H - 13) / H * H / (H - 16);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
tMa,
|
|
159
|
+
moonDistanceMetres: (year) => moonDistanceMetresCore(tMa(year)),
|
|
160
|
+
lodSeconds: (year) => lodSecondsCore(tMa(year)),
|
|
161
|
+
holisticH: (year) => holisticHCore(tMa(year)),
|
|
162
|
+
siderealYearSeconds: (year) => siderealYearSecondsCore(tMa(year)),
|
|
163
|
+
tropicalYearSeconds: (year) => tropicalYearSecondsCore(tMa(year)),
|
|
164
|
+
anomalisticYearSeconds: (year) => anomalisticYearSecondsCore(tMa(year)),
|
|
165
|
+
});
|
|
166
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 — derived views. Pure, composed only from Layer 0.
|
|
3
|
+
*
|
|
4
|
+
* The point of this layer is that TWO AXES DISAPPEAR (§2c):
|
|
5
|
+
*
|
|
6
|
+
* "Kinematic" is not a code path. It is `f(2000)`.
|
|
7
|
+
* "RealLOD" is not a function variant. It is a division by `lodSeconds(Y)`.
|
|
8
|
+
*
|
|
9
|
+
* Everything here is a year length expressed in DAYS, and a day is
|
|
10
|
+
* `lodSeconds(Y)` — the epoch's own rotation period, not 86400 SI seconds. That
|
|
11
|
+
* distinction is not cosmetic: the CSV measures JD intervals (SI days) while the
|
|
12
|
+
* ESSRT chart is in real days, and detrending one against the other injects a
|
|
13
|
+
* 2590 s ramp. Unit in the name, always.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {import('../layer0/index.js').EpochPrimitives} EpochPrimitives
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} DerivedViews
|
|
22
|
+
* @property {(year: number) => number|null} tropicalYearDays
|
|
23
|
+
* @property {(year: number) => number|null} siderealYearDays
|
|
24
|
+
* @property {(year: number) => number|null} anomalisticYearDays
|
|
25
|
+
* @property {(year: number) => number|null} siderealYearDaysViaLattice
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{primitives: EpochPrimitives}} deps
|
|
30
|
+
* @returns {DerivedViews}
|
|
31
|
+
*/
|
|
32
|
+
export const createDerivedViews = ({ primitives: L0 }) => {
|
|
33
|
+
/**
|
|
34
|
+
* Days per year at the epoch's own day length. `null` past tidal lock, which
|
|
35
|
+
* propagates rather than silently becoming Infinity.
|
|
36
|
+
* @param {(year: number) => number|null} seconds
|
|
37
|
+
* @returns {(year: number) => number|null}
|
|
38
|
+
*/
|
|
39
|
+
const perDay = (seconds) => (year) => {
|
|
40
|
+
const s = seconds(year);
|
|
41
|
+
const lod = L0.lodSeconds(year);
|
|
42
|
+
return (s === null || lod === null || lod === 0) ? null : s / lod;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const tropicalYearDays = perDay(L0.tropicalYearSeconds);
|
|
46
|
+
const siderealYearDays = perDay(L0.siderealYearSeconds);
|
|
47
|
+
const anomalisticYearDays = perDay(L0.anomalisticYearSeconds);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The sidereal year in days, reached the long way — from the tropical year
|
|
51
|
+
* through the H/13 lattice identity instead of from sidereal seconds.
|
|
52
|
+
*
|
|
53
|
+
* ALGEBRAICALLY THE SAME NUMBER. `T_trop = T_sid·(H−13)/H`, so
|
|
54
|
+
* `(T_trop/LOD)·H/(H−13)` cancels to `T_sid/LOD` — but the operation ORDER
|
|
55
|
+
* differs, so it is not bit-identical everywhere. Measured across the fixture
|
|
56
|
+
* grid: identical at six of seven epochs, 0.7 ULP apart at +0.3 Ma. The
|
|
57
|
+
* browser shows exactly the same one-epoch split, which is how we know the
|
|
58
|
+
* port is faithful rather than merely close.
|
|
59
|
+
*
|
|
60
|
+
* It exists because `src/script.js` carries BOTH as separate mutable globals
|
|
61
|
+
* — `meansiderealyearlengthinDays` and `meansiderealyearlengthinDays_kinematic`
|
|
62
|
+
* — and they hold the same value at every epoch once `recomputeEpochAnchors`
|
|
63
|
+
* has run. They diverge only at module load, where one is the IAU anchor
|
|
64
|
+
* (365.256363004) and the other is lattice-derived: the 118 ms of R16.
|
|
65
|
+
*
|
|
66
|
+
* So the "kinematic" variant was never a different quantity, only a
|
|
67
|
+
* different arrival route with a different starting constant. Keeping the
|
|
68
|
+
* route explicit here lets B.3 retire one global without asserting the
|
|
69
|
+
* equality on faith.
|
|
70
|
+
*
|
|
71
|
+
* @param {number} year
|
|
72
|
+
* @returns {number|null}
|
|
73
|
+
*/
|
|
74
|
+
const siderealYearDaysViaLattice = (year) => {
|
|
75
|
+
const trop = tropicalYearDays(year);
|
|
76
|
+
const H = L0.holisticH(year);
|
|
77
|
+
return (trop === null || H === null) ? null : trop * H / (H - 13);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
tropicalYearDays,
|
|
82
|
+
siderealYearDays,
|
|
83
|
+
anomalisticYearDays,
|
|
84
|
+
siderealYearDaysViaLattice,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moon apparent position — THE shared implementation (Phase 8.2-7, the
|
|
3
|
+
* final lunar layer). The D5 derived optics + the post-hoc RA/Dec override:
|
|
4
|
+
*
|
|
5
|
+
* - sunGeoVecEqD5: framework-native geocentric Sun vector (equatorial km).
|
|
6
|
+
* e(T) from the anchored observed eccentricity + drift; EoC coefficients
|
|
7
|
+
* DERIVED from e via the Kepler series (2e − e³/4, (5/4)e², (13/12)e³ —
|
|
8
|
+
* the identity the D1 laboratory proved at 2 ppm); mean-longitude rate =
|
|
9
|
+
* framework tropical year; mean-anomaly rate = that minus the H/16
|
|
10
|
+
* perihelion rate; R from the AU distance; ε from the framework
|
|
11
|
+
* obliquity. One J2000 anchor: sunMeanLongitudeJ2000_deg.
|
|
12
|
+
* - moonAberrationRaDec: central-difference v_E/c annual aberration,
|
|
13
|
+
* SUBTRACTED (delta TO the aberration-removed direction u − v/c).
|
|
14
|
+
* - overrideRaDec: ecl→eq (Meeus eq. 13.3/13.4) on the series output,
|
|
15
|
+
* then framework-native analytic aberration + the small fitted residual
|
|
16
|
+
* (MOON_CORRECTION_RESIDUAL), or the legacy 3-argument fitted
|
|
17
|
+
* MOON_CORRECTION in pure-Meeus A/B mode.
|
|
18
|
+
*
|
|
19
|
+
* LOAD-BEARING: the correction patch's hard-coded linear arguments
|
|
20
|
+
* (297.850 + 12.19074912·dJD, 134.963 + 13.06499295·dJD,
|
|
21
|
+
* 357.529 + 0.98560028·dJD) are DELIBERATELY independent of the argument
|
|
22
|
+
* skeleton — the fitted coefficients were produced against THIS form
|
|
23
|
+
* (coefficients and runtime form are a matched pair). Do not "unify" them
|
|
24
|
+
* with the args dispatcher.
|
|
25
|
+
*
|
|
26
|
+
* S8 (obliquity source) stays ENGINE-INJECTED per call: the browser passes
|
|
27
|
+
* its live scene value (o.obliquityEarth, refreshed in updatePositions);
|
|
28
|
+
* the Node engine recomputes for the scene year. Equal when both describe
|
|
29
|
+
* the same year — a scene-state convention, not physics.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {{raSinD: number, raCosD: number, raSinMp: number, raCosMp: number,
|
|
36
|
+
* raSinMs: number, raCosMs: number, decSinD: number, decCosD: number,
|
|
37
|
+
* decSinMp: number, decCosMp: number, decSinMs: number, decCosMs: number}} MoonCorrectionTable
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {{
|
|
42
|
+
* constants: {
|
|
43
|
+
* j2000JD: number,
|
|
44
|
+
* julianCenturyDays: number,
|
|
45
|
+
* sunMeanLongitudeJ2000Deg: number,
|
|
46
|
+
* perihelionLongitudeJ2000Deg: number,
|
|
47
|
+
* eccentricityJ2000: number,
|
|
48
|
+
* eccentricityDotJ2000: number,
|
|
49
|
+
* d5RateLDegPerDay: number,
|
|
50
|
+
* d5RatePeriDegPerDay: number,
|
|
51
|
+
* speedOfLight: number,
|
|
52
|
+
* },
|
|
53
|
+
* fns: {
|
|
54
|
+
* computeObliquityEarth: (year: number) => number,
|
|
55
|
+
* getAuDistanceKm: () => number,
|
|
56
|
+
* isFrameworkNative: () => boolean,
|
|
57
|
+
* getCorrectionResidual: () => (MoonCorrectionTable | null),
|
|
58
|
+
* getCorrectionLegacy: () => (MoonCorrectionTable | null),
|
|
59
|
+
* },
|
|
60
|
+
* }} deps — the D5 rates are the engines' J2000-frozen values (the year
|
|
61
|
+
* globals are deep-time-mutable, same pattern as FW_A2_RATE); the AU
|
|
62
|
+
* distance is a GETTER (browser-mutable under deep time).
|
|
63
|
+
*/
|
|
64
|
+
function createMoonApparent({ constants, fns }) {
|
|
65
|
+
const {
|
|
66
|
+
j2000JD, julianCenturyDays, sunMeanLongitudeJ2000Deg,
|
|
67
|
+
perihelionLongitudeJ2000Deg, eccentricityJ2000, eccentricityDotJ2000,
|
|
68
|
+
d5RateLDegPerDay, d5RatePeriDegPerDay, speedOfLight,
|
|
69
|
+
} = constants;
|
|
70
|
+
const {
|
|
71
|
+
computeObliquityEarth, getAuDistanceKm, isFrameworkNative,
|
|
72
|
+
getCorrectionResidual, getCorrectionLegacy,
|
|
73
|
+
} = fns;
|
|
74
|
+
|
|
75
|
+
/** Framework-native geocentric Sun vector, equatorial km.
|
|
76
|
+
* @param {number} jd @returns {[number, number, number]} */
|
|
77
|
+
function sunGeoVecEqD5(jd) {
|
|
78
|
+
const T = (jd - j2000JD) / julianCenturyDays;
|
|
79
|
+
const d = jd - j2000JD;
|
|
80
|
+
const L0 = sunMeanLongitudeJ2000Deg + d5RateLDegPerDay * d;
|
|
81
|
+
const M = ((L0 - (perihelionLongitudeJ2000Deg + d5RatePeriDegPerDay * d)) + 180) * Math.PI / 180; // geocentric-perigee convention (Sun perigee = Earth perihelion + 180°)
|
|
82
|
+
const e = eccentricityJ2000 + eccentricityDotJ2000 * T;
|
|
83
|
+
const Ceq = ((2 * e - Math.pow(e, 3) / 4) * Math.sin(M)
|
|
84
|
+
+ 1.25 * e * e * Math.sin(2 * M)
|
|
85
|
+
+ (13 / 12) * Math.pow(e, 3) * Math.sin(3 * M)) * 180 / Math.PI; // deg (Kepler EoC series)
|
|
86
|
+
const lam = (L0 + Ceq) * Math.PI / 180;
|
|
87
|
+
const v = M + Ceq * Math.PI / 180;
|
|
88
|
+
const R = (1 - e * e) / (1 + e * Math.cos(v)) * getAuDistanceKm();
|
|
89
|
+
const eps = computeObliquityEarth(2000 + d / 365.2425) * Math.PI / 180;
|
|
90
|
+
return [R * Math.cos(lam), R * Math.sin(lam) * Math.cos(eps), R * Math.sin(lam) * Math.sin(eps)];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Annual-aberration delta (central difference, v_E/c SUBTRACTED).
|
|
94
|
+
* @param {number} jd @param {number} ra @param {number} dec
|
|
95
|
+
* @returns {{dRA: number, dDec: number}} */
|
|
96
|
+
function moonAberrationRaDec(jd, ra, dec) {
|
|
97
|
+
const h = 0.02; // days (central difference)
|
|
98
|
+
const a = sunGeoVecEqD5(jd - h), b = sunGeoVecEqD5(jd + h);
|
|
99
|
+
const s = 1 / (2 * h * 86400 * speedOfLight);
|
|
100
|
+
const kx = -(b[0] - a[0]) * s, ky = -(b[1] - a[1]) * s, kz = -(b[2] - a[2]) * s; // v_E/c
|
|
101
|
+
const cd = Math.cos(dec);
|
|
102
|
+
const ux = cd * Math.cos(ra), uy = cd * Math.sin(ra), uz = Math.sin(dec);
|
|
103
|
+
const wx = ux - kx, wy = uy - ky, wz = uz - kz; // SUBTRACT the aberration content
|
|
104
|
+
const wr = Math.sqrt(wx * wx + wy * wy + wz * wz);
|
|
105
|
+
let dRA = Math.atan2(wy, wx) - ra;
|
|
106
|
+
dRA = Math.atan2(Math.sin(dRA), Math.cos(dRA));
|
|
107
|
+
return { dRA, dDec: Math.asin(Math.max(-1, Math.min(1, wz / wr))) - dec };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The post-hoc RA/Dec override on the series output. Returns EQUATORIAL
|
|
111
|
+
* radians — the engines own their scene storage conventions (the browser
|
|
112
|
+
* stores dec as π/2 − dec, phi form).
|
|
113
|
+
* @param {{lonDeg: number, betRad: number, meeusT: (number | undefined), obliquityDeg: number}} p
|
|
114
|
+
* @returns {{raRad: number, decRad: number}} */
|
|
115
|
+
function overrideRaDec({ lonDeg, betRad, meeusT, obliquityDeg }) {
|
|
116
|
+
const eps = obliquityDeg * (Math.PI / 180);
|
|
117
|
+
const cosE = Math.cos(eps), sinE = Math.sin(eps);
|
|
118
|
+
const lamR = lonDeg * (Math.PI / 180);
|
|
119
|
+
const sinLam = Math.sin(lamR), cosLam = Math.cos(lamR);
|
|
120
|
+
const sinBet = Math.sin(betRad), cosBet = Math.cos(betRad);
|
|
121
|
+
|
|
122
|
+
// Ecliptic → equatorial (Meeus eq. 13.3, 13.4)
|
|
123
|
+
let newRA = Math.atan2(sinLam * cosE - Math.tan(betRad) * sinE, cosLam);
|
|
124
|
+
if (newRA < 0) newRA += 2 * Math.PI;
|
|
125
|
+
let newDec = Math.asin(sinBet * cosE + cosBet * sinE * sinLam);
|
|
126
|
+
|
|
127
|
+
if (isFrameworkNative()) {
|
|
128
|
+
const ab = moonAberrationRaDec(j2000JD + (meeusT || 0) * julianCenturyDays, newRA, newDec);
|
|
129
|
+
newRA += ab.dRA;
|
|
130
|
+
newDec += ab.dDec;
|
|
131
|
+
}
|
|
132
|
+
const mc = isFrameworkNative() ? getCorrectionResidual() : getCorrectionLegacy();
|
|
133
|
+
if (mc) {
|
|
134
|
+
const d2r = Math.PI / 180;
|
|
135
|
+
const dJD = (meeusT || 0) * julianCenturyDays;
|
|
136
|
+
// MATCHED-PAIR arguments — fitted against these exact linear forms.
|
|
137
|
+
const Dc = (297.850 + 12.19074912 * dJD) * d2r;
|
|
138
|
+
const Mpc = (134.963 + 13.06499295 * dJD) * d2r;
|
|
139
|
+
const Msc = (357.529 + 0.98560028 * dJD) * d2r;
|
|
140
|
+
newRA -= (mc.raSinD * Math.sin(Dc) + mc.raCosD * Math.cos(Dc)
|
|
141
|
+
+ mc.raSinMp * Math.sin(Mpc) + mc.raCosMp * Math.cos(Mpc)
|
|
142
|
+
+ mc.raSinMs * Math.sin(Msc) + mc.raCosMs * Math.cos(Msc)) * d2r;
|
|
143
|
+
newDec -= (mc.decSinD * Math.sin(Dc) + mc.decCosD * Math.cos(Dc)
|
|
144
|
+
+ mc.decSinMp * Math.sin(Mpc) + mc.decCosMp * Math.cos(Mpc)
|
|
145
|
+
+ mc.decSinMs * Math.sin(Msc) + mc.decCosMs * Math.cos(Msc)) * d2r;
|
|
146
|
+
}
|
|
147
|
+
return { raRad: newRA, decRad: newDec };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { sunGeoVecEqD5, moonAberrationRaDec, overrideRaDec };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { createMoonApparent };
|