@essrt/physics 8.0.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,391 @@
1
+ /**
2
+ * THE PLANETS' SPIN CHANNEL — the ONE home (plan 06 Phase 7, ex-K8b-3).
3
+ *
4
+ * The Earth channel composes its precession rate from the solar and lunar
5
+ * torques on the model's own recession history (earth/precession-composed).
6
+ * This module is the same question for the seven planets: the precession
7
+ * constant from the planet's OWN quadrupole and spin, with its regular
8
+ * satellites folded in the Ward & Hamilton (2004, AJ 128, 2501) way,
9
+ *
10
+ * α = (3/2) (n²/ω) (1 − e²)^(−3/2) (J₂ + q) / (λ + l)
11
+ * q = ½ Σ (m_i/M)(a_i/R)² the satellites' effective quadrupole
12
+ * l = Σ (m_i/M)(a_i/R)² (n_i/ω) the satellites' normalized angular momentum
13
+ *
14
+ * and the spin integrated on the planet's OWN orbit-plane history,
15
+ *
16
+ * dŝ/dt = α (ŝ·n̂)(ŝ×n̂),
17
+ *
18
+ * with n̂(t) from the planet's deep ζ mode table anchored at the chain's
19
+ * J2000 plane (the same construction as the Earth obliquity hybrid,
20
+ * earth/deep-orbital-history). The initial spin is the IAU J2000 pole
21
+ * (angular-momentum sense: the pole times the sign of the rotation rate),
22
+ * so the J2000 obliquity is DERIVED, not an input — its agreement with the
23
+ * IAU tilt is the first check of every planet's row.
24
+ *
25
+ * What the numbers mean (plan 06 Phase 7 record): where a planet's C/MR²
26
+ * was itself inferred from the measured precession rate (Mars, Venus,
27
+ * Mercury — 'spin-inferred'), this channel's rate is a CLOSURE of that
28
+ * inference through the model's own orbit (a, e, n); where C/MR² comes from
29
+ * gravity-constrained interior models (Jupiter, Saturn), the rate is a
30
+ * prediction, checked against the literature range and against the
31
+ * engine's own nodal line (Saturn's s8). Uranus and Neptune carry the
32
+ * interior-model class with no measured rate to close against.
33
+ *
34
+ * Time here is `year` (Julian years from J2000 on the chain's TT axis —
35
+ * the engine year). Samples are grown from J2000 outward on fixed steps and
36
+ * never revised, so every value is pure in `year` (visit-order independent —
37
+ * the sampler-purity lesson). TWO TIERS, each its own append-only track from
38
+ * J2000: a fine track (25-yr steps) serves |t| ≤ FINE_SPAN_YR, a coarse track
39
+ * (250-yr steps) serves beyond, to MAX_SPAN_YR; the tier a year reads is fixed
40
+ * by the year alone. Beyond ±MAX_SPAN_YR the channel returns null: the ζ
41
+ * tables are the deep tier's, and the domain is the model's.
42
+ *
43
+ * Since commit 2 of Phase 7 the channel also REPLACES the retired device
44
+ * rows: the planets' rendered obliquity of date (both scene twins), the K
45
+ * eccentricity law's "mean obliquity" input (the derived J2000 obliquity,
46
+ * `computeObliquityJ2000Deg`), and — through the chain's own g-mode beat
47
+ * (keplerian-chain `computeSecularShape`) — the "wobble period" the K
48
+ * device beat from its integer axial and obliquity fractions.
49
+ */
50
+
51
+ 'use strict';
52
+
53
+ const D2R = Math.PI / 180;
54
+ const R2D = 180 / Math.PI;
55
+ const ARCSEC_PER_RAD = R2D * 3600;
56
+ const DAYS_PER_JULIAN_YEAR = 365.25;
57
+ /**
58
+ * The integration step is the planet's OWN: 1/300 of its J2000 precession
59
+ * period, clamped to [STEP_MIN_YR, STEP_MAX_YR] — Venus (29-kyr period) at
60
+ * the 100-yr floor, Mars at ~570 yr, Jupiter ~1,600, the rest at the
61
+ * 2,000-yr cap (the cap resolves the fastest ζ mode, ~45 kyr, at 22 samples
62
+ * per cycle). One step per planet from J2000 → one track per direction, no
63
+ * tiers, no seam. MEASURED against a 25-yr research step: 1/100 of the
64
+ * period left Mars 2–5 mdeg off (its plane rides the dense inner s-band, the
65
+ * sensitive class) while Jupiter agreed to 1e-8°; 1/300 brings Mars to
66
+ * 3e-4° and Venus to 1e-7°. Cost: all seven channels to −5.34 Myr in ~0.3 s
67
+ * on a fresh model, to −100 kyr in ~10 ms.
68
+ */
69
+ const STEP_MIN_YR = 100;
70
+ const STEP_MAX_YR = 2000;
71
+ const STEPS_PER_PRECESSION_PERIOD = 300;
72
+ /** the reachable span (yr): the deep ζ tables' domain for the obliquity hybrid */
73
+ const MAX_SPAN_YR = 10_000_000;
74
+
75
+ /** @typedef {{ name?: string, gmKm3S2: number, semiMajorAxisKm: number, orbitalPeriodDays: number, retrograde?: boolean }} SpinSatellite */
76
+ /** @typedef {{ j2: number, j2ReferenceRadiusKm: number, momentOfInertiaFactor: number,
77
+ * momentOfInertiaFactorClass?: string, rotationRateDegPerDay: number,
78
+ * poleRaJ2000Deg: number, poleDecJ2000Deg: number, cassiniLocked?: boolean,
79
+ * satellites: ReadonlyArray<SpinSatellite> }} SpinInputs */
80
+
81
+ /**
82
+ * The precession constant from the planet's own torques.
83
+ * @param {{
84
+ * j2: number, j2ReferenceRadiusKm: number, momentOfInertiaFactor: number,
85
+ * rotationRateDegPerDay: number, satellites: ReadonlyArray<SpinSatellite>,
86
+ * gmPlanetSystemKm3S2: number, meanMotionRadPerYr: number, eccentricity: number,
87
+ * }} p
88
+ * @returns {{ alphaArcsecPerYr: number, alphaRadPerYr: number, q: number, l: number,
89
+ * spinRadPerYr: number, spinRetrograde: boolean, gmPlanetAloneKm3S2: number }}
90
+ */
91
+ function computePlanetPrecessionConstant(p) {
92
+ const spinRadPerDay = Math.abs(p.rotationRateDegPerDay) * D2R;
93
+ const spinRetrograde = p.rotationRateDegPerDay < 0;
94
+ let gmSats = 0;
95
+ for (const s of p.satellites) gmSats += s.gmKm3S2;
96
+ const gmAlone = p.gmPlanetSystemKm3S2 - gmSats;
97
+ let q = 0, l = 0;
98
+ for (const s of p.satellites) {
99
+ const x = (s.gmKm3S2 / gmAlone) * (s.semiMajorAxisKm / p.j2ReferenceRadiusKm) ** 2;
100
+ const nSatRadPerDay = (2 * Math.PI / s.orbitalPeriodDays) * (s.retrograde ? -1 : 1);
101
+ q += 0.5 * x;
102
+ l += x * (nSatRadPerDay / spinRadPerDay);
103
+ }
104
+ const spinRadPerYr = spinRadPerDay * DAYS_PER_JULIAN_YEAR;
105
+ const n = p.meanMotionRadPerYr;
106
+ const eFactor = Math.pow(1 - p.eccentricity * p.eccentricity, -1.5);
107
+ const alphaRadPerYr = 1.5 * (n * n / spinRadPerYr) * eFactor * (p.j2 + q) / (p.momentOfInertiaFactor + l);
108
+ return {
109
+ alphaArcsecPerYr: alphaRadPerYr * ARCSEC_PER_RAD,
110
+ alphaRadPerYr,
111
+ q,
112
+ l,
113
+ spinRadPerYr,
114
+ spinRetrograde,
115
+ gmPlanetAloneKm3S2: gmAlone,
116
+ };
117
+ }
118
+
119
+ /** @param {number[]} a @param {number[]} b */
120
+ const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
121
+ /** @param {number[]} a @param {number[]} b */
122
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
123
+ /** @param {number[]} a */
124
+ const unit = (a) => { const r = Math.hypot(a[0], a[1], a[2]); return [a[0] / r, a[1] / r, a[2] / r]; };
125
+ /** @param {number[]} a @param {number[]} b */
126
+ const angleDeg = (a, b) => Math.acos(Math.max(-1, Math.min(1, dot(a, b)))) * R2D;
127
+
128
+ /**
129
+ * The IAU pole (ICRF equatorial RA/Dec) as an ecliptic-J2000 unit vector.
130
+ * @param {number} raDeg @param {number} decDeg @param {number} obliquityJ2000Deg
131
+ * @returns {number[]}
132
+ */
133
+ function poleEclipticJ2000(raDeg, decDeg, obliquityJ2000Deg) {
134
+ const ra = raDeg * D2R, dec = decDeg * D2R, eps = obliquityJ2000Deg * D2R;
135
+ const x = Math.cos(dec) * Math.cos(ra), y = Math.cos(dec) * Math.sin(ra), z = Math.sin(dec);
136
+ return [x, y * Math.cos(eps) + z * Math.sin(eps), -y * Math.sin(eps) + z * Math.cos(eps)];
137
+ }
138
+
139
+ /**
140
+ * The orbit normal of a plane (i, Ω) in ecliptic J2000 — the h-vector direction.
141
+ * @param {number} inclDeg @param {number} ascNodeDeg @returns {number[]}
142
+ */
143
+ function orbitNormalFromElements(inclDeg, ascNodeDeg) {
144
+ const i = inclDeg * D2R, Om = ascNodeDeg * D2R;
145
+ return [Math.sin(i) * Math.sin(Om), -Math.sin(i) * Math.cos(Om), Math.cos(i)];
146
+ }
147
+
148
+ /**
149
+ * The spin direction at J2000 in the angular-momentum sense: the IAU pole,
150
+ * reversed for a retrograde rotator (negative IAU rotation rate).
151
+ * @param {SpinInputs} spin @param {number} obliquityJ2000Deg @returns {number[]}
152
+ */
153
+ function spinAxisJ2000(spin, obliquityJ2000Deg) {
154
+ const pole = poleEclipticJ2000(spin.poleRaJ2000Deg, spin.poleDecJ2000Deg, obliquityJ2000Deg);
155
+ return spin.rotationRateDegPerDay < 0 ? [-pole[0], -pole[1], -pole[2]] : pole;
156
+ }
157
+
158
+ /**
159
+ * The DERIVED J2000 obliquity to the planet's own orbit, degrees: the angle
160
+ * between the J2000 spin axis (angular-momentum sense — Venus ≈ 177°,
161
+ * Uranus ≈ 98°) and the chain's J2000 orbit plane. Needs no mode table and
162
+ * no integration — the load-time twins read it for the K device's
163
+ * "mean obliquity" slot (commit 2 of Phase 7); the acute form is
164
+ * `min(ε, 180 − ε)`.
165
+ * @param {{ spin: SpinInputs, anchorInclEclipticDeg: number,
166
+ * anchorAscNodeEclipticDeg: number, obliquityJ2000Deg: number }} p
167
+ * @returns {number}
168
+ */
169
+ function computeObliquityJ2000Deg(p) {
170
+ return angleDeg(spinAxisJ2000(p.spin, p.obliquityJ2000Deg),
171
+ orbitNormalFromElements(p.anchorInclEclipticDeg, p.anchorAscNodeEclipticDeg));
172
+ }
173
+
174
+ /**
175
+ * The planet's orbit normal n̂(t) in ecliptic J2000 from its deep ζ table,
176
+ * DC-anchored at the chain's J2000 plane (the Earth hybrid's ζ convention:
177
+ * ζ = sin(i/2)·e^{iΩ}; the anchor is the coordinate the modes wander around).
178
+ * @param {ReadonlyArray<{omegaRadPerYr: number, re: number, im: number}>} zetaModes
179
+ * @param {number} anchorInclEclipticDeg @param {number} anchorAscNodeEclipticDeg
180
+ * @returns {(tYr: number) => number[]}
181
+ */
182
+ function createOrbitNormalEvaluator(zetaModes, anchorInclEclipticDeg, anchorAscNodeEclipticDeg) {
183
+ const s2 = Math.sin(anchorInclEclipticDeg / 2 * D2R);
184
+ const anchor = [s2 * Math.cos(anchorAscNodeEclipticDeg * D2R), s2 * Math.sin(anchorAscNodeEclipticDeg * D2R)];
185
+ const sum = (/** @type {number} */ t) => {
186
+ let re = 0, im = 0;
187
+ for (const m of zetaModes) {
188
+ const c = Math.cos(m.omegaRadPerYr * t), s = Math.sin(m.omegaRadPerYr * t);
189
+ re += m.re * c - m.im * s;
190
+ im += m.re * s + m.im * c;
191
+ }
192
+ return [re, im];
193
+ };
194
+ const s0 = sum(0);
195
+ const R = [anchor[0] - s0[0], anchor[1] - s0[1]];
196
+ return (t) => {
197
+ const [x, y] = sum(t);
198
+ const q = x + R[0], p = y + R[1];
199
+ const i = 2 * Math.asin(Math.min(1, Math.hypot(q, p)));
200
+ const Om = Math.atan2(p, q);
201
+ return [Math.sin(i) * Math.sin(Om), -Math.sin(i) * Math.cos(Om), Math.cos(i)];
202
+ };
203
+ }
204
+
205
+ /**
206
+ * @param {{
207
+ * key: string,
208
+ * spin: SpinInputs,
209
+ * zetaModes: ReadonlyArray<{omegaRadPerYr: number, re: number, im: number}>,
210
+ * anchorInclEclipticDeg: number, anchorAscNodeEclipticDeg: number,
211
+ * semiMajorAxisAU: number, eccentricity: number, massFractionOfSun: number,
212
+ * gmSunKm3S2: number, obliquityJ2000Deg: number, stepYr?: number,
213
+ * }} deps — spin from the astro-reference planetSpinPhysical block; the ζ
214
+ * table and the J2000 anchor elements from the governed artifacts; the
215
+ * mass fraction (planet system / Sun) for the two-body mean motion
216
+ * n = 2π√((1 + μ)/a³); the J2000 obliquity of the ecliptic for the pole
217
+ * conversion.
218
+ */
219
+ function createPlanetSpinChannel(deps) {
220
+ const {
221
+ key, spin, zetaModes, anchorInclEclipticDeg, anchorAscNodeEclipticDeg,
222
+ semiMajorAxisAU, eccentricity, massFractionOfSun, gmSunKm3S2, obliquityJ2000Deg,
223
+ } = deps;
224
+ // the two-body mean motion in the model's own AU–year system: n² a³ = GM_sun (1 + μ)
225
+ const meanMotionRadPerYr = 2 * Math.PI * Math.sqrt((1 + massFractionOfSun) / Math.pow(semiMajorAxisAU, 3));
226
+ const constant = computePlanetPrecessionConstant({
227
+ j2: spin.j2,
228
+ j2ReferenceRadiusKm: spin.j2ReferenceRadiusKm,
229
+ momentOfInertiaFactor: spin.momentOfInertiaFactor,
230
+ rotationRateDegPerDay: spin.rotationRateDegPerDay,
231
+ satellites: spin.satellites,
232
+ gmPlanetSystemKm3S2: gmSunKm3S2 * massFractionOfSun,
233
+ meanMotionRadPerYr,
234
+ eccentricity,
235
+ });
236
+ if (!Number.isFinite(constant.alphaRadPerYr)) {
237
+ // a NaN α would integrate to NaN samples that read as the J2000 value
238
+ // through the envelope's min/max — refuse loudly instead
239
+ throw new Error(`createPlanetSpinChannel(${key}): the precession constant is not finite — check J2, C/MR², the rotation rate, the satellite table and gmSunKm3S2`);
240
+ }
241
+ const orbitNormalAt = createOrbitNormalEvaluator(zetaModes, anchorInclEclipticDeg, anchorAscNodeEclipticDeg);
242
+ const n0 = orbitNormalAt(0);
243
+ // the J2000 spin: the IAU pole in the angular-momentum sense
244
+ const s0 = spinAxisJ2000(spin, obliquityJ2000Deg);
245
+ const obliquityJ2000 = angleDeg(s0, n0);
246
+ // the closed-form J2000 pole precession on the orbit normal: ψ̇ = −α cos ε
247
+ const spinPrecessionRateArcsecPerYrJ2000 = -constant.alphaArcsecPerYr * Math.cos(obliquityJ2000 * D2R);
248
+ const axialPrecessionPeriodYearsJ2000 = 1296000 / Math.abs(spinPrecessionRateArcsecPerYrJ2000);
249
+ const cassiniLocked = spin.cassiniLocked === true;
250
+
251
+ /** @param {number[]} s @param {number} t */
252
+ const deriv = (s, t) => {
253
+ const n = orbitNormalAt(t);
254
+ const k = constant.alphaRadPerYr * dot(s, n);
255
+ const c = cross(s, n);
256
+ return [k * c[0], k * c[1], k * c[2]];
257
+ };
258
+ /** one RK4 step of the spin equation @param {number[]} s @param {number} t @param {number} h */
259
+ const rk4 = (s, t, h) => {
260
+ const k1 = deriv(s, t);
261
+ const k2 = deriv([s[0] + h / 2 * k1[0], s[1] + h / 2 * k1[1], s[2] + h / 2 * k1[2]], t + h / 2);
262
+ const k3 = deriv([s[0] + h / 2 * k2[0], s[1] + h / 2 * k2[1], s[2] + h / 2 * k2[2]], t + h / 2);
263
+ const k4 = deriv([s[0] + h * k3[0], s[1] + h * k3[1], s[2] + h * k3[2]], t + h);
264
+ return unit([
265
+ s[0] + h / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]),
266
+ s[1] + h / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]),
267
+ s[2] + h / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]),
268
+ ]);
269
+ };
270
+
271
+ // the planet's own step (a research override `deps.stepYr` for the labs)
272
+ const stepYr = deps.stepYr ?? (cassiniLocked ? STEP_MAX_YR
273
+ : Math.min(STEP_MAX_YR, Math.max(STEP_MIN_YR, axialPrecessionPeriodYearsJ2000 / STEPS_PER_PRECESSION_PERIOD)));
274
+ // append-only trajectories from J2000, one per direction: sample i sits at t = sign · i · step
275
+ /** @typedef {{ eps: number[], last: number[] }} Track */
276
+ /** @returns {Track} */
277
+ const mkTrack = () => ({ eps: [obliquityJ2000], last: s0.slice() });
278
+ const fwd = mkTrack(), bwd = mkTrack();
279
+ /** @param {Track} tr @param {number} sign @param {number} count */
280
+ const grow = (tr, sign, count) => {
281
+ while (tr.eps.length <= count) {
282
+ const i = tr.eps.length - 1;
283
+ const t = sign * i * stepYr;
284
+ const h = sign * stepYr;
285
+ const s = rk4(tr.last, t, h);
286
+ tr.eps.push(angleDeg(s, orbitNormalAt(t + h)));
287
+ tr.last = s;
288
+ }
289
+ };
290
+ /** the samples bracketing t, or null outside the domain @param {number} tYr */
291
+ const bracket = (tYr) => {
292
+ if (!Number.isFinite(tYr) || Math.abs(tYr) > MAX_SPAN_YR) return null;
293
+ const tr = tYr < 0 ? bwd : fwd;
294
+ const sign = tYr < 0 ? -1 : 1;
295
+ const x = Math.abs(tYr) / stepYr;
296
+ const i = Math.floor(x);
297
+ grow(tr, sign, i + 1);
298
+ return { tr, i, f: x - i };
299
+ };
300
+
301
+ /** obliquity to the planet's own orbit of date, degrees (angular-momentum sense: Venus ≈ 177°, Uranus ≈ 98°); null beyond ±10 Myr @param {number} year */
302
+ const obliquityDegAtYear = (year) => {
303
+ if (cassiniLocked) return obliquityJ2000;
304
+ const b = bracket(year - 2000);
305
+ if (!b) return null;
306
+ return b.tr.eps[b.i] + b.f * (b.tr.eps[b.i + 1] - b.tr.eps[b.i]);
307
+ };
308
+ /**
309
+ * the pole's precession rate about the orbit normal of date, ″/yr — the
310
+ * spin equation's own rate, ψ̇ = −α cos ε(t), exact at the instant (the
311
+ * orbit plane's own precession is NOT in it: this is the rate the
312
+ * observers' constants quote, about the instantaneous orbit normal)
313
+ * @param {number} year
314
+ */
315
+ const spinPrecessionRateArcsecPerYrAtYear = (year) => {
316
+ if (cassiniLocked) return null;
317
+ const eps = obliquityDegAtYear(year);
318
+ return eps === null ? null : -constant.alphaArcsecPerYr * Math.cos(eps * D2R);
319
+ };
320
+ /** the obliquity envelope over [year − spanYr, year + spanYr] on the sample grid @param {number} year @param {number} spanYr */
321
+ const obliquityEnvelopeDeg = (year, spanYr) => {
322
+ if (cassiniLocked) return { minDeg: obliquityJ2000, meanDeg: obliquityJ2000, maxDeg: obliquityJ2000 };
323
+ const lo = bracket(year - 2000 - spanYr), hi = bracket(year - 2000 + spanYr);
324
+ if (!lo || !hi) return null;
325
+ let mn = Infinity, mx = -Infinity, sum = 0, cnt = 0;
326
+ for (let t = year - 2000 - spanYr; t <= year - 2000 + spanYr; t += stepYr) {
327
+ const b = bracket(t);
328
+ if (!b) continue;
329
+ const e = b.tr.eps[b.i];
330
+ if (e < mn) mn = e;
331
+ if (e > mx) mx = e;
332
+ sum += e; cnt++;
333
+ }
334
+ return { minDeg: mn, meanDeg: sum / cnt, maxDeg: mx };
335
+ };
336
+
337
+ return Object.freeze({
338
+ key,
339
+ cassiniLocked,
340
+ momentOfInertiaFactor: spin.momentOfInertiaFactor,
341
+ momentOfInertiaFactorClass: spin.momentOfInertiaFactorClass || 'unstated',
342
+ alphaArcsecPerYr: constant.alphaArcsecPerYr,
343
+ satelliteQuadrupole: constant.q,
344
+ satelliteAngularMomentum: constant.l,
345
+ spinRetrograde: constant.spinRetrograde,
346
+ meanMotionRadPerYr,
347
+ obliquityJ2000Deg: obliquityJ2000,
348
+ spinPrecessionRateArcsecPerYrJ2000: cassiniLocked ? null : spinPrecessionRateArcsecPerYrJ2000,
349
+ axialPrecessionPeriodYearsJ2000: cassiniLocked ? null : axialPrecessionPeriodYearsJ2000,
350
+ obliquityDegAtYear,
351
+ spinPrecessionRateArcsecPerYrAtYear,
352
+ obliquityEnvelopeDeg,
353
+ stepYr,
354
+ maxSpanYr: MAX_SPAN_YR,
355
+ });
356
+ }
357
+
358
+ /**
359
+ * The channel from the governed artifacts — the ONE construction every
360
+ * runtime shares (the package model, the Node scene engine, the browser
361
+ * through the model): the spin block, the chain's J2000 anchor elements and
362
+ * the deep ζ table for `key`.
363
+ * @param {{
364
+ * key: string,
365
+ * planetSpinPhysical: Record<string, SpinInputs>,
366
+ * chainAnchorElements: Record<string, { aAU: number, e: number, inclEclipticDeg: number, ascNodeEclipticDeg: number }>,
367
+ * planetZeta: Record<string, ReadonlyArray<{omegaRadPerYr: number, re: number, im: number}>>,
368
+ * massFractionOfSun: number, gmSunKm3S2: number, obliquityJ2000Deg: number,
369
+ * }} deps
370
+ */
371
+ function createPlanetSpinChannelFromArtifacts(deps) {
372
+ const A = deps.chainAnchorElements[deps.key];
373
+ if (!A) throw new Error(`createPlanetSpinChannelFromArtifacts: no chain anchor for '${deps.key}'`);
374
+ return createPlanetSpinChannel({
375
+ key: deps.key,
376
+ spin: deps.planetSpinPhysical[deps.key],
377
+ zetaModes: deps.planetZeta[deps.key],
378
+ anchorInclEclipticDeg: A.inclEclipticDeg,
379
+ anchorAscNodeEclipticDeg: A.ascNodeEclipticDeg,
380
+ semiMajorAxisAU: A.aAU,
381
+ eccentricity: A.e,
382
+ massFractionOfSun: deps.massFractionOfSun,
383
+ gmSunKm3S2: deps.gmSunKm3S2,
384
+ obliquityJ2000Deg: deps.obliquityJ2000Deg,
385
+ });
386
+ }
387
+
388
+ module.exports = {
389
+ computePlanetPrecessionConstant, createPlanetSpinChannel, createPlanetSpinChannelFromArtifacts,
390
+ computeObliquityJ2000Deg, poleEclipticJ2000, createOrbitNormalEvaluator,
391
+ };
@@ -2,14 +2,14 @@
2
2
  // Gives the TypeScript website full type safety at the boundary (§2g) while
