Turn each body at its own rate, from Horizons' own figures

The view had one rotation in it — the planet on the detail page, at 0.08 rad/s,
a number with no source. Nothing in the system view turned at all.

The data was already on disk: every cached Horizons page carries how its body
spins, in one of five forms. The rate in radians per second is preferred where
it appears, because it is signed — that is how Venus and Uranus are known to
turn backwards — then a period in hours or days, then the `9h 55m 29.711 s`
the giant planets use, and finally the word every major moon here carries
instead of a number: Synchronous. A tidally locked moon's day is its orbit, so
Kepler supplies it from the elements already parsed and the parent it goes
round.

Seventeen of the eighteen bodies come out within 1% of their published period —
Earth 23.934 h, Jupiter 9.925 h, Venus -5832.5 h, Io 42.5 h, Callisto 400.5 h.
Titan is the exception: its page states no period at all, so it is left still
rather than turned at an invented rate.

The axis is the orbit normal tilted by the obliquity about the orbit's
ascending node, which is where an obliquity is measured from and the only line
in the orbit the elements name. The phase at the epoch is published for none of
these bodies, so the face turned toward the camera is not a claim; the rate and
the direction are.

At true rates nothing is visible moving — Earth turns 15 degrees an hour. A
clock the reader can run faster is the next piece, and the audit asks for it
anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
2026-09-21 23:12:02 +02:00
co-authored by Claude Opus 5
parent 3f0abf8717
commit 225d676ab0
6 changed files with 243 additions and 27 deletions
+16 -1
View File
@@ -1,10 +1,14 @@
import { writeFileSync } from 'node:fs';
import { BodyRecord } from '../../src/app/shared/models/body.model';
import { gmForParent } from '../../src/app/shared/astro/constants';
import { orbitalPeriodDays } from '../../src/app/shared/astro/kepler';
import { SUN_STAR_ID } from '../../src/app/shared/models/star.model';
import { fetchHorizonsBody } from './lib/horizons';
import { dataPath, ensureDataDir } from './lib/paths';
const HOURS_PER_DAY = 24;
interface BodySpec {
id: string;
name: string;
@@ -56,6 +60,15 @@ export async function fetchSolarSystem(): Promise<BodyRecord[]> {
console.warn(` no physical radius found for ${spec.name}; defaulting to 0.`);
}
// A tidally locked moon's day is its orbit, which Horizons states as a word rather than a
// number: Kepler gives the period from the elements just parsed and the parent it goes round.
const rotationPeriodHours = result.tidallyLocked
? orbitalPeriodDays(result.orbit.semiMajorAxisAu, gmForParent(spec.parentBodyId)) * HOURS_PER_DAY
: result.rotationPeriodHours;
if (rotationPeriodHours === undefined) {
console.warn(` no rotation period found for ${spec.name}; it will not turn.`);
}
bodies.push({
id: spec.id,
systemStarId: SUN_STAR_ID,
@@ -63,7 +76,9 @@ export async function fetchSolarSystem(): Promise<BodyRecord[]> {
kind: spec.kind,
radiusKm: result.radiusKm ?? 0,
orbit: result.orbit,
...(spec.parentBodyId ? { parentBodyId: spec.parentBodyId } : {})
...(spec.parentBodyId ? { parentBodyId: spec.parentBodyId } : {}),
...(rotationPeriodHours !== undefined ? { rotationPeriodHours } : {}),
...(result.obliquityDeg !== undefined ? { obliquityDeg: result.obliquityDeg } : {})
});
}