@essrt/physics 1.9.0 → 1.10.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@essrt/physics",
3
- "version": "1.9.0",
3
+ "version": "1.10.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",
@@ -35,6 +35,17 @@
35
35
 
36
36
  'use strict';
37
37
 
38
+ /**
39
+ * @typedef {Object} FrameworkSunDeps
40
+ * @property {number} sunMeanLongitudeJ2000Deg - L0 anchor (input anchor, 280.46646)
41
+ * @property {number} tropicalRateDegPerCy - the framework tropical rate (from the year chain)
42
+ * @property {(year: number) => number} eccentricityAt - the framework e(t) law
43
+ * @property {(year: number) => number} perihelionLongitudeDegAt - the framework ϖ(t) law (heliocentric)
44
+ * @property {(year: number) => number} [meanLongitudeDegAt] - optional epoch-dependent
45
+ * mean longitude L(t) (e.g. L0 + ∫rate·dt from the f(Y) tropical-year chain);
46
+ * absent = the linear form L0 + rate·T
47
+ */
48
+
38
49
  /**
39
50
  * @typedef {Object} EclipseFinderDeps
40
51
  * @property {(jd: number) => number} moonLonDegAt - truncated-series ecliptic longitude
@@ -43,6 +54,13 @@
43
54
  * @property {(jd: number) => number} deltaTSecondsAt - the engine's ΔT convention
44
55
  * @property {() => number} getSynodicMonthDays - live (epoch-mutable)
45
56
  * @property {() => number} getSunDistanceKm - live (epoch-mutable)
57
+ * @property {FrameworkSunDeps} [frameworkSun] - E4 (the native-Sun landing,
58
+ * plan §12i item 11): when present, sunLonDegAt uses the FRAMEWORK form —
59
+ * linear tropical rate + Kepler EoC (to e³) on the framework e(t)/ϖ(t)
60
+ * laws, TT clock unchanged — instead of the Meeus Ch. 25 polynomials.
61
+ * Absent = the historical Meeus form (every certified number's current
62
+ * basis). The swap is a conscious matched-pair event: the D2 completion's
63
+ * fitted 2lE residue and the certified statistics re-measure with it.
46
64
  * @property {{ rEarthMetres: number, moonDiameterKm: number,
47
65
  * sunDiameterKm: number, j2000JD: number, julianCenturyDays: number }} constants
48
66
  */
@@ -52,10 +70,27 @@ function createEclipseFinders(deps) {
52
70
  const K = deps.constants;
53
71
 
54
72
  /** Sun's geocentric ecliptic longitude in degrees (0–360) at given JD_UT.
55
- * MEAN GEOMETRIC — see the module header. @param {number} jd @returns {number} */
73
+ * MEAN GEOMETRIC — see the module header. Two forms (E4): the framework
74
+ * form when deps.frameworkSun is injected, else Meeus Ch. 25.
75
+ * @param {number} jd @returns {number} */
56
76
  function sunLonDegAt(jd) {
57
77
  const _d2r = Math.PI / 180;
58
78
  const T = (jd + deps.deltaTSecondsAt(jd) / 86400 - K.j2000JD) / K.julianCenturyDays;
79
+ if (deps.frameworkSun) {
80
+ // E4 framework form: linear tropical rate + Kepler EoC (to e³) on the
81
+ // framework e(t)/ϖ(t) laws. Same TT clock; same L0 input anchor.
82
+ const F = deps.frameworkSun;
83
+ const year = 2000 + T * 100;
84
+ const L = F.meanLongitudeDegAt
85
+ ? F.meanLongitudeDegAt(year)
86
+ : F.sunMeanLongitudeJ2000Deg + F.tropicalRateDegPerCy * T;
87
+ const e = F.eccentricityAt(year);
88
+ const M = (L - (F.perihelionLongitudeDegAt(year) + 180)) * _d2r;
89
+ const C = (2 * e - e * e * e / 4) * Math.sin(M)
90
+ + 1.25 * e * e * Math.sin(2 * M)
91
+ + (13 / 12) * e * e * e * Math.sin(3 * M);
92
+ return ((L + C / _d2r) % 360 + 360) % 360;
93
+ }
59
94
  const L0 = 280.46646 + 36000.76983 * T + 0.0003032 * T * T;
60
95
  const M = (357.52911 + 35999.05029 * T - 0.0001537 * T * T) * _d2r;
61
96
  const C = (1.914602 - 0.004817 * T - 0.000014 * T * T) * Math.sin(M)
package/src/model.js CHANGED
@@ -829,6 +829,63 @@ export function assembleModel(C, F) {
829
829
  },
830
830
  });