3
3
  // packages/physics stays JavaScript.
4
4
 
5
- export declare const CONSTANTS_HASH: "2f0bb1d372d35ed9";
5
+ export declare const CONSTANTS_HASH: "b9c4d78647577e67";
6
6
 
7
7
  export declare const MODEL_VERSION: string;
8
8
 
9
9
  export declare const PREPRINT_DOI: string;
10
10
 
11
11
  export declare const DEFAULT_CONSTANTS: {
12
- readonly hash: "2f0bb1d372d35ed9";
12
+ readonly hash: "b9c4d78647577e67";
13
13
  readonly additionalBodies: {
14
14
  "pluto": {
15
15
  "name": string;
@@ -372,6 +372,113 @@ export declare const DEFAULT_CONSTANTS: {
372
372
  "rotationPeriodDays": number;
373
373
  };
374
374
  };
375
+ readonly planetSpinPhysical: {
376
+ "mercury": {
377
+ "j2": number;
378
+ "j2ReferenceRadiusKm": number;
379
+ "momentOfInertiaFactor": number;
380
+ "momentOfInertiaFactorClass": string;
381
+ "rotationRateDegPerDay": number;
382
+ "poleRaJ2000Deg": number;
383
+ "poleDecJ2000Deg": number;
384
+ "cassiniLocked": boolean;
385
+ "satellites": unknown[];
386
+ };
387
+ "venus": {
388
+ "j2": number;
389
+ "j2ReferenceRadiusKm": number;
390
+ "momentOfInertiaFactor": number;
391
+ "momentOfInertiaFactorClass": string;
392
+ "rotationRateDegPerDay": number;
393
+ "poleRaJ2000Deg": number;
394
+ "poleDecJ2000Deg": number;
395
+ "cassiniLocked": boolean;
396
+ "satellites": unknown[];
397
+ };
398
+ "mars": {
399
+ "j2": number;
400
+ "j2ReferenceRadiusKm": number;
401
+ "momentOfInertiaFactor": number;
402
+ "momentOfInertiaFactorClass": string;
403
+ "rotationRateDegPerDay": number;
404
+ "poleRaJ2000Deg": number;
405
+ "poleDecJ2000Deg": number;
406
+ "cassiniLocked": boolean;
407
+ "satellites": Array<{
408
+ "name": string;
409
+ "gmKm3S2": number;
410
+ "semiMajorAxisKm": number;
411
+ "orbitalPeriodDays": number;
412
+ }>;
413
+ };
414
+ "jupiter": {
415
+ "j2": number;
416
+ "j2ReferenceRadiusKm": number;
417
+ "momentOfInertiaFactor": number;
418
+ "momentOfInertiaFactorClass": string;
419
+ "momentOfInertiaFactorAlternative": number;
420
+ "rotationRateDegPerDay": number;
421
+ "poleRaJ2000Deg": number;
422
+ "poleDecJ2000Deg": number;
423
+ "cassiniLocked": boolean;
424
+ "satellites": Array<{
425
+ "name": string;
426
+ "gmKm3S2": number;
427
+ "semiMajorAxisKm": number;
428
+ "orbitalPeriodDays": number;
429
+ }>;
430
+ };
431
+ "saturn": {
432
+ "j2": number;
433
+ "j2ReferenceRadiusKm": number;
434
+ "momentOfInertiaFactor": number;
435
+ "momentOfInertiaFactorClass": string;
436
+ "momentOfInertiaFactorAlternative": [number, number];
437
+ "rotationRateDegPerDay": number;
438
+ "poleRaJ2000Deg": number;
439
+ "poleDecJ2000Deg": number;
440
+ "cassiniLocked": boolean;
441
+ "satellites": Array<{
442
+ "name": string;
443
+ "gmKm3S2": number;
444
+ "semiMajorAxisKm": number;
445
+ "orbitalPeriodDays": number;
446
+ }>;
447
+ };
448
+ "uranus": {
449
+ "j2": number;
450
+ "j2ReferenceRadiusKm": number;
451
+ "momentOfInertiaFactor": number;
452
+ "momentOfInertiaFactorClass": string;
453
+ "rotationRateDegPerDay": number;
454
+ "poleRaJ2000Deg": number;
455
+ "poleDecJ2000Deg": number;
456
+ "cassiniLocked": boolean;
457
+ "satellites": Array<{
458
+ "name": string;
459
+ "gmKm3S2": number;
460
+ "semiMajorAxisKm": number;
461
+ "orbitalPeriodDays": number;
462
+ }>;
463
+ };
464
+ "neptune": {
465
+ "j2": number;
466
+ "j2ReferenceRadiusKm": number;
467
+ "momentOfInertiaFactor": number;
468
+ "momentOfInertiaFactorClass": string;
469
+ "rotationRateDegPerDay": number;
470
+ "poleRaJ2000Deg": number;
471
+ "poleDecJ2000Deg": number;
472
+ "cassiniLocked": boolean;
473
+ "satellites": Array<{
474
+ "name": string;
475
+ "gmKm3S2": number;
476
+ "semiMajorAxisKm": number;
477
+ "orbitalPeriodDays": number;
478
+ "retrograde": boolean;
479
+ }>;
480
+ };
481
+ };
375
482
  readonly planets: {
376
483
  "mercury": {
377
484
  "name": string;
@@ -386,9 +493,6 @@ export declare const DEFAULT_CONSTANTS: {
386
493
  "inclinationCycleAnchor": number;
387
494
  "antiPhase": boolean;
388
495
  "ascendingNodeCyclesIn8H": number;
389
- "obliquityCycleFraction": [number, number];
390
- "axialPrecessionFraction": [number, number];
391
- "eccentricityCycleFraction": [number, number];
392
496
  };
393
497
  "venus": {
394
498
  "name": string;
@@ -403,9 +507,6 @@ export declare const DEFAULT_CONSTANTS: {
403
507
  "inclinationCycleAnchor": number;
404
508
  "antiPhase": boolean;
405
509
  "ascendingNodeCyclesIn8H": number;
406
- "obliquityCycleFraction": null;
407
- "axialPrecessionFraction": [number, number];
408
- "eccentricityCycleFraction": [number, number];
409
510
  };
410
511
  "mars": {
411
512
  "name": string;
@@ -420,9 +521,6 @@ export declare const DEFAULT_CONSTANTS: {
420
521
  "inclinationCycleAnchor": number;
421
522
  "antiPhase": boolean;
422
523
  "ascendingNodeCyclesIn8H": number;
423
- "obliquityCycleFraction": [number, number];
424
- "axialPrecessionFraction": [number, number];
425
- "eccentricityCycleFraction": [number, number];
426
524
  };
427
525
  "jupiter": {
428
526
  "name": string;
@@ -437,9 +535,6 @@ export declare const DEFAULT_CONSTANTS: {
437
535
  "inclinationCycleAnchor": number;
438
536
  "antiPhase": boolean;
439
537
  "ascendingNodeCyclesIn8H": number;
440
- "obliquityCycleFraction": [number, number];
441
- "axialPrecessionFraction": [number, number];
442
- "eccentricityCycleFraction": [number, number];
443
538
  };
444
539
  "saturn": {
445
540
  "name": string;
@@ -454,9 +549,6 @@ export declare const DEFAULT_CONSTANTS: {
454
549
  "inclinationCycleAnchor": number;
455
550
  "antiPhase": boolean;
456
551
  "ascendingNodeCyclesIn8H": number;
457
- "obliquityCycleFraction": [number, number];
458
- "axialPrecessionFraction": [number, number];
459
- "eccentricityCycleFraction": [number, number];
460
552
  };
461
553
  "uranus": {
462
554
  "name": string;
@@ -471,10 +563,6 @@ export declare const DEFAULT_CONSTANTS: {
471
563
  "inclinationCycleAnchor": number;
472
564
  "antiPhase": boolean;
473
565
  "ascendingNodeCyclesIn8H": number;
474
- "obliquityCycleFraction": [number, number];
475
- "axialPrecessionFraction": [number, number];
476
- "eccentricityCycleFraction": [number, number];
477
- "eccentricityCycleApprox": boolean;
478
566
  };
479
567
  "neptune": {
480
568
  "name": string;
@@ -489,10 +577,6 @@ export declare const DEFAULT_CONSTANTS: {
489
577
  "inclinationCycleAnchor": number;
490
578
  "antiPhase": boolean;
491
579
  "ascendingNodeCyclesIn8H": number;
492
- "obliquityCycleFraction": null;
493
- "axialPrecessionFraction": [number, number];
494
- "eccentricityCycleFraction": [number, number];
495
- "eccentricityCycleApprox": boolean;
496
580
  };
497
581
  };
498
582
  readonly timeReference: {
package/types/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export * as planetFibonacciLaws from "./planets/fibonacci-laws.cjs";
15
15
  export * as planetOrientation from "./planets/orientation.cjs";
16
16
  export { integrateAscendingNode } from "./planets/asc-node-integrator.cjs";
17
17
  export * as planetOrbitChain from "./planets/orbit-chain.cjs";
18
+ export { computeSecularShape } from "./planets/secular-shape.cjs";
18
19
  export { createSecularSeriesOverride } from "./planets/secular-series.cjs";
19
20
  export { createDeepEccChannel } from "./moon/deep-ecc-channel.cjs";
20
21
  export { createDeepOrbitalHistory } from "./earth/deep-orbital-history.cjs";
@@ -70,6 +71,7 @@ export { computeEarthFrameOfDate, solveWheelAngleForLongitude } from "./earth/fr
70
71
  export { eccentricityFromCycles, computeEccentricityIntegrated } from "./planets/ecc-channel.cjs";
71
72
  export { buildPlanetChainsFromArtifactData, computePlanetElementsAtYear, computeHeliocentricEclipticFromElements, computePoissonArgRad, computeOsculatingElements, solveKeplerRad, computeApsidalSecularDegPerYr, ANCHOR_EPOCH_YEAR, ANCHOR_EPOCH_JD } from "./planets/keplerian-chain.cjs";
72
73
  export { computeEquatorNodeOriginSFrameDeg, convertNodeSFrameToEquatorOriginDeg } from "./planets/inv-plane-frame.cjs";
74
+ export { computePlanetPrecessionConstant, createPlanetSpinChannel, createPlanetSpinChannelFromArtifacts, computeObliquityJ2000Deg } from "./planets/spin-channel.cjs";
73
75
  export { CHAIN_ARTIFACT, CHAIN_ARTIFACT_HASH } from "./planets/chain-artifact.js";
74
76
  export { DEEP_MODES_ARTIFACT, DEEP_MODES_ARTIFACT_HASH } from "./moon/deep-modes-artifact.cjs";
75
77
  export { createYearLengths, ONE_FAMILY_WINDOW_YEARS } from "./earth/year-lengths.cjs";
package/types/model.d.ts CHANGED
@@ -189,5 +189,28 @@ export function assembleModel(C: Readonly<Record<string, any>>, F: Readonly<Reco
189
189
  perihelionLongitudeDeg: (k: string, year: number) => number;
190
190
  ascendingNodeInvPlaneDeg: (k: string, year: number) => number;
191
191
  invPlaneInclinationDeg: (k: string, year: number) => number;
192
+ spin: (k: string) => Readonly<{
193
+ key: string;
194
+ cassiniLocked: boolean;
195
+ momentOfInertiaFactor: number;
196
+ momentOfInertiaFactorClass: string;
197
+ alphaArcsecPerYr: number;
198
+ satelliteQuadrupole: number;
199
+ satelliteAngularMomentum: number;
200
+ spinRetrograde: boolean;
201
+ meanMotionRadPerYr: number;
202
+ obliquityJ2000Deg: number;
203
+ spinPrecessionRateArcsecPerYrJ2000: number | null;
204
+ axialPrecessionPeriodYearsJ2000: number | null;
205
+ obliquityDegAtYear: (year: number) => number | null;
206
+ spinPrecessionRateArcsecPerYrAtYear: (year: number) => number | null;
207
+ obliquityEnvelopeDeg: (year: number, spanYr: number) => {
208
+ minDeg: number;
209
+ meanDeg: number;
210
+ maxDeg: number;
211
+ } | null;
212
+ stepYr: number;
213
+ maxSpanYr: 10000000;
214
+ }>;
192
215
  }>;
193
216
  }>;