# `Sidereon.GNSS.BroadcastComparison`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/gnss/broadcast_comparison.ex#L1)

Broadcast-ephemeris accuracy: compare a broadcast navigation product against a
precise SP3 product over a window.

This is the standard broadcast-orbit / clock accuracy check (the orbit and clock
pieces of the signal-in-space range error, SISRE). For each satellite at each
epoch it differences the broadcast-evaluated ECEF position and clock against the
precise SP3 values, decomposes the position error into radial / along-track /
cross-track (RAC) components, and summarizes the differences as RMS and maximum
statistics per satellite and overall.

Both products are evaluated through `Sidereon.GNSS.Ephemeris`, so the frame
(ITRF/IGS ECEF, meters), the time scale (GPST), and the clock sign convention
(positive = satellite clock ahead of system time) are exactly as documented
there. Only epochs where **both** sources return a valid state contribute to the
statistics; an epoch missing from either product is skipped, never extrapolated.

## RAC frame

The radial/along-track/cross-track unit vectors are built from the **precise**
state: radial along the position vector, cross-track along the orbital angular
momentum `r x v`, and along-track completing the right-handed triad. The SP3
position/clock interpolation does not expose a velocity, so the velocity is
derived by a centered finite difference of the precise position
(`(r(t+dt) - r(t-dt)) / 2dt`, falling back to a one-sided difference at a window
edge). The position-difference vector `broadcast - precise` is projected onto
this triad.

## Expected magnitudes

GPS LNAV broadcast orbits differ from IGS precise orbits at roughly the 1-2 m
RMS level (3D), dominated by the along-track and radial components; Galileo and
BeiDou MEO are comparable. A result far outside this band (tens of meters or
more) indicates a parse or evaluation defect rather than normal broadcast error.

The broadcast models follow IS-GPS-200 (GPS LNAV), the Galileo OS-SIS-ICD, and
the BeiDou BDS-SIS-ICD; the precise product is SP3-c / SP3-d (IGS).

## Example

    {:ok, broadcast} = Sidereon.GNSS.Broadcast.load("BRDC.rnx")
    {:ok, sp3} = Sidereon.GNSS.SP3.load("igs.sp3")

    report =
      Sidereon.GNSS.BroadcastComparison.compare(broadcast, sp3, ["G01", "E11"], %{
        from: ~N[2020-06-25 02:00:00],
        to: ~N[2020-06-25 04:00:00],
        step_s: 300
      })

    report.overall.orbit_3d_rms_m   # 3D orbit RMS over all satellites, meters
    report.per_satellite["G01"].radial_rms_m

## Broadcast and precise products on a sample day

Comparing the GPS LNAV broadcast message against the GBM precise SP3 product for
2020 day-of-year 177, all GPS satellites over a multi-hour window at a 15 min
step, gives an overall 3D orbit RMS of about **1.5 m** (max ~4 m), split as
roughly 1.1 m radial / 0.9 m along-track / 0.5 m cross-track, the expected GPS
broadcast accuracy. The raw clock differences (`clock_rms_m`) are larger
(several meters) because the broadcast and precise clocks are referenced to
different time datums, which differ by a common per-epoch offset that drifts
over the day. `clock_datum_removed_rms_m` removes that common offset (the
per-epoch median over satellites) and reports the actual signal-in-space clock
error, which is several times smaller.
The same call works against a broadcast product by calling
`compare_broadcast_vs_precise/4` directly with loaded products.

The orbit/clock differencing, RAC decomposition, finite-difference velocity, and
the RMS/median/datum statistics are computed in the Rust core; this module
validates inputs, marshals the per-epoch evaluation keys across the NIF, and
rebuilds the public report.

# `compare`

```elixir
@spec compare(
  Sidereon.GNSS.Broadcast.t(),
  Sidereon.GNSS.SP3.t(),
  [String.t()],
  Sidereon.GNSS.Ephemeris.window()
) :: Sidereon.GNSS.BroadcastComparison.Report.t()
```

Compare a broadcast product against a precise SP3 product over `window`.

`broadcast` is an `Sidereon.GNSS.Broadcast` handle and `precise` an
`Sidereon.GNSS.SP3` handle for the same day; `sat_ids` is a list of canonical
RINEX tokens; `window` is the `Sidereon.GNSS.Ephemeris` window map (`:from`,
`:to`, `:step_s`). Returns an `Sidereon.GNSS.BroadcastComparison.Report`.

The window should sit within both the SP3 file span and the broadcast records'
fit intervals; epochs missing from either product are counted in
`report.missing` and excluded from the statistics.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
