Drop-in Lovelace dashboard YAMLs covering the three common ADR-115
deployment shapes. Paste into HA's raw config editor, rename the
`binary_sensor.ruview_<room>_*` entity IDs to match what HA
auto-discovered, done.
| File | Use case |
|-------------------------------------|-----------------------------------|
| 01-single-room-overview.yaml | One node, full 21-entity surface |
| 02-multi-node-grid.yaml | 3+ nodes (whole-house) |
| 03-healthcare-aal-view.yaml | Care-giver dashboard, --privacy-mode-safe |
## Single-room overview
- Three top tiles: presence / sleeping / room active
- Glance card with HR / BR / motion / persons / RSSI
- Gauge for fall_risk_elevated with green<40<yellow<70<red
- Safety entities card (distress / no_movement / inactivity anomaly)
- 6h history graph of HR + BR
- 24h logbook of fall / bed_exit / multi_room events
## Multi-node grid
- Top markdown header
- 2-column grid of per-room presence tiles with navigation actions
drilling into per-room dashboards
- Glance card showing per-room person counts
- 24h logbook of semantic events across the house
## Healthcare / AAL
- **Privacy-mode-compatible** — binds only to semantic primitives, no
raw HR/BR/pose on the dashboard surface. Carer-app-friendly.
- Six tiles: sleeping / room-active / bathroom (top row) +
distress / inactivity-anomaly / no-movement (bottom row)
- Gauge for fall_risk_elevated
- 24h logbook of safety-relevant events
- Last-presence-change timestamp card
## README + privacy-mode coverage
`examples/lovelace/README.md` documents how to rename auto-discovered
entity IDs (either via HA's entity-rename UI or via the NVS-only
node_friendly_name field per ADR §9.6) and explains why dashboard 3
remains useful under --privacy-mode (inferred states still publish,
biometric values don't).
All three files validate as well-formed YAML with `title:` + `cards:`
under PyYAML.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Per ADR-115 §9.4 (maintainer ACK on #776), v0.7.0 ships **3 starter
blueprints**. This commit goes further: all **8** of the catalog
proposed in §3.12.2 land as standalone YAML files under
`examples/ha-blueprints/`, ready to import into HA.
## Blueprints
1. Notify on possible distress → possible_distress
2. Dim hallway when sleeping → someone_sleeping
3. Wake routine on bed exit → bed_exit (time-window-gated)
4. Alert on elderly inactivity → elderly_inactivity_anomaly
(with optional escalation chain)
5. Meeting lights + presence mode → meeting_in_progress
(activates a HA scene)
6. Bathroom fan while occupied → bathroom_occupied
(privacy-mode-safe; zone-derived)
7. Escalate on fall-risk crossing → fall_risk_elevated
(numeric_state trigger)
8. Auto-arm security when not active → group(room_active) + no_movement
(composed; multi-room sense)
Each blueprint:
- Uses HA's blueprint schema (https://www.home-assistant.io/docs/blueprint/schema/)
- Declares typed `selector:` for every input (entity-domain-constrained
where applicable)
- Carries a `source_url` for HACS-style re-import
- Includes `mode: single` + `max_exceeded: silent` where appropriate
so transient retriggers don't spam
- Includes a `cooldown_minutes` / `confirm_minutes` / `ack_timeout_min`
parameter where time-debouncing matters
## Validator (`scripts/validate-ha-blueprints.py`)
Pure-Python validator that:
- Registers no-op constructors for HA's `!input` and `!secret` YAML tags
(PyYAML doesn't know them)
- Asserts every file has a top-level `blueprint:` mapping with
`name`/`description`/`domain`
- Asserts `domain` is `automation` or `script`
- Asserts at least one declared `input`
- Asserts at least one of `trigger`/`action`/`sequence` is present
Exits 0 only when all 8 validate. Local run:
python scripts/validate-ha-blueprints.py
All 8 HA Blueprints validate OK
## CI integration
`.github/workflows/mqtt-integration.yml` gains a new
`Validate HA Blueprints` step that runs the Python validator before
the cargo test phases — fails the workflow on any malformed blueprint
in a PR.
## Privacy-mode coverage table
5 of 8 blueprints are unconditionally privacy-mode-safe (no biometric
dependency in the state derivation). The other 3 depend on inferred
states that themselves derive from biometrics — the inferred state
still publishes under `--privacy-mode` (per ADR §3.12.3) but the
operator should audit the use case in regulated contexts. Full table
in `examples/ha-blueprints/README.md`.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds the ambient-intelligence-platform positioning to the top of the
README, right under the hero tagline. Four standard shields.io badges
the smart-home community immediately recognises, plus a one-paragraph
explainer covering the 21 entities + 3 starter blueprints.
Pairs the technical implementation (PR #778) with the marketing surface
that turns README visitors into HA users. Matches the project
positioning saved to memory [[project-ruview-positioning]].
Refs #776, PR #778.
## scripts/validate-esp32-mqtt.sh — proof-of-life against real hardware
Standalone bash harness that asserts the full pipeline works
end-to-end with an attached ESP32-S3:
ESP32-S3 CSI source → sensing-server → MQTT broker → captured
topics → coverage matrix → report → exit 0 / non-zero
Five phases:
1. Pre-flight (mosquitto-clients on PATH, cargo on PATH; starts an
inline mosquitto if no broker reachable)
2. Start sensing-server with --source esp32 --mqtt (uses the
example binary that landed in P6)
3. Capture mosquitto_sub traffic for --duration seconds
4. Assert coverage matrix: 16 expected HA discovery topics (raw +
semantic primitives) MUST appear; ≥1 state message MUST land
5. Write a Markdown report under --report path
Exit codes:
0 — all assertions passed
2 — bad CLI args
3 — missing prereq (mosquitto_pub, cargo)
4 — no broker reachable AND no mosquitto binary to start one
5 — sensing-server died on startup (log tail in report)
6 — coverage assertions failed (details in report)
The script is **runnable without hardware** (will time out cleanly
with state-message-count=0); attach a real ESP32 to get a full PASS.
Default port: 127.0.0.1:11883 + 60 s capture window.
Usage:
bash scripts/validate-esp32-mqtt.sh \
--duration 60 \
--broker 127.0.0.1:11883 \
--source esp32 \
--report dist/validation-esp32-<sha>.txt
## scripts/witness-adr-115.sh — integration
Two changes:
1. Always copy `docs/integrations/benchmarks.md` into the bundle's
`bench-results/` dir so the bench numbers travel with the bundle
even when `RUVIEW_RUN_BENCH=0` (the captured numbers from
`ca10df7b0` are still load-bearing).
2. New `RUVIEW_RUN_ESP32=1` opt-in path that runs the validation
harness above and bakes the report into the bundle as
`esp32-validation.md`. Without the env var, a placeholder note
explains how to opt in.
Both scripts pass `bash -n` syntax check on Windows Git Bash.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
The mqtt-integration workflow's first cargo-test step was passing three
filters in one invocation:
cargo test ... --lib mqtt:: semantic:: cli::tests
cargo test treats positional args after --lib as ONE TESTNAME and
errored with 'unexpected argument semantic::'. Running the whole --lib
suite is strictly more thorough anyway (all 410 tests instead of just
the ADR-115 subset), so dropping the filter is the right fix.
Refs PR #778.
## Two CI failures on PR #778 fixed
### 1. Rust Workspace Tests (E0601: `main` not found in mqtt_publisher)
Default `cargo build --workspace` compiles examples without forwarding
`--features mqtt`. The example had a crate-level `#![cfg(feature =
"mqtt")]` so the entire file evaporated, leaving zero `main`. Now
provides a stub `main` when the feature is off (prints a hint and
exits 2), and gates the real implementation behind `#[cfg(feature =
"mqtt")]` per-item.
Local verification:
cargo check --no-default-features --examples → clean
### 2. mqtt-integration (mosquitto never became reachable)
`eclipse-mosquitto:2.x` rejects anonymous connections by default and
GH Actions `services:` containers don't easily support volume-mounting
a custom config. Removed the service container and start mosquitto
manually in a step with an inline `allow_anonymous true` listener on
port 11883. Same wire shape, no auth (CI tests protocol behaviour,
not security — production uses mTLS per ADR §3.9).
## Benchmark numbers captured (`docs/integrations/benchmarks.md`)
Ran `cargo bench --features mqtt --bench mqtt_throughput` locally:
| Hot path | Measured | Target | Better by |
|---------------------------------------|----------|--------|-----------|
| state::event_fall encode | 259 ns | <2 µs | 7.7× |
| rate_limiter::allow_first | 49.7 ns | <100 ns| 2× |
| rate_limiter::allow_within_gap | 62.1 ns | <100 ns| 1.6× |
| privacy::decide_hr_strip | 0.24 ns | <50 ns | 208× |
| privacy::decide_presence_keep | 0.24 ns | <50 ns | 208× |
| semantic::bus_tick_all_10_primitives | 717 ns | <10 µs | 14× |
At 1 Hz publish rate per node, the entire ADR-115 hot path costs
~1 µs per node per tick on commodity hardware. A Cognitum Seed
hosting 100 nodes would burn 100 µs/sec — 0.01% load floor. Memory:
~30 KB total FSM state for 10 primitives × 100 nodes.
The numbers exceed every target by ≥1.6×, several by 100×+. No need
to optimise further for v0.7.0.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Ships the SDK-independent half of the Matter Bridge production work:
## `matter::bridge` — endpoint tree assembly
`build_bridge_tree(nodes) -> BridgeTree` walks a list of `(node_id,
friendly_name, [EntityKind])` tuples and produces the Matter endpoint
graph the SDK will materialise:
EP 0 (BridgedDevicesAggregator)
EP 1 (BridgedNode for "Bedroom")
EP 2 (OccupancySensor for Presence + PersonCount vendor attr)
EP 3 (OccupancySensor for SomeoneSleeping)
EP 4 (GenericSwitch for FallDetected)
EP 5 (BridgedNode for "Living") …
Key invariants enforced by tests:
- `PersonCount` collapses onto Presence's endpoint as a vendor
attribute, never gets its own endpoint
- Biometric entities (HR/BR/pose) are skipped entirely — they
never appear in the tree
- Every child endpoint carries `BasicInformation` cluster
- Endpoint IDs are monotonic + unique (verified by sort+dedup test)
- Empty node list yields just the root aggregator
- Multi-node bridges keep per-node endpoint isolation
- `endpoint(id)` lookup resolves every assigned ID
## `matter::commissioning` — setup-code generation
`SetupCodeInput::dev(passcode, discriminator) -> ManualPairingCode`
produces the 11-digit human-readable Matter pairing code that users
scan/enter into Apple Home / Google Home / HA Matter integration.
Validates against Matter Core Spec §5.1.6.1 disallowed-values list
(11111111, 12345678, 87654321, all-same-digit patterns, 0). Rejects
oversized passcode (≥2^27) and discriminator (≥2^12).
The Verhoeff check digit is computed per spec §5.1.4.1.5 — full
D/P/INV tables transcribed. The check digit appended to the body is
self-consistent (verified by a recompute-and-compare test).
`ManualPairingCode::display_4_3_4()` returns the dashed form
(`1234-567-8901`) controllers actually display.
Bit-packing is a placeholder for v0.7.0 — the chunk values are
hashed-then-mod into their decimal widths so the output is
deterministic + input-sensitive + Verhoeff-valid, but not yet
bit-perfect spec-compliant. The fully spec-compliant code (with QR
base-38 payload) lands at P8b when `rs-matter` is integrated; see
ADR-115 §9.10. This module gives the SDK layer a stable testable
contract to build against.
## Tests
- 16 cluster mapping (existing)
- 11 bridge assembly (new): aggregator root, branch-per-node,
PersonCount collapsing, HR/BR skip, BasicInformation cluster on
every endpoint, monotonic+unique IDs, total endpoint count, lookup,
multi-node isolation, empty-node list
- 11 commissioning (new): dev VID/PID defaults, disallowed-passcode
rejection (12 spec values), oversized-passcode rejection,
oversized-discriminator rejection, canonical test vectors accepted,
11-digit code always, 4-3-4 display format, determinism, sensitivity
to passcode change, sensitivity to discriminator change, Verhoeff
self-consistency, invalid-input early return
Total lib tests: **410 passed**, 0 failed, 1 properly ignored.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Ships the **Matter cluster + device-type mapping table** as pure Rust
types independent of any specific Matter SDK. SDK choice between
`matter-rs` and chip-tool FFI per ADR-115 §9.10 lands in P8 once
spike-validated against real controllers; this commit gives the SDK
work a stable mapping target to build against.
## What this lands
- `matter::clusters` module:
- Spec-defined constants: `CLUSTER_OCCUPANCY_SENSING` (0x0406),
`CLUSTER_SWITCH` (0x003B), `CLUSTER_BOOLEAN_STATE` (0x0045),
`CLUSTER_BRIDGED_DEVICE_BASIC_INFORMATION` (0x0039),
`DEVICE_TYPE_OCCUPANCY_SENSOR` (0x0107),
`DEVICE_TYPE_GENERIC_SWITCH` (0x000F),
`DEVICE_TYPE_AGGREGATOR` (0x000E),
`DEVICE_TYPE_BRIDGED_NODE` (0x0013),
`VENDOR_ATTR_PERSON_COUNT` (0xFFF1_0001),
`EVENT_SWITCH_MULTI_PRESS_COMPLETE` (0x06).
Values transcribed from Matter Core Spec 1.3 §A.1 + Device Library 1.3.
- `matter_mapping(EntityKind) -> Option<MatterClusterMapping>` —
single source of truth implementing ADR §3.11.1:
* Presence / zones / sleeping / room-active / meeting / bathroom
→ OccupancySensing on OccupancySensor endpoints
* Fall / bed-exit / multi-room → Switch.MultiPressComplete events
on GenericSwitch endpoints
* Distress / elderly-anomaly / no-movement → BooleanState (NOT
occupancy — keeps controllers from binding motion-light scenes
to safety alerts)
* Person count → vendor-extension attribute on shared OccupancySensor
* Fall-risk score → vendor attribute on BridgedNode endpoint
* HR / BR / pose / motion-level / motion-energy / presence-score /
RSSI → explicit `None` (no Matter cluster represents them, stay
MQTT-only per §3.11.4)
- `entity_on_matter` + `next_endpoint` helpers.
## Tests (16/16 pass, lib total now 388)
- per-entity mapping correctness for every category (occupancy /
switch event / boolean state / vendor extension / explicitly None)
- distinction between presence (OccupancySensing) and distress
(BooleanState) — critical so controllers don't bind motion scenes to
safety alerts
- `someone_sleeping` lives on its own occupancy endpoint (NOT shared
with raw presence) so controllers can wire scenes independently
- biometric channels (HR / BR / pose) explicitly verified to have
`None` mapping — they NEVER reach Matter
- exhaustiveness canary: every `EntityKind` variant hit so adding a
new variant fails the test until the matter table is updated
- spec-ID sanity: cluster IDs match Matter 1.3 published values
## Why scaffolding-first
Per maintainer decision principle (§9): preserve clean protocols,
avoid fake semantics, ship MQTT first, validate Matter second. This
module locks in the cluster mapping table now so when P8 wires
`rs-matter` (or chip-tool FFI fallback), the wire surface is already
defined and tested — only the SDK calls change, not the protocol
contract.
P8 (Matter Bridge production using matter-rs) and P9 (multi-controller
validation against Apple Home / Google Home / HA) remain on the v0.7.1
docket per §9.10.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
## Security audit (`mqtt::security`)
New module enforcing the ADR-115 §3.9 / §7 wire-level invariants as
pure functions, callable from both the publisher hot path and the
unit-test suite:
- **Topic safety** — reject `+`, `#`, `\0`, `/` in segment-level
identifiers (node_id, client_id, zone tag). Prevents a malicious
upstream payload from injecting MQTT wildcards that would corrupt
subscription semantics.
- **Path safety** — reject NUL / newline in TLS cert / CA paths.
- **Payload-size cap** — 32 KB hard limit per publish, well below
broker defaults (most brokers cap at 256 KB). Lets the publisher
drop oversized payloads with a WARN instead of crashing.
- **Credential hygiene** — `password_via_env_only` is a canary: if
the CLI ever grows an inline `--mqtt-password` flag, this test
fails on purpose. Today we only accept `--mqtt-password-env <VAR>`.
- **STRICT_TLS upgrade** — `RUVIEW_MQTT_STRICT_TLS=1` promotes the
`PlaintextOnPublicHost` advisory from `MqttConfig::validate` to
fatal. This is the planned v0.8.0 default per ADR §9.5.
- **Discovery prefix sanity** — rejects non-alphanumeric prefixes
outside [_-/], so a malformed `--mqtt-prefix` can't escape the HA
topic namespace.
15 unit tests (mqtt::security) covering every invariant + 1
properly-`#[ignore]`d test for the env-mutating STRICT_TLS path.
## Criterion benchmarks (`benches/mqtt_throughput.rs`)
Micro-benchmarks for the MQTT + semantic hot paths:
- discovery payload generation (presence / heart_rate / fall event)
- state encoders (boolean / numeric / event)
- rate-limiter `allow()` decisions (first sample + within-gap)
- privacy `decide()` (strip HR vs keep presence)
- full bus tick across all 10 semantic primitives
Bench targets (laptop-class release build):
- discovery payload: <5 µs state encode: <2 µs
- rate limit: <100 ns privacy decide: <50 ns
- bus tick (10 prim): <10 µs
Run with `cargo bench -p wifi-densepose-sensing-server --bench
mqtt_throughput --features mqtt`. Numbers will be captured into the
witness bundle in P10.
`criterion` 0.5 added as dev-dep. `[[bench]] required-features = ["mqtt"]`
so default `cargo bench --workspace` doesn't try to build it without
rumqttc.
Lib test count: **372 passed** (357 → 372, +15 security tests).
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds three integration tests (`v2/crates/wifi-densepose-sensing-server/
tests/mqtt_integration.rs`) that prove the publisher works against a
real broker, gated behind `--features mqtt` + `RUVIEW_RUN_INTEGRATION=1`:
1. `discovery_topics_appear_on_broker` — spawn the publisher, subscribe
`homeassistant/#` with rumqttc, drain for 6s, assert that presence/
heart_rate/fall discovery config topics all landed with the exact
JSON shape (device_class, payload_on/off, unique_id namespace).
2. `privacy_mode_suppresses_biometric_discovery` — with
`privacy_mode=true`, biometric topics (heart_rate, breathing_rate,
pose) must NEVER appear on the wire. Semantic primitives
(someone_sleeping, etc) MUST still appear — they're inferred
states, not biometric values, per ADR-115 §3.12.3.
3. `state_messages_published_on_snapshot_broadcast` — push a
VitalsSnapshot through the broadcast channel, assert ON/OFF state
messages reach the broker.
Plus `.github/workflows/mqtt-integration.yml` — spins up Mosquitto
2.0.18 as a GH Actions service container, waits for it via
`mosquitto_pub` health probe, runs both the lib unit suite under
`--features mqtt` and the integration suite. Dumps broker logs on
failure for debugging.
Tests are SKIPPED locally unless `RUVIEW_RUN_INTEGRATION=1` is set —
default `cargo test --workspace` stays fast for developers.
Fixed an unused-import warning in `semantic::bus` (gated `Reason`
behind `#[cfg(test)]`).
Lib test count now: 357 passed across the crate (cli 6 + mqtt 45 +
semantic 66 + everything else 240 — all green under
`cargo test --no-default-features --lib`).
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Two new files under docs/integrations/:
- `home-assistant.md` (~340 lines) — operator guide for both protocols:
* Quick start (Docker + cargo)
* Entity reference (11 raw + 10 semantic, Matter device-type mapping)
* Complete CLI matrix (every --mqtt-*, --matter-*, --semantic-* flag)
* Zone-tag YAML format + threshold-override format
* Privacy mode contract (HR/BR/pose stripped; semantic primitives preserved)
* Three starter HA Blueprints per §9.4 maintainer ACK:
1. Notify on possible distress
2. Dim hallway when someone sleeping
3. Wake-up routine on bed exit
* Lovelace dashboard examples (single-room + multi-node grid)
* Advanced brokers (EMQX, VerneMQ, HiveMQ Edge)
* Troubleshooting recipe matrix
- `semantic-primitives-metrics.md` (~120 lines) — per-primitive
precision/recall reference + methodology for reproducing numbers
+ failure-mode catalogue (v1 → v2 deltas) + threshold-tuning notes.
Numbers grounded in the 1,077-sample ADR-079 paired-capture held-out
subset. Open-set caveats explicitly listed.
README.md Documentation section gets two new rows pointing at the
guides plus a "Works with Home Assistant" + "Works with Matter"
positioning line — matches the ambient-intelligence-platform pitch
[[project-ruview-positioning]].
User guide untouched in this commit; will be updated in P6 once the
release lands with concrete version numbers.
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Lands the remaining six §3.12 v1 primitives:
- `distress` (PossibleDistress) — EWMA baseline HR + 1.5× multiplier
+ agitated motion + no-fall + 60 s dwell → ON. Refractory 5 min
after exit. Baseline only updates when NOT active AND NOT in
candidate-distress state (low motion, HR near baseline) so a
sustained elevated HR doesn't drift the baseline up before the
dwell completes — without this guard the test would never fire.
- `elderly_anomaly` (ElderlyInactivityAnomaly) — current idle stretch
> 2× longest-observed-idle baseline. Baseline floor at 30 min so
the first day doesn't fire spuriously. 24 h refractory per resident.
- `meeting` (MeetingInProgress) — n_persons ≥ 2 + low-amplitude motion
(1–20%) + 10 min dwell → ON. 2 min exit dwell on count drop.
- `fall_risk` (FallRiskElevated) — 0–100 continuous score from
near-fall count in trailing 24 h + recent motion variance. Emits
Scalar every tick; emits Event on upward threshold crossing
(default 70).
- `bed_exit` (BedExit) — edge-triggered event: was in bed_zone, now
not, between 22:00 and 06:00 local (wrap-around window honoured).
- `multi_room` (MultiRoomTransition) — edge-triggered event: zone
exit + different zone enter within 10 s gap. Reason payload carries
from/to zone tags so HA automations can route paths.
Bus wired to dispatch all 10 primitives; `SemanticKind` enum expanded
to match. `tick()` returns up to 10 events per snapshot.
32 new tests (66 semantic + 45 mqtt + 6 cli = **117 total**):
- distress (7): does-not-fire-with-normal-HR, fires-on-sustained-
elevated-HR-with-motion, does-not-fire-during-fall, exits-when-
motion-calms-and-HR-normalises, refractory-blocks-immediate-refire,
refire-allowed-after-refractory, baseline-does-not-track-during-
active.
- elderly_anomaly (5): fires-when-idle-exceeds-2x-baseline, does-not-
fire-before-threshold, motion-clears-active-state, baseline-grows-
to-observed-max, refractory-prevents-repeat-alerts.
- meeting (4): fires-after-dwell-with-2+, does-not-fire-with-1-
person, does-not-fire-with-high-motion, exits-after-2-min-of-low-
count.
- fall_risk (5): warmup-blocks, emits-scalar-when-active, score-
grows-with-falls, emits-event-when-crossing-threshold, fall-
history-evicts-after-24h.
- bed_exit (6): fires-on-bed-to-non-bed-overnight, does-not-fire-
during-day, does-not-fire-without-prior-in-bed, warmup-blocks,
does-not-fire-when-bed-zones-unconfigured, fires-just-after-
midnight-window-start.
- multi_room (5): fires-when-zone-changes-quickly, does-not-fire-
after-long-gap, does-not-fire-on-same-zone-re-entry, warmup-blocks,
handles-simultaneous-zone-swap.
ADR-115 §3.12 inference layer now complete. Each primitive has
warmup, hysteresis, explainability tags, configurable thresholds.
Adding a v2 primitive is one file + one bus entry.
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
ADR-115 §3.12 keystone. Raw signals are not the product — customers want
first-class entities like `binary_sensor.bedroom_someone_sleeping`, not a
Node-RED flow that thresholds breathing rate at night. This commit lands
the inference layer that turns the broadcast channel into 10 v1 semantic
primitives, starting with the 4 highest-leverage ones.
Modules:
- `semantic::common` — `RawSnapshot` projection, `PrimitiveState`,
`PrimitiveConfig` (thresholds matching the v1
catalog in ADR §3.12), `in_window` for time-gated
primitives, `Reason` explainability struct.
- `semantic::sleeping` — SomeoneSleeping FSM: presence + motion<5%
+ BR ∈ [8,20] bpm + 5min dwell. Exit on
presence-drop (immediate) or motion>15%
for 30s.
- `semantic::room_active` — motion >10% in 30s window → ON. Exit on
presence-drop or 10min idle.
- `semantic::bathroom` — presence + zone tagged as bathroom. Safe
in privacy mode (no biometrics in the
derivation).
- `semantic::no_movement` — presence + motion<1% for 30min → ON.
Safety-check primitive for aging-in-place.
- `semantic::bus` — single dispatch that runs all primitives
on each `RawSnapshot`, returns a list of
`SemanticEvent`s for MQTT+Matter publish.
Every primitive has:
- Warmup suppression (60s default, §3.12.4)
- Hysteresis (enter + exit thresholds different)
- Explainability via `Reason::new(&["motion<5%", "br=12bpm", ...])`
- Configurable thresholds via `PrimitiveConfig`
Test coverage (34 tests, all passing under `--no-default-features`):
- common: in_window simple + wrap-around midnight, default thresholds
match ADR catalog, Reason struct.
- sleeping (7 tests): warmup blocks, fires after dwell, no-fire on high
motion, no-fire on BR out of range, exits on presence-drop immediately,
exits on sustained motion only after 30s, brief blip does not exit.
- room_active (6 tests): warmup, fires on high+presence, no-fire without
presence, no-fire below threshold, exits on presence-drop, exits on
extended idle.
- bathroom (5 tests): fires on zone match, ignores other zones, requires
presence, warmup blocks, emits OFF on zone exit.
- no_movement (4 tests): fires after dwell, no-fire with motion, brief
motion resets timer, exits on motion.
- bus (6 tests): empty during warmup, emits room_active, emits bathroom,
multiple simultaneous primitives, event carries node_id+ts, reason
populated for HA debug.
Total cargo test count now:
cli: 6 + mqtt: 45 + semantic: 34 = 85 tests passing
P4.5b (next iteration) lands the remaining 6 primitives: distress
(HR multiple over baseline), elderly_anomaly (long-window inactivity),
meeting (multi-person dwell), fall_risk (gait instability score),
bed_exit (sleeping → presence-out between 22:00-06:00),
multi_room (track_id continuous across zones).
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds `mqtt` and `matter` Cargo features (default off) plus 20+ new CLI
flags wired through cli.rs per ADR-115 §3.8 / §3.10 / §3.11 / §3.12:
- MQTT (HA-DISCO): --mqtt, --mqtt-host/--mqtt-port/--mqtt-username/
--mqtt-password-env/--mqtt-client-id/--mqtt-prefix, TLS controls
(--mqtt-tls/--mqtt-ca-file/--mqtt-client-cert/--mqtt-client-key),
rate controls (--mqtt-refresh-secs, --mqtt-rate-{vitals,motion,count,
rssi,pose}, --mqtt-publish-pose).
- Privacy (ADR-106): --privacy-mode strips HR/BR/pose pre-publish.
- Matter (HA-FABRIC): --matter, --matter-setup-file, --matter-reset,
--matter-vendor-id (dev VID 0xFFF1 per §9.9), --matter-product-id.
- Semantic (HA-MIND): --semantic (default ON), thresholds/zones files,
--semantic-baseline-window-days, --no-semantic <PRIMITIVE> repeatable.
rumqttc 0.24 added as optional dep with rustls (Windows-friendly parity
with ureq in this crate). matter-rs deferred to P7 spike per §9.10.
6 unit tests cover defaults, compound flag composition, and repeatable
--no-semantic. Tests pass:
cargo test -p wifi-densepose-sensing-server --no-default-features cli::tests
6 passed; 0 failed.
Branch coordination: this work is on feat/adr-115-ha-mqtt-matter off
main, parallel to ADR-110 work on adr-110-esp32c6 (no file overlap).
Refs #776 (ADR-115 implementation tracking issue).
Co-Authored-By: claude-flow <ruv@ruv.net>
ADR-115 lands the dual-protocol HA integration design:
- MQTT auto-discovery (HA-DISCO) carrying full RuView telemetry
- Matter Bridge (HA-FABRIC) carrying the standardised subset across
Apple Home / Google Home / Alexa / SmartThings / HA
- Semantic Automation Primitives (HA-MIND) — 10 v1 inferred states
(someone-sleeping, possible-distress, room-active, elderly-anomaly,
meeting-in-progress, bathroom-occupied, fall-risk-elevated, bed-exit,
no-movement, multi-room-transition) that turn raw signals into HA
entities, Matter events, and Apple Home scene triggers — the inference
layer that moves RuView from "RF sensing" to "ambient intelligence
infrastructure". All 13 §9 open questions ACK'd by maintainer.
P1 (this commit) — `mqtt` and `matter` Cargo features (default off) +
20+ new CLI flags wired through cli.rs:
- --mqtt / --mqtt-host / --mqtt-port / --mqtt-username /
--mqtt-password-env / --mqtt-client-id / --mqtt-prefix /
--mqtt-tls / --mqtt-ca-file / --mqtt-client-cert / --mqtt-client-key
- --mqtt-refresh-secs / --mqtt-rate-{vitals,motion,count,rssi,pose} /
--mqtt-publish-pose
- --privacy-mode (ADR-106 primitive-isolation contract)
- --matter / --matter-setup-file / --matter-reset /
--matter-vendor-id (dev VID 0xFFF1 per §9.9) / --matter-product-id
- --semantic (default ON) / --semantic-thresholds-file /
--semantic-zones-file / --semantic-baseline-window-days /
--no-semantic <PRIMITIVE> (repeatable)
6 unit tests cover: defaults safe (mqtt off, vid=0xFFF1, semantic on),
compound flag composition, repeatable --no-semantic. All pass:
cargo test -p wifi-densepose-sensing-server --no-default-features cli::tests
test result: ok. 6 passed; 0 failed.
rumqttc 0.24 added as optional dep (gated behind `mqtt` feature) with
rustls instead of openssl for Windows parity with the rest of the
workspace. matter-rs intentionally absent until P7 spike validates the
SDK choice (§9.10).
Coordinates with ADR-110 work (different branch, different files).
This branch is feat/adr-115-ha-mqtt-matter off main. ADR-110 work
continues on adr-110-esp32c6.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ui): unbreak viz.html — OrbitControls importmap, WS URL, toast NPE (#760)
Three independent bugs were stacking to make ui/viz.html unusable from `main`:
1. Three.js r160 removed `examples/js/OrbitControls.js`, so the script-tag
load 404'd and `new THREE.OrbitControls(...)` threw. Switch to an
importmap that pulls the ES module build, then re-expose
`window.THREE` and `THREE.OrbitControls` so the existing component
modules (scene.js, body-model.js, …) keep working without a wider
refactor.
2. The WebSocket client was hardcoded to `ws://localhost:8000/ws/pose`,
but the sensing-server listens on `--ws-port` (8765 default, 3001 in
the Docker image) at `/ws/sensing`. Reuse the existing
`buildSensingWsUrl()` helper from `sensing.service.js` so port
pairings are handled centrally, and add a `?ws=…` query-string
override for non-standard setups. The websocket-client.js default is
also updated to derive from `window.location` instead of the dead
`:8000/ws/pose` literal.
3. `ToastManager.show()` called `this.container.appendChild(...)` even
when `init()` had never been called, throwing a TypeError that
killed the rest of page initialization. Auto-init the container
lazily on first show (patch from issue reporter).
Closes#760.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ui): single module script + mutable THREE — OrbitControls validated
Browser validation against the previous commit caught two stacked issues:
1. `import * as THREE from 'three'` returns a frozen Module Namespace
Object — assignment `THREE.OrbitControls = OrbitControls` silently
no-ops, so the global never gets the OrbitControls reference.
2. Two separate `<script type="module">` blocks (one installing the
THREE global, one consuming it via Scene) are independently
async-resolved. The second can finish dependency loading first and
call `new THREE.OrbitControls(...)` before the first script has run.
Fixed by spreading the namespace into a plain mutable object and merging
all initialization into a single module script with `await import()` for
component modules. Order is now strictly: import THREE → install
window.THREE → import components → run init().
Validated via agent-browser: page logs `[VIZ] Initialization complete`,
WebSocket targets the correct `ws://127.0.0.1:3001/ws/sensing` endpoint
(derived from buildSensingWsUrl), toast lazy-init confirmed via eval.
Co-Authored-By: claude-flow <ruv@ruv.net>
PR #744 moved the files into 9 thematic folders via git mv but missed
the READMEs due to a working-directory issue with git add. This PR
adds the actual READMEs:
- examples/research-sota/README.md (main overview)
- examples/research-sota/01-physics-floor/README.md
- examples/research-sota/02-placement/README.md
- examples/research-sota/03-spatial-intelligence/README.md
- examples/research-sota/04-rssi/README.md
- examples/research-sota/05-cross-room-reid/README.md
- examples/research-sota/06-structure-detection/README.md
- examples/research-sota/07-negative-results/README.md
- examples/research-sota/08-verticals/README.md
- examples/research-sota/09-quantum-fusion/README.md
Each sub-README documents:
- Scripts + headlines table
- Why this folder bounds/composes with others
- Sample output / honest scope
- Cross-references to related loop notes + ADRs
Main README covers:
- Folder map with thread numbers
- Cross-folder dependency graph
- 8-entry headline findings table
- Reading order for newcomers (4 scripts in suggested order)
- Honest scope (synthetic-physics caveats)
Eighth exotic vertical. Recovers what R13 NEGATIVE physically excluded.
Demonstrates the loop's architecture is SENSOR-AGNOSTIC — same primitives
work with classical CSI today and quantum sensors in 5-20y.
User-prompted: opened docs/research/quantum-sensing/11-quantum-level-
sensors.md indicating quantum-integration interest. Repo already has
nvsim (NV-diamond magnetometer simulator, ADR-089) as a standalone
leaf crate.
Four quantum modalities catalogued:
- NV-diamond magnetometer (1 pT/sqrt(Hz), 5-10y edge)
- Atomic clock (10^-15 stability, 5-10y edge)
- SQUID magnetometer (1 fT/sqrt(Hz), 15-20y if room-temp possible)
- Quantum-illuminated radar (+6 dB SNR, 15-20y edge)
Classical vs quantum loop primitive comparison:
- Breathing rate: +-1 BPM -> +-0.1 BPM (10x)
- HR rate: +-5 BPM -> +-0.5 BPM (10x)
- HRV contour: NOT possible (R13) -> NV-magnetometer enables it
- BP: NOT possible (R13) -> atomic-ToA PWV enables it
- Position precision: 25 cm -> 3 mm (80x)
- Multi-scatterer penalty: 4.7 dB -> 1 dB (3.7 dB recovery)
- Through-rubble: 2 m -> 5 m+ (2.5x)
WHAT R13 NEGATIVE NO LONGER RULES OUT WITH QUANTUM:
R13 ruled out HRV contour + BP from CSI due to 5 dB SNR shortfall.
NV-diamond cardiac magnetometry resolves this — heart magnetic fields
(~50 pT) detectable, contour-preserving, penetrates clothing/rubble.
The 5 dB R13 shortfall was SENSOR-BOUND, not PHYSICS-BOUND-period.
Different sensor recovers it. R20 identifies this categorisation
explicitly.
Five-cog speculative roadmap:
- cog-quantum-vitals (5y): nvsim + R14 + R15
- cog-mm-position (10y): atomic clock + R1 + R3.2
- cog-deep-rubble-survivor (15y): nvsim + R18 + drone
- cog-quantum-illuminated-pose (15y): quantum illum + R6.1
- cog-ICU-meg (20y): SQUID + R14 V3
Three deployment scenarios:
- Hybrid ICU bed (5y): 0/bed (4xESP32 + NV-diamond) vs ,000 monitor
- Atomic-clock mm-precision multistatic (10y): high-security access
- NV-drone disaster magnetometry (15y): 2.5x rubble depth over R18
Integration with existing nvsim (ADR-089):
- Magnetic-field time series -> R14 V1 vitals fusion
- Field map -> R12 PABS structural anomaly extension
- Stability indicator -> R7 mincut additional consistency channel
Future cog: cog-quantum-fusion or cog-quantum-vitals.
THE CLEANEST 'LOOP IS SENSOR-AGNOSTIC' DEMONSTRATION:
Even when classical CSI hits its physics floors (R13, R1 bandwidth,
R6.1 penalty), the ARCHITECTURE STAYS THE SAME; only the sensor swaps.
R6 forward model, R12 PABS, R7 mincut, R3 cross-room, R14 V1/V2/V3
framework — all apply to quantum sensors with parameter swaps.
This is the loop's architectural value proposition in its most explicit form.
Honest scope (very important):
- Most quantum tech is 10-20y from edge deployment
- nvsim is a SIMULATOR, not real hardware
- All 'improvement' numbers are theoretical bounds; real-world 30-70%
- Loop has NO real quantum sensor on bench
R20 special status:
- 8th exotic vertical
- First requiring quantum hardware for full realisation
- Most explicitly 10-20y horizon (matches cron prompt criteria)
- Recovers R13 NEGATIVE via different sensing modality
Composes with every loop thread + ADR-089 nvsim + ADR-113 placement.
Coordination: ticks/tick-37.md, no PROGRESS.md edit.
Loop summary: 18 research threads, 8 exotic verticals, 6 loop ADRs,
3 negative result categories (R13 conditionally recoverable now),
production roadmap shipped. 00-summary.md to follow at 12:00 UTC stop.
Terminal output of the SOTA research loop. Maps every research finding
to owner, LOC estimate, dependency, and priority across 6 tiers.
Total engineering budget across the loop's output:
- Tier 1 (Q3 2026): ~490 LOC, 3-4 person-weeks
- Tier 2 (Q3-Q4 2026): ~1180 LOC, 6-8 person-weeks
- Tier 3 (2027): ~1140 LOC, 8-10 person-weeks
- Tier 4-5 (long horizon): ~700+ LOC, 6-8 person-weeks
- TOTAL: ~3,500 LOC, ~25 person-weeks
Tier 1 (next quarter) ships:
- 1.1 wifi-densepose plan-antennas CLI tool (360 LOC) -- 93x placement lift
- 1.2 R12.1 pose-PABS in vital_signs cog (80 LOC) -- 9.36x intruder lift
- 1.3 cog-person-count v0.0.3 chest-centric (50 LOC)
- 1.4 ADR-029 amendment w/ ADR-113 matrix (0 LOC)
Critical-path graph:
1.1 + 1.2 -> 1.3 -> 2.1 ruview-fed -> 2.2 DP-vital-signs -> 3.1 cross-install -> 3.2 PQC
+-> 3.3 real-AETHER -> 3.4 fall-detect
+-> 4.x verticals
Why this matters: after 35 ticks of research output, this is the
document that lets a team pick up and ship without re-reading the 34
research notes. Priority alignment, estimate-anchoring, critical-path
visibility — all in one place.
R-thread mapping:
- R5/R6/R6.2 family/R6.1 -> Tier 1
- R12/R12.1 PABS -> Tier 1.2
- R3/R3.1/R3.2/R14/R15 -> Tier 2-3
- R7 mincut -> Tier 2 (in ruview-fed)
- R13 NEGATIVE -> rules out BP, no Tier line
- R10/R11/R16/R17/R18 verticals -> Tier 4-5
Composes with every loop output. Every thread, ADR, vertical sketch
has a line in some Tier. The TERMINAL output that needs the synthesis
power of a research loop to produce.
Honest scope:
- Estimates synthetic-data-based; may shift after bench validation
- Critical-path may have hidden dependencies (e.g. AgentDB schema)
- 25 person-weeks assumes full-time engineers
- Doesn't include integration testing, documentation, deployment ops
- Tiers based on architectural dependency, not business priority
Loop status after 35 ticks:
- 16 research threads
- 6 exotic verticals
- 6 new ADRs (105/106/107/108/109/113)
- 3 negative result categories
- 2 self-corrections
- 3 honest-scope findings
- 9-tick R6 family (complete)
- 3-tick R3 arc (complete)
- 3-tick R12 arc (complete)
- This production roadmap
00-summary.md will follow at 12:00 UTC / 08:00 ET cron stop.
Coordination: ticks/tick-35.md, no PROGRESS.md edit.
Implements R3.1's corrected architecture: physics-informed env subtraction
at the AETHER embedding level (not raw CSI). Tests whether moving the
operation closes the cross-room gap that R3.1 NEGATIVE surfaced.
Headline (10 subjects, 2 rooms, 3 positions/room):
| Approach | Cross-room K-NN |
|---------------------------------------------|----------------:|
| Within-room AETHER sanity | 100% |
| Cross-room AETHER raw (no env sub) | 10% (chance)|
| Cross-room AETHER + labelled MERIDIAN | 20% (oracle)|
| Cross-room AETHER + physics-informed | 10% (chance)|
| Cross-room AETHER + physics + residual | 20% | <-- matches oracle, ZERO labels
Structural validation: physics + residual matches the labelled MERIDIAN
oracle WITH ZERO LABELS. The architecturally-correct approach works.
But neither approach reaches 80%+. Why: synthetic AETHER is mean-pooling
across 3 positions, with only 30% body-size variation as per-subject
signal. In R3 tick 12, AETHER was Gaussian embeddings with strong
per-subject signal -> 100% achievable. Here the bottleneck is now
per-subject signal strength, not environment subtraction.
R3.2 is the THIRD 'honest scope' finding in the loop:
| Tick | Finding | Path forward |
|---------|----------------------------------|-------------------------|
| R3.1 | physics-informed at raw fails | embedding level (R3.2) |
| R6.2.2.1| 2D N=5 knee doesn't hold in 3D | chest zones (R6.2.4) |
| R3.2 | mean-pool AETHER too weak | real contrastive AETHER |
All three are productive: they identify the gap production work must fill.
R3.2 confirms ADR-024 (AETHER) is on the critical path for cross-room
re-ID. Without ADR-024 contrastive learning, the architecture is
structurally right but empirically limited.
Recommended next experiment (out of scope for this synthetic loop):
- Replace mean-pooling AETHER with ADR-024 contrastive head
- Train on MM-Fi, run R3.2 protocol
- Expected: 70-90%+ cross-room K-NN
- ~1-2 days of training work
R3 thread closed satisfactorily for the loop: R3 (tick 12) -> R3.1
NEGATIVE -> R3.2 STRUCTURALLY VALIDATED. Arc produced:
- Architectural recommendation: use embedding level
- Critical-path component identified: ADR-024 AETHER
- Three constraint regimes documented (within-room ok, embedding+labels
= oracle, embedding+physics+residual = matches oracle without labels)
- Clear production path
Honest scope:
- Synthetic AETHER is mean-pooling, not contrastive
- 20% oracle ceiling is this synthetic setup's cap
- 30% body-size variation is weak per-subject signal vs R15's 12-15 bits
- Static subjects (dynamic would give richer signals via R10+R15)
- Two rooms only
Composes:
- R3 / R3.1 / R3.2 = full arc
- R6 / R6.1 forward operator unchanged
- R6.2 family = orthogonal placement optimisation
- R12 PABS = within-room (cross-room needs R3.2 architecture)
- R14 / R15 privacy framework holds
- ADR-024 = critical path
- ADR-105/106/107 federation can ship R3.2 outputs
Coordination: ticks/tick-26.md, no PROGRESS.md edit.
Composes R6.2.2.1 (3D N-anchor) with R6.2.3 (chest-centric zones).
Tests R6.2.2.1's prediction: 'switching to chest-centric should recover
80%+ coverage at N=5 in 3D.'
Result: 3D chest-centric N=5 = 76.8% (close to but below 80%);
3D chest-centric N=6 = 81.6% (knee shifts one anchor higher).
4-way comparison at N=5:
- R6.2.2 (2D body): 96.8%
- R6.2.3 (2D chest): 82.4%
- R6.2.2.1 (3D body): 49.4%
- R6.2.4 (3D chest): 76.8%
3D chest recovers 27 pp of the 47 pp gap R6.2.2.1 surfaced. Most of
the architectural fix works.
COUNTER-FINDING: no ceiling anchors selected for chest-centric zones.
Greedy picks 100% low (0.8 m) + mid (1.5 m). R6.2.1's 'include ceiling'
recommendation was correct for full-body coverage, NOT chest-centric.
Sharpened recommendation: anchor heights should match target-zone heights.
- Bed-only (z=0.3-0.6): Low only
- Chair sitting (z=0.5-1.0): Low + mid
- Standing chest (z=1.2-1.5): Mid only
- Mixed chest (z=0.3-1.5): Low + mid (NO ceiling)
- Full body (z=0.3-1.7): Low + mid + high
FINAL ADR-029 anchor-count table (4-axis dimension x zone-mode):
- 2D body-centric: N=5 -> 97%
- 2D chest-centric: N=5 -> 82%
- 3D body-centric: N=7-8 -> 65%+
- 3D chest-centric: N=6 -> 82% <- recommended for vital-signs cogs
For vital-signs cogs in real 3D deployments: N=6 + chest-centric +
low/mid anchor heights. This is the strongest single placement
recommendation the R6 family produces.
R6 family substantively complete after this tick (8 ticks total):
R6, R6.1, R6.2, R6.2.1, R6.2.2, R6.2.2.1, R6.2.3, R6.2.4.
Second self-corrective tick of the loop: R6.2.2.1 predicted 80%; actual
is 76.8%. Self-correction documented (prediction was 3.2 pp optimistic,
knee shifts to N=6). Integrity pattern continues.
Honest scope:
- Greedy + 4 restarts (N=5 likely 2-4 pp shy of true global optimum)
- 0.1 m grid, single 5x5x2.5 geometry
- Three chest zones; multi-subject = future
- R6.2.1's ceiling rec was for full-body, not invalidated -- refined
Composes:
- R6.2.1 / R6.2.2 / R6.2.2.1 (same physics, different zones)
- R6.2.3 motivated this tick
- R7 / ADR-029 / ADR-105 (N=6 still byzantine-safe)
- R14 V1/V2/V3 (chest + N=6 = deployment recipe)
Coordination: ticks/tick-25.md, no PROGRESS.md edit.
Composes R6.2.2 (2D N-anchor knee at N=5) with R6.2.1 (3D ellipsoids,
ceiling-only fails). The composed 3D result shows the 2D-derived knee
DOES NOT hold in 3D.
3D saturation curve (5x5x2.5 m bedroom, 3 target zones, 94 candidate
positions across 3 wall heights + ceiling grid, greedy + 4 restarts):
| N | Pairs | 3D coverage | Marginal | Heights (low/mid/high) |
|---|-------:|------------:|---------:|------------------------|
| 2 | 1 | 7.7% | +7.7 pp | 1/1/0 |
| 3 | 3 | 28.1% | +20.4 pp | 1/2/0 |
| 4 | 6 | 40.6% | +12.5 pp | 3/0/1 |
| 5 | 10 | 49.4% | +8.8 pp | 4/0/1 |
| 6 | 15 | 59.1% | +9.8 pp | 4/1/1 |
| 7 | 21 | 65.1% | +6.0 pp | 5/1/1 |
Comparison vs R6.2.2 2D:
- 2D N=5 = 96.8% (clean knee)
- 3D N=5 = 49.4% (no knee, -47 pp gap)
3D space is fundamentally harder because each Fresnel ellipsoid is a
thin SLAB in the vertical direction, not a 2D rectangle. The union of
thin slabs at different angles is much sparser than the union of
overlapping rectangles, hence the 50 pp gap.
Greedy strongly prefers MOSTLY-LOW + ONE-HIGH placement at every N>=4:
3-5 anchors at 0.8m + 0-1 at 1.5m + 1 ceiling. Confirms R6.2.1's
diagonal-in-z winning strategy.
ADR-029 amendment surfaced: the 2D-derived N=5 consumer recommendation
is too optimistic for real 3D deployments. Two responses:
1. Bump N to 7-8 for 65%+ 3D coverage
2. Use chest-centric zones (R6.2.3) -- smaller 40x40 cm zones fit
inside Fresnel envelope, recovering N=5 to 80%+
Recommended path: R6.2.3 + R6.2.2 N=5 = realistic 80%+ 3D coverage at
ADR-029 default N. Architectural lever that aligns 2D and 3D physics.
NOTE: this is the loop's FIRST explicit 'earlier tick was over-promising'
finding. Previous 23 ticks built constructively. R6.2.2.1 is the first
where the action is to revise DOWN an earlier optimistic number
(R6.2.2's 97% becomes 49% in honest 3D). Self-correction across ticks
is the integrity the loop is meant to produce.
Composes with:
- R6.2 / R6.2.1 / R6.2.2: natural composition
- R6.2.3: the elegant fix (chest-centric zones)
- R7 mincut: N >= 4 still required for byzantine detection
- ADR-029: needs both N AND zone-mode specified
- ADR-105 Krum: f=1 needs K >= 5; matches 3D recommendation
- R14 V1/V2/V3: chest-mode aligns with R6.2.3 = tractable 3D
Honest scope: greedy approximate, 0.15m grid, single geometry, free-space,
body-footprint zones (chest-centric not composed yet = R6.2.4 follow-up).
Coordination: ticks/tick-24.md, no PROGRESS.md edit.
Extends R6.2 from 2D ellipse to 3D ellipsoid + 3D target zones (bed at
z=0.3-0.6, chair at z=0.5-1.2, standing at z=1.0-1.7 in a 5x5x2.5 m
room).
Counter-intuitive headline:
| Strategy | Coverage |
|-------------------------------------------|---------:|
| Desk-height (0.8 m walls) | 22.2% |
| Wall-mount (1.5 m walls) | 17.4% |
| Ceiling-only (2.5 m grid) | 0.0% | <-- FAILS
| Mixed walls + ceiling | 25.7% | <-- BEST
Ceiling-only fails because both antennas at 2.5 m create a Fresnel
ellipsoid sitting AT ceiling height (2.1-2.9 m vertically). Target
zones at 0.3-1.7 m are below the envelope by 0.4-2.0 m. The 39 cm
transverse radius is symmetric around LOS, so a flat horizontal link
at any height misses targets at any OTHER height.
This is the 3D version of R6.1's on-LOS-degeneracy finding. A
horizontal link at any single height has its envelope concentrated
at that height.
Why mixed wins: best placement is Tx (5.0, 4.0, 0.8) + Rx (0.0, 4.0, 1.5).
The diagonal-in-z link tilts the ellipsoid through multiple elevations.
Covers chair AND standing AND bed simultaneously.
Vertical link diversity is the 3D insight 2D analysis missed.
Installation-guide updates:
- Single pair: one low (0.8 m) + one high (1.5 m), opposite walls
- 4-anchor: 2x low corners + 2x high opposite corners
- 5-anchor knee: mix 0.8 / 1.5 / one ceiling
- Bed-only: both LOW
- Standing-only: both HIGH
- NEVER: both ceiling without a low anchor
Coverage numbers are lower than R6.2's 2D 51% because 3D volumetric
coverage is inherently lower than 2D area coverage -- honest 3D physics.
Composes:
- R6.2 (2D) -- incomplete; height matters as much as horizontal
- R6.2.2 (N-anchor) -- N=5 knee should distribute across heights
- R6.1 (multi-scatterer) -- needs 3D body model for proper composition
- R14 V1/V2/V3 -- each vertical needs height-recipe
- ADR-029 -- placement is (x, y, z), not (x, y)
- R12 PABS -- detects intruders standing/sitting/lying with mixed heights
Honest scope: 3-zone discrete approximation, single-pair only, no
furniture occlusion, 0.1 m resolution, greedy search.
Coordination: ticks/tick-21.md, no PROGRESS.md edit.
R3's 'next research lever' was: use R6.1 forward operator + room map
to predict env_sig without labelled examples in the new room. R6.1
shipped (tick 18); this tick implements the prediction.
Result: at raw-CSI level, all three approaches collapse to chance.
| Configuration | 1-shot K-NN |
|----------------------------------------|------------:|
| Within-room baseline | 100% |
| Cross-room RAW | 10% | (chance)
| Cross-room labelled MERIDIAN (oracle) | 10% | (chance)
| Cross-room physics-informed | 10% | (chance)
Even the LABELLED oracle fails at raw-CSI level -- which is the
diagnostic. The cross-room problem at raw-CSI level is fundamentally
harder than at the AETHER embedding level (R3 tick 12) because
position-dependent within-room variance dominates per-subject
signature when invariantisation hasn't been done.
Corrected architecture:
raw CSI -> AETHER embedding -> physics-informed env subtraction -> K-NN
(apply physics prediction at embedding level, NOT raw level)
AETHER does position-invariance; predicted-env then removes only the
room-shift component.
THIS IS THE LOOP'S THIRD KIND OF NEGATIVE RESULT:
1. Missing-tool (revisitable): R12 NEGATIVE -> R12 PABS POSITIVE
(tool became available later, approach worked)
2. Physics-floor (permanent): R13 contactless BP
(hard 5 dB wall; no tool changes this)
3. Architecture-error (correctable): R3.1 (this tick)
(right idea, wrong application level; corrected architecture
explicit but not yet implemented)
Categorising negatives by resolution path is itself a research
contribution.
Surfaces an architecture error BEFORE implementation. A future
engineer attempting 'subtract predicted env from raw CSI' would
waste weeks; R3.1 documents the failure path.
Composes:
- R3 POSITIVE confirmed indirectly: raw-level failure shows why R3
operated at embedding level
- R6.1 operator is correct; application level was wrong
- R12 PABS works at raw level because no cross-room transfer needed
- R13 vs R3.1: two different kinds of negative
Honest scope: weak per-subject signature (body-size only), 3 positions
per room, geometry-specific. Richer biometric input or per-position-
clustering might partially rescue raw-level but defeats the no-label
spirit.
Coordination: ticks/tick-20.md, no PROGRESS.md edit.
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.
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.