@essrt/physics 4.9.0 → 5.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.
@@ -113,4 +113,15 @@ function createYearLengths({ sampleAt, massLossSiderealSecondsAtYearFn, apsidalS
113
113
  });
114
114
  }
115
115
 
116
- module.exports = { createYearLengths };
116
+ /**
117
+ * The published of-date window (plan 06 Phase 3 S2): inside |year − 2000| ≤
118
+ * this span the one-family route is THE published year-length / precession
119
+ * family (the banked tiers — 100-yr and 1000-yr grids, no unbounded sampler
120
+ * growth); beyond it the published surfaces return the unit's SECULAR mean
121
+ * (H(t)/13 for the precession period, the tidal-chain mean year), because the
122
+ * of-date wobble is unresolved there and the deep sampler grows ~0.1 s per Myr
123
+ * of span. A displayed rate must name its window — this is the window.
124
+ */
125
+ const ONE_FAMILY_WINDOW_YEARS = 2000000;
126
+
127
+ module.exports = { createYearLengths, ONE_FAMILY_WINDOW_YEARS };
package/src/index.js CHANGED
@@ -138,7 +138,11 @@ export { createDeepOrbitalHistory } from './earth/deep-orbital-history.cjs';
138
138
  // D6: the sidereal-year-of-date channel (banked λ̇ ratio × the caller's mass-loss law)
139
139
  export { createSiderealYearChannel } from './earth/sidereal-year-channel.cjs';
140
140
  // S2: THE ONE of-date year-length family (years + precession beats, SI seconds)
141
- export { createYearLengths } from './earth/year-lengths.cjs';
141
+ export { createYearLengths, ONE_FAMILY_WINDOW_YEARS } from './earth/year-lengths.cjs';
142
+ // Plan 06 D6: THE ONE home of the composed lunisolar precession rate ψ̇(t)
143
+ // (spin ω(t) × [solar torque + lunar torque on the recession history]) —
144
+ // leg-1's physical rate; the structural H(t)/13 clock is its named diagnostic.
145
+ export { computeSolarTorqueShare, createComposedPrecession } from './earth/precession-composed.cjs';
142
146
  export { createPredictivePrecession, calcPlanetPerihelionLongDeg } from './planets/predict.cjs';
143
147
  // L10 — the composition front door: one law set, N body records. Thin by
144
148
  // design; engines keep their direct call sites (see planets/model.cjs).
@@ -148,7 +152,7 @@ export { createDeltaTCycles } from './deltat/cycles.cjs';
148
152
  export { createDeepTimeLod } from './deltat/deep-time.cjs';
149
153
  export { createMoonRecessionHistory, createSolarChannelBudget } from './deltat/recession-history.cjs';
150
154
  export { deltaTEspenakMeeusCanonSeconds } from './deltat/historical.cjs';
151
- export { evalClimateL1OrbitalPermil } from './climate/l1-orbital.cjs';
155
+ export { evalClimateL1OrbitalPermil, laggedL1Terms, createAlphaGiaChannel } from './climate/l1-orbital.cjs';
152
156
  // Phase 8.5 — eclipse geometry (single-copy: the browser had no Node twin).
153
157
  export { createEclipseFinders } from './eclipse/finders.cjs';
154
158
  // Phase 8.6 — the published reference curves (external comparison formulas
@@ -9,12 +9,16 @@
9
9
  * If the two ever disagree, one of them changed and the other did not — which
10
10
  * is the exact drift mode that produced five diverging implementations.
11
11
  *
12
- * Pure function, imports nothing. Lives in physics so the browser and the Node
13
- * engine can both build Layer 0 from ONE derivation (deep-time.js keeps its
14
- * own inline copy only until Phase C rewrites it; until then the test is the
15
- * enforcement).
12
+ * Pure function; its only import is the torque-share derivation (plan 06
13
+ * Phase 3: the unit H(t) = 13·T_p,composed needs the solar fraction of the
14
+ * J2000 precession torque, derived ONCE here from the shared constants).
15
+ * Lives in physics so the browser and the Node engine can both build Layer 0
16
+ * from ONE derivation (deep-time.js keeps its own inline copy only until
17
+ * Phase C rewrites it; until then the test is the enforcement).
16
18
  */
