diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 027729f3e..520b04848 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1184,6 +1184,10 @@ The Node-only `gods-eye-view/server/providers/space` export supplies factories; `sources/space` supplies fixed upstream URL builders with no I/O or environment access. Callers retain validation, transport and response policy. +The shared TLE text parser skips incomplete named sets and resumes at the next +complete set. A missing orbital line does not discard the later satellites in +the catalog; satellite layers and next-pass tools use the same parser. + ## Build configuration and local provider boundaries `vite.config.js` delegates to `server/standalone/vite.config.js`, which loads diff --git a/src/sources/tle.js b/src/sources/tle.js index 423651e6b..ac7aedc2c 100644 --- a/src/sources/tle.js +++ b/src/sources/tle.js @@ -1,6 +1,6 @@ /** * Parse three-line TLE catalog text into `{ name, line1, line2 }` entries. - * Blocks whose second and third lines are not TLE lines 1 and 2 are skipped. + * Malformed sets are skipped without dropping the valid sets that follow. */ export function parseTleText(text) { const lines = String(text) @@ -9,12 +9,13 @@ export function parseTleText(text) { .map((line) => line.trim()) .filter((line) => line.length > 0); const result = []; - for (let i = 0; i < lines.length - 2; i += 3) { + for (let i = 0; i < lines.length - 2; i++) { const name = lines[i]; const line1 = lines[i + 1]; const line2 = lines[i + 2]; if (line1.startsWith('1 ') && line2.startsWith('2 ')) { result.push({ name, line1, line2 }); + i += 2; } } return result; diff --git a/src/tools/queries/space.test.mjs b/src/tools/queries/space.test.mjs index 376cf54d7..f6c2a8df8 100644 --- a/src/tools/queries/space.test.mjs +++ b/src/tools/queries/space.test.mjs @@ -30,6 +30,27 @@ test('TLE text parses into named entries with catalog numbers', () => { assert.equal(tleCatalogNumber('1 xx'), null); }); +for (const missingLine of [1, 2]) { + test(`TLE parsing resumes after a set missing line ${missingLine}`, () => { + const sets = Array.from({ length: 8 }, (_, i) => { + const lines = TLE.trim().split('\n').slice(0, 3); + lines[0] = `SAT ${i}`; + return lines; + }); + sets[1].splice(missingLine, 1); + const entries = parseTleText(sets.flat().join('\r\n')); + assert.deepEqual( + entries.map((entry) => entry.name), + ['SAT 0', 'SAT 2', 'SAT 3', 'SAT 4', 'SAT 5', 'SAT 6', 'SAT 7'], + ); + assert.ok( + entries.every( + (entry) => entry.line1.startsWith('1 ') && entry.line2.startsWith('2 '), + ), + ); + }); +} + test('the next pass defaults to the ISS and reports times, peak and direction', async () => { const catalog = composeCatalog({ tools: coreTools,