Skip to content

fix(gpscnav): don't require T_GD for positioning readiness#68

Merged
siebc merged 1 commit into
masterfrom
sc/cnav-positioning-without-tgd
Jul 3, 2026
Merged

fix(gpscnav): don't require T_GD for positioning readiness#68
siebc merged 1 commit into
masterfrom
sc/cnav-positioning-without-tgd

Conversation

@giove-a

@giove-a giove-a commented Jul 3, 2026

Copy link
Copy Markdown

Summary

is_clock_correction_decoded(::GPSCNAVData) gated positioning readiness on
T_GD. Since T_GD is broadcast only in CNAV message type 30 — separately
from, and far less often than, the SV clock — a receiver could fully decode a
satellite's ephemeris and clock yet never report it usable for positioning. This
drops the T_GD requirement (the SV clock polynomial is sufficient for a fix);
T_GD is still applied downstream when present. Affects GPS L2C and L5I
(shared CNAV core).

Root cause

is_decoding_completed_for_positioningis_clock_correction_decoded required
t_0c, a_f0, a_f1, a_f2 and T_GD. The clock polynomial rides in the shared
clock block of any of MT30–37 (parse_clock_block), but T_GD is set only by
parse_mt30. Positioning readiness was therefore silently coupled to MT30 being
in the broadcast schedule.

On a CNAV stream that carries the clock via MT31–37 without MT30, every message
decodes and the ephemeris + clock complete, but positioning never validates — and
because validate_data is gated on the same check, raw_data is never promoted
to data (and the streaming counter never re-armed), so nothing downstream works
either.

Why T_GD must not gate a fix (ICD)

T_GD is a ~metre-level inter-signal (group-delay) correction, not required for a
position solution, and it is structurally infrequent. Maximum broadcast intervals:

data message type(s) L2C (IS-GPS-200 Tbl 30-XII) L5 (IS-GPS-705J Tbl 20-XII)
ephemeris 10 & 11 48 s 24 s
clock any of 30–37 48 s 24 s
T_GD (in ISC/IONO) 30 only 288 s 144 s

So T_GD is guaranteed only ~6× less often than the clock/ephemeris — requiring
it can stall a fix for up to 288 s (L2C) / 144 s (L5) on a fully spec-compliant
satellite.

For reference within the codebase: GPS L1C-D (CNAV-2) already does not gate
positioning on T_GD/ISC (only TOI + subframe-2 CED), and LNAV is unaffected
(T_GD rides in subframe 1 alongside the clock). This change brings CNAV in line.

The fix

Remove !isnothing(data.T_GD) from is_clock_correction_decoded; require only
the SV clock polynomial (t_0c, a_f0, a_f1, a_f2). One function, plus a comment
documenting the ICD rationale, and a note on the GPSCNAVData docstring that the
MT30-only fields (T_GD, ISCs, Klobuchar α/β) may be nothing even once
positioning is ready.

Validation (real + simulated captures)

  • TEXCUP (UT Austin, real GPS, NTLAB front-end): before — L2C stalls on
    satellites whose MT30 lags; after — L2C produces a continuous PVT that agrees
    with the independent L1 C/A solution to < 1 m (and the L2C-decoded orbit
    matches the L1 broadcast to 0.82 m).
  • Fraunhofer Flexiband (Spirent simulation whose CNAV schedule omits MT30):
    before — all 10 L2C and all 10 L5I satellites decode a full ephemeris but 0
    position (T_GD never arrives); after — they validate and position, all three
    bands (L1 / L2C / L5I) landing on the Fraunhofer IIS site to ~0.2 m.

Testing

  • Pkg.test()2446/2446 pass, including Aqua.test_all.
  • JuliaFormatter 2.8.5 (repo config) — clean, no diff.
  • Existing CNAV tests unaffected: full-decode / decode_once fixtures carry MT30,
    and the !is_decoding_completed_for_positioning assertion in the L5I tests is
    gated by the ephemeris check (MT11 absent), not the clock check.

Related (not in this PR)

On MT30-less streams PositionVelocityTime still needs the MT30-only corrections
T_GD, ISC_L2C / ISC_L5I5, and the Klobuchar α/β, and throws
+(::Float64, ::Nothing) when they're absent. A companion PVT change to treat
these as absent-→-zero (and skip iono when α/β are missing) would let L2C/L5
position end-to-end without a workaround.

🤖 Generated with Claude Code

`is_clock_correction_decoded` gated `is_decoding_completed_for_positioning`
on T_GD, but CNAV broadcasts T_GD only in message type 30. On streams that
carry the SV clock via MT31-37 without MT30, every message decodes and the
ephemeris + clock polynomial complete, yet positioning never validates
(and, downstream, raw_data is never promoted to data / the streaming
counter never re-armed).

Per IS-GPS-200 Table 30-XII (L2C) and IS-GPS-705J Table 20-XII (L5), T_GD
(MT30 only) has a maximum broadcast interval of 288 s (L2C) / 144 s (L5),
versus 48 s / 24 s for the clock (any of MT30-37) and the ephemeris
(MT10/11) — ~6x less often. Gating a fix on T_GD can therefore stall
positioning for minutes on a fully spec-compliant SV.

Require only the SV clock polynomial (t_0c, a_f0/a_f1/a_f2); T_GD is a
~metre-level inter-signal correction applied downstream when present.
Affects both GPS L2C and L5I (shared CNAV core).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.15%. Comparing base (7610c18) to head (98c5050).

Additional details and impacted files
@@           Coverage Diff           @@
##           master      #68   +/-   ##
=======================================
  Coverage   98.15%   98.15%           
=======================================
  Files          14       14           
  Lines        1515     1515           
=======================================
  Hits         1487     1487           
  Misses         28       28           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@siebc
siebc merged commit b4a2bd5 into master Jul 3, 2026
9 checks passed
@siebc
siebc deleted the sc/cnav-positioning-without-tgd branch July 4, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants