Release God's Eye View as open source

This commit is contained in:
Bilawal Sidhu committed 2026-08-24 17:02:16 -05:00
1 parent a5aaea7e16
commit 880a672b5e
433 files changed
+219893 -23

No files matched your search

+413
View File
@@ -0,0 +1,413 @@
#!/usr/bin/env node
/**
* qa-perf — render-governor regression gate (perf wave 2).
*
* Asserts the governor's observable contract with RELATIVE frame-count
* assertions (SwiftShader-safe; no wall-clock GPU numbers):
*
* 1. Idle + zero layers + parked camera → the scene stops rendering
* (near-zero postRender fires over a settle-then-observe window).
* 2. A discrete mutation while idle (style slider write routed through
* governorRequestRender) → at least one render, then settles again.
* 3. Camera movement while idle → renders happen (Cesium-native path).
* 4. Flights enabled → continuous mode (postRender cadence ≈ rAF cadence,
* and ≥5× the idle count over the same window).
* 5. Flights disabled again → back to idle (near-zero fires).
* 6. Governor diagnostics agree with the mode at every step.
*
* ── WHAT THE IDLE WINDOWS MEASURE, AND WHY THEY WAIT ────────────────────────
*
* The idle checks (1 and 5) measure the STEADY-STATE floor: what a parked scene
* costs once it has settled. They do not measure how long settling takes, so the
* counted window has to begin AFTER settling — and "after" is a stronger
* condition than "nothing is happening right now".
*
* That distinction became load-bearing when detection stopped holding the render
* loop open (2026-08-22) and became a default. With a paint lane active, the
* world-overlay host honours its occluder observers with frames: chrome that
* changes SIZE resizes an occluder, `markLayoutDirty` fires, and that becomes a
* `requestRender`. While detection forced continuous mode this was invisible.
* Event-driven, it shows up as renders.
*
* The specific contaminant, pinned with stack traces on this tree, is triggered
* by THIS HARNESS'S OWN SETUP. Disabling every layer publishes a `visibility`
* change; the HUD subscribes to that and marks its semantic summary dirty
* (`src/hud.js`), and the summary refreshes on a 15-SECOND interval
* (`HUD_SUMMARY_INTERVAL_MS`). So up to 15 s after the teardown a tick fetches a
* new summary and TYPES it in with a typewriter animation; the growing text
* reflows `.hud-corner.hud-top-left`, an occluder, and the host turns that
* reflow into a handful of frames. Two instrumented runs (150 s and 180 s
* watches) agree to the second: the teardown lands at 15 s, the burst at 31 s —
* seven frames in that one second — which is 16 s after the teardown and 4 s
* INTO the window the old settle had already started counting. Neither run then
* saw a single further render, across 146 s and 176 s respectively. It is a
* one-shot: the tick that fetches also clears the summary's dirty flag and
* commits its context signature, so every later tick returns early.
*
* Waiting for "an empty second" cannot survive that, because the scene really IS
* empty for the ~15 s between the teardown and the tick that notices it. So the
* settle below requires a quiet RUN LONGER THAN ONE FULL SUMMARY TICK, and that
* run resets on any activity. Quiet then means something stronger and checkable:
* a complete refresh period has elapsed in which the app looked at its new state
* and found nothing to do. The length is derived from the app's own cadence
* rather than tuned against a flake rate — if that interval changes, this moves
* with it.
*
* The threshold inside the window (≤4 fires / 5 s) is untouched. The teeth live
* in the WAIT, not the threshold: a scene rendering every frame never produces an
* empty second at all, so the bounded wait expires and FAILS. Quiet that never
* arrives IS the failure, which is why the timeout is an assertion rather than a
* fall-through — and it is what keeps this honest if the churn ever stops being a
* one-shot. It would: when `/api/openai/hud-summary` is unreachable the summary
* stays dirty and re-types every 15 s, so that machine gets a loud repeatable
* failure here rather than a coin flip.
*
* The underlying fix belongs to the host, not to this harness: an active lane
* with nothing to place is not paint work and should not be honoured with a
* frame. That is worldOverlay surgery — see the post-launch ledger entry
* "world-overlay honours occluder churn as paint work" in
* the performance contract in `docs/CURRENT-STATE.md`.
*
* Usage: node scripts/qa-perf.mjs [--url http://localhost:4173]
* Requires a running dev server. Headless; flags disable occlusion
* throttling so rAF cadence is trustworthy (hidden-pane gotcha).
*/
import puppeteer from 'puppeteer';
const argv = process.argv;
const url = argv.includes('--url') ? argv[argv.indexOf('--url') + 1] : 'http://localhost:4173';
const results = [];
function check(name, pass, detail) {
results.push({ name, pass });
const tag = pass ? 'PASS' : 'FAIL';
console.log(` [${tag}] ${name}${detail ? ` — ${JSON.stringify(detail)}` : ''}`);
}
const browser = await puppeteer.launch({
headless: 'new',
protocolTimeout: 300_000,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--window-size=1440,900',
// Never let background/occlusion throttling freeze rAF or timers — the
// measurements below depend on an honest frame clock.
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-background-timer-throttling',
],
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 860 });
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => !!window.__godsEyeView?.viewer, { timeout: 90_000 });
// Boot flyTo + tile warm + all deferred init.
await new Promise((r) => setTimeout(r, 15_000));
// Park deterministically and disable every layer.
await page.evaluate(async () => {
const gev = window.__godsEyeView;
const v = gev.viewer;
v.camera.cancelFlight();
const ell = v.scene.globe.ellipsoid;
v.camera.setView({
destination: ell.cartographicToCartesian({
longitude: -97.74 * Math.PI / 180, latitude: 30.27 * Math.PI / 180, height: 60_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
for (const [id, entry] of gev.dataManager.layers) {
if (entry.enabled) { try { await gev.dataManager.setEnabled(id, false, { origin: 'user' }); } catch { /* gate reports via counts */ } }
}
});
// Let tiles finish + fades settle + the settling frames drain.
await new Promise((r) => setTimeout(r, 12_000));
/** Count scene postRender fires and rAF ticks over windowMs. */
const countFrames = (windowMs) => page.evaluate((ms) => new Promise((resolve) => {
const scene = window.__godsEyeView.viewer.scene;
let renders = 0; let rafs = 0;
const remove = scene.postRender.addEventListener(() => { renders += 1; });
const t0 = performance.now();
const tick = () => {
rafs += 1;
if (performance.now() - t0 < ms) requestAnimationFrame(tick);
else { remove(); resolve({ renders, rafs }); }
};
requestAnimationFrame(tick);
}), windowMs);
const diag = () => page.evaluate(() => window.__godsEyeView.getRenderGovernorDiagnostics?.()
|| window.__gevRenderGovernor?.getDiagnostics?.() || null);
/**
* The HUD's semantic summary refreshes on this cadence (`src/hud.js`,
* `HUD_SUMMARY_INTERVAL_MS`). A layer-visibility change marks it dirty, and
* the tick that picks that up types the new text in — reflowing an occluder
* and, with a paint lane active, costing frames. Mirrored rather than
* imported because this harness runs against the built app, not the module.
*/
const HUD_SUMMARY_INTERVAL_MS = 15_000;
/** One full refresh period, plus a second of margin, in 1 s windows. */
const QUIET_RUN_WINDOWS = Math.ceil(HUD_SUMMARY_INTERVAL_MS / 1_000) + 1;
/**
* Wait until the scene is genuinely settled, and report whether it ever was.
* This is the START of the idle windows below — see the header.
*
* Requires a CONSECUTIVE RUN of empty one-second windows at least one full
* summary refresh period long, and resets that run on any activity. One empty
* window — or three — proves nothing here: the scene is genuinely idle for the
* whole gap between a teardown and the tick that notices it, so a short
* confirmation happily reports "quiet" moments before the typewriter fires.
* A run longer than the period cannot straddle that gap: it either contains
* the burst, and restarts, or it postdates it.
*
* `quiet: false` is a FAILURE, not a fall-through: a scene rendering every
* frame never produces an empty window at all, so this is what catches a real
* hot loop — and equally a contaminant that has stopped being a one-shot. The
* bound stays comfortably longer than settling takes and comfortably shorter
* than forever: worst case is one interrupted run, the burst itself, then a
* clean run (~55 s), against a 120 s ceiling.
*/
const settleUntilQuiet = async (maxMs = 120_000, requiredConsecutive = QUIET_RUN_WINDOWS) => {
const deadline = Date.now() + maxMs;
let windows = 0;
let consecutive = 0;
let busiest = 0;
let restarts = 0;
while (Date.now() < deadline) {
windows += 1;
const { renders } = await countFrames(1_000);
busiest = Math.max(busiest, renders);
if (renders === 0) consecutive += 1;
else { if (consecutive > 0) restarts += 1; consecutive = 0; }
if (consecutive >= requiredConsecutive) {
return { quiet: true, windows, busiest, restarts, ranFor: consecutive, needRun: requiredConsecutive };
}
}
// A failure here reads as "never held N empty seconds in a row, best run was
// M, restarted R times, busiest window was B" — enough to tell a hot loop
// from a contaminant that has stopped being a one-shot.
return { quiet: false, windows, busiest, restarts, ranFor: consecutive, needRun: requiredConsecutive };
};
// ── 1. idle: near-zero renders ────────────────────────────────────────
const idleSettle = await settleUntilQuiet();
const idle = await countFrames(5_000);
const d1 = await diag();
// Quiet that never arrives is the hot-loop failure — assert it, do not skip it.
check('the parked scene holds a settled quiet run before the idle window is counted', idleSettle.quiet, idleSettle);
check('governor reports idle mode with zero layers', d1?.mode === 'idle', d1);
check('idle parked scene stops rendering (≤4 fires / 5s)', idle.renders <= 4, idle);
// ── 1b. detection at its FIRST-RUN DEFAULT must not hold the parked scene ─
//
// Detection became ON by default on 2026-08-22. It used to take an
// unconditional continuous-render hold whenever it was on — nobody felt that
// while it defaulted OFF, but as a DEFAULT it would pin every idle first-run
// tab at 60 fps forever, defeating this governor outright. The hold is gone
// (`src/data/detectionRenderDemand.js`): detection repaints on change and asks
// for single frames only while a bounded animation is running.
//
// This is the gate for that. The scene above is parked with zero layers, so
// detection-on and detection-off must yield the SAME near-zero render count.
const detectionDefault = await page.evaluate(
() => window.__godsEyeView.styleManager.getDetectionState?.() || null,
);
check(
'precondition: detection is ON at its first-run default (Dense @ 75)',
detectionDefault?.detectionMode === 'DENSE' && detectionDefault?.densityPct === 75,
detectionDefault,
);
check(
'detection ON takes NO continuous-render hold',
d1?.mode === 'idle' && !d1.holds.includes('detection'),
d1,
);
// The control: the same window with detection explicitly OFF.
await page.evaluate(() => { window.__godsEyeView.styleManager._setDetectionMode('OFF'); });
await new Promise((r) => setTimeout(r, 1_500)); // let any fade chain terminate
const idleDetectOff = await countFrames(5_000);
check('idle baseline with detection OFF (≤4 fires / 5s)', idleDetectOff.renders <= 4, idleDetectOff);
// Back to the default. A regression here is the entire point of this gate: the
// old hold produced a full 60 fps window instead of near-zero.
await page.evaluate(() => { window.__godsEyeView.styleManager._setDetectionMode('DENSE'); });
await new Promise((r) => setTimeout(r, 1_500));
const idleDetectOn = await countFrames(5_000);
const dDetect = await diag();
check('idle parked scene with detection ON (≤4 fires / 5s)', idleDetectOn.renders <= 4, idleDetectOn);
check(
'detection ON costs no more idle frames than detection OFF',
idleDetectOn.renders <= idleDetectOff.renders + 2,
{ on: idleDetectOn.renders, off: idleDetectOff.renders },
);
check(
'governor still idle with detection ON',
dDetect?.mode === 'idle' && !dDetect.holds.includes('detection'),
dDetect,
);
// …and the overlay must still be LIVE, not merely quiet: with detection on, a
// camera nudge has to repaint promptly, or "idle" would only mean "stale".
const detectMove = await Promise.all([
countFrames(2_500),
page.evaluate(() => new Promise((resolve) => {
const v = window.__godsEyeView.viewer;
let steps = 0;
const id = setInterval(() => {
v.camera.moveForward(50);
steps += 1;
if (steps >= 20) { clearInterval(id); resolve(true); }
}, 60);
})),
]).then(([frames]) => frames);
check(
'detection ON still repaints promptly on camera motion (≥10 / 2.5s)',
detectMove.renders >= 10,
detectMove,
);
// A render count alone proves the SCENE rendered, not that the detection
// painter ran — a painter disabled outright would score a perfect idle and
// sail through every check above. Count the painter's own frames across the
// same kind of motion, so the teeth reach the thing this change touched.
const detectPainted = await page.evaluate(() => new Promise((resolve) => {
const gev = window.__godsEyeView;
const before = gev.styleManager.getDetectionDiagnostics?.()?.frameCount ?? null;
let paints = 0;
// The diagnostics object is rebuilt on every detection paint, so a fresh
// identity is one painted frame. Sampling it per scene frame is enough to
// tell "painting" from "silent" without reaching into module internals.
let last = gev.styleManager.getDetectionDiagnostics?.();
const remove = gev.viewer.scene.postRender.addEventListener(() => {
const now = gev.styleManager.getDetectionDiagnostics?.();
if (now && now !== last) { paints += 1; last = now; }
});
let steps = 0;
const id = setInterval(() => {
gev.viewer.camera.moveForward(50);
steps += 1;
if (steps >= 20) {
clearInterval(id);
setTimeout(() => {
remove();
resolve({ paints, before, after: gev.styleManager.getDetectionDiagnostics?.()?.frameCount ?? null });
}, 400);
}
}, 60);
}));
check(
'detection is still PAINTING, not merely quiet (≥5 painted frames on motion)',
detectPainted.paints >= 5,
detectPainted,
);
await new Promise((r) => setTimeout(r, 3_000)); // settle back to parked
// ── 2. a REAL slider mutation renders (full UI → uniform → request path) ─
const afterMutation = await Promise.all([
countFrames(2_500),
page.evaluate(() => new Promise((resolve) => setTimeout(() => {
// Drive the actual sharpen slider: input event → handler →
// _applySharpenIntensity → governorRequestRender. Proves the wiring,
// not just the facade.
const slider = document.getElementById('sharpen-intensity-slider');
if (!slider) { resolve({ ok: false }); return; }
slider.value = String(Math.min(100, Number(slider.value) + 7));
slider.dispatchEvent(new Event('input', { bubbles: true }));
resolve({ ok: true });
}, 500))),
]).then(([frames]) => frames);
check('real slider mutation while idle renders ≥1 and ≤10 frames', afterMutation.renders >= 1 && afterMutation.renders <= 10, afterMutation);
// ── 2b. animated style cycle: style-anim holds, then releases ─────────
await page.evaluate(() => { window.__godsEyeView.styleManager.setStyle('retro'); });
await new Promise((r) => setTimeout(r, 900)); // crossfade + first ticks
const dAnim = await diag();
check('animated style takes the style-anim hold (continuous)', dAnim?.mode === 'continuous' && dAnim.holds.includes('style-anim'), dAnim);
const animFrames = await countFrames(2_000);
check('animated style keeps frames flowing (≥50% rAF)', animFrames.renders >= animFrames.rafs * 0.5, animFrames);
// Returning to normal keeps detection ON by design (it is the default, and
// detection persists across style switches). Since 2026-08-22 detection holds
// nothing, so this asserts the strictly harder thing: the scene returns to
// idle with detection still ON — where before it could only go idle by also
// turning detection off.
await page.evaluate(() => { window.__godsEyeView.styleManager.setStyle('normal'); });
await new Promise((r) => setTimeout(r, 1_500)); // fade out + loop self-stop
const dAnimOff = await diag();
const detectionStillOn = await page.evaluate(
() => window.__godsEyeView.styleManager.getDetectionState?.()?.detectionMode || null,
);
check(
'style-anim hold releases and the scene goes idle with detection still ON',
dAnimOff?.mode === 'idle'
&& !dAnimOff.holds.includes('style-anim')
&& !dAnimOff.holds.includes('detection')
&& detectionStillOn !== 'OFF',
{ diag: dAnimOff, detection: detectionStillOn },
);
// ── 2c. satellites holder enters and leaves diagnostics ───────────────
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.setEnabled('satellites', true, { origin: 'user' });
});
const dSat = await diag();
check('satellites enable registers its holder', dSat?.holds.includes('satellites'), dSat);
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.setEnabled('satellites', false, { origin: 'user' });
});
await new Promise((r) => setTimeout(r, 2_000));
const dSatOff = await diag();
check('satellites disable releases its holder', !dSatOff?.holds.includes('satellites'), dSatOff);
// ── 3. camera movement renders (Cesium-native path) ───────────────────
const duringMove = await Promise.all([
countFrames(2_500),
page.evaluate(() => new Promise((resolve) => {
const v = window.__godsEyeView.viewer;
let steps = 0;
const id = setInterval(() => {
v.camera.moveForward(50);
steps += 1;
if (steps >= 20) { clearInterval(id); resolve(true); }
}, 60);
})),
]).then(([frames]) => frames);
check('camera movement while idle produces renders (≥10 / 2.5s)', duringMove.renders >= 10, duringMove);
await new Promise((r) => setTimeout(r, 3_000)); // settle
// ── 4. flights enabled → continuous ───────────────────────────────────
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.setEnabled('flights', true, { origin: 'user' });
});
await new Promise((r) => setTimeout(r, 5_000));
const active = await countFrames(5_000);
const d4 = await diag();
check('governor reports continuous mode with flights on', d4?.mode === 'continuous', d4);
check('flights-on cadence ≈ rAF cadence (≥70%)', active.renders >= active.rafs * 0.7, active);
check('flights-on renders ≥5× idle renders', active.renders >= Math.max(1, idle.renders) * 5, { active: active.renders, idle: idle.renders });
// ── 5. flights disabled → idle again ──────────────────────────────────
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.setEnabled('flights', false, { origin: 'user' });
});
// Deselect flows, fades, and the chrome churn the overlay host re-evaluates
// its occluders against all have to drain first — and this teardown, like the
// one in the setup above, marks the HUD summary dirty, so the same deferred
// typewriter is still ahead of us. Same rule: hold a quiet run longer than one
// refresh period before counting anything.
const teardownSettle = await settleUntilQuiet();
const idleAgain = await countFrames(5_000);
const d5 = await diag();
check('the scene holds a settled quiet run again before the second idle window', teardownSettle.quiet, teardownSettle);
check('governor returns to idle after disable', d5?.mode === 'idle', d5);
check('scene stops rendering again (≤4 fires / 5s)', idleAgain.renders <= 4, idleAgain);
} finally {
await browser.close();
}
const passed = results.filter((r) => r.pass).length;
console.log(`\nqa-perf: ${passed}/${results.length} passed`);
console.log(`RESULT: ${passed} passed, ${results.length - passed} failed, 0 skipped`);
process.exit(passed === results.length ? 0 : 1);