Recorded replay — simulated data only
Outage

Dropout

An outage. The channels go silent, and the shaded gaps are the absence itself — jamming looks exactly like this.

OK LAST_AGREED: T−0s
glonass_cn0_spread: OK gps_cn0_spread: OK gps_position_error: OK gps_time_offset: OK ntp_time_offset: OK roughtime_time_offset: OK

GPS vs the assembly

GPS TIME OFFSET

Every channel

glonass_cn0_spread

OK
APPLIED THRESHOLDS
Variable
<constellation>_cn0_spread — dB-Hz
Source
The per-satellite carrier-to-noise values in each $GPGSV cycle, one channel per constellation.
Means
Spread of signal strength across the satellites in view. A real sky fades with elevation, so healthy spread is wide; a simulator transmitting every channel at equal power collapses it toward zero.
Formula
cn0_spread = pstdev(per-satellite C/N₀) (collectors/cn0.py:106)

gps_cn0_spread

OK
APPLIED THRESHOLDS
Variable
<constellation>_cn0_spread — dB-Hz
Source
The per-satellite carrier-to-noise values in each $GPGSV cycle, one channel per constellation.
Means
Spread of signal strength across the satellites in view. A real sky fades with elevation, so healthy spread is wide; a simulator transmitting every channel at equal power collapses it toward zero.
Formula
cn0_spread = pstdev(per-satellite C/N₀) (collectors/cn0.py:106)

gps_position_error

OK
APPLIED THRESHOLDS
Variable
gps_position_error — metres
Source
The latitude/longitude of each $GPRMC fix, against the site's surveyed antenna position.
Means
Distance between where the receiver says it is and where it actually is. The antenna does not move, so anything but noise here is the fix being wrong.
Formula
gps_position_error = haversine_m(fix, surveyed_site) (collectors/gps.py:55, geo.py:10)

ntp_time_offset

OK
APPLIED THRESHOLDS
Variable
ntp_time_offset — seconds
Source
An SNTP exchange with one or more network time servers (RFC 4330). With two or more, a median consensus corroborated by a quorum.
Means
Network time minus local time — a reference that does not come from the sky. A GPS spoofer would also have to own the network to move this.
Formula
offset = ((t2 − t1) + (t3 − t4)) / 2 (collectors/ntp.py:153)

roughtime_time_offset

OK
APPLIED THRESHOLDS
Variable
roughtime_time_offset — seconds
Source
A Roughtime server's signed response, verified with Ed25519 and a Merkle inclusion proof for our nonce.
Means
Cryptographically verified time minus local time. An attacker cannot forge this without the server's private key, which is what separates it from plain NTP.
Formula
roughtime_time_offset = verified_midpoint − local_unix_time (collectors/roughtime.py)

How every channel is judged

The same three detectors run on every channel above. Thresholds are per channel and are the ones the daemon actually applied — the dotted bands on each chart are drawn from them.

DetectorRuleSource
stepalarm when abs(x − target) > thresholddiscrepancy/step.py:31
cusumS⁺ = max(0, S⁺ + (x − target − k)); S⁻ = max(0, S⁻ + (target − x − k)); alarm when max(S⁺, S⁻) > h, then resetdiscrepancy/cusum.py:55-58
stalenessalarm when now − last_sample.timestamp > max_sample_age_sdiscrepancy/engine.py:126