Three threads in this commit:
1) Per-frame attractor analysis (default analyze_every_n: 8 → 1).
The I5 benchmark put per-frame update at 0.012 ms p99 — 83× under D4's
1 ms budget. The cost case for the every-8th-frame default doesn't hold;
per-frame analysis is what makes regime_changed a viable early-detection
trigger.
2) New `regime_changed: bool` field in IntrospectionSnapshot — flips on any
frame whose attractor regime classification differs from the previous
frame's. Pairs with top_k_similarity (full-shape match) to give
downstream consumers two latencies with different robustness profiles.
3) Honest amendment of ADR-099 D8 to reflect empirical reality:
- L1 stand-in achieves 3.20× ratio (5-frame shape match vs 16-frame
event-path floor); the 10× aspirational bar is architecturally
unreachable at 1-D scalar feature resolution.
- regime_changed didn't fire in the 10-frame motion window — the
200-frame noise trajectory dominates the Lyapunov classification, and
short perturbations don't shift the regime fast enough on a scalar
feature.
- Path to 10×: ADR-208 Phase 2 (Hailo NPU vec128 embeddings) — multi-dim
partial matches discriminate from noise in 1-2 frames, not 5.
- Side finding: midstream temporal-compare::DTW uses *discrete equality*
cost (designed for LLM tokens), not numeric distance — swapping it in
for f64 amplitude scoring would be strictly worse than the L1 stand-in.
A numeric DTW is a separate concern (hand-roll or new crate).
- Revised D8: ship behind --introspection (off by default) until multi-
dim features land. Per-frame update budget IS met (0.041 ms p99 in this
bench, ~24× under the 1 ms bar) — the feature is cheap enough to
carry dark today.
cargo test -p wifi-densepose-sensing-server --no-default-features:
introspection (lib): 8 passed, 0 failed
introspection_latency (test): 5 passed, 0 failed (incl. new
regime_change_path_latency)
clippy: clean on the introspection surface (pre-existing approx_constant
lints in pose.rs / main.rs unchanged).
Co-Authored-By: claude-flow <ruv@ruv.net>
ADR-098 rejected midstream as a *replacement* for RuView's existing seams.
ADR-099 is the other half: midstream's `temporal-compare` (DTW) and
`temporal-attractor-studio` (Lyapunov + regime classification) crates as a
*parallel* per-frame introspection tap, alongside the existing window-aggregated
event pipeline.
The 8 decisions:
D1 — Only midstreamer-temporal-compare 0.2 + midstreamer-attractor 0.2;
scheduler / neural-solver / strange-loop are out of scope of this ADR.
D2 — Tap point: post-validate, parallel to WindowBuffer::push in csi.rs.
The existing /ws/sensing path is unchanged.
D3 — New /ws/introspection topic + /api/v1/introspection/snapshot REST endpoint
carrying IntrospectionSnapshot { regime, lyapunov_exponent,
attractor_dim, top_k_similarity }.
D4 — Per-frame updates only, never window-blocked. Soonest-event latency on
the "shape recognized" path collapses from ~533 ms (16-frame @ 30 Hz
window) to ~33 ms (one frame), a ~16× win.
D5 — temporal-neural-solver (LTL) is out of scope (separate MAT audit ADR).
D6 — ESP32 firmware unchanged; deployment is host-side only.
D7 — Signature library is JSON, on-disk, customer-owned; three reference
signatures ship as developer fixtures.
D8 — Promotion bar is empirical: ≥10× p99 latency reduction vs. the existing
/ws/sensing event path, or the feature stays behind a CLI flag.
Indexed in docs/adr/README.md. Phased adoption (P0 spike + benchmark → P1 first
real signature library → P2 dashboard widget → P3 capture workflow → P4 optional
adaptive_classifier hook). Implementation lands as ~150–250 lines + one
integration test in v2/crates/wifi-densepose-sensing-server in follow-up PRs.
Co-Authored-By: claude-flow <ruv@ruv.net>
rvCSI was extracted to its own repo (PR #542→#544): 9 crates on crates.io @
0.3.1, `@ruv/rvcsi` on npm, vendored at `vendor/rvcsi`. RuView currently
*vendors but does not consume* it — zero `rvcsi-*` deps in `v2/`, zero
`use rvcsi_…` imports, zero `@ruv/rvcsi` JS imports. ADR-097 decides:
D1 — Depend on the published crates from crates.io, not the submodule path.
D2 — Pilot in `wifi-densepose-sensing-server` (smallest, best-bounded
touchpoint: UDP receiver + handlers + WS fan-out).
D3 — `wifi-densepose-signal` is *layered on top of* rvCSI, not replaced.
The SOTA / RuvSense modules go beyond rvCSI's scope and stay in
RuView; they consume `rvcsi_core::CsiFrame`. Overlapping basic DSP
primitives delegate to `rvcsi-dsp` or become thin shims.
D4 — `wifi-densepose-hardware` stops carrying ESP32 wire-format parsing;
the parser moves to a new `rvcsi-adapter-esp32` crate (ADR-095 §1.2
/ D15 follow-up, owned in the rvCSI repo).
D5 — `wifi-densepose-ruvector` (training pipeline) and `rvcsi-ruvector`
(runtime RF memory) stay separate for now; a follow-up unifies them
once the production RuVector binding lands.
D6 — `rvcsi_core::CsiFrame` is the boundary type at the runtime edge;
one explicit `From`/`Into` conversion point at that edge.
D7 — Track via `rvcsi-* = "0.3"` SemVer ranges + bump the `vendor/rvcsi`
submodule pin per RuView release for reproducible offline builds.
D8 — Once every consumer depends on crates.io, decide (separately)
whether to drop the submodule.
Adoption is phased (P1 pilot → P2 signal shim → P3 ESP32 adapter →
P4 clean-up → P5 submodule review); each phase is one PR with tests.
Indexed in docs/adr/README.md.
Co-Authored-By: claude-flow <ruv@ruv.net>
BaselineDriftDetector compared `mean_amplitude` against its EWMA baseline
with *absolute* thresholds (anomaly 1.0, drift 0.15). Fine for the synthetic
unit tests (amplitudes ~1.0), but raw ESP32 CSI is int8 I/Q with amplitudes
up to ~128, so window-to-window RMS distance is routinely 5-50 >> 1.0 and
AnomalyDetected fired on ~96% of windows (319/331 on a real node-1 capture).
Drift is now `||current - baseline||2 / ||baseline||2` (a fraction, with an
eps floor that falls back to absolute for a degenerate near-zero baseline),
so one tuning is valid across raw-int8 ESP32, int16-scaled Nexmon, and
baseline-subtracted streams. AnomalyDetected drops to 40/331 on the same
data; the existing detector tests still pass (their explicit configs are
valid relative thresholds too); added baseline_drift_is_scale_invariant_
no_anomaly_storm. rvcsi-events 18 -> 19 tests; 162 rvcsi tests, 0 failures,
clippy-clean.
Surfaced by an end-to-end test against real ESP32 CSI on COM7: the device
(ESP32-S3, node 1, ADR-018 firmware, WiFi "ruv.net" ch5 RSSI -39, CSI cb
only because nothing listens at .156). rvcsi has no ESP32 adapter yet, so a
7,000-frame node-1 recording was transcoded to .rvcsi via the new
scripts/esp32_jsonl_to_rvcsi.py (stand-in for `record --source esp32-jsonl`)
and run through `rvcsi inspect`/`replay`/`calibrate`/`events` end-to-end.
ADR-095 D13 and ADR-096 sections 2.1/5 updated; CHANGELOG entry added;
rvcsi-adapter-esp32 (live serial/UDP source) noted as a follow-up.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds first-class support for the Raspberry Pi 5's WiFi chip (CYW43455 /
BCM43455c0 — the same 802.11ac wireless as the Pi 4 / Pi 3B+ / Pi 400, and the
chip with the most mature nexmon_csi support), plus a registry of the other
Nexmon-supported Broadcom/Cypress chips.
rvcsi-adapter-nexmon — new `chips.rs`:
- `NexmonChip` (Bcm43455c0, Bcm43436b0, Bcm4366c0, Bcm4375b1, Bcm4358, Bcm4339,
Unknown{chip_ver}) + `RaspberryPiModel` (Pi5/Pi4/Pi400/Pi3BPlus/PiZero2W/
PiZeroW) — Pi5/Pi4/Pi400/Pi3B+ → Bcm43455c0; PiZero2W → Bcm43436b0.
- `nexmon_adapter_profile(chip)` / `raspberry_pi_profile(model)` build the
per-device `AdapterProfile` (channels: 2.4 GHz 1-13 + 5 GHz UNII for dual-band;
bandwidths 20/40/80[/160]; expected subcarrier counts 64/128/256[/512]) that
`validate_frame` bounds CSI frames against.
- `NexmonChip::from_chip_ver` (0x4345 → Bcm43455c0, 0x4339, 0x4358, 0x4366,
0x4375 — best-effort; the raw `chip_ver` is always preserved) and `from_slug`
/ `RaspberryPiModel::from_slug` ("pi5", "raspberry pi 4", "bcm43455c0", ...).
- `NexmonCsiHeader::chip()`; `NexmonPcapAdapter` auto-detects the chip from the
packets' `chip_ver` and uses the matching profile, overridable via
`.with_chip(NexmonChip)` / `.with_pi_model(RaspberryPiModel)`; `.detected_chip()`.
rvcsi-runtime: `decode_nexmon_pcap_for(.., chip_spec)` (validate against a chip /
Pi model, drop non-conforming) + `nexmon_profile_for(spec)`; `NexmonPcapSummary`
gains `chip_names` + `detected_chip`; `CaptureSummary` gains `chip`.
rvcsi-cli: `record --source nexmon-pcap --chip pi5`; new `nexmon-chips`
subcommand (lists chips + Pi models, human or `--json`); `inspect-nexmon` and
`inspect` now print the resolved chip.
rvcsi-node (napi-rs): `nexmonDecodePcap` gains an optional `chip` arg;
`nexmonChipName(chipVer)`, `nexmonProfile(spec)`, `nexmonChips()`. @ruv/rvcsi
SDK + `.d.ts` updated (AdapterProfile / NexmonChipsListing interfaces, the new
fns, `chip` on CaptureSummary, `chip_names`/`detected_chip` on NexmonPcapSummary).
168 rvcsi tests pass (adapter-nexmon 22→28, cli 9→10), 0 failures, clippy-clean.
The synthetic test captures now stamp chip_ver = 0x4345 (the BCM4345 family chip
ID), so the chip-detection happy path is exercised end to end.
ADR-096, CHANGELOG, README, CLAUDE.md updated.
https://claude.ai/code/session_01CdYAPvRTjcch6YrYf42n1z
Addresses three findings from the 2026-05-11 training-pipeline audit:
#1/#2 — `wifi-densepose-signal` was a phantom dependency of `wifi-densepose-train`
(listed in Cargo.toml, never imported), and vitals/CSI signal features were
absent from the pipeline. New module `wifi_densepose_train::signal_features`:
`extract_signal_features(&Array4<f32>, &Array4<f32>) -> Array1<f32>` (and the
convenience method `CsiSample::signal_features()`) runs a windowed observation's
centre frame through `wifi_densepose_signal::features::FeatureExtractor`,
producing a fixed-length (FEATURE_LEN=12) amplitude / phase-coherence / PSD
feature vector — the hook for a future vitals / multi-task supervision head
(breathing- and heart-rate-band power are read off the PSD summary). The vector
is produced on demand and is not yet fed back into the loss; wiring it as a
training target is the documented follow-up. `wifi-densepose-signal` is now an
actually-used dependency. 5 new tests (2 unit in signal_features.rs, 3
integration in tests/test_dataset.rs); existing wifi-densepose-train tests
unchanged and green.
#3 — `docs/huggingface/MODEL_CARD.md` presented PIR/BME280 environmental-sensor
weak-label fine-tuning as a current capability; there is no env-sensor
ingestion in the training pipeline. Marked that path as planned/not-implemented
in the training-steps list and the data-provenance section.
(#5 — README's "92.9% PCK@20" overclaim — fixed separately in PR #535.)
CHANGELOG updated.
The hosted GitHub Pages viewer can now act as a thin client for a
locally-running ruview-pointcloud serve instance — flip a button, the
ESP32's CSI fusion (camera depth + WiFi CSI + mmWave) renders inside
the same Three.js scene that previously only showed the face mesh
demo. No clone, no rebuild, no toolchain on the visitor's side.
Server (stream.rs):
- Add tower_http::cors::CorsLayer with a deliberate allowlist:
https://ruvnet.github.io, http://localhost:*, http://127.0.0.1:*,
and 'null' (for file:// origins). Anything else is denied — not a
wildcard CORS. Modern browsers (Chrome 94+, Firefox 116+, Safari
16.4+) treat 127.0.0.1 as a "potentially trustworthy" origin so
HTTPS Pages → HTTP loopback is permitted. The new layer wraps the
existing /api/cloud, /api/splats, /api/status, /health routes.
- Cargo.toml: pull in workspace tower-http (cors feature already on).
Viewer:
- New "📡 Connect ESP32…" CTA bottom-right. Clicking prompts for a
ruview-pointcloud serve URL (default http://127.0.0.1:9880),
persists the last-used value in localStorage, and reloads with
?backend=<url> so the existing remote-mode fetch path takes over.
When already connected the button toggles to "disconnect" and
reloads back to the demo.
- Reuses the existing transport selector — no new code path to
maintain. The face mesh / synthetic demo render path is unaffected;
this is purely an additive UI affordance over the ?backend= query.
Docs:
- ADR-094 §2.3 expanded with the local-ESP32 workflow and the CORS
posture rationale.
- Workflow README documents ?backend=http://127.0.0.1:9880 as the
intended local-ESP32 path.
Tests: cargo test -p wifi-densepose-pointcloud → 15/15 passed.
Co-Authored-By: claude-flow <ruv@ruv.net>
The previous synthetic procedural demo did not represent what the local
fusion pipeline produces — a real depth-backprojected point cloud of
the user's face and surroundings. This commit ports the closest browser
equivalent: MediaPipe Face Mesh runs in-browser at ~30 fps and emits
478 3D landmarks per frame. Each visitor now sees the outline of their
own face rendered as a point cloud, with a small floor + back wall for
spatial context.
- Adds MediaPipe Face Mesh + Camera Utils via jsdelivr CDN.
- Adds an "▶ Enable camera" CTA so getUserMedia is gated on a user
gesture (required by some browsers and good UX regardless).
- New face-mesh frame generator uses the same splat shape as the live
/api/splats payload, so a single render path drives both modes.
- Mirrors x to match selfie convention; maps lm.z (relative depth) to
the world-coord range used by the live pipeline.
- Falls back automatically to the procedural floor + walls + figure
when the camera is denied, dismissed, or unavailable.
- Badge surfaces the new state: '● DEMO Your Face (MediaPipe)'.
- Bumps poll cadence to 4 Hz so face mesh updates feel live.
- ADR-094 updated to reflect the new default behavior.
Co-Authored-By: claude-flow <ruv@ruv.net>
Publishes the live 3D point cloud viewer to gh-pages/pointcloud/ so it
can be linked from the README alongside the Observatory and Dual-Modal
Pose Fusion demos. The viewer auto-selects its transport from URL
parameters:
- default / ?backend=auto — try /api/splats, fall back to synthetic demo
- ?backend=demo — synthetic in-browser only, no network
- ?backend=<url> — fetch from a CORS-permitting host running
ruview-pointcloud serve
- ?live=1 — strict mode, show offline panel instead of demo fallback
The synthetic frame matches the live API JSON shape (splats, count,
frame, live, pipeline.{skeleton,vitals}) so a single render path drives
both modes. New workflow uses keep_files: true to preserve the existing
observatory/, pose-fusion/, and nvsim/ deployments on gh-pages.
See docs/adr/ADR-094-pointcloud-github-pages-deployment.md for the full
decision record and 6 acceptance gates.
- Move Latest Additions, Key Features, and everything from Installation
through Changelog (1855 lines) into docs/readme-details.md.
- Keep README focused on overview, capability table, How It Works,
Use Cases, Documentation, License, and Support.
- Add per-row emojis to the top capability table.
- Add 3D point cloud row noting optional camera + WiFi CSI + mmWave
fusion with link to the live viewer demo.
- Move Documentation table closer to the bottom (just above License).
- Collapse Edge Intelligence (ADR-041) into a <details> block matching
the sibling Use Case sections.
Co-Authored-By: claude-flow <ruv@ruv.net>
All five implementation passes plus four security-review hardenings
shipped in PR #435 (squash-merged as d71ef9a). Acceptance numbers
measured on synthetic AETHER-shape data:
- Compare-cost reduction: 8x-30x floor → 43-51x pair-wise (d=512),
12.4x top-K (d=128 n=1024 k=8), 7.6x full pipeline (d=128 n=4096 k=8).
- Top-K coverage: ≥90% floor → 90%+ at prefilter_factor=8 (78.9%
at factor=4 documented as fail; codified in
test_search_prefilter_topk_coverage_meets_adr_084).
- Wire envelope: 28-byte AETHER 128-d (vs 512-byte raw float; 18x
compression).
The third acceptance criterion (`< 1 pp end-to-end accuracy regression`)
needs a real-CSI soak test against a multi-day AETHER trace; that's
post-merge follow-up rather than a merge-blocker. Synthetic-data
acceptance was sufficient evidence to ship.
PR #434 (ADR-086 firmware-side gate) merged separately as 17509a2.
Co-Authored-By: claude-flow <ruv@ruv.net>
Pushes the ADR-084 novelty sensor down into the ESP32 sensor MCU's
Layer 4 (On-device Feature Extraction) of ADR-081's 5-layer kernel:
sketch + 32-slot ring bank in IRAM, suppress UDP send when novelty
< CONFIG_RV_EDGE_NOVELTY_THRESHOLD (default 0.05).
Wire format bumps to magic 0xC5110007 with two new fields
(suppressed_since_last: u16, gate_version: u8) packed in by narrowing
the existing 16-bit quality_flags to 8-bit (only 8 bits were ever
defined). Frame size stays at 60 bytes; v6 receivers fall back
gracefully.
Stuck-gate self-heal at CONFIG_RV_EDGE_MAX_CONSEC_SUPPRESS (default
50 frames ≈ 10 s) so a wedged threshold can't silently disappear a
node. Default-off Kconfig so existing deployments are unaffected.
Validation commitments:
- ≤ 200 µs sketch insert+score on Xtensa LX7
- ≥ 30% UDP TX-energy reduction in steady-state quiet rooms
- ≤ 5 pp drop on cluster-Pi novelty top-K coverage vs unsuppressed
- ≥ 50% bandwidth reduction in stable-room scenarios
Six-pass implementation plan, default-off Kconfig, QEMU + COM7
hardware-in-loop validation. Honest gaps flagged: Xtensa LX7 POPCNT
absence is conjecture (Pass 2 bench is the falsifier); interaction
with ADR-082's Tentative→Active gate is the likeliest weak point
(Open Q4).
ADR-087 / ADR-088 reserved as pointer stubs at end:
- ADR-087: Pass-4 mesh-exchange scope (cluster↔cluster vs sensor→Pi)
- ADR-088: Firmware-release coordination policy
Status: Proposed. SOTA review by goal-planner agent.
Extends ADR-084's RaBitQ-as-similarity-sensor pattern from five sites
to twelve, adding seven additional pipeline locations the user
identified during ADR-084 implementation:
- Per-room adaptive classifier short-circuit (Mahalanobis prefilter)
- Recording-search REST endpoint (GET /api/v1/recordings/similar)
- WiFi BSSID fingerprinting (channel-hop scheduler input)
- mmWave (LD2410 / MR60BHA2) signature wake-gate
- Witness bundle drift detection (CI ratchet)
- Agent / swarm memory routing (ADR-066 swarm bridge)
- Log / event-pattern anomaly detection (cluster Pi)
Each site has a 2-3 sentence decision (what gets sketched, what
triggers the comparison, what the refinement does on miss) and a
witness-hash artifact (what the system stores in place of the raw
embedding/event/signal).
Implementation plan ordered cheapest-first / least-risky-first.
Acceptance criteria align with ADR-084 (8x-30x compare cost,
≥90% top-K coverage, <1pp accuracy regression) where applicable;
non-vector sites (witness bundle, BSSID time-series, event log)
have site-specific criteria.
Three open questions explicitly flagged:
1. Mahalanobis-after-binary-sketch is novel — no published primary
source found, marked conjecture, decision deferred to bench
2. Canonical "non-vector → sketchable" encoding is unsolved
3. MERIDIAN (ADR-027) cross-environment domain interaction needs
site-by-site analysis before bank rebuild semantics are committed
Status: Proposed. SOTA review by goal-planner agent.
Adopt RaBitQ-style binary sketches as a first-class cheap similarity
sensor at four points in the RuView pipeline: AETHER re-ID hot-cache
filter, per-room novelty / drift detection, mesh-exchange compression,
and privacy-preserving event logs. Implementation home is
ruvector-core::quantization::BinaryQuantized (already vendored, already
SIMD-accelerated NEON+POPCNT, 32x compression, 1-bit sign quantization
+ hamming distance), re-exported through a thin RuView-flavored API in
wifi-densepose-ruvector::sketch.
Pattern at every site: dense embedding -> RaBitQ sketch -> hamming
pre-filter to top-K -> full-precision refinement only on miss. Decision
boundary unchanged; sketch is a sensor that gates *which* comparisons
run, not *what* they decide.
Acceptance test (per source proposal):
- sketch compare cost reduction: 8x-30x vs full float
- top-K candidate coverage: >= 90% agreement with full-float pass
- end-to-end accuracy regression: < 1 percentage point
Site-by-site rollback if any criterion fails at a given site;
remaining sites continue. Five implementation passes, each
independently testable: ruvector module wrap, AETHER re-ID pre-filter,
cluster-Pi novelty sensor, mesh-exchange compression, privacy log.
Sensor MCU unchanged; sketches happen at the cluster Pi (ADR-083).
Validation requires acceptance numbers on >= 3 of 5 passes.
Open question (out-of-scope until pass-1 benchmark): whether RuView
embeddings need a Johnson-Lindenstrauss / RaBitQ-paper randomized
rotation before sign-quantization, or whether pure 1-bit sign
quantization (today's BinaryQuantized) is sufficient.
Adopt one Pi per cluster of 3-6 ESP32-S3 sensor nodes as the canonical
fleet-shape, rather than the full three-tier (dual-MCU + per-node Pi)
shape. Sensor nodes are unchanged from ADR-028 / ADR-081; the cluster
Pi gains the responsibilities the ESP32-S3 cannot carry — pose-grade
ML inference, QUIC backhaul to gateway/cloud, and a cluster-level OTA
+ secure-boot anchor.
The cluster-Pi shape is the L3-hybrid path identified in
docs/research/architecture/decision-tree.md §2 — the cheapest viable
upgrade. The full three-tier shape remains the long-term exploration
target, gated behind no_std CSI maturity (decision-tree L4) and
per-node ISR-jitter evidence (L2).
Status: Proposed. Acceptance gated on:
1. Cross-compile to aarch64 / armv7 with workspace tests passing
2. 3-sensor + 1-Pi field test demonstrating end-to-end CSI → fusion →
cloud at <=100 ms cluster latency
3. Cluster-Pi SoC choice ADR (decision-tree L6) approved
References:
- docs/research/architecture/three-tier-rust-node.md (seed exploration)
- docs/research/architecture/decision-tree.md (L3 hybrid path)
- docs/research/sota/2026-Q2-rf-sensing-and-edge-rust.md (SOTA evidence)
The Rust port at v2/ has been the primary codebase since the rename
in #427. The Python implementation at v1/ is no longer the active
target; the only load-bearing path is the deterministic proof bundle
at v1/data/proof/ (per ADR-011 / ADR-028 witness verification).
Move the whole Python tree into archive/v1/ and document the policy
in archive/README.md: no new features, bug fixes only when they affect
a still-load-bearing path (currently just the proof), CI continues to
verify the proof on every push and PR.
Path references updated in 26 files via path-pattern sed (only
matches v1/<known-child> patterns, never bare v1 or API URLs like
/api/v1/). Two double-prefix typos (archive/archive/v1/) caught and
hand-fixed in verify-pipeline.yml and ADR-011.
Validated:
- Python proof verify.py imports cleanly at archive/v1/data/proof/
(numpy/scipy still required; CI installs requirements-lock.txt
from archive/v1/ now)
- cargo test --workspace --no-default-features → 1,539 passed,
0 failed, 8 ignored (unaffected by Python tree relocation)
- ESP32-S3 on COM7 untouched (no firmware paths changed)
After-merge: contributors should re-run any local `python v1/...`
commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already
updated).
Two leftover references missed by the sed pass in #427 (which only
matched the full `rust-port/wifi-densepose-rs` path). These are bare
references to the workspace directory name, which is now v2/.
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.
Three exploratory research documents under docs/research/:
- architecture/three-tier-rust-node.md (3,382 words) — exploration of a
dual-ESP32-S3 + Pi Zero 2W node architecture with BQ24074 power-path,
ESP-WIFI-MESH + LoRa fallback + QUIC backhaul, and an esp-hal/Embassy
vs esp-idf-svc Rust toolchain split. Status: Exploratory — not adopted.
- sota/2026-Q2-rf-sensing-and-edge-rust.md (3,757 words) — twelve-section
state-of-the-art survey covering WiFi CSI through-wall pose, IEEE 802.11bf
(ratified 2025-09-26), edge ML on ESP32-class hardware, embedded Rust
ecosystem maturity (esp-hal 1.x, esp-radio rename, embassy-executor
ISR-safety on esp-idf-svc), LoRa for sensor mesh fallback, QUIC for IoT
backhaul, solar power-path management beyond BQ24074, mesh routing
alternatives, and Pi Zero 2W secure-boot reality.
- architecture/decision-tree.md (1,461 words) — Mermaid decision tree
mapping each load-bearing decision in the three-tier proposal to its
dependencies, evidence-for-yes/no, and prospective ADR slot.
No production code, firmware, or ADRs touched. Research-only.
Co-Authored-By: claude-flow <ruv@ruv.net>
`tracker_bridge::tracker_to_person_detections` documented itself as filtering
to `is_alive()` but never actually filtered — it forwarded every non-Terminated
track to the WebSocket stream. With 3 ESP32-S3 nodes × ~10 Hz CSI, transient
detections that fell outside the Mahalanobis gate created a steady stream of
new Tentative tracks that aged through Active and into Lost. Lost tracks are
kept in the tracker for `reid_window` (~3 s) so re-identification can match
them when a similar detection reappears, but they are NOT currently observed
and must not render as live skeletons. Up to ~90 ghost skeletons could
accumulate at any moment, hence the 22-24 phantoms users saw while
`estimated_persons` correctly reported 1.
Add `PoseTracker::confirmed_tracks()` that returns only `Tentative ∪ Active`
and rewire the bridge to use it. `Lost` tracks remain in the tracker for
re-ID; they just no longer ship to the UI. `active_tracks()` is left
unchanged for the AETHER re-ID consumers (ADR-024).
Regression test `test_lost_tracks_excluded_from_bridge_output` drives a
track to Active, lapses for `loss_misses + 1` ticks to push it to Lost,
and asserts `tracker_update` returns an empty Vec while the Lost track
is still present in `all_tracks()` (re-ID still works).
Validated:
- cargo test --workspace --no-default-features → 1,539 passed, 0 failed
- ESP32-S3 on COM7 still streaming live CSI (cb #32800)
* Add wifi-densepose-pointcloud: real-time dense point cloud from camera + WiFi CSI
New crate with 5 modules:
- depth: monocular depth estimation + 3D backprojection (ONNX-ready, synthetic fallback)
- pointcloud: Point3D/ColorPoint types, PLY export, Gaussian splat conversion
- fusion: WiFi occupancy volume → point cloud + multi-modal voxel fusion
- stream: HTTP + Three.js viewer server (Axum, port 9880)
- main: CLI with serve/capture/demo subcommands
Demo output: 271 WiFi points + 19,200 depth points → 4,886 fused → 1,718 Gaussian splats.
Serves interactive 3D viewer at http://localhost:9880 with Three.js orbit controls.
ADR-SYS-0021 documents the architecture for camera + WiFi CSI dense point cloud pipeline.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Optimize pointcloud: larger splat voxels, smaller responses, faster fusion
- Gaussian splat voxel size: 0.10 → 0.15 (42% fewer splats: 1718 → 994)
- Splat response: 399 KB → 225 KB (44% smaller)
- Pipeline: 22.2ms mean (100 runs, σ=0.3ms)
- Cloud API: 1.11ms avg, 905 req/s
- Splats API: 1.39ms avg, 719 req/s
- Binary: 1.0 MB arm64 (Mac Mini), tested
Co-Authored-By: claude-flow <ruv@ruv.net>
* Complete implementation: camera capture, WiFi CSI receiver, training pipeline
Three new modules added to wifi-densepose-pointcloud:
1. camera.rs — Cross-platform camera capture
- macOS: AVFoundation via Swift, ffmpeg avfoundation
- Linux: V4L2, ffmpeg v4l2
- Camera detection, listing, frame capture to RGB
- Graceful fallback to synthetic data when no camera
2. csi.rs — WiFi CSI receiver for ESP32 nodes
- UDP listener for CSI JSON frames from ESP32
- Per-link attenuation tracking with EMA smoothing
- Simplified RF tomography (backprojection to occupancy grid)
- Test frame sender for development without hardware
- Ready for real ESP32 CSI data from ruvzen
3. training.rs — Calibration and training pipeline
- Depth calibration: grid search over scale/offset/gamma
- Occupancy training: threshold optimization for presence detection
- Ground truth reference points for depth RMSE measurement
- Preference pair export (JSONL) for DPO training on ruOS brain
- Brain integration: submit observations as memories
- Persistent calibration files (JSON)
New CLI commands:
ruview-pointcloud cameras # list available cameras
ruview-pointcloud train # run calibration + training
ruview-pointcloud csi-test # send test CSI frames
ruview-pointcloud serve --csi # serve with live CSI input
All tested: demo, training (10 samples, 4 reference points, 3 pairs),
CSI receiver (50 test frames), server API.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Fix viewer: replace WebSocket with fetch polling
Co-Authored-By: claude-flow <ruv@ruv.net>
* Wire live camera into server — real-time updating point cloud
- Server captures from /dev/video0 at 2fps via ffmpeg
- Background tokio task refreshes cloud + splats every 500ms
- Viewer polls /api/splats every 500ms, only updates on new frame
- Shows 🟢 LIVE / 🔴 DEMO indicator
- Camera position set for first-person view (looking forward into scene)
- Downsample 4x for performance (19,200 points per frame)
- Graceful fallback to demo data if camera capture fails
Co-Authored-By: claude-flow <ruv@ruv.net>
* Add MiDaS GPU depth, serial CSI reader, full sensor fusion
- MiDaS depth server: PyTorch on CUDA, real monocular depth estimation
- Rust server calls MiDaS via HTTP for neural depth (falls back to luminance)
- Serial CSI reader for ESP32 with motion detection + presence estimation
- CSI disabled by default (RUVIEW_CSI=1 to enable) — serial reader needs baud config
- Edge-enhanced depth for better object boundaries
- All sensors wired: camera, ESP32 CSI, mmWave (CSI gated until serial fixed)
Co-Authored-By: claude-flow <ruv@ruv.net>
* Complete 7-component sensor fusion pipeline (all working)
1. ADR-018 binary parser — decodes ESP32 CSI UDP frames, extracts I/Q subcarriers
2. WiFlow pose — 17 COCO keypoints from CSI (186K param model loaded)
3. Camera depth — MiDaS on CUDA + luminance fallback
4. Sensor fusion — camera depth + CSI occupancy grid + skeleton overlay
5. RF tomography — ISTA-inspired backprojection from per-node RSSI
6. Vital signs — breathing rate from CSI phase analysis
7. Motion-adaptive — skip expensive depth when CSI shows no motion
Live results: 510 CSI frames/session, 17 keypoints, 26% motion, 40 BPM breathing.
Both ESP32 nodes provisioned to send CSI to 192.168.1.123:3333.
Magic number fix: supports both 0xC5110001 (v1) and 0xC5110006 (v6) frames.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Add brain bridge — sparse spatial observation sync every 60s
Stores room scan summaries, motion events, and vital signs
in the ruOS brain as memories. Only syncs every 120 frames
(~60 seconds) to keep the brain sparse and optimized.
Categories: spatial-observation, spatial-motion, spatial-vitals.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Update README + user guide with dense point cloud features
Added pointcloud section to README (quick start, CLI, performance).
Added comprehensive user guide section: setup, sensors, commands,
pipeline components, API endpoints, training, output formats,
deep room scan, ESP32 provisioning.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Add ruview-geo: geospatial satellite integration (11 modules, 8/8 tests)
New crate with free satellite imagery, terrain, OSM, weather, and brain integration.
Modules: types, coord, locate, cache, tiles, terrain, osm, register, fuse, brain, temporal
Tests: 8 passed (haversine, ENU roundtrip, tiles, HGT parse, registration)
Validation: real data — 43.49N 79.71W, 4 Sentinel-2 tiles, 2°C weather, brain stored
Data sources (all free, no API keys):
- EOX Sentinel-2 cloudless (10m satellite tiles)
- SRTM GL1 (30m elevation)
- Overpass API (OSM buildings/roads)
- ip-api.com (geolocation)
- Open Meteo (weather)
ADR-044 documents architecture decisions.
README.md in crate subdirectory.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Update ADR-044: add Common Crawl WET, NASA FIRMS, OpenAQ, Overture Maps sources
Extended geospatial data sources leveraging ruvector's existing web_ingest
and Common Crawl support for hyperlocal context.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Fix OSM/SRTM queries, add change detection + night mode
- OSM: use inclusive building filter with relation query and 25s timeout
- SRTM: switch to NASA public mirror with viewfinderpanoramas fallback
- Add detect_tile_changes() for pixel-diff satellite change detection
- Add is_night() solar-declination model for CSI-only night mode
- 6 new unit tests (night mode + tile change detection)
Co-Authored-By: claude-flow <ruv@ruv.net>
* Enhance viewer: skeleton overlay, weather, buildings, better camera
Add COCO skeleton rendering with yellow keypoint spheres and white bone
lines, info panel sections for weather/buildings/CSI rate/confidence,
overhead camera at (0,2,-4), and denser point size with sizeAttenuation.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Add CSI fingerprint DB + night mode detection
Co-Authored-By: claude-flow <ruv@ruv.net>
* Fix ADR-044 numbering conflict, update geo README
Renumbered provisioning tool ADR from 044 to 050 to avoid conflict
with geospatial satellite integration ADR-044.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Clean up warnings: suppress dead_code for conditional pipeline modules
Removes unused imports/variables via cargo fix and adds #[allow(dead_code)]
for modules used conditionally at runtime (CSI, depth, fusion, serial).
Pointcloud: 28 → 0 warnings. Geo: 2 → 0 warnings. 8/8 tests pass.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Fix PR #405 blockers: async runtime panic, crate rename, path traversal, brain URL config
- brain_bridge.rs: replace `Handle::current().block_on(...)` inside async fn
with `.await` (was a guaranteed "runtime within runtime" panic). Brain URL
now read from RUVIEW_BRAIN_URL env var (default http://127.0.0.1:9876),
logged once via OnceLock.
- wifi-densepose-geo: rename Cargo package from `ruview-geo` to
`wifi-densepose-geo` to match directory and workspace conventions. Update
all use sites (tests/examples/README). Same env-var pattern for brain URL
in brain.rs + temporal.rs.
- training.rs: add sanitize_data_path() rejecting `..` components and
safe_join() that canonicalises + enforces base-dir containment on every
write (calibration.json, samples.json, preference_pairs.jsonl,
occupancy_calibration.json). Defence-in-depth check also in main.rs
before TrainingSession::new.
- osm.rs: clamp Overpass radius to MAX_RADIUS_M=5000m; return Err beyond
that. Add parse_overpass_json() that rejects malformed payloads
(missing top-level `elements` array).
Co-Authored-By: claude-flow <ruv@ruv.net>
* csi_pipeline: rename WiFlow stub to heuristic_pose_from_amplitude, decouple UDP
Blocker 3 (PR #405 review): The "WiFlow inference" path was a stub that
built a model from empty weight vectors and synthesised keypoints from
amplitude energy. Presenting this as "WiFlow inference" was misleading.
- Rename WiFlowModel to PoseModelMetadata (empty tag struct; we only care
if the on-disk file exists)
- Rename load_wiflow_model() -> detect_pose_model_metadata() and log
"amplitude-energy heuristic enabled/disabled" (no "WiFlow" claim)
- Rename estimate_pose() -> heuristic_pose_from_amplitude() with
prominent `STUB:` doc comment saying this is NOT a trained model
Blocker 4 (PR #405 review): The UDP receiver held the shared Arc<Mutex>
across a synchronous process_frame() call, starving HTTP handlers.
- Introduce a std::sync::mpsc channel between the UDP thread (which only
parses + pushes) and a dedicated processor thread (which locks only
briefly around a single process_frame). HTTP snapshots via
get_pipeline_output no longer contend with the socket read loop.
Also:
- Move ADR-018 parser to parser.rs (see next commit); csi_pipeline re-exports
- send_test_frames now uses parser::build_test_frame for synthetic frames
- Log a one-line node stats summary every 500 frames (reads every public
CsiFrame field on the runtime path)
Co-Authored-By: claude-flow <ruv@ruv.net>
* Extract ADR-018 parser into parser.rs + wire Fingerprint CLI
File-split (strong concern #9 in PR #405 review): csi_pipeline.rs was 602
LOC; extract the pure-function ADR-018 parser + synthetic frame builder
into src/parser.rs. Inline unit tests in parser.rs cover:
- 0xC5110001 (raw CSI, v1) roundtrip
- 0xC5110006 (feature state, v6) roundtrip
- wrong magic is rejected
- truncated header is rejected
- truncated payload is rejected
main.rs: expose `fingerprint NAME [--seconds N]` subcommand wiring
record_fingerprint() (this was the only caller needed to make the public
API non-dead on the runtime path). Also:
- Replace `--host/--port` + external `--csi` with a single `--bind`
defaulting to loopback (`127.0.0.1:9880`) — addresses strong concern
#7 about exposing camera/CSI/vitals by default.
- Update synthetic `csi-test` to target UDP 3333 (matching the ADR-018
listener) and use the shared parser::build_test_frame.
- Defence-in-depth: call training::sanitize_data_path on the expanded
--data-dir before TrainingSession::new does the same.
Co-Authored-By: claude-flow <ruv@ruv.net>
* stream: extract viewer HTML to viewer.html, default bind to loopback
Strong concern #7 (PR #405): default HTTP bind leaked camera/CSI/vitals
to the LAN. The `serve` fn now takes a single `bind` arg and prints a
loud WARNING when bound outside loopback.
Strong concern #10 (PR #405): embedded HTML+JS was ~220 LOC of the 418
LOC stream.rs. Moved the markup verbatim into viewer.html and inlined
via `include_str!("viewer.html")`. Also:
- Drop the #![allow(dead_code)] crate-level silencing (reviewer point
#11). Remove the now-unused AppState.csi_pipeline field.
- capture_camera_cloud_with_luminance returns the mean luminance of the
captured frame; the background loop feeds that to
CsiPipelineState::set_light_level so the night-mode flag actually
toggles at runtime (previously it could only be set from tests).
Net effect on file size: stream.rs 418 → 232 LOC.
Co-Authored-By: claude-flow <ruv@ruv.net>
* Dead-code cleanup + tests for fusion/depth/OSM/training/fingerprinting
Reviewer point #11 (PR #405): remove the `#![allow(dead_code)]`
silencing added in 8eb808d and fix the underlying issues.
- Delete csi.rs: duplicate of csi_pipeline.rs with incompatible wire
format (JSON vs ADR-018 binary). csi_pipeline is the real path.
- Delete serial_csi.rs: never referenced by any module.
- Drop Frame.timestamp_ms (unread), AppState.csi_pipeline (unread),
brain_bridge::brain_available (caller-less), fusion::fetch_wifi_occupancy
(caller-less) — these had no runtime users.
- Drop crate-level #![allow(dead_code)] from camera.rs, depth.rs,
fusion.rs, pointcloud.rs.
Tests (target: 8-12, actual: 15 unit + 9 geo unit + 8 geo integration
= 32 total, all pass):
- parser.rs: 5 tests (v1/v6 magic roundtrip, wrong magic, truncated
header, truncated payload).
- fusion.rs: 2 tests (non-overlapping merge, voxel dedup).
- depth.rs: 2 tests (2x2 backproject → 4 points at z=1, NaN rejected).
- training.rs: 4 tests (rejects `..`, accepts relative child, refuses
TrainingSession::new("../etc/passwd"), accepts a clean tmpdir).
- csi_pipeline.rs: 2 tests (set_light_level toggles is_dark,
record_fingerprint stores and self-identifies).
- osm.rs: 3 tests (parse_overpass_json minimal fixture, rejects
malformed payload, fetch_buildings rejects > MAX_RADIUS_M).
Co-Authored-By: claude-flow <ruv@ruv.net>
* Update README + user-guide for PR #405 review-fix additions
- serve now uses --bind 127.0.0.1:9880 (loopback default) instead of --port
- Add fingerprint subcommand to CLI tables
- Document RUVIEW_BRAIN_URL env var + --brain flag
- Flag pose path as amplitude-energy heuristic stub (not trained WiFlow)
- Security note on exposing server outside loopback
- Add wifi-densepose-pointcloud + wifi-densepose-geo rows to crate table
Co-Authored-By: claude-flow <ruv@ruv.net>
- add Debian/Ubuntu desktop build prerequisites to the Rust source build guide
- document required GTK/WebKit development packages for Linux release builds
- add a matching troubleshooting entry for native desktop build dependencies
- keep installation and troubleshooting guidance aligned and context-consistent
Covers 8 known issues encountered during multi-node ESP32-S3 deployments:
1. Node not appearing (limping state after USB flash)
2. Person count stuck at 1 (ADR-044)
3. Heart rate/breathing rate jitter (last-write-wins from multiple nodes)
4. Signal quality placeholder
5. Dashboard freezing (WS disconnect loop)
6. OTA crash at 59% (BLE vs OTA conflict)
7. SSH LAN hang (Tailscale workaround)
8. USB-C port selection
Helps with #268 (no nodes found), #375 (node_id), #366 (build errors).
- Add v0.7.0 section with 92.9% PCK@20 result and new scripts
- Add camera-supervised training section to user guide with step-by-step
- Update release table (v0.7.0 as latest)
- Update ADR count (62 → 79)
- Update beta notice with camera ground-truth link
Co-Authored-By: claude-flow <ruv@ruv.net>
Address all 5 P0 issues from QE analysis (55/100 score):
- P0-1: Rate limiter bypass — validate X-Forwarded-For against trusted proxy list
- P0-2: Exception detail leak — generic 500 messages, exception_type gated by dev mode
- P0-3: WebSocket JWT in URL (CWE-598) — first-message auth pattern replaces query param
- P0-4: Rust tests not in CI — add rust-tests job gating docker-build and notify
- P0-5: WebSocket path mismatch — use WS_PATH constant instead of hardcoded /ws/sensing
Includes ADR-080 remediation plan and 9 QE reports (4,914 lines).
Firmware validated on ESP32-S3 (COM8): CSI collecting, calibration OK.
Co-Authored-By: claude-flow <ruv@ruv.net>
- ADR-079: strip SSH user/IP from optimization description
- mac-mini-train.sh: replace hardcoded IP with env var WINDOWS_HOST
Co-Authored-By: claude-flow <ruv@ruv.net>
Stoer-Wagner min-cut on subcarrier correlation graph replaces broken
threshold-based person counting (was always 4, now correct).
Validated: 24/24 windows correctly report 1 person on test data
where old firmware reported 4. Pure JS, <5ms per window.
- mincut-person-counter.js: live UDP + JSONL replay, overrides vitals
- csi-graph-visualizer.js: ASCII spectrum + correlation heatmap
- ADR-075: algorithm, comparison, migration path
Co-Authored-By: claude-flow <ruv@ruv.net>
128→64→8 SNN with STDP online learning — adapts to room in <30s
without labels. Event-driven: 16-160x less compute than FC encoder.
- snn-csi-processor.js: live UDP with ASCII visualization, EWMA
- ADR-073 updated with SNN integration for multi-channel fusion
- Fixed magic number parsing to use ADR-018 format (0xC5110001)
Co-Authored-By: claude-flow <ruv@ruv.net>
Contains GCloud project ID and secret names — not appropriate for
a public repo. Publishing instructions kept in scripts/ only.
Co-Authored-By: claude-flow <ruv@ruv.net>
- publish-huggingface.sh: retrieves HF token from GCloud Secrets,
uploads models to ruvnet/wifi-densepose-pretrained
- publish-huggingface.py: Python alternative with --dry-run support
- docs/huggingface/MODEL_CARD.md: beginner-friendly model card with
WiFi sensing explanation, quick start code, hardware BOM, and citation
GCloud Secret: HUGGINGFACE_API_KEY in project cognitum-20260110
Co-Authored-By: claude-flow <ruv@ruv.net>