diff --git a/dashboard/src/components/nv-app.ts b/dashboard/src/components/nv-app.ts index a1362f78..e4d825f8 100644 --- a/dashboard/src/components/nv-app.ts +++ b/dashboard/src/components/nv-app.ts @@ -102,7 +102,11 @@ export class NvApp extends LitElement { ? html`` : this.view === 'ghost-murmur' ? html`` - : html``} + : this.view === 'inspector' + ? html`` + : this.view === 'witness' + ? html`` + : html``} .card { margin-bottom: 0; } + @media (max-width: 1024px) { + :host([expanded]) .grid-2 { grid-template-columns: 1fr; } + } .tabs { display: flex; border-bottom: 1px solid var(--line); } @@ -160,6 +201,27 @@ export class NvInspector extends LitElement { } } + private renderHeader() { + if (!this.expanded) return ''; + const titles: Record = { + signal: 'Signal inspector — live B-vector trace + frame stream', + frame: 'Frame inspector — MagFrame v1 fields + raw bytes', + witness: 'Witness panel — SHA-256 determinism gate', + }; + return html` +

+ ${titles[this.tab]} +

+

+ ${this.tab === 'signal' + ? 'Real-time recovered field-vector and frame-stream sparkline. Both update at the running pipeline\'s frame rate. Use the Tunables panel in the sidebar to change f_s, f_mod, dt, and shot-noise behaviour.' + : this.tab === 'frame' + ? 'Decoded view of the most recent MagFrame: typed fields plus the raw 60-byte little-endian binary record (magic 0xC51A_6E70).' + : 'Re-derive the SHA-256 witness for the canonical reference scene (seed=42, N=256) right now in your browser and compare against Proof::EXPECTED_WITNESS_HEX. Same inputs → same hash, byte-for-byte, across every machine and transport.'} +

+ `; + } + private renderSignalTab() { const W = 320, H = 130, cy = 65, scale = 22; const cap = 200; @@ -173,27 +235,43 @@ export class NvInspector extends LitElement { return p; }; - return html` -
-
- B-vector trace - 3-axis · nT -
- - - ${svg``} - ${svg``} - ${svg``} - -
+ const b = lastB.value; + const bnT = [b[0] * 1e9, b[1] * 1e9, b[2] * 1e9]; -
-
- Frame stream - live + return html` +
+
+
+ B-vector trace + 3-axis · nT +
+ + + ${svg``} + ${svg``} + ${svg``} + + ${this.expanded ? html`
+ x: ${bnT[0].toFixed(3)} nT + y: ${bnT[1].toFixed(3)} nT + z: ${bnT[2].toFixed(3)} nT + |B| ${(bMag.value * 1e9).toFixed(3)} nT +
` : ''}
-
- ${stripBars.value.map((v) => html`
`)} + +
+
+ Frame stream + live +
+
+ ${stripBars.value.map((v) => html`
`)} +
+ ${this.expanded ? html` +
+ frames in window: ${stripBars.value.length} + noise floor: ${lastFrame.value ? lastFrame.value.noiseFloorPtSqrtHz.toFixed(2) + ' pT/√Hz' : '—'} +
` : ''}
`; @@ -208,6 +286,7 @@ export class NvInspector extends LitElement { hex = arr.slice(0, 60).join(' '); } return html` +
MagFrame v1 fields @@ -232,6 +311,11 @@ export class NvInspector extends LitElement { LE
${hex || '—'}
+ ${this.expanded ? html` +
+ Layout (little-endian): magic(u32) version(u16) flags(u16) sensor_id(u16) _reserved(u16) t_us(u64) b_pt[3](f32) sigma_pt[3](f32) noise_floor(f32) temp_K(f32). +
` : ''} +
`; } @@ -244,7 +328,34 @@ export class NvInspector extends LitElement { status === 'ok' ? '✓ Witness verified · determinism gate' : status === 'fail' ? '✗ Witness mismatch · audit required' : 'Verify witness'; + const match = expectedWitness.value && witnessHex.value && expectedWitness.value === witnessHex.value; return html` + ${this.expanded ? html` +
+
+
Reference scene
+
Proof::REFERENCE
+
2 dipoles · 1 loop · 1 ferrous · 1 sensor
+
+
+
Seed
+
0x0000002A
+
canonical Proof::SEED
+
+
+
Sample count
+
256
+
Proof::N_SAMPLES
+
+
+
Status
+
+ ${status === 'ok' ? '✓ matches' : status === 'fail' ? '✗ drift' : status === 'pending' ? '… running' : '— idle'} +
+
${match ? 'byte-equivalent' : 'not yet verified'}
+
+
+ ` : ''}
Expected (Proof::EXPECTED_WITNESS_HEX) @@ -260,17 +371,44 @@ export class NvInspector extends LitElement {
${witnessHex.value || '(not verified yet)'}
+ ${this.expanded ? html` +
+
+ What this verifies + ADR-089 §5 +
+
+

Pressing Verify runs the canonical reference pipeline + (Proof::generate) end-to-end inside this browser's WASM Worker: + scene → Biot-Savart synthesis → material attenuation → NV ensemble → ADC + lock-in → + concatenated MagFrame bytes → SHA-256.

+

If the resulting hash matches the constant pinned at build time + (cc8de9b01b0ff5bd…), every constant — γ_e, D_GS, μ₀, T₂*, contrast, the PRNG + stream, the frame layout, the pipeline ordering — is byte-identical to the published + reference. If it doesn't match, something drifted; the dashboard names which.

+

This is the same regression test that runs in + cargo test -p nvsim — running in your browser, against your own WASM build.

+
+
+ ` : ''} `; } override render() { return html` -
- - - +
+ + +
-
+
+ ${this.renderHeader()} ${this.tab === 'signal' ? this.renderSignalTab() : this.tab === 'frame' ? this.renderFrameTab() : this.renderWitnessTab()}