feat(sdr): local receivers — browser RTL-SDR, Local ADS-B layer and decoder feeds (#732)

Adds a Local ADS-B layer fed by your own receivers: a browser WebUSB
RTL-SDR (with FM listening) and local 1090/978 MHz decoder feeds
(LOCAL_RECEIVER_FEEDS, local and private addresses only). Local aircraft
draw in magenta with class, silhouettes, 3D models, trails and a position
sanity filter; the click card names the band and input that heard them.

Builds on Sameh Khamis's SDR prototype and the initial GEV port by
Mazeyar Moeini Feizabadi (#134); the decoder-feed design follows the
notes on #57. Closes #57.

Co-authored-by: Sameh Khamis <[email protected]>
Co-authored-by: Mazeyar Moeini Feizabadi <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-09-23 16:00:53 -05:00
1 parent 143cffd7ca
commit 1fc795577a
78 files changed
+12267 -108

No files matched your search

+5 -3
View File
@@ -183,21 +183,23 @@ test('no unchanged Realtime tool definition drifts silently', () => {
'analyst_query',
'next_iss_pass',
'next_satellite_pass',
// Local ADS-B adds one layer enum value and its common-name mapping.
'set_layer_visibility',
]);
const unchanged = realtimeTools()
.filter((tool) => !TOUCHED.has(tool.name))
.sort((a, b) => a.name.localeCompare(b.name));
assert.equal(unchanged.length, 19);
assert.equal(unchanged.length, 18);
const digest = createHash('sha256')
.update(JSON.stringify(unchanged))
.digest('hex')
.slice(0, 16);
// Analyst additions and ISS wording correction are explicitly excluded above; all other tool definitions retain their pin.
assert.equal(digest, '7411ea585e534795', 'an unchanged Realtime tool definition drifted');
assert.equal(digest, '8494f344fabb67f5', 'an unchanged Realtime tool definition drifted');
});
test('Radio volume and mission speed share the Sharpen slider visual language', () => {
for (const id of ['cockpit-radio-volume', 'context-radio-mini-volume', 'radio-volume']) {
for (const id of ['cockpit-radio-volume', 'context-radio-mini-volume', 'radio-volume', 'sdr-volume']) {
assert.match(
html,
new RegExp(`id="${id}"[^>]*class="gev-quantitative-slider"[^>]*type="range"`),