@essrt/physics 25.0.1 → 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 CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@essrt/physics",
3
- "version": "25.0.1",
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
- "modelVersion": "v27.0",
6
+ "modelVersion": "v27.1",
7
7
  "_note": "The model identity this package version ships (IP-unified-architecture §10: package semver tracks the API axis; the model version + CONSTANTS_HASH/COEFFICIENTS_HASH in src/constants/ carry the scientific identity). The version-pinning gate enforces this pairing against public/input/model-version.json."
8
8
  },
9
9
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  "./math/angles": { "types": "./types/math/angles.d.cts", "default": "./src/math/angles.cjs" },
40
40
  "./cardinal/one-source-structure": { "types": "./types/cardinal/one-source-structure.d.cts", "default": "./src/cardinal/one-source-structure.cjs" },
41
41
  "./moon/deep-ecc-channel": { "types": "./types/moon/deep-ecc-channel.d.cts", "default": "./src/moon/deep-ecc-channel.cjs" },
42
+ "./moon/ecc-sensitivities": { "types": "./types/moon/ecc-sensitivities.d.cts", "default": "./src/moon/ecc-sensitivities.cjs" },
42
43
  "./moon/deep-modes-artifact": { "types": "./types/moon/deep-modes-artifact.d.cts", "default": "./src/moon/deep-modes-artifact.cjs" },
43
44
  "./earth/deep-orbital-history": { "types": "./types/earth/deep-orbital-history.d.cts", "default": "./src/earth/deep-orbital-history.cjs" },
44
45
  "./earth/sidereal-year-channel": { "types": "./types/earth/sidereal-year-channel.d.cts", "default": "./src/earth/sidereal-year-channel.cjs" },
@@ -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) grown lazily in 250-kyr chunks in the direction
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 the same
140
- // linear interpolation. The period laws vary on Myr scales at deep time, so
141
- // 1-kyr cells hold the count to far below a cycle — the old fallback sampled
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 idxF = distance / COARSE_STEP;
182
- const i = Math.floor(idxF);
183
- return side.cum[i] + (idxF - i) * (side.cum[i + 1] - side.cum[i]);
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} */
@@ -39,7 +39,7 @@
39
39
  export const CONSTANTS_HASH = "da713fc2d846767d";
40
40
 
41
41
  /** Model version label — single source: public/input/model-version.json (§10 two-axis scheme). */
42
- export const MODEL_VERSION = "v27.0";
42
+ export const MODEL_VERSION = "v27.1";
43
43
 
44
44
  /** Canonical preprint DOI — single source: public/input/model-version.json. */
45
45
  export const PREPRINT_DOI = "10.21203/rs.3.rs-8758810/v4";
@@ -43,14 +43,9 @@ import { createMoonMonthChain } from '../moon/month-chain.cjs';
43
43
  import { massLossScaledLinearAtAge, driver2PeriodSecondsAtAge, synodicPeriodSeconds } from '../planets/orbit-chain.cjs';
44
44
  import { computeMassChainJ2000, computeMoonPeriodsJ2000 } from '../earth/mass-chain.cjs';
45
45
 
46
- /**
47
- * Moon-channel eccentricity sensitivities (perigee/node), the [g/g₀]^s
48
- * exponents of the factored deep-time law — Meeus-effective (S_N moved
49
- * 1.0 → 1.018 with the v4 frame-attribution batch). ONE home since B2 (they
50
- * were literals in model.js, tools/lib/deep-time.js and script.js `_FW_MOON`).
51
- */
52
- export const MOON_ECC_SENSITIVITY_PERIGEE = 2.407;
53
- export const MOON_ECC_SENSITIVITY_NODE = 1.018;
46
+ // The lunar perigee/node eccentricity sensitivities — ONE home moon/ecc-sensitivities.cjs
47
+ // (plan 23): the Meeus-effective pair and the PHYSICAL pair derived from it there.
48
+ import { deriveMoonEccSensitivities } from '../moon/ecc-sensitivities.cjs';
54
49
 
55
50
  /**
56
51
  * The ΔT stack's switches. `resonator` gates the swing's δLOD terms; the
@@ -127,6 +122,18 @@ export function createDeepTimeChain(deps) {
127
122
  /** p₀, ″/yr. @returns {number} */
128
123
  const precessionRateJ2000ArcsecPerYr = () => 1296000 / certifiedAxialPrecessionJ2000Years();
129
124
 
