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
+3
View File
@@ -153,6 +153,9 @@ protected:
bool filterRecvFloodPacket(mesh::Packet* packet) override;
bool allowPacketForward(const mesh::Packet* packet) override;
bool isRepeatLooped(const mesh::Packet* packet) const;
// Overhear suppression only makes sense while repeating; gated behind its own
// opt-in pref (Settings > Radio > Suppress dup).
bool wantsOverhearSuppress() const override { return _prefs.client_repeat && _prefs.repeat_suppress_dup; }
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis);
void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override;