Autonomously shed inter-region traffic when a repeater's own TX duty
cycle is high, protecting the local cluster during high-traffic events
(issue #2747). Opt-in, off by default.
When TX duty cycle exceeds a configurable threshold, regions are gated
from the outermost layer inward: wildcard '*' first, then the broadest
named regions, always keeping the innermost cluster. Recovery re-enables
regions inside-out once duty cycle drops below (threshold - hysteresis),
with per-step random jitter to desync recovery across repeaters.
Design notes:
- Gating is transient: a new non-persisted rt_flags overlay on
RegionEntry is OR'd with config flags in the forward path, so a
'region save' or reboot mid-event can never make a deny permanent.
- The gate level is re-asserted every check interval, so region edits
or a bulk 'region load' commit (which rebuild entries with cleared
rt_flags) cannot desync the gate state; disabling the feature while
gated lifts the gate immediately.
- Dispatcher::getTxDutyCyclePercent() refreshes the airtime budget
before reading, so recovery still fires when traffic (and TX) stops.
Config (persisted, appended for upgrade compat):
set dc.gate <0|1>, set dc.gate.thresh <1-100>, set dc.gate.hyst <0-50>
get dc.gate[.thresh|.hyst], get dc.gate.status (live duty% + level)
Adds a native gtest suite for the gating logic (test/test_region_gating)
and extends the native test mocks (Arduino/Stream/SHA256) so RegionMap
builds off-device.
Co-Authored-By: Claude Opus 4.8 <[email protected]>