Iter 21 — ultra-opt for protocol correctness across the two production
decoders. Pin the same 32-byte canonical hex in both Python and Rust
tests; if either decoder drifts from the wire, ONE of the tests starts
failing — and it's clear which side moved.
Canonical packet: COM9 sync-pkt #1 from §A0.12 live capture, expressed
as exact little-endian bytes:
10a111c5 09 01 06 00 magic + node + ver + flags + rsvd
f26db70100000000 local_us = 28_798_450
c5aca50100000000 epoch_us = 27_634_885
1400000000000000 sequence = 20 + reserved
Python test:
archive/v1/tests/unit/test_esp32_binary_parser.py::TestSyncPacketParser
::test_canonical_wire_bytes_match_rust_decoder
— decodes the pinned hex, asserts every field including the §A0.10
1,163,565 µs offset.
Rust test:
v2/crates/wifi-densepose-hardware/src/sync_packet.rs::tests
::canonical_wire_bytes_match_python_decoder
— decodes the same bytes, asserts the same fields, then re-encodes
via to_bytes() and asserts the round-trip produces the EXACT same
32 bytes. So this also catches drift in the Rust encoder.
Test counts after this iter:
Rust sync_packet: 15/15 green (was 14)
Python SyncPacketParser: 7/7 green (was 6)
Branch contract: if a future PR changes the firmware wire format, BOTH
tests must be updated atomically with the new canonical hex. CI will
gate this naturally.
Co-Authored-By: claude-flow <ruv@ruv.net>
Iter 20 — defensive ultra-opt: one test that exercises the entire
iter 14→17 chain in a single assertion, so any future refactor that
breaks the contract surfaces as a single, named regression instead of
14 unit-test diffs to triangulate.
1. firmware emits sync packet (bytes built here as a stand-in)
2. host decodes via SyncPacket::from_bytes — assert round-trip
3. a CSI frame arrives 100 sequences later (≈ 5 s @ 20 fps)
4. mesh_aligned_us_for_sequence recovers the mesh timestamp
5. cross-check: same value via raw apply_to_local
Asserts mesh_us == sync.epoch_us + 5_000_000 µs exactly, plus both
paths (sequence-interpolation + direct local→mesh) agree byte-for-byte.
Result: 14/14 sync_packet tests pass, full wifi-densepose-hardware
crate at 136/136 (no regression from iter 1-19). Contract for any
ADR-029/030 multistatic fusion consumer is now defended by a test that
fails loud if either piece of the chain drifts.
Co-Authored-By: claude-flow <ruv@ruv.net>
Iter 17 — closes the per-frame mesh-time loop for ADR-018 CSI frames
that carry no per-frame local_us field (the v1 wire format reserves no
slot — see WITNESS-LOG-110 §A0.11).
Math: pair the frame's sequence number against the sync packet's
sequence high-water + an assumed CSI frame rate. Δframes × 1/fps
estimates the node-local delta from the sync, then apply_to_local
recovers the mesh epoch.
SyncPacket::mesh_aligned_us_for_sequence(frame_seq: u32, fps_hz: f64) -> u64
3 new unit tests (13 total in sync_packet::tests, all green):
* mesh_aligned_for_sequence_identity_at_sync_point — at sync.sequence
returns sync.epoch_us exactly
* mesh_aligned_for_sequence_extrapolates_forward — 20 frames @ 20 fps
extrapolates by exactly 1 s
* mesh_aligned_for_sequence_handles_seq_wraparound — u32 sequence
wrap doesn't jump backward by 2^32 (wrapping_sub guards it)
NodeState hook:
NodeState::mesh_aligned_us_for_csi_frame(frame_sequence: u32) -> Option<u64>
Wraps the SyncPacket method, defaults fps_hz=20.0 (matches the
firmware's CSI_MIN_SEND_INTERVAL_US-implied ceiling), enforces the
same 9 s staleness gate as mesh_aligned_us.
cargo check -p wifi-densepose-sensing-server --no-default-features → green.
cargo test -p wifi-densepose-hardware sync_packet → 13/13, 122 filtered.
Downstream ADR-029/030 multistatic fusion code can now do:
if frame.adr018_flags.ieee802154_sync_valid {
if let Some(mesh_us) = ns.mesh_aligned_us_for_csi_frame(frame.sequence) {
// pair this frame with frames from sibling nodes by mesh_us
}
}
Co-Authored-By: claude-flow <ruv@ruv.net>
Iter 16 closes the math loop and updates ADR-110 to reflect the full
P1-P10 sprint outcome (per user request).
Code (the math layer that converts the iter 15 stored sync into a
per-frame mesh-aligned timestamp):
wifi-densepose-hardware:
SyncPacket::apply_to_local(local_at_frame_us: u64) -> u64
Pure integer math: offset = epoch - local; mesh = local_at_frame + offset.
3 new unit tests (10 total, all green):
- apply_to_local_recovers_packet_epoch (identity at the packet's local_us)
- apply_to_local_preserves_inter_frame_delta (Δlocal == Δmesh)
- apply_to_local_on_leader_is_near_identity (leader offset ≈ 0)
wifi-densepose-sensing-server:
NodeState::mesh_aligned_us(local_at_frame_us: u64) -> Option<u64>
Returns the recovered mesh timestamp using the most-recent sync
packet, or None if no sync seen or last one older than 9 s
(3× firmware VALID_WINDOW_MS = 9 s staleness gate).
cargo check -p wifi-densepose-sensing-server --no-default-features
→ green
ADR-110 substantial rewrite (per user "update adr 110 with details"):
- Status line: P1-P10 complete, firmware-side substrate closed at v0.7.0.
- Front matter now lists all 4 firmware releases + witness link.
- Phase table grows a P10 row capturing the v0.6.8 / v0.6.9 / v0.7.0
arc (EMA smoother + sync packet + bit-4 wire-fix + host crates).
- New §4.1 — /loop 5m SOTA sprint summary table (iters 1-16, 4 releases,
17 commits, 13 unit tests, what shipped each iter).
- New §4.2 — measured numbers table with 99.56% RX, 104.1 µs smoothed
stdev, 3.95x suppression, 1.4 ppm crystal skew, etc — every cell
backed by a witness §A0.x entry and a preserved bench log.
- New §4.3 — host-side production surface listing (sync_packet.rs +
sensing-server NodeState + Python parser, with file paths).
- §5 open question on 802.15.4 channel resolved (Kconfig, default ch26
not ch15, with the witness §D1 rationale).
- New §6 — explicit scope of what's outside this ADR (multistatic fusion
math in ADR-029/030, hardware-gated measurements needing INA / 11ax AP,
IDF upstream fixes pending).
Co-Authored-By: claude-flow <ruv@ruv.net>
Iter 14 — moves the v0.7.0 Python stub into the Rust production tree
so the sensing-server can decode incoming UDP datagrams by leading
magic and apply mesh-aligned timestamps to in-flight CSI frames.
Module: v2/crates/wifi-densepose-hardware/src/sync_packet.rs
Public surface (re-exported from the crate root):
- SyncPacket — 32-byte decoded packet
- SyncPacketFlags — bit0=leader, bit1=valid, bit2=smoothed
- SYNC_PACKET_MAGIC = 0xC511A110, SYNC_PACKET_SIZE = 32
Tests (all 7 passing, plus 122 existing hardware-crate tests still pass):
* follower_typical_packet_roundtrips — reproduces COM9 sync-pkt #1
from §A0.12, including the 1,163,565 µs offset §A0.10 measured
* leader_packet_has_local_close_to_epoch — COM12 leader case
(flags=0x03, epoch ≈ local, offset = -7 µs call-stack only)
* magic_mismatch_is_typed_error
* short_packet_is_typed_error
* all_flag_combinations_roundtrip — every (leader,valid,smoothed) triple
* sync_and_csi_magics_differ — host can dispatch by leading u32
* wire_size_constant_is_correct
Uses the existing ParseError variants (InvalidMagic, InsufficientData) so
the sensing-server's dispatch code can treat sync-packet decode failures
the same way it treats CSI frame decode failures.
Co-Authored-By: claude-flow <ruv@ruv.net>
The ESP32 firmware multiplexes several wire packet types onto the same
UDP port as ADR-018 raw CSI frames (magic 0xC5110001):
0xC5110002 ADR-039 edge vitals (32 B)
0xC5110003 ADR-069 feature vector
0xC5110004 ADR-063 fused vitals
0xC5110005 ADR-039 compressed CSI
0xC5110006 ADR-081 feature state
0xC5110007 ADR-095/#513 temporal classification
Esp32CsiParser only knew 0xC5110001, so the standalone `aggregator`
binary printed "parse error: Invalid magic: expected 0xc5110001, got
0xc5110002" for every vitals packet. No CSI data was lost — just noise.
Add the sibling-magic constants + ruview_sibling_packet_name(), classify
recognized siblings before the CSI-frame length gate, and return a new
ParseError::NonCsiPacket { magic, kind } instead of InvalidMagic. The
`aggregator` CLI now skips them quietly (logs "[skipped ADR-039 edge
vitals packet — not a CSI frame]" only with --verbose); the library-level
CsiAggregator already dropped them silently. New regression tests cover
all seven magics.
Closes#517
Co-Authored-By: claude-flow <ruv@ruv.net>
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/)
without any sibling under rust-port/ that warranted the extra level.
Move the whole workspace up to v2/ to match v1/ (Python) at the same
depth and shorten every cd / build command across the repo.
git mv preserves history for all tracked files. 60 files updated for
path references (CI workflows, ADRs, docs, scripts, READMEs, internal
.claude-flow state). Two manual fixes for relative-cd paths in
CLAUDE.md and ADR-043 that became wrong after the depth change
(cd ../.. → cd ..).
Validated:
- cargo check --workspace --no-default-features → clean (after target/
nuke; the gitignored target/ was carried by the OS rename and had
hard-coded old paths in build scripts)
- cargo test --workspace --no-default-features → 1,539 passed, 0 failed,
8 ignored (same totals as pre-rename)
- ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm)
After-merge follow-up: contributors should `rm -rf v2/target` once and
let cargo regenerate from the new path.