Commit Graph

174 Commits

Author SHA1 Message Date
rUv 9cd1b8ce2a
research(R12 PABS): NEGATIVE -> POSITIVE — 1161x detection lift via R6.1 forward model (#722)
R12 (tick 5) was a NEGATIVE result: naive SVD-spectrum cosine distance
detected structure changes at 0.69x the natural drift floor (= undetectable).
R12 explicitly identified the revision: 'PABS over Fresnel basis'.

R6.1 (tick 18) shipped the multi-scatterer Fresnel forward operator.
This tick implements PABS on top of it.

PABS = ||y_observed - y_predicted||^2 / ||y_observed||^2

Benchmark (5 m link, 2.4 GHz, subject + 4 wall reflectors expected):

| Scenario                       | PABS / drift  | SVD (R12) / drift |
|--------------------------------|---------------:|------------------:|
| Empty room (subject missing)   |      7,362x   |               65x |
| Subject as expected (sanity)   |          0x   |                0x |
| +1 new furniture               |         84x   |               11x |
| +1 unexpected human            |      1,161x   |               11x |
| Subject moved 10 cm            |     21,966x   |               90x |
| Natural drift (5% wall shift)  |          1x   |                1x |

PABS detects unexpected human at 1161x natural drift; R12 SVD detected
at 11x. ~100x lift purely from physics-grounded prediction vs naive
statistical eigenshift.

R12 NEGATIVE -> POSITIVE. The meta-lesson: a research loop that catalogues
NEGATIVE results creates a backlog of revisitable work that pays off
when later tools become available. R12 -> R12 PABS is the worked example.

R13 cannot be similarly revisited -- its 5 dB shortfall is a hard
physics floor, not a missing model.

The subject-moved-10cm caveat: PABS detects ANY mismatch between
expected and observed scene. Real production PABS needs a pose-aware
forward model that updates from pose_tracker.rs in real-time. The
actual detection signal is PABS-after-pose-update. ~50-100 LOC Rust
glue, catalogued as R12.1 follow-up.

Composes:
- R6.1 unblocked this implementation
- R7 gets precise per-link consistency: residual small on all links =
  no structure; spike on one = local structure OR compromised link;
  mincut disambiguates
- R11 enables maritime container-tamper / hatch-seal apps
- R14 gets V0 security feature (intruder detection w/o biometric storage)
- ADR-029 needs to reference PABS as structure-detection primitive
- R10 PABS-vs-canopy works if forest modelled or learned

Honest scope:
- Pose-PABS closed loop not yet built
- Synthetic data only; real-world drift floor needs measurement
- Population-prior body; per-subject would tighten residual
- Single time-frame; real pipeline needs temporal averaging

Coordination: ticks/tick-19.md, no PROGRESS.md edit.
2026-05-22 03:49:41 -04:00
rUv bac6962689
research(R6.1): multi-scatterer Fresnel — discovers 4.7 dB penalty matching R13's 5-dB shortfall (#721)
Extends R6's point-scatterer to distributed-body model (6 scatterers:
head + chest + 2 arms + 2 legs). Combined CSI = coherent sum of
per-body-part contributions.

Headline finding: 5 m link, 2.4 GHz, subject 25 cm off LOS, breathing
at 0.25 Hz with 8 mm chest amplitude:

| Configuration                          | Breathing SNR (best subcarrier) |
|----------------------------------------|--------------------------------:|
| Single-scatterer ideal (R6)            |  +23.7 dB |
| Multi-scatterer realistic (R6.1)       |  +19.0 dB |
| MULTI-SCATTERER PENALTY                |  +4.7 dB  |

This 4.7 dB penalty matches R13's 5-dB-shortfall finding to within
0.3 dB. R13 NEGATIVE concluded that pulse-contour recovery needs
+25 dB SNR, only +20 dB is available. R6.1 says the 5-dB gap has a
physical origin: static body parts add coherent-sum confusion that
doesn't exist in the idealised single-scatterer model.

The three threads now form a coherent physics story:
- R6   = bound  (idealised single-scatterer = +23.7 dB)
- R6.1 = floor  (realistic 6-scatterer    = +19.0 dB)
- R13  = failure (contour needs +25 dB, gets +20 dB)

Pulse-contour recovery is bounded below by what R6.1 leaves achievable,
which is 4.7 dB worse than R6's idealised limit, enough to make R13's
contour recovery infeasible.

Per-body-part contribution: chest = 27.6% of CSI energy (5x per-limb
reflectivity). The chest IS the breathing signal; limbs are confound.

Architectural implications:
- Chest-centric placement targeting (R6.2.3 motivated)
- Mask limbs in vital_signs pipeline (use pose pipeline ADR-079/101)
- R14 V3 rescope to rate-only (no contour-shape recovery)
- R12 PABS revision unblocked: R6.1 is the explicit A(voxel) operator

Surprise finding: on-LOS placement (y=0) is degenerate -- path delta
is 2nd-order in offset for on-LOS scatterers, so breathing barely
changes path length. Real installations need subject OFF the LOS
line. The R6.2 placement search should respect this.

Honest scope:
- 6 scatterers is 1st-order; 50-100 voxel body would refine
- Reflectivity ratios are guesses (RCS measurements would refine)
- Static body assumption (limbs do micro-move during breathing)
- 2D top-down, no multipath (model general enough to include them)

Composes:
- R5: subcarrier selection picks reliable, not high-SNR
- R6: per-scatterer building block
- R6.2.x: chest-centric placement
- R7: residual-vs-forward-model = tighter adversarial detection
- R12 NEGATIVE: PABS A operator unblocked
- R13 NEGATIVE: 5-dB gap has physical origin
- R14 V3: needs rescope

