@essrt/physics 26.0.0 → 26.0.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 +1 -1
- package/src/chain-cycles/index.cjs +23 -7
- package/src/moon/arguments.cjs +61 -73
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essrt/physics",
|
|
3
|
-
"version": "26.0.
|
|
3
|
+
"version": "26.0.1",
|
|
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
6
|
"modelVersion": "v27.1",
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
* NUMERICS ARE LOAD-BEARING: fixed 10-yr grid over ±250 kyr, per-cell
|
|
29
29
|
* 3-point Simpson at build, LINEAR interpolation on read; beyond the dense
|
|
30
30
|
* grid a COARSE cumulative table (1-kyr cells, the same per-cell Simpson,
|
|
31
|
-
* anchored at the dense edge
|
|
31
|
+
* anchored at the dense edge; read with a 3-point Simpson over the partial
|
|
32
|
+
* cell, so the count's RATE is continuous — plan 23 follow-up, see the tier
|
|
33
|
+
* note below) grown lazily in 250-kyr chunks in the direction
|
|
32
34
|
* asked for — the owner's playback at 1,000 yr/s past year 250,000 had fallen
|
|
33
35
|
* to 190 ms of JS per frame: the former fallback, an adaptive Simpson of up to
|
|
34
36
|
* 1,024 samples per call cached by exact year pair, re-integrated every one
|
|
@@ -136,9 +138,19 @@ function createChainCycleIntegrator({
|
|
|
136
138
|
// Per chain and per direction, a cumulative table in COARSE_STEP cells that
|
|
137
139
|
// starts at the dense edge with the edge's own value and grows by COARSE_CHUNK
|
|
138
140
|
// 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
|
|
140
|
-
//
|
|
141
|
-
//
|
|
141
|
+
// with the same 3-point Simpson as the dense grid; the read is a 3-point
|
|
142
|
+
// Simpson over the partial cell from the cell's start (three integrand
|
|
143
|
+
// evaluations per read), NOT the dense grid's linear interpolation: a
|
|
144
|
+
// linear read makes the count's rate piecewise constant per cell, and the
|
|
145
|
+
// of-date node and perigee are SMALL differences of two large counts
|
|
146
|
+
// (N_drac − N_trop: 13.4 − 13.37 cycles/yr), so each table's per-cell slope
|
|
147
|
+
// step — 1e-6 of its own rate, the month's tidal drift over 1 kyr — landed
|
|
148
|
+
// fully on the difference: a ±0.3-d sawtooth per cell on the node period
|
|
149
|
+
// beyond ±250 kyr (measured on the Node Regression panel, plan 23
|
|
150
|
+
// follow-up). On the dense 10-yr grid the same step is 100× smaller and
|
|
151
|
+
// below every reader, so its linear read — the byte-identical in-window
|
|
152
|
+
// path — stays. The period laws vary on Myr scales at deep time, so 1-kyr
|
|
153
|
+
// cells hold the count to far below a cycle — the old fallback sampled
|
|
142
154
|
// ~1 per kyr too, but re-did the whole span on every call.
|
|
143
155
|
const COARSE_STEP = 1000, COARSE_CHUNK = 250000, COARSE_LIMIT_YEARS = 600e6;
|
|
144
156
|
/** @typedef {{ cum: number[], ok: boolean }} CoarseSide */
|
|
@@ -178,9 +190,13 @@ function createChainCycleIntegrator({
|
|
|
178
190
|
if (distance > COARSE_LIMIT_YEARS) return null;
|
|
179
191
|
if (side.cum.length < Math.ceil(distance / COARSE_STEP) + 1) growCoarse(periodFnSeconds, side, edgeYear, dir, distance);
|
|
180
192
|
if (!side.ok || side.cum.length < Math.ceil(distance / COARSE_STEP) + 1) return null;
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
const i = Math.floor(distance / COARSE_STEP);
|
|
194
|
+
// the partial cell from its start y0 to y: a 3-point Simpson, signed by (y − y0)
|
|
195
|
+
const y0 = edgeYear + dir * i * COARSE_STEP;
|
|
196
|
+
if (y === y0) return side.cum[i];
|
|
197
|
+
const f0 = integrandAt(periodFnSeconds, y0), fm = integrandAt(periodFnSeconds, (y0 + y) / 2), f1 = integrandAt(periodFnSeconds, y);
|
|
198
|
+
if (f0 === null || fm === null || f1 === null) return null;
|
|
199
|
+
return side.cum[i] + (f0 + 4 * fm + f1) * ((y - y0) / 6);
|
|
184
200
|
}
|
|
185
201
|
/** C(y) anywhere the tables reach: dense inside the grid, coarse beyond.
|
|
186
202
|
* @param {PeriodSecondsAtAge} periodFnSeconds @param {Float64Array} tab @param {number} y @returns {number | null} */
|
package/src/moon/arguments.cjs
CHANGED
|
@@ -163,59 +163,63 @@ function createMoonArguments({ constants, fns }) {
|
|
|
163
163
|
return kPl;
|
|
164
164
|
}
|
|
165
165
|
// ── Deep-time carrier tables ────────────────────────────────────────────────
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
|
|
166
|
+
// Every carrier is ∫₀ᵀ f dt on ONE cumulative table per direction: fine
|
|
167
|
+
// cells of CARRIER_FINE_CY (100 yr) to ±CARRIER_FINE_RANGE_CY (250 kyr),
|
|
168
|
+
// grown in CARRIER_FINE_CHUNK cells (10 kyr — an in-window call never
|
|
169
|
+
// samples deeper than it must), then CARRIER_CELL_CY cells (1 kyr) grown in
|
|
170
|
+
// CARRIER_CHUNK (the former deep tier, unchanged); per-cell 3-point Simpson
|
|
171
|
+
// and a 3-point Simpson over the partial last cell. Continuous in value AND
|
|
172
|
+
// in rate: the per-cell error is fixed by the cell, so nothing jumps when T
|
|
173
|
+
// grows. (Plan 23 follow-up, measured on the Node Regression panel: the
|
|
174
|
+
// in-era form was one Simpson from J2000 to T whose node count stepped every
|
|
175
|
+
// 2 kyr (obliquity) / 8 kyr (planetary), so the accumulated error jumped
|
|
176
|
+
// there — 0.3–6″ on the obliquity carrier at 20–250 kyr, 10–15″ on the
|
|
177
|
+
// planetary one at ±6/22 kyr — invisible on L′'s rate, a notch of 0.5 d on
|
|
178
|
+
// the node period once the remainder rode Ω. The earlier form that
|
|
179
|
+
// re-integrated the whole span on every call cost the deep-time playback
|
|
180
|
+
// 190 ms a frame — the owner's measurement behind the tables.)
|
|
181
|
+
const CARRIER_FINE_CY = 1, CARRIER_FINE_RANGE_CY = 2500, CARRIER_FINE_CHUNK = 100;
|
|
182
|
+
const CARRIER_CELL_CY = 10, CARRIER_CHUNK = 2500;
|
|
176
183
|
/** @param {(t: number) => number} f the SCALED integrand per century
|
|
177
|
-
* @
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
/** @type {{ fwd: number[] | null, bwd: number[] | null }} */
|
|
184
|
+
* @returns {(T: number) => number} ∫₀ᵀ f dt, T in Julian centuries (signed) */
|
|
185
|
+
function makeDeepCarrier(f) {
|
|
186
|
+
/** @type {{ fwd: {fine: number[], coarse: number[] | null} | null, bwd: {fine: number[], coarse: number[] | null} | null }} */
|
|
181
187
|
const sides = { fwd: null, bwd: null };
|
|
188
|
+
const NF = CARRIER_FINE_RANGE_CY / CARRIER_FINE_CY;
|
|
189
|
+
/** The signed cell integral from t0 (nearer J2000) to t1. @param {number} t0 @param {number} t1 */
|
|
190
|
+
const cell = (t0, t1) => (f(t0) + 4 * f((t0 + t1) / 2) + f(t1)) * ((t1 - t0) / 6);
|
|
191
|
+
/** @param {{fine: number[]}} s @param {number} dir @param {number} upTo grow the fine table to this many cells */
|
|
192
|
+
const growFine = (s, dir, upTo) => {
|
|
193
|
+
for (let i = s.fine.length; i <= upTo; i++) s.fine.push(s.fine[i - 1] + cell(dir * (i - 1) * CARRIER_FINE_CY, dir * i * CARRIER_FINE_CY));
|
|
194
|
+
};
|
|
182
195
|
return (T) => {
|
|
183
|
-
if (
|
|
184
|
-
const dir = T > 0 ? 1 : -1;
|
|
185
|
-
let
|
|
186
|
-
if (!
|
|
187
|
-
|
|
196
|
+
if (T === 0) return 0;
|
|
197
|
+
const dir = T > 0 ? 1 : -1, a = Math.abs(T);
|
|
198
|
+
let s = dir === 1 ? sides.fwd : sides.bwd;
|
|
199
|
+
if (!s) { s = { fine: [0], coarse: null }; if (dir === 1) sides.fwd = s; else sides.bwd = s; }
|
|
200
|
+
if (a <= CARRIER_FINE_RANGE_CY) {
|
|
201
|
+
const k = Math.floor(a / CARRIER_FINE_CY);
|
|
202
|
+
if (s.fine.length < k + 2) growFine(s, dir, Math.min(NF, Math.ceil((k + 1) / CARRIER_FINE_CHUNK) * CARRIER_FINE_CHUNK));
|
|
203
|
+
return s.fine[k] + cell(dir * k * CARRIER_FINE_CY, T);
|
|
204
|
+
}
|
|
205
|
+
if (!s.coarse) { growFine(s, dir, NF); s.coarse = [s.fine[NF]]; }
|
|
206
|
+
const dist = a - CARRIER_FINE_RANGE_CY;
|
|
188
207
|
const k = Math.floor(dist / CARRIER_CELL_CY);
|
|
189
|
-
let target =
|
|
208
|
+
let target = s.coarse.length;
|
|
190
209
|
while (target < k + 2) target += CARRIER_CHUNK;
|
|
191
|
-
for (let i =
|
|
192
|
-
const t0 = dir * (
|
|
193
|
-
|
|
210
|
+
for (let i = s.coarse.length; i < target; i++) {
|
|
211
|
+
const t0 = dir * (CARRIER_FINE_RANGE_CY + (i - 1) * CARRIER_CELL_CY);
|
|
212
|
+
s.coarse.push(s.coarse[i - 1] + cell(t0, t0 + dir * CARRIER_CELL_CY));
|
|
194
213
|
}
|
|
195
|
-
|
|
196
|
-
const hp = T - ta;
|
|
197
|
-
return cum[k] + (f(ta) + 4 * f(ta + hp / 2) + f(T)) * (hp / 6);
|
|
214
|
+
return s.coarse[k] + cell(dir * (CARRIER_FINE_RANGE_CY + k * CARRIER_CELL_CY), T);
|
|
198
215
|
};
|
|
199
216
|
}
|
|
200
217
|
|
|
201
|
-
/** The planetary carrier's SCALED integrand, k_PL · (e² − e0²), per century
|
|
218
|
+
/** The planetary carrier's SCALED integrand, k_PL · (e² − e0²), per century
|
|
219
|
+
* (e0 the channel's anchor CONST — not eccAt(0), which under integrated
|
|
220
|
+
* phase carries the R3 drift correction, the 8.2-2 convention). @param {number} t */
|
|
202
221
|
const planetaryIntegrand = (t) => { const e = eccAt(t * 100); return kPlValue() * (e * e - eccE0 * eccE0); };
|
|
203
|
-
|
|
204
|
-
function planetaryCarrierDense(T) {
|
|
205
|
-
if (T === 0) return 0;
|
|
206
|
-
const k = kPlValue();
|
|
207
|
-
// The channel's e0 anchor CONST — not eccAt(0), which under integrated
|
|
208
|
-
// phase carries the R3 drift correction (the 8.2-2 convention).
|
|
209
|
-
const e0sq = eccE0 * eccE0;
|
|
210
|
-
/** @param {number} t */
|
|
211
|
-
const f = (t) => { const e = eccAt(t * 100); return e * e - e0sq; };
|
|
212
|
-
const N = Math.max(2, 2 * Math.ceil(Math.abs(T) * 100 / 8000));
|
|
213
|
-
const h = T / N;
|
|
214
|
-
let sum = f(0) + f(T);
|
|
215
|
-
for (let i = 1; i < N; i++) sum += f(i * h) * (i % 2 ? 4 : 2);
|
|
216
|
-
return k * sum * h / 3;
|
|
217
|
-
}
|
|
218
|
-
const planetaryCarrier = makeDeepCarrier(planetaryIntegrand, planetaryCarrierDense);
|
|
222
|
+
const planetaryCarrier = makeDeepCarrier(planetaryIntegrand);
|
|
219
223
|
|
|
220
224
|
/** @type {{eps0: number, C: number} | null} */
|
|
221
225
|
let obl = null;
|
|
@@ -238,19 +242,7 @@ function createMoonArguments({ constants, fns }) {
|
|
|
238
242
|
}
|
|
239
243
|
/** The obliquity carrier's SCALED integrand, C · (ε − ε0), per century. @param {number} t */
|
|
240
244
|
const obliquityIntegrand = (t) => { const O = oblInit(); return O.C * (computeObliquityEarth(2000 + t * 100) - O.eps0); };
|
|
241
|
-
|
|
242
|
-
function obliquityCarrierDense(T) {
|
|
243
|
-
if (T === 0) return 0;
|
|
244
|
-
const O = oblInit();
|
|
245
|
-
/** @param {number} t */
|
|
246
|
-
const f = (t) => computeObliquityEarth(2000 + t * 100) - O.eps0;
|
|
247
|
-
const N = Math.max(2, 2 * Math.ceil(Math.abs(T) * 100 / 2000));
|
|
248
|
-
const h = T / N;
|
|
249
|
-
let sum = f(0) + f(T);
|
|
250
|
-
for (let i = 1; i < N; i++) sum += f(i * h) * (i % 2 ? 4 : 2);
|
|
251
|
-
return O.C * sum * h / 3;
|
|
252
|
-
}
|
|
253
|
-
const obliquityCarrier = makeDeepCarrier(obliquityIntegrand, obliquityCarrierDense);
|
|
245
|
+
const obliquityCarrier = makeDeepCarrier(obliquityIntegrand);
|
|
254
246
|
|
|
255
247
|
/** The frame share's REMAINDER for ϖ and Ω (plan 23). The physical exponents gave
|
|
256
248
|
* up the frame's T² the Meeus-effective pair carried (T2F, Lieske's); pFix
|
|
@@ -307,24 +299,20 @@ function createMoonArguments({ constants, fns }) {
|
|
|
307
299
|
const d2Ch = (e2(10) - 2 * e2(0) + e2(-10)) / 100;
|
|
308
300
|
secComp = { dd2: d2Sec - d2Ch, d3Sec, Tq: SECULAR_COMPLETION_TAPER_YEARS / 100 };
|
|
309
301
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
if (q >= 1) return 0;
|
|
315
|
-
const env = Math.cos(Math.PI / 2 * q) ** 2;
|
|
316
|
-
return (0.5 * S.dd2 * t * t + S.d3Sec * t * t * t / 6) * env;
|
|
317
|
-
};
|
|
318
|
-
// Beyond the era the envelope has long closed (|t| ≥ Tq ≈ 279 cy → 0): the
|
|
319
|
-
// integral is the constant to ±Tq, so integrate exactly that span instead of
|
|
320
|
-
// N ∝ |T| nodes of zeros (in-era calls untouched).
|
|
321
|
-
if (Math.abs(T) > CARRIER_DENSE_CY) T = Math.sign(T) * S.Tq;
|
|
322
|
-
const N = Math.max(2, 2 * Math.ceil(Math.abs(T) * 100 / 2000));
|
|
323
|
-
const h = T / N;
|
|
324
|
-
let sum = f(0) + f(T);
|
|
325
|
-
for (let i = 1; i < N; i++) sum += f(i * h) * (i % 2 ? 4 : 2);
|
|
326
|
-
return kPlValue() * sum * h / 3;
|
|
302
|
+
// Beyond the taper the envelope has closed (|t| ≥ Tq ≈ 279 cy → 0): the
|
|
303
|
+
// integral is the constant to ±Tq, so the table is read at ±Tq there.
|
|
304
|
+
const Tq = secComp.Tq;
|
|
305
|
+
return secCarrier(Math.abs(T) > Tq ? Math.sign(T) * Tq : T);
|
|
327
306
|
}
|
|
307
|
+
/** The completion's SCALED integrand, k_PL · the tapered curvature difference, per century. @param {number} t */
|
|
308
|
+
const secIntegrand = (t) => {
|
|
309
|
+
const S = /** @type {{dd2: number, d3Sec: number, Tq: number}} */ (secComp);
|
|
310
|
+
const q = Math.abs(t) / S.Tq;
|
|
311
|
+
if (q >= 1) return 0;
|
|
312
|
+
const env = Math.cos(Math.PI / 2 * q) ** 2;
|
|
313
|
+
return kPlValue() * (0.5 * S.dd2 * t * t + S.d3Sec * t * t * t / 6) * env;
|
|
314
|
+
};
|
|
315
|
+
const secCarrier = makeDeepCarrier(secIntegrand);
|
|
328
316
|
|
|
329
317
|
// ── Deep-time branch: always-chains (Stage B) ────────────────────────────
|
|
330
318
|
/** @type {number | null} */
|