@essrt/physics 10.0.0 → 11.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/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@essrt/physics",
3
- "version": "10.0.0",
3
+ "version": "11.0.0",
4
4
  "description": "ESSRT (Expanding Solar System Resonance Theory) physics core — 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
- "modelVersion": "v15.0",
6
+ "modelVersion": "v16.0",
7
7
  "_note": "The model identity this package version ships (IP-unified-architecture §10: package semver tracks the API axis; the model version + CONSTANTS_HASH/COEFFICIENTS_HASH in src/constants/ carry the scientific identity). The version-pinning gate enforces this pairing against public/input/model-version.json."
8
8
  },
9
9
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  export const CONSTANTS_HASH = "726b10e23d8ace09";
37
37
 
38
38
  /** Model version label — single source: public/input/model-version.json (§10 two-axis scheme). */
39
- export const MODEL_VERSION = "v15.0";
39
+ export const MODEL_VERSION = "v16.0";
40
40
 
41
41
  /** Canonical preprint DOI — single source: public/input/model-version.json. */
42
42
  export const PREPRINT_DOI = "10.21203/rs.3.rs-8758810/v4";
@@ -342,12 +342,46 @@ function createDeepOrbitalHistory({
342
342
  return [o[0] / r, o[1] / r, o[2] / r];
343
343
  };
344
344
 
345
- /** Equinox-node longitude only (J2000 ecliptic frame) — the light read
346
- * used for the node-rate finite difference at store time. */
347
- function eqLonOnlyDeg(/** @type {number[]} */ s, /** @type {number} */ t) {
345
+ /** The equinox's GENERAL-PRECESSION longitude (deg, J2000 ecliptic frame):
346
+ * the broken angle through the ascending node of the ecliptic of date on
347
+ * the J2000 ecliptic — Π (the node's longitude) plus the arc along the
348
+ * ecliptic of date from that node to the equinox. This is the p_A of the
349
+ * precession literature (Lieske 1977; Simon et al. 1994: p_A = Λ_A − Π_A):
350
+ * the quantity the mean longitude OF DATE subtracts from the fixed-frame
351
+ * mean longitude — the N-body λ = Ω + ω + M is itself this broken angle
352
+ * (lattice-long-window-test.mjs), so the tropical year of date must be
353
+ * built on THIS rate. NOT the projected longitude atan2(g_y, g_x)
354
+ * (equinoxLonJ2000Deg, which the frame reconstruction needs): the two
355
+ * agree at J2000, where the ecliptic of date coincides with the J2000
356
+ * ecliptic, and part with the SQUARE of the ecliptic's tilt to its J2000
357
+ * position — (i²/4)·sin 2u class: 0.9″ at 0 AD, 5.9″ at −3000,
358
+ * arcminutes at deep time; as a rate 0.05 s of tropical year at ±4 kyr,
359
+ * 0.1–0.2 s at ±10 kyr, ~1 s beyond 100 kyr (measured 2026-09: the scene's
360
+ * own sidereal angle read exactly this against the projected-rate law at
361
+ * 16 epochs, and the re-booked Sun is flat against DE441 in the inertial
362
+ * frame over −2500…+2000 where the projected booking ramped to +5.6″).
363
+ * Degenerate node as i → 0: the dogleg's two parts compensate each other's
364
+ * error, and below 1e-9 the projected form is the exact limit.
365
+ * @param {number[]} n the ecliptic pole of date @param {number[]} gu the unit equinox */
366
+ function generalPrecessionLonDeg(n, gu) {
367
+ const Nx = -n[1], Ny = n[0]; // ẑ × n̂ — the ascending node of the ecliptic of date
368
+ const sN = Math.hypot(Nx, Ny);
369
+ if (sN < 1e-9) return Math.atan2(gu[1], gu[0]) * R2D;
370
+ const N = [Nx / sN, Ny / sN, 0];
371
+ const Pi = Math.atan2(N[1], N[0]);
372
+ const u = Math.atan2(dot(n, cross(N, gu)), dot(N, gu));
373
+ return (Pi + u) * R2D;
374
+ }
375
+
376
+ /** Equinox-node longitudes only (J2000 ecliptic frame; projected and
377
+ * general-precession forms) — the light read used for the node-rate
378
+ * finite differences at store time. */
379
+ function eqLonsOnlyDeg(/** @type {number[]} */ s, /** @type {number} */ t) {
348
380
  const n = orbitNormal(t);
349
381
  const g = cross(s, n);
350
- return Math.atan2(g[1], g[0]) * R2D;
382
+ const gn = Math.hypot(g[0], g[1], g[2]);
383
+ const gu = [g[0] / gn, g[1] / gn, g[2] / gn];
384
+ return { proj: Math.atan2(gu[1], gu[0]) * R2D, general: generalPrecessionLonDeg(n, gu) };
351
385
  }
352
386
 
353
387
  /** One quantity bundle at time t (years from J2000) from spin axis s. */
@@ -383,26 +417,38 @@ function createDeepOrbitalHistory({
383
417
  // interpolation on the grid is exact to the ζ series' own resolution.
384
418
  orbitNormalX: n[0],
385
419
  orbitNormalY: n[1],
386
- // The equinox node (ŝ×n̂) longitude in the J2000 ecliptic frame —
387
- // its year-over-year retrograde advance IS the general precession of
388
- // date, wobble included (the n̂(t) geometry generates the equinox
389
- // wobble; ψ̇ itself is the secular α(H(t))). Consumers derive the
390
- // tropical year of date from it: T_trop = T_sid·(1 − p_yr/360°).
391
- // ⚠ RATE-CONSUMER CONTRACT (measured): this field's rate is correct
420
+ // The equinox node (ŝ×n̂) PROJECTED longitude in the J2000 ecliptic
421
+ // frame — the frame reconstruction's input (frame-of-date.cjs rebuilds
422
+ // the unit equinox from it). Its rate is the general precession only
423
+ // to first order in the ecliptic's tilt to its J2000 position: the
424
+ // tropical-year consumers read generalPrecessionLonDeg below (measured
425
+ // 2026-09 — the projected rate booked the Sun's mean longitude of date
426
+ // 5.9″ off at −3000 and the sidereal year of date 1 s off at depth).
427
+ // ⚠ RATE-CONSUMER CONTRACT (measured): these fields' rates are correct
392
428
  // AS IS — α is self-anchored to the LUNISOLAR rate at construction
393
429
  // (K_LUNI, the one-RK4-step probe above), so the realized general
394
- // precession p_geom(J2000) equals 360/axialPrecessionYearsJ2000 by
395
- // construction. No correction, no δ subtraction. History (why this
396
- // contract exists): before the self-anchor, α carried the GENERAL
397
- // sid/(sid−sol) rate and the n̂(t) geometry re-added the planetary
398
- // mean — a double-count of +0.097″/yr ≈ +2.4 s of tropical year at
399
- // J2000 — and consumers had to subtract the runtime anchor
430
+ // precession p_geom(J2000) equals 360/axialPrecessionYearsJ2000 per
431
+ // JULIAN year (the sampler's time unit) by construction. No
432
+ // correction, no δ subtraction. History (why this contract exists):
433
+ // before the self-anchor, α carried the GENERAL sid/(sid−sol) rate and
434
+ // the n̂(t) geometry re-added the planetary mean — a double-count of
435
+ // +0.097″/yr ≈ +2.4 s of tropical year at J2000 — and consumers had to
436
+ // subtract the runtime anchor
400
437
  // δ = p_geom(J2000) − 360/axialPrecessionYearsJ2000.
401
438
  // A consumer may still measure δ at runtime as a SELF-CHECK; it
402
439
  // must read ≈0 (sub-1e-4 ″/yr class). The LONGITUDE itself is raw
403
440
  // geometry and has always needed no correction.
404
441
  equinoxLonJ2000Deg: ((Math.atan2(gu[1], gu[0]) * R2D) % 360 + 360) % 360,
405
442
  equinoxLonRateDegPerYr: 0, // filled at store time (build's ±2.5-yr central difference)
443
+ // The equinox's GENERAL-PRECESSION longitude (the broken angle through
444
+ // the node of the ecliptic of date — the literature's p_A; see
445
+ // generalPrecessionLonDeg above): its year-over-year retrograde advance
446
+ // IS the general precession of date, wobble included (the n̂(t)
447
+ // geometry generates the equinox wobble; ψ̇ itself is the secular
448
+ // α(H(t)) times the two factors of date). The tropical year of date is
449
+ // built on this rate (earth/year-lengths.cjs).
450
+ generalPrecessionLonDeg: ((generalPrecessionLonDeg(n, gu) % 360) + 360) % 360,
451
+ generalPrecessionLonRateDegPerYr: 0, // filled at store time (build's ±2.5-yr central difference)
406
452
  };
407
453
  }
408
454
 
@@ -445,9 +491,10 @@ function createDeepOrbitalHistory({
445
491
  const storeWithRate = (/** @type {number} */ key, /** @type {number[]} */ sv, /** @type {number} */ tv) => {
446
492
  const smp = sampleAt(sv, tv);
447
493
  const hR = 2.5;
448
- const lp = eqLonOnlyDeg(rk4(sv, tv, +hR), tv + hR);
449
- const lm = eqLonOnlyDeg(rk4(sv, tv, -hR), tv - hR);
450
- smp.equinoxLonRateDegPerYr = (((lp - lm + 540) % 360) - 180) / (2 * hR);
494
+ const lp = eqLonsOnlyDeg(rk4(sv, tv, +hR), tv + hR);
495
+ const lm = eqLonsOnlyDeg(rk4(sv, tv, -hR), tv - hR);
496
+ smp.equinoxLonRateDegPerYr = (((lp.proj - lm.proj + 540) % 360) - 180) / (2 * hR);
497
+ smp.generalPrecessionLonRateDegPerYr = (((lp.general - lm.general + 540) % 360) - 180) / (2 * hR);
451
498
  grid.set(key, smp);
452
499
  };
453
500
  storeWithRate(0, s, 0);
@@ -476,6 +523,19 @@ function createDeepOrbitalHistory({
476
523
  const f = (tYr - k0) / stepYr;
477
524
  const lerp = (/** @type {number} */ x, /** @type {number} */ y) => x + (y - x) * f;
478
525
  const dAng = (/** @type {number} */ x, /** @type {number} */ y) => x + (((y - x + 540) % 360) - 180) * f;
526
+ // C1 (cubic Hermite) on a wrapped angle with node rates (deg/yr) —
527
+ // linear interpolation here made the realized equinox RATE
528
+ // piecewise-constant per grid cell (the measured per-year
529
+ // precession staircase). Node rates come from the build's own
530
+ // ±2.5-yr central differences.
531
+ const hermiteAngle = (/** @type {number} */ l0, /** @type {number} */ l1, /** @type {number} */ r0, /** @type {number} */ r1) => {
532
+ const dl = ((l1 - l0 + 540) % 360) - 180; // unwrapped segment
533
+ const m0 = r0 * stepYr, m1 = r1 * stepYr;
534
+ const f2 = f * f, f3 = f2 * f;
535
+ const v = (2 * f3 - 3 * f2 + 1) * l0 + (f3 - 2 * f2 + f) * m0
536
+ + (-2 * f3 + 3 * f2) * (l0 + dl) + (f3 - f2) * m1;
537
+ return ((v % 360) + 360) % 360;
538
+ };
479
539
  return {
480
540
  epsDeg: lerp(a.epsDeg, b.epsDeg),
481
541
  e: lerp(a.e, b.e),
@@ -485,21 +545,12 @@ function createDeepOrbitalHistory({
485
545
  inclEclDeg: lerp(a.inclEclDeg, b.inclEclDeg),
486
546
  orbitNormalX: lerp(a.orbitNormalX, b.orbitNormalX),
487
547
  orbitNormalY: lerp(a.orbitNormalY, b.orbitNormalY),
488
- // C1 (cubic Hermite) — linear interpolation here made the
489
- // realized equinox RATE piecewise-constant per grid cell (the
490
- // measured per-year precession staircase). Node rates come from
491
- // the build's own ±2.5-yr central differences.
492
- equinoxLonJ2000Deg: (() => {
493
- const l0 = a.equinoxLonJ2000Deg;
494
- const dl = ((b.equinoxLonJ2000Deg - l0 + 540) % 360) - 180; // unwrapped segment
495
- const m0 = a.equinoxLonRateDegPerYr * stepYr;
496
- const m1 = (b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr) * stepYr;
497
- const f2 = f * f, f3 = f2 * f;
498
- const v = (2 * f3 - 3 * f2 + 1) * l0 + (f3 - 2 * f2 + f) * m0
499
- + (-2 * f3 + 3 * f2) * (l0 + dl) + (f3 - f2) * m1;
500
- return ((v % 360) + 360) % 360;
501
- })(),
548
+ equinoxLonJ2000Deg: hermiteAngle(a.equinoxLonJ2000Deg, b.equinoxLonJ2000Deg, a.equinoxLonRateDegPerYr, b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr),
502
549
  equinoxLonRateDegPerYr: lerp(a.equinoxLonRateDegPerYr, b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr),
550
+ // the same C1 form for the general-precession longitude (its
551
+ // year-over-year difference is the tropical-year law's p).
552
+ generalPrecessionLonDeg: hermiteAngle(a.generalPrecessionLonDeg, b.generalPrecessionLonDeg, a.generalPrecessionLonRateDegPerYr, b.generalPrecessionLonRateDegPerYr ?? a.generalPrecessionLonRateDegPerYr),
553
+ generalPrecessionLonRateDegPerYr: lerp(a.generalPrecessionLonRateDegPerYr, b.generalPrecessionLonRateDegPerYr ?? a.generalPrecessionLonRateDegPerYr),
503
554
  };
504
555
  },
505
556
  };