feat(c6_sync_espnow): EMA-smooth cross-board offset, expose via get_epoch_us

SOTA iter 5 — converted the iter 4 ADR-110 §A0.8 closing recommendation
("host-side Kalman / linear fit on the offset trajectory") into a
firmware-side, fixed-point EMA so every downstream consumer of
c6_sync_espnow_get_epoch_us() gets bounded-jitter timestamps for free.

Implementation:
* α = 1/8 (Q3.3 shift = 3), ≈8-sample effective window at the 10 Hz
  beacon rate. Tracks the ≈1.4 ppm crystal drift §A0.8 measured while
  averaging out per-beacon WiFi-MAC jitter spikes.
* y[n] = y[n-1] + (raw - y[n-1]) >> 3  — integer arithmetic, two cycles
  on the RISC-V LP/HP cores, no float dependency.
* Seeded from the first follower-mode sample so we don't bias toward 0.
* New getter: int64_t c6_sync_espnow_get_offset_us_smoothed(void).
* c6_sync_espnow_get_offset_us() (raw) stays for diagnostics, unchanged.
* c6_sync_espnow_get_epoch_us() now prefers the smoothed offset once
  s_smoothed_seeded — meaning every CSI frame timestamp ADR-029/030
  consumes is already filtered, no host-side rework required.

Diag log line now prints both:
  c6_espnow: tx#N ... offset_us=R smoothed=S

90 s bench verification (witness §A0.9 + iter5-COM9-ema-90s.log) shows
both values tracking. Methodology caveat in §A0.9: short windows don't
let the smoothing benefit emerge over the raw noise floor — the
suppression ratio measurement needs ≥5 min, deferred to a long-soak
iteration.

Binary size cost: ~32 bytes (one int64, one bool, one getter). C6 build
still 45% partition slack.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-05-23 12:04:22 -04:00
parent 676297c48f
commit f41f5fc85b
3 changed files with 45 additions and 4 deletions
@@ -48,6 +48,15 @@ bool c6_sync_espnow_is_leader(void);
bool c6_sync_espnow_is_valid(void);
int64_t c6_sync_espnow_get_offset_us(void);
/**
* EMA-smoothed offset (α=1/8, ~8-sample effective window at the 10 Hz
* beacon rate). Tracks the ≈1.4 ppm crystal drift between two C6 boards
* (measured in §A0.8) while suppressing the 540 µs per-beacon WiFi-MAC
* jitter. CSI frame timestamps should stamp from this value, not the raw
* offset — `c6_sync_espnow_get_epoch_us()` already does so internally.
*/
int64_t c6_sync_espnow_get_offset_us_smoothed(void);
/* Counters for the witness harness — exposed for tests/diagnostics. */
uint32_t c6_sync_espnow_tx_count(void);
uint32_t c6_sync_espnow_tx_fail(void);