Skip to content

N-channel dwell-1 hopping — closes the standard-driver FHSS series (exp 5/5)#322

Merged
josephnef merged 1 commit into
masterfrom
n-channel-hopping
Jul 20, 2026
Merged

N-channel dwell-1 hopping — closes the standard-driver FHSS series (exp 5/5)#322
josephnef merged 1 commit into
masterfrom
n-channel-hopping

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Closes #273. Final experiment of the standard-driver FHSS series.

#273 was framed around escaping MCC's two-context limit by rewriting the inactive MCC context while the active one carries traffic ("ping-pong"). That needs a running MCC scheduler, which #271 proved never engages here — and the premise is moot: the exp-4 dwell-1 data plane runs over the ordinary synchronous channel switch, which has no context limit, so N channels is just channel_index(slot, N). This delivers exp-5's real goal — arbitrary N-channel dwell-1 hopping with the same rigor — over that data plane, and records the MCC ping-pong mechanism as a no-go inherited from #271.

Almost entirely reuse

dwelltx generalises to any N (DEVOURER_DWELL_CHANNELS=c0,c1,…; HopSchedule::channel_index(slot, n) and its Fisher-Yates permutation were already N-general — no library change). dwell1_ab.py spawns one oracle per channel and attributes each slot to the strongest-RSSI oracle, so near-field adjacent-channel leakage isn't miscounted as a wrong-channel while a true mis-air still surfaces (the wrong oracle would then be strongest). Keyed attribution reuses the SipHash-2-4 + Fisher-Yates port in tests/hop_rx_probe.py — the Python order matches the C++ on-air order slot-for-slot. dwell1_parity.sh sweeps hop_parity_check.sh across AGC buckets.

On-air results (RTL8822BU DUT, four oracles incl. an 8852BU)

schedule slots delivered correct channel wrong-channel dup
3-channel (36/40/44) 100 020 96.7 % 0 0
4-channel (36/40/44/48) 100 020 95.4 % 0 0
  • Zero wrong-channel / mixed-context over 200 k dwell-1 slots. Per-channel 92–98 %; the spread is oracle sensitivity (near-field oracles saturate at full power, so the DUT runs a reduced flat TXAGC index), not the data plane.
  • Late-enqueue fault: 613/614 slots dropped-late, zero mis-aired — deterministic overload.
  • Keyed determinism: keyed 3-channel delivered wrong-channel-free; a single receiver acquires the keyed schedule (798 tracked decodes) and re-acquires from a cold restart (784), no phase inversion.
  • Register parity 6/6 from UNII-1 (bucket 1) to targets including UNII-3 (bucket 3): a fast hop is register-identical to a full set across AGC buckets — channel/bandwidth/calibration change as one generation.

Benchmark — flat scaling

channels switch med/p99 (µs) admit jitter p99−p1 (µs) delivery wrong-ch
2 2107 / 2346 132 96.2 % 0
3 2108 / 2341 109 96.7 % 0
4 1952 / 2311 123 95.4 % 0

Transition cost, jitter and delivery are flat from 2 to 4 channels — a hop is one synchronous retune regardless of how many channels the schedule spans, so N-channel hopping carries no per-channel penalty. The ping-pong-MCC / fixed-A/B-MCC rows are N/A: MCC never runs (#271).

Go/no-go: N-channel dwell-1 hopping works and needed none of MCC's context juggling; the ping-pong mechanism is a no-go. This closes the five-experiment series — the synchronous switch plus a caller-side admission scheduler is the deterministic per-slot FHSS data plane, scaling cleanly to arbitrary N. Full note: docs/n-channel-hopping.md.

🤖 Generated with Claude Code

…xp 5/5)

#273 was framed as escaping MCC's two-context limit by rewriting the inactive
context ("ping-pong"). That needs a running MCC scheduler, which #271 proved
never engages here — and the premise is moot: the exp-4 dwell-1 data plane runs
over the synchronous channel switch, which has no context limit, so N channels
is just channel_index(slot, N). This delivers exp-5's real goal over that data
plane and records the MCC ping-pong mechanism as a no-go inherited from #271.

dwelltx generalises to any N (DEVOURER_DWELL_CHANNELS=c0,c1,...; HopSchedule was
already N-general, no library change). dwell1_ab.py spawns one oracle per
channel and attributes each slot to the strongest-RSSI oracle so near-field
adjacent-channel leakage isn't miscounted as a wrong-channel while a true
mis-air still surfaces; keyed attribution reuses hop_rx_probe.py's SipHash port
(Python order matches the C++ on-air order slot-for-slot). dwell1_parity.sh
sweeps hop_parity_check across AGC buckets.

On-air (RTL8822BU DUT, four oracles incl. an 8852BU): 3-channel and 4-channel
each 100,020 dwell-1 slots with ZERO wrong-channel and zero duplicates
(96.7 % / 95.4 % delivered); late-enqueue drops deterministically (613/614),
zero mis-aired; keyed 3-channel delivered wrong-channel-free with a single
receiver acquiring + re-acquiring from cold restart; register parity 6/6 from
UNII-1 to UNII-3 (cross AGC bucket). Benchmark: switch cost (~2 ms), admit
jitter (~110-130 us) and delivery are flat from 2 to 4 channels — no
per-channel penalty; MCC/ping-pong rows N/A (never runs). Full note:
docs/n-channel-hopping.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef
josephnef merged commit d2932f9 into master Jul 20, 2026
18 checks passed
@josephnef
josephnef deleted the n-channel-hopping branch July 20, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standard-driver FHSS experiment 5/5: N-channel hopping via inactive MCC context rewrite

1 participant