17
19
 
20
+ import { computeSolarTorqueShare } from '../earth/precession-composed.cjs';
21
+
18
22
  /**
19
23
  * @typedef {Object} RawEpochConstants
20
24
  * @property {number} solarLuminosityW IAU 2015 nominal (W)
@@ -35,6 +39,10 @@
35
39
  * @property {number} moonDistanceKm
36
40
  * @property {number} moonOrbitalEccentricity
37
41
  * @property {number} gmEarthMoonSystemKm3S2
42
+ * @property {number} gmSunKm3S2 GM☉ (km³/s²) — the solar precession torque
43
+ * @property {number} astronomicalUnitKm 1 AU (km)
44
+ * @property {number} earthOrbitalEccentricityJ2000
45
+ * @property {number} moonEclipticInclinationJ2000Deg
38
46
  */
39
47
 
40
48
  /**
@@ -64,6 +72,14 @@ export const deriveEpochParams = (raw) => {
64
72
  + (moonMassKg * Math.sqrt(gmEarthMoonM3S2 * moonDistanceNowM) * moonEccentricityFactor);
65
73
  const moonLockDistanceM = (totalAngularMomentumKgM2S / (moonMassKg * Math.sqrt(gmEarthMoonM3S2) * moonEccentricityFactor)) ** 2;
66
74
 
75
+ // The solar fraction of the J2000 precession torque (plan 06 Phase 3): the
76
+ // unit H(t) ≡ 13·T_p,composed = H_era(t) / [f_S + (1 − f_S)(a₀/a_M(t))³].
77
+ const precessionSolarShareJ2000 = computeSolarTorqueShare({
78
+ gmSunKm3S2: raw.gmSunKm3S2, auKm: raw.astronomicalUnitKm, earthEccentricity: raw.earthOrbitalEccentricityJ2000,
79
+ gmMoonKm3S2: raw.gmMoonAloneKm3S2, moonDistanceKm: raw.moonDistanceKm, moonEccentricity: raw.moonOrbitalEccentricity,
80
+ moonInclinationDeg: raw.moonEclipticInclinationJ2000Deg,
81
+ });
82
+
67
83
  return Object.freeze({
68
84
  epochYear: 2000,
69
85
  alpha1PerMa: raw.alpha1PerMa,
@@ -81,5 +97,6 @@ export const deriveEpochParams = (raw) => {
81
97
  lodNowH13Seconds,
82
98
  siderealYearJ2000Seconds: raw.meanSiderealYearSeconds,
83
99
  solarMassLossFracPerYear,
100
+ precessionSolarShareJ2000,
84
101
  });
85
102
  };
@@ -47,6 +47,7 @@
47
47
  * @property {number} lodNowH13Seconds
48
48
  * @property {number} siderealYearJ2000Seconds
49
49
  * @property {number} solarMassLossFracPerYear
50
+ * @property {number} precessionSolarShareJ2000 f_S, the solar fraction of the J2000 precession torque
50
51
  */
51
52
 
52
53
  /**
@@ -54,10 +55,12 @@
54
55
  * @property {(year: number) => number} tMa
55
56
  * @property {(year: number) => number} moonDistanceMetres
56
57
  * @property {(year: number) => number|null} lodSeconds
57
- * @property {(year: number) => number|null} holisticH
58
+ * @property {(year: number) => number|null} holisticH the UNIT: 13 × the composed lunisolar precession period
58
59
  * @property {(year: number) => number} siderealYearSeconds
59
60
  * @property {(year: number) => number} tropicalYearSeconds
60
61
  * @property {(year: number) => number|null} anomalisticYearSeconds
62
+ * @property {(year: number) => number|null} eraClockH the frozen era clock's phase convention H₀·LOD/LOD₀ (device tier)
63
+ * @property {(year: number) => number} eraClockTropicalYearSeconds T_sid·(1 − 13/H_era) — the frozen comb family's base
61
64
  */
