@essrt/physics 7.0.0 → 9.1.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.
Files changed (41) hide show
  1. package/README.md +2 -2
  2. package/package.json +4 -2
  3. package/src/constants/coefficients.d.ts +1 -38
  4. package/src/constants/coefficients.js +2 -5
  5. package/src/constants/generated.d.ts +109 -25
  6. package/src/constants/generated.js +226 -96
  7. package/src/earth/deep-orbital-history.cjs +10 -0
  8. package/src/earth/frame-of-date.cjs +105 -0
  9. package/src/eclipse/besselian.cjs +41 -17
  10. package/src/eclipse/sun-planetary-completion.cjs +55 -14
  11. package/src/index.js +9 -1
  12. package/src/model.js +262 -124
  13. package/src/moon/apparent.cjs +45 -126
  14. package/src/moon/deep-ecc-channel.cjs +59 -12
  15. package/src/moon/deep-modes-artifact.cjs +4 -2
  16. package/src/moon/month-chain.cjs +21 -10
  17. package/src/moon/series-extension.cjs +2 -1
  18. package/src/moon/series.cjs +21 -4
  19. package/src/planets/chain-artifact.js +2 -2
  20. package/src/planets/fibonacci-laws.cjs +7 -42
  21. package/src/planets/model.cjs +10 -13
  22. package/src/planets/secular-shape.cjs +36 -0
  23. package/src/planets/spin-channel.cjs +391 -0
  24. package/types/constants/coefficients.d.ts +1 -38
  25. package/types/constants/generated.d.ts +109 -25
  26. package/types/earth/deep-orbital-history.d.cts +2 -0
  27. package/types/earth/frame-of-date.d.cts +37 -0
  28. package/types/eclipse/besselian.d.cts +5 -7
  29. package/types/eclipse/sun-planetary-completion.d.cts +14 -10
  30. package/types/index.d.ts +3 -1
  31. package/types/model.d.ts +26 -0
  32. package/types/moon/apparent.d.cts +7 -68
  33. package/types/moon/deep-ecc-channel.d.cts +31 -2
  34. package/types/moon/deep-modes-artifact.d.cts +2 -0
  35. package/types/moon/month-chain.d.cts +4 -0
  36. package/types/planets/fibonacci-laws.d.cts +0 -29
  37. package/types/planets/model.d.cts +10 -4
  38. package/types/planets/secular-shape.d.cts +30 -0
  39. package/types/planets/spin-channel.d.cts +193 -0
  40. package/src/planets/predict.cjs +0 -324
  41. package/types/planets/predict.d.cts +0 -88
package/src/model.js CHANGED
@@ -21,11 +21,12 @@ import { deriveEpochParams } from './layer0/derive-params.js';
21
21
  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
- import { createCardinalModel } from './cardinal/index.cjs';
25
24
  import { createYearLengths, ONE_FAMILY_WINDOW_YEARS } from './earth/year-lengths.cjs';
26
25
  import { createDeepOrbitalHistory } from './earth/deep-orbital-history.cjs';
27
26
  import { CHAIN_ARTIFACT } from './planets/chain-artifact.js';
28
27
  import { buildPlanetChainsFromArtifactData, computeApsidalSecularDegPerYr } from './planets/keplerian-chain.cjs';
28
+ import { computeSecularShape } from './planets/secular-shape.cjs';
29
+ import { createPlanetSpinChannelFromArtifacts, computeObliquityJ2000Deg } from './planets/spin-channel.cjs';
29
30
  import { createDeltaTCycles } from './deltat/cycles.cjs';
30
31
  import { createDeepTimeLod } from './deltat/deep-time.cjs';
31
32
  import { createMoonRecessionHistory, createSolarChannelBudget } from './deltat/recession-history.cjs';
@@ -37,7 +38,6 @@ import { createChainCycleIntegrator } from './chain-cycles/index.cjs';
37
38
  import { createMoonArguments, jdToDecimalYear } from './moon/arguments.cjs';
38
39
  import { createMoonSeries } from './moon/series.cjs';
39
40
  import { createSunPlanetaryCompletion } from './eclipse/sun-planetary-completion.cjs';
40
- import { moonSeriesExtensionDeg } from './moon/series-extension.cjs';
41
41
  import { createEclipseFinders } from './eclipse/finders.cjs';
42
42
  import { createBesselian } from './eclipse/besselian.cjs';
43
43
  import { driver2PeriodSecondsAtAge } from './planets/orbit-chain.cjs';
@@ -102,7 +102,6 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
102
102
  // wobble-marker distance). It is the K calibration input ONLY — Earth's
103
103
  // eccentricity law does not use it; see eccentricityAt (base' derived) below.
104
104
  const eccentricityAmplitude = C.earth.eccentricityAmplitude;
105
- const earthRAAngle = 2 * earthInclAmplitude - (earthInclAmplitude * earthInclAmplitude) / earthtiltMean;
106
105
  const earthInclMean = C.earthOrbital.earthInclinationJ2000_deg
107
106
  - earthInclAmplitude * Math.cos(((C.earthOrbital.earthPerihelionLongitudeJ2000
108
107
  - C.earthOrbital.earthInclinationCycleAnchor) * Math.PI) / 180);
@@ -426,7 +425,6 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
426
425
  /** de/dyear of the Sun's e — the cardinal braid's equation-of-centre
427
426
  * derivative rides it (±0.5-yr central difference; the constant offset
428
427
  * cancels). @param {number} year @returns {number} */
429
- const sunEccentricityRateAt = laws.eccentricityRateAt ?? ((year) => oneSourceM.eAt(year + 0.5) - oneSourceM.eAt(year - 0.5));
430
428
  /** @param {number} year @returns {number} */
