@@ -5,6 +5,49 @@ All notable changes to pyModeS are documented in this file.
55The format is based on [ Keep a Changelog] ( https://keepachangelog.com/en/1.1.0/ ) ,
66and this project adheres to [ Semantic Versioning] ( https://semver.org/ ) .
77
8+ ## [ 3.1.0] — 2026-04-14
9+
10+ Streaming-robustness release. Adds defences against phantom positions
11+ at stream start and against DF20 CRC-collisions that attribute a
12+ Comm-B reply to the wrong aircraft.
13+
14+ ### Added
15+
16+ - ` PipeDecoder ` bootstrap cluster analysis for the first position per
17+ ICAO: lat/lon emission is held until ` _BOOTSTRAP_K ` (=5) candidate
18+ positions agree under a motion-consistency check. Prevents an
19+ initial phantom frame from anchoring the rolling position history.
20+ On each decode the held result dicts are retro-filled once the
21+ cluster locks. Scattered buffers reset and accumulate a fresh K.
22+ - ` PipeDecoder ` altitude cross-check on DF20 Comm-B replies: if the
23+ message's 13-bit AC-code disagrees with the ICAO's most recent
24+ ADS-B-derived altitude by more than ` _altitude_tolerance(dt) ` (a
25+ linearly growing, floored-and-capped window), the BDS payload
26+ fields are stripped from the result and ` altitude_mismatch=True `
27+ is set. The AC-code altitude itself is preserved so callers see
28+ why the frame was flagged.
29+ - ` PipeDecoder.flush() ` method that finalizes any still-bootstrapping
30+ ICAOs, running cluster analysis on whatever candidates are
31+ buffered (even if fewer than K) and retro-filling the held result
32+ dicts in place. Automatically called by the batch `decode(list,
33+ timestamps=list)` code path so batch callers get positions on
34+ every resolvable frame without having to reach into ` PipeDecoder ` .
35+ - Four new per-stream stat counters exposed via ` PipeDecoder.stats ` :
36+ ` altitude_mismatch ` , ` position_rejected ` , ` bootstrap_held ` ,
37+ ` bootstrap_reset ` .
38+
39+ ### Fixed
40+
41+ - ` airborne_position_pair ` now rejects CPR pairs that resolve to
42+ physically impossible latitudes (|lat| > 90). Root cause: real
43+ DF17 pairs that straddle a ` cprNL ` zone can slip past the
44+ pre-pair ` NL(lat_even) == NL(lat_odd) ` check and produce
45+ latitudes in [ 90, 270) — e.g. an aircraft climbing out of
46+ Amsterdam resolving to lat ≈ 113° over the Bering Sea.
47+ Observed on ICAO 485A33 (2025-04-13 19:34:57 UTC, OpenSky feed)
48+ — now rejected at the pair resolver rather than poisoning the
49+ receiver's position history.
50+
851## [ 3.0.0] — 2026-04-13
952
1053First release of the v3 ground-up rewrite. ** Not backwards-compatible
0 commit comments