Findings

Two parts. First, each finding reported to the people who make a program, and how far it has got. Then the wider picture: we fed today’s real catalog data to three programs: one written carelessly the way many older programs are, one written carefully from the format documents, and one popular free library. Here is what broke, in plain words, with a link to the evidence for each item.

Reported findings first, a card each, with its track and its checks. Then runner results for three adapters across the eighteen cases of corpus v0.7.0, then each finding with its evidence and case. Statuses: pass exact, pass-tolerance needed a documented tolerance (1e-12 relative for floats, 2 µs for epochs, and 432 µs, half the TLE field’s resolution, when a TLE epoch is compared with an OMM one), fail, skip format unsupported by the parser, not-exercised the check never reached the parser (the SATCAT data check for every parser; the nine-digit case outside a launch window).

Reported to the people who make each programReported findings

Each card is one thing the test kit found in one program, at the version named. The steps on a card say how far it has got: found here, reported to the program’s makers, a fix merged into its source, a release that carries the fix. Open a card for what the program read beside what it should have read, and for the commands that run the same test on your own machine.

One card for each clause of a one-off run’s finding that has a report, and two for python-sgp4. The track is found (the corpus decision entry that records the run), reported (the report’s date), merged (the fix in the project’s source) and released (a release that carries it), each with its date or “not yet”. A card’s checks are the ones the library page’s matrix attributes to its report, one of each kind, with the runner’s own line; where a line holds a value read and the value expected, the two are set under each other and marked where their characters differ.

A finding is a result for the version named, on the date named, not a verdict on the project. The cards are in the order they were reported, newest first, and are not ranked. A state is the one this site last recorded; the report’s own page is the live one.

Library

Format

