fix: combined pilot+data decode for quadrature signals (GPS L5, Galileo E5a)#201
Conversation
c325777 to
c35eec6
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #201 +/- ##
==========================================
- Coverage 97.87% 97.63% -0.25%
==========================================
Files 32 32
Lines 3296 3299 +3
==========================================
- Hits 3226 3221 -5
- Misses 70 78 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Benchmark Results (minimum time) — macos-14Reporting the minimum over all samples (robust to shared-runner contention), not the median. Alternative backends vs Float32 (
|
| Scenario | F32 | I16 | 1b | 2b | ×I16 | ×1b | ×2b |
|---|---|---|---|---|---|---|---|
| 4-antenna @ 5 MHz | 11.5 μs | 5.88 μs | 4.01 μs | 8.44 μs | 1.95 ✅ | 2.86 ✅ | 1.36 ✅ |
| GPS L1CA, 8 sats @ 40 MHz | 367.0 μs | 117.0 μs | 59.9 μs | 113.0 μs | 3.14 ✅ | 6.13 ✅ | 3.24 ✅ |
| GPS L1CA, 8 sats @ 5 MHz | 52.7 μs | 20.4 μs | 15.0 μs | 23.9 μs | 2.58 ✅ | 3.5 ✅ | 2.2 ✅ |
| Galileo E1B, 4 sats @ 25 MHz | 141.0 μs | 58.3 μs | 2.42 ✅ | ||||
| dynamic taps @ 5 MHz (kernel) | 6.68 μs | 2.31 μs | 1.49 μs | 2.56 μs | 2.9 ✅ | 4.48 ✅ | 2.61 ✅ |
| multi-signal N=3 @ 5 MHz | 10.4 μs | 4.91 μs | 3.64 μs | 5.98 μs | 2.12 ✅ | 2.87 ✅ | 1.74 ✅ |
Time benchmarks (base vs PR head)
Ratio = ce2e56e… / 6f589e2…: >1 means the PR is faster. ✅ ≥ 5 % faster,
| ce2e56e… | 6f589e2… | ce2e56e… / 6f589e2… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2.66 μs | 2.48 μs | 1.07 ✅ |
| downconvert and correlate/CPU/Float32 4ant | 5.06 μs | 4.86 μs | 1.04 |
| downconvert and correlate/CPU/Float64 | 2.9 μs | 2.68 μs | 1.08 ✅ |
| downconvert and correlate/CPU/Int16 | 2.63 μs | 2.62 μs | 1.01 |
| downconvert and correlate/CPU/Int16 4ant | 5.18 μs | 4.81 μs | 1.08 ✅ |
| downconvert and correlate/CPU/Int32 | 2.73 μs | 2.54 μs | 1.08 ✅ |
| fused kernel/1-ant dynamic taps | 2.74 μs | 2.65 μs | 1.03 |
| fused kernel/1-ant static taps | 2.06 μs | 2.06 μs | 1.0 |
| fused kernel/4-ant dynamic taps | 7.84 μs | 7.77 μs | 1.01 |
| fused kernel/4-ant static taps | 4.6 μs | 4.43 μs | 1.04 |
| fused tuple kernel/1-ant N=2 | 3.51 μs | 3.51 μs | 1.0 |
| fused tuple kernel/1-ant N=3 | 3.53 μs | 3.52 μs | 1.0 |
| fused tuple kernel/2-ant N=2 | 6.52 μs | 6.31 μs | 1.03 |
| fused tuple kernel/2-ant N=3 | 5.63 μs | 5.61 μs | 1.0 |
| fused tuple kernel/4-ant N=2 | 12.0 μs | 11.5 μs | 1.04 |
| fused tuple kernel/4-ant N=3 | 10.8 μs | 10.4 μs | 1.04 |
| track/1. Float32/2K – track | 2.78 μs | 2.63 μs | 1.05 ✅ |
| track/1. Float32/2K – track! | 2.93 μs | 2.73 μs | 1.07 ✅ |
| track/2. L1 8sat/5K – track | 53.2 μs | 49.2 μs | 1.08 ✅ |
| track/2. L1 8sat/5K – track! | 52.9 μs | 49.0 μs | 1.08 ✅ |
| track/2. L1 8sat/5K – track! Int16 | 20.4 μs | 20.5 μs | 0.998 |
| track/2. L1 8sat/5K – track! OneBit | 14.9 μs | 15.0 μs | 0.994 |
| track/2. L1 8sat/5K – track!-threaded | 49.7 μs | 49.5 μs | 1.01 |
| track/2. L1 8sat/5K – track-threaded | 49.9 μs | 49.8 μs | 1.0 |
| track/3. E1B 4sat/25K – track | 136.0 μs | 136.0 μs | 1.0 |
| track/3. E1B 4sat/25K – track! | 135.0 μs | 135.0 μs | 1.0 |
| track/3. E1B 4sat/25K – track! Int16 | 58.6 μs | 58.0 μs | 1.01 |
| track/3. E1B 4sat/25K – track!-threaded | 135.0 μs | 135.0 μs | 0.999 |
| track/3. E1B 4sat/25K – track-threaded | 136.0 μs | 136.0 μs | 1.0 |
| track/4. 8L1+8E1B/25K – track | 490.0 μs | 489.0 μs | 1.0 |
| track/4. 8L1+8E1B/25K – track! | 489.0 μs | 488.0 μs | 1.0 |
| track/4. 8L1+8E1B/25K – track!-threaded | 506.0 μs | 488.0 μs | 1.04 |
| track/4. 8L1+8E1B/25K – track-threaded | 490.0 μs | 489.0 μs | 1.0 |
| track/5. multi-signal N=1/5K – track | 6.9 μs | 6.37 μs | 1.08 ✅ |
| track/5. multi-signal N=1/5K – track! | 6.38 μs | 6.39 μs | 0.999 |
| track/6. multi-signal N=2/5K – track | 10.4 μs | 10.3 μs | 1.01 |
| track/6. multi-signal N=2/5K – track! | 10.5 μs | 10.4 μs | 1.01 |
| track/7. multi-signal N=3/5K – track | 12.4 μs | 11.6 μs | 1.07 ✅ |
| track/7. multi-signal N=3/5K – track! | 12.0 μs | 11.5 μs | 1.04 |
| track/8. L1CA presync 2 blk – track! | 12.4 μs | 12.3 μs | 1.0 |
| track/8. L1CA presync 20 blk – track! | 120.0 μs | 120.0 μs | 1.0 |
| track/8. L1CA synced 2 blk – track! | 12.2 μs | 12.2 μs | 1.0 |
| track/8. L1CA synced 20 blk – track! | 119.0 μs | 118.0 μs | 1.0 |
| time_to_load | 141.0 μs | 171.0 μs | 0.828 |
Memory benchmarks (base vs PR head)
Ratio = ce2e56e… / 6f589e2… (bytes allocated): >1 means the PR allocates less. ✅ ≥ 5 % less, ∞/0 mark a benchmark that drops to / picks up allocations, — means both revisions allocate nothing. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).
| ce2e56e… | 6f589e2… | ce2e56e… / 6f589e2… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| downconvert and correlate/CPU/Float32 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Float64 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| downconvert and correlate/CPU/Int16 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| downconvert and correlate/CPU/Int16 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Int32 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| fused kernel/1-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/1-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/1. Float32/2K – track | 9 allocs: 944 B | 9 allocs: 944 B | 1.0 |
| track/1. Float32/2K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/2. L1 8sat/5K – track | 10 allocs: 4656 B | 10 allocs: 4656 B | 1.0 |
| track/2. L1 8sat/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/2. L1 8sat/5K – track! Int16 | 13 allocs: 1056 B | 13 allocs: 1056 B | 1.0 |
| track/2. L1 8sat/5K – track! OneBit | 45 allocs: 2736 B | 45 allocs: 2736 B | 1.0 |
| track/2. L1 8sat/5K – track!-threaded | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/2. L1 8sat/5K – track-threaded | 10 allocs: 4656 B | 10 allocs: 4656 B | 1.0 |
| track/3. E1B 4sat/25K – track | 10 allocs: 3056 B | 10 allocs: 3056 B | 1.0 |
| track/3. E1B 4sat/25K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/3. E1B 4sat/25K – track! Int16 | 5 allocs: 416 B | 5 allocs: 416 B | 1.0 |
| track/3. E1B 4sat/25K – track!-threaded | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/3. E1B 4sat/25K – track-threaded | 10 allocs: 3056 B | 10 allocs: 3056 B | 1.0 |
| track/4. 8L1+8E1B/25K – track | 26 allocs: 10464 B | 26 allocs: 10464 B | 1.0 |
| track/4. 8L1+8E1B/25K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/4. 8L1+8E1B/25K – track!-threaded | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/4. 8L1+8E1B/25K – track-threaded | 26 allocs: 10464 B | 26 allocs: 10464 B | 1.0 |
| track/5. multi-signal N=1/5K – track | 9 allocs: 944 B | 9 allocs: 944 B | 1.0 |
| track/5. multi-signal N=1/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/6. multi-signal N=2/5K – track | 9 allocs: 1408 B | 9 allocs: 1408 B | 1.0 |
| track/6. multi-signal N=2/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/7. multi-signal N=3/5K – track | 9 allocs: 1760 B | 9 allocs: 1760 B | 1.0 |
| track/7. multi-signal N=3/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/8. L1CA presync 2 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| track/8. L1CA presync 20 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| track/8. L1CA synced 2 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| track/8. L1CA synced 20 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| time_to_load | 196 allocs: 13984 B | 196 allocs: 13984 B | 1.0 |
Benchmark Results (minimum time) — ubuntu-latestReporting the minimum over all samples (robust to shared-runner contention), not the median. Alternative backends vs Float32 (
|
| Scenario | F32 | I16 | 1b | 2b | ×I16 | ×1b | ×2b |
|---|---|---|---|---|---|---|---|
| 4-antenna @ 5 MHz | 12.2 μs | 6.16 μs | 5.22 μs | 9.73 μs | 1.98 ✅ | 2.34 ✅ | 1.26 ✅ |
| GPS L1CA, 8 sats @ 40 MHz | 331.0 μs | 130.0 μs | 71.3 μs | 120.0 μs | 2.54 ✅ | 4.64 ✅ | 2.77 ✅ |
| GPS L1CA, 8 sats @ 5 MHz | 49.3 μs | 28.2 μs | 20.9 μs | 27.9 μs | 1.75 ✅ | 2.36 ✅ | 1.77 ✅ |
| Galileo E1B, 4 sats @ 25 MHz | 122.0 μs | 54.7 μs | 2.23 ✅ | ||||
| dynamic taps @ 5 MHz (kernel) | 5.79 μs | 3.43 μs | 2.03 μs | 2.82 μs | 1.69 ✅ | 2.85 ✅ | 2.05 ✅ |
| multi-signal N=3 @ 5 MHz | 9.54 μs | 5.87 μs | 5.06 μs | 7.13 μs | 1.62 ✅ | 1.88 ✅ | 1.34 ✅ |
Time benchmarks (base vs PR head)
Ratio = ce2e56e… / 6f589e2…: >1 means the PR is faster. ✅ ≥ 5 % faster,
| ce2e56e… | 6f589e2… | ce2e56e… / 6f589e2… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2.38 μs | 2.35 μs | 1.01 |
| downconvert and correlate/CPU/Float32 4ant | 4.71 μs | 4.75 μs | 0.991 |
| downconvert and correlate/CPU/Float64 | 2.77 μs | 2.76 μs | 1.01 |
| downconvert and correlate/CPU/Int16 | 2.43 μs | 2.43 μs | 1.0 |
| downconvert and correlate/CPU/Int16 4ant | 5.18 μs | 5.21 μs | 0.995 |
| downconvert and correlate/CPU/Int32 | 2.42 μs | 2.42 μs | 0.998 |
| fused kernel/1-ant dynamic taps | 2.17 μs | 2.2 μs | 0.984 |
| fused kernel/1-ant static taps | 1.88 μs | 1.88 μs | 0.999 |
| fused kernel/4-ant dynamic taps | 5.42 μs | 5.3 μs | 1.02 |
| fused kernel/4-ant static taps | 4.18 μs | 4.16 μs | 1.0 |
| fused tuple kernel/1-ant N=2 | 2.35 μs | 2.41 μs | 0.973 |
| fused tuple kernel/1-ant N=3 | 2.81 μs | 2.81 μs | 1.0 |
| fused tuple kernel/2-ant N=2 | 3.6 μs | 3.6 μs | 1.0 |
| fused tuple kernel/2-ant N=3 | 4.67 μs | 4.54 μs | 1.03 |
| fused tuple kernel/4-ant N=2 | 6.03 μs | 6.07 μs | 0.994 |
| fused tuple kernel/4-ant N=3 | 9.77 μs | 9.85 μs | 0.992 |
| track/1. Float32/2K – track | 2.58 μs | 2.68 μs | 0.965 |
| track/1. Float32/2K – track! | 2.69 μs | 2.6 μs | 1.04 |
| track/2. L1 8sat/5K – track | 47.9 μs | 47.8 μs | 1.0 |
| track/2. L1 8sat/5K – track! | 47.6 μs | 47.1 μs | 1.01 |
| track/2. L1 8sat/5K – track! Int16 | 28.5 μs | 27.7 μs | 1.03 |
| track/2. L1 8sat/5K – track! OneBit | 20.3 μs | 20.0 μs | 1.02 |
| track/2. L1 8sat/5K – track!-threaded | 47.7 μs | 47.4 μs | 1.0 |
| track/2. L1 8sat/5K – track-threaded | 48.1 μs | 48.1 μs | 0.999 |
| track/3. E1B 4sat/25K – track | 119.0 μs | 119.0 μs | 1.0 |
| track/3. E1B 4sat/25K – track! | 118.0 μs | 118.0 μs | 1.0 |
| track/3. E1B 4sat/25K – track! Int16 | 54.6 μs | 54.7 μs | 0.998 |
| track/3. E1B 4sat/25K – track!-threaded | 118.0 μs | 118.0 μs | 0.998 |
| track/3. E1B 4sat/25K – track-threaded | 119.0 μs | 119.0 μs | 1.0 |
| track/4. 8L1+8E1B/25K – track | 436.0 μs | 437.0 μs | 0.998 |
| track/4. 8L1+8E1B/25K – track! | 433.0 μs | 433.0 μs | 1.0 |
| track/4. 8L1+8E1B/25K – track!-threaded | 434.0 μs | 434.0 μs | 0.999 |
| track/4. 8L1+8E1B/25K – track-threaded | 436.0 μs | 436.0 μs | 1.0 |
| track/5. multi-signal N=1/5K – track | 6.16 μs | 6.14 μs | 1.0 |
| track/5. multi-signal N=1/5K – track! | 6.19 μs | 6.14 μs | 1.01 |
| track/6. multi-signal N=2/5K – track | 8.4 μs | 8.41 μs | 0.999 |
| track/6. multi-signal N=2/5K – track! | 8.6 μs | 8.53 μs | 1.01 |
| track/7. multi-signal N=3/5K – track | 10.8 μs | 10.7 μs | 1.01 |
| track/7. multi-signal N=3/5K – track! | 10.9 μs | 10.9 μs | 1.0 |
| track/8. L1CA presync 2 blk – track! | 11.6 μs | 11.7 μs | 0.996 |
| track/8. L1CA presync 20 blk – track! | 114.0 μs | 112.0 μs | 1.01 |
| track/8. L1CA synced 2 blk – track! | 11.5 μs | 11.6 μs | 0.996 |
| track/8. L1CA synced 20 blk – track! | 112.0 μs | 111.0 μs | 1.02 |
| time_to_load | 102.0 μs | 113.0 μs | 0.905 |
Memory benchmarks (base vs PR head)
Ratio = ce2e56e… / 6f589e2… (bytes allocated): >1 means the PR allocates less. ✅ ≥ 5 % less, ∞/0 mark a benchmark that drops to / picks up allocations, — means both revisions allocate nothing. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).
| ce2e56e… | 6f589e2… | ce2e56e… / 6f589e2… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| downconvert and correlate/CPU/Float32 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Float64 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| downconvert and correlate/CPU/Int16 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| downconvert and correlate/CPU/Int16 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Int32 | 2 allocs: 576 B | 2 allocs: 576 B | 1.0 |
| fused kernel/1-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/1-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/1. Float32/2K – track | 9 allocs: 944 B | 9 allocs: 944 B | 1.0 |
| track/1. Float32/2K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/2. L1 8sat/5K – track | 10 allocs: 4536 B | 10 allocs: 4536 B | 1.0 |
| track/2. L1 8sat/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/2. L1 8sat/5K – track! Int16 | 13 allocs: 1056 B | 13 allocs: 1056 B | 1.0 |
| track/2. L1 8sat/5K – track! OneBit | 45 allocs: 2736 B | 45 allocs: 2736 B | 1.0 |
| track/2. L1 8sat/5K – track!-threaded | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/2. L1 8sat/5K – track-threaded | 10 allocs: 4536 B | 10 allocs: 4536 B | 1.0 |
| track/3. E1B 4sat/25K – track | 10 allocs: 2808 B | 10 allocs: 2808 B | 1.0 |
| track/3. E1B 4sat/25K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/3. E1B 4sat/25K – track! Int16 | 5 allocs: 416 B | 5 allocs: 416 B | 1.0 |
| track/3. E1B 4sat/25K – track!-threaded | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/3. E1B 4sat/25K – track-threaded | 10 allocs: 2808 B | 10 allocs: 2808 B | 1.0 |
| track/4. 8L1+8E1B/25K – track | 26 allocs: 10352 B | 26 allocs: 10352 B | 1.0 |
| track/4. 8L1+8E1B/25K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/4. 8L1+8E1B/25K – track!-threaded | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/4. 8L1+8E1B/25K – track-threaded | 26 allocs: 10352 B | 26 allocs: 10352 B | 1.0 |
| track/5. multi-signal N=1/5K – track | 9 allocs: 944 B | 9 allocs: 944 B | 1.0 |
| track/5. multi-signal N=1/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/6. multi-signal N=2/5K – track | 9 allocs: 1408 B | 9 allocs: 1408 B | 1.0 |
| track/6. multi-signal N=2/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/7. multi-signal N=3/5K – track | 9 allocs: 1760 B | 9 allocs: 1760 B | 1.0 |
| track/7. multi-signal N=3/5K – track! | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/8. L1CA presync 2 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| track/8. L1CA presync 20 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| track/8. L1CA synced 2 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| track/8. L1CA synced 20 blk – track! | 7 allocs: 320 B | 7 allocs: 320 B | 1.0 |
| time_to_load | 145 allocs: 11216 B | 145 allocs: 11216 B | 1.0 |
…eo E5a)
In a combined group the loops lock the driver (signals[1], the pilot) onto
the real axis, so a QPSK data component on the orthogonal carrier (GPS L5,
Galileo E5a) lands on the imaginary axis and its real(prompt) bit decision
collapses to noise — the nav message never syncs.
De-rotate each component's bit-buffer prompt onto the driver's frame before
the sync search and symbol accumulation:
prompt *= cis(get_carrier_phase_offset(driver) - get_carrier_phase_offset(signal))
A no-op (cis(0)) for the driver, co-phased pairs (E1B/E1C) and data-only
sats; a quadrature component rotates ±90° onto the real axis, sign resolved
downstream by the decoder. Requires GNSSSignals 3.6 (introduces
get_carrier_phase_offset); compat floor pinned. Adds test/carrier_phase.jl.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c35eec6 to
6f589e2
Compare
Summary
In a combined tracking group the loops lock the driver (
signals[1], the pilot) onto the real axis. A QPSK data component sitting on the orthogonal carrier (GPS L5, Galileo E5a) therefore lands on the imaginary axis, so itsreal(prompt)bit decision collapses to noise and the navigation message never syncs.This PR de-rotates each component's bit-buffer prompt onto the driver's phase frame before the secondary/bit-sync search and coherent symbol accumulation:
cis(0) === 1 + 0im, a bit-identical no-op. Pre-fix behaviour is preserved exactly.Changes
src/conventional_pll_and_dll.jl— add_carrier_phase_derotation, thread the driver's carrier phase through the driver and passenger signal paths, and apply the de-rotation to the bit-buffer prompt in the shared per-signal advance.Project.toml— bump GNSSSignals compat floor to3.6(introducesget_carrier_phase_offset).test/carrier_phase.jl(+test/runtests.jl) — new tests.Tests
test/carrier_phase.jlcovers:signals[1]), not hard-coded to "the pilot" — reversing the driver/passenger roles stays correct.Notes
Requires GNSSSignals 3.6.
🤖 Generated with Claude Code