1
0

feat: one California as one pack, behind ?one=1

The nesting preview is gone and this replaces it. That one drew each metro's
built scene inside the state board's under a similarity transform; the transform
was right and two independently built terrains then occupied the same ground and
interpenetrated. `cities/unify.ts` is the version with no seam to stitch,
because the way to not have two grids is to not have two grids: one `City`
carrying the state's landform and both metros' detail, so the engine builds one
world, one lattice and one terrain mesh.

**The merge is mostly a union and twice it must not be.** Everything that
describes a place in a `City` is absolute lat/lng, so hills, districts,
landmarks, bridges, ports and airports concatenate. Two do not:

  - `World.elevationAt` *blends* every hill covering a point, so leaving the
    state's coarse hills under a metro's fine ones adds their heights and lifts
    the whole Bay. 25 state hills are dropped where a metro replaces them.
  - The state's 17 districts include coarse stand-ins for both metros against
    the metros' 99 real ones, and both drawn is two cities in one place. 9 go.

Filtering is by centre-in-bounds, which is honest and not exact, and the file
says so rather than hiding it.

**The lattice was the whole question and the answer was measured, twice.**
`TODO.md` records a statewide lattice at metro fidelity as 23.7M points and
dead. That is true at San Francisco's own 45 m cell, because `buildAxis` refines
per *axis* — a plus, not a box — so two metros at opposite corners refine most
of the state. The blowup is a function of the fine cell and is gentle until 45 m,
so the first pass took 150 m / x8, or 1.23M points.

That rendered **1,114,226 triangles at the whole-board pose against a 440,000
cap**, and the useful part is where it did *not* come from: with every metro
building hidden, triangles fell by 43,000 and draw calls by 111. The buildings
were never the cost. Terrain was. So the fine cell is what a statewide board can
afford without terrain LOD, and that is **300 m / x16** — 0.18M points, 11.6x
finer than the state pack's 3,473 m cell and 6.7x coarser than San Francisco's
own board. Measured at that size: california 392,862 triangles of 440,000 and
california-drive 405,124 of 430,000, both green on desktop.

**And the detail LOD is the cheapest kind there is.** `createBlocks` now orders
every `detail` district's lots *last*, so hiding 99 districts' buildings is one
assignment to `InstancedMesh.count` — no second mesh, no second draw call, no
allocation, and nothing for `nightlights.ts` to learn, since it holds the same
mesh either way. Landmarks are one mesh each so theirs is a `visible` flag.

The reveal is two tests and needed both. Stand-off alone passes for the drive
chapter, whose camera sits a few tens of metres off the ground half way up the
state while being two hundred kilometres from the nearest building this governs
— measured, it failed `california-drive` on triangles for lots nobody could see.
So detail draws only when the camera is inside `DETAIL_STANDOFF_M` *and* its
**target** is within `DETAIL_REACH_M` of a detail district. The target rather
than the camera, because the target is what is being looked at.

`src/test/unify.test.ts` pins the two non-union merges, the detail marking and
the lattice size. Writing it turned up something worth keeping: **six hills are
named in both the state pack and a metro pack** — Marin Headlands, San Bruno
Mountain, Mount Diablo, Bolinas Ridge, Mount Gleason, Mount Wilson — so the
first version failed on San Francisco's own Marin Headlands, identified as a
state hill by its name. `ladder.ts` hit this with "Whole Board" on two boards.
A name is not an identity; the test keys on the object.

Known and not fixed: mobile draw calls are 428 of 415 and 315 of 280 on the
merged board, from the port, vessel and bridge layers that the state board never
carried before and that no LOD hides yet. That is why this is still a flag.
Default is untouched and the full budget passes with it off.

1,700 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-24 02:19:14 -07:00
parent 122497088c
commit 15d7accdbb
6 changed files with 531 additions and 92 deletions
+236
View File
@@ -0,0 +1,236 @@
/**
* One California, from three packs — as **one pack**, not three boards.
*
* ## Why this exists, and why it is not `reconcile.ts`
*
* `reconcile.ts` makes the three packs *agree*: one projection, one relief-in-
* frame, one road width. It leaves three boards that draw the same California
* and hands the camera between them. That was the answer to "the boards feel
* like three products". It is not the answer to "I want one full California",
* which is this file: a single `City` carrying the state's landform and both
* metros' detail, so the engine builds **one world, one lattice and one terrain
* mesh** and there is no seam anywhere to stitch.
*
* ## Why one pack rather than three scenes nested in each other
*
* The first attempt nested each metro's built scene inside the state board's
* under a similarity transform. The transform was right — measured, both metros
* landed where they belong at the size they should be — but two independently
* built terrains then occupied the same ground and interpenetrated: a block sat
* neatly on a coarse hill in one place and was half-buried in it three hundred
* metres away. That is nested-grid stitching, and the way to not have the
* problem is to not have two grids. One pack has one.
*
* ## The lattice is affordable, and the number that says so was measured
*
* `TODO.md` records a statewide lattice at metro fidelity as 23.7M points and
* dead. That is true at San Francisco's own 45 m cell, because `buildAxis`
* refines per **axis** — a focus region refines its whole row *and* its whole
* column, a plus rather than a box — so two metros at opposite corners refine
* most of the state. The blowup is a function of the fine cell, and it is
* gentle until 45 m:
*
* | fine cell | coarse | lattice | points | memory |
* | --- | --- | --- | --- | --- |
* | 45 m | x16 | 6002 x 7600 | 45.6M | 411 MB |
* | 100 m | x8 | 1676 x 2002 | 3.36M | 30 MB |
* | **150 m** | **x8** | **1120 x 1349** | **1.51M** | **14 MB** |
* | 200 m | x8 | 850 x 1013 | 0.86M | 8 MB |
*
* All three of today's boards together are 0.75M points. So 150 m costs about
* twice the whole product's current terrain and buys ground **23 times finer
* than the state board's 3,473 m cell** everywhere a city stands. No quadtree,
* no per-rectangle refinement, no change to `buildAxis` at all.
*
* ## What this file must do beyond concatenating arrays
*
* Everything in a `City` that describes a place is absolute — hills, districts,
* landmarks, bridges, ports and airports are all lat/lng — so most of the merge
* is a union. Two things are not:
*
* - **Hills sum.** `World.elevationAt` blends every hill that covers a point
* (tallest, plus 35% of the rest), so leaving the state's coarse hills under
* a metro's fine ones *adds* their heights and lifts the whole Bay. The
* state's hills are dropped where a metro replaces them.
* - **Districts overlap.** The state's seventeen districts include coarse
* stand-ins for Los Angeles and San Francisco; the metros carry 99 real
* ones. Both drawn is two cities in one place, so the coarse stand-ins go.
*
* Both filters are by centre-in-bounds, which is honest but not exact: a state
* hill centred outside a metro with a radius reaching into it still contributes.
* At the state's hill radii that is a gentle lift at the edge of a metro rather
* than a doubled mountain, and it is recorded here rather than hidden.
*/
import type { City, District, FocusRegion, Hill, LatLng } from "../engine/types.ts";
import { reconciledCity } from "./reconcile.ts";
import CALIFORNIA from "./california.ts";
import SAN_FRANCISCO from "./sf.ts";
import SOCAL from "./socal.ts";
/**
* The fine cell, in metres, and the factor the rest of the state is drawn at.
*
* **300 m, and it was measured down to that from 150.** The first pass used
* 150 m / x8, which is 1.23M lattice points, and the merged board then rendered
* **1,114,226 triangles at the whole-board pose against a 440,000 cap** — with
* every metro building already hidden by the detail LOD. That number is almost
* entirely *terrain*: hiding 99 districts' worth of lots moved it by 43,000
* triangles and moved draw calls by 111, which is how it was established that
* the buildings were never the problem.
*
* So the fine cell is what a statewide board can afford, and the honest ceiling
* on this engine — with no terrain level of detail, which is the piece that does
* not exist yet — is around 300 m / x16, or 0.18M points. That is **11.6x finer
* than the state pack's 3,473 m cell** and 6.7x coarser than San Francisco's own
* 45 m board.
*
* Closing that last gap is terrain LOD, and `TODO.md` already names its three
* blockers: placement drift (everything is placed once against `groundAt`), the
* shadow caster (15x triangles if selected independently), and determinism (the
* budget harness assumes geometry is not a function of camera path). None of
* them is in this file's way, and all of them are in that one's.
*/
export const UNIFIED_FINE_METRES = 300;
export const UNIFIED_COARSE_FACTOR = 16;
/** Is this point inside the rectangle? */
function within(lat: number, lng: number, r: FocusRegion): boolean {
return lat >= r.minLat && lat <= r.maxLat && lng >= r.minLng && lng <= r.maxLng;
}
/**
* The average of a polygon's vertices — enough to say which metro owns it.
*
* `LatLng` is a `[number, number]` tuple in this codebase and not an object, so
* the destructuring below is the whole of the type story.
*/
function centroid(polygon: readonly LatLng[]): { lat: number; lng: number } {
let lat = 0;
let lng = 0;
for (const [pLat, pLng] of polygon) {
lat += pLat;
lng += pLng;
}
const n = Math.max(1, polygon.length);
return { lat: lat / n, lng: lng / n };
}
export interface UnifyReport {
hills: { state: number; dropped: number; metro: number; total: number };
districts: { state: number; dropped: number; metro: number; total: number };
focusRegions: number;
cellLat: number;
coarseFactor: number;
}
let cached: { city: City; report: UnifyReport } | null = null;
/**
* The one board. Memoised, because `World` builds one per mount and the merge
* walks every district polygon in the product.
*/
export function unifiedCalifornia(): { city: City; report: UnifyReport } {
if (cached !== null) return cached;
const state = reconciledCity(CALIFORNIA);
const metros = [reconciledCity(SAN_FRANCISCO), reconciledCity(SOCAL)];
/*
* A metro's *bounds* rather than its focus regions decide what the state stops
* drawing, and the two are very different rectangles: San Francisco's focus
* region is 9.2% of its own board and its bounds are all of it. The state's
* coarse stand-in for a city covers the whole metro, so the whole metro is
* what has to be cleared — clearing only the focus region would leave a coarse
* Oakland beside a fine San Francisco.
*/
const claimed: FocusRegion[] = metros.map((m) => ({ ...m.bounds }));
const claims = (lat: number, lng: number): boolean =>
claimed.some((r) => within(lat, lng, r));
const stateHills = state.hills.filter((h: Hill) => !claims(h.lat, h.lng));
const metroHills = metros.flatMap((m) => m.hills);
const stateDistricts = state.districts.filter((d: District) => {
const c = centroid(d.polygon);
return !claims(c.lat, c.lng);
});
/*
* Marked `detail`, which is the flag `createBlocks` orders on and `scene.ts`
* reveals by camera stand-off. Without it the merged board measured 1,157,802
* triangles at the whole-board pose against a 440,000 cap, for buildings that
* are sub-pixel at 1,919 m to the unit. The lots exist either way; the flag
* decides when they are drawn.
*/
const metroDistricts: District[] = metros.flatMap((m) =>
m.districts.map((d) => ({ ...d, detail: true as const })),
);
const fine = UNIFIED_FINE_METRES / 111_320;
const city: City = {
...state,
/*
* The state's own id, on purpose. Every consumer that keys off a board — the
* fire gate, the ladder's region table, the budget harness's `data-board`
* assertion, `?city=` — already knows "california", and this *is* California.
* A new id would mean teaching all of them about a fourth board, which is
* the opposite of the point.
*/
id: state.id,
name: state.name,
cellLat: fine,
// The same lat:lng cell ratio the state pack authored, so cells stay square
// on the ground at this latitude rather than becoming letterboxes.
cellLng: fine * (CALIFORNIA.cellLng / CALIFORNIA.cellLat),
coarseFactor: UNIFIED_COARSE_FACTOR,
focusRegions: metros.flatMap((m) => m.focusRegions ?? []),
hills: [...stateHills, ...metroHills],
districts: [...stateDistricts, ...metroDistricts],
landmarks: [
...state.landmarks,
...metros.flatMap((m) => m.landmarks.map((l) => ({ ...l, detail: true as const }))),
],
bridges: [...state.bridges, ...metros.flatMap((m) => m.bridges)],
parks: [...state.parks, ...metros.flatMap((m) => m.parks)],
inlandWater: [...state.inlandWater, ...metros.flatMap((m) => m.inlandWater)],
airports: [...(state.airports ?? []), ...metros.flatMap((m) => m.airports ?? [])],
ports: [...(state.ports ?? []), ...metros.flatMap((m) => m.ports ?? [])],
/*
* Chapters are the state's alone. The metros' twenty-four rungs are poses
* expressed in *their* boards' framing, and `ladder.ts` already presents all
* of them as one list; re-pointing them at this board is its own piece of
* work and a wrong pose is a camera in the ground.
*/
chapters: state.chapters,
/*
* Landmasses are NOT unioned. `isLand` is a point-in-any-polygon test, so a
* metro's finer coastline can only ever add land the state's outline already
* claimed — while costing a polygon test per sample over a 1.5M-point
* lattice. The state's coastline is the state's coastline.
*/
landmasses: state.landmasses,
reconciled: true,
};
const report: UnifyReport = {
hills: {
state: state.hills.length,
dropped: state.hills.length - stateHills.length,
metro: metroHills.length,
total: city.hills.length,
},
districts: {
state: state.districts.length,
dropped: state.districts.length - stateDistricts.length,
metro: metroDistricts.length,
total: city.districts.length,
},
focusRegions: city.focusRegions?.length ?? 0,
cellLat: fine,
coarseFactor: UNIFIED_COARSE_FACTOR,
};
cached = { city, report };
return cached;
}
+28 -1
View File
@@ -204,7 +204,24 @@ export function createBlocks(
const lotMetres = LOT * world.metresPerUnit;
const lotIsABlock = lotMetres <= NEIGHBOURHOOD_LOT_METRES;
for (const district of world.city.districts) {
/*
* Base districts first, detail districts last, and that ordering is the whole
* of this layer's level of detail.
*
* `InstancedMesh.count` draws the first N instances, so a merged board can
* drop every metro building by lowering one number — no second mesh, no
* second draw call, no allocation, and nothing for `nightlights.ts` to learn,
* since it reads this mesh exactly as it is returned. `userData.baseCount`
* below is where the boundary is published; `scene.ts` is what moves it.
*
* A pack with no `detail` districts sorts to itself and pays nothing.
*/
const ordered = [...world.city.districts].sort(
(a, b) => Number(a.detail ?? false) - Number(b.detail ?? false),
);
let baseBoxes = -1;
for (const district of ordered) {
if (baseBoxes < 0 && district.detail === true) baseBoxes = boxes.length;
const rand = seededRandom(seedBase);
seedBase += 7919;
@@ -380,6 +397,13 @@ export function createBlocks(
mesh.instanceMatrix.needsUpdate = true;
if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
/*
* Where the base set ends, for `scene.ts`'s detail LOD. `boxes.length` when
* the pack declares no detail districts, which makes "show everything" the
* behaviour of every board that is not the merged one.
*/
mesh.userData.baseCount = baseBoxes < 0 ? boxes.length : baseBoxes;
return mesh;
}
@@ -396,6 +420,8 @@ export function createLandmarks(
group.name = "landmarks";
for (const lm of world.city.landmarks) {
// Tagged rather than sorted: landmarks are one mesh each, so the lever here
// is visibility and not a count. `scene.ts` reads `userData.detail`.
const [x, z] = world.project(lm.lat, lm.lng);
// A richer stable glyph at this address supersedes the coarse landmark
// primitive. Drawing both would hide the glyph inside the old mesh and
@@ -440,6 +466,7 @@ export function createLandmarks(
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.userData.landmark = lm;
if (lm.detail === true) mesh.userData.detail = true;
group.add(mesh);
}
+106 -1
View File
@@ -866,7 +866,94 @@ export async function createScene(
}
const blocks = createBlocks(world, buildingReservations);
scene.add(blocks);
scene.add(createLandmarks(world, buildingReservations));
const landmarkGroup = createLandmarks(world, buildingReservations);
scene.add(landmarkGroup);
/**
* Metro detail on a merged board, revealed by how far the camera is standing
* off. **A no-op on every board that is not the merged one.**
*
* `cities/unify.ts` folds two metros into the state board, which takes it from
* 17 districts to 107 and from 58 landmarks to well past that. Drawn
* unconditionally that measured **1,157,802 triangles against a 440,000 cap**
* and 542 draw calls against 480 — for buildings sub-pixel at 1,919 m to the
* unit, which is to say for nothing. This is the level of detail that makes
* one California affordable, and it is deliberately the cheapest kind there
* is: `createBlocks` has already ordered every detail lot *last*, so hiding
* them is one assignment to `InstancedMesh.count` — no second mesh, no second
* draw call, no allocation, and nothing for `nightlights.ts` to know about,
* since it holds the same mesh either way. Landmarks are one mesh each, so
* theirs is a `visible` flag rather than a count.
*
* `DETAIL_STANDOFF_M` is 120 km because that is just outside the ladder's own
* handover ceilings — 71.2 km for the Bay and 108.8 for the Southland — so
* the city under the camera has already appeared by the time the old build
* would have swapped boards. Above it the merged board draws exactly what the
* state pack always drew.
*/
const baseCount = (blocks.userData.baseCount as number | undefined) ?? blocks.count;
const detailLots = blocks.count - baseCount;
const detailLandmarks = landmarkGroup.children.filter(
(child) => child.userData.detail === true,
);
const hasDetail = detailLots > 0 || detailLandmarks.length > 0;
/*
* Where the detail actually is, in scene units, so "close enough to draw the
* cities" is a question about the cities and not only about altitude.
*
* Stand-off alone is not enough and the drive chapter is the proof: the
* corridor camera sits a few tens of metres off the ground half way up the
* state, which passes any altitude test while being two hundred kilometres
* from the nearest building this flag governs. Measured that way the merged
* board failed `california-drive` on triangles for lots nobody could see.
*
* One centre per detail district — 99 of them — and the test is the nearest.
* That is 99 squared distances a frame against a saving of nearly eight
* hundred thousand triangles, and it is computed from `controls.target`
* rather than the camera because the target is *what is being looked at*: a
* camera high over the Bay is looking at the Bay however far away it is.
*/
const detailCentres: { x: number; z: number }[] = [];
if (hasDetail) {
for (const district of world.city.districts) {
if (district.detail !== true || district.polygon.length === 0) continue;
let lat = 0;
let lng = 0;
for (const [pLat, pLng] of district.polygon) {
lat += pLat;
lng += pLng;
}
const [x, z] = world.project(lat / district.polygon.length, lng / district.polygon.length);
detailCentres.push({ x, z });
}
}
const detailReachUnits = DETAIL_REACH_M / world.metresPerUnit;
let detailShown = true;
function applyDetailLod(): void {
if (!hasDetail) return;
const standoff = kit.camera.position.distanceTo(kit.controls.target) * world.metresPerUnit;
let want = standoff < DETAIL_STANDOFF_M;
if (want) {
const target = kit.controls.target;
const reach = detailReachUnits * detailReachUnits;
want = detailCentres.some((c) => {
const dx = target.x - c.x;
const dz = target.z - c.z;
return dx * dx + dz * dz < reach;
});
}
if (want === detailShown) return;
detailShown = want;
blocks.count = want ? baseCount + detailLots : baseCount;
for (const child of detailLandmarks) child.visible = want;
}
// Applied once up front so the opening frame is already correct rather than
// correct one tick later, which is a frame the capture harness can catch.
if (hasDetail) {
detailShown = true;
applyDetailLod();
}
scene.add(createBridges(world));
// Airfields. Laid flush on the terrain rather than draped over it like a
// road, which is why the packs no longer carry runways as `Road` records —
@@ -1241,6 +1328,7 @@ export async function createScene(
* camera starts from a standstill: a curve that began at full speed would
* read as a cut followed by a glide.
*/
applyDetailLod();
if (arrival !== null) {
arrival.elapsed += dt;
const t = Math.min(1, arrival.elapsed / ARRIVAL_SECONDS);
@@ -1698,6 +1786,23 @@ const HERO_ELEVATION_DEG = 29;
const HERO_DISTANCE = 0.9;
/** How much further out the camera stands before the move, as a multiple. */
/**
* The stand-off, in true metres, below which a merged board draws its metro
* detail. Just outside the ladder's handover ceilings; see `applyDetailLod`.
*/
export const DETAIL_STANDOFF_M = 120_000;
/**
* How near a detail district the camera's *target* must be before that detail
* is drawn, in true metres.
*
* 150 km covers a metro and its approaches from any pose that can resolve a
* building, and excludes the middle of the state — which is what the drive
* chapter needed. Paired with `DETAIL_STANDOFF_M`: one asks "close enough to
* see a building", the other "looking at somewhere that has any".
*/
export const DETAIL_REACH_M = 150_000;
export const ARRIVAL_STANDOFF = 1.5;
/** How much higher, as a multiple. Larger than the stand-off: the move descends. */
const ARRIVAL_LIFT = 2.2;
+16
View File
@@ -51,6 +51,20 @@ export interface District {
palette: "downtown" | "residential" | "industrial";
/** Fraction of lots that get built on at all. Defaults to 0.88. */
coverage?: number;
/**
* Metro detail on a merged board: drawn only when the camera is close enough
* to resolve it.
*
* Absent on every hand-authored pack and set only by `cities/unify.ts`, which
* folds two metros into the state board. One California carries 107 districts
* against the state pack's 17, and drawn unconditionally that measured
* 1,157,802 triangles at the whole-board pose against a 440,000 cap — for
* buildings that are comfortably sub-pixel at 1,919 m to the unit. The flag is
* what lets `createBlocks` order those lots last, so a single `count` on one
* `InstancedMesh` is the whole of the level of detail: no second mesh, no
* second draw call, and nothing to dispose.
*/
detail?: true;
}
/** A building placed by hand because the eye goes looking for it. */
@@ -63,6 +77,8 @@ export interface Landmark {
/** Half-width in degrees of longitude. */
footprint: number;
shape: "box" | "pyramid" | "tower" | "cylinder";
/** Metro detail on a merged board; see `District.detail`. */
detail?: true;
color?: number;
label?: boolean;
}
+16 -90
View File
@@ -73,6 +73,7 @@ import { createStage, deviceProfile } from "./engine/stage.ts";
import { daylightPhase } from "./engine/solar.ts";
import type { Aircraft, City, Marker, MarkerPalette, Port, View } from "./engine/types.ts";
import CALIFORNIA from "./cities/california.ts";
import { unifiedCalifornia } from "./cities/unify.ts";
import { reconciledCity } from "./cities/reconcile.ts";
import SAN_FRANCISCO from "./cities/sf.ts";
import SOCAL from "./cities/socal.ts";
@@ -151,7 +152,7 @@ import {
type WebcamFaceTextureAdapter,
} from "./profile/index.ts";
import type { ActorIdentity } from "./actors/controller.ts";
import { Group, SRGBColorSpace, Vector3, VideoTexture } from "three";
import { SRGBColorSpace, Vector3, VideoTexture } from "three";
import {
CALIFORNIA_AIR_ROUTE,
createAircraftPoseSnapshot,
@@ -224,8 +225,21 @@ import type {
} from "./realtime/index.ts";
import type { PresenceIndicator } from "./realtime/presenceIndicator.ts";
/**
* One full California, as **one pack**, behind `?one=1`.
*
* The first attempt at this nested each metro's built scene inside the state
* board's under a similarity transform. The transform was right and the result
* was not: two independently built terrains occupied the same ground and
* interpenetrated. `cities/unify.ts` is the answer that has no seam to stitch
* one `City` carrying the state's landform and both metros' detail, so the
* engine builds one world, one lattice and one terrain mesh. See that file for
* the measured lattice cost and for the two merges that are not a union.
*/
const ONE_CALIFORNIA = new URLSearchParams(location.search).get("one") === "1";
const CITIES: { id: string; label: string; city: City }[] = [
{ id: "california", label: "California", city: CALIFORNIA },
{ id: "california", label: "California", city: ONE_CALIFORNIA ? unifiedCalifornia().city : CALIFORNIA },
{ id: "sf", label: "Bay Area", city: SAN_FRANCISCO },
{ id: "socal", label: "SoCal", city: SOCAL },
];
@@ -2112,92 +2126,6 @@ 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<void> {
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<void> {
const outgoing = visibleBoard;
if (outgoing === record) {
@@ -2238,8 +2166,6 @@ async function presentBoard(record: MountedBoard): Promise<void> {
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.
*
+129
View File
@@ -0,0 +1,129 @@
/**
* One California, as one pack.
*
* These assert the three things about `unifiedCalifornia` that a picture cannot
* check and that a careless edit would silently undo: that the state stops
* drawing what a metro now draws, that every metro lot is marked as detail so
* the level of detail has something to act on, and that the lattice this all
* rides on is the size the file claims it is. The last one is the whole reason
* the merge is affordable, and it is one multiplication away from not being.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
UNIFIED_COARSE_FACTOR,
UNIFIED_FINE_METRES,
unifiedCalifornia,
} from "../cities/unify.ts";
import CALIFORNIA from "../cities/california.ts";
import SAN_FRANCISCO from "../cities/sf.ts";
import SOCAL from "../cities/socal.ts";
const { city, report } = unifiedCalifornia();
/** `buildAxis`, restated — the lattice cost is the claim being checked. */
function axisLength(min: number, max: number, fine: number, coarse: number, bands: number[][]): number {
let n = 1;
let x = min;
const inFine = (v: number) => bands.some(([a, b]) => v >= (a as number) - coarse && v <= (b as number) + coarse);
while (x < max) {
x += inFine(x) ? fine : coarse;
n += 1;
}
return n;
}
describe("one California", () => {
it("is still California, by id, because everything downstream keys on that", () => {
// The fire gate, the ladder's region table, `?city=`, and the budget
// harness's `data-board` assertion all name "california" already. A fourth
// board id would mean teaching every one of them about a board that is the
// same place.
assert.equal(city.id, CALIFORNIA.id);
assert.equal(city.bounds.minLat, CALIFORNIA.bounds.minLat);
assert.equal(city.bounds.maxLat, CALIFORNIA.bounds.maxLat);
});
it("stops drawing the state's coarse stand-ins where a metro now stands", () => {
/*
* Hills *sum*: `World.elevationAt` blends every hill covering a point, so a
* coarse state hill left under a metro's fine ones lifts the whole Bay. And
* the state's districts include coarse stand-ins for both metros, which
* drawn together with the real ones is two cities in one place.
*/
assert.ok(report.hills.dropped > 0, "no state hill was cleared for a metro");
assert.ok(report.districts.dropped > 0, "no state district was cleared for a metro");
const metroBounds = [SAN_FRANCISCO.bounds, SOCAL.bounds];
const inside = (lat: number, lng: number) =>
metroBounds.some((b) => lat >= b.minLat && lat <= b.maxLat && lng >= b.minLng && lng <= b.maxLng);
/*
* By **identity**, not by name, and that is a finding rather than a style
* choice: six hills are named in both the state pack and a metro pack —
* Marin Headlands, San Bruno Mountain, Mount Diablo, Bolinas Ridge, Mount
* Gleason, Mount Wilson — because two packs independently named the same
* real hill. Keyed on the string, San Francisco's own Marin Headlands reads
* as a state hill that survived inside a metro and fails this test for a
* bug that is not there. `ladder.ts` hit the same thing with "Whole Board"
* on two boards; the rule both times is that a name is not an identity.
*
* The merge spreads the arrays without copying their elements, so the state
* pack's own hill objects are the ones to look for.
*/
const stateHills = new Set<unknown>(CALIFORNIA.hills);
let checked = 0;
for (const hill of city.hills) {
if (!stateHills.has(hill)) continue;
checked += 1;
assert.ok(!inside(hill.lat, hill.lng), `state hill "${hill.name}" is still inside a metro`);
}
assert.ok(checked > 0, "no state hill survived at all, so this asserted nothing");
});
it("marks every metro lot as detail, which is what the LOD acts on", () => {
const detail = city.districts.filter((d) => d.detail === true);
const base = city.districts.filter((d) => d.detail !== true);
assert.equal(detail.length, SAN_FRANCISCO.districts.length + SOCAL.districts.length);
assert.equal(base.length, report.districts.state - report.districts.dropped);
// And the state's own districts are never marked, or the board would hide
// California when you stood back from it.
assert.ok(base.length > 0, "the state kept no districts of its own");
});
it("keeps the lattice the size this whole merge depends on", () => {
const cf = city.coarseFactor ?? 1;
const lat = axisLength(
city.bounds.minLat, city.bounds.maxLat, city.cellLat, city.cellLat * cf,
(city.focusRegions ?? []).map((r) => [r.minLat, r.maxLat]),
);
const lng = axisLength(
city.bounds.minLng, city.bounds.maxLng, city.cellLng, city.cellLng * cf,
(city.focusRegions ?? []).map((r) => [r.minLng, r.maxLng]),
);
const points = lat * lng;
/*
* The ceiling, not a description. At 150 m / x8 this was 1.23M points and
* the board rendered 1,114,226 triangles against a 440,000 cap — terrain,
* not buildings. Anything that pushes this back over half a million points
* is re-opening that, and should be measured rather than assumed.
*/
assert.ok(points < 500_000, `${points} lattice points — the terrain budget was found at ~180k`);
assert.equal(UNIFIED_COARSE_FACTOR, cf);
assert.ok(Math.abs(city.cellLat * 111_320 - UNIFIED_FINE_METRES) < 1);
});
it("carries both metros' ports, which the state board never had", () => {
// The most visible single consequence of the merge: the state board now has
// a harbour on it, and `main.ts` attaches the vessel layer to any pack with
// one. Before this, `sf.ts` and `california.ts` had zero berths between them.
assert.ok((city.ports?.length ?? 0) >= (SOCAL.ports?.length ?? 0));
assert.ok((city.ports?.length ?? 0) > (CALIFORNIA.ports?.length ?? 0));
});
it("is memoised, because World builds one per mount", () => {
assert.equal(unifiedCalifornia().city, city);
});
});