feat(repeater): overhear suppression — cancel queued retransmit if a peer relays first

When enabled (Settings > Radio > Suppress dup), a received flood packet whose
hash matches one still waiting in the outbound queue cancels that queued
retransmit: another node already relayed it, so this node stays quiet. Cuts
redundant airtime in dense meshes; pairs with Yield (longer delay = wider window
to overhear a peer first).

- Dispatcher: wantsOverhearSuppress() hook (default off) + suppressQueuedDuplicate()
  scan over the outbound queue by packet hash; onRetransmitCancelled() hook.
- Mesh: overrides onRetransmitCancelled() to back out n_forwarded so the count
  reflects what actually goes on air. resetStats() already covered.
- Flood-only and hash-matched (payload hash ignores path for non-TRACE, so our
  copy and the peer's relayed copy hash equal). Own sends can't be overheard
  before their first TX, so suppression only ever cancels forwards.
- New repeat_suppress_dup pref, schema sentinel 0xC0DE000F, stray-byte clamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-18 23:51:01 +02:00
parent e213ff6efb
commit d790f7e5cd
7 changed files with 59 additions and 2 deletions

View File

@@ -343,12 +343,14 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
rd(&_prefs.repeat_max_hops, sizeof(_prefs.repeat_max_hops));
rd(&_prefs.repeat_delay_boost, sizeof(_prefs.repeat_delay_boost));
rd(&_prefs.repeat_min_snr, sizeof(_prefs.repeat_min_snr));
rd(&_prefs.repeat_suppress_dup, sizeof(_prefs.repeat_suppress_dup));
if (_prefs.repeat_skip_adverts > 1) _prefs.repeat_skip_adverts = 0;
if (_prefs.repeat_max_hops > 64) _prefs.repeat_max_hops = 0;
if (_prefs.repeat_delay_boost > 8) _prefs.repeat_delay_boost = 0;
if (_prefs.repeat_min_snr != NodePrefs::REPEAT_SNR_DISABLED &&
(_prefs.repeat_min_snr < -30 || _prefs.repeat_min_snr > 20))
_prefs.repeat_min_snr = NodePrefs::REPEAT_SNR_DISABLED;
if (_prefs.repeat_suppress_dup > 1) _prefs.repeat_suppress_dup = 0;
// → 0xC0DE000B: append bot_commands_enabled + quiet-hours. Older files leave
// stray bytes here; clamp so upgraders fall back to off / no quiet hours.
if (_prefs.bot_commands_enabled > 1) _prefs.bot_commands_enabled = 0;
@@ -510,6 +512,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)&_prefs.repeat_max_hops, sizeof(_prefs.repeat_max_hops));
file.write((uint8_t *)&_prefs.repeat_delay_boost, sizeof(_prefs.repeat_delay_boost));
file.write((uint8_t *)&_prefs.repeat_min_snr, sizeof(_prefs.repeat_min_snr));
file.write((uint8_t *)&_prefs.repeat_suppress_dup, sizeof(_prefs.repeat_suppress_dup));
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL.
uint32_t sentinel = NodePrefs::SCHEMA_SENTINEL;