/** * What one statewide board would actually cost, measured rather than argued. * * node scripts/merge-feasibility.mjs [--no-still] [--out ] * * **Nothing here is wired into the product.** No board id, no route, no flag * that reaches a deploy. It builds the merged pack in node, counts what comes * out, and takes one photograph — and the photograph is the deliverable, because * the decision it supports is not an engineering one. * * ## The question * * "Why are there still three boards" has an answer that costs nothing — * `cities/reconcile.ts`, four data rules — and an answer that costs a * rebuild: one pack, one lattice, one lot size, from Yreka to the border. * The second one is priced here. * * A lot is `LOT = 0.42` **scene units** (`blocks.ts`), so how much ground a * building stands on is decided entirely by the board's metres-per-unit: 40 m on * the Bay Area, 164 m on Southern California, 806 m on the state. A merged board * therefore has to pick one, and the pick is the whole decision: * * - at Bay Area density the state is **dead** — the anonymous city alone is * tens of millions of triangles against a 2.6M desktop cap; * - at Southern California density it **fits**, and San Francisco is drawn the * way Southern California is drawn now: 164 m lots instead of 40 m. * * The second is a real option and it costs the product its best-looking board. * That trade is the owner's to make against a picture, which is why this ends * with `--out/merge-sf-40m-vs-164m.png` rather than with a recommendation. * * ## What the still actually varies * * `LOT`, not `latScale`. Rescaling San Francisco's pack would move its camera, * its terrain lattice and every chapter pose at once, and the resulting pair * would differ in a dozen ways of which lot size was one. Changing `LOT` to * `0.42 × (390.6 / 94.34) = 1.7392` in a **throwaway tree under /tmp** puts * exactly 164 m of ground under a building and leaves the frame otherwise * identical — which is the comparison that was asked for. `blocks.ts` in this * repo is not touched, and `git status` after a run says so. */ import { execFileSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { performance } from "node:perf_hooks"; import CALIFORNIA from "../src/cities/california.ts"; import SF from "../src/cities/sf.ts"; import SOCAL from "../src/cities/socal.ts"; import { createBlocks } from "../src/engine/blocks.ts"; import { createTerrain } from "../src/engine/terrain.ts"; import { World } from "../src/engine/world.ts"; const args = process.argv.slice(2); const flag = (name, fallback) => { const i = args.indexOf(name); return i === -1 ? fallback : args[i + 1]; }; const OUT = flag("--out", "/tmp/tera-look/merge"); const STILL = !args.includes("--no-still"); mkdirSync(OUT, { recursive: true }); const REPO = new URL("..", import.meta.url).pathname.replace(/\/$/, ""); const BUDGETS = JSON.parse(readFileSync(`${REPO}/scripts/performance-budgets.json`, "utf8")); // ---- The merged pack -------------------------------------------------------- /** Does a lat/lng fall inside a board's rectangle? */ const inside = (city, lat, lng) => lat >= city.bounds.minLat && lat <= city.bounds.maxLat && lng >= city.bounds.minLng && lng <= city.bounds.maxLng; const centroid = (polygon) => { let lat = 0; let lng = 0; for (const [a, b] of polygon) { lat += a; lng += b; } return [lat / polygon.length, lng / polygon.length]; }; /** * One California, at whatever density the caller asks for. * * The two metro rectangles do not intersect and California contains both, so * merging the districts is a union with one subtraction: a state district whose * centre falls inside a metro board is dropped, because the metro pack draws * that ground in far more detail. That is 116 authored districts before the * subtraction — which is the number that kills a per-axis focus lattice, since * two focus regions at opposite corners refine nearly the whole board. */ function merged(metresPerUnit, dropCovered = true) { const latScale = 111_320 / metresPerUnit; const metros = [SF, SOCAL]; const covered = (polygon) => { const [lat, lng] = centroid(polygon); return metros.some((city) => inside(city, lat, lng)); }; const districts = [ ...CALIFORNIA.districts.filter((d) => !dropCovered || !covered(d.polygon)), ...SF.districts, ...SOCAL.districts, ]; return { ...CALIFORNIA, id: "california-merged", name: "California", latScale, // California's own lattice, unchanged: it is the only ground cell that // covers 989,000 km² at all. Southern California's coarse cell over the // state is 1.6M lattice points and 21 s of single-threaded field. landmasses: [...CALIFORNIA.landmasses, ...SF.landmasses, ...SOCAL.landmasses], inlandWater: [...CALIFORNIA.inlandWater, ...SF.inlandWater, ...SOCAL.inlandWater], parks: [...CALIFORNIA.parks, ...SF.parks, ...SOCAL.parks], hills: [...CALIFORNIA.hills, ...SF.hills, ...SOCAL.hills], districts, roads: [...CALIFORNIA.roads, ...SF.roads, ...SOCAL.roads], landmarks: [...CALIFORNIA.landmarks, ...SF.landmarks, ...SOCAL.landmarks], bridges: [...SF.bridges, ...SOCAL.bridges], airports: [...(SF.airports ?? []), ...(SOCAL.airports ?? [])], ports: [...(SF.ports ?? []), ...(SOCAL.ports ?? [])], reconciled: true, }; } const triangles = (mesh) => { const geometry = mesh.geometry; const perInstance = geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3; return perInstance * (mesh.isInstancedMesh ? mesh.count : 1); }; function measure(label, metresPerUnit, dropCovered = true) { const city = merged(metresPerUnit, dropCovered); const world = new World(city); // `createBlocks` samples the land and park masks, so the field is built here, // lazily, on this thread — which is also where the placement time below is // measured from, and is why the field build is outside the timer. world.lattice(); const t0 = performance.now(); const blocks = createBlocks(world); const placementMs = performance.now() - t0; const lat = (city.bounds.maxLat - city.bounds.minLat) * city.latScale; const lng = (city.bounds.maxLng - city.bounds.minLng) * world.lngScale; return { label, metresPerUnit, lotMetres: 0.42 * metresPerUnit, extent: [lng, lat], districts: city.districts.length, instances: blocks.count, triangles: triangles(blocks), placementMs, world, }; } console.log("=== the merged pack, built and counted ===\n"); console.log("A lot is 0.42 scene units, so lot size in metres is the board's scale and nothing else."); console.log("These are the ANONYMOUS CITY only — `createBlocks`, one instanced mesh — which is the"); console.log("layer that scales with density and the layer that decides the answer.\n"); const socalDensity = measure("SoCal density", 111_320 / 285); const bayDensity = measure("Bay Area density", 111_320 / 1180); /** * The same two boards without the subtraction, because the subtraction is a * judgement and the reader should be able to see what it is worth. * * Keeping all 116 authored districts means the state pack's coarse Bay Area and * Los Angeles polygons place a second, 164 m city on top of the metro packs' * own. It is the wrong merge — but it is the honest upper bound, and quoting a * number without saying which of the two it is has already produced one 1.8x * disagreement in this round's own brief. */ const socalDensityAll = measure("SoCal density, all 116 districts", 111_320 / 285, false); const bayDensityAll = measure("Bay Area density, all 116 districts", 111_320 / 1180, false); const cap = BUDGETS.scenes["bay-area"].desktop.maxTriangles; for (const row of [socalDensity, socalDensityAll, bayDensity, bayDensityAll]) { console.log(`${row.label}`); console.log(` metres per unit ${row.metresPerUnit.toFixed(2)} lot ${row.lotMetres.toFixed(0)} m`); console.log(` board extent ${row.extent[0].toFixed(0)} x ${row.extent[1].toFixed(0)} units`); console.log(` districts ${row.districts}`); console.log(` instances ${row.instances.toLocaleString("en-US")}`); console.log(` triangles ${row.triangles.toLocaleString("en-US")}` + ` (${(row.triangles / cap).toFixed(2)}x the bay-area desktop cap of ${cap.toLocaleString("en-US")})`); console.log(` placement ${row.placementMs.toFixed(0)} ms on the main thread (NOISY — see below)\n`); } /** * Read the counts, not the clock. * * Instances and triangles are a function of the pack and the seeded RNG and come * back identical to the digit on every run. The placement millisecond does not: * five runs of the *same* build on this box gave 323, 554, 617, 909 and 1,989 ms * for the same 93,253 lots — a factor of six, on a box whose GPU never leaves * 500 MHz of a possible 2,725 and which usually has two other agents on it. The * brief's 432 ms is inside that spread and so is almost anything else. Quote the * counts; treat the milliseconds as an order of magnitude. */ console.log("Placement time on this box spans ~320-2,000 ms for the same 93,253 lots across runs."); console.log("The instance and triangle counts are deterministic to the digit. Trust those.\n"); // The terrain the merged board would carry, on California's own lattice. Held // back until after the block counts because it is the cheap half and saying so // in the wrong order invites the wrong conclusion. { const t0 = performance.now(); const terrain = createTerrain(socalDensity.world); const ms = performance.now() - t0; let tris = 0; terrain.traverse((node) => { if (node.isMesh) tris += triangles(node); }); console.log(`terrain, on California's 3,473 m cell: ${tris.toLocaleString("en-US")} triangles, ${ms.toFixed(0)} ms`); console.log(`whole merged board at SoCal density: ${(tris + socalDensity.triangles).toLocaleString("en-US")} triangles\n`); } console.log("Today, for comparison — measured by scripts/performance-budget.mjs at bcac6aa:"); console.log(" california 375,351 tri / 374 draws socal 1,429,993 / 218 bay-area 2,264,956 / 209\n"); // ---- The picture ------------------------------------------------------------ if (!STILL) { console.log("merge-feasibility: --no-still, stopping before the photograph"); process.exit(0); } /** * Two builds of the same commit, differing in one constant. * * `git archive` rather than a copy of the working tree: the point of the * picture is what a lot size does, and a working tree with three agents in it * is not a controlled variable. */ const SCRATCH = `${OUT}/scratch`; const COARSE_LOT = (0.42 * (111_320 / 285)) / (111_320 / 1180); const trees = [ { name: "sf-40m", lot: null }, { name: "sf-164m", lot: COARSE_LOT }, ]; for (const tree of trees) { const root = `${SCRATCH}/${tree.name}`; rmSync(root, { recursive: true, force: true }); mkdirSync(root, { recursive: true }); execFileSync("bash", ["-c", `cd ${REPO} && git archive HEAD | tar -x -C ${root}`]); execFileSync("ln", ["-sfn", `${REPO}/node_modules`, `${root}/node_modules`]); if (tree.lot !== null) { const path = `${root}/src/engine/blocks.ts`; const source = readFileSync(path, "utf8"); const next = source.replace( /^const LOT = 0\.42;.*$/m, `const LOT = ${tree.lot.toFixed(6)}; // 164 m at SF's scale — merge-feasibility.mjs, throwaway tree`, ); if (next === source) throw new Error("merge-feasibility: could not find LOT in blocks.ts"); writeFileSync(path, next); } execFileSync("npx", ["vite", "build", "--outDir", `${OUT}/dist-${tree.name}`, "--emptyOutDir"], { cwd: root, stdio: "ignore", }); console.log(`merge-feasibility: built ${tree.name}${tree.lot === null ? "" : ` (LOT ${tree.lot.toFixed(4)})`}`); } const { chromium } = await import("playwright"); const { spawn } = await import("node:child_process"); const { createServer } = await import("node:net"); async function shoot(dist, out) { const port = await new Promise((resolve, reject) => { const probe = createServer(); probe.once("error", reject); probe.listen(0, "127.0.0.1", () => { const { port: p } = probe.address(); probe.close(() => resolve(p)); }); }); const server = spawn(`${REPO}/node_modules/.bin/vite`, ["preview", "--outDir", dist, "--port", String(port), "--strictPort"], { detached: true, stdio: ["ignore", "pipe", "pipe"], cwd: REPO }); await new Promise((resolve) => { const settle = setTimeout(resolve, 30000); const read = (chunk) => { if (/http:\/\/(?:localhost|127\.0\.0\.1):\d+/.test(String(chunk))) { clearTimeout(settle); resolve(); } }; server.stdout.on("data", read); server.stderr.on("data", read); }); const browser = await chromium.launch({ channel: "chrome", args: ["--use-gl=angle", "--use-angle=vulkan", "--enable-unsafe-swiftshader", "--ignore-gpu-blocklist"], }); const context = await browser.newContext({ viewport: { width: 1200, height: 900 }, deviceScaleFactor: 1, timezoneId: "America/Los_Angeles", reducedMotion: "reduce", }); await context.clock.setFixedTime(new Date("2026-08-21T20:00:00Z")); const page = await context.newPage(); await page.goto(`http://localhost:${port}/?city=sf`, { waitUntil: "networkidle", timeout: 60000 }); await page.waitForTimeout(11000); try { await page.getByText(/^Skip$/).first().click({ timeout: 2000 }); await page.waitForTimeout(1200); } catch { /* not shown */ } // Downtown, by identity rather than by position in the list. await page.locator('.chapter[data-view="fidi"]').first().click({ timeout: 5000 }); await page.waitForTimeout(7000); await page.screenshot({ path: out, clip: { x: 340, y: 0, width: 860, height: 900 } }); await browser.close(); try { process.kill(-server.pid, "SIGTERM"); } catch { /* already gone */ } } const halves = []; for (const tree of trees) { const path = `${OUT}/${tree.name}.png`; await shoot(`${OUT}/dist-${tree.name}`, path); halves.push(path); console.log(`merge-feasibility: shot ${path}`); } const still = `${OUT}/merge-sf-40m-vs-164m.png`; execFileSync("montage", [...halves, "-tile", "2x1", "-geometry", "+4+4", "-background", "#101418", still]); console.log(`\nmerge-feasibility: ${still}`); console.log("Left: San Francisco today, 40 m lots. Right: the same view at 164 m lots — what"); console.log("San Francisco looks like on a statewide board that fits. That picture is the decision."); if (!existsSync(still)) process.exit(1);