← RFC

Guided roam — announced neighbor-channel plan (#65 multi-cell)

Status: design (2026-06-22). Lets a client hand off to an adjacent cell by retuning directly to a known neighbour PHY instead of blind-scanning the (channel × SF) grid.

Why

A client today finds a cell by blind-scanning the grid. The discovery-latency result (discovery-latency-result.md) measured that at E[L] ≈ (G−1)/2·D49 s at 3×3, 139 s at 4×6 — vs a ~1.1 s constant when the client knows where to look. In a multi-cell deployment (the two-tower setup, or a campus) a moving client must hand off between cells; without a neighbour plan every handoff pays the linear blind-scan tax. The sim's explicit conclusion: ship the guided plan before enabling multi-cell roaming. This is that plan.

Wire format — ADV_NEIGHBORS advert

A new signed advert subtype the coordinator broadcasts periodically in the CW (like the identity advert), alongside the existing ADV_IDENTITY/ADV_COORD_CLAIM/ADV_LSA/ADV_NAME:

pub const ADV_NEIGHBORS: u8 = 0x05;
body: n(1) ‖ n × { freq_offset: u16 BE, sf: u8 }    // 3 bytes per neighbour cell

freq_offset is the channel-plan offset (ch_offset_to_hz, base 900 MHz / 100 kHz; offset 0 unused). The advert is signed by the coordinator's Ed25519 key (via Advert.sig), so a client trusts the neighbour list from the coordinator it has already verified. The client does NOT need the neighbours' identities in advance — on retuning to a neighbour PHY it hears that neighbour's OWN signed beacon + identity advert and joins it as a normal (but FAST) discovery.

Coordinator

Knows its neighbours from LW_NEIGHBORS="freqHz:sf,freqHz:sf,..." (static config for v1; auto-derivation from the #64 wired backbone — coordinators already exchange state over the LAN — is the follow-on). Each listed neighbour is mapped to { ch_hz_to_offset(freqHz), sf } and broadcast in the periodic ADV_NEIGHBORS. The advert costs airtime only when neighbours are configured (none → never sent).

Client

Verification (bench, two real cells)

Two cells A @915 / B @905 (the two-tower channel plan), client joined to A with B configured as A's neighbour. KILL cell A → the client should retune directly to 905 and re-join B in ~1–2 s (the guided path), vs the tens-of-seconds blind grid scan it would otherwise pay. Measure the re-acquisition time both ways. The two tower nodes (.176 @915, .108 @905, 20 ft apart) make this a real multi-cell test.

Reuse map