* nodes[].rssi_dbm of 0 used to display literally as "0.0 dBm",
misleading the operator when rssi_history was empty on the first
few ticks. Now coerce to "--" and skip pushing zeros to the trace.
* per-node fps was 1/dt instantaneous, blown up to 235 by multiple
SensingUpdate emit paths firing back-to-back. Replaced with a
1-second windowed counter — now matches the real ~38 fps per node.
Surfaces the raw-amplitude classifier's per-node decision in
node_features[].classification so the UI can show which sensor is
actually seeing motion at any moment. Lets the operator visually find
the best sensor placement without physically moving things — just walk
around and watch which badge lights up.
Server side: adds amp_node_level() pure helper + amp_node_snapshot()
that reads AMP_LATEST, then plugs it into build_node_features so the
existing PerNodeFeatureInfo.classification carries the new labels.
UI: adds a global badge in the top bar and a per-node badge inline in
each h2, color-coded (grey/absent, blue/present_still, green/moving,
red/active) plus the live per-node CV %.
Ports Francesco Pace's ESPectre gain-lock (GPLv3) to RuView FW: medians
AGC and FFT scale over the first 300 packets after boot, then freezes
them via phy_force_rx_gain / phy_fft_scale_force. With both sensors
locked and proper AP→body→sensor geometry, a 30-s × 3-state capture
(empty / still / walk) now separates by ×3.4–×5.9 instead of ±0.02
within ±0.10 noise as in ADR-099.
Adds static/raw.html — per-node 56-subcarrier amplitude bars + RSSI/
broadband traces, no DSP, for live calibration.
ADR-100 documents the technique, boot calibration values for the
operator's deployment (AGC=42/44, both APPLIED), and the verified
three-state separation table.