62
65
 
63
66
  /**
@@ -98,16 +101,35 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
98
101
  };
99
102
 
100
103
  /**
101
- * The Earth Fundamental Cycle. H scales with LOD — the lattice is defined by
102
- * the rotation rate, so a longer day is a longer H.
104
+ * The frozen era clock's phase convention: H_era(t) = H₀·LOD(t)/LOD₀ —
105
+ * pure spin scaling. This is the counter the FROZEN devices (the cardinal
106
+ * era clock, the year-length comb family) were fitted against; it ships
107
+ * with their coefficients as a named device constant (plan 06 Phase 3,
108
+ * D8), never as "H(t)".
103
109
  * @param {number} t age in Ma
104
110
  * @returns {number|null} years
105
111
  */
106
- const holisticHCore = (t) => {
112
+ const eraClockHCore = (t) => {
107
113
  const lod = lodSecondsCore(t);
108
114
  return lod === null ? null : p.holisticYearJ2000 * lod / p.lodNowH13Seconds;
109
115
  };
110
116
 
117
+ /**
118
+ * The unit H(t) ≡ 13 × the composed lunisolar precession period:
119
+ * H_era(t) / [f_S + (1 − f_S)·(a₀/a_M(t))³] — the spin scaling AND the
120
+ * lunar torque growing on the recession history (plan 06 D6/Phase 3; the
121
+ * formula's home is earth/precession-composed, this is its Layer-0 twin,
122
+ * held bit-identical to deltat/deep-time.cjs by the layer0 gate).
123
+ * @param {number} t age in Ma
124
+ * @returns {number|null} years
125
+ */
126
+ const holisticHCore = (t) => {
127
+ const hEra = eraClockHCore(t);
128
+ if (hEra === null) return null;
129
+ const lf = Math.pow(p.moonDistanceNowM / moonDistanceMetresCore(t), 3);
130
+ return hEra / (p.precessionSolarShareJ2000 + (1 - p.precessionSolarShareJ2000) * lf);
131
+ };
132
+
111
133
  /**
112
134
  * Sidereal year. Driver 2 alone — solar mass loss via Kepler, `dT/T = -2 dM/M`
113
135
  * under the adiabatic `a*M = const` coupling. Carries NO alpha and NO LOD,
@@ -159,6 +181,13 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
159
181
  return sid * (H - 13) / H * H / (H - 16);
160
182
  };
161
183
 
184
+ /** The frozen comb family's tropical base: T_sid·(1 − 13/H_era). @param {number} t */
185
+ const eraClockTropicalYearSecondsCore = (t) => {
186
+ const sid = siderealYearSecondsCore(t);
187
+ const H = eraClockHCore(t);
188
+ return H === null ? sid * (1 - 13 / p.holisticYearJ2000) : sid * (1 - 13 / H);
189
+ };
190
+
162
191
  return Object.freeze({
163
192
  tMa,
164
193
  moonDistanceMetres: (year) => moonDistanceMetresCore(tMa(year)),
@@ -167,5 +196,7 @@ export const createEpochPrimitives = ({ params: p, alphaAtAgeMa }) => {
167
196
  siderealYearSeconds: (year) => siderealYearSecondsCore(tMa(year)),
168
197
  tropicalYearSeconds: (year) => tropicalYearSecondsCore(tMa(year)),
169
198
  anomalisticYearSeconds: (year) => anomalisticYearSecondsCore(tMa(year)),
199
+ eraClockH: (year) => eraClockHCore(tMa(year)),
200
+ eraClockTropicalYearSeconds: (year) => eraClockTropicalYearSecondsCore(tMa(year)),
170
201
  });
171
202
  };
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,6 +281,7 @@ 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,
@@ -283,8 +293,12 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
283
293
  });
