Summary
Build the deterministic state model and authenticated control protocol for adaptive FHSS hopset changes. This is the protocol foundation for receiver-driven exclusion and later autonomous TX decisions; it does not choose which channels are bad.
Follow-up to #153 / #262. The fixed-hop implementation and measured behavior are documented in docs/fhss.md; adaptive exclusion is its stated next step.
Design
Keep the configured base hopset immutable. Every adaptive state identifies active channels by stable base-hopset bit positions:
struct HopsetState {
uint32_t epoch;
uint32_t generation;
uint64_t active_mask;
uint64_t activate_round;
};
Derive each round's permutation from (schedule_key, generation, round, active_mask). Including generation ensures a mask change produces a fresh keyed order instead of merely exposing a filtered form of the previous permutation. The schedule remains stateless by absolute slot.
Add versioned authenticated messages:
HopsetProposal: epoch, proposed generation/mask, observation count, activation round, reason bitmap, authentication tag.
HopsetCommit: epoch, generation, mask, activation round, authentication tag.
HopsetStatus: epoch, current generation/mask/round, authentication tag.
TX is the schedule authority: RX proposes; TX validates policy constraints and repeatedly commits; both activate on the announced future round. Derive separate schedule and control keys from DEVOURER_HOP_SEED with explicit domain separation. A schedule fingerprint is not authentication.
Commits must be announced at least 8 rounds ahead and repeated until activation. Acquisition always scans the immutable base hopset; sync/status markers advertise generation and mask fingerprint so an RX that missed the transition can recover. Add infrequent keyed base-hopset rendezvous/probe slots if hardware testing shows acquisition cannot reliably find a reduced set.
Reject stale epochs, replayed/non-monotonic generations, unknown base-hopset fingerprints, activation outside the allowed window, and masks below the configured minimum diversity.
Implementation
- Extend
HopSchedule (or a sibling header-only module) with generation/mask-aware stateless lookup and stable base-hopset indexing.
- Add dependency-free encode/decode/MAC helpers for the three control messages; do not change caller-owned FEC payload bytes.
- Extend FHSS sync markers with generation plus active-mask fingerprint while retaining versioned backward rejection.
- Add structured events for proposal, commit, activation, rejection reason, generation mismatch, and recovery.
- Document the state machine and wire format in
docs/fhss.md.
Acceptance criteria
- Known-answer tests pin schedule output for fixed key/generation/mask/slot tuples.
- Every adaptive round covers every active channel exactly once.
- Two independent instances agree after direct join at a nonzero generation.
- Tests cover lost, duplicated, reordered, forged, and replayed control frames; TX/RX restart; generation rollover policy; invalid masks; and missed pre-activation commits.
- A process-level test demonstrates synchronized future-round activation and recovery after one peer misses the commit window.
- Existing sequential/keyed fixed-hop behavior remains unchanged when adaptive mode is disabled.
Out of scope
- Deciding which channel to exclude.
- RF feedback transport policy.
- Autonomous TX sensing.
Summary
Build the deterministic state model and authenticated control protocol for adaptive FHSS hopset changes. This is the protocol foundation for receiver-driven exclusion and later autonomous TX decisions; it does not choose which channels are bad.
Follow-up to #153 / #262. The fixed-hop implementation and measured behavior are documented in
docs/fhss.md; adaptive exclusion is its stated next step.Design
Keep the configured base hopset immutable. Every adaptive state identifies active channels by stable base-hopset bit positions:
Derive each round's permutation from
(schedule_key, generation, round, active_mask). Includinggenerationensures a mask change produces a fresh keyed order instead of merely exposing a filtered form of the previous permutation. The schedule remains stateless by absolute slot.Add versioned authenticated messages:
HopsetProposal: epoch, proposed generation/mask, observation count, activation round, reason bitmap, authentication tag.HopsetCommit: epoch, generation, mask, activation round, authentication tag.HopsetStatus: epoch, current generation/mask/round, authentication tag.TX is the schedule authority: RX proposes; TX validates policy constraints and repeatedly commits; both activate on the announced future round. Derive separate schedule and control keys from
DEVOURER_HOP_SEEDwith explicit domain separation. A schedule fingerprint is not authentication.Commits must be announced at least 8 rounds ahead and repeated until activation. Acquisition always scans the immutable base hopset; sync/status markers advertise generation and mask fingerprint so an RX that missed the transition can recover. Add infrequent keyed base-hopset rendezvous/probe slots if hardware testing shows acquisition cannot reliably find a reduced set.
Reject stale epochs, replayed/non-monotonic generations, unknown base-hopset fingerprints, activation outside the allowed window, and masks below the configured minimum diversity.
Implementation
HopSchedule(or a sibling header-only module) with generation/mask-aware stateless lookup and stable base-hopset indexing.docs/fhss.md.Acceptance criteria
Out of scope