125
+ // The lunar perigee/node eccentricity sensitivities (plan 23): the Meeus-effective pair and the
126
+ // PHYSICAL pair derived from it by the T² identity (moon/ecc-sensitivities.cjs, the ONE home).
127
+ // The month chain and the arguments module run the physical pair; the frame share the effective
128
+ // pair carried rides the model's own precession of date instead (the arguments' pFix, on L′, ϖ
129
+ // and Ω alike), so the frame-free arguments M′ and F cancel it at every epoch.
130
+ const moonEccSensitivities = deriveMoonEccSensitivities({
131
+ eccentricityJ2000: C.earthOrbital.earthEccentricityJ2000,
132
+ eccentricityDotJ2000: C.earthOrbital.earthEccentricityDotJ2000,
133
+ frameT2ArcsecPerCy2: C.moonMeeus.elpW1T2Decomposition_arcsecPerCy2.generalPrecessionPA_T2_Lieske1976,
134
+ generalPrecessionArcsecPerYr: C.earthOrbital.generalPrecessionJ2000_arcsecPerYr,
135
+ });
136
+
130
137
  // ── α(t): the GIA channel (lagged L1 response), the memo and the lattice pin ──
131
138
  const CLIMATE_REGIME = F.CLIMATE_FORMULA_COEFFS.regimes['lr04-post-mpt'];
