From 8e3a494fe95d599275094042cccb80529aac9278 Mon Sep 17 00:00:00 2001 From: Senrokai Date: Tue, 29 Sep 2026 19:17:06 +0200 Subject: [PATCH] Print Hyperion's eccentricity as JPL measures it now, 0.105, not the archived row's 0.023 The card listed Hyperion's eccentricity under "Measured" as 0.023: the archived JPL satellite row the orbit is drawn from gives 0.0232. JPL's current table (SAT441) gives 0.105, and Horizons' osculating orbit ranges 0.074 to 0.132 from 1980 to 2100 (0.1099 on 2025-01-01). The row stays the orbit: with 0.105 put into it, Hyperion is further from Horizons, not nearer (median 9.6 degrees against 7.8 over 1980-2100, as a reviewer measured), so only the card changes. BodyRecord.measuredEccentricity carries the figure the card prints where it is not the orbit's own; the ETL sets it for Hyperion, and buildBodyViewModel prints it. build.ts now checks every card's eccentricity against Horizons' osculating one on 2025-01-01, within 0.03: measured at most 0.0151 (Phoebe, and the Moon, whose eccentricity swings) once Hyperion prints 0.105, where the row put it 0.0867 out. The live Hyperion page reads "ECCENTRICITY 0.105". Controls: the ETL with Hyperion on its row's figure fails ("Hyperion's card gives an eccentricity of 0.0232, where Horizons' osculating orbit has 0.1099"); the view model ignoring the field fails "prints the eccentricity measured for a moon whose orbit keeps an older one". Co-Authored-By: Claude Opus 5.5 (1M context) --- src/app/features/body-detail/body-view-model.spec.ts | 6 ++++++ src/app/features/body-detail/body-view-model.ts | 2 +- src/app/shared/models/body.model.ts | 7 +++++++ src/assets/data/bodies.json | 1 + tools/etl/build.ts | 9 +++++++++ tools/etl/fetchSolarSystem.ts | 6 +++++- 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/app/features/body-detail/body-view-model.spec.ts b/src/app/features/body-detail/body-view-model.spec.ts index 999d6dd..bf5315a 100644 --- a/src/app/features/body-detail/body-view-model.spec.ts +++ b/src/app/features/body-detail/body-view-model.spec.ts @@ -74,6 +74,12 @@ describe('buildBodyViewModel', () => { expect(bodyReadouts(model).measured.find((row) => row.label === 'Inclination')?.value).toBe('0.00°'); }); + it('prints the eccentricity measured for a moon whose orbit keeps an older one', () => { + const hyperion: BodyRecord = { ...luna, id: 'hyperion', orbit: orbit({ eccentricity: 0.0232 }), measuredEccentricity: 0.105 }; + const model = buildBodyViewModel('hyperion', { ...catalogues, bodies: [earth, hyperion] })!; + expect(bodyReadouts(model).measured.find((row) => row.label === 'Eccentricity')?.value).toBe('0.105'); + }); + it('says where the orbit comes from, in the card’s provenance', () => { expect(bodyReadouts(buildBodyViewModel('luna', catalogues)!).provenance).toContain('Orbit: JPL SSD satellite mean elements, epoch 2000 Jan 1.'); }); diff --git a/src/app/features/body-detail/body-view-model.ts b/src/app/features/body-detail/body-view-model.ts index 9df6c18..f7b562c 100644 --- a/src/app/features/body-detail/body-view-model.ts +++ b/src/app/features/body-detail/body-view-model.ts @@ -52,7 +52,7 @@ export function buildBodyViewModel(id: string, catalogues: BodyCatalogues): Body hostStarName: hostStar?.name ?? 'Unknown star', hostStarId: body.systemStarId, radiusKm: body.radiusKm, - orbit: body.orbit, + orbit: body.measuredEccentricity === undefined ? body.orbit : { ...body.orbit, eccentricity: body.measuredEccentricity }, appearance: appearanceForBody(body, catalogues.bodies, luminosityOf(hostStar)), hasPhotography: bodyTexturePath(body.id) !== undefined, orbitalPeriodDays: periodDays, diff --git a/src/app/shared/models/body.model.ts b/src/app/shared/models/body.model.ts index d1a53ae..aa607ea 100644 --- a/src/app/shared/models/body.model.ts +++ b/src/app/shared/models/body.model.ts @@ -63,6 +63,13 @@ export interface BodyRecord { 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. diff --git a/src/assets/data/bodies.json b/src/assets/data/bodies.json index 7ff2a89..78c0544 100644 --- a/src/assets/data/bodies.json +++ b/src/assets/data/bodies.json @@ -1512,6 +1512,7 @@ "decDeg": 83.862 }, "orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1", + "measuredEccentricity": 0.105, "parentBodyId": "saturn" }, { diff --git a/tools/etl/build.ts b/tools/etl/build.ts index e6908d6..cefeb76 100644 --- a/tools/etl/build.ts +++ b/tools/etl/build.ts @@ -145,6 +145,8 @@ function validateMerge(stars: StarRecord[]): void { * 0.9. */ const MAX_PLANET_OFFSET_DEG = 0.25; +/** Measured on this catalogue: at most 0.0151 (Phoebe and the Moon) once Hyperion prints its current 0.105. */ +const MAX_ECCENTRICITY_OFFSET = 0.03; const MAX_MOON_OFFSET_DEG = 2.5; const KM_PER_AU = 149597870.7; const DEG_TO_RAD = Math.PI / 180; @@ -259,6 +261,13 @@ function validateBodies(bodies: BodyRecord[], horizonsOrbits: Map 0, `Body ${body.id} has no radius; its page states it in a form the ETL does not read.`); diff --git a/tools/etl/fetchSolarSystem.ts b/tools/etl/fetchSolarSystem.ts index 8b0d1ef..6a635ee 100644 --- a/tools/etl/fetchSolarSystem.ts +++ b/tools/etl/fetchSolarSystem.ts @@ -33,6 +33,8 @@ interface BodySpec { radiusKm?: number; /** A measured sidereal day, in hours, where a later measurement overturns the one its source gives. */ rotationPeriodHours?: number; + /** The eccentricity for the card, where the row the orbit is drawn from gives an outdated one; see `BodyRecord.measuredEccentricity`. */ + measuredEccentricity?: number; /** A moon that does not keep one face to its planet: its page's own spin, or none, is kept. */ spinsFreely?: boolean; /** A moon heavy enough to move its planet round their barycentre visibly; see `BodyRecord.massRatio`. */ @@ -111,7 +113,8 @@ const BODY_SPECS: BodySpec[] = [ { id: 'rhea', name: 'Rhea', kind: 'moon', horizonsCommand: '605', center: '500@699', parentBodyId: 'saturn' }, { id: 'titan', name: 'Titan', kind: 'moon', horizonsCommand: '606', center: '500@699', parentBodyId: 'saturn' }, // Hyperion tumbles ("Rotational period = Chaotic") and Phoebe, captured, turns in 9.27 hours. - { id: 'hyperion', name: 'Hyperion', kind: 'moon', horizonsCommand: '607', center: '500@699', parentBodyId: 'saturn', spinsFreely: true }, + // Hyperion's eccentricity is 0.105 in JPL's current table (ssd.jpl.nasa.gov/sats/elem, SAT441). + { id: 'hyperion', name: 'Hyperion', kind: 'moon', horizonsCommand: '607', center: '500@699', parentBodyId: 'saturn', spinsFreely: true, measuredEccentricity: 0.105 }, { id: 'iapetus', name: 'Iapetus', kind: 'moon', horizonsCommand: '608', center: '500@699', parentBodyId: 'saturn' }, // Phoebe's row gives a mean motion of 0.6569114 degrees a day, a 548.02-day year, where its // Horizons page and JPL's current table (SAT441) give 550.30: the table's own note warns that @@ -232,6 +235,7 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo rates: mean.rates, ...(mean.laplacePole ? { laplacePole: mean.laplacePole } : {}), orbitSource: mean.orbitSource, + ...(spec.measuredEccentricity !== undefined ? { measuredEccentricity: spec.measuredEccentricity } : {}), ...(spec.parentBodyId ? { parentBodyId: spec.parentBodyId } : {}), ...(parentGm !== undefined ? { massRatio: result.gmKm3PerS2! / parentGm } : {}), ...(rotationPeriodHours !== undefined ? { rotationPeriodHours } : {}),