@essrt/physics 1.0.0 → 1.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 +50 -0
- package/package.json +1 -1
- package/src/constants/coefficients.js +2 -2
- package/src/constants/generated.d.ts +4 -0
- package/src/constants/generated.js +6 -0
- package/src/constants/index.js +1 -1
- package/src/index.js +46 -11
- package/src/model.js +856 -0
- package/src/planets/asc-node-integrator.cjs +1 -1
- package/src/reference/published-curves.cjs +2 -1
package/src/model.js
ADDED
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createModel() — the canonical assembly of the parts library (§7a step 1).
|
|
3
|
+
*
|
|
4
|
+
* The package deliberately ships unassembled factories; every consumer wires
|
|
5
|
+
* them (tools/lib, script.js's generated-constants side, the website's
|
|
6
|
+
* adapter). This module is that wiring, extracted once: constants in,
|
|
7
|
+
* assembled model out. The api surface (Phase 15) builds on it, and the
|
|
8
|
+
* eventual tools/lib adapter collapse swaps onto it instead of keeping a
|
|
9
|
+
* private twin.
|
|
10
|
+
*
|
|
11
|
+
* Counterfactuals are first-class (§2d): the context validation and the
|
|
12
|
+
* counterfactual hashing live in index.js's `createModel`, which composes
|
|
13
|
+
* this assembly with the resolved, frozen context. This module stays pure
|
|
14
|
+
* and browser-safe.
|
|
15
|
+
*
|
|
16
|
+
* Wiring order mirrors the reference adapter (holisticuniverse
|
|
17
|
+
* src/lib/essrt.ts) operation-for-operation where FP association matters,
|
|
18
|
+
* which itself mirrors tools/lib/constants.js §9.
|
|
19
|
+
*/
|
|
20
|
+
import { deriveEpochParams } from './layer0/derive-params.js';
|
|
21
|
+
import * as FL from './planets/fibonacci-laws.cjs';
|
|
22
|
+
import * as planetOrientation from './planets/orientation.cjs';
|
|
23
|
+
import { createPhaseMachinery } from './phase/index.cjs';
|
|
24
|
+
import { createCardinalModel } from './cardinal/index.cjs';
|
|
25
|
+
import { createDeltaTCycles } from './deltat/cycles.cjs';
|
|
26
|
+
import { createDeepTimeLod } from './deltat/deep-time.cjs';
|
|
27
|
+
import { evalClimateL1OrbitalPermil } from './climate/l1-orbital.cjs';
|
|
28
|
+
import { createMoonEccChannel } from './moon/ecc-channel.cjs';
|
|
29
|
+
import { createMoonMonthChain } from './moon/month-chain.cjs';
|
|
30
|
+
import { createChainCycleIntegrator } from './chain-cycles/index.cjs';
|
|
31
|
+
import { createMoonArguments, jdToDecimalYear } from './moon/arguments.cjs';
|
|
32
|
+
import { createMoonSeries } from './moon/series.cjs';
|
|
33
|
+
import { createEclipseFinders } from './eclipse/finders.cjs';
|
|
34
|
+
import { driver2PeriodSecondsAtAge } from './planets/orbit-chain.cjs';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* RA-day-offset Fourier amplitudes (ms). KNOWN EXCEPTION carried over from
|
|
38
|
+
* the reference adapters: these are fit results that live as literals in the
|
|
39
|
+
* engine too — packaging them into FITTED_COEFFICIENTS is the remaining
|
|
40
|
+
* §7a-step-1b move; until then this is their single packaged home.
|
|
41
|
+
*/
|
|
42
|
+
const RA_DAY_OFFSET_MEAN_MS = -14.194;
|
|
43
|
+
const RA_DAY_OFFSET_ECC_MS = -5.64;
|
|
44
|
+
const RA_DAY_OFFSET_OBLIQ_MS = -1.684;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Moon-channel eccentricity sensitivities (perigee/node), the [g/g₀]^s
|
|
48
|
+
* exponents of the factored deep-time law. Same known-exception class as the
|
|
49
|
+
* RA offsets above: single source src/script.js _FW_MOON, mirrored as
|
|
50
|
+
* literals in tools/lib/deep-time.js (_ECOMP_S_W/_ECOMP_S_N). Both are
|
|
51
|
+
* Meeus-effective — S_N moved 1.0 → 1.018 with the v4 frame-attribution
|
|
52
|
+
* batch.
|
|
53
|
+
*/
|
|
54
|
+
const MOON_ECC_SENSITIVITY_PERIGEE = 2.407;
|
|
55
|
+
const MOON_ECC_SENSITIVITY_NODE = 1.018;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Assemble the model surfaces from a resolved constants context + fitted
|
|
59
|
+
* coefficients. Internal: `createModel` in index.js composes this with the
|
|
60
|
+
* §2d context validation and counterfactual hashing — call that, not this.
|
|
61
|
+
*
|
|
62
|
+
* @param {Readonly<Record<string, any>>} C the frozen constants context
|
|
63
|
+
* @param {Readonly<Record<string, any>>} F the fitted coefficients
|
|
64
|
+
* @returns the assembled surfaces (epoch, earth, lengths, cardinal, moon) — type inferred so ReturnType stays precise
|
|
65
|
+
*/
|
|
66
|
+
export function assembleModel(C, F) {
|
|
67
|
+
// ── Derived constants (constants.js §9 order) ─────────────────────────────
|
|
68
|
+
const H = C.foundational.holisticyearLength;
|
|
69
|
+
const meanSolarYearDays = Math.round(C.foundational.inputmeanlengthsolaryearindays * (H / 8)) / (H / 8);
|
|
70
|
+
const startmodelYear = C.foundational.startmodelYear;
|
|
71
|
+
const startmodelJD = C.foundational.startmodelJD;
|
|
72
|
+
const startModelYearWithCorrection = startmodelYear + C.foundational.correctionDays / meanSolarYearDays;
|
|
73
|
+
const balancedYear = C.earthOrbital.perihelionalignmentYear
|
|
74
|
+
- C.foundational.temperatureGraphMostLikely * (H / 16);
|
|
75
|
+
|
|
76
|
+
const meanSiderealYearDays = C.yearLengthRef.siderealYear;
|
|
77
|
+
const meanSiderealYearSeconds = meanSiderealYearDays * 86400;
|
|
78
|
+
const meanSiderealYearDaysKinematic = (meanSolarYearDays * H) / (H - 13);
|
|
79
|
+
const meanLengthOfDay = meanSiderealYearSeconds / meanSiderealYearDaysKinematic;
|
|
80
|
+
const meanAnomalisticYearDays = (meanSolarYearDays * (H / 16)) / (H / 16 - 1);
|
|
81
|
+
const meanTropicalYearJ2000Seconds = meanSolarYearDays * meanLengthOfDay;
|
|
82
|
+
|
|
83
|
+
const earthtiltMean = C.earth.earthtiltMean;
|
|
84
|
+
const earthInclAmplitude = C.earth.earthInvPlaneInclinationAmplitude;
|
|
85
|
+
const eccentricityBase = C.earth.eccentricityBase;
|
|
86
|
+
const eccentricityAmplitude = C.earth.eccentricityAmplitude;
|
|
87
|
+
const earthRAAngle = 2 * earthInclAmplitude - (earthInclAmplitude * earthInclAmplitude) / earthtiltMean;
|
|
88
|
+
const earthInclMean = C.earthOrbital.earthInclinationJ2000_deg
|
|
89
|
+
- earthInclAmplitude * Math.cos(((C.earthOrbital.earthPerihelionLongitudeJ2000
|
|
90
|
+
- C.earthOrbital.earthInclinationCycleAnchor) * Math.PI) / 180);
|
|
91
|
+
const solsticeObliquityMean = F.SOLSTICE_OBLIQUITY_MEAN_FITTED;
|
|
92
|
+
|
|
93
|
+
const G_CONSTANT = C.physicalConstants.G_CONSTANT;
|
|
94
|
+
const MASS_RATIO_EARTH_MOON = C.physicalConstants.MASS_RATIO_EARTH_MOON;
|
|
95
|
+
const currentAUDistance = C.physicalConstants.currentAUDistance;
|
|
96
|
+
const earthMoiFactorJ2000 = C.physicalConstants.earthMoiFactorJ2000;
|
|
97
|
+
const moonDistanceKm = C.moonReference.moonDistance;
|
|
98
|
+
const moonSiderealMonthInput = C.moonReference.moonSiderealMonthInput;
|
|
99
|
+
|
|
100
|
+
// 8H-lattice moon sidereal month (constants.js §"Moon derived months")
|
|
101
|
+
const totalDaysInH = H * meanSolarYearDays;
|
|
102
|
+
const moonSiderealMonth = totalDaysInH / (Math.round((8 * totalDaysInH) / moonSiderealMonthInput) / 8);
|
|
103
|
+
|
|
104
|
+
// Mass chain: Moon Kepler → GM_EM → Earth/Moon split → Sun (§9 order)
|
|
105
|
+
const moonOrbitalShift = moonDistanceKm * (1 / (MASS_RATIO_EARTH_MOON + 1)) * (moonSiderealMonth / meanSiderealYearDays);
|
|
106
|
+
const moonDistanceCorrected = moonDistanceKm + moonOrbitalShift;
|
|
107
|
+
const GM_EARTH_MOON_SYSTEM = (4 * Math.PI * Math.PI * Math.pow(moonDistanceCorrected, 3))
|
|
108
|
+
/ Math.pow(moonSiderealMonth * meanLengthOfDay, 2);
|
|
109
|
+
const GM_EARTH_ALONE = GM_EARTH_MOON_SYSTEM * (MASS_RATIO_EARTH_MOON / (MASS_RATIO_EARTH_MOON + 1));
|
|
110
|
+
const GM_MOON_ALONE = GM_EARTH_MOON_SYSTEM / (MASS_RATIO_EARTH_MOON + 1);
|
|
111
|
+
const GM_SUN_PLUS_EARTH = (4 * Math.PI * Math.PI * Math.pow(currentAUDistance, 3))
|
|
112
|
+
/ Math.pow(meanSiderealYearSeconds, 2);
|
|
113
|
+
const GM_SUN = GM_SUN_PLUS_EARTH - GM_EARTH_ALONE;
|
|
114
|
+
const M_SUN = GM_SUN / G_CONSTANT;
|
|
115
|
+
|
|
116
|
+
// ── α(t): climate-driven GIA channel with the R2 lattice pin ──────────────
|
|
117
|
+
const CLIMATE = F.CLIMATE_FORMULA_COEFFS;
|
|
118
|
+
const CLIMATE_REGIME = CLIMATE.regimes['lr04-post-mpt'];
|
|
119
|
+
const alphaClimateScale = C.deepTime.alphaClimateScalePerMille;
|
|
120
|
+
|
|
121
|
+
/** @param {number} year @returns {number} */
|
|
122
|
+
const evalClimateL1 = (year) => evalClimateL1OrbitalPermil(year, {
|
|
123
|
+
l1Terms: CLIMATE_REGIME.L1,
|
|
124
|
+
yStdDenormalization: CLIMATE_REGIME.denormalization.y_std,
|
|
125
|
+
eightHKyr: CLIMATE.config.eight_H_kyr,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
let latticeAlphaPin = false;
|
|
129
|
+
/** @type {number|null} */
|
|
130
|
+
let alphaL1J2000 = null;
|
|
131
|
+
/** @param {number} tMa @returns {number} */
|
|
132
|
+
const earthMoiFactorAtAge = (tMa) => {
|
|
133
|
+
if (latticeAlphaPin) return earthMoiFactorJ2000;
|
|
134
|
+
if (alphaL1J2000 === null) alphaL1J2000 = evalClimateL1(2000);
|
|
135
|
+
const L1at = evalClimateL1(2000 - tMa * 1e6);
|
|
136
|
+
return earthMoiFactorJ2000 - alphaClimateScale * (L1at - alphaL1J2000);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ── Layer 0 + deep-time core ──────────────────────────────────────────────
|
|
140
|
+
const EPOCH_PARAMS = deriveEpochParams({
|
|
141
|
+
solarLuminosityW: C.physicalConstants.solarLuminosityW,
|
|
142
|
+
solarWindKgPerS: C.physicalConstants.solarWindMassLossKgPerS,
|
|
143
|
+
speedOfLightKmPerS: C.physicalConstants.speedOfLight,
|
|
144
|
+
alpha1PerMa: C.deepTime.alpha1PerMa,
|
|
145
|
+
alpha3PerMa3: C.deepTime.alpha3PerMa3,
|
|
146
|
+
alpha4PerMa4: C.deepTime.alpha4PerMa4,
|
|
147
|
+
holisticYearJ2000: H,
|
|
148
|
+
meanSiderealYearSeconds,
|
|
149
|
+
meanSiderealYearDaysKinematic,
|
|
150
|
+
sunMassKg: M_SUN,
|
|
151
|
+
gmEarthAloneKm3S2: GM_EARTH_ALONE,
|
|
152
|
+
gmMoonAloneKm3S2: GM_MOON_ALONE,
|
|
153
|
+
gravitationalConstantKm3KgS2: G_CONSTANT,
|
|
154
|
+
earthMoiFactorJ2000,
|
|
155
|
+
earthDiameterKm: C.bodyDiametersKm.earth,
|
|
156
|
+
moonDistanceKm,
|
|
157
|
+
moonOrbitalEccentricity: C.moonReference.moonOrbitalEccentricityBase,
|
|
158
|
+
gmEarthMoonSystemKm3S2: GM_EARTH_MOON_SYSTEM,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/** Farhat polynomial on the t_Ma axis. @param {number} tMa @returns {number} */
|
|
162
|
+
const moonDistanceMetresAtAge = (tMa) => EPOCH_PARAMS.moonDistanceNowM
|
|
163
|
+
* (1 + EPOCH_PARAMS.alpha1PerMa * tMa
|
|
164
|
+
+ EPOCH_PARAMS.alpha3PerMa3 * tMa * tMa * tMa
|
|
165
|
+
+ EPOCH_PARAMS.alpha4PerMa4 * tMa * tMa * tMa * tMa);
|
|
166
|
+
|
|
167
|
+
const DT = F.DT_STACK;
|
|
168
|
+
const RES = F.DT_RESONATOR;
|
|
169
|
+
const dtCycles = createDeltaTCycles({
|
|
170
|
+
eightHYears: 8 * H,
|
|
171
|
+
taperFullHalfwidthYears: C.deepTime.dtStackTaperFullHalfwidthYr,
|
|
172
|
+
taperTotalHalfwidthYears: C.deepTime.dtStackTaperTotalHalfwidthYr,
|
|
173
|
+
tropicalYearSecondsJ2000: meanTropicalYearJ2000Seconds,
|
|
174
|
+
cycles: {
|
|
175
|
+
bond: { latticeN: DT.bond.lattice_n, cosCoeffSeconds: DT.bond.cos_coeff_s, sinCoeffSeconds: DT.bond.sin_coeff_s },
|
|
176
|
+
hallstatt: { latticeN: DT.hallstatt.lattice_n, cosCoeffSeconds: DT.hallstatt.cos_coeff_s, sinCoeffSeconds: DT.hallstatt.sin_coeff_s },
|
|
177
|
+
jose5: { latticeN: DT.jose5.lattice_n, cosCoeffSeconds: DT.jose5.cos_coeff_s, sinCoeffSeconds: DT.jose5.sin_coeff_s },
|
|
178
|
+
jose4: { latticeN: DT.jose4.lattice_n, cosCoeffSeconds: DT.jose4.cos_coeff_s, sinCoeffSeconds: DT.jose4.sin_coeff_s },
|
|
179
|
+
},
|
|
180
|
+
resonator: {
|
|
181
|
+
t0LatticeN: RES.T0_lattice_n,
|
|
182
|
+
q: RES.Q,
|
|
183
|
+
kicks: RES.kick_epochs_year.map(/** @param {number} t @param {number} i */ (t, i) => ({
|
|
184
|
+
tYear: t,
|
|
185
|
+
cosSeconds: RES.kick_coefficients_s[i].cos,
|
|
186
|
+
sinSeconds: RES.kick_coefficients_s[i].sin,
|
|
187
|
+
})),
|
|
188
|
+
tones: RES.drive_tones.map(/** @param {{dn: number, phi_locked_rad: number, amp_s: number}} t */ (t) => ({ dn: t.dn, phiLockedRad: t.phi_locked_rad, ampSeconds: t.amp_s })),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/** Layer-3/4 cyclic δLOD sum (all shipped channels ON). @param {number} year @returns {number} */
|
|
193
|
+
const dtCycleLodCorrectionSum = (year) =>
|
|
194
|
+
dtCycles.cycleLodSecondsAt('bond', year)
|
|
195
|
+
+ dtCycles.cycleLodSecondsAt('hallstatt', year)
|
|
196
|
+
+ dtCycles.cycleLodSecondsAt('jose5', year)
|
|
197
|
+
+ dtCycles.cycleLodSecondsAt('jose4', year)
|
|
198
|
+
+ dtCycles.swingLodSecondsAt(year);
|
|
199
|
+
|
|
200
|
+
const deepLod = createDeepTimeLod({
|
|
201
|
+
constants: {
|
|
202
|
+
lTotalEmKgm2S: EPOCH_PARAMS.totalAngularMomentumKgM2S,
|
|
203
|
+
mMoonAloneKg: EPOCH_PARAMS.moonMassKg,
|
|
204
|
+
mEarthAloneKg: EPOCH_PARAMS.earthMassKg,
|
|
205
|
+
rEarthMetres: EPOCH_PARAMS.earthRadiusM,
|
|
206
|
+
gmEmM3PerS2: EPOCH_PARAMS.gmEarthMoonM3S2,
|
|
207
|
+
eFactorMoon: EPOCH_PARAMS.moonEccentricityFactor,
|
|
208
|
+
aLockMetres: EPOCH_PARAMS.moonLockDistanceM,
|
|
209
|
+
aMoonNowMetres: EPOCH_PARAMS.moonDistanceNowM,
|
|
210
|
+
alpha1PerMa: EPOCH_PARAMS.alpha1PerMa,
|
|
211
|
+
alpha3PerMa3: EPOCH_PARAMS.alpha3PerMa3,
|
|
212
|
+
alpha4PerMa4: EPOCH_PARAMS.alpha4PerMa4,
|
|
213
|
+
holisticYearJ2000: H,
|
|
214
|
+
lodNowH13Seconds: EPOCH_PARAMS.lodNowH13Seconds,
|
|
215
|
+
meanSiderealYearJ2000Seconds: meanSiderealYearSeconds,
|
|
216
|
+
solarMassLossFracPerYear: EPOCH_PARAMS.solarMassLossFracPerYear,
|
|
217
|
+
siderealYearDaysKinematicJ2000: meanSiderealYearDaysKinematic,
|
|
218
|
+
},
|
|
219
|
+
moonDistanceMetresAtAge,
|
|
220
|
+
moiFactorAtAge: earthMoiFactorAtAge,
|
|
221
|
+
siderealYearDaysFourierAt: (year) => evalSiderealYearFourierIAU(year),
|
|
222
|
+
cycleLodSumAt: dtCycleLodCorrectionSum,
|
|
223
|
+
swingLodAt: (year) => dtCycles.swingLodSecondsAt(year),
|
|
224
|
+
swingLodRateAt: (year) => dtCycles.swingLodRateAt(year),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ── Integrated ∫1/H(t)dt phase (R2 α-pin honoured while building) ─────────
|
|
228
|
+
const phaseM = createPhaseMachinery({
|
|
229
|
+
holisticHAtAgeMa: (tMa) => deepLod.hAtAge(tMa),
|
|
230
|
+
tableAnchorYear: startmodelYear,
|
|
231
|
+
driftRefYear: startModelYearWithCorrection,
|
|
232
|
+
hJ2000: H,
|
|
233
|
+
yearMin: -500e6,
|
|
234
|
+
yearMax: 500e6,
|
|
235
|
+
stepYears: 10000,
|
|
236
|
+
});
|
|
237
|
+
let phaseTableBuilt = false;
|
|
238
|
+
const phase = () => {
|
|
239
|
+
if (!phaseTableBuilt) {
|
|
240
|
+
latticeAlphaPin = true;
|
|
241
|
+
try { phaseM.ensureTable(); } finally { latticeAlphaPin = false; }
|
|
242
|
+
phaseTableBuilt = true;
|
|
243
|
+
}
|
|
244
|
+
return phaseM;
|
|
245
|
+
};
|
|
246
|
+
/** @param {number} yearA @param {number} yearB @param {number} divisorN @returns {number} */
|
|
247
|
+
const cyclesBetween = (yearA, yearB, divisorN) => {
|
|
248
|
+
const cyc = phase().cyclesBetween(yearA, yearB, divisorN);
|
|
249
|
+
return cyc === null ? (divisorN * (yearB - yearA)) / H : cyc;
|
|
250
|
+
};
|
|
251
|
+
/** @param {number} anchorYear @param {number} year @param {number} divisorN @returns {number} */
|
|
252
|
+
const phaseRadians = (anchorYear, year, divisorN) => 2 * Math.PI * cyclesBetween(anchorYear, year, divisorN);
|
|
253
|
+
|
|
254
|
+
/** @param {number} year @returns {number} */
|
|
255
|
+
const yearToTMa = (year) => (2000 - year) / 1e6;
|
|
256
|
+
|
|
257
|
+
// ── Earth scalars (integrated-phase display semantics) ────────────────────
|
|
258
|
+
/** @param {number} year @returns {number} */
|
|
259
|
+
const earthPerihelionDeg = (year) => {
|
|
260
|
+
let longitude = 270.0 + 360.0 * cyclesBetween(balancedYear, year, 16);
|
|
261
|
+
for (const [div, sinC, cosC] of F.PERI_HARMONICS_RAW) {
|
|
262
|
+
const ph = phaseRadians(balancedYear, year, div);
|
|
263
|
+
longitude += sinC * Math.sin(ph) + cosC * Math.cos(ph);
|
|
264
|
+
}
|
|
265
|
+
return (((longitude + F.PERI_OFFSET) % 360) + 360) % 360;
|
|
266
|
+
};
|
|
267
|
+
/** @param {number} year @returns {number} */
|
|
268
|
+
const obliquityDeg = (year) => {
|
|
269
|
+
let obliq = solsticeObliquityMean;
|
|
270
|
+
for (const [div, sinC, cosC] of F.SOLSTICE_OBLIQUITY_HARMONICS) {
|
|
271
|
+
const ph = phaseRadians(balancedYear, year, div);
|
|
272
|
+
obliq += sinC * Math.sin(ph) + cosC * Math.cos(ph);
|
|
273
|
+
}
|
|
274
|
+
return obliq;
|
|
275
|
+
};
|
|
276
|
+
/** @param {number} year @returns {number} */
|
|
277
|
+
const eccentricityAt = (year) => {
|
|
278
|
+
const th = phaseRadians(balancedYear, year, 16);
|
|
279
|
+
return Math.sqrt(eccentricityBase * eccentricityBase
|
|
280
|
+
+ eccentricityAmplitude * eccentricityAmplitude
|
|
281
|
+
- 2 * eccentricityBase * eccentricityAmplitude * Math.cos(th));
|
|
282
|
+
};
|
|
283
|
+
/** @param {number} year @returns {number} */
|
|
284
|
+
const inclinationDeg = (year) => earthInclMean
|
|
285
|
+
- earthInclAmplitude * Math.cos(phaseRadians(balancedYear, year, 3));
|
|
286
|
+
/** @param {number} year @returns {number} */
|
|
287
|
+
const ascendingNodeDeg = (year) => {
|
|
288
|
+
const period = -H / 5;
|
|
289
|
+
return (((C.earthOrbital.earthAscendingNodeInvPlane + (360.0 * (year - 2000)) / period) % 360) + 360) % 360;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// ── Year/day lengths ──────────────────────────────────────────────────────
|
|
293
|
+
/** @param {number} year @param {number} base @param {Array<[number, number, number]>} harmonics @returns {number} */
|
|
294
|
+
const evalYearFourier = (year, base, harmonics) => {
|
|
295
|
+
let result = base;
|
|
296
|
+
const c1 = phase().cyclesBetween(balancedYear, year, 1);
|
|
297
|
+
if (c1 === null) return result;
|
|
298
|
+
for (const [div, sinC, cosC] of harmonics) {
|
|
299
|
+
const ph = div * c1 * 2 * Math.PI;
|
|
300
|
+
result += sinC * Math.sin(ph) + cosC * Math.cos(ph);
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
};
|
|
304
|
+
/** @param {number} year @returns {number} */
|
|
305
|
+
const evalSiderealYearFourierIAU = (year) => evalYearFourier(year, meanSiderealYearDays, F.SIDEREAL_YEAR_HARMONICS);
|
|
306
|
+
/** @param {number} year @returns {number} */
|
|
307
|
+
const siderealYearDaysBase = (year) => {
|
|
308
|
+
const tMa = yearToTMa(year);
|
|
309
|
+
const lod = deepLod.lodSecondsAtAge(tMa);
|
|
310
|
+
if (lod === null) return meanSiderealYearDays;
|
|
311
|
+
return deepLod.siderealYearSecondsAtAge(tMa) / lod;
|
|
312
|
+
};
|
|
313
|
+
/** @param {number} year @returns {number} */
|
|
314
|
+
const tropicalYearDaysBase = (year) => {
|
|
315
|
+
const days = deepLod.yearInDaysAtAge(yearToTMa(year));
|
|
316
|
+
return days === null ? meanSolarYearDays : days;
|
|
317
|
+
};
|
|
318
|
+
/** @param {number} year @returns {number} */
|
|
319
|
+
const anomalisticYearDaysBase = (year) => {
|
|
320
|
+
const tMa = yearToTMa(year);
|
|
321
|
+
const Ht = deepLod.hAtAge(tMa);
|
|
322
|
+
const tropD = deepLod.yearInDaysAtAge(tMa);
|
|
323
|
+
if (Ht === null || tropD === null) return meanAnomalisticYearDays;
|
|
324
|
+
return (tropD * (Ht / 16)) / (Ht / 16 - 1);
|
|
325
|
+
};
|
|
326
|
+
/** @param {number} year @returns {number} */
|
|
327
|
+
const siderealYearDays = (year) => evalYearFourier(year, siderealYearDaysBase(year), F.SIDEREAL_YEAR_HARMONICS);
|
|
328
|
+
/** @param {number} year @returns {number} */
|
|
329
|
+
const anomalisticYearDays = (year) => evalYearFourier(year, anomalisticYearDaysBase(year), F.ANOMALISTIC_YEAR_HARMONICS);
|
|
330
|
+
/** Kinematic LOD (Layer 0). @param {number} year @returns {number} */
|
|
331
|
+
const dayLengthSeconds = (year) => deepLod.siderealYearSecondsAtAge(yearToTMa(year)) / siderealYearDays(year);
|
|
332
|
+
/** @param {number} year @returns {number} */
|
|
333
|
+
const raDayOffsetMs = (year) => RA_DAY_OFFSET_MEAN_MS
|
|
334
|
+
+ RA_DAY_OFFSET_ECC_MS * Math.cos(phaseRadians(balancedYear, year, 16))
|
|
335
|
+
+ RA_DAY_OFFSET_OBLIQ_MS * Math.cos(phaseRadians(balancedYear, year, 8));
|
|
336
|
+
|
|
337
|
+
// ── Cardinal-point model ──────────────────────────────────────────────────
|
|
338
|
+
const cardinalM = createCardinalModel({
|
|
339
|
+
isDeepTime: () => true,
|
|
340
|
+
constants: {
|
|
341
|
+
anchors: F.CARDINAL_POINT_ANCHORS_ADJUSTED,
|
|
342
|
+
harmonics: F.CARDINAL_POINT_HARMONICS,
|
|
343
|
+
eccTerms: F.CARDINAL_POINT_ECC_TERMS,
|
|
344
|
+
jointTerms: F.CARDINAL_POINT_JOINT_TERMS,
|
|
345
|
+
derived: F.CARDINAL_POINT_DERIVED,
|
|
346
|
+
tropicalHarmonics: F.TROPICAL_YEAR_HARMONICS,
|
|
347
|
+
balancedYear,
|
|
348
|
+
meanSolarYearDays,
|
|
349
|
+
hJ2000: H,
|
|
350
|
+
eccentricityBase,
|
|
351
|
+
eccentricityAmplitude,
|
|
352
|
+
tiltMeanDeg: earthtiltMean,
|
|
353
|
+
raAngleDeg: earthRAAngle,
|
|
354
|
+
inclAmplitudeDeg: earthInclAmplitude,
|
|
355
|
+
},
|
|
356
|
+
fns: {
|
|
357
|
+
cyclesBetween: (a, b, n) => phase().cyclesBetween(a, b, n),
|
|
358
|
+
analyticTropicalDays: (year) => {
|
|
359
|
+
const tMa = (startmodelYear - year) / 1e6;
|
|
360
|
+
const Ht = deepLod.hAtAge(tMa);
|
|
361
|
+
if (Ht === null) return null;
|
|
362
|
+
return (deepLod.siderealYearSecondsAtAge(tMa) / 86400) * (1 - 13 / Ht);
|
|
363
|
+
},
|
|
364
|
+
meanHAtAgeMa: (tMa) => deepLod.hAtAge(tMa),
|
|
365
|
+
meanYearRealLodDays: (tMa) => deepLod.yearInDaysAtAge(tMa),
|
|
366
|
+
eccentricityAt,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
/** Tropical year: mean of the four cardinal intervals. @param {number} year @returns {number} */
|
|
370
|
+
const tropicalYearDays = (year) => cardinalM.computeTropicalYearLength(year);
|
|
371
|
+
/** @param {number} year @returns {number} */
|
|
372
|
+
const tropicalYearDirectDays = (year) => evalYearFourier(year, tropicalYearDaysBase(year), F.TROPICAL_YEAR_HARMONICS);
|
|
373
|
+
|
|
374
|
+
/** @param {number} year @returns {number} */
|
|
375
|
+
const solarYearSeconds = (year) => tropicalYearDays(year) * dayLengthSeconds(year);
|
|
376
|
+
/** @param {number} year @returns {number} */
|
|
377
|
+
const siderealDaySeconds = (year) => solarYearSeconds(year) / (tropicalYearDays(year) + 1);
|
|
378
|
+
/** @param {number} year @returns {number} */
|
|
379
|
+
const stellarDaySeconds = (year) => {
|
|
380
|
+
const tMa = yearToTMa(year);
|
|
381
|
+
const HtRaw = deepLod.hAtAge(tMa);
|
|
382
|
+
const Ht = HtRaw === null ? H : HtRaw;
|
|
383
|
+
const syS = solarYearSeconds(year);
|
|
384
|
+
const syD = tropicalYearDays(year);
|
|
385
|
+
const sidDay = siderealDaySeconds(year);
|
|
386
|
+
const raProjection = Math.cos((obliquityDeg(year) * Math.PI) / 180);
|
|
387
|
+
return (syS / (syD + 1) / (Ht / 13) / (syD + 1)) * raProjection + sidDay;
|
|
388
|
+
};
|
|
389
|
+
/** @param {number} year @returns {number} */
|
|
390
|
+
const measuredSolarDaySeconds = (year) => dayLengthSeconds(year) + raDayOffsetMs(year) / 1000;
|
|
391
|
+
|
|
392
|
+
// ── Moon at epoch ─────────────────────────────────────────────────────────
|
|
393
|
+
/** Solar-Δa-corrected Kepler month. @param {number} year @returns {number} */
|
|
394
|
+
const moonSiderealMonthDaysAt = (year) => {
|
|
395
|
+
const tMa = yearToTMa(year);
|
|
396
|
+
const lod = deepLod.lodSecondsAtAge(tMa);
|
|
397
|
+
if (lod === null) return NaN;
|
|
398
|
+
const aAppKm = moonDistanceMetresAtAge(tMa) / 1000;
|
|
399
|
+
const sidYrDays = deepLod.siderealYearSecondsAtAge(tMa) / lod;
|
|
400
|
+
const deltaA = aAppKm * (1 / (MASS_RATIO_EARTH_MOON + 1)) * (moonSiderealMonthInput / sidYrDays);
|
|
401
|
+
const aCorrM = (aAppKm + deltaA) * 1000;
|
|
402
|
+
const monthSeconds = 2 * Math.PI * Math.sqrt(Math.pow(aCorrM, 3) / EPOCH_PARAMS.gmEarthMoonM3S2);
|
|
403
|
+
return monthSeconds / lod;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// ── ΔT (TT − UT1): raw Simpson + sequential stack adds ────────────────────
|
|
407
|
+
/** @param {number} tMa @returns {number} */
|
|
408
|
+
const meanDeltaTSecondsAtAge = (tMa) => {
|
|
409
|
+
if (tMa === 0) return 0;
|
|
410
|
+
let result = deepLod.deltaTRawSecondsAtAge(tMa);
|
|
411
|
+
const yearY = 2000 - tMa * 1e6;
|
|
412
|
+
result += dtCycles.cycleDeltaTSecondsAt('bond', yearY);
|
|
413
|
+
result += dtCycles.cycleDeltaTSecondsAt('hallstatt', yearY);
|
|
414
|
+
result += dtCycles.cycleDeltaTSecondsAt('jose5', yearY);
|
|
415
|
+
result += dtCycles.cycleDeltaTSecondsAt('jose4', yearY);
|
|
416
|
+
result += dtCycles.swingDeltaTSecondsAt(yearY);
|
|
417
|
+
return result;
|
|
418
|
+
};
|
|
419
|
+
/** @param {number} year @returns {number} */
|
|
420
|
+
const deltaTSeconds = (year) => C.earthOrbital.deltaTStart + meanDeltaTSecondsAtAge(yearToTMa(year));
|
|
421
|
+
|
|
422
|
+
// ── Planets: Fibonacci-law derivation chain + orientation ─────────────────
|
|
423
|
+
const PLANET_KEYS = ['mercury', 'venus', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune'];
|
|
424
|
+
const massEarthAlone = GM_EARTH_ALONE / G_CONSTANT;
|
|
425
|
+
/** @type {Record<string, number>} */
|
|
426
|
+
const massFraction = {};
|
|
427
|
+
for (const k of PLANET_KEYS) massFraction[k] = 1 / C.physicalConstants.massRatioDE440[k];
|
|
428
|
+
massFraction.earth = massEarthAlone / M_SUN;
|
|
429
|
+
|
|
430
|
+
const PSI = FL.computePsiConstant({
|
|
431
|
+
earthInvPlaneInclinationAmplitude: earthInclAmplitude,
|
|
432
|
+
massEarthAlone,
|
|
433
|
+
massSun: M_SUN,
|
|
434
|
+
});
|
|
435
|
+
const eccentricityAmplitudeK = FL.computeKConstant({
|
|
436
|
+
eccentricityAmplitude,
|
|
437
|
+
massEarthAlone,
|
|
438
|
+
massSun: M_SUN,
|
|
439
|
+
earthTiltMeanDeg: earthtiltMean,
|
|
440
|
+
});
|
|
441
|
+
const systemResetN = C.foundational.systemResetN;
|
|
442
|
+
const t2000 = 2000 - (balancedYear - systemResetN * H);
|
|
443
|
+
const balancedJD = startmodelJD - meanSolarYearDays * (startModelYearWithCorrection - balancedYear);
|
|
444
|
+
|
|
445
|
+
/** @param {[number, number]|null} frac @returns {number|null} */
|
|
446
|
+
const fractionToYears = (frac) => (frac === null ? null : (H * frac[0]) / frac[1]);
|
|
447
|
+
|
|
448
|
+
/** @type {Record<string, Record<string, any>>} */
|
|
449
|
+
const PLANET_RECORDS = {};
|
|
450
|
+
for (const k of PLANET_KEYS) {
|
|
451
|
+
const mp = C.planets[k];
|
|
452
|
+
const ar = C.planetOrbitalElements[k];
|
|
453
|
+
const ecl = /** @type {number} */ (fractionToYears(mp.perihelionEclipticFraction));
|
|
454
|
+
const axial = /** @type {number} */ (fractionToYears(mp.axialPrecessionFraction));
|
|
455
|
+
const obliquityCycle = fractionToYears(mp.obliquityCycleFraction)
|
|
456
|
+
?? Math.abs(1 / (1 / ecl - 1 / (H / 13)));
|
|
457
|
+
const wobble = FL.computeWobblePeriodYears(ecl, axial, H);
|
|
458
|
+
const il = FL.computeInclinationLaw({
|
|
459
|
+
fibonacciD: mp.fibonacciD,
|
|
460
|
+
massFrac: massFraction[k],
|
|
461
|
+
invPlaneInclinationJ2000: ar.invPlaneInclinationJ2000,
|
|
462
|
+
longitudePerihelion: ar.longitudePerihelion,
|
|
463
|
+
inclinationCycleAnchor: mp.inclinationCycleAnchor,
|
|
464
|
+
antiPhase: mp.antiPhase || false,
|
|
465
|
+
}, PSI);
|
|
466
|
+
const obliquityMean = FL.computeObliquityMeanSnapshot({
|
|
467
|
+
axialTiltJ2000: ar.axialTiltJ2000,
|
|
468
|
+
invPlaneInclinationAmplitude: il.amplitude,
|
|
469
|
+
perihelionEclipticYears: ecl,
|
|
470
|
+
}, obliquityCycle, { H, t2000 });
|
|
471
|
+
const el = FL.computeEccentricityLaw({
|
|
472
|
+
fibonacciD: mp.fibonacciD,
|
|
473
|
+
massFrac: massFraction[k],
|
|
474
|
+
solarYearInput: ar.solarYearInput,
|
|
475
|
+
orbitalEccentricityJ2000: ar.orbitalEccentricityJ2000,
|
|
476
|
+
antiPhase: mp.antiPhase || false,
|
|
477
|
+
}, {
|
|
478
|
+
kConstant: eccentricityAmplitudeK,
|
|
479
|
+
obliquityMeanDeg: obliquityMean,
|
|
480
|
+
wobblePeriodYears: wobble,
|
|
481
|
+
t2000,
|
|
482
|
+
meanSolarYearDays,
|
|
483
|
+
});
|
|
484
|
+
PLANET_RECORDS[k] = Object.freeze({
|
|
485
|
+
name: mp.name,
|
|
486
|
+
perihelionEclipticYears: ecl,
|
|
487
|
+
longitudePerihelion: ar.longitudePerihelion,
|
|
488
|
+
ascendingNodeCyclesIn8H: mp.ascendingNodeCyclesIn8H,
|
|
489
|
+
ascendingNodePeriod: -(8 * H) / mp.ascendingNodeCyclesIn8H,
|
|
490
|
+
axialPrecessionYears: axial,
|
|
491
|
+
obliquityCycle,
|
|
492
|
+
wobblePeriod: wobble,
|
|
493
|
+
fibonacciD: mp.fibonacciD,
|
|
494
|
+
antiPhase: mp.antiPhase || false,
|
|
495
|
+
ascendingNodeInvPlane: mp.ascendingNodeInvPlane,
|
|
496
|
+
inclinationCycleAnchor: mp.inclinationCycleAnchor,
|
|
497
|
+
invPlaneInclinationJ2000: ar.invPlaneInclinationJ2000,
|
|
498
|
+
invPlaneInclinationAmplitude: il.amplitude,
|
|
499
|
+
invPlaneInclinationMean: il.mean,
|
|
500
|
+
obliquityMean,
|
|
501
|
+
orbitalEccentricityJ2000: ar.orbitalEccentricityJ2000,
|
|
502
|
+
orbitalEccentricityAmplitude: el.amplitude,
|
|
503
|
+
orbitalEccentricityBase: el.base,
|
|
504
|
+
eccentricityPhaseJ2000: el.phaseJ2000,
|
|
505
|
+
solarYearInput: ar.solarYearInput,
|
|
506
|
+
axialTiltJ2000: ar.axialTiltJ2000,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** Perihelion longitude (linear lattice rate). @param {string} k @param {number} year @returns {number} */
|
|
511
|
+
const planetPerihelionDeg = (k, year) => {
|
|
512
|
+
const p = PLANET_RECORDS[k];
|
|
513
|
+
return (((p.longitudePerihelion + (360.0 * (year - 2000)) / p.perihelionEclipticYears) % 360) + 360) % 360;
|
|
514
|
+
};
|
|
515
|
+
/** Ascending node on the invariable plane. @param {string} k @param {number} year @returns {number} */
|
|
516
|
+
const planetAscNodeDeg = (k, year) => {
|
|
517
|
+
const p = PLANET_RECORDS[k];
|
|
518
|
+
return planetOrientation.ascendingNodeInvPlaneLinearAt({
|
|
519
|
+
ascendingNodeInvPlane: p.ascendingNodeInvPlane,
|
|
520
|
+
ascendingNodePeriod: p.ascendingNodePeriod,
|
|
521
|
+
perihelionEclipticYears: p.perihelionEclipticYears,
|
|
522
|
+
}, year);
|
|
523
|
+
};
|
|
524
|
+
/** Invariable-plane inclination (signed ICRF rate, scene year→JD axis). @param {string} k @param {number} year @returns {number} */
|
|
525
|
+
const planetInclinationDeg = (k, year) => {
|
|
526
|
+
const p = PLANET_RECORDS[k];
|
|
527
|
+
const jd = startmodelJD + (year - startmodelYear) * meanSolarYearDays;
|
|
528
|
+
const yearsSinceBalanced = (jd - balancedJD) / meanSolarYearDays;
|
|
529
|
+
return planetOrientation.invPlaneInclinationAt({
|
|
530
|
+
isEarth: false,
|
|
531
|
+
invPlaneInclinationJ2000: p.invPlaneInclinationJ2000,
|
|
532
|
+
invPlaneInclinationMean: p.invPlaneInclinationMean,
|
|
533
|
+
invPlaneInclinationAmplitude: p.invPlaneInclinationAmplitude,
|
|
534
|
+
inclinationCycleAnchor: p.inclinationCycleAnchor,
|
|
535
|
+
longitudePerihelion: p.longitudePerihelion,
|
|
536
|
+
perihelionEclipticYears: p.perihelionEclipticYears,
|
|
537
|
+
antiPhase: p.antiPhase,
|
|
538
|
+
}, yearsSinceBalanced, {
|
|
539
|
+
H,
|
|
540
|
+
yearsFromBalancedToJ2000: (startmodelJD - balancedJD) / meanSolarYearDays,
|
|
541
|
+
});
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// ── Time axis: exact JD ↔ model-year conversion ───────────────────────────
|
|
545
|
+
// The model's `year` inputs live on the SI axis (the axis the fits were
|
|
546
|
+
// anchored on — tools/lib `_jdToSIyear`): linear in SI 86400-s days from the
|
|
547
|
+
// model start, so the JD↔year map is closed-form and exact to double
|
|
548
|
+
// precision. Callers holding an exact JD (e.g. 2058768.5385006 TT) convert
|
|
549
|
+
// here and NEVER roll their own — a caller-side linear-vs-calendar mix once
|
|
550
|
+
// put the eclipse umbra twin 8 km off (§12h; ≤1 m once unified).
|
|
551
|
+
const siTropicalYearDays = meanTropicalYearJ2000Seconds / 86400;
|
|
552
|
+
/** Model year (SI axis) at a JD(TT). @param {number} jd @returns {number} */
|
|
553
|
+
const yearFromJD = (jd) => startModelYearWithCorrection + (jd - startmodelJD) / siTropicalYearDays;
|
|
554
|
+
/** JD(TT) at a model year (SI axis). @param {number} year @returns {number} */
|
|
555
|
+
const jdFromYear = (year) => startmodelJD + (year - startModelYearWithCorrection) * siTropicalYearDays;
|
|
556
|
+
|
|
557
|
+
// ── Lunar theory: the shared-chain assembly (§7a slice-2b) ────────────────
|
|
558
|
+
// Wiring mirrors the engine call sites exactly — tools/lib/deep-time.js
|
|
559
|
+
// (ecc channel, month chain, J2000 precession anchors),
|
|
560
|
+
// tools/lib/scene-graph.js (chain-cycles, arguments, series) and
|
|
561
|
+
// tools/verify/eclipse-audit.js (finders). Deep-time and framework-native
|
|
562
|
+
// are hardwired ON here: they are the shipped defaults; the A/B env
|
|
563
|
+
// toggles (SG_DEEP_TIME/MOON_ARGS_PURE_MEEUS) stay an engine concern.
|
|
564
|
+
const j2000JD = 2451545.0;
|
|
565
|
+
const julianCenturyDays = 36525;
|
|
566
|
+
|
|
567
|
+
// J2000 Moon precession anchors (Option C+ — of-date observational anchors
|
|
568
|
+
// in the legacy-'ICRF'-named inputs; the E values are star-referenced ∓13)
|
|
569
|
+
const nApsidalIJ2000 = Math.round((8 * totalDaysInH) / C.moonReference.moonApsidalPrecessionDaysInputICRF) / 8;
|
|
570
|
+
const nNodalIJ2000 = Math.round((8 * totalDaysInH) / C.moonReference.moonNodalPrecessionDaysInputICRF) / 8;
|
|
571
|
+
const nApsidalEJ2000 = nApsidalIJ2000 - 13;
|
|
572
|
+
const nNodalEJ2000 = nNodalIJ2000 + 13;
|
|
573
|
+
const moonApsidalJ2000Seconds = (totalDaysInH / nApsidalEJ2000) * meanLengthOfDay;
|
|
574
|
+
const moonNodalJ2000Seconds = (totalDaysInH / nNodalEJ2000) * meanLengthOfDay;
|
|
575
|
+
const moonSiderealMonthJ2000Seconds = moonSiderealMonth * meanLengthOfDay;
|
|
576
|
+
|
|
577
|
+
// 8H-lattice derived months (constants.js §Moon derived months)
|
|
578
|
+
const nSid = Math.round((8 * totalDaysInH) / moonSiderealMonthInput) / 8;
|
|
579
|
+
const moonTropicalMonthDays = totalDaysInH / (nSid + 13);
|
|
580
|
+
const moonAnomalisticMonthDays = totalDaysInH / (nSid - nApsidalEJ2000);
|
|
581
|
+
const moonSynodicMonthDays = totalDaysInH / (nSid + 13 - H);
|
|
582
|
+
|
|
583
|
+
// The framework H/3 eccentricity line (the Moon channel's view of the
|
|
584
|
+
// wobble movement — NOT the H/16 orbital eccentricity above)
|
|
585
|
+
const moonEcc = createMoonEccChannel({
|
|
586
|
+
cyclesBetween,
|
|
587
|
+
eccentricityBase,
|
|
588
|
+
perihelionLongitudeJ2000Deg: C.earthOrbital.earthPerihelionLongitudeJ2000,
|
|
589
|
+
inclinationCycleAnchorDeg: C.earthOrbital.earthInclinationCycleAnchor,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Layer-2 month/precession chain (Brouwer-Clemence m² scaling × the
|
|
593
|
+
// e_E-line modulation)
|
|
594
|
+
const moonChain = createMoonMonthChain({
|
|
595
|
+
constants: {
|
|
596
|
+
aMoonNowMetres: EPOCH_PARAMS.moonDistanceNowM,
|
|
597
|
+
alpha1PerMa: EPOCH_PARAMS.alpha1PerMa,
|
|
598
|
+
alpha3PerMa3: EPOCH_PARAMS.alpha3PerMa3,
|
|
599
|
+
alpha4PerMa4: EPOCH_PARAMS.alpha4PerMa4,
|
|
600
|
+
gmEarthMoonM3PerS2: EPOCH_PARAMS.gmEarthMoonM3S2,
|
|
601
|
+
massRatioEarthMoon: MASS_RATIO_EARTH_MOON,
|
|
602
|
+
moonSiderealMonthInputDays: moonSiderealMonthInput,
|
|
603
|
+
holisticYearJ2000: H,
|
|
604
|
+
meanSiderealYearJ2000Seconds: meanSiderealYearSeconds,
|
|
605
|
+
nApsidalOfDateJ2000: nApsidalIJ2000,
|
|
606
|
+
nNodalOfDateJ2000: nNodalIJ2000,
|
|
607
|
+
moonApsidalJ2000Seconds,
|
|
608
|
+
moonNodalJ2000Seconds,
|
|
609
|
+
moonSiderealMonthJ2000Seconds,
|
|
610
|
+
sPerigee: MOON_ECC_SENSITIVITY_PERIGEE,
|
|
611
|
+
sNode: MOON_ECC_SENSITIVITY_NODE,
|
|
612
|
+
},
|
|
613
|
+
fns: {
|
|
614
|
+
meanLodSecondsAtAge: /** @param {number} tMa */ (tMa) => deepLod.lodSecondsAtAge(tMa),
|
|
615
|
+
meanSiderealYearSecondsAtAge: /** @param {number} tMa */ (tMa) => deepLod.siderealYearSecondsAtAge(tMa),
|
|
616
|
+
meanHAtAge: /** @param {number} tMa */ (tMa) => deepLod.hAtAge(tMa),
|
|
617
|
+
modulation: /** @param {number} tMa @param {number} s */ (tMa, s) => moonEcc.modulation(tMa, s),
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// Chain-cycle integrator. S5/S12 conventions: age anchor = startmodelYear
|
|
622
|
+
// (the scene's t_Ma convention), grid anchor C(2000) = 0 — grid anchor ≠
|
|
623
|
+
// age anchor, deliberately. One stable period fn per chain so the shared
|
|
624
|
+
// Float64Array tables key correctly and persist.
|
|
625
|
+
const chainCycles = createChainCycleIntegrator({
|
|
626
|
+
ageAnchorYear: startmodelYear,
|
|
627
|
+
tropicalYearSecondsAtAge: /** @param {number} tMa */ (tMa) => deepLod.tropicalYearSecondsAtAge(tMa),
|
|
628
|
+
tropicalYearJ2000Seconds: meanTropicalYearJ2000Seconds,
|
|
629
|
+
isDeepTime: () => true,
|
|
630
|
+
});
|
|
631
|
+
/** @param {number} tMa @returns {number|null} */
|
|
632
|
+
const nodalMonthPeriodFn = (tMa) => moonChain.nodalMonthSecondsAtAge(tMa);
|
|
633
|
+
/** @param {number} tMa @returns {number|null} */
|
|
634
|
+
const tropicalMonthPeriodFn = (tMa) => moonChain.tropicalMonthSecondsAtAge(tMa);
|
|
635
|
+
/** @param {number} tMa @returns {number|null} */
|
|
636
|
+
const anomalisticMonthPeriodFn = (tMa) => moonChain.anomalisticMonthSecondsAtAge(tMa);
|
|
637
|
+
const jupiterT0Seconds = C.planetOrbitalElements.jupiter.solarYearInput * 86400;
|
|
638
|
+
/** @param {number} tMa @returns {number} */
|
|
639
|
+
const jupiterPeriodFn = (tMa) => driver2PeriodSecondsAtAge(tMa, jupiterT0Seconds, EPOCH_PARAMS.solarMassLossFracPerYear);
|
|
640
|
+
/** @param {number} a @param {number} b @returns {number|null} */
|
|
641
|
+
const mcDraconic = (a, b) => chainCycles.cyclesBetween(nodalMonthPeriodFn, a, b);
|
|
642
|
+
/** @param {number} a @param {number} b @returns {number|null} */
|
|
643
|
+
const mcTropical = (a, b) => chainCycles.cyclesBetween(tropicalMonthPeriodFn, a, b);
|
|
644
|
+
/** @param {number} a @param {number} b @returns {number|null} */
|
|
645
|
+
const mcAnomalistic = (a, b) => chainCycles.cyclesBetween(anomalisticMonthPeriodFn, a, b);
|
|
646
|
+
/** @param {number} a @param {number} b @returns {number|null} */
|
|
647
|
+
const mcJupiter = (a, b) => chainCycles.cyclesBetween(jupiterPeriodFn, a, b);
|
|
648
|
+
/** @param {number} a @param {number} b @returns {number|null} */
|
|
649
|
+
const mcApsidalOfDate = (a, b) => {
|
|
650
|
+
const t = mcTropical(a, b), n = mcAnomalistic(a, b);
|
|
651
|
+
return (t === null || n === null) ? null : t - n;
|
|
652
|
+
};
|
|
653
|
+
/** @param {number} a @param {number} b @returns {number|null} */
|
|
654
|
+
const mcNodalOfDate = (a, b) => {
|
|
655
|
+
const dr = mcDraconic(a, b), t = mcTropical(a, b);
|
|
656
|
+
return (dr === null || t === null) ? null : dr - t;
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// Snapshot-phase obliquity — the engine's computeObliquityEarth convention
|
|
660
|
+
// for the lunar chain (linear H-lattice phase; orbital-engine.js). NOT the
|
|
661
|
+
// integrated-phase display obliquity above — the chain was certified
|
|
662
|
+
// against this form.
|
|
663
|
+
/** @param {number} year @returns {number} */
|
|
664
|
+
const obliquitySnapshotDeg = (year) => {
|
|
665
|
+
const t = year - balancedYear;
|
|
666
|
+
let obliq = solsticeObliquityMean;
|
|
667
|
+
for (const [div, sinC, cosC] of F.SOLSTICE_OBLIQUITY_HARMONICS) {
|
|
668
|
+
const ph = (2 * Math.PI * t) / (H / div);
|
|
669
|
+
obliq += sinC * Math.sin(ph) + cosC * Math.cos(ph);
|
|
670
|
+
}
|
|
671
|
+
return obliq;
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// The argument skeleton (the _FW_MOON bundle; Sun secular deviations on
|
|
675
|
+
// the CALENDAR year coordinate — S3)
|
|
676
|
+
const moonArgs = createMoonArguments({
|
|
677
|
+
constants: {
|
|
678
|
+
j2000JD,
|
|
679
|
+
julianCenturyDays,
|
|
680
|
+
holisticYearJ2000: H,
|
|
681
|
+
balancedYearJ2000: balancedYear,
|
|
682
|
+
meanSolarYearDays,
|
|
683
|
+
meanAnomalisticYearDays,
|
|
684
|
+
tropicalYearHarmonics: F.TROPICAL_YEAR_HARMONICS,
|
|
685
|
+
anomalisticYearHarmonics: F.ANOMALISTIC_YEAR_HARMONICS,
|
|
686
|
+
eccentricityJ2000: C.earthOrbital.earthEccentricityJ2000,
|
|
687
|
+
eccentricityDotJ2000: C.earthOrbital.earthEccentricityDotJ2000,
|
|
688
|
+
eccentricityDotDotJ2000: C.earthOrbital.earthEccentricityDotDotJ2000,
|
|
689
|
+
elpEarthFigureJ2ArcsecPerCy2: C.moonMeeus.elpW1T2Decomposition_arcsecPerCy2.earthFigureJ2,
|
|
690
|
+
elpGeneralPrecessionPA_T2ArcsecPerCy2: C.moonMeeus.elpW1T2Decomposition_arcsecPerCy2.generalPrecessionPA_T2_Lieske1976,
|
|
691
|
+
eccE0: moonEcc.e0,
|
|
692
|
+
},
|
|
693
|
+
fns: {
|
|
694
|
+
eccAt: /** @param {number} tYr */ (tYr) => moonEcc.eccAt(tYr),
|
|
695
|
+
channelIntegral: /** @param {number} T @param {number} s */ (T, s) => moonEcc.channelIntegral(T, s),
|
|
696
|
+
computeObliquityEarth: obliquitySnapshotDeg,
|
|
697
|
+
jdToSIyear: yearFromJD,
|
|
698
|
+
tropicalOrbitsBetween: mcTropical,
|
|
699
|
+
apsidalOfDateCyclesBetween: mcApsidalOfDate,
|
|
700
|
+
nodalOfDateCyclesBetween: mcNodalOfDate,
|
|
701
|
+
cyclesBetween,
|
|
702
|
+
isDeepTime: () => true,
|
|
703
|
+
isFrameworkNative: () => true,
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// UT→TT on the CALENDAR decimal-year coordinate (script.js Phase 9.16 —
|
|
708
|
+
// a linear-year approximation here once cost ~5–6 s of ΔT and ~1e-3° of
|
|
709
|
+
// Moon longitude at the Babylonian epochs)
|
|
710
|
+
/** @param {number} jd @returns {number} */
|
|
711
|
+
const jdTTFromUT = (jd) => {
|
|
712
|
+
const tMa = (startmodelYear - jdToDecimalYear(jd)) / 1e6;
|
|
713
|
+
const dT = meanDeltaTSecondsAtAge(tMa);
|
|
714
|
+
return Number.isFinite(dT) ? jd + dT / 86400 : jd;
|
|
715
|
+
};
|
|
716
|
+
// ΔT at a JD on the CALENDAR-year axis (mirrors deep-time frameworkDeltaT;
|
|
717
|
+
// NOT deltaTSeconds above, which adds the deltaTStart anchor)
|
|
718
|
+
/** @param {number} jd @returns {number} */
|
|
719
|
+
const frameworkDeltaTSecondsAtJD = (jd) => {
|
|
720
|
+
const decYear = startmodelYear + (jd - startmodelJD) / meanSolarYearDays;
|
|
721
|
+
const dT = meanDeltaTSecondsAtAge((startmodelYear - decYear) / 1e6);
|
|
722
|
+
return Number.isFinite(dT) ? dT : 0;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// Bounded Meeus E-factor from the H/3 line (framework-native branch only;
|
|
726
|
+
// the pure-Meeus polynomial A/B branch stays engine-local)
|
|
727
|
+
/** @param {number} dDays @returns {number} */
|
|
728
|
+
const fwEFactor = (dDays) => moonEcc.eFactorAt(dDays / C.foundational.inputmeanlengthsolaryearindays);
|
|
729
|
+
|
|
730
|
+
// D2 derived additional-argument rates (deg/cy, J2000 8H-lattice months;
|
|
731
|
+
// record: tools/explore/derive-a1a2a3.js)
|
|
732
|
+
const fwA2RateDegPerCy = 2 * ((360 * 36525) / moonTropicalMonthDays)
|
|
733
|
+
- (360 * 36525) / moonAnomalisticMonthDays
|
|
734
|
+
- 2 * ((360 * 36525) / C.planetOrbitalElements.jupiter.solarYearInput);
|
|
735
|
+
const fwA3RateDegPerCy = (360 * 36525) / moonSiderealMonth;
|
|
736
|
+
|
|
737
|
+
// Meeus Ch. 47 truncated series (framework-native arguments + E-factor)
|
|
738
|
+
const moonSeries = createMoonSeries({
|
|
739
|
+
constants: {
|
|
740
|
+
moonL: F.MEEUS_LONGITUDE_TERMS,
|
|
741
|
+
moonB: F.MEEUS_LATITUDE_TERMS,
|
|
742
|
+
j2000JD,
|
|
743
|
+
julianCenturyDays,
|
|
744
|
+
moonMeeusLpCorrectionDeg: C.moon.moonMeeusLpCorrection,
|
|
745
|
+
fwA2RateDegPerCy,
|
|
746
|
+
fwA3RateDegPerCy,
|
|
747
|
+
},
|
|
748
|
+
fns: {
|
|
749
|
+
argsAt: /** @param {number} jdTT */ (jdTT) => moonArgs.argsAt(jdTT),
|
|
750
|
+
eFactorForD: fwEFactor,
|
|
751
|
+
eFactorAtJdTT: /** @param {number} jdTT */ (jdTT) => fwEFactor(jdTT - j2000JD),
|
|
752
|
+
getMoonDistanceKm: () => moonDistanceKm,
|
|
753
|
+
getEccentricityBase: () => C.moonReference.moonOrbitalEccentricityBase,
|
|
754
|
+
deltaTSeconds: /** @param {number} jd */ (jd) => (jdTTFromUT(jd) - jd) * 86400,
|
|
755
|
+
jdToSIyear: yearFromJD,
|
|
756
|
+
tropicalOrbitsBetween: mcTropical,
|
|
757
|
+
apsidalOfDateCyclesBetween: mcApsidalOfDate,
|
|
758
|
+
cyclesBetween,
|
|
759
|
+
jupiterOrbitsBetween: mcJupiter,
|
|
760
|
+
isDeepTime: () => true,
|
|
761
|
+
isFrameworkNative: () => true,
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
// Eclipse finders — wired like the engine probe (tools/verify/
|
|
766
|
+
// eclipse-audit.js). The finder axis is JD(UT): the series wrapper applies
|
|
767
|
+
// UT→TT internally. Ground-track/umbra paths deliberately absent: the
|
|
768
|
+
// scene-umbra projection navigates the Tychosium-derived scaffold, which
|
|
769
|
+
// never enters this package (§2h).
|
|
770
|
+
const eclipseFinders = createEclipseFinders({
|
|
771
|
+
moonLonDegAt: /** @param {number} jd */ (jd) => moonSeries.truncatedLonDeg(jd),
|
|
772
|
+
moonBetaDegAt: /** @param {number} jd */ (jd) => moonSeries.truncatedBetaDeg(jd),
|
|
773
|
+
moonDistanceKmAt: /** @param {number} jd */ (jd) => moonSeries.truncatedDistanceKm(jd),
|
|
774
|
+
deltaTSecondsAt: frameworkDeltaTSecondsAtJD,
|
|
775
|
+
getSynodicMonthDays: () => moonSynodicMonthDays,
|
|
776
|
+
getSunDistanceKm: () => currentAUDistance,
|
|
777
|
+
constants: {
|
|
778
|
+
rEarthMetres: (C.bodyDiametersKm.earth / 2) * 1000,
|
|
779
|
+
moonDiameterKm: C.bodyDiametersKm.moon,
|
|
780
|
+
sunDiameterKm: C.bodyDiametersKm.sun,
|
|
781
|
+
j2000JD,
|
|
782
|
+
julianCenturyDays,
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// ── The assembled surface ─────────────────────────────────────────────────
|
|
787
|
+
return Object.freeze({
|
|
788
|
+
time: Object.freeze({
|
|
789
|
+
yearFromJD,
|
|
790
|
+
jdFromYear,
|
|
791
|
+
siTropicalYearDays,
|
|
792
|
+
}),
|
|
793
|
+
epoch: Object.freeze({
|
|
794
|
+
yearToTMa,
|
|
795
|
+
hAtYear: /** @param {number} year @returns {number|null} */ (year) => deepLod.hAtAge(yearToTMa(year)),
|
|
796
|
+
lodSecondsAtYear: /** @param {number} year @returns {number|null} */ (year) => deepLod.lodSecondsAtAge(yearToTMa(year)),
|
|
797
|
+
alphaAtYear: /** @param {number} year @returns {number} */ (year) => earthMoiFactorAtAge(yearToTMa(year)),
|
|
798
|
+
moonDistanceKmAtYear: /** @param {number} year @returns {number} */ (year) => moonDistanceMetresAtAge(yearToTMa(year)) / 1000,
|
|
799
|
+
siderealYearSecondsAtYear: /** @param {number} year @returns {number} */ (year) => deepLod.siderealYearSecondsAtAge(yearToTMa(year)),
|
|
800
|
+
deltaTSecondsAtYear: deltaTSeconds,
|
|
801
|
+
cyclesBetween,
|
|
802
|
+
}),
|
|
803
|
+
earth: Object.freeze({
|
|
804
|
+
perihelionLongitudeDeg: earthPerihelionDeg,
|
|
805
|
+
obliquityDeg,
|
|
806
|
+
eccentricity: eccentricityAt,
|
|
807
|
+
inclinationDeg,
|
|
808
|
+
ascendingNodeDeg,
|
|
809
|
+
}),
|
|
810
|
+
lengths: Object.freeze({
|
|
811
|
+
tropicalYearDays,
|
|
812
|
+
tropicalYearDirectDays,
|
|
813
|
+
siderealYearDays,
|
|
814
|
+
anomalisticYearDays,
|
|
815
|
+
dayLengthSeconds,
|
|
816
|
+
siderealDaySeconds,
|
|
817
|
+
stellarDaySeconds,
|
|
818
|
+
measuredSolarDaySeconds,
|
|
819
|
+
raDayOffsetMs,
|
|
820
|
+
}),
|
|
821
|
+
cardinal: Object.freeze({
|
|
822
|
+
jd: /** @param {number} year @param {string} type @returns {number} */ (year, type) => cardinalM.computeSolsticeJD(year, type),
|
|
823
|
+
raDeg: /** @param {number} year @param {string} type @returns {number} */ (year, type) => {
|
|
824
|
+
const ra = cardinalM.computeSolsticeRA(year, type);
|
|
825
|
+
return ((ra % 360) + 360) % 360;
|
|
826
|
+
},
|
|
827
|
+
yearLengthDays: /** @param {number} year @param {string} type @returns {number} */ (year, type) => cardinalM.computeSolsticeYearLength(year, type),
|
|
828
|
+
}),
|
|
829
|
+
moon: Object.freeze({
|
|
830
|
+
distanceKmAtYear: /** @param {number} year @returns {number} */ (year) => moonDistanceMetresAtAge(yearToTMa(year)) / 1000,
|
|
831
|
+
siderealMonthDaysAtYear: moonSiderealMonthDaysAt,
|
|
832
|
+
synodicMonthDays: moonSynodicMonthDays,
|
|
833
|
+
// The apparent-position chain (truncated Meeus Ch. 47 series on
|
|
834
|
+
// framework-native arguments). JD(UT) axis — UT→TT applied internally.
|
|
835
|
+
lonDegAtJD: /** @param {number} jd @returns {number} */ (jd) => moonSeries.truncatedLonDeg(jd),
|
|
836
|
+
betaDegAtJD: /** @param {number} jd @returns {number} */ (jd) => moonSeries.truncatedBetaDeg(jd),
|
|
837
|
+
distanceKmAtJD: /** @param {number} jd @returns {number} */ (jd) => moonSeries.truncatedDistanceKm(jd),
|
|
838
|
+
}),
|
|
839
|
+
eclipse: Object.freeze({
|
|
840
|
+
sunLonDegAtJD: /** @param {number} jd @returns {number} */ (jd) => eclipseFinders.sunLonDegAt(jd),
|
|
841
|
+
findLunarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findLunarEclipsesInRange(jdStart, jdEnd),
|
|
842
|
+
findSolarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findSolarEclipsesInRange(jdStart, jdEnd),
|
|
843
|
+
deltaTSecondsAtJD: frameworkDeltaTSecondsAtJD,
|
|
844
|
+
}),
|
|
845
|
+
climate: Object.freeze({
|
|
846
|
+
l1OrbitalPermil: evalClimateL1,
|
|
847
|
+
}),
|
|
848
|
+
planets: Object.freeze({
|
|
849
|
+
keys: Object.freeze([...PLANET_KEYS]),
|
|
850
|
+
record: /** @param {string} k @returns {Record<string, any>|undefined} */ (k) => PLANET_RECORDS[k],
|
|
851
|
+
perihelionLongitudeDeg: planetPerihelionDeg,
|
|
852
|
+
ascendingNodeInvPlaneDeg: planetAscNodeDeg,
|
|
853
|
+
invPlaneInclinationDeg: planetInclinationDeg,
|
|
854
|
+
}),
|
|
855
|
+
});
|
|
856
|
+
}
|