Closes ADR-106 open item #1: server now receives the real WiFi RX
timestamp from the sensor's hardware controller instead of stamping
on receipt with SystemTime.
FW (csi_collector.c csi_serialize_frame):
Append uint32_t = info->rx_ctrl.timestamp (µs since FW boot,
monotonic per ESP-IDF docs) as 4 trailing bytes after I/Q data.
Header layout unchanged → old server parsers still work (they
ignore tail bytes per existing `if buf.len() >= expected` check).
Server (parse_esp32_frame):
Opportunistically read trailing 4 bytes as u32 LE into
Esp32Frame.sensor_timestamp_us. Old FW → None, new FW → Some(µs).
udp_receiver_task uses sensor timestamp when present, falls back
to server SystemTime if not. Result published as NodeInfo.timestamp_us.
Flashed both sensors via OTA (no USB dance):
192.168.0.101: ota_0 → ota_1 ✓
192.168.0.100: ota_1 → ota_0 ✓
Live verify: WS timestamps now sub-1e12 (sensor monotonic, ~39s
after FW boot), Δ between successive frames = 43.3 ms ≈ 23 fps
sampling jitter, sub-ms precision. Cross-node skew = sensor boot
time delta (here ~292 ms). For sync the host can subtract per-node
boot offset learned from the first packet pair.