#!/usr/bin/env node
/** Repeatable anonymous WebGL performance gate over a built Tera bundle. */

import { chromium } from "playwright";
import { createServer } from "node:http";
import { access, readdir, readFile, writeFile } from "node:fs/promises";
import { extname, join, normalize, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const ROOT = fileURLToPath(new URL("..", import.meta.url));
const DIST = join(ROOT, "dist");
const DEFAULT_BUDGETS = join(ROOT, "scripts", "performance-budgets.json");
const PRIVATE_PATH = /^\/api\/v1\/(?:media|realtime|presence|offices)(?:\/|$)|^\/assets\/(?:remoteMedia|presenceIndicator|scenePeers)-/;
const MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".json": "application/json", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp", ".webmanifest": "application/manifest+json" };
const VIEWPORTS = {
  desktop: { width: 1440, height: 900, deviceScaleFactor: 1 },
  mobile: { width: 390, height: 844, deviceScaleFactor: 2, isMobile: true, hasTouch: true },
};
/*
 * EVERY SCENE NAMES THE BOARD IT MEANS, AND CHECKS IT GOT IT.
 *
 * `?city=` falls back to the first board rather than failing on an unknown id —
 * deliberately, so a deep link to a city this build does not have shows a city
 * instead of a black screen — and that fallback is silent. A `bay-area` cell
 * that quietly measured California would pass its 2.6M-triangle cap by a factor
 * of six and would keep passing through any regression a phone would choke on:
 * a budget that cannot fail. So every scene names a `signature` — one chapter
 * `data-view` that exists on that board and on no other — and does not become
 * ready until it is on screen.
 *
 * A SIGNATURE CHAPTER RATHER THAN THE BOARD TAB, deliberately. The obvious check
 * is the `data-board` of the pressed tab, and it was written that way first; it
 * broke within the hour, because the tab strip is chrome and chrome is exactly
 * what the continuity work is allowed to change. A chapter id is pack data, it
 * is what `scripts/fixtures/chapter-identity.json` pins, and it is what every
 * capture aim resolves to. `hayes-valley` and `dtla` are unique across the three
 * packs; `all` is not, which is why neither metro is identified by its first
 * chapter.
 *
 * `california-drive` used to reach its chapter with
 * `document.querySelectorAll("#chapters .chapter")[1].click()`. That is an
 * unguarded index into pack data, and it is the exact pattern that once shipped
 * a share card of a chase camera on a freeway under the headline "Cities from
 * above" — see the note at `scripts/brand-assets/capture.mjs:70`. It now aims at
 * `data-view="la-sf-us-101"` and asserts the printed label, so a reordered pack
 * stops the run instead of quietly measuring a different scene under the old
 * scene's caps.
 */
const SCENES = {
  california: {
    host: "tera.lumbridgecorp.com",
    // Explicit, and it was implicit until the app's default board moved off the
    // state tier. `signature` would have caught it — a board with no
    // `california-overview` chapter never becomes ready — but a harness that
    // depends on an app default is a harness that reports someone else's change
    // as its own flake.
    // `one=0` on purpose: both California cells' caps were measured against the
    // **plain** state board, which is now what `?one=0` means and no longer what
    // a bare `?city=california` gives. Without it these measure the merged
    // board — which passes, and which `california-one` already covers, so the
    // failure is two cells measuring one board and nobody watching the other.
    query: "?city=california&one=0&handover=0",
    signature: "california-overview",
    ready: () => document.getElementById("boot")?.hidden === true && document.querySelector("#chapters .chapter[data-view='california-overview']") !== null,
  },
  "california-drive": {
    host: "tera.lumbridgecorp.com",
    // `one=0` on purpose: both California cells' caps were measured against the
    // **plain** state board, which is now what `?one=0` means and no longer what
    // a bare `?city=california` gives. Without it these measure the merged
    // board — which passes, and which `california-one` already covers, so the
    // failure is two cells measuring one board and nobody watching the other.
    query: "?city=california&one=0&handover=0",
    signature: "california-overview",
    ready: () => document.getElementById("boot")?.hidden === true && document.querySelector("#chapters .chapter[data-view='california-overview']") !== null && document.querySelector("#chapters .chapter[data-view='la-sf-us-101']") !== null,
    activate: () => {
      const button = document.querySelector("#chapters .chapter[data-view='la-sf-us-101']");
      if (button === null) throw new Error("no chapter with data-view='la-sf-us-101' on this board");
      const spans = [...button.querySelectorAll("span")];
      const label = (spans[spans.length - 1]?.textContent ?? "").trim();
      if (label !== "101") throw new Error(`chapter la-sf-us-101 prints "${label}", not "101" — the pack was re-labelled`);
      button.click();
    },
    active: () => document.querySelector("[data-control-mode='drive']")?.getAttribute("aria-pressed") === "true" && document.getElementById("play-hud")?.hidden === false,
  },
  office: { host: "office.lumbridgecorp.com", query: "?handover=0", ready: () => document.getElementById("boot")?.hidden === true && document.getElementById("enter")?.textContent?.includes("Back to the city") === true },
  /*
   * The two metro boards, added after they became the boards with the most on
   * them and were still the boards nothing measured.
   *
   * For a long time the matrix was california, california-drive and office. That
   * was defensible while the metro boards were terrain and buildings, and stopped
   * being defensible the moment SFO, LAX, the Golden Gate, the Bay Bridge and a
   * surfaced freeway all landed on them — every one of those is city-frame
   * geometry, and every one of them arrived in a frame with no budget watching
   * it. A cap you do not measure is a cap you do not have.
   *
   * Their own numbers rather than California's: `sf` carries the densest built
   * ground in the product and `socal` the widest basin, and holding either to a
   * board tuned for a whole state would be arbitrary in both directions. These
   * are set from the first measured run with headroom deliberately left, in the
   * same spirit as the others.
   */
  "bay-area": {
    host: "tera.lumbridgecorp.com",
    query: "?city=sf&handover=0",
    signature: "hayes-valley",
    ready: () => document.getElementById("boot")?.hidden === true && document.querySelector("#chapters .chapter[data-view='hayes-valley']") !== null,
  },
  /*
   * One California — the merged board, behind `?one=1`.
   *
   * It needs a cell of its own, and until it had one the only way to measure it
   * was to hand-edit the `california` cell's query, run, and edit it back. That
   * was done six times in one session and is exactly the sort of procedure that
   * gets half-done: the numbers quoted for this board came from a harness that
   * was, at the time, lying about which board it measured.
   *
   * **`ready` asserts the merged board specifically, not just California.** The
   * signature chapter cannot do it — `california-overview` is on the coarse
   * state board too, so a cell whose `?one=1` silently stopped working would
   * measure the coarse board and pass, which is the "a budget that cannot fail"
   * failure this harness already has a comment about. `#sea-section` is the
   * discriminator and it is a real one: the note is written only for a board
   * that has ports, `california.ts` authors none, and `cities/unify.ts` folds in
   * the Southland's two. No ports, no section, no readiness.
   *
   * The caps are RECORDED from the first measured run with headroom left, in the
   * same spirit as `bay-area` and `socal` above — not copied from `california`,
   * whose numbers describe a board with no cities on it.
   */
  "california-one": {
    host: "tera.lumbridgecorp.com",
    query: "?city=california&one=1&handover=0",
    signature: "california-overview",
    ready: () =>
      document.getElementById("boot")?.hidden === true &&
      document.querySelector("#chapters .chapter[data-view='california-overview']") !== null &&
      document.getElementById("sea-section") !== null,
  },
  /**
   * The merged board over the Los Angeles basin, from eleven kilometres.
   *
   * **This cell exists because that pose is the board's worst and nothing stood
   * on it.** `california-one` above is judged at `california-overview`, which is
   * the whole state from 1,551 km — the pose where the terrain grid saves
   * nothing and the city is switched off. Every expensive pose this product has
   * is somewhere else, and the consequence was not hypothetical: six poses over
   * the LA basin were measured over the 400,000 cap, one of them at 761,648, and
   * every one of them had been reachable from the board's own chapters for as
   * long as the merged board has existed. A cap nobody measures is not a cap.
   *
   * Eleven kilometres and not seven or two, because the ladder in `blocks.ts`
   * makes the worst pose an interior one rather than the closest one: swept at
   * 12 / 11.5 / 11 / 10.5 / 10 / 7.7 km the board reads 340,328 / 390,598 /
   * 390,578 / 390,578 / 387,428 / 375,172. The peak sits where the rung has just
   * gone fine enough to cost but the basin is still wholly in frame. Closer is
   * cheaper, further is cheaper, and a cell at either would have watched the
   * wrong thing.
   *
   * The pose is planted through `__teraCamera.seek`, which takes true metres, so
   * this reads the same whatever a board's scale is. That is a deliberate
   * departure from every other cell here — they aim by clicking a chapter, which
   * is the control a reader also uses — and the reason is that no chapter points
   * here. The alternative was to author one, which would put a rung on the
   * ladder for a pose whose only purpose is to be expensive.
   */
  "california-one-basin": {
    host: "tera.lumbridgecorp.com",
    query: "?city=california&one=1&handover=0",
    signature: "california-overview",
    ready: () =>
      document.getElementById("boot")?.hidden === true &&
      document.querySelector("#chapters .chapter[data-view='california-overview']") !== null,
    activate: () => {
      const camera = globalThis.__teraCamera;
      if (camera === undefined || typeof camera.seek !== "function") {
        throw new Error("no __teraCamera hook on this build — the basin cell cannot aim");
      }
      if (camera.board !== "california") {
        throw new Error(`the basin cell landed on "${camera.board}", not the merged board`);
      }
      camera.seek({ lat: 34.05, lng: -118.24, standoffM: 11_000 });
    },
    // The lot ladder settling is what this cell measures, so it must not sample
    // until it has: `updateBlocksDetail` runs off the frame loop and a frame
    // taken on arrival is a frame of the pose the camera left.
    active: () => (globalThis.__teraCamera?.lots?.()?.packed ?? 0) > 0,
  },
  socal: {
    host: "tera.lumbridgecorp.com",
    query: "?city=socal&handover=0",
    signature: "dtla",
    ready: () => document.getElementById("boot")?.hidden === true && document.querySelector("#chapters .chapter[data-view='dtla']") !== null,
  },
};

const args = process.argv.slice(2);
function option(name, fallback) {
  const at = args.indexOf(`--${name}`);
  return at < 0 ? fallback : args[at + 1];
}
function positive(name, fallback) {
  const value = Number(option(name, fallback));
  if (!Number.isFinite(value) || value <= 0) throw new Error(`--${name} must be a positive number`);
  return value;
}
/*
 * WHY THE TWO METRO BOARDS CARRY CAPS THREE TIMES CALIFORNIA'S.
 *
 * Measured on the first run that included them: bay-area 2,265,056 triangles and
 * socal 1,417,648, against california's 391,169. That is not a regression and it
 * is not slack — it is what those boards are. California is one state at a
 * standoff where a building is a speck; the Bay Area is the densest built ground
 * in the product with every lot, the freeway network, both bridges and SFO in
 * frame at once, and the Southland is the widest basin with LAX and five more
 * fields on it. Holding either to a cap tuned for a whole state would be
 * arbitrary in both directions.
 *
 * Both render at 60 fps (p95 16.7-16.8 ms) on the box that measured them, which
 * has a Radeon RX 6700 XT. That is the honest limit of what these numbers prove.
 *
 * WHY THE MOBILE CELLS NOW CARRY THEIR OWN GEOMETRY CAPS.
 *
 * They used to carry desktop's, and a cap that cannot move is a cap that catches
 * nothing. Until 2026-08-22 the handheld path reduced the pixel ratio and the
 * shadow-map edge and reduced *nothing about the scene*: the mobile cell drew
 * 2,263,784 triangles against desktop's 2,265,056 — a phone drew every triangle a
 * desktop did — so the mobile row was a frame-time gate wearing a geometry gate's
 * clothes, and it would have stayed green through any geometry regression a phone
 * would choke on. `blocksCastShadow()` in `src/engine/blocks.ts` now takes the
 * anonymous city out of the shadow caster set on a handheld, and these caps are
 * set from what that reduced path actually produces, with the same headroom the
 * desktop rows were given. Every one of them is a tightening — the mobile rows
 * used to carry desktop's numbers, so all ten moved down. Measured 2026-08-22:
 *
 * | cell | triangles before | after | cap | draws | cap |
 * |---|---|---|---|---|---|
 * | bay-area/mobile | 2,263,784 | 1,266,176 | 1,450,000 | 201 | 240 |
 * | socal/mobile | 1,410,800 | 765,596 | 900,000 | 137 | 170 |
 * | california/mobile | 389,843 | 389,843 | 460,000 | 368 | 420 |
 * | california-drive/mobile | 371,599 | 371,599 | 440,000 | 232 | 280 |
 * | office/mobile | 143,780 | 143,780 | 200,000 | 431 | 520 |
 *
 * California does not move because its 806 m lots were never in the caster set
 * (see `NEIGHBOURHOOD_LOT_METRES`), and the office does not move because a desk
 * shadow at 1 unit = 1 m is the whole read of depth.
 *
 * ONE CORRECTION WORTH LEAVING HERE, because the plan for this work carried it:
 * the shadow pass on bay-area is ~70 draw calls, and those 70 are NOT the
 * buildings. The anonymous city is one `InstancedMesh`, so taking it out of the
 * caster set is worth 997,608 triangles and exactly ONE draw call. The other 69
 * are the landmarks, the bridges, the surfaced freeway and the airports — every
 * one of them a separate mesh, and every one of them a silhouette somebody put
 * there on purpose. Dropping them too would get the cell under 150 draws and
 * would be a visual regression bought with a number. Triangles were the lever;
 * draw calls were never going to be.
 *
 * AND THE MOBILE CELL'S PIXEL COUNT IS 0.74 MP, NOT 1.32.
 *
 * This file used to claim the mobile cell rendered "1.32 MP against desktop's
 * 1.30", which was arithmetic on the CSS size times `deviceScaleFactor: 2` and
 * was never what the renderer did. Measured in-page — `renderer.getPixelRatio()`
 * is 1.5 and the canvas backing store is 585x1266 — the mobile cell renders
 * **0.74 MP, 57% of desktop's 1.30**, because `deviceProfile()` caps the handheld
 * pixel ratio at 1.5. Every cell now records its own `measuredPixels`, so nobody
 * has to take a comment's word for it again.
 *
 * ITEM 8 IS CLOSED: THE BAY AREA ALLOWANCE IS GONE AND THE SUSPECT WAS WRONG.
 *
 * `bay-area.desktop.p95FrameIntervalMs` carried 33.4 as a recorded defect, with
 * the desktop shadow map named as the first thing to rule in or out. It is ruled
 * OUT, by measurement rather than by argument. On the same build in one session,
 * timed with `EXT_disjoint_timer_query_webgl2`: a 4096-texel map renders the
 * bay-area frame in 1.31 ms, 2048 in 1.22, 1024 in 1.21 and 256 in 1.26 — the
 * whole spread is inside the run-to-run noise of a 1.2 ms frame, because the
 * shadow pass costs geometry submission and not rasterisation.
 *
 * What did correlate is the card. Sampling `/sys/class/drm/card1/device/pp_dpm_sclk`
 * and `pp_dpm_mclk` every 250 ms through a session that reproduced p95 33.3, the
 * GPU never left its lowest DPM states — 500 MHz core out of 2725, and 96 or
 * 456 MHz memory out of 1000 — for every one of ~250 samples. A board that needs
 * 1.2 ms at boost needs an order of magnitude more at 18% of core clock, which
 * puts it near the 16.7 ms deadline and makes the miss a coin flip on when the
 * DPM state machine steps. That is why the drop rate is stochastic, why it scales
 * with board weight (bay-area > socal > california = never), and why two
 * consecutive runs over a byte-identical `dist` gave socal desktop 33.4 then 16.7
 * with triangle and draw counts identical to the digit.
 *
 * So two things changed here rather than the cap being left raised:
 *
 *  - Every cell is measured up to `--repeats` times and the first passing run is
 *    the one recorded, with all attempts kept in `attempts[]`. A single red p95
 *    on this class of box is noise until it reproduces; a geometry regression
 *    reproduces on the first attempt and every attempt after it.
 *  - The report records the GPU's DPM state next to the renderer string, so the
 *    next person reading a red frame-time cell can see whether the card was
 *    awake. The confirming experiment, if you have root:
 *    `echo high > /sys/class/drm/card1/device/power_dpm_force_performance_level`.
 *
 * Frame times on a shared box are not trustworthy from a single run. Triangle and
 * draw-call counts are: they were identical to the digit across every run of a
 * given build in every experiment above. Believe the geometry columns; re-run
 * before believing a frame-time column.
 */
function sceneBudget(value, label) {
  if (!value || typeof value !== "object") throw new Error(`missing budget for ${label}`);
  for (const key of ["p95FrameIntervalMs", "maxDrawCalls", "maxTriangles"]) {
    if (typeof value[key] !== "number" || !Number.isFinite(value[key]) || value[key] <= 0) {
      throw new Error(`${label}.${key} must be a positive number`);
    }
  }
  return value;
}
const outputPath = resolve(option("output", "/tmp/tera-performance-budget.json"));
const budgetPath = resolve(option("budgets", DEFAULT_BUDGETS));
const warmupMs = positive("warmup-ms", 3_000);
const sampleMs = positive("sample-ms", 8_000);
const readyTimeoutMs = positive("ready-timeout-ms", 180_000);
const softwareOnly = args.includes("--software");
/*
 * How many times a cell may be measured before its result is believed.
 *
 * See the DPM paragraph above. The first attempt that passes is the recorded
 * one; if none passes, the last is recorded and every attempt is kept in
 * `attempts[]` so a reader can see whether a red cell was one bad run or a
 * property of the build. Three because two consecutive runs over a
 * byte-identical `dist` have been observed to disagree by a factor of two on
 * frame time while agreeing to the digit on triangles and draw calls.
 */
const repeats = Math.max(1, Math.round(positive("repeats", 3)));
// Chrome exposes rAF timestamps at 0.1 ms precision, so an ideal 60 Hz cadence
// can quantize to 16.8 ms. This tolerance is measurement resolution, not budget
// headroom; reported values and declared budgets remain unchanged.
const FRAME_COMPARISON_EPSILON_MS = 0.1;

function percentile(values, fraction) {
  if (values.length === 0) return null;
  const ordered = [...values].sort((a, b) => a - b);
  return ordered[Math.max(0, Math.ceil(ordered.length * fraction) - 1)];
}
const rounded = (value) => value === null ? null : Math.round(value * 1_000) / 1_000;

async function serve() {
  const requests = [];
  const server = createServer(async (req, res) => {
    const url = new URL(req.url ?? "/", "http://local.invalid");
    requests.push({ method: req.method ?? "GET", path: url.pathname });
    if (url.pathname === "/api/v1/health") {
      res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
      res.end(JSON.stringify({ auth: { mode: "jwt", entryUrl: "/login.html" }, sources: { weather: "none", flights: "none", satellites: "none", markers: "none" } }));
      return;
    }
    if (url.pathname === "/api/v1/session") {
      res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
      res.end(JSON.stringify({ authenticated: false, subject: null, passwordLogin: false, admin: false }));
      return;
    }
    if (url.pathname.startsWith("/api/v1/")) {
      res.writeHead(404, { "content-type": "application/json", "cache-control": "no-store" });
      res.end(JSON.stringify({ error: "not_found" }));
      return;
    }
    const requested = normalize(decodeURIComponent(url.pathname)).replace(/^(?:\.\.[/\\])+/, "");
    for (const relative of [requested === "/" ? "/index.html" : requested, "/index.html"]) {
      const target = resolve(DIST, `.${relative}`);
      if (!target.startsWith(`${resolve(DIST)}/`)) continue;
      try {
        const body = await readFile(target);
        res.writeHead(200, { "content-type": MIME[extname(target)] ?? "application/octet-stream" });
        res.end(body);
        return;
      } catch { /* SPA fallback */ }
    }
    res.writeHead(404).end("not found");
  });
  await new Promise((ok, fail) => { server.once("error", fail); server.listen(0, "127.0.0.1", ok); });
  const address = server.address();
  if (!address || typeof address === "string") throw new Error("performance server did not expose a TCP port");
  return { server, requests, port: address.port };
}

/*
 * What the GPU's power management was doing while this ran.
 *
 * Recorded next to the renderer string because a red frame-time cell on this
 * class of box is very often a downclocked card rather than a heavy scene: a
 * Radeon sitting at 500 MHz of a possible 2725 needs an order of magnitude more
 * time for a frame it renders in 1.2 ms at boost, which is enough to put a
 * healthy board near the 16.7 ms deadline. amdgpu marks the active state with a
 * trailing `*` in `pp_dpm_sclk` / `pp_dpm_mclk`. Best-effort and Linux-only —
 * every read is allowed to fail, and a machine without these files simply
 * records `null`, because a missing instrument must never fail a build.
 */
async function gpuPowerState() {
  try {
    const cards = (await readdir("/sys/class/drm")).filter((name) => /^card\d+$/.test(name)).sort();
    const states = [];
    for (const card of cards) {
      const base = `/sys/class/drm/${card}/device`;
      const read = async (leaf) => {
        try { return (await readFile(`${base}/${leaf}`, "utf8")).trim(); } catch { return null; }
      };
      const active = (table) => {
        if (!table) return null;
        const line = table.split("\n").find((row) => row.trimEnd().endsWith("*"));
        return line ? line.replace(/^\s*\d+:\s*/, "").replace(/\s*\*$/, "").trim() : null;
      };
      const ceiling = (table) => {
        if (!table) return null;
        const rows = table.split("\n").filter(Boolean);
        const last = rows[rows.length - 1];
        return last ? last.replace(/^\s*\d+:\s*/, "").replace(/\s*\*$/, "").trim() : null;
      };
      const sclk = await read("pp_dpm_sclk");
      const mclk = await read("pp_dpm_mclk");
      if (sclk === null && mclk === null) continue;
      states.push({
        card,
        forcePerformanceLevel: await read("power_dpm_force_performance_level"),
        coreClock: active(sclk), coreClockCeiling: ceiling(sclk),
        memoryClock: active(mclk), memoryClockCeiling: ceiling(mclk),
      });
    }
    return states.length ? states : null;
  } catch { return null; }
}

const COMMON_ARGS = ["--no-sandbox", "--disable-dev-shm-usage"];
async function rendererOf(browser) {
  const page = await browser.newPage();
  try {
    await page.goto("about:blank");
    return await page.evaluate(() => {
      const gl = document.createElement("canvas").getContext("webgl2");
      const extension = gl?.getExtension("WEBGL_debug_renderer_info");
      return extension ? String(gl.getParameter(extension.UNMASKED_RENDERER_WEBGL)) : null;
    });
  } finally { await page.close(); }
}
async function launchBrowser(port) {
  const resolver = `--host-resolver-rules=MAP tera.lumbridgecorp.com 127.0.0.1, MAP office.lumbridgecorp.com 127.0.0.1`;
  const attempts = softwareOnly
    ? [["swiftshader", ["--use-gl=angle", "--use-angle=swiftshader"]]]
    : [["vulkan", ["--use-gl=angle", "--use-angle=vulkan"]], ["swiftshader", ["--use-gl=angle", "--use-angle=swiftshader"]]];
  let last;
  for (const [backend, flags] of attempts) {
    try {
      const browser = await chromium.launch({ channel: "chrome", args: [...COMMON_ARGS, resolver, ...flags] });
      const renderer = await rendererOf(browser);
      const softwareRenderer = renderer !== null && /SwiftShader|llvmpipe/i.test(renderer);
      if (renderer && (backend === "swiftshader" || !softwareRenderer)) return { browser, backend, renderer, port };
      await browser.close();
    } catch (error) { last = error; }
  }
  for (const executablePath of ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"]) {
    try {
      await access(executablePath);
      const browser = await chromium.launch({ executablePath, args: [...COMMON_ARGS, resolver, "--use-gl=angle", "--use-angle=swiftshader"] });
      return { browser, backend: "system-chrome-swiftshader", renderer: await rendererOf(browser), port };
    } catch (error) { last = error; }
  }
  throw new Error(`Chrome launch failed: ${last instanceof Error ? last.message : String(last)}`);
}

function instrumentation() {
  // Custom production hostnames mapped to loopback are not secure contexts over
  // HTTP, so Chrome withholds randomUUID even though getRandomValues remains.
  // Production is HTTPS; this shim only restores that API in the local harness.
  if (typeof crypto.randomUUID !== "function") {
    crypto.randomUUID = () => {
      const bytes = crypto.getRandomValues(new Uint8Array(16));
      bytes[6] = (bytes[6] & 0x0f) | 0x40;
      bytes[8] = (bytes[8] & 0x3f) | 0x80;
      const hex = [...bytes].map((value) => value.toString(16).padStart(2, "0")).join("");
      return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
    };
  }
  const state = { frames: [], longTasks: [], drawCalls: [], triangles: [], currentCalls: 0, currentTriangles: 0, last: 0, longTaskSupported: false };
  Object.defineProperty(globalThis, "__teraPerformanceBudget", { value: state });
  const triangleCount = (mode, count) => mode === 4 ? count / 3 : (mode === 5 || mode === 6) ? Math.max(0, count - 2) : 0;
  const patch = (prototype, method, countAt, instancesAt = null) => {
    if (!prototype || typeof prototype[method] !== "function") return;
    const original = prototype[method];
    if (original.__teraPerformancePatched) return;
    const wrapped = function (...values) {
      state.currentCalls += 1;
      const instances = instancesAt === null ? 1 : Number(values[instancesAt]) || 0;
      state.currentTriangles += triangleCount(Number(values[0]), Number(values[countAt]) || 0) * instances;
      return original.apply(this, values);
    };
    Object.defineProperty(wrapped, "__teraPerformancePatched", { value: true });
    prototype[method] = wrapped;
  };
  for (const prototype of [globalThis.WebGLRenderingContext?.prototype, globalThis.WebGL2RenderingContext?.prototype]) {
    patch(prototype, "drawArrays", 2);
    patch(prototype, "drawElements", 1);
    patch(prototype, "drawArraysInstanced", 2, 3);
    patch(prototype, "drawElementsInstanced", 1, 4);
  }
  try {
    new PerformanceObserver((list) => state.longTasks.push(...list.getEntries().map((entry) => ({ startTime: entry.startTime, duration: entry.duration })))).observe({ type: "longtask", buffered: true });
    state.longTaskSupported = true;
  } catch { /* Long Tasks API is optional. */ }
  requestAnimationFrame(function sample(now) {
    if (state.last > 0) {
      state.frames.push(now - state.last);
      state.drawCalls.push(state.currentCalls);
      state.triangles.push(state.currentTriangles);
    }
    state.last = now;
    state.currentCalls = 0;
    state.currentTriangles = 0;
    requestAnimationFrame(sample);
  });
}

async function measure(browser, port, sceneName, viewportName, budget, requestLog) {
  const viewport = VIEWPORTS[viewportName];
  /*
   * `reducedMotion: "reduce"` because the harness measures a *settled* frame.
   *
   * The opening arrival — `arrive()` in `scene.ts`, and `flyTo` before it —
   * collapses to a cut under the media query, exactly as a viewer who has asked
   * their operating system for less motion gets. Without it the shot's length
   * has to be chosen against `warmup-ms`, since a move still running inside the
   * sample window is a moving frustum and everything downstream of it is
   * measuring a fly-in rather than a board. With it the two are decoupled: the
   * arrival can be as long as it wants to be and this file still samples a
   * stationary camera. Requested by whoever owns the hero landing; if you raise
   * `warmup-ms`, tell them, because the shot can then be lengthened.
   */
  const context = await browser.newContext({ viewport: { width: viewport.width, height: viewport.height }, deviceScaleFactor: viewport.deviceScaleFactor, isMobile: viewport.isMobile, hasTouch: viewport.hasTouch, reducedMotion: "reduce" });
  const page = await context.newPage();
  const consoleErrors = [];
  page.on("pageerror", (error) => consoleErrors.push(String(error)));
  page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text()); });
  const before = requestLog.length;
  await page.addInitScript(instrumentation);
  const scene = SCENES[sceneName];
  const url = `http://${scene.host}:${port}/${scene.query ?? ""}`;
  try {
    await page.goto(url, { waitUntil: "networkidle", timeout: readyTimeoutMs });
    try {
      await page.waitForFunction(scene.ready, null, { timeout: readyTimeoutMs });
    } catch (error) {
      const state = await page.evaluate(() => ({
        title: document.title,
        bootHidden: document.getElementById("boot")?.hidden ?? null,
        bootText: document.getElementById("boot")?.textContent?.trim().slice(0, 240) ?? null,
        chapters: document.querySelectorAll("#chapters .chapter").length,
        // Which chapters actually answered. A readiness timeout on a metro cell
        // is now most likely to mean `?city=` fell back to California, and
        // reading the ids back makes that a one-word diagnosis rather than a
        // twenty-minute one. `activeBoard` is best-effort: the board tab strip
        // is chrome and may not exist.
        views: [...document.querySelectorAll("#chapters .chapter")].map((node) => node.getAttribute("data-view")),
        activeBoard: document.querySelector("[data-board][aria-pressed='true']")?.getAttribute("data-board") ?? null,
        enter: document.getElementById("enter")?.textContent?.trim() ?? null,
      })).catch(() => null);
      throw new Error(`scene readiness timed out: ${JSON.stringify({ state, consoleErrors, requests: requestLog.slice(before) })}`, { cause: error });
    }
    if (scene.activate) {
      await page.evaluate(scene.activate);
      await page.waitForFunction(scene.active, null, { timeout: readyTimeoutMs });
    }
    await page.waitForTimeout(warmupMs);
    await page.evaluate(() => {
      const state = globalThis.__teraPerformanceBudget;
      state.frames.length = state.drawCalls.length = state.triangles.length = state.longTasks.length = 0;
      state.last = performance.now(); state.currentCalls = 0; state.currentTriangles = 0;
    });
    await page.waitForTimeout(sampleMs);
    const raw = await page.evaluate(() => {
      const state = globalThis.__teraPerformanceBudget;
      const canvas = document.querySelector("canvas");
      const gl = canvas?.getContext("webgl2") ?? canvas?.getContext("webgl");
      const extension = gl?.getExtension("WEBGL_debug_renderer_info");
      return {
        frames: [...state.frames], drawCalls: [...state.drawCalls], triangles: [...state.triangles],
        longTasks: [...state.longTasks], longTaskSupported: state.longTaskSupported,
        renderer: extension ? String(gl.getParameter(extension.UNMASKED_RENDERER_WEBGL)) : null,
        // The canvas backing store, which is the only honest pixel count: the
        // renderer caps its own pixel ratio (1.5 on a handheld) and does not
        // care what `deviceScaleFactor` the harness asked for. See the header.
        drawingBuffer: canvas ? { width: canvas.width, height: canvas.height, cssWidth: canvas.clientWidth, cssHeight: canvas.clientHeight } : null,
      };
    });
    const relevantRequests = requestLog.slice(before);
    const privateRequests = relevantRequests.filter((request) => PRIVATE_PATH.test(request.path));
    const metrics = {
      frameSamples: raw.frames.length,
      p50FrameIntervalMs: rounded(percentile(raw.frames, 0.5)),
      p95FrameIntervalMs: rounded(percentile(raw.frames, 0.95)),
      p99FrameIntervalMs: rounded(percentile(raw.frames, 0.99)),
      maxFrameIntervalMs: rounded(raw.frames.length ? Math.max(...raw.frames) : null),
      p95DrawCalls: rounded(percentile(raw.drawCalls, 0.95)),
      maxDrawCalls: raw.drawCalls.length ? Math.max(...raw.drawCalls) : null,
      p95Triangles: rounded(percentile(raw.triangles, 0.95)),
      maxTriangles: raw.triangles.length ? Math.max(...raw.triangles) : null,
      longTasksSupported: raw.longTaskSupported,
      longTaskCount: raw.longTasks.length,
      longTaskTotalMs: raw.longTasks.reduce((sum, task) => sum + task.duration, 0),
    };
    const checks = {
      p95FrameIntervalMs: metrics.p95FrameIntervalMs !== null &&
        Math.round(metrics.p95FrameIntervalMs * 10) <=
          Math.round((budget.p95FrameIntervalMs + FRAME_COMPARISON_EPSILON_MS) * 10),
      maxDrawCalls: metrics.maxDrawCalls !== null && metrics.maxDrawCalls <= budget.maxDrawCalls,
      maxTriangles: metrics.maxTriangles !== null && metrics.maxTriangles <= budget.maxTriangles,
      anonymousPrivateRequests: privateRequests.length === 0,
      consoleErrors: consoleErrors.length === 0,
      enoughFrameSamples: metrics.frameSamples >= Math.max(30, Math.floor(sampleMs / 100)),
    };
    const measuredPixels = raw.drawingBuffer
      ? {
          ...raw.drawingBuffer,
          megapixels: Math.round((raw.drawingBuffer.width * raw.drawingBuffer.height) / 10_000) / 100,
        }
      : null;
    return { scene: sceneName, viewport: viewportName, url, viewportPixels: viewport, measuredPixels, renderer: raw.renderer, budget, metrics, checks, passed: Object.values(checks).every(Boolean), privateRequests, consoleErrors };
  } finally { await context.close(); }
}

