@essrt/physics 1.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/README.md +52 -0
- package/package.json +58 -0
- package/src/cardinal/index.cjs +323 -0
- package/src/chain-cycles/index.cjs +189 -0
- package/src/climate/l1-orbital.cjs +46 -0
- package/src/constants/coefficients.js +52 -0
- package/src/constants/generated.d.ts +710 -0
- package/src/constants/generated.js +868 -0
- package/src/constants/index.js +30 -0
- package/src/deltat/cycles.cjs +256 -0
- package/src/deltat/deep-time.cjs +266 -0
- package/src/deltat/historical.cjs +84 -0
- package/src/eclipse/finders.cjs +311 -0
- package/src/index.js +252 -0
- package/src/layer0/derive-params.js +85 -0
- package/src/layer0/index.js +166 -0
- package/src/layer1/index.js +86 -0
- package/src/layer2/index.js +6 -0
- package/src/moon/apparent.cjs +153 -0
- package/src/moon/arguments.cjs +320 -0
- package/src/moon/ecc-channel.cjs +108 -0
- package/src/moon/month-chain.cjs +251 -0
- package/src/moon/series.cjs +211 -0
- package/src/phase/index.cjs +186 -0
- package/src/planets/asc-node-integrator.cjs +120 -0
- package/src/planets/corrections.cjs +167 -0
- package/src/planets/ecc-channel.cjs +59 -0
- package/src/planets/fibonacci-laws.cjs +136 -0
- package/src/planets/geometry.cjs +145 -0
- package/src/planets/model.cjs +185 -0
- package/src/planets/orbit-chain.cjs +46 -0
- package/src/planets/orientation.cjs +133 -0
- package/src/planets/predict.cjs +324 -0
- package/src/reference/published-curves.cjs +247 -0
- package/src/sun/longitude-correction.cjs +68 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planet eccentricity channel — THE shared implementation (Phase 8.3, L3).
|
|
3
|
+
* The law-of-cosines oscillation every body's runtime eccentricity rides
|
|
4
|
+
* (Earth on H/16, each planet on its OWN wobble period — S-P1):
|
|
5
|
+
*
|
|
6
|
+
* e(t) = √(base² + amp² − 2·base·amp·cos θ), θ = 2π·N·∫1/H dt
|
|
7
|
+
*
|
|
8
|
+
* — the distance between two circular orbits (body orbits the wobble
|
|
9
|
+
* centre at radius amp; the centre orbits the Sun at radius base).
|
|
10
|
+
*
|
|
11
|
+
* Extracted from src/script.js computeEccentricityEarth (the browser's
|
|
12
|
+
* 5-arg generic form) and tools/lib/orbital-engine.js computeEccentricity
|
|
13
|
+
* (identical law; R6 aligned its phase to integrated). The structural twin
|
|
14
|
+
* of moon/ecc-channel — but a DIFFERENT law (H/16 orbit law vs the Moon
|
|
15
|
+
* channel's H/3 line; doc 66 keeps the two deliberately distinct).
|
|
16
|
+
*
|
|
17
|
+
* Engine null/toggle semantics stay ENGINE-SIDE, deliberately:
|
|
18
|
+
* - the browser's cyclesBetween carries its DEEP_TIME_MODE toggle and maps
|
|
19
|
+
* null (past tidal lock) → the MEAN eccentricity;
|
|
20
|
+
* - the Node engine pre-computes its snapshot cycles and keeps them when
|
|
21
|
+
* the integrated phase is null (its documented R6 flag polarity).
|
|
22
|
+
* Both call the same law; the dispatch difference is recorded here so
|
|
23
|
+
* nobody "unifies" it without measuring.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The law of cosines over resolved cycles. null cycles → the MEAN
|
|
30
|
+
* eccentricity √(base² + amp²) (past the tidal-lock asymptote).
|
|
31
|
+
* @param {number | null} cycles @param {number} base @param {number} amplitude
|
|
32
|
+
* @returns {number} */
|
|
33
|
+
function eccentricityFromCycles(cycles, base, amplitude) {
|
|
34
|
+
if (cycles === null) {
|
|
35
|
+
return Math.sqrt(base * base + amplitude * amplitude);
|
|
36
|
+
}
|
|
37
|
+
const phase = cycles * 2 * Math.PI;
|
|
38
|
+
return Math.sqrt(
|
|
39
|
+
base * base + amplitude * amplitude - 2 * base * amplitude * Math.cos(phase)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Browser-convention evaluator: J2000-FIXED anchor + cycle length, divisor
|
|
45
|
+
* N = H_J2000/cycleLength, integrated phase via the injected engine
|
|
46
|
+
* cyclesBetween (toggle semantics ride along), null → mean.
|
|
47
|
+
* @param {number} currentYear @param {number} anchorYearJ2000
|
|
48
|
+
* @param {number} cycleLengthYearsJ2000 @param {number} base
|
|
49
|
+
* @param {number} amplitude
|
|
50
|
+
* @param {{ holisticYearJ2000: number,
|
|
51
|
+
* cyclesBetween: (yearA: number, yearB: number, divisorN: number) => (number | null) }} env
|
|
52
|
+
* @returns {number} */
|
|
53
|
+
function computeEccentricityIntegrated(currentYear, anchorYearJ2000, cycleLengthYearsJ2000, base, amplitude, env) {
|
|
54
|
+
const divisorN = env.holisticYearJ2000 / cycleLengthYearsJ2000;
|
|
55
|
+
const cycles = env.cyclesBetween(anchorYearJ2000, currentYear, divisorN);
|
|
56
|
+
return eccentricityFromCycles(cycles, base, amplitude);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { eccentricityFromCycles, computeEccentricityIntegrated };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Fibonacci Laws — THE shared implementation (Phase 8.3, layer L2).
|
|
3
|
+
*
|
|
4
|
+
* The scientific heart of the model: the closed derivation loop
|
|
5
|
+
* ψ → inclination amplitude/mean → wobble period → obliquity mean →
|
|
6
|
+
* K → eccentricity amplitude → phase → base
|
|
7
|
+
* that turns per-body Fibonacci divisors and mass fractions into every
|
|
8
|
+
* planet's inclination and eccentricity structure (docs/37, doc 99).
|
|
9
|
+
*
|
|
10
|
+
* Extracted VERBATIM in the BROWSER's expression forms from src/script.js
|
|
11
|
+
* (the ψ loop, calcWobblePeriod, the obliquity-cycle aliases,
|
|
12
|
+
* calcObliquityMean's load-time snapshot branch, the K loop), which
|
|
13
|
+
* tools/lib/constants.js + constants/utils.js hand-mirrored with last-ulp
|
|
14
|
+
* operation-order variants (measured: 32/42 law outputs bit-exact, 10 at
|
|
15
|
+
* the 1e-11..1e-15 relative class) — those variants dissolve here.
|
|
16
|
+
*
|
|
17
|
+
* LOAD-BEARING conventions:
|
|
18
|
+
* - ψ = 3·A_earth·√(M_EARTH_ALONE/M_SUN): the ALONE/SYSTEM mass asymmetry
|
|
19
|
+
* is calibration convention (doc 25) — switching Earth to SYSTEM shifts
|
|
20
|
+
* ψ by 0.612% and would require re-calibrating A_earth.
|
|
21
|
+
* - Wobble = beat of |axial| and |ICRF| RATES (sign-free — Venus's
|
|
22
|
+
* prograde-axial/retrograde-ICRF case); |axial| > 8H ⇒ frozen ⇒
|
|
23
|
+
* wobble = |ICRF| exactly.
|
|
24
|
+
* - The obliquity-mean law here is the SNAPSHOT form (the browser's
|
|
25
|
+
* module-load TDZ fallback — the value both engines actually ship at
|
|
26
|
+
* load); the browser's runtime integrated path stays engine-side with
|
|
27
|
+
* its Phase-8 anchors. Note it uses 1/(H/13), where the runtime
|
|
28
|
+
* integrated form uses 13/H — historical operation orders, preserved.
|
|
29
|
+
* - K-law phase offset: 90° in-phase, 270° anti-phase (Saturn) — the n=7
|
|
30
|
+
* System Reset state (all planets at mean e, Saturn falling).
|
|
31
|
+
* - The Node mirror carried an `else` branch for bodies without a wobble
|
|
32
|
+
* period — DEAD code (the loop is fibonacciD-guarded and all seven
|
|
33
|
+
* carriers have wobble periods); dropped here, recorded in the commit.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ψ constant from Earth's calibration. @param {{
|
|
40
|
+
* earthInvPlaneInclinationAmplitude: number,
|
|
41
|
+
* massEarthAlone: number, massSun: number }} c @returns {number} */
|
|
42
|
+
function computePsiConstant(c) {
|
|
43
|
+
return 3 * c.earthInvPlaneInclinationAmplitude * Math.sqrt(c.massEarthAlone / c.massSun);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* ψ law: invariable-plane inclination amplitude and mean.
|
|
48
|
+
* @param {{ fibonacciD: number, massFrac: number,
|
|
49
|
+
* invPlaneInclinationJ2000: number, longitudePerihelion: number,
|
|
50
|
+
* inclinationCycleAnchor: number, antiPhase: boolean }} b
|
|
51
|
+
* @param {number} psiConstant
|
|
52
|
+
* @returns {{ amplitude: number, mean: number }} */
|
|
53
|
+
function computeInclinationLaw(b, psiConstant) {
|
|
54
|
+
const amplitude = psiConstant / (b.fibonacciD * Math.sqrt(b.massFrac));
|
|
55
|
+
const antiPhase = b.antiPhase ? -1 : 1;
|
|
56
|
+
const mean = b.invPlaneInclinationJ2000
|
|
57
|
+
- antiPhase * amplitude * Math.cos((b.longitudePerihelion - b.inclinationCycleAnchor) * Math.PI / 180);
|
|
58
|
+
return { amplitude, mean };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Wobble period: beat of axial precession and perihelion ICRF precession.
|
|
63
|
+
* @param {number} periEclYr @param {number} axialYr @param {number} H
|
|
64
|
+
* @returns {number} years */
|
|
65
|
+
function computeWobblePeriodYears(periEclYr, axialYr, H) {
|
|
66
|
+
const H13 = H / 13;
|
|
67
|
+
const inclICRF = (periEclYr * H13) / (H13 - periEclYr);
|
|
68
|
+
if (Math.abs(axialYr) > 8 * H) return Math.abs(inclICRF);
|
|
69
|
+
const wobbleRate = Math.abs(1 / Math.abs(axialYr) - 1 / Math.abs(inclICRF));
|
|
70
|
+
return 1 / wobbleRate;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Obliquity cycle with the Venus/Neptune fallback: the record's cycle if
|
|
75
|
+
* present, else |ICRF| (tidally damped — the two-component obliquity
|
|
76
|
+
* formula cancels exactly, constant tilt).
|
|
77
|
+
* @param {number | null | undefined} obliquityCycleYears
|
|
78
|
+
* @param {number} periEclYr @param {number} H @returns {number} */
|
|
79
|
+
function resolveObliquityCycleYears(obliquityCycleYears, periEclYr, H) {
|
|
80
|
+
if (obliquityCycleYears !== undefined && obliquityCycleYears !== null) return obliquityCycleYears;
|
|
81
|
+
return Math.abs(1 / (1 / periEclYr - 13 / H));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Mean obliquity, SNAPSHOT form (the load-time law both engines ship):
|
|
86
|
+
* mean = tiltJ2000 + amp·cos(ωᵢ·t₂₀₀₀) − amp·cos(ωₒ·t₂₀₀₀).
|
|
87
|
+
* @param {{ axialTiltJ2000: number, invPlaneInclinationAmplitude: number,
|
|
88
|
+
* perihelionEclipticYears: number }} b
|
|
89
|
+
* @param {number | null | undefined} obliqCycleYears — falsy ⇒ static tilt
|
|
90
|
+
* @param {{ H: number, t2000: number }} env — t2000 = 2000 − eccentricity
|
|
91
|
+
* anchor (balancedYear − systemResetN·H)
|
|
92
|
+
* @returns {number} degrees */
|
|
93
|
+
function computeObliquityMeanSnapshot(b, obliqCycleYears, env) {
|
|
94
|
+
if (!obliqCycleYears) return b.axialTiltJ2000;
|
|
95
|
+
const amp = b.invPlaneInclinationAmplitude;
|
|
96
|
+
const genPrecRate = 1 / (env.H / 13);
|
|
97
|
+
const icrfPeriod = 1 / (1 / b.perihelionEclipticYears - genPrecRate);
|
|
98
|
+
return b.axialTiltJ2000 + amp * Math.cos(2 * Math.PI * env.t2000 / icrfPeriod)
|
|
99
|
+
- amp * Math.cos(2 * Math.PI * env.t2000 / obliqCycleYears);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* K constant from Earth's calibration. @param {{
|
|
104
|
+
* eccentricityAmplitude: number, massEarthAlone: number, massSun: number,
|
|
105
|
+
* earthTiltMeanDeg: number }} c @returns {number} */
|
|
106
|
+
function computeKConstant(c) {
|
|
107
|
+
return c.eccentricityAmplitude * Math.sqrt(c.massEarthAlone / c.massSun)
|
|
108
|
+
/ (Math.sin(c.earthTiltMeanDeg * Math.PI / 180) * Math.sqrt(3));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* K law: eccentricity amplitude, base and J2000 phase.
|
|
113
|
+
* @param {{ fibonacciD: number, massFrac: number, solarYearInput: number,
|
|
114
|
+
* orbitalEccentricityJ2000: number, antiPhase: boolean }} b
|
|
115
|
+
* @param {{ kConstant: number, obliquityMeanDeg: number,
|
|
116
|
+
* wobblePeriodYears: number, t2000: number, meanSolarYearDays: number }} env
|
|
117
|
+
* @returns {{ amplitude: number, base: number, phaseJ2000: number }} */
|
|
118
|
+
function computeEccentricityLaw(b, env) {
|
|
119
|
+
const a = Math.pow(b.solarYearInput / env.meanSolarYearDays, 2 / 3);
|
|
120
|
+
const amplitude = env.kConstant * Math.sin(Math.abs(env.obliquityMeanDeg) * Math.PI / 180) * Math.sqrt(b.fibonacciD)
|
|
121
|
+
/ (Math.sqrt(b.massFrac) * Math.pow(a, 1.5));
|
|
122
|
+
const eJ2000 = b.orbitalEccentricityJ2000;
|
|
123
|
+
const phaseOffset = b.antiPhase ? 270 : 90;
|
|
124
|
+
const phaseDeg = (env.t2000 / env.wobblePeriodYears) * 360 + phaseOffset;
|
|
125
|
+
const cosTheta = Math.cos(phaseDeg * Math.PI / 180);
|
|
126
|
+
const sinTheta = Math.sin(phaseDeg * Math.PI / 180);
|
|
127
|
+
const disc = eJ2000 * eJ2000 - amplitude * amplitude * sinTheta * sinTheta;
|
|
128
|
+
const base = amplitude * cosTheta + Math.sqrt(Math.max(0, disc));
|
|
129
|
+
return { amplitude, base, phaseJ2000: ((phaseDeg % 360) + 360) % 360 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
computePsiConstant, computeInclinationLaw, computeWobblePeriodYears,
|
|
134
|
+
resolveObliquityCycleYears, computeObliquityMeanSnapshot,
|
|
135
|
+
computeKConstant, computeEccentricityLaw,
|
|
136
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planet orbital geometry — THE shared implementation (Phase 8.3, layer L1).
|
|
3
|
+
*
|
|
4
|
+
* ONE law set, N body records: the derivation the browser hand-unrolled per
|
|
5
|
+
* planet (mercurySolarYearCount … halleysRotationPeriod, src/script.js
|
|
6
|
+
* ~2145–2237) and Node kept as computePlanetDerived/computeAdditionalDerived
|
|
7
|
+
* (tools/lib/constants.js). This file replaces all three copies with one
|
|
8
|
+
* type-branched function over a body record — the shape the model's claim
|
|
9
|
+
* has always had (six laws, per-body parameters).
|
|
10
|
+
*
|
|
11
|
+
* EXPRESSION FORMS ARE THE BROWSER'S (the certified engine) — e.g.
|
|
12
|
+
* `Math.round((H·mSY)/input)`, `((H/N)**2)**(1/3)` — so browser bit-identity
|
|
13
|
+
* holds by construction; Node's last-ulp variants dissolve here (the
|
|
14
|
+
* cardinal-extraction pattern).
|
|
15
|
+
*
|
|
16
|
+
* Type classification (the survey's measured taxonomy):
|
|
17
|
+
* - Type I (mercury, venus): peri = a·e_real·100, elip = peri/2
|
|
18
|
+
* - Type II (mars, eros): elip first, peri = a·e_base·100 + elip
|
|
19
|
+
* - Type III (jupiter…neptune): elip = geocentric 2·e_E·100·sin(ϖ_E − ϖ_p)
|
|
20
|
+
* — the only formula that reads Earth's elements; peri = e_real·a·100
|
|
21
|
+
* - pluto: peri = e_BASE·a·100 (raw base, no e_real), elip = peri/2
|
|
22
|
+
* - halleys: elip = (e_base·a − e_real·a)·100, peri = e_real·a·2·100
|
|
23
|
+
* - ceres: orbitDistanceOverride, geometry-only (no ellipse family)
|
|
24
|
+
* - mercury rotation: the 3:2 spin-orbit lock (period from N, not a
|
|
25
|
+
* rotation input)
|
|
26
|
+
*
|
|
27
|
+
* Saturn's scene-side negation of elipticOrbit (antiPhase) is a SCENE
|
|
28
|
+
* convention and stays engine-side.
|
|
29
|
+
*
|
|
30
|
+
* MINOR-BODY TYPES ARE PLACEHOLDERS (model-parameters.json _typeNote): the
|
|
31
|
+
* small bodies borrow the planet taxonomy geometrically but are more
|
|
32
|
+
* susceptible to planetary gravity disturbances; a perturbation-aware
|
|
33
|
+
* type is expected from the Phase 18 refit. It lands as ONE new branch
|
|
34
|
+
* here + a JSON type flip per body — both engines pick it up automatically.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
'use strict';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {{
|
|
41
|
+
* key: string,
|
|
42
|
+
* type?: string,
|
|
43
|
+
* solarYearInput: number,
|
|
44
|
+
* orbitalEccentricityBase?: number,
|
|
45
|
+
* longitudePerihelion?: number,
|
|
46
|
+
* ascendingNode?: number,
|
|
47
|
+
* rotationPeriodDays?: number,
|
|
48
|
+
* orbitDistanceOverride?: number,
|
|
49
|
+
* }} PlanetGeometryBody
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {PlanetGeometryBody} body
|
|
54
|
+
* @param {{
|
|
55
|
+
* holisticYears: number,
|
|
56
|
+
* meanSolarYearDays: number,
|
|
57
|
+
* currentAUDistanceKm: number,
|
|
58
|
+
* earthEccentricityJ2000: number,
|
|
59
|
+
* earthPerihelionLongitudeJ2000Deg: number,
|
|
60
|
+
* }} env — the engine's J2000 values (browser passes its live globals at
|
|
61
|
+
* load time, preserving its original initial-derivation semantics; the
|
|
62
|
+
* epoch machinery that later mutates the browser's `let` aliases is
|
|
63
|
+
* untouched by this layer).
|
|
64
|
+
* @returns {{
|
|
65
|
+
* solarYearCount: number, orbitDistance: number, periodYears: number,
|
|
66
|
+
* realOrbitalEccentricity: (number | undefined),
|
|
67
|
+
* elipticOrbit: (number | undefined), perihelionDistance: (number | undefined),
|
|
68
|
+
* speedKmh: number, rotationPeriodHours: (number | undefined),
|
|
69
|
+
* eccentricityPerihelion: (number | undefined), lowestPoint: (number | undefined),
|
|
70
|
+
* }}
|
|
71
|
+
*/
|
|
72
|
+
function derivePlanetGeometry(body, env) {
|
|
73
|
+
const { key, type, solarYearInput, orbitalEccentricityBase: base,
|
|
74
|
+
longitudePerihelion, ascendingNode, rotationPeriodDays, orbitDistanceOverride } = body;
|
|
75
|
+
const { holisticYears: H, meanSolarYearDays: mSY, currentAUDistanceKm: AU,
|
|
76
|
+
earthEccentricityJ2000, earthPerihelionLongitudeJ2000Deg } = env;
|
|
77
|
+
|
|
78
|
+
const solarYearCount = Math.round((H * mSY) / solarYearInput);
|
|
79
|
+
const orbitDistance = orbitDistanceOverride !== undefined
|
|
80
|
+
? orbitDistanceOverride
|
|
81
|
+
: ((H / solarYearCount) ** 2) ** (1 / 3);
|
|
82
|
+
const periodYears = H / solarYearCount;
|
|
83
|
+
const speedKmh = (orbitDistance * AU * Math.PI * 2) / (mSY * (H / solarYearCount)) / 24;
|
|
84
|
+
|
|
85
|
+
/** @type {number | undefined} */
|
|
86
|
+
let realOrbitalEccentricity;
|
|
87
|
+
/** @type {number | undefined} */
|
|
88
|
+
let elipticOrbit;
|
|
89
|
+
/** @type {number | undefined} */
|
|
90
|
+
let perihelionDistance;
|
|
91
|
+
/** @type {number | undefined} */
|
|
92
|
+
let eccentricityPerihelion;
|
|
93
|
+
|
|
94
|
+
if (key === 'ceres') {
|
|
95
|
+
// Geometry-only body — no ellipse family in either engine.
|
|
96
|
+
} else if (key === 'pluto') {
|
|
97
|
+
realOrbitalEccentricity = undefined; // deliberately absent (raw base used)
|
|
98
|
+
perihelionDistance = /** @type {number} */ (base) * orbitDistance * 100;
|
|
99
|
+
elipticOrbit = perihelionDistance / 2;
|
|
100
|
+
} else if (key === 'halleys') {
|
|
101
|
+
const b = /** @type {number} */ (base);
|
|
102
|
+
realOrbitalEccentricity = b / (1 + b);
|
|
103
|
+
elipticOrbit = ((b * orbitDistance) - (realOrbitalEccentricity * orbitDistance)) * 100;
|
|
104
|
+
perihelionDistance = realOrbitalEccentricity * orbitDistance * 2 * 100;
|
|
105
|
+
} else if (type === 'I') {
|
|
106
|
+
const b = /** @type {number} */ (base);
|
|
107
|
+
realOrbitalEccentricity = b / (1 + b);
|
|
108
|
+
perihelionDistance = orbitDistance * realOrbitalEccentricity * 100;
|
|
109
|
+
elipticOrbit = perihelionDistance / 2;
|
|
110
|
+
eccentricityPerihelion = (perihelionDistance / 2) * b;
|
|
111
|
+
} else if (type === 'II') {
|
|
112
|
+
const b = /** @type {number} */ (base);
|
|
113
|
+
realOrbitalEccentricity = b / (1 + b);
|
|
114
|
+
elipticOrbit = ((realOrbitalEccentricity * orbitDistance) / 2) * 100
|
|
115
|
+
+ ((b * orbitDistance) - (realOrbitalEccentricity * orbitDistance)) * 100;
|
|
116
|
+
perihelionDistance = (orbitDistance * b * 100) + elipticOrbit;
|
|
117
|
+
} else if (type === 'III') {
|
|
118
|
+
const b = /** @type {number} */ (base);
|
|
119
|
+
realOrbitalEccentricity = b / (1 + b);
|
|
120
|
+
// Geocentric correction: Earth's eccentricity creates annual parallax
|
|
121
|
+
// variation ∝ sin(ϖ_Earth − ϖ_planet); factor 2 from off-centre geometry.
|
|
122
|
+
const dw = (earthPerihelionLongitudeJ2000Deg - /** @type {number} */ (longitudePerihelion)) * Math.PI / 180;
|
|
123
|
+
elipticOrbit = 2 * earthEccentricityJ2000 * 100 * Math.sin(dw);
|
|
124
|
+
perihelionDistance = realOrbitalEccentricity * orbitDistance * 100;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @type {number | undefined} */
|
|
128
|
+
let rotationPeriodHours;
|
|
129
|
+
if (key === 'mercury') {
|
|
130
|
+
// 3:2 spin-orbit lock — period from the orbit count itself.
|
|
131
|
+
rotationPeriodHours = 24 * (mSY * H) / (solarYearCount * 3 / 2);
|
|
132
|
+
} else if (rotationPeriodDays !== undefined) {
|
|
133
|
+
rotationPeriodHours = 24 * (mSY * H) / Math.round((mSY * H) / rotationPeriodDays);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const lowestPoint = ascendingNode !== undefined ? 180 - ascendingNode : undefined;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
solarYearCount, orbitDistance, periodYears, realOrbitalEccentricity,
|
|
140
|
+
elipticOrbit, perihelionDistance, speedKmh, rotationPeriodHours,
|
|
141
|
+
eccentricityPerihelion, lowestPoint,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { derivePlanetGeometry };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createPlanetModel — the planet composition front door (Phase 8.3, L10).
|
|
3
|
+
*
|
|
4
|
+
* ONE law set, N body records: this factory binds an environment once and
|
|
5
|
+
* runs the certified derivation chain over every body record, in the order
|
|
6
|
+
* the chain requires (each step feeds the next):
|
|
7
|
+
*
|
|
8
|
+
* ψ constant → inclination law (amplitude, mean)
|
|
9
|
+
* → wobble period (beat of |axial| and |ICRF|)
|
|
10
|
+
* K constant → obliquity mean (snapshot form) → eccentricity law
|
|
11
|
+
* (amplitude, base, J2000 phase)
|
|
12
|
+
* geometry (the type-branched ellipse family — the law-derived
|
|
13
|
+
* eccentricity base feeds geometry for the seven carriers;
|
|
14
|
+
* minor bodies use their record's base)
|
|
15
|
+
*
|
|
16
|
+
* THIN BY DESIGN. This is the composition surface, not a rewiring: both
|
|
17
|
+
* engines keep their existing direct call sites into the law modules, and
|
|
18
|
+
* the runtime channels (eccentricity-at-year, orientation, the ascending-
|
|
19
|
+
* node integrator, predictive precession) stay direct module calls because
|
|
20
|
+
* they consume engine-owned state (scene JD, epoch machinery, fitted
|
|
21
|
+
* tables). What this factory adds is the seam future bodies plug into:
|
|
22
|
+
* a new body is a record + (at most) one new geometry branch — see the
|
|
23
|
+
* minor-body placeholder note in geometry.cjs (Phase 18 perturbation
|
|
24
|
+
* types land the same way).
|
|
25
|
+
*
|
|
26
|
+
* Guard semantics mirror tools/lib/constants.js verbatim: the law steps
|
|
27
|
+
* run only where the record carries the required fields (fibonacciD +
|
|
28
|
+
* mass fraction for the ψ/K families, perihelion + axial periods for
|
|
29
|
+
* wobble); geometry runs for every body. The identity gate
|
|
30
|
+
* (test/planet-model-identity.test.mjs) holds this factory bit-exact
|
|
31
|
+
* against the shipped Node derivation.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
'use strict';
|
|
35
|
+
|
|
36
|
+
const { derivePlanetGeometry } = require('./geometry.cjs');
|
|
37
|
+
const FL = require('./fibonacci-laws.cjs');
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} PlanetModelBody
|
|
41
|
+
* @property {string} [type] - geometry type branch ('I' | 'II' | 'III')
|
|
42
|
+
* @property {number} solarYearInput
|
|
43
|
+
* @property {number} [fibonacciD]
|
|
44
|
+
* @property {number} [invPlaneInclinationJ2000]
|
|
45
|
+
* @property {number} [longitudePerihelion]
|
|
46
|
+
* @property {number} [inclinationCycleAnchor]
|
|
47
|
+
* @property {boolean} [antiPhase]
|
|
48
|
+
* @property {number} [perihelionEclipticYears]
|
|
49
|
+
* @property {number} [axialPrecessionYears]
|
|
50
|
+
* @property {number|null} [obliquityCycle]
|
|
51
|
+
* @property {number} [axialTiltJ2000]
|
|
52
|
+
* @property {number} [orbitalEccentricityJ2000]
|
|
53
|
+
* @property {number} [ascendingNode]
|
|
54
|
+
* @property {number} [rotationPeriodDays]
|
|
55
|
+
* @property {number} [orbitalEccentricityBase] - minor bodies: JSON input;
|
|
56
|
+
* carriers: ignored (the K law derives it)
|
|
57
|
+
* @property {number} [orbitDistanceOverride]
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {Object} PlanetModelEnv
|
|
62
|
+
* @property {number} holisticYears
|
|
63
|
+
* @property {number} meanSolarYearDays
|
|
64
|
+
* @property {number} balancedYear
|
|
65
|
+
* @property {number} systemResetN
|
|
66
|
+
* @property {number} currentAUDistanceKm
|
|
67
|
+
* @property {number} earthEccentricityJ2000
|
|
68
|
+
* @property {number} earthPerihelionLongitudeJ2000Deg
|
|
69
|
+
* @property {{ earthInvPlaneInclinationAmplitude: number,
|
|
70
|
+
* massEarthAlone: number, massSun: number,
|
|
71
|
+
* eccentricityAmplitude: number, earthTiltMeanDeg: number }} calibration
|
|
72
|
+
* @property {Record<string, number>} massFractions
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @typedef {Object} PlanetModelRecord
|
|
77
|
+
* @property {number} [invPlaneInclinationAmplitude]
|
|
78
|
+
* @property {number} [invPlaneInclinationMean]
|
|
79
|
+
* @property {number} [wobblePeriodYears]
|
|
80
|
+
* @property {number} [obliquityMeanDeg]
|
|
81
|
+
* @property {number} [eccentricityAmplitude]
|
|
82
|
+
* @property {number} [eccentricityBase]
|
|
83
|
+
* @property {number} [eccentricityPhaseJ2000Deg]
|
|
84
|
+
* @property {ReturnType<typeof derivePlanetGeometry>} geometry
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Run the full derivation chain over a set of body records.
|
|
89
|
+
*
|
|
90
|
+
* @param {PlanetModelEnv} env
|
|
91
|
+
* @param {Record<string, PlanetModelBody>} bodies - keyed by body name
|
|
92
|
+
* (the key selects the body-unique geometry branches: mercury, pluto,
|
|
93
|
+
* halleys, ceres)
|
|
94
|
+
* @returns {{ psiConstant: number, kConstant: number,
|
|
95
|
+
* eccentricityAnchor: number, t2000: number,
|
|
96
|
+
* bodies: Record<string, PlanetModelRecord> }}
|
|
97
|
+
*/
|
|
98
|
+
function createPlanetModel(env, bodies) {
|
|
99
|
+
const psiConstant = FL.computePsiConstant({
|
|
100
|
+
earthInvPlaneInclinationAmplitude: env.calibration.earthInvPlaneInclinationAmplitude,
|
|
101
|
+
massEarthAlone: env.calibration.massEarthAlone,
|
|
102
|
+
massSun: env.calibration.massSun,
|
|
103
|
+
});
|
|
104
|
+
const kConstant = FL.computeKConstant({
|
|
105
|
+
eccentricityAmplitude: env.calibration.eccentricityAmplitude,
|
|
106
|
+
massEarthAlone: env.calibration.massEarthAlone,
|
|
107
|
+
massSun: env.calibration.massSun,
|
|
108
|
+
earthTiltMeanDeg: env.calibration.earthTiltMeanDeg,
|
|
109
|
+
});
|
|
110
|
+
// Anchor = balancedYear − systemResetN·H (n=7: the System Reset state).
|
|
111
|
+
const eccentricityAnchor = env.balancedYear - env.systemResetN * env.holisticYears;
|
|
112
|
+
const t2000 = 2000 - eccentricityAnchor;
|
|
113
|
+
|
|
114
|
+
const geomEnv = {
|
|
115
|
+
holisticYears: env.holisticYears,
|
|
116
|
+
meanSolarYearDays: env.meanSolarYearDays,
|
|
117
|
+
currentAUDistanceKm: env.currentAUDistanceKm,
|
|
118
|
+
earthEccentricityJ2000: env.earthEccentricityJ2000,
|
|
119
|
+
earthPerihelionLongitudeJ2000Deg: env.earthPerihelionLongitudeJ2000Deg,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** @type {Record<string, PlanetModelRecord>} */
|
|
123
|
+
const out = {};
|
|
124
|
+
for (const [key, b] of Object.entries(bodies)) {
|
|
125
|
+
const massFrac = env.massFractions[key];
|
|
126
|
+
/** @type {PlanetModelRecord} */
|
|
127
|
+
const rec = /** @type {PlanetModelRecord} */ ({});
|
|
128
|
+
|
|
129
|
+
if (b.fibonacciD && massFrac && b.invPlaneInclinationJ2000 !== undefined) {
|
|
130
|
+
const il = FL.computeInclinationLaw({
|
|
131
|
+
fibonacciD: b.fibonacciD, massFrac,
|
|
132
|
+
invPlaneInclinationJ2000: b.invPlaneInclinationJ2000,
|
|
133
|
+
longitudePerihelion: /** @type {number} */ (b.longitudePerihelion),
|
|
134
|
+
inclinationCycleAnchor: /** @type {number} */ (b.inclinationCycleAnchor),
|
|
135
|
+
antiPhase: /** @type {boolean} */ (b.antiPhase),
|
|
136
|
+
}, psiConstant);
|
|
137
|
+
rec.invPlaneInclinationAmplitude = il.amplitude;
|
|
138
|
+
rec.invPlaneInclinationMean = il.mean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (b.perihelionEclipticYears && b.axialPrecessionYears) {
|
|
142
|
+
rec.wobblePeriodYears = FL.computeWobblePeriodYears(
|
|
143
|
+
b.perihelionEclipticYears, b.axialPrecessionYears, env.holisticYears,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (b.fibonacciD && massFrac) {
|
|
148
|
+
rec.obliquityMeanDeg = FL.computeObliquityMeanSnapshot({
|
|
149
|
+
axialTiltJ2000: /** @type {number} */ (b.axialTiltJ2000),
|
|
150
|
+
invPlaneInclinationAmplitude: /** @type {number} */ (rec.invPlaneInclinationAmplitude),
|
|
151
|
+
perihelionEclipticYears: /** @type {number} */ (b.perihelionEclipticYears),
|
|
152
|
+
}, b.obliquityCycle, { H: env.holisticYears, t2000 });
|
|
153
|
+
const el = FL.computeEccentricityLaw({
|
|
154
|
+
fibonacciD: b.fibonacciD, massFrac,
|
|
155
|
+
solarYearInput: b.solarYearInput,
|
|
156
|
+
orbitalEccentricityJ2000: /** @type {number} */ (b.orbitalEccentricityJ2000),
|
|
157
|
+
antiPhase: /** @type {boolean} */ (b.antiPhase),
|
|
158
|
+
}, {
|
|
159
|
+
kConstant, obliquityMeanDeg: rec.obliquityMeanDeg,
|
|
160
|
+
wobblePeriodYears: /** @type {number} */ (rec.wobblePeriodYears), t2000,
|
|
161
|
+
meanSolarYearDays: env.meanSolarYearDays,
|
|
162
|
+
});
|
|
163
|
+
rec.eccentricityAmplitude = el.amplitude;
|
|
164
|
+
rec.eccentricityBase = el.base;
|
|
165
|
+
rec.eccentricityPhaseJ2000Deg = el.phaseJ2000;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
rec.geometry = derivePlanetGeometry({
|
|
169
|
+
key, type: b.type,
|
|
170
|
+
solarYearInput: b.solarYearInput,
|
|
171
|
+
orbitalEccentricityBase: rec.eccentricityBase !== undefined
|
|
172
|
+
? rec.eccentricityBase : b.orbitalEccentricityBase,
|
|
173
|
+
longitudePerihelion: b.longitudePerihelion,
|
|
174
|
+
ascendingNode: b.ascendingNode,
|
|
175
|
+
rotationPeriodDays: b.rotationPeriodDays,
|
|
176
|
+
orbitDistanceOverride: b.orbitDistanceOverride,
|
|
177
|
+
}, geomEnv);
|
|
178
|
+
|
|
179
|
+
out[key] = rec;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { psiConstant, kConstant, eccentricityAnchor, t2000, bodies: out };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { createPlanetModel };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-time planet orbit chain — THE shared implementation (Phase 8.3, L6).
|
|
3
|
+
* Driver 2: solar mass loss. Three pure laws:
|
|
4
|
+
*
|
|
5
|
+
* - massLossScaledLinearAtAge — adiabatic a·M = const ⇒ a(t) scales
|
|
6
|
+
* LINEARLY with (1 − Δm). Serves Earth's AU (km) and every planet's
|
|
7
|
+
* semi-major axis (units are the caller's — S-P11 resolved: the two
|
|
8
|
+
* engines shared this driver all along, differing only in km vs
|
|
9
|
+
* AU-ratio normalization).
|
|
10
|
+
* - driver2PeriodSecondsAtAge — Kepler under mass loss, dT/T = −2·dM/M ⇒
|
|
11
|
+
* T(t) = T₀·(1 − Δm)². The integrand of the planet cycle chains
|
|
12
|
+
* (chain-cycles) and the scene integrators (S-P2).
|
|
13
|
+
* - synodicPeriodSeconds — the Earth–planet beat Tp·Ty/|Tp − Ty|.
|
|
14
|
+
*
|
|
15
|
+
* t_Ma is AGE in Myr (positive = past); Δm = massLossFracPerYear·t_Ma·1e6.
|
|
16
|
+
* The t_Ma === 0 fast paths return the J2000 values EXACTLY.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {number} tMa @param {number} valueJ2000
|
|
23
|
+
* @param {number} massLossFracPerYear @returns {number} */
|
|
24
|
+
function massLossScaledLinearAtAge(tMa, valueJ2000, massLossFracPerYear) {
|
|
25
|
+
if (tMa === 0) return valueJ2000;
|
|
26
|
+
const massLossFraction = massLossFracPerYear * tMa * 1e6;
|
|
27
|
+
return valueJ2000 * (1 - massLossFraction);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {number} tMa @param {number} periodJ2000Seconds
|
|
32
|
+
* @param {number} massLossFracPerYear @returns {number} */
|
|
33
|
+
function driver2PeriodSecondsAtAge(tMa, periodJ2000Seconds, massLossFracPerYear) {
|
|
34
|
+
if (tMa === 0) return periodJ2000Seconds;
|
|
35
|
+
const massLossFraction = massLossFracPerYear * tMa * 1e6;
|
|
36
|
+
return periodJ2000Seconds * Math.pow(1 - massLossFraction, 2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {number} planetPeriodSeconds @param {number} yearSeconds
|
|
41
|
+
* @returns {number} */
|
|
42
|
+
function synodicPeriodSeconds(planetPeriodSeconds, yearSeconds) {
|
|
43
|
+
return planetPeriodSeconds * yearSeconds / Math.abs(planetPeriodSeconds - yearSeconds);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { massLossScaledLinearAtAge, driver2PeriodSecondsAtAge, synodicPeriodSeconds };
|