@essrt/physics 4.9.0 → 6.0.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/src/model.js CHANGED
@@ -22,14 +22,14 @@ import * as FL from './planets/fibonacci-laws.cjs';
22
22
  import * as planetOrientation from './planets/orientation.cjs';
23
23
  import { createPhaseMachinery } from './phase/index.cjs';
24
24
  import { createCardinalModel } from './cardinal/index.cjs';
25
- import { createYearLengths } from './earth/year-lengths.cjs';
25
+ import { createYearLengths, ONE_FAMILY_WINDOW_YEARS } from './earth/year-lengths.cjs';
26
26
  import { createDeepOrbitalHistory } from './earth/deep-orbital-history.cjs';
27
27
  import { CHAIN_ARTIFACT } from './planets/chain-artifact.js';
28
28
  import { buildPlanetChainsFromArtifactData, computeApsidalSecularDegPerYr } from './planets/keplerian-chain.cjs';
29
29
  import { createDeltaTCycles } from './deltat/cycles.cjs';
30
30
  import { createDeepTimeLod } from './deltat/deep-time.cjs';
31
31
  import { createMoonRecessionHistory, createSolarChannelBudget } from './deltat/recession-history.cjs';
32
- import { evalClimateL1OrbitalPermil } from './climate/l1-orbital.cjs';
32
+ import { evalClimateL1OrbitalPermil, createAlphaGiaChannel } from './climate/l1-orbital.cjs';
33
33
  import { createMoonEccChannel } from './moon/ecc-channel.cjs';
34
34
  import { createDeepEccChannel } from './moon/deep-ecc-channel.cjs';
35
35
  import { DEEP_MODES_ARTIFACT } from './moon/deep-modes-artifact.cjs';
@@ -132,21 +132,27 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
132
132
  const GM_SUN = GM_SUN_PLUS_EARTH - GM_EARTH_ALONE;
133
133
  const M_SUN = GM_SUN / G_CONSTANT;
134
134
 
135
- // ── α(t): climate-driven GIA channel with the R2 lattice pin ──────────────
135
+ // ── α(t): the GIA channel — the LAGGED response to the L1 ice history, with the R2 lattice pin ──
136
136
  const CLIMATE = F.CLIMATE_FORMULA_COEFFS;
137
137
  const CLIMATE_REGIME = CLIMATE.regimes['lr04-post-mpt'];
138
- const alphaClimateScale = C.deepTime.alphaClimateScalePerMille;
139
138
 
140
- /** @param {number} year @returns {number} */
139
+ /** The climate formula's L1 (unlagged) — the orbital δ¹⁸O layer itself. @param {number} year @returns {number} */
141
140
  const evalClimateL1 = (year) => evalClimateL1OrbitalPermil(year, {
142
141
  l1Terms: CLIMATE_REGIME.L1,
143
142
  yStdDenormalization: CLIMATE_REGIME.denormalization.y_std,
144
- eightHKyr: CLIMATE.config.eight_H_kyr,
143
+ });
144
+ // ONE home (climate/l1-orbital.cjs): k derived from the Cox–Chao rate, τ the
145
+ // record-measured relaxation time (plan 06 D7). Same construction in
146
+ // src/script.js, tools/lib/deep-time.js and the website's essrt.ts.
147
+ const alphaGia = createAlphaGiaChannel({
148
+ l1Terms: CLIMATE_REGIME.L1,
149
+ yStdDenormalization: CLIMATE_REGIME.denormalization.y_std,
150
+ relaxationKyr: C.deepTime.alphaGiaRelaxationKyr,
151
+ alphaGiaRateJ2000PerYr: C.deepTime.alphaGiaRateJ2000PerYr,
152
+ alphaJ2000: earthMoiFactorJ2000,
145
153
  });
146
154
 
147
155
  let latticeAlphaPin = false;
