position jump

A crude spoof. The reported position leaps away from the surveyed antenna, which cannot physically move. The step detector fires on the first bad fix, and the time channels stay clean — telling you the attack is on position, not on the clock.

ALARM

last agreed 0s before the end

glonass_cn0_spread: OKgps_cn0_spread: OKgps_position_error: ALARMgps_time_offset: OKntp_time_offset: OKroughtime_time_offset: OK

What each chart shows

Every formula below is the one the code runs, with its source location. Thresholds are not repeated here — they come from the daemon's own published settings.

gps_time_offset

Variable
gps_time_offset — seconds
Source
The time field of each $GPRMC sentence, against the receiving host's own clock.
Description
How far GPS-reported UTC sits from local time. A time spoof drags this away while the independent references stay put — which is the disagreement the monitor exists to notice.
Formula
gps_time_offset = gps_utc − local_unix_time (collectors/gps.py:54)

glonass_cn0_spread

Variable
<constellation>_cn0_spread — dB-Hz
Source
The per-satellite carrier-to-noise values in each $GPGSV cycle, one channel per constellation.
Description
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

Variable
<constellation>_cn0_spread — dB-Hz
Source
The per-satellite carrier-to-noise values in each $GPGSV cycle, one channel per constellation.
Description
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

Variable
gps_position_error — metres
Source
The latitude/longitude of each $GPRMC fix, against the site's surveyed antenna position.
Description
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

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.
Description
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

Variable
roughtime_time_offset — seconds
Source
A Roughtime server's signed response, verified with Ed25519 and a Merkle inclusion proof for our nonce.
Description
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