← RFC

Loomwave Protocol Specification — MAC Layer

Stage 3 / Checkpoint C deliverable. Status: Draft normative spec. Date: 2026-06-09. Depends on: packet-format.md (BEACON/SLOTMAP/CELL_DESC fields, P plane bit, TC). Validated by: sim-result-v3 (TDMA hidden-node elimination), v4 (adaptive modulation/reuse), v6 (epoch-hash fallback ordering), v7 (scenario suite).

Normative language per RFC 2119/8174 (packet-format.md preamble). This document specifies when a node may transmit and how the channel is shared. It defines two cooperating planes — a scheduled (TDMA) data plane and a contention plane — and the rules for switching between a coordinated and a coordinator-free regime without losing the network.


1. Overview and the two-plane model

Loomwave's central result (hidden-node-analysis.md, sim-result-v1/v3) is that carrier sense cannot fix the hidden-node problem — a node cannot defer to a carrier it cannot hear — so the data plane MUST NOT rely on CSMA for collision avoidance at scale. The MAC is therefore a hybrid:

The plane of a given frame is carried in the P bit (packet-format.md §3): P=1 scheduled, P=0 contention. A node MUST transmit a frame only in a region of the superframe that matches its P bit and the rules below.

2. Superframe structure

Under a coordinator, time is divided into superframes of length FRAME_T ms (announced in the BEACON, packet-format.md §8.1). Each superframe is:

|<--------------------------- FRAME_T ms --------------------------->|
| BEACON | G |  scheduled data slots (SLOTMAP)        | contention window |
|  (B0)  |   | s0 | s1 | s2 | ... | s(N-1)            | c0 | c1 | ... |
          ^guard         ^each slot = ToA(cell SF, max frame) + guard
  1. BEACON slot (B0): the coordinator transmits exactly one BEACON at superframe start. It anchors time (§3), names the cell modulation CELL_DESC, and carries the SLOTMAP and contention-window pointer NEXT_PTR.
  2. Scheduled region: SLOTMAP_LEN slots, each owned by the 24-bit address at that index. Slot width = airtime of a maximum-size frame at the cell's (SF, BW) plus one guard interval G (§3). An entry of 0x000000 marks an open slot usable as extra contention capacity.
  3. Contention window: NEXT_PTR-delimited tail of the superframe for the contention plane (§5).

A coordinator MUST size the scheduled region so the superframe (beacon + slots + contention window) does not exceed FRAME_T, and MUST keep the contention window ≥ a floor (§5.3) so newly arrived nodes always have a way in.

Geometry is computed from airtimes, not chosen (Stage-4 field rule). The contention window MUST satisfy CW ≥ ToA(largest contention-plane frame) + G at the cell's modulation — found the hard way when a 135 B identity ADVERT (~670 ms at SF9/BW125) exceeded an entire 4-slot superframe's CW and silently never delivered. Long contention frames (identity ADVERTs) SHOULD additionally be restricted to the early contention micro-slots so their tails stay inside the receiver's window.