431
429
  const inclinationDeg = (year) => earthInclMean
432
430
  - earthInclAmplitude * Math.cos(phaseRadians(balancedYear, year, 3));
@@ -502,39 +500,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
502
500
  + RA_DAY_OFFSET_ECC_MS * Math.cos(phaseRadians(balancedYear, year, 16))
503
501
  + RA_DAY_OFFSET_OBLIQ_MS * Math.cos(phaseRadians(balancedYear, year, 8));
504
502
 
505
- // ── Cardinal-point model ──────────────────────────────────────────────────
506
- const cardinalM = createCardinalModel({
507
- isDeepTime: () => true,
508
- constants: {
509
- anchors: F.CARDINAL_POINT_ANCHORS_ADJUSTED,
510
- harmonics: F.CARDINAL_POINT_HARMONICS,
511
- eccTerms: F.CARDINAL_POINT_ECC_TERMS,
512
- jointTerms: F.CARDINAL_POINT_JOINT_TERMS,
513
- derived: F.CARDINAL_POINT_DERIVED,
514
- tropicalHarmonics: F.TROPICAL_YEAR_HARMONICS,
515
- balancedYear,
516
- meanSolarYearDays,
517
- hJ2000: H,
518
- tiltMeanDeg: earthtiltMean,
519
- raAngleDeg: earthRAAngle,
520
- inclAmplitudeDeg: earthInclAmplitude,
521
- },
522
- fns: {
523
- cyclesBetween: (a, b, n) => phase().cyclesBetween(a, b, n),
524
- // The frozen era clock's deps ride ITS convention H_era (plan 06 D8):
525
- // drift integrand, dc/dY and the real-LOD convention were fitted on it.
526
- analyticTropicalDays: (year) => {
527
- const tMa = (startmodelYear - year) / 1e6;
528
- const Ht = deepLod.eraClockHAtAge(tMa);
529
- if (Ht === null) return null;
530
- return (deepLod.siderealYearSecondsAtAge(tMa) / 86400) * (1 - 13 / Ht);
531
- },
532
- meanHAtAgeMa: (tMa) => deepLod.eraClockHAtAge(tMa),
533
- meanYearRealLodDays: (tMa) => deepLod.eraClockYearInDaysAtAge(tMa),
534
- eccentricityAt: sunEccentricityAt,
535
- eccentricityRateAt: sunEccentricityRateAt,
536
- },
537
- });
503
+ // ── Cardinal points (the fitted model retired, R1)──────────────────────────────────────────────────
504
+ // Plan 06 R1: the fitted cardinal-point model (CARDINAL_POINT_* — the §10
505
+ // derived form on the frozen era clock, fitted to the retired K scene's
506
+ // events; measured 60–140 min from Meeus ch. 27 at 0..−1000 and 6–25 min
507
+ // in 1500–2900) left this package. The instants are the CROSSINGS of the
508
+ // one Sun the scene renders and the eclipse chain certifies — see
509
+ // `cardinalCrossingJdUT` below the finders.
538
510
  // ── One-source cardinal structure (D4b) ───────────────────────────────────
539
511
  // The EoC layer (year lengths, crossing offsets, the e(t)-proportional
540
512
  // spread) on the one-source movement's own e(t)/ϖ(t) — the MODE tier (the
@@ -646,9 +618,19 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
646
618
  };
647
619
  })();
648
620
  const yearLengthsM = oneSourceM.yearLengths;
621
+ /** THE one-family tropical year of date in SI seconds: route B inside the
622
+ * one-family window, the tidal-chain year beyond (plan 06 R1 — the Sun's
623
+ * mean longitude, the cardinal instants and the published year lengths
624
+ * all ride this one function). @param {number} year @returns {number} */
625
+ const oneFamilyTropicalYearSeconds = (year) => (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS
626
+ ? yearLengthsM.tropicalYearSecondsAtYear(year)
627
+ : (deepLod.tropicalYearSecondsAtAge(yearToTMa(year)) ?? meanSolarYearDays * 86400));
649
628
 
650
- /** Tropical year: mean of the four cardinal intervals. @param {number} year @returns {number} */
651
- const tropicalYearDays = (year) => cardinalM.computeTropicalYearLength(year);
629
+ /** Tropical year of date in DAYS OF THE EPOCH (the kinematic day below) —
630
+ * the one-family SI year over the epoch's day length, so solarYearSeconds
631
+ * ≡ the one-family year. Plan 06 R1: formerly the fitted cardinal model's
632
+ * 4-mean. @param {number} year @returns {number} */
633
+ const tropicalYearDays = (year) => oneFamilyTropicalYearSeconds(year) / dayLengthSeconds(year);
652
634
  /** @param {number} year @returns {number} */
653
635
  const tropicalYearDirectDays = (year) => evalYearFourier(year, tropicalYearDaysBase(year), F.TROPICAL_YEAR_HARMONICS);
654
636
 
@@ -737,10 +719,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
737
719
  const mp = C.planets[k];
738
720
  const ar = C.planetOrbitalElements[k];
739
721
  const ecl = /** @type {number} */ (fractionToYears(mp.perihelionEclipticFraction));
