mirror of
https://github.com/ruvnet/RuView
synced 2026-08-08 20:11:43 +00:00
feat(dashboard): live Ghost Murmur WASM demo + ADR-093 gap analysis
## ADR-093 — dashboard gap analysis (new)
Deep review of the deployed dashboard against ADR-092 §4.2 inventory,
the original mockup at assets/NVsim Dashboard.zip, and live behavior.
Catalogues 21 gaps in 3 priority tiers:
- P0 (10 items): broken/missing functional surface — including the
rail buttons fixed in 4483a88b2 and the Ghost Murmur view.
- P1 (13 items): visible mockup features missing — sim-controls
overlay, scene toolbar, density/motion polish, modal contents.
- P2 (8 items): a11y + polish.
§5 ships a 9-iteration plan (A-I), one P0/P1 item per iteration, with
each iteration ending in build → deploy → agent-browser validation.
## Iteration A: Functional Ghost Murmur demo (P0.4)
The Ghost Murmur view was a static document. Now it ships a "Try it
yourself" section that drives the *real* nvsim Rust pipeline via WASM
when the user moves either slider:
- New `runTransient` export on nvsim WASM — accepts scene_json +
config_json + seed + n_samples, returns recovered |B|, per-axis
sigma, noise floor, frame count, and a SHA-256 witness.
- Threaded through worker.ts → WasmClient → NvsimClient interface.
- Demo UI: distance slider (10 cm → 100 km log scale), heart-dipole
moment slider (10⁻¹⁰ → 10⁻⁶ A·m²), live readout of predicted
|B| (closed-form 1/r³) vs recovered |B| (full pipeline) vs noise
floor, per-tier detectability bars (NV-ensemble lab, COTS DNV-B1,
SQUID, 60 GHz mmWave, WiFi CSI) with verdict pills, and an overall
press-physics-vs-real verdict.
- Transient witness shown so users can see byte-equivalent
determinism per (scene, config, seed) selection.
Validated end-to-end:
- agent-browser drove the slider and ran the demo on localhost
- predicted=501 fT, recovered=2.07 nT (ADC quant-floor at 10 cm with
COTS sensor, exactly the physics the spec teaches), 64 frames,
witness 1834ff374b839ec8…
- per-tier bars correctly show "NV-DNV-B1 6.0e+2× too weak" at 10 cm
with cardiac-strength dipole — vindicates the spec's central thesis
Live at https://ruvnet.github.io/RuView/nvsim/ → Ghost Murmur tab.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -158,3 +158,78 @@ pub fn reference_witness() -> Result<js_sys::Uint8Array, JsValue> {
|
||||
arr.copy_from(&bytes);
|
||||
Ok(arr)
|
||||
}
|
||||
|
||||
/// One-shot pipeline run that doesn't disturb the dashboard's main
|
||||
/// pipeline. Used by the Ghost Murmur interactive demo (and any other
|
||||
/// "run-against-this-scene-please" flow) to ask: given a scene + config,
|
||||
/// what does the NV sensor recover at the origin?
|
||||
///
|
||||
/// Returns a JS object:
|
||||
/// ```js
|
||||
/// {
|
||||
/// bRecoveredT: [number, number, number], // recovered B (Tesla)
|
||||
/// bMagT: number, // |B| (Tesla)
|
||||
/// noiseFloorPtSqrtHz: number, // δB pT/√Hz from this config
|
||||
/// sigmaPt: [number, number, number], // per-axis 1σ noise estimate (pT)
|
||||
/// nFrames: number, // samples actually run
|
||||
/// witnessHex: string // SHA-256 witness for this run
|
||||
/// }
|
||||
/// ```
|
||||
#[wasm_bindgen(js_name = runTransient)]
|
||||
pub fn run_transient(
|
||||
scene_json: &str,
|
||||
config_json: &str,
|
||||
seed: f64,
|
||||
n_samples: usize,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
let scene: crate::scene::Scene =
|
||||
serde_json::from_str(scene_json).map_err(|e| js_err(format!("scene parse: {e}")))?;
|
||||
let config: crate::pipeline::PipelineConfig = serde_json::from_str(config_json)
|
||||
.map_err(|e| js_err(format!("config parse: {e}")))?;
|
||||
let pipeline = crate::pipeline::Pipeline::new(scene, config, seed as u64);
|
||||
let (frames, witness) = pipeline.run_with_witness(n_samples);
|
||||
|
||||
// Average the recovered b_pt / sigma over the run for a stable point estimate.
|
||||
let mut sum_b = [0.0_f64; 3];
|
||||
let mut sum_s = [0.0_f64; 3];
|
||||
let mut sum_nf = 0.0_f64;
|
||||
let n = frames.len().max(1) as f64;
|
||||
for f in &frames {
|
||||
for k in 0..3 {
|
||||
sum_b[k] += f.b_pt[k] as f64;
|
||||
sum_s[k] += f.sigma_pt[k] as f64;
|
||||
}
|
||||
sum_nf += f.noise_floor_pt_sqrt_hz as f64;
|
||||
}
|
||||
let avg_b_pt = [sum_b[0] / n, sum_b[1] / n, sum_b[2] / n];
|
||||
let avg_s_pt = [sum_s[0] / n, sum_s[1] / n, sum_s[2] / n];
|
||||
let avg_nf = sum_nf / n;
|
||||
let b_t = [
|
||||
avg_b_pt[0] * 1.0e-12,
|
||||
avg_b_pt[1] * 1.0e-12,
|
||||
avg_b_pt[2] * 1.0e-12,
|
||||
];
|
||||
let bmag_t = (b_t[0] * b_t[0] + b_t[1] * b_t[1] + b_t[2] * b_t[2]).sqrt();
|
||||
|
||||
let obj = js_sys::Object::new();
|
||||
let b_arr = js_sys::Float64Array::new_with_length(3);
|
||||
b_arr.copy_from(&b_t);
|
||||
let s_arr = js_sys::Float64Array::new_with_length(3);
|
||||
s_arr.copy_from(&avg_s_pt);
|
||||
js_sys::Reflect::set(&obj, &JsValue::from_str("bRecoveredT"), &b_arr)?;
|
||||
js_sys::Reflect::set(&obj, &JsValue::from_str("bMagT"), &JsValue::from_f64(bmag_t))?;
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("noiseFloorPtSqrtHz"),
|
||||
&JsValue::from_f64(avg_nf),
|
||||
)?;
|
||||
js_sys::Reflect::set(&obj, &JsValue::from_str("sigmaPt"), &s_arr)?;
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("nFrames"),
|
||||
&JsValue::from_f64(frames.len() as f64),
|
||||
)?;
|
||||
let witness_hex = crate::proof::Proof::hex(&witness);
|
||||
js_sys::Reflect::set(&obj, &JsValue::from_str("witnessHex"), &JsValue::from_str(&witness_hex))?;
|
||||
Ok(obj.into())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user