Files
star-map/src/app/shared/models/body.model.ts
T
SenrokaiandClaude Opus 5.5 3b4fd1af6c Turn each locked moon at its orbit's rate and Iapetus's pole round its orbit, so they face their planets at every date the clock reaches
The IAU gives a locked moon's W the mean motion of whichever orbit its authors had, and JPL's table
has another. Near the present the difference is nothing; over the clock's AD 1 to 3000 it turned
Proteus's far side to Neptune at AD 1 (146 degrees), Mimas 52 degrees from Saturn and Miranda 23.
Iapetus was worse for another reason: its IAU pole is a straight line, 3.9 degrees a century in
right ascension, through its orbit normal's 3 439-year circle round the Laplace pole, which by AD 1
has run past the celestial pole (Dec 97.9), 11 degrees off the orbit, with the face 87 degrees
from Saturn. Mimas and Iapetus carry mission maps, so a wrong hemisphere was drawn facing Saturn.
The ETL's lock check sampled only 1950-2100, so none of it failed.

tools/etl/lib/locked-spin.ts, lockedToOrbit, called for every locked moon:
- W's rate becomes the orbit's own mean motion, its constant moved so W is unchanged on
  2025-01-01; the pole and every periodic term stay the IAU's. A W with a quadratic is left
  (Phobos's orbit already takes it; the Moon's is its tidal slowing, 0.75 degrees at AD 1). The
  kernel's rate must be within 1e-5 of the orbit's first (at most 3.4e-6, Iapetus).
- Iapetus (poleFollowsOrbit): the pole follows its orbit normal, as a moon in a Cassini state does,
  in the IAU's own form: sines of the node's angle and four harmonics on right ascension, cosines
  on declination, fitted to the normal's circle and pinned to the IAU pole at the present; W takes
  sines of the same angles, fitted to hold the face where it is today.

build.ts samples the lock over AD 1 to 3000 (8 114 dates, every 135 days) instead of 1950-2100, and
subPlanetLongitudeDeg moved to the lib, shared by both. Measured on the real catalogue: at most
5.36 degrees (Titan) but the Moon 7.62 (its eccentricity, and W's quadratic at AD 1: a new named
ceiling of 8), Mimas 8.94 (ceiling 11 -> 9.5) and Iapetus 15.95 (19 -> 16.5, 9.4 of it its row's
lag); Proteus's own ceiling of 9 is gone, at 2.66. Iapetus's axis stays within 0.74 degrees of its
orbit normal (11.06 before) and its pole is the IAU's at the present to 1e-4 degrees.

Live on :4301, the longitude facing the planet at AD 1 / 1000 / 2025 / 2999: Proteus 2.6 / 2.6 /
2.6 / 2.6 (was -146.5 / -72.9 / 2.6 / 74.5), Iapetus -15.9 / -15.4 / -15.3 / -9.3 (-87.0 / -50.9 /
-15.3 / 22.8), Mimas 4.1 / 6.8 / 5.7 / 8.4 (48.6 / 29.3 / 5.7 / -13.0), Miranda -0.1 / 2.2 / 0.0 /
1.4 (-23.4 / -9.6 / 0.0 / 12.6); the present is unchanged.

The renderer spec now takes the rotational elements from bodies.json too, so no hand copy is left,
and a new test turns Proteus, Miranda, Mimas and Iapetus to their planets at AD 1 and AD 3000.
Guarded mutants, each run through the solar ETL and then the full suite on what it wrote:
lockedToOrbit bypassed (validator: Mimas 52.30, ceiling 9.5; the new test fails), Iapetus on the
IAU's straight pole (98.48), and its pole round the orbit without W's terms (73.13); each fails the
new test and only it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 15:58:04 +02:00

129 lines
5.9 KiB
TypeScript

