The WSPR decoder was producing many false positive decodes due to
several overly permissive thresholds that allowed noise to reach the
Fano sequential decoder, which could then converge on random data:
- Raise normalized sync score threshold from 0.10 to 0.20 to reject
noise candidates before attempting expensive Fano decoding
- Add minimum SNR gate (-20 dB) to skip candidates where the signal
is indistinguishable from noise
- Return and check the Fano decoder's cumulative path metric, rejecting
low-confidence decodes (metric < 20) that are likely noise artifacts
- Raise RMS threshold from 0.0005 to 0.005 to reject near-silent audio
- Add near-frequency deduplication to prevent the same signal decoded
at slightly different (freq, dt) offsets from appearing multiple times
- Add noise-only regression test to verify no false positives on random
input
https://claude.ai/code/session_01HTBoEsD1hp99TiYMSaHMVG
Signed-off-by: Claude <noreply@anthropic.com>
The `chips_to_rds_signal` test helper was generating rectangular chip
pulses, but the receiver expects RRC-shaped transmit pulses so that
RRC(tx) × RRC(rx) = raised cosine with zero ISI. The rectangular
pulses caused ISI that drifted the symbol clock sampling point,
consistently skipping PS segment 2 in the end-to-end test.
Replace rectangular pulses with an impulse train convolved with the
same RRC taps used by the receiver. All 15 tests now pass including
`end_to_end_clean_signal_decodes_ps`.
https://claude.ai/code/session_01N2UcGaLDzYiM3gNrZ6kFBj
Signed-off-by: Claude <noreply@anthropic.com>
The map module was tagging all decode markers (APRS, AIS, VDES,
FT8/FT4/FT2/WSPR locators) with the global rig picker's active rig
instead of the actual source rig. This made the map's own rig filter
dropdown ineffective in multi-rig setups.
- Add rig_id field to all decode message structs (AisMessage,
VdesMessage, AprsPacket, CwEvent, Ft8Message, WsprMessage)
- Set rig_id on messages in audio_client before broadcasting, using
the actual rig connection identifier
- Update history collector to prefer message rig_id over the global
active rig fallback
- Pass rig_id through plugin normalize functions (AIS, APRS, VDES,
HF-APRS) so it reaches the map add functions
- Update all map marker functions (aprsMapAddStation, aisMapAddVessel,
vdesMapAddPoint, mapAddLocator) to use the message's rig_id with
fallback to the global picker for backward compatibility
https://claude.ai/code/session_015gC7axHk2jmp7HbFPdbivN
Signed-off-by: Claude <noreply@anthropic.com>
Drop the plugin loading infrastructure (libloading-based dynamic .so/.dylib/.dll
loading) from both trx-server and trx-client. The feature was unused and posed an
unnecessary security risk by executing arbitrary native code from disk.
Removed:
- src/trx-app/src/plugins.rs (plugin discovery, validation, FFI registration)
- examples/trx-plugin-example/ (cdylib example plugin)
- libloading dependency from trx-app
- load_backend_plugins / load_frontend_plugins calls from server and client
- Plugin documentation from README.md and CLAUDE.md
https://claude.ai/code/session_01DTEUpz3XPUeWmz74NeaFgb
Signed-off-by: Claude <noreply@anthropic.com>
Update all SDR command handlers in rig_task to access SDR methods via
ctx.rig.as_sdr() instead of calling them directly on RigCat. Query-only
SDR operations (filter_state, get_spectrum, get_vchan_rds) use
as_sdr_ref(). Non-SDR rigs now get proper not_supported errors.
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
Extract 13 SDR-specific methods (set_center_freq, set_bandwidth,
set_sdr_gain/lna/agc/squelch/nb, set_wfm_*, filter_state, get_spectrum,
get_vchan_rds) into a new RigSdr trait. RigCat retains core CAT
operations and gains as_sdr()/as_sdr_ref() for optional SDR access.
Non-SDR backends no longer see SDR methods in their trait impl.
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
Replace all .unwrap() on RwLock/Mutex acquisitions with
.unwrap_or_else(|e| e.into_inner()) to gracefully recover from poisoned
locks instead of panicking. Add lock ordering documentation to the
module header to prevent deadlocks.
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
Add an AtomicUsize total_count field to DecoderHistories, maintained by
record/prune/clear methods, so estimated_total_count() avoids 9 separate
mutex acquisitions. Also replace audio ring buffer .unwrap() calls with
.unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks.
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
Use a StateWithMeta wrapper struct with #[serde(flatten)] for merging
rig state with frontend meta, replacing the manual string manipulation.
Also add Serialize derive and skip_serializing_if to FrontendMeta.
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>
Introduce DecodeHistory<T> alias for the repeated
Arc<Mutex<VecDeque<(Instant, Option<String>, T)>>> pattern (9 fields).
Also switch VChanAudioCmd channel senders from UnboundedSender to Sender
to prevent unbounded memory growth under backpressure.
https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4
Signed-off-by: Claude <noreply@anthropic.com>