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
co-authored by Claude Sonnet 4.6
parent e213ff6efb
commit d790f7e5cd
7 changed files with 59 additions and 2 deletions
+4 -1
View File
@@ -164,17 +164,20 @@ struct NodePrefs { // persisted to file
// fixed repeaters. Effective delay = base * (1 + repeat_delay_boost). 0 = off.
// repeat_min_snr: drop a flood packet received below this SNR (dB), so marginal
// fringe traffic isn't re-flooded. REPEAT_SNR_DISABLED (-128) = off.
// repeat_suppress_dup: 1 = cancel a queued retransmit when the same flood is
// overheard from another node first (less redundant airtime in dense mesh).
uint8_t repeat_skip_adverts;
uint8_t repeat_max_hops;
uint8_t repeat_delay_boost;
int8_t repeat_min_snr;
static const int8_t REPEAT_SNR_DISABLED = -128;
uint8_t repeat_suppress_dup;
// Tail sentinel written at the end of /new_prefs. Bump the low byte when
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so
// older saves are detected on load and skipped (zero-init defaults kept).
// High 24 bits identify the file format; low byte is the schema revision.
static const uint32_t SCHEMA_SENTINEL = 0xC0DE000E;
static const uint32_t SCHEMA_SENTINEL = 0xC0DE000F;
// Bit-index for each home page. Used by page_order (entries store bit+1) and
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage