@essrt/physics 4.4.1 → 4.5.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/package.json +1 -1
- package/src/earth/deep-orbital-history.cjs +155 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essrt/physics",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "ESSRT (Expanding Solar System Resonance Theory) physics core \u2014 pure, injectable-constants, no I/O, no globals, no DOM. The complete model incl. fitted coefficients; every published version immutably ships one recorded model identity.",
|
|
5
5
|
"essrt": {
|
|
6
6
|
"modelVersion": "v13.0",
|
|
@@ -131,16 +131,29 @@ function createDeepOrbitalHistory({
|
|
|
131
131
|
// engine's own ζ series (anchored at J2000 exactly like the mode sums);
|
|
132
132
|
// outside it, the anchored mode sum is the tail. Without zetaSeries the
|
|
133
133
|
// mode sum serves at every t — the pre-C-2 behavior, bit-identical.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
// C1 series interpolation (D4g, measured): the 500-yr LINEAR series lerp
|
|
135
|
+
// put kinks in n̂(t)/z(t) at its nodes, and the equinox RATE inherited
|
|
136
|
+
// them — the grid-dependent bump around J2000 in per-year P(t) (the same
|
|
137
|
+
// interpolation-order disease as the spin grid, one level down). Cubic
|
|
138
|
+
// Hermite with central-difference node slopes (one-sided at the ends);
|
|
139
|
+
// exact at nodes, so the J2000 anchors are unchanged.
|
|
140
|
+
const mkSeriesCubic = (/** @type {number} */ t0Yr, /** @type {number} */ stepYr, /** @type {number} */ nS) =>
|
|
141
|
+
(/** @type {ReadonlyArray<number>} */ arr, /** @type {number} */ tt) => {
|
|
139
142
|
const x = (tt - t0Yr) / stepYr;
|
|
140
143
|
const i = Math.max(0, Math.min(nS - 2, Math.floor(x)));
|
|
141
144
|
const f = x - i;
|
|
142
|
-
|
|
145
|
+
const y0 = arr[i], y1 = arr[i + 1];
|
|
146
|
+
const m0 = i > 0 ? (y1 - arr[i - 1]) / 2 : y1 - y0;
|
|
147
|
+
const m1 = i + 2 < nS ? (arr[i + 2] - y0) / 2 : y1 - y0;
|
|
148
|
+
const f2 = f * f, f3 = f2 * f;
|
|
149
|
+
return (2 * f3 - 3 * f2 + 1) * y0 + (f3 - 2 * f2 + f) * m0
|
|
150
|
+
+ (-2 * f3 + 3 * f2) * y1 + (f3 - f2) * m1;
|
|
143
151
|
};
|
|
152
|
+
let zetaAt = zetaModeSum;
|
|
153
|
+
if (zetaSeries) {
|
|
154
|
+
const { t0Yr, stepYr, q: sq, p: sp } = zetaSeries;
|
|
155
|
+
const nS = sq.length, tEndYr = t0Yr + (nS - 1) * stepYr;
|
|
156
|
+
const li = mkSeriesCubic(t0Yr, stepYr, nS);
|
|
144
157
|
const R = [zetaAnchor[0] - li(sq, 0), zetaAnchor[1] - li(sp, 0)];
|
|
145
158
|
zetaAt = (/** @type {number} */ t) => (t >= t0Yr && t <= tEndYr)
|
|
146
159
|
? [li(sq, t) + R[0], li(sp, t) + R[1]]
|
|
@@ -155,12 +168,7 @@ function createDeepOrbitalHistory({
|
|
|
155
168
|
if (zSeries) {
|
|
156
169
|
const { t0Yr, stepYr, q: sq, p: sp } = zSeries;
|
|
157
170
|
const nS = sq.length, tEndYr = t0Yr + (nS - 1) * stepYr;
|
|
158
|
-
const li = (
|
|
159
|
-
const x = (tt - t0Yr) / stepYr;
|
|
160
|
-
const i = Math.max(0, Math.min(nS - 2, Math.floor(x)));
|
|
161
|
-
const f = x - i;
|
|
162
|
-
return arr[i] * (1 - f) + arr[i + 1] * f;
|
|
163
|
-
};
|
|
171
|
+
const li = mkSeriesCubic(t0Yr, stepYr, nS); // C1 (see mkSeriesCubic)
|
|
164
172
|
const R = [zAnchor[0] - li(sq, 0), zAnchor[1] - li(sp, 0)];
|
|
165
173
|
zAt = (/** @type {number} */ t) => (t >= t0Yr && t <= tEndYr)
|
|
166
174
|
? [li(sq, t) + R[0], li(sp, t) + R[1]]
|
|
@@ -189,7 +197,7 @@ function createDeepOrbitalHistory({
|
|
|
189
197
|
// constants: ψ̇(t) comes from the same certified year-length machinery
|
|
190
198
|
// as the J2000 anchor. Absent the option, α stays constant (the
|
|
191
199
|
// pre-D1 ±Myr-class behavior, bit-identical).
|
|
192
|
-
const
|
|
200
|
+
const alphaAtGeneral = axialPrecessionYearsAtYearFn
|
|
193
201
|
? (/** @type {number} */ t) =>
|
|
194
202
|
((2 * Math.PI) / axialPrecessionYearsAtYearFn(2000 + t)) / Math.cos(EPS0)
|
|
195
203
|
: () => ALPHA;
|
|
@@ -197,6 +205,71 @@ function createDeepOrbitalHistory({
|
|
|
197
205
|
const cross = (/** @type {number[]} */ a, /** @type {number[]} */ b) =>
|
|
198
206
|
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
199
207
|
const dot = (/** @type {number[]} */ a, /** @type {number[]} */ b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
208
|
+
|
|
209
|
+
// THE LUNISOLAR SELF-ANCHOR (D4e — measured, plan 02): the injected
|
|
210
|
+
// anchor axialPrecessionYearsJ2000 = sid/(sid−sol) is the GENERAL
|
|
211
|
+
// precession period, but ŝ must precess at the LUNISOLAR rate — the
|
|
212
|
+
// n̂(t) geometry supplies the planetary part itself, so integrating at
|
|
213
|
+
// the general rate DOUBLE-COUNTS the planetary mean (measured:
|
|
214
|
+
// −0.0966″/yr of equinox rate → +2.4 s of tropical year at J2000, and
|
|
215
|
+
// 0.19% of ε wobble phasing — the dominant term of the old 0.039° rms
|
|
216
|
+
// vs La2004 over −200 kyr; the lunisolar anchor collapses it to
|
|
217
|
+
// 0.0048°). Self-anchored at construction, no pasted numbers: a one-RK4
|
|
218
|
+
// -step probe at the general rate measures the realized equinox rate at
|
|
219
|
+
// J2000; the deficit rescales α so the REALIZED general precession hits
|
|
220
|
+
// the injected anchor exactly (dp/dα = 1 to first order — one
|
|
221
|
+
// fixed-point step lands on target, measured). H(t) shape preserved
|
|
222
|
+
// (constant factor on the caller's evaluator).
|
|
223
|
+
|
|
224
|
+
// THE ANCHORED SPIN AXIS (measured): ε₀ is defined against the ECLIPTIC
|
|
225
|
+
// — Earth's own mean orbit plane — never against the chain artifact's
|
|
226
|
+
// coordinate pole. The anchor elements carry i₀ ≈ 0.37″ of integrator-
|
|
227
|
+
// frame residue, and tilting ŝ(0) from the coordinate pole projected
|
|
228
|
+
// −0.2865″ of it straight into ε(J2000) (the C-1 era gate's offset,
|
|
229
|
+
// masked until the C1 series interpolation removed the lerp kink at the
|
|
230
|
+
// J2000 node). Build ŝ(0) FROM n̂(0): tilt ε₀ about the J2000 equinox
|
|
231
|
+
// direction projected into the orbit plane. Frame-invariant (the
|
|
232
|
+
// artifact's coordinate frame drops out) and ε(J2000) ≡ ε₀ by
|
|
233
|
+
// construction; reduces to [0, sin ε₀, cos ε₀] exactly when n̂(0) = ẑ.
|
|
234
|
+
const S0A = (() => {
|
|
235
|
+
const n0 = orbitNormal(0);
|
|
236
|
+
const px = [1 - n0[0] * n0[0], -n0[0] * n0[1], -n0[0] * n0[2]];
|
|
237
|
+
const pm = Math.hypot(px[0], px[1], px[2]);
|
|
238
|
+
const u = [px[0] / pm, px[1] / pm, px[2] / pm];
|
|
239
|
+
const v = cross(n0, u);
|
|
240
|
+
return [0, 1, 2].map((i) => Math.cos(EPS0) * n0[i] + Math.sin(EPS0) * v[i]);
|
|
241
|
+
})();
|
|
242
|
+
|
|
243
|
+
const K_LUNI = (() => {
|
|
244
|
+
const S0P = S0A;
|
|
245
|
+
const dv = (/** @type {number[]} */ s, /** @type {number} */ t) => {
|
|
246
|
+
const n = orbitNormal(t);
|
|
247
|
+
const k = alphaAtGeneral(t) * dot(s, n);
|
|
248
|
+
const c = cross(s, n);
|
|
249
|
+
return [k * c[0], k * c[1], k * c[2]];
|
|
250
|
+
};
|
|
251
|
+
const step = (/** @type {number[]} */ s, /** @type {number} */ t, /** @type {number} */ h) => {
|
|
252
|
+
const k1 = dv(s, t);
|
|
253
|
+
const k2 = dv([s[0] + h / 2 * k1[0], s[1] + h / 2 * k1[1], s[2] + h / 2 * k1[2]], t + h / 2);
|
|
254
|
+
const k3 = dv([s[0] + h / 2 * k2[0], s[1] + h / 2 * k2[1], s[2] + h / 2 * k2[2]], t + h / 2);
|
|
255
|
+
const k4 = dv([s[0] + h * k3[0], s[1] + h * k3[1], s[2] + h * k3[2]], t + h);
|
|
256
|
+
const o = [0, 1, 2].map((i) => s[i] + h / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]));
|
|
257
|
+
const r = Math.hypot(o[0], o[1], o[2]);
|
|
258
|
+
return [o[0] / r, o[1] / r, o[2] / r];
|
|
259
|
+
};
|
|
260
|
+
const eqLonDeg = (/** @type {number[]} */ s, /** @type {number} */ t) => {
|
|
261
|
+
const n = orbitNormal(t);
|
|
262
|
+
const g = cross(s, n);
|
|
263
|
+
return Math.atan2(g[1], g[0]) * R2D;
|
|
264
|
+
};
|
|
265
|
+
const lM = eqLonDeg(step(S0P, 0, -0.5), -0.5);
|
|
266
|
+
const lP = eqLonDeg(step(S0P, 0, +0.5), +0.5);
|
|
267
|
+
const pProbeDegYr = ((lM - lP + 540) % 360) - 180; // retrograde → positive
|
|
268
|
+
const pTargetDegYr = 360 / axialPrecessionYearsJ2000;
|
|
269
|
+
const psiDot0 = alphaAtGeneral(0) * Math.cos(EPS0);
|
|
270
|
+
return (psiDot0 + (pTargetDegYr - pProbeDegYr) * D2R) / psiDot0;
|
|
271
|
+
})();
|
|
272
|
+
const alphaAt = (/** @type {number} */ t) => alphaAtGeneral(t) * K_LUNI;
|
|
200
273
|
const deriv = (/** @type {number[]} */ s, /** @type {number} */ t) => {
|
|
201
274
|
const n = orbitNormal(t);
|
|
202
275
|
const k = alphaAt(t) * dot(s, n);
|
|
@@ -213,6 +286,14 @@ function createDeepOrbitalHistory({
|
|
|
213
286
|
return [o[0] / r, o[1] / r, o[2] / r];
|
|
214
287
|
};
|
|
215
288
|
|
|
289
|
+
/** Equinox-node longitude only (J2000 ecliptic frame) — the light read
|
|
290
|
+
* used for the node-rate finite difference at store time. */
|
|
291
|
+
function eqLonOnlyDeg(/** @type {number[]} */ s, /** @type {number} */ t) {
|
|
292
|
+
const n = orbitNormal(t);
|
|
293
|
+
const g = cross(s, n);
|
|
294
|
+
return Math.atan2(g[1], g[0]) * R2D;
|
|
295
|
+
}
|
|
296
|
+
|
|
216
297
|
/** One quantity bundle at time t (years from J2000) from spin axis s. */
|
|
217
298
|
function sampleAt(/** @type {number[]} */ s, /** @type {number} */ t) {
|
|
218
299
|
const n = orbitNormal(t);
|
|
@@ -238,6 +319,26 @@ function createDeepOrbitalHistory({
|
|
|
238
319
|
eSinPeri: e * Math.sin(periOfDateDeg * D2R),
|
|
239
320
|
eCosPeri: e * Math.cos(periOfDateDeg * D2R),
|
|
240
321
|
inclEclDeg: i * R2D,
|
|
322
|
+
// The equinox node (ŝ×n̂) longitude in the J2000 ecliptic frame —
|
|
323
|
+
// its year-over-year retrograde advance IS the general precession of
|
|
324
|
+
// date, wobble included (the n̂(t) geometry generates the equinox
|
|
325
|
+
// wobble; ψ̇ itself is the secular α(H(t))). Consumers derive the
|
|
326
|
+
// tropical year of date from it: T_trop = T_sid·(1 − p_yr/360°).
|
|
327
|
+
// ⚠ RATE-CONSUMER CONTRACT (measured): this field's rate is correct
|
|
328
|
+
// AS IS — α is self-anchored to the LUNISOLAR rate at construction
|
|
329
|
+
// (K_LUNI, the one-RK4-step probe above), so the realized general
|
|
330
|
+
// precession p_geom(J2000) equals 360/axialPrecessionYearsJ2000 by
|
|
331
|
+
// construction. No correction, no δ subtraction. History (why this
|
|
332
|
+
// contract exists): before the self-anchor, α carried the GENERAL
|
|
333
|
+
// sid/(sid−sol) rate and the n̂(t) geometry re-added the planetary
|
|
334
|
+
// mean — a double-count of +0.097″/yr ≈ +2.4 s of tropical year at
|
|
335
|
+
// J2000 — and consumers had to subtract the runtime anchor
|
|
336
|
+
// δ = p_geom(J2000) − 360/axialPrecessionYearsJ2000.
|
|
337
|
+
// A consumer may still measure δ at runtime as a SELF-CHECK; it
|
|
338
|
+
// must read ≈0 (sub-1e-4 ″/yr class). The LONGITUDE itself is raw
|
|
339
|
+
// geometry and has always needed no correction.
|
|
340
|
+
equinoxLonJ2000Deg: ((Math.atan2(gu[1], gu[0]) * R2D) % 360 + 360) % 360,
|
|
341
|
+
equinoxLonRateDegPerYr: 0, // filled at store time (build's ±2.5-yr central difference)
|
|
241
342
|
};
|
|
242
343
|
}
|
|
243
344
|
|
|
@@ -256,7 +357,7 @@ function createDeepOrbitalHistory({
|
|
|
256
357
|
throw new Error(`deep-orbital-history: builds beyond ±50 kyr need stepYr % 250 === 0 (got ${stepYr})`);
|
|
257
358
|
}
|
|
258
359
|
const grid = new Map();
|
|
259
|
-
const S0 =
|
|
360
|
+
const S0 = S0A;
|
|
260
361
|
// D1-revised adaptive stepping: 5-yr RK4 near the era (the certified-
|
|
261
362
|
// precision zone), 250-yr beyond ±50 kyr — still 103 steps per
|
|
262
363
|
// precession cycle and ≥196 samples of the fastest ζ mode (49 kyr), so
|
|
@@ -269,7 +370,23 @@ function createDeepOrbitalHistory({
|
|
|
269
370
|
const hStepAt = (/** @type {number} */ t) => (Math.abs(t) < 50000 ? 5 : 250);
|
|
270
371
|
for (const dir of [-1, +1]) {
|
|
271
372
|
let s = S0, t = 0;
|
|
272
|
-
|
|
373
|
+
// Each stored node also carries the equinox-node RATE (deg/yr) from a
|
|
374
|
+
// ±2.5-yr central difference on the integrator's own state — the C1
|
|
375
|
+
// (Hermite) interpolation input. Linear interpolation of the node
|
|
376
|
+
// longitude made the realized equinox rate PIECEWISE-CONSTANT per
|
|
377
|
+
// grid cell — the report's per-year precession column showed flat
|
|
378
|
+
// centuries with steps at the 1700/1800/1900/2000 cell edges
|
|
379
|
+
// (measured; the levels were the real wobble at 100-yr resolution,
|
|
380
|
+
// the staircase was this interpolation order).
|
|
381
|
+
const storeWithRate = (/** @type {number} */ key, /** @type {number[]} */ sv, /** @type {number} */ tv) => {
|
|
382
|
+
const smp = sampleAt(sv, tv);
|
|
383
|
+
const hR = 2.5;
|
|
384
|
+
const lp = eqLonOnlyDeg(rk4(sv, tv, +hR), tv + hR);
|
|
385
|
+
const lm = eqLonOnlyDeg(rk4(sv, tv, -hR), tv - hR);
|
|
386
|
+
smp.equinoxLonRateDegPerYr = (((lp - lm + 540) % 360) - 180) / (2 * hR);
|
|
387
|
+
grid.set(key, smp);
|
|
388
|
+
};
|
|
389
|
+
storeWithRate(0, s, 0);
|
|
273
390
|
const end = dir < 0 ? tMin : tMax;
|
|
274
391
|
while (dir < 0 ? t > end : t < end) {
|
|
275
392
|
const H_STEP = hStepAt(t);
|
|
@@ -283,7 +400,7 @@ function createDeepOrbitalHistory({
|
|
|
283
400
|
// 100-yr keys, 50 yr off). Exact alignment is guaranteed by the
|
|
284
401
|
// guard below (coarse builds use 250-aligned grids; the 50,000-yr
|
|
285
402
|
// zone boundary is itself 250-aligned, so the walk stays on-grid).
|
|
286
|
-
if (Math.abs(t - Math.round(t / stepYr) * stepYr) < 1e-6)
|
|
403
|
+
if (Math.abs(t - Math.round(t / stepYr) * stepYr) < 1e-6) storeWithRate(Math.round(t / stepYr) * stepYr, s, t);
|
|
287
404
|
}
|
|
288
405
|
}
|
|
289
406
|
return {
|
|
@@ -302,12 +419,33 @@ function createDeepOrbitalHistory({
|
|
|
302
419
|
eSinPeri: lerp(a.eSinPeri, b.eSinPeri),
|
|
303
420
|
eCosPeri: lerp(a.eCosPeri, b.eCosPeri),
|
|
304
421
|
inclEclDeg: lerp(a.inclEclDeg, b.inclEclDeg),
|
|
422
|
+
// C1 (cubic Hermite) — linear interpolation here made the
|
|
423
|
+
// realized equinox RATE piecewise-constant per grid cell (the
|
|
424
|
+
// measured per-year precession staircase). Node rates come from
|
|
425
|
+
// the build's own ±2.5-yr central differences.
|
|
426
|
+
equinoxLonJ2000Deg: (() => {
|
|
427
|
+
const l0 = a.equinoxLonJ2000Deg;
|
|
428
|
+
const dl = ((b.equinoxLonJ2000Deg - l0 + 540) % 360) - 180; // unwrapped segment
|
|
429
|
+
const m0 = a.equinoxLonRateDegPerYr * stepYr;
|
|
430
|
+
const m1 = (b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr) * stepYr;
|
|
431
|
+
const f2 = f * f, f3 = f2 * f;
|
|
432
|
+
const v = (2 * f3 - 3 * f2 + 1) * l0 + (f3 - 2 * f2 + f) * m0
|
|
433
|
+
+ (-2 * f3 + 3 * f2) * (l0 + dl) + (f3 - f2) * m1;
|
|
434
|
+
return ((v % 360) + 360) % 360;
|
|
435
|
+
})(),
|
|
436
|
+
equinoxLonRateDegPerYr: lerp(a.equinoxLonRateDegPerYr, b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr),
|
|
305
437
|
};
|
|
306
438
|
},
|
|
307
439
|
};
|
|
308
440
|
}
|
|
309
441
|
|
|
310
|
-
return {
|
|
442
|
+
return {
|
|
443
|
+
build,
|
|
444
|
+
alphaArcsecPerYr: ALPHA * R2D * 3600, // the injected GENERAL-anchor form (input echo, unchanged semantics)
|
|
445
|
+
// D4e diagnostics: the self-anchored lunisolar rate actually integrated.
|
|
446
|
+
alphaLunisolarArcsecPerYr: ALPHA * K_LUNI * R2D * 3600,
|
|
447
|
+
axialPrecessionYearsLunisolarJ2000: axialPrecessionYearsJ2000 / K_LUNI,
|
|
448
|
+
};
|
|
311
449
|
}
|
|
312
450
|
|
|
313
451
|
module.exports = { createDeepOrbitalHistory };
|