Coordination: ticks/tick-18.md, no PROGRESS.md edit.
2026-05-22 03:36:42 -04:00
rUv 065521dc9e
research(R6.2.2): N-anchor multistatic placement saturation — practical knee at N=5 (#720)
Extends R6.2 from single-pair to N-anchor placement search via union of
all C(N,2) pairwise Fresnel ellipses. Greedy + K=8 random restarts.

Saturation curve on 5x5 m bedroom (3 target zones: bed + chair + desk,
40 wall-candidates, 434 grid points, 2.4 GHz):

| N | Pairs | Coverage | Marginal |
|---|------:|---------:|---------:|
| 2 |     1 |   35.7%  |  +35.7 pp |
| 3 |     3 |   63.4%  |  +27.6 pp |
| 4 |     6 |   86.2%  |  +22.8 pp |
| 5 |    10 |   96.8%  |  +10.6 pp |  <- knee
| 6 |    15 |  100.0%  |   +3.2 pp |
| 7 |    21 |  100.0%  |   +0.0 pp |

Practical knee at N=5. Past this, diminishing returns.

Three regimes:
- Single-feature (presence):       2-3 anchors  (36-63%)
- Multi-feature (pose+vitals+count): 4-5 anchors  (86-97%)
- Mission-critical (medical):       6 anchors   (100%)
- Beyond 6:                         wasted

Cost-optimisation: Cognitum Seed BOM is 9-15 USD. The 4->5 anchor jump
buys +10.6 pp coverage; the 5->6 jump buys only +3.2 pp for the same
cost. Consumer recommendation: 5 anchors. Commercial / medical: 6.

Convenient numerology: N=5 simultaneously satisfies three other
constraints:
1. R7 multi-link mincut: needs N >= 4 for single-anchor-compromise
   detection
2. ADR-105 federation Krum: f=1 byzantine tolerance requires K >= 5
3. R6.2.2 coverage knee: 5 hits practical saturation

These all bound by similar inverse-square-of-geometry scaling, so the
alignment is not coincidental.

ADR-029 (multistatic) didn't specify anchor counts; R6.2.2 fills that
gap with a benchmark-backed number.

Honest scope: single 5x5m geometry tested, 2D still (R6.2.1 = 3D not
yet built), free-space (multipath adds +5-15% beyond Fresnel), greedy
with 8 restarts approximates global optimum to 1-2 pp.

Composes with:
- R6/R6.2 (direct generalisation)
- R7 (mincut needs N>=4)
- R1 (placement x precision = full geometry budget)
- ADR-029 (architectural recommendation now has a number)
- ADR-105 (Krum bound matches)
- R10, R11, R14 (other geometries / use cases)

Coordination: ticks/tick-17.md, no PROGRESS.md edit.
2026-05-22 03:17:14 -04:00
rUv 719875ea1d
research(R6.2): Fresnel-aware antenna placement — 93x sensing-coverage lift from physics alone (#719)
First deferred follow-up from R6. Productises R6's Fresnel forward model
into a 2D placement-search CLI: given a room + target occupancy zones,
recommend Tx/Rx positions that maximise first-Fresnel coverage.

Benchmark on 5x5 m bedroom (bed 3 m^2 + chair 0.64 m^2, 2900 pairs
evaluated at 2.4 GHz):
- OPTIMAL: 51.1% coverage (Tx 1.25,0; Rx 4.75,5; diagonal 6.10 m link)
- MEDIAN:  0.5% coverage
- WORST:   0.0% coverage
- 93x improvement, median to optimal

Counter-intuitive insight: longer links cover MORE space. Fresnel envelope
width = sqrt(d * lambda) / 2 grows with link length, so the 6.10 m
diagonal beats wall-parallel 5.00 m links. Up to the R10 link-budget
gate.

Per-cog deployment recommendations:
- cog-person-count: diagonal across longest axis
- cog-pose: zone inside ~50% midpoint envelope
- AETHER re-ID: Tx near doorway, Rx diagonal
- cog-maritime-watch: vertical diagonal through cabin
- cog-wildlife (future): Tx/Rx opposite trees, threading clearing midline

Improvements come from physics, not algorithms - no model retraining
needed. Existing customers can re-mount seeds today for 10-100x better
sensing.

Honest scope: 2D approximation, free-space, rectangular zones, single-pair
only, perimeter-only candidates, no link-budget gate.

CLI shape ready for productisation as 'wifi-densepose plan-antennas'.
Also surfaces as a deferred MCP tool 'ruview_placement_recommend'.

Composes with:
- R6 (direct 2D extension)
- R1 (placement x precision = full geometry budget)
- R10 (sets the link-budget gate this ignores)
- R11 (same recipe in steel cabins)
- R14 (determines whether V1/V2/V3 see the right occupant)
- ADR-105 (better placement = faster epsilon convergence)

Next R6.2 follow-ups catalogued: R6.2.1 (3D), R6.2.2 (N-anchor union),
R6.2.3 (pose-trajectory target zones).

Coordination: ticks/tick-16.md, no PROGRESS.md edit.
2026-05-22 03:04:17 -04:00
rUv 28d97e8f6a
adr-106: differential privacy + biometric primitive isolation for federation (#718)
Direct extension of ADR-105. Closes both items deferred from ADR-105:
(1) member-inference defence, (2) biometric primitive isolation
enforcement.

Three-layer defence:
1. PRIMITIVE ISOLATION (R15 binding) -- API-level tagging of on-device-
   only tensors. Compile-time error when  tagged tensors are passed
   to submit_delta().
2. GRADIENT CLIPPING (Abadi 2016) -- per-sample L2 norm <= C (default
   C=1.0) before delta computation.
3. GAUSSIAN NOISE (DP-SGD) -- N(0, sigma^2*C^2*I) added to aggregated
   LoRA delta before transmission.

Privacy budget via Moments Accountant (delta=1e-5):
- Conservative (medical-grade): sigma=1.5, 50 rounds, epsilon=2.0
- Standard (typical RuView):    sigma=1.0, 100 rounds, epsilon=5.0
- Lenient:                      sigma=0.5, 100 rounds, epsilon=8.0

On-device-only primitive list (R15-binding):
- Raw CSI window
- Gait stride frequency
- Breathing rate (per-subject)
- HRV rate signature
- RCS frequency response curve
- Limb timing vector
- Per-subject embedding centroid

Implementation budget: +300 LOC on top of ADR-105's 500 LOC = total
~800 LOC ruview-fed crate. 3-week effort estimate.

Composes:
- R3: Layer 1 blocks per-subject embedding centroid transmission
- R7: mincut compatible with DP-noised deltas (operates on noised graph)
- R12/R13 negative results: informed the noise-vs-structure-detection
  design choice (treat adversarial deltas as outliers from noisy
  distribution, not structural-detection problem)
- R14: privacy framework now has formal (epsilon, delta) backing
- R15: requirements basis = on-device-only primitive list made executable
- ADR-105: DP-SGD slots into step 4 of federation protocol

Closes the privacy story: R3 + R14 + R15 + ADR-105 + ADR-106 = complete
chain from physics (R6) -> embeddings (R3) -> personalised features (R14)
-> trained how (ADR-105) -> defended how (R7) -> privacy-bounded how
(ADR-106).

Honest scope:
- sigma values are recommendations, not measurements (per-cog tuning needed)
- (epsilon, delta)-DP is worst-case bound; auxiliary info changes practical leakage
- Moments Accountant is conservative
- Subject-level DP not formalised (household of 4 = K=4 subjects)
- Side-channel timing leaks out of scope (future ADR)

Explicitly deferred:
- ADR-107: cross-installation federation w/ secure aggregation

Coordination: ticks/tick-15.md, no PROGRESS.md edit.
2026-05-22 02:48:16 -04:00
rUv 50029d6eb2
research(R15): RF biometric primitives — 5 environment-invariant features with quantified discriminability (#717)
Catalogues 5 biometric primitives in CSI that survive cross-environment
transfer by physical construction (not just statistical learning), with
quantified discriminability:

| Primitive                          | Bits | Invariance |
|------------------------------------|-----:|------------|
| Gait stride frequency              |   5  | HIGH       |
| Breathing rate + envelope          |   5  | HIGH       |
| HRV (rate-level only)              |   4  | HIGH at rate, LOW at contour |
| Body-size RCS frequency response   |   4  | MEDIUM (needs calibration target) |
| Walking dynamics (limb timing)     |   7  | HIGH (if pose works cross-room) |

Composite biometric strength: ~12-15 bits realistic vs 25-bit independence
upper bound. Enough for household + building-scale ID; insufficient for
forensic / city-scale.

R15 strengthens the R14/R3/ADR-105 privacy framework: RF biometric is
PHYSICAL not learned, so the same primitive that enables empathic
appliances is a surveillance primitive that's harder to opt out of than
visual ID. There is no behavioural countermeasure short of jamming
(illegal) or physical alteration (impossible).

Surfaces required amendment to ADR-105 federation protocol:
'The federation aggregator MUST NOT receive any raw per-subject biometric
primitive. It MAY receive aggregated, MERIDIAN-normalised model deltas.
Per-subject primitives stay on-device.'

This becomes the requirements basis for ADR-106 (deferred DP-SGD ADR).

R15 closes the last unaddressed PROGRESS.md research thread. After R15:
- Closed: 'what RF biometrics exist and how do they invariantise' = answered
- Open: ADR-106, R6.1 multi-scatterer, R3 physics-informed env prediction,
  R6.2 Fresnel-aware antenna placement

The per-occupant feature surface (R14 V1/V2/V3) is now fully grounded in
physics + constraints; remaining work is implementation, not research.

Composes with every prior thread:
- R5 saliency: primitive-specific maps
- R6 Fresnel: physical basis for RCS invariance
- R7 mincut: defends primitive-level poisoning
- R10 per-species gait: transfers to per-individual gait biometric
- R13 NEGATIVE: 5-dB-short wall rules out contour-level HRV
- R3: embedding space combines 5 primitives
- R14: all 3 verticals (V1/V2/V3) work with rate-level subset

Honest scope:
- Bit counts are upper bounds; 30-50% loss to noise/multipath
- Contour-level HRV not achievable (R13 wall)
- Walking dynamics 7-bit assumes pose-from-CSI works cross-room (unmeasured)
- Body-size RCS needs calibration target in new room

Coordination: ticks/tick-14.md, no PROGRESS.md edit.
2026-05-22 02:38:10 -04:00
rUv 09fe73eb87
research(R4) + adr-105: federated CSI training with MERIDIAN+Krum+mincut (#716)
Federated learning is the unique design that satisfies the three
constraints from this loop's earlier work:
- R14 (data stays on-device)
- R3  (no cross-installation linkage)
- R7  (multi-node adversarial defence)

ADR-105 proposes MERIDIAN-FedAvg with Byzantine-robust (Krum)
aggregation and R7-style Stoer-Wagner mincut on inter-node update
similarity. Per-round bandwidth at typical 4-seed installation:
~12 MB; weekly cadence x monthly = 50-180 MB/month (0.06% of home
broadband cap).

Composes with every prior thread:
- R3 MERIDIAN centroid subtraction is mandatory pre-aggregation
- R7 mincut extended from multi-link CSI to multi-node updates
- R12/R13 negative results informed the byzantine + SNR-threshold choices
- R14 privacy framework baseline is now operational
- ADR-024/027/029/100/103/104 all bridged in the ADR

Implementation plan: ~500 LOC for ruview-fed crate. Krum aggregator
(80 LOC), LoRA+int8 delta codec (120 LOC, reuse ruvllm-microlora),
MERIDIAN centroid hook (50 LOC, extend AgentDB), inter-seed mincut
(100 LOC, reuse ruvector-mincut), CLI surface (80 LOC).

Explicitly deferred:
- Cross-installation federation (legal + DP work needed, future ADR)
- Member inference defence (ADR-106 with formal DP-SGD)
- Per-cog training-loop details (each cog implements local_train)
- Compute scheduling (cognitum fleet manager territory)

Tick chose the 'one ADR' unit from the cron prompt rather than another
numpy demo -- federation is fundamentally a protocol-design problem,
not a numerical-experiment problem.

Coordination: ticks/tick-13.md, no PROGRESS.md edit.
2026-05-22 02:24:42 -04:00
rUv db64b4c671
research(R3): cross-room re-ID — MERIDIAN closes the env-shift gap + 4 privacy constraints (#715)
Synthesis of AETHER (ADR-024) + MERIDIAN (ADR-027) + privacy framing
+ identified next research lever (physics-informed env prediction).

Simulation results (10 subjects, 3 rooms, 128-dim embeddings, env/person
scale ratio 4.7x):

| Configuration                            | 1-shot acc |
|------------------------------------------|-----------:|
| Within-room (matches AETHER ~95% target) |      100%  |
| Cross-room, raw cosine K-NN              |       70%  |
| Cross-room, MERIDIAN 100% env removal    |      100%  |
| Cross-room, MERIDIAN 70% env removal     |      100%  |
| Chance                                   |       10%  |

The 30 pp gap from within-room to raw cross-room is the angular
contribution of env-shift that cosine similarity can't normalise away.
MERIDIAN per-room centroid subtraction recovers it -- robust even at
70% effectiveness (realistic for limited labelled examples).

Privacy framing: R14 baseline + 4 new constraints specific to
biometric-class re-ID data:
1. No cross-installation linkage
2. Embedding storage requires explicit opt-in (biometric consent class)
3. Cryptographically verifiable forgetting
4. No re-ID across legal entities

These rule out cross-building tracking, mass surveillance, long-term
unlabelled storage, third-party sharing. They allow per-installation
personalisation, household anomaly detection, multi-person pose
association in the same room.

R3 closes the loop on R14's empathic-appliance vision: re-ID is THE
primitive that makes per-occupant features possible. Without R3,
R14's verticals can't ship.

Identifies next research lever: physics-informed env_sig prediction
from R6's forward operator + room map = zero-shot cross-room transfer
without labelled examples in the new room.

Composes:
- R5/R6: person+env decomposition in embedding space
- R7: mincut = defence against re-ID spoofing
- R9: RSSI K-NN showed env-locality dominance for the K-NN primitive
- R14: 4 new constraints extend R14's framework to biometric class

Honest scope: additive decomposition is first-order; real CSI env
effects are multiplicative in subcarrier domain. Adversarial scenarios
not simulated.

Coordination: ticks/tick-12.md, no PROGRESS.md edit.
2026-05-22 02:13:10 -04:00
rUv bcfdf0a4d0
research(R13): NEGATIVE — contactless BP from CSI is physically inferior to a cuff (#713)
Critical-physics scrutiny of published 'contactless BP from WiFi CSI'
claims (Yang 2022, Liu 2021, others). Four physics floors quantified;
all four make CSI-based BP provably worse than a 20 dollar arm cuff.

1. PTT temporal resolution: need 0.5 ms for 1 mmHg precision; ESP32-S3
   maxes at 1 ms (1000 Hz CSI) and typical deployment is 10 ms (100 Hz)
   = 20 mmHg precision floor. Achievable but requires sacrificing every
   other sensing pipeline.

2. Spatial separation: carotid-femoral distance 55 cm, Fresnel envelope
   at 5 m link is 40 cm. Single-link CSI cannot resolve the two sites
   independently. Multistatic with 4-6 anchors is severely ill-posed
   (same regime that defeated R12).

3. Pulse-contour SNR: pulse motion at chest is 0.3 mm; breathing is
   8 mm (27x larger). After 4th-order bandpass we get +20 dB HR-band
   SNR; literature (Mukkamala 2015) says +25 dB minimum for waveform-
   shape recovery. **5 dB short.**

4. Vs 0 arm cuff: best published CSI BP is +/-10 mmHg with per-subject
   calibration; arm cuff is +/-2 mmHg uncalibrated. CSI is 5x worse
   AND requires calibration the user doesn't otherwise need.

Verdict: do not ship BP as a primary RuView feature. The breathing/HR
features we already ship work because their motion amplitudes are
30-100x larger than the pulse waveform. Adding BP would force 1 kHz
CSI rate (degrading every other pipeline), require per-subject
calibration (defeating no-setup story), and ship a feature that's
worse than a 20 dollar device the user can buy.

Three niche scenarios remain open:
- Single-subject trend monitoring (relative not absolute)
- Bed-instrumented controlled-still subject (25+ dB achievable)
- Multistatic PWV with 6+ anchors + per-installation calibration

The general 'BP from a 9 dollar ESP32 in the corner' claim does not close.

Composes:
- R1 (CRLB) confirms temporal-resolution floor for PTT
- R6 (Fresnel) provides the spatial floor that defeats two-site PTT
- R5 (saliency) explains why whole-chest observable but 0.3 mm pulse not
- R12 = loop's other negative result, same failure pattern
- R14's assumption (no BP) is now empirically validated

Two negative results in this loop (R12, R13) prevent the field from
biasing toward overclaiming. This is the most valuable kind of tick
because it marks BP-from-CSI as off-roadmap with explicit numbers, so
future contributors don't waste cycles attempting it.

Coordination: ticks/tick-11.md, no PROGRESS.md edit.
2026-05-22 02:00:35 -04:00
rUv 4072455d1e
research(R11): maritime sensing — through-bulkhead impossible, through-seam works (#712)
Physics scrutiny of WiFi-band maritime sensing scenarios. Steel skin depth
is 3.25 um at 2.4 GHz, making bulkheads utterly opaque. Saltwater
attenuation is 853 dB/m. The 'through-bulkhead WiFi radar' framing
common in conservation/maritime is wrong; the actual feasible category
is 'through-seam' sensing exploiting slot diffraction through gaskets,
hatch seals, and vent grilles.

Composite link budget for 7 maritime scenarios (ESP32-S3 121 dB budget,
10 dB SNR margin):

FEASIBLE:
- Man-overboard surface @ 200 m: +25 dB
- Cabin door, 2 mm seam:         +31 dB
- Cabin door, 5 mm seam:         +39 dB
- Container, 30 mm vent slot:    +45 dB

IMPOSSIBLE:
- Closed 10 mm steel door:       -938 dB
- Submarine pressure hull:       -929 dB
- Head 30 cm underwater:         -231 dB

Five feasible verticals catalogued: man-overboard surface, through-seam
crew vitals, container tamper detection, hatch-seal predictive
maintenance, engine-room thermal anomaly via condensation.

Composes with prior threads:
- R6 Fresnel envelope + slot diffraction = narrower composite envelope
- R10 link-budget primitives reused unmodified for air-side maritime
- R7 multi-link consistency essential against superstructure jammers
- R14 privacy framework transfers directly to crew-cabin monitoring

Honest scope: best-case ignores vessel vibration (5-30 Hz, in-band with
R10 gait frequencies), engine ignition noise, salt-spray, steel-surface
multipath. Maritime gait-classification is harder than land.

The romantic 'through-hull radar' is now explicitly debunked. The actual
product roadmap is gasket-leakage sensing, surface detection, and
predictive-maintenance audits.

Coordination: ticks/tick-10.md, no PROGRESS.md edit.
2026-05-22 01:53:51 -04:00
rUv a1bbe2e8a6
research(R1): ToA CRLB — precision floor for WiFi multistatic localisation (#711)
Quantitative Cramer-Rao Lower Bound analysis for WiFi ranging via both
Time-of-Arrival and phase-based methods, with multistatic 4-anchor
position-error budget.

Headline (20 MHz HT20, 20 dB SNR, 100 averaged frames):
- ToA range CRLB:     4.1 cm
- Phase (5 deg noise): 0.17 mm
- Phase advantage:    240x (after ambiguity resolution)

4-anchor convex-hull room (GDOP 1.5):
- ToA position precision:   25 cm  (room-pose-quality floor)
- Phase position precision:  1 mm  (RTK-quality, ambiguity-resolved)

This is the strongest architectural lever this loop has surfaced for
ADR-029 (multistatic sensing). The current learning-based attention
approach has no provable precision floor; an explicit ToA-then-phase
pipeline sits within 2x of CRLB by Kay's theory.

Composes cleanly with R6:
- R6 gives the spatial sensitivity envelope (40 cm Fresnel at 2.4 GHz)
- R1 gives the ranging precision within it (1 mm phase, 4 cm ToA averaged)
- Independent, additive, together bound full multistatic geometry budget

Closes a gap R10 created: foliage drops SNR, which directly worsens
ToA CRLB. A 50 m foliage link at 5 dB SNR drops to ~1 m ToA precision.
R10's 100 m sparse-foliage range is *detectable* not *localisable*.

Honest scope:
- CRLB is a lower bound; real estimators sit 1-2x above it
- 5 deg phase noise assumes phase_align.rs is applied
- Multipath degrades CRLB by 2-5x even with MUSIC super-resolution
- Integer-ambiguity (cycle-slip) is unsolved per-subcarrier; needs
  multi-subcarrier wide-lane unwrap

Coordination: ticks/tick-9.md, no PROGRESS.md edit.
2026-05-22 01:38:35 -04:00
rUv 650612e5a2
research(R6): Fresnel-zone forward model — bedrock physics for CSI sensitivity (#710)
The workspace DSP (vital_signs, multistatic, pose_tracker, tomography)
implicitly assumes a forward model that maps scatterer geometry to
per-subcarrier phase shifts. Nobody had written it down. This tick
makes it explicit.

Closed-form first-Fresnel-zone radius + point-scatterer path-delta +
per-subcarrier phase prediction over 802.11n/ac 20 MHz channels (52
subcarriers, 312.5 kHz spacing). Pure NumPy demo + JSON output for
downstream consumers.

Headline numbers:
- 5 m link first-Fresnel radius @ midpoint: 40 cm (2.4 GHz), 27 cm (5 GHz)
- Inside zone-1: phase spread <0.5 deg across 52 subcarriers (band-flat)
- Outside zone-1: phase spread up to 16 deg (band-dispersed)

This unifies R5 + R6: R5's experimentally measured band-spread top
subcarriers is exactly what the Fresnel forward model predicts for
zone-1 occupancy.

Closes the loop on three earlier threads:
- R7 (mincut adversarial) gets a precise definition of 'physically
  inconsistent' instead of a learned classifier
- R10 (foliage range) needs to retract 100 m sparse estimate to ~70 m
  to account for Fresnel-zone obstruction
- R12 (eigenshift negative result) gets its revision basis: PABS over
  Fresnel-grounded forward operator

Honest scope: point-scatterer only, first Fresnel only, frequency-flat
reflectivity, LOS-only (no multipath). The scalar version is the right
first-order approximation; volume-integral / multi-zone / multipath
extensions catalogued as R6.1+R6.2 follow-ups.

Coordination: ticks/tick-8.md, no PROGRESS.md edit.
2026-05-22 01:31:09 -04:00
rUv 7bd188ab60
research(R14): empathic appliances — vision + ethical framework + infrastructure gap inventory (#709)
Speculative 10-20y vision thread covering three concrete vertical sketches:

* V1 stress-responsive lighting (5y) — breathing-rate baseline + warm-shift lights
* V2 adaptive HVAC for thermal-stress envelopes (10y) — published HVAC-personalisation 15-20% energy savings
* V3 conversational appliances respecting attention state (15y) — don't interrupt during focused work

Maps existing RuView components to each: 5 already shipped (breathing rate
detector, occupancy gates via cog-pose / cog-count, motion intensity, partial
RollingP95 baseline learner, MCP API via ADR-104), 4 still to build (full per-room
baseline learner, state classifier model, MCP vitals subscribe tool, consent UI).

Ethical framework drafted as binding constraints any product must honour:
1. Opt-in by default — sensing on only after active enable
2. Data stays on-device — per-second values never cross the building boundary
3. Override is one tap — physical kill switch must work without WiFi/cloud

6-row privacy threat model with mitigations: compromised appliance, MCP raw-signal
leak, adversarial poisoning (mitigated by R7 multi-link consistency), long-term
re-identification, insurance/employer access, non-consenting cohabitants.

Honest scope: clinical breathing-rate-as-stress literature is lab-condition adults;
real-home generalisation unproven. R14 is CSI-only (RSSI loses the per-subcarrier
shape needed for shallow-breathing-during-focus signature), bounds rollout to
ESP32-S3-class deployments.

Connections established to R5, R7, R8, ADR-103, ADR-104. Identifies ruview_vitals_subscribe
as the highest-leverage next MCP tool addition.

Coordination: ticks/tick-7.md, no PROGRESS.md touch.
2026-05-22 01:18:01 -04:00
ruv 2e742305ba research(R10): through-foliage wildlife sensing — physics feasibility + per-species gait taxonomy
ITU-R P.833-9 vegetation-attenuation model + ESP32-S3 link-budget
solver produce bounded sensing range estimates per frequency and
foliage density. Plus a biomechanics-grounded gait-frequency taxonomy
spanning bears (0.5 Hz) to mice (15 Hz).

Headline ranges (121 dB link budget, 10 dB SNR margin):

  freq    sparse   moderate   dense
  2.4 GHz 99.6 m   12.0 m     4.1 m
  5 GHz   19.9 m   5.2 m      2.1 m

The 2.4 GHz / sparse cell (~100 m) is the practical sweet spot —
10x camera-trap coverage, always-on rather than PIR-triggered.

Honest scope called out explicitly: this is feasibility math, not
field measurements. Animal cooperation, foliage flutter, regulatory
limits, and BSSID-fingerprint degradation in remote forest are all
real follow-up problems.

Vertical applications (10-20 year horizon) catalogued:
- Endangered-species population census
- Wildlife corridor verification
- Invasive-species early warning
- Anti-poaching (human gait well-separated from wildlife)
- Livestock-on-rangeland tracking
- Agricultural pest control

Cross-connects to:
- R5 (saliency is task-specific — per-species classifier needs own
  saliency map, same lesson as R12)
- R8 (wildlife sensing wants CSI not RSSI for per-subcarrier shape)
- R9 (fingerprint K-NN primitive transfers to per-individual ID)
- R7 (multi-link consistency for corridor coverage)

Pure-NumPy, no framework deps. ITU model + binary search solver.
Coordination: tick avoided PROGRESS.md to prevent races (horizon-
tracker M3+ track concurrent at the time).

Files:
* examples/research-sota/r10_foliage_attenuation.py
* examples/research-sota/r10_foliage_results.json
* docs/research/sota-2026-05-22/R10-through-foliage-wildlife.md
* docs/research/sota-2026-05-22/ticks/tick-6.md
2026-05-22 00:59:11 -04:00
ruv 6bfb29accf docs(horizon): M3-M7 complete — close 12h autonomous SOTA run
Mark M2-M7 COMPLETE in HORIZON.md; add Session 2 log; write final
summary table (shipped/deferred), npm publish commands, and horizon
verdict. All 6 milestones finished ahead of 08:00 ET auto-stop.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-22 00:06:40 -04:00
rUv 6b35896847
research(R12): RF weather mapping eigenshift — negative-ish, with clearly-actionable revision path (#707)
Tests the simplest possible algorithm for RF-weather change detection:
SVD on per-frame CSI matrix, top-10 singular values, cosine distance
between spectra over time. Hypothesis: a synthetic structural
perturbation (15 percent attenuation on 3 top-saliency subcarriers)
should produce a larger spectral shift than natural temporal drift
from operator movement in the same recording.

Result honestly: it does not. The perturbation distance (0.00024) is
*smaller* than the control distance (0.00035) — signal/drift ratio
0.69x. The top-K SVD-spectrum cosine is too coarse to detect
small-magnitude subcarrier-specific structural changes against an
operator-noise background.

Three concrete fixes identified for follow-up ticks:
1. Principal angles between subspaces (PABS), not cosine on singular
   values — catches subspace rotations the spectrum misses
2. Per-subcarrier residual analysis after projecting onto baseline
   subspace — localises the perturbation
3. Multi-day baseline — knocks down operator-noise floor by 50-100x

Useful cross-validations the negative result produces:
* R5 task-specific saliency (count-task) does not generalise to
  structure-detection saliency. Same data, different relevant
  features. Publishable distinction.
* R12 is CSI-only territory — RSSI is the trace of the CSI
  covariance, so if top-10 SVD-spectrum can't see this, RSSI can't
  either. Bounds R8 commercial-enablement story to counting only.
* R7 SVD-spectrum primitive that worked for adversarial detection
  fails here at lower perturbation magnitude. Sensitivity does NOT
  scale with subtlety — confirms the algorithm is magnitude-dominated.

Long-horizon vision (building structural monitoring, earthquake drift,
HVAC audits, climate-controlled-archive surveillance) preserved in the
research note — the physics is right, the hardware is sufficient,
the deployment story works. Just need PABS + multi-day data.

Coordination note: this tick avoided PROGRESS.md edits entirely
because horizon-tracker is concurrently editing it. Tick-5 summary
written to ticks/tick-5.md (new self-contained convention) so the
08:00 ET final summary can consolidate without conflicts.

Files:
* examples/research-sota/r12_rf_weather_eigenshift.py
* examples/research-sota/r12_rf_weather_results.json
* docs/research/sota-2026-05-22/R12-rf-weather-mapping.md
* docs/research/sota-2026-05-22/ticks/tick-5.md
2026-05-21 23:52:49 -04:00
rUv 2783f40bd1
feat(tools/ruview-mcp): M2 — wire real inference via cog health (#706)
* research(R9): RSSI fingerprint K-NN — 2.18x lift (MODERATE); surfaces counting-vs-localization asymmetry

Hypothesis: if temporal proximity correlates with RSSI-feature
proximity in the existing single-session data, RSSI fingerprinting is
viable. If K-NN of each query is random in time, RSSI sequences are
too noisy for fingerprint localization.

Test: 1077 samples, 20-dim RSSI proxy (band-mean across 56
subcarriers), cosine-NN with K=5, measure fraction of K-NN within
plus/minus 60s of each query timestamp. Compare to random baseline.

Result (honest):

  5-NN within +/-60s    0.169
  Random baseline       0.077
  Lift over random      2.18x   (verdict: MODERATE)
  Per-query stdev       0.183

Below the >=3x STRONG-fingerprint threshold but well above 1x random.
Real signal, but weaker than R8 counting result on the same data.

Important asymmetry surfaced (publishable distinction):

  Task            RSSI vs CSI retention   Verdict
  -------         -----                   -----
  Counting        94.82% (R8)             RSSI works well
  Localization    ~2x random (R9)         RSSI struggles in this regime

This is consistent with R5's band-spread observation: the count signal
integrates across the band, but localization may require per-subcarrier
shape that the band-mean discards.

Three actionable explanations for the MODERATE result:
1. 20-frame windows (~2s) too short for stable fingerprint while operator
   moves — longer windows might lift to 3-4x.
2. Within-room fingerprint space too narrow — multi-room data would
   show categorical lift jump (5-10x).
3. Band-mean discards the per-subcarrier shape needed for localization.

Once multi-room data lands (#645), this test should be re-run; if
hypothesis (2) is right, the lift will jump categorically.

Files:
* examples/research-sota/r9_rssi_fingerprint_knn.py
* examples/research-sota/r9_rssi_fingerprint_results.json
* docs/research/sota-2026-05-22/R9-rssi-fingerprint-knn.md
* docs/research/sota-2026-05-22/PROGRESS.md updated

* feat(tools/ruview-mcp): M2 — wire real inference via cog health subcommand

ruview_pose_infer and ruview_count_infer now run the cog binary's `health`
subcommand (ADR-100 contract) which performs real Candle forward-pass
inference on a synthetic CSI window and emits a structured health.ok JSON
event containing backend, confidence (pose) or count/confidence/p95_range
(count). The MCP tools parse this event and return typed inference results.

This satisfies the ADR-104 acceptance gate: "ruview_pose_infer returns a
finite output for a synthetic CSI window" when the cog binary is installed.
On machines without the binary, both tools still fail-open with {ok:false,
warn:true} and actionable install hints.

Also updates PROGRESS.md with cross-links: R7 (Stoer-Wagner) and R8
(RSSI-only 94.82% retained) marked done with cron-originated findings
distilled into the research vectors section.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-21 23:43:32 -04:00
rUv 3f462a254d
feat(tools): scaffold ruview MCP server + CLI + ADR-104 (#705)
Adds two new npm packages that expose RuView's WiFi-DensePose
sensing capabilities outside the Cognitum appliance ecosystem:

- tools/ruview-mcp/ (@ruv/ruview-mcp) — MCP server with 6 tools:
  ruview_csi_latest, ruview_pose_infer, ruview_count_infer,
  ruview_registry_list, ruview_train_count, ruview_job_status.
  Uses @modelcontextprotocol/sdk with stdio transport.
  6/6 smoke tests pass. TypeScript strict mode, Node 20.

- tools/ruview-cli/ (@ruv/ruview-cli) — Yargs CLI with matching
  subcommands: csi tail, pose infer, count infer, cogs list,
  train count, job status. Same fail-open pattern as the cog
  binaries (WARN to stderr, exit 0 on unavailable sensing-server).

- docs/adr/ADR-104-ruview-mcp-cli-distribution.md — design rationale,
  6-row threat table, packaging plan, acceptance gates, failure modes.

- docs/research/sota-2026-05-22/HORIZON.md — 12-hour horizon plan
  with 7 milestones tracked (M1 complete in this commit).

Both packages are private:true pending the user's publish decision.
Inference is via subprocess to the signed cog binaries (ADR-100/101/103)
— no JS/WASM ML engine bundled.
2026-05-21 23:33:18 -04:00
rUv bb92419ccb
research(R7): Stoer-Wagner mincut detects adversarial CSI nodes 3/3 in synthetic (#704)
Premise: in a multi-node CSI mesh, all nodes see the same physical
scene through slightly different multipath. Their per-window CSI
vectors cluster tightly under cosine similarity. An adversarial node
(replay / shift / noise injection) sits *outside* that cluster. The
Stoer-Wagner minimum cut on the inter-node similarity graph isolates
it cleanly when the cut is sharp.

Demo synthesises 4 honest nodes (one real CSI window from the paired
data + per-node Gaussian noise 6 dB below signal) and 1 adversarial
node under three attack modes. Cosine-similarity matrix, then
Stoer-Wagner mincut, then check whether partition_B is the singleton
{4} — the adversarial node.

  Attack       Mincut value   Partition_B   Isolated?
  -------      ------------   -----------   ---------
  replay       3.4513         {4}           YES
  shift        3.5724         {4}           YES
  noise        2.5586         {4}           YES

Detection rate: 3/3 = 100%.

Architectural payoff: this is the primitive that fills the stub at
. ADR-103 v0.2.0
can wire it in directly. The mincut value also becomes a continuous
'mesh trustworthiness' metric for the cog-gateway dashboard.

Honest scope: the demo uses sloppy attackers. Adaptive attackers who
have read this note can almost certainly evade by adding calibrated
noise that keeps cosine similarity above the cluster floor. The next
research step is the Stackelberg-game extension. See the
'Honest scope of this result' section in the research note.

Connections:
* R5 — top-8 saliency subcarriers are the priority list for a
  more-targeted per-subcarrier consistency check.
* R8 — same primitive likely works at lower SNR with RSSI-only
  metrics; cluster structure is preserved by the band integral.

Files:
* examples/research-sota/r7_multilink_consistency.py — pure-NumPy
  Stoer-Wagner mincut + synthetic-adversary harness.
* examples/research-sota/r7_multilink_consistency_results.json —
  full result JSON for cross-tick reproducibility.
* docs/research/sota-2026-05-22/R7-multilink-consistency.md — note.
* docs/research/sota-2026-05-22/PROGRESS.md — updated index + Done.
2026-05-21 23:28:46 -04:00
rUv d9ca9b3684
research(R8): RSSI-only person count retains 95% of full-CSI accuracy (#703)
Builds directly on R5's band-spread observation. If the count-task
signal is spread across the WiFi band (R5: max/mean ratio 2.85× across
56 subcarriers), then RSSI — which is the integral of |H_k|^2 across
the band — keeps most of the information. The naive prior (RSSI throws
away 98% of CSI bytes) is misleading; the relevant metric is how much
of the *signal* is in the integral, not how many bytes are in the
representation.

Tested by aggregating each existing [56 × 20] CSI window down to a
[20]-vector RSSI proxy (mean across subcarriers per frame), training a
tiny MLP (Linear 20→32→8, 656 params, 5 KB) with vanilla NumPy SGD for
200 epochs on the same random 80/20 split as cog-person-count v0.0.2.

Result:

  Full CSI v0.0.2   62.3% accuracy
  RSSI-only (this)  59.1% accuracy   = 94.82% retained

Per-class is also markedly more *balanced* (RSSI: 59.5 / 58.6 ; full
CSI: 86.2 / 34.3) — the tiny model on a low-dim input can't cheat by
leaning on class 0 the way v0.0.2's larger model does at inference.

What this enables on a 10-year horizon: phones, laptops, smart
speakers, smart TVs, smart lights — anything with WiFi reports RSSI
and anything with a CPU can run a 656-param MLP. Person counting
becomes a federated property of any room with WiFi, not a property of
the ESP32-S3 fleet.

What this doesn't prove (called out explicitly in the research note):
- Single room, single operator, single 30-min recording
- 2-class problem (label distribution is {0, 1})
- Single random draw — needs K-fold + multi-room replication

Three follow-up experiments queued in R8-rssi-only-count.md §'What's
next on this thread':
- Multi-room replication once #645 lands
- 3-class extension (0 / 1 / 2+) — measure the info-rate cliff
- Run on a non-ESP32 RSSI source (e.g. iw event on Linux laptop)

Files:
* examples/research-sota/r8_rssi_only_count.py — pure-NumPy, no
  framework deps. Trains + evals in 0.72 s on CPU.
* examples/research-sota/r8_rssi_only_results.json — full JSON dump
  for cross-tick reproducibility.
* docs/research/sota-2026-05-22/R8-rssi-only-count.md — method,
  measured numbers, interpretation, what doesn't work yet.
* docs/research/sota-2026-05-22/PROGRESS.md — updated index + Done
  log.

Coordination note: horizon-tracker is working on tools/ruview-mcp/
+ tools/ruview-cli/ + ADR-104 — this commit deliberately stays out
of those paths.
2026-05-21 23:18:09 -04:00
rUv a85d4e31e4
research(sota): kick off SOTA research loop + first R5 saliency measurement (#702)
Sets up docs/research/sota-2026-05-22/ as the autonomous-research
output dir, with PROGRESS.md as the canonical 15-vector research
agenda spanning spatial intelligence, RF features, RSSI-only, and
exotic/long-horizon verticals. Cron d6e5c473 (*/10 * * * *) picks
threads from this file and self-terminates at 2026-05-22 08:00 ET.

First concrete contribution this tick — R5 subcarrier saliency:

* examples/research-sota/r5_subcarrier_saliency.py: pure-numpy port
  of the count cog's Conv1d encoder + count head, computes per-
  subcarrier input×gradient saliency via central-difference. 128
  samples × 56 subcarriers × 2 forward passes/subcarrier ≈ ~3 s on
  CPU, no GPU or framework dependency.
* docs/research/sota-2026-05-22/R5-subcarrier-saliency.md: research
  note with motivation, method, novelty argument, and the first
  measured ranking. Top-8 subcarriers for cog-person-count v0.0.2:
  [41, 52, 30, 31, 10, 35, 2, 38]. Max/mean ratio 2.85x.
* v2/crates/cog-person-count/cog/artifacts/saliency.json: machine-
  readable per-subcarrier saliency + top-K lists, so future-tick
  experiments (retrain at K=8/16/32) consume it without re-running.

Key insight from the first measurement: top-8 saliency is *band-
spread* (indices span 2-52), not concentrated. This directly raises
R8's (RSSI-only) feasibility ceiling, because RSSI is a band-
aggregate — it retains the integral of a band-spread signal. First-
order estimate: RSSI-only should hit ~60% of full-CSI accuracy for
the count task. R7 (adversarial defence) inherits a concrete defender-
priority list: corroborate these 8 subcarriers across nodes.

This commit is the first of many short, focused contributions over
the next ~12 hours. PROGRESS.md is the canonical pointer for the
next tick to pick up the next thread.
2026-05-21 23:05:55 -04:00
ruv b16d7431bc docs(bench): append v0.0.2 section to person-count benchmark log
Documents the K-fold diagnostic (62.2 ± 1.9% / class-1 57.1%) that
justified v0.0.2, the v0.0.2 numbers (class-1 0% → 34.3%), and the
honest read that the gap to the K-fold mean is run-to-run variance
not missing improvement.
2026-05-21 19:47:55 -04:00
rUv a5e99670f8
feat(cog-person-count): release v0.0.1 — signed binaries on GCS, live on cognitum-v0 (#696)
Phase 3 of ADR-103. Cross-compiled aarch64 + x86_64 on ruvultra, signed
with COGNITUM_OWNER_SIGNING_KEY (Ed25519), uploaded to GCS, and live-
installed on the cognitum-v0 Pi 5 alongside cog-pose-estimation.

Real-hardware bench on cognitum-v0:
  ./cog-person-count-arm health
  → backend=candle-cpu, count=0, confidence=0.49, p95=[0,7]
  30 sequential health invocations: 0.276 s → 9.2 ms/invocation cold

Compares to cog-pose-estimation's 8.4 ms — count cog is ~10% slower
because the dual-head (count softmax + confidence sigmoid) does ~2x
the work after the shared encoder.

GCS release artifacts (publicly downloadable, SHA-verified):
  arm/cog-person-count-arm                          2,168,816 B
    sha:  36bc0bb0...0d47b507b3c3
    sig:  R/00xdzHriyr/2r...JK+a6k71NDg==  (Ed25519)
  x86_64/cog-person-count-x86_64                    2,615,528 B
    sha:  76cdd1ec...3923 7392b01db
    sig:  QB+8cnGSMQmu...ZtTNIQ2rDg==  (Ed25519)
  arm/cog-person-count-count_v1.safetensors           392,088 B
    sha:  dacb0551...e6e04ff56d15c3a65a9ff

Live install at /var/lib/cognitum/apps/person-count/ on cognitum-v0
matches the layout of every other installed cog (anomaly-detect,
seizure-detect, pose-estimation): cog-person-count-arm binary,
count_v1.safetensors weights, manifest.json, config.json.

Adds:
* v2/.../cog/artifacts/manifests/{arm,x86_64}/manifest.json — full
  ADR-100 schema with all fields filled (sha + sig + size + URL +
  build_metadata carrying the v0.0.1 honest training caveats).
* docs/benchmarks/person-count-cog.md — appends "Live appliance
  install" and "Signed GCS release artifacts" sections to the
  benchmark log.

Honest v0.0.1 caveat still applies (class-1 accuracy 0% on the held-
out tail of the single-session training data) — same data-bound
limit as pose_v1. The shipped artifact is the *vehicle*; production-
quality accuracy follows from multi-room paired data per ADR-103's
v0.2.0 plan + #645.
2026-05-21 19:02:26 -04:00
rUv 6b4994e105
feat(cog-person-count): train count_v1.safetensors — honest v0.0.1 (ADR-103) (#695)
Phase 2 of ADR-103: trained count head on the existing 1,077 paired
samples (the same data that produced pose_v1 yesterday).

Honest result: 65.1% eval accuracy / 100% within ±1 / MAE 0.349 on
the held-out time-window. Per-class: 100% on "empty room" / 0% on
"1 person". The model overfit by epoch 100 (train_acc → 1.0,
eval_loss climbed 0.67 → 7.8) and the "best" checkpoint is the
snapshot that happened to predict the eval window's class
distribution (140/215 = 65.1%, matches eval_acc exactly). Confidence
head Spearman = 0.023 ⇒ uncalibrated. Same data-bound failure mode
as pose_v1 (#645), bounded by single-session training data; same
fix path (multi-room).

What v0.0.1 still validates end-to-end:
* PyTorch → safetensors → Candle Rust loads cleanly on first try.
  `cog-person-count health` reports `backend: candle-cpu` and emits
  real per-frame predictions instead of the stub backend's hard-coded
  {1 person, 0 confidence}. Architecture parity between train-count.py
  and src/inference.rs::CountNet is bit-exact.
* ONNX export bit-clean (16 KB, opset 18, dynamic batch axis).
* Training wall time: 5.6 s for 400 epochs on RTX 5080.
* Binary size unchanged (2.36 MB stripped), model loads via mmap at
  runtime.

This commit ships:

* scripts/align-ground-truth.js: extended to emit n_persons_mode +
  n_persons_max per window so the training pipeline has count
  labels. Backwards-compatible (additive fields).
* scripts/train-count.py: new — mirrors CountNet architecture
  exactly, loads paired.jsonl, trains 400 epochs with
  CE+BCE+Brier loss, exports safetensors + ONNX + per-epoch JSON.
* v2/.../cog/artifacts/{count_v1.safetensors,count_v1.onnx,
  count_train_results.json}: the trained artifacts.
* v2/.../cog/README.md: Status table updated with the v0.0.1 numbers
  + an Honest Caveat section explaining the data-bound result.
* docs/benchmarks/person-count-cog.md: new — full v0.0.1 benchmark
  log mirroring the format docs/benchmarks/pose-estimation-cog.md
  established. Includes comparison to ADR-103 v0.1.0 acceptance
  gates and per-class breakdown.

Still pending:
* `run` subcommand wiring (long-running polling loop, same as pose)
* Cross-compile + sign + GCS upload (mirror of pose cog pipeline)
* Live install on cognitum-v0
* v0.2.0: re-train on multi-room data, LoRA per-room adapters,
  Stoer-Wagner min-cut clip in fusion stage
2026-05-21 18:56:52 -04:00
rUv 962e0f4a34
docs(adr): ADR-103 — learned multi-person counter (SOTA path) (#693)
Motivated by #499 (multi-node double-skeletons) which PR #491 stopped
the bleeding on but didn't take to the WiFi-CSI literature's state of
the art. Designs a learned counter that replaces today's slot
heuristic + dedup_factor knob, reusing the primitives we've already
shipped this week:

  * Candle / RTX 5080 training pipeline (proven yesterday, 2.1 s for
    400 epochs on pose_v1.safetensors)
  * HF presence encoder as initialization (architectures compatible,
    unlike the pose head case)
  * ruvector-mincut (Stoer-Wagner) for multi-node fusion upper-bound
  * Cog packaging spec (ADR-100) + edge module registry (ADR-102)
  * Paired-data pipeline (PR #641 streaming-safe align-ground-truth.js)
    — `n_persons` labels come for free; no new data collection
    campaign required to bootstrap.

Architecture:
  per-node CSI [56×20] -> frozen HF encoder -> 128-dim embedding
                                          \
                                           > count head (softmax {0..7})
                                           > confidence head (sigmoid)
  N nodes' distributions -> confidence-weighted log-sum
                         -> Stoer-Wagner min-cut upper-bound clip
                         -> { count, confidence,
                              count_p95_low, count_p95_high,
                              per_node_breakdown }

Compares the proposal explicitly against WiCount / DeepCount /
CrossCount / HeadCount published numbers and is honest about the
hardware gap (their 3x3 MIMO research NICs vs our 1x1 SISO ESP32-S3).

v0.1.0 acceptance gates target >=80% within-+/-1 same-room and
>=60% cross-room — modest on purpose; bounded by the same paired-
data scarcity #645 documents for pose. The framework is the
deliverable; the accuracy follows the data.

Includes:
  * Architecture diagram in ascii
  * Comparison table vs published WiFi-CSI counting SOTA
  * Per-failure-mode mapping from #499 symptoms to how the
    learned counter addresses each
  * v0.1.0 + v0.2.0 acceptance gates with measurable thresholds
  * Repo layout for the new `v2/crates/cog-person-count/` crate
  * Five-step migration plan from this ADR -> first GCS release

Status: Proposed. Implementation follows in the same incremental
pattern ADR-101 used: scaffold-cog PR -> train+publish PR ->
server-wiring PR.
2026-05-21 18:28:18 -04:00
rUv 67fec45e61
feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry (#648)
* feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry

Adds a new sensing-server endpoint that fetches and caches the canonical
Cognitum app registry at
https://storage.googleapis.com/cognitum-apps/app-registry.json (105 cogs
across 11 categories as of v2.1.0). RuView previously had no live
awareness of the catalog — the README's capability table was hand-
curated and went stale as Cognitum shipped new cogs (the registry was
last updated 6 days ago).

ADR:
* docs/adr/ADR-102-edge-module-registry.md — full design, response
  shape, configuration flags, failure modes, and a 12-row security
  review covering SSRF, response inflation, ?refresh abuse, stale-serve
  semantics, TLS, cache poisoning, JSON-panic resistance, etc.

Code:
* v2/.../edge_registry.rs — EdgeRegistry struct + UreqFetcher +
  MockFetcher trait + 7 unit tests. RwLock<Option<CachedEntry>> with
  stale-on-error fallback. MAX_PAYLOAD_BYTES=8 MiB, 10s wire timeout.
* v2/.../main.rs — constructs Option<Arc<EdgeRegistry>> at startup,
  registers GET /api/v1/edge/registry handler, wires Extension layer.
  Handler runs the blocking ureq fetch via tokio::task::spawn_blocking
  so the async runtime stays free.
* v2/.../cli.rs / main.rs Args — three new flags (per user request to
  "allow the registry to be disabled or changed"):
    --edge-registry-url <URL>       (env RUVIEW_EDGE_REGISTRY_URL)
    --edge-registry-ttl-secs <N>    (env RUVIEW_EDGE_REGISTRY_TTL_SECS)
    --no-edge-registry              (env RUVIEW_NO_EDGE_REGISTRY)
  When --no-edge-registry is set or the URL is empty, the endpoint
  returns 404.

Cargo.toml: adds ureq (rustls), sha2, thiserror as direct deps.

README:
* New collapsed "🧩 Edge Module Catalog" section with the full 105-cog
  table generated from the registry, grouped by category with practical
  one-line descriptions (e.g. "Spots irregular heartbeats and abnormal
  heart rhythms", "Detects walking problems and scores fall risk").
  Links to https://seed.cognitum.one/store and the local appliance
  /cogs page. Sits between the HF model section and How It Works.

Tests (7/7 pass):
  first_call_hits_upstream_and_caches
  ttl_expiry_triggers_refetch
  force_refresh_bypasses_fresh_cache
  stale_serve_on_upstream_failure_after_cached_success
  no_cache_no_upstream_returns_error
  upstream_invalid_json_is_treated_as_error
  upstream_sha256_is_deterministic

Security highlights (full review in ADR-102 §"Security review"):
- The registry is metadata-only; per-cog binary signatures (ADR-100)
  remain the trust root for installs. A compromised registry can
  mislead a human reader but cannot ship malicious binaries.
- 8 MiB cap + 10s timeout + Option<Arc<...>> via Extension layer means
  the endpoint can't be used to exhaust memory or pin tokio threads.
- Stale-on-error responses carry an explicit `stale: true` field so
  upstream outages are visible to consumers rather than silently
  masked.
- Endpoint sits behind the existing RUVIEW_API_TOKEN bearer gate when
  set, otherwise unauthenticated (registry contents are public anyway).

* chore: refresh Cargo.lock for ureq/sha2/thiserror deps added by ADR-102
2026-05-19 18:08:43 -04:00
rUv 4b1a835107
docs: repoint #640 references to #645 (original deleted, replaced) (#646)
Issue #640 (PCK gap follow-up) was deleted upstream after the cog v0.0.1
PRs landed today. Re-opened as #645 with the same context plus the
new measured v0.0.1 numbers (PCK@20 3.0%, PCK@50 18.5%, MPJPE 0.093).
This patch updates the three files in main that still pointed at the
dead #640 to point at #645 instead — ADR-101, the cog README, and the
benchmark log.
2026-05-19 17:18:05 -04:00
rUv 9c3c8b98bc
docs(adr): ADR-100 + ADR-101 — record v0.0.1 shipping status (#644)
Updates both ADRs to reflect that the first cog (`cog-pose-estimation@0.0.1`)
landed today via PRs #642 + #643.

ADR-100 (Cog Packaging Specification):
* Status line: "first conforming cog shipped 2026-05-19".
* Migration step 2 marked complete with PR references and the GCS
  paths the binaries live at.

ADR-101 (Pose Estimation Cog):
* Status line: "v0.0.1 shipped 2026-05-19".
* New "v0.0.1 shipping status" section that walks through every
  ADR-100 acceptance gate with concrete pass/fail evidence (binary
  sizes, sha256 round-trip, signature, manifest path, live install
  on cognitum-v0, runtime contract, real-weights load assertion,
  ONNX parity).
* Measured-metrics table: training time (2.1 s/400 epochs on RTX 5080),
  PCK@20/PCK@50/MPJPE, cold-start latency for Windows/ruvultra/Pi 5.
* Carries forward the two open follow-ups: Hailo HEF (SDK-gated) and
  PCK@20 >= 35% (data-bound, #640).
* "See also" link to docs/benchmarks/pose-estimation-cog.md.

Docs-only; no code changes.
2026-05-19 17:13:31 -04:00
rUv fcb6f4bf12
feat(cog-pose-estimation): x86_64 release v0.0.1 — parallel to arm (#643)
Adds the x86_64-unknown-linux-gnu binary uploaded to
gs://cognitum-apps/cogs/x86_64/, signed with the same Ed25519
COGNITUM_OWNER_SIGNING_KEY as the arm release. Together with the
already-shipped arm artifact, the cog now ships natively for both
target architectures the Cognitum fleet supports.

x86_64 release:
  sha256:    a434739a24415b34e1aff50e5e1c3c32e568db96af473bbb3e5ecc9b95fe71fa
  signature: pNNuxhgM18PztN8BSZdfw5oAShG2pV3na5T/q2QdlJWX/5FJgo4QTiUCbcTAxI2Uiva8VURSOlRzMU3xoQPqCQ==
  size:      4,548,856 bytes
  cold-start: 5.4 ms / invocation on ruvultra (RTX 5080, NVMe)

Reorganizes manifests under cog/artifacts/manifests/{arm,x86_64}/
so each arch carries its own manifest with the matching binary_sha256
and signature — same layout the release pipeline will use for the
future hailo8 / hailo10 variants.

Updates docs/benchmarks/pose-estimation-cog.md with the cross-arch
cold-start table:

  Windows (x86_64)   76.2 ms
  ruvultra (x86_64)   5.4 ms   <- this release
  Pi 5 (aarch64)     8.4 ms

Verified via anonymous GCS download + SHA round-trip — identical to
local build.

Hailo HEF remains the only pending arch, still blocked on Hailo SDK
provisioning to a self-hosted runner.
2026-05-19 17:08:23 -04:00
rUv 3314c8db8d
feat(cog-pose-estimation): scaffold first Cog from this repo (ADR-100 + ADR-101) (#642)
* feat(cog-pose-estimation): scaffold first Cog from this repo (ADR-100 + ADR-101)

Adds the foundation for the pose-estimation Cog that ships from this
repo into Cognitum V0 appliances. Companion ADR-225 + crate land in
cognitum-one/v0-appliance.

ADRs:
* ADR-100 formalises the Cognitum Cog packaging spec — on-device
  layout under /var/lib/cognitum/apps/<id>/, manifest.json schema
  (incl. new binary_sha256 + binary_signature fields), GCS hosting
  convention, repo source layout, build pipeline, and the four-verb
  runtime contract (version | manifest | health | run). Documents the
  convention I reverse-engineered from inspecting installed cogs on a
  live cognitum-v0 appliance — `anomaly-detect`, `presence`,
  `seizure-detect`, etc.
* ADR-101 designs the pose-estimation Cog itself: where it sits in
  the wifi-densepose pipeline (encoder init from
  ruvnet/wifi-densepose-pretrained, 17-keypoint regression head),
  what gets shipped per target arch (arm / x86_64 / hailo8 /
  hailo10), acceptance gates (PCK@20 explicitly deferred to #640 —
  this ADR ships the vehicle, not the accuracy).

Crate v2/crates/cog-pose-estimation/:
* Cargo.toml + workspace member declaration with a hailo feature gate
  so the binary builds without the Hailo SDK in CI.
* main.rs implements the four-verb CLI exactly per ADR-100.
* config.rs / manifest.rs / publisher.rs / inference.rs / runtime.rs —
  small modules, each <100 lines.
* publisher.rs emits ADR-100 structured JSON events.
* inference.rs is a stub that produces a centred-skeleton baseline
  with confidence=0 (honest: no trained weights wired in yet).
* runtime.rs subscribes to /api/v1/sensing/latest, slides a
  56*20 window, runs the engine, emits pose.frame events.
* cog/manifest.template.json + cog/config.schema.json define the
  release artifact + runtime config schemas.
* cog/Makefile holds build / sign / upload targets.
* tests/smoke.rs covers manifest roundtrip + engine I/O surface.

Verified locally:
* cargo check -p cog-pose-estimation: clean.
* cargo test  -p cog-pose-estimation: 4/4 pass.
* ./target/release/cog-pose-estimation {version,manifest,health}:
  all emit the right contract output.

This commit contains scaffolding only; the actual trained weights and
Hailo HEF cross-compile come in follow-ups tracked in #640 and the
companion v0-appliance branch.

* feat(cog-pose-estimation): first measured run — Candle CUDA on RTX 5080

Trained pose_v1 on ruvultra (RTX 5080) via Candle 0.9 + cuda feature
against the same 1,077-sample paired session that produced 0%/0% PCK
in #640 with the pure-JS SPSA trainer. First real numbers:

  PCK@20 = 3.0%   (up from 0.0%)
  PCK@50 = 18.5%  (up from 0.0%)
  MPJPE  = 0.093  (down from 0.66, ~7x improvement)

400 epochs in 2.1 s wall time, full-batch, ~5 ms/epoch. Loss curve
0.181 -> 0.014 over the run, eval 0.010. Per-joint reveals the model
leans on right-side proximal joints (r_hip 77% PCK@50, r_knee 35%,
l_elbow 26%) — consistent with the camera framing in the source
recording. Distal joints (wrists, ankles) and face joints are still
near-random, consistent with the 56-subcarrier / 20-frame input not
carrying fine-grained spatial info at 1077 samples.

This commit:

* Adds v2/crates/cog-pose-estimation/cog/artifacts/{pose_v1.safetensors,
  train_results.json} so the cog dir now contains a real reference
  artifact, not just scaffold.
* Updates cog/README.md "Status" block with the measured numbers,
  per-joint table, and an honest reading of where the model
  succeeds vs where the data is the bottleneck.
* Adds docs/benchmarks/pose-estimation-cog.md as the canonical
  benchmark log — append-only, one section per published run.
* Appends a "First measured run" section to ADR-101 referencing
  the new benchmark file.

Still pending in the follow-up:
* Wire pose_v1.safetensors into src/inference.rs (replace stub).
* ONNX export (Candle lacks a writer — needs external conversion).
* Hailo HEF cross-compile + cluster deploy.

The data-bound gap to PCK@20 >= 35% is tracked in #640.

* feat(cog-pose-estimation): wire real weights — cog is no longer a stub

Replaces the centred-skeleton stub in src/inference.rs with a real
Candle-based loader that reads cog/artifacts/pose_v1.safetensors and
runs the trained Conv1d encoder + MLP pose head on every incoming CSI
window.

What changes:

* src/inference.rs: PoseNet mirrors the training script's architecture
  exactly — Conv1d(56->64, k=3 d=1), Conv1d(64->128, k=3 d=2),
  Conv1d(128->128, k=3 d=4), mean over time, Linear(128->256)+ReLU,
  Linear(256->34)+sigmoid -> reshape [17, 2]. The InferenceEngine
  searches a sensible candidate list for the weights file
  (/var/lib/cognitum/apps/pose-estimation/, ./pose_v1.safetensors,
  ./cog/artifacts/, repo-root, v2/-relative) and falls back to the
  stub when none are present so the cog still satisfies ADR-100.
* Cargo.toml: adds candle-core 0.9 + candle-nn 0.9 (no-default-features,
  CPU build by default) + safetensors 0.4. New `cuda` feature opt-in
  for GPU inference on hosts that have it. Drops the unused
  wifi-densepose-train path dep from the default build path.
* src/main.rs + src/publisher.rs: health.ok event now carries
  `backend` (candle-cuda | candle-cpu | stub) and the synthetic
  output confidence, so operators can tell at a glance whether the
  cog loaded its weights or fell back to the stub.
* tests/smoke.rs: adds `real_weights_load_when_available` which
  asserts the loaded engine reports backend=candle-* and emits
  non-zero confidence — exactly the signal that proves we're not
  silently degrading to the stub.

Verified locally:

* `cargo check -p cog-pose-estimation --no-default-features` — clean
* `cargo test  -p cog-pose-estimation --no-default-features` — 5/5 pass
* `./target/release/cog-pose-estimation health` emits:
  {"event":"health.ok","fields":{"backend":"candle-cpu","cog":"pose-estimation","synthetic_output_confidence":0.185}}
  — 0.185 is the published PCK@50 from cog/artifacts/train_results.json,
  emitted by the real Candle inference path (would be 0.0 if it had
  fallen back to the stub).

The cog now runs the trained pose_v1 model end-to-end. Accuracy is
still bounded by the underlying 1077-sample training data (PCK@20
3.0%, PCK@50 18.5% per docs/benchmarks/pose-estimation-cog.md) — that
gap is data-bound and tracked in #640. ONNX export + Hailo HEF
cross-compile remain follow-ups.

* docs(benchmarks): measure cog-pose-estimation cold-start latency

100 sequential `cog-pose-estimation health` invocations average 76.2 ms
each on a Windows x86_64 host using the `candle-cpu` backend. Each
invocation re-loads pose_v1.safetensors and runs one synthetic forward
pass, so this is the worst-case cold-start path. Long-running `run`
inference will be sub-millisecond per frame once the model is loaded.

Updates the benchmarks doc accordingly.

* feat(cog-pose-estimation): ONNX export — pose_v1.onnx + scripts/export-onnx.py

Adds the canonical ONNX artifact that unblocks downstream Hailo HEF
cross-compile + ONNX Runtime benchmarks. Generated on ruvultra (torch
2.12.0 + CUDA), 12,059 bytes, opset 18, dynamic batch axis.

* scripts/export-onnx.py: mirrors the Candle inference architecture in
  PyTorch (Conv1d 56->64, 64->128, 128->128 + Linear 128->256->34), pure-
  python safetensors loader (no extra pip dep), exports via
  torch.onnx.export, then verifies via onnx.checker.check_model and
  numerical parity against the torch reference.
* Verified parity vs torch: max |torch - onnx| = 8.94e-8 (1e-5
  threshold). Effectively bit-perfect.
* v2/crates/cog-pose-estimation/cog/artifacts/pose_v1.onnx — the
  artifact itself, 12 KB.
* docs/benchmarks/pose-estimation-cog.md — adds an ONNX export
  section with the verification numbers.

Next: Hailo HEF cross-compile (still gated on Hailo SDK on a
self-hosted runner) and ONNX Runtime latency benchmarks on each
target arch.

* feat(cog-pose-estimation): release v0.0.1 — signed aarch64 binary on GCS

End-to-end deploy: cross-compiled to aarch64-unknown-linux-gnu on
ruvultra, ran via qemu-aarch64-static, then smoke-tested on a real
cognitum-v0 Pi 5. Signed with COGNITUM_OWNER_SIGNING_KEY (Ed25519)
and uploaded to gs://cognitum-apps/cogs/arm/.

Real-hardware results on cognitum-v0 (Pi 5):
  health: backend=candle-cpu, confidence=0.185, real weights loaded
  30x sequential `health`: 0.251 s total -> 8.4 ms / invocation (cold)

GCS release artifacts (publicly downloadable):
  binary:  3,741,976 bytes
    sha256 1e1a7d3dd01ca05d5bfc5dbb142a5941b7866ed9f3224a21edc04d3f09a99bf5
  weights:   507,032 bytes
    sha256 eb249b9a6b2e10130437a10976ed0230b0d085f86a0553d7226e1ae6eae4b9e5
  signature (Ed25519, b64): LUN7xqLPYD3MFzm5dKB5MnYU0LvoRtek5ci5KiKPHBg+Xo6xuazwokn2Dw2JPMaLYJzmWn/SpT4djuR7hYvVDw==

Adds:
* v2/crates/cog-pose-estimation/cog/artifacts/manifest.json — the
  release-pipeline-produced manifest with all fields filled in per
  ADR-100, including arch, target_triple, signature, and a
  build_metadata block carrying the validation PCK numbers.
* docs/benchmarks/pose-estimation-cog.md — new sections covering
  the real Pi 5 smoke (8.4 ms cold-start) and the signed GCS
  release artifacts.

Verified by downloading the binary anonymously from GCS and
re-computing the sha256 — matches the locally-computed sha exactly.
Signature decoded to the expected 64-byte Ed25519 length.

Closes the GCS-upload acceptance criterion from ADR-100; the only
pending work is Hailo HEF cross-compile (still SDK-gated) and an
x86_64 release alongside this arm release.

* docs(benchmarks): record live cognitum-v0 install + 5-sec smoke run

Adds the "Live appliance install" section documenting what happened
when the signed v0.0.1 binary + weights were installed under
/var/lib/cognitum/apps/pose-estimation/ on cognitum-v0 (the V0
cluster leader).

* Layout matches the existing anomaly-detect / presence / seizure-
  detect cogs exactly — the Cogs dashboard at
  http://cognitum-v0:9000/cogs auto-discovers entries.
* `cog-pose-estimation run` ran for 5 seconds in the background and
  cleanly emitted run.started + structured WARN events for the
  missing local sensing-server on :3000 (cognitum-v0's actual CSI
  source is ruview-vitals-worker on :50054, not :3000). No crashes,
  no NaN, no leaks.
* Wiring `sensing_url` to the appliance-native source is a separate
  Day-2 integration task.
2026-05-19 17:03:09 -04:00
rUv ad15f1b049
docs: truth-up README + user-guide on Hugging Face model release (#637)
The previous wording in both README.md and docs/user-guide.md claimed
no pretrained weights were released yet. That was wrong — the
contrastive CSI encoder + presence-detection head + per-node LoRA
adapters have been published as
ruvnet/wifi-densepose-pretrained on Hugging Face for several weeks
(124 downloads at time of writing), with 100% presence accuracy on
the validation set and 164,183 emb/s on M4 Pro.

This commit replaces the "no shipped weights" framing with the actual
state, and surfaces a real loader gap discovered during a
before/after benchmark of the sensing-server:

* Baseline run (no --model): server produced presence/motion/vitals
  output at ~19 ticks/s, as expected.
* After run (--model models/wifi-densepose-pretrained.rvf): the
  progressive RVF loader errored with
  "invalid magic at offset 0: expected 0x52564653, got 0x7974227B"
  (0x7974227B is the ASCII bytes {"ty… from the JSONL header).
  v2/.../rvf_container.rs only parses the binary RVF segment
  format; the HF artifact is JSONL RVF. When the load fails the
  pipeline degraded to null output (variance=0, presence=None) rather
  than falling back to heuristic mode.

The docs now describe (a) what works today — Python / training-side
consumption of model.safetensors — and (b) what is gated on a JSONL
adapter or a binary-RVF republish — sensing-server --model loading.
The 17-keypoint pose model remains separately pending (#509,
ADR-079 phases P7–P9).
2026-05-19 13:03:54 -04:00
Grzegorz Malopolski ec73109d57
docs: add visual architecture overview images (#208)
Co-authored-by: Grzegorz Małopolski <grzegorzmalopolskipraca@gmail.com>
2026-05-17 18:18:07 -04:00
rUv d33962eff2
fix(docker): UDP relay for multi-source ESP32 on Docker Desktop Windows (#502)
Docker Desktop on Windows demultiplexes inbound UDP from multiple source
IPs onto a single virtual socket, silently dropping packets from all but
one ESP32 node. This makes multi-node sensing setups appear to work
(WebSocket connects, packets flow on the host) while only one node's CSI
ever reaches the container.

Adds scripts/udp-relay.py (stdlib only) which collapses multi-source UDP
to a single loopback source so Docker's forwarding accepts every packet.
Verified locally: 6 packets from 3 distinct source ports all arrive at
the receiver from a single relay socket.

Updates docker/docker-compose.yml with an inline comment pointing
Windows users at the relay + 5006:5005 mapping. Linux/macOS hosts are
unaffected and need no changes.

Also documents the workaround alongside fixes for #188 (UI 404 from
relative --ui-path) and #438 (boot loop on --edge-tier 1/2 against
pre-v0.4.3.1 firmware) as new sections 9-11 of docs/TROUBLESHOOTING.md.
Supersedes the docs-only PR #413.

Closes #374, #386
Refs #188, #438, #301
2026-05-17 18:01:44 -04:00
rUv 9d4f7820b2
docs(adr): ADR-098 — evaluate midstream for RuView's CSI/WS/mesh pipeline (Rejected) (#553)
`vendor/midstream` is a git submodule of RuView but no `v2/crates/*` depends
on a `midstreamer-*` crate and no Rust source uses one — i.e. it is vendored
but not consumed, the same state `vendor/rvcsi` was in before ADR-097.

ADR-098 evaluates whether to change that. The candidate seams (from the
prompt) were:

  1. Streaming / pub-sub for the WS fan-out (today: `tokio::sync::broadcast`
     at `wifi-densepose-sensing-server/src/main.rs:4769`).
  2. CSI → DSP → event pipeline (today: rvcsi-events::EventPipeline, just
     adopted by ADR-097).
  3. Multi-source merging / TDM for the ESP32 mesh (ADR-029, ADR-073).
  4. Backpressure / flow control between the UDP receiver and downstream
     consumers (firmware `stream_sender` ENOMEM; host-side bounded
     broadcast channel).

Reading all six midstream workspace crates end-to-end
(`vendor/midstream/crates/{temporal-compare,nanosecond-scheduler,
temporal-attractor-studio,temporal-neural-solver,strange-loop,
quic-multistream}/src/*.rs` — ~3,455 LOC) shows midstream's identity
unambiguously: `Cargo.toml:16` calls itself "Real-time LLM streaming with
inflight analysis", the README frames it as analyzing *LLM token streams*
in real time, and zero hits across the workspace for `csi|wifi|sensing|
sensor`. midstream's abstractions are LLM-token / dashboard-telemetry
shaped; RuView's pipeline is RF-frame / event-detector shaped.

Decisions:

  D1 — WS fan-out: keep `tokio::sync::broadcast::channel::<String>(256)`.
       midstream offers no equivalent in-process broadcast primitive.
  D2 — CSI pipeline: keep `rvcsi-events::EventPipeline` (deterministic,
       single-frame-at-a-time, replayable per ADR-095 D9). midstream's
       attractor / LTL crates operate on multi-dimensional trajectories,
       not validated single CSI frames.
  D3 — TDM / aggregator: keep `wifi-densepose-hardware::aggregator` +
       firmware-side TDM. midstream has no UDP merger and no cross-device
       wall-clock scheduler.
  D4 — Backpressure: the firmware ENOMEM rate-limit and the bounded host
       `broadcast` channel are correct at each end; midstream's QUIC
       primitives don't help the actual UDP+WS topology.
  D5 — Carve-out: `midstreamer-temporal-compare` (DTW / LCS / Levenshtein)
       is a plausible future-evaluation option if a *second* DTW use case
       appears in RuView. RuvSense already has one (`gesture.rs`).
  D6 — Carve-out: `midstreamer-scheduler` (deadline-aware, EDF / LLF /
       RM) is a plausible future option if the cluster-Pi aggregator ever
       takes over real-time scheduling. Today that lives in firmware.
  D7 — Submodule: keep `vendor/midstream` pinned at `30fe5eb` as reference
       material; do not advance the pin per-release (unlike vendor/rvcsi
       under ADR-097 D7) because there is no in-build consumer.
  D8 — Docs: cross-reference, don't import. ADR-098 added to
       `docs/adr/README.md`.

Status: Rejected (with named re-evaluation triggers in §6 — second DTW use
case, host-side real-time scheduler, midstream gains a CSI adapter, or a
QUIC-to-external-client requirement that WS can't service).
2026-05-17 17:49:21 -04:00
rUv b2fe452e74
docs(tutorials): Pi 5 + Hailo cluster rvcsi tutorial (#546)
* docs(tutorials): add Pi 5 + Hailo cluster rvcsi tutorial

Field-tested walkthrough for building a 4-node Raspberry Pi 5 + 2×
Hailo-8 multistatic Wi-Fi CSI cognitive RF observer using rvcsi. Built
against the v0-appliance v0.5.0-cognitive-rf-observer milestone — 446k+
observed fingerprints, 16 stable RF states, 2nd-order Markov running at
39% top-1 ceiling (1.06× over 1st-order, 16× chance baseline).

Covers:
  - Pi 5 + Hailo hardware bring-up (BOM ~$580 + workstation)
  - nexmon_csi native ARM build recipe (cross-compile is a dead end)
  - Per-node services + per-host topology (15 expected services across 4 hosts)
  - Workstation pipeline: 3 daemons + 7 timers, brain HTTP + SQLite
  - 12 brain categories from spatial-vitals through rfmem-fleet
  - cog-query CLI: 34 subcommands, 4 JSON modes, --post for 2
  - Calibration recipe: walk → cluster → warm-start IDs → Markov chain
  - 13-axis anomaly detector w/ composite info score (1.0–8.0)
  - Fleet-health triad: check-drift + replica-status + fleet-status
  - Troubleshooting table for the painful lessons (clock skew, cp -r footgun,
    self-loop dominance in Markov argmax, etc.)

Pairs with a detailed cookbook gist (linked from intro + steps 3, 4,
and the Reference section):
https://gist.github.com/ruvnet/88e7b053c41cb4f4af7a7ec4af873017

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

* docs(tutorials): clarify rvcsi naming + add ADR-207 cutover note

Two amendments per ADR-207's "naming defect — fix immediately regardless"
action item:

1. Intro callout: when the tutorial was first written, "rvcsi" was a
   naming convention only (no upstream library dep). As of 2026-05-13
   the v0-appliance accepted ADR-207 Option D and shipped a Rust
   binary built on the real rvcsi-runtime. Both stacks can coexist on
   a mixed cluster during cutover.

2. Per-node services section: explicit note that cog-csi-emitter +
   cog-csi-adapter + cog-rvcsi-stream are being consolidated into one
   cog-rvcsi-pi Rust binary, with deploy + rollback commands and
   scope (per-Pi cutover, mixed clusters OK).

The tutorial's overall instructions remain correct for both pre- and
post-cutover deployments — fleet-status, the operator surface, and
the architectural model are unchanged.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-17 17:41:39 -04:00
NgoQuocViet2001 8a155e07ec
docs: explain mesh data path to dashboard and Observatory (#602) 2026-05-17 17:05:51 -04:00
ruv ca97527646 feat(introspection): I6 — regime-changed signal + per-frame analyze + honest ADR-099 D8 amendment
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>
2026-05-13 23:29:37 -04:00
ruv 900b877c64 docs(adr): ADR-099 — adopt midstream as RuView's real-time introspection + low-latency tap (Proposed)
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>
2026-05-13 22:42:05 -04:00
ruv 7a407556ba docs(adr): ADR-097 — adopt rvCSI as RuView's primary CSI runtime (Proposed)
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>
2026-05-13 09:23:25 -04:00
ruv deb561bf9c fix(rvcsi): scale-relative baseline-drift thresholds + ESP32 end-to-end validation
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>
2026-05-12 22:19:15 -04:00
Claude d40411e6d7
feat(rvcsi): Raspberry Pi 5 (BCM43455c0) + Nexmon chip registry
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
2026-05-13 01:32:27 +00:00
Claude b116a99481
feat(rvcsi): real nexmon_csi UDP/PCAP fidelity — chanspec decode, libpcap reader, NexmonPcapAdapter
Raises the Nexmon path from a normalized record format to parsing what the
patched Broadcom firmware actually emits, end to end.

napi-c shim (ABI 1.0 -> 1.1, additive):
- rvcsi_nx_csi_udp_header / rvcsi_nx_csi_udp_decode — parse the real nexmon_csi
  UDP payload: the 18-byte header (magic 0x1111, rssi int8, fctl, src_mac[6],
  seq_cnt, core/spatial-stream, Broadcom chanspec, chip_ver) + nsub complex CSI
  samples (modern int16 LE I/Q export — what CSIKit/csireader.py read for the
  BCM43455c0 / 4358 / 4366c0; nsub = (len-18)/4). rvcsi_nx_csi_udp_write to
  synthesize payloads for tests. rvcsi_nx_decode_chanspec — d11ac chanspec ->
  channel (chanspec & 0xff) / bandwidth (bits [13:11], cross-checked against the
  FFT size) / band (bits [15:14], cross-checked against the channel number).
  Still allocation-free, bounds-checked, structured errors, never panics.
- ffi.rs wraps it: decode_chanspec / parse_nexmon_udp_header / decode_nexmon_udp
  / encode_nexmon_udp + DecodedChanspec / NexmonCsiHeader; every unsafe block
  documented; the ABI guard now expects 1.1.

rvcsi-adapter-nexmon:
- pcap.rs — a dependency-free classic-libpcap reader (all four byte-order /
  timestamp-resolution magics; Ethernet / raw-IPv4 / Linux-SLL link types;
  tolerates a truncated final record; pcapng is a follow-up) + extract_udp_payload
  + a synthetic_udp_pcap / synthetic_nexmon_pcap test/example generator.
- NexmonPcapAdapter (a CsiSource) — reads the CSI UDP packets out of a
  `tcpdump -i wlan0 dst port 5500 -w csi.pcap` capture, decodes each via the C
  shim, stamps the frame timestamp from the pcap packet time; non-CSI packets
  counted as "skipped" in health.

rvcsi-runtime: decode_nexmon_pcap, summarize_nexmon_pcap (+ NexmonPcapSummary:
link type, CSI frame count, channels, bandwidths, subcarrier counts, chip
versions, RSSI range, time span), CaptureRuntime::open_nexmon_pcap[_bytes].

rvcsi-node (napi-rs): nexmonDecodePcap, inspectNexmonPcap, decodeChanspec,
RvcsiRuntime.openNexmonPcap. @ruv/rvcsi SDK + .d.ts updated (NexmonPcapSummary,
DecodedChanspec). rvcsi-cli: `record --source nexmon-pcap`, `inspect-nexmon`,
`decode-chanspec`.

161 rvcsi tests pass (adapter-nexmon 9->22), 0 failures, clippy-clean.
ADR-096 §2.2/§2.3/§5, CHANGELOG, CLAUDE.md updated.

https://claude.ai/code/session_01CdYAPvRTjcch6YrYf42n1z
2026-05-13 01:15:22 +00:00
Claude 94745242a8
feat(rvcsi): rvcsi-dsp (DSP stages + SignalPipeline) + ADR-096 (FFI/crate layout)
- rvcsi-dsp — reusable signal-processing stages (ADR-095 FR4): mean/variance/
  std_dev/median, remove_dc_offset, unwrap_phase, moving_average, ewma,
  hampel_filter(_count), short_window_variance, subtract_baseline + DspError;
  scalar features motion_energy(_series), presence_score (logistic, ≈0.5 at
  threshold), confidence_score, breathing_band_estimate (heuristic, FFT-free);
  SignalPipeline (hampel → smooth → DC-remove → baseline-subtract → unwrap,
  non-destructive of validation state) + learn_baseline. 28 tests, clippy-clean,
  forbid(unsafe_code), no heavy deps.
- docs/adr/ADR-096-rvcsi-ffi-crate-layout.md — the implementation ADR: 8-crate
  topology, the napi-c shim record format + contract, the napi-rs Node surface,
  build/test invariants, alternatives. Indexed in docs/adr/README.md.
- CHANGELOG: rvCSI entry updated to cover the implementation crates.

https://claude.ai/code/session_01CdYAPvRTjcch6YrYf42n1z
2026-05-13 00:00:40 +00:00
Claude d98b7e3f65
docs: rvCSI edge RF sensing platform — PRD, ADR-095, DDD domain model
Adds design documentation for rvCSI, a Rust-first / TypeScript-accessible /
hardware-abstracted edge RF sensing runtime that normalizes WiFi CSI from
Nexmon, ESP32, Intel, Atheros, file and replay sources into one validated
CsiFrame schema, runs reusable DSP, emits typed confidence-scored events,
and bridges to RuVector RF memory, an MCP tool server and a TS SDK.

- docs/prd/rvcsi-platform-prd.md — purpose, users, success criteria,
  FR1-FR10, NFRs (safety/perf/reliability/privacy/security/portability),
  system architecture, runtime components, reference layout, data model
- docs/adr/ADR-095-rvcsi-edge-rf-sensing-platform.md — the 15 architectural
  decisions (Rust core, C-at-the-boundary, TS SDK via napi-rs, normalized
  schema, validate-before-FFI, CSI-as-temporal-delta, RuVector as RF memory,
  replayability, detection != decision, local-first, read-first/write-gated
  MCP, mandatory quality scoring, versioned calibration, plugin adapters)
- docs/ddd/rvcsi-domain-model.md — 7 bounded contexts (Capture, Validation,
  Signal, Calibration, Event, Memory, Agent) with aggregates, invariants,
  context map, data model and domain services
- indexed in docs/adr/README.md and docs/ddd/README.md; CHANGELOG entry

Design-only; no code or crates added yet.

https://claude.ai/code/session_01CdYAPvRTjcch6YrYf42n1z
2026-05-12 23:15:10 +00:00
rUv eaedfded6f
fix(train): wire wifi-densepose-signal into the pipeline; correct MODEL_CARD env-sensor claim (#536)
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.
2026-05-11 23:40:55 -04:00
ruv ad41a89960 feat(pointcloud): integrate ESP32 CSI as optional data stream from hosted viewer
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>
2026-04-29 20:33:00 -04:00
ruv cbedbce9e3 feat(pointcloud): use MediaPipe Face Mesh for the live demo (ADR-094)
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>
2026-04-29 19:42:51 -04:00
rUv 21b2b3352f
feat(pointcloud): GitHub Pages demo with optional live backend (ADR-094) (#495)
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.
2026-04-29 19:35:41 -04:00
ruv e11d569a39 docs(readme): split details to docs/readme-details.md and reorganize
- 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>
2026-04-29 19:34:24 -04:00
rUv 7f5a692632
feat(nvsim): full simulator stack — Rust crate, dashboard, server, App Store, Ghost Murmur [ADR-089/090/091/092/093]
Squashed merge of feat/nvsim-pipeline-simulator (29 commits).

## Shipped

- ADR-089 nvsim crate (Accepted) — 50/50 tests, ~4.5 M samples/s, pinned witness cc8de9b01b0ff5bd…
- ADR-092 dashboard implementation (Implemented) — 8/12 §11 gates , 4/12 ⚠ (external infra)
- ADR-093 dashboard gap analysis (Implemented) — 21/21 catalogued gaps closed
- Plus ADR-090 (proposed conditional) and ADR-091 (proposed research-only)

## Live deploy
https://ruvnet.github.io/RuView/nvsim/

## Infra

- nvsim-server Dockerfile + GHCR publish workflow (.github/workflows/nvsim-server-docker.yml)
- axe-core + Playwright cross-browser CI (.github/workflows/dashboard-a11y.yml)
- gh-pages auto-deploy workflow already in place (preserves observatory + pose-fusion siblings)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-27 12:41:01 -04:00