From 6ca53febad0ca1f761e2af38190ff89a0e8b1cb1 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sun, 8 Nov 2026 15:43:09 -0800 Subject: [PATCH] fix(cockpit): no swath readout while the orbital view looks past the Earth's limb --- src/cockpitOrbital.js | 15 +++++++++++++++ src/cockpitOrbital.test.mjs | 14 ++++++++++++++ src/ui/cockpitOrbitalView.js | 12 ++++++++---- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/cockpitOrbital.js b/src/cockpitOrbital.js index fafe2a857..e49bfb5bc 100644 --- a/src/cockpitOrbital.js +++ b/src/cockpitOrbital.js @@ -364,6 +364,21 @@ export function offNadirAngleDeg({ northDeg = 0, eastDeg = 0 } = {}) { return (Math.acos(cosine) * 180) / Math.PI; } +/** + * Whether the view's centre, tilted off nadir, still meets the Earth: its + * angle from nadir (`offNadirAngleDeg`) is within the Earth's angular radius + * from the satellite, asin(R / (R + altitude)). + * @param {number} altitudeM + * @param {{northDeg?: number, eastDeg?: number}} [offset] + * @returns {boolean} + */ +export function offNadirCentreMeetsEarth(altitudeM, offset = {}) { + if (!Number.isFinite(altitudeM) || altitudeM <= 0) return false; + const R = ORBITAL_EARTH_RADIUS_M; + const limbDeg = (Math.asin(R / (R + altitudeM)) * 180) / Math.PI; + return offNadirAngleDeg(offset) < limbDeg; +} + /** * Roughly how much wider the ground across the frame is at the centre of a * tilted view than at nadir, on a flat Earth: tilting across the frame (east, diff --git a/src/cockpitOrbital.test.mjs b/src/cockpitOrbital.test.mjs index e47ad1141..450b482cb 100644 --- a/src/cockpitOrbital.test.mjs +++ b/src/cockpitOrbital.test.mjs @@ -13,6 +13,7 @@ import { nadirOrientation, nextOrbitalZoom, offNadirAngleDeg, + offNadirCentreMeetsEarth, offNadirSwathScale, offNadirOrientation, orbitalBaseFovRad, @@ -300,3 +301,16 @@ test("a tilted view's width grows faster across the frame than along it", () => } assert.equal(offNadirSwathScale({ northDeg: Number.NaN }), 1); }); + +test('a view tilted past the limb no longer meets the Earth', () => { + // GEO: the Earth spans about 8.7 degrees either side of nadir. + assert.equal(offNadirCentreMeetsEarth(35_786_000, { northDeg: 5 }), true); + assert.equal(offNadirCentreMeetsEarth(35_786_000, { northDeg: 15 }), false); + // LEO: a 30 degree tilt still lands on the ground. + assert.equal( + offNadirCentreMeetsEarth(420_000, { northDeg: 20, eastDeg: 20 }), + true, + ); + assert.equal(offNadirCentreMeetsEarth(420_000, { eastDeg: 80 }), false); + assert.equal(offNadirCentreMeetsEarth(NaN, {}), false); +}); diff --git a/src/ui/cockpitOrbitalView.js b/src/ui/cockpitOrbitalView.js index d7958a2c1..f1238a9b2 100644 --- a/src/ui/cockpitOrbitalView.js +++ b/src/ui/cockpitOrbitalView.js @@ -14,6 +14,7 @@ import { nextOrbitalZoom, offNadirAngleDeg, offNadirOrientation, + offNadirCentreMeetsEarth, offNadirSwathScale, orbitalBaseFovRad, previousOrbitalZoom, @@ -410,9 +411,12 @@ export function publishOrbitalView(info, transition = IDLE_TRANSITION) { const settledFov = fovForZoom(orbitalBaseFovRad(info.altitudeM), state.zoom); const footprintKm = footprintWidthKm(info.altitudeM, settledFov, aspect); const trackUp = Number.isFinite(info.groundTrackDeg); - const swathScale = offset - ? offNadirSwathScale(lookTilt(orbitalLookReadoutParts(state.lookTarget))) - : 1; + const tilt = offset + ? lookTilt(orbitalLookReadoutParts(state.lookTarget)) + : null; + const swathScale = offset ? offNadirSwathScale(tilt) : 1; + // Tilted past the limb, the view's centre looks into space: no swath. + const seesEarth = !offset || offNadirCentreMeetsEarth(info.altitudeM, tilt); this.publishOrbital({ name: info.name, classLabel: info.classLabel || '', @@ -432,7 +436,7 @@ export function publishOrbitalView(info, transition = IDLE_TRANSITION) { // the nadir footprint (`offNadirSwathScale`); 1 and null at nadir. swathScale, swathKm: - offset && Number.isFinite(footprintKm) + offset && seesEarth && Number.isFinite(footprintKm) ? footprintKm * swathScale : null, },