740
- const axial = /** @type {number} */ (fractionToYears(mp.axialPrecessionFraction));
741
- const obliquityCycle = fractionToYears(mp.obliquityCycleFraction)
742
- ?? Math.abs(1 / (1 / ecl - 1 / (H / 13)));
743
- const wobble = FL.computeWobblePeriodYears(ecl, axial, H);
722
+ // Plan 06 Phase 7 commit 2: the K device's integer axial and obliquity
723
+ // fractions are retired. The eccentricity law's cycle period is the
724
+ // chain's OWN g-mode beat (the dominant mode × largest companion of the
725
+ // planet's eccentricity vector), its obliquity input the DERIVED J2000
726
+ // obliquity of the spin channel (the IAU pole against the chain's J2000
727
+ // plane, acute form) — both from the governed artifacts, no fractions.
728
+ const wobble = computeSecularShape(/** @type {any} */ (CHAIN_ARTIFACT), k).beatYears;
744
729
  const il = FL.computeInclinationLaw({
745
730
  fibonacciD: mp.fibonacciD,
746
731
  massFrac: massFraction[k],
@@ -749,11 +734,13 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
749
734
  inclinationCycleAnchor: mp.inclinationCycleAnchor,
750
735
  antiPhase: mp.antiPhase || false,
751
736
  }, PSI);
752
- const obliquityMean = FL.computeObliquityMeanSnapshot({
753
- axialTiltJ2000: ar.axialTiltJ2000,
754
- invPlaneInclinationAmplitude: il.amplitude,
755
- perihelionEclipticYears: ecl,
756
- }, obliquityCycle, { H, t2000 });
737
+ const obliquityDerived = computeObliquityJ2000Deg({
738
+ spin: C.planetSpinPhysical[k],
739
+ anchorInclEclipticDeg: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements[k].inclEclipticDeg,
740
+ anchorAscNodeEclipticDeg: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements[k].ascNodeEclipticDeg,
741
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
742
+ });
743
+ const obliquityMean = Math.min(obliquityDerived, 180 - obliquityDerived); // acute: the K law reads sin|ε|
757
744
  const el = FL.computeEccentricityLaw({
758
745
  fibonacciD: mp.fibonacciD,
759
746
  massFrac: massFraction[k],
@@ -773,8 +760,6 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
773
760
  longitudePerihelion: ar.longitudePerihelion,
774
761
  ascendingNodeCyclesIn8H: mp.ascendingNodeCyclesIn8H,
775
762
  ascendingNodePeriod: -(8 * H) / mp.ascendingNodeCyclesIn8H,
776
- axialPrecessionYears: axial,
777
- obliquityCycle,
778
763
  wobblePeriod: wobble,
779
764
  fibonacciD: mp.fibonacciD,
780
765
  antiPhase: mp.antiPhase || false,
@@ -793,6 +778,32 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
793
778
  });
794
779
  }
795
780
 
781
+ // ── The planets' spin channel (plan 06 Phase 7): the precession constant
782
+ // from each planet's OWN torques (astro-reference planetSpinPhysical) on the
783
+ // model's OWN orbit — the chain's J2000 a/e/plane and the deep ζ table —
784
+ // integrated as dŝ/dt = α(ŝ·n̂)(ŝ×n̂) from the IAU J2000 pole. ONE home:
785
+ // planets/spin-channel.cjs; built lazily per planet, pure in `year`.
786
+ /** @type {Map<string, ReturnType<typeof createPlanetSpinChannelFromArtifacts>>} */
787
+ const spinChannels = new Map();
788
+ /** @param {string} k */
789
+ const planetSpin = (k) => {
790
+ if (!PLANET_KEYS.includes(k)) throw new Error(`planets.spin: unknown planet '${k}'`);
791
+ let ch = spinChannels.get(k);
792
+ if (!ch) {
793
+ ch = createPlanetSpinChannelFromArtifacts({
794
+ key: k,
795
+ planetSpinPhysical: C.planetSpinPhysical,
796
+ chainAnchorElements: /** @type {any} */ (CHAIN_ARTIFACT).j2000AnchorElements,
797
+ planetZeta: /** @type {any} */ (DEEP_MODES_ARTIFACT).planetZeta,
798
+ massFractionOfSun: massFraction[k],
799
+ gmSunKm3S2: GM_SUN,
800
+ obliquityJ2000Deg: C.earthOrbital.obliquityJ2000_deg,
801
+ });
802
+ spinChannels.set(k, ch);
803
+ }
804
+ return ch;
805
+ };
806
+
796
807
  /** Perihelion longitude (linear lattice rate). @param {string} k @param {number} year @returns {number} */
