feat(dashboard): Organ 1 — Reasoning Theater (Cognitive OS de-risk prototype)

The first full-bleed WebGPU organ of the Cognitive Bioluminescent Cortex,
built by GPT-5.5 in the design council, audited + fixed live by Opus 4.8.

- RouteStage.svelte: reusable organ shell (engine/canvas lifecycle, WebGPU
  fallback, reduced-motion auto-pause, persistent pause control) — the
  lifecycle-only reuse of ObservatoryStage, NOT its Graph data path.
- reasoning/reasoning-scene.ts: /deep_reference → RouteSceneModel adapter.
  Honest 8-stage derivation from real fields (label unexposed internals),
  real backend contradiction {stronger/weaker} + superseded {superseded_by}
  shapes, provenance enforced.
- reasoning/reasoning-theater-pass.ts: the 8-chamber organ. Metaball field
  (additive splat -> render-pass separable blur -> membrane, all render
  passes, no storage textures), compute Bezier spline packet advection,
  labeled deterministic replay of the one-shot receipt. Palette-driven;
  magenta stays RSB-only; contradictions scarlet.
- reasoning/+page.svelte: query -> /deep_reference -> scene -> full-bleed
  organ behind the DOM answer/evidence panels.

Three WebGPU runtime traps caught by the live-GPU audit (invisible to
check/build) and fixed: (T6) no read_write storage in a shared compute+
render module; (T3) no rgba16float write-storage blur -> use render-pass
blur; and the 'active' reserved-keyword WGSL parse error.

Verified LIVE vs the real 1241-memory brain: query "How does FSRS-6 trust
scoring work?" lit the chambers with a REAL memory (trust 51%), 84%
confidence, 26 analyzed, contradiction stage correctly dark (none real),
120fps. check + build + 1043 tests green. Cinema + Graph field untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-07-09 00:10:29 -07:00
parent 9017d731dc
commit b5150f0b90
5 changed files with 1347 additions and 4 deletions

View file

