Constant Chain of Custody
This is the single most critical design constraint in pg_orrery. Get it wrong and positions silently drift by kilometers. There is no runtime check that can detect this class of error after the fact.
The problem
Section titled “The problem”Two-Line Elements are not raw orbital measurements. They are mean elements produced by a differential correction process that fits observed positions against an SGP4 propagator running with a specific set of geopotential constants --- WGS-72. The mean elements absorb geodetic model biases: the eccentricity, inclination, and mean motion encode corrections that only make physical sense when propagated with the same constants used to generate them.
Substituting WGS-84 constants into the propagator does not “upgrade” accuracy. It breaks the internal consistency of the element set. The resulting position error can exceed the natural prediction error of the TLE by an order of magnitude.
The rules
Section titled “The rules”Four rules govern constant usage across the entire codebase. No exceptions.
Rule 1: WGS-72 for SGP4/SDP4 propagation
Section titled “Rule 1: WGS-72 for SGP4/SDP4 propagation”All propagation uses WGS-72 constants: , , , , , . These flow through the vendored src/sgp4/norad_in.h defines and are never overridden. The functions SGP4_init(), SGP4(), SDP4_init(), and SDP4() operate entirely in the WGS-72 domain.
Rule 2: WGS-84 for coordinate output
Section titled “Rule 2: WGS-84 for coordinate output”Geodetic latitude, longitude, and altitude use the WGS-84 ellipsoid ( km, ). This is the modern standard for ground-station positioning, GPS receivers, and mapping services. The conversion from ECEF to geodetic in coord_funcs.c:ecef_to_geodetic() uses WGS-84.
Rule 3: Reduced TEME nutation
Section titled “Rule 3: Reduced TEME nutation”The SGP4 output frame (True Equator, Mean Equinox) uses only 4 of the 106 IAU-80 nutation terms. Applying the full nutation model would “correct” for effects that SGP4 already accounts for internally, introducing error rather than removing it.
Rule 4: No other combination is valid
Section titled “Rule 4: No other combination is valid”WGS-72 for propagation, WGS-84 for output. Perigee and apogee altitudes use WGS-72 because they derive from mean elements. Geodetic altitude uses WGS-84 because it converts a physical position. There is no scenario where mixing these is correct.
Constant inventory
Section titled “Constant inventory”The complete set of constants, with provenance and location in both pg_orrery and the vendored SGP4 code.
WGS-72 constants (propagation domain)
Section titled “WGS-72 constants (propagation domain)”Source: Hoots & Roehrich, “Models for Propagation of NORAD Element Sets,” Spacetrack Report No. 3, 1980.
| Constant | Symbol | Value | types.h | norad_in.h |
|---|---|---|---|---|
| Gravitational parameter | WGS72_MU | (implicit in ) | ||
| Equatorial radius | WGS72_AE | earth_radius_in_km | ||
| Zonal harmonic | WGS72_J2 | xj2 | ||
| Zonal harmonic | WGS72_J3 | xj3 | ||
| Zonal harmonic | WGS72_J4 | xj4 | ||
| Derived rate constant | WGS72_KE | xke |
The rate constant is derived from and :
The factor of 60 converts from seconds to minutes, matching the SGP4 convention of radians per minute for mean motion.
WGS-84 constants (output domain)
Section titled “WGS-84 constants (output domain)”Source: NIMA TR8350.2, “Department of Defense World Geodetic System 1984.”
| Constant | Symbol | Value | types.h |
|---|---|---|---|
| Equatorial radius | WGS84_A | ||
| Flattening | WGS84_F | ||
| Eccentricity squared | WGS84_E2 |
Why two copies of AE?
Section titled “Why two copies of AE?”types.h carries a parallel copy of the WGS-72 constants even though the vendored SGP4 code defines them in norad_in.h. This is intentional.
types.h is the single header for all pg_orrery C sources. norad_in.h is an internal SGP4 header in src/sgp4/ not meant for external consumers. The GiST index (gist_tle.c) and TLE accessor functions (tle_type.c) need and without pulling in sat_code internals. The values must be identical.
The perigee and apogee altitude computations derive from mean elements:
These must use WGS-72 (6378.135 km), not WGS-84 (6378.137 km), because is a mean motion fitted against the WGS-72 geopotential. Using the wrong radius shifts every altitude by 2 meters. The error compounds in GiST index operations where altitude-band overlap determines whether two orbits are candidates for conjunction screening.
Where the boundary lives
Section titled “Where the boundary lives”The WGS-72/WGS-84 boundary is crossed in exactly two places in the codebase:
coord_funcs.c:ecef_to_geodetic() converts ECEF Cartesian coordinates (derived from WGS-72 propagation through GMST rotation) to geodetic latitude, longitude, and altitude on the WGS-84 ellipsoid. This is the correct boundary --- the ECEF position is a physical location in space, and WGS-84 is the standard for expressing that location as geodetic coordinates.
coord_funcs.c:observer_to_ecef() converts a ground station’s geodetic coordinates (on WGS-84, as entered by the user) to ECEF Cartesian for the topocentric transform. The observer’s position is a real-world location defined in WGS-84; converting it to ECEF puts it in the same Cartesian frame as the satellite.
Everything upstream of these functions operates in the WGS-72 domain. Everything downstream operates on physical positions that have already been converted. The boundary is narrow, explicit, and documented in the source comments.
The GMST question
Section titled “The GMST question”The GMST computation uses the IAU 1982 formula (Vallado Eq. 3-47):
where , and the result is in seconds of time, converted to radians by multiplying by and normalized to .
pg_orrery deliberately does not use a higher-precision GMST model (e.g., IAU 2000A). The SGP4 output is only accurate to the precision of its own GMST model. Applying a more precise rotation would not improve the final position and could introduce a systematic offset between the propagated TEME position and the Earth-fixed frame.
This is the constant chain of custody in action: match the precision of the input, not the precision available in the literature.
Rules 6-8: DE Ephemeris Pipeline (v0.3.0)
Section titled “Rules 6-8: DE Ephemeris Pipeline (v0.3.0)”The v0.3.0 DE integration adds three rules to the chain of custody.
Rule 6: ICRS-to-ecliptic frame rotation at the provider boundary
Section titled “Rule 6: ICRS-to-ecliptic frame rotation at the provider boundary”DE ephemerides return positions in the ICRS equatorial frame. The observation pipeline expects ecliptic J2000. The conversion uses equatorial_to_ecliptic() from astro_math.h, applied in eph_provider.c before the position enters the shared observation pipeline. This rotation is applied to both the target body and Earth.
Rule 7: Same-provider consistency
Section titled “Rule 7: Same-provider consistency”Both the target body and Earth must come from the same provider in any geocentric computation. If DE succeeds for the target but fails for Earth (or vice versa), the entire computation falls back to VSOP87. This prevents mixing DE Mars with VSOP87 Earth, which would introduce a frame-dependent systematic error at the arcsecond level.
/* From de_funcs.c: both positions or neither */if (eph_de_planet(body_id, jd, target_xyz) && eph_de_earth(jd, earth_xyz)){ /* Use DE positions for both */}else{ /* Fall back to VSOP87 for both */}Rule 8: AU consistency verification
Section titled “Rule 8: AU consistency verification”The DE header contains an AU value (in km). At init time, eph_provider.c verifies this matches pg_orrery’s compiled-in AU_KM constant (149597870.7 km, IAU 2012). A mismatch would corrupt every distance calculation. If they disagree, the DE file is rejected and fallback to VSOP87 activates with a log message.
Verification
Section titled “Verification”The chain of custody is verified through the Vallado 518 test vectors --- 518 reference propagations across a range of orbit types (LEO, MEO, GEO, deep-space, high-eccentricity). Every test vector must match to machine epsilon before any other development proceeds.
If a code change causes even one vector to drift, the constant chain has been broken somewhere. The test suite is the enforcement mechanism for the design constraint.
-- Verify against Vallado test vector (ISS-like orbit)-- Expected: position match within 1e-8 kmSELECT eci_x(sgp4_propagate(tle, epoch + interval '720 minutes'))FROM vallado_test_vectorsWHERE norad_id = 25544;