/**
* Keplerian orbital elements at a reference epoch. Positions are derived client-side by
* propagating these elements forward/backward from `epochJd` (see `shared/astro/kepler.ts`),
* rather than fetching per-frame positions.
*/
export interface OrbitalElements {
semiMajorAxisAu: number;
eccentricity: number;
inclinationDeg: number;
longitudeOfAscendingNodeDeg: number;
argumentOfPeriapsisDeg: number;
meanAnomalyAtEpochDeg: number;
epochJd: number;
}
/**
* How a body's mean elements move away from their epoch, per day.
*
* Mean elements rather than one osculating set, because the map's clock runs decades in minutes.
* An osculating orbit is exact at its instant and drifts from then on: fed to Kepler with a mass
* ratio, the Moon's went round in 27.70 days instead of 27.32 and was 66 degrees out after a year.
* A mean set carries its own measured motion, and the slow turning of its node and periapsis, so
* it holds for as long as its source was fit over.
*/
export interface MeanElementRates {
/**
* How fast the body goes round in space, in degrees per day: the rate of its mean longitude.
* 360 over this is its sidereal period.
*/
meanMotionDegPerDay: number;
longitudeOfAscendingNodeDegPerDay: number;
argumentOfPeriapsisDegPerDay: number;
semiMajorAxisAuPerDay?: number;
eccentricityPerDay?: number;
inclinationDegPerDay?: number;
/**
* Standish's extra terms in the mean anomaly of Jupiter and beyond, `b T² + c cos(f T) +
* s sin(f T)` degrees, with T in Julian centuries from the epoch and f in degrees per century:
* the great-inequality wobble his 3000 BC to AD 3000 fit needs on top of its linear rates.
*/
meanAnomalyTerms?: { b: number; c: number; s: number; f: number };
}
/**
* A solar-system planet, moon, or dwarf planet: JPL mean orbital elements, and JPL Horizons
* physical data. `systemStarId` links back to the HYG star index (the Sun, see `SUN_STAR_ID`).
*/
export interface BodyRecord {
id: string;
systemStarId: number;
name: string;
kind: 'planet' | 'moon' | 'dwarf';
radiusKm: number;
/** Mean elements at `orbit.epochJd`, moving at `rates`. */
orbit: OrbitalElements;
rates: MeanElementRates;
/**
* The pole of the plane a moon's elements are measured against, where that is its local
* Laplace plane or, for Uranus's and Pluto's moons, the planet's equator: right ascension and
* declination in the ICRF. The node is then counted from where that plane crosses the ICRF
* equator. Absent means the J2000 ecliptic, as for the planets and the Moon.
*/
laplacePole?: { raDeg: number; decDeg: number };
/** Where the elements come from and the span they hold over, as the card prints it. */
orbitSource: string;
/**
* The eccentricity the card prints, where it is not the orbit's own: Hyperion's row in the table
* its orbit is drawn from gives 0.0232, under a quarter of the 0.105 JPL's current table (SAT441)
* and Horizons (0.074 to 0.132 from 1980 to 2100) give. The older row still places Hyperion
* nearer where Horizons has it than the same row with 0.105 does, so the orbit keeps it.
*/
measuredEccentricity?: number;
/**
* For `kind: 'moon'`, the `id` of the planet it orbits — its `orbit` is expressed
* relative to that planet, not heliocentrically. Undefined for planets/dwarfs.
*/
parentBodyId?: string;
/**
* For a moon heavy enough that it and its planet go round a point outside the planet — Charon,
* an eighth of Pluto's mass, puts it 2 100 km from Pluto's centre, 900 km above its surface —
* the moon's mass over the planet's, from the GMs on their Horizons pages. The planet's own
* elements then place that barycentre, as Standish's "Pluto" does, and both bodies are drawn
* going round it. Absent for every other moon.
*/
massRatio?: number;
/**
* How the body turns on its own axis: the sidereal rotation period in hours, negative where
* Horizons gives a negative rate (Venus, Uranus), and the tilt of that axis from its orbital
* plane — which past 90 degrees already says the turn is retrograde.
*
* Absent where Horizons publishes neither — the view then leaves the body still rather than
* spinning it at an invented rate.
*/
rotationPeriodHours?: number;
obliquityDeg?: number;
/**
* Where the body's pole points and which way its prime meridian faces at any date, from the IAU
* WGCCRE 2015 report (Archinal et al. 2018) as NAIF's `pck00011.tpc` carries it, but that a locked
* moon's W turns at its drawn orbit's rate and Iapetus's pole goes round with its orbit's, so they
* keep their faces to their planets over the clock's AD 1 to 3000 (see `lockedToOrbit` in the
* ETL). Where present it alone sets how the body is drawn, and the ETL checks the period and
* obliquity above against it. Absent where the report gives none: Hyperion tumbles, and Nereid,
* Eris, Haumea and Makemake have no model.
*/
rotationalElements?: RotationalElements;
}
/**
* The IAU's rotational elements for one body: polynomials in time, plus periodic terms.
*
* The pole's right ascension and declination are in degrees in the ICRF, `[c0, c1, c2]` for
* `c0 + c1 T + c2 T²`, T in Julian centuries from J2000.0 TDB. The prime meridian W is the angle
* along the body's equator, anticlockwise seen from above that pole, from where the equator rises
* through the ICRF equator to the body's longitude 0, `c0 + c1 d + c2 d²` with d in days. A
* negative rate turns the body clockwise about the pole the IAU names: Venus, Uranus and its
* moons, Triton.
*/
export interface RotationalElements {
poleRaDeg: number[];
poleDecDeg: number[];
primeMeridianDeg: number[];
/**
* Each adds `ra sin θ` to the right ascension, `dec cos θ` to the declination and `pm sin θ` to
* W, θ being `angleDeg[0] + angleDeg[1] T + angleDeg[2] T²`. The smallest are left out; see
* `parsePckRotationalElements`.
*/
terms?: Array<{ angleDeg: number[]; ra: number; dec: number; pm: number }>;
}