284
294
 
285
295
  // ── Integrated ∫1/H(t)dt phase (R2 α-pin honoured while building) ─────────
296
+ // The phase counter every FROZEN comb reads rides the frozen era clock's
297
+ // own convention H_era = H₀·LOD/LOD₀ (plan 06 D8: two named counters — the
298
+ // coefficients were fitted against this counter and ship with it), NOT the
299
+ // unit H(t) = 13·T_p,composed that the physics publishes.
286
300
  const phaseM = createPhaseMachinery({
287
- holisticHAtAgeMa: (tMa) => deepLod.hAtAge(tMa),
301
+ holisticHAtAgeMa: (tMa) => deepLod.eraClockHAtAge(tMa),
288
302
  tableAnchorYear: startmodelYear,
289
303
  driftRefYear: startModelYearWithCorrection,
290
304
  hJ2000: H,
@@ -413,16 +427,19 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
413
427
  if (lod === null) return meanSiderealYearDays;
414
428
  return deepLod.siderealYearSecondsAtAge(tMa) / lod;
415
429
  };
430
+ // The comb family's bases are the FROZEN era clock's forms (H_era; plan 06
431
+ // D8) — the combs were fitted on them. The physical year lengths ride the
432
+ // unit through yearLengths (family B) and the tidal-chain mean.
416
433
  /** @param {number} year @returns {number} */
417
434
  const tropicalYearDaysBase = (year) => {
418
- const days = deepLod.yearInDaysAtAge(yearToTMa(year));
435
+ const days = deepLod.eraClockYearInDaysAtAge(yearToTMa(year));
419
436
  return days === null ? meanSolarYearDays : days;
420
437
  };
421
438
  /** @param {number} year @returns {number} */
422
439
  const anomalisticYearDaysBase = (year) => {
423
440
  const tMa = yearToTMa(year);
424
- const Ht = deepLod.hAtAge(tMa);
425
- const tropD = deepLod.yearInDaysAtAge(tMa);
441
+ const Ht = deepLod.eraClockHAtAge(tMa);
442
+ const tropD = deepLod.eraClockYearInDaysAtAge(tMa);
426
443
  if (Ht === null || tropD === null) return meanAnomalisticYearDays;
427
444
  return (tropD * (Ht / 16)) / (Ht / 16 - 1);
428
445
  };
@@ -456,14 +473,16 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
456
473
  },
457
474
  fns: {
458
475
  cyclesBetween: (a, b, n) => phase().cyclesBetween(a, b, n),
476
+ // The frozen era clock's deps ride ITS convention H_era (plan 06 D8):
477
+ // drift integrand, dc/dY and the real-LOD convention were fitted on it.
459
478
  analyticTropicalDays: (year) => {
460
479
  const tMa = (startmodelYear - year) / 1e6;
461
- const Ht = deepLod.hAtAge(tMa);
480
+ const Ht = deepLod.eraClockHAtAge(tMa);
462
481
  if (Ht === null) return null;
463
482
  return (deepLod.siderealYearSecondsAtAge(tMa) / 86400) * (1 - 13 / Ht);
464
483
  },
465
- meanHAtAgeMa: (tMa) => deepLod.hAtAge(tMa),
466
- meanYearRealLodDays: (tMa) => deepLod.yearInDaysAtAge(tMa),
484
+ meanHAtAgeMa: (tMa) => deepLod.eraClockHAtAge(tMa),
485
+ meanYearRealLodDays: (tMa) => deepLod.eraClockYearInDaysAtAge(tMa),
467
486
  eccentricityAt,
468
487
  eccentricityRateAt,
469
488
  },
@@ -474,9 +493,10 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
474
493
  // package's embedded deep-modes artifact; the banked-series tier is the
475
494
  // repo-data-bound instrument). Absolute dates deliberately absent — see
476
495
  // 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).
496
+ // sidereal year of date reduced by the SECULAR equinox precession
497
+ // (axial0·H(t)/H₀ with H(t) the UNIT = 13·T_p,composed — plan 06 D6/Phase
498
+ // 3, the movement's leg-1 convention, NOT the H/13 kinematic identity at
499
+ // J2000; the two are a recorded 0.09% relation tension there).
480
500
  // THE ONE of-date year-length family (owner: "move to 1 implementation")
481
501
  // — createYearLengths owns tropical (equinox-rate mean, wobble included),
482
502
  // sidereal (the D6 λ̇ channel), anomalistic (the cardinal structure on
@@ -489,7 +509,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
489
509
  // path; the npm package alone has no 8 MB series), the movement runs the
490
510
  // SERIES tier exactly like the browser — killing the mode-vs-series
491
511
  // value split (measured: 6 s on the anomalistic year at J2000).
492
- const yearLengthsM = (() => {
512
+ // The engine-D planet chains (ONE build; the one-family route's apsidal
513
+ // tangent and the lunisolar surface's n_aps read the same instance).
514
+ const kcChainsM = buildPlanetChainsFromArtifactData(CHAIN_ARTIFACT);
515
+ // The ONE-SOURCE movement: the hybrid's ε(t) (plan 06 Phase 3 S3b — THE
516
+ // published obliquity) and the one-family year lengths, on the same
517
+ // per-tier sampler.
518
+ const oneSourceM = (() => {
493
519
  const AEarth = /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements.earth;
494
520
  // The ψ̇ anchor: the CERTIFIED of-date laws at 2000 — identical to the
495
521
  // engine/browser (computeSiderealYearDaysDirect / computeSolarYearDays
@@ -501,6 +527,12 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
501
527
  const sidLaw2000 = evalYearFourier(2000, siderealYearDaysBase(2000), F.SIDEREAL_YEAR_HARMONICS);
502
528
  const solLaw2000 = evalYearFourier(2000, tropicalYearDaysBase(2000), F.TROPICAL_YEAR_HARMONICS);
503
529
  const axial0 = sidLaw2000 / (sidLaw2000 - solLaw2000);
530
+ // D6 → Phase 3 (plan 06): the deep-time ψ̇(t) the hybrid precesses on is
531
+ // the COMPOSED lunisolar rate, and since Phase 3 the unit H(t) IS
532
+ // 13 × that period (deltat/deep-time.cjs hAtAge), so the injection is
533
+ // the plain period₀·H(t)/H₀ again — on the unit, not on the frozen
534
+ // era clock's H_era (which misses the lunar 1/a³ growth: 70.9 vs
535
+ // Lantink 2022's 108.6 ± 8.5 ″/yr at 2.46 Ga; the unit reads 104.5).
504
536
  const H0 = /** @type {number} */ (deepLod.hAtAge(0));
505
537
  const seriesArt = /** @type {any} */ (secularSeriesArtifact);
506
538
  const sb = seriesArt && seriesArt.bodies && seriesArt.bodies.earth;
@@ -516,7 +548,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
516
548
  anchorInclEclipticDeg: AEarth.inclEclipticDeg,
517
549
  anchorAscNodeEclipticDeg: AEarth.ascNodeEclipticDeg,
518
550
  axialPrecessionYearsJ2000: axial0,
519
- obliquityJ2000Deg: obliquityDeg(2000),
551
+ // Phase 3 S3b: the hybrid's initial condition is the OBSERVED J2000
552
+ // mean obliquity — the IAU 2006 input constant (84381.406″), the same
553
+ // anchor the browser's _deepHistSeries and the Node one-source movement
554
+ // use. It was the K comb's own J2000 value, 0.233″ above IAU: a
555
+ // rendering-device residual imported as the initial condition of the
556
+ // physics (found when the package began publishing the hybrid).
557
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
520
558
  axialPrecessionYearsAtYearFn: (yr) => {
521
559
  const h = deepLod.hAtAge((startmodelYear - yr) / 1e6);
522
560
  return axial0 * (h === null ? 1 : h / H0);
@@ -545,13 +583,19 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
545
583
  };
546
584
  // The anomalistic rides the chain's SECULAR apsidal tangent (the same
547
585
  // rate family the panel's Prec. cell shows) — ONE helper, keplerian-chain.
548
- const kcChains = buildPlanetChainsFromArtifactData(CHAIN_ARTIFACT);
549
- return createYearLengths({
586
+ const kcChains = kcChainsM;
587
+ const yearLengths = createYearLengths({
550
588
  sampleAt,
551
589
  massLossSiderealSecondsAtYearFn: (year) => deepLod.siderealYearSecondsAtAge(yearToTMa(year)),
552
590
  apsidalSecularDegPerYrFn: (year) => computeApsidalSecularDegPerYr(year, kcChains.earth, kcChains),
553
591
  });
592
+ return {
593
+ yearLengths,
594
+ /** 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} */
595
+ epsAt: (year) => sampleAt(year).epsDeg,
596
+ };
554
597
  })();
598
+ const yearLengthsM = oneSourceM.yearLengths;
555
599
 
556
600
  /** Tropical year: mean of the four cardinal intervals. @param {number} year @returns {number} */
557
601
  const tropicalYearDays = (year) => cardinalM.computeTropicalYearLength(year);
@@ -1170,6 +1214,22 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1170
1214
  });
1171
1215
 
1172
1216
  // ── The assembled surface ─────────────────────────────────────────────────
1217
+ // Plan 06 Phase 3 S2 — THE published of-date precession period and mean
1218
+ // tropical year: the one-family route (B) inside the banked tiers
1219
+ // (|Δyear| ≤ ONE_FAMILY_WINDOW_YEARS), the unit's secular mean beyond
1220
+ // (H(t)/13; the tidal-chain year). Shared by `epoch` and `lunisolar`.
1221
+ /** @param {number} year @returns {number} */
1222
+ const publishedAxialPrecessionYearsAtYear = (year) => {
1223
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.axialPrecessionYearsAtYear(year);
1224
+ const h = deepLod.hAtAge(yearToTMa(year));
1225
+ return (h === null ? H : h) / 13;
1226
+ };
1227
+ /** @param {number} year @returns {number} */
1228
+ const publishedTropicalYearSecondsAtYear = (year) => {
1229
+ if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.tropicalYearSecondsAtYear(year);
1230
+ return deepLod.tropicalYearSecondsAtAge(yearToTMa(year));
1231
+ };
1232
+
1173
1233
  return Object.freeze({
1174
1234
  time: Object.freeze({
1175
1235
  yearFromJD,
@@ -1187,22 +1247,94 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1187
1247
  siderealYearSecondsAtYear: /** @param {number} year @returns {number} */ (year) => yearLengthsM.siderealYearSecondsAtYear(year),
1188
1248
  deltaTSecondsAtYear: deltaTSeconds,
1189
1249
  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));
1250
+ // Plan 06 Phase 3 S2 — THE published of-date precession period is the
1251
+ // one-family route (B): the hybrid's own equinox regression against
1252
+ // the λ̇-corrected sidereal year (~25,771 yr at J2000 ≈ IAU), inside
1253
+ // the banked tiers (|Δyear| ≤ ONE_FAMILY_WINDOW_YEARS); beyond, the
1254
+ // unit's SECULAR mean H(t)/13 = the composed lunisolar period (the
1255
+ // of-date wobble is unresolved there). The comb family's beat
1256
+ // sid/(sid − tropicalYearDirectDays) — the frozen era clock's
1257
+ // kinematic-day device — is no longer published here; it still
1258
+ // anchors the lunar chain's (d′) rate completion (pDynDegPerYearAt,
1259
+ // a MATCHED TRIPLE with the eclipse gates: M0(a) measured a 3–15 min
1260
+ // lunar-timing shift if it moved).
1261
+ axialPrecessionYearsAtYear: publishedAxialPrecessionYearsAtYear,
1262
+ // The same publication rule for the mean tropical year of date (SI s):
1263
+ // one-family inside the window, the tidal-chain mean beyond.
1264
+ tropicalYearSecondsAtYear: publishedTropicalYearSecondsAtYear,
1265
+ }),
1266
+ // Plan 06 Phase 3 S3 — the lunisolar precession clock as a first-class
1267
+ // surface: Earth's spin clock, ONE home for its published faces, spoken
1268
+ // in PERIODS and their RATIOS — no unit, no integer. T_p(t) is the
1269
+ // composed lunisolar period (calculation map 2.1 step 6; the internal
1270
+ // identifier H is 13·T_p by definition and is not a face here); the
1271
+ // composition's terms; the of-date beat (the published family, seamed
1272
+ // at ±2 Myr); the apsidal period from the engine-D chain's secular
1273
+ // tangent and the two ratios the owner found wandering by hand —
1274
+ // T_aps/T_p (4.33 at J2000, 0.84 … 9.5 across ±26 kyr) and T_peri/T_p
1275
+ // with T_peri = 1/(1/T_p + 1/T_aps) the perihelion-of-date period
1276
+ // (0.812 at J2000). That these read close to 13/3 and 13/16 today is
1277
+ // the J2000 reading, stated nowhere as structure.
1278
+ lunisolar: Object.freeze({
1279
+ /** The mean lunisolar precession period T_p(t), years — the composed torque rate's period. @param {number} year @returns {number} */
1280
+ meanPeriodYearsAtYear: (year) => { const h = deepLod.hAtAge(yearToTMa(year)); return (h === null ? H : h) / 13; },
1281
+ /** The composed rate ψ̇(t) = [ω/ω₀]·p₀·[f_S + (1 − f_S)(a₀/a_M)³], ″/yr. @param {number} year @returns {number} */
1282
+ meanRateArcsecPerYrAtYear: (year) => { const r = deepLod.lunisolarPrecessionRateArcsecPerYrAtAge(yearToTMa(year)); return r === null ? 1296000 / (H / 13) : r; },
1283
+ /** The of-date period — the one-family beat inside ±2 Myr, the mean beyond. @param {number} year @returns {number} */
1284
+ ofDatePeriodYearsAtYear: publishedAxialPrecessionYearsAtYear,
1285
+ /** f_S, the solar fraction of the J2000 precession torque. */
1286
+ solarShareJ2000: EPOCH_PARAMS.precessionSolarShareJ2000,
1287
+ /** (a₀/a_M(t))³ — the lunar torque's growth on the recession history. @param {number} year @returns {number} */
1288
+ lunarTorqueFactorAtYear: (year) => { const f = deepLod.lunarTorqueFactorAtAge(yearToTMa(year)); return f === null ? 1 : f; },
1289
+ /** f_S + (1 − f_S)(a₀/a_M)³ — the torque term the unit divides H_era by. @param {number} year @returns {number} */
1290
+ torqueTermAtYear: (year) => { const t = deepLod.precessionTorqueTermAtAge(yearToTMa(year)); return t === null ? 1 : t; },
1291
+ /** The hybrid's precession constant α = p₀ / cos ε₀, ″/yr (ε₀ the J2000 obliquity input). */
1292
+ torqueConstantJ2000ArcsecPerYr: (1296000 / (H / 13)) / Math.cos((C.earthOrbital.obliquityJ2000_deg * Math.PI) / 180),
1293
+ /** 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} */
1294
+ apsidalPeriodYearsAtYear: (year) => (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS ? 360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM) : null),
1295
+ /** T_aps(t) / T_p(t) — the apsidal period in mean precession periods (4.33 at J2000, a fitted reading; 0.84 … 9.5 across ±26 kyr, measured); null beyond the published window. @param {number} year @returns {number|null} */
1296
+ apsidalPerPrecessionAtYear: (year) => {
1297
+ if (Math.abs(year - 2000) > ONE_FAMILY_WINDOW_YEARS) return null;
1298
+ const h = deepLod.hAtAge(yearToTMa(year));
1299
+ return (360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM)) / ((h === null ? H : h) / 13);
1300
+ },
1301
+ /** 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; null beyond the published window. @param {number} year @returns {number|null} */
1302
+ periOfDatePeriodYearsAtYear: (year) => {
1303
+ if (Math.abs(year - 2000) > ONE_FAMILY_WINDOW_YEARS) return null;
1304
+ const h = deepLod.hAtAge(yearToTMa(year));
1305
+ const tp = (h === null ? H : h) / 13, taps = 360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM);
1306
+ return 1 / (1 / tp + 1 / taps);
1307
+ },
1308
+ /** T_peri(t) / T_p(t) (0.812 at J2000 — the J2000 reading); null beyond the published window. @param {number} year @returns {number|null} */
1309
+ periOfDatePerPrecessionAtYear: (year) => {
1310
+ if (Math.abs(year - 2000) > ONE_FAMILY_WINDOW_YEARS) return null;
1311
+ const h = deepLod.hAtAge(yearToTMa(year));
1312
+ const tp = (h === null ? H : h) / 13, taps = 360 / computeApsidalSecularDegPerYr(year, kcChainsM.earth, kcChainsM);
1313
+ return (1 / (1 / tp + 1 / taps)) / tp;
1197
1314
  },
1315
+ /** The published of-date window, years from 2000. */
1316
+ publishedWindowYears: ONE_FAMILY_WINDOW_YEARS,
1198
1317
  }),
