feat(repeater): politeness knobs in Settings (skip-advert, max-hops, yield, min-SNR)

Four opt-in filters, all default off, consulted only when the repeater is on
(MyMesh::allowPacketForward) and shown under Settings > Radio only while it is:
- Skip advert: don't re-flood ADVERT packets (highest-volume flood traffic).
- Max hops: drop a flood packet once it has travelled N hops.
- Yield: scale the FORWARDED flood retransmit delay so a mobile companion
  defers to better-sited fixed repeaters; never touches the node's own sends.
- Min SNR: drop a flood copy received below a dB threshold so marginal fringe
  traffic isn't re-flooded.

All four are flood-only — on a direct route this node is the named next hop, so
dropping there would kill delivery. Persisted behind a new schema sentinel
(0xC0DE000E) with stray-byte clamps; min-SNR uses a -128 "disabled" sentinel so
an upgraded file can't read as "filter at 0 dB".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-18 23:44:36 +02:00
co-authored by Claude Sonnet 4.6
parent f55b869fbe
commit e213ff6efb
4 changed files with 120 additions and 2 deletions
+14 -1
View File
@@ -282,7 +282,12 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.5f);
return getRNG()->nextInt(0, 5*t + 1);
uint32_t d = getRNG()->nextInt(0, 5*t + 1);
// Politeness yield (Settings > Radio): scale the flood retransmit delay so a
// mobile companion waits longer and lets better-sited fixed repeaters win the
// flood first. Only forwarded floods reach here — own sends pass their own
// delay to sendFlood() — so this never slows the companion's own traffic.
return d * (1 + _prefs.repeat_delay_boost);
}
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.2f);
@@ -551,7 +556,15 @@ bool MyMesh::isRepeatLooped(const mesh::Packet* packet) const {
bool MyMesh::allowPacketForward(const mesh::Packet* packet) {
if (_prefs.client_repeat == 0) return false;
// "Politeness" filters (Settings > Radio) — all default off, so a plain repeater
// is unaffected. Flood-only by design: on a direct route this node is the named
// next hop, so dropping there would kill delivery with no alternate path, while
// dropping a flood copy just trims redundancy other nodes still carry.
if (packet->isRouteFlood()) {
if (_prefs.repeat_min_snr != NodePrefs::REPEAT_SNR_DISABLED
&& packet->getSNR() < (float)_prefs.repeat_min_snr) return false;
if (_prefs.repeat_skip_adverts && packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) return false;
if (_prefs.repeat_max_hops > 0 && packet->getPathHashCount() >= _prefs.repeat_max_hops) return false;
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->getPathHashCount() >= REPEAT_MAX_ADVERT_HOPS) return false;
if (isRepeatLooped(packet)) return false;
}