148
- /** @type {number|null} */
149
- let alphaL1J2000 = null;
150
156
  // Exact-argument memo (mirrors src/script.js + tools/lib/deep-time.js —
151
157
  // the round-5 perf campaign): same tMa → bit-identical value; the pin
152
158
  // branch bypasses the memo; bounded by wholesale clear.
@@ -157,9 +163,7 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
157
163
  if (latticeAlphaPin) return earthMoiFactorJ2000;
158
164
  let v = alphaMemo.get(tMa);
159
165
  if (v === undefined) {
160
- if (alphaL1J2000 === null) alphaL1J2000 = evalClimateL1(2000);
161
- const L1at = evalClimateL1(2000 - tMa * 1e6);
162
- v = earthMoiFactorJ2000 - alphaClimateScale * (L1at - alphaL1J2000);
166
+ v = alphaGia.alphaAt(2000 - tMa * 1e6);
163
167
  if (alphaMemo.size >= 8192) alphaMemo.clear();
164
168
  alphaMemo.set(tMa, v);
165
169
  }
@@ -186,6 +190,11 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
186
190
  moonDistanceKm,
187
191
  moonOrbitalEccentricity: C.moonReference.moonOrbitalEccentricityBase,
188
192
  gmEarthMoonSystemKm3S2: GM_EARTH_MOON_SYSTEM,
193
+ // the solar precession torque's inputs (plan 06 Phase 3: the unit H(t) = 13·T_p,composed)
194
+ gmSunKm3S2: GM_SUN,
195
+ astronomicalUnitKm: currentAUDistance,
196
+ earthOrbitalEccentricityJ2000: C.earthOrbital.earthEccentricityJ2000,
197
+ moonEclipticInclinationJ2000Deg: C.moonReference.moonEclipticInclinationJ2000,
189
198
  });
190
199
 
191
200
  // ── Driver 1½: regime-aware recession history + the solar channels ────────
@@ -272,9 +281,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
272
281
  meanSiderealYearJ2000Seconds: meanSiderealYearSeconds,
273
282
  solarMassLossFracPerYear: EPOCH_PARAMS.solarMassLossFracPerYear,
274
283
  siderealYearDaysKinematicJ2000: meanSiderealYearDaysKinematic,
284
+ precessionSolarShareJ2000: EPOCH_PARAMS.precessionSolarShareJ2000,
275
285
  },
276
286
  moonDistanceMetresAtAge,
277
287
  moiFactorAtAge: earthMoiFactorAtAge,
288
+ // S5: the composed clock's J2000 anchor — the certified year laws' beat
289
+ // at 2000 (ONE home below, shared with the hybrid's self-anchor); lazy.
290
+ precessionPeriodJ2000YearsFn: () => certifiedAxialPrecessionJ2000Years(),
278
291
  siderealYearDaysFourierAt: (year) => evalSiderealYearFourierIAU(year),
279
292
  cycleLodSumAt: dtCycleLodCorrectionSum,
280
293
  swingLodAt: (year) => dtCycles.swingLodSecondsAt(year),
@@ -283,8 +296,12 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
283
296
  });
284
297
 
285
298
  // ── Integrated ∫1/H(t)dt phase (R2 α-pin honoured while building) ─────────
