@essrt/physics 1.11.0 → 1.12.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/model.js +53 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@essrt/physics",
3
- "version": "1.11.0",
3
+ "version": "1.12.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": "v11.0",
package/src/model.js CHANGED
@@ -867,40 +867,64 @@ export function assembleModel(C, F) {
867
867
  return eccentricityAt(year) + (eccentricityBase / 2) * (cos3 - cos3J2000);
868
868
  };
869
869
  const sunMeanLongitudeDegAt = (() => {
870
- const Y_LO = -3000, Y_HI = 3000, STEP = 10;
871
- /** @type {Float64Array|null} */
872
- let cum = null;
870
+ // SW PHASE B the CLOSED FORM on the integrated lattice phase
871
+ // (supersedes the E5 ±3,000-yr trapezoid table; valid at EVERY epoch,
872
+ // no table, no domain window). drift = D_smooth + Σₖ D_k + D_torque:
873
+ // · D_smooth — the deep chain's smooth SI tropical-year physics
874
+ // (tropicalYearDaysBase × LOD), two-point trapezoid: the same
875
+ // structure the scene twin uses, so the scene δ stays bounded.
876
+ // · Each oscillatory term integrates ANALYTICALLY: the antiderivative
877
+ // of cos/sin on the integrated phase is (H/2πk)·[sin/−cos] — the H
878
+ // drift lives in the phase itself; the amplitude factor uses H_J2000
879
+ // (ppm-class difference, negligible).
880
+ // · THE REBASE (E5's corpus-preferred J2000 rate anchor) is applied
881
+ // PER HARMONIC as a SIN-SATURATED ramp, (H/2πk)·sin(Δφₖ): equal to
882
+ // the linear rebase for |Δφₖ| ≪ 1 (the corpus era) and bounded by
883
+ // the harmonic's own period beyond — extending a linearized local
884
+ // slope to deep time would be the fitted-linear-slope trap. Every
885
+ // quantity is derived; zero new constants; each component and its
886
+ // slope vanish at year 2000 by construction (the drift-only
887
+ // property is exact, not tabulated).
888
+ const RATE_LIN = sunTropicalRateDegPerCy / 100; // deg / SI yr
873
889
  const precessionP0DegPerYr = 13 * 360 / H;
874
890
  const tanEps = Math.tan(earthtiltMean * Math.PI / 180);
875
- /** E5 the two-component obliquity excursion, radians, integrated phase.
876
- * @param {number} year @returns {number} */
877
- const obliquityExcursionRad = (year) => (
878
- -earthInclAmplitude * Math.cos(phaseRadians(balancedYear, year, 3))
879
- + earthInclAmplitude * Math.cos(phaseRadians(balancedYear, year, 8))
880
- ) * Math.PI / 180;
891
+ const A_RAD = earthInclAmplitude * Math.PI / 180;
881
892
  /** @param {number} year @returns {number} */
882
- const rateSIYr = (year) => 360 * (365.25 * 86400)
883
- / (tropicalYearDirectDays(year)
884
- * (deepLod.lodSecondsAtAge(yearToTMa(year)) ?? meanLengthOfDay))
885
- - precessionP0DegPerYr * tanEps * obliquityExcursionRad(year);
893
+ const rateSmooth = (year) => 360 * (365.25 * 86400)
894
+ / (tropicalYearDaysBase(year)
895
+ * (deepLod.lodSecondsAtAge(yearToTMa(year)) ?? meanLengthOfDay));
896
+ /** @type {{rateSm0: number, ph0: Map<number, number>}|null} */
897
+ let anchor = null;
886
898
  return /** @param {number} year @returns {number} */ (year) => {
887
- if (cum === null) {
888
- const n = (Y_HI - Y_LO) / STEP + 1;
889
- cum = new Float64Array(n);
890
- const ref = rateSIYr(2000);
891
- let prev = rateSIYr(Y_LO) - ref;
892
- for (let i = 1; i < n; i++) {
893
- const next = rateSIYr(Y_LO + i * STEP) - ref;
894
- cum[i] = cum[i - 1] + 0.5 * (prev + next) * STEP;
895
- prev = next;
896
- }
897
- const at2000 = cum[(2000 - Y_LO) / STEP];
898
- for (let i = 0; i < n; i++) cum[i] -= at2000;
899
+ if (anchor === null) {
900
+ const ph0 = new Map();
901
+ for (const [k] of F.TROPICAL_YEAR_HARMONICS) ph0.set(k, phaseRadians(balancedYear, 2000, k));
902
+ for (const k of [3, 8]) if (!ph0.has(k)) ph0.set(k, phaseRadians(balancedYear, 2000, k));
903
+ anchor = { rateSm0: rateSmooth(2000), ph0 };
899
904
  }
900
- const x = (Math.min(Math.max(year, Y_LO), Y_HI) - Y_LO) / STEP;
901
- const i = Math.min(Math.floor(x), cum.length - 2);
902
- const drift = cum[i] + (x - i) * (cum[i + 1] - cum[i]);
903
- return sunL0Deg + sunTropicalRateDegPerCy * (year - 2000) / 100 + drift;
905
+ const dy = year - 2000;
906
+ // smooth deep physics (two-point trapezoid; exact 0 at 2000)
907
+ let drift = 0.5 * (rateSmooth(year) - anchor.rateSm0) * dy;
908
+ // year-harmonic ripple: rate (RATE_LIN/T̄)·h(y); antiderivative +
909
+ // sin-saturated per-harmonic rebase
910
+ const HK = H / (2 * Math.PI);
911
+ for (const [k, sK, cK] of F.TROPICAL_YEAR_HARMONICS) {
912
+ const ph = phaseRadians(balancedYear, year, k);
913
+ const ph0 = /** @type {number} */ (anchor.ph0.get(k));
914
+ const scale = -(RATE_LIN / meanSolarYearDays) * (HK / k);
915
+ const anti = (-sK) * (Math.cos(ph) - Math.cos(ph0)) + cK * (Math.sin(ph) - Math.sin(ph0));
916
+ const h0 = sK * Math.sin(ph0) + cK * Math.cos(ph0);
917
+ drift += scale * (anti - h0 * Math.sin(ph - ph0));
918
+ }
919
+ // torque (E5): rate = −p₀·tanε·δε, δε = A(−cos φ₃ + cos φ₈); same
920
+ // antiderivative + per-component sin-saturated rebase
921
+ for (const [k, sgn] of [[3, -1], [8, 1]]) {
922
+ const ph = phaseRadians(balancedYear, year, k);
923
+ const ph0 = /** @type {number} */ (anchor.ph0.get(k));
924
+ const scale = -precessionP0DegPerYr * tanEps * A_RAD * sgn * (HK / k);
925
+ drift += scale * ((Math.sin(ph) - Math.sin(ph0)) - Math.cos(ph0) * Math.sin(ph - ph0));
926
+ }
927
+ return sunL0Deg + sunTropicalRateDegPerCy * dy / 100 + drift;
904
928
  };
905
929
  })();
906
930