@essrt/physics 10.0.0 → 11.0.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.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@essrt/physics",
3
- "version": "10.0.0",
3
+ "version": "11.0.1",
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,44 @@ 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) {
348
- const n = orbitNormal(t);
349
- const g = cross(s, n);
350
- return Math.atan2(g[1], g[0]) * R2D;
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
+ /** The three of-date longitudes whose node RATES the grid stores (the
377
+ * projected and general-precession equinox longitudes, the perihelion
378
+ * longitude of date) — read from a full sample at the probe state, so the
379
+ * rates are finite differences of exactly the stored quantities. */
380
+ function eqLonsOnlyDeg(/** @type {number[]} */ s, /** @type {number} */ t) {
381
+ const smp = sampleAt(s, t);
382
+ return { proj: smp.equinoxLonJ2000Deg, general: smp.generalPrecessionLonDeg, peri: smp.periOfDateDeg };
351
383
  }
352
384
 
353
385
  /** One quantity bundle at time t (years from J2000) from spin axis s. */
@@ -383,26 +415,39 @@ function createDeepOrbitalHistory({
383
415
  // interpolation on the grid is exact to the ζ series' own resolution.
384
416
  orbitNormalX: n[0],
385
417
  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
418
+ // The equinox node (ŝ×n̂) PROJECTED longitude in the J2000 ecliptic
419
+ // frame — the frame reconstruction's input (frame-of-date.cjs rebuilds
420
+ // the unit equinox from it). Its rate is the general precession only
421
+ // to first order in the ecliptic's tilt to its J2000 position: the
422
+ // tropical-year consumers read generalPrecessionLonDeg below (measured
423
+ // 2026-09 — the projected rate booked the Sun's mean longitude of date
424
+ // 5.9″ off at −3000 and the sidereal year of date 1 s off at depth).
425
+ // ⚠ RATE-CONSUMER CONTRACT (measured): these fields' rates are correct
392
426
  // AS IS — α is self-anchored to the LUNISOLAR rate at construction
393
427
  // (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
428
+ // precession p_geom(J2000) equals 360/axialPrecessionYearsJ2000 per
429
+ // JULIAN year (the sampler's time unit) by construction. No
430
+ // correction, no δ subtraction. History (why this contract exists):
431
+ // before the self-anchor, α carried the GENERAL sid/(sid−sol) rate and
432
+ // the n̂(t) geometry re-added the planetary mean — a double-count of
433
+ // +0.097″/yr ≈ +2.4 s of tropical year at J2000 — and consumers had to
434
+ // subtract the runtime anchor
400
435
  // δ = p_geom(J2000) − 360/axialPrecessionYearsJ2000.
401
436
  // A consumer may still measure δ at runtime as a SELF-CHECK; it
402
437
  // must read ≈0 (sub-1e-4 ″/yr class). The LONGITUDE itself is raw
403
438
  // geometry and has always needed no correction.
404
439
  equinoxLonJ2000Deg: ((Math.atan2(gu[1], gu[0]) * R2D) % 360 + 360) % 360,
405
440
  equinoxLonRateDegPerYr: 0, // filled at store time (build's ±2.5-yr central difference)
441
+ // The equinox's GENERAL-PRECESSION longitude (the broken angle through
442
+ // the node of the ecliptic of date — the literature's p_A; see
443
+ // generalPrecessionLonDeg above): its year-over-year retrograde advance
444
+ // IS the general precession of date, wobble included (the n̂(t)
445
+ // geometry generates the equinox wobble; ψ̇ itself is the secular
446
+ // α(H(t)) times the two factors of date). The tropical year of date is
447
+ // built on this rate (earth/year-lengths.cjs).
448
+ generalPrecessionLonDeg: ((generalPrecessionLonDeg(n, gu) % 360) + 360) % 360,
449
+ generalPrecessionLonRateDegPerYr: 0, // filled at store time (build's ±2.5-yr central difference)
450
+ periOfDateRateDegPerYr: 0, // filled at store time — the C1 input for periOfDateDeg (the apsidal rate of date)
406
451
  };
407
452
  }
408
453
 
@@ -442,12 +487,20 @@ function createDeepOrbitalHistory({
442
487
  // centuries with steps at the 1700/1800/1900/2000 cell edges
443
488
  // (measured; the levels were the real wobble at 100-yr resolution,
444
489
  // the staircase was this interpolation order).
490
+ // The perihelion longitude of date gets the same C1 treatment (2026-09,
491
+ // owner-found): linear interpolation of periOfDateDeg made the
492
+ // movement's year-over-year apsidal rate — the anomalistic year, the
493
+ // Prec. cell — a staircase of ±0.1 s across the 100-yr cells, reading
494
+ // 365.2596375 d at 2000.0 and 365.2596390 d at 2000.5, and the panel's
495
+ // apsidal beat 111,494 where the registry read 111,548.
445
496
  const storeWithRate = (/** @type {number} */ key, /** @type {number[]} */ sv, /** @type {number} */ tv) => {
446
497
  const smp = sampleAt(sv, tv);
447
498
  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);
499
+ const lp = eqLonsOnlyDeg(rk4(sv, tv, +hR), tv + hR);
500
+ const lm = eqLonsOnlyDeg(rk4(sv, tv, -hR), tv - hR);
501
+ smp.equinoxLonRateDegPerYr = (((lp.proj - lm.proj + 540) % 360) - 180) / (2 * hR);
502
+ smp.generalPrecessionLonRateDegPerYr = (((lp.general - lm.general + 540) % 360) - 180) / (2 * hR);
503
+ smp.periOfDateRateDegPerYr = (((lp.peri - lm.peri + 540) % 360) - 180) / (2 * hR);
451
504
  grid.set(key, smp);
452
505
  };
453
506
  storeWithRate(0, s, 0);
@@ -475,31 +528,35 @@ function createDeepOrbitalHistory({
475
528
  if (!a) throw new Error(`deep-orbital-history: ${tYr} outside the built range`);
476
529
  const f = (tYr - k0) / stepYr;
477
530
  const lerp = (/** @type {number} */ x, /** @type {number} */ y) => x + (y - x) * f;
478
- const dAng = (/** @type {number} */ x, /** @type {number} */ y) => x + (((y - x + 540) % 360) - 180) * f;
531
+ // C1 (cubic Hermite) on a wrapped angle with node rates (deg/yr) —
532
+ // linear interpolation here made the realized equinox RATE
533
+ // piecewise-constant per grid cell (the measured per-year
534
+ // precession staircase). Node rates come from the build's own
535
+ // ±2.5-yr central differences.
536
+ const hermiteAngle = (/** @type {number} */ l0, /** @type {number} */ l1, /** @type {number} */ r0, /** @type {number} */ r1) => {
537
+ const dl = ((l1 - l0 + 540) % 360) - 180; // unwrapped segment
538
+ const m0 = r0 * stepYr, m1 = r1 * stepYr;
539
+ const f2 = f * f, f3 = f2 * f;
540
+ const v = (2 * f3 - 3 * f2 + 1) * l0 + (f3 - 2 * f2 + f) * m0
541
+ + (-2 * f3 + 3 * f2) * (l0 + dl) + (f3 - f2) * m1;
542
+ return ((v % 360) + 360) % 360;
543
+ };
479
544
  return {
480
545
  epsDeg: lerp(a.epsDeg, b.epsDeg),
481
546
  e: lerp(a.e, b.e),
482
- periOfDateDeg: ((dAng(a.periOfDateDeg, b.periOfDateDeg) % 360) + 360) % 360,
547
+ periOfDateDeg: hermiteAngle(a.periOfDateDeg, b.periOfDateDeg, a.periOfDateRateDegPerYr, b.periOfDateRateDegPerYr ?? a.periOfDateRateDegPerYr),
548
+ periOfDateRateDegPerYr: lerp(a.periOfDateRateDegPerYr, b.periOfDateRateDegPerYr ?? a.periOfDateRateDegPerYr),
483
549
  eSinPeri: lerp(a.eSinPeri, b.eSinPeri),
484
550
  eCosPeri: lerp(a.eCosPeri, b.eCosPeri),
485
551
  inclEclDeg: lerp(a.inclEclDeg, b.inclEclDeg),
486
552
  orbitNormalX: lerp(a.orbitNormalX, b.orbitNormalX),
487
553
  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
- })(),
554
+ equinoxLonJ2000Deg: hermiteAngle(a.equinoxLonJ2000Deg, b.equinoxLonJ2000Deg, a.equinoxLonRateDegPerYr, b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr),
502
555
  equinoxLonRateDegPerYr: lerp(a.equinoxLonRateDegPerYr, b.equinoxLonRateDegPerYr ?? a.equinoxLonRateDegPerYr),
556
+ // the same C1 form for the general-precession longitude (its
557
+ // year-over-year difference is the tropical-year law's p).
558
+ generalPrecessionLonDeg: hermiteAngle(a.generalPrecessionLonDeg, b.generalPrecessionLonDeg, a.generalPrecessionLonRateDegPerYr, b.generalPrecessionLonRateDegPerYr ?? a.generalPrecessionLonRateDegPerYr),
559
+ generalPrecessionLonRateDegPerYr: lerp(a.generalPrecessionLonRateDegPerYr, b.generalPrecessionLonRateDegPerYr ?? a.generalPrecessionLonRateDegPerYr),
503
560
  };
504
561
  },
505
562
  };