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
- No dependencies, and no network. GPKit reads what it is given.K-02K-03
- No Foundation in the core. One file bridges to
Date, and CI checks that the rest compiles without Foundation. K-14 - Not watchOS, in 1.0.0. GPKit 1.0.0 counts microseconds in
Int, which is 32 bits wide on most watches, and it doesn't compile there.K-05 Onmain, not yet in a release, they're counted inInt64and GPKit builds for watchOS 9 and later.K-91
Testing choices
- Against the original, compiled as it is, with the compiler told not to fuse a multiplication and an addition. Clang does that by default on arm64 and Swift never does, so without it the two would differ for a reason that is neither's arithmetic.K-40K-41b
- Against independent references: Skyfield for passes and positions, python-sgp4 over real catalogs.K-42K-43
- Against what providers actually serve: gpconf, a corpus built from real CelesTrak data with every expected answer traced to its source.G-04K-82
- The README is tested: its examples are compiled and held to the words it prints.K-44
Sources
K-24Catalog 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.G-10On 11 July 2026 the catalog assigned number 100000, to SARAMAGO. Source: gpconf:README.md:98-99;docs/BRAND.md:59.G-12Space-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.K-27Values are ExactDecimal. Applied defaults are recorded indefaulted. Source: GPKit:ExactDecimal.swift:1-6;ElementSet.swift:54-76.K-28Epochs are integers to the attosecond; a leap second at 23:59:60 is kept. Source: GPKit:Epoch.swift:1-7.K-37The 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.K-12One record type whatever the format; "the TLE is a way in, not the model". Source: GPKit:ElementSet.swift:3-4;README.md:64-65.K-25Refusals 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.K-26A file cut short is refused whole. Source: GPKit:README.md:85-87; testOMMTests.swift:120.K-22OMM 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.K-21The checksum is verified by default;.ignoreskips only the checksum. Source: GPKit:TLE.swift:8-34;README.md:93-96; testTLETests.swift:77-99.K-29The 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.K-33Error codes map toPropagationFailure.Kind. Source: GPKit:Propagator.swift:33-48,115-122.K-30SGP4 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.K-40SGP4 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.K-36A Propagator is a value and can be shared between tasks. Source: GPKit:Propagator.swift:71-73; testSGP4Tests.swift:170.K-54A search stops at the first failure and keeps what it found. Source: GPKit:Passes.swift:29-31,164-175; testsPassTests.swift:313-367.K-02A 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.K-03GPKit makes no network request of its own. Source: GPKit:README.md:14-15.K-14No 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.K-05No watchOS, because Int is 32 bits on most watches. Source: GPKit:Package.swift:7-8.K-91watchOS 9 and later: GPKit builds for watchOS (arm64 and arm64_32), in CI on every push; what doesn't fit a 32-bitIntis counted inInt64. Source: GPKit main 11b7ab4 (PR #25, 2026-11-07):CHANGELOG.md:61,:65-68;README.md:19-21;.github/workflows/ci.yml:208.K-41bThe 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.K-42Skyfield 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; testPassTests.swift:168-248.K-43python-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.G-04gpconf 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.K-82CI 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.K-4468 tests in 14 suites; the README examples are compiled by tests. Source: GPKit: grep of@Testand@Suite;ReadmeTests.swift:5-6.