diff --git a/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte b/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte index 1c8881d..0693e34 100644 --- a/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte +++ b/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte @@ -96,6 +96,11 @@ let projectionDays = $state(0); let liveBridge: LiveBridge | null = null; let liveDecayReady = $state(false); + // Live contradiction-firewall verdict — set when a real MemorySuppressed / + // contradiction event quarantines a memory on camera. Held ~7s then fades. + let liveFirewallLabel = $state(''); + let liveFirewallAt = $state(0); + let liveFirewallVisible = $derived(liveFirewallLabel !== '' && liveFirewallAt > 0); // GPU picking — screen px → NDC → NodeRenderer.pickAt (one readback/click). let canvasLayerEl: HTMLDivElement | null = $state(null); @@ -297,7 +302,12 @@ engine, renderer, graph: renderer.graph, - projectionDays: () => projectionDays + seed, + projectionDays: () => projectionDays, + onFirewall: (info) => { + liveFirewallLabel = info.intruderLabel; + liveFirewallAt = Date.now(); + } }); liveDecayReady = liveBridge.liveDecayAvailable; engine.setPreFrameHook((simFrame) => liveBridge?.drain(simFrame)); @@ -324,6 +334,17 @@ liveBridge?.refreshDecay(); }); + // Auto-clear the live firewall verdict ~7s after it fires (matches the + // quarantine choreography length so the card leaves with the crimson ring). + $effect(() => { + if (!liveFirewallAt) return; + const t = setTimeout(() => { + liveFirewallLabel = ''; + liveFirewallAt = 0; + }, 7000); + return () => clearTimeout(t); + }); + onMount(() => { loadGraph(); }); @@ -359,6 +380,28 @@ chrome='none' leaves only loading/error (host page owns the chrome). --> {#if showHud}