Status

  1. One set with a missing line drops every set after it in its group, with nothing said gods-eye-view 0.2.1 · TLE · reported foundreportedmergednot yetreleasednot yet

    gods-eye-view 0.2.1, run on 2026-10-03 against corpus v0.5.0: its row on the Library status page. Found: corpus D-179.

    What was read, and what was expected

    case 18 corrupt-input · corrupt-tle-missing-line-2 · gods-eye-view 0.2.1

    input
    the case’s own files, which this site does not show: fixtures/corrupt-input
    expected
    A TLE set whose line 1 is followed directly by the next set's name line: the orphan line 1 is refused with a reason.
    read
    the corrupt record dropped (refusals not reported by this adapter)

    case 18 corrupt-input · corrupt-input-neighbours-load · gods-eye-view 0.2.1

    input
    the case’s own files, which this site does not show: fixtures/corrupt-input
    expected
    The valid records around each corrupt input load exactly as the same parser reads them from the unedited file: the corrupt input changes nothing about them, so the parser picks up again after it.
    read
    1 of 2 valid set(s) around the corrupt one loaded as the parser reads them from the unedited file unedited-sets.tle; 20453 dropped

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "cd '$H' && node --import ./register.mjs harness.mjs {fmt}" --case corrupt-input

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  2. A letter in a numeric field is read up to the letter, with no error PyEphem 4.2.1 · TLE · closed foundreportedclosed

    PyEphem 4.2.1, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-174. Closed: a letter in a numeric field; no validation added, by the maintainer's choice for a legacy library, reopenable.

    What was read, and what was expected

    case 18 corrupt-input · corrupt-tle-letter-in-number · PyEphem 4.2.1

    input
    the case’s own files, which this site does not show: fixtures/corrupt-input
    expected
    A TLE set with the letter O in place of a 0 in line 1's epoch field is refused with a reason, not read as a record with a wrong epoch and not dropped silently.
    read
    epochread'2026-07-08T16:48:00.000000'unedited'2026-07-08T17:02:16.167840'

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0" "ephem==4.2.1"
    gpconf run --preset pyephem --case corrupt-input

    These run case corrupt-input of corpus v0.7.0 against the version of the library named on this card. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  3. A letter in a numeric TLE field is read up to the letter, with no error satellite.js 7.1.0 · TLE · reported foundreportedmergednot yetreleasednot yet

    satellite.js 7.1.0, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-176.

    What was read, and what was expected

    case 18 corrupt-input · corrupt-tle-letter-in-number · satellite.js 7.1.0

    input
    the case’s own files, which this site does not show: fixtures/corrupt-input
    expected
    A TLE set with the letter O in place of a 0 in line 1's epoch field is refused with a reason, not read as a record with a wrong epoch and not dropped silently.
    read
    epochread'2026-07-08T16:48:00.000000'unedited'2026-07-08T17:02:16.167840'

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0"
    npm install satellite.js@7.1.0
    gpconf run --preset satellite.js --case corrupt-input

    These run case corrupt-input of corpus v0.7.0 against the version of the library named on this card. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  4. A letter in the epoch field passes the set's checks and is read up to the letter Gpredict 2.6 · TLE · reported foundreportedmergednot yetreleasednot yet

    Gpredict 2.6, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-178.

    What was read, and what was expected

    case 18 corrupt-input · corrupt-tle-letter-in-number · Gpredict 2.6

    input
    the case’s own files, which this site does not show: fixtures/corrupt-input
    expected
    A TLE set with the letter O in place of a 0 in line 1's epoch field is refused with a reason, not read as a record with a wrong epoch and not dropped silently.
    read
    epochread'2026-07-08T16:48:00.000000'unedited'2026-07-08T17:02:16.167840'

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "'$H/gpredict-harness' {fmt}" --case corrupt-input

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  5. A letter in the epoch field is read up to the letter, and isValidTLE() calls the set valid tle.js 5.0.3 · TLE · reported foundreportedmergednot yetreleasednot yet

    tle.js 5.0.3, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-184.

    What was read, and what was expected

    case 18 corrupt-input · corrupt-tle-letter-in-number · tle.js 5.0.3, raw fields

    input
    the case’s own files, which this site does not show: fixtures/corrupt-input
    expected
    A TLE set with the letter O in place of a 0 in line 1's epoch field is refused with a reason, not read as a record with a wrong epoch and not dropped silently.
    read
    epochread'2026-07-08T16:48:00.000000'unedited'2026-07-08T17:02:16.167840' More records of this kind are in the library page’s entry.

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0"
    npm install tle.js@5.0.3
    gpconf run --preset tle.js --case corrupt-input

    These run case corrupt-input of corpus v0.7.0 against the version of the library named on this card. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  6. Alpha-5 fields decode to catalog number 0, silently, the elements unaffected PyEphem 4.2.1 · TLE · reported foundreportedmergednot yetreleasednot yet

    PyEphem 4.2.1, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-128.

    What was read, and what was expected

    case 15 alpha5-tle-derived · values · PyEphem 4.2.1

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    1 expected record(s) not returned, e.g. [100000]; 1 unexpected record id(s), e.g. [0]

    case 15 alpha5-tle-derived · tle-catalog-field-decodes · PyEphem 4.2.1

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Columns 3-7 of TLE lines 1 and 2 decode to the same integer (five digits, or Alpha-5 letter + four digits).
    read
    1 catalog field(s) not decoded to the right integer, e.g. [100000]

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0" "ephem==4.2.1"
    gpconf run --preset pyephem --case alpha5-tle-derived

    These run case alpha5-tle-derived of corpus v0.7.0 against the version of the library named on this card. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  7. Alpha-5 decoding and the epoch field fixed astroz 0.14.0 · TLE · released foundreportedmergedreleased

    astroz 0.14.0, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-140. Merged: the maintainer’s pull request #99.

    In the run this page reports, of the release that carries the fix, the checks this finding failed pass: there is nothing read wrongly to show.

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "'$H/harness' {fmt} fields" --vectors-cmd "'$H/vectors'" --case alpha5-tle-derived

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This case’s files ship with the corpus, so nothing is fetched. At v0.14.0 the case still reports a failure, on the object name, which the parser does not keep; the catalog numbers and the epochs match. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  8. OMM JSON epochs are truncated to milliseconds, and the TLE catalog field is returned as a string and never decoded satellite.js 7.1.0 · OMM JSON, TLE · merged foundreportedmergedreleasednot yet

    satellite.js 7.1.0, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-132. Merged: PR #186 and PR #187.

    What was read, and what was expected

    case 1 epoch-year-19xx · values · satellite.js 7.1.0

    input
    the case’s own files, which this site does not show: fixtures/epoch-year-19xx
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    id 25544 · epochread'1998-11-20T06:49:59.999000'expected'1998-11-20T06:49:59.999808' More records of this kind are in the library page’s entry.

    case 1 epoch-year-19xx · tle-values-match-omm-within-tle-precision · satellite.js 7.1.0

    input
    the case’s own files, which this site does not show: fixtures/epoch-year-19xx
    expected
    The TLE rendering equals the OMM values except: eccentricity truncated to 7 digits, BSTAR and second derivative rounded (half up) to a 5-digit mantissa, epoch at 1e-8 day resolution.
    read
    id 25544 · epochTLE1998-11-20T06:49:59.999808OMM1998-11-20T06:49:59.999000

    case 15 alpha5-tle-derived · values · satellite.js 7.1.0

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    1 record(s) whose norad_cat_id is not an integer, e.g. 'A0000' (str); 1 expected record(s) not returned, e.g. [100000]

    case 15 alpha5-tle-derived · tle-catalog-field-decodes · satellite.js 7.1.0

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Columns 3-7 of TLE lines 1 and 2 decode to the same integer (five digits, or Alpha-5 letter + four digits).
    read
    1 catalog field(s) not decoded to the right integer, e.g. [100000]

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0"
    npm install satellite.js@7.1.0
    gpconf run --preset satellite.js --case alpha5-tle-derived
    gpconf fetch
    gpconf run --preset satellite.js --case six-digit-omm-saramago

    These run case alpha5-tle-derived of corpus v0.7.0 against the version of the library named on this card. The fetch runs once per corpus version: 46 requests to CelesTrak, about 3.2 MB, cached. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  9. Alpha-5 fields become catalog number 0 Gpredict 2.6 · TLE · not filed foundreportednot filedmergednot yetreleasednot yet

    Gpredict 2.6, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-133. No report was filed: the project’s maintainer had already said in public where it stands, and the site records that in its list of what projects have said.

    What was read, and what was expected

    case 15 alpha5-tle-derived · values · Gpredict 2.6

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    1 expected record(s) not returned, e.g. [100000]; 1 unexpected record id(s), e.g. [0]

    case 15 alpha5-tle-derived · tle-catalog-field-decodes · Gpredict 2.6

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Columns 3-7 of TLE lines 1 and 2 decode to the same integer (five digits, or Alpha-5 letter + four digits).
    read
    1 catalog field(s) not decoded to the right integer, e.g. [100000]

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "'$H/gpredict-harness' {fmt}" --case alpha5-tle-derived

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  10. The mean motion loses its last digit on every record Gpredict 2.6 · TLE · merged foundreportedmergedreleasednot yet

    Gpredict 2.6, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-133. Merged: PR #428.

    What was read, and what was expected

    case 1 epoch-year-19xx · values · Gpredict 2.6

    input
    the case’s own files, which this site does not show: fixtures/epoch-year-19xx
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    id 25544 · mean_motionread16.0506483expected16.05064833 More records of this kind are in the library page’s entry.

    Where it stands

    Run it yourself

    python3 tools/fetch.py
    python3 -m gpconf run --cmd "'$H/gpredict-harness' {fmt}" --case baseline-iss-five-formats

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. The fetch runs once per corpus version: 46 requests to CelesTrak, about 3.2 MB, cached. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  11. Every Alpha-5 satellite collapses onto one catalog entry keyed NaN and the rest are dropped as duplicates gods-eye-view 0.2.1 · TLE · reported foundreportedmergednot yetreleasednot yet

    gods-eye-view 0.2.1, run on 2026-10-03 against corpus v0.5.0: its row on the Library status page. Found: corpus D-136.

    What was read, and what was expected

    case 15 alpha5-tle-derived · values · gods-eye-view 0.2.1

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    1 record(s) without norad_cat_id; 1 expected record(s) not returned, e.g. [100000]

    case 15 alpha5-tle-derived · tle-catalog-field-decodes · gods-eye-view 0.2.1

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Columns 3-7 of TLE lines 1 and 2 decode to the same integer (five digits, or Alpha-5 letter + four digits).
    read
    1 catalog field(s) not decoded to the right integer, e.g. [100000]

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "cd '$H' && node --import ./register.mjs harness.mjs {fmt}" --case alpha5-tle-derived

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  12. The TLE registry silently drops every Alpha-5 set; master's CSV reader rejects CelesTrak SupGP files whole SatDump 1.2.2 and master · TLE, OMM CSV · closed foundreportedclosed

    SatDump 1.2.2 and master, run on 2026-09-24 against corpus v0.2.1: its row on the Library status page. Found: corpus D-137. Closed: Alpha-5 sets and the SupGP CSV; a collaborator confirmed both and said fixes are planned, neither changed yet; closed by the reporter.

    What was read, and what was expected

    case 15 alpha5-tle-derived · values · SatDump 1.2.2

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    1 expected record(s) not returned, e.g. [100000] More records of this kind are in the library page’s entry.

    case 15 alpha5-tle-derived · tle-catalog-field-decodes · SatDump 1.2.2

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Columns 3-7 of TLE lines 1 and 2 decode to the same integer (five digits, or Alpha-5 letter + four digits).
    read
    1 catalog field(s) not decoded to the right integer, e.g. [100000] More records of this kind are in the library page’s entry.

    case 7 supgp-celestrak-classification-c · values · SatDump master

    input
    the case’s own files, which this site does not show: fixtures/supgp-celestrak-classification-c
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    2 expected record(s) not returned, e.g. [72000, 72001]

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "'$H/build/satdump-harness' {fmt}" --case alpha5-tle-derived
    python3 tools/fetch.py
    python3 -m gpconf run --cmd "'$H/build/satdump-harness' {fmt}" --case nine-digit-supgp-launch-nominals

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. The build needs a file of your own, stand-ins.cpp, which the README describes. This runs master’s build; the release’s, build-rel/satdump-rel, reads the first case the same way. The fetch runs once per corpus version: 46 requests to CelesTrak, about 3.2 MB, cached. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  13. Alpha-5 decoding correct in v3.0 libsgp4 3.0 · TLE · released foundreportedmergedreleased

    libsgp4 3.0, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-138. Merged: the maintainer’s own pull request #46.

    In the run this page reports, of the release that carries the fix, the checks this finding failed pass: there is nothing read wrongly to show.

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "'$H/build-v3.0/harness' {fmt}" --vectors-cmd "'$H/build-v3.0/vectors'" --case alpha5-tle-derived

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This is the build of release v3.0, which the README has you compile into build-v3.0; its first build block is an earlier commit, which refuses every Alpha-5 set. This case’s files ship with the corpus, so nothing is fetched. At v3.0 the case still reports a failure, on the epochs of #45; every catalog number decodes. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  14. CelesTrak's SupGP CSV is still refused whole, and some TLE epochs still come back 8 µs off libsgp4 3.0 · OMM CSV, TLE · reported foundreportedmergednot yetreleasednot yet

    libsgp4 3.0, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-138.

    What was read, and what was expected

    case 5 analyst-objects · values · libsgp4 3.0

    input
    the case’s own files, which this site does not show: fixtures/analyst-objects
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    id 81011 · epochread'2026-09-19T01:05:16.147400'expected'2026-09-19T01:05:16.147392' More records of this kind are in the library page’s entry.

    case 6 nine-digit-supgp-launch-nominals · records-returned · libsgp4 3.0

    input
    the case’s own files, which this site does not show: fixtures/nine-digit-supgp-launch-nominals
    expected
    A file the corpus's own reader reads records from yields records; a parser that returns none for it fails the file, and no other check on it runs.
    read
    parser returned 0 of 1 record(s): 1 refused with a reason (TleException: Invalid CSV field count) More records of this kind are in the library page’s entry.

    case 14 alpha5-encoding-vectors · catalog-number-is-integer · libsgp4 3.0

    input
    the case’s own files, which this site does not show: fixtures/alpha5-encoding-vectors
    expected
    NORAD_CAT_ID parses as an integer in every OMM format, including values of six and nine digits, and a ten-digit value is rejected (CCSDS allows up to nine digits); leading zeros and an explicit '+' are legal in KVN.
    read
    '1000000000' accepted as 1000000000 (should be rejected: ten digits exceed 'up to nine digits'; schema xsd:integer would accept it, the standard's text does not)

    Where it stands

    Run it yourself

    python3 -m gpconf run --cmd "'$H/build-v3.0/harness' {fmt}" --vectors-cmd "'$H/build-v3.0/vectors'" --case alpha5-tle-derived
    python3 tools/fetch.py
    python3 -m gpconf run --cmd "'$H/build-v3.0/harness' {fmt}" --vectors-cmd "'$H/build-v3.0/vectors'" --case nine-digit-supgp-launch-nominals

    This library is not a preset of the runner: it runs through the corpus’s recipe for it, in a clone of the corpus, after the build steps in the recipe’s README, which set $H. This is the build of release v3.0, which the README has you compile into build-v3.0; its first build block is an earlier commit, which refuses every Alpha-5 set. The fetch runs once per corpus version: 46 requests to CelesTrak, about 3.2 MB, cached. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  15. Alpha-5 fields give NaN with no error, and two-digit epoch years pivot at 50 instead of 57 tle.js 5.0.3 · TLE · released foundreportedmergedreleased

    tle.js 5.0.3, run on 2026-09-27 against corpus v0.4.0: its row on the Library status page. Found: corpus D-139. Merged: PR #64.

    What was read, and what was expected

    case 14 alpha5-encoding-vectors · alpha5-decode · tle.js 5.0.3, raw fields

    input
    A0000, one of the corpus’s specification vectors
    expected
    An Alpha-5 catalog field decodes as (letter value)*10000 + digits with A=10 ... Z=33 and I, O unused; 'I', 'O' and lowercase are invalid.
    read
    input A0000readNaNexpected100000 More records of this kind are in the library page’s entry.

    case 14 alpha5-encoding-vectors · two-digit-year-pivot · tle.js 5.0.3, raw fields

    input
    the case’s own files, which this site does not show: fixtures/alpha5-encoding-vectors
    expected
    TLE epoch years 57-99 map to 1957-1999 and 00-56 to 2000-2056; the resulting ISO epoch equals the OMM EPOCH string.
    read
    56 -> 1956 More records of this kind are in the library page’s entry.

    case 15 alpha5-tle-derived · values · tle.js 5.0.3, raw fields

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Every record the file holds comes back, under the right catalog number, with each field equal to the corpus's frozen expected value.
    read
    1 record(s) whose norad_cat_id is not an integer, e.g. 'NaN' (str); 1 expected record(s) not returned, e.g. [100000] More records of this kind are in the library page’s entry.

    case 15 alpha5-tle-derived · tle-catalog-field-decodes · tle.js 5.0.3, raw fields

    input
    A0000, columns 3 to 7 of the corpus’s Alpha-5 rendering of SARAMAGO (shown whole on the Tools page)
    expected
    Columns 3-7 of TLE lines 1 and 2 decode to the same integer (five digits, or Alpha-5 letter + four digits).
    read
    1 catalog field(s) not decoded to the right integer, e.g. [100000] More records of this kind are in the library page’s entry.

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0"
    npm install tle.js@5.0.3
    gpconf run --preset tle.js --case alpha5-encoding-vectors

    These run case alpha5-encoding-vectors of corpus v0.7.0 against the version of the library named on this card. This case’s files ship with the corpus, so nothing is fetched. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  16. Objects with no launch designation crash the library’s XML reader python-sgp4 2.27 · OMM XML · merged foundreportedmergedreleasednot yet

    python-sgp4 2.27, in the corpus’s own table at v0.7.0, below. Found: corpus D-029. Merged: PR #172.

    What it does, with the numbers, is described below.

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0" "sgp4==2.27"
    gpconf fetch
    gpconf run --preset sgp4 --case analyst-objects

    These run case analyst-objects of corpus v0.7.0 against the version of the library named on this card. The fetch runs once per corpus version: 46 requests to CelesTrak, about 3.2 MB, cached. A count from a run today can differ from the card’s: it is a result for that day’s versions.

  17. Nine-digit numbers are real, and a popular library cannot load them python-sgp4 2.27 · OMM CSV, OMM JSON, OMM XML · reported reported by another userfound independently mergednot yetreleasednot yet

    python-sgp4 2.27, in the corpus’s own table at v0.7.0, below. Found: corpus D-029.

    What it does, with the numbers, is described below.

    Where it stands

    Run it yourself

    pip install "gpconf==0.7.0" "sgp4==2.27"
    gpconf fetch
    gpconf run --preset sgp4 --case nine-digit-supgp-launch-nominals

    These run case nine-digit-supgp-launch-nominals of corpus v0.7.0 against the version of the library named on this card. The fetch runs once per corpus version: 46 requests to CelesTrak, about 3.2 MB, cached. Nine-digit records exist only in the days after a launch, so a fetch today may bring none (corpus D-229); the Library status page runs a reproducer for this every week. A count from a run today can differ from the card’s: it is a result for that day’s versions.

Three parsers, eighteen cases

Naive is the program most projects have: it assumes five digits, one date format, every column present. Reference is the corpus’s own careful reader, the control. python-sgp4 is a widely used free library, version 2.27, used as-is. The seventeenth case turns the question around: does a program that writes the old format put the right lettered plate on it, and does it refuse numbers the plate cannot hold? The eighteenth slips one damaged record in among good ones: the right answer is to refuse it, say why, and keep reading the rest.

In the table, pass means every value matched exactly; pass-tolerance means it matched within a documented allowance; fail means something broke; skip means the program cannot read that format at all; not-exercised means the check never reached the program: one case checks a data file itself, and the nine-digit case needs data that exists only in the days after a launch.

From docs/FAILURES.md: reference = the corpus’s readers (control, must be exact); naive = int() on five columns, one epoch format, classification assumed U, every column assumed present, strict KEY = value KVN, the catalog number written through an integer format; python-sgp4 2.27 through sgp4.omm, twoline2rv and export_tle, no KVN reader. The writer-side case tle-writer-alpha5 checks a tool that writes a TLE: a correct Alpha-5 catalog field on both lines, and a refusal for ids the field cannot represent, which counts as a pass. The eighteenth case, corrupt-input, hands a parser one corrupt input between valid records: a refusal with a reason passes, and the records around it must load as they do without it.

casereferencenaivepython-sgp4
epoch-year-19xx pass pass pass-tolerance
baseline-iss-five-formats pass pass pass-tolerance
six-digit-omm-saramago pass fail 8 failing pass-tolerance
tle-omits-six-digit-objects pass fail 1 failing pass-tolerance
analyst-objects pass fail 13 failing fail 2 failing
nine-digit-supgp-launch-nominals pass fail 5 failing fail 5 failing
supgp-celestrak-classification-c pass fail 5 failing fail 6 failing
bstar-and-derivative-forms pass fail 1 failing pass-tolerance
satcat-70000-cutoff not-exercised not-exercised not-exercised
csv-json-omitted-mandatory-fields pass fail 8 failing fail 2 failing
mean-motion-derivative-convention pass fail 7 failing pass
tle-vs-omm-precision-loss pass fail 7 failing pass-tolerance
omm-xml-schema pass fail 6 failing fail 3 failing
alpha5-encoding-vectors pass fail 4 failing fail 2 failing
alpha5-tle-derived pass fail 6 failing pass-tolerance
kvn-syntax-variants pass fail 4 failing skip
tle-writer-alpha5 pass fail 3 failing fail 3 failing
corrupt-input pass fail 3 failing fail 3 failing

Naive fails 15 of 18 cases; python-sgp4 fails 8, with 26 failing items in total; in the writer case its compiled writer puts the letters I and O, which Alpha-5 skips, in the field for 18xxxx and 23xxxx (reported as #173) and writes a line for the synthetic −1 input, and it refuses 340000 and nine-digit ids; the reference passes all seventeen cases a parser exercises exactly; the eighteenth, satcat-70000-cutoff, is a check the runner makes on the legacy SATCAT file itself and reports as not exercised for every parser. From a fresh fetch the same adapters fail 14 and 6, and the reference passes 16 with one case not available: the counts above were measured with launch-window files that a new user’s fetch cannot obtain (corpus D-229).

What broke, item by item

1. Ask CelesTrak for recent launches in the old format and you get “not found”

Every satellite launched since July 2026 has a six-digit number, and the old TLE format cannot hold it. CelesTrak leaves those objects out. When a whole group is six-digit, the answer is an HTTP 404. Old software that treats “not found” as “nothing new” goes quiet and misses every recent launch. The tracker checks this every day.

A TLE/3LE/2LE request returns only objects below 100000; when none qualify the response is HTTP 404 with the body No GP data found. At the snapshot (fetched 2026-09-21 00:10 UTC; the tracker's later fetches of the rolling group show other counts with their own times) the last-30-days group was 256 records in CSV, all six-digit, and 404 as TLE (captured twice). The count relation “TLE records = OMM records below 100000” holds for the analyst group (565 CSV, 219 TLE). Treat the 404 as “unrepresentable in this format”.

Cases tle-omits-six-digit-objects, six-digit-omm-saramago; naive fails both (it also cannot parse the six-digit CSV).

2. Nine-digit numbers are real, and a popular library cannot load them

For about a week after each Starlink launch, CelesTrak’s operator-supplied data uses nine-digit placeholder numbers. python-sgp4, and the Skyfield library built on it, refuse every such record with an error, in every format, because they store the number internally in the five-character lettered form, which tops out at 339999. This had already been reported by someone else, and a change was proposed; we tested it and reported back that it adds tests but does not change the library, so the problem stands.

27 nine-digit ids (799501621–799501647) were in the full Starlink SupGP file at the snapshot. python-sgp4 2.27 omm.initialize and Skyfield 1.55 EarthSatellite.from_omm raise ValueError: satellite number cannot exceed 339999 for each, because sgp4init stores the number through to_alpha5. Reported upstream independently as #169; PR #170 at commit 5e4f308 adds two tests and no surviving library change, and the corpus’s local test found 0 fixed and 0 regressed across the sixteen cases the corpus had at the time (report). The library status page re-runs the reproducer weekly.

Case nine-digit-supgp-launch-nominals (perishable; reports “not exercised” when no launch is recent). SupGP element values are withheld from the public corpus; only ids and structure are published.

3. Objects with no launch designation crash the library’s XML reader

Some tracked objects have not been matched to a launch yet, so their “international designator” is empty. In the XML format, python-sgp4 turns that empty field into “nothing” and then crashes on it. We reported this as issue #171 and, at the maintainer’s invitation, submitted a fix (PR #172), which was merged on 24 September 2026; it reaches users with the library’s next release.

All 565 analyst objects at the snapshot carry OBJECT_NAME = UNKNOWN and 563 an empty OBJECT_ID. sgp4.omm.parse_xml returns None for the empty element and initialize() raises TypeError: 564 of 566 analyst XML records fail while the same records in CSV pass. Filed as #171 with a reproducer on CCSDS example values; the fix is PR #172, submitted at the maintainer’s invitation, amended after review and merged 2026-09-24 as 8126f77 (#171 closed); no release carries it yet, so the 2.27 results in the table stand. CCSDS says an unknown designator “should be set to UNKNOWN”; CelesTrak writes it empty; the corpus records the ambiguity and expects the provider’s output.

Case analyst-objects; naive fails in four formats (int(''), None[...]).

4. The two catalogs write the same TLE slightly differently

CelesTrak and Space-Track publish the same orbit with two tiny cosmetic differences in the old format: how a zero is spelled in one column, and whether a seven-digit value is cut off or rounded. The numbers mean the same thing, but software that compares text character by character sees a mismatch. The highlighted columns below show where, on the standard’s example line; no live values from either catalog are shown.

Conventions, not values. On the CCSDS example rendered as a TLE: columns 45–52 (second derivative) hold the zero that CelesTrak writes 00000+0 and Space-Track 00000-0 (the sign at column 51); columns 27–33 (eccentricity) are the seven digits CelesTrak truncates and Space-Track rounds, so the last digit can differ by one; column 69 (checksum) then differs as a consequence. Observed in the maintainer’s Space-Track verification run: 8 of 21 same-epoch records differed at column 33 exactly where rounding and truncation disagree, and every line differed at column 51. CelesTrak also rounds the BSTAR and second-derivative mantissas half up (in 304 of 304 fetched TLE sets); no document states either rule. python-sgp4’s export_tle follows Space-Track’s zero sign, which is a provider divergence, not a bug (corpus decision D-072).

GOES 9                  
1 23581U 95025A   20064.44075725 -.00000113  00000+0  10000-3 0  9254
2 23581   3.0539  81.7939 0005013 249.2363 150.1602  1.00273272 43169
  • columns 45–52 zero second derivative: 00000+0 (CelesTrak) vs 00000-0 (Space-Track)
  • columns 27–33 eccentricity: truncated (CelesTrak) vs rounded (Space-Track)
  • column 69 checksum follows the bytes

Cases tle-vs-omm-precision-loss, alpha5-tle-derived; ambiguity ecc-truncation-vs-mantissa-rounding in the manifest.

5. The old format is a lossy copy of the new one

The old format has fewer digits than the new one, so converting new → old → new does not give back the same numbers. Software that expects a perfect round trip will report false differences.

Eccentricity truncated to 7 digits; BSTAR and second-derivative mantissas rounded half up to 5 digits; supplemental epochs quantised to the TLE’s 864 µs resolution (86 µs differences observed). MEAN_MOTION_DOT in the OMM equals the TLE field as printed (the ndot/2 convention, in 304 of 304 fetched TLE sets), although CCSDS’s note could be read either way. The corpus compares TLE against OMM under these rules and reports tolerance-dependent passes separately.

Cases tle-vs-omm-precision-loss, mean-motion-derivative-convention; naive fails both.

6. CelesTrak’s CSV and JSON leave out fields the standard calls mandatory

Four fields that never change are simply omitted, along with the header. A program that insists on seeing them fails on every file.

CENTER_NAME, REF_FRAME, TIME_SYSTEM and MEAN_ELEMENT_THEORY are absent from CSV and JSON; XML and KVN carry them, with SGP/SGP4 in KVN and SGP4 in XML for the same record. Default them (EARTH, TEME, UTC, SGP4) and accept both spellings.

Case csv-json-omitted-mandatory-fields; naive fails, python-sgp4 fails only through the nine-digit record in the same set.

7. A “classified” marker is silently reset, and the letter rules are applied loosely

CelesTrak’s operator-supplied records are marked C rather than U. python-sgp4 reads the C and then overwrites it with U. Separately, its lettered-number decoder accepts letters that the rules forbid (I, O, lowercase), so two programs can disagree about what a field means.

omm.initialize assigns classification and sgp4init resets it to U; the pure-Python twoline2rv does the same while the accelerated build keeps the C. from_alpha5 accepts I0000 (as 180000), O1234, lowercase and four-character input, all excluded by Space-Track’s definition; to_alpha5(-1) returns -0001. Low severity, documented in the corpus’s upstream notes.

Cases supgp-celestrak-classification-c, alpha5-encoding-vectors.

8. Legal variations of the text formats trip strict readers

The standard allows several ways to write a date and a value; CelesTrak uses one, other producers use others. Programs that only know CelesTrak’s habits fail on perfectly valid files, and the library has no reader for the key = value format at all.

Day-of-year epochs, no fraction, trailing Z, bracketed units, comment lines, leading zeros and signed integers are all CCSDS-legal KVN; the corpus’s six derived variants show naive failing four of them, and python-sgp4 skips the format. CelesTrak’s XML validates against the SANA 2.0.0 schema set and fails the current 4.0.0 set on the version attribute alone. python-sgp4’s epoch parser is hard-coded to one strftime pattern.

Cases kvn-syntax-variants, omm-xml-schema; vectors ccsds-epoch-strings.json.

Evidence and limits

Every statement above links to a case in the test kit, where the exact web address, time and fingerprint of the data are recorded. Some things could not be tested yet, for example lettered numbers beyond A and T, because no real satellite has them; those are stated as gaps, not guessed.

Snapshot 2026-09-21; sources, hashes and per-case gaps in MANIFEST.md; library cross-check in CROSSCHECK.md. Known gaps: only letters A and T occur in real data; no positive-exponent BSTAR observed; nine-digit ids are perishable; gp-first stability is assumed and now monitored weekly by the tracker.