diff --git a/src/main.ts b/src/main.ts index f23b81d..ac1d92c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -151,7 +151,7 @@ import { type WebcamFaceTextureAdapter, } from "./profile/index.ts"; import type { ActorIdentity } from "./actors/controller.ts"; -import { SRGBColorSpace, Vector3, VideoTexture } from "three"; +import { Group, SRGBColorSpace, Vector3, VideoTexture } from "three"; import { CALIFORNIA_AIR_ROUTE, createAircraftPoseSnapshot, @@ -2112,6 +2112,92 @@ async function buildBoard( * `prefers-reduced-motion` collapses the whole thing to a cut, exactly as * `arrive()` and `beginOfficeArrival` already do. */ +/** + * One full California, as a **preview behind `?nest=1`. Off by default, and it + * is not finished.** + * + * ## What it is for + * + * The owner has asked for one map of the whole state rather than three boards, + * and the question that had to be answered before anyone spent a week on it was + * whether the three packs already *are* the detail tiers of one map and only + * ever needed drawing in one scene. They are. This is the evidence, shipped + * behind a flag for the same reason `reconcile` was: an argument about how a + * board looks is settled by looking at it. + * + * ## Why the placement is a similarity and nothing more + * + * `World.project` is a uniform scale in x/z with no vertical term, so a metro's + * ground enters the state board's space under a plain similarity transform: + * scale x/z by the ratio of metres-per-unit, scale y by the ratio of + * `exaggeration / metresPerUnit` — separately, because the two boards stretch + * height by different amounts — and translate to where the state board projects + * the metro's own centre. Measured: San Francisco is xz 0.04915, y 0.12764, at + * (-146.2, -27.5) on a 551-unit board; Southern California is xz 0.20351, + * y 0.89640, at (55.4, 201.8). Both land where they should, at the size they + * should be. + * + * ## What is broken in it, precisely + * + * **Two terrains occupy the same ground.** The state board's coarse mounds are + * still drawn under the metro's fine surface, so they interpenetrate: a block + * sits on a smooth coarse hill in one place and is buried in it three hundred + * metres away. That is the nested-grid stitching problem and it is the whole of + * the remaining work — the coarse lattice needs a hole cut over each fine + * rectangle and the seam sewn. Nothing about the transform above is implicated. + * + * Also not done, and visible: the metros keep their own lighting-free lift into + * a scene whose rig belongs to the state board, no LOD decides which of the two + * grounds should draw, and the triangles are additive rather than traded. + * + * ## Why the cheap statewide lattice is possible at all + * + * `buildAxis` refines per **axis** — a focus region refines its whole row and + * its whole column, a plus rather than a box — which is why `TODO.md` records a + * statewide lattice at metro fidelity as 23.7M points and dead. Measured against + * the packs' real focus regions, per-*rectangle* refinement is **2.93M points + * and about 26 MB** at a 720 m coarse cell, against 0.75M for all three boards + * as they stand. The verdict in TODO is a fact about the lattice's shape, not + * about one California. + */ +const NEST = new URLSearchParams(location.search).get("nest") === "1"; + +/** The ground of a board, without its sky, its sea or its rig. */ +const NESTED_LAYERS = ["terrain", "shorePlates", "blocks", "landmarks"] as const; + +async function nestMetros(host: MountedBoard): Promise { + if (!NEST || host.id !== "california") return; + const hostWorld = host.handle.world; + for (const id of ["sf", "socal"]) { + const entry = CITIES.find((candidate) => candidate.id === id); + if (entry === undefined) continue; + const built = await buildBoard(entry, new AbortController(), { quiet: true }); + if (built === null) continue; + const metro = built.handle.world; + const group = new Group(); + group.name = `nested:${id}`; + const xz = metro.metresPerUnit / hostWorld.metresPerUnit; + const y = + (hostWorld.city.verticalExaggeration / hostWorld.metresPerUnit) / + (metro.city.verticalExaggeration / metro.metresPerUnit); + const [px, pz] = hostWorld.project(metro.city.center.lat, metro.city.center.lng); + group.scale.set(xz, y, xz); + group.position.set(px, 0, pz); + /* + * `water` and `sea` are deliberately left behind. The state board already + * has an ocean at y=0 and a second one nested inside it is a z-fight rather + * than a sea — which is the same defect the terrain still has, and the one + * place it was cheap to avoid. + */ + for (const name of NESTED_LAYERS) { + const source = built.handle.stageScene.scene.getObjectByName(name); + if (source === undefined) continue; + group.add(source); + } + host.handle.stageScene.scene.add(group); + } +} + async function presentBoard(record: MountedBoard): Promise { const outgoing = visibleBoard; if (outgoing === record) { @@ -2152,6 +2238,8 @@ async function presentBoard(record: MountedBoard): Promise { activateBoard(record); hideSwitchProgress(); + // The one-California preview. A no-op unless `?nest=1`; see `nestMetros`. + void nestMetros(record); /* * Arriving is itself a reason to look ahead. *