Commit Graph

33 Commits

Author SHA1 Message Date
ruv 5bd0d59aa6 feat: ADR-080 P1+P2 remediation — refactor, perf, tests, safety
P1 fixes (this sprint):
- P1-6: Extract sensing-server modules (cli, types, csi, pose) from main.rs
- P1-7: DDA ray march for tomography — O(max(n)) replaces O(n^3) voxel scan
- P1-8: Batch neural inference — Tensor::stack/split for single GPU call
- P1-10: Eliminate 112KB/frame alloc — islice replaces deque→list copy

P2 fixes (this quarter):
- P2-11: Python unit tests for 8 modules (rate_limit, auth, error_handler,
  pose_service, stream_service, hardware_service, health_check, metrics)
- P2-13: MAT simulated data safety guard — blocking overlay + pulsing banner
- P2-14: Wire token blacklist into auth verification + logout endpoint
- P2-15: Frame budget benchmark — confirms pipeline well under 50ms budget

Addresses 8 of 10 remaining issues from QE analysis (ADR-080).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-06 17:00:27 -04:00
ruv b7650b5243 feat(server): accuracy sprint 001 — Kalman tracker, multi-node fusion, eigenvalue counting
Original work by @taylorjdawson (PR #341). Merged with v0.5.5 firmware
preserved (ADR-069 feature vectors, ADR-073 channel hopping, batch-limited
watchdog from #266 fix).

New server features:
- Kalman tracker bridge for temporal smoothing
- Multi-node CSI fusion with field model
- Eigenvalue-based person counting
- Calibration endpoints (start/stop/status)
- Node positions parsing
- Adaptive classifier enhancements

Co-Authored-By: taylorjdawson <taylor@users.noreply.github.com>
Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-03 08:59:17 -04:00
rUv a23bd2ec01 fix(server): resolve adversarial review findings C1-C5, H1-H3, H5, M1-M2
Critical fixes:
- C1: FieldModel created with n_links=1 (single_link_config) so
  feed_calibration/extract_perturbation no longer get DimensionMismatch
- C2: variance_explained now uses centered covariance trace (E[x²]-E[x]²)
  matching mode_energies normalization
- C3: MP ratio uses total_obs = frames * links for consistent threshold
  between calibration and runtime
- C4: Noise estimator filters to positive eigenvalues only, preventing
  collapse to ~0 on rank-deficient matrices (p > n)
- C5: ESP32 paths gate total_persons on presence — empty room reports 0

High fixes:
- H1: Bounding box computed from observed keypoints only (confidence > 0),
  preventing collapse from centroid-filled unobserved slots
- H2: fuse_or_fallback returns Option<usize> instead of sentinel 0,
  eliminating type ambiguity between "fusion succeeded" and "zero people"
- H3: Monotonic epoch-relative timestamps replace wall-clock/Instant mixing,
  preventing spurious TimestampMismatch on NTP steps
- H5: ndarray-linalg gated behind "eigenvalue" feature flag (default=on),
  diagonal fallback used with --no-default-features

Moderate fixes:
- M1: calibration_start guards against replacing Fresh calibration
- M2: parse_node_positions logs warning for malformed entries

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-31 18:50:00 +00:00
rUv 3733e54aef feat: cross-node fusion + DynamicMinCut + RSSI tracking (v0.5.3)
* feat(server): cross-node RSSI-weighted feature fusion + benchmarks

Adds fuse_multi_node_features() that combines CSI features across all
active ESP32 nodes using RSSI-based weighting (closer node = higher weight).

Benchmark results (2 ESP32 nodes, 30s, ~1500 frames):

  Metric               | Baseline | Fusion  | Improvement
  ---------------------|----------|---------|------------
  Variance mean        |    109.4 |    77.6 | -29% noise
  Variance std         |    154.1 |   105.4 | -32% stability
  Confidence           |    0.643 |   0.686 | +7%
  Keypoint spread std  |      4.5 |     1.3 | -72% jitter
  Presence ratio       |   93.4%  |  94.6%  | +1.3pp

Person count still fluctuates near threshold — tracked as known issue.

Verified on real hardware: COM6 (node 1) + COM9 (node 2) on ruv.net.

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(ui): add client-side lerp smoothing to pose renderer

Keypoints now interpolate between frames (alpha=0.25) instead of
jumping directly to new positions. This eliminates visual jitter
that persists even with server-side EMA smoothing, because the
renderer was drawing every WebSocket frame at full rate.

Applied to skeleton, keypoints, and dense body rendering paths.

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat: DynamicMinCut person separation + UI lerp smoothing

- Added ruvector-mincut dependency to sensing server
- Replaced variance-based person scoring with actual graph min-cut on
  subcarrier temporal correlation matrix (Pearson correlation edges,
  DynamicMinCut exact max-flow)
- Recalibrated feature scaling for real ESP32 data ranges
- UI: client-side lerp interpolation (alpha=0.25) on keypoint positions
- Dampened procedural animation (noise, stride, extremity jitter)
- Person count thresholds retuned for mincut ratio

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs: update CHANGELOG with v0.5.1-v0.5.3 releases

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-30 21:55:44 -04:00
rUv cd84c35f8f feat: cross-node RSSI-weighted feature fusion (benchmarked)
Adds fuse_multi_node_features() that combines CSI features across all
active ESP32 nodes using RSSI-based weighting (closer node = higher weight).

Benchmark results (2 ESP32 nodes, 30s, ~1500 frames):

  Metric               | Baseline | Fusion  | Improvement
  ---------------------|----------|---------|------------
  Variance mean        |    109.4 |    77.6 | -29% noise
  Variance std         |    154.1 |   105.4 | -32% stability
  Confidence           |    0.643 |   0.686 | +7%
  Keypoint spread std  |      4.5 |     1.3 | -72% jitter
  Presence ratio       |   93.4%  |  94.6%  | +1.3pp

Person count still fluctuates near threshold — tracked as known issue.

Verified on real hardware: COM6 (node 1) + COM9 (node 2) on ruv.net.
2026-03-30 15:48:33 -04:00
rUv dd45160cc5 fix: skeleton jitter + person count stability (hardware-verified)
* chore: update vendored ruvector to latest main (v2.1.0-40)

Was at v2.0.5-172 (f8f2c600a), now at v2.1.0-40 (050c3fe6f).
316 commits with new crates: ruvector-coherence, sona, ruvector-core,
ruvector-gnn improvements, and security hardening.

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat: RuVector Phases 2+3 — temporal smoothing, kinematic constraints, coherence gating

Phase 2 (sensing server):
- Temporal keypoint smoothing via EMA (alpha=0.3) with coherence-adaptive blending
- Coherence scoring: running variance of motion_energy over 20 frames
  - Low coherence → reduce alpha to 0.1 (trust measurements less)
- Per-node prev_keypoints for frame-to-frame smoothing
- Bone length clamping (±20%) in derive_single_person_pose

Phase 3 (signal crate):
- SkeletonConstraints: Jakobsen relaxation (3 iterations) on 12-bone
  COCO-17 kinematic tree — prevents impossible skeletons
- CompressedPoseHistory: two-tier storage (hot f32 + warm i16 quantized)
  for trajectory matching and re-ID
- 8 new tests for constraints + history

Vendored ruvector updated to v2.1.0-40 (latest main, 316 commits).
Workspace deps remain at v2.0.4 (crates.io) until v2.1.0 is published.

647 tests pass across both crates (0 failures).

Refs #296

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(server): use max instead of sum for multi-node person aggregation

With nodes in the same room, each node sees the same people. Summing
per-node counts double-counted (2 nodes × 1 person = 2 persons).
Now uses max() so 2 nodes × 1 person = 1 person.

Verified on real hardware: COM6 (node 1) + COM9 (node 2) on ruv.net,
estimated_persons=1 with 1 person in room.

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(server): reduce skeleton jitter + raise person count thresholds

- EMA alpha 0.3→0.15, low-coherence 0.1→0.05
- Remove tick-based noise (main jitter source)
- Breathing 5x slower, extremity jitter 3x smaller, stride 2x smaller
- Person count 1→2 threshold 0.65→0.80
- Aggregation sum→max for same-room nodes

Verified on COM6+COM9: 1 person stable.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-30 15:17:48 -04:00
rUv 5e5781b28a feat: RuVector all phases — temporal smoothing + kinematic constraints + coherence
* chore: update vendored ruvector to latest main (v2.1.0-40)

Was at v2.0.5-172 (f8f2c600a), now at v2.1.0-40 (050c3fe6f).
316 commits with new crates: ruvector-coherence, sona, ruvector-core,
ruvector-gnn improvements, and security hardening.

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat: RuVector Phases 2+3 — temporal smoothing, kinematic constraints, coherence gating

Phase 2 (sensing server):
- Temporal keypoint smoothing via EMA (alpha=0.3) with coherence-adaptive blending
- Coherence scoring: running variance of motion_energy over 20 frames
  - Low coherence → reduce alpha to 0.1 (trust measurements less)
- Per-node prev_keypoints for frame-to-frame smoothing
- Bone length clamping (±20%) in derive_single_person_pose

Phase 3 (signal crate):
- SkeletonConstraints: Jakobsen relaxation (3 iterations) on 12-bone
  COCO-17 kinematic tree — prevents impossible skeletons
- CompressedPoseHistory: two-tier storage (hot f32 + warm i16 quantized)
  for trajectory matching and re-ID
- 8 new tests for constraints + history

Vendored ruvector updated to v2.1.0-40 (latest main, 316 commits).
Workspace deps remain at v2.0.4 (crates.io) until v2.1.0 is published.

647 tests pass across both crates (0 failures).

Refs #296

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-30 14:23:21 -04:00
rUv 6f23e89909 fix: deep review optimizations — firmware + server
* feat(signal): subcarrier importance weighting via mincut partition (Phase 1)

Adds subcarrier_importance_weights() to ruvector signal crate — converts
mincut partition into per-subcarrier float weights (>1.0 for sensitive,
0.5 for insensitive subcarriers).

Sensing server now uses weighted mean/variance in extract_features_from_frame
instead of treating all 56 subcarriers equally. This emphasizes body-motion-
sensitive subcarriers and reduces noise from static multipath.

Expected: ~26% reduction in keypoint jitter (±15cm → ±11cm RMS).

284 tests pass (191 trainer + 51 lib + 18 vital_signs + 16 dataset + 8 multi_node).

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(firmware): stack overflow risk + tick-rate independence (review findings)

Critical fixes from deep review:

1. **Stack overflow prevention**: Moved BPM scratch buffers (br_buf, hr_buf)
   from stack to static storage in both process_frame() and
   update_multi_person_vitals(). Combined stack was ~6.5-7.5 KB of 8 KB
   limit — now reduced by ~4 KB to safe margins.

2. **Tick-rate independence**: Post-batch yield now uses
   pdMS_TO_TICKS(20) with min-1 guard instead of raw vTaskDelay(2).
   Previously assumed 100Hz tick rate.

3. **EDGE_BATCH_LIMIT to header**: Moved from local const to
   edge_processing.h #define for configurability.

Firmware builds clean at 843 KB.

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(server): stale node eviction, remove unsafe pointer (review findings)

Critical fixes from deep review:

1. **Stale node eviction**: node_states HashMap now evicts nodes with no
   frame for >60 seconds, every 100 ticks. Prevents unbounded memory
   growth and stale smoothing data when nodes are replaced.

2. **Remove unsafe raw pointer**: Replaced the unsafe raw pointer to
   adaptive_model (used to break borrow checker deadlock with
   node_states) with a safe .clone() before the mutable borrow.
   AdaptiveModel derives Clone so this is a clean copy.

284 tests pass, zero failures.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-30 13:31:07 -04:00
rUv 1dcf5d42eb feat(signal): subcarrier importance weighting — RuVector Phase 1
Adds subcarrier_importance_weights() to ruvector signal crate — converts
mincut partition into per-subcarrier float weights (>1.0 for sensitive,
0.5 for insensitive subcarriers).

Sensing server now uses weighted mean/variance in extract_features_from_frame
instead of treating all 56 subcarriers equally. This emphasizes body-motion-
sensitive subcarriers and reduces noise from static multipath.

Expected: ~26% reduction in keypoint jitter (±15cm → ±11cm RMS).

284 tests pass (191 trainer + 51 lib + 18 vital_signs + 16 dataset + 8 multi_node).
2026-03-30 13:20:05 -04:00
rUv 9814d2bc62 fix(server): correct RSSI byte offset in frame parser (#332)
The server parsed rssi from buf[14] and noise_floor from buf[15], but
the firmware (csi_collector.c) packs them at buf[16] and buf[17]:

  Firmware:  n_subcarriers=u16(6-7) freq=u32(8-11) seq=u32(12-15) rssi=i8(16)
  Server:    n_subcarriers=u8(6)    freq=u16(8-9)  seq=u32(10-13) rssi=i8(14) ← WRONG

This caused RSSI to read the high byte of the sequence counter instead
of the actual signed RSSI value, producing positive values (e.g., +9)
instead of the correct negative values (e.g., -46 dBm).

Added inline documentation of the frame layout matching csi_collector.c.

Closes #332
2026-03-30 11:54:03 -04:00
rUv 74e0ebbd41 feat(server): accuracy sprint 001 — Kalman tracker, multi-node fusion, eigenvalue counting
Wire three existing signal-crate components into the live sensing path:

Step 1 — Kalman Tracker (tracker_bridge.rs):
- PoseTracker from wifi-densepose-signal wired into all 5 mutable
  derive_pose_from_sensing call sites
- Stable TrackId-based person IDs replace ephemeral 0-based indices
- Greedy Mahalanobis assignment with proper lifecycle transitions
  (Tentative → Active → Lost → Terminated)
- Kalman-smoothed keypoint positions reduce frame-to-frame jitter

Step 2 — Multi-Node Fusion (multistatic_bridge.rs):
- MultistaticFuser replaces naive .sum() aggregation at both ESP32 paths
- Attention-weighted CSI fusion across nodes with cosine-similarity weights
- Fallback uses max (not sum) to avoid double-counting overlapping coverage
- Node positions configurable via --node-positions CLI arg
- Single-node passthrough preserved (min_nodes=1)

Step 3 — Eigenvalue Person Counting (field_model.rs upgrade):
- Full covariance matrix accumulation (replaces diagonal variance approx)
- True eigendecomposition via ndarray-linalg Eigh (Marcenko-Pastur threshold)
- estimate_occupancy() for runtime eigenvalue-based counting
- Calibration API: POST /calibration/start|stop, GET /calibration/status
- Graceful fallback to score_to_person_count when uncalibrated

New files: tracker_bridge.rs, multistatic_bridge.rs, field_bridge.rs
Modified: sensing-server main.rs, Cargo.toml; signal field_model.rs, Cargo.toml

Refs: .swarm/plans/accuracy-sprint-001.md

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-30 15:04:30 +00:00
ruv 7f02c87c6f test(server): add multi-node mesh integration tests (ADR-068)
8 tests covering per-node state pipeline:
- Frame builder validity (CSI + vitals packet formats)
- Different nodes produce different I/Q patterns
- Multi-node UDP send (1/3/5/7/11 nodes)
- Mesh simulation with variable rates and node dropout
- Large mesh: 100 nodes x 10 frames = 1,000 frames
- Max scale: 255 unique node_ids

All 26 server tests pass (8 new + 18 existing vital signs).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-28 11:06:57 -04:00
Taylor Dawson d88994816f feat: dynamic classifier classes, per-node UI, XSS fix, RSSI fix
Complements #326 (per-node state pipeline) with additional features:

- Dynamic adaptive classifier: discover activity classes from training
  data filenames instead of hardcoded array. Users add classes via
  filename convention (train_<class>_<desc>.jsonl), no code changes.
- Per-node UI cards: SensingTab shows individual node status with
  color-coded markers, RSSI, variance, and classification per node.
- Colored node markers in 3D gaussian splat view (8-color palette).
- Per-node RSSI history tracking in sensing service.
- XSS fix: UI uses createElement/textContent instead of innerHTML.
- RSSI sign fix: ensure dBm values are always negative.
- GET /api/v1/nodes endpoint for per-node health monitoring.
- node_features field in WebSocket SensingUpdate messages.
- Firmware watchdog fix: yield after every frame to prevent IDLE1 starvation.

Addresses #237, #276, #282

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 21:21:15 -07:00
rUv 3c02f6cfb0 feat(server): per-node state pipeline for multi-node sensing (#249)
* docs(adr): ADR-068 per-node state pipeline for multi-node sensing (#249)

Documents the architectural change from single shared state to per-node
HashMap<u8, NodeState> in the sensing server. Includes scaling analysis
(256 nodes < 13 MB), QEMU validation plan, and aggregation strategy.

Also links README hero image to the explainer video.

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(server): per-node state pipeline for multi-node sensing (ADR-068, #249)

Replaces the single shared state pipeline with per-node HashMap<u8, NodeState>.
Each ESP32 node now gets independent:
- frame_history (temporal analysis)
- smoothed_person_score / prev_person_count
- smoothed_motion / baseline / debounce state
- vital sign detector + smoothing buffers
- RSSI history

Multi-node aggregation:
- Person count = sum of per-node counts for active nodes (seen <10s)
- SensingUpdate.nodes includes all active nodes
- estimated_persons reflects cross-node aggregate

Single-node deployments behave identically (HashMap has one entry).
Simulated data path unchanged for backward compatibility.

Closes #249
Refs #237, #276, #282

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-27 17:52:51 -04:00
rUv 40f19622af fix(firmware,server): watchdog crash + no detection from edge vitals (#321, #323)
* fix(firmware,server): watchdog crash on busy LANs + no detection from edge vitals (#321, #323)

**Firmware (#321):** edge_dsp task now batch-limits frame processing to 4
frames before a 10ms yield. On corporate LANs with high CSI frame rates,
the previous 1-tick-per-frame yield wasn't enough to prevent IDLE1
starvation and task watchdog triggers.

**Sensing server (#323):** When ESP32 runs the edge DSP pipeline (Tier 2+),
it sends vitals packets (magic 0xC5110002) instead of raw CSI frames.
Previously, the server broadcast these as raw edge_vitals but never
generated a sensing_update, so the UI showed "connected" but "0 persons".
Now synthesizes a full sensing_update from vitals data including
classification, person count, and pose generation.

Closes #321
Closes #323

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(firmware): address review findings — idle busy-spin and observability

- Fix pdMS_TO_TICKS(5)==0 at 100Hz causing busy-spin in idle path (use
  vTaskDelay(1) instead)
- Post-batch yield now 2 ticks (20ms) for genuinely longer pause
- Add s_ring_drops counter to ring_push for diagnosing frame drops
- Expose drop count in periodic vitals log line

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(server): set breathing_band_power for skeleton animation from vitals

When presence is detected via edge vitals, set breathing_band_power to
0.5 so the UI's torso breathing animation works. Previously hardcoded
to 0.0 which made the skeleton appear static even when breathing rate
was being reported.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-27 17:31:06 -04:00
rUv 7a13877fa3 fix(sensing-server): detect ESP32 offline after 5s frame timeout (#300)
The source field was set to "esp32" on the first UDP frame but never
reverted when frames stopped arriving. This caused the UI to show
"Real hardware connected" indefinitely after powering off all nodes.

Changes:
- Add last_esp32_frame timestamp to AppStateInner
- Add effective_source() method with 5-second timeout
- Source becomes "esp32:offline" when no frames received within 5s
- Health endpoint shows "degraded" instead of "healthy" when offline
- All 6 status/health/info API endpoints use effective_source()

Fixes #297

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-03-24 08:00:18 -04:00
rUv 5f3c90bf1c fix(sensing-server): add real hysteresis to person count estimation (#295)
The person-count heuristic was causing widespread flickering (#237, #249,
#280, #292) because:

1. Threshold 0.50 for 2-persons was too low — multipath reflections in
   small rooms easily exceeded it
2. No actual hysteresis despite the comment claiming asymmetric thresholds
3. EMA smoothing (α=0.15) was too responsive to transient spikes

Changes:
- Raise up-thresholds: 1→2 persons at 0.65 (was 0.50), 2→3 at 0.85 (was 0.80)
- Add true hysteresis with asymmetric down-thresholds: 2→1 at 0.45, 3→2 at 0.70
- Track prev_person_count in SensingState for state-aware transitions
- Increase EMA smoothing to α=0.10 (~2s time constant at 20 Hz)
- Update all 4 call sites (ESP32, Windows WiFi, multi-BSSID, simulated)

Fixes #292, #280, #237

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-03-23 21:37:52 -04:00
ruv 47223a98be fix: security hardening — replace fake HMAC, add path traversal protection, OTA auth (ADR-050)
Sprint 1 security fixes from quality engineering analysis (issue #170):

- Replace XOR-fold fake HMAC with real HMAC-SHA256 (hmac + sha2 crates) in secure_tdm.rs
- Add path traversal sanitization on DELETE /api/v1/models/:id and /api/v1/recording/:id
- Default bind address changed from 0.0.0.0 to 127.0.0.1 (configurable via --bind-addr / SENSING_BIND_ADDR)
- Add PSK authentication to ESP32 OTA firmware upload endpoint (ota_update.c)
- Flip WASM signature verification to default-on (CONFIG_WASM_SKIP_SIGNATURE opt-out vs opt-in)
- Add 6 new security tests: HMAC key/message sensitivity, determinism, wrong-key rejection, bit-flip detection, enforcing mode
- Add clap env feature for environment variable configuration

All 106 hardware crate tests pass. Sensing server compiles clean.

Closes #170

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-06 13:11:04 -05:00
rUv 5fa61ba7ea feat: adaptive CSI classifier with signal smoothing pipeline (ADR-048) (#144)
Add environment-tuned activity classification that learns from labeled
ESP32 CSI recordings, replacing brittle static thresholds.

- Adaptive classifier: 15-feature logistic regression trained from JSONL
  recordings (variance, motion band, subcarrier stats: skew, kurtosis,
  entropy, IQR). Trains in <1s, persists as JSON, auto-loads on restart.
- Three-stage signal smoothing: adaptive baseline subtraction (α=0.003),
  EMA + trimmed-mean median filter (21-frame window), hysteresis debounce
  (4 frames). Motion classification now stable across seconds, not frames.
- Vital signs stabilization: outlier rejection (±8 BPM HR, ±2 BPM BR),
  trimmed mean, dead-band (±2 BPM HR), EMA α=0.02. HR holds steady for
  10+ seconds instead of jumping 50 BPM every frame.
- Observatory auto-detect: always probes /health on startup, connects
  WebSocket to live ESP32 data automatically.
- New API endpoints: POST /api/v1/adaptive/train, GET /adaptive/status,
  POST /adaptive/unload for runtime model management.
- Updated user guide with Observatory, adaptive classifier tutorial,
  signal smoothing docs, and new troubleshooting entries.
2026-03-05 10:15:18 -05:00
rUv d4fb7d30d3 fix: complete sensing server API, WebSocket connectivity, and mobile tests (#125)
The web UI had persistent 404 errors on model, recording, and training
endpoints, and the sensing WebSocket never connected on Dashboard/Live
Demo tabs because sensingService.start() was only called lazily on
Sensing tab visit.

Server (main.rs):
- Add 14 fully-functional Axum handlers: model CRUD (7), recording
  lifecycle (4), training control (3)
- Scan data/models/ and data/recordings/ at startup
- Recording writes CSI frames to .jsonl via tokio background task
- Model load/unload lifecycle with state tracking

Web UI (app.js):
- Import and start sensingService early in initializeServices() so
  Dashboard and Live Demo tabs connect to /ws/sensing immediately

Mobile (ws.service.ts):
- Fix WebSocket URL builder to use same-origin port instead of
  hardcoded port 3001

Mobile (jest.config.js):
- Fix testPathIgnorePatterns that was ignoring the entire test directory

Mobile (25 test files):
- Replace all it.todo() placeholder tests with real implementations
  covering components, services, stores, hooks, screens, and utils

ADR-043 documents all changes.
2026-03-03 13:27:03 -05:00
ruv 4b1005524e feat: complete vendor repos, add edge intelligence and WASM modules
- Add 154 missing vendor files (gitignore was filtering them)
  - vendor/midstream: 564 files (was 561)
  - vendor/sublinear-time-solver: 1190 files (was 1039)
- Add ESP32 edge processing (ADR-039): presence, vitals, fall detection
- Add WASM programmable sensing (ADR-040/041) with wasm3 runtime
- Add firmware CI workflow (.github/workflows/firmware-ci.yml)
- Add wifi-densepose-wasm-edge crate for edge WASM modules
- Update sensing server, provision.py, UI components

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-02 23:53:25 -05:00
rUv 113011e704 fix: WebSocket race condition, data source indicators, auto-start pose detection (#96)
* feat: RVF training pipeline & UI integration (ADR-036)

Implement full model training, management, and inference pipeline:

Backend (Rust):
- recording.rs: CSI recording API (start/stop/list/download/delete)
- model_manager.rs: RVF model loading, LoRA profile switching, model library
- training_api.rs: Training API with WebSocket progress streaming, simulated
  training mode with realistic loss curves, auto-RVF export on completion
- main.rs: Wire new modules, recording hooks in all CSI paths, data dirs

UI (new components):
- ModelPanel.js: Dark-mode model library with load/unload, LoRA dropdown
- TrainingPanel.js: Recording controls, training config, live Canvas charts
- model.service.js: Model REST API client with events
- training.service.js: Training + recording API client with WebSocket progress

UI (enhancements):
- LiveDemoTab: Model selector, LoRA profile switcher, A/B split view toggle,
  training quick-panel with 60s recording shortcut
- SettingsPanel: Full dark mode conversion (issue #92), model configuration
  (device, threads, auto-load), training configuration (epochs, LR, patience)
- PoseDetectionCanvas: 10-frame pose trail with ghost keypoints and motion
  trajectory lines, cyan trail toggle button
- pose.service.js: Model-inference confidence thresholds

UI (plumbing):
- index.html: Training tab (8th tab)
- app.js: Panel initialization and tab routing
- style.css: ~250 lines of training/model panel dark-mode styles

191 Rust tests pass, 0 failures. Closes #92.

Refs: ADR-036, #93

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix: real RuVector training pipeline + UI service fixes

Training pipeline (training_api.rs):
- Replace simulated training with real signal-based training loop
- Load actual CSI data from .csi.jsonl recordings or live frame history
- Extract 180 features per frame: subcarrier amplitudes, temporal variance,
  Goertzel frequency analysis (9 bands), motion gradients, global stats
- Train calibrated linear CSI-to-pose mapping via mini-batch gradient descent
  with L2 regularization (ridge regression), Xavier init, cosine LR decay
- Self-supervised: teacher targets from derive_pose_from_sensing() heuristics
- Real validation metrics: MSE and PCK@0.2 on 80/20 train/val split
- Export trained .rvf with real weights, feature normalization stats, witness
- Add infer_pose_from_model() for live inference from trained model
- 16 new tests covering features, training, inference, serialization

UI fixes:
- Fix double-URL bug in model.service.js and training.service.js
  (buildApiUrl was called twice — once in service, once in apiService)
- Fix route paths to match Rust backend (/api/v1/train/*, /api/v1/recording/*)
- Fix request body formats (session_name, nested config object)
- Fix top-level await in LiveDemoTab.js blocking module graph
- Dynamic imports for ModelPanel/TrainingPanel in app.js
- Center nav tabs with flex-wrap for 8-tab layout

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix: WebSocket onOpen race condition, data source indicators, auto-start pose detection

- Fix WebSocket onOpen race condition in websocket.service.js where
  setupEventHandlers replaced onopen after socket was already open,
  preventing pose service from receiving connection signal
- Add 4-state data source indicator (LIVE/SIMULATED/RECONNECTING/OFFLINE)
  across Dashboard, Sensing, and Live Demo tabs via sensing.service.js
- Add hot-plug ESP32 auto-detection in sensing server (auto mode runs
  both UDP listener and simulation, switches on ESP32_TIMEOUT)
- Auto-start pose detection when backend is reachable
- Hide duplicate PoseDetectionCanvas controls when enableControls=false
- Add standalone Demo button in LiveDemoTab for offline animated demo
- Add data source banner and status styling

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-02 13:47:49 -05:00
ruv 8166d8d822 fix: live demo static pose & inaccurate sensing data (issue #86)
- Docker default changed from --source simulated to --source auto
  (auto-detects ESP32 on UDP 5005, falls back to simulation)
- Pose derivation now driven by real sensing features: motion_band_power,
  breathing_band_power, variance, dominant_freq_hz, change_points
- Temporal feature extraction: 100-frame circular buffer, Goertzel
  breathing rate estimation (0.1-0.5 Hz), frame-to-frame L2 motion
  detection, SNR-based signal quality metric
- Signal field driven by subcarrier variance spatial mapping instead
  of fixed animation circle
- UI data source indicators: LIVE/RECONNECTING/SIMULATED banner on
  sensing tab, estimation mode badge on live demo tab
- Setup guide panel explaining ESP32 count requirements for each
  capability level (1x: presence, 3x: localization, 4x+: full pose)
- Tick rate improved from 500ms to 100ms (2fps to 10fps)
- Fixed Option<f64> division bug from PR #83
- ADR-035 documents all decisions

Closes #86

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-02 10:54:07 -05:00
ruv e99a41434d chore: bump workspace to v0.3.0 and publish 15 crates to crates.io
- Workspace version: 0.2.0 → 0.3.0
- All internal path dependency versions updated
- ruvector-crv/gnn gated behind optional `crv` feature (removed [patch.crates-io])
- All 15 crates published to crates.io at v0.3.0

Published crates (in order):
  1. wifi-densepose-core
  2. wifi-densepose-vitals
  3. wifi-densepose-wifiscan
  4. wifi-densepose-hardware
  5. wifi-densepose-config
  6. wifi-densepose-db
  7. wifi-densepose-signal
  8. wifi-densepose-nn
  9. wifi-densepose-ruvector
  10. wifi-densepose-api
  11. wifi-densepose-train
  12. wifi-densepose-mat
  13. wifi-densepose-wasm
  14. wifi-densepose-sensing-server
  15. wifi-densepose-cli

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-02 08:39:23 -05:00
ruv 3febf72674 chore: bump all crates to v0.2.0 for MERIDIAN release
Workspace version 0.1.0 → 0.2.0. All internal cross-crate
dependencies updated to match.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-01 12:14:39 -05:00
ruv 3b72f35306 fix: UI auto-detects server port from page origin (#55)
The UI had hardcoded localhost:8080 for HTTP and localhost:8765 for
WebSocket, causing "Backend unavailable" when served from Docker
(port 3000) or any non-default port.

Changes:
- api.config.js: BASE_URL now uses window.location.origin instead
  of hardcoded localhost:8080
- api.config.js: buildWsUrl() uses window.location.host instead of
  hardcoded localhost:8080
- sensing.service.js: WebSocket URL derived from page origin instead
  of hardcoded localhost:8765
- main.rs: Added /ws/sensing route to the HTTP server so WebSocket
  and REST are reachable on a single port

Fixes #55

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-01 02:09:23 -05:00
rUv 9bbe95648c feat: ADR-024 Contrastive CSI Embedding Model — all 7 phases (#52)
Full implementation of Project AETHER — Contrastive CSI Embedding Model.

## Phases Delivered
1. ProjectionHead (64→128→128) + L2 normalization
2. CsiAugmenter (5 physically-motivated augmentations)
3. InfoNCE contrastive loss + SimCLR pretraining
4. FingerprintIndex (4 index types: env, activity, temporal, person)
5. RVF SEG_EMBED (0x0C) + CLI integration
6. Cross-modal alignment (PoseEncoder + InfoNCE)
7. Deep RuVector: MicroLoRA, EWC++, drift detection, hard-negative mining, SEG_LORA

## Stats
- 276 tests passing (191 lib + 51 bin + 16 rvf + 18 vitals)
- 3,342 additions across 8 files
- Zero unsafe/unwrap/panic/todo stubs
- ~55KB INT8 model for ESP32 edge deployment

Also fixes deprecated GitHub Actions (v3→v4) and adds feat/* branch CI triggers.

Closes #50
2026-03-01 01:44:38 -05:00
ruv 45f0304d52 fix: Review fixes for end-to-end training pipeline
- Snapshot best-epoch weights during training and restore before
  checkpoint/RVF export (prevents exporting overfit final-epoch params)
- Add CsiToPoseTransformer::zeros() for fast zero-init when weights
  will be overwritten, avoiding wasteful Xavier init during gradient
  estimation (~2*param_count transformer constructions per batch)
- Deduplicate synthetic data generation in main.rs training mode

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 23:58:20 -05:00
ruv 3e06970428 feat: Training mode, ADR docs, vitals and wifiscan crates
- Add --train CLI flag with dataset loading, graph transformer training,
  cosine-scheduled SGD, PCK/OKS validation, and checkpoint saving
- Refactor main.rs to import training modules from lib.rs instead of
  duplicating mod declarations
- Add ADR-021 (vital sign detection), ADR-022 (Windows WiFi enhanced
  fidelity), ADR-023 (trained DensePose pipeline) documentation
- Add wifi-densepose-vitals crate: breathing, heartrate, anomaly
  detection, preprocessor, and temporal store
- Add wifi-densepose-wifiscan crate: 8-stage signal intelligence
  pipeline with netsh/wlanapi adapters, multi-BSSID registry,
  attention weighting, spatial correlation, and breathing extraction

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 23:50:20 -05:00
ruv add9f192aa feat: Docker images, RVF export, and README update
- Add docker/ folder with Dockerfile.rust (132MB), Dockerfile.python (569MB),
  and docker-compose.yml
- Remove stale root-level Dockerfile and docker-compose files
- Implement --export-rvf CLI flag for standalone RVF package generation
- Generate wifi-densepose-v1.rvf (13KB) with model weights, vital config,
  SONA profile, and training provenance
- Update README with Docker pull/run commands and RVF export instructions
- Update test count to 542+ and fix Docker port mappings
- Reply to issues #43, #44, #45 with Docker/RVF availability

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 23:44:30 -05:00
ruv fc409dfd6a feat: ADR-023 full DensePose training pipeline (Phases 1-8)
Implement complete WiFi CSI-to-DensePose neural network pipeline:

Phase 1 - Dataset loaders: .npy/.mat v5 parsers, MM-Fi + Wi-Pose
  loaders, subcarrier resampling (114->56, 30->56), DataPipeline
Phase 2 - Graph transformer: COCO BodyGraph (17 kp, 16 edges),
  AntennaGraph, multi-head CrossAttention, GCN message passing,
  CsiToPoseTransformer full pipeline
Phase 4 - Training loop: 6-term composite loss (MSE, cross-entropy,
  UV regression, temporal consistency, bone length, symmetry),
  SGD+momentum, cosine+warmup scheduler, PCK/OKS metrics, checkpoints
Phase 5 - SONA adaptation: LoRA (rank-4, A*B delta), EWC++ Fisher
  regularization, EnvironmentDetector (3-sigma drift), temporal
  consistency loss
Phase 6 - Sparse inference: NeuronProfiler hot/cold partitioning,
  SparseLinear (skip cold rows), INT8/FP16 quantization with <0.01
  MSE, SparseModel engine, BenchmarkRunner
Phase 7 - RVF pipeline: 6 new segment types (Index, Overlay, Crypto,
  WASM, Dashboard, AggregateWeights), HNSW index, OverlayGraph,
  RvfModelBuilder, ProgressiveLoader (3-layer: A=instant, B=hot, C=full)
Phase 8 - Server integration: --model, --progressive CLI flags,
  4 new REST endpoints, WebSocket pose_keypoints + model_status

229 tests passing (147 unit + 48 bin + 34 integration)
Benchmark: 9,520 frames/sec (105μs/frame), 476x real-time at 20 Hz
7,832 lines of pure Rust, zero external ML dependencies

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 23:22:15 -05:00
ruv 1192de951a feat: ADR-021 vital sign detection + RVF container format (closes #45)
Implement WiFi CSI-based vital sign detection and RVF model container:

- Pure-Rust radix-2 DIT FFT with Hann windowing and parabolic interpolation
- FIR bandpass filter (windowed-sinc, Hamming) for breathing (0.1-0.5 Hz)
  and heartbeat (0.8-2.0 Hz) band isolation
- VitalSignDetector with rolling buffers (30s breathing, 15s heartbeat)
- RVF binary container with 64-byte SegmentHeader, CRC32 integrity,
  6 segment types (Vec, Manifest, Quant, Meta, Witness, Profile)
- RvfBuilder/RvfReader with file I/O and VitalSignConfig support
- Server integration: --benchmark, --load-rvf, --save-rvf CLI flags
- REST endpoint /api/v1/vital-signs and WebSocket vital_signs field
- 98 tests (32 unit + 16 RVF integration + 18 vital signs integration)
- Benchmark: 7,313 frames/sec (136μs/frame), 365x real-time at 20 Hz

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 22:52:19 -05:00
ruv d956c30f9e feat: Rust sensing server with full DensePose-compatible API
Replace Python FastAPI + WebSocket servers with a single 2.1MB Rust binary
(wifi-densepose-sensing-server) that serves all UI endpoints:

- REST: /health/*, /api/v1/info, /api/v1/pose/current, /api/v1/pose/stats,
  /api/v1/pose/zones/summary, /api/v1/stream/status
- WebSocket: /api/v1/stream/pose (pose_data with 17 COCO keypoints),
  /ws/sensing (raw sensing_update stream on port 8765)
- Static: /ui/* with no-cache headers

WiFi-derived pose estimation: derive_pose_from_sensing() generates 17 COCO
keypoints from CSI/WiFi signal data with motion-driven animation.

Data sources: ESP32 CSI via UDP :5005, Windows WiFi via netsh, simulation
fallback. Auto-detection probes each in order.

UI changes:
- Point all endpoints to Rust server on :8080 (was Python :8000)
- Fix WebSocket sensing URL to include /ws/sensing path
- Remove sensingOnlyMode gating — all tabs init normally
- Remove api.service.js sensing-only short-circuit
- Fix clearPingInterval bug in websocket.service.js

Also removes obsolete k8s/ template manifests.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 21:29:45 -05:00