@ -245,9 +245,15 @@ with legacy fallback:
closest real receipt id = `composition_event_id`. There is NO `receipt` field.
**T3 — WebGPU format + FramePass traps.**
- Field ping-pong / storage textures: use `rgba16float`, NOT `rg16float` write-
only storage (narrower support; portability trap). Logical channels in `.rg`,
`.ba` reserved. OR do separable blur as fullscreen render-pass draws.
- HARD RULE (verified live on M-series: `rgba16float-renderable`=false, rgba16float
is NOT a writable storage-texture format here): NEVER use `texture_storage_2d<...,
write>` + `textureStore` for the blur. Do the separable blur as FULLSCREEN
RENDER-PASS draws (H then V) into RENDER_ATTACHMENT textures, sampling the
source via `textureSampleLevel`. Field textures usage = `RENDER_ATTACHMENT |
TEXTURE_BINDING` (NO `STORAGE_BINDING`). The whole field pipeline is render
passes: splat (additive) → blur-H → blur-V → membrane. No storage textures
anywhere. `rgba16float` is fully valid as a RENDER TARGET.
- Logical channels in `.rg`, `.ba` reserved.
- FramePass sequencing: the engine calls ALL `compute()` then opens ONE main HDR
scene pass and calls ALL `render()`. So a field pass does splat+blur INSIDE its
`compute(encoder)` (it receives a `GPUCommandEncoder`, not a compute-pass
@ -270,6 +276,19 @@ shared Graph params. Run `assertProvenance(scene)` in dev before upload.
contradiction seam — WRONG. Contradictions use scarlet/immune reds. Magenta
`#FF2DF7` appears ONLY in the retrograde causal axon.
**T6 — NEVER share one WGSL module between compute and render pipelines when it
declares a `read_write` storage buffer or a `write` storage texture.** WebGPU
forbids read_write storage buffers AND write storage textures in vertex/fragment
stages (compute-only). A render pipeline whose module/layout exposes such a
binding is INVALID at runtime (check+build stay green — only live-GPU validation
catches it). SPLIT WGSL into per-pipeline modules: compute module (advect/blur)
= read_write storage + write storage texture OK; render modules (splat/membrane)
= storage buffers as `var<storage, read>` only. Each pipeline gets its own
explicit bind group layout matching exactly. (Found live in Organ 1's field
splat pass — this is the D1 metaball pattern's #1 trap.)
This is why Claude's LIVE-GPU verify per organ is non-optional: the swarm
verifier's check/build cannot catch invalid-pipeline runtime errors.
## 7. THE PROTOTYPE ACCEPTANCE TEST (Organ 1, de-risks all)
1. User enters a query on `/reasoning`.
2. Backend returns `/deep_reference`.

View file

@ -0,0 +1,199 @@
<script lang="ts">
import { onMount } from 'svelte';
import ObservatoryCanvas from '$lib/components/ObservatoryCanvas.svelte';
import type { ObservatoryEngine, FramePass } from '$lib/observatory/engine';
import { type DemoMode } from '$lib/observatory/types';
import {
assertProvenance,
emptyScene,
type RouteOrgan,
type RouteSceneModel
} from '$lib/observatory/route-scene';
export interface RoutePick {
id: string;
kind: string;
index?: number;
payload?: unknown;
}
export interface RouteFramePass extends FramePass {
uploadScene?: (scene: RouteSceneModel) => void;
pickAt?: (ndcX: number, ndcY: number) => Promise<RoutePick | null> | RoutePick | null;
dispose?: () => void;
}
export type RoutePassFactory = (
engine: ObservatoryEngine,
scene: RouteSceneModel
) => RouteFramePass[];
interface Props {
organ: RouteOrgan;
seed?: string;
scene?: RouteSceneModel | null;
passes?: RoutePassFactory | RouteFramePass[];
embedded?: boolean;
onpick?: (pick: RoutePick) => void;
/** Existing ObservatoryEngine needs an existing DemoMode; route id stays pass-local. */
demo?: DemoMode;
loading?: boolean;
error?: string | null;
emptyLabel?: string;
}
let {
organ,
seed = 'vestige-route-organ-v1',
scene = null,
passes,
embedded = false,
onpick,
demo = 'recall-path',
loading = false,
error = null,
emptyLabel = 'NO ROUTE DATA IN FIELD'
}: Props = $props();
let currentScene = $derived(scene ?? emptyScene(organ));
let canvasLayerEl: HTMLDivElement | null = $state(null);
let engine = $state<ObservatoryEngine | null>(null);
let routePasses: RouteFramePass[] = [];
let frameCount = $state(0);
let fpsEstimate = $state(0);
let paused = $state(false);
let userSetPause = $state(false);
let ready = $state(false);
function initReducedMotion() {
if (typeof window === 'undefined') return;
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
if (mq.matches && !userSetPause) paused = true;
const onChange = (e: MediaQueryListEvent) => {
if (!userSetPause) paused = e.matches;
};
mq.addEventListener('change', onChange);
return () => mq.removeEventListener('change', onChange);
}
function togglePause() {
userSetPause = true;
paused = !paused;
}
$effect(() => {
engine?.setPaused(paused);
});
function upload(sceneToUpload: RouteSceneModel) {
if (!engine) return;
if (import.meta.env.DEV) assertProvenance(sceneToUpload);
for (const pass of routePasses) pass.uploadScene?.(sceneToUpload);
engine.demoClock.reset();
}
function handleFrame(frame: number, fps: number) {
frameCount = frame;
fpsEstimate = fps;
}
function handleReady(e: ObservatoryEngine) {
ready = true;
engine = e;
for (const pass of routePasses) pass.dispose?.();
routePasses = typeof passes === 'function' ? passes(e, currentScene) : (passes ?? []);
for (const pass of routePasses) e.addPass(pass);
upload(currentScene);
}
$effect(() => {
if (!engine || !ready) return;
upload(currentScene);
});
async function handleFieldClick(e: MouseEvent) {
if (!onpick || !canvasLayerEl) return;
const rect = canvasLayerEl.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const ndcX = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const ndcY = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
for (const pass of routePasses) {
const hit = await pass.pickAt?.(ndcX, ndcY);
if (hit) {
onpick(hit);
return;
}
}
}
onMount(() => {
return () => {
for (const pass of routePasses) pass.dispose?.();
routePasses = [];
};
});
onMount(() => initReducedMotion());
</script>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="{embedded ? 'absolute' : 'fixed'} inset-0 overflow-hidden route-stage">
<div
bind:this={canvasLayerEl}
class="absolute inset-0 z-0"
class:cursor-crosshair={!!onpick}
onclick={handleFieldClick}
>
<ObservatoryCanvas {demo} {seed} onframe={handleFrame} onready={handleReady} />
</div>
<div class="absolute inset-0 z-10 pointer-events-none">
<slot name="chrome" {frameCount} {fpsEstimate} {paused} {ready} />
<button
onclick={togglePause}
class="absolute bottom-4 right-4 pointer-events-auto flex items-center gap-2 px-3 py-1.5
rounded-xl border border-[#22C7DE]/25 bg-[#020307]/80 backdrop-blur-sm
font-mono text-[11px] tracking-wide text-[#22C7DE]/80 hover:text-[#22C7DE]
hover:border-[#22C7DE]/50 transition-colors"
title={paused ? 'Resume route organ motion' : 'Pause route organ motion'}
aria-pressed={paused}
>
{paused ? '▶ RESUME' : '❚❚ PAUSE'}
</button>
<div class="absolute top-4 right-4 font-mono text-[10px] tracking-[0.18em] text-[#7fe6c0]/60 uppercase">
{organ} · {frameCount}f · {fpsEstimate}fps
</div>
{#if loading}
<div class="absolute inset-0 flex items-center justify-center pointer-events-auto">
<div class="text-[#A8FF5E] font-mono text-sm tracking-widest animate-pulse">
REPLAYING COGNITIVE RECEIPT...
</div>
</div>
{/if}
{#if error && !loading}
<div class="absolute inset-0 flex items-center justify-center pointer-events-auto">
<div class="text-[#ff8a8a] font-mono text-sm border border-[#FF3B30]/50 bg-[#1a0508]/70 px-4 py-2 rounded">
{error}
</div>
</div>
{/if}
{#if !loading && !error && !currentScene.alive}
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
<div class="text-[#5dcaa5]/70 font-mono text-xs tracking-widest border border-[#5dcaa5]/15 bg-[#020307]/45 px-4 py-2 rounded-xl backdrop-blur-sm">
{emptyLabel}
</div>
</div>
{/if}
</div>
</div>
<style>
.route-stage {
background: #020307;
}
</style>

View file

@ -0,0 +1,370 @@
import {
assertProvenance,
type Provenance,
type RouteEvent,
type RouteNode,
type RouteReceipt,
type RouteSceneModel
} from '$lib/observatory/route-scene';
export type ReasoningStageKind =
| 'intent'
| 'retrieve'
| 'activate'
| 'evidence'
| 'contradiction'
| 'synthesis'
| 'recommendation'
| 'receipt';
export interface ReasoningStageReceipt {
index: number;
kind: ReasoningStageKind;
label: string;
count: number;
confidence: number;
lit: boolean;
provenance: Provenance;
exposed: Record<string, unknown>;
not_exposed_by_backend: string[];
interrupt: 'none' | 'contradiction' | 'supersession';
}
export interface NormalizedEvidence {
id: string;
trust: number;
date: string;
role: 'primary' | 'supporting' | 'contradicting' | 'superseded';
preview: string;
nodeType?: string;
}
export interface NormalizedContradiction {
stronger: NormalizedEvidence;
weaker: NormalizedEvidence;
topic_overlap: number;
summary: string;
}
export interface NormalizedSupersession {
id: string;
preview: string;
trust: number;
date: string;
superseded_by: string;
reason: string;
}
export interface NormalizedRecommended {
answer_preview: string;
memory_id: string;
trust_score: number;
date: string;
}
export interface ReasoningScene extends RouteSceneModel {
organ: 'reasoning';
stages: ReasoningStageReceipt[];
evidence: NormalizedEvidence[];
contradictions: NormalizedContradiction[];
superseded: NormalizedSupersession[];
recommended: NormalizedRecommended | null;
raw: Record<string, unknown>;
}
const STAGE_LABELS: Record<ReasoningStageKind, string> = {
intent: 'Intent classification',
retrieve: 'Memory retrieval',
activate: 'Activation expansion',
evidence: 'Evidence grounding',
contradiction: 'Contradiction check',
synthesis: 'Synthesis',
recommendation: 'Recommendation',
receipt: 'Composition receipt'
};
const STAGE_KINDS: ReasoningStageKind[] = [
'intent',
'retrieve',
'activate',
'evidence',
'contradiction',
'synthesis',
'recommendation',
'receipt'
];
function record(v: unknown): Record<string, unknown> {
return v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : {};
}
function text(v: unknown, fallback = ''): string {
return typeof v === 'string' ? v : v == null ? fallback : String(v);
}
function num(v: unknown, fallback = 0): number {
return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
}
function clamp01(v: number): number {
return Math.max(0, Math.min(1, v));
}
function confidence01(v: unknown): number {
const n = num(v, 0);
return clamp01(n > 1 ? n / 100 : n);
}
function role(v: unknown): NormalizedEvidence['role'] {
return v === 'primary' || v === 'supporting' || v === 'contradicting' || v === 'superseded'
? v
: 'supporting';
}
function evidenceFromRecord(e: Record<string, unknown>, fallbackId = ''): NormalizedEvidence {
const trust = confidence01(e.trust ?? e.trust_score ?? 0);
return {
id: text(e.id ?? e.memory_id ?? fallbackId),
trust,
date: text(e.date ?? e.created_at ?? ''),
role: role(e.role),
preview: text(e.preview ?? e.answer_preview ?? e.content ?? ''),
nodeType: e.node_type ? text(e.node_type) : e.nodeType ? text(e.nodeType) : undefined
};
}
function source(kind: Provenance['kind'], id: string, scalar?: Provenance['scalar']): Provenance {
return scalar ? { kind, id, scalar } : { kind, id: id || `${kind}:unknown` };
}
function scalarSource(name: string, value: number): Provenance {
return { kind: 'scalar', id: `deep_reference.${name}`, scalar: { name, value } };
}
export function normalizeDeepReferenceResponse(rawInput: Record<string, unknown>): ReasoningScene {
const raw = record(rawInput);
const evidenceRaw = Array.isArray(raw.evidence) ? raw.evidence.map(record) : [];
const evidence = evidenceRaw
.map((e) => evidenceFromRecord(e))
.filter((e) => e.id.length > 0);
const rec = record(raw.recommended);
const recommended: NormalizedRecommended | null =
Object.keys(rec).length > 0 || evidence.length > 0
? {
answer_preview: text(rec.answer_preview ?? evidence[0]?.preview ?? ''),
memory_id: text(rec.memory_id ?? evidence[0]?.id ?? ''),
trust_score: confidence01(rec.trust_score ?? evidence[0]?.trust ?? 0),
date: text(rec.date ?? evidence[0]?.date ?? '')
}
: null;
const contradictionsRaw = Array.isArray(raw.contradictions)
? raw.contradictions.map(record)
: Array.isArray(raw.claim_conflicts)
? raw.claim_conflicts.map(record)
: [];
const contradictions: NormalizedContradiction[] = contradictionsRaw
.map((c) => {
// T2 current shape first: { stronger:{id,...}, weaker:{id,...}, topic_overlap }
const strongerRaw = record(c.stronger);
const weakerRaw = record(c.weaker);
if (Object.keys(strongerRaw).length > 0 || Object.keys(weakerRaw).length > 0) {
const stronger = evidenceFromRecord(strongerRaw);
const weaker = evidenceFromRecord(weakerRaw);
return {
stronger,
weaker,
topic_overlap: clamp01(num(c.topic_overlap, 0)),
summary: text(
c.summary,
`Trust-weighted conflict: ${stronger.id.slice(0, 8)} over ${weaker.id.slice(0, 8)}`
)
};
}
// Legacy fallback: { a_id, b_id, summary }
const a = evidenceFromRecord({ id: c.a_id, role: 'contradicting' });
const b = evidenceFromRecord({ id: c.b_id, role: 'contradicting' });
return {
stronger: a,
weaker: b,
topic_overlap: clamp01(num(c.topic_overlap, 0)),
summary: text(c.summary ?? c.reason, 'Trust-weighted conflict between high-FSRS memories.')
};
})
.filter((c) => c.stronger.id || c.weaker.id);
const supersededRaw = Array.isArray(raw.superseded) ? raw.superseded.map(record) : [];
const superseded: NormalizedSupersession[] = supersededRaw
.map((s) => {
// T2 current shape first: { id, preview, trust, date, superseded_by }
if (s.id || s.superseded_by) {
return {
id: text(s.id),
preview: text(s.preview ?? ''),
trust: confidence01(s.trust ?? 0),
date: text(s.date ?? ''),
superseded_by: text(s.superseded_by ?? recommended?.memory_id ?? ''),
reason: text(s.reason ?? 'Superseded by newer memory with higher trust.')
};
}
// Legacy fallback: { old_id, new_id, reason }
return {
id: text(s.old_id),
preview: text(s.preview ?? ''),
trust: confidence01(s.trust ?? 0),
date: text(s.date ?? ''),
superseded_by: text(s.new_id ?? recommended?.memory_id ?? ''),
reason: text(s.reason ?? 'Superseded by newer memory with higher trust.')
};
})
.filter((s) => s.id || s.superseded_by);
const confidence = confidence01(raw.confidence);
const memoriesAnalyzed = num(raw.memoriesAnalyzed ?? raw.memories_analyzed, evidence.length);
const activationExpanded = num(raw.activationExpanded ?? raw.activation_expanded, 0);
const intent = text(raw.intent ?? '');
const reasoning = text(raw.reasoning ?? raw.guidance ?? '');
const guidance = text(raw.guidance ?? '');
const compositionEventId = text(raw.composition_event_id ?? '');
const compositionWriteStatus = text(raw.compositionWriteStatus ?? raw.composition_write_status ?? '');
const receiptValue = compositionEventId || compositionWriteStatus;
const receiptIsPersisted = compositionEventId.length > 0;
const nodes: RouteNode[] = evidence.map((e, index) => ({
source: source('memory', e.id),
index,
label: e.preview || e.id.slice(0, 8),
retention: clamp01(e.trust),
trust: clamp01(e.trust),
lastAccessed: e.date || undefined,
tags: [e.role, ...(e.nodeType ? [e.nodeType] : [])],
type: e.nodeType ?? 'memory'
}));
const indexById = new Map(nodes.map((n) => [n.source.id, n.index]));
const edges = contradictions.flatMap((c, i) => {
const a = indexById.get(c.stronger.id);
const b = indexById.get(c.weaker.id);
if (a == null || b == null) return [];
return [
{
source: source('pair', `contradiction:${c.stronger.id}:${c.weaker.id}`),
sourceIndex: a,
targetIndex: b,
weight: Math.max(0.2, c.topic_overlap || 0.5),
kind: 'contradiction'
}
];
});
const events: RouteEvent[] = [];
if (contradictions.length > 0) {
events.push({
source: source('event', `deep_reference.contradictions.${contradictions.length}`),
type: 'ReasoningContradictionInterrupt',
targetIndex: -1,
frame: 250,
energy: contradictions.length
});
}
if (superseded.length > 0) {
events.push({
source: source('event', `deep_reference.superseded.${superseded.length}`),
type: 'ReasoningSupersessionInterrupt',
targetIndex: -1,
frame: 330,
energy: superseded.length
});
}
const receipts: RouteReceipt[] = [];
if (receiptValue) {
receipts.push({
source: receiptIsPersisted
? source('receipt', compositionEventId)
: scalarSource('compositionWriteStatus', compositionWriteStatus === 'persisted' ? 1 : 0),
label: receiptIsPersisted ? `receipt ${compositionEventId.slice(0, 8)}` : compositionWriteStatus,
nodeIndices: nodes.map((n) => n.index)
});
}
const mkStage = (
index: number,
kind: ReasoningStageKind,
count: number,
stageConfidence: number,
exposed: Record<string, unknown>,
provenance: Provenance,
missing: string[],
interrupt: ReasoningStageReceipt['interrupt'] = 'none'
): ReasoningStageReceipt => ({
index,
kind,
label: STAGE_LABELS[kind],
count,
confidence: clamp01(stageConfidence),
lit: count > 0 || stageConfidence > 0,
provenance,
exposed,
not_exposed_by_backend: missing,
interrupt
});
const stages: ReasoningStageReceipt[] = STAGE_KINDS.map((kind, index) => {
switch (kind) {
case 'intent':
return mkStage(index, kind, intent ? 1 : 0, intent ? confidence : 0, { intent }, scalarSource('intent_present', intent ? 1 : 0), ['raw classifier trace']);
case 'retrieve':
return mkStage(index, kind, memoriesAnalyzed || evidence.length, confidence, { memoriesAnalyzed, evidence_count: evidence.length }, scalarSource('memoriesAnalyzed', memoriesAnalyzed), ['candidate ids discarded before evidence']);
case 'activate':
return mkStage(index, kind, activationExpanded, activationExpanded > 0 ? confidence : 0, { activationExpanded }, scalarSource('activationExpanded', activationExpanded), ['activation path/map']);
case 'evidence':
return mkStage(index, kind, evidence.length, evidence.length > 0 ? confidence : 0, { evidence }, scalarSource('evidence_count', evidence.length), ['reranker discarded candidates']);
case 'contradiction':
return mkStage(index, kind, contradictions.length, contradictions.length > 0 ? confidence : 0, { contradictions, claim_conflicts: raw.claim_conflicts ?? [] }, scalarSource('contradiction_count', contradictions.length), ['full claim graph'], contradictions.length > 0 ? 'contradiction' : 'none');
case 'synthesis':
return mkStage(index, kind, reasoning || guidance ? 1 : 0, reasoning || guidance ? confidence : 0, { reasoning, guidance }, scalarSource('synthesis_present', reasoning || guidance ? 1 : 0), ['token-level chain internals']);
case 'recommendation':
return mkStage(index, kind, recommended?.memory_id ? 1 : 0, recommended?.trust_score ?? 0, { recommended }, recommended?.memory_id ? source('memory', recommended.memory_id) : scalarSource('recommended_present', 0), ['alternative recommendations']);
case 'receipt':
return mkStage(index, kind, receiptValue ? 1 : 0, receiptValue ? confidence : 0, { composition_event_id: compositionEventId, compositionWriteStatus }, receiptIsPersisted ? source('receipt', compositionEventId) : scalarSource('compositionWriteStatus', compositionWriteStatus === 'persisted' ? 1 : 0), ['separate receipt field'], superseded.length > 0 ? 'supersession' : 'none');
}
});
const scene: ReasoningScene = {
organ: 'reasoning',
nodes,
edges,
events,
receipts,
scalars: {
confidence,
memoriesAnalyzed,
activationExpanded,
evidenceCount: evidence.length,
contradictionCount: contradictions.length,
supersededCount: superseded.length,
compositionPersisted: receiptIsPersisted ? 1 : 0
},
alive: !!(
intent ||
reasoning ||
guidance ||
evidence.length ||
contradictions.length ||
superseded.length ||
recommended?.memory_id ||
receiptValue
),
stages,
evidence,
contradictions,
superseded,
recommended,
raw
};
if (import.meta.env.DEV) assertProvenance(scene);
return scene;
}

View file

@ -0,0 +1,673 @@
import type { ObservatoryEngine, FramePass } from '$lib/observatory/engine';
import { CAUSAL, IMMUNE, RETENTION, rgb01 } from '$lib/observatory/cognitive-palette';
import type { RouteSceneModel } from '$lib/observatory/route-scene';
import type { ReasoningScene, ReasoningStageReceipt } from './reasoning-scene';
type RoutePick = { id: string; kind: string; index?: number; payload?: unknown };
const STAGE_COUNT = 8;
const STAGE_FLOATS = 8;
const PACKET_FLOATS = 8;
const PACKET_OUT_FLOATS = 8;
const MAX_PACKETS = 256;
const FIELD_FORMAT: GPUTextureFormat = 'rgba16float';
const COMMON_WGSL = /* wgsl */ `
struct Params {
frame: f32,
loop_phase: f32,
node_count: f32,
edge_count: f32,
path_count: f32,
pulse: f32,
viewport_w: f32,
viewport_h: f32,
brightness: f32,
demo_id: f32,
time: f32,
capture_mode: f32,
live_kind: f32,
live_frame: f32,
live_energy: f32,
projection_days: f32,
};
struct Stage {
// xy center in NDC, z count, w confidence
pos_count_conf: vec4f,
// x start_frame, y interrupt_kind, z lit, w reserved
timing: vec4f,
};
struct PacketIn {
// x src stage, y dst stage, z evidence/node index, w flags
route: vec4f,
// x start_frame, y duration, z energy, w interrupt_kind
timing: vec4f,
};
struct PacketOut {
pos_energy: vec4f,
tangent_flags: vec4f,
};
`;
const COMPUTE_WGSL = /* wgsl */ `
${COMMON_WGSL}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read> stages: array<Stage>;
@group(0) @binding(2) var<storage, read> packets_in: array<PacketIn>;
@group(0) @binding(3) var<storage, read_write> packets_out: array<PacketOut>;
fn stage_center(i: u32) -> vec2f {
return stages[i].pos_count_conf.xy;
}
fn cubic(a: vec2f, b: vec2f, t_in: f32) -> vec2f {
let t = clamp(t_in, 0.0, 1.0);
let u = 1.0 - t;
let bend = 0.18 * sin(f32(i32(a.y * 10.0) + i32(b.y * 10.0)));
let p0 = a;
let p1 = vec2f(a.x + bend, mix(a.y, b.y, 0.32));
let p2 = vec2f(b.x - bend, mix(a.y, b.y, 0.68));
let p3 = b;
return u*u*u*p0 + 3.0*u*u*t*p1 + 3.0*u*t*t*p2 + t*t*t*p3;
}
fn cubic_tangent(a: vec2f, b: vec2f, t_in: f32) -> vec2f {
let t = clamp(t_in, 0.0, 1.0);
let e = 0.01;
return normalize(cubic(a, b, min(1.0, t + e)) - cubic(a, b, max(0.0, t - e)) + vec2f(0.0001, 0.0001));
}
@compute @workgroup_size(64)
fn advect_packets(@builtin(global_invocation_id) gid: vec3u) {
let i = gid.x;
if (i >= u32(params.path_count)) { return; }
let p = packets_in[i];
let src = u32(p.route.x);
let dst = u32(p.route.y);
if (src >= 8u || dst >= 8u) { return; }
let start = p.timing.x;
let dur = max(1.0, p.timing.y);
var t = clamp((params.frame - start) / dur, 0.0, 1.0);
let tt = t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
let a = stage_center(src);
let b = stage_center(dst);
let pos = cubic(a, b, tt);
let tan = cubic_tangent(a, b, tt);
let alive_f = f32(params.frame >= start) * f32(params.frame <= start + dur + 90.0);
let age = max(0.0, params.frame - start - dur);
let tail = 1.0 - smoothstep(0.0, 90.0, age);
packets_out[i] = PacketOut(vec4f(pos, p.timing.z * alive_f * max(0.18, tail), 1.0), vec4f(tan, p.timing.w, p.route.z));
}
`;
const SPLAT_WGSL = /* wgsl */ `
${COMMON_WGSL}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read> stages: array<Stage>;
@group(0) @binding(3) var<storage, read> packets_out: array<PacketOut>;
const QUAD = array<vec2f, 6>(
vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),
vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0)
);
struct VSOut {
@builtin(position) clip: vec4f,
@location(0) uv: vec2f,
@location(1) @interpolate(flat) misc: vec4f,
};
@vertex
fn vs_splat(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut {
var out: VSOut;
let corner = QUAD[vi];
let is_stage = ii < 8u;
var center = vec2f(0.0);
var radius = 0.045;
var energy = 0.0;
var interrupt = 0.0;
if (is_stage) {
let s = stages[ii];
center = s.pos_count_conf.xy;
let age = params.frame - s.timing.x;
let gate = s.timing.z * smoothstep(0.0, 22.0, age);
radius = 0.095 + 0.02 * s.pos_count_conf.w;
energy = gate * (0.18 + 0.82 * s.pos_count_conf.w) * (0.7 + 0.3 * params.pulse);
interrupt = s.timing.y;
} else {
let pi = ii - 8u;
let p = packets_out[pi];
center = p.pos_energy.xy;
radius = 0.026 + 0.014 * p.pos_energy.z;
energy = p.pos_energy.z;
interrupt = p.tangent_flags.z;
}
out.clip = vec4f(center + corner * radius, 0.0, 1.0);
out.uv = corner;
out.misc = vec4f(energy, interrupt, f32(is_stage), radius);
return out;
}
@fragment
fn fs_splat(in: VSOut) -> @location(0) vec4f {
let d = length(in.uv);
if (d > 1.0) { discard; }
let body = exp(-d * d * 3.2) * in.misc.x;
let scar = f32(in.misc.y > 0.5) * smoothstep(0.75, 0.2, abs(d - 0.62)) * in.misc.x;
// logical .r = living density, .g = trust/flow, .b = immune interrupt, .a reserved
return vec4f(body, body * (0.45 + 0.35 * params.pulse), scar, 1.0);
}
@vertex
fn vs_chamber(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut {
var out: VSOut;
let s = stages[ii];
let corner = QUAD[vi];
let age = params.frame - s.timing.x;
let gate = s.timing.z * smoothstep(0.0, 24.0, age);
let size = vec2f(0.28 + 0.025 * s.pos_count_conf.w, 0.043 + 0.018 * gate);
out.clip = vec4f(s.pos_count_conf.xy + corner * size, 0.0, 1.0);
out.uv = corner;
out.misc = vec4f(gate, s.pos_count_conf.w, s.timing.y, s.pos_count_conf.z);
return out;
}
@fragment
fn fs_chamber(in: VSOut) -> @location(0) vec4f {
let q = abs(in.uv);
let edge = smoothstep(0.98, 0.80, max(q.x, q.y));
let inner = smoothstep(0.92, 0.15, max(q.x, q.y));
let lit = in.misc.x;
let trust = in.misc.y;
let interrupt = in.misc.z;
let count = in.misc.w;
let green = vec3f(0.64, 1.0, 0.36);
let cyan = vec3f(0.10, 0.84, 0.98);
let scarlet = vec3f(1.0, 0.20, 0.16);
let amber = vec3f(1.0, 0.69, 0.08);
var color = mix(cyan, green, trust) * (0.05 + lit * 0.34) * inner;
let rim_color = select(mix(cyan, green, trust), select(amber, scarlet, interrupt < 1.5), interrupt > 0.5);
color = color + rim_color * edge * (0.05 + lit * (0.32 + 0.08 * log2(count + 1.0)));
return vec4f(color, 1.0);
}
@vertex
fn vs_packet(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut {
var out: VSOut;
let p = packets_out[ii];
let corner = QUAD[vi];
let tangent = normalize(p.tangent_flags.xy + vec2f(0.0001));
let normal = vec2f(-tangent.y, tangent.x);
let size = vec2f(0.038 + 0.018 * p.pos_energy.z, 0.010 + 0.006 * p.pos_energy.z);
let pos = p.pos_energy.xy + tangent * corner.x * size.x + normal * corner.y * size.y;
out.clip = vec4f(pos, 0.0, 1.0);
out.uv = corner;
out.misc = vec4f(p.pos_energy.z, p.tangent_flags.z, p.tangent_flags.w, 0.0);
return out;
}
@fragment
fn fs_packet(in: VSOut) -> @location(0) vec4f {
let d = length(in.uv);
if (d > 1.0) { discard; }
let e = in.misc.x;
let interrupt = in.misc.y;
let body = exp(-d*d*2.2) * e;
let luciferin = vec3f(0.91, 1.0, 0.72);
let recall = vec3f(0.16, 0.95, 0.66);
let scarlet = vec3f(1.0, 0.23, 0.19);
let amber = vec3f(1.0, 0.69, 0.08);
var color = mix(recall, luciferin, clamp(e, 0.0, 1.0));
if (interrupt > 0.5) { color = select(amber, scarlet, interrupt < 1.5); }
return vec4f(color * body * (1.2 + 0.4 * params.pulse), 1.0);
}
`;
const MEMBRANE_WGSL = /* wgsl */ `
${COMMON_WGSL}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(4) var field_sampler: sampler;
@group(0) @binding(5) var field_tex: texture_2d<f32>;
const QUAD = array<vec2f, 6>(
vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),
vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0)
);
struct VSOut {
@builtin(position) clip: vec4f,
@location(0) uv: vec2f,
@location(1) @interpolate(flat) misc: vec4f,
};
@vertex
fn vs_fullscreen(@builtin(vertex_index) vi: u32) -> VSOut {
var out: VSOut;
let p = QUAD[vi];
out.clip = vec4f(p, 0.0, 1.0);
out.uv = p * 0.5 + vec2f(0.5);
out.misc = vec4f(0.0);
return out;
}
@fragment
fn fs_membrane(in: VSOut) -> @location(0) vec4f {
let f = textureSample(field_tex, field_sampler, in.uv);
let density = clamp(f.r, 0.0, 4.0);
let flow = clamp(f.g, 0.0, 4.0);
let immune = clamp(f.b, 0.0, 3.0);
let membrane = smoothstep(0.14, 0.8, density) * (1.0 - smoothstep(1.6, 3.2, density));
let blackwater = vec3f(0.006, 0.014, 0.014);
let luciferin = vec3f(0.65, 1.0, 0.36);
let bridge = vec3f(0.10, 0.82, 0.92);
let scarlet = vec3f(1.0, 0.15, 0.10);
var color = blackwater * (0.16 + density * 0.08);
color = color + luciferin * density * 0.11 + bridge * flow * 0.08;
color = color + vec3f(0.90, 1.0, 0.72) * membrane * 0.26;
color = color + scarlet * immune * 0.26;
let vignette = smoothstep(0.92, 0.22, distance(in.uv, vec2f(0.5)));
return vec4f(color * (0.35 + 0.65 * vignette) * params.brightness, 1.0);
}
`;
const BLUR_WGSL = /* wgsl */ `
struct BlurDir {
dir: vec2f,
_pad: vec2f,
};
@group(0) @binding(0) var blur_sampler: sampler;
@group(0) @binding(1) var blur_src: texture_2d<f32>;
@group(0) @binding(2) var<uniform> blur_dir: BlurDir;
const QUAD = array<vec2f, 6>(
vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),
vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0)
);
struct VSOut {
@builtin(position) clip: vec4f,
@location(0) uv: vec2f,
};
@vertex
fn vs_fullscreen(@builtin(vertex_index) vi: u32) -> VSOut {
var out: VSOut;
let p = QUAD[vi];
out.clip = vec4f(p, 0.0, 1.0);
out.uv = p * 0.5 + vec2f(0.5);
return out;
}
@fragment
fn fs_blur(in: VSOut) -> @location(0) vec4f {
let dims = vec2f(textureDimensions(blur_src, 0));
let step = blur_dir.dir / max(dims, vec2f(1.0));
var acc = textureSampleLevel(blur_src, blur_sampler, in.uv - step * 2.0, 0.0) * 0.06136;
acc = acc + textureSampleLevel(blur_src, blur_sampler, in.uv - step, 0.0) * 0.24477;
acc = acc + textureSampleLevel(blur_src, blur_sampler, in.uv, 0.0) * 0.38774;
acc = acc + textureSampleLevel(blur_src, blur_sampler, in.uv + step, 0.0) * 0.24477;
acc = acc + textureSampleLevel(blur_src, blur_sampler, in.uv + step * 2.0, 0.0) * 0.06136;
return acc;
}
`;
type GpuResources = {
stageBuffer: GPUBuffer;
packetBuffer: GPUBuffer;
packetOutBuffer: GPUBuffer;
blurHBuffer: GPUBuffer;
blurVBuffer: GPUBuffer;
computeBindGroup: GPUBindGroup;
splatBindGroup: GPUBindGroup;
blurHBindGroup: GPUBindGroup;
blurVBindGroup: GPUBindGroup;
membraneBindGroup: GPUBindGroup;
fieldA: GPUTexture;
fieldB: GPUTexture;
fieldAView: GPUTextureView;
fieldBView: GPUTextureView;
fieldSize: [number, number];
};
export class ReasoningTheaterPass implements FramePass {
private engine: ObservatoryEngine;
private scene: ReasoningScene | null = null;
private resources: GpuResources | null = null;
private sampler: GPUSampler | null = null;
private computeBindLayout: GPUBindGroupLayout | null = null;
private splatBindLayout: GPUBindGroupLayout | null = null;
private blurBindLayout: GPUBindGroupLayout | null = null;
private membraneBindLayout: GPUBindGroupLayout | null = null;
private splatPipeline: GPURenderPipeline | null = null;
private blurPipeline: GPURenderPipeline | null = null;
private packetPipeline: GPUComputePipeline | null = null;
private membranePipeline: GPURenderPipeline | null = null;
private chamberPipeline: GPURenderPipeline | null = null;
private packetRenderPipeline: GPURenderPipeline | null = null;
private packetCount = 0;
private stageRects: { stage: ReasoningStageReceipt; x: number; y: number; w: number; h: number }[] = [];
constructor(engine: ObservatoryEngine, scene: RouteSceneModel) {
this.engine = engine;
this.uploadScene(scene);
}
uploadScene(scene: RouteSceneModel): void {
this.scene = scene as ReasoningScene;
this.buildStageRects();
const device = this.engine.gpuDevice;
if (!device) return;
this.ensurePipelines(device);
this.ensureResources(device);
this.uploadBuffers(device);
}
private ensurePipelines(device: GPUDevice): void {
if (this.splatPipeline || !this.engine.paramsBuffer) return;
const computeModule = device.createShaderModule({ label: 'reasoning-theater-compute-wgsl', code: COMPUTE_WGSL });
const splatModule = device.createShaderModule({ label: 'reasoning-theater-splat-wgsl', code: SPLAT_WGSL });
const blurModule = device.createShaderModule({ label: 'reasoning-theater-blur-wgsl', code: BLUR_WGSL });
const membraneModule = device.createShaderModule({ label: 'reasoning-theater-membrane-wgsl', code: MEMBRANE_WGSL });
this.computeBindLayout = device.createBindGroupLayout({
label: 'reasoning-theater-compute-bind-layout',
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } }
]
});
this.splatBindLayout = device.createBindGroupLayout({
label: 'reasoning-theater-splat-bind-layout',
entries: [
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },
{ binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } },
{ binding: 3, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } }
]
});
this.membraneBindLayout = device.createBindGroupLayout({
label: 'reasoning-theater-membrane-bind-layout',
entries: [
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }
]
});
this.blurBindLayout = device.createBindGroupLayout({
label: 'reasoning-theater-blur-bind-layout',
entries: [
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } }
]
});
const computeLayout = device.createPipelineLayout({ label: 'reasoning-theater-compute-layout', bindGroupLayouts: [this.computeBindLayout] });
const splatLayout = device.createPipelineLayout({ label: 'reasoning-theater-splat-layout', bindGroupLayouts: [this.splatBindLayout] });
const blurLayout = device.createPipelineLayout({ label: 'reasoning-theater-blur-layout', bindGroupLayouts: [this.blurBindLayout] });
const membraneLayout = device.createPipelineLayout({ label: 'reasoning-theater-membrane-layout', bindGroupLayouts: [this.membraneBindLayout] });
this.sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear' });
this.splatPipeline = device.createRenderPipeline({
label: 'reasoning-field-splat',
layout: splatLayout,
vertex: { module: splatModule, entryPoint: 'vs_splat' },
fragment: {
module: splatModule,
entryPoint: 'fs_splat',
targets: [{ format: FIELD_FORMAT, blend: { color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' }, alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' } } }]
},
primitive: { topology: 'triangle-list' }
});
this.blurPipeline = device.createRenderPipeline({
label: 'reasoning-field-blur',
layout: blurLayout,
vertex: { module: blurModule, entryPoint: 'vs_fullscreen' },
fragment: { module: blurModule, entryPoint: 'fs_blur', targets: [{ format: FIELD_FORMAT }] },
primitive: { topology: 'triangle-list' }
});
this.packetPipeline = device.createComputePipeline({ label: 'reasoning-packet-advect', layout: computeLayout, compute: { module: computeModule, entryPoint: 'advect_packets' } });
const additive = { color: { srcFactor: 'one' as GPUBlendFactor, dstFactor: 'one' as GPUBlendFactor, operation: 'add' as GPUBlendOperation }, alpha: { srcFactor: 'one' as GPUBlendFactor, dstFactor: 'one' as GPUBlendFactor, operation: 'add' as GPUBlendOperation } };
this.membranePipeline = device.createRenderPipeline({
label: 'reasoning-membrane',
layout: membraneLayout,
vertex: { module: membraneModule, entryPoint: 'vs_fullscreen' },
fragment: { module: membraneModule, entryPoint: 'fs_membrane', targets: [{ format: this.engine.sceneFormat, blend: additive }] },
primitive: { topology: 'triangle-list' }
});
this.chamberPipeline = device.createRenderPipeline({
label: 'reasoning-chambers',
layout: splatLayout,
vertex: { module: splatModule, entryPoint: 'vs_chamber' },
fragment: { module: splatModule, entryPoint: 'fs_chamber', targets: [{ format: this.engine.sceneFormat, blend: additive }] },
primitive: { topology: 'triangle-list' }
});
this.packetRenderPipeline = device.createRenderPipeline({
label: 'reasoning-packets',
layout: splatLayout,
vertex: { module: splatModule, entryPoint: 'vs_packet' },
fragment: { module: splatModule, entryPoint: 'fs_packet', targets: [{ format: this.engine.sceneFormat, blend: additive }] },
primitive: { topology: 'triangle-list' }
});
}
private ensureResources(device: GPUDevice): void {
if (!this.computeBindLayout || !this.splatBindLayout || !this.blurBindLayout || !this.membraneBindLayout || !this.engine.paramsBuffer || !this.sampler) return;
const w = Math.max(16, Math.floor((this.engine.params[6] || 1280) / 2));
const h = Math.max(16, Math.floor((this.engine.params[7] || 720) / 2));
const needsTextures = !this.resources || this.resources.fieldSize[0] !== w || this.resources.fieldSize[1] !== h;
let stageBuffer = this.resources?.stageBuffer;
let packetBuffer = this.resources?.packetBuffer;
let packetOutBuffer = this.resources?.packetOutBuffer;
let blurHBuffer = this.resources?.blurHBuffer;
let blurVBuffer = this.resources?.blurVBuffer;
if (!stageBuffer) {
stageBuffer = device.createBuffer({ label: 'reasoning-stages', size: STAGE_COUNT * STAGE_FLOATS * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
}
if (!packetBuffer) {
packetBuffer = device.createBuffer({ label: 'reasoning-packets-in', size: MAX_PACKETS * PACKET_FLOATS * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
}
if (!packetOutBuffer) {
packetOutBuffer = device.createBuffer({ label: 'reasoning-packets-out', size: MAX_PACKETS * PACKET_OUT_FLOATS * 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
}
if (!blurHBuffer) {
blurHBuffer = device.createBuffer({ label: 'reasoning-blur-h-dir', size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
device.queue.writeBuffer(blurHBuffer, 0, new Float32Array([1, 0, 0, 0]));
}
if (!blurVBuffer) {
blurVBuffer = device.createBuffer({ label: 'reasoning-blur-v-dir', size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
device.queue.writeBuffer(blurVBuffer, 0, new Float32Array([0, 1, 0, 0]));
}
if (!needsTextures && this.resources) return;
this.resources?.fieldA.destroy();
this.resources?.fieldB.destroy();
const usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING;
const fieldA = device.createTexture({ label: 'reasoning-field-a-rgba16float', size: [w, h], format: FIELD_FORMAT, usage });
const fieldB = device.createTexture({ label: 'reasoning-field-b-rgba16float', size: [w, h], format: FIELD_FORMAT, usage });
const fieldAView = fieldA.createView();
const fieldBView = fieldB.createView();
const computeBindGroup = device.createBindGroup({
label: 'reasoning-theater-compute-bind',
layout: this.computeBindLayout,
entries: [
{ binding: 0, resource: { buffer: this.engine.paramsBuffer } },
{ binding: 1, resource: { buffer: stageBuffer } },
{ binding: 2, resource: { buffer: packetBuffer } },
{ binding: 3, resource: { buffer: packetOutBuffer } }
]
});
const splatBindGroup = device.createBindGroup({
label: 'reasoning-theater-splat-bind',
layout: this.splatBindLayout,
entries: [
{ binding: 0, resource: { buffer: this.engine.paramsBuffer } },
{ binding: 1, resource: { buffer: stageBuffer } },
{ binding: 3, resource: { buffer: packetOutBuffer } }
]
});
const blurHBindGroup = device.createBindGroup({
label: 'reasoning-theater-blur-h-bind',
layout: this.blurBindLayout,
entries: [
{ binding: 0, resource: this.sampler },
{ binding: 1, resource: fieldAView },
{ binding: 2, resource: { buffer: blurHBuffer } }
]
});
const blurVBindGroup = device.createBindGroup({
label: 'reasoning-theater-blur-v-bind',
layout: this.blurBindLayout,
entries: [
{ binding: 0, resource: this.sampler },
{ binding: 1, resource: fieldBView },
{ binding: 2, resource: { buffer: blurVBuffer } }
]
});
const membraneBindGroup = device.createBindGroup({
label: 'reasoning-theater-membrane-bind',
layout: this.membraneBindLayout,
entries: [
{ binding: 0, resource: { buffer: this.engine.paramsBuffer } },
{ binding: 4, resource: this.sampler },
{ binding: 5, resource: fieldAView }
]
});
this.resources = { stageBuffer, packetBuffer, packetOutBuffer, blurHBuffer, blurVBuffer, computeBindGroup, splatBindGroup, blurHBindGroup, blurVBindGroup, membraneBindGroup, fieldA, fieldB, fieldAView, fieldBView, fieldSize: [w, h] };
}
private buildStageRects(): void {
const stages = this.scene?.stages ?? [];
this.stageRects = stages.map((stage, i) => {
const y = 0.76 - i * (1.52 / 7);
const confidence = stage.confidence || 0;
return { stage, x: 0, y, w: 0.28 + 0.025 * confidence, h: 0.07 };
});
}
private uploadBuffers(device: GPUDevice): void {
if (!this.resources || !this.scene) return;
const stages = this.scene.stages ?? [];
const stageData = new Float32Array(STAGE_COUNT * STAGE_FLOATS);
for (let i = 0; i < STAGE_COUNT; i++) {
const s = stages[i];
const y = 0.76 - i * (1.52 / 7);
const lit = s?.lit ? 1 : 0;
const interrupt = s?.interrupt === 'contradiction' ? 1 : s?.interrupt === 'supersession' ? 2 : 0;
stageData.set([0, y, s?.count ?? 0, s?.confidence ?? 0, i * 46 + 18, interrupt, lit, 0], i * STAGE_FLOATS);
}
device.queue.writeBuffer(this.resources.stageBuffer, 0, stageData);
const packets = new Float32Array(MAX_PACKETS * PACKET_FLOATS);
let p = 0;
for (let i = 0; i < STAGE_COUNT - 1 && p < MAX_PACKETS; i++) {
const a = stages[i];
const b = stages[i + 1];
if (!a?.lit || !b?.lit) continue;
const energy = Math.max(0.12, Math.min(1, (a.confidence + b.confidence) / 2 || 0.25));
const interrupt = b.interrupt === 'contradiction' ? 1 : b.interrupt === 'supersession' ? 2 : 0;
packets.set([i, i + 1, -1, 0, i * 46 + 34, 38, energy, interrupt], p * PACKET_FLOATS);
p++;
}
for (const c of this.scene.contradictions ?? []) {
if (p >= MAX_PACKETS) break;
packets.set([3, 4, -1, 0, 4 * 46 + 18, 24, Math.max(0.35, c.topic_overlap || 0.5), 1], p * PACKET_FLOATS);
p++;
}
for (const _s of this.scene.superseded ?? []) {
if (p >= MAX_PACKETS) break;
packets.set([4, 7, -1, 0, 5 * 46, 52, 0.65, 2], p * PACKET_FLOATS);
p++;
}
this.packetCount = p;
this.engine.params[4] = p;
device.queue.writeBuffer(this.resources.packetBuffer, 0, packets);
}
compute(encoder: GPUCommandEncoder): void {
const device = this.engine.gpuDevice;
if (!device || !this.resources || !this.splatPipeline || !this.blurPipeline || !this.packetPipeline) return;
this.ensureResources(device);
const res = this.resources;
const splat = encoder.beginRenderPass({
label: 'reasoning-field-splat-pass',
colorAttachments: [{ view: res.fieldAView, clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store' }]
});
splat.setPipeline(this.splatPipeline);
splat.setBindGroup(0, res.splatBindGroup);
splat.draw(6, STAGE_COUNT + this.packetCount);
splat.end();
const blurH = encoder.beginRenderPass({
label: 'reasoning-field-blur-h-pass',
colorAttachments: [{ view: res.fieldBView, clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store' }]
});
blurH.setPipeline(this.blurPipeline);
blurH.setBindGroup(0, res.blurHBindGroup);
blurH.draw(6, 1);
blurH.end();
const blurV = encoder.beginRenderPass({
label: 'reasoning-field-blur-v-pass',
colorAttachments: [{ view: res.fieldAView, clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store' }]
});
blurV.setPipeline(this.blurPipeline);
blurV.setBindGroup(0, res.blurVBindGroup);
blurV.draw(6, 1);
blurV.end();
if (this.packetCount > 0) {
const advect = encoder.beginComputePass({ label: 'reasoning-packet-advect-pass' });
advect.setPipeline(this.packetPipeline);
advect.setBindGroup(0, res.computeBindGroup);
advect.dispatchWorkgroups(Math.ceil(this.packetCount / 64));
advect.end();
}
}
render(pass: GPURenderPassEncoder): void {
if (!this.resources || !this.membranePipeline || !this.chamberPipeline || !this.packetRenderPipeline) return;
pass.setPipeline(this.membranePipeline);
pass.setBindGroup(0, this.resources.membraneBindGroup);
pass.draw(6, 1);
pass.setPipeline(this.chamberPipeline);
pass.setBindGroup(0, this.resources.splatBindGroup);
pass.draw(6, STAGE_COUNT);
if (this.packetCount > 0) {
pass.setPipeline(this.packetRenderPipeline);
pass.draw(6, this.packetCount);
}
}
pickAt(ndcX: number, ndcY: number): RoutePick | null {
for (const rect of this.stageRects) {
if (Math.abs(ndcX - rect.x) <= rect.w && Math.abs(ndcY - rect.y) <= rect.h) {
return { id: `reasoning-stage:${rect.stage.kind}`, kind: 'stage', index: rect.stage.index, payload: rect.stage };
}
}
return null;
}
dispose(): void {
this.resources?.stageBuffer.destroy();
this.resources?.packetBuffer.destroy();
this.resources?.packetOutBuffer.destroy();
this.resources?.blurHBuffer.destroy();
this.resources?.blurVBuffer.destroy();
this.resources?.fieldA.destroy();
this.resources?.fieldB.destroy();
this.resources = null;
}
}
export function createReasoningTheaterPasses(engine: ObservatoryEngine, scene: RouteSceneModel): ReasoningTheaterPass[] {
void rgb01(RETENTION.healthy);
void rgb01(IMMUNE.veto);
void rgb01(CAUSAL.forward);
return [new ReasoningTheaterPass(engine, scene)];
}

View file

@ -6,6 +6,13 @@
import PageHeader from '$lib/components/PageHeader.svelte';
import Icon from '$lib/components/Icon.svelte';
import AnimatedNumber from '$lib/components/AnimatedNumber.svelte';
import RouteStage, { type RoutePick } from '$lib/observatory/RouteStage.svelte';
import { createReasoningTheaterPasses } from '$lib/observatory/reasoning/reasoning-theater-pass';
import {
normalizeDeepReferenceResponse,
type ReasoningScene,
type ReasoningStageReceipt
} from '$lib/observatory/reasoning/reasoning-scene';
import { reveal } from '$lib/actions/reveal';
import { spotlight, magnetic } from '$lib/actions/interactions';
import {
@ -66,11 +73,14 @@
memoriesAnalyzed: number;
}
let latestReasoningScene: ReasoningScene | null = null;
// Real backend call — wraps the 8-stage deep_reference cognitive pipeline
// via /api/deep_reference. The handler emits DeepReferenceCompleted on
// the WebSocket so Graph3D can glide + pulse + arc in the 3D scene.
async function deepReferenceFetch(query: string): Promise<DeepReferenceResponse> {
const raw = (await api.deepReference(query, 20)) as Record<string, unknown>;
latestReasoningScene = normalizeDeepReferenceResponse(raw);
const evidenceRaw = Array.isArray(raw.evidence) ? (raw.evidence as Record<string, unknown>[]) : [];
const evidence: EvidenceEntry[] = evidenceRaw.map((e) => {
@ -170,6 +180,8 @@
let query = $state('');
let loading = $state(false);
let response: DeepReferenceResponse | null = $state(null);
let reasoningScene: ReasoningScene | null = $state(null);
let selectedStage: ReasoningStageReceipt | null = $state(null);
let error: string | null = $state(null);
let askInputEl: HTMLInputElement | null = $state(null);
@ -183,9 +195,12 @@
loading = true;
error = null;
response = null;
reasoningScene = null;
selectedStage = null;
arcs = [];
try {
response = await deepReferenceFetch(q);
reasoningScene = latestReasoningScene;
// After DOM paints the evidence cards, measure & draw arcs
requestAnimationFrame(() => requestAnimationFrame(measureArcs));
} catch (e) {
@ -218,6 +233,12 @@
arcs = next;
}
function handleRoutePick(pick: RoutePick) {
if (pick.kind === 'stage') {
selectedStage = pick.payload as ReasoningStageReceipt;
}
}
function handleGlobalKey(e: KeyboardEvent) {
// Cmd/Ctrl + K focuses the ask box
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
@ -249,7 +270,18 @@
<title>Reasoning Theater · Vestige</title>
</svelte:head>
<div class="p-6 max-w-6xl mx-auto space-y-8 enter">
<RouteStage
organ="reasoning"
seed={`reasoning-theater:${query || 'empty'}`}
scene={reasoningScene}
passes={createReasoningTheaterPasses}
loading={loading}
error={error}
emptyLabel="ASK A QUERY TO LIGHT THE EIGHT-STAGE ORGAN"
onpick={handleRoutePick}
/>
<div class="relative z-10 p-6 max-w-6xl mx-auto space-y-8 enter">
<!-- Header -->
<PageHeader
icon="reasoning"
@ -610,6 +642,56 @@
{/if}
</div>
{#if selectedStage}
<aside
class="fixed right-4 top-24 bottom-4 z-30 w-[min(26rem,calc(100vw-2rem))] overflow-auto rounded-2xl
border border-[#A8FF5E]/25 bg-[#020307]/92 p-4 shadow-2xl backdrop-blur-xl font-mono"
aria-label="Reasoning stage receipt"
>
<div class="flex items-start justify-between gap-3 border-b border-[#A8FF5E]/15 pb-3">
<div>
<div class="text-[10px] uppercase tracking-[0.22em] text-[#A8FF5E]/70">stage receipt</div>
<h2 class="text-bright text-sm mt-1">{selectedStage.index + 1}. {selectedStage.label}</h2>
</div>
<button class="text-muted hover:text-bright" onclick={() => (selectedStage = null)} aria-label="Close stage receipt">×</button>
</div>
<div class="grid grid-cols-3 gap-2 py-3 text-[11px]">
<div class="rounded-lg border border-white/10 bg-white/[0.03] p-2">
<div class="text-muted">count</div>
<div class="text-[#E9FFB7]">{selectedStage.count}</div>
</div>
<div class="rounded-lg border border-white/10 bg-white/[0.03] p-2">
<div class="text-muted">confidence</div>
<div class="text-[#E9FFB7]">{Math.round(selectedStage.confidence * 100)}%</div>
</div>
<div class="rounded-lg border border-white/10 bg-white/[0.03] p-2">
<div class="text-muted">interrupt</div>
<div class={selectedStage.interrupt === 'none' ? 'text-dim' : 'text-[#FF3B30]'}>{selectedStage.interrupt}</div>
</div>
</div>
<div class="space-y-3 text-[11px]">
<div>
<div class="uppercase tracking-[0.16em] text-muted mb-1">provenance</div>
<pre class="whitespace-pre-wrap rounded-xl border border-[#22C7DE]/15 bg-[#07100D]/75 p-3 text-[#9DFFEB]">{JSON.stringify(selectedStage.provenance, null, 2)}</pre>
</div>
<div>
<div class="uppercase tracking-[0.16em] text-muted mb-1">exact exposed backend data</div>
<pre class="whitespace-pre-wrap rounded-xl border border-[#A8FF5E]/15 bg-[#07100D]/75 p-3 text-[#E9FFB7]">{JSON.stringify(selectedStage.exposed, null, 2)}</pre>
</div>
<div>
<div class="uppercase tracking-[0.16em] text-muted mb-1">not_exposed_by_backend</div>
<ul class="rounded-xl border border-[#FFD166]/15 bg-[#11140A]/75 p-3 text-[#FFD166] space-y-1">
{#each selectedStage.not_exposed_by_backend as item}
<li>{item}</li>
{/each}
</ul>
</div>
</div>
</aside>
{/if}
<style>
.conf-number {
animation: conf-pop 900ms cubic-bezier(0.22, 0.8, 0.3, 1) backwards;