299
+ // The phase counter every FROZEN comb reads rides the frozen era clock's
300
+ // own convention H_era = H₀·LOD/LOD₀ (plan 06 D8: two named counters — the
301
+ // coefficients were fitted against this counter and ship with it), NOT the
302
+ // unit H(t) = 13·T_p,composed that the physics publishes.
286
303
  const phaseM = createPhaseMachinery({
287
- holisticHAtAgeMa: (tMa) => deepLod.hAtAge(tMa),
304
+ holisticHAtAgeMa: (tMa) => deepLod.eraClockHAtAge(tMa),
288
305
  tableAnchorYear: startmodelYear,
289
306
  driftRefYear: startModelYearWithCorrection,
290
307
  hJ2000: H,
@@ -413,16 +430,37 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
413
430
  if (lod === null) return meanSiderealYearDays;
414
431
  return deepLod.siderealYearSecondsAtAge(tMa) / lod;
415
432
  };
433
+ // The comb family's bases are the FROZEN era clock's forms (H_era; plan 06
434
+ // D8) — the combs were fitted on them. The physical year lengths ride the
435
+ // unit through yearLengths (family B) and the tidal-chain mean.
416
436
  /** @param {number} year @returns {number} */
417
437
  const tropicalYearDaysBase = (year) => {
418
- const days = deepLod.yearInDaysAtAge(yearToTMa(year));
438
+ const days = deepLod.eraClockYearInDaysAtAge(yearToTMa(year));
419
439
  return days === null ? meanSolarYearDays : days;
420
440
  };
441
+ /** THE J2000 precession anchor (plan 06 S5 — one J2000 precession
442
+ * reading): the certified of-date year laws at 2000, sid/(sid − trop) =
443
+ * 25,771.4 yr = 50.2883 ″/yr (IAU 50.2879 to 8×10⁻⁶) — identical to the
444
+ * engine/browser Direct twins. ONE home: the composed clock's p₀, the
445
+ * hybrid's self-anchor and the lunisolar surface all read it. The former
446
+ * H/13 reading (25,793.6) is the fit anchor's, not a period. Memoized;
447
+ * hoisted so the deep-time factory's lazy dep can name it.
448
+ * @returns {number} */
449
+ function certifiedAxialPrecessionJ2000Years() {
450
+ if (certifiedAxial0Memo === undefined) {
451
+ const sidLaw2000 = evalYearFourier(2000, siderealYearDaysBase(2000), F.SIDEREAL_YEAR_HARMONICS);
452
+ const solLaw2000 = evalYearFourier(2000, tropicalYearDaysBase(2000), F.TROPICAL_YEAR_HARMONICS);
453
+ certifiedAxial0Memo = sidLaw2000 / (sidLaw2000 - solLaw2000);
454
+ }
455
+ return certifiedAxial0Memo;
456
+ }
457
+ /** @type {number|undefined} */
458
+ let certifiedAxial0Memo;
421
459
  /** @param {number} year @returns {number} */
422
460
  const anomalisticYearDaysBase = (year) => {
423
461
  const tMa = yearToTMa(year);
424
- const Ht = deepLod.hAtAge(tMa);
425
- const tropD = deepLod.yearInDaysAtAge(tMa);
462
+ const Ht = deepLod.eraClockHAtAge(tMa);
463
+ const tropD = deepLod.eraClockYearInDaysAtAge(tMa);
426
464
  if (Ht === null || tropD === null) return meanAnomalisticYearDays;
427
465
  return (tropD * (Ht / 16)) / (Ht / 16 - 1);
428
466
  };
@@ -456,14 +494,16 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
456
494
  },
457
495
  fns: {
458
496
  cyclesBetween: (a, b, n) => phase().cyclesBetween(a, b, n),
497
+ // The frozen era clock's deps ride ITS convention H_era (plan 06 D8):
498
+ // drift integrand, dc/dY and the real-LOD convention were fitted on it.
459
499
  analyticTropicalDays: (year) => {
460
500
  const tMa = (startmodelYear - year) / 1e6;
461
- const Ht = deepLod.hAtAge(tMa);
501
+ const Ht = deepLod.eraClockHAtAge(tMa);
462
502
  if (Ht === null) return null;
463
503
  return (deepLod.siderealYearSecondsAtAge(tMa) / 86400) * (1 - 13 / Ht);
464
504
  },
465
- meanHAtAgeMa: (tMa) => deepLod.hAtAge(tMa),
466
- meanYearRealLodDays: (tMa) => deepLod.yearInDaysAtAge(tMa),
505
+ meanHAtAgeMa: (tMa) => deepLod.eraClockHAtAge(tMa),
506
+ meanYearRealLodDays: (tMa) => deepLod.eraClockYearInDaysAtAge(tMa),
467
507
  eccentricityAt,
468
508
  eccentricityRateAt,
469
509
  },
