From ee1b95cc9e17b3e1b459921727dd502e0f02feaa Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 22:18:18 -0600 Subject: [PATCH] =?UTF-8?q?forge:=20add=20patch=2013=20=E2=80=94=20quadrat?= =?UTF-8?q?ic=20hop=20delay=20growth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bias = hop * step + hop^2 * BOT_HOP_GROW_MILLIS Bumps hop_step 3000->2000, adds BOT_HOP_GROW_MILLIS=400, raises cap 15000->50000 and TTL 75000->95000. Forces re-default of hop_step on existing bots via BOT_PREFS_VERSION 5->6. Gap between adjacent hops now grows with hop count (3200ms at hop 1 all the way to 8000ms at hop 8), so a far bot reliably has time to hear a near bot's reply through the mesh before its own scheduled fire time -- previous linear bias kept the gap constant at 3000ms, which wasn't enough at 6+ hops where reply propagation takes ~5-10s. --- ...on-radio-firmware-bot-command-parity.patch | 2 +- ...n-firmware-bot-response-coordination.patch | 2 +- ...03-Allow-prefixless-firmware-bot-DMs.patch | 2 +- ...-bot-commands-with-upstream-behavior.patch | 2 +- ...t-coordination-utility-commands-and-.patch | 2 +- ...-speed-up-initial-advert-default-con.patch | 2 +- ...nses-with-request-token-for-inter-bo.patch | 2 +- ...t-suffix-and-auto-learn-known-bot-pa.patch | 2 +- ...p-drop-ack-prefix-from-test-response.patch | 2 +- ...n-coordinator-delays-a-tiny-bit-more.patch | 2 +- ...tartup-initial-flood-at-5s-instead-o.patch | 2 +- ...yte-in-test-path-drop-channel-name-f.patch | 2 +- ...quadratically-wider-gaps-at-higher-h.patch | 94 +++++++++++++++++++ tests/firmware_bot/test_firmware_bot.cpp | 14 ++- 14 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 patches/meshcore/0013-Hop-delay-grows-quadratically-wider-gaps-at-higher-h.patch diff --git a/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch b/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch index 6d7ff21..b434341 100644 --- a/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch +++ b/patches/meshcore/0001-Add-companion-radio-firmware-bot-command-parity.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Fri, 15 May 2026 11:44:21 -0600 -Subject: [PATCH 01/12] Add companion radio firmware bot command parity +Subject: [PATCH 01/13] Add companion radio firmware bot command parity --- .../companion_radio/BotCommandRegistry.cpp | 121 ++ diff --git a/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch b/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch index 52ecfda..4a050f8 100644 --- a/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch +++ b/patches/meshcore/0002-Harden-firmware-bot-response-coordination.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Fri, 15 May 2026 19:58:42 -0600 -Subject: [PATCH 02/12] Harden firmware bot response coordination +Subject: [PATCH 02/13] Harden firmware bot response coordination --- examples/companion_radio/BotCommands.cpp | 2 +- diff --git a/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch b/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch index a45eca3..84777f8 100644 --- a/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch +++ b/patches/meshcore/0003-Allow-prefixless-firmware-bot-DMs.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Fri, 15 May 2026 20:09:58 -0600 -Subject: [PATCH 03/12] Allow prefixless firmware bot DMs +Subject: [PATCH 03/13] Allow prefixless firmware bot DMs --- examples/companion_radio/BotPolicy.cpp | 2 +- diff --git a/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch b/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch index bdccf7c..f4bdb1a 100644 --- a/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch +++ b/patches/meshcore/0004-Align-firmware-bot-commands-with-upstream-behavior.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 04:12:02 +0000 -Subject: [PATCH 04/12] Align firmware bot commands with upstream behavior +Subject: [PATCH 04/13] Align firmware bot commands with upstream behavior --- .../companion_radio/BotCommandRegistry.cpp | 25 +- diff --git a/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch b/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch index 4a55064..5c55eb6 100644 --- a/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch +++ b/patches/meshcore/0005-Add-hop-aware-bot-coordination-utility-commands-and-.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 12:38:13 -0600 -Subject: [PATCH 05/12] Add hop-aware bot coordination, utility commands, and +Subject: [PATCH 05/13] Add hop-aware bot coordination, utility commands, and bounded delays Adds new utility commands (TIME, LORA, ID, NEIGHBORS), neighbor tracking, diff --git a/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch b/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch index 3962ce6..c21e39d 100644 --- a/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch +++ b/patches/meshcore/0006-Mark-bot-adverts-speed-up-initial-advert-default-con.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 18:58:28 -0600 -Subject: [PATCH 06/12] Mark bot adverts, speed up initial advert, default +Subject: [PATCH 06/13] Mark bot adverts, speed up initial advert, default contacts auto-overwrite Three coordination/discoverability fixes: diff --git a/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch b/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch index 8a17b7d..93f8909 100644 --- a/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch +++ b/patches/meshcore/0007-Prefix-bot-responses-with-request-token-for-inter-bo.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 19:33:04 -0600 -Subject: [PATCH 07/12] Prefix bot responses with request token for inter-bot +Subject: [PATCH 07/13] Prefix bot responses with request token for inter-bot suppression Adds a 4-hex request token (low 16 bits of the request fingerprint) as a diff --git a/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch b/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch index 59644d7..92e7157 100644 --- a/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch +++ b/patches/meshcore/0008-Drop-MCBOT-advert-suffix-and-auto-learn-known-bot-pa.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 19:55:36 -0600 -Subject: [PATCH 08/12] Drop [MCBOT] advert suffix and auto-learn known-bot +Subject: [PATCH 08/13] Drop [MCBOT] advert suffix and auto-learn known-bot path Removes BOT_ADVERT_MARKER / writeBotAdvertName / isBotAdvertName from diff --git a/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch b/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch index 60af5a8..bebf530 100644 --- a/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch +++ b/patches/meshcore/0009-Widen-hop-step-drop-ack-prefix-from-test-response.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 20:05:19 -0600 -Subject: [PATCH 09/12] Widen hop-step + drop "ack " prefix from test response +Subject: [PATCH 09/13] Widen hop-step + drop "ack " prefix from test response Two changes: diff --git a/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch b/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch index dbfbef0..05e68ac 100644 --- a/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch +++ b/patches/meshcore/0010-Widen-coordinator-delays-a-tiny-bit-more.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 20:31:12 -0600 -Subject: [PATCH 10/12] Widen coordinator delays a tiny bit more +Subject: [PATCH 10/13] Widen coordinator delays a tiny bit more ~25% bump across the main coordination knobs: - BOT_RESPONSE_DELAY_BASE_MILLIS 1200 -> 1500 diff --git a/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch b/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch index 2bc399b..3691435 100644 --- a/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch +++ b/patches/meshcore/0011-Auto-advert-on-startup-initial-flood-at-5s-instead-o.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 20:49:51 -0600 -Subject: [PATCH 11/12] Auto-advert on startup (initial flood at 5s instead of +Subject: [PATCH 11/13] Auto-advert on startup (initial flood at 5s instead of 120s) BOT_PREFS_INITIAL_FLOOD_ADVERT_MILLIS 120000 -> 5000. The bot now sends diff --git a/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch b/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch index 40fd0a1..0f0aa61 100644 --- a/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch +++ b/patches/meshcore/0012-Spell-out-hops-byte-in-test-path-drop-channel-name-f.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 21:32:04 -0600 -Subject: [PATCH 12/12] Spell out hops/byte in test+path; drop channel name +Subject: [PATCH 12/13] Spell out hops/byte in test+path; drop channel name from test ack Test response (writeAckResponse): diff --git a/patches/meshcore/0013-Hop-delay-grows-quadratically-wider-gaps-at-higher-h.patch b/patches/meshcore/0013-Hop-delay-grows-quadratically-wider-gaps-at-higher-h.patch new file mode 100644 index 0000000..bbd4ef7 --- /dev/null +++ b/patches/meshcore/0013-Hop-delay-grows-quadratically-wider-gaps-at-higher-h.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: cj-vana +Date: Sat, 16 May 2026 22:18:02 -0600 +Subject: [PATCH 13/13] =?UTF-8?q?Hop=20delay=20grows=20quadratically=20?= + =?UTF-8?q?=E2=80=94=20wider=20gaps=20at=20higher=20hop=20counts?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +New formula: bias = hop * step + hop^2 * BOT_HOP_GROW_MILLIS + +Constants: + BOT_HOP_STEP_MILLIS_DEFAULT 3000 -> 2000 + BOT_HOP_GROW_MILLIS NEW = 400 + BOT_HOP_BIAS_MAX_MILLIS 15000 -> 50000 + BOT_RESPONSE_PENDING_TTL_MILLIS 75000 -> 95000 + BOT_PREFS_VERSION 5 -> 6 + +Hop -> bias / gap from previous: + 1 -> 2400ms / -- + 2 -> 5600ms / +3200 + 3 -> 9600ms / +4000 + 4 -> 14400ms / +4800 + 5 -> 20000ms / +5600 + 6 -> 26400ms / +6400 + 7 -> 33600ms / +7200 + 8 -> 41600ms / +8000 + 9+-> 50000ms (cap) + +Previous linear bias gave the same gap between every adjacent hop. At +higher hop counts that wasn't enough: a 7-hop bot's scheduled delay was +only one step longer than the 6-hop bot's, but the 6-hop bot's reply +needed ~5-10s of mesh airtime to propagate one extra hop to the 7-hop +bot. Result: the further bot fired before the closer bot's reply +arrived -> token suppression never triggered. + +Now an N-hop bot has roughly 2*N*400ms more spacing than an (N-1)-hop +bot, which keeps growing as the mesh gets deeper. +--- + examples/companion_radio/BotTypes.h | 9 +++++---- + examples/companion_radio/ResponseCoordinator.cpp | 6 +++++- + 2 files changed, 10 insertions(+), 5 deletions(-) + +diff --git a/examples/companion_radio/BotTypes.h b/examples/companion_radio/BotTypes.h +index 08fe8bc7..f3afa21f 100644 +--- a/examples/companion_radio/BotTypes.h ++++ b/examples/companion_radio/BotTypes.h +@@ -30,14 +30,14 @@ + #define BOT_NEIGHBOR_RECENT_MILLIS (60UL * 60UL * 1000UL) + #define BOT_RESPONSE_DELAY_BASE_MILLIS 1500UL + #define BOT_RESPONSE_DELAY_JITTER_MILLIS 2200UL +-#define BOT_RESPONSE_PENDING_TTL_MILLIS 75000UL ++#define BOT_RESPONSE_PENDING_TTL_MILLIS 95000UL + #define BOT_RESPONSE_RECENT_TTL_MILLIS 30000UL + #define BOT_KNOWN_BOT_FLAG_SUPPRESS_NORMAL 0x01 + #define BOT_SENDER_KEY_PREFIX_LEN 6 + #define BOT_MIN_AUTH_SENDER_KEY_PREFIX_LEN 4 + #define BOT_KNOWN_BOT_LABEL_LEN 12 + #define BOT_PREFS_MAGIC 0x31504642UL +-#define BOT_PREFS_VERSION 5 ++#define BOT_PREFS_VERSION 6 + #define BOT_PREFS_DEFAULT_LOCAL_ADVERT_MILLIS (24UL * 60UL * 60UL * 1000UL) + #define BOT_PREFS_DEFAULT_FLOOD_ADVERT_MILLIS (24UL * 60UL * 60UL * 1000UL) + #define BOT_PREFS_INITIAL_LOCAL_ADVERT_MILLIS 60000UL +@@ -45,9 +45,10 @@ + #define BOT_PREFS_MAX_DELAY_MILLIS 60000U + #define BOT_PREFS_MAX_ADVERT_MILLIS (7UL * 24UL * 60UL * 60UL * 1000UL) + #define BOT_PREFS_SERIALIZED_SIZE 296 +-#define BOT_HOP_STEP_MILLIS_DEFAULT 3000U ++#define BOT_HOP_STEP_MILLIS_DEFAULT 2000U + #define BOT_HOP_STEP_MILLIS_MAX 30000U +-#define BOT_HOP_BIAS_MAX_MILLIS 15000UL ++#define BOT_HOP_GROW_MILLIS 400UL ++#define BOT_HOP_BIAS_MAX_MILLIS 50000UL + + enum BotChannelKind : uint8_t { + BOT_CHANNEL_DM = 0, +diff --git a/examples/companion_radio/ResponseCoordinator.cpp b/examples/companion_radio/ResponseCoordinator.cpp +index 3640f8b6..837b3f12 100644 +--- a/examples/companion_radio/ResponseCoordinator.cpp ++++ b/examples/companion_radio/ResponseCoordinator.cpp +@@ -23,7 +23,11 @@ uint32_t channelDelayBias(BotChannelKind kind) { + + uint32_t hopDelayBias(BotChannelKind kind, uint8_t path_hash_count, uint16_t hop_step_millis) { + if (kind == BOT_CHANNEL_DM) return 0; +- uint32_t bias = (uint32_t)path_hash_count * (uint32_t)hop_step_millis; ++ // Linear + quadratic growth: gap between consecutive hops widens with hop ++ // count, so a far bot always has more slack than a near bot to receive ++ // the near bot's reply before its own scheduled fire time. ++ uint32_t hop = (uint32_t)path_hash_count; ++ uint32_t bias = hop * (uint32_t)hop_step_millis + hop * hop * BOT_HOP_GROW_MILLIS; + if (bias > BOT_HOP_BIAS_MAX_MILLIS) bias = BOT_HOP_BIAS_MAX_MILLIS; + return bias; + } diff --git a/tests/firmware_bot/test_firmware_bot.cpp b/tests/firmware_bot/test_firmware_bot.cpp index eea66c5..db5fbb4 100644 --- a/tests/firmware_bot/test_firmware_bot.cpp +++ b/tests/firmware_bot/test_firmware_bot.cpp @@ -1533,8 +1533,13 @@ static void test_response_coordinator_hop_count_ranking() { base_delay, jitter, hop_step); uint32_t d2 = ResponseCoordinator::responseDelayMillis(two_hop, BOT_COMMAND_PING, request, 0x01020304UL, 0, 0, base_delay, jitter, hop_step); - assert(d1 == d0 + hop_step); - assert(d2 == d0 + (uint32_t)hop_step * 2); + // Linear + quadratic growth: bias(h) = h*step + h*h*grow + assert(d1 == d0 + (uint32_t)hop_step + BOT_HOP_GROW_MILLIS); + assert(d2 == d0 + (uint32_t)hop_step * 2 + 4 * BOT_HOP_GROW_MILLIS); + // Gap between hops widens as hop count rises: + uint32_t gap_0_to_1 = d1 - d0; + uint32_t gap_1_to_2 = d2 - d1; + assert(gap_1_to_2 > gap_0_to_1); uint32_t alt_step = 2000; uint32_t d0_alt = ResponseCoordinator::responseDelayMillis(near, BOT_COMMAND_PING, request, 0x01020304UL, 0, 0, @@ -1542,7 +1547,7 @@ static void test_response_coordinator_hop_count_ranking() { uint32_t d1_alt = ResponseCoordinator::responseDelayMillis(one_hop, BOT_COMMAND_PING, request, 0x01020304UL, 0, 0, base_delay, jitter, alt_step); assert(d0_alt == d0); - assert(d1_alt == d0 + alt_step); + assert(d1_alt == d0 + alt_step + BOT_HOP_GROW_MILLIS); BotMessage dm = make_message("dm", "!ping"); dm.channel_kind = BOT_CHANNEL_DM; @@ -1570,7 +1575,8 @@ static void test_response_coordinator_hop_bias_cap() { uint32_t dmany = ResponseCoordinator::responseDelayMillis(many_hops, BOT_COMMAND_PING, request, 0x01020304UL, 0, 0, base_delay, jitter, hop_step); assert(dmany == d0 + BOT_HOP_BIAS_MAX_MILLIS); - assert((uint32_t)many_hops.path_hash_count * hop_step > BOT_HOP_BIAS_MAX_MILLIS); + uint32_t hop_u32 = (uint32_t)many_hops.path_hash_count; + assert(hop_u32 * (uint32_t)hop_step + hop_u32 * hop_u32 * BOT_HOP_GROW_MILLIS > BOT_HOP_BIAS_MAX_MILLIS); assert(dmany + BOT_RESPONSE_DELAY_JITTER_MILLIS < BOT_RESPONSE_PENDING_TTL_MILLIS); }