mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-24 23:41:01 +02:00
Verified-green pre-OS-shell checkpoint. Mobile portrait reflow (portraitAdapt + MobileNav), desktop field-richness on 7 text organs, 4 fake-data fixes (settings version, TRIGGER BIRTH removed, predict real per-memory urgency, palace DIAG), timeline dim-backdrop, stats depth-floor, em-dash cleanup, per-tier launch-gate fill floors. check 0/0, 977 files. Cinema WIP isolated to stash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
228 lines
9.6 KiB
TypeScript
228 lines
9.6 KiB
TypeScript
// ─────────────────────────────────────────────────────────────────────────────
|
|
// THE LAUNCH GATE — "click any orb, land on a working page, every time."
|
|
//
|
|
// The Spatial Palace (/dashboard/palace) is a live 3D WebGPU constellation. As of
|
|
// the Jul 2026 launch curation it surfaces 9 HERO organs (ORGAN_REGIONS in
|
|
// src/lib/observatory/palace-map.ts); the other 10 organs still exist and are
|
|
// reachable by direct URL, just not palace-linked. A pointerdown on an orb runs
|
|
// pass.pickAt -> goto(`${base}${href}`), diving into that organ.
|
|
//
|
|
// This one spec is the ship gate, in two parts:
|
|
// 1. PALACE LOOP — the palace opens live, a real click-to-enter dive lands on a
|
|
// curated organ, and every one of the 9 curated organs lands on a LIVE,
|
|
// non-black, error-free page (then returns to the palace).
|
|
// 2. FULL URL SMOKE — all 20 routes (the 10 hidden organs included) mount a
|
|
// canvas and render a non-black, error-free field by direct URL.
|
|
//
|
|
// If even one lands dead/black/errored, this spec fails and we do not ship.
|
|
//
|
|
// Anti-false-green: non-black is asserted on DECODED luminance (avgLum + variance
|
|
// from a real compositor screenshot), never the sampleCanvas `rendered` flag
|
|
// whose size>20_000 fallback can pass a black field. WebGPU-readback constraint:
|
|
// reading the live canvas via Canvas2D returns BLACK in ANGLE/headless, so every
|
|
// pixel proof goes through sampleCanvas's screenshot path.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
import { test, expect, type Page } from '@playwright/test';
|
|
import { BASE, captureErrors, expectNoErrors, sampleCanvas, isAnimating } from './helpers/dashboard';
|
|
|
|
// The 9 organs the launch palace surfaces (mirrors ORGAN_REGIONS). settings is
|
|
// now a full-WebGPU in-canvas console like every other organ (no DOM panel).
|
|
const CURATED_ORGANS = [
|
|
'observatory',
|
|
'graph',
|
|
'memories',
|
|
'timeline',
|
|
'feed',
|
|
'explore',
|
|
'reasoning',
|
|
'stats',
|
|
'settings'
|
|
];
|
|
|
|
// The 10 organs hidden from the palace but still reachable by direct URL.
|
|
const HIDDEN_ORGANS = [
|
|
'contradictions',
|
|
'blackbox',
|
|
'duplicates',
|
|
'memory-prs',
|
|
'activation',
|
|
'dreams',
|
|
'schedule',
|
|
'importance',
|
|
'patterns',
|
|
'intentions'
|
|
];
|
|
|
|
const ALL_ROUTES = ['palace', ...CURATED_ORGANS, ...HIDDEN_ORGANS];
|
|
const CURATED_URL_RE = new RegExp(`/dashboard/(?:${CURATED_ORGANS.join('|')})(?:[/?#]|$)`);
|
|
|
|
// Some organs build their field over a couple seconds; give every landing a
|
|
// generous settle before sampling so slow-mounting organs aren't flaky.
|
|
const SETTLE_MS = 3200;
|
|
// /reasoning auto-runs a real 8-stage deep_reference on mount and stages its
|
|
// evidence galaxy from the response — that round-trip legitimately takes longer
|
|
// than a static field, so it gets an extended settle before we sample.
|
|
const SLOW_ORGANS: Record<string, number> = { reasoning: 12_000 };
|
|
|
|
// The liveness bar. TWO tiers, matching the SHIPPED design direction (Sam's
|
|
// Jul 2026 correction: "the field is COMPLETELY OVERPOWERING THE TEXT" — the
|
|
// field must be a DIM, legible BACKDROP on text-heavy organs, and the hero
|
|
// only on the visual organs).
|
|
//
|
|
// VISUAL organs (palace, graph, observatory, memories, activation, explore,
|
|
// timeline, blackbox): the field IS the hero — it fills the screen. High bar.
|
|
//
|
|
// TEXT-HEAVY organs (stats, settings, reasoning, feed, schedule, importance,
|
|
// contradictions, patterns, intentions, dreams, memory-prs, duplicates): the
|
|
// field is a DIM breathing backdrop behind a reading well so the MSDF text
|
|
// wins the contrast fight (each carries setIntensity ~0.18-0.26 + a reading
|
|
// well). fill% legitimately reads LOW here BY DESIGN — a high fill would mean
|
|
// the blinding-blob bug is back. The bar is still "lit + structured + moving,
|
|
// not black", just a lower fill floor.
|
|
//
|
|
// The gate samples a 96x96 decode of a real compositor screenshot.
|
|
const MIN_FILL_PCT_VISUAL = 30; // field is the hero → must be full-bleed
|
|
const MIN_FILL_PCT_TEXT = 6; // dim backdrop by design → lit but not blob
|
|
|
|
// Organs where the field is the HERO (full-bleed expected). Everything else is
|
|
// a text-heavy instrument with a dim backdrop.
|
|
const VISUAL_ORGANS = new Set([
|
|
'palace',
|
|
'graph',
|
|
'observatory',
|
|
'memories',
|
|
'activation',
|
|
'explore',
|
|
'timeline',
|
|
'blackbox'
|
|
]);
|
|
|
|
function minFillFor(label: string): number {
|
|
// label is like "/stats" or "dived organ .../stats" or "palace hub" — match
|
|
// the organ name loosely so both smoke and tour callers resolve correctly.
|
|
for (const organ of VISUAL_ORGANS) {
|
|
if (label.includes(organ)) return MIN_FILL_PCT_VISUAL;
|
|
}
|
|
return MIN_FILL_PCT_TEXT;
|
|
}
|
|
|
|
/** Assert a genuinely-lit, MOVING, error-free field on the current route. */
|
|
async function assertLiveField(
|
|
page: Page,
|
|
label: string
|
|
): Promise<{ avgLum: number; variance: number; fillPct: number }> {
|
|
const canvas = page.locator('canvas').first();
|
|
await canvas.waitFor({ state: 'attached', timeout: 15_000 });
|
|
// Slow organs (reasoning's deep_reference round-trip) settle longer.
|
|
const slow = Object.entries(SLOW_ORGANS).find(([name]) => label.includes(name));
|
|
await page.waitForTimeout(slow ? slow[1] : SETTLE_MS);
|
|
const s = await sampleCanvas(page);
|
|
expect(s.avgLum, `${label} avgLum (field must not be black)`).toBeGreaterThan(3);
|
|
expect(s.variance, `${label} variance (field must have structure)`).toBeGreaterThan(6);
|
|
const minFill = minFillFor(label);
|
|
expect(
|
|
s.fillPct,
|
|
`${label} fill% (field must be lit + structured; visual organs full-bleed, text organs a dim backdrop)`
|
|
).toBeGreaterThan(minFill);
|
|
return { avgLum: s.avgLum, variance: s.variance, fillPct: s.fillPct };
|
|
}
|
|
|
|
/** Probe a grid across the orbiting constellation until a pointerdown dives. */
|
|
async function clickToEnter(page: Page): Promise<string | null> {
|
|
const canvas = page.locator('canvas').first();
|
|
const box = await canvas.boundingBox();
|
|
if (!box) return null;
|
|
const cx = box.x + box.width / 2;
|
|
const cy = box.y + box.height / 2;
|
|
const offsets: Array<[number, number]> = [
|
|
[0, 0],
|
|
[0.08, 0], [-0.08, 0], [0, 0.08], [0, -0.08],
|
|
[0.16, 0.1], [-0.16, 0.1], [0.16, -0.1], [-0.16, -0.1],
|
|
[0.28, 0], [-0.28, 0], [0, 0.24], [0, -0.24],
|
|
[0.34, 0.18], [-0.34, 0.18], [0.34, -0.18], [-0.34, -0.18]
|
|
];
|
|
for (let pass = 0; pass < 3; pass++) {
|
|
for (const [ox, oy] of offsets) {
|
|
const x = cx + ox * (box.width / 2);
|
|
const y = cy + oy * (box.height / 2);
|
|
await page.mouse.move(x, y);
|
|
await page.mouse.down();
|
|
await page.mouse.up();
|
|
try {
|
|
await page.waitForURL(CURATED_URL_RE, { timeout: 900 });
|
|
return page.url();
|
|
} catch {
|
|
/* missed every orb at this probe point — keep spiralling */
|
|
}
|
|
}
|
|
await page.waitForTimeout(1200);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
test.describe('LAUNCH GATE — palace loop', () => {
|
|
test('palace opens live and a real click-to-enter dive lands on a curated organ', async ({ page }) => {
|
|
const errors = captureErrors(page);
|
|
|
|
await page.goto(`${BASE}/palace`, { waitUntil: 'networkidle' });
|
|
await assertLiveField(page, 'palace');
|
|
expect(await isAnimating(page, 800), 'palace constellation should be orbiting').toBe(true);
|
|
|
|
const entered = await clickToEnter(page);
|
|
expect(entered, 'a pointerdown on an orb should dive into a curated organ').not.toBeNull();
|
|
expect(entered!, `landed URL should be a curated organ: ${entered}`).toMatch(CURATED_URL_RE);
|
|
|
|
await assertLiveField(page, `dived organ ${entered}`);
|
|
expectNoErrors(errors);
|
|
});
|
|
|
|
test('every curated organ lands on a live, error-free page and returns to the palace', async ({ page }) => {
|
|
// The tour dives 9 organs sequentially, each with a full settle + palace
|
|
// return, so it legitimately runs longer than the 60s per-test default.
|
|
test.setTimeout(180_000);
|
|
const errors = captureErrors(page);
|
|
|
|
await page.goto(`${BASE}/palace`, { waitUntil: 'networkidle' });
|
|
await assertLiveField(page, 'palace hub');
|
|
|
|
const report: Array<{ path: string; avgLum: number; variance: number; fillPct: number }> = [];
|
|
|
|
for (const path of CURATED_ORGANS) {
|
|
// Dive — exactly what the palace pick handler runs: goto(base + href).
|
|
await page.goto(`${BASE}/${path}`, { waitUntil: 'networkidle' });
|
|
const { avgLum, variance, fillPct } = await assertLiveField(page, `organ /${path}`);
|
|
report.push({ path, avgLum, variance, fillPct });
|
|
await test.info().attach(`organ-${path}.png`, {
|
|
body: await page.screenshot(),
|
|
contentType: 'image/png'
|
|
});
|
|
|
|
// Return to the palace between dives (the real orbit loop).
|
|
await page.goto(`${BASE}/palace`, { waitUntil: 'networkidle' });
|
|
await page.waitForTimeout(400);
|
|
}
|
|
|
|
await test.info().attach('launch-gate-report.json', {
|
|
body: Buffer.from(JSON.stringify(report, null, 2)),
|
|
contentType: 'application/json'
|
|
});
|
|
|
|
// Zero page / WebGPU-validation errors across the ENTIRE curated tour.
|
|
expectNoErrors(errors);
|
|
});
|
|
});
|
|
|
|
test.describe('LAUNCH GATE — full URL smoke (all 20 routes)', () => {
|
|
for (const path of ALL_ROUTES) {
|
|
test(`/${path} mounts a live, full-bleed, MOVING field`, async ({ page }) => {
|
|
const errors = captureErrors(page);
|
|
await page.goto(`${BASE}/${path}`, { waitUntil: 'networkidle' });
|
|
await assertLiveField(page, `/${path}`);
|
|
// Every organ must MOVE (living field, not a frozen frame). palace +
|
|
// timeline + the 17 field organs all orbit deterministically at rest.
|
|
expect(await isAnimating(page, 800), `/${path} field should be animating`).toBe(true);
|
|
expectNoErrors(errors);
|
|
});
|
|
}
|
|
});
|