From fc7677e715923bb0d7430bf7b06c4eb341849190 Mon Sep 17 00:00:00 2001 From: Senrokai Date: Wed, 30 Sep 2026 16:02:30 +0200 Subject: [PATCH] Turn Nereid in the 11.594 hours Kepler measured, where it was drawn still Nereid's Horizons page states no spin, and the ETL, finding none, left it still; the validator's comment read that as "Nereid has no spin". Its rotation is measured: Kepler's K2 light curve gives 11.594 +/- 0.017 hours, confirming earlier ground-based periods (Kiss et al. 2016, MNRAS 457, 2908; arXiv:1601.02395). Its spec now carries that day, as Eris's carries Bernstein et al.'s, and with no known pole it turns about its orbit normal, as Eris, Haumea and Makemake do. The free-spinner check accepts it (11.594 hours against a 360-day orbit). A new validator: a moon without a lock must have a day unless it tumbles, and only Hyperion ("Rotational period = Chaotic") does. Nereid, left without one, fails it: "Moon nereid is drawn not turning, and is not known to tumble". The renderer spec's example of a body left still was Titan, said to have no period on Horizons, though it carries its orbit's; it is Hyperion now, and BodyRecord.rotationPeriodHours says where each kind of period comes from. Measured: the solar ETL passes; in bodies.json only Hyperion has no rotationPeriodHours; live on :4301 Nereid's marker turns 60.000 degrees in a sixth of its day. Test: Nereid, as shipped, turns 60 degrees in 1.93 hours. Guarded mutant, the day removed from its spec and run through the solar ETL: the validator fails, and the suite on the data it wrote fails that test and only it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../galaxy-system/system-orbits-renderer.spec.ts | 12 +++++++++++- .../galaxy-system/system-orbits-renderer.ts | 6 +++--- src/app/shared/models/body.model.ts | 6 ++++-- src/assets/data/bodies.json | 3 ++- tools/etl/build.ts | 15 +++++++++++++-- tools/etl/fetchSolarSystem.ts | 6 ++++-- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/app/features/galaxy-system/system-orbits-renderer.spec.ts b/src/app/features/galaxy-system/system-orbits-renderer.spec.ts index 0c01ec2..333be1e 100644 --- a/src/app/features/galaxy-system/system-orbits-renderer.spec.ts +++ b/src/app/features/galaxy-system/system-orbits-renderer.spec.ts @@ -442,8 +442,18 @@ describe('rotation without IAU elements', () => { expect(spinSense(spinning({ rotationPeriodHours: 23.934 }))).toBeGreaterThan(0.99); }); + it('turns Nereid, as shipped, once in the 11.594 hours Kepler measured: a sixth of a turn in 1.93 hours', () => { + const shipped: BodyRecord[] = JSON.parse(readFileSync(`${process.cwd()}/src/assets/data/bodies.json`, 'utf8')); + const renderer = new SystemOrbitsRenderer(shipped.filter((body) => body.id === 'neptune' || body.id === 'nereid'), []); + const nereid = renderer.members.find((member) => member.id === 'nereid')!.marker; + renderer.update(DEFAULT_EPOCH_JD); + const start = nereid.quaternion.clone(); + renderer.update(DEFAULT_EPOCH_JD + 11.594 / 6 / 24); + expect((nereid.quaternion.angleTo(start) * 180) / Math.PI).toBeCloseTo(60, 1); + }); + it('leaves a body with no published rotation still', () => { - // Titan: Horizons states no period for it, and an invented one would be a claim. + // Hyperion, which tumbles: an invented period would be a claim. const renderer = new SystemOrbitsRenderer([spinning({ rotationPeriodHours: undefined })], [], undefined, 1); renderer.update(DEFAULT_EPOCH_JD); const start = renderer.members[0].marker.quaternion.clone(); diff --git a/src/app/features/galaxy-system/system-orbits-renderer.ts b/src/app/features/galaxy-system/system-orbits-renderer.ts index 4d4084e..aafff2f 100644 --- a/src/app/features/galaxy-system/system-orbits-renderer.ts +++ b/src/app/features/galaxy-system/system-orbits-renderer.ts @@ -297,9 +297,9 @@ const SPIN_AXIS = new THREE.Vector3(0, 1, 0); const HOURS_PER_DAY = 24; /** - * How a body the IAU gives no rotational elements for is turned at a given date — Eris, Haumea - * and Makemake, whose periods are measured (Makemake's only to a factor of two, see its spec in - * `fetchSolarSystem.ts`) and whose poles are not: at its own sidereal rate, about + * How a body the IAU gives no rotational elements for is turned at a given date — Eris, Haumea, + * Makemake and Nereid, whose periods are measured (Makemake's only to a factor of two, see its + * spec in `fetchSolarSystem.ts`) and whose poles are not: at its own sidereal rate, about * its orbit's normal, backwards for a negative period. None of them has an obliquity, so none is * applied. The phase is arbitrary: each body starts at its elements' epoch in the shortest * rotation of +Y onto its axis, and turns from there. Exoplanets have no published rotation at diff --git a/src/app/shared/models/body.model.ts b/src/app/shared/models/body.model.ts index 25fe0f8..8fd579d 100644 --- a/src/app/shared/models/body.model.ts +++ b/src/app/shared/models/body.model.ts @@ -88,8 +88,10 @@ export interface BodyRecord { * 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. + * For a locked moon the period is its orbit's, from the mean motion that carries it round; for + * a body whose source states none, the measured one its ETL spec carries (Eris, Nereid). Absent + * only for Hyperion, which tumbles — the view leaves it still rather than spinning it at an + * invented rate. */ rotationPeriodHours?: number; obliquityDeg?: number; diff --git a/src/assets/data/bodies.json b/src/assets/data/bodies.json index 2fde121..fe24818 100644 --- a/src/assets/data/bodies.json +++ b/src/assets/data/bodies.json @@ -2130,7 +2130,8 @@ "decDeg": 69.117 }, "orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1, within 11.2 degrees of Horizons from 1950 to 2100", - "parentBodyId": "neptune" + "parentBodyId": "neptune", + "rotationPeriodHours": 11.594 }, { "id": "proteus", diff --git a/tools/etl/build.ts b/tools/etl/build.ts index e0f8990..b5ceba1 100644 --- a/tools/etl/build.ts +++ b/tools/etl/build.ts @@ -204,6 +204,13 @@ const TRACK_OFFSET_CEILINGS_DEG: Record = { hyperion: 23, nereid */ const WITHOUT_ROTATIONAL_ELEMENTS = new Set(['hyperion', 'nereid', 'eris', 'haumea', 'makemake']); +/** + * The one moon drawn still: Hyperion, whose page says "Rotational period = Chaotic". Every other + * moon without a lock has a measured day; Nereid's page states none, and it was drawn still until + * its K2 light curve's 11.594 hours was taken (see its spec). + */ +const TUMBLING = new Set(['hyperion']); + /** * How far the IAU's day, 360 degrees over W's rate, may be from the one Horizons states, as a * fraction of it. Measured on this catalogue: at most 1.8e-5 (Jupiter's System III, 9.92492 hours @@ -357,8 +364,12 @@ function validateBodies(bodies: BodyRecord[], horizonsOrbits: Map orbitHours * 0.1, `Moon ${body.id} does not keep one face to its planet, yet turns once in ${body.rotationPeriodHours} hours against an orbit of ${orbitHours}.` diff --git a/tools/etl/fetchSolarSystem.ts b/tools/etl/fetchSolarSystem.ts index cfe4239..984fb79 100644 --- a/tools/etl/fetchSolarSystem.ts +++ b/tools/etl/fetchSolarSystem.ts @@ -147,8 +147,10 @@ const BODY_SPECS: BodySpec[] = [ { id: 'titania', name: 'Titania', horizonsCommand: '703', ...URANUS_MOON }, { id: 'oberon', name: 'Oberon', horizonsCommand: '704', ...URANUS_MOON }, { id: 'triton', name: 'Triton', kind: 'moon', horizonsCommand: '801', center: '500@899', parentBodyId: 'neptune' }, - // Nereid's eccentric orbit, 0.75, cannot hold a face to Neptune; its page states no spin. - { id: 'nereid', name: 'Nereid', kind: 'moon', horizonsCommand: '802', center: '500@899', parentBodyId: 'neptune', spinsFreely: true, trackStepDays: 1 }, + // Nereid's eccentric orbit, 0.75, cannot hold a face to Neptune. Its page states no spin, but + // Kepler's K2 light curve gives 11.594 +/- 0.017 hours, confirming the short periods measured + // from the ground (Kiss et al. 2016, MNRAS 457, 2908; arXiv:1601.02395). No pole is known. + { id: 'nereid', name: 'Nereid', kind: 'moon', horizonsCommand: '802', center: '500@899', parentBodyId: 'neptune', spinsFreely: true, rotationPeriodHours: 11.594, trackStepDays: 1 }, { id: 'proteus', name: 'Proteus', kind: 'moon', horizonsCommand: '808', center: '500@899', parentBodyId: 'neptune' }, // Pluto's section prints its epoch as 2000 Jan 1.0; JPL's current table gives Charon's as // 2000-01-01.5, and read at 1.0 Charon sat 27.8 to 28.2 degrees — half a day of its motion is