Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **Phase 8: UDP→WebSocket multi-target gateway + data association (`serve-demo.py`, `mock_udp_injector.py`, `06-cyber-hud.html`).** `serve-demo.py --gateway` (or `RUVIEW_UDP_GATEWAY=1`; default off, static-serve behaviour unchanged) runs a concurrent UDP:5555 listener + a **from-scratch stdlib WebSocket server** on :8770 (no `websockets` dependency; RFC 6455 handshake, unmasked server text frames) + a 20 Hz broadcaster. It ingests `{"node_id","mac","rssi"}` datagrams, aggregates per-MAC with a node TTL, and broadcasts `{"type":"pose","src":"udp","targets":[{mac,ranges:[{node_id,rssi_dbm}]}]}`. Design correction (surfaced, not silently worked around): the server forwards raw `rssi_dbm` — it can't apply "the UI calibration coefficients" because N/P0 live in the browser; the HUD converts with its live sliders. HUD multi-target association: `activeTargets` Map<mac, track>, each with an independent alpha filter + Gauss-Newton solve + uniquely MAC-hued capsule; unknown MACs spawn a track + log line, 10 s silence GCs it (`TARGET … lost (timeout)`). New **Target Roster** panel (live MAC list with x/y, velocity, and a Focus toggle that camera-locks the orbit pivot onto a capsule). Backward-compatible with the legacy single-target contract; synthetic driver now walks **two** independent devices to demo it. New `mock_udp_injector.py` streams N devices for hardware-free testing. VERIFIED: gateway proven **live** (subprocess + stdlib WS client + real UDP packets → 16/16, incl. RFC 6455 accept-key vector, TTL aggregation, both MACs in one frame); shipped HUD `[MTGT]` block chained with `[MLAT]`+`[SIM-ATTEN]` in Node → 15/15 (deterministic MAC hue, per-target independent solve/filter, GC, two-target LOS 0.10 m vs NLOS-behind-concrete 0.71 m). Note: multi-target + the UDP gateway did **not** exist before this phase (the prior mandate's "accepted port 5555 / multi-target" premise was fictitious — built for real here).
- **Cyber-HUD Phase 7: live calibration + intercept log (`06-cyber-hud.html`), demo 06 in `serve-demo.py` startup listing.** Signal→distance calibration sliders in the layout editor: path-loss exponent N (1.6–4.0) and P₀@1 m (dBm) drive a real log-distance conversion `r = 10^((P0−rssi)/(10N))` for bridges that send `rssi_dbm` instead of `r_m` in `ranges` (new contract option, honest scope: RSSI ranging carries metre-scale error indoors — the residual readout stays the truth-teller); material-loss × slider scales the SIM wall-degradation only. Scrolling intercept log feed (bottom-left, tagged SYS_INT/SIM_ENV/TRK/LAYOUT): bridge connect/loss, track acquired/lost, sim target wall-crossing events (`TARGET_01 crossed brick_wall_05`), per-node sight-line obstructed/restored transitions, node placement/removal, layout export, calibration changes. Shipped-block validation extended to 22/22 (matScale scaling + backward-compat default, log-distance known values incl. N=2 −60 dBm → 10 m, bad-input null). Fixed en route: `connect.wasOpen` read-before-set.
- **Cyber-HUD Phase 5: visual layout editor + material attenuation simulator (`06-cyber-hud.html`).** Layout editor (✎ Layout): drag-in floor-plan image rendered as a translucent textured plane under the grid; double-click the floor to place a node / a marker to remove it (raycast against ground plane and markers — new nodes join the multilateration matrix immediately); live width/height rescaling; one-click export of the current layout as schema-conformant JSON. NLOS attenuation model (SIM ONLY, marked block): proper 2D segment-intersection (orientation test incl. collinear cases) finds every wall crossing each node→target sight line; each crossing adds a **positive range bias + extra noise per material** (drywall σ 0.08 m / brick σ 0.25 m / concrete σ 0.65 m per spec, bias ≈ σ — NLOS ranges read systematically LONG, zero-mean noise alone would be unrealistically kind). Sight lines render colour-coded by worst material (green LOS / amber drywall / magenta brick / red concrete) with an `NLOS nodes` readout; the default sim path threads the demo map's door gap. Shipped-JS validation (extracted `[SIM-ATTEN]` block executed under Node, 16/16): intersection truth table, demo-map crossings incl. the door-gap LOS case, deterministic bias accumulation, measured concrete σ 0.652 vs 0.65 spec — this test caught and fixed a real ×0.577 sampler-scale bug in the shipped noise generator. End-to-end (both shipped blocks chained): mean tracking error 0.110 m LOS → 0.713 m behind concrete, demonstrating the intended solver-vs-degradation behaviour.
- **Range-only multilateration engine (`wifi-densepose-signal::multilateration`).** Closed-form linearized initialization (pairwise sphere-equation differences) refined by damped Gauss-Newton, pure Rust via the crate's Cholesky/Jacobi helpers (no BLAS). `solve_3d` (≥4 nodes with 3D spread) and `solve_2d` (≥3 nodes — the correct mode for near-coplanar indoor arrays, which the 3D solve **refuses as mirror-ambiguous** rather than silently picking a side); `AlphaFilter3` anti-jitter smoother. Degeneracy is refused, never fudged: `InsufficientNodes`, `DegenerateGeometry` (collinear / coplanar-z via node-spread eigenvalue rank check). Honest scope in docs: the solver turns ranges into positions and reports `rms_residual_m` — it cannot make ESP32 RSSI-derived ranges good, and vertical DOP dominates flat arrays (numeric proof, 11/11: exact at zero noise; σ=0.1 m ranges/6 nodes → horizontal p95 0.15 m, 3D p95 0.61 m; alpha filter halves tracking error). 8 Rust unit tests mirroring the proof (uncompiled here — same cargo gate as above).
- **Blueprint ingestion + IPS layer in the Cyber HUD (`06-cyber-hud.html`).** The hardcoded room is gone: a JSON layout (`map_metadata` dims + `nodes[{node_id,x,y,z}]` + `structural_walls[{start,end,material,height?}]`, metres) drives volumetric wall slabs (material-coded: concrete/brick/drywall), sensing-node markers, a size-adaptive RTI voxel field, grid, and camera framing — loaded via `?map=<url>` with an embedded 20×15 m demo default. The `/pose` contract gains optional `target`/`ranges`/`target_true`; when ≥3 map nodes have ranges, an in-browser **2D multilateration solve** (JS mirror of the Rust, alpha-filtered) positions the target capsule + rig + velocity vector + RTI blob in the blueprint frame. The synthetic driver now walks a waypoint loop inside the map emitting σ≈0.25 m noisy ranges plus ground truth, so the full ranges→solve→render pipeline runs end-to-end with a visible true-vs-estimate error readout and an amber ghost marker at sim truth (estimate can never masquerade as ground truth). The **shipped** JS block was extracted and executed under Node: exact recovery 1.8e-15, p95 0.435 m at sim noise, 2-node/collinear/NaN inputs → null (7/7). `wiflow_infer.py` forwards `target`/`ranges`/`target_true` from the upstream frame (forward-only, never synthesised).
- **Adaptive multi-antenna MUSIC AoA engine (`wifi-densepose-signal::aoa_music`).** Pure-Rust (no BLAS — works under `--no-default-features`): M×M complex Hermitian covariance → real-symmetric 2M×2M embedding → cyclic Jacobi eigendecomposition → noise-projector MUSIC pseudo-spectrum `P(θ)=1/(ãᵀQₙã)`, with forward-backward averaging and optional spatial smoothing for coherent multipath. **Aperture-gated**: `M = 1` returns `AoaError::InsufficientAperture` so single-antenna ESP32 streams fall back to the legacy pipeline — the module never fabricates an angle; the high-resolution path unlocks only on real multi-antenna input. Honest scope in the module docs: output is an AoA spectrum per array, **not** positions. Every algorithmic step validated numerically before porting (Jacobi ≡ `numpy.linalg.eigh` on 20 random matrices; embedding noise-projector ≡ complex `|aᴴEₙ|²` over 200 random vectors; peaks exact at ±0.0° for 2 uncorrelated sources M=6 SNR 10 dB; coherent sources resolved via subarray smoothing; M=1 refused). 7 Rust unit tests mirroring the proof.
- **Tikhonov ridge tomography (`RfTomographer::reconstruct_tikhonov`).** Direct one-shot solve of `(WᵀW+λI)x = Wᵀy` via pure-Rust Cholesky (SPD by construction, handles rank-deficient link matrices) alongside the existing iterative ISTA-L1 — no iteration-truncation flicker frame-to-frame, giving the HUD RTI backdrop a temporally stable field. Docs state the validated trade-off honestly: near-exact recovery when links ≥ voxels (corr 1.000 measured in the numeric proof), blurrier-but-support-localizing in the usual underdetermined RTI regime (5/5 true-support voxels in top-5, measured). 4 Rust unit tests incl. forward-model recovery through the tomographer's own weight matrix.
- **CFO/SFO sanitization wired into the live server path (opt-in).** New `phase_sanitizer::unwrap_and_detrend_rows` slice API (no ndarray needed at call sites): per-antenna-row 1D unwrap **then** linear de-trend (unwrap-first is required — fitting a line to wrapped `atan2` phase is meaningless). `wifi-densepose-sensing-server::csi::parse_esp32_frame` applies it to every parsed frame when `SENSING_CFO_SFO_DETREND=1|true` (OnceLock env gate matching the existing `WDP_*` idiom); default off, legacy bit-identical. Unit test covers wrapped multi-row round-trip and invalid-shape no-op.
- NOTE for the three items above: `cargo` was not available in the authoring environment — the Rust was **not** compiled here (algorithms were instead proven in a mirrored numeric harness first). Gate on `cd v2 && cargo test -p wifi-densepose-signal -p wifi-densepose-sensing-server --no-default-features` before merge.
- **CFO/SFO linear phase de-trend in `wifi-densepose-signal` (`PhaseSanitizer::remove_linear_phase`).** Subtracts a per-row least-squares line fit across subcarriers, removing the SFO/STO slope + CFO constant offset (the standard CSI phase-calibration step, SpotFi §3.1) while preserving nonlinear multipath structure. Gated behind a new `enable_cfo_sfo_removal` config flag (default **off**, so no existing test behaviour changes) and inserted into `sanitize_phase` after unwrapping. The exact closed-form fit was validated numerically before porting — output is invariant to any injected linear phase (worst |Δ| = 7.7e-14 over 2000 random CFO/SFO injections; matches `numpy.polyfit`; nonlinear structure corr = 1.000000). Two Rust unit tests added (`test_cfo_sfo_linear_removal_is_invariant`, `test_cfo_sfo_removal_disabled_is_noop`). NOTE: `cargo` was not available in the authoring environment, so these were **not** compiled/run here — verify with `cargo test -p wifi-densepose-signal --no-default-features`.
- **`examples/three.js/demos/06-cyber-hud.html` — standalone cyberpunk composite viewport.** An articulated low-poly rig driven directly from the live `ws://<host>:8770/pose` WiFi feed (the same contract as `examples/through-wall/pose.html`) via **direct quaternionic retargeting** (each bone's rest axis rotated to aim at its COCO keypoint pair, composed in parent-local space in topological order) with **3-frame temporal smoothing + dropout hold** to damp CSI jitter. Composited on a dark cyber-grid with translucent volumetric room walls, a 10×10 **RTI attenuation field** (accepts a real `rti` array from the bridge, else a body-centred demo blob), a target **capsule + velocity vector**, and a **vitals HUD** (Catmull-Rom→bezier breathing waveform, pulsing double-beat heart ring, green→amber→red stress gradient). Reuses the three.js r128 + UnrealBloom stack from demo 05; **no FBX asset needed** (procedural rig) and runs a clearly-labelled synthetic driver when no bridge is present. HONEST SCOPE: WiFi pose is coarse (~59.5% PCK@0.10 vs 50% baseline), the feed is 2D so avatar **depth is estimated for display**, and vitals/RTI are **synthetic unless the bridge supplies them**. Registered as demo 06 in `examples/three.js/index.html`. Core retargeting math validated against real three.js r128 (all 10 driven bones align to target joints at dot=1.000000; smoothing + dropout-hold covered).
- **`wiflow_infer.py` now forwards real vitals into the `/pose` payload (`vitals_fields`).** Breathing rate is taken from an explicit `breathing_rate_bpm` when the sensing pipeline (`csi.rs`) reports one, else **derived from the breathing-band spectral peak** (`features.dominant_freq_hz` in 0.1–0.5 Hz → bpm), and heart rate is forwarded **only** when the feed explicitly carries a plausible `heart_rate_bpm` (40–180). Stress is never emitted and WiFi heart-rate is never synthesised — fields are simply omitted when unavailable, so the HUD shows `sim`. 9/9 extraction unit checks (in-band derivation, out-of-band rejection, explicit-field precedence, no-fabrication guarantees).

### Changed
- **`@ruvnet/rvagent` startup optimization — stdio time-to-first-response ~242 ms → ~189 ms (−22%; MEASURED, median of repeated `initialize` round-trips against `dist/index.js`, this container, reproduce with a piped-stdin timer).** Two changes: (1) `./http-transport.js` is now imported **lazily** inside the `RVAGENT_HTTP_PORT` branch — it chain-loads the MCP SDK's `streamableHttp` module (~48 ms MEASURED via per-module `import()` timing), which the default stdio path never uses; (2) the advertised JSON Schemas generated from the Zod sources are memoized per tool instead of re-walking the Zod tree on every `tools/list` (matters under the session-per-server HTTP model where each session lists tools). No behavior change: 99/99 jest tests, HTTP session flow re-smoke-tested through the lazy path. The `@ruvnet/ruview` harness CLI was profiled too and left alone — 50 ms vs the ~29 ms bare `node -e ''` floor on the same box (MEASURED), i.e. already near the interpreter floor with zero dependencies.

Expand Down
53 changes: 53 additions & 0 deletions _READ_ME_FIRST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# RuView — Cyber-HUD IPS contribution (PR staging copy)

This folder is a **copy** of the files added/changed during the Cyber-HUD +
indoor-positioning work. The originals remain in place in the repo; nothing here
is a move. Paths below mirror the real repo layout so you can review the exact
file set, then apply it to a fork.

## ⚠️ How to actually open the PR (important)

This working tree is **not a git repository** (`git rev-parse` fails here), and a
PR is *not* made by pushing a folder called `PR-github`. If you push this folder
as-is, the upstream repo would get files nested under `PR-github/…`, which is not
a valid contribution. Do it the normal way instead:

1. Fork `github.com/ruvnet/RuView` on GitHub → clone your fork.
2. `git checkout -b feature/cyber-hud-ips`
3. Copy each file below **to its real path** in the fork (the paths shown here,
minus the `PR-github/` prefix — e.g. `examples/three.js/demos/06-cyber-hud.html`).
4. `git add -A && git commit` → `git push -u origin feature/cyber-hud-ips`
5. Open the PR on GitHub from your branch.

Once you've done that, you can delete this `PR-github/` folder — it's only a
staging convenience.

## Files in this contribution

### New files
| Path | What it is |
|------|-----------|
| `examples/three.js/demos/06-cyber-hud.html` | Cyber-HUD dashboard: rigged avatar, RTI backdrop, vitals, blueprint editor, multilateration IPS, multi-target roster, live calibration, intercept log |
| `examples/three.js/server/mock_udp_injector.py` | Mock multi-device UDP injector for hardware-free testing of the gateway |
| `v2/crates/wifi-densepose-signal/src/aoa_music.rs` | Pure-Rust multi-antenna MUSIC AoA (no BLAS), aperture-gated |
| `v2/crates/wifi-densepose-signal/src/multilateration.rs` | Range-only multilateration (2D/3D Gauss-Newton) + alpha filter |

### Modified files
| Path | Change |
|------|--------|
| `examples/three.js/index.html` | Registered demo 06 in the series index |
| `examples/three.js/server/serve-demo.py` | Added demo 06 to listing + optional UDP:5555→WebSocket:8770 gateway (`--gateway`) |
| `examples/through-wall/wiflow_infer.py` | Forwards real vitals + IPS `target`/`ranges` fields into `/pose` |
| `v2/crates/wifi-densepose-signal/src/phase_sanitizer.rs` | CFO/SFO linear phase de-trend (opt-in) |
| `v2/crates/wifi-densepose-signal/src/lib.rs` | Registered `aoa_music` + `multilateration` modules |
| `v2/crates/wifi-densepose-signal/src/ruvsense/tomography.rs` | Added Tikhonov ridge solver alongside ISTA-L1 |
| `v2/crates/wifi-densepose-sensing-server/src/csi.rs` | Wired CFO/SFO de-trend into the live path (env-gated) |
| `CHANGELOG.md` | `[Unreleased]` entries for all of the above |

## ⚠️ Before submitting — two things the maintainer will expect

1. **Compile the Rust.** It was authored where `cargo` was unavailable, so it was
validated by mirrored numeric proofs but **not compiled**. Run and green it
first: `cd v2 && cargo test -p wifi-densepose-signal -p wifi-densepose-sensing-server --no-default-features`
2. **Frontend/gateway JS + Python are verified** (syntax + extracted-block unit
tests + a live UDP→WebSocket integration test), so those are ready to review.
Loading