132
139
  const alphaGia = createAlphaGiaChannel({
@@ -308,7 +315,8 @@ export function createDeepTimeChain(deps) {
308
315
  moonApsidalJ2000Seconds,
309
316
  moonNodalJ2000Seconds,
310
317
  moonSiderealMonthJ2000Seconds,
311
- sPerigee: MOON_ECC_SENSITIVITY_PERIGEE, sNode: MOON_ECC_SENSITIVITY_NODE,
318
+ // plan 23: the PHYSICAL pair in the channel; the frame share rides the arguments' pFix
319
+ sPerigee: moonEccSensitivities.perigeePhysical, sNode: moonEccSensitivities.nodePhysical,
312
320
  },
313
321
  fns: {
314
322
  meanLodSecondsAtAge,
@@ -405,7 +413,7 @@ export function createDeepTimeChain(deps) {
405
413
  EPOCH_PARAMS, ageAnchorYear,
406
414
  // the pieces
407
415
  alphaGia, earthMoiFactorAtAge, withLatticeAlpha, j2RatioAtYear, iEarthAtAge,
408
- recession, meanObliquity, dtCycles, s3ArcsecPerYr, deepLod, l0, deepEcc, eCompModulation, moonChain,
416
+ recession, meanObliquity, dtCycles, s3ArcsecPerYr, deepLod, l0, deepEcc, eCompModulation, moonChain, moonEccSensitivities,
409
417
  // the family
410
418
  meanMoonDistanceMetresAtAge: recession.distanceMetresAtAge,
411
419
  meanMoonDistanceAtAge: moonChain.distanceKmAtAge,
@@ -35,11 +35,10 @@
35
35
  * the derived series-extension tail — deliberate for the certified
36
36
  * finder statistics (knife-edge canon events), required here. The
37
37
  * injected moonFullAtDaysTT is the series' sceneEvalAt — the same
38
- * evaluation the browser scene ships (the extension inside it since
39
- * plan 06 R3 item 1: one scene Moon).
38
+ * evaluation the browser scene ships (the extension inside it: one scene
39
+ * Moon).
40
40
  *
41
- * APPARENT PLACES (plan 06 R3 item 1 — measured, the cancellation the I2
42
- * Sun exposed): both bodies are evaluated mean-of-date (nutation is a common
41
+ * APPARENT PLACES (measured — the cancellation the I2 Sun exposed): both bodies are evaluated mean-of-date (nutation is a common
43
42
  * rotation of Sun and Moon and cancels in the elongation) but with their
44
43
  * PLANETARY ABERRATION: the Sun's derived κ = 2π a/(c T_sid √(1−e²))
45
44
  * (≈20.5″, the light-time of a source at rest in the barycentric frame) and
@@ -131,8 +130,8 @@ function createBesselian(deps) {
131
130
  }
132
131
 
133
132
  /** Elliptical Sun distance — FRAMEWORK-NATIVE: the model's own
134
- * eccentricity (Law-5-locked base + drift) and perihelion longitude
135
- * (H/16 precession) close the ellipse, with the true anomaly from the
133
+ * eccentricity and perihelion longitude of date close the ellipse, with
134
+ * the true anomaly from the
136
135
  * already-computed sun longitude (v = λ − (ϖ + 180°), geocentric-perigee
137
136
  * convention). Agrees with the Meeus Ch. 25 polynomial to ±0.001% over a
138
137
  * year, with no fitted external coefficients, and stays honest at deep
@@ -68,8 +68,7 @@
68
68
  * inconsistency for elongation. Removed: the nutation-free syzygy
69
69
  * fleet improves 3.877 → 3.756″ and the fuller-bridged Sun
70
70
  * comparison 2.344 → 2.123″ (measured by the fq7s preview and
71
- * annual-channel instruments, deleted at plan 09 step 7e — docs/109
72
- * §22). This file now carries ZERO fitted
71
+ * annual-channel instruments — docs/109 §22). This file carries ZERO fitted
73
72
  * constants. The Earth-around-EMB wobble is DERIVED: amplitude
74
73
  * a_M·μ/AU with μ = 1/(1+M_E/M_M), injected by the model wiring
75
74
  * from live package constants (6.4399″ at current constants), sign
@@ -143,8 +142,8 @@ const ARG_D0 = 297.8501921;
143
142
  * D2 window (the term's in-window ramp went to the projected-out secular
144
143
  * basis; the 240-yr term folded into it). WHY they were missing: the
145
144
  * certified Sun integrates a smooth tropical year for its mean longitude;
146
- * against Horizons its residual carried exactly this 6″ ripple (plan 06 I1
147
- * analysis) — with the same phase in the sidereal residual, a perturbation
145
+ * against Horizons its residual carried exactly this 6″ ripple — with the
146
+ * same phase in the sidereal residual, a perturbation
148
147
  * of Earth's mean motion, not a frame effect. Sub-0.2″ candidates left out
149
148
  * (2J−5S 0.17″, 5V−8E 0.17″: window-dependent phase). The CARRIERS moved
150
149
  * with I3 from the planet records' rounded of-date periods minus p₀ to the
@@ -225,7 +224,7 @@ const TERMS = [
225
224
  [[0, 0, 1, -1, 0, 0], [0, 0, 1, 0, 0, 0], 0.0188, 0.0523],
226
225
  [[0, 2, -2, 0, 0, 0], [0, 0, 1, 0, 0, 0], -0.0421, -0.0340],
227
226
  [[0, 0, 1, -1, 0, 0], [0, 0, -1, 0, 0, 0], -0.0353, -0.0379],
228
- // plan 06 I3 — the Venus–Earth term 8λ_V − 13λ_E (≈239 yr on the banked
227
+ // The Venus–Earth term 8λ_V − 13λ_E (≈239 yr on the banked
229
228
  // carriers): a CONSTANT row, derived on the model's own run over ±3100 yr
230
229
  // (i3-long-period-dalembert.mjs `2 20000 3100 4`; the I2 row read 1.84/0.14″
231
230
  // on the record carriers over −5100..+1100). The term is formally FIFTH order
@@ -394,13 +393,9 @@ function createSunPlanetaryCompletion({ embWobbleArcsec, carrierRatesDegPerCy, e
394
393
  return { sunPlanetaryCompletionDeg };
395
394
  }
396
395
 
397
- /* (Plan 09 step 5a: PAIRED_SUN_HARMONICS_SHA256 — the sha256/16 of the Step-0
398
- * SUN_LONGITUDE_HARMONICS this table was derived beside, last 53530a28a60b3c4a
399
- * (the R10 rotation of a comb never refit since the eccentricity unification)
400
- * — is retired with the comb. The comb left every Sun path at plan 06 R4, so
401
- * the residual the table was derived from is the certified Sun's; the table
402
- * stands (fidelity 0.616″, 70 terms) and the carrier-rate pair below remains
403
- * its enforced matched pair.) */
396
+ /* The table was derived from the certified Sun's residual and stands (fidelity
397
+ * 0.616″, 70 terms); the carrier-rate pair below is its enforced matched pair.
398
+ * (The retired paired-harmonics fingerprint: doc 109 §23.) */
404
399
 
405
400
  /** sha256/16 of JSON.stringify([...planets, moonElongation]) — the seven
406
401
  * full-precision carrier rates (deg/cy TT) the tables pair with: since plan
package/src/index.js CHANGED
@@ -108,7 +108,10 @@ export { DEG2RAD, RAD2DEG, ARCSEC_PER_TURN, ARCSEC_PER_RAD, wrap360, wrap180, wr
108
108
  // cited Moon-distance history, the ΔT cycle stack, |s₃|, the LOD/ΔT core, the
109
109
  // lunar month chain and the mean*AtAge family, assembled ONCE from the
110
110
  // generated constants; the Node twin and the browser destructure it.
111
- export { createDeepTimeChain, MOON_ECC_SENSITIVITY_PERIGEE, MOON_ECC_SENSITIVITY_NODE } from './deltat/deep-time-chain.js';
111
+ export { createDeepTimeChain } from './deltat/deep-time-chain.js';
112
+ // The lunar perigee/node eccentricity sensitivities — the Meeus-effective pair, the physical pair's
113
+ // derivation and the Meeus Ch. 47 argument rates, ONE home (plan 23).
114
+ export { MOON_ECC_SENSITIVITY_PERIGEE, MOON_ECC_SENSITIVITY_NODE, deriveMoonEccSensitivities, MEEUS_ARGUMENT_RATES_DEG_PER_CY } from './moon/ecc-sensitivities.cjs';
112
115
  // The J2000 mass chain, the Moon's J2000 periods and the unit constants — ONE home.
113
116
  export { computeMassChainJ2000, computeMoonPeriodsJ2000 } from './earth/mass-chain.cjs';
114
117
  export { SECONDS_PER_DAY, DAYS_PER_JULIAN_YEAR, JULIAN_CENTURY_DAYS, SECONDS_PER_JULIAN_YEAR } from './constants/units.cjs';
@@ -57,7 +57,7 @@ export function assembleCardinalInstants(ctx) {
57
57
  const CARDINAL_SI_YEAR_D = 365.2422;
58
58
  /** The crossing NEAREST a seed instant — the unique root within ±½ tropical
59
59
  * year of the seed (the wrapped longitude difference is the Newton residual).
60
- * Plan 06 R4c: the seed is a JD because the caller's year may live on a
60
+ * The seed is a JD because the caller's year may live on a
61
61
  * different axis than the model year — the simulator's cardinal panel
62
62
  * labels its year with the displayed CALENDAR date (the Julian calendar
63
63
  * before 1582, 365.25-d years) while `year` below is the model year (the SI
@@ -79,13 +79,12 @@ export function assembleCardinalInstants(ctx) {
79
79
  /** The TRUE-UT JD at which the APPARENT Sun's longitude of date equals
80
80
  * the cardinal target in the given year — Newton on the crossing (the
81
81
  * Sun's rate 360°/tropical year; a handful of steps from the tropical-year
82
- * seed). Replaces the fitted cardinal model's instants (R1). Plan 06 R3
83
- * item 2: the Newton root lives on the FINDER axis (the curve-only ΔT the
82
+ * seed). The Newton root lives on the FINDER axis (the curve-only ΔT the
84
83
  * eclipse finders certify), and the returned instant is that root minus the
85
84
  * deltaTStart bridge — true UT = TT − (deltaTStart + curve), the same
86
85
  * bridge the besselian and the registry instruments apply. Before this the
87
86
  * published instants were 54.55 s (0.9 min) late. Against Horizons over
88
- * ±3000 yr the remaining offset is the Sun's (plan 06 I2 measured).
87
+ * ±3000 yr the remaining offset is the Sun's (measured).
89
88
  * `year` is the MODEL year (the SI axis) — see cardinalCrossingNearJdUT.
90
89
  * @param {number} year @param {string} type @returns {number} */
91
90
  const cardinalCrossingJdUT = (year, type) => {
@@ -12,8 +12,8 @@ import { DEEP_MODES_ARTIFACT } from '../moon/deep-modes-artifact.cjs';
12
12
  export function assembleDeepTimeChain(ctx) {
13
13
  const { C, F, late, startmodelYear } = ctx;
14
14
  // The GIA α channel (the lagged response to the L1 ice history, with the R2
15
- // lattice pin and the exact-argument memo), the Layer-0 bundle, the cited
16
- // Earth–Moon distance history (plan 12 option A), the ΔT cycle stack (all
15
+ // pin and the exact-argument memo), the Layer-0 bundle, the cited
16
+ // Earth–Moon distance history, the ΔT cycle stack (all
17
17
  // channels ON here), |s₃|, the LOD/ΔT core and the lunar month chain are
18
18
  // assembled once in deltat/deep-time-chain.js from this context's constants
19
19
  // and fitted coefficients (so a counterfactual context flows through them);
@@ -39,7 +39,7 @@ export function assembleDeepTimeChain(ctx) {
39
39
  * integration actually turns the pole at: the inertial secular rate of date × the solar-torque factor at
40
40
  * e(t) × J₂(t)/J₂₀ × cos ε(t)/cos ε₀ (ds/dt = α (ŝ·n̂)(ŝ×n̂): the azimuthal rate about n̂ is α cos ε).
41
41
  * The secular rate here is the CONSTANT's scaling (lunisolarTorqueRate…), not the published rate of
42
- * date: the hybrid's own ε(t) carries the ONE cosine, so the cited mean obliquity's factor (plan 12 §8)
42
+ * date: the hybrid's own ε(t) carries the ONE cosine, so the cited mean obliquity's factor
43
43
  * does not enter twice — inside the ζ-series span the hybrid IS the model's obliquity of date.
44
44
  * Measured (2026-10): the dominant obliquity line of the model's own ε(t) over the last 5 Myr sits at
45
45
  * this mean minus |s₃| (40,938 fitted vs 40,936 predicted), NOT at the J2000 beat (41,098) — the pole
@@ -23,7 +23,7 @@ export function assembleLunarTheory(ctx) {
23
23
  // tools/verify/eclipse-audit.js (finders). Deep-time and framework-native
24
24
  // are hardwired ON here: they are the shipped defaults; the A/B env
25
25
  // toggles (SG_DEEP_TIME/MOON_ARGS_PURE_MEEUS) stay an engine concern.
26
- const j2000JD = J2000_JD; // ONE home: constants/units (plan 14 step 2)
26
+ const j2000JD = J2000_JD; // ONE home: constants/units
27
27
  const julianCenturyDays = JULIAN_CENTURY_DAYS;
28
28
 
29
29
  // J2000 Moon precession anchors (the IAU inputs themselves). The
@@ -50,7 +50,7 @@ export function assembleLunarTheory(ctx) {
50
50
  // Float64Array tables key correctly and persist.
51
51
  const chainCycles = createChainCycleIntegrator({
52
52
  ageAnchorYear: startmodelYear,
53
- tropicalYearJ2000Seconds: meanTropicalYearJ2000Seconds, // the SI tick (plan 09 step 7c′)
53
+ tropicalYearJ2000Seconds: meanTropicalYearJ2000Seconds, // the SI tick
54
54
  isDeepTime: () => true,
55
55
  });
56
56
  /** @param {number} tMa @returns {number|null} */
@@ -72,7 +72,7 @@ export function assembleLunarTheory(ctx) {
72
72
  const mcAnomalistic = (a, b) => chainCycles.cyclesBetween(anomalisticMonthPeriodFn, a, b);
73
73
  /** @param {number} a @param {number} b @returns {number|null} */
74
74
  const mcJupiter = (a, b) => chainCycles.cyclesBetween(jupiterPeriodFn, a, b);
75
- /** The Moon's sidereal (star-referenced) orbits — the series' A3 argument (plan 09 step 5c). @param {number} a @param {number} b @returns {number|null} */
75
+ /** The Moon's sidereal (star-referenced) orbits — the series' A3 argument. @param {number} a @param {number} b @returns {number|null} */
76
76
  const mcSidereal = (a, b) => chainCycles.cyclesBetween(siderealMonthPeriodFn, a, b);
77
77
  /** @param {number} a @param {number} b @returns {number|null} */
78
78
  const mcApsidalOfDate = (a, b) => {
@@ -98,6 +98,8 @@ export function assembleLunarTheory(ctx) {
98
98
  elpGeneralPrecessionPA_T2ArcsecPerCy2: C.moonMeeus.elpW1T2Decomposition_arcsecPerCy2.generalPrecessionPA_T2_Lieske1976,
99
99
  // Decision (ii): the lunar chain reads ONE deep e end to end
100
100
  eccE0: deepEcc.e0,
101
+ // plan 23: the perigee/node sensitivities — the chain's derived pair (ONE home)
102
+ moonEccSensitivities: chain.moonEccSensitivities,
101
103
  },
102
104
  fns: {
103
105
  eccAt: /** @param {number} tYr */ (tYr) => deepEcc.eccAt(tYr),
@@ -191,8 +193,7 @@ export function assembleLunarTheory(ctx) {
191
193
  // against the MODEL-START anchor (startmodelYear, the browser's
192
194
  // J2000_CALENDAR_YEAR — NOT this model's 2000.0 yearToTMa convention:
193
195
  // the 0.5-yr difference is 4.95e-11 of distance and failed the
194
- // bit-exact parity gate; plan 11 step 5 dropped the −0.83-d correction
195
- // from the anchor, both twins alike). Falls back to the J2000 constant
196
+ // bit-exact parity gate). Falls back to the J2000 constant
196
197
  // without a jd (legacy call shape) or past the domain.
197
198
  getMoonDistanceKm: /** @param {number} [jdTT] */ (jdTT) => {
198
199
  if (jdTT === undefined) return moonDistanceKm;
@@ -32,8 +32,8 @@ export function assembleOneSourceMovement(ctx) {
32
32
  // path; the npm package alone has no 8 MB series), the movement runs the
33
33
  // SERIES tier exactly like the browser — killing the mode-vs-series
34
34
  // value split (measured: 6 s on the anomalistic year at J2000).
35
- // The ONE-SOURCE movement: the hybrid's ε(t) (plan 06 Phase 3 S3b — THE
36
- // published obliquity) and the one-family year lengths, on the same
35
+ // The ONE-SOURCE movement: the hybrid's ε(t) (THE published obliquity) and
36
+ // the one-family year lengths, on the same
37
37
  // per-tier sampler.
38
38
  const oneSourceM = (() => {
39
39
  const AEarth = /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements.earth;
@@ -47,7 +47,7 @@ export function assembleOneSourceMovement(ctx) {
47
47
  // 108.6 ± 8.5).
48
48
  const seriesArt = /** @type {any} */ (secularSeriesArtifact);
49
49
  const sb = seriesArt && seriesArt.bodies && seriesArt.bodies.earth;
50
- // Plan 12 §8.5 (owner): the hybrid's own domain is the banked ζ series' span (years; null
50
+ // The hybrid's own domain is the banked ζ series' span (years; null
51
51
  // without a series). Beyond it the ζ-mode tail carries the Quaternary mean with no tidal
52
52
  // evolution — the API refuses the obliquity there and serves the cited mean beside it.
53
53
  const obliquityHybridDomainYears = sb
@@ -73,13 +73,13 @@ export function assembleOneSourceMovement(ctx) {
73
73
  obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
74
74
  // The CONSTANT's scaling (the torque period), not the published rate of date: the
75
75
  // integration's own ŝ·n̂ carries the obliquity's cosine, so the cited mean obliquity's
76
- // factor (plan 12 §8 option (a)) must not enter twice. Bit-identical to the pre-§8 form.
76
+ // factor must not enter twice.
77
77
  axialPrecessionYearsAtYearFn: (yr) => deepLod.lunisolarTorquePeriodYearsAtAge((startmodelYear - yr) / 1e6) ?? axial0,
78
78
  // THE PRECESSION OF DATE: on top of the secular law above, the solar
79
79
  // torque at the eccentricity of date and the oblateness of date from
80
80
  // the GIA channel — both exactly 1 at J2000, so the J2000 anchor is
81
81
  // untouched. The channel is
82
- // read PURE (alphaGia, not the lattice-pin-aware earthMoiFactorAtAge):
82
+ // read PURE (alphaGia, not the pinned earthMoiFactorAtAge):
83
83
  // the samplers are cached, their values must be pure in year.
84
84
  solarTorqueShareJ2000: EPOCH_PARAMS.precessionSolarShareJ2000,
85
85
  // options.giaEllipticityOfDate === false: the orbital forcing alone (the T1 line derivation).
@@ -100,7 +100,7 @@ export function assembleOneSourceMovement(ctx) {
100
100
  if (need > 2000000) {
101
101
  // The deep tier (5000-yr grid): walked in the queried DIRECTION only,
102
102
  // to two nodes past the query, and EXTENDED by the factory's shared
103
- // walk when a deeper epoch arrives (plan 13 A1). It was built to
103
+ // walk when a deeper epoch arrives. It was built to
104
104
  // ±1.25·|t| in both directions and rebuilt from J2000 on every deeper
105
105
  // probe — bit-identical values, 4× the RK4 steps of one walk.
106
106
  const span = Math.ceil((Math.abs(t) + 2 * 5000) / 5000) * 5000;
@@ -127,7 +127,7 @@ export function assembleOneSourceMovement(ctx) {
127
127
  periOfDateDegAt: (year) => sampleAt(year).periOfDateDeg,
128
128
  /** The hybrid's obliquity at a decimal year, degrees — the banked series inside its span, the α(t)-coupled ζ-tail integration beyond (the deep sampler grows ~0.1 s/Myr). @param {number} year @returns {number} */
129
129
  epsAt: (year) => sampleAt(year).epsDeg,
130
- /** The hybrid's own domain, [yearFrom, yearTo] — the banked ζ series' span (plan 12 §8.5); null without a series. */
130
+ /** The hybrid's own domain, [yearFrom, yearTo] — the banked ζ series' span; null without a series. */
131
131
  obliquityHybridDomainYears,
132
132
  /** The precession of date's two factors at a decimal year (the factory's own — ONE home). @param {number} year @returns {{solarTorque: number, ellipticity: number}} */
133
133
  precessionOfDateFactorsAt: (year) => hist.precessionOfDateFactorsAt(year - 2000),
@@ -139,7 +139,7 @@ export function assembleOneSourceMovement(ctx) {
139
139
  })();
140
140
  const yearLengthsM = oneSourceM.yearLengths;
141
141
  /** THE one-family tropical year of date in SI seconds: route B inside the
142
- * one-family window, the tidal-chain year beyond (plan 06 R1 — the Sun's
142
+ * one-family window, the tidal-chain year beyond (the Sun's
143
143
  * mean longitude, the cardinal instants and the published year lengths
144
144
  * all ride this one function). @param {number} year @returns {number} */
145
145
  const oneFamilyTropicalYearSeconds = (year) => (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS
@@ -45,10 +45,10 @@ export function assemblePlanetRecords(ctx) {
45
45
  const gArc = chainModes.g[k] && chainModes.g[k].arcsecPerYr;
46
46
  const sArc = chainModes.s[k] && chainModes.s[k].arcsecPerYr;
47
47
  PUBLISHED_PLANET_RECORDS[k] = Object.freeze({
48
- name: PLANET_DISPLAY_NAMES[k], // presentation — planets/display-names, the deleted JSON's last leaf (plan 11 step 6)
48
+ name: PLANET_DISPLAY_NAMES[k], // presentation — planets/display-names
49
49
  orbitalPeriodInputDays: ar.orbitalPeriodInputDays,
50
50
  longitudePerihelion: ar.longitudePerihelion,
51
- ascendingNodeInvPlane: ar.ascendingNodeInvPlaneClosedForm, // the closed-form verified J2000 node — astro-reference since plan 11 step 2
51
+ ascendingNodeInvPlane: ar.ascendingNodeInvPlaneClosedForm, // the closed-form verified J2000 node (astro-reference)
52
52
  invPlaneInclinationJ2000: ar.invPlaneInclinationJ2000,
53
53
  orbitalEccentricityJ2000: ar.orbitalEccentricityJ2000,
54
54
  axialTiltJ2000: ar.axialTiltJ2000,
@@ -8,7 +8,7 @@ import { ONE_FAMILY_WINDOW_YEARS } from '../earth/year-lengths.cjs';
8
8
  /** @param {import('./sun-and-eclipses.js').Ctx} ctx @returns the bindings the later slices read */
9
9
  export function assemblePublishedPeriods(ctx) {
10
10
  const { certifiedAxialPrecessionJ2000Years, deepLod, yearLengthsM, yearToTMa } = ctx;
11
- // Plan 06 Phase 3 S2 — THE published of-date precession period and mean
11
+ // THE published of-date precession period and mean
12
12
  // tropical year: the one-family route (B) inside the banked tiers
13
13
  // (|Δyear| ≤ ONE_FAMILY_WINDOW_YEARS), the composed lunisolar period
14
14
  // beyond (S5: on the derived J2000 anchor, so the two faces agree at
@@ -56,7 +56,7 @@ export function assembleSunAndEclipses(ctx) {
56
56
  const cyclesTo = (year) => {
57
57
  const dy = year - 2000;
58
58
  const s = dy >= 0 ? 1 : -1, a = Math.abs(dy);
59
- // WARM THE SAMPLER TO THE TARGET FIRST (plan 06 R4, measured): the
59
+ // WARM THE SAMPLER TO THE TARGET FIRST (measured): the
60
60
  // table below grows outward in 1-yr / 100-yr steps, and every step
61
61
  // beyond ~1.6 Myr enlarged the one-family sampler's >2-Myr tier just
62
62
  // past its span, rebuilding the whole deep integration about every 32
@@ -131,7 +131,7 @@ export function assembleSunAndEclipses(ctx) {
131
131
  * (648000 / Math.PI);
132
132
  // FQ-5 N3 — the completion's carriers are FRAMEWORK-derived from the
133
133
  // model's own planet period records (the Moon-elongation rate from the
134
- // sidereal month/year identity). Plan 06 I2 — the carriers are SIDEREAL
134
+ // sidereal month/year identity). The carriers are SIDEREAL
135
135
  // (measured): the records' orbitalPeriodInputDays are OF-DATE periods (their
136
136
  // rates sit +1.39..1.62°/cy above the VSOP sidereal mean motions — the
137
137
  // framework precession plus input rounding), and a perturbation argument
@@ -169,7 +169,7 @@ export function assembleSunAndEclipses(ctx) {
169
169
  },
170
170
  sunLonDegAt: /** @param {number} jdUT */ (jdUT) => eclipseFinders.sunLonDegAt(jdUT),
171
171
  sunCompletionDeg: sunPlanetaryCompletionDeg,
172
- // plan 06 R3 item 1: the location tier on APPARENT places — the Sun's derived κ
172
+ // The location tier on APPARENT places — the Sun's derived κ
173
173
  // (the same helper the cardinal instants use), the Moon's relative light-time
174
174
  // inside the besselian; the series-extension tail now lives in sceneEvalAt.
175
175
  sunAberrationDegAt: /** @param {number} year */ (year) => late.sunAberrationArcsecAt(year) / 3600,
@@ -94,7 +94,7 @@ export function assembleSurface(ctx) {
94
94
  // one-family inside the window, the tidal-chain mean beyond.
95
95
  tropicalYearSecondsAtYear: publishedTropicalYearSecondsAtYear,
96
96
  }),
97
- // Plan 06 Phase 3 S3 → S5 — the lunisolar precession clock as a
97
+ // The lunisolar precession clock as a
98
98
  // first-class surface: Earth's spin clock, ONE home for its published
99
99
  // faces, spoken in PERIODS and their RATIOS — no unit, no integer.
100
100
  // ONE J2000 reading: every face reads 25,771.6 yr at J2000 — the
@@ -108,7 +108,7 @@ export function assembleSurface(ctx) {
108
108
  lunisolar: Object.freeze({
109
109
  /** The mean lunisolar precession period T_p(t), years — 1,296,000/the precession RATE of date (25,771.6 at J2000): THE published deep-time clock. @param {number} year @returns {number} */
110
110
  meanPeriodYearsAtYear: (year) => { const p = deepLod.lunisolarPrecessionPeriodYearsAtAge(yearToTMa(year)); return p === null ? certifiedAxialPrecessionJ2000Years() : p; },
111
- /** THE PRECESSION RATE OF DATE ψ̇(t) = [ω/ω₀]·p₀·[f_S + (1 − f_S)(a₀/a_M)³] × cos ε̄(t)/cos ε̄(0), ″/yr — the constant's scaling on the spin and the lunar torque times the cited mean obliquity's cosine (plan 12 §8 option (a); p₀ = 50.2880, the IAU 2006 input). @param {number} year @returns {number} */
111
+ /** THE PRECESSION RATE OF DATE ψ̇(t) = [ω/ω₀]·p₀·[f_S + (1 − f_S)(a₀/a_M)³] × cos ε̄(t)/cos ε̄(0), ″/yr — the constant's scaling on the spin and the lunar torque times the cited mean obliquity's cosine (p₀ = 50.2880, the IAU 2006 input). @param {number} year @returns {number} */
112
112
  meanRateArcsecPerYrAtYear: (year) => { const r = deepLod.lunisolarPrecessionRateArcsecPerYrAtAge(yearToTMa(year)); return r === null ? 1296000 / certifiedAxialPrecessionJ2000Years() : r; },
113
113
  /** THE PRECESSION CONSTANT OF DATE as a rate on the J2000 obliquity, ″/yr — the spin and torque scaling alone (the hybrid obliquity integration's integrand: its own ŝ·n̂ carries the cosine). @param {number} year @returns {number} */
114
114
  torqueRateArcsecPerYrAtYear: (year) => { const r = deepLod.lunisolarTorqueRateArcsecPerYrAtAge(yearToTMa(year)); return r === null ? 1296000 / certifiedAxialPrecessionJ2000Years() : r; },
@@ -212,9 +212,9 @@ export function assembleSurface(ctx) {
212
212
  // its deep mean is the Quaternary mean on the tail's mean plane (plan 12
213
213
  // §8.3). The cited mean obliquity of date is `meanObliquityCitedDeg`.
214
214
  obliquityDeg: /** @param {number} year @returns {number} */ (year) => oneSourceM.epsAt(year),
215
- /** The hybrid's own domain, [yearFrom, yearTo] — the banked ζ series' span, ±10 Myr about J2000 (plan 12 §8.5: the published surfaces serve `obliquityDeg` inside it only; null without a series, i.e. a bare createModel()). */
215
+ /** The hybrid's own domain, [yearFrom, yearTo] — the banked ζ series' span, ±10 Myr about J2000 (the published surfaces serve `obliquityDeg` inside it only; null without a series, i.e. a bare createModel()). */
216
216
  obliquityHybridDomainYears: oneSourceM.obliquityHybridDomainYears,
217
- /** The cited mean obliquity of date ε̄(t) (Farhat et al. 2022's column; plan 12 §8 option (a)), degrees — a Myr-scale mean; NaN beyond 4,429 Ma. @param {number} year @returns {number} */
217
+ /** The cited mean obliquity of date ε̄(t) (Farhat et al. 2022's column), degrees — a Myr-scale mean; NaN beyond 4,429 Ma. @param {number} year @returns {number} */
218
218
  meanObliquityCitedDeg: /** @param {number} year @returns {number} */ (year) => { const e = chain.meanObliquityCitedDegAtAge(yearToTMa(year)); return e === null ? NaN : e; },
219
219
  eccentricity: eccentricityAt,
220
220
  inclinationDeg,
@@ -234,7 +234,7 @@ export function assembleSurface(ctx) {
234
234
  siderealDaySeconds,
235
235
  stellarDaySeconds,
236
236
  }),
237
- // Plan 06 R1: the instants are the crossings of the ONE Sun (the
237
+ // The instants are the crossings of the ONE Sun (the
238
238
  // certified completed Sun the scene renders), UT model-JD; the RA at a
239
239
  // crossing is the target longitude by construction (λ = 0/90/180/270 ⇒
240
240
  // RA = 0/90/180/270, any obliquity); the per-type year length is the
@@ -266,6 +266,10 @@ export function assembleSurface(ctx) {
266
266
  distanceKmAtYear: /** @param {number} year @returns {number} */ (year) => moonDistanceKmAtYearOrNaN(year),
267
267
  siderealMonthDaysAtYear: moonSiderealMonthDaysAt,
268
268
  synodicMonthDays: moonSynodicMonthDays,
269
+ // The perigee/node eccentricity sensitivities (plan 23): the Meeus-effective
270
+ // pair the record fixes and the PHYSICAL pair the runtime derives from it
271
+ // (moon/ecc-sensitivities.cjs, the ONE home) — the registry's source.
272
+ eccSensitivities: chain.moonEccSensitivities,
269
273
  // The apparent-position chain (truncated Meeus Ch. 47 series on
270
274
  // framework-native arguments). JD(UT) axis — UT→TT applied internally.
271
275
  lonDegAtJD: /** @param {number} jd @returns {number} */ (jd) => moonSeries.truncatedLonDeg(jd),
@@ -288,7 +292,7 @@ export function assembleSurface(ctx) {
288
292
  sunLonCompletedDegAtJD: /** @param {number} jd @returns {number} */ (jd) =>
289
293
  eclipseFinders.sunLonDegAt(jd)
290
294
  - sunPlanetaryCompletionDeg((jdTTFromUT(jd) - j2000JD) / julianCenturyDays),
291
- /** The APPARENT Sun (completed geometric − the derived aberration constant + the leading nutation terms on the model's own arguments) — the quantity Horizons' observer ecliptic longitude is; the cardinal instants are its crossings (plan 06 R1/I1). @param {number} jdUT @returns {number} */
295
+ /** The APPARENT Sun (completed geometric − the derived aberration constant + the leading nutation terms on the model's own arguments) — the quantity Horizons' observer ecliptic longitude is; the cardinal instants are its crossings. @param {number} jdUT @returns {number} */
292
296
  sunApparentLonDegAtJD: /** @param {number} jdUT @returns {number} */ (jdUT) => sunApparentLonDegAtJdUT(jdUT),
293
297
  findLunarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findLunarEclipsesInRange(jdStart, jdEnd),
294
298
  findSolarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findSolarEclipsesInRange(jdStart, jdEnd),