Iter 15 — converts the iter 14 SyncPacket decoder from "shipped" to
"consumed" by wiring it into the sensing-server UDP receive loop.
Wiring:
- Cargo.toml gains wifi-densepose-hardware = path = "../wifi-densepose-hardware"
to pull in the SyncPacket decoder + SYNC_PACKET_MAGIC dispatch constant.
- NodeState gains two new fields:
latest_sync: Option<SyncPacket> — the parsed packet
latest_sync_at: Option<std::time::Instant> — staleness clock
- udp_receiver_task now magic-dispatches every incoming datagram against
SYNC_PACKET_MAGIC (0xC511A110) before falling through to the existing
ADR-039 vitals / ADR-040 WASM / ADR-018 CSI parsers. Same Option-returning
pattern as the other parsers, so future packet types slot in cleanly.
When a sync packet arrives:
* write-lock state, lookup-or-create NodeState by node_id
* stash the SyncPacket + Instant::now() on the node
* debug-log node, leader/valid/smoothed flags, sequence, offset_us
* continue (don't fall through — we know it's not a CSI frame)
Downstream multistatic CSI fusion now has a documented landing pad: any
CSI frame with byte 19 bit 4 set looks up the matching NodeState, applies
ns.latest_sync.epoch_us + (now_local - ns.latest_sync.local_us) to get a
mesh-aligned timestamp. Implementation of that fusion math is the next
ADR-029/030 layer (wifi-densepose-signal).
Verification:
- cargo check -p wifi-densepose-sensing-server --no-default-features → green
- cargo test -p wifi-densepose-hardware sync_packet → 7/7 pass, 122 filtered
- Zero behavioral change for nodes that don't emit sync packets — the
dispatch only fires on magic match.
Co-Authored-By: claude-flow <ruv@ruv.net>