Skip to content

feat(bit-buffer)!: emit complex soft bits, drop the hard-bit buffer#200

Closed
giove-a wants to merge 1 commit into
masterfrom
sc/complex-bit-buffer
Closed

feat(bit-buffer)!: emit complex soft bits, drop the hard-bit buffer#200
giove-a wants to merge 1 commit into
masterfrom
sc/complex-bit-buffer

Conversation

@giove-a

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

Copy link
Copy Markdown
Collaborator

Summary

Reworks the BitBuffer so the coherently accumulated complex prompt of
each completed navigation bit is the buffer's sole decoded-bit output
(get_soft_bits, now Vector{ComplexF32}). A downstream receiver decides
whether the navigation data lives in the real component, the imaginary
component, or a combination of both, and forms its own hard decision.

The legacy hard-bit concept — get_bits and the fixed-width
buffer::UInt128 / length fields — is removed.

Motivation

Previously the buffer collapsed the accumulated prompt to real(...) for
both the stored soft bit and the packed hard bits. That discards the
quadrature component, which is exactly where the data lands in L5/E5a
pilot+data tracking
: the pilot drives the carrier lock, leaving the data
90° out of phase in the imaginary component. Retaining the full complex
accumulation lets a GNSSReceiver recover that data. (E1, already in-phase,
is unaffected.)

Once soft bits carry the full complex value, get_bits is a strictly lossy,
in-phase-only projection of them (real(soft) > 0) — redundant for in-phase
signals and wrong for quadrature-data signals. Rather than carry a parallel,
misleading representation, this removes it.

What changed

  • soft_bits: Vector{Float32}Vector{ComplexF32}; stores the full
    polarity-corrected complex accumulation. Polarity is applied to the whole
    phasor (a 180° ambiguity = ×(-1), both quadratures), not just the real part.
  • Removed the buffer::UInt128 and length::Int fields, get_bits
    (accessor, export, and all TrackedSignal/TrackedSat/TrackState
    forwards), the hard-bit packing, and the 128-bit overflow guard (a
    growable Vector has no fixed cap).
  • Retained get_num_bits, now backed by the soft-bit count
    (length(soft_bits)).
  • Convenience BitBuffer constructor drops its buffer / length
    arguments (7-arg → 5-arg).
  • Pre-sync bit recovery pushes real-valued soft bits (imaginary part zero):
    the pre-sync window only ever stored the in-phase sign, and only in-phase
    signals with no secondary code reach that path (GPS L1 C/A). Quadrature-data
    signals route through the secondary-code sync path and decode fresh
    post-sync, where the full complex path applies.
  • Docs (bit_sync.md, tracking_state.md) and the affected jldoctests
    updated to get_soft_bits.

Note — pre-sync bits stay real-valued by design (performance). The
pre-sync sliding window is a bit-packed integer (code_block_buffer) holding
one in-phase sign per code block, so the secondary-code sync search can
rotate / XOR / Hamming-compare it cheaply and allocation-free on the hot
pre-sync path. Buffering the full complex prompt per block instead would
require a growing ComplexF32 vector and give up that property. Since the
only signals that recover pre-sync bits are in-phase (data in the real
component), the quadrature is genuinely zero there and nothing is lost — the
complex path is exercised post-sync, where it matters. A hypothetical future
signal with multi-block bits, no secondary code, and quadrature data
would be the one case this doesn't cover; none exists today.

⚠️ Breaking change

get_bits is gone. Consumers form their own hard decision from
get_soft_bits:

# before
bits = get_bits(track_state, prn)          # UInt128 of packed hard bits

# after — in-phase signals (GPS L1 C/A, Galileo E1B):
hard = real.(get_soft_bits(track_state, prn)) .> 0
# quadrature-data (L5/E5a pilot+data): project onto the data component, e.g.
hard = imag.(get_soft_bits(track_state, prn)) .> 0

get_num_bits is unchanged in meaning. The convenience BitBuffer(...)
constructor no longer takes buffer / length positional args.

Testing

  • Pkg.test() passes in full (Flexiband integration test skipped as usual —
    1.6 GB download).
  • The 128-bit overflow test is replaced by one asserting the soft-bit vector
    grows past 128 bits without error.
  • New test covers recovering a bit whose data sits purely in the quadrature
    component.

🤖 Generated with Claude Code

@siebc
siebc requested a review from zsoerenm July 11, 2026 16:48
Store the coherently accumulated complex prompt of each completed
navigation bit in `soft_bits` (now `Vector{ComplexF32}` instead of
`Vector{Float32}`) as the buffer's sole decoded-bit output, so a
downstream receiver can decide whether the navigation data lives in the
real component, the imaginary component, or a combination of both.

This is what enables L5/E5a pilot+data tracking: a pilot-driven lock
leaves the data 90 degrees out of phase, so the data bits sit in the
quadrature component that a real-only accumulation discards.

BREAKING CHANGE: the legacy hard-bit concept is removed. `get_bits` and
the fixed-width `buffer::UInt128` / `length` fields (with their 128-bit
overflow guard) are gone. Consumers form their own hard decision from
`get_soft_bits` (e.g. `real.(soft) .> 0` for in-phase signals like GPS
L1 C/A and Galileo E1B). `get_num_bits` is retained, now backed by the
soft-bit count. The convenience `BitBuffer` constructor drops its
`buffer` / `length` arguments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@giove-a
giove-a force-pushed the sc/complex-bit-buffer branch from 2ee4f5d to 105ee47 Compare July 11, 2026 16:49
@giove-a giove-a changed the title Emit complex soft bits and drop the hard-bit buffer feat(bit-buffer)!: emit complex soft bits, drop the hard-bit buffer Jul 11, 2026
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.62%. Comparing base (687157e) to head (105ee47).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #200      +/-   ##
==========================================
- Coverage   97.63%   97.62%   -0.01%     
==========================================
  Files          32       32              
  Lines        3296     3291       -5     
==========================================
- Hits         3218     3213       -5     
  Misses         78       78              

☔ 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 commented Jul 14, 2026

Copy link
Copy Markdown
Member

This PR points out a real problem, but the proposed solution does not resolve the bit sync problem which still relies on the real part only.

@siebc siebc closed this Jul 14, 2026
@giove-a
giove-a deleted the sc/complex-bit-buffer branch July 14, 2026 06:19
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