vestige/apps/dashboard/e2e/organ-reasoning.spec.ts
Sam Valladares 844b91a804 feat(dashboard): every organ is a living WebGPU field (LivingFieldPass)
Turn all 17 sparse organs from text-on-black voids into full-bleed, moving,
bioluminescent WebGPU fields. Each cell maps to a real backend fact; verified
live on the real brain with a decoded fill% + motion + zero-error probe.

Foundation: one reusable LivingFieldPass (src/lib/observatory/field/) does
splat -> separable-blur (render passes) -> membrane base coat -> HDR cells with
orbital drift and a CPU pick-mirror. Each organ writes a ~20-line data->cells
mapper (cell-layout.ts: layoutGalaxy / layoutRings / FIELD_HUE).

Organs brought alive (fill% before -> after, all zero WebGPU errors):
stats 0.8->80.9, observatory 10.8->81.4, graph 12.3->82.4, memories ->78.4,
blackbox 18.2->83.3, contradictions 1.6->69.6, dreams 0.3->64.5,
settings 2.7->76.4, reasoning 2.7->39.2, patterns 7.1->45.6,
activation 10->64.9, feed ->89, explore ->83.3, intentions ->66.6,
importance ->73.7, memory-prs ->74.4, schedule ->37.1.

Backend surfaces: add api.memoryChangelog() + ChangelogResponse; surface
suppress/unsuppress on the memories field (shift/alt-click -> scar).

Tests: palace-launch-gate now asserts fillPct>30 + isAnimating (not just
non-black), with a longer settle for reasoning's deep_reference and a raised
timeout for the curated tour. all-routes-smoke treats settings as the WebGPU
field it now is. Gates green: check 0/0, build ok, palace-launch-gate 22/22 +
tour, all-routes-smoke 20/20.

Protected work untouched: MemoryCinema, graph/cinema, observatory scene
shaders, node-renderer, palace-map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 06:20:35 +07:00

169 lines
8.5 KiB
TypeScript

