From 46b3864d616d6e73e1112ba70177c2218d61de89 Mon Sep 17 00:00:00 2001 From: Aditya <205600203+Rohilalala@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:15:42 +0530 Subject: [PATCH] fix(satellites): stop matching a mission to a namesake from another year The mission orbit lookup reads each loaded satellite's launch year from its international designator, but satellite.js never parses that field, so the year check never ran and a new payload could take an older namesake's orbit and position. The loaded catalog now keeps the designator from the TLE line. --- src/layers/satellites/catalog.js | 2 ++ src/layers/satellites/ingestion.js | 2 ++ src/layers/satellites/ingestion.test.mjs | 20 ++++++++++++++++++++ src/layers/satellites/stale.test.mjs | 14 ++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/src/layers/satellites/catalog.js b/src/layers/satellites/catalog.js index 097d9e3be..008b2a19d 100644 --- a/src/layers/satellites/catalog.js +++ b/src/layers/satellites/catalog.js @@ -114,6 +114,8 @@ export function createCatalog({ state: layerState, services, parts, source }) { const entry = entries[i]; const satrec = twoline2satrec(entry.line1, entry.line2); if (!satrec || satrec.error !== 0) continue; + // satellite.js omits the designator the launch-year match reads. + satrec.intldesg = entry.line1.slice(9, 17).trim(); const noradId = noradIdFromSatnum(satrec.satnum); if (layerState._catalog.has(noradId)) continue; // core catalog keeps priority const pos = parts.orbits.propagatePosition(satrec, now); diff --git a/src/layers/satellites/ingestion.js b/src/layers/satellites/ingestion.js index 01653611c..168a5903b 100644 --- a/src/layers/satellites/ingestion.js +++ b/src/layers/satellites/ingestion.js @@ -119,6 +119,8 @@ export function createIngestion({ for (const entry of allEntries) { const satrec = twoline2satrec(entry.line1, entry.line2); if (!satrec || satrec.error !== 0) continue; + // satellite.js omits the designator the launch-year match reads. + satrec.intldesg = entry.line1.slice(9, 17).trim(); const noradId = noradIdFromSatnum(satrec.satnum); if (seen.has(noradId)) continue; diff --git a/src/layers/satellites/ingestion.test.mjs b/src/layers/satellites/ingestion.test.mjs index 313d999ee..7605b318c 100644 --- a/src/layers/satellites/ingestion.test.mjs +++ b/src/layers/satellites/ingestion.test.mjs @@ -2,6 +2,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { parseTleText } from '../../sources/tle.js'; import { createIngestion } from './ingestion.js'; +import { createOrbits } from './orbits.js'; // The ISS set (1 January 2024) with its catalog field swapped per satellite. const ISS_LINE1 = @@ -78,3 +79,22 @@ test('every Alpha-5 satellite keeps its own catalog entry through dedupe', async assert.deepEqual([...state._points.keys()], [100000, 110001, 339999]); assert.equal(state._count, 3); }); + +test('a mission payload does not match a loaded satellite from another launch year', async (t) => { + t.mock.method(console, 'log', () => {}); + t.mock.method(console, 'warn', () => {}); + // SXM-11, international designator 2026-148A. + const { state, update } = ingestionFor( + [ + 'SXM-11', + '1 69728U 26148A 26208.65166678 .00000015 00000+0 00000+0 0 9990', + '2 69728 0.0784 264.9826 0001797 240.4588 273.9506 1.00271527 440', + ].join('\n'), + ); + await update(); + const { getSatelliteOrbitTrack } = createOrbits({ state }); + const track = (launchTime) => + getSatelliteOrbitTrack('Sirius SXM-11', { launchTime }); + assert.equal(track('2026-06-29T02:25:00Z')?.noradId, 69728); + assert.equal(track('2030-06-29T02:25:00Z'), null); +}); diff --git a/src/layers/satellites/stale.test.mjs b/src/layers/satellites/stale.test.mjs index 89b4d2dbf..9cca23610 100644 --- a/src/layers/satellites/stale.test.mjs +++ b/src/layers/satellites/stale.test.mjs @@ -235,6 +235,20 @@ test('dense provenance belongs to its admitted records and clears on removal', a ); }); +test('dense extras keep their launch year for mission orbit matching', async (t) => { + const f = await fixture(t); + await f.update(); + f.state._params.catalog = 'dense'; + assert.equal((await f.parts.catalog._loadDenseCatalog()).status, 'ready'); + // Every fixture set carries the 1998-067A designator. + const track = (launchTime) => + f.parts.orbits.getSatelliteOrbitTrack('OWNED ORBIT FIXTURE 25551', { + launchTime, + }); + assert.equal(track('1998-11-20T06:40:00Z')?.noradId, 25551); + assert.equal(track('2026-06-29T02:25:00Z'), null); +}); + test('cancelling dense replacement preserves admitted stale extras until rebuild', async (t) => { const f = await fixture(t); await f.update();