@@ -474,9 +514,10 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
474
514
  // package's embedded deep-modes artifact; the banked-series tier is the
475
515
  // repo-data-bound instrument). Absolute dates deliberately absent — see
476
516
  // cardinal/one-source-structure.cjs. The mean year is SI SECONDS: the
477
- // sidereal year of date reduced by the SECULAR α(H(t)) equinox precession
478
- // (axial0·H(t)/H0 — the movement's leg-1 convention, NOT the H/13
479
- // kinematic identity; the two are a recorded 0.09% relation tension).
517
+ // sidereal year of date reduced by the SECULAR equinox precession
518
+ // (axial0·H(t)/H₀ with H(t) the UNIT = 13·T_p,composed — plan 06 D6/Phase
519
+ // 3, the movement's leg-1 convention, NOT the H/13 kinematic identity at
520
+ // J2000; the two are a recorded 0.09% relation tension there).
480
521
  // THE ONE of-date year-length family (owner: "move to 1 implementation")
481
522
  // — createYearLengths owns tropical (equinox-rate mean, wobble included),
482
523
  // sidereal (the D6 λ̇ channel), anomalistic (the cardinal structure on
@@ -489,7 +530,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
489
530
  // path; the npm package alone has no 8 MB series), the movement runs the
490
531
  // SERIES tier exactly like the browser — killing the mode-vs-series
491
532
  // value split (measured: 6 s on the anomalistic year at J2000).
