@essrt/physics 9.1.0 → 9.2.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/README.md +1 -1
- package/package.json +2 -1
- package/src/deltat/deep-time.cjs +23 -6
- package/src/index.js +4 -0
- package/src/layer1/index.js +9 -7
- package/src/model.js +14 -3
- package/src/phase/deep-calendar.cjs +202 -0
- package/src/planets/orientation.cjs +2 -2
- package/types/index.d.ts +1 -0
- package/types/phase/deep-calendar.d.cts +110 -0
- package/types/planets/orientation.d.cts +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @essrt/physics
|
|
2
2
|
|
|
3
3
|
The physics core of **ESSRT** — the Expanding Solar System Resonance Theory —
|
|
4
|
-
as implemented by the [
|
|
4
|
+
as implemented by the [ESSRT simulator](https://3d.holisticuniverse.com)
|
|
5
5
|
([source, AGPL-3.0](https://github.com/dvansonsbeek/3d)). Pure computation:
|
|
6
6
|
no I/O, no globals, no DOM, constants injected. The package ships the
|
|
7
7
|
**complete model**, including the fitted coefficients, so an installed copy
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essrt/physics",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.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
6
|
"modelVersion": "v14.0",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"exports": {
|
|
33
33
|
".": { "types": "./types/index.d.ts", "default": "./src/index.js" },
|
|
34
34
|
"./phase": { "types": "./types/phase/index.d.cts", "default": "./src/phase/index.cjs" },
|
|
35
|
+
"./phase/deep-calendar": { "types": "./types/phase/deep-calendar.d.cts", "default": "./src/phase/deep-calendar.cjs" },
|
|
35
36
|
"./cardinal": { "types": "./types/cardinal/index.d.cts", "default": "./src/cardinal/index.cjs" },
|
|
36
37
|
"./cardinal/one-source-structure": { "types": "./types/cardinal/one-source-structure.d.cts", "default": "./src/cardinal/one-source-structure.cjs" },
|
|
37
38
|
"./moon/ecc-channel": { "types": "./types/moon/ecc-channel.d.cts", "default": "./src/moon/ecc-channel.cjs" },
|
package/src/deltat/deep-time.cjs
CHANGED
|
@@ -125,7 +125,15 @@ function createDeepTimeLod(deps) {
|
|
|
125
125
|
* the unit H(t) below.
|
|
126
126
|
* @param {number} t_Ma @returns {number|null} */
|
|
127
127
|
function eraClockHAtAge(t_Ma) {
|
|
128
|
-
|
|
128
|
+
return eraClockHFromLod(lodSecondsAtAge(t_Ma));
|
|
129
|
+
}
|
|
130
|
+
// The `…FromLod` twins take an ALREADY-COMPUTED LOD(t): the Simpson ΔT
|
|
131
|
+
// integrand below evaluated the LOD four times and the tropical year twice
|
|
132
|
+
// per node through these helpers (measured 8.8 → 4.8 ms per cardinal panel
|
|
133
|
+
// year with the model's ΔT memo). Same operations, same inputs — every
|
|
134
|
+
// value bit-identical (11,958-value sweep, 0 differences).
|
|
135
|
+
/** @param {number|null} LOD_s @returns {number|null} */
|
|
136
|
+
function eraClockHFromLod(LOD_s) {
|
|
129
137
|
if (LOD_s === null) return null;
|
|
130
138
|
return K.holisticYearJ2000 * LOD_s / K.lodNowH13Seconds;
|
|
131
139
|
}
|
|
@@ -156,7 +164,11 @@ function createDeepTimeLod(deps) {
|
|
|
156
164
|
* ≡ H_era wherever (a₀/a_M)³ ≈ 1. Identifier kept (plan 06 P4).
|
|
157
165
|
* @param {number} t_Ma @returns {number|null} */
|
|
158
166
|
function hAtAge(t_Ma) {
|
|
159
|
-
|
|
167
|
+
return hFromLod(t_Ma, lodSecondsAtAge(t_Ma));
|
|
168
|
+
}
|
|
169
|
+
/** @param {number} t_Ma @param {number|null} LOD_s @returns {number|null} */
|
|
170
|
+
function hFromLod(t_Ma, LOD_s) {
|
|
171
|
+
const hEra = eraClockHFromLod(LOD_s);
|
|
160
172
|
const term = composed.torqueTermAtAge(t_Ma);
|
|
161
173
|
return hEra === null || term === null ? null : hEra / term;
|
|
162
174
|
}
|
|
@@ -202,8 +214,12 @@ function createDeepTimeLod(deps) {
|
|
|
202
214
|
* ±2 Myr, measured), and re-basing would move every comb coefficient
|
|
203
215
|
* for a rounding-level gain. This tier stays a named device. */
|
|
204
216
|
function tropicalYearSecondsAtAge(t_Ma) {
|
|
217
|
+
return tropicalYearSecondsFromLod(t_Ma, lodSecondsAtAge(t_Ma));
|
|
218
|
+
}
|
|
219
|
+
/** @param {number} t_Ma @param {number|null} LOD_s @returns {number} */
|
|
220
|
+
function tropicalYearSecondsFromLod(t_Ma, LOD_s) {
|
|
205
221
|
const sidSec = siderealYearSecondsAtAge(t_Ma);
|
|
206
|
-
const Ht =
|
|
222
|
+
const Ht = hFromLod(t_Ma, LOD_s);
|
|
207
223
|
if (Ht === null) return sidSec * (1 - 13 / K.holisticYearJ2000);
|
|
208
224
|
return sidSec * (1 - 13 / Ht);
|
|
209
225
|
}
|
|
@@ -281,15 +297,16 @@ function createDeepTimeLod(deps) {
|
|
|
281
297
|
const tau = i * h;
|
|
282
298
|
const lodMean = lodSecondsAtAge(tau);
|
|
283
299
|
if (lodMean === null) return NaN;
|
|
284
|
-
const yearS =
|
|
300
|
+
const yearS = tropicalYearSecondsFromLod(tau, lodMean);
|
|
285
301
|
// Ecliptic "missing motion" — the solar day is measured against the
|
|
286
302
|
// Sun on the ECLIPTIC, whose plane turns on the invariable plane at the
|
|
287
303
|
// nodal rate s₃ (period ≈ 68,751 yr, an orbital quantity), not in the
|
|
288
304
|
// inclination frame. Adds ~3.4 ms at J2000; the fitted cycle stack
|
|
289
305
|
// closes Layer-4 LOD_real onto the USNO anchor. Non-null: the helper
|
|
290
306
|
// can only be null when lodSecondsAtAge(tau) is null, which already
|
|
291
|
-
// returned NaN above
|
|
292
|
-
|
|
307
|
+
// returned NaN above. Spelled inline from the node's own lodMean and
|
|
308
|
+
// yearS — the same operations as eclipticLodCorrectionSecondsAtAge(tau).
|
|
309
|
+
const lodH5Raw = lodMean + lodMean / (deps.nodalPeriodYearsFn() * (yearS / 86400));
|
|
293
310
|
const integrand = (86400 - lodH5Raw) * yearS * 1e6 / 86400;
|
|
294
311
|
const w = (i === 0 || i === n) ? 1 : (i % 2 === 1 ? 4 : 2);
|
|
295
312
|
sum += w * integrand;
|
package/src/index.js
CHANGED
|
@@ -99,6 +99,10 @@ export { createDerivedViews } from './layer1/index.js';
|
|
|
99
99
|
// purpose: tools/lib requires the same files via the exports-map subpaths;
|
|
100
100
|
// re-exported here so bundled ESM consumers need only the package root).
|
|
101
101
|
export { createPhaseMachinery } from './phase/index.cjs';
|
|
102
|
+
// The deep-time calendar (year ↔ JD under the drifting year length, the
|
|
103
|
+
// H-balanced event finder) — ONE implementation for both engines since the
|
|
104
|
+
// year/day collapse (formerly the PHASE-B-DUPLICATE family).
|
|
105
|
+
export { createDeepCalendar } from './phase/deep-calendar.cjs';
|
|
102
106
|
export { createCardinalModel } from './cardinal/index.cjs';
|
|
103
107
|
// D4b: the one-source cardinal structure — the EoC layer (year lengths,
|
|
104
108
|
// crossing offsets, the e(t)-proportional spread) on the movement's own
|
package/src/layer1/index.js
CHANGED
|
@@ -57,16 +57,18 @@ export const createDerivedViews = ({ primitives: L0 }) => {
|
|
|
57
57
|
* browser shows exactly the same one-epoch split, which is how we know the
|
|
58
58
|
* port is faithful rather than merely close.
|
|
59
59
|
*
|
|
60
|
-
* It exists because `src/script.js`
|
|
60
|
+
* It exists because `src/script.js` carried BOTH as separate mutable globals
|
|
61
61
|
* — `meansiderealyearlengthinDays` and `meansiderealyearlengthinDays_kinematic`
|
|
62
|
-
* —
|
|
63
|
-
*
|
|
64
|
-
* (365.256363004) and the other
|
|
62
|
+
* — holding the same value at every epoch once `recomputeEpochAnchors` had
|
|
63
|
+
* run, diverging only at module load, where one is the IAU anchor
|
|
64
|
+
* (365.256363004) and the other lattice-derived: the 118 ms of R16.
|
|
65
65
|
*
|
|
66
66
|
* So the "kinematic" variant was never a different quantity, only a
|
|
67
|
-
* different arrival route with a different starting constant.
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* different arrival route with a different starting constant. The year/day
|
|
68
|
+
* collapse retired the mutable global: `meansiderealyearlengthinDays_kinematic`
|
|
69
|
+
* is now a J2000 CONSTANT in the browser (this route at f(2000)), and the
|
|
70
|
+
* epoch value is read from `meansiderealyearlengthinDays`. This view stays
|
|
71
|
+
* so the equality is measured, not asserted.
|
|
70
72
|
*
|
|
71
73
|
* @param {number} year
|
|
72
74
|
* @returns {number|null}
|
package/src/model.js
CHANGED
|
@@ -411,9 +411,9 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
|
|
|
411
411
|
// own ±10-Myr mode table, anchored form — feeds the ENTIRE lunar chain
|
|
412
412
|
// (modulation, cycle counts, arguments eccAt/channelIntegral, E-factor).
|
|
413
413
|
// The Sun/clock machinery (eclipse Sun EoC, besselian Sun distance,
|
|
414
|
-
// cardinal braid)
|
|
415
|
-
//
|
|
416
|
-
//
|
|
414
|
+
// cardinal braid) reads the one-source series below (plan 06 layer B —
|
|
415
|
+
// the two agree within 4.2e-5 in-era). The laws hook deliberately does
|
|
416
|
+
// NOT reach this channel.
|
|
417
417
|
const deepEcc = createDeepEccChannel(DEEP_MODES_ARTIFACT);
|
|
418
418
|
/** @param {number} year @returns {number} */
|
|
419
419
|
// Plan 06 layer B: the PUBLISHED Earth eccentricity is the one-source series
|
|
@@ -673,8 +673,17 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
|
|
|
673
673
|
|
|
674
674
|
// ── ΔT (TT − UT1): raw Simpson + sequential stack adds ────────────────────
|
|
675
675
|
/** @param {number} tMa @returns {number} */
|
|
676
|
+
// Exact-argument memo (the alphaMemo pattern above): the cardinal Newton
|
|
677
|
+
// solve asks for ΔT at the SAME jd twice per step (jdTTFromUT in the
|
|
678
|
+
// apparent Sun and again under the planetary completion) — measured 8.8 →
|
|
679
|
+
// 5.6 ms per panel year (8 solves) at modern epochs, values bit-identical.
|
|
680
|
+
/** @type {Map<number, number>} */
|
|
681
|
+
const dtMemo = new Map();
|
|
682
|
+
/** @param {number} tMa @returns {number} */
|
|
676
683
|
const meanDeltaTSecondsAtAge = (tMa) => {
|
|
677
684
|
if (tMa === 0) return 0;
|
|
685
|
+
const hit = dtMemo.get(tMa);
|
|
686
|
+
if (hit !== undefined) return hit;
|
|
678
687
|
let result = deepLod.deltaTRawSecondsAtAge(tMa);
|
|
679
688
|
const yearY = 2000 - tMa * 1e6;
|
|
680
689
|
result += dtCycles.cycleDeltaTSecondsAt('bond', yearY);
|
|
@@ -682,6 +691,8 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
|
|
|
682
691
|
result += dtCycles.cycleDeltaTSecondsAt('jose5', yearY);
|
|
683
692
|
result += dtCycles.cycleDeltaTSecondsAt('jose4', yearY);
|
|
684
693
|
result += dtCycles.swingDeltaTSecondsAt(yearY);
|
|
694
|
+
if (dtMemo.size >= 8192) dtMemo.clear();
|
|
695
|
+
dtMemo.set(tMa, result);
|
|
685
696
|
return result;
|
|
686
697
|
};
|
|
687
698
|
/** @param {number} year @returns {number} */
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-time calendar — year ↔ JD under the drifting year length, and the
|
|
3
|
+
* H-balanced event finder. ONE implementation for both engines.
|
|
4
|
+
*
|
|
5
|
+
* Until the year/day collapse these five functions lived twice, hand-mirrored:
|
|
6
|
+
* `src/script.js` (the original) and `tools/lib/deep-time.js` (the port,
|
|
7
|
+
* marked PHASE-B-DUPLICATE). The website's runtime twin was a third copy, and
|
|
8
|
+
* both Phase E website bugs (Simpson-vs-trapezoid convention, the missing
|
|
9
|
+
* lattice-α pin) were hand-mirror divergences. This module is extracted
|
|
10
|
+
* VERBATIM from the browser copy — operation for operation — so the numerics
|
|
11
|
+
* are the shipped ones:
|
|
12
|
+
*
|
|
13
|
+
* - the days-per-year integral rides the phase machinery's grid (10-kyr
|
|
14
|
+
* cells, trapezoid rule, linear interpolation between cells) — the same
|
|
15
|
+
* discretization the fitted coefficients were produced against;
|
|
16
|
+
* - the table is built under the LATTICE α (R2): `withLatticeAlpha` is the
|
|
17
|
+
* engine's pin, injected, so a table build can never see the climate-
|
|
18
|
+
* modulated α(t);
|
|
19
|
+
* - `yearToJD` CARRIES A KNOWN 0.6-d ZERO-POINT OFFSET (measured −0.600 d in
|
|
20
|
+
* both engines): the anchor cell is seeded by linear extrapolation but read
|
|
21
|
+
* back by interpolation across a 10-kyr cell. DO NOT "fix" it in
|
|
22
|
+
* isolation — `balancedYearAtCycle` round-trips through `cyclesBetween`,
|
|
23
|
+
* which absorbs the offset (that is why the bracket lands on
|
|
24
|
+
* −302635.004 / 32682.268 exactly); normalising the table without
|
|
25
|
+
* re-checking that round-trip moves the Step 6a window;
|
|
26
|
+
* - `balancedYearAtCycle` round-trips through the JD deliberately, NOT as a
|
|
27
|
+
* shortcut to bisecting `cyclesBetween` in calendar units: calendar-year
|
|
28
|
+
* delta = H_J2000 exactly, but SI-year delta does not — they differ by
|
|
29
|
+
* ~6.7 SI yr per H. Dropping the round-trip returns the SI label instead of
|
|
30
|
+
* the calendar year. The Newton step uses H at J2000 (the browser stepped
|
|
31
|
+
* with its live H; the iteration converges to 1e-12 cycles either way and
|
|
32
|
+
* the two agree at J2000 scene state by construction).
|
|
33
|
+
*
|
|
34
|
+
* CommonJS on purpose, like `./index.cjs`: `tools/lib` is CJS and cannot
|
|
35
|
+
* require an ESM module synchronously; ESM imports CJS natively.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
'use strict';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} PhaseMachinery
|
|
42
|
+
* @property {() => void} ensureTable
|
|
43
|
+
* @property {(year: number) => (number | null)} cumulAtYear
|
|
44
|
+
* @property {(targetCumul: number) => (number | null)} yearAtCumul
|
|
45
|
+
* @property {() => {yearMin: number, yearMax: number, stepYears: number, j2000Idx: number, length: number}} grid
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} DeepCalendar
|
|
50
|
+
* @property {(jd: number) => number} jdToSIyear SI-tropical-year label for a
|
|
51
|
+
* JD, anchored at startModelYearWithCorrection — NOT a calendar year (the
|
|
52
|
+
* scene's precession rotations integrate on this axis; a fit on it agrees
|
|
53
|
+
* with the runtime by construction; the round-trip bias Y_SI − Y is −11.0 yr
|
|
54
|
+
* at −302,635 and grows quadratically).
|
|
55
|
+
* @property {(targetCumul: number) => (number | null)} yearAtCumulIntegral
|
|
56
|
+
* inverse of the phase machinery's cumulAtYear — null outside the table.
|
|
57
|
+
* @property {() => void} ensureCumulDaysTable builds the ∫ daysPerYear dt table
|
|
58
|
+
* (idempotent; under the lattice α).
|
|
59
|
+
* @property {(year: number) => (number | null)} yearToJD calendar year → JD,
|
|
60
|
+
* integrating days-per-year from startModelYear; null outside the table
|
|
61
|
+
* domain or past the tidal-lock asymptote; startModelJD exactly at
|
|
62
|
+
* year = startModelYear.
|
|
63
|
+
* @property {(cycleOffset: number) => (number | null)} balancedYearAtCycle
|
|
64
|
+
* calendar year of the k-th H-balanced event, k = 0 being `balancedYear`;
|
|
65
|
+
* negative k = past; null outside the table domain.
|
|
66
|
+
* @property {() => (number | null)} cumulDaysTableLength diagnostic — the
|
|
67
|
+
* table length once built, null before.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {{
|
|
72
|
+
* phase: () => PhaseMachinery,
|
|
73
|
+
* meanYearInDaysAtAgeMa: (tMa: number) => (number | null),
|
|
74
|
+
* withLatticeAlpha: (build: () => void) => void,
|
|
75
|
+
* startModelJD: number,
|
|
76
|
+
* startModelYear: number,
|
|
77
|
+
* startModelYearWithCorrection: number,
|
|
78
|
+
* siTropicalYearDays: number,
|
|
79
|
+
* balancedYear: number,
|
|
80
|
+
* hJ2000: number,
|
|
81
|
+
* cyclesBetween: (yearA: number, yearB: number, divisorN: number) => (number | null),
|
|
82
|
+
* }} deps — `phase` is the engine's (lazily built) phase machinery, whose
|
|
83
|
+
* grid the days table shares; `meanYearInDaysAtAgeMa` the engine's
|
|
84
|
+
* days-per-year source (the LOD channel's `yearInDaysAtAge`);
|
|
85
|
+
* `withLatticeAlpha` the engine's R2 pin; `cyclesBetween` the engine's
|
|
86
|
+
* cycle counter (the browser passes its toggle-aware form, Node the
|
|
87
|
+
* integrated form — the caller's convention, preserved); `startModelYear`
|
|
88
|
+
* is the table zero (2000.5) and `startModelYearWithCorrection` the SI-axis
|
|
89
|
+
* anchor (≈ 2000.4977) — two different constants, do not unify.
|
|
90
|
+
* @returns {DeepCalendar}
|
|
91
|
+
*/
|
|
92
|
+
function createDeepCalendar({
|
|
93
|
+
phase,
|
|
94
|
+
meanYearInDaysAtAgeMa,
|
|
95
|
+
withLatticeAlpha,
|
|
96
|
+
startModelJD,
|
|
97
|
+
startModelYear,
|
|
98
|
+
startModelYearWithCorrection,
|
|
99
|
+
siTropicalYearDays,
|
|
100
|
+
balancedYear,
|
|
101
|
+
hJ2000,
|
|
102
|
+
cyclesBetween,
|
|
103
|
+
}) {
|
|
104
|
+
/** @type {(jd: number) => number} */
|
|
105
|
+
const jdToSIyear = (jd) => startModelYearWithCorrection + (jd - startModelJD) / siTropicalYearDays;
|
|
106
|
+
|
|
107
|
+
/** @type {(targetCumul: number) => (number | null)} */
|
|
108
|
+
const yearAtCumulIntegral = (targetCumul) => phase().yearAtCumul(targetCumul);
|
|
109
|
+
|
|
110
|
+
/** ∫ daysPerYear dt from startModelYear, on the phase grid. @type {Float64Array | null} */
|
|
111
|
+
let cumulDaysTable = null;
|
|
112
|
+
|
|
113
|
+
function ensureCumulDaysTable() {
|
|
114
|
+
if (cumulDaysTable !== null) return;
|
|
115
|
+
withLatticeAlpha(buildCumulDaysTable); // R2: lattice tables pin α
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildCumulDaysTable() {
|
|
119
|
+
const P = phase();
|
|
120
|
+
P.ensureTable();
|
|
121
|
+
const { yearMin, stepYears, j2000Idx, length: N } = P.grid();
|
|
122
|
+
const table = new Float64Array(N);
|
|
123
|
+
|
|
124
|
+
/** @type {(year: number) => (number | null)} */
|
|
125
|
+
const daysPerYear = (year) => meanYearInDaysAtAgeMa((startModelYear - year) / 1e6);
|
|
126
|
+
|
|
127
|
+
const gridYearAtJ2000Idx = yearMin + j2000Idx * stepYears;
|
|
128
|
+
const partialYearOffset = startModelYear - gridYearAtJ2000Idx;
|
|
129
|
+
const daysAtJ2000 = meanYearInDaysAtAgeMa(0);
|
|
130
|
+
table[j2000Idx] = -partialYearOffset * /** @type {number} */ (daysAtJ2000);
|
|
131
|
+
|
|
132
|
+
let prev = daysPerYear(gridYearAtJ2000Idx);
|
|
133
|
+
for (let i = j2000Idx + 1; i < N; i++) {
|
|
134
|
+
const curr = daysPerYear(yearMin + i * stepYears);
|
|
135
|
+
table[i] = (prev !== null && curr !== null && !Number.isNaN(table[i - 1]))
|
|
136
|
+
? table[i - 1] + 0.5 * (prev + curr) * stepYears
|
|
137
|
+
: NaN;
|
|
138
|
+
prev = curr;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
prev = daysPerYear(gridYearAtJ2000Idx);
|
|
142
|
+
for (let i = j2000Idx - 1; i >= 0; i--) {
|
|
143
|
+
const curr = daysPerYear(yearMin + i * stepYears);
|
|
144
|
+
table[i] = (prev !== null && curr !== null && !Number.isNaN(table[i + 1]))
|
|
145
|
+
? table[i + 1] - 0.5 * (prev + curr) * stepYears
|
|
146
|
+
: NaN;
|
|
147
|
+
prev = curr;
|
|
148
|
+
}
|
|
149
|
+
cumulDaysTable = table;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @type {(year: number) => (number | null)} */
|
|
153
|
+
function yearToJD(year) {
|
|
154
|
+
if (!Number.isFinite(year)) return null;
|
|
155
|
+
ensureCumulDaysTable();
|
|
156
|
+
const table = /** @type {Float64Array} */ (cumulDaysTable);
|
|
157
|
+
const { yearMin, yearMax, stepYears } = phase().grid();
|
|
158
|
+
if (year < yearMin || year > yearMax) return null;
|
|
159
|
+
const idx_f = (year - yearMin) / stepYears;
|
|
160
|
+
const idx_lo = Math.floor(idx_f);
|
|
161
|
+
const idx_hi = Math.min(idx_lo + 1, table.length - 1);
|
|
162
|
+
const v_lo = table[idx_lo];
|
|
163
|
+
const v_hi = table[idx_hi];
|
|
164
|
+
if (Number.isNaN(v_lo) || Number.isNaN(v_hi)) return null;
|
|
165
|
+
const daysFromStartModel = v_lo + (idx_f - idx_lo) * (v_hi - v_lo);
|
|
166
|
+
return startModelJD + daysFromStartModel;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** @type {(cycleOffset: number) => (number | null)} */
|
|
170
|
+
function balancedYearAtCycle(cycleOffset) {
|
|
171
|
+
// No cycleOffset === 0 short-circuit: returning balancedYear directly
|
|
172
|
+
// skips the JD round-trip below, misses the exact integer-cycle JD by
|
|
173
|
+
// ~0.5 d and gives ~21″ obliquity drift + ~3e-9 e drift at cycle 0.
|
|
174
|
+
const refCumul = phase().cumulAtYear(balancedYear);
|
|
175
|
+
if (refCumul === null) return null;
|
|
176
|
+
let Y = yearAtCumulIntegral(refCumul + cycleOffset);
|
|
177
|
+
if (Y === null) return null;
|
|
178
|
+
for (let iter = 0; iter < 5; iter++) {
|
|
179
|
+
const jd = yearToJD(Y);
|
|
180
|
+
if (jd === null) return Y;
|
|
181
|
+
const Y_SI = jdToSIyear(jd);
|
|
182
|
+
if (!Number.isFinite(Y_SI)) return Y;
|
|
183
|
+
const corrected = cyclesBetween(balancedYear, Y_SI, 1);
|
|
184
|
+
if (corrected === null) return Y;
|
|
185
|
+
const error = corrected - cycleOffset;
|
|
186
|
+
if (Math.abs(error) < 1e-12) break;
|
|
187
|
+
Y = Y - error * hJ2000;
|
|
188
|
+
}
|
|
189
|
+
return Y;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return Object.freeze({
|
|
193
|
+
jdToSIyear,
|
|
194
|
+
yearAtCumulIntegral,
|
|
195
|
+
ensureCumulDaysTable,
|
|
196
|
+
yearToJD,
|
|
197
|
+
balancedYearAtCycle,
|
|
198
|
+
cumulDaysTableLength: () => (cumulDaysTable === null ? null : cumulDaysTable.length),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = { createDeepCalendar };
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Two ascending-node conventions exist and BOTH are quantities (the S-P4
|
|
16
16
|
* lesson): the year-2000-anchored linear node here
|
|
17
|
-
* (ascendingNodeInvPlaneLinearAt — the node-integrator
|
|
17
|
+
* (ascendingNodeInvPlaneLinearAt — the node-integrator mirror)
|
|
18
18
|
* and the balanced-year −8H/N node inside the scene tilt
|
|
19
19
|
* (eclipticInclinationFromBalanced). Do not merge them.
|
|
20
20
|
*/
|
|
@@ -54,7 +54,7 @@ function invPlaneInclinationAt(body, yearsSinceBalanced, env) {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Ascending node on the invariable plane — the LINEAR year-2000-anchored
|
|
57
|
-
* convention (node-integrator
|
|
57
|
+
* convention (node-integrator; see the header).
|
|
58
58
|
* @param {{ ascendingNodeInvPlane?: number, ascendingNodePeriod?: number,
|
|
59
59
|
* perihelionEclipticYears: number }} body
|
|
60
60
|
* @param {number} year @returns {number} degrees 0–360 */
|
package/types/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createEpochPrimitives } from "./layer0/index.js";
|
|
|
2
2
|
export { deriveEpochParams } from "./layer0/derive-params.js";
|
|
3
3
|
export { createDerivedViews } from "./layer1/index.js";
|
|
4
4
|
export { createPhaseMachinery } from "./phase/index.cjs";
|
|
5
|
+
export { createDeepCalendar } from "./phase/deep-calendar.cjs";
|
|
5
6
|
export { createCardinalModel } from "./cardinal/index.cjs";
|
|
6
7
|
export { createCardinalStructure } from "./cardinal/one-source-structure.cjs";
|
|
7
8
|
export { createMoonEccChannel } from "./moon/ecc-channel.cjs";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export type PhaseMachinery = {
|
|
2
|
+
ensureTable: () => void;
|
|
3
|
+
cumulAtYear: (year: number) => (number | null);
|
|
4
|
+
yearAtCumul: (targetCumul: number) => (number | null);
|
|
5
|
+
grid: () => {
|
|
6
|
+
yearMin: number;
|
|
7
|
+
yearMax: number;
|
|
8
|
+
stepYears: number;
|
|
9
|
+
j2000Idx: number;
|
|
10
|
+
length: number;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type DeepCalendar = {
|
|
14
|
+
/**
|
|
15
|
+
* SI-tropical-year label for a
|
|
16
|
+
* JD, anchored at startModelYearWithCorrection — NOT a calendar year (the
|
|
17
|
+
* scene's precession rotations integrate on this axis; a fit on it agrees
|
|
18
|
+
* with the runtime by construction; the round-trip bias Y_SI − Y is −11.0 yr
|
|
19
|
+
* at −302,635 and grows quadratically).
|
|
20
|
+
*/
|
|
21
|
+
jdToSIyear: (jd: number) => number;
|
|
22
|
+
/**
|
|
23
|
+
* inverse of the phase machinery's cumulAtYear — null outside the table.
|
|
24
|
+
*/
|
|
25
|
+
yearAtCumulIntegral: (targetCumul: number) => (number | null);
|
|
26
|
+
/**
|
|
27
|
+
* builds the ∫ daysPerYear dt table
|
|
28
|
+
* (idempotent; under the lattice α).
|
|
29
|
+
*/
|
|
30
|
+
ensureCumulDaysTable: () => void;
|
|
31
|
+
/**
|
|
32
|
+
* calendar year → JD,
|
|
33
|
+
* integrating days-per-year from startModelYear; null outside the table
|
|
34
|
+
* domain or past the tidal-lock asymptote; startModelJD exactly at
|
|
35
|
+
* year = startModelYear.
|
|
36
|
+
*/
|
|
37
|
+
yearToJD: (year: number) => (number | null);
|
|
38
|
+
/**
|
|
39
|
+
* calendar year of the k-th H-balanced event, k = 0 being `balancedYear`;
|
|
40
|
+
* negative k = past; null outside the table domain.
|
|
41
|
+
*/
|
|
42
|
+
balancedYearAtCycle: (cycleOffset: number) => (number | null);
|
|
43
|
+
/**
|
|
44
|
+
* diagnostic — the
|
|
45
|
+
* table length once built, null before.
|
|
46
|
+
*/
|
|
47
|
+
cumulDaysTableLength: () => (number | null);
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {Object} PhaseMachinery
|
|
51
|
+
* @property {() => void} ensureTable
|
|
52
|
+
* @property {(year: number) => (number | null)} cumulAtYear
|
|
53
|
+
* @property {(targetCumul: number) => (number | null)} yearAtCumul
|
|
54
|
+
* @property {() => {yearMin: number, yearMax: number, stepYears: number, j2000Idx: number, length: number}} grid
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} DeepCalendar
|
|
58
|
+
* @property {(jd: number) => number} jdToSIyear SI-tropical-year label for a
|
|
59
|
+
* JD, anchored at startModelYearWithCorrection — NOT a calendar year (the
|
|
60
|
+
* scene's precession rotations integrate on this axis; a fit on it agrees
|
|
61
|
+
* with the runtime by construction; the round-trip bias Y_SI − Y is −11.0 yr
|
|
62
|
+
* at −302,635 and grows quadratically).
|
|
63
|
+
* @property {(targetCumul: number) => (number | null)} yearAtCumulIntegral
|
|
64
|
+
* inverse of the phase machinery's cumulAtYear — null outside the table.
|
|
65
|
+
* @property {() => void} ensureCumulDaysTable builds the ∫ daysPerYear dt table
|
|
66
|
+
* (idempotent; under the lattice α).
|
|
67
|
+
* @property {(year: number) => (number | null)} yearToJD calendar year → JD,
|
|
68
|
+
* integrating days-per-year from startModelYear; null outside the table
|
|
69
|
+
* domain or past the tidal-lock asymptote; startModelJD exactly at
|
|
70
|
+
* year = startModelYear.
|
|
71
|
+
* @property {(cycleOffset: number) => (number | null)} balancedYearAtCycle
|
|
72
|
+
* calendar year of the k-th H-balanced event, k = 0 being `balancedYear`;
|
|
73
|
+
* negative k = past; null outside the table domain.
|
|
74
|
+
* @property {() => (number | null)} cumulDaysTableLength diagnostic — the
|
|
75
|
+
* table length once built, null before.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* @param {{
|
|
79
|
+
* phase: () => PhaseMachinery,
|
|
80
|
+
* meanYearInDaysAtAgeMa: (tMa: number) => (number | null),
|
|
81
|
+
* withLatticeAlpha: (build: () => void) => void,
|
|
82
|
+
* startModelJD: number,
|
|
83
|
+
* startModelYear: number,
|
|
84
|
+
* startModelYearWithCorrection: number,
|
|
85
|
+
* siTropicalYearDays: number,
|
|
86
|
+
* balancedYear: number,
|
|
87
|
+
* hJ2000: number,
|
|
88
|
+
* cyclesBetween: (yearA: number, yearB: number, divisorN: number) => (number | null),
|
|
89
|
+
* }} deps — `phase` is the engine's (lazily built) phase machinery, whose
|
|
90
|
+
* grid the days table shares; `meanYearInDaysAtAgeMa` the engine's
|
|
91
|
+
* days-per-year source (the LOD channel's `yearInDaysAtAge`);
|
|
92
|
+
* `withLatticeAlpha` the engine's R2 pin; `cyclesBetween` the engine's
|
|
93
|
+
* cycle counter (the browser passes its toggle-aware form, Node the
|
|
94
|
+
* integrated form — the caller's convention, preserved); `startModelYear`
|
|
95
|
+
* is the table zero (2000.5) and `startModelYearWithCorrection` the SI-axis
|
|
96
|
+
* anchor (≈ 2000.4977) — two different constants, do not unify.
|
|
97
|
+
* @returns {DeepCalendar}
|
|
98
|
+
*/
|
|
99
|
+
export function createDeepCalendar({ phase, meanYearInDaysAtAgeMa, withLatticeAlpha, startModelJD, startModelYear, startModelYearWithCorrection, siTropicalYearDays, balancedYear, hJ2000, cyclesBetween, }: {
|
|
100
|
+
phase: () => PhaseMachinery;
|
|
101
|
+
meanYearInDaysAtAgeMa: (tMa: number) => (number | null);
|
|
102
|
+
withLatticeAlpha: (build: () => void) => void;
|
|
103
|
+
startModelJD: number;
|
|
104
|
+
startModelYear: number;
|
|
105
|
+
startModelYearWithCorrection: number;
|
|
106
|
+
siTropicalYearDays: number;
|
|
107
|
+
balancedYear: number;
|
|
108
|
+
hJ2000: number;
|
|
109
|
+
cyclesBetween: (yearA: number, yearB: number, divisorN: number) => (number | null);
|
|
110
|
+
}): DeepCalendar;
|
|
@@ -25,7 +25,7 @@ export function invPlaneInclinationAt(body: {
|
|
|
25
25
|
}): number;
|
|
26
26
|
/**
|
|
27
27
|
* Ascending node on the invariable plane — the LINEAR year-2000-anchored
|
|
28
|
-
* convention (node-integrator
|
|
28
|
+
* convention (node-integrator; see the header).
|
|
29
29
|
* @param {{ ascendingNodeInvPlane?: number, ascendingNodePeriod?: number,
|
|
30
30
|
* perihelionEclipticYears: number }} body
|
|
31
31
|
* @param {number} year @returns {number} degrees 0–360 */
|