Background
Acquisition v2.5+ added a secondary_code_phase::Union{Int,Nothing} field to AcquisitionResults — the secondary-code chip the acquisition's coherent integration window aligned to, recovered by its secondary-code rotation search. It is nothing when no rotation search ran (no secondary code, use_secondary_code = false, single-period coherent integration, or no detected peak).
This only ever works for short secondary codes. Recovering a secondary-code phase in acquisition means coherently integrating over (at least) one full secondary period. That is fine for a 10 ms code like GPS L5I's NH10, but infeasible for a long overlay like GPS L1C-P's 1800-chip / 18 s code — integrating that long in the acquisition search is far too resource-intensive. So in practice acq.secondary_code_phase is populated for short secondary codes (e.g. L5I) and stays nothing for long ones (e.g. L1C-P), which must continue to self-sync inside the tracker via its own rotation search.
Tracking now recovers the secondary-code phase itself via the unified rotation search (detect_bit_or_secondary_code_sync → _secondary_code_search), but only after buffering one full secondary-code period (~10 ms for L5I; ~18 s for L1C-P).
Proposal
When acq.secondary_code_phase !== nothing (i.e. a short secondary code the acquisition stage could resolve), seed the acquisition→tracking handoff so the tracker starts already secondary-synced instead of re-running the search.
The handoff in ext/TrackingAcquisitionExt/TrackingAcquisitionExt.jl currently reads only acq.code_phase and acq.carrier_doppler:
TrackedSat(acq::AcquisitionResults; …)
add_satellite! / add_satellite(track_state, acq; …)
TrackState(acqs::AbstractVector{<:AcquisitionResults}; …)
These should also consume acq.secondary_code_phase and:
- pre-populate the relevant signal's
BitBuffer with found = true, secondary_phase, and polarity, and
- anchor
TrackedSat.code_phase into the widened (primary × secondary) wrap window (cf. _snap_code_phase_from_synced_signal / current_code_wrap in src/sat_state.jl).
Scope / benefit
- In scope: short secondary codes (e.g. GPS L5I, NH10). The tracker starts with the correct secondary-code alignment immediately at handoff, skipping its own ~10 ms re-search and avoiding the brief window where
code_phase is anchored only to the primary period. Modest in wall-clock terms, but it removes redundant work and gives correct anchoring from sample 0.
- Out of scope: long overlays (e.g. GPS L1C-P, 18 s). Acquisition cannot recover these (integration too long), so
secondary_code_phase is nothing and the tracker's own rotation search remains the only path. No change to that flow.
Open questions / considerations
- Convention alignment. Tracking's
SyncResult.phase / BitBuffer.secondary_phase is the upcoming integration's secondary chip at the moment of sync. Acquisition's secondary_code_phase is "the chip the start of the coherent window aligned to." These must be reconciled (likely an offset by the number of code periods between the acquisition window start and the tracking start sample). Add a test that acquires + tracks a synthetic L5I signal and checks the seeded phase equals what the tracker recovers on its own.
- Polarity. Acquisition does not expose the locked polarity, so
BitBuffer.polarity (±1) would still need recovery for data-bearing signals — and the short-secondary-code signals this applies to (e.g. L5I) are data-bearing. Either recover polarity from the acquisition correlation sign, or leave it to be resolved on the first decoded data bit.
nothing handling. Fall back to today's behavior (track until self-sync) when secondary_code_phase === nothing — this is the normal case for long-overlay signals like L1C-P.
- Multi-signal sats. Seed only the signal the acquisition result corresponds to.
References
Background
Acquisition v2.5+ added a
secondary_code_phase::Union{Int,Nothing}field toAcquisitionResults— the secondary-code chip the acquisition's coherent integration window aligned to, recovered by its secondary-code rotation search. It isnothingwhen no rotation search ran (no secondary code,use_secondary_code = false, single-period coherent integration, or no detected peak).This only ever works for short secondary codes. Recovering a secondary-code phase in acquisition means coherently integrating over (at least) one full secondary period. That is fine for a 10 ms code like GPS L5I's NH10, but infeasible for a long overlay like GPS L1C-P's 1800-chip / 18 s code — integrating that long in the acquisition search is far too resource-intensive. So in practice
acq.secondary_code_phaseis populated for short secondary codes (e.g. L5I) and staysnothingfor long ones (e.g. L1C-P), which must continue to self-sync inside the tracker via its own rotation search.Tracking now recovers the secondary-code phase itself via the unified rotation search (
detect_bit_or_secondary_code_sync→_secondary_code_search), but only after buffering one full secondary-code period (~10 ms for L5I; ~18 s for L1C-P).Proposal
When
acq.secondary_code_phase !== nothing(i.e. a short secondary code the acquisition stage could resolve), seed the acquisition→tracking handoff so the tracker starts already secondary-synced instead of re-running the search.The handoff in
ext/TrackingAcquisitionExt/TrackingAcquisitionExt.jlcurrently reads onlyacq.code_phaseandacq.carrier_doppler:TrackedSat(acq::AcquisitionResults; …)add_satellite!/add_satellite(track_state, acq; …)TrackState(acqs::AbstractVector{<:AcquisitionResults}; …)These should also consume
acq.secondary_code_phaseand:BitBufferwithfound = true,secondary_phase, andpolarity, andTrackedSat.code_phaseinto the widened (primary × secondary) wrap window (cf._snap_code_phase_from_synced_signal/current_code_wrapinsrc/sat_state.jl).Scope / benefit
code_phaseis anchored only to the primary period. Modest in wall-clock terms, but it removes redundant work and gives correct anchoring from sample 0.secondary_code_phaseisnothingand the tracker's own rotation search remains the only path. No change to that flow.Open questions / considerations
SyncResult.phase/BitBuffer.secondary_phaseis the upcoming integration's secondary chip at the moment of sync. Acquisition'ssecondary_code_phaseis "the chip the start of the coherent window aligned to." These must be reconciled (likely an offset by the number of code periods between the acquisition window start and the tracking start sample). Add a test that acquires + tracks a synthetic L5I signal and checks the seeded phase equals what the tracker recovers on its own.BitBuffer.polarity(±1) would still need recovery for data-bearing signals — and the short-secondary-code signals this applies to (e.g. L5I) are data-bearing. Either recover polarity from the acquisition correlation sign, or leave it to be resolved on the first decoded data bit.nothinghandling. Fall back to today's behavior (track until self-sync) whensecondary_code_phase === nothing— this is the normal case for long-overlay signals like L1C-P.References
tracking-fix-hard-coded-secondary-phase, follow-up to GPS L5I secondary-code (NH10) sync unreliable: exact-match requirement and phase hard-coded to 0 #118).BitBufferfieldsfound/secondary_phase/polarity—src/bit_buffer.jl._snap_code_phase_from_synced_signal,current_code_wrap—src/sat_state.jl.