1199
1318
  earth: Object.freeze({
1200
1319
  perihelionLongitudeDeg: earthPerihelionDeg,
1201
- obliquityDeg,
1320
+ // Plan 06 Phase 3 S3b: THE published obliquity is the hybrid — one
1321
+ // torque law integrated on the dynamical orbit plane of the N-body
1322
+ // chain, zero fitted constants (≡ La2004 to ≤ 4″ over ±50 kyr). The
1323
+ // fitted 16-harmonic K comb, a device fitted to the scene's own
1324
+ // wheel geometry (0.005″ to it, 1.27° off La2004 at −20 kyr), keeps
1325
+ // its name below as the frozen era clock's device: it anchors the
1326
+ // hybrid at J2000, the kinematic-day stack and the lunar arguments.
1327
+ obliquityDeg: /** @param {number} year @returns {number} */ (year) => oneSourceM.epsAt(year),
1328
+ obliquityCombDeg: obliquityDeg,
1202
1329
  eccentricity: eccentricityAt,
1203
1330
  inclinationDeg,
1204
1331
  ascendingNodeDeg,
1205
1332
  }),
1333
+ // The DEVICE family (plan 06 Phase 3 S2): the frozen era clock's comb
1334
+ // year lengths (A), its cardinal 4-mean (A′) and the kinematic-day stack
1335
+ // built on them (the ΔT stack's Layer 3/4 basis, USNO-anchored). NOT the
1336
+ // published year lengths — those are `yearLengths` (the one-family
1337
+ // route) and the seamed `epoch.*AtYear` surfaces above.
1206
1338
  lengths: Object.freeze({
1207
1339
  tropicalYearDays,
1208
1340
  tropicalYearDirectDays,