@essrt/physics 24.0.0 → 24.1.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 +2 -2
- package/src/chain-cycles/index.cjs +80 -10
- package/src/constants/generated.js +1 -1
- package/src/moon/arguments.cjs +68 -6
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essrt/physics",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.1.0",
|
|
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": "v26.
|
|
6
|
+
"modelVersion": "v26.7",
|
|
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",
|
|
@@ -26,11 +26,18 @@
|
|
|
26
26
|
* tableAnchorYear vs driftRefYear pair).
|
|
27
27
|
*
|
|
28
28
|
* NUMERICS ARE LOAD-BEARING: fixed 10-yr grid over ±250 kyr, per-cell
|
|
29
|
-
* 3-point Simpson at build, LINEAR interpolation on read;
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* 3-point Simpson at build, LINEAR interpolation on read; beyond the dense
|
|
30
|
+
* grid a COARSE cumulative table (1-kyr cells, the same per-cell Simpson,
|
|
31
|
+
* anchored at the dense edge) grown lazily in 250-kyr chunks in the direction
|
|
32
|
+
* asked for — the owner's playback at 1,000 yr/s past year 250,000 had fallen
|
|
33
|
+
* to 190 ms of JS per frame: the former fallback, an adaptive Simpson of up to
|
|
34
|
+
* 1,024 samples per call cached by exact year pair, re-integrated every one
|
|
35
|
+
* of the four Moon chains from the grid edge on every frame, each sample a
|
|
36
|
+
* deep-time LOD/α evaluation. The adaptive Simpson remains only for a chain
|
|
37
|
+
* undefined somewhere in range (null) or beyond the coarse limit (±600 Myr,
|
|
38
|
+
* past the model's domain). An "analytically better" integrator here would
|
|
39
|
+
* detach the runtime from the certified numbers: the in-range path is
|
|
40
|
+
* byte-identical to before.
|
|
34
41
|
*/
|
|
35
42
|
|
|
36
43
|
'use strict';
|
|
@@ -125,6 +132,62 @@ function createChainCycleIntegrator({
|
|
|
125
132
|
return tab[i] + (idxF - i) * (tab[i + 1] - tab[i]);
|
|
126
133
|
}
|
|
127
134
|
|
|
135
|
+
// ── The coarse extension beyond the dense grid ─────────────────────────────
|
|
136
|
+
// Per chain and per direction, a cumulative table in COARSE_STEP cells that
|
|
137
|
+
// starts at the dense edge with the edge's own value and grows by COARSE_CHUNK
|
|
138
|
+
// years whenever a year beyond its reach is asked for. The cells are built
|
|
139
|
+
// with the same 3-point Simpson as the dense grid; the read is the same
|
|
140
|
+
// linear interpolation. The period laws vary on Myr scales at deep time, so
|
|
141
|
+
// 1-kyr cells hold the count to far below a cycle — the old fallback sampled
|
|
142
|
+
// ~1 per kyr too, but re-did the whole span on every call.
|
|
143
|
+
const COARSE_STEP = 1000, COARSE_CHUNK = 250000, COARSE_LIMIT_YEARS = 600e6;
|
|
144
|
+
/** @typedef {{ cum: number[], ok: boolean }} CoarseSide */
|
|
145
|
+
/** @type {WeakMap<PeriodSecondsAtAge, { fwd: CoarseSide, bwd: CoarseSide }>} */
|
|
146
|
+
const coarse = new WeakMap();
|
|
147
|
+
/** @param {PeriodSecondsAtAge} periodFnSeconds @param {number} y @returns {number | null} cycles per tick */
|
|
148
|
+
const integrandAt = (periodFnSeconds, y) => {
|
|
149
|
+
const tPeriodS = periodFnSeconds((ageAnchorYear - y) / 1e6);
|
|
150
|
+
return tPeriodS === null ? null : tropicalYearJ2000Seconds / tPeriodS;
|
|
151
|
+
};
|
|
152
|
+
/** Grow one side until it covers `distance` years from the dense edge (or fails on a null chain).
|
|
153
|
+
* @param {PeriodSecondsAtAge} periodFnSeconds @param {CoarseSide} side @param {number} edgeYear
|
|
154
|
+
* @param {1|-1} dir @param {number} distance */
|
|
155
|
+
function growCoarse(periodFnSeconds, side, edgeYear, dir, distance) {
|
|
156
|
+
const need = Math.ceil(distance / COARSE_STEP) + 1;
|
|
157
|
+
let target = side.cum.length;
|
|
158
|
+
while (target < need) target += COARSE_CHUNK / COARSE_STEP;
|
|
159
|
+
for (let i = side.cum.length; i < target && side.ok; i++) {
|
|
160
|
+
const y0 = edgeYear + dir * (i - 1) * COARSE_STEP, y1 = y0 + dir * COARSE_STEP;
|
|
161
|
+
const f0 = integrandAt(periodFnSeconds, y0), fm = integrandAt(periodFnSeconds, (y0 + y1) / 2), f1 = integrandAt(periodFnSeconds, y1);
|
|
162
|
+
if (f0 === null || fm === null || f1 === null) { side.ok = false; break; }
|
|
163
|
+
side.cum.push(side.cum[i - 1] + dir * (f0 + 4 * fm + f1) * (COARSE_STEP / 6));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** C(y) beyond the dense grid, or null when the chain is undefined on the way there.
|
|
167
|
+
* @param {PeriodSecondsAtAge} periodFnSeconds @param {Float64Array} tab @param {number} y @returns {number | null} */
|
|
168
|
+
function coarseAt(periodFnSeconds, tab, y) {
|
|
169
|
+
let c = coarse.get(periodFnSeconds);
|
|
170
|
+
if (!c) {
|
|
171
|
+
c = { fwd: { cum: [tab[tab.length - 1]], ok: true }, bwd: { cum: [tab[0]], ok: true } };
|
|
172
|
+
coarse.set(periodFnSeconds, c);
|
|
173
|
+
}
|
|
174
|
+
const dir = y >= gridMaxYear ? 1 : -1;
|
|
175
|
+
const side = dir === 1 ? c.fwd : c.bwd;
|
|
176
|
+
const edgeYear = dir === 1 ? gridMaxYear : gridMinYear;
|
|
177
|
+
const distance = Math.abs(y - edgeYear);
|
|
178
|
+
if (distance > COARSE_LIMIT_YEARS) return null;
|
|
179
|
+
if (side.cum.length < Math.ceil(distance / COARSE_STEP) + 1) growCoarse(periodFnSeconds, side, edgeYear, dir, distance);
|
|
180
|
+
if (!side.ok || side.cum.length < Math.ceil(distance / COARSE_STEP) + 1) return null;
|
|
181
|
+
const idxF = distance / COARSE_STEP;
|
|
182
|
+
const i = Math.floor(idxF);
|
|
183
|
+
return side.cum[i] + (idxF - i) * (side.cum[i + 1] - side.cum[i]);
|
|
184
|
+
}
|
|
185
|
+
/** C(y) anywhere the tables reach: dense inside the grid, coarse beyond.
|
|
186
|
+
* @param {PeriodSecondsAtAge} periodFnSeconds @param {Float64Array} tab @param {number} y @returns {number | null} */
|
|
187
|
+
function cumulativeAt(periodFnSeconds, tab, y) {
|
|
188
|
+
return (y > gridMinYear && y < gridMaxYear) ? tableAt(tab, y) : coarseAt(periodFnSeconds, tab, y);
|
|
189
|
+
}
|
|
190
|
+
|
|
128
191
|
/** Cycles of the chain between two calendar years. Snapshot mode: linear
|
|
129
192
|
* at the J2000 rate. Deep time: table lookup in ±range, adaptive Simpson
|
|
130
193
|
* beyond. null past the tidal-lock asymptote.
|
|
@@ -147,10 +210,16 @@ function createChainCycleIntegrator({
|
|
|
147
210
|
return dy * tropicalYearJ2000Seconds / tJ2000;
|
|
148
211
|
}
|
|
149
212
|
|
|
150
|
-
// Cumulative-table fast path (deterministic, call-order independent)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
213
|
+
// Cumulative-table fast path (deterministic, call-order independent): the
|
|
214
|
+
// dense grid inside ±250 kyr — byte-identical to before — and the lazily
|
|
215
|
+
// grown coarse extension beyond it.
|
|
216
|
+
const tab = table(periodFnSeconds);
|
|
217
|
+
if (tab !== null) {
|
|
218
|
+
if (yearA > gridMinYear && yearA < gridMaxYear && yearB > gridMinYear && yearB < gridMaxYear) {
|
|
219
|
+
return tableAt(tab, yearB) - tableAt(tab, yearA);
|
|
220
|
+
}
|
|
221
|
+
const cA = cumulativeAt(periodFnSeconds, tab, yearA), cB = cumulativeAt(periodFnSeconds, tab, yearB);
|
|
222
|
+
if (cA !== null && cB !== null) return cB - cA;
|
|
154
223
|
}
|
|
155
224
|
|
|
156
225
|
let cache = caches.get(periodFnSeconds);
|
|
@@ -162,7 +231,8 @@ function createChainCycleIntegrator({
|
|
|
162
231
|
const hit = cache.get(cacheKey);
|
|
163
232
|
if (hit !== undefined) return hit;
|
|
164
233
|
|
|
165
|
-
// Adaptive Simpson
|
|
234
|
+
// Adaptive Simpson (a chain undefined in range, or beyond the coarse limit):
|
|
235
|
+
// ~1 sample per 1 kyr, capped at 1024 to bound per-call cost.
|
|
166
236
|
let n = Math.max(32, Math.ceil(Math.abs(dy) / 1000));
|
|
167
237
|
if (n > 1024) n = 1024;
|
|
168
238
|
if (n % 2 === 1) n++;
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
export const CONSTANTS_HASH = "da713fc2d846767d";
|
|
40
40
|
|
|
41
41
|
/** Model version label — single source: public/input/model-version.json (§10 two-axis scheme). */
|
|
42
|
-
export const MODEL_VERSION = "v26.
|
|
42
|
+
export const MODEL_VERSION = "v26.7";
|
|
43
43
|
|
|
44
44
|
/** Canonical preprint DOI — single source: public/input/model-version.json. */
|
|
45
45
|
export const PREPRINT_DOI = "10.21203/rs.3.rs-8758810/v4";
|
package/src/moon/arguments.cjs
CHANGED
|
@@ -145,8 +145,46 @@ function createMoonArguments({ constants, fns }) {
|
|
|
145
145
|
}
|
|
146
146
|
return kPl;
|
|
147
147
|
}
|
|
148
|
+
// ── Deep-time carrier tables ────────────────────────────────────────────────
|
|
149
|
+
// In the era (|T| ≤ CARRIER_DENSE_CY = 250 kyr) each carrier is the Simpson
|
|
150
|
+
// sum below, untouched — the certified numbers. Beyond, the integral
|
|
151
|
+
// continues from the dense edge on a cumulative table of CARRIER_CELL_CY
|
|
152
|
+
// cells (1 kyr; per-cell 3-point Simpson, a 3-point Simpson over the
|
|
153
|
+
// partial last cell), grown in CARRIER_CHUNK cells in the direction asked
|
|
154
|
+
// for. The former form re-integrated the whole span from J2000 on EVERY
|
|
155
|
+
// call with N ∝ |T| nodes — 77 evaluations of the e channel at 300 kyr,
|
|
156
|
+
// 2,500 at 10 Myr, per lunar argument set and frame — the deep-time
|
|
157
|
+
// playback cost the owner measured (the loop's JS at 190 ms a frame).
|
|
158
|
+
const CARRIER_DENSE_CY = 2500, CARRIER_CELL_CY = 10, CARRIER_CHUNK = 2500;
|
|
159
|
+
/** @param {(t: number) => number} f the SCALED integrand per century
|
|
160
|
+
* @param {(T: number) => number} dense the in-era Simpson (its value at ±CARRIER_DENSE_CY seeds the table)
|
|
161
|
+
* @returns {(T: number) => number} */
|
|
162
|
+
function makeDeepCarrier(f, dense) {
|
|
163
|
+
/** @type {{ fwd: number[] | null, bwd: number[] | null }} */
|
|
164
|
+
const sides = { fwd: null, bwd: null };
|
|
165
|
+
return (T) => {
|
|
166
|
+
if (Math.abs(T) <= CARRIER_DENSE_CY) return dense(T);
|
|
167
|
+
const dir = T > 0 ? 1 : -1;
|
|
168
|
+
let cum = dir === 1 ? sides.fwd : sides.bwd;
|
|
169
|
+
if (!cum) { cum = [dense(dir * CARRIER_DENSE_CY)]; if (dir === 1) sides.fwd = cum; else sides.bwd = cum; }
|
|
170
|
+
const dist = Math.abs(T) - CARRIER_DENSE_CY;
|
|
171
|
+
const k = Math.floor(dist / CARRIER_CELL_CY);
|
|
172
|
+
let target = cum.length;
|
|
173
|
+
while (target < k + 2) target += CARRIER_CHUNK;
|
|
174
|
+
for (let i = cum.length; i < target; i++) {
|
|
175
|
+
const t0 = dir * (CARRIER_DENSE_CY + (i - 1) * CARRIER_CELL_CY), t1 = t0 + dir * CARRIER_CELL_CY;
|
|
176
|
+
cum.push(cum[i - 1] + dir * (f(t0) + 4 * f((t0 + t1) / 2) + f(t1)) * (CARRIER_CELL_CY / 6));
|
|
177
|
+
}
|
|
178
|
+
const ta = dir * (CARRIER_DENSE_CY + k * CARRIER_CELL_CY);
|
|
179
|
+
const hp = T - ta;
|
|
180
|
+
return cum[k] + (f(ta) + 4 * f(ta + hp / 2) + f(T)) * (hp / 6);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The planetary carrier's SCALED integrand, k_PL · (e² − e0²), per century. @param {number} t */
|
|
185
|
+
const planetaryIntegrand = (t) => { const e = eccAt(t * 100); return kPlValue() * (e * e - eccE0 * eccE0); };
|
|
148
186
|
/** @param {number} T @returns {number} */
|
|
149
|
-
function
|
|
187
|
+
function planetaryCarrierDense(T) {
|
|
150
188
|
if (T === 0) return 0;
|
|
151
189
|
const k = kPlValue();
|
|
152
190
|
// The channel's e0 anchor CONST — not eccAt(0), which under integrated
|
|
@@ -160,12 +198,12 @@ function createMoonArguments({ constants, fns }) {
|
|
|
160
198
|
for (let i = 1; i < N; i++) sum += f(i * h) * (i % 2 ? 4 : 2);
|
|
161
199
|
return k * sum * h / 3;
|
|
162
200
|
}
|
|
201
|
+
const planetaryCarrier = makeDeepCarrier(planetaryIntegrand, planetaryCarrierDense);
|
|
163
202
|
|
|
164
203
|
/** @type {{eps0: number, C: number} | null} */
|
|
165
204
|
let obl = null;
|
|
166
|
-
/**
|
|
167
|
-
function
|
|
168
|
-
if (T === 0) return 0;
|
|
205
|
+
/** The obliquity carrier's normalization, once. @returns {{eps0: number, C: number}} */
|
|
206
|
+
function oblInit() {
|
|
169
207
|
if (obl === null) {
|
|
170
208
|
// (d′): when the of-date rate completion is live, its ∫(p_dyn − p_kin)
|
|
171
209
|
// carries the model-native share of the precession ACCELERATION
|
|
@@ -179,14 +217,23 @@ function createMoonArguments({ constants, fns }) {
|
|
|
179
217
|
const epsDot = computeObliquityEarth(2050) - computeObliquityEarth(1950);
|
|
180
218
|
obl = { eps0, C: 2 * T2_OBL / epsDot };
|
|
181
219
|
}
|
|
220
|
+
return obl;
|
|
221
|
+
}
|
|
222
|
+
/** The obliquity carrier's SCALED integrand, C · (ε − ε0), per century. @param {number} t */
|
|
223
|
+
const obliquityIntegrand = (t) => { const O = oblInit(); return O.C * (computeObliquityEarth(2000 + t * 100) - O.eps0); };
|
|
224
|
+
/** @param {number} T @returns {number} */
|
|
225
|
+
function obliquityCarrierDense(T) {
|
|
226
|
+
if (T === 0) return 0;
|
|
227
|
+
const O = oblInit();
|
|
182
228
|
/** @param {number} t */
|
|
183
|
-
const f = (t) => computeObliquityEarth(2000 + t * 100) -
|
|
229
|
+
const f = (t) => computeObliquityEarth(2000 + t * 100) - O.eps0;
|
|
184
230
|
const N = Math.max(2, 2 * Math.ceil(Math.abs(T) * 100 / 2000));
|
|
185
231
|
const h = T / N;
|
|
186
232
|
let sum = f(0) + f(T);
|
|
187
233
|
for (let i = 1; i < N; i++) sum += f(i * h) * (i % 2 ? 4 : 2);
|
|
188
|
-
return
|
|
234
|
+
return O.C * sum * h / 3;
|
|
189
235
|
}
|
|
236
|
+
const obliquityCarrier = makeDeepCarrier(obliquityIntegrand, obliquityCarrierDense);
|
|
190
237
|
|
|
191
238
|
// ── Secular-ë completion carrier (ṅ campaign, plan §12i item 0 (c)) ──────
|
|
192
239
|
// The certified snapshot's Lp T³/T⁴ tail is DERIVED physics (v4 D3,
|
|
@@ -234,6 +281,10 @@ function createMoonArguments({ constants, fns }) {
|
|
|
234
281
|
const env = Math.cos(Math.PI / 2 * q) ** 2;
|
|
235
282
|
return (0.5 * S.dd2 * t * t + S.d3Sec * t * t * t / 6) * env;
|
|
236
283
|
};
|
|
284
|
+
// Beyond the era the envelope has long closed (|t| ≥ Tq ≈ 279 cy → 0): the
|
|
285
|
+
// integral is the constant to ±Tq, so integrate exactly that span instead of
|
|
286
|
+
// N ∝ |T| nodes of zeros (in-era calls untouched).
|
|
287
|
+
if (Math.abs(T) > CARRIER_DENSE_CY) T = Math.sign(T) * S.Tq;
|
|
237
288
|
const N = Math.max(2, 2 * Math.ceil(Math.abs(T) * 100 / 2000));
|
|
238
289
|
const h = T / N;
|
|
239
290
|
let sum = f(0) + f(T);
|
|
@@ -374,7 +425,18 @@ function createMoonArguments({ constants, fns }) {
|
|
|
374
425
|
* (the anchors are measured against exactly this).
|
|
375
426
|
* @param {number} jd
|
|
376
427
|
* @returns {{Lp: number, w: number, om: number, Lsun: number, ws: number, dy: number} | null} */
|
|
428
|
+
/** @type {number} */ let _partsJd = NaN;
|
|
429
|
+
/** @type {ReturnType<typeof fwArgsDeepPartsAt>} */ let _partsLast = null;
|
|
430
|
+
/** The parts at a JD — memoized on the exact JD: a frame asks the same instant for
|
|
431
|
+
* the Moon, the umbra disc and the element rows. @param {number} jd */
|
|
377
432
|
function fwArgsDeepParts(jd) {
|
|
433
|
+
if (jd === _partsJd && _partsLast !== null) return _partsLast;
|
|
434
|
+
const P = fwArgsDeepPartsAt(jd);
|
|
435
|
+
_partsJd = jd; _partsLast = P;
|
|
436
|
+
return P;
|
|
437
|
+
}
|
|
438
|
+
/** @param {number} jd */
|
|
439
|
+
function fwArgsDeepPartsAt(jd) {
|
|
378
440
|
const A = bundle;
|
|
379
441
|
if (argsY0 === null) argsY0 = jdToSIyear(j2000JD);
|
|
380
442
|
const y = jdToSIyear(jd);
|