wifi-densepose/nvsim/assets/index-DPROtCvT.js

2190 lines
143 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import{f as tt,u as at,i as y,a as w,b as c,w as Y}from"./lit-BS7WqYd5.js";import{y as p,g as st,j as f}from"./signals-SG45zFCj.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))i(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const n of r.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&i(n)}).observe(document,{childList:!0,subtree:!0});function a(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(s){if(s.ep)return;s.ep=!0;const r=a(s);fetch(s.href,r)}})();/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/const k=e=>(t,a)=>{a!==void 0?a.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)};/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/const it={attribute:!0,type:String,converter:at,reflect:!1,hasChanged:tt},rt=(e=it,t,a)=>{const{kind:i,metadata:s}=a;let r=globalThis.litPropertyMetadata.get(s);if(r===void 0&&globalThis.litPropertyMetadata.set(s,r=new Map),i==="setter"&&((e=Object.create(e)).wrapped=!0),r.set(a.name,e),i==="accessor"){const{name:n}=a;return{set(o){const l=t.get.call(this);t.set.call(this,o),this.requestUpdate(n,l,e,!0,o)},init(o){return o!==void 0&&this.C(n,void 0,e,o),o}}}if(i==="setter"){const{name:n}=a;return function(o){const l=this[n];t.call(this,o),this.requestUpdate(n,l,e,!0,o)}}throw Error("Unsupported decorator location: "+i)};function Ce(e){return(t,a)=>typeof a=="object"?rt(e,t,a):((i,s,r)=>{const n=s.hasOwnProperty(r);return s.constructor.createProperty(r,i),n?Object.getOwnPropertyDescriptor(s,r):void 0})(e,t,a)}/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/function u(e){return Ce({...e,state:!0,attribute:!1})}/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/const nt=(e,t,a)=>(a.configurable=!0,a.enumerable=!0,Reflect.decorate&&typeof t!="object"&&Object.defineProperty(e,t,a),a);/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/function Ue(e,t){return(a,i,s)=>{const r=n=>n.renderRoot?.querySelector(e)??null;return nt(a,i,{get(){return r(this)}})}}var ot=Object.defineProperty,lt=Object.getOwnPropertyDescriptor,qe=(e,t,a,i)=>{for(var s=i>1?void 0:i?lt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&ot(t,a,s),s};let ye=class extends w{constructor(){super(...arguments),this.view="scene"}navigate(e){this.dispatchEvent(new CustomEvent("navigate",{detail:e}))}render(){return c`
<div class="logo" aria-hidden="true">NV</div>
<nav role="navigation" aria-label="Primary"
style="display:flex; flex-direction:column; align-items:center; gap:4px; flex:1;">
<button class="btn ${this.view==="scene"?"active":""}"
data-id="scene-btn" title="Scene" aria-label="Scene"
aria-current=${this.view==="scene"?"page":"false"}
@click=${()=>this.navigate("scene")}>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2L3 7l9 5 9-5-9-5zm0 13l-9-5v6l9 5 9-5v-6l-9 5z"/></svg>
</button>
<button class="btn ${this.view==="apps"?"active":""}"
data-id="apps-btn" title="App Store" aria-label="App Store"
aria-current=${this.view==="apps"?"page":"false"}
@click=${()=>this.navigate("apps")}>
<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
</button>
<button class="btn ${this.view==="inspector"?"active":""}"
data-id="inspector-btn" title="Inspector" aria-label="Inspector"
aria-current=${this.view==="inspector"?"page":"false"}
@click=${()=>this.navigate("inspector")}>
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.6" y2="16.6"/></svg>
</button>
<button class="btn ${this.view==="witness"?"active":""}"
data-id="witness-btn" title="Witness" aria-label="Witness"
aria-current=${this.view==="witness"?"page":"false"}
@click=${()=>this.navigate("witness")}>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 12l2 2 4-4M21 12c0 4.97-4.03 9-9 9s-9-4.03-9-9 4.03-9 9-9 9 4.03 9 9z"/></svg>
</button>
<button class="btn ghost ${this.view==="ghost-murmur"?"active":""}"
data-id="ghost-murmur-btn" title="Ghost Murmur — research spec"
aria-label="Ghost Murmur research"
aria-current=${this.view==="ghost-murmur"?"page":"false"}
@click=${()=>this.navigate("ghost-murmur")}>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 2C5.7 2 3 4.7 3 8v12l3-2 3 2 3-2 3 2 3-2 3 2V8c0-3.3-2.7-6-6-6H9z"/>
<circle cx="9" cy="10" r="1.2" fill="currentColor"/>
<circle cx="15" cy="10" r="1.2" fill="currentColor"/>
</svg>
</button>
</nav>
<div class="spacer"></div>
<button class="btn" data-id="settings-btn" title="Settings" aria-label="Settings"
@click=${()=>this.dispatchEvent(new CustomEvent("open-settings",{bubbles:!0,composed:!0}))}>
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06A1.65 1.65 0 0015 19.4a1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06A1.65 1.65 0 004.6 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06A1.65 1.65 0 009 4.6a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09A1.65 1.65 0 0015 4.6a1.65 1.65 0 001.82-.33l.06.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
</button>
`}};ye.styles=y`
:host {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 0;
gap: 4px;
background: var(--bg-1);
border-right: 1px solid var(--line);
}
.logo {
width: 36px; height: 36px;
border-radius: 10px;
background: linear-gradient(135deg, oklch(0.78 0.14 70) 0%, oklch(0.55 0.16 30) 100%);
display: grid; place-items: center;
color: #1a0f00;
font-weight: 700;
font-family: var(--mono);
font-size: 11px;
margin-bottom: 14px;
box-shadow: 0 4px 12px -2px oklch(0.55 0.16 30 / 0.35);
}
.btn {
width: 36px; height: 36px;
border-radius: 8px;
background: transparent;
border: 1px solid transparent;
color: var(--ink-3);
display: grid; place-items: center;
transition: all 0.15s;
position: relative;
cursor: pointer;
}
.btn:hover { color: var(--ink); background: var(--bg-2); }
.btn.active {
color: var(--ink);
background: var(--bg-3);
border-color: var(--line-2);
}
.btn.active::before {
content: ''; position: absolute; left: -10px; top: 8px; bottom: 8px;
width: 2px; background: var(--accent); border-radius: 2px;
}
.btn.ghost.active::before { background: var(--accent-3); }
.spacer { flex: 1; }
svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; }
`;qe([Ce()],ye.prototype,"view",2);ye=qe([k("nv-rail")],ye);const T=p("wasm"),Re=p("");p(!1);p(null);const g=p(!1);p(!0);const me=p(1),dt=p(0),Fe=p(0n),L=p(0xCAFEBABEn),q=p(1e4),G=p(1e3),K=p(1),X=p(!0),x=p("dark"),_=p("default"),C=p(!1),be=p(!0),ce=p([0,0,0]),V=p(0),N=p(0),M=p(0),z=p(""),$=p("idle"),R=p(""),B=p(null),we=p([]),ke=p([]),$e=p([]),le=p([]),Ne=p("rebar-walkby-01"),ct=p(""),re=p(!1),Ee=p("all"),W=p([]);function pt(e){const t=W.value.slice();for(t.push(e);t.length>200;)t.shift();W.value=t}const de=p([]),Be=st(()=>T.value==="wasm"?"wasm":"ws");let Ge=null;function ut(e){Ge=e}function h(){return Ge}const A=p([]),vt=200;function d(e,t){if(re.value)return;const a=A.value.slice();for(a.push({ts:Date.now(),level:e,msg:t});a.length>vt;)a.shift();A.value=a}function gt(e){const a=we.value.slice();a.push(e[0]),a.length>200&&a.shift();const i=ke.value.slice();i.push(e[1]),i.length>200&&i.shift();const s=$e.value.slice();s.push(e[2]),s.length>200&&s.shift(),we.value=a,ke.value=i,$e.value=s}function mt(e){const a=le.value.slice();for(a.push(Math.max(0,Math.min(1,e)));a.length>48;)a.shift();le.value=a}var bt=Object.defineProperty,ht=Object.getOwnPropertyDescriptor,ve=(e,t,a,i)=>{for(var s=i>1?void 0:i?ht(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&bt(t,a,s),s};let j=class extends w{constructor(){super(...arguments),this.open=!1,this.mTitle="",this.mBody="",this.buttons=[],this.onModal=e=>{const t=e.detail;this.mTitle=t.title,this.mBody=t.body,this.buttons=t.buttons??[{label:"Close",variant:"primary"}],this.open=!0,this.setAttribute("open",""),requestAnimationFrame(()=>{const a=this.shadowRoot;if(!a)return;a.querySelector("input, select, textarea, button:not(.close)")?.focus()})},this.onKey=e=>{e.key==="Escape"&&this.open&&this.close()}}connectedCallback(){super.connectedCallback(),window.addEventListener("nv-modal",this.onModal),window.addEventListener("keydown",this.onKey)}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("nv-modal",this.onModal),window.removeEventListener("keydown",this.onKey)}updated(){if(!this.open)return;const e=this.shadowRoot;if(!e)return;const t=a=>{if(a.key!=="Tab")return;const i=Array.from(e.querySelectorAll("input, select, textarea, button, [href]")).filter(o=>!o.hasAttribute("disabled"));if(i.length===0)return;const s=i[0],r=i[i.length-1],n=e.activeElement??null;a.shiftKey&&n===s?(a.preventDefault(),r.focus()):!a.shiftKey&&n===r&&(a.preventDefault(),s.focus())};e.removeEventListener("keydown",t),e.addEventListener("keydown",t)}close(){this.open=!1,this.removeAttribute("open")}clickBtn(e){e.onClick?.(),this.close()}render(){return c`
<div class="modal" role="dialog" aria-modal="true">
<div class="h">
<div class="ttl">${this.mTitle}</div>
<button class="close" @click=${()=>this.close()}>×</button>
</div>
<div class="body" .innerHTML=${this.mBody}></div>
<div class="f">
${this.buttons.map(e=>c`
<button class=${e.variant??""} @click=${()=>this.clickBtn(e)}>${e.label}</button>
`)}
</div>
</div>
`}};j.styles=y`
:host {
position: fixed; inset: 0;
background: rgba(0,0,0,0.55);
backdrop-filter: blur(4px);
z-index: 200;
display: grid; place-items: center;
opacity: 0; pointer-events: none;
transition: opacity 0.18s;
}
:host([open]) { opacity: 1; pointer-events: auto; }
.modal {
background: var(--bg-1);
border: 1px solid var(--line-2);
border-radius: var(--radius);
box-shadow: 0 30px 80px -20px rgba(0,0,0,0.7);
width: min(520px, 92vw);
max-height: 86vh;
display: flex; flex-direction: column;
transform: translateY(12px) scale(0.98);
transition: transform 0.22s cubic-bezier(0.2,0.7,0.3,1);
}
:host([open]) .modal { transform: translateY(0) scale(1); }
.h {
padding: 14px 16px;
border-bottom: 1px solid var(--line);
display: flex; align-items: center; justify-content: space-between;
}
.h .ttl { font-size: 14px; font-weight: 600; }
.body { padding: 16px; overflow-y: auto; font-size: 13px; color: var(--ink-2); line-height: 1.55; }
.f {
padding: 12px 16px;
border-top: 1px solid var(--line);
display: flex; gap: 8px; justify-content: flex-end;
}
button {
padding: 6px 12px;
border-radius: 8px;
font-size: 12.5px;
cursor: pointer;
font-family: inherit;
border: 1px solid var(--line);
background: var(--bg-2); color: var(--ink);
}
button.ghost { background: transparent; }
button.primary { background: var(--accent); border-color: var(--accent); color: #1a0f00; }
button.danger { background: var(--bad); border-color: var(--bad); color: #fff; }
.close {
width: 28px; height: 28px;
background: transparent; border: 1px solid var(--line);
border-radius: 6px;
color: var(--ink-2);
}
`;ve([u()],j.prototype,"open",2);ve([u()],j.prototype,"mTitle",2);ve([u()],j.prototype,"mBody",2);ve([u()],j.prototype,"buttons",2);j=ve([k("nv-modal")],j);function ne(e){window.dispatchEvent(new CustomEvent("nv-modal",{detail:e}))}var ft=Object.defineProperty,xt=Object.getOwnPropertyDescriptor,Pe=(e,t,a,i)=>{for(var s=i>1?void 0:i?xt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&ft(t,a,s),s};let J=class extends w{constructor(){super(...arguments),this.visible=!1,this.msg="",this.icon="✓",this.timer=null,this.onToast=e=>{const t=e.detail;this.msg=t.msg??"Done",this.icon=t.icon??"✓",this.visible=!0,this.setAttribute("visible",""),this.timer!==null&&window.clearTimeout(this.timer),this.timer=window.setTimeout(()=>{this.visible=!1,this.removeAttribute("visible")},1800)}}connectedCallback(){super.connectedCallback(),window.addEventListener("nv-toast",this.onToast)}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("nv-toast",this.onToast)}render(){return c`<span class="icon">${this.icon}</span><span>${this.msg}</span>`}};J.styles=y`
:host {
position: fixed; bottom: 24px; left: 50%;
transform: translateX(-50%) translateY(80px);
background: var(--bg-2);
border: 1px solid var(--line-2);
border-radius: var(--radius);
padding: 10px 14px;
font-size: 12.5px;
box-shadow: var(--shadow);
z-index: 100;
opacity: 0; pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
display: flex; align-items: center; gap: 8px;
}
:host([visible]) {
opacity: 1;
transform: translateX(-50%) translateY(0);
pointer-events: auto;
}
.icon { color: var(--accent); }
`;Pe([u()],J.prototype,"visible",2);Pe([u()],J.prototype,"msg",2);Pe([u()],J.prototype,"icon",2);J=Pe([k("nv-toast")],J);function P(e,t="✓"){window.dispatchEvent(new CustomEvent("nv-toast",{detail:{msg:e,icon:t}}))}var yt=Object.getOwnPropertyDescriptor,wt=(e,t,a,i)=>{for(var s=i>1?void 0:i?yt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=n(s)||s);return s};let Oe=class extends w{connectedCallback(){super.connectedCallback(),f(()=>{M.value,Be.value,L.value,x.value,Ne.value,g.value,this.requestUpdate()})}async toggleRun(){const e=h();e&&(g.value?(await e.pause(),g.value=!1):(await e.run(),g.value=!0))}async reset(){const e=h();e&&await e.reset()}toggleTheme(){x.value=x.value==="dark"?"light":"dark"}async openSeedModal(){const e=`0x${L.value.toString(16).toUpperCase().padStart(8,"0")}`;ne({title:"Set seed",body:`<p>Set the 32-bit hex seed for the shot-noise PRNG. Same <code>(scene, config, seed)</code> → byte-identical witness.</p>
<label>Hex seed</label>
<input type="text" id="seed-input" value="${e}" autofocus />`,buttons:[{label:"Cancel",variant:"ghost"},{label:"Apply",variant:"primary",onClick:async()=>{const t=document.querySelector("nv-modal")?.shadowRoot?.querySelector("#seed-input");if(!t)return;const a=t.value.trim().replace(/^0x/i,""),i=BigInt("0x"+a);L.value=i,await h()?.setSeed(i),d("ok",`seed → 0x${i.toString(16).toUpperCase()}`),P(`Seed → 0x${i.toString(16).toUpperCase().slice(0,8)}`,"⟳")}}]})}openTransportSettings(){window.dispatchEvent(new CustomEvent("open-settings"))}render(){const e=L.value.toString(16).toUpperCase().padStart(8,"0");return c`
<div class="crumbs">
<span class="home">RuView</span><span class="sep">/</span>
<span>nvsim</span><span class="sep">/</span>
<span class="cur" id="scene-name">${Ne.value}</span>
</div>
<div class="spacer"></div>
<span class="pill" id="fps-pill">
<span class="dot"></span>
<span id="fps-val">${M.value>0?(M.value/1e3).toFixed(2)+" kHz":"idle"}</span>
</span>
<span class="pill wasm" id="transport-pill" title="Transport settings"
@click=${this.openTransportSettings}>
<span class="dot"></span>${Be.value}
</span>
<span class="pill seed" id="seed-pill" title="Set seed"
@click=${this.openSeedModal}>
seed: <b>0x${e}</b>
</span>
<button class="ghost" id="theme-btn" title="Toggle theme" @click=${this.toggleTheme}>
${x.value==="dark"?"☼":"☾"}
</button>
<button id="reset-btn" @click=${this.reset}>↺ Reset</button>
<button class="primary" id="run-btn" @click=${this.toggleRun}>
${g.value?"❚❚ Pause":"▶ Run"}
</button>
`}};Oe.styles=y`
:host {
display: flex; align-items: center;
padding: 0 16px; gap: 12px;
background: var(--bg-1);
border-bottom: 1px solid var(--line);
z-index: 10;
}
.crumbs { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-3); }
.crumbs .sep { color: var(--ink-4); }
.crumbs .cur { color: var(--ink); font-weight: 500; }
.spacer { flex: 1; }
.pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 5px 10px;
background: var(--bg-2); border: 1px solid var(--line);
border-radius: 999px;
font-size: 12px; color: var(--ink-2);
font-family: var(--mono); font-weight: 500;
}
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 6px var(--ok); animation: pulse 2s infinite; }
.pill.wasm .dot { background: var(--accent-2); box-shadow: 0 0 6px var(--accent-2); }
.pill.seed { color: var(--ink-3); cursor: pointer; }
.pill.seed:hover { border-color: var(--line-2); }
.pill.seed b { color: var(--accent); font-weight: 600; }
.pill.wasm { cursor: pointer; }
.pill.wasm:hover { border-color: var(--line-2); }
button {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 12px;
background: var(--bg-2); border: 1px solid var(--line);
border-radius: 8px;
font-size: 12.5px; font-weight: 500; color: var(--ink);
cursor: pointer;
transition: all 0.15s;
}
button:hover { border-color: var(--line-2); background: var(--bg-3); }
button.primary { background: var(--accent); border-color: var(--accent); color: #1a0f00; }
button.primary:hover { filter: brightness(1.08); }
button.ghost { background: transparent; }
`;Oe=wt([k("nv-topbar")],Oe);var kt=Object.getOwnPropertyDescriptor,$t=(e,t,a,i)=>{for(var s=i>1?void 0:i?kt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=n(s)||s);return s};let De=null;function he(){De!==null&&window.clearTimeout(De),De=window.setTimeout(async()=>{const e=h();if(e)try{await e.setConfig({digitiser:{f_s_hz:q.value,f_mod_hz:G.value},sensor:{gamma_fwhm_hz:1e6,t1_s:.005,t2_s:1e-6,t2_star_s:2e-7,contrast:.03,n_spins:1e12,shot_noise_disabled:!X.value},dt_s:K.value*.001}),d("dbg",`config pushed · fs=${q.value} f_mod=${G.value} dt=${K.value.toFixed(1)}ms noise=${X.value?"on":"off"}`)}catch(t){d("warn",`config push failed: ${t.message}`)}},300)}let Ie=class extends w{connectedCallback(){super.connectedCallback(),f(()=>{q.value,G.value,K.value,X.value,g.value,this.requestUpdate()})}render(){return c`
<div class="panel">
<div class="panel-h">Scene <span class="count">4 sources</span></div>
<div class="scene-item">
<span class="swatch" style="background:oklch(0.72 0.18 330)"></span>
<span class="name">rebar.steel.coil</span>
<span class="meta">χ=5000</span>
</div>
<div class="scene-item">
<span class="swatch" style="background:oklch(0.78 0.14 195)"></span>
<span class="name">heart_proxy</span>
<span class="meta">1e-6 A·m²</span>
</div>
<div class="scene-item">
<span class="swatch" style="background:oklch(0.72 0.18 330)"></span>
<span class="name">mains_60Hz</span>
<span class="meta">2 A · 60 Hz</span>
</div>
<div class="scene-item">
<span class="swatch" style="background:oklch(0.78 0.14 145)"></span>
<span class="name">door.steel</span>
<span class="meta">eddy</span>
</div>
</div>
<div class="panel">
<div class="panel-h">NV sensor <span class="count">COTS</span></div>
<div class="field-row"><span class="lbl">V</span><span class="val">1 mm³</span></div>
<div class="field-row"><span class="lbl">N</span><span class="val">1e12 NV</span></div>
<div class="field-row"><span class="lbl">C</span><span class="val">0.030</span></div>
<div class="field-row"><span class="lbl">T₂*</span><span class="val">200 ns</span></div>
<div class="field-row"><span class="lbl">δB</span><span class="val">1.18 pT/√Hz</span></div>
</div>
<div class="panel">
<div class="panel-h">Tunables</div>
<div class="slider-row">
<div class="top"><span class="lbl">Sample rate</span><span class="val">${(q.value/1e3).toFixed(1)} kHz</span></div>
<input type="range" min="1000" max="100000" .value=${String(q.value)}
@input=${e=>{q.value=+e.target.value,he()}} />
</div>
<div class="slider-row">
<div class="top"><span class="lbl">Lockin f_mod</span><span class="val">${(G.value/1e3).toFixed(3)} kHz</span></div>
<input type="range" min="100" max="5000" .value=${String(G.value)}
@input=${e=>{G.value=+e.target.value,he()}} />
</div>
<div class="slider-row">
<div class="top"><span class="lbl">Integration t</span><span class="val">${K.value.toFixed(1)} ms</span></div>
<input type="range" min="0.1" max="10" step="0.1" .value=${String(K.value)}
@input=${e=>{K.value=+e.target.value,he()}} />
</div>
<div class="slider-row">
<div class="top"><span class="lbl">Shot noise</span><span class="val">${X.value?"ON":"OFF"}</span></div>
<input type="range" min="0" max="1" .value=${X.value?"1":"0"}
@input=${e=>{X.value=e.target.value==="1",he()}} />
</div>
</div>
<div class="panel">
<div class="panel-h">Pipeline</div>
<div class="pipeline">
<span class="stage ${g.value?"live":""}">scene</span>
<span class="stage-arrow">→</span>
<span class="stage ${g.value?"live":""}">B-S</span>
<span class="stage-arrow">→</span>
<span class="stage ${g.value?"live":""}">prop</span>
<span class="stage-arrow">→</span>
<span class="stage ${g.value?"live":""}">NV</span>
<span class="stage-arrow">→</span>
<span class="stage ${g.value?"live":""}">ADC</span>
<span class="stage-arrow">→</span>
<span class="stage ${g.value?"live":""}">frame</span>
</div>
</div>
`}};Ie.styles=y`
:host {
display: flex; flex-direction: column; gap: 14px;
padding: 14px; overflow-y: auto;
background: var(--bg-1); border-right: 1px solid var(--line);
}
.panel {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius); padding: 12px;
}
.panel-h {
display: flex; align-items: center; justify-content: space-between;
font-size: 11px; font-weight: 600; color: var(--ink-3);
text-transform: uppercase; letter-spacing: 0.08em;
margin-bottom: 10px;
}
.count {
background: var(--bg-3); color: var(--ink-2);
padding: 1px 6px; border-radius: 999px;
font-family: var(--mono); font-size: 10px;
text-transform: none; letter-spacing: 0;
}
.scene-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.15s;
border: 1px solid transparent;
}
.scene-item:hover { background: var(--bg-3); }
.scene-item .swatch { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.scene-item .name { font-size: 13px; flex: 1; }
.scene-item .meta { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); }
.field-row {
display: flex; align-items: center; justify-content: space-between;
padding: 6px 0; font-size: 12.5px;
border-bottom: 1px solid var(--line);
}
.field-row:last-child { border-bottom: 0; }
.field-row .lbl { color: var(--ink-3); }
.field-row .val { font-family: var(--mono); color: var(--ink); font-size: 12px; }
.slider-row { padding: 8px 0; border-bottom: 1px solid var(--line); }
.slider-row:last-child { border-bottom: 0; padding-bottom: 0; }
.slider-row .top { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 12px; }
.slider-row .top .lbl { color: var(--ink-3); }
.slider-row .top .val { font-family: var(--mono); color: var(--ink); }
input[type="range"] {
-webkit-appearance: none; appearance: none;
width: 100%; height: 4px;
background: var(--bg-3); border-radius: 2px; outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 14px; height: 14px; border-radius: 50%;
background: var(--accent); cursor: pointer;
border: 2px solid var(--bg-2);
box-shadow: 0 0 0 1px var(--line-2);
}
.pipeline { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; margin-top: 6px; }
.stage {
flex: 1; min-width: 50px;
padding: 4px 6px;
background: var(--bg-3); border: 1px solid var(--line);
border-radius: 6px; font-size: 9.5px; text-align: center;
color: var(--ink-2); font-family: var(--mono);
}
.stage.live { border-color: var(--accent-2); color: var(--accent-2); }
.stage-arrow { color: var(--ink-4); font-size: 10px; }
`;Ie=$t([k("nv-sidebar")],Ie);var St=Object.defineProperty,_t=Object.getOwnPropertyDescriptor,ee=(e,t,a,i)=>{for(var s=i>1?void 0:i?_t(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&St(t,a,s),s};let E=class extends w{constructor(){super(...arguments),this.zoom=1,this.layerVisible={source:!0,field:!0,label:!0},this.items=[{id:"rebar",x:740,y:240,color:"oklch(0.72 0.18 330)",name:"rebar.steel"},{id:"heart",x:220,y:180,color:"oklch(0.78 0.14 195)",name:"heart_proxy"},{id:"mains",x:180,y:380,color:"oklch(0.72 0.18 330)",name:"mains_60Hz"},{id:"door",x:800,y:470,color:"oklch(0.78 0.14 145)",name:"door.steel"}],this.dragging=null,this.selected=null,this.dragOffset={dx:0,dy:0},this.onDown=(e,t)=>{t.preventDefault(),this.dragging=e,this.selected=e;const a=this.items.find(r=>r.id===e);if(!a)return;const i=this.renderRoot.querySelector("svg");if(!i)return;const s=this.toSvg(t,i);this.dragOffset={dx:s.x-a.x,dy:s.y-a.y}},this.onPointerMove=e=>{if(!this.dragging)return;const t=this.renderRoot.querySelector("svg");if(!t)return;const a=this.toSvg(e,t);this.items=this.items.map(i=>i.id===this.dragging?{...i,x:a.x-this.dragOffset.dx,y:a.y-this.dragOffset.dy}:i)},this.onPointerUp=()=>{this.dragging&&(de.value=this.items.map(({id:e,x:t,y:a})=>({id:e,x:t,y:a}))),this.dragging=null}}connectedCallback(){super.connectedCallback(),de.value.length>0&&(this.items=this.items.map(e=>{const t=de.value.find(a=>a.id===e.id);return t?{...e,x:t.x,y:t.y}:e})),f(()=>{ce.value,V.value,M.value,N.value,C.value,g.value,me.value,B.value,this.requestUpdate()}),f(()=>{const e=B.value;if(!e)return;const t=Math.sqrt(e.bPt[0]**2+e.bPt[1]**2+e.bPt[2]**2),a=Math.max(Math.abs(e.sigmaPt[0]),Math.abs(e.sigmaPt[1]),Math.abs(e.sigmaPt[2]),.001),i=t/a;Number.isFinite(i)&&(N.value=i)}),window.addEventListener("pointermove",this.onPointerMove),window.addEventListener("pointerup",this.onPointerUp)}async toggleRun(){const e=h();e&&(g.value?(await e.pause(),g.value=!1):(await e.run(),g.value=!0))}async stepFwd(){const e=h();e&&(await e.step("fwd",10),d("dbg","sim step → +1 frame"))}async stepBack(){const e=h();e&&(await e.step("back",10),d("dbg","sim step ← -1 frame"))}cycleSpeed(){const e=[.25,.5,1,2,4],t=e.indexOf(me.value);me.value=e[(t+1)%e.length]}zoomIn(){this.zoom=Math.min(2.5,this.zoom*1.2)}zoomOut(){this.zoom=Math.max(.5,this.zoom/1.2)}fitView(){this.zoom=1}toggleLayer(e){this.layerVisible={...this.layerVisible,[e]:!this.layerVisible[e]}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("pointermove",this.onPointerMove),window.removeEventListener("pointerup",this.onPointerUp)}toSvg(e,t){const a=t.getBoundingClientRect(),i=(e.clientX-a.left)/a.width*1e3,s=(e.clientY-a.top)/a.height*600;return{x:i,y:s}}render(){const e=ce.value,t=[e[0]*1e9,e[1]*1e9,e[2]*1e9],a=V.value*1e9,i=C.value?"":"anim",s=1e3/this.zoom,r=600/this.zoom,n=(1e3-s)/2,o=(600-r)/2;return c`
<div class="grid"></div>
<svg viewBox="${n.toFixed(1)} ${o.toFixed(1)} ${s.toFixed(1)} ${r.toFixed(1)}"
preserveAspectRatio="xMidYMid meet" id="scene-svg">
<defs>
<radialGradient id="g-sensor" cx="50%" cy="50%" r="50%">
<stop offset="0" stop-color="oklch(0.78 0.14 70)" stop-opacity="0.4"/>
<stop offset="1" stop-color="oklch(0.78 0.14 70)" stop-opacity="0"/>
</radialGradient>
<filter id="glow"><feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
</defs>
<!-- Field lines from each source to sensor -->
${this.layerVisible.field?this.items.map(l=>Y`
<line class="field-line ${i}" x1=${l.x} y1=${l.y}
x2="500" y2="320"
stroke=${l.color} stroke-width="1" stroke-opacity="0.5"/>
`):""}
<!-- Source primitives -->
${this.layerVisible.source?this.items.map(l=>Y`
<g class=${`draggable ${this.dragging===l.id?"dragging":""} ${this.selected===l.id?"selected":""}`}
data-id=${l.id} data-source-id=${l.id}
transform=${`translate(${l.x.toFixed(0)},${l.y.toFixed(0)})`}
@pointerdown=${v=>this.onDown(l.id,v)}>
<ellipse cx="0" cy="0" rx="32" ry="22" fill=${l.color} fill-opacity="0.18"
stroke=${l.color} stroke-width="1.2"/>
<circle cx="0" cy="0" r="4" fill=${l.color}/>
${this.layerVisible.label?Y`<text class="label" x="0" y="40" text-anchor="middle">${l.name}</text>`:""}
</g>
`):""}
<!-- Sensor (NV diamond) at center -->
<g id="sensor-g" class="draggable" data-id="sensor" transform="translate(500, 320)">
<circle cx="0" cy="0" r="46" fill="url(#g-sensor)"/>
<g class=${`crystal ${i}`} stroke="oklch(0.78 0.14 70)" stroke-width="2"
fill="oklch(0.78 0.14 70 / 0.08)" filter="url(#glow)">
<polygon points="0,-22 19,-7 12,18 -12,18 -19,-7"/>
</g>
<circle cx="0" cy="0" r="3" fill="var(--accent)"/>
<text class="label" x="0" y="56" text-anchor="middle">
sensor · 〈111〉 NV
</text>
<text class="label" x="0" y="72" text-anchor="middle">
B_in: <tspan fill="var(--accent)" id="b-in-svg">[${t[0].toFixed(2)}, ${t[1].toFixed(2)}, ${t[2].toFixed(2)}] nT</tspan>
</text>
</g>
</svg>
<div class="scene-toolbar" id="scene-toolbar">
<button id="zoom-in-btn" title="Zoom in" @click=${this.zoomIn}>+</button>
<button id="zoom-out-btn" title="Zoom out" @click=${this.zoomOut}></button>
<button id="fit-btn" title="Fit to view" @click=${this.fitView}>⊡</button>
<button id="layer-source-btn" class=${this.layerVisible.source?"on":""}
title="Sources" @click=${()=>this.toggleLayer("source")}>●</button>
<button id="layer-field-btn" class=${this.layerVisible.field?"on":""}
title="Field lines" @click=${()=>this.toggleLayer("field")}>≈</button>
<button id="layer-label-btn" class=${this.layerVisible.label?"on":""}
title="Labels" @click=${()=>this.toggleLayer("label")}>T</button>
</div>
<div class="sim-controls" id="sim-controls">
<button class="step" id="step-back-btn" title="Step back" @click=${this.stepBack}>⏮</button>
<button class="play" id="play-btn" title="Play / pause" @click=${this.toggleRun}>
${g.value?"❚❚":"▶"}
</button>
<button class="step" id="step-fwd-btn" title="Step forward" @click=${this.stepFwd}>⏭</button>
<span class="speed" id="speed-val" title="Cycle speed" @click=${this.cycleSpeed}>${me.value}×</span>
</div>
<div class="scene-readout">
<div class="stat-card">
<div class="lbl">|B|</div>
<div class="val amber" id="bmag-readout">${a.toFixed(3)} nT</div>
</div>
<div class="stat-card">
<div class="lbl">FPS</div>
<div class="val cyan" id="fps-readout">${M.value>0?Math.round(M.value):"—"}</div>
</div>
<div class="stat-card">
<div class="lbl">SNR</div>
<div class="val mint" id="snr-readout">${N.value>0?N.value.toFixed(1):"—"}</div>
</div>
</div>
`}};E.styles=y`
:host {
display: block; height: 100%; width: 100%;
background: radial-gradient(ellipse at 50% 30%, var(--bg-2) 0%, var(--bg-0) 70%);
position: relative; overflow: hidden;
border-bottom: 1px solid var(--line);
}
.grid {
position: absolute; inset: 0;
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size: 32px 32px;
pointer-events: none;
mask-image: radial-gradient(ellipse at center, black 40%, transparent 100%);
}
svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.stat-card {
background: rgba(13,17,23,0.7);
backdrop-filter: blur(8px);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 8px 12px;
font-size: 11px;
min-width: 96px;
}
[data-theme="light"] .stat-card { background: rgba(255,255,255,0.85); }
.stat-card .lbl {
color: var(--ink-3);
text-transform: uppercase; font-weight: 600; letter-spacing: 0.06em; font-size: 9.5px;
}
.stat-card .val { font-family: var(--mono); font-size: 16px; font-weight: 600; margin-top: 2px; }
.stat-card .val.amber { color: var(--accent); }
.stat-card .val.cyan { color: var(--accent-2); }
.stat-card .val.mint { color: var(--accent-4); }
.scene-readout {
position: absolute; top: 14px; right: 14px;
display: flex; gap: 8px; z-index: 5;
}
.draggable { cursor: grab; transition: filter 0.15s; }
.draggable:hover { filter: brightness(1.15) drop-shadow(0 0 6px currentColor); }
.draggable.dragging { cursor: grabbing; filter: brightness(1.25) drop-shadow(0 0 10px currentColor); }
.field-line { stroke-dasharray: 4 6; }
@keyframes dash { to { stroke-dashoffset: -200; } }
.field-line.anim { animation: dash 4s linear infinite; }
@keyframes spin {
0% { transform: rotateY(0) rotateX(8deg); }
100% { transform: rotateY(360deg) rotateX(8deg); }
}
.crystal { transform-origin: center; transform-box: fill-box; }
.crystal.anim { animation: spin 12s linear infinite; }
.label {
font-family: var(--mono); font-size: 11px; fill: var(--ink-2);
pointer-events: none;
}
.scene-toolbar {
position: absolute; top: 14px; left: 14px;
display: flex; gap: 6px; z-index: 5;
background: rgba(13,17,23,0.85);
backdrop-filter: blur(8px);
border: 1px solid var(--line);
border-radius: 8px;
padding: 4px;
}
[data-theme="light"] .scene-toolbar { background: rgba(255,255,255,0.85); }
.scene-toolbar button {
width: 28px; height: 28px;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
color: var(--ink-2);
cursor: pointer;
display: grid; place-items: center;
font-size: 13px;
}
.scene-toolbar button:hover { color: var(--ink); background: var(--bg-2); }
.scene-toolbar button.on { background: var(--bg-3); color: var(--accent); border-color: var(--line-2); }
.sim-controls {
position: absolute; bottom: 14px; right: 14px;
display: flex; gap: 6px; align-items: center;
background: rgba(13,17,23,0.85);
backdrop-filter: blur(12px);
border: 1px solid var(--line-2);
border-radius: 999px;
padding: 6px 10px;
z-index: 5;
}
[data-theme="light"] .sim-controls { background: rgba(255,255,255,0.92); }
.sim-controls .play {
width: 32px; height: 32px;
background: var(--accent);
border: none;
border-radius: 50%;
color: #1a0f00;
cursor: pointer;
display: grid; place-items: center;
font-size: 13px;
}
.sim-controls .play:hover { filter: brightness(1.08); }
.sim-controls .step {
width: 26px; height: 26px;
border-radius: 6px;
background: transparent;
color: var(--ink-2);
border: 1px solid var(--line);
cursor: pointer;
font-size: 11px;
}
.sim-controls .step:hover { color: var(--ink); border-color: var(--line-2); }
.sim-controls .speed {
font-family: var(--mono); font-size: 11px;
color: var(--ink-2);
padding: 0 6px;
min-width: 36px;
text-align: center;
cursor: pointer;
}
`;ee([u()],E.prototype,"zoom",2);ee([u()],E.prototype,"layerVisible",2);ee([u()],E.prototype,"items",2);ee([u()],E.prototype,"dragging",2);ee([u()],E.prototype,"selected",2);E=ee([k("nv-scene")],E);var zt=Object.defineProperty,Mt=Object.getOwnPropertyDescriptor,Te=(e,t,a,i)=>{for(var s=i>1?void 0:i?Mt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&zt(t,a,s),s};let Q=class extends w{constructor(){super(...arguments),this.tab="signal",this.pinTab=null,this.expanded=!1}connectedCallback(){super.connectedCallback(),f(()=>{we.value,ke.value,$e.value,le.value,B.value,z.value,$.value,ce.value,V.value,this.requestUpdate()})}willUpdate(e){e.has("pinTab")&&this.pinTab&&this.tab!==this.pinTab&&(this.tab=this.pinTab)}async verify(){const e=h();if(e){$.value="pending",d("info","verifying witness over 256 frames…");try{const t=R.value,a=new Uint8Array(32);for(let s=0;s<32;s++)a[s]=parseInt(t.slice(s*2,s*2+2),16);const i=await e.verifyWitness(a);if(i.ok)$.value="ok",z.value=t,d("ok",`witness ${t.slice(0,16)}… matches · determinism gate ✓`);else{$.value="fail";const s=Array.from(i.actual).map(r=>r.toString(16).padStart(2,"0")).join("");z.value=s,d("err",`WITNESS MISMATCH actual=${s.slice(0,16)}`)}}catch(t){$.value="fail",d("err",`verify failed: ${t.message}`)}}}renderHeader(){return this.expanded?c`
<h1 style="margin: 8px 0 14px; font-size: 20px; letter-spacing: -0.01em;">
${{signal:"Signal inspector — live B-vector trace + frame stream",frame:"Frame inspector — MagFrame v1 fields + raw bytes",witness:"Witness panel — SHA-256 determinism gate"}[this.tab]}
</h1>
<p style="margin: 0 0 18px; font-size: 12.5px; color: var(--ink-3); line-height: 1.55; max-width: 780px;">
${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."}
</p>
`:""}renderSignalTab(){const r=l=>{let v="";return l.forEach((m,b)=>{const ae=b/Math.max(1,199)*320,Ae=65-m*22;v+=(b===0?"M":"L")+` ${ae.toFixed(1)} ${Ae.toFixed(1)} `}),v},n=ce.value,o=[n[0]*1e9,n[1]*1e9,n[2]*1e9];return c`
<div class=${this.expanded?"grid-2":""}>
<div class="card">
<div class="card-h">
<span class="ttl">B-vector trace</span>
<span class="badge">3-axis · nT</span>
</div>
<svg viewBox="0 0 ${320} ${130}" preserveAspectRatio="none">
<line x1="0" y1=${65} x2=${320} y2=${65} stroke="var(--line)" stroke-width="0.5"/>
${Y`<path id="trace-x" d=${r(we.value)} stroke="oklch(0.78 0.14 70)" stroke-width="1.2" fill="none"/>`}
${Y`<path id="trace-y" d=${r(ke.value)} stroke="oklch(0.78 0.12 195)" stroke-width="1.2" fill="none" opacity="0.8"/>`}
${Y`<path id="trace-z" d=${r($e.value)} stroke="oklch(0.72 0.18 330)" stroke-width="1.2" fill="none" opacity="0.7"/>`}
</svg>
${this.expanded?c`<div style="display:flex;gap:14px;font-size:12px;font-family:var(--mono);margin-top:8px;">
<span style="color:oklch(0.78 0.14 70);">x: ${o[0].toFixed(3)} nT</span>
<span style="color:oklch(0.78 0.12 195);">y: ${o[1].toFixed(3)} nT</span>
<span style="color:oklch(0.72 0.18 330);">z: ${o[2].toFixed(3)} nT</span>
<span style="color:var(--accent);margin-left:auto;">|B| ${(V.value*1e9).toFixed(3)} nT</span>
</div>`:""}
</div>
<div class="card">
<div class="card-h">
<span class="ttl">Frame stream</span>
<span class="badge" id="strip-rate">live</span>
</div>
<div class="frame-strip" id="frame-strip">
${le.value.map(l=>c`<div class="bar" style=${`height:${Math.max(4,l*100)}%`}></div>`)}
</div>
${this.expanded?c`
<div style="display:flex;gap:24px;font-family:var(--mono);font-size:12px;color:var(--ink-3);margin-top:12px;">
<span>frames in window: <span style="color:var(--ink);">${le.value.length}</span></span>
<span>noise floor: <span style="color:var(--ink);">${B.value?B.value.noiseFloorPtSqrtHz.toFixed(2)+" pT/√Hz":"—"}</span></span>
</div>`:""}
</div>
</div>
`}renderFrameTab(){const e=B.value,t=e?.raw;let a="";return t&&(a=Array.from(t).map(s=>s.toString(16).padStart(2,"0")).slice(0,60).join(" ")),c`
<div class=${this.expanded?"grid-2":""}>
<div class="card">
<div class="card-h">
<span class="ttl">MagFrame v1 fields</span>
<span class="badge">60 B</span>
</div>
<table>
<tr><td>magic</td><td id="frame-magic">${e?"0x"+e.magic.toString(16).toUpperCase():"—"}</td></tr>
<tr><td>version</td><td>${e?.version??"—"}</td></tr>
<tr><td>flags</td><td>0x${(e?.flags??0).toString(16).padStart(4,"0")}</td></tr>
<tr><td>sensor_id</td><td>${e?.sensorId??"—"}</td></tr>
<tr><td>t_us</td><td>${e?e.tUs.toString():"—"}</td></tr>
<tr><td>b_pT[0]</td><td id="frame-bx">${e?e.bPt[0].toFixed(1):"—"}</td></tr>
<tr><td>b_pT[1]</td><td id="frame-by">${e?e.bPt[1].toFixed(1):"—"}</td></tr>
<tr><td>b_pT[2]</td><td id="frame-bz">${e?e.bPt[2].toFixed(1):"—"}</td></tr>
<tr><td>noise_floor</td><td>${e?e.noiseFloorPtSqrtHz.toFixed(2):"—"}</td></tr>
<tr><td>temp_K</td><td>${e?e.temperatureK.toFixed(1):"—"}</td></tr>
</table>
</div>
<div class="card">
<div class="card-h">
<span class="ttl">Hex dump</span>
<span class="badge">LE</span>
</div>
<div class="hex" id="frame-hex">${a||"—"}</div>
${this.expanded?c`
<div style="font-size: 11.5px; color: var(--ink-3); margin-top: 10px; line-height: 1.6;">
Layout (little-endian): <code>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)</code>.
</div>`:""}
</div>
</div>
`}renderWitnessTab(){const e=$.value,t=e==="ok"?"ok":e==="fail"?"fail":"",a=e==="pending"?"Verifying…":e==="ok"?"✓ Witness verified · determinism gate":e==="fail"?"✗ Witness mismatch · audit required":"Verify witness",i=R.value&&z.value&&R.value===z.value;return c`
${this.expanded?c`
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(180px, 1fr));gap:12px;margin-bottom:18px;">
<div class="card" style="margin:0;">
<div style="font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:0.06em;">Reference scene</div>
<div style="font-family:var(--mono);font-size:14px;color:var(--ink);margin-top:4px;">Proof::REFERENCE</div>
<div style="font-size:11.5px;color:var(--ink-3);margin-top:2px;">2 dipoles · 1 loop · 1 ferrous · 1 sensor</div>
</div>
<div class="card" style="margin:0;">
<div style="font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:0.06em;">Seed</div>
<div style="font-family:var(--mono);font-size:14px;color:var(--accent);margin-top:4px;">0x0000002A</div>
<div style="font-size:11.5px;color:var(--ink-3);margin-top:2px;">canonical Proof::SEED</div>
</div>
<div class="card" style="margin:0;">
<div style="font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:0.06em;">Sample count</div>
<div style="font-family:var(--mono);font-size:14px;color:var(--ink);margin-top:4px;">256</div>
<div style="font-size:11.5px;color:var(--ink-3);margin-top:2px;">Proof::N_SAMPLES</div>
</div>
<div class="card" style="margin:0;">
<div style="font-size:10px;color:var(--ink-3);text-transform:uppercase;letter-spacing:0.06em;">Status</div>
<div style="font-family:var(--mono);font-size:14px;margin-top:4px;color:${e==="ok"?"var(--ok)":e==="fail"?"var(--bad)":"var(--ink-3)"};">
${e==="ok"?"✓ matches":e==="fail"?"✗ drift":e==="pending"?"… running":"— idle"}
</div>
<div style="font-size:11.5px;color:var(--ink-3);margin-top:2px;">${i?"byte-equivalent":"not yet verified"}</div>
</div>
</div>
`:""}
<div class="card">
<div class="card-h">
<span class="ttl">Expected (Proof::EXPECTED_WITNESS_HEX)</span>
<span class="badge">SHA-256</span>
</div>
<div class="witness-box" id="expected-witness">${R.value||"(loading…)"}</div>
</div>
<div class="card">
<div class="card-h">
<span class="ttl">Actual (last verify)</span>
<span class="badge">SHA-256</span>
</div>
<div class="witness-box" id="actual-witness">${z.value||"(not verified yet)"}</div>
<button class="verify-btn ${t}" id="verify-btn" @click=${this.verify}>${a}</button>
</div>
${this.expanded?c`
<div class="card">
<div class="card-h">
<span class="ttl">What this verifies</span>
<span class="badge">ADR-089 §5</span>
</div>
<div style="font-size: 12.5px; color: var(--ink-2); line-height: 1.6;">
<p style="margin: 0 0 10px;">Pressing <b>Verify</b> runs the canonical reference pipeline
(<code>Proof::generate</code>) end-to-end inside this browser's WASM Worker:
scene → Biot-Savart synthesis → material attenuation → NV ensemble → ADC + lock-in →
concatenated <code>MagFrame</code> bytes → SHA-256.</p>
<p style="margin: 0 0 10px;">If the resulting hash matches the constant pinned at build time
(<code>cc8de9b01b0ff5bd…</code>), 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, <i>something</i> drifted; the dashboard names which.</p>
<p style="margin: 0;">This is the same regression test that runs in
<code>cargo test -p nvsim</code> — running in your browser, against your own WASM build.</p>
</div>
</div>
`:""}
`}render(){return c`
<div class="tabs" role="tablist">
<button class="tab ${this.tab==="signal"?"active":""}" data-pane="signal"
role="tab" aria-selected=${this.tab==="signal"}
@click=${()=>this.tab="signal"}>Signal</button>
<button class="tab ${this.tab==="frame"?"active":""}" data-pane="frame"
role="tab" aria-selected=${this.tab==="frame"}
@click=${()=>this.tab="frame"}>Frame</button>
<button class="tab ${this.tab==="witness"?"active":""}" data-pane="witness"
role="tab" aria-selected=${this.tab==="witness"}
@click=${()=>this.tab="witness"}>Witness</button>
</div>
<div class="body" role="tabpanel">
${this.renderHeader()}
${this.tab==="signal"?this.renderSignalTab():this.tab==="frame"?this.renderFrameTab():this.renderWitnessTab()}
</div>
`}};Q.styles=y`
:host {
display: flex; flex-direction: column;
background: var(--bg-1);
border-left: 1px solid var(--line);
overflow: hidden;
height: 100%;
}
:host([expanded]) {
border-left: 0;
background: radial-gradient(ellipse at 50% 30%, var(--bg-2) 0%, var(--bg-0) 70%);
}
:host([expanded]) .tabs {
padding: 0 24px;
background: var(--bg-1);
}
:host([expanded]) .tab {
padding: 16px 22px;
font-size: 13.5px;
flex: 0 0 auto;
}
:host([expanded]) .body {
padding: 24px 28px;
max-width: 1400px;
width: 100%;
margin: 0 auto;
}
:host([expanded]) .card { padding: 18px 20px; }
:host([expanded]) .card-h .ttl { font-size: 14px; }
:host([expanded]) svg { height: 220px; }
:host([expanded]) .frame-strip { height: 48px; }
:host([expanded]) table { font-size: 12.5px; }
:host([expanded]) td { padding: 6px 0; }
:host([expanded]) .hex { font-size: 12px; padding: 14px; line-height: 1.7; }
:host([expanded]) .witness-box { font-size: 13px; padding: 14px 16px; line-height: 1.6; }
:host([expanded]) .verify-btn { padding: 12px; font-size: 13px; }
:host([expanded]) .grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
:host([expanded]) .grid-2 > .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);
}
.tab {
flex: 1;
padding: 11px 8px;
background: transparent; border: none;
font-size: 11.5px; font-weight: 500;
color: var(--ink-3);
border-bottom: 2px solid transparent;
cursor: pointer; transition: color 0.15s, border-color 0.15s;
}
.tab.active { color: var(--ink); border-bottom-color: var(--accent); }
.tab:hover { color: var(--ink-2); }
.body { padding: 14px; flex: 1; overflow-y: auto; }
.card {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius); padding: 12px;
margin-bottom: 12px;
}
.card-h {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 8px;
}
.card-h .ttl { font-size: 12px; font-weight: 600; }
.badge {
font-family: var(--mono); font-size: 10px;
padding: 2px 6px;
background: oklch(0.78 0.14 195 / 0.12);
color: var(--accent-2);
border-radius: 4px;
border: 1px solid oklch(0.78 0.14 195 / 0.3);
}
svg { width: 100%; height: 130px; }
.frame-strip {
height: 28px;
display: flex; align-items: flex-end; gap: 1px;
padding: 4px 0;
}
.bar {
flex: 1;
background: linear-gradient(to top, var(--accent-2), var(--accent));
border-radius: 1px;
min-height: 2px;
}
table { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 10.5px; }
td { padding: 4px 0; border-bottom: 1px solid var(--line); }
td:first-child { color: var(--ink-3); }
td:last-child { text-align: right; color: var(--ink); }
.hex {
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 10px;
font-family: var(--mono);
font-size: 10.5px;
color: var(--ink-2);
line-height: 1.6;
overflow-x: auto;
white-space: nowrap;
}
.hex .magic { color: var(--accent); font-weight: 600; }
.witness-box {
font-family: var(--mono);
font-size: 11px;
color: var(--ink-2);
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: 6px;
padding: 8px 10px;
word-break: break-all;
line-height: 1.5;
}
.verify-btn {
margin-top: 10px;
width: 100%;
padding: 8px;
border: 1px solid var(--line);
background: var(--bg-3);
color: var(--ink);
border-radius: 8px;
cursor: pointer;
font-family: var(--mono);
font-size: 12px;
}
.verify-btn:hover { border-color: var(--accent); }
.verify-btn.ok { border-color: var(--ok); color: var(--ok); }
.verify-btn.fail { border-color: var(--bad); color: var(--bad); }
`;Te([u()],Q.prototype,"tab",2);Te([Ce({attribute:!1})],Q.prototype,"pinTab",2);Te([Ce({type:Boolean,reflect:!0})],Q.prototype,"expanded",2);Q=Te([k("nv-inspector")],Q);var Ct=Object.defineProperty,Pt=Object.getOwnPropertyDescriptor,Ke=(e,t,a,i)=>{for(var s=i>1?void 0:i?Pt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Ct(t,a,s),s};let Se=class extends w{constructor(){super(...arguments),this.hIdx=-1,this.onKey=e=>{if(e.key==="Enter")this.exec(this.inputEl.value),this.inputEl.value="";else if(e.key==="ArrowUp"){const t=W.value;t.length&&(this.hIdx=Math.max(0,this.hIdx-1),this.inputEl.value=t[this.hIdx]??"",e.preventDefault())}else if(e.key==="ArrowDown"){const t=W.value;t.length&&(this.hIdx=Math.min(t.length,this.hIdx+1),this.inputEl.value=t[this.hIdx]??"",e.preventDefault())}}}connectedCallback(){super.connectedCallback(),f(()=>{A.value,Ee.value,re.value,this.requestUpdate()})}updated(){const e=this.renderRoot.querySelector(".body");e&&(e.scrollTop=e.scrollHeight)}counts(){const e={info:0,warn:0,err:0,dbg:0,ok:0};for(const t of A.value)e[t.level]=(e[t.level]??0)+1;return e.all=A.value.length,e}async exec(e){if(e=e.trim(),!e)return;d("info",`<span style="color:var(--accent);">nvsim&gt;</span> ${e}`),pt(e),this.hIdx=W.value.length;const[t,...a]=e.split(/\s+/),i=a.join(" "),s=h();switch(t){case"help":d("info","commands: help · scene.list · sensor.config · run · pause · reset · seed · proof.verify · proof.export · clear · theme · status");break;case"scene.list":d("info","scene rebar-walkby-01:"),d("info"," rebar.steel.coil @ [+2.7, 0.0, +0.3] m χ=5000"),d("info"," dipole.heart_proxy @ [-1.4, +0.2, +0.4] m m=1.0e-6 A·m²"),d("info"," loop.mains_60Hz @ [-1.6, -0.4, 0.0] m I=2 A"),d("info"," eddy.door_steel @ [+0.0, +1.8, +0.4] m σ=1e6 S/m");break;case"sensor.config":d("info","NvSensor::cots_defaults() {"),d("info"," pos=[0,0,0], V=1mm³, N=1e12, C=0.03, T2*=200ns"),d("info"," D=2.870 GHz, γe=28 GHz/T, Γ=1.0 MHz, axes=4×〈111〉"),d("info"," δB ≈ 1.18 pT/√Hz (Barry 2020 §III.A) }");break;case"run":s&&(await s.run(),g.value=!0,d("ok","pipeline RUN"));break;case"pause":s&&(await s.pause(),g.value=!1,d("warn","pipeline PAUSED"));break;case"reset":s&&(await s.reset(),d("info","pipeline reset · t=0"));break;case"seed":{if(!i){d("info",`current seed = 0x${L.value.toString(16).toUpperCase()}`);break}const r=BigInt(i.startsWith("0x")?i:"0x"+i);L.value=r,s&&await s.setSeed(r),d("ok",`seed → 0x${r.toString(16).toUpperCase()}`);break}case"proof.verify":{if(!s)break;d("dbg","computing SHA-256 over 256 frames…");try{const r=R.value,n=new Uint8Array(32);for(let l=0;l<32;l++)n[l]=parseInt(r.slice(l*2,l*2+2),16);(await s.verifyWitness(n)).ok?($.value="ok",z.value=r,d("ok",`witness ${r.slice(0,16)}… matches · determinism gate ✓`)):($.value="fail",d("err","WITNESS MISMATCH"))}catch(r){d("err",`verify failed: ${r.message}`)}break}case"proof.export":{if(!s)break;d("dbg","building proof bundle…");try{const r=await s.exportProofBundle(),n=URL.createObjectURL(r),o=document.createElement("a");o.href=n,o.download=`nvsim-proof-${Date.now()}.json`,o.click(),URL.revokeObjectURL(n),d("ok",`proof bundle exported · ${r.size} bytes`)}catch(r){d("err",`export failed: ${r.message}`)}break}case"clear":A.value=[];break;case"theme":{const r=(i||"").toLowerCase();r==="light"||r==="dark"?(x.value=r,d("ok",`theme → ${r}`)):d("info","theme [light|dark]");break}case"status":d("info",`running=${g.value} seed=0x${L.value.toString(16).toUpperCase()} verified=${$.value}`);break;default:d("err",`unknown command: ${t} · try help`)}}render(){const e=this.counts(),t=Ee.value,a=A.value.filter(i=>t==="all"||i.level===t);return c`
<div class="tabs">
${["all","info","warn","err","dbg"].map(i=>c`
<button class="tab ${t===i?"active":""}" data-tab=${i}
@click=${()=>Ee.value=i}>
${i} <span class="cnt">${e[i]??0}</span>
</button>
`)}
<span class="spacer"></span>
<div class="tools">
<button id="clear-log" title="Clear" @click=${()=>A.value=[]}>×</button>
<button id="pause-log" title="Pause" @click=${()=>re.value=!re.value}>
${re.value?"▶":"❚❚"}
</button>
</div>
</div>
<div class="body" role="log" aria-live="polite" aria-label="Console output">
${a.map(i=>{const s=new Date(i.ts),r=`${String(s.getSeconds()).padStart(2,"0")}.${String(s.getMilliseconds()).padStart(3,"0")}`;return c`<div class="line ${i.level}">
<div class="ts">${r}</div>
<div class="lvl">${i.level}</div>
<div class="msg" .innerHTML=${i.msg}></div>
</div>`})}
</div>
<div class="input">
<span class="prompt">nvsim&gt;</span>
<input id="console-input" type="text"
placeholder="help · scene.list · sensor.config · run · proof.verify · clear"
@keydown=${this.onKey}/>
</div>
`}};Se.styles=y`
:host {
display: flex; flex-direction: column;
background: var(--bg-1);
overflow: hidden;
}
.tabs {
display: flex; align-items: center;
border-bottom: 1px solid var(--line);
padding: 0 10px;
gap: 2px;
}
.tab {
padding: 8px 12px;
background: transparent; border: none;
font-size: 11.5px; color: var(--ink-3);
font-family: var(--mono);
border-bottom: 2px solid transparent;
cursor: pointer;
margin-bottom: -1px;
}
.tab.active { color: var(--ink); border-bottom-color: var(--accent); }
.tab .cnt {
background: var(--bg-3); padding: 1px 5px; border-radius: 999px;
font-size: 9.5px; color: var(--ink-2); margin-left: 4px;
}
.spacer { flex: 1; }
.tools { display: flex; gap: 4px; padding: 4px 0; }
.tools button {
width: 24px; height: 24px;
background: transparent; border: 1px solid var(--line);
border-radius: 6px;
color: var(--ink-3);
font-size: 11px; cursor: pointer;
}
.tools button:hover { color: var(--ink); border-color: var(--line-2); }
.body {
flex: 1; overflow-y: auto;
font-family: var(--mono);
font-size: 11.5px;
padding: 6px 0;
background: var(--bg-0);
}
.line {
display: grid;
grid-template-columns: 70px 60px 1fr;
gap: 12px;
padding: 2px 12px;
color: var(--ink-2);
border-left: 2px solid transparent;
}
.line:hover { background: var(--bg-1); }
.ts { color: var(--ink-4); font-size: 10.5px; padding-top: 1px; }
.lvl {
font-size: 10px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.04em; padding-top: 1px;
}
.line.info .lvl { color: var(--accent-2); }
.line.warn .lvl { color: var(--warn); }
.line.warn { border-left-color: var(--warn); background: oklch(0.7 0.18 35 / 0.04); }
.line.err .lvl { color: var(--bad); }
.line.err { border-left-color: var(--bad); background: oklch(0.65 0.22 25 / 0.05); }
.line.dbg .lvl { color: var(--ink-3); }
.line.ok .lvl { color: var(--ok); }
.msg { color: var(--ink); white-space: pre-wrap; word-break: break-word; }
.input {
display: flex; align-items: center;
border-top: 1px solid var(--line);
background: var(--bg-0);
padding: 0 10px;
height: 32px; gap: 8px;
}
.prompt { color: var(--accent); font-family: var(--mono); font-size: 12px; }
input[type="text"] {
flex: 1; background: transparent; border: none; outline: none;
color: var(--ink); font-family: var(--mono); font-size: 12px;
height: 100%;
}
input::placeholder { color: var(--ink-4); }
`;Ke([Ue("#console-input")],Se.prototype,"inputEl",2);Se=Ke([k("nv-console")],Se);const oe=[{id:"nvsim",name:"nvsim — NV-diamond magnetometer",category:"sim",crate:"nvsim",summary:"Deterministic forward simulator: scene → BiotSavart → NV ensemble → ADC → MagFrame stream + SHA-256 witness.",budget:"L",active:!0,status:"available",tags:["quantum","magnetometer","simulator","witness","wasm"],adr:"ADR-089"},{id:"gesture",name:"Gesture (DTW)",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"Dynamic-Time-Warping gesture classifier from CSI motion templates.",events:[1],budget:"M",status:"available",tags:["hci","csi","classifier","dtw"],adr:"ADR-014"},{id:"coherence",name:"Coherence gate",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"Z-score coherence scoring + Accept/PredictOnly/Reject/Recalibrate gate.",events:[2],budget:"S",status:"available",tags:["gate","csi","coherence","drift"],adr:"ADR-029"},{id:"adversarial",name:"Adversarial-signal detector",category:"ais",crate:"wifi-densepose-wasm-edge",summary:"Physically-impossible-signal detector — multi-link consistency, used to flag spoofed CSI.",events:[3],budget:"M",status:"available",tags:["security","csi","spoofing","mesh"],adr:"ADR-032"},{id:"rvf",name:"RVF — Rust Verified Feature stream",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"Verified-frame builder with SHA-256 hash + version metadata for the feature stream.",budget:"S",status:"available",tags:["witness","csi","hash"],adr:"ADR-040"},{id:"occupancy",name:"Occupancy estimator",category:"bld",crate:"wifi-densepose-wasm-edge",summary:"Through-wall presence + person-count via CSI amplitude perturbation.",events:[300,301,302],budget:"S",status:"available",tags:["csi","building","presence"]},{id:"vital_trend",name:"Vital-trend monitor",category:"med",crate:"wifi-densepose-wasm-edge",summary:"HR + BR trend tracking with bradycardia/tachycardia/apnea events.",events:[100,101,102,103,104,105],budget:"S",status:"available",tags:["medical","vitals","csi"],adr:"ADR-021"},{id:"intrusion",name:"Intrusion detector",category:"sec",crate:"wifi-densepose-wasm-edge",summary:"Zone-based intrusion alert from CSI motion patterns.",events:[200,201],budget:"S",status:"available",tags:["security","zone","csi"]},{id:"med_sleep_apnea",name:"Sleep-apnea detector",category:"med",crate:"wifi-densepose-wasm-edge",summary:"Episodic respiratory pause detection during sleep cycles.",events:[105],budget:"S",status:"available",tags:["medical","sleep","breathing"]},{id:"med_cardiac_arrhythmia",name:"Cardiac arrhythmia",category:"med",crate:"wifi-densepose-wasm-edge",summary:"Beat-to-beat irregularity classifier from cardiac micro-Doppler.",events:[103,104],budget:"M",status:"available",tags:["medical","cardiac","arrhythmia"]},{id:"med_respiratory_distress",name:"Respiratory distress",category:"med",crate:"wifi-densepose-wasm-edge",summary:"Distress signature: rapid shallow breathing + accessory-muscle motion.",events:[101,102],budget:"S",status:"available",tags:["medical","breathing","icu"]},{id:"med_gait_analysis",name:"Gait analysis",category:"med",crate:"wifi-densepose-wasm-edge",summary:"Stride length, cadence, asymmetry from through-wall CSI pose tracking.",budget:"M",status:"available",tags:["medical","gait","pose"]},{id:"med_seizure_detect",name:"Seizure detector",category:"med",crate:"wifi-densepose-wasm-edge",summary:"Tonic-clonic seizure motion signature.",budget:"M",status:"beta",tags:["medical","neuro"]},{id:"sec_perimeter_breach",name:"Perimeter breach",category:"sec",crate:"wifi-densepose-wasm-edge",summary:"Approach/departure detection at user-defined boundary segments.",events:[210,211,212,213],budget:"S",status:"available",tags:["security","perimeter"]},{id:"sec_weapon_detect",name:"Metal anomaly / weapon",category:"sec",crate:"wifi-densepose-wasm-edge",summary:"Metal-perturbation flag in CSI; potential weapon presence (research).",events:[220,221,222],budget:"M",status:"research",tags:["security","metal","csi"]},{id:"sec_tailgating",name:"Tailgating detector",category:"sec",crate:"wifi-densepose-wasm-edge",summary:"Detect 2+ persons crossing a single-passage threshold.",events:[230,231,232],budget:"S",status:"available",tags:["security","access-control"]},{id:"sec_loitering",name:"Loitering detector",category:"sec",crate:"wifi-densepose-wasm-edge",summary:"Stationary occupancy past a configurable dwell threshold.",events:[240,241,242],budget:"S",status:"available",tags:["security","dwell"]},{id:"sec_panic_motion",name:"Panic motion",category:"sec",crate:"wifi-densepose-wasm-edge",summary:"High-energy distress motion: struggle / fleeing pattern.",events:[250,251,252],budget:"S",status:"beta",tags:["security","distress"]},{id:"bld_hvac_presence",name:"HVAC presence",category:"bld",crate:"wifi-densepose-wasm-edge",summary:"Occupied/activity-level/departure-countdown for HVAC zones.",events:[310,311,312],budget:"S",status:"available",tags:["hvac","building","energy"]},{id:"bld_lighting_zones",name:"Lighting zones",category:"bld",crate:"wifi-densepose-wasm-edge",summary:"Per-zone light on/dim/off cues from occupancy.",events:[320,321,322],budget:"S",status:"available",tags:["lighting","building"]},{id:"bld_elevator_count",name:"Elevator count",category:"bld",crate:"wifi-densepose-wasm-edge",summary:"Person count inside elevator car from CSI.",events:[330],budget:"S",status:"available",tags:["elevator","building"]},{id:"bld_meeting_room",name:"Meeting-room utilization",category:"bld",crate:"wifi-densepose-wasm-edge",summary:"Meeting size + duration analytics for booking systems.",budget:"S",status:"available",tags:["meeting","analytics"]},{id:"bld_energy_audit",name:"Energy audit",category:"bld",crate:"wifi-densepose-wasm-edge",summary:"Continuous occupancy-vs-HVAC-state audit for energy savings.",budget:"M",status:"available",tags:["energy","audit"]},{id:"ret_queue_length",name:"Queue length",category:"ret",crate:"wifi-densepose-wasm-edge",summary:"Live queue-length tracking for checkout / kiosks.",budget:"S",status:"available",tags:["retail","queue"]},{id:"ret_dwell_heatmap",name:"Dwell heatmap",category:"ret",crate:"wifi-densepose-wasm-edge",summary:"Per-zone dwell time accumulation; analytics-only export.",budget:"M",status:"available",tags:["retail","heatmap"]},{id:"ret_customer_flow",name:"Customer flow",category:"ret",crate:"wifi-densepose-wasm-edge",summary:"Origin-destination flow graph through a store layout.",budget:"M",status:"available",tags:["retail","flow"]},{id:"ret_table_turnover",name:"Table turnover",category:"ret",crate:"wifi-densepose-wasm-edge",summary:"Restaurant table seat / vacate transitions.",budget:"S",status:"available",tags:["retail","restaurant"]},{id:"ret_shelf_engagement",name:"Shelf engagement",category:"ret",crate:"wifi-densepose-wasm-edge",summary:"Reach-to-shelf gestures and dwell at product zones.",budget:"M",status:"available",tags:["retail","shelf"]},{id:"ind_forklift_proximity",name:"Forklift proximity",category:"ind",crate:"wifi-densepose-wasm-edge",summary:"Worker-near-forklift safety alert.",budget:"S",status:"available",tags:["industrial","safety"]},{id:"ind_confined_space",name:"Confined-space monitor",category:"ind",crate:"wifi-densepose-wasm-edge",summary:"Last-person-out detection + presence audit for OSHA confined-space entries.",budget:"S",status:"available",tags:["industrial","osha"]},{id:"ind_clean_room",name:"Clean-room PPE / motion",category:"ind",crate:"wifi-densepose-wasm-edge",summary:"Motion patterns consistent with proper PPE-clad movement.",budget:"M",status:"beta",tags:["industrial","cleanroom"]},{id:"ind_livestock_monitor",name:"Livestock monitor",category:"ind",crate:"wifi-densepose-wasm-edge",summary:"Vital-sign + activity tracking for stall-bound livestock.",budget:"M",status:"beta",tags:["agriculture","livestock"]},{id:"ind_structural_vibration",name:"Structural vibration",category:"ind",crate:"wifi-densepose-wasm-edge",summary:"Building/equipment micro-vibration via CSI phase derivative.",budget:"M",status:"research",tags:["industrial","vibration"]},{id:"sig_coherence_gate",name:"Coherence gate (extended)",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"Hysteresis + multi-state coherence gate driving downstream apps.",budget:"S",status:"available",tags:["gate","csi"]},{id:"sig_flash_attention",name:"Flash attention (CSI)",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"Edge-friendly attention block for CSI subcarrier weighting.",budget:"M",status:"beta",tags:["attention","csi"]},{id:"sig_temporal_compress",name:"Temporal-tensor compress",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"RuVector temporal-tensor compression on the CSI buffer.",budget:"M",status:"available",tags:["compress","tensor"]},{id:"sig_sparse_recovery",name:"Sparse recovery",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"114→56 subcarrier sparse interpolation via L1 solver.",budget:"M",status:"available",tags:["sparse","csi"]},{id:"sig_mincut_person_match",name:"Mincut person-match",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"Min-cut person assignment across multistatic frames.",budget:"M",status:"available",tags:["mincut","matching"]},{id:"sig_optimal_transport",name:"Optimal transport",category:"sig",crate:"wifi-densepose-wasm-edge",summary:"OT-based feature alignment between mesh nodes.",budget:"M",status:"beta",tags:["ot","alignment"]},{id:"lrn_dtw_gesture_learn",name:"DTW gesture learn",category:"lrn",crate:"wifi-densepose-wasm-edge",summary:"On-device template learning for personalized gesture libraries.",budget:"M",status:"beta",tags:["lifelong","gesture"]},{id:"lrn_anomaly_attractor",name:"Anomaly attractor",category:"lrn",crate:"wifi-densepose-wasm-edge",summary:"Novelty detector with dynamic-attractor recall.",budget:"M",status:"research",tags:["novelty","lifelong"]},{id:"lrn_meta_adapt",name:"Meta-adapt",category:"lrn",crate:"wifi-densepose-wasm-edge",summary:"Meta-learning adapter for fast site-to-site transfer.",budget:"L",status:"research",tags:["meta-learning"]},{id:"lrn_ewc_lifelong",name:"EWC++ lifelong",category:"lrn",crate:"wifi-densepose-wasm-edge",summary:"Elastic-weight-consolidation gate to avoid catastrophic forgetting.",budget:"M",status:"beta",tags:["lifelong","ewc"]},{id:"spt_pagerank_influence",name:"PageRank influence",category:"spt",crate:"wifi-densepose-wasm-edge",summary:"Graph-influence ranking on the multistatic mesh.",budget:"M",status:"beta",tags:["graph","pagerank"]},{id:"spt_micro_hnsw",name:"µHNSW vector index",category:"spt",crate:"wifi-densepose-wasm-edge",summary:"Tiny HNSW index for AETHER re-ID embeddings on-device.",budget:"M",status:"available",tags:["hnsw","reid"]},{id:"spt_spiking_tracker",name:"Spiking tracker",category:"spt",crate:"wifi-densepose-wasm-edge",summary:"Spiking-network multi-target tracker.",budget:"L",status:"research",tags:["snn","tracker"]},{id:"tmp_pattern_sequence",name:"Pattern sequence",category:"tmp",crate:"wifi-densepose-wasm-edge",summary:"Sequence-of-events pattern matcher (e.g. ingress→linger→egress).",budget:"M",status:"available",tags:["temporal","pattern"]},{id:"tmp_temporal_logic_guard",name:"Temporal logic guard",category:"tmp",crate:"wifi-densepose-wasm-edge",summary:"LTL/MTL safety-property guard over event streams.",budget:"M",status:"beta",tags:["ltl","safety"]},{id:"tmp_goap_autonomy",name:"GOAP autonomy",category:"tmp",crate:"wifi-densepose-wasm-edge",summary:"Goal-oriented action planning for adaptive routines.",budget:"L",status:"research",tags:["planning","autonomy"]},{id:"ais_prompt_shield",name:"Prompt shield",category:"ais",crate:"wifi-densepose-wasm-edge",summary:"Edge-side LLM prompt-injection guard for on-device assistants.",budget:"M",status:"beta",tags:["security","llm"]},{id:"ais_behavioral_profiler",name:"Behavioral profiler",category:"ais",crate:"wifi-densepose-wasm-edge",summary:"Anomalous-behaviour profiler (drift in motion habits).",budget:"M",status:"beta",tags:["anomaly","behaviour"]},{id:"qnt_quantum_coherence",name:"Quantum coherence",category:"qnt",crate:"wifi-densepose-wasm-edge",summary:"Coherence diagnostics adapted for quantum-sensor signals.",budget:"M",status:"research",tags:["quantum","coherence"]},{id:"qnt_interference_search",name:"Interference search",category:"qnt",crate:"wifi-densepose-wasm-edge",summary:"Interferometric anomaly search across mesh viewpoints.",budget:"L",status:"research",tags:["quantum","interference"]},{id:"aut_psycho_symbolic",name:"Psycho-symbolic agent",category:"aut",crate:"wifi-densepose-wasm-edge",summary:"Symbolic-rule + neural-feature hybrid for low-power autonomy loops.",budget:"L",status:"research",tags:["autonomy","symbolic"]},{id:"aut_self_healing_mesh",name:"Self-healing mesh",category:"aut",crate:"wifi-densepose-wasm-edge",summary:"Mesh-topology repair with per-node health gossip.",budget:"M",status:"beta",tags:["mesh","health"]},{id:"exo_ghost_hunter",name:"Ghost hunter (anomaly)",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Empty-room CSI anomaly detector — impulsive/periodic/drift/random + hidden-presence sub-detector.",events:[650,651,652,653],budget:"S",status:"available",tags:["anomaly","paranormal","csi"],adr:"ADR-041"},{id:"exo_breathing_sync",name:"Breathing sync",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Multi-person breathing synchrony analytics.",budget:"M",status:"beta",tags:["breathing","sync"]},{id:"exo_dream_stage",name:"Dream-stage classifier",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"NREM/REM stage classification from breathing + micro-motion.",budget:"M",status:"research",tags:["sleep","rem"]},{id:"exo_emotion_detect",name:"Emotion detector",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Coarse arousal/valence from breathing + heart-rate variability.",budget:"M",status:"research",tags:["affect"]},{id:"exo_gesture_language",name:"Gesture language",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Sign-language pattern recognition.",budget:"L",status:"research",tags:["hci","sign"]},{id:"exo_happiness_score",name:"Happiness score",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Aggregate well-being score from co-occupancy + activity dynamics.",budget:"M",status:"research",tags:["affect","wellbeing"]},{id:"exo_hyperbolic_space",name:"Hyperbolic space embed",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Hyperbolic embeddings for hierarchical scene structure.",budget:"L",status:"research",tags:["embedding","hyperbolic"]},{id:"exo_music_conductor",name:"Music conductor",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Map gesture energy to MIDI tempo/dynamics.",budget:"M",status:"research",tags:["midi","art"]},{id:"exo_plant_growth",name:"Plant-growth tracker",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Slow CSI drift tracking for greenhouse foliage growth.",budget:"L",status:"research",tags:["agriculture"]},{id:"exo_rain_detect",name:"Rain detector",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Outdoor CSI signature of rainfall.",budget:"M",status:"research",tags:["weather"]},{id:"exo_time_crystal",name:"Time-crystal periodicity",category:"exo",crate:"wifi-densepose-wasm-edge",summary:"Periodicity diagnostics with anti-aliasing harmonics.",budget:"M",status:"research",tags:["periodicity"]}],se={sim:{label:"Simulators",color:"oklch(0.78 0.14 70)",range:"—"},med:{label:"Medical & Health",color:"oklch(0.65 0.22 25)",range:"100199"},sec:{label:"Security & Safety",color:"oklch(0.7 0.18 35)",range:"200299"},bld:{label:"Smart Building",color:"oklch(0.78 0.12 195)",range:"300399"},ret:{label:"Retail & Hospitality",color:"oklch(0.78 0.14 145)",range:"400499"},ind:{label:"Industrial",color:"oklch(0.72 0.18 330)",range:"500599"},sig:{label:"Signal Processing",color:"oklch(0.78 0.14 70)",range:"600619"},lrn:{label:"Online Learning",color:"oklch(0.78 0.12 260)",range:"620639"},spt:{label:"Spatial / Graph",color:"oklch(0.7 0.18 100)",range:"640659"},tmp:{label:"Temporal / Planning",color:"oklch(0.7 0.16 50)",range:"660679"},ais:{label:"AI Safety",color:"oklch(0.65 0.22 25)",range:"700719"},qnt:{label:"Quantum",color:"oklch(0.72 0.18 290)",range:"720739"},aut:{label:"Autonomy",color:"oklch(0.78 0.14 145)",range:"740759"},exo:{label:"Exotic / Research",color:"oklch(0.72 0.18 330)",range:"650699"}};function Tt(){return oe.map(e=>({id:e.id,active:e.active===!0,eventCount:0}))}function At(e,t){if(!e)return 1;const a=e.toLowerCase();let i=0;return t.id.toLowerCase().includes(a)&&(i+=3),t.name.toLowerCase().includes(a)&&(i+=3),t.summary.toLowerCase().includes(a)&&(i+=1),t.tags?.some(s=>s.toLowerCase().includes(a))&&(i+=2),t.category===a&&(i+=5),i}const Rt="nvsim",Et=1,Z="kv";let fe=null;function Xe(){return fe||(fe=new Promise((e,t)=>{const a=indexedDB.open(Rt,Et);a.onupgradeneeded=()=>{const i=a.result;i.objectStoreNames.contains(Z)||i.createObjectStore(Z)},a.onsuccess=()=>e(a.result),a.onerror=()=>t(a.error)}),fe)}async function O(e){const t=await Xe();return await new Promise((a,i)=>{const r=t.transaction(Z,"readonly").objectStore(Z).get(e);r.onsuccess=()=>a(r.result),r.onerror=()=>i(r.error)})}async function I(e,t){const a=await Xe();return await new Promise((i,s)=>{const r=a.transaction(Z,"readwrite");r.objectStore(Z).put(t,e),r.oncomplete=()=>i(),r.onerror=()=>s(r.error)})}var Dt=Object.defineProperty,Ft=Object.getOwnPropertyDescriptor,Ye=(e,t,a,i)=>{for(var s=i>1?void 0:i?Ft(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Dt(t,a,s),s};const H=p(Tt()),ie=p(""),F=p("all"),S=p("all");(async()=>{const e=await O("app-activations");e&&(H.value=e)})();f(()=>{const e=H.value;e.length>0&&I("app-activations",e)});let _e=class extends w{constructor(){super(...arguments),this.renderTick=0}connectedCallback(){super.connectedCallback(),f(()=>{H.value,ie.value,F.value,S.value,this.renderTick++})}isActive(e){return H.value.find(t=>t.id===e)?.active===!0}toggle(e){const t=H.value.map(a=>a.id===e.id?{...a,active:!a.active,lastActivatedAt:Date.now()}:a);H.value=t,d(this.isActive(e.id)?"ok":"info",`app <span class="k">${e.id}</span> deactivated`)}filtered(){let e=oe;return F.value!=="all"&&(e=e.filter(t=>t.category===F.value)),S.value!=="all"&&(e=e.filter(t=>t.status===S.value)),ie.value.trim()&&(e=e.map(t=>({a:t,s:At(ie.value,t)})).filter(t=>t.s>0).sort((t,a)=>a.s-t.s).map(t=>t.a)),e}categoryCounts(){const e={all:oe.length};for(const t of Object.keys(se))e[t]=0;for(const t of oe)e[t.category]=(e[t.category]??0)+1;return e}render(){const e=this.filtered(),t=this.categoryCounts(),a=H.value.filter(i=>i.active).length;return c`
<div class="head">
<div class="ttl">
App Store
<small>${oe.length} edge apps · ${a} active</small>
</div>
<input class="search" id="app-search" placeholder="Search by name, tag, or category…"
.value=${ie.value}
@input=${i=>{ie.value=i.target.value}} />
</div>
<div class="filters">
<span class="chip ${F.value==="all"?"on":""}"
@click=${()=>F.value="all"}>
All<span class="count">${t.all}</span>
</span>
${Object.keys(se).map(i=>c`
<span class="chip ${F.value===i?"on":""}"
@click=${()=>F.value=i}>
<span class="swatch" style=${`background:${se[i].color}`}></span>
${se[i].label}
<span class="count">${t[i]??0}</span>
</span>
`)}
<span style="flex:1; min-width:8px"></span>
<span class="chip ${S.value==="all"?"on":""}" @click=${()=>S.value="all"}>any</span>
<span class="chip ${S.value==="available"?"on":""}" @click=${()=>S.value="available"}>available</span>
<span class="chip ${S.value==="beta"?"on":""}" @click=${()=>S.value="beta"}>beta</span>
<span class="chip ${S.value==="research"?"on":""}" @click=${()=>S.value="research"}>research</span>
</div>
${e.length===0?c`<div class="empty">No apps match the current filters.</div>`:c`<div class="grid">${e.map(i=>this.card(i))}</div>`}
`}card(e){const t=this.isActive(e.id),a=se[e.category];return c`
<div class="card ${t?"active":""}" data-app-id=${e.id}>
<div class="card-h">
<span class="swatch" style=${`background:${a.color}`}></span>
<span class="name">${e.name}</span>
</div>
<div class="summary">${e.summary}</div>
<div class="meta">
<span class="badge cat">${a.label}</span>
<span class="badge status-${e.status}">${e.status}</span>
${e.budget?c`<span class="badge budget">budget ${e.budget}</span>`:""}
${e.adr?c`<span class="badge">${e.adr}</span>`:""}
${e.events?.length?c`<span class="badge">events ${e.events.join("·")}</span>`:""}
</div>
<div class="card-foot">
<span class="events">${e.crate}</span>
<span class="toggle ${t?"on":""}" role="switch"
aria-checked=${t}
data-app-toggle=${e.id}
@click=${()=>this.toggle(e)}></span>
</div>
</div>
`}};_e.styles=y`
:host {
display: block;
height: 100%;
overflow-y: auto;
background: radial-gradient(ellipse at 50% 30%, var(--bg-2) 0%, var(--bg-0) 70%);
padding: 24px;
}
.head {
display: flex; align-items: center; gap: 16px;
margin-bottom: 18px;
flex-wrap: wrap;
}
.ttl {
font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
color: var(--ink);
flex: 1; min-width: 200px;
}
.ttl small {
font-size: 12.5px; font-weight: 400;
color: var(--ink-3); margin-left: 8px;
}
.search {
width: 320px; max-width: 100%;
padding: 8px 12px;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: 8px;
font-family: var(--mono);
font-size: 12.5px;
color: var(--ink); outline: none;
}
.search:focus { border-color: var(--accent); }
.filters {
display: flex; flex-wrap: wrap; gap: 6px;
margin-bottom: 18px;
}
.chip {
padding: 4px 10px;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: 999px;
font-size: 11.5px; color: var(--ink-3);
cursor: pointer;
font-family: var(--mono);
display: inline-flex; align-items: center; gap: 4px;
}
.chip:hover { color: var(--ink); border-color: var(--line-2); }
.chip.on { background: var(--bg-3); border-color: var(--accent); color: var(--ink); }
.chip .swatch {
width: 7px; height: 7px; border-radius: 50%;
}
.chip .count { color: var(--ink-3); font-size: 10px; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
}
.card {
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 12px 14px;
display: flex; flex-direction: column; gap: 6px;
transition: border-color 0.15s, transform 0.15s;
position: relative;
}
.card:hover { border-color: var(--line-2); transform: translateY(-1px); }
.card.active {
border-color: oklch(0.78 0.14 145 / 0.7);
background: linear-gradient(180deg, var(--bg-2) 0%, oklch(0.78 0.14 145 / 0.04) 100%);
}
.card-h {
display: flex; align-items: flex-start; gap: 8px;
margin-bottom: 2px;
}
.card-h .name {
font-size: 13.5px; font-weight: 600; color: var(--ink);
flex: 1; line-height: 1.3;
}
.card-h .swatch {
width: 10px; height: 10px; border-radius: 50%;
flex-shrink: 0; margin-top: 4px;
}
.summary {
font-size: 12px; color: var(--ink-2); line-height: 1.45;
flex: 1;
}
.meta {
display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px;
font-family: var(--mono); font-size: 10px;
}
.badge {
padding: 1px 6px; border-radius: 4px;
background: var(--bg-3); color: var(--ink-3);
border: 1px solid var(--line);
}
.badge.cat { color: var(--accent); border-color: oklch(0.78 0.14 70 / 0.3); }
.badge.status-available { color: var(--ok); border-color: oklch(0.78 0.14 145 / 0.4); }
.badge.status-beta { color: var(--warn); border-color: oklch(0.7 0.18 35 / 0.4); }
.badge.status-research { color: var(--accent-3); border-color: oklch(0.72 0.18 330 / 0.4); }
.badge.budget { color: var(--accent-2); border-color: oklch(0.78 0.12 195 / 0.3); }
.card-foot {
display: flex; align-items: center; gap: 8px;
padding-top: 8px; margin-top: 4px;
border-top: 1px solid var(--line);
font-size: 11px; color: var(--ink-3);
}
.toggle {
position: relative;
width: 32px; height: 18px;
background: var(--bg-3); border: 1px solid var(--line-2);
border-radius: 999px; cursor: pointer;
transition: background 0.15s;
flex-shrink: 0;
}
.toggle::after {
content: ''; position: absolute;
top: 1px; left: 1px;
width: 12px; height: 12px;
background: var(--ink-3); border-radius: 50%;
transition: transform 0.15s, background 0.15s;
}
.toggle.on { background: var(--accent); border-color: var(--accent); }
.toggle.on::after { background: #1a0f00; transform: translateX(14px); }
.events {
font-family: var(--mono); font-size: 10px; color: var(--ink-3);
flex: 1;
}
.empty {
padding: 40px;
text-align: center; color: var(--ink-3);
font-size: 13px;
}
`;Ye([u()],_e.prototype,"renderTick",2);_e=Ye([k("nv-app-store")],_e);var Ot=Object.defineProperty,It=Object.getOwnPropertyDescriptor,ge=(e,t,a,i)=>{for(var s=i>1?void 0:i?It(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Ot(t,a,s),s};let U=class extends w{constructor(){super(...arguments),this.open=!1,this.filter="",this.idx=0,this.cmds=[{ico:"▶",label:"Run pipeline",kbd:"Space",run:async()=>{await h()?.run(),g.value=!0,P("Pipeline running","▶")}},{ico:"❚",label:"Pause pipeline",run:async()=>{await h()?.pause(),g.value=!1,P("Paused","❚❚")}},{ico:"+",label:"New scene…",kbd:"⌘N",run:()=>ne({title:"New scene",body:`<p>Build a fresh magnetic scene. The dashboard generates the JSON
and pushes it to the running pipeline (or you can copy the JSON
for offline use).</p>
<label>Name</label>
<input type="text" id="ns-name" value="custom-scene-${Date.now().toString(36)}" />
<label>Heart-proxy dipole moment (A·m²)</label>
<input type="text" id="ns-moment" value="1.0e-6" />
<label>Distance heart → sensor (m)</label>
<input type="text" id="ns-distance" value="0.5" />
<label>Add ferrous distractor at +x = 1 m?</label>
<select id="ns-ferrous">
<option value="0">No</option>
<option value="1" selected>Yes (steel coil, χ=5000)</option>
</select>
<label>Add 60 Hz mains-current loop?</label>
<select id="ns-mains">
<option value="0">No</option>
<option value="1" selected>Yes (2 A loop, 5 cm radius, +y = 1 m)</option>
</select>`,buttons:[{label:"Cancel",variant:"ghost"},{label:"Create",variant:"primary",onClick:async()=>{const e=document.querySelector("nv-app")?.shadowRoot?.querySelector("nv-modal")?.shadowRoot;if(!e)return;const t=(e.querySelector("#ns-name")?.value??"custom").trim(),a=parseFloat(e.querySelector("#ns-moment")?.value??"1e-6"),i=parseFloat(e.querySelector("#ns-distance")?.value??"0.5"),s=e.querySelector("#ns-ferrous")?.value==="1",r=e.querySelector("#ns-mains")?.value==="1",n={dipoles:[{position:[0,0,i],moment:[0,0,a]}],loops:r?[{centre:[0,1,0],normal:[0,1,0],radius:.05,current:2,n_segments:64}]:[],ferrous:s?[{position:[1,0,0],volume:1e-4,susceptibility:5e3}]:[],eddy:[],sensors:[[0,0,0]],ambient_field:[1e-6,0,0]};await h()?.loadScene(n),d("ok",`scene <span class="s">${t}</span> loaded · 1 dipole · ${r?"1 loop · ":""}${s?"1 ferrous · ":""}1 sensor`),P(`Scene "${t}" loaded`,"+")}}]})},{ico:"📦",label:"Export proof bundle…",kbd:"⌘E",run:async()=>{const e=h();if(e){d("dbg","building proof bundle…");try{const t=await e.exportProofBundle(),a=URL.createObjectURL(t),i=document.createElement("a");i.href=a,i.download=`nvsim-proof-${Date.now()}.json`,i.click(),URL.revokeObjectURL(a),d("ok",`proof bundle exported · ${t.size} bytes`),P(`Proof bundle saved (${t.size} B)`,"📦")}catch(t){d("err",`export failed: ${t.message}`)}}}},{ico:"⟳",label:"Reset pipeline",kbd:"⌘R",run:()=>ne({title:"Reset pipeline?",body:"<p>Clears the frame stream and rewinds <code>t</code> to 0.</p>",buttons:[{label:"Cancel",variant:"ghost"},{label:"Reset",variant:"danger",onClick:async()=>{await h()?.reset(),d("warn","pipeline reset · t=0"),P("Pipeline reset","⟳")}}]})},{ico:"✓",label:"Verify witness",run:async()=>{const e=h();if(!e)return;$.value="pending";const t=R.value,a=new Uint8Array(32);for(let s=0;s<32;s++)a[s]=parseInt(t.slice(s*2,s*2+2),16);(await e.verifyWitness(a)).ok?($.value="ok",z.value=t,P("Witness verified","✓")):($.value="fail",P("Witness mismatch!","✗"))}},{ico:"☼",label:"Toggle theme",kbd:"⌘/",run:()=>{x.value=x.value==="dark"?"light":"dark"}},{ico:"⚙",label:"Open settings",kbd:"⌘,",run:()=>window.dispatchEvent(new CustomEvent("open-settings"))},{ico:"?",label:"Keyboard shortcuts…",run:()=>ne({title:"Keyboard shortcuts",body:`<div style="display:grid;grid-template-columns:auto 1fr;gap:6px 16px;font-size:13px;">
<div><code>⌘K / Ctrl K</code></div><div>Command palette</div>
<div><code>Space</code></div><div>Play / pause</div>
<div><code>⌘R</code></div><div>Reset</div>
<div><code>⌘,</code></div><div>Settings</div>
<div><code>⌘/</code></div><div>Toggle theme</div>
<div><code>\`</code></div><div>Debug HUD</div>
<div><code>1 · 2 · 3</code></div><div>Inspector tabs</div>
<div><code>Esc</code></div><div>Close modal/palette</div>
<div><code>/</code></div><div>Focus REPL</div>
</div>`,buttons:[{label:"Close",variant:"primary"}]})},{ico:"i",label:"About nvsim…",run:()=>ne({title:"About nvsim",body:`<p><b>nvsim</b> is a deterministic, byte-reproducible forward simulator for nitrogen-vacancy diamond magnetometry.</p>
<p>This dashboard runs nvsim as WASM in a Web Worker. Same <code>(scene, config, seed)</code> → byte-identical SHA-256 witness across runs and machines.</p>
<p>License: MIT OR Apache-2.0 · See ADR-089, ADR-092.</p>`,buttons:[{label:"Close",variant:"primary"}]})}],this.onKey=e=>{(e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==="k"?(e.preventDefault(),this.openPal()):e.key==="Escape"&&this.open?this.closePal():this.open&&(e.key==="ArrowDown"?(this.idx=Math.min(this.cmds.length-1,this.idx+1),e.preventDefault()):e.key==="ArrowUp"?(this.idx=Math.max(0,this.idx-1),e.preventDefault()):e.key==="Enter"&&(this.runIdx(),e.preventDefault()))},this.onOpen=()=>this.openPal()}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this.onKey),window.addEventListener("nv-palette",this.onOpen)}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.onKey),window.removeEventListener("nv-palette",this.onOpen)}openPal(){this.open=!0,this.setAttribute("open",""),this.filter="",this.idx=0,setTimeout(()=>this.inputEl?.focus(),0)}closePal(){this.open=!1,this.removeAttribute("open")}filtered(){if(!this.filter.trim())return this.cmds;const e=this.filter.toLowerCase();return this.cmds.filter(t=>t.label.toLowerCase().includes(e))}runIdx(){const t=this.filtered()[this.idx];t&&(t.run(),this.closePal())}render(){const e=this.filtered();return c`
<div class="palette" data-id="palette">
<div class="input">
<input id="palette-input" type="text" placeholder="Type a command…"
.value=${this.filter}
@input=${t=>{this.filter=t.target.value,this.idx=0}} />
</div>
<div class="list">
${e.map((t,a)=>c`
<div class="item ${a===this.idx?"active":""}" @click=${()=>{this.idx=a,this.runIdx()}}>
<span class="ico">${t.ico}</span>
<span class="lbl">${t.label}</span>
${t.kbd?c`<span class="kbd">${t.kbd}</span>`:""}
</div>
`)}
</div>
</div>
`}};U.styles=y`
:host {
position: fixed; inset: 0; z-index: 220;
background: rgba(0,0,0,0.5);
opacity: 0; pointer-events: none;
transition: opacity 0.15s;
display: flex; justify-content: center; padding-top: 12vh;
backdrop-filter: blur(4px);
}
:host([open]) { opacity: 1; pointer-events: auto; }
.palette {
width: min(560px, 92vw);
background: var(--bg-1);
border: 1px solid var(--line-2);
border-radius: var(--radius);
box-shadow: 0 30px 80px -20px rgba(0,0,0,0.7);
overflow: hidden;
display: flex; flex-direction: column;
max-height: 60vh;
}
.input {
padding: 14px 16px;
border-bottom: 1px solid var(--line);
}
input {
width: 100%;
background: transparent; border: none; outline: none;
color: var(--ink); font-size: 14px;
font-family: inherit;
}
.list { flex: 1; overflow-y: auto; padding: 4px; }
.item {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 12.5px;
}
.item.active { background: var(--bg-3); }
.item .ico { width: 20px; text-align: center; color: var(--accent); }
.item .lbl { flex: 1; }
.item .kbd {
font-family: var(--mono); font-size: 10.5px;
color: var(--ink-3);
padding: 1px 5px; background: var(--bg-3); border-radius: 4px;
}
`;ge([u()],U.prototype,"open",2);ge([u()],U.prototype,"filter",2);ge([u()],U.prototype,"idx",2);ge([Ue("#palette-input")],U.prototype,"inputEl",2);U=ge([k("nv-palette")],U);var Ht=Object.defineProperty,Lt=Object.getOwnPropertyDescriptor,He=(e,t,a,i)=>{for(var s=i>1?void 0:i?Lt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Ht(t,a,s),s};let pe=class extends w{constructor(){super(...arguments),this.open=!1,this.renderFps=0,this.lastTs=performance.now(),this.frameCount=0,this.rafId=0,this.onKey=e=>{e.key==="`"&&!e.target.matches("input, textarea")&&(this.open=!this.open,this.toggleAttribute("open",this.open))},this.tick=()=>{this.rafId=requestAnimationFrame(this.tick);const e=performance.now();this.frameCount++,e-this.lastTs>=500&&(this.renderFps=this.frameCount*1e3/(e-this.lastTs),this.frameCount=0,this.lastTs=e,this.requestUpdate())}}connectedCallback(){super.connectedCallback(),window.addEventListener("keydown",this.onKey),f(()=>{M.value,Fe.value,V.value,N.value,dt.value,this.requestUpdate()}),this.tick()}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("keydown",this.onKey),cancelAnimationFrame(this.rafId)}render(){return c`
<div class="h"><span>nvsim · debug</span><span class="x" @click=${()=>{this.open=!1,this.removeAttribute("open")}}>✕</span></div>
<div class="row"><span class="k">render fps</span><span class="v">${this.renderFps.toFixed(1)}</span></div>
<div class="row"><span class="k">sim fps</span><span class="v">${M.value>0?Math.round(M.value):"—"}</span></div>
<div class="row"><span class="k">frames</span><span class="v">${Fe.value.toString()}</span></div>
<div class="row"><span class="k">|B|</span><span class="v">${(V.value*1e9).toFixed(3)} nT</span></div>
<div class="row"><span class="k">SNR</span><span class="v">${N.value>0?N.value.toFixed(1):"—"}</span></div>
<div class="row"><span class="k">DOM</span><span class="v">${document.querySelectorAll("*").length}</span></div>
`}};pe.styles=y`
:host {
position: fixed; bottom: 8px; right: 8px;
width: 220px;
background: rgba(13,17,23,0.85);
backdrop-filter: blur(8px);
border: 1px solid var(--line-2);
border-radius: 8px;
padding: 8px 10px;
font-family: var(--mono); font-size: 11px;
color: var(--ink-2);
z-index: 99;
display: none;
box-shadow: var(--shadow);
}
:host([open]) { display: block; }
.h {
display: flex; justify-content: space-between;
font-weight: 600; color: var(--ink);
margin-bottom: 6px; padding-bottom: 4px;
border-bottom: 1px solid var(--line);
}
.x { cursor: pointer; color: var(--ink-3); }
.row {
display: flex; justify-content: space-between;
padding: 1px 0;
}
.k { color: var(--ink-3); }
.v { color: var(--ink); }
`;He([u()],pe.prototype,"open",2);He([u()],pe.prototype,"renderFps",2);pe=He([k("nv-debug-hud")],pe);var Nt=Object.defineProperty,Bt=Object.getOwnPropertyDescriptor,Je=(e,t,a,i)=>{for(var s=i>1?void 0:i?Bt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Nt(t,a,s),s};let ze=class extends w{constructor(){super(...arguments),this.open=!1}connectedCallback(){super.connectedCallback(),f(()=>{x.value,_.value,C.value,be.value,T.value,Re.value,this.requestUpdate()}),window.addEventListener("open-settings",()=>{this.open=!0,this.setAttribute("open","")})}close(){this.open=!1,this.removeAttribute("open")}render(){return c`
<div class="scrim" @click=${()=>this.close()}></div>
<div class="h">
<div class="ttl">Settings</div>
<button class="close" @click=${()=>this.close()}>×</button>
</div>
<div class="body">
<div class="group">
<h4>Appearance</h4>
<div class="row">
<div><div class="lbl">Theme</div></div>
<div class="seg">
<button class=${x.value==="dark"?"on":""} @click=${()=>x.value="dark"}>dark</button>
<button class=${x.value==="light"?"on":""} @click=${()=>x.value="light"}>light</button>
</div>
</div>
<div class="row">
<div>
<div class="lbl">Density</div>
<div class="desc">Affects panel padding and font scale.</div>
</div>
<div class="seg">
<button class=${_.value==="comfy"?"on":""} @click=${()=>_.value="comfy"}>comfy</button>
<button class=${_.value==="default"?"on":""} @click=${()=>_.value="default"}>default</button>
<button class=${_.value==="compact"?"on":""} @click=${()=>_.value="compact"}>compact</button>
</div>
</div>
<div class="row">
<div>
<div class="lbl">Reduce motion</div>
<div class="desc">Disable rotating crystal & field-line animation.</div>
</div>
<span class="toggle ${C.value?"on":""}"
@click=${()=>C.value=!C.value}></span>
</div>
</div>
<div class="group">
<h4>Pipeline</h4>
<div class="row">
<div><div class="lbl">Auto-rerun on edit</div>
<div class="desc">Restart pipeline when scene/config changes.</div></div>
<span class="toggle ${be.value?"on":""}"
@click=${()=>be.value=!be.value}></span>
</div>
</div>
<div class="group">
<h4>Transport</h4>
<div class="row">
<div><div class="lbl">Mode</div></div>
<div class="seg">
<button class=${T.value==="wasm"?"on":""} @click=${()=>T.value="wasm"}>WASM</button>
<button class=${T.value==="ws"?"on":""} @click=${()=>T.value="ws"}>WS</button>
</div>
</div>
${T.value==="ws"?c`
<div class="row">
<div><div class="lbl">WS URL</div></div>
<input type="text" placeholder="ws://localhost:7878" .value=${Re.value}
@input=${e=>Re.value=e.target.value} />
</div>`:""}
</div>
</div>
`}};ze.styles=y`
:host {
position: fixed; top: 0; right: 0; bottom: 0;
width: 420px; max-width: 100vw;
background: var(--bg-1);
border-left: 1px solid var(--line);
z-index: 51;
transform: translateX(100%);
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex; flex-direction: column;
box-shadow: -20px 0 60px -20px rgba(0,0,0,0.5);
}
:host([open]) { transform: translateX(0); }
.scrim {
position: fixed; inset: 0;
background: rgba(0,0,0,0.5);
z-index: 50;
opacity: 0; pointer-events: none;
transition: opacity 0.2s;
}
:host([open]) .scrim { opacity: 1; pointer-events: auto; }
.h {
padding: 14px 16px;
border-bottom: 1px solid var(--line);
display: flex; align-items: center; justify-content: space-between;
}
.h .ttl { font-size: 14px; font-weight: 600; }
.body { flex: 1; overflow-y: auto; padding: 16px; }
.group { margin-bottom: 22px; }
.group h4 {
margin: 0 0 10px;
font-size: 11px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.08em;
color: var(--ink-3);
}
.row {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 0;
border-bottom: 1px solid var(--line);
}
.row:last-child { border-bottom: 0; }
.row .lbl { font-size: 13px; }
.row .desc { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.row > div:first-child { flex: 1; padding-right: 12px; }
.seg {
display: inline-flex;
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 2px;
}
.seg button {
padding: 4px 10px;
background: transparent; border: none;
border-radius: 6px;
font-size: 11.5px; color: var(--ink-3);
font-family: var(--mono);
cursor: pointer;
}
.seg button.on { background: var(--bg-1); color: var(--ink); }
.toggle {
position: relative;
width: 36px; height: 20px;
background: var(--bg-3);
border: 1px solid var(--line-2);
border-radius: 999px;
cursor: pointer;
flex-shrink: 0;
}
.toggle::after {
content: ''; position: absolute;
top: 2px; left: 2px;
width: 14px; height: 14px;
background: var(--ink-3);
border-radius: 50%;
transition: transform 0.15s, background 0.15s;
}
.toggle.on { background: var(--accent); border-color: var(--accent); }
.toggle.on::after { background: #1a0f00; transform: translateX(16px); }
.close {
width: 28px; height: 28px;
background: transparent; border: 1px solid var(--line);
border-radius: 6px;
color: var(--ink-2);
}
input[type="text"] {
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: 6px;
padding: 6px 10px;
color: var(--ink); font-family: var(--mono); font-size: 12px;
outline: none;
}
`;Je([u()],ze.prototype,"open",2);ze=Je([k("nv-settings-drawer")],ze);var Wt=Object.defineProperty,Vt=Object.getOwnPropertyDescriptor,Le=(e,t,a,i)=>{for(var s=i>1?void 0:i?Vt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Wt(t,a,s),s};const xe=[{title:"Welcome to nvsim",body:`<p><b>nvsim</b> is an open-source, deterministic forward simulator for
nitrogen-vacancy diamond magnetometry — a real Rust crate compiled to
WASM and running in your browser, right now.</p>
<p>This 30-second tour highlights the four panels you'll use most.</p>`,cta:"Start tour"},{title:"1. Scene canvas",body:`<p>The middle panel shows your <b>magnetic scene</b> — sources you can
drag (rebar, heart proxy, mains hum, ferrous door) and a single NV-diamond
sensor in the centre. Field lines from each source connect to the sensor
and animate while the pipeline runs.</p>
<p>Click <code>2</code> on your keyboard any time to jump to the Frame inspector.</p>`},{title:"2. Run the pipeline",body:`<p>Click the <b>▶ Run</b> button (top-right) to start streaming
<code>MagFrame</code> records at the digitiser's sample rate. The B-vector
trace and Frame stream sparkline update live, and the FPS pill in the
topbar shows the simulator's throughput in kHz.</p>
<p><kbd>Space</kbd> toggles run/pause from anywhere.</p>`},{title:"3. Witness panel",body:`<p>The <b>Witness</b> tab is the heart of nvsim's determinism contract.
Click <b>Verify</b> and the pipeline re-derives the SHA-256 over a 256-frame
reference run and asserts it matches the constant pinned in the Rust crate.</p>
<p>Same input → same hash → byte-for-byte across browsers, OSes, transports.
If the hash drifts, your build is non-canonical.</p>`},{title:"4. App Store",body:`<p>The grid icon on the left rail opens the <b>App Store</b> — every
hot-loadable WASM edge module RuView ships, plus the simulators. 66 apps
across 13 categories: medical, security, building, retail, industrial,
signal, learning, autonomy, and more.</p>
<p>Toggle any card to mark it active in this session; the WS transport
will push the activation set to a connected ESP32 mesh.</p>`},{title:"You are ready",body:`<p>Press <kbd>⌘K</kbd> (or <kbd>Ctrl K</kbd>) any time for the command
palette, <kbd>?</kbd> for the full shortcuts list, or just start clicking.</p>
<p>Source on GitHub:
<code>github.com/ruvnet/RuView</code> · ADR-089, ADR-092 · MIT/Apache-2.0.</p>`,cta:"Get started"}];let ue=class extends w{constructor(){super(...arguments),this.open=!1,this.step=0,this.show=()=>{this.step=0,this.open=!0,this.setAttribute("open","")}}async connectedCallback(){super.connectedCallback(),window.addEventListener("nv-show-tour",this.show),await O("onboarding-seen")||(this.open=!0,this.setAttribute("open",""))}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("nv-show-tour",this.show)}async dismiss(){this.open=!1,this.removeAttribute("open"),await I("onboarding-seen",!0)}next(){this.step<xe.length-1?this.step++:this.dismiss()}prev(){this.step>0&&this.step--}render(){const e=xe[this.step];return c`
<div class="card" role="dialog" aria-modal="true" aria-label="Welcome tour">
<div class="h">
<h2>${e.title}</h2>
<button class="skip" @click=${()=>this.dismiss()} aria-label="Skip tour">×</button>
</div>
<div class="body" .innerHTML=${e.body}></div>
<div class="footer">
<div class="dots">
${xe.map((t,a)=>c`<div class="dot ${a===this.step?"active":""}"></div>`)}
</div>
${this.step>0?c`<button class="ghost" @click=${()=>this.prev()}>Back</button>`:""}
<button class="primary" @click=${()=>this.next()}>
${this.step===xe.length-1?e.cta??"Done":e.cta??"Next"}
</button>
</div>
</div>
`}};ue.styles=y`
:host {
position: fixed; inset: 0;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(4px);
z-index: 240;
display: grid; place-items: center;
opacity: 0; pointer-events: none;
transition: opacity 0.18s;
}
:host([open]) { opacity: 1; pointer-events: auto; }
.card {
background: var(--bg-1);
border: 1px solid var(--line-2);
border-radius: var(--radius);
box-shadow: 0 30px 80px -20px rgba(0,0,0,0.7);
width: min(560px, 92vw);
max-height: 86vh;
display: flex; flex-direction: column;
transform: translateY(12px) scale(0.98);
transition: transform 0.22s cubic-bezier(0.2,0.7,0.3,1);
}
:host([open]) .card { transform: translateY(0) scale(1); }
.h {
padding: 20px 22px 8px;
display: flex; justify-content: space-between; align-items: flex-start;
}
.h h2 { margin: 0; font-size: 18px; letter-spacing: -0.01em; }
.body {
padding: 8px 22px 16px;
font-size: 13px; color: var(--ink-2); line-height: 1.55;
overflow-y: auto;
}
.body p { margin: 0 0 12px; }
.body code, .body kbd {
font-family: var(--mono); font-size: 11.5px;
padding: 1px 5px; background: var(--bg-3);
border: 1px solid var(--line); border-radius: 4px;
color: var(--accent);
}
.footer {
display: flex; align-items: center; gap: 12px;
padding: 12px 22px;
border-top: 1px solid var(--line);
}
.dots { display: flex; gap: 6px; flex: 1; }
.dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--bg-3); border: 1px solid var(--line-2);
}
.dot.active { background: var(--accent); border-color: var(--accent); }
button {
padding: 8px 14px;
border-radius: 8px;
font-size: 12.5px; font-weight: 500;
border: 1px solid var(--line);
background: var(--bg-2); color: var(--ink);
cursor: pointer;
font-family: inherit;
}
button.primary {
background: var(--accent); border-color: var(--accent);
color: #1a0f00;
}
button.ghost { background: transparent; }
.skip {
width: 28px; height: 28px;
background: transparent; border: 1px solid var(--line);
border-radius: 6px; color: var(--ink-2);
}
`;Le([u()],ue.prototype,"open",2);Le([u()],ue.prototype,"step",2);ue=Le([k("nv-onboarding")],ue);var jt=Object.defineProperty,Ut=Object.getOwnPropertyDescriptor,te=(e,t,a,i)=>{for(var s=i>1?void 0:i?Ut(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&jt(t,a,s),s};const qt=[{id:"nvBest",label:"NV-ensemble (best lab)",floorT:1e-12,color:"oklch(0.78 0.14 70)"},{id:"nvCots",label:"NV-DNV-B1 (COTS)",floorT:3e-10,color:"oklch(0.72 0.18 50)"},{id:"squid",label:"SQUID (shielded room)",floorT:1e-15,color:"oklch(0.78 0.12 195)"},{id:"mmw",label:"60 GHz mmWave (μ-Doppler)",floorT:0,color:"oklch(0.78 0.14 145)"},{id:"csi",label:"WiFi CSI (presence)",floorT:0,color:"oklch(0.72 0.18 330)"}];let D=class extends w{constructor(){super(...arguments),this.distanceM=.1,this.momentLog10=-8.3,this.result=null,this.running=!1,this.err=null}predictedDipoleFieldT(e,t){return 4*Math.PI*1e-7*t/(4*Math.PI*Math.pow(Math.max(e,1e-6),3))}async runDemo(){const e=h();if(!e){this.err="WASM client not ready";return}this.err=null,this.running=!0,this.requestUpdate();try{const t=this.distanceM,a=Math.pow(10,this.momentLog10),i={dipoles:[{position:[0,0,t],moment:[0,0,a]}],loops:[],ferrous:[],eddy:[],sensors:[[0,0,0]],ambient_field:[0,0,0]},s={digitiser:{f_s_hz:1e4,f_mod_hz:1e3},sensor:{gamma_fwhm_hz:1e6,t1_s:.005,t2_s:1e-6,t2_star_s:2e-7,contrast:.03,n_spins:1e12,shot_noise_disabled:!1},dt_s:null};this.result=await e.runTransient(i,s,42n,64),d("ok",`ghost-demo · r=${t.toFixed(3)} m · |B| recovered = ${(this.result.bMagT*1e12).toExponential(2)} pT`)}catch(t){this.err=t.message,d("err",`ghost-demo failed: ${this.err}`)}finally{this.running=!1,this.requestUpdate()}}formatField(e){if(e===0)return"0 T";const t=Math.abs(e);return t>=.001?`${(e*1e3).toFixed(2)} mT`:t>=1e-6?`${(e*1e6).toFixed(2)} µT`:t>=1e-9?`${(e*1e9).toFixed(3)} nT`:t>=1e-12?`${(e*1e12).toFixed(2)} pT`:t>=1e-15?`${(e*1e15).toFixed(2)} fT`:t>=1e-18?`${(e*1e18).toFixed(2)} aT`:`${e.toExponential(2)} T`}formatDistance(e){return e<1?`${(e*100).toFixed(1)} cm`:e<1e3?`${e.toFixed(2)} m`:e<1e5?`${(e/1e3).toFixed(2)} km`:`${(e/1609).toFixed(0)} mi`}renderDemo(){const e=Math.pow(10,this.momentLog10),t=this.predictedDipoleFieldT(this.distanceM,e),a=this.result?.bMagT??0,i=(this.result?.noiseFloorPtSqrtHz??0)*1e-12,s=qt.map(o=>{let l="bad",v="below floor";if(o.id==="mmw")this.distanceM<=5?(l="ok",v="µ-Doppler @ chest"):this.distanceM<=15?(l="warn",v="edge of range"):(l="bad",v="out of range");else if(o.id==="csi")this.distanceM<=30?(l=this.distanceM<=10?"ok":"warn",v="presence/breathing"):(l="bad",v="out of range");else if(o.floorT>0){const b=t/o.floorT;b>100?(l="ok",v=`${b.toExponential(1)}× floor`):b>1?(l="warn",v=`${b.toFixed(1)}× floor`):(l="bad",v=`${(1/b).toExponential(1)}× too weak`)}const m=o.floorT>0?Math.max(2,Math.min(100,100+12*Math.log10(t/o.floorT))):o.id==="mmw"?Math.max(2,100-this.distanceM*7):Math.max(2,100-this.distanceM*2);return c`
<div class="tier-bar" data-tier=${o.id}>
<div class="fill" style=${`width:${m}%; background:${o.color}; border-color:${o.color}`}></div>
<div class="lbl">
<span>${o.label}</span>
<span class="verdict-${l}" style=${`color:${l==="ok"?"var(--ok)":l==="warn"?"var(--warn)":"var(--bad)"}`}>${v}</span>
</div>
</div>
`}),r=t>1e-12?"ok":t>1e-15?"warn":"bad",n=r==="ok"?`Above NV-ensemble lab floor — close-range MCG plausible at ${this.formatDistance(this.distanceM)}.`:r==="warn"?`Below NV ensemble best, above SQUID — research-grade only at ${this.formatDistance(this.distanceM)}.`:`Below every published instrument's noise floor at ${this.formatDistance(this.distanceM)}. Press-release physics.`;return c`
<div class="demo">
<h3 style="margin: 0 0 6px;">Try it yourself</h3>
<div style="font-size: 12.5px; color: var(--ink-2); margin-bottom: 4px; line-height: 1.5;">
Place a cardiac dipole at variable distance from the NV sensor. The
dashboard runs the <i>real</i> nvsim Rust pipeline (compiled to WASM)
end-to-end and reports what each tier would actually detect. Same
determinism contract as the rest of the dashboard.
</div>
<div class="demo-grid">
<div>
<div class="control">
<div class="top">
<span class="lbl">Distance from sensor</span>
<span class="val" id="demo-dist-val">${this.formatDistance(this.distanceM)}</span>
</div>
<input type="range" id="demo-distance"
min="-2" max="5" step="0.05"
.value=${String(Math.log10(this.distanceM))}
@input=${o=>{this.distanceM=Math.pow(10,+o.target.value)}} />
<div style="font-size: 10.5px; color: var(--ink-3); margin-top: 4px; font-family: var(--mono);">
10 cm → 100 km log scale
</div>
</div>
<div class="control">
<div class="top">
<span class="lbl">Heart dipole moment</span>
<span class="val" id="demo-moment-val">${e.toExponential(2)} A·m²</span>
</div>
<input type="range" id="demo-moment"
min="-10" max="-6" step="0.05"
.value=${String(this.momentLog10)}
@input=${o=>{this.momentLog10=+o.target.value}} />
<div style="font-size: 10.5px; color: var(--ink-3); margin-top: 4px; font-family: var(--mono);">
published cardiac MCG ≈ 5×10⁻⁹ A·m²
</div>
</div>
<button class="demo-btn" id="demo-run-btn" ?disabled=${this.running}
@click=${()=>this.runDemo()}>
${this.running?"Running nvsim…":"▶ Run nvsim at this distance"}
</button>
${this.err?c`<div class="verdict bad" style="margin-top: 10px;">Error: ${this.err}</div>`:""}
</div>
<div>
<div class="readout">
<div class="readout-row">
<span class="l">Predicted |B| (1/r³)</span>
<span class="v amber" id="demo-predicted">${this.formatField(t)}</span>
</div>
<div class="readout-row">
<span class="l">Recovered |B| (nvsim)</span>
<span class="v" id="demo-recovered">${this.result?this.formatField(a):"—"}</span>
</div>
<div class="readout-row">
<span class="l">Sensor noise floor</span>
<span class="v" id="demo-floor">${this.result?this.formatField(i)+"/√Hz":"—"}</span>
</div>
<div class="readout-row">
<span class="l">Frames run</span>
<span class="v" id="demo-frames">${this.result?.nFrames??"—"}</span>
</div>
<div class="readout-row">
<span class="l">Witness (this run)</span>
<span class="v" style="font-size: 10px;" id="demo-witness">${this.result?.witnessHex.slice(0,16)??"—"}…</span>
</div>
</div>
<div style="margin-top: 14px;">
<div style="font-size: 11.5px; color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px;">
Per-tier detectability
</div>
${s}
</div>
</div>
</div>
<div class="verdict ${r}" id="demo-verdict">${n}</div>
<div class="demo-notes">
The <code>predicted</code> value uses the closed-form magnetic-dipole
far field <code>|B| = μ₀·m / (4π·r³)</code>. The <code>recovered</code>
value comes from the same Rust pipeline that drives the Witness panel —
scene → Biot-Savart → NV ensemble → ADC → MagFrame. Use the moment
slider to ask "what if the heart were stronger?". Use the distance
slider to walk through 10 cm (clinical MCG), 1 m (close approach),
10 m (room-scale), 1 km (skeptic's range), and 65 km (the press claim).
</div>
</div>
`}render(){return c`
<h1>Ghost Murmur — open-source reality check</h1>
<div class="subtitle">
The physics-vs-press audit for the publicly-reported April 2026
CIA NV-diamond heartbeat detector, and how RuView's existing
stack maps onto an honest, civilian version of the same idea.
</div>
<div class="links">
<a href="https://github.com/ruvnet/RuView/blob/feat/nvsim-pipeline-simulator/docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md" target="_blank" rel="noopener">
📄 Full spec (583 lines)
</a>
<a href="https://gist.github.com/ruvnet/e44d0c3f0ad10d9c4933a196a16d405c" target="_blank" rel="noopener">
✦ Public gist
</a>
<a href="https://github.com/ruvnet/RuView/issues/437" target="_blank" rel="noopener">
# Issue #437
</a>
<a href="https://www.scientificamerican.com/article/what-is-the-quantum-ghost-murmur-purportedly-used-in-iran-scientists/" target="_blank" rel="noopener">
↗ Scientific American
</a>
</div>
<h2>What the press reported</h2>
<div class="grid">
<div class="card">
<h3>The story</h3>
<p>3 Apr 2026: USAF F-15E pilot "Dude 44 Bravo" goes down in southern Iran during the regional exchange and evades for ~2 days.</p>
<p>President Trump publicly suggests detection from <b>40 miles away</b> on a mountainside at night; CIA Director Ratcliffe says "invisible to the enemy, but not to the CIA."</p>
</div>
<div class="card">
<h3>The named tech</h3>
<p><b>"Ghost Murmur"</b> — Lockheed Skunk Works system using NV defects in synthetic diamond + AI to extract a heartbeat from environmental noise.</p>
<p>Outlets: <i>Newsweek, Scientific American, Military.com, WION, Open The Magazine, Yahoo, Calcalist</i> + HN thread #47679241.</p>
</div>
<div class="card">
<h3>What physicists said</h3>
<p>Wikswo (Vanderbilt), Orzel (Union College), Roth (Oakland) — all pushing back hard.</p>
<p>"At 1 km, the heartbeat field drops to ~10⁻¹² of its 10 cm value." MCG-only at multi-mile range is <span class="pill skeptical">not consistent with published physics</span>.</p>
</div>
</div>
<h2>Live demo — nvsim WASM</h2>
${this.renderDemo()}
<h2>Physics reality check</h2>
<div class="card" style="padding: 6px 14px;">
<table>
<thead>
<tr><th>Distance</th><th>Cardiac MCG (peak QRS)</th><th>vs Earth field (~50 µT)</th></tr>
</thead>
<tbody>
<tr><td>10 cm</td><td class="amber">50 pT</td><td>10⁹× weaker</td></tr>
<tr><td>1 m</td><td class="amber">50 fT</td><td>10¹²× weaker</td></tr>
<tr><td>10 m</td><td class="cyan">50 aT</td><td>10¹⁵× weaker</td></tr>
<tr><td>1 km</td><td class="bad">5 × 10⁻²³ T</td><td>10²⁷× weaker</td></tr>
<tr><td>40 mi (65 km)</td><td class="bad">~10⁻²⁸ T</td><td>10³³× weaker</td></tr>
</tbody>
</table>
<p style="font-size: 12px; color: var(--ink-3); margin: 10px 0 0; line-height: 1.5;">
Best published NV-ensemble lab record: <b>0.9 pT/√Hz</b> [Wolf 2015].
Best SQUID in a shielded room: <b>~1 fT/√Hz</b>. To detect a single heartbeat at 10 m
you'd need ~2 billion× more sensitivity than any published ensemble has ever shown,
in a magnetically silent environment. <i>40 miles is press-release physics.</i>
</p>
</div>
<h2>RuView's three-tier mesh — what is actually buildable</h2>
<div class="architecture"> ┌──────────────────────────┐
│ Tier 3 — NV-diamond │ Range: 0.12 m (lab)
│ magnetometer ring │ Status: nvsim simulator only
│ (close-confirm) │ Hardware: $$$ (≥$8k DNV-B1)
└──────────┬───────────────┘
┌──────────┴───────────────┐
│ Tier 2 — 60 GHz FMCW │ Range: 110 m HR/BR
│ mmWave radar mesh │ Status: shipping (ADR-021)
│ (vital signs, posture) │ Hardware: $15 (MR60BHA2 + ESP32-C6)
└──────────┬───────────────┘
┌──────────┴───────────────┐
│ Tier 1 — WiFi CSI mesh │ Range: 1030 m through-wall
│ (presence, breathing, │ Status: shipping (ADR-014, ADR-029)
│ pose, intention) │ Hardware: $9 (ESP32-S3 8MB)
└──────────┬───────────────┘
┌────────────────────────────────┐
│ RuvSense multistatic fusion │
│ + cross-viewpoint attention │
│ + AETHER re-ID embeddings │
│ + Cramer-Rao gating │
└────────────────────────────────┘</div>
<h2>Press claim → RuView equivalent</h2>
<div class="card" style="padding: 6px 14px;">
<table>
<thead>
<tr><th>Press claim</th><th>RuView equivalent today</th><th>Crate / ADR</th><th>Honest range</th></tr>
</thead>
<tbody>
<tr>
<td>NV-diamond magnetometry</td>
<td>Deterministic NV pipeline simulator</td>
<td><code>nvsim</code> · ADR-089</td>
<td>Simulator only</td>
</tr>
<tr>
<td>"AI strips environmental noise"</td>
<td>RuvSense multistatic fusion + AETHER</td>
<td>signal/ruvsense/ · ADR-029</td>
<td>Mature</td>
</tr>
<tr>
<td>Heartbeat at distance</td>
<td>60 GHz FMCW HR/BR + WiFi CSI breathing</td>
<td>vitals · ADR-021</td>
<td><span class="pill ok">15 m HR · 1030 m presence</span></td>
</tr>
<tr>
<td>Long-range localisation</td>
<td>Multistatic time-of-flight + CRLB</td>
<td>ruvector/viewpoint/</td>
<td>Limited by node spacing</td>
</tr>
<tr>
<td><i>40-mile single-heartbeat detection</i></td>
<td><i>Not feasible at any tier</i></td>
<td>—</td>
<td><span class="pill skeptical">Press-release physics</span></td>
</tr>
</tbody>
</table>
</div>
<h2>Build today on $165</h2>
<div class="grid">
<div class="card">
<h3>Bill of materials</h3>
<p style="font-family: var(--mono); font-size: 11.5px; line-height: 1.7; color: var(--ink-2);">
3 × ESP32-S3 8 MB ($9 ea)<br>
3 × PoE injector + cat6 ($6 ea)<br>
1 × ESP32-C6 + Seeed MR60BHA2 ($15)<br>
1 × Raspberry Pi 5 8 GB ($80)<br>
1 × unmanaged GbE switch ($25)
</p>
<p><b>Total: $165</b></p>
</div>
<div class="card">
<h3>Honest performance</h3>
<span class="stat"><span class="v">95%</span><span class="l">TPR (LOS, 015 m)</span></span><br><br>
<span class="stat"><span class="v">±2 bpm</span><span class="l">HR (LOS 03 m)</span></span><br><br>
<span class="stat"><span class="v">±1 br/min</span><span class="l">BR (any mode)</span></span><br><br>
<span class="stat"><span class="v">~10 cm</span><span class="l">pose error</span></span><br><br>
<span class="stat"><span class="v">80150 ms</span><span class="l">end-to-end latency</span></span>
</div>
<div class="card">
<h3>Determinism</h3>
<p>Same <code style="font-family: var(--mono); color: var(--accent);">(scene, config, seed)</code> → byte-identical SHA-256 witness across browsers, OSes, transports.</p>
<p>Reference: <span style="font-family: var(--mono); font-size: 10.5px; color: var(--accent-3);">cc8de9b01b0ff5bd…</span></p>
<p>Try the Witness tab on the right — it re-derives the hash live in this browser and compares against the published reference.</p>
</div>
</div>
<h2>Privacy, ethics, legal</h2>
<div class="ethics">
<h3>This is the open-source version. Same physics, opposite governance.</h3>
<ul>
<li><b>Civilian opt-in only</b> — search-and-rescue, elder-care, occupancy, ICU vitals. Not surveillance.</li>
<li><b>No directional pursuit</b> — no beam-steering, target-following, or remote person-of-interest tracking.</li>
<li><b>Data minimisation</b> — fused output is <code>(presence, HR, BR, pose, p_alive)</code>; raw streams discarded at the edge.</li>
<li><b>PII gates</b> (ADR-040) block identifying biometric streams from leaving the local mesh without consent.</li>
<li><b>Adversarial-signal detection</b> flags physically-impossible signal patterns from compromised mesh nodes.</li>
<li><b>No export-controlled hardware</b> — RuView targets &lt; $50 COTS. ITAR/EAR sub-THz coherent radars and shielded NV ensembles are out of scope.</li>
</ul>
<p style="font-size: 11.5px; color: var(--ink-3); margin: 10px 0 0;">
RuView is not affiliated with the United States government, the CIA, Lockheed Martin,
or any classified program. References to "Ghost Murmur" in this view refer
exclusively to the publicly-reported program of that name as covered in the open
press in April 2026.
</p>
</div>
<h2>Cross-references</h2>
<div class="card">
<p style="font-size: 12px; color: var(--ink-2); line-height: 1.7; margin: 0;">
<b>ADRs:</b> 014 (signal) · 021 (vitals) · 024 (AETHER) · 027 (MERIDIAN) ·
028 (witness audit) · 029 (RuvSense) · 040 (PII gates) · 086 (ESP32 RaBitQ) ·
<b>089 (nvsim, Accepted)</b> · 090 (Lindblad, Proposed-conditional) ·
091 (sub-THz radar research) · <b>092 (this dashboard)</b>.<br><br>
<b>Primary physics:</b> Cohen 1970 · Bison 2009 · Wolf 2015 · Barry RMP 2020 · Doherty 2013 · Jackson 3e §5.6/§5.8.
</p>
</div>
`}};D.styles=y`
:host {
display: block;
height: 100%;
overflow-y: auto;
background: radial-gradient(ellipse at 50% 30%, var(--bg-2) 0%, var(--bg-0) 70%);
padding: 24px 28px 60px;
}
h1 {
margin: 0 0 4px;
font-size: 22px;
letter-spacing: -0.02em;
color: var(--ink);
}
.subtitle {
color: var(--ink-3);
font-size: 13px;
margin-bottom: 22px;
}
.links {
display: flex; flex-wrap: wrap; gap: 6px;
margin-bottom: 22px;
}
.links a {
padding: 5px 10px;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: 999px;
font-size: 11.5px;
font-family: var(--mono);
color: var(--accent-2);
text-decoration: none;
}
.links a:hover { border-color: var(--accent-2); }
h2 {
font-size: 14px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-3);
margin: 28px 0 10px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 12px;
}
.card {
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 14px;
}
.card h3 {
margin: 0 0 8px;
font-size: 13.5px; font-weight: 600;
color: var(--ink);
}
.card p {
font-size: 12.5px; color: var(--ink-2);
margin: 0 0 8px;
line-height: 1.5;
}
.card p:last-child { margin-bottom: 0; }
.stat {
display: inline-flex; align-items: baseline; gap: 6px;
margin-right: 10px;
}
.stat .v {
font-family: var(--mono); font-size: 16px; font-weight: 600;
color: var(--accent);
}
.stat .l {
font-size: 10px; color: var(--ink-3);
text-transform: uppercase; letter-spacing: 0.04em;
}
table {
width: 100%; border-collapse: collapse;
font-size: 12.5px;
}
th, td {
padding: 8px 10px;
text-align: left;
border-bottom: 1px solid var(--line);
}
th {
color: var(--ink-3);
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
td.amber { color: var(--accent); font-family: var(--mono); }
td.cyan { color: var(--accent-2); font-family: var(--mono); }
td.bad { color: var(--bad); font-family: var(--mono); }
.pill {
display: inline-block;
padding: 1px 6px;
border-radius: 4px;
font-family: var(--mono);
font-size: 10px;
border: 1px solid var(--line);
}
.pill.ok { color: var(--ok); border-color: oklch(0.78 0.14 145 / 0.4); }
.pill.skeptical { color: var(--bad); border-color: oklch(0.65 0.22 25 / 0.4); }
.pill.partial { color: var(--warn); border-color: oklch(0.7 0.18 35 / 0.4); }
.architecture {
font-family: var(--mono);
font-size: 11px;
color: var(--ink-2);
background: var(--bg-3);
padding: 16px;
border-radius: var(--radius-sm);
border: 1px solid var(--line);
white-space: pre;
overflow-x: auto;
line-height: 1.4;
}
.ethics {
background: linear-gradient(180deg, var(--bg-2) 0%, oklch(0.65 0.22 25 / 0.04) 100%);
border: 1px solid oklch(0.65 0.22 25 / 0.25);
border-radius: var(--radius);
padding: 16px;
}
.ethics h3 { color: var(--bad); margin-top: 0; }
.ethics ul { padding-left: 18px; margin: 8px 0; }
.ethics li { font-size: 12.5px; color: var(--ink-2); margin-bottom: 4px; }
/* Demo */
.demo {
background: linear-gradient(180deg, var(--bg-2) 0%, oklch(0.78 0.14 70 / 0.04) 100%);
border: 1px solid oklch(0.78 0.14 70 / 0.3);
border-radius: var(--radius);
padding: 18px;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin-top: 12px;
}
@media (max-width: 720px) { .demo-grid { grid-template-columns: 1fr; } }
.control { margin-bottom: 14px; }
.control .top {
display: flex; justify-content: space-between;
font-size: 12px; margin-bottom: 6px;
}
.control .top .lbl { color: var(--ink-3); }
.control .top .val {
font-family: var(--mono); color: var(--ink);
}
.control input[type="range"] {
-webkit-appearance: none; appearance: none;
width: 100%; height: 4px;
background: var(--bg-3); border-radius: 2px; outline: none;
}
.control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 14px; height: 14px; border-radius: 50%;
background: var(--accent); cursor: pointer;
border: 2px solid var(--bg-2);
}
.demo-btn {
width: 100%;
padding: 10px;
border: 1px solid var(--accent);
background: var(--accent);
color: #1a0f00;
border-radius: 8px;
font-size: 13px; font-weight: 600;
cursor: pointer;
}
.demo-btn:hover { filter: brightness(1.08); }
.demo-btn:disabled { opacity: 0.6; cursor: progress; }
.readout {
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: 8px;
padding: 12px;
}
.readout-row {
display: flex; justify-content: space-between;
padding: 4px 0;
font-family: var(--mono); font-size: 12px;
}
.readout-row .l { color: var(--ink-3); }
.readout-row .v { color: var(--ink); }
.readout-row .v.amber { color: var(--accent); }
.tier-bar {
position: relative;
margin: 6px 0;
height: 22px;
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: 4px;
overflow: hidden;
}
.tier-bar .fill {
position: absolute; top: 0; bottom: 0; left: 0;
transition: width 0.2s ease-out;
border-right: 2px solid;
}
.tier-bar .lbl {
position: relative; z-index: 1;
font-family: var(--mono); font-size: 11px;
padding: 3px 8px;
color: var(--ink);
display: flex; justify-content: space-between;
pointer-events: none;
}
.verdict {
margin-top: 10px;
padding: 10px 12px;
border-radius: 8px;
font-size: 12.5px; font-weight: 500;
border: 1px solid;
}
.verdict.ok { background: oklch(0.78 0.14 145 / 0.08); border-color: oklch(0.78 0.14 145 / 0.4); color: var(--ok); }
.verdict.warn { background: oklch(0.7 0.18 35 / 0.08); border-color: oklch(0.7 0.18 35 / 0.4); color: var(--warn); }
.verdict.bad { background: oklch(0.65 0.22 25 / 0.08); border-color: oklch(0.65 0.22 25 / 0.4); color: var(--bad); }
.demo-notes {
font-size: 11.5px; color: var(--ink-3);
margin-top: 10px; line-height: 1.5;
}
`;te([u()],D.prototype,"distanceM",2);te([u()],D.prototype,"momentLog10",2);te([u()],D.prototype,"result",2);te([u()],D.prototype,"running",2);te([u()],D.prototype,"err",2);D=te([k("nv-ghost-murmur")],D);var Gt=Object.defineProperty,Kt=Object.getOwnPropertyDescriptor,Qe=(e,t,a,i)=>{for(var s=i>1?void 0:i?Kt(t,a):t,r=e.length-1,n;r>=0;r--)(n=e[r])&&(s=(i?n(t,a,s):n(s))||s);return i&&s&&Gt(t,a,s),s};let Me=class extends w{constructor(){super(...arguments),this.view="scene"}render(){return c`
<a class="skip-link" href="#main-content"
@click=${e=>{e.preventDefault(),this.shadowRoot?.querySelector(".main")?.focus()}}>
Skip to main content
</a>
<div class="app">
<nv-rail .view=${this.view} @navigate=${e=>this.view=e.detail}></nv-rail>
<nv-topbar></nv-topbar>
<nv-sidebar></nv-sidebar>
<main class="main" id="main-content" tabindex="-1" role="main" aria-label="Main view">
${this.view==="apps"?c`<nv-app-store></nv-app-store>`:this.view==="ghost-murmur"?c`<nv-ghost-murmur></nv-ghost-murmur>`:this.view==="inspector"?c`<nv-inspector expanded .pinTab=${"signal"}></nv-inspector>`:this.view==="witness"?c`<nv-inspector expanded .pinTab=${"witness"}></nv-inspector>`:c`<nv-scene></nv-scene>`}
</main>
<nv-inspector
.pinTab=${this.view==="inspector"?"signal":this.view==="witness"?"witness":null}>
</nv-inspector>
<nv-console></nv-console>
</div>
<nv-toast></nv-toast>
<nv-modal></nv-modal>
<nv-palette></nv-palette>
<nv-debug-hud></nv-debug-hud>
<nv-settings-drawer></nv-settings-drawer>
<nv-onboarding></nv-onboarding>
`}};Me.styles=y`
:host {
display: block;
height: 100vh;
width: 100vw;
background: var(--bg-0);
}
.skip-link {
position: absolute;
top: -40px;
left: 8px;
padding: 6px 12px;
background: var(--accent);
color: #1a0f00;
border-radius: 6px;
font-size: 12.5px;
font-weight: 600;
text-decoration: none;
z-index: 1000;
transition: top 0.15s;
}
.skip-link:focus { top: 8px; }
.app {
display: grid;
grid-template-columns: 56px 280px 1fr 340px;
grid-template-rows: 48px 1fr 220px;
grid-template-areas:
'rail topbar topbar topbar'
'rail sidebar main inspector'
'rail sidebar console inspector';
height: 100vh;
width: 100vw;
}
nv-rail { grid-area: rail; }
nv-topbar { grid-area: topbar; }
nv-sidebar { grid-area: sidebar; }
.main { grid-area: main; min-width: 0; min-height: 0; position: relative; overflow: hidden; }
nv-inspector { grid-area: inspector; }
nv-console { grid-area: console; min-height: 0; }
@media (max-width: 1180px) {
.app {
grid-template-columns: 56px 1fr 320px;
grid-template-areas:
'rail topbar topbar'
'rail main inspector'
'rail console console';
}
nv-sidebar { display: none; }
}
@media (max-width: 860px) {
.app {
grid-template-columns: 1fr;
grid-template-rows: 52px 1fr 200px;
grid-template-areas:
'topbar'
'main'
'console';
}
nv-rail, nv-sidebar, nv-inspector { display: none; }
}
`;Qe([u()],Me.prototype,"view",2);Me=Qe([k("nv-app")],Me);function Xt(e,t,a){const i=e.getUint32(t+0,!0),s=e.getUint16(t+4,!0),r=e.getUint16(t+6,!0),n=e.getUint16(t+8,!0),o=e.getBigUint64(t+12,!0),l=e.getFloat32(t+20,!0),v=e.getFloat32(t+24,!0),m=e.getFloat32(t+28,!0),b=e.getFloat32(t+32,!0),ae=e.getFloat32(t+36,!0),Ae=e.getFloat32(t+40,!0),Ze=e.getFloat32(t+44,!0),et=e.getFloat32(t+48,!0);return{magic:i,version:s,flags:r,sensorId:n,tUs:o,bPt:[l,v,m],sigmaPt:[b,ae,Ae],noiseFloorPtSqrtHz:Ze,temperatureK:et,raw:a.subarray(t,t+60)}}function Yt(e){const a=new DataView(e.buffer,e.byteOffset,e.byteLength),i=[];for(let s=0;s+60<=e.byteLength;s+=60)i.push(Xt(a,s,e));return i}class Jt{constructor(){this.nextId=1,this.pending=new Map,this.frameSubs=new Set,this.eventSubs=new Set,this.bootInfo=null,this.worker=new Worker(new URL("/RuView/nvsim/assets/worker-C19MRcXs.js",import.meta.url),{type:"module"}),this.worker.addEventListener("message",t=>this.onMessage(t)),this.worker.addEventListener("error",t=>this.eventSubs.forEach(a=>a({type:"log",level:"err",msg:String(t.message)})))}onMessage(t){const a=t.data;if(a.type==="frames"){const i=a.batch,s=new Uint8Array(i),n={frames:Yt(s),bytes:s};this.frameSubs.forEach(l=>l(n));const o=a.fps;o>0&&this.eventSubs.forEach(l=>l({type:"fps",value:o}));return}if(a.type==="state"){this.eventSubs.forEach(i=>i({type:"state",running:!!a.running,t:0,framesEmitted:Number(a.framesEmitted??0)}));return}if(a.type!=="ready"){if(a.type==="err"&&a.id==null){this.eventSubs.forEach(i=>i({type:"log",level:"err",msg:String(a.msg)}));return}if(typeof a.id=="number"&&this.pending.has(a.id)){const i=this.pending.get(a.id);this.pending.delete(a.id),a.type==="err"?i.reject(new Error(String(a.msg))):i.resolve(a)}}}rpc(t,a=[]){const i=this.nextId++;return new Promise((s,r)=>{this.pending.set(i,{resolve:s,reject:r}),this.worker.postMessage({...t,id:i},a)})}async boot(){if(this.bootInfo)return this.bootInfo;const a=await this.rpc({type:"boot",base:"/RuView/nvsim/"});return this.bootInfo={buildVersion:a.buildVersion,frameMagic:a.frameMagic,frameBytes:a.frameBytes,expectedWitnessHex:a.expectedWitnessHex},this.bootInfo}async loadScene(t){await this.rpc({type:"setScene",json:JSON.stringify(t)})}async setConfig(t){await this.rpc({type:"setConfig",json:JSON.stringify(t)})}async setSeed(t){await this.rpc({type:"setSeed",seed:Number(t&0xFFFFFFFFn)})}async reset(){await this.rpc({type:"reset"})}async run(t){await this.rpc({type:"run"})}async pause(){await this.rpc({type:"pause"})}async step(t,a){await this.rpc({type:"step"})}onFrames(t){this.frameSubs.add(t)}onEvent(t){this.eventSubs.add(t)}async generateWitness(t){const a=await this.rpc({type:"witnessGenerate",samples:t});return new Uint8Array(a.witness)}async verifyWitness(t){const a=t.slice().buffer,i=await this.rpc({type:"witnessVerify",samples:256,expected:a},[a]);return i.ok?{ok:!0}:{ok:!1,actual:new Uint8Array(i.actual)}}async runTransient(t,a,i,s){const r=await this.rpc({type:"runTransient",scene:JSON.stringify(t),config:JSON.stringify(a),seed:Number(i&0xFFFFFFFFn),samples:s});return{bRecoveredT:[r.bRecoveredT[0],r.bRecoveredT[1],r.bRecoveredT[2]],bMagT:r.bMagT,noiseFloorPtSqrtHz:r.noiseFloorPtSqrtHz,sigmaPt:[r.sigmaPt[0],r.sigmaPt[1],r.sigmaPt[2]],nFrames:r.nFrames,witnessHex:r.witnessHex}}async exportProofBundle(){const t=await this.generateWitness(256),a=Array.from(t).map(r=>r.toString(16).padStart(2,"0")).join(""),i=this.bootInfo??await this.boot(),s=JSON.stringify({kind:"nvsim-proof-bundle",version:i.buildVersion,seed:"0x0000002A",nSamples:256,witness:a,expected:i.expectedWitnessHex,ok:a===i.expectedWitnessHex,ts:new Date().toISOString()},null,2);return new Blob([s],{type:"application/json"})}async buildId(){return(await this.rpc({type:"buildId"})).buildId}async close(){this.worker.terminate()}}function We(e){document.documentElement.setAttribute("data-theme",e)}function Ve(e){document.body.classList.remove("density-comfy","density-default","density-compact"),document.body.classList.add(`density-${e}`)}function je(e){document.body.classList.toggle("reduce-motion",e)}(async()=>{const e=await O("theme")??"dark",t=await O("density")??"default",a=window.matchMedia?.("(prefers-reduced-motion: reduce)").matches??!1,i=await O("motionReduced")??a;x.value=e,We(e),_.value=t,Ve(t),C.value=i,je(i),f(()=>{We(x.value),I("theme",x.value)}),f(()=>{Ve(_.value),I("density",_.value)}),f(()=>{je(C.value),I("motionReduced",C.value)});const s=await O("repl-history");s&&Array.isArray(s)&&(W.value=s),f(()=>{I("repl-history",W.value)});const r=await O("scene-positions");r&&Array.isArray(r)&&(de.value=r),f(()=>{I("scene-positions",de.value)});const n=new Jt;ut(n),d("info","nvsim — booting WASM runtime"),n.onEvent(o=>{o.type==="log"&&d(o.level,o.msg),o.type==="fps"&&(M.value=o.value),o.type==="state"&&(Fe.value=BigInt(o.framesEmitted))}),n.onFrames(o=>{if(o.frames.length===0)return;const l=o.frames[o.frames.length-1];B.value=l;const v=l.bPt[0]*1e-12,m=l.bPt[1]*1e-12,b=l.bPt[2]*1e-12;ce.value=[v,m,b],V.value=Math.sqrt(v*v+m*m+b*b),gt([v*1e9,m*1e9,b*1e9]);const ae=Math.min(1,Math.abs(b*1e9)/5+.3);mt(ae)});try{const o=await n.boot();R.value=o.expectedWitnessHex,d("ok",`WASM module ready · nvsim@${o.buildVersion} · magic=0x${o.frameMagic.toString(16).toUpperCase()}`),d("info",`expected witness · ${o.expectedWitnessHex.slice(0,16)}`),ct.value="(reference scene)",T.value="wasm"}catch(o){d("err",`boot failed: ${o.message}`)}try{const o=R.value;if(o){const l=new Uint8Array(32);for(let m=0;m<32;m++)l[m]=parseInt(o.slice(m*2,m*2+2),16);const v=await n.verifyWitness(l);if(v.ok)z.value=o,d("ok","witness verified · determinism gate ✓");else{const m=Array.from(v.actual).map(b=>b.toString(16).padStart(2,"0")).join("");z.value=m,d("err",`WITNESS MISMATCH · expected ${o.slice(0,16)}… got ${m.slice(0,16)}`)}}}catch(o){d("warn",`witness verify skipped: ${o.message}`)}})();
//# sourceMappingURL=index-DPROtCvT.js.map