| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { mkdirSync } from "node:fs"; |
| import { createServer } from "node:net"; |
| import { spawn } from "node:child_process"; |
| import { fileURLToPath } from "node:url"; |
| import path from "node:path"; |
|
|
| const HERE = path.dirname(fileURLToPath(import.meta.url)); |
| const WEB = path.join(HERE, "..", "web"); |
| const OUT = path.join(HERE, "shots"); |
| mkdirSync(OUT, { recursive: true }); |
|
|
| const FLAGS = ["--god-day", "--god-night", "--street", "--heatmap", "--construction"]; |
| const argv = process.argv.slice(2); |
| const baseIdx = argv.indexOf("--base"); |
| let BASE = baseIdx > -1 ? argv[baseIdx + 1] : null; |
| const chosen = new Set(argv.filter((a) => FLAGS.includes(a))); |
| const want = (k) => chosen.size === 0 || chosen.has(k); |
|
|
| async function loadPlaywright() { |
| try { |
| return await import("playwright"); |
| } catch { |
| const fallback = path.resolve(HERE, "../../glassblower/node_modules/playwright/index.mjs"); |
| return import(`file://${fallback}`); |
| } |
| } |
|
|
| function freePort(start = 8200, end = 8299) { |
| return new Promise((resolve, reject) => { |
| const tryPort = (p) => { |
| if (p > end) return reject(new Error("no free port in 8200-8299")); |
| const srv = createServer(); |
| srv.once("error", () => tryPort(p + 1)); |
| srv.once("listening", () => srv.close(() => resolve(p))); |
| srv.listen(p, "127.0.0.1"); |
| }; |
| tryPort(start); |
| }); |
| } |
|
|
| let server = null; |
| if (!BASE) { |
| const port = await freePort(); |
| server = spawn("python3", ["-m", "http.server", String(port), "--directory", WEB], { |
| stdio: "ignore", |
| }); |
| BASE = `http://127.0.0.1:${port}`; |
| await new Promise((r) => setTimeout(r, 900)); |
| } |
| const cleanup = () => { if (server) { server.kill(); server = null; } }; |
| process.on("exit", cleanup); |
| process.on("SIGINT", () => { cleanup(); process.exit(130); }); |
|
|
| const { chromium } = await loadPlaywright(); |
| const browser = await chromium.launch({ |
| headless: true, |
| args: [ |
| "--enable-unsafe-swiftshader", |
| "--use-gl=angle", |
| "--use-angle=swiftshader", |
| "--ignore-gpu-blocklist", |
| "--enable-webgl", |
| ], |
| }); |
|
|
| const page = await browser.newPage({ viewport: { width: 1512, height: 945 }, deviceScaleFactor: 2 }); |
| const errors = []; |
| page.on("console", (m) => { if (m.type() === "error") errors.push("console: " + m.text()); }); |
| page.on("pageerror", (e) => errors.push("pageerror: " + e.message)); |
|
|
| async function open(url) { |
| console.log("->", url); |
| await page.goto(`${BASE}/${url}`, { waitUntil: "networkidle", timeout: 45000 }); |
| await page.waitForFunction(() => window.__app && window.__app.ready, { timeout: 30000 }); |
| await page.evaluate(() => window.__app.pause(true)); |
| await page.evaluate(async () => { if (document.fonts?.ready) await document.fonts.ready; }); |
| } |
|
|
| async function shot(name) { |
| await page.screenshot({ path: path.join(OUT, name) }); |
| const s = await page.evaluate(() => window.__app.stats()); |
| console.log(` shot ${name} simTime=${s.simTime} calls=${s.calls ?? "?"} tris=${s.triangles ?? "?"} epoch=${s.epoch}`); |
| } |
|
|
| |
| |
| |
| |
| const needDayPage = want("--god-day") || want("--heatmap") || want("--construction") || want("--street"); |
| if (needDayPage) { |
| await open("index.html?mock=1&t=78"); |
| await page.evaluate(() => window.__app.tick(14)); |
| if (want("--god-day")) await shot("god-day.png"); |
|
|
| if (want("--heatmap")) { |
| await page.evaluate(() => { window.__app.setHeatmap(true); window.__app.tick(3); }); |
| await shot("heatmap.png"); |
| await page.evaluate(() => { window.__app.setHeatmap(false); window.__app.tick(0.5); }); |
| } |
|
|
| if (want("--construction")) { |
| |
| await page.evaluate(() => { |
| window.__app.runScript(1); |
| window.__app.tick(6); |
| window.__app.gazeCell(2, -7, 30); |
| window.__app.tick(9); |
| }); |
| await shot("construction.png"); |
| } |
|
|
| if (want("--street")) { |
| |
| |
| await page.evaluate(() => { |
| const a = window.__app.app; |
| window.__app.pause(true); |
| if (a.rig?.streetView) { |
| a.rig.streetView(); |
| window.__app.tick(0.5); |
| } else if (a.camera) { |
| a.camera.position.set(2, 5, 60); |
| a.camera.lookAt(2, 2, -20); |
| a.renderOnce?.(); |
| } else { |
| console.warn("street shot: no rig.streetView or app.camera exposed"); |
| } |
| }); |
| await shot("street.png"); |
| } |
| } |
|
|
| |
| |
| if (want("--god-night")) { |
| await open("index.html?mock=1&t=206"); |
| await page.evaluate(() => window.__app.tick(8)); |
| await shot("god-night.png"); |
| } |
|
|
| |
| if (errors.length) { |
| console.log("\npage errors:"); |
| for (const e of errors.slice(0, 25)) console.log(" " + e); |
| process.exitCode = 1; |
| } else { |
| console.log("\nno page errors"); |
| } |
|
|
| await browser.close(); |
| cleanup(); |
| console.log("done ->", OUT); |
|
|