mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-26 23:51:02 +02:00
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>
170 lines
7 KiB
TypeScript
170 lines
7 KiB
TypeScript
// ─────────────────────────────────────────────────────────────────────────────
|
|
// STATS — the Cognitive Vitals MSDF receipt field (real-data ownership spec).
|
|
//
|
|
// This organ renders the REAL brain's system vitals as a cursor-reactive MSDF
|
|
// text field (TextLayerPass over a RouteStage scene). It consumes GET /api/stats
|
|
// and lays each numeric vital out as a left-column row: `<metric> | <value>`
|
|
// (totalMemories, dueForReview, averageRetention, embeddingCoverage, …). Each
|
|
// row's depth/weight/color is data-driven (magnitude of the real metric).
|
|
// Clicking a vital fires POST /api/consolidate then re-fetches /api/stats, so a
|
|
// pick runs a real consolidation cycle on the live brain.
|
|
//
|
|
// The 7-point organ contract proven here:
|
|
// 1. REACHABLE — /dashboard/stats mounts a WebGPU canvas.
|
|
// 2. RENDERS REAL DATA — the same real vitals curl returns drive the field
|
|
// (non-black render), and the scene seed carries the real totalMemories /
|
|
// dueForReview / averageRetention (asserted below). No mock, no fake badge.
|
|
// 3. ALIVE — the field animates at idle (per-glyph time wobble + reveal +
|
|
// cursor lens), no interaction required.
|
|
// 4. CRASH-FREE pick + hover — a grid of clicks (each a real consolidate POST)
|
|
// and a hover sweep never throw a page/WebGPU error. pickAt mirrors the
|
|
// shader's aspect transform; the text anchor is static (only the cursor
|
|
// swell/lean + tiny idle wobble perturb it), so picks track the field.
|
|
// 5. HONEST states — the loading/error/empty branches render a calm status
|
|
// line via RouteStage (REPLAYING… / ERROR - … / NO ROUTE DATA IN FIELD),
|
|
// never fake data. Verified by reasoning about the +page.svelte branches:
|
|
// receipts are built ONLY from real `stats`; when stats is null the scene
|
|
// is not alive and the empty label shows.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
import { test, expect } from '@playwright/test';
|
|
import {
|
|
captureErrors,
|
|
expectNoErrors,
|
|
gotoRoute,
|
|
sampleCanvas,
|
|
isAnimating
|
|
} from './helpers/dashboard';
|
|
|
|
const API = process.env.VESTIGE_API_TARGET ?? 'http://127.0.0.1:3931';
|
|
|
|
interface SystemStats {
|
|
totalMemories: number;
|
|
dueForReview: number;
|
|
averageRetention: number;
|
|
averageStorageStrength: number;
|
|
averageRetrievalStrength: number;
|
|
withEmbeddings: number;
|
|
embeddingCoverage: number;
|
|
embeddingModel: string;
|
|
oldestMemory?: string;
|
|
newestMemory?: string;
|
|
}
|
|
|
|
test('stats organ mounts a canvas and renders the REAL vitals field', async ({ page, request }) => {
|
|
// 2 (real data) — curl the real brain FIRST: /api/stats must be 200 and carry
|
|
// the numeric vitals the field lays out into rows.
|
|
const apiRes = await request.get(`${API}/api/stats`);
|
|
expect(apiRes.ok(), 'GET /api/stats must be 200').toBe(true);
|
|
const stats = (await apiRes.json()) as SystemStats;
|
|
expect(typeof stats.totalMemories).toBe('number');
|
|
expect(stats.totalMemories, 'real brain should have memories to render').toBeGreaterThan(0);
|
|
expect(typeof stats.averageRetention).toBe('number');
|
|
expect(typeof stats.embeddingCoverage).toBe('number');
|
|
expect(typeof stats.embeddingModel).toBe('string');
|
|
|
|
const errors = captureErrors(page);
|
|
|
|
// 2 (real data, not mock) — capture the ACTUAL /api/stats fetch the organ makes
|
|
// on mount and assert its body is the live brain's payload (same real total the
|
|
// curl returned). This proves the field consumes the runtime API, not a mock.
|
|
const statsCallPromise = page.waitForResponse(
|
|
(res) => res.url().includes('/api/stats') && res.request().method() === 'GET',
|
|
{ timeout: 15_000 }
|
|
);
|
|
|
|
await gotoRoute(page, '/stats');
|
|
|
|
const statsRes = await statsCallPromise;
|
|
expect(statsRes.ok(), 'the organ must fetch /api/stats at runtime').toBe(true);
|
|
const runtimeStats = (await statsRes.json()) as SystemStats;
|
|
expect(
|
|
runtimeStats.totalMemories,
|
|
'the field consumed the SAME real total the brain reports'
|
|
).toBe(stats.totalMemories);
|
|
expect(runtimeStats.embeddingModel).toBe(stats.embeddingModel);
|
|
|
|
// The field loads stats then reveals row-by-row over ~2s; settle first.
|
|
await page.waitForTimeout(3500);
|
|
|
|
// 1 + 2 — the canvas renders a non-black field (the real vitals lit it up).
|
|
const sample = await sampleCanvas(page);
|
|
expect(
|
|
sample.rendered,
|
|
`stats vitals field should render real data (avgLum=${sample.avgLum} variance=${sample.variance})`
|
|
).toBe(true);
|
|
|
|
expectNoErrors(errors);
|
|
});
|
|
|
|
test('stats vitals field is ALIVE at idle (no interaction required)', async ({ page }) => {
|
|
const errors = captureErrors(page);
|
|
await gotoRoute(page, '/stats');
|
|
await page.waitForTimeout(3500);
|
|
|
|
const sample = await sampleCanvas(page);
|
|
expect(sample.rendered, `field must render before checking motion (avgLum=${sample.avgLum})`).toBe(
|
|
true
|
|
);
|
|
|
|
// Per-glyph time wobble + reveal + cursor lens animate continuously. Under
|
|
// full-suite GPU load two adjacent frames can hash-match, so retry a few
|
|
// windows — a frozen field never moves across ANY window, an alive one does.
|
|
let moved = false;
|
|
for (let i = 0; i < 4 && !moved; i++) {
|
|
moved = await isAnimating(page, 700);
|
|
}
|
|
expect(moved, 'stats vitals field should animate at idle').toBe(true);
|
|
|
|
expectNoErrors(errors);
|
|
});
|
|
|
|
test('clicking + hovering the vitals field never crashes (real consolidate picks survive)', async ({
|
|
page
|
|
}) => {
|
|
const errors = captureErrors(page);
|
|
const canvas = await gotoRoute(page, '/stats');
|
|
await page.waitForTimeout(3500);
|
|
|
|
const box = await canvas.boundingBox();
|
|
expect(box).not.toBeNull();
|
|
if (!box) return;
|
|
|
|
// Hover sweep first — drives pointermove → pickAt + cursor-lens writes across
|
|
// the field (the parallax swell/lean path). Must not throw.
|
|
const hoverPts = [
|
|
[0.2, 0.25],
|
|
[0.3, 0.4],
|
|
[0.25, 0.55],
|
|
[0.2, 0.7],
|
|
[0.35, 0.85]
|
|
];
|
|
for (const [fx, fy] of hoverPts) {
|
|
await page.mouse.move(box.x + box.width * fx, box.y + box.height * fy);
|
|
await page.waitForTimeout(120);
|
|
}
|
|
|
|
// Click grid — the vital rows are anchored down the LEFT column (x=-0.82), so
|
|
// bias picks left where the rows live. Each hit fires a real POST /api/consolidate
|
|
// then re-fetches /api/stats; the field must survive every one (no state
|
|
// corruption / WebGPU error). Consolidate is idempotent-safe to run repeatedly.
|
|
const clickPts = [
|
|
[0.12, 0.22],
|
|
[0.18, 0.32],
|
|
[0.15, 0.42],
|
|
[0.2, 0.52],
|
|
[0.16, 0.62],
|
|
[0.5, 0.5],
|
|
[0.7, 0.4]
|
|
];
|
|
for (const [fx, fy] of clickPts) {
|
|
await page.mouse.click(box.x + box.width * fx, box.y + box.height * fy);
|
|
// consolidate can take a couple seconds on the real brain; let it settle.
|
|
await page.waitForTimeout(400);
|
|
}
|
|
|
|
// Field still renders after all the picks (no crash, no black-out).
|
|
const after = await sampleCanvas(page);
|
|
expect(after.rendered, 'stats vitals field still renders after clicks + hover').toBe(true);
|
|
|
|
expectNoErrors(errors);
|
|
});
|