# Tracking model and coordinate conventions The optional tracking provider reads CelesTrak general-perturbation (GP) orbital elements, propagates them with SGP4 using WGS72 constants, and transforms each complete Cartesian state from TEME into GCRS or ITRS using Astropy. Velocity is attached as a Cartesian differential, so a rotating-frame conversion includes the frame-motion contribution. Velocity is never transformed as a position. These are ephemeris predictions from published orbital elements, not streaming telemetry or a measured satellite trajectory. Their differences from an ideal simulation do not establish the accuracy of either trajectory. No learned model or fitted synchronization offset is used to reduce the reported differences. ## Data and time policy The provider defaults to CelesTrak's JSON GP representation, which supports catalog identifiers beyond the legacy five-digit TLE limit. TLE input remains available. TLE checksums, identifiers, format and OMM conventions are validated. The exact response, source URL, retrieval UTC, and SHA-256 hash are cached. The default element-age limit is seven days in either direction from the requested propagation epoch. This is a configurable operational cutoff, not a seven-day guarantee of orbital accuracy. A labeled `allow_stale` override permits retrospective sensitivity tests. HTTP failures do not generate synthetic data or silently substitute stale records. Offline mode requires a valid existing cache. Records are refreshed after two hours by default; the live runner controls its own iteration times and does not need a network request for every snapshot. Every epoch must have an explicit UTC interpretation. In SR the worldline argument uses uniform TAI/TT-rate seconds; in static GR it uses TCG coordinate seconds. Astropy constructs time increments separately and both Julian-date components are passed to SGP4, avoiding the approximately 20-microsecond steps introduced by collapsing modern Julian dates into one 64-bit float. This improves numerical time resolution; it does not confer nanosecond physical accuracy on GP data. Earth-orientation data are required for frame conversions. The provider records the table, prediction statuses and permitted degradation. Dates outside the IERS table are rejected unless `allow_degraded_iers` is explicitly selected. Live mode allows Astropy to obtain current IERS data; offline tests disable downloads. When `comparison.iers_file` names a local IERS-A table, all three CLI runners pin that table during their calculations and disable automatic Earth-orientation table replacement for that context. Tracking coverage checks still apply. Leap-second data are checked separately from Earth orientation. Before any propagation age calculation or UTC/TAI/TCG conversion, the provider explicitly loads an IERS/ntp leap-second list and updates ERFA's conversion table. Online runs use Astropy's automatic refresh under `tracking.iers_auto_download`. `tracking.offline: true` disables both orbital-element and timing-data downloads. This refresh updates data only; Python 3.8 and the pinned packages are retained. If automatic lookup raises an error or returns a table that does not cover the requested epoch, the provider tries `timing_data/Leap_Second.dat`, resolved relative to the installed module rather than the working directory. That file is an unmodified IERS Bulletin C72 list, valid before 2027-06-28, with a source URL and raw SHA256 recorded in `timing_data/PROVENANCE.json`. The provider uses it only when it covers the requested epoch. It reports the fallback once and records `fallback_used`, `fallback_reason`, `selection` and `refresh_attempt` in the tracking provenance. A covered in-memory table can be reused on subsequent live updates without repeating the message. Astropy 5.2 may hide individual URL failures while returning an expired table. The recorded outcome therefore distinguishes an expired return from an exception; it does not claim to identify a particular certificate, proxy or server failure. Superseded stale warnings are retained in JSON diagnostics instead of being presented as unresolved failures after a valid replacement. Other warnings remain visible. Astropy's first lazy UTC conversion is restricted to the verified local state, so it does not perform a second unnecessary network refresh or undo the selected table's verified expiry. Missing, corrupt or expired fallback data never silently extend coverage. Satellite GP and Earth-orientation refresh remain independent and obey their existing online/offline policies. For a network-independent run, set `tracking.leap_second_file` to the local path of a current official IERS `Leap_Second.dat` or ntp `leap-seconds.list` file. In CLI configuration files, this path and `tracking.cache_dir` are relative to the configuration file's directory, as is `comparison.iers_file`. Absolute paths are retained. An omitted cache path becomes `tracking_cache` in the configuration directory. These rules apply to the scenario runner, matched-state comparison runner and legacy standalone comparison CLI. Direct Python provider calls receive paths as supplied; use `execution_config.read_config()` when loading the same portable JSON from an application. CLI output paths remain relative to the working directory. The automatic bundled leap-list fallback above continues to use the installed module's directory. The supplied Windows batch launchers change to the package directory before starting Python; direct Python invocations retain the caller's working directory. Download the IERS file from https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat if the cached or bundled list is too old. An explicit local file is used as supplied and never silently replaced by another list. The requested UTC epoch must precede the list's expiration date; an uncovered epoch stops with a refresh instruction. A covered historical epoch can still be replayed when the list is expired today, with that fact recorded and warnings retained. `allow_degraded_iers` does not waive leap-second coverage. A propagated worldline cannot silently continue beyond the verified leap-second validity limit. The provider records `leap_seconds` provenance separately from `earth_orientation`: source, expiration, historical coverage, whether the list is expired today, effective table hashes, ERFA updates, and the applied TAI−UTC offset. Refresh failures never invent an unannounced leap second. ## Optional static-GR chart adapter GCRS Cartesian coordinates are approximately identified with Earth-monopole isotropic coordinates. The radial mapping to the Schwarzschild areal chart is r = rho * (1 + GM / (2*c*c*rho))**2. The spatial direction is retained and velocities use the full Jacobian of that map. For Earth, the leading areal-minus-isotropic radial difference is GM/c^2, approximately 4.4 millimetres. The algebraic radial transformation is exact for the Schwarzschild metric; identifying full GCRS coordinates with that monopole chart is an approximation. TCG is identified with the isolated-monopole Schwarzschild coordinate time. The exact conventional rate relation `dTT/dTCG = 1 - L_G`, with `L_G = 6.969290134e-10`, is applied using Astropy's explicit `TimeDelta(scale="tcg")`; velocities receive the same chain-rule factor before the spatial Jacobian. Multipoles, external tidal fields and a full relativistic ephemeris-to-metric conversion are excluded. SGP4 trajectories are prescribed worldlines in the photon model, not Schwarzschild geodesic orbits. Static-GR photon propagation does not turn SGP4 into a GR orbit integrator. ## Numerical validation The optional tracking tests cover the published Vallado verification case at zero and 360 minutes, TLE/OMM consistency, sub-microsecond propagation steps, position/velocity transformations and inverse transformations, the rotational velocity of an Earth-fixed point, the radial-coordinate Jacobian, and explicit handling of unavailable, invalid and stale records. They also check that comparisons reject mixed frames or epochs. The reference TLEs are historical numerical test data and never serve as an automatic live-mode fallback. Numerical agreement with the reference propagator validates the implementation of that propagator, not satellite prediction error. The original 26 tracking tests are retained, using SGP4 2.24. Current full and tracking-only logs are in `validation_logs`. The Python 3.8 Linux Astropy table extension emitted a NumPy structure-size runtime warning; numerical checks passed and the warning is retained in the log. The deliberately out-of-range IERS test also emits ERFA's expected dubious-year warning. The September 16 execution-audit corrections add 11 regression cases for explicit local lists, online refresh policy, expiry rejection, covered historical replay, offline download prevention, timing provenance, and propagation beyond a list's validity, leap-second-day offsets, and pre-1972 UTC drift. All 37 tracking tests passed on Python 3.8.20 with Astropy 5.2.2 and Python 3.12.14 with Astropy 7.1.1. The automatic-fallback follow-up adds 11 further tracking regressions; all 48 pass on both interpreters. The current full-suite log has 392 passing tests. The synthetic list expirations used in policy tests are test fixtures only and are never an operational timing-data source. The `tracking_fixtures/capture_20260916` directory contains actual HTTP GP responses retrieved on September 16, 2026 for NORAD 25544 (ISS) and 42829 (TECHNOSAT), their unmodified provenance, and a GCRS replay of those two cached objects. The simultaneous attempt to obtain NORAD 44479 received HTTP proxy 403; the three-object provider correctly failed. A subsequent, explicitly selected public example obtained NORAD 20580 (HST), and the three-object ISS/TECHNOSAT/HST provider completed at 2026-09-16T17:13:22.933650Z. All three element ages were below 0.60 day and IERS data were in-range predictions. The earlier failure is preserved; no automatic substitute satellite was selected. The original upload's comment calling 42829 Galileo was incorrect; object names in the new output come from the published response. The three-object data were also exercised through the simulation engine with an explicit zero boost: the SR collection was Earth-blocked and static-GR propagation reported occultation, each giving zero accepted pairs. These are successful integration checks with honest unavailable-link outcomes, not an optical-link demonstration. The comparison module is also an executable command: ```bat python real_time_satellite_comparison.py --config config_tracking_replay.json --steps 1 ``` It compares configured ideal circular trajectories with same-epoch GCRS predictions, without fitting the initial conditions. `--duration` requests future predictions; it does not wait in real time. Use the top-level `config_tracking_replay.json` for the supplied fixed-epoch example. Nested configuration records in `tracking_fixtures/` preserve the original capture and may contain historical paths; they are evidence, not current CLI presets. To recreate a particular historical timestamp, use the top-level preset with `--epoch` here, or `--epoch-utc` in the scenario runner, together with elements and timing tables that cover that timestamp. ## Primary sources - Astropy satellite-state construction and TEME frame conventions: https://docs.astropy.org/en/stable/coordinates/satellites.html - SGP4 package documentation, WGS72 convention, split Julian dates, and stated limitations of TLE predictions: https://pypi.org/project/sgp4/2.24/ - Vallado et al., *Revisiting Spacetrack Report #3*, AIAA 2006-6753, reference implementation and verification data: https://celestrak.org/publications/AIAA/2006-6753/ - CelesTrak GP representations and omitted default OMM metadata: https://celestrak.org/NORAD/documentation/gp-data-formats.php - IERS Conventions 2010, Chapter 10, TCG/TT relation and geocentric coordinates: https://iers-conventions.obspm.fr/content/chapter10/tn36_c10.pdf - Astropy explicit time-interval scales: https://docs.astropy.org/en/stable/api/astropy.time.TimeDelta.html - Astropy leap-second file selection, validity and ERFA updates: https://docs.astropy.org/en/stable/api/astropy.utils.iers.LeapSeconds.html