@essrt/physics 4.3.0 → 4.4.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@essrt/physics",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
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",
@@ -101,6 +101,31 @@ function createCardinalStructure({ sampleAt, tropicalYearSecondsAtYearFn }) {
101
101
  + eocOffsetSeconds(year + 1, type) - eocOffsetSeconds(year, type);
102
102
  }
103
103
 
104
+ /**
105
+ * The ANOMALISTIC year — time between successive perihelion passages,
106
+ * SI seconds. At perihelion the equation of center is ZERO by definition
107
+ * (M = 0), so perihelion passages are pure mean-anomaly events and the
108
+ * closed form needs no EoC term at all:
109
+ *
110
+ * T_anom(year) = T_mean(year+½) · 360 / (360 − Δϖ_yr)
111
+ *
112
+ * with Δϖ_yr = ϖ(year+1) − ϖ(year) (wrapped; the year-over-year apsidal
113
+ * advance RELATIVE TO THE EQUINOX — periOfDateDeg is equinox-referenced,
114
+ * so this is the climatic-precession rate, ~0.017°/yr ≈ +25 min at
115
+ * J2000). Exact difference form; driven by the ENGINE's ϖ(t) — this is
116
+ * the model's physics, replacing the retired K-family anomalistic
117
+ * harmonic fit (the family that measured worst against the one-source
118
+ * movement: 0.41 s broadband).
119
+ * @param {number} year the interval starts at this year's perihelion
120
+ * @returns {number} seconds
121
+ */
122
+ function anomalisticYearSeconds(year) {
123
+ const p0 = sampleAt(year).periOfDateDeg;
124
+ const p1 = sampleAt(year + 1).periOfDateDeg;
125
+ const dPeriDeg = ((p1 - p0 + 540) % 360) - 180; // wrapped, prograde +
126
+ return tropicalYearSecondsAtYearFn(year + 0.5) * 360 / (360 - dPeriDeg);
127
+ }
128
+
104
129
  /**
105
130
  * The four year lengths minus the mean — the e(t)-proportional spread.
106
131
  * @param {number} year
@@ -116,7 +141,7 @@ function createCardinalStructure({ sampleAt, tropicalYearSecondsAtYearFn }) {
116
141
  return out;
117
142
  }
118
143
 
119
- return { eocOffsetSeconds, yearLengthSeconds, spreadSeconds };
144
+ return { eocOffsetSeconds, yearLengthSeconds, spreadSeconds, anomalisticYearSeconds };
120
145
  }
121
146
 
122
147
  module.exports = { createCardinalStructure, equationOfCenterDeg, CARDINAL_LONGITUDE_DEG };
@@ -83,6 +83,46 @@ function createDeepOrbitalHistory({
83
83
  const s0 = sum(0), R = [anchor[0] - s0[0], anchor[1] - s0[1]];
84
84
  return (/** @type {number} */ t) => { const [x, y] = sum(t); return [x + R[0], y + R[1]]; };
85
85
  };