831
831
 
832
+ // ── E4: the framework-native Sun (§12i item 11 — the 3b landing) ─────────
833
+ // Assembled from the model's own laws, ZERO fitted sun constants:
834
+ // e(t) = the H/16 eccentricity-channel law + the derived H/3
835
+ // inclination-coupling imprint (amplitude base/2, lattice phase,
836
+ // J2000-anchored difference form) — the osculating decomposition:
837
+ // osculating e = H/16 channel + inclination coupling.
838
+ // L(t) = L0 + the mean tropical rate + the f(Y) drift SHAPE only,
839
+ // ∫(rate_SI(y) − rate_SI(2000)) dy: the rate ANCHOR stays the mean
840
+ // year (eclipse-endorsed); the drift is the Step 6d year-harmonic
841
+ // claim in SI/TT (year-in-days × LOD — the LOD-day part of the
842
+ // raw drift is UT-vs-TT and stays ΔT's job). Trapezoid table,
843
+ // 10-yr steps over −3000..3000; outside, the drift freezes at the
844
+ // edge (rate reverts to linear — the finder domain is the corpus
845
+ // era).
846
+ // ϖ(t) = the shipped H/16 perihelion law (earthPerihelionDeg).
847
+ // Measured (tools/explore/e3b-native-sun.mjs): beats the Meeus Ch. 25
848
+ // basis on JPL all-phase (0.95″ vs 1.28″ scatter) and on ancient-corpus
849
+ // timing structure (0.37 vs 0.50 min detrended vs Meeus-T²); required-ΔT
850
+ // shift 2–4 min ≈ 0.23σ of Stephenson scatter (lunar bias improves); the
851
+ // D2 completion table is unchanged (residual 2lE re-fit ≈ 0.1″ — noise).
852
+ const sunL0Deg = C.earthOrbital.sunMeanLongitudeJ2000_deg;
853
+ const sunTropicalRateDegPerCy = 360 * julianCenturyDays / meanSolarYearDays;
854
+ /** @param {number} year @returns {number} */
855
+ const sunEccentricityAt = (year) => {
856
+ const cos3 = -Math.cos(phaseRadians(balancedYear, year, 3));
857
+ const cos3J2000 = -Math.cos(phaseRadians(balancedYear, 2000, 3));
858
+ return eccentricityAt(year) + (eccentricityBase / 2) * (cos3 - cos3J2000);
859
+ };
860
+ const sunMeanLongitudeDegAt = (() => {
861
+ const Y_LO = -3000, Y_HI = 3000, STEP = 10;
862
+ /** @type {Float64Array|null} */
863
+ let cum = null;
864
+ /** @param {number} year @returns {number} */
865
+ const rateSIYr = (year) => 360 * (365.25 * 86400)
866
+ / (tropicalYearDirectDays(year)
867
+ * (deepLod.lodSecondsAtAge(yearToTMa(year)) ?? meanLengthOfDay));
868
+ return /** @param {number} year @returns {number} */ (year) => {
869
+ if (cum === null) {
870
+ const n = (Y_HI - Y_LO) / STEP + 1;
871
+ cum = new Float64Array(n);
872
+ const ref = rateSIYr(2000);
873
+ let prev = rateSIYr(Y_LO) - ref;
874
+ for (let i = 1; i < n; i++) {
875
+ const next = rateSIYr(Y_LO + i * STEP) - ref;
876
+ cum[i] = cum[i - 1] + 0.5 * (prev + next) * STEP;
877
+ prev = next;
878
+ }
879
+ const at2000 = cum[(2000 - Y_LO) / STEP];
880
+ for (let i = 0; i < n; i++) cum[i] -= at2000;
881
+ }
882
+ const x = (Math.min(Math.max(year, Y_LO), Y_HI) - Y_LO) / STEP;
883
+ const i = Math.min(Math.floor(x), cum.length - 2);
884
+ const drift = cum[i] + (x - i) * (cum[i + 1] - cum[i]);
885
+ return sunL0Deg + sunTropicalRateDegPerCy * (year - 2000) / 100 + drift;
886
+ };
887
+ })();
888
+
832
889
  // Eclipse finders — wired like the engine probe (tools/verify/