797
808
  const planetPerihelionDeg = (k, year) => {
798
809
  const p = PLANET_RECORDS[k];
@@ -858,6 +869,10 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
858
869
  const nNodalEJ2000 = nNodalIJ2000 + 13;
859
870
  const moonApsidalJ2000Seconds = (totalDaysInH / nApsidalEJ2000) * meanLengthOfDay;
860
871
  const moonNodalJ2000Seconds = (totalDaysInH / nNodalEJ2000) * meanLengthOfDay;
872
+ // the OF-DATE anchors (the H² counter's J2000 period — the Meeus/IERS
873
+ // observables 3231.49 d / 6798.38 d)
874
+ const moonApsidalOfDateJ2000Seconds = (totalDaysInH / nApsidalIJ2000) * meanLengthOfDay;
875
+ const moonNodalOfDateJ2000Seconds = (totalDaysInH / nNodalIJ2000) * meanLengthOfDay;
861
876
  const moonSiderealMonthJ2000Seconds = moonSiderealMonth * meanLengthOfDay;
862
877
 
863
878
  // 8H-lattice derived months (constants.js §Moon derived months)
@@ -887,6 +902,8 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
887
902
  nNodalOfDateJ2000: nNodalIJ2000,
888
903
  moonApsidalJ2000Seconds,
889
904
  moonNodalJ2000Seconds,
905
+ moonApsidalOfDateJ2000Seconds,
906
+ moonNodalOfDateJ2000Seconds,
890
907
  moonSiderealMonthJ2000Seconds,
891
908
  sPerigee: MOON_ECC_SENSITIVITY_PERIGEE,
892
909
  sNode: MOON_ECC_SENSITIVITY_NODE,
@@ -1113,65 +1130,73 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1113
1130
  // same movement and is retired by the unification (measured identical on
1114
1131
  // every modern gate).
1115
1132
  const sunMeanLongitudeDegAt = (() => {
1116
- // SW PHASE B — the CLOSED FORM on the integrated lattice phase
1117
- // (supersedes the E5 ±3,000-yr trapezoid table; valid at EVERY epoch,
1118
- // no table, no domain window). drift = D_smooth + Σₖ D_k + D_torque:
1119
- // · D_smooth — the deep chain's smooth SI tropical-year physics
1120
- // (tropicalYearDaysBase × LOD), two-point trapezoid: the same
1121
- // structure the scene twin uses, so the scene δ stays bounded.
1122
- // · Each oscillatory term integrates ANALYTICALLY: the antiderivative
1123
- // of cos/sin on the integrated phase is (H/2πk)·[sin/−cos] — the H
1124
- // drift lives in the phase itself; the amplitude factor uses H_J2000
1125
- // (ppm-class difference, negligible).
1126
- // · THE REBASE (E5's corpus-preferred J2000 rate anchor) is applied
1127
- // PER HARMONIC as a SIN-SATURATED ramp, (H/2πk)·sin(Δφₖ): equal to
1128
- // the linear rebase for |Δφₖ| ≪ 1 (the corpus era) and bounded by
1129
- // the harmonic's own period beyond — extending a linearized local
1130
- // slope to deep time would be the fitted-linear-slope trap. Every
1131
- // quantity is derived; zero new constants; each component and its
1132
- // slope vanish at year 2000 by construction (the drift-only
1133
- // property is exact, not tabulated).
1134
- const RATE_LIN = sunTropicalRateDegPerCy / 100; // deg / SI yr
1135
- const precessionP0DegPerYr = 13 * 360 / H;
1136
- const tanEps = Math.tan(earthtiltMean * Math.PI / 180);
1137
- const A_RAD = earthInclAmplitude * Math.PI / 180;
1138
- /** @param {number} year @returns {number} */
1139
- const rateSmooth = (year) => 360 * (365.25 * 86400)
1140
- / (tropicalYearDaysBase(year)
1141
- * (deepLod.lodSecondsAtAge(yearToTMa(year)) ?? meanLengthOfDay));
1142
- /** @type {{rateSm0: number, ph0: Map<number, number>}|null} */
1143
- let anchor = null;
1144
- return /** @param {number} year @returns {number} */ (year) => {
1145
- if (anchor === null) {
1146
- const ph0 = new Map();
1147
- for (const [k] of F.TROPICAL_YEAR_HARMONICS) ph0.set(k, phaseRadians(balancedYear, 2000, k));
1148
- for (const k of [3, 8]) if (!ph0.has(k)) ph0.set(k, phaseRadians(balancedYear, 2000, k));
1149
- anchor = { rateSm0: rateSmooth(2000), ph0 };
1150
- }
1133
+ // Plan 06 R1 — the Sun's mean longitude of date is the INTEGRAL of the
1134
+ // one-source tropical year: L(t) = L0 + 360·∫_{2000}^{t} dt′/T_trop(t′),
1135
+ // T_trop the route-B tropical year of date in SI seconds (the ONE
1136
+ // year-length family; the frozen era clock's tropicalYearDaysBase × LOD
1137
+ // and its comb ripple + torque term left here). MEASURED before the
1138
+ // move (tools/explore r1 probes, plan 06 record): the route-B tropical
1139
+ // year equals the IAU/Laskar drift expression to 0.1 s over −1000..+3000
1140
+ // while the era-clock year carried NO secular drift (+1.09 s flat), so
1141
+ // the former mean longitude wandered +12 min around 500 AD and diverged
1142
+ // −3.3 min/cy after 2100 against both Meeus ch. 27 and the one-source
1143
+ // integral — outside every eclipse and JPL gate. MODERN-WINDOW COST,
1144
+ // stated (plan 06 I1 corrected an earlier misreading of the cache's
1145
+ // clock): against the JPL Sun cache (UT instants, verified; the registry
1146
+ // instrument's bridge) the one-source Sun read 1.29″ sd in 1970–2049
1147
+ // where the era-clock Sun read 0.80″ — both carried ~0.02–0.03″/yr
1148
+ // trends against JPL over 1900–2100; accepted because over millennia
1149
+ // the era-clock Sun is minutes off. Plan 06 I2 located the trend: the
1150
+ // Earth–Mars–Jupiter long inequality (1783 yr, 6.3″) the smooth year
1151
+ // cannot carry — derived on the model's own N-body and shipped in the
1152
+ // planetary completion, the instrument reads 1.03″ (Horizons ±3000 yr:
1153
+ // modern-window mean +8.4 → +0.8″, per-millennium sd roughly halved).
1154
+ // NUMERICS (rate vs point value): a cumulative trapezoid table of
1155
+ // cycles, yearly inside ±20,000 yr and per century beyond, grown on
1156
+ // demand from 2000 in both directions and interpolated inside a cell —
1157
+ // never a rate multiplied by a span. Beyond the one-family window the
1158
+ // tidal-chain year continues the integrand (the scene overlay ends at
1159
+ // 20,000 yr; the finders refuse out-of-domain epochs upstream).
1160
+ const JULIAN_YEAR_S = 365.25 * 86400;
1161
+ const tropSec = oneFamilyTropicalYearSeconds;
1162
+ const FINE_SPAN = 20000, COARSE_STEP = 100;
1163
+ /** cycles from 2000 to 2000 ± i·step (fwd/bwd tiers) */
1164
+ /** @type {number[]} */ const fwdFine = [0];
1165
+ /** @type {number[]} */ const bwdFine = [0];
1166
+ /** @type {number[]} */ const fwdCoarse = [];
1167
+ /** @type {number[]} */ const bwdCoarse = [];
1168
+ /** UNSIGNED trapezoid cycles over the span between two years (Julian
1169
+ * years → SI seconds over the one-family year); the caller applies the
1170
+ * direction sign. @param {number} ya @param {number} yb @returns {number} */
1171
+ const segAbs = (ya, yb) => 0.5 * (Math.abs(yb - ya) * JULIAN_YEAR_S) * (1 / tropSec(ya) + 1 / tropSec(yb));
1172
+ /** signed cycles from 2000 to `year` @param {number} year @returns {number} */
1173
+ const cyclesTo = (year) => {
1151
1174
  const dy = year - 2000;
1152
- // smooth deep physics (two-point trapezoid; exact 0 at 2000)
1153
- let drift = 0.5 * (rateSmooth(year) - anchor.rateSm0) * dy;
1154
- // year-harmonic ripple: rate ≈ −(RATE_LIN/T̄)·h(y); antiderivative +
1155
- // sin-saturated per-harmonic rebase
1156
- const HK = H / (2 * Math.PI);
1157
- for (const [k, sK, cK] of F.TROPICAL_YEAR_HARMONICS) {
1158
- const ph = phaseRadians(balancedYear, year, k);
1159
- const ph0 = /** @type {number} */ (anchor.ph0.get(k));
1160
- const scale = -(RATE_LIN / meanSolarYearDays) * (HK / k);
1161
- const anti = (-sK) * (Math.cos(ph) - Math.cos(ph0)) + cK * (Math.sin(ph) - Math.sin(ph0));
1162
- const h0 = sK * Math.sin(ph0) + cK * Math.cos(ph0);
1163
- drift += scale * (anti - h0 * Math.sin(ph - ph0));
1175
+ const s = dy >= 0 ? 1 : -1, a = Math.abs(dy);
1176
+ // WARM THE SAMPLER TO THE TARGET FIRST (plan 06 R4, measured): the
1177
+ // table below grows outward in 1-yr / 100-yr steps, and every step
1178
+ // beyond ~1.6 Myr enlarged the one-family sampler's >2-Myr tier just
1179
+ // past its span, rebuilding the whole deep integration about every 32
1180
+ // steps — ~125 rebuilds and 22 s for the first deep-epoch call (the
1181
+ // scene's first jump to −5 Myr), 0.4 ms once built. One read at the
1182
+ // clamped target builds the tier at its final span; the grid stores
1183
+ // are endpoint-independent, so every value is bit-identical.
1184
+ tropSec(2000 + s * Math.min(a, ONE_FAMILY_WINDOW_YEARS));
1185
+ const fine = s > 0 ? fwdFine : bwdFine;
1186
+ const growFine = (/** @type {number} */ upto) => { while (fine.length <= upto) { const k = fine.length; fine.push(fine[k - 1] + s * segAbs(2000 + s * (k - 1), 2000 + s * k)); } };
1187
+ if (a <= FINE_SPAN) {
1188
+ const i = Math.floor(a), f = a - i;
1189
+ growFine(i + 1);
1190
+ return fine[i] + f * (fine[i + 1] - fine[i]);
1164
1191
  }
1165
- // torque (E5): rate = −p₀·tanε·δε, δε = A(−cos φ₃ + cos φ₈); same
1166
- // antiderivative + per-component sin-saturated rebase
1167
- for (const [k, sgn] of [[3, -1], [8, 1]]) {
1168
- const ph = phaseRadians(balancedYear, year, k);
1169
- const ph0 = /** @type {number} */ (anchor.ph0.get(k));
1170
- const scale = -precessionP0DegPerYr * tanEps * A_RAD * sgn * (HK / k);
1171
- drift += scale * ((Math.sin(ph) - Math.sin(ph0)) - Math.cos(ph0) * Math.sin(ph - ph0));
1172
- }
1173
- return sunL0Deg + sunTropicalRateDegPerCy * dy / 100 + drift;
1192
+ growFine(FINE_SPAN);
1193
+ const coarse = s > 0 ? fwdCoarse : bwdCoarse;
1194
+ if (coarse.length === 0) coarse.push(fine[FINE_SPAN]);
1195
+ const c = (a - FINE_SPAN) / COARSE_STEP, ci = Math.floor(c), cf = c - ci;
1196
+ while (coarse.length <= ci + 1) { const k = coarse.length; const y0 = 2000 + s * (FINE_SPAN + (k - 1) * COARSE_STEP); coarse.push(coarse[k - 1] + s * segAbs(y0, y0 + s * COARSE_STEP)); }
1197
+ return coarse[ci] + cf * (coarse[ci + 1] - coarse[ci]);
1174
1198
  };
1199
+ return /** @param {number} year @returns {number} */ (year) => sunL0Deg + 360 * cyclesTo(year);
1175
1200
  })();
1176
1201
 
1177
1202
  // Eclipse finders — wired like the engine probe (tools/verify/
@@ -1213,20 +1238,34 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1213
1238
  // μ = 1/(1+M_E/M_M) — 6.4399″ at current constants, tracks them live.
1214
1239
  const embWobbleArcsec = (moonDistanceKm / (MASS_RATIO_EARTH_MOON + 1) / currentAUDistance)
1215
1240
  * (648000 / Math.PI);
1216
- // FQ-5 N3 — the completion's carriers are FRAMEWORK-derived: one
1217
- // revolution per the model's own tropical period records (Earth from
1218
- // the framework mean solar year; the Moon-elongation rate from the
1219
- // sidereal month/year identity). The v3 table's amplitudes were
1220
- // re-extracted on exactly these rates (the carrier↔table matched pair).
1241
+ // FQ-5 N3 — the completion's carriers are FRAMEWORK-derived from the
1242
+ // model's own planet period records (the Moon-elongation rate from the
1243
+ // sidereal month/year identity). Plan 06 I2 — the carriers are SIDEREAL
1244
+ // (measured): the records' solarYearInput are OF-DATE periods (their
1245
+ // rates sit +1.39..1.62°/cy above the VSOP sidereal mean motions — the
1246
+ // framework precession plus input rounding), and a perturbation argument
1247
+ // is inertial (D'Alembert: only Σk = 0 arguments are frame-free), so on
1248
+ // of-date carriers every Σk ≠ 0 argument drifted by Σk·ψ(t) — 42..52° at
1249
+ // −3000 for the table's Σk = −1 terms (their ancient scatter against
1250
+ // Horizons 6.4 → 4.4″ on sidereal carriers), and 6 % of frequency for the
1251
+ // 1783-yr long inequality (1683 yr on the of-date carriers). Constant-free:
1252
+ // the record rate minus the model's own J2000 precession p₀ =
1253
+ // 360·36525·(1/T_trop − 1/T_sid) for the planets, the framework sidereal
1254
+ // year for Earth; identical to the N3 carriers at J2000 (same phase
1255
+ // anchors), ≤ 1.4°·|Σk| apart at the 200-yr extraction window's edges
1256
+ // (the composed table reproduces the N3 one there: JPL 1900–2100
1257
+ // all-phase sd unchanged to 0.01″). The parity gate's fingerprint mirrors
1258
+ // this arithmetic (test/create-model-parity.test.mjs).
1221
1259
  const degPerCyOf = /** @param {number} cyclesPerDay */ (cyclesPerDay) => 360 * 36525 * cyclesPerDay;
1260
+ const carrierPrecessionDegPerCy = degPerCyOf(1 / meanSolarYearDays) - degPerCyOf(1 / meanSiderealYearDays);
1222
1261
  const carrierRatesDegPerCy = {
1223
1262
  planets: [
1224
- degPerCyOf(1 / C.planetOrbitalElements.mercury.solarYearInput),
1225
- degPerCyOf(1 / C.planetOrbitalElements.venus.solarYearInput),
1226
- degPerCyOf(1 / meanSolarYearDays),
1227
- degPerCyOf(1 / C.planetOrbitalElements.mars.solarYearInput),
1228
- degPerCyOf(1 / C.planetOrbitalElements.jupiter.solarYearInput),
1229
- degPerCyOf(1 / C.planetOrbitalElements.saturn.solarYearInput),
1263
+ degPerCyOf(1 / C.planetOrbitalElements.mercury.solarYearInput) - carrierPrecessionDegPerCy,
1264
+ degPerCyOf(1 / C.planetOrbitalElements.venus.solarYearInput) - carrierPrecessionDegPerCy,
1265
+ degPerCyOf(1 / meanSiderealYearDays),
1266
+ degPerCyOf(1 / C.planetOrbitalElements.mars.solarYearInput) - carrierPrecessionDegPerCy,
1267
+ degPerCyOf(1 / C.planetOrbitalElements.jupiter.solarYearInput) - carrierPrecessionDegPerCy,
1268
+ degPerCyOf(1 / C.planetOrbitalElements.saturn.solarYearInput) - carrierPrecessionDegPerCy,
1230
1269
  ],
1231
1270
  moonElongation: degPerCyOf(1 / moonSiderealMonthInput - 1 / meanSiderealYearDays),
1232
1271
  };
@@ -1238,7 +1277,10 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1238
1277
  },
1239
1278
  sunLonDegAt: /** @param {number} jdUT */ (jdUT) => eclipseFinders.sunLonDegAt(jdUT),
1240
1279
  sunCompletionDeg: sunPlanetaryCompletionDeg,
1241
- moonExtensionAt: moonSeriesExtensionDeg,
1280
+ // plan 06 R3 item 1: the location tier on APPARENT places — the Sun's derived κ
1281
+ // (the same helper the cardinal instants use), the Moon's relative light-time
1282
+ // inside the besselian; the series-extension tail now lives in sceneEvalAt.
1283
+ sunAberrationDegAt: /** @param {number} year */ (year) => sunAberrationArcsecAt(year) / 3600,
1242
1284
  deltaTSecondsAt: /** @param {number} jd */ (jd) => (jdTTFromUT(jd) - jd) * 86400,
1243
1285
  obliquityDegAt: obliquityDeg,
1244
1286
  eccentricityAt: sunEccentricityAt,
@@ -1256,6 +1298,7 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1256
1298
  gmstMeanSiderealT0Deg: C.physicalConstants.gmstMeanSiderealT0Deg,
1257
1299
  gmstMeanSiderealRateDegPerDay: C.physicalConstants.gmstMeanSiderealRateDegPerDay,
1258
1300
  gmstMeanSiderealT2Deg: C.physicalConstants.gmstMeanSiderealT2Deg,
1301
+ speedOfLightKmS: C.physicalConstants.speedOfLight,
1259
1302
  },
1260
1303
  });
