How GPKit is different

Every library makes choices. These are GPKit's, and why.

Data choices

A catalog number is an Int, everywhere

Alpha-5 exists only in a TLE's five columns. GPKit decodes it on the way in and never keeps it. Everywhere else a catalog number is an Int, from 0 to 999,999,999. No number is ever routed through five characters.K-24

Our reasoning (GPKit doesn't state one): the catalog passed 99,999 on 11 July 2026, and OMM carries numbers of up to nine digits.G-10G-12 A five-character field can't hold every number that's already in use.

Values are the source's digits

An element value is an ExactDecimal, not a Double.K-27

Why: reading a number into a Double changes it in the last place. Keeping the digits means the same number from a CSV, a JSON file and a TLE column compares equal. It also means a value can be written back at any resolution without ever having passed through binary.K-27

An epoch is integers

A date, a time of day, and the fraction of a second in attoseconds. A TLE's eight-decimal day is an exact number of microseconds, and a leap second is kept.K-28

Why: an epoch is never kept as a Double of days or seconds, so a TLE's eight-decimal day and an OMM's written fraction both arrive unchanged.K-28 And the epoch is where every propagation starts: GPKit starts from the element set's own epoch, exactly.K-37

One record, whatever the format

TLE, OMM CSV, JSON, XML and KVN are all read into one ElementSet, whose properties are the OMM's keywords. The TLE is a way in, not the model.K-12

Our reasoning (GPKit doesn't state one): a program that works with one record type doesn't need to know which format arrived.

Reading choices

Refuse, don't guess

A record that can't be read as it stands comes back as a Refusal: what was wrong, the field, the text and where. The records around it are still read.K-25

Why, in the README's words: "GPKit drops nothing without a word and guesses nothing."K-25

A file cut short is refused whole

Why: so part of a catalog is never loaded as if it were all of it.K-26

Defaults are visible

Where OMM metadata is missing (as in CelesTrak's CSV and JSON, and every TLE), GPKit fills in EARTH, TEME, UTC and SGP4, and defaulted lists exactly which keywords it filled.K-27K-22

Why: each default is a decision made for the reader, so it's written down and can be seen.K-27

The checksum is on by default

checksum: .ignore exists for sources known to write bad check digits, and every other check is still made.K-21

Why: without the checksum, an altered line gives no sign of it. A digit changed elsewhere is read as the value it now spells.K-21

What a TLE can't carry is refused

The writer refuses a catalog number above 339999, an epoch outside 1957–2056, or a value wider than its columns. It never approximates.K-29

Form, not physics

Reading checks that a record is well formed, not that its orbit is possible: an eccentricity of 2 is read. Whether it can be propagated is SGP4's question, and SGP4 says so with a PropagationFailure.K-22K-33

Propagation choices

A transcription, not a rewrite

The SGP4 port keeps the C++'s names, its statement order, and every expression's order of operations.K-30

Why: so the Swift computes as the C++ does, and the tests can compile the C++ itself and compare the two.K-30 They agree within 1 micrometre at all 2,349 steps of the verification cases.K-40

A propagator is a value

Making one does the initialisation. Every call after that is independent, so one can be shared between tasks.K-36

Passes keep what they found

If a satellite decays partway through a search, the passes found before that time are returned, and failure says why.K-54

Platform choices

Testing choices

Sources

  1. K-24 Catalog numbers are an Int; OMM NORAD_CAT_ID takes up to nine digits; Alpha-5 is refused in OMM. Source: GPKit: OMM.swift:4-17; ElementSet.swift:9-10; OMMTests.swift:58-70.
  2. G-10 On 11 July 2026 the catalog assigned number 100000, to SARAMAGO. Source: gpconf: README.md:98-99; docs/BRAND.md:59.
  3. G-12 Space-Track serves Alpha-5 and CelesTrak does not; a number above 339999 can't be written as a TLE. Source: gpconf: README.md:105-113,136-138.
  4. K-27 Values are ExactDecimal. Applied defaults are recorded in defaulted. Source: GPKit: ExactDecimal.swift:1-6; ElementSet.swift:54-76.
  5. K-28 Epochs are integers to the attosecond; a leap second at 23:59:60 is kept. Source: GPKit: Epoch.swift:1-7.
  6. K-37 The TLE epoch is kept exactly; the C++ reader's round trip moves it by up to 20 µs. Source: GPKit: Propagator.swift:91-93; README.md:237-243.
  7. K-12 One record type whatever the format; "the TLE is a way in, not the model". Source: GPKit: ElementSet.swift:3-4; README.md:64-65.
  8. K-25 Refusals carry a kind (19 kinds), a message, a field and a line or record. One bad record costs one record. Source: GPKit: Refusal.swift:7-89; README.md:81-83.
  9. K-26 A file cut short is refused whole. Source: GPKit: README.md:85-87; test OMMTests.swift:120.
  10. K-22 OMM in CSV, JSON, XML (505.0-B-3) and KVN (502.0-B-3 §7). Quirks handled as listed. DOCTYPE refused. Source: GPKit: ElementSets.swift:1-13; XML.swift:1-7; README.md:105-119.
  11. K-21 The checksum is verified by default; .ignore skips only the checksum. Source: GPKit: TLE.swift:8-34; README.md:93-96; test TLETests.swift:77-99.
  12. K-29 The TLE writer uses exact decimal rounding, Alpha-5 from 100000, and refuses numbers above 339999. Source: GPKit: TLE.swift:242-354; README.md:121-133.
  13. K-33 Error codes map to PropagationFailure.Kind. Source: GPKit: Propagator.swift:33-48,115-122.
  14. K-30 SGP4 is a port of Vallado's SGP4.cpp (CelesTrak/fundamentals-of-astrodynamics 49df0479c950), with names and order of operations kept. Source: GPKit: NOTICE:9-17; SGP4Core.swift:1-15.
  15. K-40 SGP4 vs the unmodified C++: 33 cases (24 deep space), 2,349 steps, equal error codes, agreeing within 1 micrometre (asserted). Source: GPKit: SGP4Tests.swift:74-75,102-126.
  16. K-36 A Propagator is a value and can be shared between tasks. Source: GPKit: Propagator.swift:71-73; test SGP4Tests.swift:170.
  17. K-54 A search stops at the first failure and keeps what it found. Source: GPKit: Passes.swift:29-31,164-175; tests PassTests.swift:313-367.
  18. K-02 A Swift package with no dependencies: OMM (CSV, JSON, XML, KVN) and TLE readers, a TLE writer, SGP4, passes with look angles and Doppler. Source: GPKit: README.md:17-18; Package.swift:14.
  19. K-03 GPKit makes no network request of its own. Source: GPKit: README.md:14-15.
  20. K-14 No Foundation outside Date.swift, enforced by a script in CI. Source: GPKit: Package.swift:14-15; tools/check-no-foundation.sh; ci.yml:24-31.
  21. K-05 No watchOS, because Int is 32 bits on most watches. Source: GPKit: Package.swift:7-8.
  22. K-91 watchOS 9 and later: GPKit builds for watchOS (arm64 and arm64_32), in CI on every push; what doesn't fit a 32-bit Int is counted in Int64. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07): CHANGELOG.md:61, :65-68; README.md:19-21; .github/workflows/ci.yml:208.
  23. K-41b The oracle compiles SGP4.cpp with #pragma STDC FP_CONTRACT OFF: Clang fuses multiply-adds by default on arm64, and Swift never does. Source: GPKit: Tests/SGP4Oracle/oracle.cpp:1-9.
  24. K-42 Skyfield 1.55: five public element sets over 40° N, 75° W, two days each; 49 passes; 95 rises and sets within 0.166 s; 47 culminations within 0.349 s; look-angle bounds. Source: GPKit: conformance/skyfield-2026-10-07.txt:1-19; test PassTests.swift:168-248.
  25. K-43 python-sgp4 2.27: 14,411 element sets, 86,466 states (0, 90, 720, 1,440, 4,320 and 10,080 min), largest difference 0.0024 mm, no differing error codes. Not run in CI. Source: GPKit: conformance/python-sgp4-crosscheck-2026-10-07.txt:5-10; CrossCheckTests.swift:5-14.
  26. G-04 gpconf is built from real CelesTrak data, with every expected answer traced to its source (G-01's sentence, paraphrased). Source: gpconf: docs/BRAND.md:40.
  27. K-82 CI runs gpconf's 5 offline cases; 13 need provider files that gpconf fetches and does not ship. Source: GPKit: README.md:153-155; ci.yml:33-56; tools/check_gpconf.py.
  28. K-44 68 tests in 14 suites; the README examples are compiled by tests. Source: GPKit: grep of @Test and @Suite; ReadmeTests.swift:5-6.