Findings

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.

Runner results for three adapters across the sixteen cases of corpus v0.1.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), fail, skip format unsupported by the parser.

Three parsers, sixteen 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.

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; python-sgp4 2.27 through sgp4.omm and twoline2rv, no KVN reader.

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 4 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 pass pass pass
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 4 failing pass-tolerance
kvn-syntax-variants pass fail 4 failing skip

Naive fails 14 of 16 cases; python-sgp4 fails 6, with 19 failing items in total; the reference passes all 16 exactly.

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 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; we tested the proposed fix and reported back.

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 (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 is awaiting review.

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 and awaiting review. 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 (304/304 records); 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, 304/304), 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 as NDM/XML 2.0 and fails the current 3.0/4.0.0 schema 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.