1261
1304
 
@@ -1277,6 +1320,93 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1277
1320
  if (Math.abs(year - 2000) <= ONE_FAMILY_WINDOW_YEARS) return yearLengthsM.tropicalYearSecondsAtYear(year);
1278
1321
  return deepLod.tropicalYearSecondsAtAge(yearToTMa(year));
1279
1322
  };
1323
+ // ── Cardinal instants (plan 06 R1) ─────────────────────────────────────
1324
+ /** The completed certified Sun (finder Sun − the derived planetary
1325
+ * completion) at a UT model-JD — the same longitude the scene renders
1326
+ * inside the overlay window; MEAN GEOMETRIC, mean equinox of date.
1327
+ * @param {number} jdUT @returns {number} */
1328
+ const sunLonCompletedDegAtJdUT = (jdUT) => eclipseFinders.sunLonDegAt(jdUT)
1329
+ - sunPlanetaryCompletionDeg((jdTTFromUT(jdUT) - j2000JD) / julianCenturyDays);
1330
+ /** The APPARENT Sun the cardinal instants are defined on (an equinox is
1331
+ * the apparent Sun crossing the equator — the USNO/Meeus instants are
1332
+ * apparent): the completed geometric Sun minus the aberration constant,
1333
+ * plus the nutation in longitude. Both derived, zero new constants —
1334
+ * κ = 2π·a/(c·T_sid·√(1−e²)) from the AU, c, the one-family sidereal year
1335
+ * and the Sun's e (20.50″ at J2000); Δψ from the four IAU 1980 leading
1336
+ * terms on the model's OWN lunar node, mean Sun and mean Moon (the
1337
+ * framework arguments), the same family the registry's JPL Sun
1338
+ * instrument bridges with. @param {number} jdUT @returns {number} */
1339
+ /** The Sun's derived aberration constant of date, arcsec — κ = 2π·a/(c·T_sid·√(1−e²))
1340
+ * from the AU, c, the one-family sidereal year and the Sun's e (20.50″ at
1341
+ * J2000); ONE home for the cardinal instants and the eclipse location tier.
1342
+ * @param {number} year @returns {number} */
1343
+ const sunAberrationArcsecAt = (year) => {
1344
+ const e = sunEccentricityAt(year);
1345
+ return (2 * Math.PI * currentAUDistance
1346
+ / (C.physicalConstants.speedOfLight * yearLengthsM.siderealYearSecondsAtYear(year) * Math.sqrt(1 - e * e)))
1347
+ * (648000 / Math.PI);
1348
+ };
1349
+ /** @param {number} jdUT @returns {number} */
1350
+ const sunApparentLonDegAtJdUT = (jdUT) => {
1351
+ const jdTT = jdTTFromUT(jdUT);
1352
+ const year = 2000 + (jdTT - j2000JD) / 365.25;
1353
+ const kappaArcsec = sunAberrationArcsecAt(year);
1354
+ const a = moonArgs.argsAt(jdTT);
1355
+ const d2r = Math.PI / 180;
1356
+ const Om = (a.Lp - a.F) * d2r, Ls = (a.Lp - a.D) * d2r, Lm = a.Lp * d2r;
1357
+ const NU = C.physicalConstants.nutationLeadingTermsArcsec;
1358
+ const dPsiArcsec = NU.psiOmega * Math.sin(Om) + NU.psi2Ls * Math.sin(2 * Ls)
1359
+ + NU.psi2Lm * Math.sin(2 * Lm) + NU.psi2Omega * Math.sin(2 * Om);
1360
+ return sunLonCompletedDegAtJdUT(jdUT) + (dPsiArcsec - kappaArcsec) / 3600;
1361
+ };
1362
+ /** @param {string} type @returns {number} */
1363
+ const cardinalTargetDeg = (type) => {
1364
+ const t = { VE: 0, SS: 90, AE: 180, WS: 270 }[type];
1365
+ if (t === undefined) throw new RangeError(`cardinal type must be VE|SS|AE|WS, got ${type}`);
1366
+ return t;
1367
+ };
1368
+ const CARDINAL_SI_YEAR_D = 365.2422;
1369
+ /** The crossing NEAREST a seed instant — the unique root within ±½ tropical
1370
+ * year of the seed (the wrapped longitude difference is the Newton residual).
1371
+ * Plan 06 R4c: the seed is a JD because the caller's year may live on a
1372
+ * different axis than the model year — the simulator's cardinal panel
1373
+ * labels its year with the displayed CALENDAR date (the Julian calendar
1374
+ * before 1582, 365.25-d years) while `year` below is the model year (the SI
1375
+ * axis, 365.2422-d count from J2000); the two part by 0.0078 d/yr, 114 yr at
1376
+ * −5.34 Myr, where the panel showed the events of −5341772 for a date in
1377
+ * −5341886 (owner). Seeding at the calendar year's midpoint returns that
1378
+ * year's own events at every epoch. @param {number} jdSeedUT @param {string} type @returns {number} */
1379
+ const cardinalCrossingNearJdUT = (jdSeedUT, type) => {
1380
+ const target = cardinalTargetDeg(type);
1381
+ const RATE = 360 / CARDINAL_SI_YEAR_D;
1382
+ let jd = jdSeedUT;
1383
+ for (let i = 0; i < 20; i++) {
1384
+ const d = ((((sunApparentLonDegAtJdUT(jd) - target) + 540) % 360 + 360) % 360) - 180;
1385
+ jd -= d / RATE;
1386
+ if (Math.abs(d) < 1e-10) break;
1387
+ }
1388
+ return jd - C.earthOrbital.deltaTStart / 86400;
1389
+ };
1390
+ /** The TRUE-UT JD at which the APPARENT Sun's longitude of date equals
1391
+ * the cardinal target in the given year — Newton on the crossing (the
1392
+ * Sun's rate 360°/tropical year; a handful of steps from the tropical-year
1393
+ * seed). Replaces the fitted cardinal model's instants (R1). Plan 06 R3
1394
+ * item 2: the Newton root lives on the FINDER axis (the curve-only ΔT the
1395
+ * eclipse finders certify), and the returned instant is that root minus the
1396
+ * deltaTStart bridge — true UT = TT − (deltaTStart + curve), the same
1397
+ * bridge the besselian and the registry instruments apply. Before this the
1398
+ * published instants were 54.55 s (0.9 min) late. Against Horizons over
1399
+ * ±3000 yr the remaining offset is the Sun's (plan 06 I2 measured).
1400
+ * `year` is the MODEL year (the SI axis) — see cardinalCrossingNearJdUT.
1401
+ * @param {number} year @param {string} type @returns {number} */
1402
+ const cardinalCrossingJdUT = (year, type) => {
1403
+ const target = cardinalTargetDeg(type);
1404
+ // seed: Jan 1.5 of the year (J2000 = 2000 Jan 1.5) + the mean date of the
1405
+ // March equinox (day 79.3) + the quarter-turns to the target
1406
+ const seed = j2000JD + (year - 2000) * CARDINAL_SI_YEAR_D + 79.3 + (target / 360) * CARDINAL_SI_YEAR_D;
1407
+ return cardinalCrossingNearJdUT(seed, type);
1408
+ };
1409
+
1280
1410
  return Object.freeze({
1281
1411
  time: Object.freeze({
1282
1412
  yearFromJD,
@@ -1399,13 +1529,18 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1399
1529
  measuredSolarDaySeconds,
1400
1530
  raDayOffsetMs,
1401
1531
  }),
1532
+ // Plan 06 R1: the instants are the crossings of the ONE Sun (the
1533
+ // certified completed Sun the scene renders), UT model-JD; the RA at a
1534
+ // crossing is the target longitude by construction (λ = 0/90/180/270 ⇒
1535
+ // RA = 0/90/180/270, any obliquity); the per-type year length is the
1536
+ // interval between successive crossings (SI days).
1402
1537
  cardinal: Object.freeze({
1403
- jd: /** @param {number} year @param {string} type @returns {number} */ (year, type) => cardinalM.computeSolsticeJD(year, type),
1404
- raDeg: /** @param {number} year @param {string} type @returns {number} */ (year, type) => {
1405
- const ra = cardinalM.computeSolsticeRA(year, type);
1406
- return ((ra % 360) + 360) % 360;
1407
- },
1408
- yearLengthDays: /** @param {number} year @param {string} type @returns {number} */ (year, type) => cardinalM.computeSolsticeYearLength(year, type),
1538
+ jd: /** @param {number} year @param {string} type @returns {number} */ (year, type) => cardinalCrossingJdUT(year, type),
1539
+ // R4c: the crossing nearest a UT instant (a JD seed — for callers whose
1540
+ // year coordinate is not the model year, e.g. a calendar year's midpoint).
1541
+ jdNearUT: /** @param {number} jdUT @param {string} type @returns {number} */ (jdUT, type) => cardinalCrossingNearJdUT(jdUT, type),
1542
+ raDeg: /** @param {number} year @param {string} type @returns {number} */ (year, type) => { void year; return cardinalTargetDeg(type); },
1543
+ yearLengthDays: /** @param {number} year @param {string} type @returns {number} */ (year, type) => cardinalCrossingJdUT(year + 1, type) - cardinalCrossingJdUT(year, type),
1409
1544
  }),
1410
1545
  // D4b: the one-source cardinal STRUCTURE — the EoC layer on the
1411
1546
  // movement's own e(t)/ϖ(t), valid at every epoch (mode tier). Absolute
@@ -1449,6 +1584,8 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1449
1584
  sunLonCompletedDegAtJD: /** @param {number} jd @returns {number} */ (jd) =>
1450
1585
  eclipseFinders.sunLonDegAt(jd)
1451
1586
  - sunPlanetaryCompletionDeg((jdTTFromUT(jd) - j2000JD) / julianCenturyDays),
1587
+ /** 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} */
1588
+ sunApparentLonDegAtJD: /** @param {number} jdUT @returns {number} */ (jdUT) => sunApparentLonDegAtJdUT(jdUT),
1452
1589
  findLunarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findLunarEclipsesInRange(jdStart, jdEnd),
1453
1590
  findSolarInRange: /** @param {number} jdStart @param {number} jdEnd */ (jdStart, jdEnd) => eclipseFinders.findSolarEclipsesInRange(jdStart, jdEnd),
1454
1591
  deltaTSecondsAtJD: frameworkDeltaTSecondsAtJD,
@@ -1477,6 +1614,7 @@ export function assembleModel(C, F, laws = {}, secularSeriesArtifact = /** @type
1477
1614
  perihelionLongitudeDeg: planetPerihelionDeg,
1478
1615
  ascendingNodeInvPlaneDeg: planetAscNodeDeg,
1479
1616
  invPlaneInclinationDeg: planetInclinationDeg,
1617
+ spin: planetSpin,
1480
1618
  }),
1481
1619
  });
1482
1620
  }