let server;
let browser;
try {
  await access(join(DIST, "index.html"));
  const budgets = JSON.parse(await readFile(budgetPath, "utf8"));
  const hosted = await serve(); server = hosted.server;
  const launched = await launchBrowser(hosted.port); browser = launched.browser;
  const results = [];
  const gpuBefore = await gpuPowerState();
  for (const scene of Object.keys(SCENES)) for (const viewport of Object.keys(VIEWPORTS)) {
    const budget = sceneBudget(budgets?.scenes?.[scene]?.[viewport], `${scene}.${viewport}`);
    /*
     * Repeat until stable, and record why.
     *
     * The first attempt that passes is the recorded result. A cell that never
     * passes records its LAST attempt — not its best — because picking the
     * kindest of three runs is how a gate stops meaning anything, and the point
     * of the retry is to distinguish "this box stuttered once" from "this build
     * is slow", not to shop for a green.
     *
     * `attempts[]` keeps every run's headline numbers either way. In practice a
     * geometry regression is identical across all three attempts (triangles and
     * draw calls have never disagreed between runs of one build) and a
     * downclocked card is the only thing that moves, which is exactly the
     * signal this is here to separate.
     */
    const attempts = [];
    let accepted = null;
    for (let attempt = 1; attempt <= repeats; attempt++) {
      process.stderr.write(`performance-budget: ${scene}/${viewport}${attempt > 1 ? ` (attempt ${attempt}/${repeats})` : ""}\n`);
      const run = await measure(browser, hosted.port, scene, viewport, budget, hosted.requests);
      attempts.push({
        attempt,
        passed: run.passed,
        failed: Object.entries(run.checks).filter(([, ok]) => !ok).map(([name]) => name),
        frameSamples: run.metrics.frameSamples,
        p50FrameIntervalMs: run.metrics.p50FrameIntervalMs,
        p95FrameIntervalMs: run.metrics.p95FrameIntervalMs,
        maxTriangles: run.metrics.maxTriangles,
        maxDrawCalls: run.metrics.maxDrawCalls,
      });
      accepted = run;
      if (run.passed) break;
    }
    results.push({ ...accepted, attempts, attemptsRun: attempts.length, repeatsAllowed: repeats });
  }
  const report = {
    schemaVersion: 1, generatedAt: new Date().toISOString(), browserPlugin: "not available; Playwright system Chrome fallback used",
    browser: { backend: launched.backend, renderer: launched.renderer }, warmupMs, sampleMs, repeats,
    /*
     * The card's own power state, before and after the matrix. A frame-time cell
     * measured against a GPU pinned at its lowest DPM step is measuring the
     * power manager; see the header. `power_dpm_force_performance_level: "auto"`
     * with a core clock far below its ceiling is the signature.
     */
    gpu: { before: gpuBefore, after: await gpuPowerState() },
    frameComparisonEpsilonMs: FRAME_COMPARISON_EPSILON_MS,
    budgets: budgetPath, passed: results.every((result) => result.passed), results,
  };
  await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`);
  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
  process.stderr.write(`performance-budget: ${report.passed ? "PASS" : "FAIL"} — ${outputPath}\n`);
  if (!report.passed) process.exitCode = 1;
} catch (error) {
  console.error(`performance-budget: ERROR — ${error instanceof Error ? error.stack : String(error)}`);
  process.exitCode = 2;
} finally {
  await browser?.close().catch(() => undefined);
  if (server) await new Promise((resolveClose) => server.close(resolveClose));
}
