godseed / tools /shot.mjs
AndresCarreon's picture
Town Mode: close town view by default, build_district + place_road, bank/market/house, grow-one-town steering, behold-the-world reveal, tamed needle
b0d758d verified
Raw
History Blame Contribute Delete
6.9 kB
// GODSEED — headless screenshot harness (adapted from glassblower tools/shot.mjs).
// Town Mode suite: the DEFAULT load is now a CLOSE TOWN VIEW (SimCity feel), the
// full planet is the dramatic "behold the world" reveal. Serves web/ itself on a
// free port (8200-8299), drives the mock world through deterministic seeks, and
// saves shots to tools/shots/.
//
// node tools/shot.mjs # full suite
// node tools/shot.mjs --base http://localhost:8234 # reuse a running server
//
// Playwright is resolved from this repo if installed, else from the glassblower
// checkout next door (its node_modules + ~/Library/Caches/ms-playwright browsers).
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 });
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);
});
}
const argBase = process.argv.indexOf("--base");
let BASE = argBase > -1 ? process.argv[argBase + 1] : null;
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)); // let it bind
}
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.__godseed && window.__godseed.ready, { timeout: 30000 });
await page.evaluate(() => window.__godseed.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(() => {
const tc = window.__godseed.townCenter?.();
return { ...window.__godseed.stats(), mode: window.__godseed.rig?.mode,
tc: tc ? `${tc.lat.toFixed(1)},${tc.lon.toFixed(1)}` : "?" };
});
console.log(` 📸 ${name} mode=${s.mode} dist=${s.camDist} tc=${s.tc} sky=${s.sky}`);
}
// ------------------------------------------------------------------ suite
// 1 · DEFAULT load — the close TOWN view (SimCity feel: a town you stand over,
// dense with houses, civic buildings, roads — NOT a tiny planet).
await open("index.html?mock=1&static=1");
await page.evaluate(() => { window.__godseed.tick(6); }); // settle camera + grow-ins
await shot("town-01-default.png");
// 2 · a dense DISTRICT close-up — lean the gaze right into the south block so the
// gabled houses + footpaths + civic crown read large at the close zoom.
await page.evaluate(() => {
window.__godseed.gazeLatLon(11, 36, 30); // the w_000003 quarter
window.__godseed.tick(5);
});
await shot("town-02-district-close.png");
// 3 · roads + civic mix at the square — frame the bank/market/tower heart.
await page.evaluate(() => {
window.__godseed.gazeLatLon(14, 38, 30);
window.__godseed.tick(5);
});
await shot("town-03-square.png");
// 4 · BEHOLD THE WORLD — pull back to the full planet; the town is one warm
// patch ringed by the orbiting inscription on a whole shared world.
await page.evaluate(() => { window.__godseed.beholdWorld(); window.__godseed.tick(9); });
await shot("town-04-behold-world.png");
// 5 · return to the town (the toggle flips back to the close view).
await page.evaluate(() => { window.__godseed.beholdWorld(); window.__godseed.tick(8); });
await shot("town-05-return.png");
// 6 · a GRANTING moment, framed WITHIN the town — run the looping town-growth
// wish (a new quarter east of the square) and freeze mid-grant with the
// thought ticker live.
await open("index.html?mock=1");
await page.evaluate(() => {
window.__godseed.tick(2); // let the loop arm
window.__godseed.runFeedOnce();
window.__godseed.tick(15); // reading → district + market + cafe deltas
});
await shot("town-06-granting.png");
// 7 · the same wish a beat later — road + carts land, the new quarter is joined.
await page.evaluate(() => { window.__godseed.tick(9); });
await shot("town-07-granted.png");
// 8 · the DESCENT within the town — swoop down over the just-granted quarter.
await page.evaluate(() => {
window.__godseed.descend(15, 43, 13); // dive over the new market quarter
window.__godseed.tick(5.4); // mid-glide, just above the surface
});
await shot("town-08-descent.png");
// 9 · genesis-only world — fallback townCenter = the monolith, framed up close.
await open("index.html?mock=1&upto=0&static=1");
await page.evaluate(() => { window.__godseed.tick(6); });
await shot("town-09-genesis.png");
// 10 · Genesis Log — replay the town-build wish (w_000002): the district + bank +
// market + tower + road rising, in the close town view.
await open("book.html?mock=1");
await page.evaluate(() => {
window.__godseed.replay("w_000002");
window.__godseed.tick(20); // through the district + civic deltas
});
await shot("town-10-book-replay.png");
// ------------------------------------------------------------------ wrap
if (errors.length) {
console.log("\n⚠️ page errors:");
for (const e of errors.slice(0, 25)) console.log(" " + e);
process.exitCode = 1;
} else {
console.log("\n✓ no page errors");
}
await browser.close();
cleanup();
console.log("done →", OUT);