mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
A scenario whose body ran past about 19 s idled until 45 s after its start, and twice one never exited and hit the runner's 300 s timeout after it had passed: something of Chrome outlived Browser.close and kept its stdout and stderr open. - scripts/qa/browser.mjs closes Chrome within 5 s, kills its process group and closes its pipes; the harness then finishes and exits with the scenario's code. QA_EXIT_DIAGNOSTICS=1 keeps the old ending and prints the open handles and Chrome's process tree 5 s after finish. - Every strict page starts with its clock at the 18:30 UTC nearest to the real time, so Navara's sun is up over the test cities whenever the acceptance runs; a scenario can ask for its own instant or the real clock (Wildlife does, for its freshness fixtures). - The runner records a scenario that passed but did not exit as hung, kills the whole process group on a timeout, runs a failed scenario once more and reports one that passes then as flaky, and appends each run to --history=FILE; scripts/report-acceptance-flake.mjs reads it.
201 lines
6.5 KiB
JavaScript
201 lines
6.5 KiB
JavaScript
// The app's Bloom on Navara: the Display bloom glows the
|
|
// Navara frame (brighter, more at a higher slider), it sits after the
|
|
// overlays and before the visual styles, Cesium's dead zone draws nothing,
|
|
// turning it off restores the frame exactly, and on Cesium the scene's own
|
|
// bloom stage still runs with the engine left alone. Keyless preview under
|
|
// the strict default-deny network policy.
|
|
//
|
|
// node scripts/qa-navara-bloom.mjs http://127.0.0.1:<PORT>/ [--out=DIR]
|
|
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import {
|
|
createStrict,
|
|
localPreview,
|
|
waitForEngine,
|
|
} from './qa-strict-harness.mjs';
|
|
import {
|
|
capture,
|
|
checker,
|
|
diff,
|
|
freeze,
|
|
pickStyle,
|
|
saveCapture,
|
|
stats,
|
|
visualPageUrl,
|
|
} from './qa/navaraVisual.mjs';
|
|
|
|
const base = localPreview(process.argv[2]);
|
|
const outDir = process.argv
|
|
.find((arg) => arg.startsWith('--out='))
|
|
?.slice('--out='.length);
|
|
const strict = createStrict({ origin: base.origin, layers: [] });
|
|
const report = { checks: [] };
|
|
const check = checker(report, assert);
|
|
const save = (name, pixels) => saveCapture(outDir, name, pixels);
|
|
const luma = (s) => 0.299 * s.r + 0.587 * s.g + 0.114 * s.b;
|
|
|
|
// The Display bloom, as its toggle and slider set it.
|
|
const setBloom = (page, enabled, intensityPct) =>
|
|
page.evaluate(
|
|
(enabled, intensityPct) => {
|
|
const a = window.__godsEyeView;
|
|
a.styleManager.setBloom({ enabled, intensityPct });
|
|
return {
|
|
control: a.styleManager.getControlState().bloom,
|
|
engine: a.engine.render.bloom?.() ?? null,
|
|
pipeline: a.engine.pipeline?.() ?? null,
|
|
};
|
|
},
|
|
enabled,
|
|
intensityPct,
|
|
);
|
|
|
|
await strict.run({}, async (browser) => {
|
|
const page = await browser.newPage();
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
// The same sun on every run: the frame's brightness decides what blooms
|
|
// and what each style's signature reads.
|
|
await page.evaluateOnNewDocument(() => {
|
|
localStorage.setItem('gev:first-run-mission:v1', 'suppressed');
|
|
});
|
|
await page.setViewport({ width: 1280, height: 800 });
|
|
await page.goto(visualPageUrl(base.origin, 'navara'), {
|
|
waitUntil: 'domcontentloaded',
|
|
});
|
|
await waitForEngine(page, 'navara');
|
|
await freeze(page);
|
|
|
|
const initial = await page.evaluate(() => {
|
|
const { engine } = window.__godsEyeView;
|
|
return { bloom: engine.render.bloom(), pipeline: engine.pipeline() };
|
|
});
|
|
check(
|
|
'Navara blooms itself and starts with bloom off',
|
|
initial.bloom.enabled === false && !initial.pipeline?.bloomShown,
|
|
initial,
|
|
);
|
|
const normal = await capture(page);
|
|
save('normal', normal);
|
|
const normalStats = stats(normal);
|
|
|
|
const full = await setBloom(page, true, 200);
|
|
check(
|
|
'the slider at 200 reaches the engine at full strength',
|
|
full.engine.enabled === true &&
|
|
full.engine.strength === 1 &&
|
|
full.pipeline.bloomShown === true,
|
|
full,
|
|
);
|
|
const order = full.pipeline.passes ?? [];
|
|
const at = order.indexOf('gevBloom');
|
|
check(
|
|
'bloom runs after the scene and overlays, just before the final copy',
|
|
at >= 0 &&
|
|
order[at + 1] === 'final' &&
|
|
['toneMapping', 'gevSharpen', 'gevOverlay', 'smaa', 'fxaa'].every(
|
|
(key) => !order.includes(key) || order.indexOf(key) < at,
|
|
),
|
|
order,
|
|
);
|
|
const glowFull = await capture(page);
|
|
save('bloom-200', glowFull);
|
|
const fullChange = diff(normal, glowFull);
|
|
const fullStats = stats(glowFull);
|
|
check(
|
|
'bloom at 200 glows the frame brighter',
|
|
fullChange.mean > 3 && luma(fullStats) > luma(normalStats) + 2,
|
|
{
|
|
...fullChange,
|
|
normalLuma: luma(normalStats),
|
|
bloomLuma: luma(fullStats),
|
|
},
|
|
);
|
|
|
|
await setBloom(page, true, 100);
|
|
const glowHalf = await capture(page);
|
|
save('bloom-100', glowHalf);
|
|
const halfChange = diff(normal, glowHalf);
|
|
check(
|
|
'a lower slider glows less',
|
|
halfChange.mean > 0.5 && halfChange.mean < fullChange.mean,
|
|
{ half: halfChange.mean, full: fullChange.mean },
|
|
);
|
|
|
|
const dead = await setBloom(page, true, 10);
|
|
const deadFrame = await capture(page);
|
|
check(
|
|
"Cesium's dead zone: the toggle on, nothing glows",
|
|
dead.engine.enabled === false &&
|
|
dead.pipeline.bloomShown === false &&
|
|
diff(normal, deadFrame).mean === 0,
|
|
{ engine: dead.engine, diff: diff(normal, deadFrame) },
|
|
);
|
|
|
|
// With a style: bloom first, then the style, then the final copy.
|
|
await setBloom(page, true, 200);
|
|
const styled = await pickStyle(page, 'noir', 'normal');
|
|
const styledOrder = styled.pipeline?.passes ?? [];
|
|
check(
|
|
'bloom runs before the visual styles',
|
|
styledOrder.indexOf('gevBloom') >= 0 &&
|
|
styledOrder.indexOf('gevBloom') + 1 ===
|
|
styledOrder.indexOf('gevStyles') &&
|
|
styledOrder.indexOf('gevStyles') + 1 === styledOrder.indexOf('final'),
|
|
styledOrder,
|
|
);
|
|
await pickStyle(page, 'normal', 'noir');
|
|
|
|
const off = await setBloom(page, false, 200);
|
|
const offFrame = await capture(page);
|
|
check(
|
|
'turning bloom off restores the frame',
|
|
off.engine.enabled === false &&
|
|
off.pipeline.bloomShown === false &&
|
|
diff(normal, offFrame).mean === 0,
|
|
{ engine: off.engine, diff: diff(normal, offFrame) },
|
|
);
|
|
|
|
// Cesium drives its scene bloom stage through the render contract.
|
|
await strict.switchTo(page, 'cesium');
|
|
await freeze(page);
|
|
await setBloom(page, false, 200);
|
|
const cesiumNormal = await capture(page);
|
|
const cesium = await setBloom(page, true, 200);
|
|
const cesiumStage = await page.evaluate(() => {
|
|
const a = window.__godsEyeView;
|
|
const stage = a.viewer?.scene?.postProcessStages?.bloom;
|
|
return {
|
|
setBloom: typeof a.engine.render.setBloom,
|
|
enabled: stage?.enabled ?? null,
|
|
contrast: stage?.uniforms?.contrast ?? null,
|
|
};
|
|
});
|
|
const cesiumGlow = await capture(page);
|
|
save('cesium-bloom-200', cesiumGlow);
|
|
check(
|
|
"Cesium: the engine drives its scene's bloom stage",
|
|
cesium.engine?.enabled === true &&
|
|
cesiumStage.setBloom === 'function' &&
|
|
cesiumStage.enabled !== false &&
|
|
diff(cesiumNormal, cesiumGlow).mean > 1,
|
|
{ stage: cesiumStage, diff: diff(cesiumNormal, cesiumGlow) },
|
|
);
|
|
await setBloom(page, false, 200);
|
|
const cesiumBack = diff(cesiumNormal, await capture(page));
|
|
check(
|
|
'Cesium: bloom off restores its frame',
|
|
cesiumBack.mean < 0.5,
|
|
cesiumBack,
|
|
);
|
|
|
|
check('no page errors', errors.length === 0, errors);
|
|
if (outDir)
|
|
fs.writeFileSync(
|
|
path.join(outDir, 'bloom.json'),
|
|
JSON.stringify(report, null, 2),
|
|
);
|
|
});
|