86
+ // CO-ROTATING anchor (the ϖ̇ fix, measured): the DC residual above is a
87
+ // spurious zero-frequency mode — it preserves the J2000 VALUE but dilutes
88
+ // the local argument rate by |sum(0)|/|anchor| (z: 11.47 → 7.93″/yr, a
89
+ // 92 s-class anomalistic-year bias in the mode tier). Here the residual
90
+ // ROTATES at the raw sum's own local arg-rate g* = Im(ż·z̄)/|z|²
91
+ // (analytic), so at t=0 both the value AND the rate are exact, and at
92
+ // depth the residual is one |R|-amplitude term at a physical frequency
93
+ // instead of a DC offset (deep envelope measured unchanged:
94
+ // [0.0001, 0.0672] → [0.0003, 0.0678] over 10–50 Myr).
95
+ // z ONLY: ζ keeps the DC anchor DELIBERATELY — its anchor is ~0 by
96
+ // definition (Earth's inclination to the J2000 ecliptic at J2000), i.e.
97
+ // the coordinate origin; a rotating residual there would inject a fake
98
+ // wobble into n̂(t).
99
+ const mkAnchoredCoRot = (
100
+ /** @type {ReadonlyArray<{omegaRadPerYr: number, re: number, im: number}>} */ modes,
101
+ /** @type {[number, number]} */ anchor,
102
+ ) => {
103
+ const sum = (/** @type {number} */ t) => {
104
+ let re = 0, im = 0;
105
+ for (const m of modes) {
106
+ const c = Math.cos(m.omegaRadPerYr * t), s = Math.sin(m.omegaRadPerYr * t);
107
+ re += m.re * c - m.im * s;
108
+ im += m.re * s + m.im * c;
109
+ }
110
+ return [re, im];
111
+ };
112
+ let q0 = 0, p0 = 0, dq0 = 0, dp0 = 0;
113
+ for (const m of modes) {
114
+ q0 += m.re; p0 += m.im;
115
+ dq0 += -m.omegaRadPerYr * m.im;
116
+ dp0 += m.omegaRadPerYr * m.re;
117
+ }
118
+ const gStar = (dp0 * q0 - dq0 * p0) / (q0 * q0 + p0 * p0);
119
+ const R = [anchor[0] - q0, anchor[1] - p0];
120
+ return (/** @type {number} */ t) => {
121
+ const [x, y] = sum(t);
122
+ const c = Math.cos(gStar * t), s = Math.sin(gStar * t);
123
+ return [x + R[0] * c - R[1] * s, y + R[0] * s + R[1] * c];
124
+ };
125
+ };
86
126
  const s2h = Math.sin(anchorInclEclipticDeg / 2 * D2R);
87
127
  const zetaAnchor = /** @type {[number, number]} */ (
88
128
  [s2h * Math.cos(anchorAscNodeEclipticDeg * D2R), s2h * Math.sin(anchorAscNodeEclipticDeg * D2R)]);
@@ -108,7 +148,7 @@ function createDeepOrbitalHistory({
108
148
  }
109
149
  const zAnchor = /** @type {[number, number]} */ (
110
150
  [anchorE * Math.cos(anchorPeriEclipticDeg * D2R), anchorE * Math.sin(anchorPeriEclipticDeg * D2R)]);
111
- const zModeSum = mkAnchored(zModes, zAnchor);
151
+ const zModeSum = mkAnchoredCoRot(zModes, zAnchor); // co-rotating residual — the ϖ̇ fix (see mkAnchoredCoRot)
112
152
  // C-4a: the z-side one-source path — same construction as zetaSeries
113
153
  // (anchored engine series inside the span, anchored mode sum as the tail).
114
154
  let zAt = zModeSum;
package/src/model.js CHANGED
@@ -1175,6 +1175,7 @@ export function assembleModel(C, F, laws = {}) {
1175
1175
  yearLengthSeconds: /** @param {number} year @param {'VE'|'SS'|'AE'|'WS'} type @returns {number} */ (year, type) => cardinalStructureM.yearLengthSeconds(year, type),
1176
1176
  eocOffsetSeconds: /** @param {number} year @param {'VE'|'SS'|'AE'|'WS'} type @returns {number} */ (year, type) => cardinalStructureM.eocOffsetSeconds(year, type),
1177
1177
  spreadSeconds: /** @param {number} year */ (year) => cardinalStructureM.spreadSeconds(year),
1178
+ anomalisticYearSeconds: /** @param {number} year @returns {number} */ (year) => cardinalStructureM.anomalisticYearSeconds(year),
1178
1179
  }),
1179
1180
  moon: Object.freeze({
1180
1181
  distanceKmAtYear: /** @param {number} year @returns {number} */ (year) => moonDistanceMetresAtAge(yearToTMa(year)) / 1000,