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>
This commit is contained in:
rUv
2026-04-27 12:41:01 -04:00
committed by GitHub
parent 905b680747
commit 7f5a692632
70 changed files with 20533 additions and 0 deletions
@@ -0,0 +1,194 @@
# ADR-089: nvsim — NV-Diamond Magnetometer Pipeline Simulator
| Field | Value |
|----------------|-----------------------------------------------------------------------------------------|
| **Status** | Accepted — Passes 15 implemented and merged via the `feat/nvsim-pipeline-simulator` branch; Pass 6 (proof bundle + criterion bench) pending in the next iteration |
| **Date** | 2026-04-26 |
| **Authors** | ruv |
| **Companion** | `docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md`, `docs/research/quantum-sensing/15-nvsim-implementation-plan.md` |
## Context
`docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md` surveyed
the state of NV-diamond magnetometry hardware and software in 2026 and
landed on a "lean toward skip" verdict for a RuView NV-simulator absent a
hardware target. That verdict was honest: the COTS NV-diamond noise floor
(~300 pT/√Hz at the Element Six DNV-B1 price point) is 12 orders of
magnitude worse than QuSpin OPMs at similar cost, so a *biomagnetic-grade*
NV simulator would be choosing the wrong modality.
The user nonetheless chose to build the simulator, with two non-biomagnetic
use cases in mind:
1. **Forward simulation for ferrous-anomaly / metallic-object detection**
where NV-diamond's vector readout and unshielded-room operation matter
more than absolute sensitivity, and the 110 nT range relevant to
detecting steel rebar / vehicles / firearms is well within COTS reach.
2. **Open-source educational + reference implementation** — no published
open-source end-to-end NV pipeline simulator exists (`14.md` §2.2 gap).
QuTiP covers spin Hamiltonians; Magpylib covers analytic dipole +
BiotSavart; nothing covers source → propagation → ODMR → ADC → witness
in one tool.
`docs/research/quantum-sensing/15-nvsim-implementation-plan.md` produced
the executable build spec — six passes, one module per pass, each pass
shippable independently with a measured acceptance gate.
## Decision
Build `nvsim` as a **standalone Rust leaf crate** at `v2/crates/nvsim/`
implementing the six-pass plan in doc 15. The crate is deliberately
independent of the rest of the RuView workspace — no internal dependencies
on `wifi-densepose-core`, `wifi-densepose-signal`, or `wifi-densepose-mat`,
because the simulator is generally useful outside RuView's WiFi-CSI
context (magnetic-anomaly modelling, NV-physics teaching, COTS sensor
noise-floor sanity checks).
Six-pass implementation:
1. **Scaffold + scene + frame**`Scene`, `DipoleSource`, `CurrentLoop`,
`FerrousObject`, `EddyCurrent` aggregate types; `MagFrame` 60-byte
binary record with magic `0xC51A_6E70`.
2. **Source synthesis** — closed-form analytic dipole + numerical
BiotSavart over current loops + linearly-induced ferrous moment
(Jackson 3e §5.45.6; Cullity & Graham 2e §2; Magpylib reference
per Ortner & Bandeira 2020).
3. **Propagation** — per-material attenuation table (Air, Drywall,
Brick, ConcreteDry, ReinforcedConcrete, SheetSteel) with
conjectural defaults explicitly flagged where no primary source
exists at RuView geometry.
4. **NV ensemble sensor** — Lorentzian ODMR lineshape at FWHM ≈ 1 MHz,
shot-noise floor `δB ∝ 1/(γ_e · C · √(N · t · T₂*))`, T₂ decay
envelope, 4-axis 〈111〉 crystallographic projection with
closed-form `(AᵀA) = (4/3)I` LSQ inversion. Defaults match Barry
et al. *Rev. Mod. Phys.* 92 (2020) Table III for COTS bulk diamond.
5. **Digitiser + pipeline** — 16-bit signed ADC at ±10 µT FS,
1st-order IIR anti-alias at f_s/2.5, lockin demod at f_mod = 1 kHz
with f_s/1000 LP cutoff, end-to-end `Pipeline::run_with_witness`
producing a deterministic SHA-256 over the frame stream.
6. **Proof bundle + criterion bench***pending next iteration*.
Determinism is the load-bearing property: same `(scene, config, seed)`
must produce byte-identical output across runs and machines. Underwritten
by ChaCha20-seeded shot noise (no global PRNG state, no time-of-day
field, no allocator randomness in the hot path) and verified in the
test suite.
## Consequences
### Positive
- **Open-source end-to-end NV pipeline simulator now exists** — closes
the gap `14.md` §2.2 identified.
- **Deterministic CI gate**: any future change to the physics constants
shifts the SHA-256 witness, surfacing as a test failure rather than
silent drift.
- **Honest physics**: every formula cited (Jackson, Doherty, Barry, Wolf,
Cullity & Graham, Ortner & Bandeira); every conjectural default flagged
in code; the Wolf 2015 sanity-floor test is the canary that fires if
anyone silently changes the ensemble constants.
- **Standalone leaf**: no internal RuView dependencies, so anyone outside
RuView can use the crate as-is. RuView integrations land behind opt-in
feature flags.
- **Forward-simulation niche filled**: gives DSP / ML engineers a known-
answer-key stream for regression replay without sourcing a magnetic
anomaly chamber.
### Negative / risks
- **Wrong modality risk**: per `14.md`, NV-diamond at COTS price points
is 12 orders of magnitude worse than OPM in the biomagnetic band.
Anyone using nvsim as a stand-in for biomagnetic sensing will get
optimistic noise-floor numbers relative to what the same money buys
in QuSpin OPMs. Mitigated by the Wolf 2015 sanity-floor test and
the README's explicit "if you need fT-floor sensitivity, this is
the wrong starting point" caveat.
- **Conjectural propagation defaults**: drywall / brick / dry-concrete
loss values are conjectural; no systematic primary source exists for
residential-wall magnetic-field penetration loss at RuView geometry.
Flagged in code and in `15.md` §2.2; the `HEAVY_ATTENUATION` flag
surfaces this to downstream consumers.
- **No pulsed-protocol simulation**: Rabi nutation, Hahn echo, dynamical
decoupling are out of scope. If a use case needs them, the Lindblad
extension lives in **ADR-090** (Proposed, conditional).
- **Maintenance debt**: 1,800+ LoC of crystallographically-correct
physics code is non-trivial to maintain. Mitigated by the
Barry-2020-anchored test suite — drift in the constants surfaces
as a test failure within ~ms.
### Neutral
- ESP32-S3 firmware is **untouched** by this work — `nvsim` is host-side
only. Existing firmware tags (`v0.6.2-esp32`) continue to ship
unchanged.
- The crate uses workspace-pinned dependencies (`ndarray`, `serde`,
`thiserror`, `rand`, `rand_chacha`, `sha2`); no new top-level
dependencies added.
- ADR-086 (edge novelty gate, firmware track) is independent of this
ADR — its `0xC51A_6E70` `MagFrame` magic is distinct from ADR-018's
CSI magic and ADR-084's sketch magic.
## Validation
Acceptance criteria measured per the implementation plan §5:
| Criterion | Floor | Measured | Verdict |
|---|---|---|---|
| Same `(scene, seed)` → byte-identical SHA-256 witness | required | `determinism_same_seed_byte_identical_witness` test passes | ✓ |
| Shot-noise-OFF reproduction of analytical BiotSavart | ≤ 0.1% RMS | `shot_noise_disabled_propagates_flag_and_yields_clean_signal` test asserts ≤ 1 ADC LSB (~305 pT, equivalent at relevant amplitudes) | ✓ |
| n=8-direction dipole field RMS error | ≤ 0.5% | Pass 2 acceptance gate test passes | ✓ |
| NV shot-noise floor at t = 1 s vs Wolf 2015 | within 4× of 0.9 pT/√Hz | Pass 4 sanity-floor test passes; falls in window | ✓ |
| Pipeline throughput ≥ 1 kHz on Cortex-A53 | ≥ 1 kHz | _pending_ — Pass 6 criterion bench | _track_ |
| Lockin SNR for 1 nT @ 1 kHz vs 100 pT/√Hz floor | ≥ 10 in 1 s | _pending_ — Pass 6 integration test | _track_ |
Test count: **45 nvsim unit tests** passing (workspace 1,620 total, +45
from baseline 1,575), zero failures, zero ignores. ESP32-S3 on COM7
unaffected throughout.
## Implementation status
| Pass | Module | Commit | Tests |
|---|---|---|---|
| 1 | scaffold + scene + frame | `9c95bfac0` | 12 |
| 2 | source.rs (BiotSavart) | `a6ac08c66` | +7 |
| 3 | propagation.rs | `8c062fbaa` | +7 |
| 4 | sensor.rs (NV ensemble) | `177624174` | +8 |
| 5 | digitiser.rs + pipeline.rs | `436d383c9` | +11 |
| 6 | proof.rs + criterion bench | _pending_ | _≥ 5_ |
Branch: `feat/nvsim-pipeline-simulator`. README at
`v2/crates/nvsim/README.md` — plain-language audience-facing front page.
## Related
- **ADR-090** (Proposed, conditional) — full Hamiltonian / Lindblad
solver extension for pulsed protocols. Built only if a use case
needs Rabi nutation, Hahn echo, or dynamical-decoupling simulation.
- **ADR-018** — CSI binary frame magic (`0xC51F...`). nvsim's
`MAG_FRAME_MAGIC` (`0xC51A_6E70`) is deliberately distinct.
- **ADR-028** — ESP32 capability audit + witness verification. nvsim's
proof bundle pattern is the same shape as `archive/v1/data/proof/`.
- **ADR-066** — Swarm bridge to Cognitum Seed coordinator. If RuView
ever wants to publish nvsim outputs across the mesh, the
`MagFrame` shape is the wire format.
- **ADR-086** — Edge novelty gate. Independent firmware-track ADR;
shares the "Cluster-Pi side is host Rust" framing but not the
pipeline.
## Open questions
- **Should nvsim be published to crates.io as a standalone crate?** It
already has no internal RuView deps. The repo's MIT/Apache-2.0
license is permissive. The blocker is the dependency on
`wifi-densepose-core` going through workspace path — but nvsim
doesn't actually depend on it. If the answer is yes, this is a
trivial follow-up.
- **Does `nvsim::Pipeline` belong in the same crate as `nvsim::scene`?**
Some users want just the scene + source primitives without the
full pipeline. A future split into `nvsim-core` (scene/source/
propagation/sensor) and `nvsim-pipeline` (digitiser/pipeline/proof)
is possible if the API surface grows.
- **What's the right venue for the deterministic-proof bundle?**
Pass 6 will write `expected_witness.sha256` alongside the test
suite. Whether that lives in-tree or as a separately-tagged release
artifact is a Pass-6 design choice.
@@ -0,0 +1,218 @@
# ADR-090: nvsim — Full Hamiltonian / Lindblad Solver Extension
| Field | Value |
|----------------|-----------------------------------------------------------------------------------------|
| **Status** | Proposed — conditional. Only built if a pulsed-protocol use case emerges. Default-off, opt-in feature gate. |
| **Date** | 2026-04-26 |
| **Authors** | ruv |
| **Refines** | ADR-089 (nvsim simulator) |
| **Companion** | `docs/research/quantum-sensing/14-nv-diamond-sensor-simulator.md` §3.1, `docs/research/quantum-sensing/15-nvsim-implementation-plan.md` §6 |
## Context
[ADR-089](ADR-089-nvsim-nv-diamond-simulator.md)'s `nvsim::sensor` module
implements a **leading-order linear-readout proxy** for NV-ensemble
magnetometry per Barry et al. *Rev. Mod. Phys.* 92, 015004 (2020) §III.A.
That paper validates the proxy as adequate for ensemble magnetometers in
the **linear regime** — which is the CW-ODMR regime RuView's actual
use case operates in. The Wolf 2015 sanity-floor test confirms the
implementation matches published bulk-diamond results within 4×.
What the proxy does *not* model:
- **Pulsed protocols**: Rabi nutation, Hahn echo, CPMG / XY-N dynamical
decoupling sequences.
- **Microwave-power saturation**: line-broadening at high CW MW power.
- **Hyperfine structure**: ¹⁴N (I=1) and ¹⁵N (I=½) nuclear spin couplings
to the NV electronic spin.
- **Coherent control**: Ramsey-style phase-accumulation experiments,
spin-echo magnetometry.
For RuView's CW-ODMR ensemble use case (ferrous-anomaly detection,
metallic-object screening), none of these matter — Barry 2020 §III.A is
explicit that the linear-readout proxy is adequate. For *future* use cases
that involve pulsed protocols (e.g., AC-magnetometry via Hahn echo to push
sensitivity past the T₂* floor), they would matter.
This ADR documents that decision-tree explicitly: **the Lindblad solver is
not built unless and until a pulsed-protocol use case opens**.
## Decision
Defer the full Hamiltonian + Lindblad solver to a **conditional, opt-in
feature gate** named `lindblad` on the `nvsim` crate. Default-off so that
the existing fast linear-readout path stays the default and the build /
test budget is unaffected. The ADR is **Proposed** — actual implementation
happens only if a triggering use case meets the gate below.
### Trigger conditions for promoting to Accepted
This ADR transitions from Proposed → Accepted when **any one** of the
following is true:
1. A use case needs **AC magnetometry**: a Hahn-echo or CPMG / XY-N
dynamical-decoupling protocol where the answer cannot be approximated
by the linear proxy because T₂* is no longer the relevant timescale.
2. A use case needs **microwave-power saturation modelling**: the
simulator is asked to predict the ODMR contrast as a function of MW
drive amplitude, which the linear proxy does not capture.
3. A use case needs **hyperfine spectroscopy**: the simulator is asked to
reproduce the ¹⁴N or ¹⁵N hyperfine triplet visible in high-resolution
ODMR scans, which the linear proxy collapses.
4. A use case needs **pulsed quantum-sensing protocols** more broadly:
Ramsey, spin-echo magnetometry, double-quantum coherence, etc.
If none of those triggers, the linear proxy is sufficient and this ADR
remains Proposed indefinitely.
### Why the deferral is the right call today
- **Adequacy validated by primary source.** Barry 2020 §III.A explicitly
validates the linear-readout proxy for ensemble magnetometers in the
linear regime. nvsim's existing `sensor.rs` matches Wolf 2015 within 4×.
We're not under-modelling — we're correctly-modelling.
- **37 days of focused work.** The implementation cost is non-trivial:
density-matrix RK4 integrator over a 3-level (or 9-level with hyperfine)
Hilbert space, careful sign / basis / normalisation conventions,
validation against a published QuTiP reference script. The downside of
building it pre-emptively is paying that cost without a downstream
consumer.
- **No current downstream consumer.** RuView's MAT (Mass Casualty
Assessment) consumer needs CW-ODMR ferrous anomaly detection, not
pulsed protocols. ADR-066 swarm-bridge (proposed) is similarly
CW-amplitude-only.
- **Not blocked.** When a triggering use case appears, the work is well-
scoped and the build path is documented (see Implementation below).
Deferral is reversible at any time.
### Why we don't just delegate to QuTiP
QuTiP is the obvious off-the-shelf option and is what `15.md` §6 originally
proposed deferring to. Two reasons we'd prefer an in-tree Rust
implementation if we ever build it:
1. **Determinism**. QuTiP runs in Python with potentially non-deterministic
ODE solver scheduling depending on threading, BLAS backend, and
NumPy version. nvsim's whole-pipeline determinism — same seed →
byte-identical witness — would be much harder to maintain across the
Python boundary.
2. **CI integration**. The Rust workspace's `cargo test --workspace
--no-default-features` already runs in seconds. Adding QuTiP would
pull a Python dependency into CI and slow the gate.
If a triggering use case opens but the cost-benefit doesn't justify in-
tree implementation, an external QuTiP harness with cached fixture
outputs is a viable fallback.
## Consequences
### Positive
- **No premature engineering.** 37 days of work not spent on a feature
with no consumer; that time goes to Pass 6 of nvsim and to ADR-066
swarm-bridge work that has actual downstream demand.
- **Honest scope.** ADR-089's README and the `nvsim::sensor` module
docstrings already say what's *not* modelled. ADR-090 is the
formal accountability for that boundary.
- **Reversible.** All four trigger conditions are observable; if any
fires, the ADR moves to Accepted and the work begins.
### Negative / risks
- **Risk of premature commitment if triggers fire.** If pulsed-protocol
use cases emerge late in the project (e.g., a contributor wants
Hahn-echo magnetometry for academic-paper reproducibility), the 37-day
cost lands at an inconvenient time. Mitigated by the work being
well-scoped and bench-bounded — see Implementation.
- **Documentation debt.** Every nvsim contributor should be aware that
pulsed protocols are out of scope. This ADR is the canonical reference
but its Proposed status means contributors might not read it. Mitigated
by the README's explicit "out of scope" section linking to this ADR.
### Neutral
- The existing linear-readout proxy is already feature-flag-free and
always-on; no API changes when ADR-090 lands. The Lindblad path is
additive.
## Implementation (when triggered)
If this ADR transitions to Accepted, the implementation is:
1. **Add `lindblad` feature to `nvsim/Cargo.toml`** — opt-in, default-off.
Pulls `ndarray` (already a dep) + `num-complex` (already a workspace
dep) for complex-matrix algebra.
2. **`src/lindblad.rs`** — new module, ≤ 600 LoC:
- `NvHamiltonian` — D·Sz² + γ_e·B·S + E·(Sx²−Sy²) on the m_s ∈ {1, 0, +1}
ground-state basis. Optional ¹⁴N or ¹⁵N hyperfine extension.
- `LindbladOps` — collapse operators for T₁ (population relaxation,
L_∓ between m_s levels) and T₂ (pure dephasing on m_s = ±1).
- `LindbladIntegrator::rk4_step(rho, dt)` — fourth-order Runge-Kutta
time-step on the density matrix.
- `Pulse` enum — supports CW, square, Gaussian-shaped MW pulses.
3. **`src/lindblad_protocols.rs`** — new module, ≤ 400 LoC:
- `Rabi::run` — fixed MW amplitude sweep, returns nutation curve.
- `HahnEcho::run` — π/2 — τ — π — τ — π/2 detection sequence.
- `Cpmg::run` — repeated π pulses for dynamical decoupling.
4. **Validation suite** — mandatory before merging:
- Reproduce a published QuTiP reference Rabi curve (e.g., from a
Doherty 2013 supplementary script) within 1% per-bin error.
- Reproduce a Hahn-echo decay against published T₂ measurement
within 5%.
- Reproduce hyperfine triplet splitting against measured A_∥ /
A_⊥ values from Doherty 2013 §3.4.
5. **Benchmarks** — criterion target: ≥ 100 Hz simulated Rabi-curve
evaluation on x86_64 (10× slower than the linear proxy is acceptable).
6. **README + ADR update** — promote ADR-089's README "not yet shipped"
section to include the new pulsed-protocol capabilities, and move
this ADR to Accepted with the merge commit.
Estimated effort: **37 days of focused work**, dominated by validation
not implementation.
## Validation (Proposed → Accepted)
This ADR is **Proposed** until any of the four trigger conditions in §"
Trigger conditions" fires. When that happens:
1. Open a follow-up issue stating which trigger fired and which use case
needs Lindblad.
2. The implementation §16 above defines the build.
3. Acceptance moves on the validation-suite criteria in step 4 (1% Rabi
curve, 5% Hahn-echo decay, hyperfine triplet match).
4. Merge promotes this ADR Proposed → Accepted with the new measured
numbers.
## Open questions
- **Which Rust complex-matrix library is the right substrate?** Three
candidates: (a) `ndarray` + `num-complex` (already workspace deps; lowest
surface area but unergonomic for matrix algebra); (b) `nalgebra` with
`ComplexField` trait (richer matrix algebra, +1 workspace dep);
(c) `faer` (more recent, focused on numerics performance, +1 workspace
dep). Decide at trigger time based on which best supports the Lindblad
RK4 step ergonomically and which version-pinning matches the workspace
conservatism.
- **Is hyperfine modelling in v1 or v2?** A pure 3-level NV ground-state
Hamiltonian is sufficient for Rabi and Hahn echo. ¹⁴N hyperfine triplet
needs 9-level Hilbert space (3 m_s × 3 m_I), 9× more matrix work. v1
could ship with hyperfine off behind a sub-feature; v2 enables it.
- **Should the Lindblad solver back-validate the linear proxy?** Once
Lindblad exists, it could be used to measure the proxy's error
envelope across operating points and tighten or loosen the existing
Wolf 2015 4× sanity floor accordingly. This is the strongest scientific
reason to build Lindblad even without an immediate use case — but
"validate the proxy" is itself the use case, so still meets trigger #4.
## Related
- **ADR-089** — nvsim NV-diamond simulator. The crate this extension
attaches to.
- **ADR-018** — CSI binary frame format. Lindblad output would still flow
through the existing `MagFrame` (`0xC51A_6E70`) shape; pulsed-protocol
results add to the per-frame metadata, not a new frame format.
- **ADR-028** — ESP32 capability audit. Lindblad is host-side only; ESP32
firmware untouched.
- **ADR-066** — Swarm bridge. If the simulator is used for swarm-routed
AC-magnetometry experiments, this ADR's outputs flow through that
channel.
@@ -0,0 +1,770 @@
# ADR-091: Stand-off Radar Tier Research — 77 GHz High-Power and 100200 GHz Coherent Sub-THz
| Field | Value |
|----------------|-----------------------------------------------------------------------------------------|
| **Status** | Proposed — Research only. No production hardware integration. Decision deferred pending sub-$1k COTS sub-THz transceiver availability and clear non-export-controlled use case. |
| **Date** | 2026-04-26 |
| **Authors** | ruv |
| **Refines** | ADR-021 (60 GHz / mmWave vital-signs pipeline) |
| **Companion** | `docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md` §6.3, ADR-029 (RuvSense multistatic), ADR-089 (nvsim simulator), ADR-090 (Lindblad extension) |
## 1. Context
### 1.1 Why this question now
On Good Friday 3 April 2026 the press reported a CIA system called "Ghost Murmur"
— a Lockheed Skunk Works NV-diamond + AI sensor reportedly used in the recovery
of an F-15E pilot in southern Iran. President Trump publicly suggested detection
ranges in the "tens of miles" against a single human heartbeat. RuView shipped
a research spec (`16-ghost-murmur-ruview-spec.md`) which (a) reality-checked the
press claims against published physics, (b) mapped the *honestly-scoped* version
onto the existing RuView three-tier mesh, and (c) explicitly deferred one
modality — high-power and sub-THz coherent radar — as out of scope. From §6.3
of that spec:
> 77 GHz automotive radars at higher power and 100200 GHz coherent sub-THz
> radars **can** resolve cardiac micro-Doppler at 50500 m in clear LOS. These
> are not COTS at the $15 price point and are not in the RuView stack today.
> They are also subject to ITAR / export-control review and **explicitly out of
> scope** for this open-source project.
That sentence is the trigger for this ADR. We need a written, citable record of
*why* the decision is "out of scope today", what would change the decision,
and — crucially — what shape any future research entry into this band would
take, given that even the research itself touches dual-use territory.
### 1.2 What gap a higher-frequency / higher-power tier would close
RuView's existing modality coverage (per the CLAUDE.md crate table):
| Modality | Crate / ADR | Honest LOS range for HR | Through-wall HR |
|---|---|---|---|
| WiFi CSI 2.4/5/6 GHz | `wifi-densepose-signal`, ADR-014, ADR-029 | 13 m (presence to 30 m) | 1 wall, weak |
| 60 GHz FMCW (MR60BHA2) | `wifi-densepose-vitals`, ADR-021 | 110 m | drywall only |
| NV-diamond magnetometer | `nvsim` (simulator), ADR-089/090 | <1 m (gradiometric, shielded) | n/a |
The ceiling of this stack on cardiac micro-Doppler in clear line-of-sight is
**~10 m** (60 GHz tier, ADR-021 / spec §6.1). A higher-frequency / higher-power
tier would, in principle, close the 10500 m gap that the published radar
literature has already explored. The two candidate bands:
1. **7781 GHz at higher than typical commercial EIRP** — the same band as
automotive radar, where the FCC ceiling is 50 dBm average / 55 dBm peak EIRP
under 47 CFR §95.M, and where published academic work has measured HR at
ranges beyond the typical 13 m used by COTS automotive sensors.
2. **100200 GHz coherent sub-THz radar** — where λ ≈ 1.53 mm gives
sub-millimetre chest-wall displacement resolution and where atmospheric
transmission windows at 94 GHz, 140 GHz, and 220 GHz make stand-off sensing
physically possible (with caveats on humidity, antenna gain, and integration
time).
This ADR examines both bands — the SOTA, the COTS reality, the regulatory
envelope, the physics ceiling, the export-control posture, and the open-source
ethics — and lands at a build / research / skip recommendation per row.
## 2. SOTA: 7781 GHz automotive radar at higher power
### 2.1 Current COTS chips at the $20$200 price point
The 7681 GHz band is now densely populated with single-chip CMOS / SiGe
transceivers. Representative parts:
| Chip | Vendor | Tx / Rx | IF BW | Notes |
|---|---|---|---|---|
| AWR1843 | Texas Instruments | 3 Tx / 4 Rx | up to ~10 MHz IF | Single-chip 7681 GHz with on-die DSP, MCU, radar accelerator. Long-range automotive ACC, AEB. ([TI AWR1843](https://www.ti.com/product/AWR1843)) |
| AWR2243 | Texas Instruments | 3 Tx / 4 Rx | up to ~20 MHz IF | Cascadable for higher angular resolution (up to 12 Tx / 16 Rx with multi-chip cascade). ([TI AWR2243](https://www.ti.com/product/AWR2243)) |
| BGT60 family | Infineon | 13 Tx / 14 Rx | Several MHz IF | 60 GHz primarily; BGT24 family at 24 GHz. Smaller, lower power, gesture / presence focus. |
| TEF82xx | NXP | up to 4 Tx / 4 Rx | several MHz IF | Automotive-grade 7681 GHz. |
COTS evaluation boards (TI AWR1843BOOST, AWR2243 cascade kits) sit in the
$300$3,000 range; single-board production costs trend toward $20$100 at
volume. None of these chips is, by itself, export-controlled at typical
configurations — the band is allocated for civilian automotive use under FCC
Part 95 Subpart M and ETSI EN 301 091 in Europe.
**EIRP envelope**: 47 CFR §95.M (and the historical §15.253 it replaced) caps
the 7681 GHz band at **50 dBm average / 55 dBm peak EIRP** measured in 1 MHz
RBW ([Federal Register notice 2017](https://www.federalregister.gov/documents/2017/09/20/2017-18463/permitting-radar-services-in-the-76-81-ghz-band),
[eCFR 47 CFR Part 95 Subpart M](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-D/part-95/subpart-M)).
That is roughly 100 W EIRP average, 316 W peak. COTS automotive radars
typically operate well below this — single-digit dBm transmit power is
multiplied by ~2530 dBi antenna gain to land at 3340 dBm EIRP.
### 2.2 What "higher power" actually means in regulatory terms
Three regulatory paths exist for an open-source project that wants to push
beyond typical commercial deployment power:
1. **Stay inside FCC Part 95 §95.M caps (50 dBm avg / 55 dBm peak EIRP)**
licence-by-rule, no application, no individual approval. The headroom from
typical automotive EIRP (~3340 dBm) to the cap (50 dBm avg) is real:
~10 dB of additional EIRP is available *without changing licence class*,
purely by using a higher-gain dish or higher Tx power within the existing
chip. This is the upper bound of "stand-off radar that is still part-95
legal".
2. **FCC Part 5 experimental licence** — needed for transmit power, antenna
gain, or duty-cycle that exceeds §95.M. Application-based, time-bounded,
non-renewable beyond limits. Typical academic radar ranges (e.g. the
long-range cardiac measurements in §2.3 below) operate under this regime.
3. **No US authorisation at all** — only legal as receive-only, or as a
simulator. Any unlicensed transmission above §95.M at 7681 GHz is a
prohibited emission under 47 CFR §15.5 / §95.335.
For an *open-source mesh node* shipping to anonymous users worldwide, only
path (1) is defensible. Anything that requires an individual experimental
licence cannot be "ship a binary and let people flash it".
### 2.3 Published cardiac micro-Doppler at 77 GHz beyond 5 m
The 77 GHz cardiac literature is dominated by short-range work (0.32 m), e.g.:
- Chen et al. (2024). "Contactless and short-range vital signs detection with
doppler radar millimetre-wave (7681 GHz) sensing firmware." *Healthcare
Technology Letters*. ([PMC11665778](https://pmc.ncbi.nlm.nih.gov/articles/PMC11665778/),
[Wiley HTL 2024](https://ietresearch.onlinelibrary.wiley.com/doi/full/10.1049/htl2.12075))
— TI IWR1443BOOST at 0.301.20 m, suggested 0.6 m.
- Wang et al. (2020). "Remote Monitoring of Human Vital Signs Based on 77-GHz
mm-Wave FMCW Radar." *Sensors* 20, 2999.
([PMC7285495](https://pmc.ncbi.nlm.nih.gov/articles/PMC7285495/),
[MDPI Sensors 2020](https://www.mdpi.com/1424-8220/20/10/2999)) — typically
short-range bench measurements.
- Liu et al. (2022). "Real-Time Heart Rate Detection Method Based on 77 GHz
FMCW Radar." *Micromachines* 13, 1960.
([PMC9693980](https://pmc.ncbi.nlm.nih.gov/articles/PMC9693980/),
[MDPI](https://www.mdpi.com/2072-666X/13/11/1960)) — 2.925% mean HR error,
short-range.
- Iyer et al. (2022). "mm-Wave Radar-Based Vital Signs Monitoring and
Arrhythmia Detection Using Machine Learning." *Sensors*.
([PMC9104941](https://pmc.ncbi.nlm.nih.gov/articles/PMC9104941/))
The most cited *long-range* radar cardiac measurement is at 24 GHz, not 77 GHz:
- **Massagram, W., Lubecke, V. M., Høst-Madsen, A., Boric-Lubecke, O. (2013).
"Parametric Study of Antennas for Long Range Doppler Radar Heart Rate
Detection."** *IEEE EMBC* / republished in *PMC*.
([PMC4900816](https://pmc.ncbi.nlm.nih.gov/articles/PMC4900816/),
[PubMed 23366747](https://pubmed.ncbi.nlm.nih.gov/23366747/)) —
measured human HR at distances of **1, 3, 6, 9, 12, 15, 18, 21 m** and
respiration to **69 m** with a PA24-16 antenna at **24 GHz CW Doppler**.
This is the ceiling reference for "what's achievable with serious antenna
gain in clear LOS, low band, with subject cued and stationary".
We could not find an equivalent peer-reviewed cardiac measurement at 77 GHz
*beyond ~5 m* with a verifiable antenna gain × power × integration-time
budget. The work that exists at 77 GHz is overwhelmingly bench-scale (≤ 2 m).
This is itself informative: it suggests that *the open published frontier at
77 GHz beyond 5 m is sparse*, not because it's impossible, but because the
research community working at automotive bands has been focused on automotive
problems (collision avoidance, in-cabin occupancy) where 5 m suffices, and
because higher-range cardiac work has historically used 24 GHz where the
antenna size for a given gain is more practical.
### 2.4 Detection range as a function of antenna gain × power × integration time
The radar equation for chest-wall displacement detection scales roughly as:
```
SNR ∝ (P_t · G_t · G_r · σ_chest) / (R^4 · k T B · NF) · √(t_int / T_coh)
```
where σ_chest ≈ 10⁻³–10⁻² m² for the cardiac scatterer at 77 GHz, NF ≈ 1015 dB
on COTS chips, and integration time t_int is bounded by T_coh ≈ 0.51 s
(physiological coherence — the heart period itself).
Doubling range requires 12 dB of system gain (4-th power dependence on R,
two-way). At the part-95 §95.M ceiling (50 dBm avg EIRP) and a generous 30 dB
antenna gain (a ~30 cm dish at 77 GHz), the addressable HR detection range in
clear LOS is roughly **1530 m for a stationary cued subject**, dropping to
310 m for an uncued subject in light clutter. Pushing to 100 m+ in an open
field would require either (a) a much larger antenna (60+ cm dish), (b)
out-of-band EIRP beyond §95.M (experimental licence territory), or (c) much
longer integration (incompatible with cardiac coherence times).
The 2013 Massagram paper achieves 21 m at 24 GHz with a high-gain antenna
under tightly controlled conditions. Pushing the same setup to 77 GHz with
the same antenna *aperture* would actually help (smaller beamwidth, same
free-space path loss), but the chest-wall RCS at 77 GHz is comparable, and
clutter / multipath are much harsher. We have **no public reference** for a
77 GHz cardiac measurement at 21 m that we could find with the same rigour.
### 2.5 Cost ceiling for an open-source mesh node
An open-source mesh node spec implies "ships in a kit, does not require
individual licensing, fits the existing PoE / mini-PC edge model". That
implies:
- Single-chip transceiver at $20$100 BOM.
- Antenna assembly at $50$200 (high-gain dish or printed array).
- Mini-PC or Pi 5 host at $80.
- Total under $500 to be plausible.
The chip cost is already met by COTS. The antenna and host are met. The
bottleneck is *not* hardware cost — it is regulatory exposure, dual-use
ethics, and the fact that the addressable range at part-95 ceilings (1530 m)
is *only marginally beyond* what the existing 60 GHz tier already does for
$15. The marginal *technical* benefit of jumping to 77 GHz at the part-95
ceiling, for a civilian opt-in mesh, does not clear the marginal *governance*
cost.
## 3. SOTA: 100200 GHz coherent sub-THz radar
### 3.1 Why sub-THz
At 140 GHz, λ ≈ 2.14 mm. A coherent radar with this wavelength can resolve
chest-wall displacement at the **sub-millimetre** level by direct phase
tracking, which makes the cardiac micro-Doppler signal-to-clutter ratio
fundamentally better than at 60 or 77 GHz for the same integration time.
Atmospheric *windows* at 94 GHz, 140 GHz, and 220 GHz — between the strong
oxygen absorption peaks at 60 GHz and 119 GHz and the water vapour peaks at
22, 183, and 325 GHz — make stand-off operation physically possible per
**ITU-R Recommendation P.676** ([ITU-R P.676-11](https://www.itu.int/dms_pubrec/itu-r/rec/p/R-REC-P.676-11-201609-I!!PDF-E.pdf),
[ITU-R P.676-9](https://www.itu.int/dms_pubrec/itu-r/rec/p/R-REC-P.676-9-201202-S!!PDF-E.pdf)).
### 3.2 Atmospheric attenuation table (clear-air, ITU-R P.676)
Order-of-magnitude values for one-way attenuation through standard atmosphere
at sea level, taken from ITU-R P.676-11 Annex 1 / 2 figures (approximate
values; consult the recommendation for precise numbers at any (T, P, ρ)):
| Frequency | Dry air, dB/km | 7.5 g/m³ humid, dB/km | Notes |
|---|---|---|---|
| 60 GHz | ~14 | ~14.5 | O₂ absorption peak — terrible for stand-off |
| 77 GHz | ~0.4 | ~0.5 | Allocated for automotive radar |
| 94 GHz | ~0.4 | ~0.7 | First major window above 60 GHz |
| 119 GHz | ~2.5 | ~3 | O₂ subsidiary peak |
| 140 GHz | ~0.5 | ~1.5 | Second major window |
| 183 GHz | ~30+ | ~100+ | H₂O peak — unusable for outdoor stand-off |
| 220 GHz | ~2 | ~5 | Third window |
| 325 GHz | ~10+ | ~50+ | H₂O peak |
| 380 GHz | ~3 | ~20 | Imaging-band window, very humidity-sensitive |
For a 100 m one-way clear-LOS link at 140 GHz in 7.5 g/m³ humidity, atmospheric
attenuation alone is ~0.15 dB — negligible compared to free-space path loss
(~115 dB at 100 m) and target RCS. The atmosphere is *not* the limiting factor
for sub-THz cardiac sensing inside ~100 m. **Beyond ~1 km in humid conditions,
atmospheric absorption dominates** and the budget breaks down quickly,
especially at 220 GHz and above.
### 3.3 COTS chipsets and academic platforms
The sub-THz commercial landscape in 2026 is sparse and expensive:
- **Analog Devices HMC8108** — 7681 GHz transceiver. Not sub-THz; named here
only to anchor "the most COTS-friendly mmWave part Analog Devices ships".
- **Virginia Diodes WR-* multipliers and mixers** — the dominant lab-grade
source for 140500 GHz work. Module prices are $5,000$50,000 each;
building a coherent transceiver typically requires $30,000$150,000 of VDI
hardware plus a stable phase reference and an external RF source.
- **Wasa Millimeter Wave imagers** — passive imagers around 90 / 220 / 380 GHz.
Receive-only.
- **imec 140 GHz FMCW transceiver in 28 nm CMOS** — reported at IEEE ISSCC and
in *Microwave Journal* (2019), centred at 145 GHz with 13 GHz RF bandwidth
giving 11 mm range resolution, on-chip antennas, integrated Tx / Rx in 28 nm
bulk CMOS. ([Microwave Journal 2019](https://www.microwavejournal.com/articles/32446-integrated-140-ghz-fmcw-radar-for-vital-sign-monitoring-and-gesture-recognition),
[imec magazine May 2019](https://www.imec-int.com/en/imec-magazine/imec-magazine-may-2019/a-compact-140ghz-radar-chip-for-detecting-small-movements-such-as-heartbeats))
This is the most COTS-relevant sub-THz cardiac chip published to date,
but it is **not** a buyable part — it is a research demo.
- **Academic platforms** at Tampere University, FAU Erlangen-Nürnberg, Bell Labs
/ Nokia, MIT Lincoln Lab, and the various US NSF / DARPA-funded sub-THz
programmes have produced sub-THz radars in the 100300 GHz band. None of
these is a ship-it part.
### 3.4 Coherent vs. incoherent
A *coherent* sub-THz radar maintains phase reference between Tx and Rx (and
ideally across multiple Tx / Rx channels for MIMO or multistatic operation).
Coherent processing buys:
- **Matched-filter SNR scaling**: SNR improves linearly with integration
time t (vs. √t for incoherent), bounded by the cardiac coherence
time T_coh.
- **Phase-based displacement extraction**: chest-wall displacement at the
micrometre level becomes directly observable as Δφ = 4π·Δd / λ.
- **MIMO / multistatic phase coherence**: multiple Tx / Rx phase-coherent
channels enable beamforming gain that scales as N_Tx × N_Rx instead of
√(N_Tx × N_Rx).
It costs:
- **Sub-picosecond clock distribution** between channels at sub-THz frequencies
(a 1 ps clock skew at 140 GHz is 50° of phase error).
- **Phase-locked LO distribution** — the LO must be coherent across the
array; this is non-trivial at 140 GHz (typical solution: distribute a low
GHz reference and multiply locally, with cm-precision cable matching).
- **Calibration burden** — phase-coherent arrays need per-channel calibration
drift correction.
For a single-aperture monostatic radar (one Tx, one Rx, one chip), coherence
is nearly free (the LO is shared on-die). For a *mesh* of coherent sub-THz
nodes, the engineering cost is significant — and would require RuView to
develop sub-ns mesh clock-synchronisation it does not have today.
### 3.5 Published cardiac micro-Doppler at sub-THz
The published peer-reviewed cardiac literature at 100300 GHz is sparse but
not empty:
- **Mostafanezhad & Boric-Lubecke (2014).** "Benefits of coherent low-IF for
vital signs monitoring." *IEEE Microw. Wireless Compon. Lett.* 24. — anchor
for *coherent* CW vital-signs radar; not specifically sub-THz, but
establishes the coherent-IF advantage.
- **imec (2019) — 140 GHz FMCW transceiver demonstration.** Reported real-time
measurement of micro-skin motion reflecting respiration and heartbeat at
short range using an integrated 28 nm CMOS transceiver with on-chip antennas.
Cited above; engineering demo, not a published systematic range study.
([Microwave Journal 2019](https://www.microwavejournal.com/articles/32446-integrated-140-ghz-fmcw-radar-for-vital-sign-monitoring-and-gesture-recognition))
- **Yamagishi et al. (2022).** "A new principle of pulse detection based on
terahertz wave plethysmography." *Scientific Reports* 12, 2022.
([Nature SREP](https://www.nature.com/articles/s41598-022-09801-w)) —
THz-band plethysmography demonstrator, contactless pulse detection at very
short range using THz transmission/reflection through skin. Not a stand-off
radar paper, but the only widely-cited THz-cardiac primary source.
- **Zhang et al. (2021).** "Non-Contact Monitoring of Human Vital Signs Using
FMCW Millimeter Wave Radar in the 120 GHz Band." *Sensors* 21.
([PMC8070581](https://pmc.ncbi.nlm.nih.gov/articles/PMC8070581/)) — 120 GHz
band, FMCW, short-range cardiac extraction.
**Honest assessment**: published primary work on cardiac micro-Doppler at
*beyond a few meters* in the 100300 GHz band is limited. The
imec / EU-funded demonstrators have shown that the chip exists; the systematic
range studies that exist for 24 GHz (Massagram 2013) and 6077 GHz
(Adib / Wang / Liu) do not yet have published sub-THz analogues. Some of this
work may exist in the classified or US-Government / EU defence-funded
literature; it is **not** in the open record at the level of detail required
for a build decision.
## 4. Physics ceiling for RuView's heartbeat-mesh use case
### 4.1 Cardiac signal vs. distance, multi-band comparison
For a stationary, cued, line-of-sight subject with chest-wall displacement
~0.2 mm at the heart fundamental and ~5 mm at the breathing fundamental,
order-of-magnitude HR-detection range estimates at three bands (compiled from
the radar equation, Massagram 2013, ITU-R P.676, and standard chest-RCS
estimates):
| Band | λ | Required Δφ for HR | Free-space loss @ 30 m | Atm loss @ 30 m | Estimated HR range (cued LOS, COTS Tx + 30 dBi antenna, part-95) |
|---|---|---|---|---|---|
| 24 GHz CW | 12.5 mm | 0.36° | 89 dB | <0.01 dB | 21 m measured (Massagram 2013) |
| 60 GHz FMCW | 5.0 mm | 0.9° | 97 dB | 0.4 dB | 510 m (ADR-021 / spec §6.1) |
| 77 GHz FMCW | 3.9 mm | 1.2° | 99 dB | 0.01 dB | ~1530 m (estimated, no rigorous public ref beyond 5 m) |
| 140 GHz FMCW | 2.1 mm | 2.2° | 105 dB | 0.04 dB | ~30100 m (estimated, sparse open lit) |
| 220 GHz FMCW | 1.4 mm | 3.3° | 109 dB | 0.15 dB | ~30100 m (estimated, sparse open lit, humidity-sensitive) |
The phase-displacement resolution *improves* with frequency (Δφ for the same
displacement scales as 1/λ), but the link budget *degrades* (R⁻⁴ in
two-way path loss, plus atmospheric absorption, plus higher noise figure on
sub-THz LNAs). The two effects partially cancel; the net result is that
**every doubling in frequency above 60 GHz buys roughly a factor of 24× in
plausible HR range when antenna aperture is held constant** — but only if
the system noise figure and Tx power can be maintained at levels comparable
to the lower-band part. Sub-THz CMOS NF is typically 10 dB worse than 77 GHz
CMOS, which eats much of the apparent gain.
### 4.2 Two-way path loss + atmospheric absorption
| Range | 77 GHz total loss | 140 GHz total loss | 220 GHz total loss |
|---|---|---|---|
| 1 m | 70 dB + 0 | 76 dB + 0 | 80 dB + 0 |
| 10 m | 90 dB + 0.01 | 96 dB + 0.03 | 100 dB + 0.1 |
| 100 m | 110 dB + 0.1 | 116 dB + 0.3 | 120 dB + 1 |
| 1 km | 130 dB + 1 | 136 dB + 3 | 140 dB + 10 |
| 10 km | 150 dB + 10 | 156 dB + 30 | 160 dB + 100 |
| 65 km (40 mi) | 168 dB + 65 | 174 dB + 200+ | 178 dB + impossible |
**Observations**:
- At 1 km, 220 GHz loses 9 dB more to atmosphere than 77 GHz; at 10 km it
loses 90 dB more. Sub-THz is fundamentally a sub-1-km modality in humid air.
- At 65 km (the "40 miles" in the press), atmospheric absorption alone makes
220 GHz cardiac detection physically impossible at any plausible Tx power.
140 GHz needs 200+ dB of antenna gain on each end to close the link in
humid air — far beyond any deployable antenna.
- **77 GHz is the only band where 1 km cardiac sensing is physically plausible
in the open air.** It is also the band that is closest to civilian COTS.
### 4.3 Required antenna gain × power × integration time
Holding integration time at 0.5 s (half a cardiac cycle, the rough coherence
limit), and assuming a 10 dB SNR target at 0.2 mm displacement, the required
EIRP × antenna-gain product to detect HR at various ranges in clear LOS at
77 GHz:
| Range | Required EIRP × G_r (one-way) | Achievable under FCC §95.M? |
|---|---|---|
| 1 m | 25 dBm + 20 dBi | Yes (commercial COTS) |
| 10 m | 45 dBm + 30 dBi | Yes (high-end COTS, 30 cm dish) |
| 30 m | 55 dBm + 35 dBi | Marginal — at the §95.M peak ceiling |
| 100 m | 70 dBm + 45 dBi | No — above §95.M, experimental-licence territory |
| 500 m | 90 dBm + 55 dBi | No — military / experimental only |
| 1 km | 100 dBm + 60 dBi | No — military only |
| 10+ km | beyond physical antenna realisability for civilian use | No |
**Bottom line**: 30 m is the honest ceiling for cardiac sensing inside FCC
§95.M power limits with a 30 cm dish at 77 GHz. Anything beyond ~30 m is
either experimental-licence territory or military.
### 4.4 Fold-over with the Ghost Murmur "tens of miles" claim
The press claim of HR detection at "40 miles" (65 km) corresponds to a one-way
path loss at 77 GHz of roughly 168 dB (free space) plus ~65 dB of atmospheric
absorption (humid). Closing this link to detect a 0.2 mm chest-wall
displacement would require:
- **Required EIRP**: roughly 200 dBm (10²⁰ W) in the simplest analysis. For
context, the entire global average solar flux is ~1.4 kW/m². A 65 km
radar would need to deliver more transmit power, focused onto a single
human chest, than the sun delivers to that chest by daylight.
- **Required antenna**: even with 100 dB of combined two-way antenna gain
(a 6 m dish at 77 GHz), the EIRP requirement is unphysical.
- **Required atmospheric conditions**: dry, stable, no rain, no fog, no
intervening terrain.
The honest reading: **HR detection at "tens of miles" against a single
heartbeat is not consistent with any physically realisable open-air radar
system at any band the laws of physics allow**. The claim either refers to
*cued* detection (i.e., a survival beacon or IR thermal already pinpointed
the target, the radar is just confirming "alive"), or it is press-release
hyperbole. RuView is not in a position to either confirm or contest the
operational reality; we are in a position to say that the *modality alone*
"detect a heartbeat at 40 miles with a radar" — is not what closed the loop.
This is consistent with the Ghost Murmur spec's analysis (§4 of doc 16) and
with `nvsim`'s magnetic-field falloff calculations (1/r³ — even more brutal
than radar's 1/r⁴).
## 5. Regulatory + ethics
### 5.1 FCC envelope summary
| Use | FCC path | Practical for open source? |
|---|---|---|
| 60 GHz unlicensed (existing tier) | Part 15.255 (5771 GHz) | Yes — current tier |
| 7681 GHz at COTS automotive EIRP | Part 95 Subpart M (50/55 dBm) | Yes — research-allowed |
| 7681 GHz pushing toward §95.M ceiling | Part 95 Subpart M | Yes — single-installation |
| 7681 GHz beyond §95.M | Part 5 experimental licence | **No** for shipping firmware |
| 90300 GHz coherent radar | Mostly experimental-only | **No** for shipping firmware |
| 300+ GHz transmitters | Almost all unallocated for civilian active use | **No** for shipping firmware |
For an *open-source civilian project*, only the unlicensed and part-95
licensed-by-rule categories are defensible. The moment a node would need an
individual experimental-licence application to operate legally, it cannot be
"flash and ship".
### 5.2 ITAR / EAR posture
- **ECCN 6A008** controls radar systems and components under the EAR
([BIS Commerce Control List Cat. 6](https://www.bis.doc.gov/index.php/documents/regulations-docs/2340-ccl9-4/file)).
The general radar control sub-paragraph 6A008.e covers "radar systems,
having any of the following characteristics" — including high power,
specific frequency / coherence properties, and certain processing
capabilities. The exact thresholds change from revision to revision; the
current authoritative source is the [BIS Interactive Commerce Control
List](https://www.bis.gov/regulations/ear/interactive-commerce-control-list).
- **USML Category XI(c)** (ITAR) covers radar that is specifically designed
or modified for military application. Sub-THz coherent radar with the
combination of frequency, coherence, and antenna gain that would matter
for stand-off cardiac sensing tends to fall in or near this category.
- **EAR99 / no-licence-required** thresholds for low-power 6077 GHz
automotive radar are clear. Sub-THz coherent radar above certain
thresholds (ECCN 6A008) requires an export licence for many destinations.
Some open-source firmware that *implements* such a radar may be subject
to "publicly available" exemptions; some may not.
- **Open-source publication.** EAR §734.7 / §734.8 ("publicly available
information") exempts most code that has been or will be published openly.
However, this exemption has limits — particularly for "specially designed"
technology supporting controlled commodities, and for encryption / certain
munitions categories. The line for radar firmware is not fully clear, and
the safe path for an open-source project is: **do not publish firmware
whose primary purpose is to push a controlled-radar configuration**.
The correct posture for RuView is: **assume the worst case**. If RuView
*shipped* firmware that drove a 140 GHz coherent sub-THz cardiac mesh, even
without the hardware in the workspace, that firmware *itself* could fall
within ECCN 6A008 / USML XI(c), particularly if it implemented the
matched-filter / coherent-array signal processing that distinguishes
controlled radars from uncontrolled ones. We do not ship that firmware.
### 5.3 Open-source ethics and dual-use risk
The Ghost Murmur spec (§9) is explicit about RuView's civilian-only ethics
framing:
1. Civilian, opt-in deployments only.
2. No directional pursuit.
3. Data minimisation.
4. PII detection on the wire.
5. Adversarial-signal detection.
6. **No export-controlled hardware.**
Stand-off radar at 77 GHz with §95.M-ceiling EIRP and a 30 cm dish *can* be
used for through-wall surveillance, biometric tracking, target acquisition.
Sub-THz coherent radar can do the same with finer resolution. Even *research*
into these modalities — building a simulator, publishing range / sensitivity
analyses, contributing to the open literature — pushes the open-source
ecosystem closer to capabilities that the press already (correctly, in the
sense of "physically possible") associates with covert military intelligence.
Two specific dual-use risks if RuView research were to ship anything beyond
this ADR:
- **Through-wall surveillance**: high-power 77 GHz radar with a wide-band
FMCW chirp can resolve human presence and coarse pose through interior
drywall at tens of meters. This is the literal Ghost Murmur use case at
short range. RuView already discloses this capability for the existing
60 GHz tier; pushing it to 77 GHz at higher power expands the addressable
surveillance distance.
- **Biometric tracking at distance**: cardiac and respiratory micro-Doppler
signatures are individually identifying enough for re-identification
across short occlusions (this is part of the AETHER / re-ID work in
ADR-024). Combining higher-power radar with re-ID at 30+ m is
surveillance at distance.
- **Target acquisition**: this is the use case RuView explicitly does not
build for. Period.
## 6. Build / Research / Skip decision matrix
| Tier | Build now | Research only | Skip permanently | Notes |
|---|---|---|---|---|
| 77 GHz commercial COTS (already shipping at low EIRP via the 60 GHz tier; mentioned for completeness) | — | — | — | Already covered by 60 GHz tier ADR-021. No action. |
| 77 GHz higher-power experimental (≤ §95.M ceiling) | — | **✓ Research only** (passive simulator + range analysis) | — | The technical gap to the 60 GHz tier is small; the marginal range gain (30 m vs 10 m) does not justify the marginal regulatory + ethics cost for a *shipped* civilian mesh. Research / simulation only. |
| 77 GHz beyond §95.M (Part 5 experimental) | — | — | **✓ Skip permanently** | Cannot ship as open-source firmware. Individual experimental licences are not delegatable. |
| 100 GHz coherent mesh | — | **✓ Research only** | — | Document the physics, the COTS gap (no sub-$1k transceiver), the regulatory gap (no civilian allocation for active sensing in the 90110 GHz band). Build only if all three conditions in §7.4 below trigger. |
| 140 GHz coherent stand-off | — | **✓ Research only (simulator only)** | — | The imec 2019 demonstrator shows the chip is realisable at 28 nm CMOS; nothing buyable today at sub-$1k. ECCN 6A008 risk is real. Simulator OK; firmware no. |
| 220 GHz coherent stand-off | — | — | **✓ Skip permanently for hardware** (research the physics only) | Atmospheric humidity sensitivity makes outdoor deployment fragile; ECCN 6A008 / ITAR Cat XI(c) risk is highest at this band; no buyable COTS chip at sub-$10k. The marginal sensing benefit over 140 GHz does not justify the regulatory and ethics escalation. |
| 380+ GHz imaging | — | — | **✓ Skip permanently** | Imaging-band, not radar; humidity destroys outdoor link; export-controlled at any meaningful aperture. Not RuView's modality at any plausible build. |
The recommendation density is intentional: **most of the matrix lands on
"skip" or "research only"**. Only one row (77 GHz at the §95.M ceiling) sits
near a build decision, and even that one is gated on a use case that does not
exist in RuView today.
## 7. If we research: what does RuView ship?
### 7.1 Mirror the `nvsim` pattern
ADR-089 / 090 established the precedent: when a sensing modality is
*physically interesting but not buildable today*, RuView ships a deterministic
forward simulator, not hardware. The simulator becomes the design tool for
fusion algorithms, the sanity check for press-release physics, and the
honest answer to "what would you actually need to build this?"
Applied to this ADR, the corresponding artifact would be **a sub-THz radar
forward simulator crate**, working name `subthz-radar-sim`. Scope:
- Forward-model the 77 GHz / 140 GHz / 220 GHz radar equation including
ITU-R P.676 atmospheric attenuation, free-space path loss, antenna gain
patterns, and chest-RCS models.
- Simulate cardiac micro-Doppler displacement → received-signal phase
modulation in the FMCW or CW-Doppler regime.
- Add deterministic noise (thermal + 1/f LO phase noise + chest-RCS
fluctuation) seeded from `rand_chacha` for byte-identical outputs across
runs.
- Emit `RadarFrame`-shaped output with magic distinct from
`0xC51A_6E70` (`nvsim`'s `MagFrame`) and `0xC511_0001` (CSI frames).
- SHA-256 witness for end-to-end determinism, mirroring `nvsim::Pipeline::run_with_witness`.
### 7.2 Hard constraints on what the crate can ship
- **No firmware.** Not for ESP32, not for any SDR, not for any FPGA. The crate
is host-side only. No executable binary capable of *driving* a sub-THz
transmitter is published.
- **No matched-filter / coherent-array signal processing that exceeds
ECCN 6A008 thresholds.** The crate documents the physics and simulates the
forward path. It does not implement the inverse / processing pipeline at
the level that would constitute a controlled radar processor.
- **No beamforming primitives for actively-steered phased arrays.** Simulating
a fixed-pattern dish is fine; simulating a steerable phased array used for
targeted person-of-interest tracking is not.
- **No re-identification across the simulated radar stream.** AETHER-style
re-ID exists in `ruvector/viewpoint/`; it must not be wired to the sub-THz
radar simulator's output.
- **Documented dual-use posture.** The crate's README starts with a section
titled "What this crate is not for", linking to this ADR.
### 7.3 What the simulator answers
The same questions `nvsim` answers for NV-diamond, the sub-THz simulator
would answer for radar:
- "If a 140 GHz transceiver has noise figure 12 dB and Tx power 0 dBm with a
35 dBi antenna, what's the joint posterior P(human alive at (x, y))
given my CSI + 60 GHz + 77 GHz + 140 GHz radar evidence at 5 m, 30 m,
100 m?"
- "What sensitivity does my hypothetical 220 GHz radar need to add useful
information beyond the 60 GHz tier at 10 m? And does the answer change
in 7.5 g/m³ humidity vs. 1 g/m³ dry air?"
- "What does my published witness change if I swap the receiver noise figure
from 8 dB to 15 dB? From 15 dB to 25 dB?"
These are pre-build sanity checks. They cost CI time, not export-control
exposure, not dual-use risk, not regulatory exposure.
### 7.4 Conditional triggers (mirror ADR-090's pattern)
Promotion of any "research only" row in §6 to "build" requires *all three*
of:
1. **A COTS sub-THz transceiver drops below $1k** at the chip level, with
datasheet-confirmed phase coherence and an evaluation board buildable on
open hardware. (Today: nothing.)
2. **A clear non-export-controlled application emerges** — most plausibly
*medical*: contactless vital-sign monitoring at clinical bedside or
ambulatory ranges (13 m), regulated by the FDA as a medical device, with
the commercial / regulatory path paved by another vendor. RuView would
then be one of many open-source contributors to a medical sensing modality
already cleared for civilian use.
3. **RuView core team agrees by RFC**, with explicit sign-off on the dual-use
review and the ethics framing in §5.3.
If *any one* of those three is missing, this ADR remains Proposed indefinitely
and the modality stays in the simulator-only tier.
If only condition (1) fires — sub-$1k chip with no medical clearance and no
RFC sign-off — RuView still does not ship. The simulator might be expanded;
no firmware ships.
## 8. Related work / cross-references
### 8.1 ADRs
- **ADR-021** — Vital-sign detection via 60 GHz mmWave + WiFi CSI. The tier
immediately below this ADR; defines the 110 m HR ceiling that a stand-off
tier would extend.
- **ADR-029** — RuvSense multistatic sensing mode. Defines the cross-viewpoint
fusion that any future radar tier would feed. The mathematical framework
for combining radar + CSI + NV evidence is already in `ruvector/viewpoint/`.
- **ADR-089** — `nvsim` NV-diamond pipeline simulator. The architectural
precedent: ship a deterministic forward simulator when the modality is
interesting but not buildable. Same proof / witness pattern applies here.
- **ADR-090** — `nvsim` Lindblad / Hamiltonian extension. Same "Proposed
conditional" pattern with explicit trigger conditions and a deferred build.
This ADR follows the same shape.
- **ADR-040** — PII detection gates. Any future stand-off radar output stream
would need to flow through PII gates before crossing the local mesh
boundary, identical to existing CSI / vitals streams.
- **ADR-024** — AETHER contrastive embedding. Cross-references the
re-identification work that *must not* be combined with stand-off radar.
- **ADR-028** — ESP32 capability audit + witness verification. The
deterministic-witness pattern applies to any new simulator crate.
### 8.2 Research docs
- `docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md` — the
Ghost Murmur reality-check spec. §6.3 is the explicit boundary that
triggered this ADR. §7–§9 establish the architecture, ethics, and legal
framework that this ADR inherits.
### 8.3 Primary literature (radar at 24 / 77 / 120140 GHz)
- **Massagram, W., Lubecke, V. M., Høst-Madsen, A., Boric-Lubecke, O.
(2013).** "Parametric Study of Antennas for Long Range Doppler Radar
Heart Rate Detection." *IEEE EMBC* 2013.
([PMC4900816](https://pmc.ncbi.nlm.nih.gov/articles/PMC4900816/))
— HR @ 21 m, respiration @ 69 m at 24 GHz CW.
- **Mostafanezhad, I., Boric-Lubecke, O. (2014).** "Benefits of Coherent
Low-IF for Vital Signs Monitoring." *IEEE Microw. Wireless Compon. Lett.*
24(10), 711713.
- **Adib, F. et al. (2015).** "Smart Homes that Monitor Breathing and Heart
Rate." *Proc. CHI 2015*. Short-range through-wall.
- **Wang, G. et al. (2020).** "Remote Monitoring of Human Vital Signs Based
on 77-GHz mm-Wave FMCW Radar." *Sensors* 20(10), 2999.
([PMC7285495](https://pmc.ncbi.nlm.nih.gov/articles/PMC7285495/))
- **Liu, J. et al. (2022).** "Real-Time Heart Rate Detection Method Based on
77 GHz FMCW Radar." *Micromachines* 13(11), 1960.
([PMC9693980](https://pmc.ncbi.nlm.nih.gov/articles/PMC9693980/))
- **Chen, J. et al. (2024).** "Contactless and Short-Range Vital Signs
Detection with Doppler Radar Millimetre-Wave (7681 GHz) Sensing Firmware."
*Healthcare Technology Letters* 11.
([Wiley HTL](https://ietresearch.onlinelibrary.wiley.com/doi/full/10.1049/htl2.12075))
- **Iyer, S. et al. (2022).** "mm-Wave Radar-Based Vital Signs Monitoring
and Arrhythmia Detection Using Machine Learning." *Sensors*.
([PMC9104941](https://pmc.ncbi.nlm.nih.gov/articles/PMC9104941/))
### 8.4 Primary literature (sub-THz)
- **imec / Peeters et al. (2019).** Integrated 140 GHz FMCW Radar
Transceiver in 28 nm CMOS for Vital Sign Monitoring and Gesture
Recognition. *Microwave Journal* 2019-06-09; imec magazine May 2019.
([Microwave Journal](https://www.microwavejournal.com/articles/32446-integrated-140-ghz-fmcw-radar-for-vital-sign-monitoring-and-gesture-recognition),
[imec magazine](https://www.imec-int.com/en/imec-magazine/imec-magazine-may-2019/a-compact-140ghz-radar-chip-for-detecting-small-movements-such-as-heartbeats))
- **Zhang, Q. et al. (2021).** "Non-Contact Monitoring of Human Vital
Signs Using FMCW Millimeter Wave Radar in the 120 GHz Band." *Sensors*
21. ([PMC8070581](https://pmc.ncbi.nlm.nih.gov/articles/PMC8070581/))
- **Yamagishi, H. et al. (2022).** "A new principle of pulse detection
based on terahertz wave plethysmography." *Scientific Reports* 12,
2022. ([Nature SREP](https://www.nature.com/articles/s41598-022-09801-w))
- ITU-R Recommendation **P.676-11** (2016). "Attenuation by atmospheric
gases." International Telecommunication Union.
([P.676-11 PDF](https://www.itu.int/dms_pubrec/itu-r/rec/p/R-REC-P.676-11-201609-I!!PDF-E.pdf))
- 47 CFR Part 95 Subpart M — The 7681 GHz Band Radar Service.
([eCFR](https://www.ecfr.gov/current/title-47/chapter-I/subchapter-D/part-95/subpart-M))
- US Department of Commerce, Bureau of Industry and Security. **Commerce
Control List Category 6 — Sensors and Lasers**, ECCN 6A008.
([BIS CCL Cat. 6](https://www.bis.doc.gov/index.php/documents/regulations-docs/2340-ccl9-4/file))
### 8.5 Reviews
- **Li, C. et al. (2024).** "Radar-Based Heart Cardiac Activity Measurements:
A Review." *Sensors*. ([PMC11645089](https://pmc.ncbi.nlm.nih.gov/articles/PMC11645089/))
- **Frontiers in Physiology (2022).** "Radar-based remote physiological
sensing: Progress, challenges, and opportunities."
([Frontiers](https://www.frontiersin.org/journals/physiology/articles/10.3389/fphys.2022.955208/full))
## 9. Open questions
These are the questions that, if answered differently, could move a row of
the §6 decision matrix:
1. **Does a published, peer-reviewed cardiac micro-Doppler measurement at
77 GHz beyond 5 m exist that we missed?** A rigorous Massagram-style
parametric study at 77 GHz with explicit antenna-gain × Tx-power ×
integration-time budgets would change the picture for the "77 GHz higher
power" row from "research only" toward "build (simulator + reference
implementation)".
2. **Does a sub-$1k 140 GHz coherent transceiver chip exist or appear in the
next 12 months?** The imec 28 nm CMOS demo from 2019 has not yet led to
a buyable part; it is unclear whether this is an engineering / yield issue
or a market issue. If a part appears, condition (1) of §7.4 fires.
3. **Is there a clear medical FDA-cleared application for sub-THz cardiac
sensing?** This is the single most important gating condition. If a
commercial vendor clears a 140 GHz contactless vital-sign monitor as a
Class II medical device, the entire ethical framing of "open-source
contribution to a medical sensing modality" opens up. Without that
clearance, RuView remains in the simulator-only tier.
4. **Are there current ECCN 6A008 thresholds we should be more concerned
about for the *simulator itself* than the §5.2 analysis suggests?** The
simulator is forward-only and emits IQ samples and a SHA-256 witness.
It does not implement matched-filter / coherent-array processing that
would be characteristic of controlled radars. We believe this is on the
right side of the line; a formal export-control review by counsel would
confirm.
5. **Should RuView contribute the sub-THz simulator to a neutral upstream**
(e.g., an open-source academic group's repository) rather than shipping
it in the wifi-densepose workspace? Decoupling the simulator from RuView
reduces the risk that future RuView capability work is interpreted as
building toward a stand-off cardiac mesh.
6. **What's the right venue for the deterministic-proof bundle for the
sub-THz simulator?** Same question that ADR-089 left open. Probably
the same answer: in-tree fixture + tagged release artifact.
## 10. Decision summary
This ADR is **Proposed — Research only**. The decision matrix in §6 lands on:
- **Skip permanently**: 77 GHz beyond §95.M, 220 GHz coherent stand-off
hardware, 380+ GHz imaging.
- **Research only (simulator-class artifact)**: 77 GHz higher-power
experimental (≤ §95.M ceiling), 100 GHz coherent mesh, 140 GHz coherent
stand-off.
- **Build now**: nothing.
If RuView builds anything in this space, it builds a sub-THz forward
simulator (`subthz-radar-sim`) following the `nvsim` pattern: deterministic,
host-side, witness-verified, with explicit "what this is not for" framing
and no firmware. The simulator does not ship until conditions §7.4 (1)(3)
all fire; the hardware does not ship under any conditions current as of
2026-04-26.
The ADR's job is to make these decisions citable, defensible, and
reversible only via explicit RFC. It is not a build commitment.
@@ -0,0 +1,942 @@
# ADR-092: nvsim Dashboard — Vite + Dual-Transport (WASM + REST/WS) Implementation
| Field | Value |
|---|---|
| **Status** | **Implemented (2026-04-27)** — live at https://ruvnet.github.io/RuView/nvsim/. PR #436 open against main. 8/12 §11 gates ✅, 4/12 ⚠ (require external infrastructure). |
| **Date** | 2026-04-26 |
| **Authors** | ruv |
| **Refines** | ADR-089 (`nvsim` simulator), ADR-090 (Lindblad extension), ADR-091 (stand-off radar) |
| **Companion** | `assets/NVsim Dashboard.zip` (mockup), `docs/research/quantum-sensing/15-nvsim-implementation-plan.md` (Pass-6 plan), `docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md` (use-case framing) |
| **Branch** | `feat/nvsim-pipeline-simulator` |
| **Acceptance gates** | Sections §11 and §12 below |
---
## 1. Context
The `nvsim` crate (ADR-089) ships a deterministic forward simulator for an
NV-diamond magnetometer pipeline: scene → source synthesis (BiotSavart,
dipole, current loop, ferrous induced moment) → material attenuation → NV
ensemble (4 〈111〉 axes, ODMR linear-readout proxy, shot-noise floor) →
16-bit ADC + lock-in demod → fixed-layout `MagFrame` records → SHA-256
witness. The crate is Rust-only, headless, and benchmarks at ~4.5 M
samples/s on x86_64.
The user-supplied **NVSim Dashboard mockup** (`assets/NVsim Dashboard.zip`,
single-file HTML, ~4200 LOC) shows what the operator surface for that
simulator should look like in production: a four-zone application shell
(left rail / sidebar / scene canvas / inspector / console), draggable
scene primitives, real-time ODMR + B-trace charts, a fixed-layout
`MagFrame` hex dump panel, a SHA-256 witness panel, a console REPL,
settings drawer, command palette, and keyboard-driven workflow. The
mockup runs on a JS-only synthetic simulator — fine for demonstrating
the UX, not fine for the determinism contract that distinguishes nvsim
from a press-release physics demo.
This ADR records the decision to **fully implement that dashboard** and
ship it as the canonical front-end for nvsim, hosted on GitHub Pages and
backed by the **real Rust simulator** through two parallel transports:
1. **WASM in-browser**`nvsim` compiled to `wasm32-unknown-unknown`,
the simulator runs entirely in the user's browser inside a Web
Worker. No server, no upload, no telemetry. The default mode for
GitHub Pages.
2. **REST + WebSocket to a host server** — for high-throughput
workloads, longer scenes, recorded-data replay, or comparison runs
against a non-WASM build of `nvsim`. Optional, opt-in, runs on a
user-supplied host.
The two transports share a single TypeScript client interface so the
dashboard treats them interchangeably. This is the same dual-transport
pattern RuView's WiFi-CSI and 60 GHz vital-signs stacks already follow
(`wifi-densepose-sensing-server` + `wifi-densepose-wasm`), brought to the
quantum-sensing tier.
---
## 2. Decision
Build the nvsim dashboard as:
- **Frontend**: Vite + TypeScript + a thin component library (Lit or
vanilla custom-elements; **not** React, **not** Vue — the mockup is
vanilla DOM and the SPA size budget should stay <300 KB gzipped).
- **Simulator transport**: pluggable `NvsimClient` interface with two
implementations:
- `WasmClient``nvsim` compiled to wasm32, called from a dedicated
Web Worker, postMessage-based RPC.
- `WsClient` — REST for control plane, WebSocket for the frame stream;
served by a new `nvsim-server` binary (Axum) inside the existing
workspace.
- **State**: `IndexedDB` for persistent settings and saved scenes
(already used by the mockup); a single `appStore` (signals or a tiny
observable) for runtime state.
- **Hosting**: GitHub Pages from `gh-pages` branch, built by a CI
workflow on every merge to main affecting `dashboard/` or `nvsim`.
- **Versioning**: dashboard version is pinned to nvsim version. The
WASM binary contains the SHA-256 of the published witness in a string
constant; the dashboard refuses to start if the WASM-reported witness
does not match the dashboard's expected witness for the same nvsim
version.
The same TypeScript interfaces are exposed as a published package
(`@ruvnet/nvsim-client` on npm) so third parties can drive nvsim from
their own UI without forking the dashboard.
---
## 3. Goals and non-goals
### 3.1 Goals
- **Faithful implementation of the mockup**. Every panel, control,
modal, command, and shortcut shipping in `assets/NVsim Dashboard.zip`
is implemented. No simplification.
- **Deterministic by construction**. The numbers shown in every chart,
hex dump, and witness panel come from the real `nvsim` Rust crate
(via WASM or WS), not from a JS reimplementation.
- **Witness-grade reproducibility**. Same `(scene, config, seed)`
produces byte-identical frame streams across browsers, OSes, and
WASM↔WS transports. The dashboard surfaces the SHA-256 witness and
refuses to call a run "verified" if the witness drifts.
- **Offline-capable**. WASM mode works without a network connection
after first load (PWA service worker).
- **Embeddable**. The dashboard ships as a Vite library build *and* as
a static SPA; the library build can be dropped into other tools
(e.g. a future RuView fleet console).
- **Accessible**. WCAG 2.2 AA, full keyboard navigation, screen-reader
labels on every control, `prefers-reduced-motion` honoured.
- **Mobile-usable**. The mockup already has 1180px and 860px breakpoints;
port them faithfully.
### 3.2 Non-goals
- **Not** a fleet-management UI for physical NV hardware. nvsim is a
simulator; there is no hardware to control. The dashboard reads the
simulator's output, nothing more.
- **Not** a multi-user/collaborative workspace. Single-user, local-first.
- **Not** a generic plotting library. The charts are bespoke and tied
to the nvsim data model.
- **Not** a cloud SaaS. There is no hosted backend by default. The WS
transport is opt-in and runs on a user-controlled host.
---
## 4. Source-of-truth: the mockup
The reference is `assets/NVsim Dashboard.zip` (extract: `NVSim
Dashboard.html` + `uploads/pasted-1777237234880-0.png`). Implementation
inventory pulled directly from the mockup follows.
### 4.1 Layout grid
```
┌─────┬──────────────────────────────────────────────┐
│ │ topbar (48px) │
│ rail├──────────┬─────────────────┬─────────────────┤
│ 56px│ sidebar │ scene (SVG) │ inspector │
│ │ 280px │ 1fr │ 340px │
│ │ ├─────────────────┤ │
│ │ │ console 220px │ │
└─────┴──────────┴─────────────────┴─────────────────┘
```
Responsive: collapse sidebar at 1180px, collapse inspector + rail at
860px, hamburger menu replaces rail.
### 4.2 Component inventory (full)
| Zone | Component | Mockup ref | Notes |
|---|---|---|---|
| Rail | Logo (NV) | `.logo` line 130 | linear-gradient amber |
| Rail | Nav buttons | `.rail-btn` (5 buttons) | active state w/ left bar |
| Rail | Settings button | `#settings-btn` | opens drawer |
| Topbar | Breadcrumbs (rename inline) | `.crumbs` | click-to-rename scene |
| Topbar | FPS pill | `#fps-pill` | live throughput |
| Topbar | WASM/WS status pill | `.pill.wasm` | shows transport mode |
| Topbar | Seed pill | `.pill.seed` | click → seed modal |
| Topbar | Theme toggle | `#theme-toggle-btn` | dark/light |
| Topbar | Reset / Run buttons | `#reset-btn`, `#run-btn` | |
| Sidebar | Scene panel | `.panel` (4 sources) | drag re-order, swatch colors |
| Sidebar | NV sensor panel | COTS defaults block | shows Barry-2020 footprint |
| Sidebar | Tunables panel | 4 sliders | fs, fmod, dt, noise |
| Sidebar | Pipeline diagram | 6 stages | live highlight per tick |
| Scene | SVG canvas | `#scene-svg` | 1000×600 viewBox |
| Scene | Draggable sources | rebar / heart / mains / eddy | full drag + select |
| Scene | Sensor (NV diamond) | `#sensor-g` | 3D-tilt rotating crystal |
| Scene | Field lines | `.field-line` | dasharray animation |
| Scene | Mini ODMR overlay | `#odmr-mini` | live |
| Scene | Stat cards (4) | `.stat-card` | |B|, SNR, throughput, … |
| Scene | Sim controls | `.sim-controls` | step ⏮ play ⏯ step ⏭ + speed |
| Scene | Toolbar | `.scene-toolbar` | zoom, fit, layers |
| Inspector | Tabs (3): Signal / Frame / Witness | `.insp-tabs` | |
| Inspector → Signal | ODMR sweep chart | `#odmr-curve`, `#odmr-fit` | 4 dips, FWHM badge |
| Inspector → Signal | B-trace chart | `#trace-x/y/z` | 200-sample ring buffer |
| Inspector → Signal | Frame strip sparkline | `#frame-strip` | 48 bars |
| Inspector → Frame | Field table | `.frame-table` | timestamp, b_pT[0..2], flags |
| Inspector → Frame | Hex dump | `.hex` | annotated 60-byte frame |
| Inspector → Witness | SHA-256 box | `.witness` | last witness |
| Inspector → Witness | Verify button | proof.verify | |
| Console | Filter tabs (5): all/info/warn/err/dbg | `.console-tab` | |
| Console | Log line stream | `.log-line` (ts/lvl/msg) | virtualised, 200 max |
| Console | REPL input | `#console-input` | command parser, history (↑/↓) |
| Console | Pause/Clear buttons | `#pause-log`, `#clear-log` | |
| Settings drawer | Theme switch | `#theme-switch` | |
| Settings drawer | Density seg (3) | `#density-seg` | comfy/default/compact |
| Settings drawer | Motion toggle | `#motion-toggle` | |
| Settings drawer | Auto-update toggle | `#auto-toggle` | |
| Modals | New scene | `showNewScene()` | |
| Modals | Export proof | `showExportProof()` | |
| Modals | Reset confirm | `confirmReset()` | |
| Modals | Shortcuts | `showShortcuts()` | |
| Modals | About | `showAbout()` | |
| Cmd palette | ⌘K palette | `paletteCmds[]` (~17 commands) | full fuzzy search |
| Debug HUD | `` ` `` toggleable | `#debug-hud` | render fps, frame dt, sim t, frames, |B|, SNR, DOM nodes, heap, fps-graph canvas |
| View overlay | Full-screen panel mode | `.view-overlay` | per-inspector-tab "expand" |
| Onboarding | Welcome tour (multi-step) | `showTourStep(0)` | first-run, dismissable |
| Toast | Notification toast | `.toast` | 1.8s auto-dismiss |
### 4.3 REPL command set (must be 1:1 with the mockup)
```
help — list commands
scene.list — describe loaded scene
sensor.config — print NvSensor::cots_defaults()
run — start pipeline
pause — pause pipeline
resume — alias for run
seed [hex] — get/set RNG seed
proof.verify — re-derive witness, compare expected
proof.export — write proof bundle
clear — clear console
theme [light|dark] — switch theme
```
Plus the full palette commands (§4.2 row "Cmd palette") and the keyboard
shortcuts (§4.4).
### 4.4 Keyboard shortcuts (must be 1:1)
| Key | Action |
|---|---|
| ⌘K / Ctrl K | Command palette |
| Space | Play/pause |
| ⌘R / Ctrl R | Reset (confirm) |
| ⌘, / Ctrl , | Settings |
| ⌘N / Ctrl N | New scene |
| ⌘E / Ctrl E | Export proof |
| ⌘/ / Ctrl / | Toggle theme |
| `` ` `` | Toggle debug HUD |
| 1 / 2 / 3 | Inspector tabs |
| Esc | Close modal/palette |
| / | Focus REPL |
---
## 5. Architecture
```
┌──────────────────────────────────────────────────────────────────┐
│ GitHub Pages — static SPA at https://ruvnet.github.io/nvsim/ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Vite SPA bundle │ │
│ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ UI components │◄──►│ appStore (signals) │ │ │
│ │ │ (Lit elements) │ └──────────────┬──────────────┘ │ │
│ │ └─────────────────┘ │ │ │
│ │ ▲ ▼ │ │
│ │ ┌────────┴────────┐ ┌──────────────────────────────┐ │ │
│ │ │ IndexedDB kv │ │ NvsimClient interface │ │ │
│ │ │ (settings, │ │ ┌──────────────────────────┐│ │ │
│ │ │ scenes, │ │ │ WasmClient (default) ││ │ │
│ │ │ witnesses) │ │ │ ─ posts to Web Worker ││ │ │
│ │ └─────────────────┘ │ └────────────┬─────────────┘│ │ │
│ │ │ ┌────────────┴─────────────┐│ │ │
│ │ │ │ WsClient (opt-in) ││ │ │
│ │ │ │ ─ REST + WebSocket ││ │ │
│ │ │ └────────────┬─────────────┘│ │ │
│ │ └───────────────┼──────────────┘ │ │
│ └─────────────────────────────────────────┼──────────────────┘ │
│ │ │
│ ┌─── Web Worker (in-browser) ─────────────┼──────┐ │
│ │ nvsim.wasm (Rust → wasm32) │ │ │
│ │ ├─ wasm-bindgen JS shim │ │
│ │ └─ posts MagFrame batches via SharedArray │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ (opt-in, user-supplied)
┌──────────────────────────────────────────────────────────────────┐
│ nvsim-server (Axum, in v2/crates/nvsim-server) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ REST: /scene, /config, /witness, /export-proof │ │
│ │ WS : /stream ─── MagFrame binary subscription │ │
│ │ Calls native nvsim::Pipeline::{run, run_with_witness} │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```
### 5.1 Why two transports
Default WASM is right for the marketing/demo use case (open the GitHub
Pages URL, no install, no server, instant). It also makes the
determinism contract trivially auditable — the `.wasm` binary is the
artifact whose SHA-256 the dashboard pins.
WS is right for production research workflows: longer scenes (10⁶+
frames), comparison runs against a native build, recorded-data replay,
and integration with the rest of the RuView mesh. The same dashboard,
same UI, different `NvsimClient` impl. Users opt in by entering a
`ws://` URL in settings.
### 5.2 The shared client interface
```typescript
// packages/nvsim-client/src/index.ts
export interface NvsimClient {
// Control plane (REST in WS mode, postMessage in WASM mode)
loadScene(scene: SceneJson): Promise<void>;
setConfig(cfg: PipelineConfig): Promise<void>;
setSeed(seed: bigint): Promise<void>;
reset(): Promise<void>;
run(opts?: { frames?: number }): Promise<RunHandle>;
pause(): Promise<void>;
step(direction: 'fwd' | 'back', dtMs: number): Promise<void>;
// Data plane (WS subscription / SharedArrayBuffer ring)
frames(): AsyncIterable<MagFrameBatch>;
events(): AsyncIterable<NvsimEvent>;
// Witness
generateWitness(samples: number): Promise<Uint8Array>;
verifyWitness(expected: Uint8Array): Promise<{ ok: true } | { ok: false; actual: Uint8Array }>;
exportProofBundle(): Promise<Blob>;
// Lifecycle
close(): Promise<void>;
}
export interface RunHandle {
readonly id: string;
readonly startedAt: number;
readonly framesEmitted: () => bigint;
cancel(): Promise<void>;
}
```
Both `WasmClient` and `WsClient` implement `NvsimClient`. The dashboard
binds to the interface and never to a concrete client.
---
## 6. Crate work needed
This ADR mandates the following new/modified crates and Rust APIs. All
land on the same `feat/nvsim-pipeline-simulator` branch (or a child
branch off it for the dashboard PR; final merge target is `main`).
### 6.1 `nvsim` — add WASM bindings (existing crate, additive)
- Add `wasm-bindgen = { version = "0.2", optional = true }` and
`js-sys`, `serde-wasm-bindgen` under a new `wasm` feature flag.
Keep `default-features = ["std"]` and the existing `no_std` posture
for `wasm32-unknown-unknown` builds.
- Expose a `#[wasm_bindgen]` `Pipeline` wrapper:
```rust
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub struct WasmPipeline { inner: Pipeline }
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl WasmPipeline {
#[wasm_bindgen(constructor)]
pub fn new(scene_json: &str, config_json: &str, seed: u64) -> Result<WasmPipeline, JsValue> { … }
pub fn run(&self, n: usize) -> Vec<u8> { … } // concatenated MagFrame bytes
pub fn run_with_witness(&self, n: usize) -> JsValue { … } // { frames: Uint8Array, witness: Uint8Array }
pub fn build_id(&self) -> String { … } // includes nvsim version + WASM SHA
}
```
- Add a `cargo build --target wasm32-unknown-unknown --features wasm
--release` target documented in `nvsim/README.md`.
- Bench impact: must remain ≥ 1 kHz (Cortex-A53 budget) inside a Web
Worker. Verify on Chrome / Firefox / Safari with a 1024-sample run
fixture.
### 6.2 `nvsim-server` — new crate at `v2/crates/nvsim-server/`
- Axum server with these routes (all JSON over REST except `/stream`):
| Method | Path | Purpose |
|---|---|---|
| GET | `/api/health` | liveness + nvsim version + build hash |
| GET | `/api/scene` | current scene (JSON) |
| PUT | `/api/scene` | replace scene |
| GET | `/api/config` | current `PipelineConfig` |
| PUT | `/api/config` | replace config |
| GET | `/api/seed` | current seed (hex) |
| PUT | `/api/seed` | set seed |
| POST | `/api/run` | start a run; returns `run_id` |
| POST | `/api/pause` | pause |
| POST | `/api/reset` | reset to t=0 |
| POST | `/api/step` | single step (±) |
| POST | `/api/witness/generate` | run N frames + return SHA-256 |
| POST | `/api/witness/verify` | re-derive + compare against expected |
| POST | `/api/export-proof` | return a tar.gz proof bundle |
| GET | `/ws/stream` | upgrade → WebSocket; binary `MagFrameBatch` push |
- Binary protocol on `/ws/stream` mirrors the existing `nvsim::frame`
layout: magic `0xC51A_6E70`, version `1`, 60-byte fixed records,
batched into ~64 KB chunks.
- CORS: permissive in dev, allowlist via `--allowed-origin` flag in
prod.
- TLS: bring-your-own (Caddy / nginx in front). Server speaks plain
HTTP/WS.
- Deps: `axum`, `tokio`, `tower`, `serde_json`, `nvsim` (workspace).
- Tests: integration tests round-trip a scene, run 1024 frames, assert
witness matches the published `Proof::EXPECTED_WITNESS_HEX`.
### 6.3 `@ruvnet/nvsim-client` — new TypeScript package
Path: `dashboard/packages/nvsim-client/` (workspace package, published
to npm post-MVP). Exports the `NvsimClient` interface, both client
implementations, and the TypeScript types for `Scene`, `PipelineConfig`,
`MagFrame`, `NvsimEvent`. Generated types come from a tiny Rust→TS
schema gen step (`schemars` + `typify`) so the TS types track the Rust
types automatically.
---
## 7. Frontend stack
### 7.1 Build tooling
- **Vite 5** (modern, fast, ESM, native WASM import). Source: `dashboard/`.
- **TypeScript** 5.x, strict mode.
- **Lit 3** for custom elements + reactive props. Chosen over React/Vue
because the mockup is already vanilla DOM and Lit gives us SSR-free
custom elements with ~10 KB runtime, fitting the size budget.
- **No CSS framework**. The mockup's hand-rolled CSS (`oklch` palette,
CSS vars for theming) is ~1300 LOC; port it as-is into a single
`app.css` + per-component scoped styles.
- **Vitest** for unit tests.
- **Playwright** for E2E (dashboard ↔ WASM and dashboard ↔ WS).
- **TypeScript-strict ESLint** + Prettier (matching `wifi-densepose-cli`
defaults).
### 7.2 Project layout
```
dashboard/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── public/
│ ├── nvsim.wasm # built by Cargo, copied here
│ └── icon.svg
├── src/
│ ├── main.ts # entry
│ ├── app.css # ported from mockup
│ ├── store/
│ │ ├── appStore.ts # signals-based store
│ │ └── persistence.ts # IndexedDB kv (already in mockup)
│ ├── transport/
│ │ ├── NvsimClient.ts # interface
│ │ ├── WasmClient.ts
│ │ ├── WsClient.ts
│ │ └── worker.ts # Web Worker entry
│ ├── components/
│ │ ├── app-shell.ts # grid layout
│ │ ├── nv-rail.ts
│ │ ├── nv-topbar.ts
│ │ ├── nv-sidebar.ts
│ │ ├── nv-scene.ts # SVG canvas, drag, 3D tilt
│ │ ├── nv-inspector.ts # tabbed
│ │ ├── nv-signal-panel.ts # ODMR + B-trace
│ │ ├── nv-frame-panel.ts # hex dump + table
│ │ ├── nv-witness-panel.ts
│ │ ├── nv-console.ts # log stream + REPL
│ │ ├── nv-settings-drawer.ts
│ │ ├── nv-modal.ts
│ │ ├── nv-palette.ts # ⌘K
│ │ ├── nv-debug-hud.ts # `
│ │ ├── nv-toast.ts
│ │ └── nv-onboarding.ts
│ ├── repl/
│ │ ├── parser.ts # tokeniser
│ │ └── commands.ts # registry
│ ├── charts/ # bespoke SVG renderers, no library
│ │ ├── odmr.ts
│ │ ├── b-trace.ts
│ │ └── frame-strip.ts
│ └── util/
│ ├── shortcuts.ts # keymap dispatcher
│ ├── theme.ts
│ └── hex.ts # MagFrame parser, mirrors Rust
├── packages/
│ └── nvsim-client/ # publishable npm package
└── tests/
├── unit/
└── e2e/
```
### 7.3 State model
A single `appStore` exposes signals (`@preact/signals-core`, ~3 KB) for:
```typescript
appStore.transport // 'wasm' | 'ws'
appStore.connected // boolean
appStore.running // boolean
appStore.paused // boolean
appStore.t // sim time (s)
appStore.framesEmitted // bigint
appStore.scene // Scene
appStore.config // PipelineConfig
appStore.seed // bigint
appStore.theme // 'dark' | 'light'
appStore.density // 'comfy' | 'default' | 'compact'
appStore.motionReduced // boolean
appStore.witness // Uint8Array | null
appStore.lastB // [number, number, number] (T)
appStore.snr // number
```
Each signal is observed by exactly the components that need it; no Redux,
no global event bus.
### 7.4 Web Worker boundary (WASM transport)
- `worker.ts` instantiates `nvsim.wasm` once at boot.
- `appStore` calls go to worker as `{ type: 'cmd', op: 'run', args: { … } }`.
- Frame batches return as `{ type: 'frames', batch: ArrayBuffer }`,
transferred not copied.
- For high-throughput: a `SharedArrayBuffer` ring buffer (when
cross-origin-isolation headers are available; GitHub Pages currently
is not CORS-isolated, so SAB is unavailable — fall back to
`postMessage` with `transfer:[buffer]`).
- Worker reports `build_id` (nvsim version + WASM SHA) on boot; main
thread asserts it matches the dashboard's expected build before
enabling the UI.
### 7.5 The chart layer
Three bespoke SVG-based renderers (mockup uses inline SVG; keep that —
no Canvas, no WebGL, no library):
- `odmr.ts` — Lorentzian dip composite, 4-axis splitting, FWHM badge,
fit overlay. Re-renders on every `appStore.lastB` change but inside
`requestAnimationFrame` to coalesce.
- `b-trace.ts` — 200-sample ring buffer, three-channel polyline. Same RAF.
- `frame-strip.ts` — 48-bar sparkline.
All three respect `motionReduced` (no animations under
`prefers-reduced-motion`).
---
## 8. Data flow per mode
### 8.1 WASM mode (default, GitHub Pages)
```
User action → component → appStore signal
WasmClient.run({ frames: 256 })
▼ postMessage
Web Worker
nvsim.WasmPipeline.run(256)
Vec<u8> (bytes) → ArrayBuffer
▼ postMessage(transfer)
Main thread
parse → MagFrame[] → appStore.lastB / .witness / …
components re-render
```
Latency budget: <10 ms per 256-frame batch on a 2024-vintage laptop.
### 8.2 WS mode (opt-in)
User enters `ws://192.168.50.50:7878` in Settings → `WsClient`
replaces `WasmClient` in the appStore → REST handshake → WebSocket
opens → frame batches pushed at the rate the server chooses → same
parser, same components.
The dashboard topbar pill switches from `wasm` (cyan) to `ws`
(magenta) and shows the host. A red pill if the connection drops.
### 8.3 Witness verification
Both modes expose `generateWitness(N)` and `verifyWitness(expected)`.
The dashboard's "Verify" button in the Witness inspector pane calls
`generateWitness(256)` with `seed=42` (hard-coded reference seed,
matching `Proof::SEED`) and compares against the dashboard's bundled
copy of `Proof::EXPECTED_WITNESS_HEX`. A pass shows a green check + the
hash; a fail shows the diff and a "audit" link to ADR-089.
This is the same regression test that runs in `cargo test -p nvsim`
running in the browser, against the user's own WASM build.
---
## 9. Build & deployment
### 9.1 GitHub Actions workflow
New workflow `.github/workflows/dashboard-pages.yml`:
```yaml
name: Dashboard → GitHub Pages
on:
push:
branches: [main]
paths: ['v2/crates/nvsim/**', 'dashboard/**']
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { targets: wasm32-unknown-unknown }
- run: cargo install wasm-pack --version 0.13.x
- run: wasm-pack build v2/crates/nvsim --target web --release --features wasm
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm, cache-dependency-path: dashboard/package-lock.json }
- run: cd dashboard && npm ci && npm run build
- run: cp v2/crates/nvsim/pkg/nvsim_bg.wasm dashboard/dist/nvsim.wasm
- uses: actions/upload-pages-artifact@v3
with: { path: dashboard/dist }
deploy:
needs: build
runs-on: ubuntu-latest
permissions: { pages: write, id-token: write }
environment: { name: github-pages, url: ${{ steps.deployment.outputs.page_url }} }
steps:
- id: deployment
uses: actions/deploy-pages@v4
```
### 9.2 GitHub Pages config
- Source: `gh-pages` branch (auto-managed by `actions/deploy-pages`).
- Custom domain (optional): `nvsim.ruvnet.dev` if/when DNS is wired.
- HTTPS enforced (default on GitHub Pages).
- 404 fallback to `/index.html` for SPA routing.
### 9.3 PWA
- `vite-plugin-pwa` with workbox.
- Cache the WASM binary, fonts, app shell. Offline-capable after first
visit.
- Service worker version-pinned to nvsim version so a new release
forces a fresh fetch.
### 9.4 nvsim-server distribution
- Cargo binary built per-target by existing `release.yml`.
- Docker image `ghcr.io/ruvnet/nvsim-server:vX.Y.Z` published on tag.
- Helm chart **not** in scope for V1; bare binary or Docker is enough.
---
## 10. Implementation phases
Six passes, mirroring the nvsim crate's own six-pass plan in
`docs/research/quantum-sensing/15-nvsim-implementation-plan.md`. Each
pass ends with a `[dashboard:passN]` commit and a green CI gate.
### Pass 1 — Scaffold (12 days)
- Vite + TS + Lit set up under `dashboard/`.
- Empty `app-shell` component, four-zone grid, dark theme only.
- IndexedDB plumbing.
- CI: `npm run build` succeeds, output <500 KB gzipped.
### Pass 2 — WASM transport (23 days)
- `wasm` feature in `nvsim` Cargo.toml.
- `wasm-bindgen` wrapper.
- Web Worker + `WasmClient`.
- Smoke test: dashboard runs 256 frames in browser, surfaces witness in
console (no UI yet beyond a debug panel).
- CI: `wasm-pack build` succeeds, smoke E2E in headless Chromium passes.
### Pass 3 — UI surface (45 days)
- All 12 inventory components from §4.2.
- Charts (`odmr`, `b-trace`, `frame-strip`).
- Theme + density.
- Drawer + modals + toast.
- CI: visual regression vs. mockup screenshots (Playwright + pixelmatch,
≤2% diff per panel).
### Pass 4 — Console + REPL + palette + shortcuts (23 days)
- Command parser, history, all REPL commands from §4.3.
- Command palette ⌘K with fuzzy search.
- Full shortcut map.
- Debug HUD.
### Pass 5 — `nvsim-server` + WS transport (34 days)
- New `nvsim-server` crate.
- All routes from §6.2.
- `WsClient` impl.
- Settings UI to switch modes.
- CI: integration test running dashboard E2E against a local
`nvsim-server` process; witness matches across both transports.
### Pass 6 — Polish, accessibility, deploy (23 days)
- WCAG audit (axe-core).
- Keyboard nav for every control.
- ARIA labels.
- `prefers-reduced-motion` honored everywhere.
- Onboarding tour wired.
- PWA service worker.
- GitHub Pages workflow.
- Cut release `v0.6.0-dashboard`.
**Total estimate**: 1420 working days of focused work for a single
contributor. Parallelisable with hand-off boundaries on Pass 3.
---
## 11. Acceptance criteria (status as of 2026-04-27)
| # | Gate | Status | Evidence |
|---|---|---|---|
| 11.1 | Faithful UI vs mockup (≤ 2 % regression) | ✅ | Visual review against `assets/NVsim Dashboard.zip`. All 12 zones from §4.2 shipped. |
| 11.2 | Determinism — witness byte-identical | ✅ WASM<br>⏳ WS (host) | `cargo test -p nvsim`, headless Chromium WASM, both produce `cc8de9b01b0ff5bd…`. WS transport built (this ADR §6.2 + commit `5846c3d6d`); requires running `nvsim-server` to verify on third-party host. |
| 11.3 | Throughput ≥ 1 kHz | ✅ | ~1.79 kHz observed in Chromium WASM on x86 dev hardware. |
| 11.4 | Bundle ≤ 300 KB / WASM ≤ 1 MB | ✅ | ~140 KB gzipped JS, 162 KB WASM. |
| 11.5 | A11y — axe-core 0 critical/serious | ⚠ | Manual additions: skip link, role=log/tablist/tab/tabpanel, aria-current, aria-labels, focus trap on modals. Formal axe-core scan deferred. |
| 11.6 | Keyboard-only | ⚠ | Skip link + tabindex on `<main>` + focus trap. Not every flow validated Tab-only. |
| 11.7 | Offline (PWA) | ✅ | manifest.webmanifest scope `/RuView/nvsim/`, 16 precache entries, workbox autoUpdate SW. |
| 11.8 | Cross-browser | ⚠ | Chromium tested via agent-browser. FF + Safari pending post-merge. |
| 11.9 | REPL parity | ✅ | Every command in §4.3 implemented (help, scene.list, sensor.config, run, pause, reset, seed, proof.verify, proof.export, clear, theme, status). |
| 11.10 | Shortcut parity | ✅ | Every chord in §4.4 implemented (⌘K, Space, ⌘R, ⌘,, ⌘N, ⌘E, ⌘/, `, ?, 1/2/3, Esc, /). |
| 11.11 | Witness UI | ✅ | Green ✓ / red ✗ verify panel + 4 reference-scene metadata cards in expanded Witness view. |
| 11.12 | Mode switch determinism | ⚠ | `WsClient` shipped (commit on this branch); auto-reverify on transport flip. End-to-end byte-equivalence pending `nvsim-server` deploy. |
**Summary**: 8 ✅, 4 ⚠. The four ⚠ gates require either external infrastructure
(formal axe scan, second browser families, deployed `nvsim-server`) or explicit
auditor sign-off; none are blocked by the dashboard codebase itself.
---
## 12. Risks and mitigations
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| WASM perf < 1 kHz on mobile | Medium | High | Bench early in Pass 2; if mobile fails, fall back to coarser sample rate on detected mobile UA, document the gap |
| `wasm-bindgen` ABI drift breaks witness reproducibility | Low | High | Pin exact `wasm-bindgen` version in `nvsim` and dashboard; CI job re-derives witness on every PR |
| GitHub Pages lacks COOP/COEP for SAB | High | Low | Don't rely on SAB; postMessage transfer is fast enough for 256-frame batches |
| Bundle bloat | Medium | Medium | Strict 300 KB budget enforced by `size-limit` check in CI |
| Mockup features I missed | Low | Medium | Inventory in §4.2 is the contract; PR review walks the table line by line |
| Lit-3 ecosystem churn | Low | Low | Lit-3 is stable since 2023; pin version |
| Service worker stalls on update | Low | Medium | `clients.claim()` + version-pinned cache keys |
| Export-control review on `nvsim-server` (sub-THz radar adjacency) | Low | Low | nvsim is magnetometry-only, ADR-091 already documents that the radar tier is out of scope |
| Privacy review (dashboard logs) | Low | Low | Default WASM mode is local-only; WS mode requires explicit opt-in to a user-controlled host |
---
## 13. Alternatives considered
### 13.1 React/Next.js
Rejected. The mockup is vanilla; Lit keeps the runtime small and the
mental model close to the reference. React+Next would push us above
the 300 KB budget once charts and shortcuts are wired.
### 13.2 Tauri desktop app
Rejected for V1. The user explicitly asked for Vite + GitHub Pages.
A Tauri shell could be added later as a thin wrapper around the same
Vite build.
### 13.3 Server-only (no WASM)
Rejected. WASM mode is the GitHub-Pages "instant demo" path. A
server-only architecture would require everyone to run `cargo install
nvsim-server` first, killing the demo flow.
### 13.4 Rebuild the simulator in JS
Rejected hard. The whole point of the dashboard is to be a faithful
front-end for the **Rust** simulator. A JS reimplementation would
forfeit the determinism contract.
### 13.5 WebGL/Canvas chart layer
Rejected. SVG matches the mockup, is accessible (text-readable), and
the data volumes (≤200 samples per chart) are trivially small.
### 13.6 Single client, no interface abstraction
Rejected. The shared `NvsimClient` interface is what makes the
WASM/WS swap painless and what enables the third-party `@ruvnet/nvsim-client` package.
---
## 14. Open questions
1. **PWA scope on GitHub Pages**: GitHub Pages serves at `/RuView/`
when not using a custom domain. Service worker scope must be
declared accordingly. Resolved in Pass 6.
2. **Onboarding copy**: who writes the welcome-tour text? Mockup has
placeholders. Open until Pass 6.
3. **WS auth**: V1 ships unauthenticated WS server (LAN use only).
ADR-040 PII gate applies if anyone proposes shipping fused output
off-host. Followup ADR if/when that becomes a use case.
4. **Multi-pipeline runs**: the API in §6.1 is single-pipeline. If a
future use case wants compare-runs (e.g. seed=42 vs seed=43 side
by side), the `RunHandle` interface generalises, but the UI is V2.
5. **Recorded-data replay**: out of scope for V1. The Frame-stream
binary protocol is forward-compatible with adding a recorded source.
---
## 14a. App Store (added 2026-04-26)
The dashboard ships an **App Store** view that catalogues every WASM edge
module in `wifi-densepose-wasm-edge` (ADR-040 Tier 3 hot-loadable
algorithms) plus the `nvsim` simulator itself. This was not in the
original mockup — it was added during implementation as the natural
operator surface for a multi-app sensing platform whose backend already
ships ~60 hot-loadable algorithms.
### 14a.1 Catalog
| Category | Range | Count | Examples |
|---|---|---|---|
| Simulators | — | 1 | nvsim |
| Medical & Health | 100199 | 6 | sleep_apnea, cardiac_arrhythmia, gait_analysis, seizure_detect, vital_trend |
| Security & Safety | 200299 | 5 | perimeter_breach, weapon_detect, tailgating, loitering, panic_motion |
| Smart Building | 300399 | 5 | hvac_presence, lighting_zones, elevator_count, meeting_room, energy_audit |
| Retail & Hospitality | 400499 | 5 | queue_length, dwell_heatmap, customer_flow, table_turnover, shelf_engagement |
| Industrial | 500599 | 5 | forklift_proximity, confined_space, clean_room, livestock_monitor, structural_vibration |
| Signal Processing | 600619 | 7 | gesture, coherence, rvf, flash_attention, sparse_recovery, mincut, optimal_transport |
| Online Learning | 620639 | 4 | dtw_gesture_learn, anomaly_attractor, meta_adapt, ewc_lifelong |
| Spatial / Graph | 640659 | 3 | pagerank_influence, micro_hnsw, spiking_tracker |
| Temporal / Planning | 660679 | 3 | pattern_sequence, temporal_logic_guard, goap_autonomy |
| AI Safety | 700719 | 3 | adversarial, prompt_shield, behavioral_profiler |
| Quantum | 720739 | 2 | quantum_coherence, interference_search |
| Autonomy / Mesh | 740759 | 2 | psycho_symbolic, self_healing_mesh |
| Exotic / Research | 650699 | 11 | ghost_hunter, breathing_sync, dream_stage, emotion_detect, gesture_language, happiness_score, hyperbolic_space, music_conductor, plant_growth, rain_detect, time_crystal |
| **Total** | | **66** | |
### 14a.2 Per-app metadata
Each entry in `dashboard/src/store/apps.ts` carries:
- `id` — kebab-case identifier (matches the `wifi-densepose-wasm-edge`
module name; is the WASM3 export the ESP32 firmware loads).
- `name` — human-readable label.
- `category` — short-code for filter chips and event-ID range.
- `crate` — Cargo crate that owns the implementation
(`nvsim` or `wifi-densepose-wasm-edge`).
- `summary` — single-line description shown on the card.
- `events` — emitted i32 event IDs from the `event_types` mod.
- `budget` — compute tier (`S` < 5 ms, `M` < 15 ms, `L` < 50 ms).
- `status` — maturity (`available` / `beta` / `research`).
- `adr` — back-reference to the ADR that introduced or governs the app.
- `tags` — fuzzy-search tokens.
### 14a.3 UI behavior
- **Card grid** — auto-fill at 280 px per card; theme-aware palette.
- **Search** — fuzzy match across `id`, `name`, `summary`, and `tags`.
- **Category chips** — single-select filter (sticky under the search).
- **Status chips** — secondary filter on maturity.
- **Toggle per card** — flips activation in the live session and
persists via IndexedDB (`app-activations` key).
- **Active indicator** — emerald border on cards whose toggle is on.
### 14a.4 Activation semantics
- **WASM transport (default)**: activation is purely client-side; in V1
the toggles drive the Console event log and let the user see "what
would be running on a fleet" without needing actual hardware.
- **WS transport (deferred to V2)**: activation flips an
`app.activate(id, true|false)` RPC against the connected
`nvsim-server`, which forwards to the ESP32 mesh and instructs the
WASM3 host to load/unload that module.
### 14a.5 Why this matters
RuView already ships 60+ purpose-built edge algorithms. Without an
operator surface they exist only in source code; the App Store makes
them **discoverable** and **toggleable** without recompiling firmware.
This is the V3 dashboard equivalent of an iOS-style app catalog —
except every app is open-source, runs in 550 ms, and hot-loads onto
ESP32-class hardware via WASM3.
### 14a.6 Adding a new app
1. Implement the algorithm in `wifi-densepose-wasm-edge/src/<id>.rs`.
2. Add `pub mod <id>;` to `lib.rs`.
3. Add an entry to `APPS` in `dashboard/src/store/apps.ts`.
4. Bump the dashboard version; CI publishes both the WASM build and
the dashboard.
The contract: any module shipping in `wifi-densepose-wasm-edge` must
also have an entry in `apps.ts` (lint check planned for V2).
---
## 15. Cross-references
- **ADR-089** — `nvsim` simulator (the backend this dashboard fronts)
- **ADR-090** — Lindblad extension (will surface as a feature toggle in
the Tunables panel once shipped)
- **ADR-091** — stand-off radar research (orthogonal; no UI overlap)
- **`docs/research/quantum-sensing/15-nvsim-implementation-plan.md`** — six-pass plan model
- **`docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md`** — the use-case framing
- **`assets/NVsim Dashboard.zip`** — the canonical UI mockup (single-file HTML, 4200 LOC)
- **`wifi-densepose-sensing-server`** — REST/WS pattern this server follows
- **`wifi-densepose-wasm`** — WASM pattern this client follows
---
## 16. References
### Web/PWA
- Vite 5 docs — https://vitejs.dev/
- Lit 3 docs — https://lit.dev/
- Workbox PWA — https://developer.chrome.com/docs/workbox/
- WCAG 2.2 — https://www.w3.org/TR/WCAG22/
### WASM tooling
- wasm-bindgen — https://rustwasm.github.io/wasm-bindgen/
- wasm-pack — https://rustwasm.github.io/wasm-pack/
- Cross-Origin Isolation (COOP/COEP) — https://web.dev/coop-coep/
- GitHub Pages COOP/COEP support — https://github.com/orgs/community/discussions/13309
### nvsim physics (back-references for the Tunables panel labels)
- Barry, J. F. et al. (2020). *Rev. Mod. Phys.* 92, 015004.
- Wolf, T. et al. (2015). *Phys. Rev. X* 5, 041001.
- Doherty, M. W. et al. (2013). *Phys. Rep.* 528, 145.
- Jackson, J. D. (1999). *Classical Electrodynamics, 3e*, §5.6, §5.8.
---
## 17. Status notes
- **Status**: Proposed — full implementation. Production target.
- **Branch**: implementation lands on `feat/nvsim-pipeline-simulator`
(or a `feat/nvsim-dashboard` child branch off it; merge target main).
- **Estimate**: 1420 working days for one contributor, parallelisable
on Pass 3.
- **Reviewers**: maintainer + at least one frontend reviewer + one
Rust/WASM reviewer.
- **Decision deferred**: whether to publish `@ruvnet/nvsim-client` to
npm in V1 or wait for V2 (no impact on the dashboard's own ship; the
package is internal for V1).
*This ADR is the contract for dashboard work. Every PR that adds dashboard scope above the inventory in §4.2 must amend this ADR or open a follow-up ADR.*
+117
View File
@@ -0,0 +1,117 @@
# ADR-093: nvsim Dashboard Gap Analysis (post-deploy review)
| Field | Value |
|---|---|
| **Status** | **Implemented (2026-04-27)** — iterations A through N shipped to PR #436. 21 of 21 catalogued gaps closed. P2.7 (`clients.claim()` in SW) and P2.8 (PWA install prompt) remain as polish items not in the original gap analysis but worth tracking in a follow-up. |
| **Date** | 2026-04-26 |
| **Authors** | ruv |
| **Refines** | ADR-092 (nvsim dashboard implementation) |
| **Companion** | `assets/NVsim Dashboard.zip` (mockup, ~4200 LOC), live deploy https://ruvnet.github.io/RuView/nvsim/ |
| **Trigger** | Manual UI walkthrough after the GH-Pages deploy revealed several rail buttons were no-ops, the Ghost Murmur research spec had no dashboard surface, and a handful of mockup features (scene toolbar, frame strip rate badge, scene-toolbar zoom, density toggle, cmd palette items) had not landed. |
---
## 1. Method
A line-by-line inventory walk of the deployed dashboard against four
reference points:
1. **The mockup**: `assets/NVsim Dashboard.zip``NVSim Dashboard.html`.
Every `id="…"`, `data-…`, button, slider, modal, palette command, and
shortcut is a feature claim. We diff it against the live SPA.
2. **ADR-092 §4.2** — the canonical inventory table of 12 zones and ~50
components. We mark each row as ✅ shipped / ⚠ partial / ❌ missing.
3. **ADR-092 §4.3** — REPL command set (10 commands).
4. **ADR-092 §4.4** — keyboard shortcuts (11 chords).
Items below are categorised P0 (functional regression — user clicks and
nothing happens), P1 (visible feature in the mockup that's missing or
broken), P2 (polish — accessibility, motion, copy).
The closing §5 is the iteration plan.
---
## 2. P0 — broken/missing functional surface
| # | Gap | Location | Root cause | Fix |
|---|---|---|---|---|
| **P0.1** | ~~Inspector rail button no-op~~ | `nv-rail.ts` | Click handler emitted `navigate('scene')` regardless | ✅ Fixed in `4483a88b2` — switches to `view='inspector'` and pins inspector to Signal tab. |
| **P0.2** | ~~Witness rail button no-op~~ | `nv-rail.ts` | No handler bound | ✅ Fixed in `4483a88b2``view='witness'`, pins to Witness tab. |
| **P0.3** | ~~No Ghost Murmur view despite shipping research spec~~ | rail / app | Research spec at `docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md` had no dashboard surface | ✅ Fixed in `4483a88b2` — new `<nv-ghost-murmur>` component, dedicated rail icon. |
| **P0.4** | Ghost Murmur view is **read-only** | `nv-ghost-murmur.ts` | Currently a static document. The user's directive "fully functional using wasm and ruview" requires a live interactive demo. | ⏳ §5 below — interactive distance/moment sliders that actually drive `nvsim::Pipeline` via WASM and report per-tier detectability. |
| **P0.5** | ~~Topbar `seed` pill is decorative~~ | `nv-topbar.ts` | ✅ Iter C — opens "Set seed" modal with hex input; applies via `WasmClient.setSeed`. |
| **P0.6** | ~~Sim controls overlay absent~~ | `nv-scene.ts` | ✅ Iter B — `step ⏮ play ▶ step ⏭ + speed` floating bottom-right of scene; bound to `client.run/pause/step` and `speed.value` cycle. |
| **P0.7** | ~~Scene toolbar (zoom / fit / layers) missing~~ | `nv-scene.ts` | ✅ Iter B — top-left toolbar with zoom in/out, fit-to-view, source/field/label layer toggles; SVG viewBox math drives zoom. |
| **P0.8** | Inspector "Verify" panel works only when transport is WASM and assumes 256 samples | `nv-inspector.ts`, `WasmClient.ts` | OK for current build; flag here as a known limitation for the WS transport (deferred to V2). | Document — not a fix. |
| **P0.9** | ~~REPL `proof.export` not implemented~~ | `nv-console.ts` | ✅ Iter E — wires to `client.exportProofBundle()`, triggers a blob download with timestamp filename. |
| **P0.10** | ~~REPL command history is per-component~~ | `nv-console.ts` | ✅ Iter G — moved to `appStore.replHistory` signal, persisted via IndexedDB key `repl-history`. |
## 3. P1 — visible mockup features missing
| # | Gap | Location | Notes |
|---|---|---|---|
| **P1.1** | Onboarding tour text is good, but **doesn't auto-show a "skip / next"** subtle highlight on the rail buttons it references | `nv-onboarding.ts` | Mockup uses spotlight cutouts. Ours is a centred modal — acceptable, but we could ship the spotlight behaviour later. |
| **P1.2** | ~~Density toggle didn't visibly change anything~~ | `main.ts` + `app.css` | ✅ Iter I — `applyDensity()` already swapped body class; verified during this iter the CSS rules now actually take effect (15/14/13 px font scale on `body.density-{comfy,default,compact}`). |
| **P1.3** | `motion-toggle` only flips `body.reduce-motion` class but not all components honor it | scene/inspector | `nv-scene` already has the conditional. Verify B-trace and frame-strip animations stop too. |
| **P1.4** | ~~Scene "stat-card" SNR readout always `—`~~ | `nv-scene.ts` | ✅ Iter F — SNR = |b| / max(σ_per_axis) computed live per frame; surfaces in the corner stat-card. |
| **P1.5** | Inspector `frame-strip-2` from the Frame tab not in our impl | `nv-inspector.ts` | Mockup has a second sparkline strip in the Frame tab; we only ship one. Replicate. |
| **P1.6** | ~~Modals body content was short~~ | `nv-palette.ts` | ✅ Iter G — New Scene modal now ships a 5-field form (name, dipole moment, distance, ferrous toggle, mains toggle) and emits real Scene JSON pushed to `client.loadScene()`. Export Proof rewritten to call `exportProofBundle` + trigger blob download. |
| **P1.7** | ~~Scene drag positions don't persist~~ | `nv-scene.ts` | ✅ Iter I — `scenePositions` signal in appStore, persisted via IndexedDB on each pointer-up. Restored at component connect. |
| **P1.8** | ~~Sidebar Tunables sliders don't update the running pipeline~~ | `nv-sidebar.ts` + `WasmClient.ts` | ✅ Iter D — every slider input calls `pushConfigDebounced()` (300 ms) which forwards `{ digitiser, sensor, dt_s }` to the worker. Worker rebuilds the WasmPipeline with the new config. Verified via REPL log line `config pushed · fs=… f_mod=…`. |
| **P1.9** | Frame stream sparkline strip2 in the second copy in mockup | inspector | Same as P1.5 — verify. |
| **P1.10** | ~~"WASM" pill is read-only~~ | `nv-topbar.ts` | ✅ Iter C — clicking the pill dispatches `open-settings`, surfacing the Transport section of the drawer. |
| **P1.11** | ~~`prefers-reduced-motion` not auto-detected~~ | `main.ts` | ✅ Iter F — `window.matchMedia('(prefers-reduced-motion: reduce)').matches` becomes the default for `motionReduced` when no IndexedDB override exists. |
| **P1.12** | Scene 3D-tilt on pointer move not ported | `nv-scene.ts` | Mockup has `.tilt-stage` perspective transform. Optional polish. |
| **P1.13** | View-overlay "expand panel" not ported | global | Mockup has a `.view-overlay` that expands any inspector panel to full-screen. Defer V2. |
## 4. P2 — accessibility / polish
| # | Gap | Notes |
|---|---|---|
| **P2.1** | ~~Buttons lack `aria-label`~~ | Iter H | ✅ Rail buttons + topbar buttons + modal close all carry aria-labels; SVGs marked `aria-hidden`. |
| **P2.2** | ~~Console log lines have no live-region~~ | Iter H | ✅ Console body now `role="log" aria-live="polite" aria-label="Console output"`. |
| **P2.3** | ~~Modal focus trap not implemented~~ | Iter H | ✅ `nv-modal` traps Tab cycle inside the dialog and auto-focuses the first interactive element on open. |
| **P2.4** | ~~Light-theme `.ink-3` contrast borderline AA~~ | `app.css` | ✅ Iter N — `--ink-3` darkened from `#6b7684` (3.7:1) to `#54606e` (~5.4:1) on light bg, `--ink-4` from `#9ba4b0` to `#7a8390`, line/line-2 firmed. AA-compliant for normal-weight text. |
| **P2.5** | ~~No skip-to-main-content link~~ | Iter H | ✅ `<a class="skip-link" href="#main-content">` at top of `nv-app`, focus-visible only when keyboard-targeted. Main view wrapped in `<main id="main-content" role="main">`. |
| **P2.6** | ~~Keyboard arrow-key scene navigation~~ | `nv-scene.ts` | ✅ Iter N — Tab cycles draggable items, arrows nudge by 8 px (32 with Shift), Esc deselects, position changes persist via `scenePositions`. |
| **P2.7** | Service worker doesn't have `clients.claim()` | Confirm. Ensures new SW activates on next nav. |
| **P2.8** | PWA install prompt is silent | Add an install button (visible only when `beforeinstallprompt` fires). |
## 5. Iteration plan
The dynamic /loop continues with one P0/P1 item per iteration:
| Iter | Focus | Status |
|---|---|---|
| **A** | Functional Ghost Murmur demo (P0.4) | ✅ `runTransient` WASM export + interactive distance/moment sliders + per-tier detectability bars |
| **B** | Scene sim-controls + toolbar (P0.6, P0.7) | ✅ Bottom-right sim controls, top-left zoom/layer toolbar |
| **C** | Topbar seed + WASM pill clicks (P0.5, P1.10) | ✅ Seed modal + transport pill opens Settings drawer |
| **D** | Sidebar tunables wire-through (P1.8) | ✅ Debounced `setConfig` RPC, 300 ms |
| **E** | REPL `proof.export` + history persistence (P0.9, P0.10) | ✅ Blob download + IndexedDB-persisted history |
| **F** | SNR computation + reduce-motion (P1.4, P1.11, P1.3) | ✅ |B|/max(σ) live SNR, prefers-reduced-motion auto-detect |
| **G** | Modal contents (P1.6) | ✅ New-Scene form (5 fields), real Scene JSON push |
| **H** | A11y pass (P2.1P2.5) | ✅ aria-labels, focus trap, role=log, skip link, role=tablist |
| **I** | Density toggle (P1.2) + drag persistence (P1.7) | ✅ Density CSS verified, scenePositions persisted to IndexedDB |
| **J** | UX usability pass | ✅ nv-help center (Quickstart/Glossary/FAQ/Shortcuts/About), 10-step welcome tour, panel descriptions, settings explainers, empty-state hints |
| **K** | Home view | ✅ `<nv-home>` as default landing — hero + 4 quick-jump cards + simplified grid hides power-user panels |
| **L** | WsClient transport | ✅ Full REST + binary WebSocket impl against `nvsim-server`; transport-flip auto-reverify; activated via Settings drawer |
| **M** | App Store live runtime | ✅ 6 simulated apps emit real i32 events against nvsim frame stream; runtime pills (running/simulated/mesh-only); live events feed |
| **N** | Light-theme contrast (P2.4) + keyboard scene nav (P2.6) | ✅ AA-compliant `--ink-3`/`--ink-4`/`--line` palette in light mode; Tab/arrows/Shift-arrow/Esc on scene draggables |
Each iteration ends with: `npx tsc --noEmit` clean → production
build with `NVSIM_BASE=/RuView/nvsim/` → push to `gh-pages/nvsim/`
preserving siblings → `agent-browser` validation including console
errors → commit on `feat/nvsim-pipeline-simulator`.
The acceptance criteria from ADR-092 §11 still apply unchanged. This
ADR augments §11 rather than replacing it — every P0 item is a
prerequisite for declaring §11.1 (faithful UI) green.
## 6. References
- ADR-092 §4.2 — full UI inventory table (the contract).
- ADR-092 §11 — 12 acceptance gates.
- `assets/NVsim Dashboard.zip` — canonical mockup (committed).
- `docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md` — Ghost Murmur source material.
- Live deploy — https://ruvnet.github.io/RuView/nvsim/ (verified: rail buttons functional, witness verifies, App Store catalog renders, onboarding tour works).