Fail the ETL when re-rating a locked moon moves its pole or W off the kernel's at the present
lockedToOrbit re-rates a locked moon's W and node terms and moves their constants so that on 2025-01-01, the date the IAU's elements were fitted near, the pole and W are the kernel's own. Nothing checked it: with the constants left where they were, the solar ETL passed and the suite passed, though every re-rated moon moved (Rhea's pole 0.018 degrees, Triton 0.0054, Miranda 0.0037, Europa 0.0027, Callisto 0.0021, Deimos 0.0016, Ganymede 0.0016, measured on the bodies.json it wrote; before the drawn node rates were corrected, Mimas's W moved 0.21 and Miranda's pole 0.12). lockedToOrbit now compares orientationAt at PRESENT_JD before and after, Iapetus's pole round its orbit included, and throws past 1e-6 degrees. Measured on the shipped catalogue: at most 4.7e-10 (Deimos's W, some 2.6 million degrees round), the pole exactly. Guarded mutants, each through the solar ETL on the real catalogue: - node terms' constants not moved: "Deimos's pole or W on 2460676.5 is 1.60e-3 degrees from the IAU's". - W's constant not moved: Deimos, 9.12e-2. - the check disabled with the first: the ETL passes (control). The unit suite does not see it: the check runs on the kernel, which only the ETL reads. The ETL writes the same bodies.json as before. Unit suite 866 passed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,9 @@ const POLE_HARMONICS = 5;
|
|||||||
const MAX_NODE_RATE_OFFSET = 0.05;
|
const MAX_NODE_RATE_OFFSET = 0.05;
|
||||||
const MAX_NODE_HARMONIC = 9;
|
const MAX_NODE_HARMONIC = 9;
|
||||||
|
|
||||||
|
/** How far, in degrees, re-rating may move a locked moon's pole or W at {@link PRESENT_JD}; see {@link lockedToOrbit}. */
|
||||||
|
const MAX_PRESENT_OFFSET_DEG = 1e-6;
|
||||||
|
|
||||||
/** The planet's east longitude on a moon's IAU body-fixed frame, from the moon's mean place, at a TDB date. */
|
/** The planet's east longitude on a moon's IAU body-fixed frame, from the moon's mean place, at a TDB date. */
|
||||||
export function subPlanetLongitudeDeg(body: Pick<BodyRecord, 'orbit' | 'rates' | 'laplacePole'>, elements: RotationalElements, jd: number): number {
|
export function subPlanetLongitudeDeg(body: Pick<BodyRecord, 'orbit' | 'rates' | 'laplacePole'>, elements: RotationalElements, jd: number): number {
|
||||||
const own = positionAtEpoch(meanElementsAt(body.orbit, body.rates, jd));
|
const own = positionAtEpoch(meanElementsAt(body.orbit, body.rates, jd));
|
||||||
@@ -106,7 +109,15 @@ export function lockedToOrbit(elements: RotationalElements, mean: Pick<MeanOrbit
|
|||||||
return { ...term, angleDeg: [constant + (rate - k * nodeRate) * present, k * nodeRate] };
|
return { ...term, angleDeg: [constant + (rate - k * nodeRate) * present, k * nodeRate] };
|
||||||
});
|
});
|
||||||
const locked: RotationalElements = { ...elements, primeMeridianDeg: [w0 + (w1 - n) * (PRESENT_JD - J2000_JD), n, 0], ...(terms ? { terms } : {}) };
|
const locked: RotationalElements = { ...elements, primeMeridianDeg: [w0 + (w1 - n) * (PRESENT_JD - J2000_JD), n, 0], ...(terms ? { terms } : {}) };
|
||||||
return poleFollowsOrbit ? poleRoundOrbit(locked, mean) : locked;
|
const turned = poleFollowsOrbit ? poleRoundOrbit(locked, mean) : locked;
|
||||||
|
// Whatever is re-rated, the pole and W at the present are the kernel's, which is what they were
|
||||||
|
// fitted to. Measured: at most 4.7e-10 degrees (Deimos's W, some 2.6 million degrees round).
|
||||||
|
const [iau, own] = [elements, turned].map((each) => orientationAt(each, PRESENT_JD));
|
||||||
|
const moved = Math.max(...(['poleRaDeg', 'poleDecDeg', 'primeMeridianDeg'] as const).map((key) => Math.abs(own[key] - iau[key])));
|
||||||
|
if (moved > MAX_PRESENT_OFFSET_DEG) {
|
||||||
|
throw new Error(`${name}'s pole or W on ${PRESENT_JD} is ${moved.toExponential(2)} degrees from the IAU's: re-rated, it should be where the kernel has it today.`);
|
||||||
|
}
|
||||||
|
return turned;
|
||||||
}
|
}
|
||||||
|
|
||||||
function poleRoundOrbit(elements: RotationalElements, mean: Pick<MeanOrbit, 'orbit' | 'rates' | 'laplacePole'>): RotationalElements {
|
function poleRoundOrbit(elements: RotationalElements, mean: Pick<MeanOrbit, 'orbit' | 'rates' | 'laplacePole'>): RotationalElements {
|
||||||
|
|||||||
Reference in New Issue
Block a user