@essrt/physics 4.7.0 → 4.8.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": "4.7.0",
3
+ "version": "4.8.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": "v13.0",
@@ -73,6 +73,29 @@ function createChainCycleIntegrator({
73
73
  /** @type {Map<PeriodSecondsAtAge, Float64Array | null>} */
74
74
  const tables = new Map();
75
75
 
76
+ // Exact-argument memo for the year-length integrand of the out-of-range
77
+ // Simpson fallback. Within one animation frame every integrator node
78
+ // (the six-plus Moon chains + the seven planets) integrates the SAME
79
+ // (anchor, currentYear) pair, so the Simpson sample lattice — and with
80
+ // it every tropicalYearSecondsAtAge(tMa) — repeats across chains, up to
81
+ // 13× per sample. Exact-key reuse returns the bit-identical value: pure
82
+ // dedupe, not a resolution change (the sampler-purity trap class is
83
+ // about grids serving NEARBY arguments; this serves only the same one).
84
+ // Bounded by wholesale clear — one frame's lattice is ≤ ~1025 entries.
85
+ /** @type {Map<number, number | null>} */
86
+ const yearSecondsMemo = new Map();
87
+ const YEAR_SECONDS_MEMO_CAP = 4096;
88
+ /** @param {number} tMa @returns {number | null} */
89
+ function tropicalYearSecondsMemo(tMa) {
90
+ let v = yearSecondsMemo.get(tMa);
91
+ if (v === undefined) {
92
+ v = tropicalYearSecondsAtAge(tMa);
93
+ if (yearSecondsMemo.size >= YEAR_SECONDS_MEMO_CAP) yearSecondsMemo.clear();
94
+ yearSecondsMemo.set(tMa, v);
95
+ }
96
+ return v;
97
+ }
98
+
76
99
  /** Lazy cumulative table for one chain; null when the chain is undefined
77
100
  * anywhere in range (→ Simpson fallback).
78
101
  * @param {PeriodSecondsAtAge} periodFnSeconds
@@ -167,7 +190,7 @@ function createChainCycleIntegrator({
167
190
  const tMa = (ageAnchorYear - y) / 1e6;
168
191
  const tPeriodS = periodFnSeconds(tMa);
169
192
  if (tPeriodS === null) return null;
170
- const tYrS = tropicalYearSecondsAtAge(tMa);
193
+ const tYrS = tropicalYearSecondsMemo(tMa);
171
194
  if (tYrS === null) return null;
172
195
  const integrand = tYrS / tPeriodS; // cycles per SI year
173
196
  const w = (i === 0 || i === n) ? 1 : (i % 2 === 1 ? 4 : 2);
@@ -97,7 +97,7 @@ function createYearLengths({ sampleAt, massLossSiderealSecondsAtYearFn, apsidalS
97
97
  axialPrecessionYearsAtYear: (y) => { const s = sid(y), t = trop(y); return s / (s - t); },
98
98
  /** Perihelion (apsidal-vs-equinox) precession period of date, years. @param {number} y */
99
99
  perihelionPrecessionYearsAtYear: (y) => { const a = anom(y), t = trop(y); return a / (a - t); },
100
- /** Inclination precession period of date, years (hypersensitive: ±1 s ↔ ≈400 yr). @param {number} y */
100
+ /** Apsidal precession period of date vs the fixed stars, years (hypersensitive: ±1 s ↔ ≈400 yr; the identifier keeps the historical "inclination" name). @param {number} y */
101
101
  inclinationPrecessionYearsAtYear: (y) => { const a = anom(y), s = sid(y); return a / (a - s); },
102
102
  /** The per-cardinal layer (D6 rules: year lengths λ̇-corrected; offsets/spreads raw — μs-class/second-order there). */
103
103
  cardinal: Object.freeze({
package/src/model.js CHANGED
@@ -1239,6 +1239,20 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1239
1239
  }),
1240
1240
  eclipse: Object.freeze({
1241
1241
  sunLonDegAtJD: /** @param {number} jd @returns {number} */ (jd) => eclipseFinders.sunLonDegAt(jd),
1242
+ // The COMPLETED tier Sun (K8b follow-up, owner-approved injection):
1243
+ // the bare finder Sun minus the DERIVED planetary-completion table —
1244
+ // 70 framework-carrier terms plus the 6.44″ Earth-around-EMB wobble
1245
+ // (the "lunar equation"; eclipse/sun-planetary-completion.cjs, zero
1246
+ // fitted constants). The finders themselves deliberately stay bare
1247
+ // (their certified canon statistics were produced on the bare form;
1248
+ // besselian.cjs applies the same subtraction internally). This
1249
+ // surface exists for the SCENE tier's E5 wheel-Sun bridge, so the
1250
+ // rendered Sun carries the model's own derived apparent-class terms.
1251
+ // Clock: the completion argument rides TT centuries from the same
1252
+ // framework ΔT the besselian composition uses.
1253
+ sunLonCompletedDegAtJD: /** @param {number} jd @returns {number} */ (jd) =>
1254
+ eclipseFinders.sunLonDegAt(jd)
1255
+ - sunPlanetaryCompletionDeg((jdTTFromUT(jd) - j2000JD) / julianCenturyDays),
1242
1256
  findLunarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findLunarEclipsesInRange(jdStart, jdEnd),
1243
1257
  findSolarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findSolarEclipsesInRange(jdStart, jdEnd),
1244
1258
  deltaTSecondsAtJD: frameworkDeltaTSecondsAtJD,