diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index d08d08a..7bc7f96 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -1200,11 +1200,15 @@ uint32_t CompanionMesh::getRetransmitDelay(const mesh::Packet *packet) uint32_t CompanionMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = _radio->getEstAirtimeFor( + uint32_t airtime = _radio->getEstAirtimeFor( packet->getPathByteLen() + packet->payload_len + 2); + /* Jitter around Arduino direct factor 0.3 using a per-packet factor + * in the range [0.25, 0.40]. */ + uint32_t factor_milli = (uint32_t)getRNG()->nextInt(250, 401); + uint32_t max_jitter = (5 * airtime * factor_milli) / 1000; /* Floor: give downstream nodes time to finish RX processing * and return to RX mode before we TX (~20ms settle + jitter) */ - return 20 + getRNG()->nextInt(0, t / 10 + 1); + return 20 + getRNG()->nextInt(0, max_jitter + 1); } uint32_t CompanionMesh::getInitialFloodJitter(const mesh::Packet *packet) diff --git a/zephcore/app/RepeaterDataStore.cpp b/zephcore/app/RepeaterDataStore.cpp index 1574011..0a2a755 100644 --- a/zephcore/app/RepeaterDataStore.cpp +++ b/zephcore/app/RepeaterDataStore.cpp @@ -203,7 +203,7 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) { /* Migrate uninitialized backoff_multiplier (0.0 or NaN) to default */ if (prefs.backoff_multiplier == 0.0f || prefs.backoff_multiplier != prefs.backoff_multiplier) { - prefs.backoff_multiplier = 0.5f; + prefs.backoff_multiplier = 0.2f; } LOG_INF("Loaded prefs from %s", path); diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index 05abefb..ffbc52d 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -571,11 +571,15 @@ uint32_t RepeaterMesh::getRetransmitDelay(const mesh::Packet* packet) { } uint32_t RepeaterMesh::getDirectRetransmitDelay(const mesh::Packet* packet) { - uint32_t t = _radio->getEstAirtimeFor( + uint32_t airtime = _radio->getEstAirtimeFor( packet->getPathByteLen() + packet->payload_len + 2); + /* Jitter around Arduino direct factor 0.3 using a per-packet factor + * in the range [0.25, 0.40]. */ + uint32_t factor_milli = (uint32_t)getRNG()->nextInt(250, 401); + uint32_t max_jitter = (5 * airtime * factor_milli) / 1000; /* Floor: give downstream nodes time to finish RX processing - * and return to RX mode before we TX (~20ms settle + jitter) */ - return 20 + getRNG()->nextInt(0, t / 10 + 1); + * and return to RX mode before we TX (~20ms settle + jitter). */ + return 20 + getRNG()->nextInt(0, max_jitter + 1); } bool RepeaterMesh::filterRecvFloodPacket(mesh::Packet* pkt) { diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index 20764c0..3a09cd0 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -124,12 +124,12 @@ void CommonCLI::loadPrefs(const char* path) { _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0.0f, 20.0f); _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0.0f, 2.0f); _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0.0f, 2.0f); - /* Migrate uninitialized pad bytes: NaN or out-of-range → default 0.5. + /* Migrate uninitialized pad bytes: NaN or out-of-range → default 0.2. * 0.0 is valid (disables reactive backoff). Old firmware upgrading * with zeroed pad bytes will get 0.0 = disabled; user can set explicitly. */ if (_prefs->backoff_multiplier != _prefs->backoff_multiplier || _prefs->backoff_multiplier < 0.0f || _prefs->backoff_multiplier > 10.0f) { - _prefs->backoff_multiplier = 0.5f; + _prefs->backoff_multiplier = 0.2f; } _prefs->backoff_multiplier = constrain(_prefs->backoff_multiplier, 0.0f, 2.0f); /* Migrate old AF multiplier (0-9) to duty cycle percentage (0-99) */ diff --git a/zephcore/include/mesh/ContentionTracker.h b/zephcore/include/mesh/ContentionTracker.h index 5fb8571..22d65ad 100644 --- a/zephcore/include/mesh/ContentionTracker.h +++ b/zephcore/include/mesh/ContentionTracker.h @@ -37,7 +37,7 @@ public: float getContentionEstimate() const; - /* sqrt curve: MIN_FLOOD_FACTOR + FLOOD_SCALE * sqrt(est), cap 2.0. + /* Saturating curve: MIN + (MAX-MIN) * est/(est+HALFPOINT). * Returns 0.5 during warmup. */ float getFloodDelayFactor() const; @@ -51,10 +51,10 @@ private: static constexpr uint32_t WINDOW_MS = 10000; /* dupe observation window; covers SF12 2-hop */ static constexpr int EMA_SHIFT = 3; /* alpha = 1/8 */ static constexpr int WARMUP_PACKETS = 4; /* min samples before EMA is trusted */ - static constexpr float MIN_FLOOD_FACTOR = 0.05f; /* floor: near-zero delay in quiet networks */ - static constexpr float FLOOD_SCALE = 0.170f; /* (0.5 - 0.05) / sqrt(15) */ - static constexpr float MAX_FLOOD_FACTOR = 2.0f; /* ceiling: 2x base airtime */ - static constexpr float DEFAULT_BACKOFF_MULT = 0.5f; /* half-airtime per dupe heard */ + static constexpr float MIN_FLOOD_FACTOR = 0.40f; /* sparse mesh baseline */ + static constexpr float MAX_FLOOD_FACTOR = 1.00f; /* dense mesh ceiling */ + static constexpr float FLOOD_EST_HALFPOINT = 4.0f; /* midpoint: factor=0.60 at est=4 */ + static constexpr float DEFAULT_BACKOFF_MULT = 0.2f; /* airtime*0.2 per dupe heard */ static constexpr uint32_t REACTIVE_HARD_CAP_MS = 2000; /* max cumulative reactive extension */ static constexpr uint32_t STALE_MS = 300000; /* 5 min: reset EMA if no traffic */ diff --git a/zephcore/src/ContentionTracker.cpp b/zephcore/src/ContentionTracker.cpp index c89a4d6..ef04b03 100644 --- a/zephcore/src/ContentionTracker.cpp +++ b/zephcore/src/ContentionTracker.cpp @@ -5,7 +5,6 @@ #include #include -#include #include namespace mesh { @@ -156,7 +155,10 @@ float ContentionTracker::getFloodDelayFactor() const float est = getContentionEstimate(); if (est <= 0.0f) return MIN_FLOOD_FACTOR; - float factor = MIN_FLOOD_FACTOR + FLOOD_SCALE * sqrtf(est); + /* Arduino-like center near 0.5 in light contention, rising smoothly + * toward 0.8 as contention increases. */ + float factor = MIN_FLOOD_FACTOR + (MAX_FLOOD_FACTOR - MIN_FLOOD_FACTOR) * + (est / (est + FLOOD_EST_HALFPOINT)); if (factor > MAX_FLOOD_FACTOR) factor = MAX_FLOOD_FACTOR; return factor; }