833
890
  // eclipse-audit.js). The finder axis is JD(UT): the series wrapper applies
834
891
  // UT→TT internally. Ground-track/umbra paths deliberately absent: the
@@ -841,6 +898,13 @@ export function assembleModel(C, F) {
841
898
  deltaTSecondsAt: frameworkDeltaTSecondsAtJD,
842
899
  getSynodicMonthDays: () => moonSynodicMonthDays,
843
900
  getSunDistanceKm: () => currentAUDistance,
901
+ frameworkSun: {
902
+ sunMeanLongitudeJ2000Deg: sunL0Deg,
903
+ tropicalRateDegPerCy: sunTropicalRateDegPerCy,
904
+ eccentricityAt: sunEccentricityAt,
905
+ perihelionLongitudeDegAt: earthPerihelionDeg,
906
+ meanLongitudeDegAt: sunMeanLongitudeDegAt,
907
+ },
844
908
  constants: {
845
909
  rEarthMetres: (C.bodyDiametersKm.earth / 2) * 1000,
846
910
  moonDiameterKm: C.bodyDiametersKm.moon,
@@ -956,6 +1020,18 @@ export function assembleModel(C, F) {
956
1020
  findLunarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findLunarEclipsesInRange(jdStart, jdEnd),
957
1021
  findSolarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findSolarEclipsesInRange(jdStart, jdEnd),
958
1022
  deltaTSecondsAtJD: frameworkDeltaTSecondsAtJD,
1023
+ // E4 — the framework-native Sun deps, exported so the OTHER finder
1024
+ // construction sites (tools/verify/eclipse-audit.js, the browser
1025
+ // _eclipse twins) spread the SAME assembly into their own
1026
+ // createEclipseFinders call instead of triplicating it (the
1027
+ // three-runtimes rule — cf. recession-history):
1028
+ frameworkSunDeps: Object.freeze({
1029
+ sunMeanLongitudeJ2000Deg: sunL0Deg,
1030
+ tropicalRateDegPerCy: sunTropicalRateDegPerCy,
1031
+ eccentricityAt: sunEccentricityAt,
1032
+ perihelionLongitudeDegAt: earthPerihelionDeg,
1033
+ meanLongitudeDegAt: sunMeanLongitudeDegAt,
1034
+ }),
959
1035
  // 20.3g location tier (see eclipse/besselian.cjs):
960
1036
  umbraGroundAtJD: /** @param {number} jd @returns {{latDeg: number, lonDeg: number} | null} */ (jd) => besselian.umbraGroundAt(jd),
961
1037
  solarLocalCircumstances: /** @param {number} jdGreatest @param {number} latDeg @param {number} lonDeg */ (jdGreatest, latDeg, lonDeg) => besselian.localCircumstances(jdGreatest, latDeg, lonDeg),