import { test, expect } from '@playwright/test';
import {
BASE,
captureErrors,
expectNoErrors,
gotoRoute,
sampleCanvas,
isAnimating
} from './helpers/dashboard';
// ─────────────────────────────────────────────────────────────────────────
// ORGAN OWNER SPEC — /reasoning (Reasoning Theater, the deep_reference organ)
//
// Proves the 7-point ship-a-working-product contract for THIS organ against
// the REAL brain (:3931, 1285 memories). Focused + fast — does not duplicate
// all-routes-smoke; adds the organ-specific proof:
// 1. REACHABLE — /dashboard/reasoning mounts its WebGPU canvas.
// 5. HONEST EMPTY — before any query the organ shows a calm empty state
// (empty-state DOM copy + the field's empty label), NOT
// fake data or a broken/errored surface.
// 2. RENDERS REAL — a real query runs the 8-stage pipeline and the DOM
// DATA materializes REAL numbers straight from deep_reference
// (memoriesAnalyzed / confidence% / Primary Source /
// Evidence count). Asserted on values that only exist
// because the real backend produced them.
// 3. ALIVE — the organ field animates after the scene is uploaded.
// 4. CRASH-FREE — a grid of in-canvas clicks (drives pass.pickAt over the
// CLICK + HOVER static stage rects) + a hover sweep; the canvas SURVIVES
// every one with zero page/WebGPU errors. Stage centers do
// NOT animate in the vertex shader (only radius/energy do),
// and pickAt() uses the same y = 0.76 - i*(1.52/7) layout
// as buildStageRects(), so picks land on the real targets.
//
// Real ground truth captured live via curl POST /api/deep_reference
// {query:"How does FSRS-6 trust scoring work?"}:
// confidence 0.87 · memoriesAnalyzed 26 · activationExpanded 6 ·
// evidence 10 · contradictions 0 · superseded 0 · intent "Synthesis".
// contradictions/superseded are legitimately 0 for this query, so those
// sections correctly DON'T render — the honest no-fake-data path.
// ─────────────────────────────────────────────────────────────────────────
const REASONING = '/reasoning';
const QUERY = 'How does FSRS-6 trust scoring work?';
test.describe('Organ /reasoning — Reasoning Theater', () => {
test('reachable, honest-empty, renders REAL data, alive, and survives click+hover', async ({
page
}) => {
const capture = captureErrors(page);
// ── 1. REACHABLE: route mounts its WebGPU canvas ───────────────────
const canvas = await gotoRoute(page, REASONING);
await expect(canvas).toBeVisible();
// ── 5. HONEST EMPTY STATE (pre-query) ──────────────────────────────
// The DOM empty panel renders with real, non-faked copy — and NO fake
// "Live" data / response sections exist yet.
await expect(
page.getByText('Ask anything. Vestige will run the full reasoning pipeline and show you its work.')
).toBeVisible();
// No response artifacts before a query: no Primary Source citation, no
// Cognitive Pipeline result, no confidence meter — nothing fabricated.
await expect(page.getByText('Primary Source', { exact: true })).toHaveCount(0);
await expect(page.getByRole('heading', { name: 'Cognitive Pipeline' })).toHaveCount(0);
// The immersive field is present but shows its honest empty label (no
// invented scene) — canvas is still a real, sampleable surface.
const emptySample = await sampleCanvas(page);
expect(emptySample.ok, 'empty-state canvas should be sampleable').toBe(true);
// ── 2. RENDERS REAL DATA: run the real 8-stage pipeline ────────────
const input = page.getByPlaceholder('Ask your memory anything…');
await expect(input).toBeVisible();
await input.click();
await input.fill(QUERY);
await input.press('Enter');
const cognitivePipeline = page.getByRole('heading', { name: 'Cognitive Pipeline' });
const errorSurface = page.locator('text=/^Error:/');
await Promise.race([
cognitivePipeline.waitFor({ state: 'visible', timeout: 30_000 }),
errorSurface.waitFor({ state: 'visible', timeout: 30_000 })
]);
// A real backend error is a genuine finding — surface it, never hide it.
const errored = await errorSurface.isVisible().catch(() => false);
if (errored) {
const msg = (await errorSurface.textContent())?.trim();
throw new Error(`Reasoning Theater surfaced a backend error: ${msg}`);
}
// The pipeline finished (button left its "Reasoning…" limbo).
await expect(page.getByRole('button', { name: /Reason(ing…)?/ })).toBeEnabled({
timeout: 15_000
});
// These assertions can ONLY pass if real deep_reference data drove the DOM:
// • the recommended Primary Source citation block,
await expect(page.getByText('Primary Source', { exact: true })).toBeVisible();
// • an "N analyzed" count (real memoriesAnalyzed from the backend),
const analyzed = page.getByText(/\banalyzed\b/).first();
await expect(analyzed).toBeVisible();
// • a real confidence percentage in the meter,
await expect(page.locator('text=/\\d+%/').first()).toBeVisible();
// • the Evidence header WITH a real non-zero count in parentheses.
const evidenceHeading = page.getByRole('heading', { name: /^Evidence/ });
await expect(evidenceHeading).toBeVisible();
const evidenceText = (await evidenceHeading.textContent()) ?? '';
const evCount = Number(evidenceText.match(/\((\d+)\)/)?.[1] ?? '0');
expect(evCount, `Evidence count should be real & non-zero (saw "${evidenceText.trim()}")`).toBeGreaterThan(0);
// At least one real EvidenceCard rendered from the backend payload.
await expect(page.locator('[data-evidence-id]').first()).toBeVisible();
// ── 3. ALIVE: the organ field animates after the scene upload ──────
await page.waitForTimeout(2500);
const lit = await sampleCanvas(page);
expect(lit.ok, 'organ canvas should be sampleable').toBe(true);
expect(
lit.rendered,
`organ field should render non-black (avgLum=${lit.avgLum}, variance=${lit.variance})`
).toBe(true);
const animating = await isAnimating(page);
expect(animating, 'organ field should animate (living, not a frozen frame)').toBe(true);
// ── 4. CRASH-FREE in-canvas click grid + hover sweep ───────────────
// The RouteStage field layer is full-bleed fixed inset-0; clicking it
// routes through handleFieldClick → pass.pickAt over the 8 static stage
// rects. Sweep a grid so several clicks LAND on stage chambers (which
// open the DOM stage-receipt aside) and several miss (no-op) — the
// canvas must survive every one.
const box = await canvas.boundingBox();
expect(box, 'canvas should have a bounding box').not.toBeNull();
if (box) {
const cols = 5;
const rows = 5;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = box.x + ((c + 0.5) / cols) * box.width;
const y = box.y + ((r + 0.5) / rows) * box.height;
await page.mouse.move(x, y); // hover sweep
await page.mouse.click(x, y); // in-canvas pick
}
}
// A hover along the vertical stage column (x≈center) — where the
// eight chambers live — must not throw either.
for (let r = 0; r < 9; r++) {
await page.mouse.move(box.x + box.width * 0.5, box.y + (r / 8) * box.height);
}
}
// The canvas is still alive and rendering after all that interaction.
const afterInteract = await sampleCanvas(page);
expect(
afterInteract.rendered,
`organ field should still render after clicks/hover (avgLum=${afterInteract.avgLum}, variance=${afterInteract.variance})`
).toBe(true);
// A stage-receipt aside may have opened from a landed pick; if so it must
// be a real receipt panel (has the "stage receipt" label), never junk.
const receiptAside = page.getByLabel('Reasoning stage receipt');
if (await receiptAside.isVisible().catch(() => false)) {
await expect(receiptAside.getByText('stage receipt')).toBeVisible();
}
await page.screenshot({ path: 'e2e/screenshots/organ-reasoning.png', fullPage: true });
// ── 4/5. No real app / WebGPU-validation errors across the whole run ─
expectNoErrors(capture);
});
});