2.1 Slot-fit invariant (bug #60)

A slot's width MUST be ≥ the airtime of the largest frame that may be granted into it, plus G. Sizing a slot to one fixed (telemetry) payload is non-conformant: an oversized frame then overruns into the next member's slot, and the coordinator silently drops both the message and its delivery-ACK. This is exactly the class of failure that bug #60 produced in the field — a ≥56 B message frame (~370 ms+ at SF9) overran a 300 ms telemetry-sized slot, so every cross-cell message was lost while telemetry (35 B / ~280 ms) fit and masked it.

The as-built US915/SF9-BW125 fleet therefore distinguishes two slot classes and pins these constants (firmware client/src/main.cpp, client/src/loomwave_msg.h; infra infra/crates/infrad/src/main.rs):

constant value meaning
SLOT_W (message slot) 900 ms holds a 162 B message frame (~874 ms ToA @ SF9) + guard
ENV_MAX_BODY 112 B max message body → 162 B frame → ≤ SLOT_W
FRAME_T 10 400 ms fits beacon + member slots + a message-sized downlink + the ~750 ms identity ADVERT, and leaves a backbone relay (role B, beacon staggered by FRAME_T/2) a full half-frame

Implementations MUST enforce the invariant at compile time, not by convention: the firmware and the Rust infra both carry static_assert/const _: () = assert!(…) guards that ToA(MAX_DATA_FRAME) ≤ SLOT_W and that the per-half frame budget (beacon + slots + downlink + advert) ≤ FRAME_T/2. A shared lora_toa_ms_sf9() is unit-tested to cross-validate the sim, firmware, and infra airtime numbers against one another (see sim/tests/test_slot_fit.py, bug #61).

3. Time synchronization without GPS

The BEACON is the time reference; GPS is an enhancement, never required (core goal #4).

Rationale. Slot discipline needs only relative alignment within one superframe, re-anchored every beacon — so absolute time (GPS) is unnecessary. The guard absorbs one inter-beacon drift, not unbounded free-running drift.

4. Scheduled (TDMA) data plane

4.1 Spatial reuse (capacity)

The coordinator MAY assign the same slot index to two owners whose cells do not overlap, so they transmit concurrently — the spatial-TDMA capacity mechanism. sim-result-v4 showed this is robust to imperfect separation because LoRa capture lets the stronger signal survive partial overlap; reuse is clean once owners are separated by ≳ 1.5× the cell range. The coordinator is the sole authority for reuse assignment (it holds the topology, routing-spec.md); a client never self-selects reuse.

4.2 Open slots

0x000000 SLOTMAP entries are open slots: any node MAY contend in them using the §5.2 epoch-hash rule (not CSMA). They give the schedule elasticity for bursty load without a REGISTER round-trip.

5. Contention plane

The contention window (and open slots) carry: BEACON-independent discovery, ADVERTs, REGISTERs, and best-effort/un-slotted DATA (e.g. a node that has not yet registered).

5.0 The coordinator obeys its own schedule

All coordinator-originated traffic other than the BEACON itself (identity ADVERTs, LSAs, coordinator-claims) MUST be transmitted through the contention window like any other node's contention traffic — never in the scheduled region. (Stage-4 field bug: a coordinator advert sent immediately after the beacon transmitted straight through a client's slot 0, producing a perfect every-Nth-frame loss signature.) Implementations with blocking receive paths MUST chunk RX so the coordinator's own scheduled transmissions fire on time.

5.1 No naive CSMA at hubs

A node MAY perform a brief listen-before-talk as a politeness optimization where it can hear peers, but the MAC MUST NOT depend on it for correctness, because hidden senders defeat it (sim-result-v1). Collision avoidance in this plane comes from epoch-hash slotting (§5.2), not sensing.

5.2 Epoch-hash-slotted ALOHA

The contention window is divided into C micro-slots. A node wishing to send chooses its micro-slot:

slot = H( SRC_ADDR ‖ EPOCH ‖ attempt ) mod C

where EPOCH is the current beacon epoch (or the fallback epoch, §6) and attempt increments on each retry. Because the hash spreads independent senders across slots without requiring them to hear each other, it cuts the birthday-collision rate sharply below slotted-ALOHA-by-random-choice and far below CSMA at a hidden-node hub (sim-result-v6). On collision, a node increments attempt and re-hashes (randomized exponential-style backoff bounded by TC).

5.3 Contention-window floor

The coordinator MUST keep C·micro_slot ≥ CW_FLOOR so unregistered/new nodes always have entry capacity; CW_FLOOR scales with observed join rate.

6. Coordinator-free fallback regime

When no BEACON is heard (cold start, or coordinator lost per §3), nodes operate entirely in the contention plane using an epoch-hash schedule keyed by a shared time-free epoch:

7. Mode and handoff (the fail-safe path)

Switching between scheduled and fallback regimes is the MAC's graceful-degradation guarantee. One frame structure spans both (a superframe with an empty SLOTMAP is a pure-contention frame), so the transition is continuous, not a re-association.

8. Adaptive modulation — INFRA as modulation authority

Per design-decision.md §5 and sim-result-v4:

This is the normative form of the Checkpoint-B rule (design-decision.md §5) that structurally prevents the Meshtastic downlink_enabled congestion failure.

10. Airtime-budget governance

11. Boundary clients (open → resolution path)

A client that hears two cells (a cell boundary) is the chief open MAC item from design-decision.md §5/§8. v1 resolution: the client associates with the cell it hears at the higher link margin (per its LINK_REPORTS), and the two coordinators, aware of the shared client via the INFRA link-state plane, MUST NOT assign it overlapping obligations. A client MUST NOT transmit scheduled frames into more than one cell in the same superframe. Exact hysteresis thresholds (to avoid ping-pong) are to be tuned in a follow-up sim run before firmware; flagged for Checkpoint C discussion.

12. Conformance (MAC)

A conformant node MUST: (1) transmit P=1 only in an owned/open slot under a valid current schedule (§4); (2) never depend on carrier sense for correctness (§5.1); (3) use epoch-hash slotting, never static-hash, in contention/fallback (§5.2, §6); (4) enter fallback after BEACON_MISS_MAX misses and rejoin on a valid BEACON (§7); (5) transmit at the announced cell modulation when scheduled (§8); (6) cap and schedule (never raw-flood) backhaul-injected traffic (§9); (7) shed by TC above the utilization ceiling (§10). A coordinator additionally MUST keep the superframe within FRAME_T, maintain the contention-window floor, and own all reuse/modulation/admission decisions.


Builds on packet-format.md. Pairs with routing-spec.md (where schedules/topology come from) and security-spec.md (beacon signatures, epoch keying). Reviewed at Checkpoint C before firmware.

Appendix A — Implementation notes from Stage-4 hardware bring-up (non-normative)