492
- const yearLengthsM = (() => {
533
+ // The engine-D planet chains (ONE build; the one-family route's apsidal
534
+ // tangent and the lunisolar surface's n_aps read the same instance).
535
+ const kcChainsM = buildPlanetChainsFromArtifactData(CHAIN_ARTIFACT);
536
+ // The ONE-SOURCE movement: the hybrid's ε(t) (plan 06 Phase 3 S3b — THE
537
+ // published obliquity) and the one-family year lengths, on the same
538
+ // per-tier sampler.
539
+ const oneSourceM = (() => {
493
540
  const AEarth = /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements.earth;
494
541
  // The ψ̇ anchor: the CERTIFIED of-date laws at 2000 — identical to the
495
542
  // engine/browser (computeSiderealYearDaysDirect / computeSolarYearDays
@@ -498,9 +545,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
498
545
  // movement's realized precession 25 yr off the certified value — the
499
546
  // hidden split behind the API's 365.242204 tropical mean (found in the
500
547
  // owner's one-implementation drive).
501
- const sidLaw2000 = evalYearFourier(2000, siderealYearDaysBase(2000), F.SIDEREAL_YEAR_HARMONICS);
502
- const solLaw2000 = evalYearFourier(2000, tropicalYearDaysBase(2000), F.TROPICAL_YEAR_HARMONICS);
503
- const axial0 = sidLaw2000 / (sidLaw2000 - solLaw2000);
548
+ const axial0 = certifiedAxialPrecessionJ2000Years(); // S5: the ONE home
549
+ // D6 → Phase 3 (plan 06): the deep-time ψ̇(t) the hybrid precesses on is
550
+ // the COMPOSED lunisolar rate, and since Phase 3 the unit H(t) IS
551
+ // 13 × that period (deltat/deep-time.cjs hAtAge), so the injection is
552
+ // the plain period₀·H(t)/H₀ again — on the unit, not on the frozen
553
+ // era clock's H_era (which misses the lunar 1/a³ growth: 70.9 vs
554
+ // Lantink 2022's 108.6 ± 8.5 ″/yr at 2.46 Ga; the unit reads 104.5).
504
555
  const H0 = /** @type {number} */ (deepLod.hAtAge(0));
505
556
  const seriesArt = /** @type {any} */ (secularSeriesArtifact);
506
557
  const sb = seriesArt && seriesArt.bodies && seriesArt.bodies.earth;
@@ -516,7 +567,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
516
567
  anchorInclEclipticDeg: AEarth.inclEclipticDeg,
517
568
  anchorAscNodeEclipticDeg: AEarth.ascNodeEclipticDeg,
518
569
  axialPrecessionYearsJ2000: axial0,
519
- obliquityJ2000Deg: obliquityDeg(2000),
570
+ // Phase 3 S3b: the hybrid's initial condition is the OBSERVED J2000
571
+ // mean obliquity — the IAU 2006 input constant (84381.406″), the same
572
+ // anchor the browser's _deepHistSeries and the Node one-source movement
573
+ // use. It was the K comb's own J2000 value, 0.233″ above IAU: a
574
+ // rendering-device residual imported as the initial condition of the
575
+ // physics (found when the package began publishing the hybrid).
576
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
520
577
  axialPrecessionYearsAtYearFn: (yr) => {
521
578
  const h = deepLod.hAtAge((startmodelYear - yr) / 1e6);
522
579
  return axial0 * (h === null ? 1 : h / H0);
@@ -545,13 +602,19 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
545
602
  };
546
603
  // The anomalistic rides the chain's SECULAR apsidal tangent (the same
547
604
  // rate family the panel's Prec. cell shows) — ONE helper, keplerian-chain.
548
- const kcChains = buildPlanetChainsFromArtifactData(CHAIN_ARTIFACT);
549
- return createYearLengths({
605
+ const kcChains = kcChainsM;
606
+ const yearLengths = createYearLengths({
550
607
  sampleAt,
551
608
  massLossSiderealSecondsAtYearFn: (year) => deepLod.siderealYearSecondsAtAge(yearToTMa(year)),
552
609
  apsidalSecularDegPerYrFn: (year) => computeApsidalSecularDegPerYr(year, kcChains.earth, kcChains),
553
610
  });
611
+ return {
612
+ yearLengths,
613
+ /** 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} */
614
+ epsAt: (year) => sampleAt(year).epsDeg,
615
+ };
554
616
  })();
617
+ const yearLengthsM = oneSourceM.yearLengths;
555
618
 
556
619
  /** Tropical year: mean of the four cardinal intervals. @param {number} year @returns {number} */
557
620
  const tropicalYearDays = (year) => cardinalM.computeTropicalYearLength(year);
@@ -1170,6 +1233,34 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1170
1233
  });
1171
1234
 
1172
1235
  // ── The assembled surface ─────────────────────────────────────────────────
1236
+ // Plan 06 Phase 3 S2 — THE published of-date precession period and mean
1237
+ // tropical year: the one-family route (B) inside the banked tiers
1238
+ // (|Δyear| ≤ ONE_FAMILY_WINDOW_YEARS), the composed lunisolar period
1239
+ // beyond (S5: on the derived J2000 anchor, so the two faces agree at
1240
+ // 25,771.4 at J2000 and the deep tail is the same clock scaled; the
1241
+ // tidal-chain year). Shared by `epoch` and `lunisolar`.
1242
+ /** @param {number} year @returns {number} */
1243
+ const publishedAxialPrecessionYearsAtYear = (year) => {
1244
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.axialPrecessionYearsAtYear(year);
1245
+ const p = deepLod.lunisolarPrecessionPeriodYearsAtAge(yearToTMa(year));
1246
+ return p === null ? certifiedAxialPrecessionJ2000Years() : p;
1247
+ };
1248
+ /** @param {number} year @returns {number} */
1249
+ const publishedTropicalYearSecondsAtYear = (year) => {
1250
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.tropicalYearSecondsAtYear(year);
1251
+ return deepLod.tropicalYearSecondsAtAge(yearToTMa(year));
1252
+ };
1253
+ // Plan 06 S6 — |s₃|, the dominant nodal mode of Earth's orbit: the
1254
+ // largest-amplitude ζ mode of the banked deep secular modes (the recipe
1255
+ // the registry's `eclPrecYears`/`obliqCycleYears` and the browser's
1256
+ // obliquity-beat helper use). ONE home here; the API reads it.
1257
+ const s3ArcsecPerYr = (() => {
1258
+ const z = DEEP_MODES_ARTIFACT.earthZeta
1259
+ .filter((m) => Math.abs(m.omegaRadPerYr) > 1e-9)
1260
+ .sort((a, b) => Math.hypot(b.re, b.im) - Math.hypot(a.re, a.im))[0];
1261
+ return Math.abs((z.omegaRadPerYr * 180) / Math.PI) * 3600;
1262
+ })();
1263
+
1173
1264
  return Object.freeze({
1174
1265
  time: Object.freeze({
1175
1266
  yearFromJD,
@@ -1187,22 +1278,100 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1187
1278
  siderealYearSecondsAtYear: /** @param {number} year @returns {number} */ (year) => yearLengthsM.siderealYearSecondsAtYear(year),
1188
1279
  deltaTSecondsAtYear: deltaTSeconds,
1189
1280
  cyclesBetween,
1190
- // The DYNAMICAL axial precession period (the tweakpane identity):
1191
- // day-form beat of the sidereal and solar year evaluators — real at
1192
- // J2000 (~25,771 yr ≈ IAU), epoch-valid at any age. The same value
1193
- // the (d′) of-date rate completion integrates.
1194
- axialPrecessionYearsAtYear: /** @param {number} year @returns {number} */ (year) => {
1195
- const sid = siderealYearDays(year);
1196
- return sid / (sid - tropicalYearDirectDays(year));
1281
+ // Plan 06 Phase 3 S2 — THE published of-date precession period is the
1282
+ // one-family route (B): the hybrid's own equinox regression against
1283
+ // the λ̇-corrected sidereal year (~25,771 yr at J2000 ≈ IAU), inside
1284
+ // the banked tiers (|Δyear| ≤ ONE_FAMILY_WINDOW_YEARS); beyond, the
1285
+ // composed lunisolar period on the same J2000 anchor (S5; the
1286
+ // of-date wobble is unresolved there). The comb family's beat
1287
+ // sid/(sid − tropicalYearDirectDays) — the frozen era clock's
1288
+ // kinematic-day device — is no longer published here; it still
1289
+ // anchors the lunar chain's (d′) rate completion (pDynDegPerYearAt,
1290
+ // a MATCHED TRIPLE with the eclipse gates: M0(a) measured a 3–15 min
1291
+ // lunar-timing shift if it moved).
1292
+ axialPrecessionYearsAtYear: publishedAxialPrecessionYearsAtYear,
1293
+ // The same publication rule for the mean tropical year of date (SI s):
1294
+ // one-family inside the window, the tidal-chain mean beyond.
1295
+ tropicalYearSecondsAtYear: publishedTropicalYearSecondsAtYear,
1296
+ }),
1297
+ // Plan 06 Phase 3 S3 → S5 — the lunisolar precession clock as a
1298
+ // first-class surface: Earth's spin clock, ONE home for its published
1299
+ // faces, spoken in PERIODS and their RATIOS — no unit, no integer.
1300
+ // ONE J2000 reading (S5): every face reads 25,771.4 yr at J2000 — the
1301
+ // of-date period (the published family, seamed at ±2 Myr) and the
1302
+ // composed mean T_p(t) (the deep-time clock, anchored on the same
1303
+ // derived value). The composition's terms; the apsidal period from the
1304
+ // engine-D chain's secular tangent; the two ratios the owner found
1305
+ // wandering by hand — T_aps/T_p (4.33 at J2000, 0.84 … 9.9 across
1306
+ // ±26 kyr) and T_peri/T_p with T_peri = 1/(1/T_p + 1/T_aps) the
1307
+ // perihelion-of-date period (0.812 at J2000) — on the of-date T_p.
1308
+ lunisolar: Object.freeze({
1309
+ /** The mean lunisolar precession period T_p(t), years — the composed torque rate's period on the derived J2000 anchor (25,771.4 at J2000). @param {number} year @returns {number} */
1310
+ meanPeriodYearsAtYear: (year) => { const p = deepLod.lunisolarPrecessionPeriodYearsAtAge(yearToTMa(year)); return p === null ? certifiedAxialPrecessionJ2000Years() : p; },
1311
+ /** The composed rate ψ̇(t) = [ω/ω₀]·p₀·[f_S + (1 − f_S)(a₀/a_M)³], ″/yr (p₀ = 50.2883, derived). @param {number} year @returns {number} */
1312
+ meanRateArcsecPerYrAtYear: (year) => { const r = deepLod.lunisolarPrecessionRateArcsecPerYrAtAge(yearToTMa(year)); return r === null ? 1296000 / certifiedAxialPrecessionJ2000Years() : r; },
1313
+ /** The of-date period — the one-family beat inside ±2 Myr, the composed mean beyond. @param {number} year @returns {number} */
1314
+ ofDatePeriodYearsAtYear: publishedAxialPrecessionYearsAtYear,
1315
+ /** f_S, the solar fraction of the J2000 precession torque. */
1316
+ solarShareJ2000: EPOCH_PARAMS.precessionSolarShareJ2000,
1317
+ /** (a₀/a_M(t))³ — the lunar torque's growth on the recession history. @param {number} year @returns {number} */
1318
+ lunarTorqueFactorAtYear: (year) => { const f = deepLod.lunarTorqueFactorAtAge(yearToTMa(year)); return f === null ? 1 : f; },
1319
+ /** f_S + (1 − f_S)(a₀/a_M)³ — the torque term the unit divides H_era by. @param {number} year @returns {number} */
1320
+ torqueTermAtYear: (year) => { const t = deepLod.precessionTorqueTermAtAge(yearToTMa(year)); return t === null ? 1 : t; },
1321
+ /** The hybrid's precession constant α = p₀ / cos ε₀, ″/yr (p₀ the derived J2000 rate, ε₀ the J2000 obliquity input; 54.81). */
1322
+ torqueConstantJ2000ArcsecPerYr: (1296000 / certifiedAxialPrecessionJ2000Years()) / Math.cos((C.earthOrbital.obliquityJ2000_deg * Math.PI) / 180),
1323
+ /** |s₃|, the dominant nodal mode of Earth's orbit — the largest-amplitude ζ mode of the banked deep secular modes, ″/yr (18.85; the obliquity beat's partner). ONE home for the registry, the browser and the API. */
1324
+ nodalModeS3ArcsecPerYr: s3ArcsecPerYr,
1325
+ /** The ecliptic (nodal) precession period 1,296,000/|s₃|, years (68,751) — an orbital quantity, fixed at every epoch at the two-body level. */
1326
+ nodalPeriodYears: 1296000 / s3ArcsecPerYr,
1327
+ /** The obliquity beat 2π/(ψ̇(t) − |s₃|), years — the SHIPPED deep-time obliquity period (falsification leg 1; 41,224 at J2000, on the composed rate). @param {number} year @returns {number} */
1328
+ obliquityBeatYearsAtYear: (year) => {
1329
+ const r = deepLod.lunisolarPrecessionRateArcsecPerYrAtAge(yearToTMa(year));
1330
+ const psiDot = r === null ? 1296000 / certifiedAxialPrecessionJ2000Years() : r;
1331
+ return 1296000 / (psiDot - s3ArcsecPerYr);
1332
+ },
1333
+ /** The apsidal (perihelion vs the stars) period from the engine-D chain's secular tangent, years — inside the published window only (the tangent is an extrapolation beyond the banked series: it turns negative at −5 Myr); null beyond. @param {number} year @returns {number|null} */
1334
+ apsidalPeriodYearsAtYear: (year) => (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS ? 360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM) : null),
1335
+ /** T_aps(t) / T_p(t) — the apsidal period in of-date precession periods (4.33 at J2000, a reading; 0.84 … 9.9 across ±26 kyr, measured); null beyond the published window. @param {number} year @returns {number|null} */
1336
+ apsidalPerPrecessionAtYear: (year) => {
1337
+ if (Math.abs(year - 2000) > ONE_FAMILY_WINDOW_YEARS) return null;
1338
+ return (360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM)) / publishedAxialPrecessionYearsAtYear(year);
1339
+ },
1340
+ /** T_peri(t) = 1/(1/T_p + 1/T_aps) — the perihelion-of-date period (equinox precession + inertial perihelion motion, frame arithmetic at every epoch), years, on the of-date T_p; null beyond the published window. @param {number} year @returns {number|null} */
1341
+ periOfDatePeriodYearsAtYear: (year) => {
1342
+ if (Math.abs(year - 2000) > ONE_FAMILY_WINDOW_YEARS) return null;
1343
+ const tp = publishedAxialPrecessionYearsAtYear(year), taps = 360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM);
1344
+ return 1 / (1 / tp + 1 / taps);
1345
+ },
1346
+ /** T_peri(t) / T_p(t) (0.812 at J2000 — the J2000 reading); null beyond the published window. @param {number} year @returns {number|null} */
1347
+ periOfDatePerPrecessionAtYear: (year) => {
1348
+ if (Math.abs(year - 2000) > ONE_FAMILY_WINDOW_YEARS) return null;
1349
+ const tp = publishedAxialPrecessionYearsAtYear(year), taps = 360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM);
1350
+ return (1 / (1 / tp + 1 / taps)) / tp;
1197
1351
  },
1352
+ /** The published of-date window, years from 2000. */
1353
+ publishedWindowYears: ONE_FAMILY_WINDOW_YEARS,
1198
1354
  }),
1199
1355
  earth: Object.freeze({
1200
1356
  perihelionLongitudeDeg: earthPerihelionDeg,
1201
- obliquityDeg,
1357
+ // Plan 06 Phase 3 S3b: THE published obliquity is the hybrid — one
1358
+ // torque law integrated on the dynamical orbit plane of the N-body
1359
+ // chain, zero fitted constants (≡ La2004 to ≤ 4″ over ±50 kyr). The
1360
+ // fitted 16-harmonic K comb, a device fitted to the scene's own
1361
+ // wheel geometry (0.005″ to it, 1.27° off La2004 at −20 kyr), keeps
1362
+ // its name below as the frozen era clock's device: it anchors the
1363
+ // hybrid at J2000, the kinematic-day stack and the lunar arguments.
1364
+ obliquityDeg: /** @param {number} year @returns {number} */ (year) => oneSourceM.epsAt(year),
1365
+ obliquityCombDeg: obliquityDeg,
1202
1366
  eccentricity: eccentricityAt,
1203
1367
  inclinationDeg,
1204
1368
  ascendingNodeDeg,
1205
1369
  }),
1370
+ // The DEVICE family (plan 06 Phase 3 S2): the frozen era clock's comb
1371
+ // year lengths (A), its cardinal 4-mean (A′) and the kinematic-day stack
1372
+ // built on them (the ΔT stack's Layer 3/4 basis, USNO-anchored). NOT the
1373
+ // published year lengths — those are `yearLengths` (the one-family
1374
+ // route) and the seamed `epoch.*AtYear` surfaces above.
1206
1375
  lengths: Object.freeze({
1207
1376
  tropicalYearDays,
1208
1377
  tropicalYearDirectDays,