# Open work on Tera

Short, and only things that are decided but not done. Anything speculative belongs
in an issue, not here.

## The state board became the whole state — what this leaves open

`src/cities/california.ts` now runs 32.50–42.05 N and -124.50 to -114.0 W. Four
things fall out of it that somebody has to pick up.

- ~~**The marketing stills are frames of a board that no longer exists.**~~
  **Done 2026-08-23.** All 21 shots and all four films were re-shot at `e7a8aa0`
  plus this round's uncommitted work; `california-relief`'s caption now says 554
  units and names the north. See "The imagery was re-shot" below for what that
  run found, and for the two manifests that are still waiting on a clean sha.
- **`scripts/performance-budget.mjs`'s header comment is now stale in one row.**
  It records `california/mobile` at 389,843 triangles; the extended board
  measures 373,825. The caps in `performance-budgets.json` were rewritten
  downward to match. Nobody owned that comment this round.
- **`ARCHITECTURE.md` §12.2 lists the span constants that stay spans**, and the
  list is the decision, not a survey. If a future round wants physical units for
  the satellite dome or the cloud tile, read the reason each one is where it is
  before moving it — two of them were tuned by photograph and one says outright
  that it is arbitrary.
- **The Bay Area desktop frame-time cell was red on all three runs that
  measured this**, at p95 33.3, 33.2 and 33.3 ms against a 16.7 budget. Recorded
  here rather than claimed green, and recorded with everything beside it,
  because the section below asks for exactly that:

  - geometry is identical to the digit across all three runs — 2,264,928
    triangles, 207 draws of a 320 cap — and unchanged from before this work to
    within 52 triangles (2,264,876);
  - `src/cities/sf.ts` was not touched and nothing in this change is reachable
    from that board's geometry;
  - the Bay Area **mobile** cell renders the same scene, with the same
    per-camera lighting listener attached, at 16.7-16.8 ms on every run — so a
    per-frame CPU regression is ruled out, because it would show on both;
  - `pp_dpm_sclk` read `0: 500Mhz *` of an available 2725 throughout, with
    `gpu_busy_percent` at 0, which is the state the section below names as the
    cause.

  Three draws from a bimodal metric on a card parked at a sixth of its clock is
  still not a scene defect. It is now three, though, and the honest state of this
  cell is "red on this box, cause identified, not reproduced anywhere else".

  **Re-measured after the layers were wired, and it is now six.** The final run
  of this round read p95 33.3 ms on all three attempts again, with the geometry
  identical to the digit across every attempt — 2,264,956 triangles and 209
  draws of a 320 cap — and **identical to the pre-round baseline for that board**,
  which is the number that matters: the Bay Area gets no port, no fleet and no
  sky layer, and its triangle count did not move by one. The card read 500 MHz of
  2725 through that run as well.

### Aerial perspective is wired, and the coarse seam is now a live defect

Fog now follows camera altitude (`aerialReach` in `engine/atmosphere.ts`, §12.2).
`main.ts` recomputes the rig on the city's tick (`onCameraFrame`, after the
follow cameras write the pose), throttled at 2% of the current altitude, which
collapses a chapter flight to a few dozen recomputations instead of sixty a
second. Drive, crow and aircraft follow-cameras write the pose directly and do
not fire OrbitControls `change`; they used to fall back to the one-hertz clock
and a fast descent stepped the fog four times. That path is on the frame loop.

The state-to-metro transition is a **fog dip** — collapse the outgoing board's
`setAerialFog` toward the camera over ~0.3 s, swap in the obscured frame, lift
the incoming board's over ~0.5 s — chosen over a crossfade because a crossfade
cannot afford the triangles (ca+sf is 2,640,307 against the Bay Area's
2,600,000 cap) and because a dip is the only transition that hides the 4.2x
vertical-exaggeration pop, the 4 km projection slide and the vanishing 2 km
freeways at once, since all three happen at maximum obscuration.

That makes fog a **transition mechanism** rather than an ambience setting. The
dip itself is already on the app pump (`stepFogDip`); aerial perspective under
a follow camera is now on the same kind of pump. What is still true: the 2%
throttle is a threshold on *altitude*, and a dip changes near/far with the
altitude held still — which is why the dip writes `setAerialFog` itself rather
than going through `refreshAerialFog`.

### The north is authored, and three things in it are thin

- The Klamath knot is four chains at four bearings, which reads as "not a
  north-south range" and does not yet read as the Trinity Alps in particular.
- The northern Sierra tapers into the Cascade arc through the Diamond Mountains
  and Honey Lake, and from the state pose that corner still reads as Basin and
  Range domes rather than as the end of a range. It is arguably correct — Honey
  Lake really is Basin and Range — but it was not checked against a photograph
  of the real ground.
- There is **no Sacramento, Redding, Chico or Santa Rosa** on the board. The
  block count is 8,881 against a pack test that caps it at 9,200, and the
  Central Valley's new northern half is farmland with nothing built on it. The
  state capital being absent is the most visible gap in the extension. Adding it
  is a district and about 300 lots; the budget that has to be checked first is
  draw calls on the california mobile cell, not triangles.

## The port, the ships and the sky are wired — what that leaves open

`main.ts` now supplies all four layer factories and feeds the three that take a
feed. `ARCHITECTURE.md` §13 is the design; these are the things it does not
cover.

- **A dark navy tower stands in the water off San Pedro, and it is not ours.**
  Roughly 1.5 km south-east of the Terminal Island shoreline, a single
  building-sized block sits on open water. It is **pre-existing**: it is in
  `/tmp/tera-look/socal-check.png`, photographed before the port kit was wired,
  when that waterfront was still a bare tan plate. Nothing in the port kit, the
  vessel layer or this wiring puts it there. What changed is that the new
  Harbour chapter frames it — it was previously only ever seen from the
  whole-board pose, where it is two pixels. It belongs to whatever places
  anonymous blocks on the Southland board, and it wants a land mask.
- **The radar sheet has never been seen against a real feed.** Both sky
  projections answer 404 on cloud-1 today, so the layer was photographed against
  a synthetic fixture served to `look.mjs --api`. Two things that showed up
  there and want re-checking against a real composite: a 50 dBZ core draws at
  `PRECIP_ALPHA_MAX` 0.92, which is opaque enough to hide the terrain under it
  (correct for a severe cell, and unreachable on a normal California day at
  0.47% wet and 20-35 dBZ, where the same ramp renders as a translucent smudge);
  and `promoteRadar`'s sentence prints `observedAt` as a raw ISO string in the
  middle of an otherwise plain-English line.
- **`Services` still does not carry the two sky services.** `server/src/app.ts`
  constructs `createRadarService` and `createBirdsService` at the route rather
  than on `Services`. `sources.radar` / `sources.birds` are now on the health
  body — `routes/health.ts` reads them off the config, which is what every other
  line in that route does — but a future route that wants either service cannot
  reach it through `Services` the way every other one can.
- **The sky is polled by the clock, not by a watcher.** `askSky` fires once per
  board and then only when the body's own `ttlSeconds` has expired, checked on
  the once-a-minute tick that already exists. That is deliberate — a fourth
  polling ladder in `adapters/http.ts` to re-ask a question whose answer changes
  twelve times an hour is machinery bought for nothing — but it does mean the
  two sky feeds have neither `watchFires`'s visibility check nor its failure
  back-off. A feed configured with a 30-second TTL would be asked once a minute,
  and a box answering 500 would be asked again every minute rather than backing
  off. Both are fine at 300 s and 600 s; neither is fine at 5 s.
- **Every hull on the board is modelled and the AIS licence is still unread.**
  `VesselsSourceId` is `'none' | 'modelled' | 'cloud1'`, the modelled body
  carries "not an observation of any vessel", and `vesselSummary` says the live
  feed is not configured. When `/api/sea` lands, the only change in `main.ts` is
  where `body` comes from — but the aisstream terms decide whether a real
  position may be shown to the public at all, and nobody has read them.
- **Nothing photographs the sky layers in CI.** `layerSeams.test.ts` asserts the
  wiring from source at both ends, which catches a deleted line; it cannot catch
  a layer that builds and draws nothing. The fixture that proved this one works
  lives in a scratchpad and is not committed.

## One command re-shoots the imagery: `npm run refresh`

```sh
npm run refresh                 # build, stills, cards, films, both manifests
npm run refresh -- --dry-run    # what it would shoot; no browser, no build
npm run refresh -- --stills-only        # …--cards-only, --films-only; they compose
npm run refresh -- --rough      # films at 24 frames, for looking at
npm run refresh -- --skip-build # you just built; do not build again
```

`scripts/brand-assets/refresh.mjs` exists because re-shooting used to be a
*procedure* — build first, because the scripts photograph `dist/` and not `src/`;
then three commands in an order that matters; then check the run actually got the
GPU. A procedure that lives in somebody's head gets half-run, and the evidence was
in this file: the stills were re-shot at one commit and the films at another two
weeks older, and nothing said so until a human compared two files.

It prints a diff of the deliverables at the end — new, changed with the size delta,
byte-identical — and it **fails loudly and specifically** on the three failures
that otherwise produce confident wrong output:

- **SwiftShader.** Software rendering makes the same pictures fifteen times slower
  and reports success. Any child that prints a software renderer stops the run.
- **A chapter `expect` guard firing.** That means a pack was reordered under the
  shot list, so re-running cannot fix it. `node scripts/brand-assets/shots.mjs
  --list` prints what the shot list believes.
- **A `play` guard firing.** A shot that takes a body did not get the one it asked
  for — see the section below. Also not a flake, and also not fixed by re-running.

Two things it does **not** do, on purpose: it does not commit, and it does not
re-stamp the manifests afterwards. Commit tera first, then
`shots.mjs --manifest-only` and `films.mjs --manifest-only`, so the manifests name
a clean sha rather than a dirty tree.

## A shot can take a body now — `play` in `shots.mjs`

Three stills are pictures of an agent rather than of a place: `drive-101`
(chase camera behind the EV on US-101), `crow-nav` (the crow over the Bay Area
board) and `california-flight` (the aircraft on the corridor). They replace three
hand-made screenshots in `lumbridge-v4/apps/web/public/arena/`, which were the
last imagery on the site no script could reproduce — and so also the last that
nobody could re-shoot when the engine moved. All three still showed a left column
reading "Explore as your crow →", a "FULL VIEW" badge no visitor to the site ever
sees, and one daylight frame served to dark-mode readers as well.

`play` drives the mode dock, which is the same class of input `aim` is: no back
door, just the control a reader uses. `#mode-dock` and `#play-hud` stay **in** these
three frames deliberately — they are what the picture is of — while remaining in
`FURNITURE.BARE` for the share cards.

Three guards, all of them tested by making them fire, because every way this goes
wrong renders a plausible-looking frame with the right filename:

- the dock button must be *offered*, not merely present — the EV and the aircraft
  exist on the California board alone, and driving also needs US-101 or I-5 to be
  the current chapter, so elsewhere the button is `hidden` and clicking it is a
  no-op that leaves an orbit wide shot;
- `aria-pressed` **and** `#play-hud`, exactly as `performance-budget.mjs` does —
  `transitionControlMode` substitutes a fallback for a request it will not honour;
- the HUD's own chip names the body ("Drive", "Crow", "Flight"), checked at the
  click *and again at the shutter*, because a route that completes or a bound that
  is hit ends the mode mid-wait.

`refresh.mjs` surfaces all three as their own headline, next to SwiftShader and the
chapter guard.

**`untilProgress`, not a duration, for anything that was already moving.** The crow
and the aircraft are built inert, so *n* ms after the dock hands them over is the
same place every time — measured identical at 1x and 2x. Road traffic is not: it
runs from page load, so a duration is really "boot plus a bit", and the same
`run: 9000` put the EV at 55% of the leg in a 1x preview and 69% in the 2x render,
which is a different stretch of road with the edge of a town across the top of the
frame. `untilProgress` waits on the app's own route percentage instead, so preview
and delivery agree. Overshoot throws rather than shooting.

### ~~`drive-101`'s night frame is the weak one~~ — fixed in the engine, 2026-08-22

This section used to say the frame's subject nearly disappeared, that the hour was
not the lever, and that both candidate fixes were in the engine: *give the EV a
headlamp spill on the road ahead*, or let the corridor's terrain keep some
moonlight. **The first one landed.** The EV now throws a low beam on the road in
front of it, the corridor carries continuous edge lines, a yellow median pair and
cat's eyes, and the frame was re-shot at the same camera and the same hour.
Canvas-region mean luminance, 1600w, before → after: **7.8 → 10.2** overall, and
**5.5 → 11.7** on a 580×470 crop around the car and the road ahead of it. It reads
as a car with its lights on rather than as two tail lamps.

**What is still true, and is a capture limit rather than a light one.** The EV is
about 55 px wide in a 1440 px frame and always will be from this camera:
`applyFollow` in `roadTraffic.ts` puts the chase camera 0.72 back and 0.92 up with
its target 0.32 ahead, which is a fixed 38° looking-down pose, so the road fills
the picture and there is no horizon in it. The stretch of road is the only lever
the shot list has, and it was swept: `untilProgress` 0.64 puts the car on a bend
but loses the far carriageway, 0.72 lands it at an off-ramp with a settlement's
buildings — drawn at 1,919 m to the unit — across the right of the frame, and 0.82
is a wider carriageway with the car smaller again. **0.55 is still the best of
them** and stays. If the car is ever wanted larger, that is a change to the chase
offsets in `roadTraffic.ts`, not to `shots.mjs`.

The crow moved to the Bay Area board for the same reason and it worked: over a city
the night frame is the *better* of its pair. The hand-made original was on the state
board, where a one-metre bird stands in front of ground drawn at 1,919 m to the unit
with an oak the size of a hill behind it.

### ~~`bay-bridge` was aimed at the wrong thing~~ — re-aimed, 2026-08-22

The old frame ran the crossing diagonally out of the top-right corner, left the
entire right half of the picture as open water, and contained **only one of the two
shores the bridge joins** — so its own `note` had to say so, and its caption
described a wide bay view rather than the bridge the id promises.

It now seeks to 37.8085, −122.363 (the causeway just east of Yerba Buena, which is
the midpoint of *what has to fit* rather than of the structure, because the panel
eats the left 310 px) at `zoom: -4`, and drags `azimuth: -45` to turn the chapter's
almost-due-north stance broadside to a crossing that runs ENE at about 57°.
`elevation: -6` drops the camera far enough to put Marin, Angel Island and the
Golden Gate along the top, which is what stops the water reading as empty. Both
landings are inside the frame, with San Francisco's skyline at one end and the East
Bay shore at the other, and the whole 2013 topology — two suspension towers, the
deck coming down onto the island, the third tower, the piered causeway — is legible
across the middle. Caption, both `note`s and both `alt`s were rewritten to the frame
that is actually delivered.

Night, 1600w: canvas-region mean **8.3 → 16.7**, and on a 420×110 strip lying on the
causeway — the half of the crossing the old frame did not contain at all — peak
**18.7 → 134.9**. The night frame no longer needs a lift before anything appears,
which is what this file used to record about it.

### ~~The Golden Gate needs an earlier hour than everything else~~ — retired

`golden-gate`'s night frame was shot at 20:50 (sun −7.5°, nautical twilight) with a
`note` explaining the exception: the other night frames are cities carried by their
own windows, and a bridge over open water had none, so 21:35 rendered this one as a
black rectangle. **The deck now carries its own lamps**, so it is back on the
standard 21:35 with everything else and the special case is gone from `shots.mjs`.

Worth knowing before anyone "fixes" the number: the canvas-region mean went **8.2 →
7.0** at the deeper hour, because losing the residual sky costs more mean luminance
than 1,600 lamps over open water add back. **Measure the subject, not the canvas,
on a bridge frame.** On the deck the lamp run, both tower silhouettes and the red
light over each tower head all read clearly at 21:35, where at 20:50 there was no
light on the structure anywhere.

## The imagery was re-shot on 2026-08-23 — what that run found

`npm run refresh` at `e7a8aa0` + this round's uncommitted work: **21 shots x 2
frames**, both share cards, all four films, both manifests. Every run printed
`ANGLE (AMD, Vulkan … Radeon RX 6700 XT (RADV NAVI22))` and not SwiftShader; no
chapter guard and no `play` guard fired. Timings on this box, for budgeting:
build 7 s, stills 545 s, cards 26 s, films 416 s.

**Both manifests are dirty and that is the one thing still owed.**
`SHOTS_COMMIT` and `FILMS_COMMIT` are `e7a8aa0` with `SHOTS_DIRTY` /
`FILMS_DIRTY` **true**, because the tree was not committed when the shutter
opened — the port, vessel and night-light work of this round is in every frame
and in no commit. That is the manifests being honest rather than naming a sha
that would render something else. Once tera is committed:

```sh
node scripts/brand-assets/shots.mjs --manifest-only   # no browser, no render
node scripts/brand-assets/films.mjs --manifest-only
```

then commit lumbridge-v4 and deploy it.

### Two new shots, and one of them needs a page

- **`harbour`** — socal chapter 5, aimed at 33.7405/-118.2565 broadside across
  Terminal Island. Four crane rails at four bearings, the container yards, hulls
  alongside and one under way, the Vincent Thomas across the Main Channel, and at
  night a red obstruction light on every gantry apex.
- **`california-fire`** — the state board with the fire feed behind it. See the
  section below; it is the only frame here that is not the keyless experience.

Both are in `shots.ts` and both are delivered as WebP. **Neither is on a page.**
Every one of the other nineteen ids is named explicitly by something in
`lumbridge-v4/apps/web/src`, so a new shot appears in the manifest and nowhere
else until somebody places it. That is a handoff, not a defect.

### The fire feed is photographable now, from a fixture

`drawsFire()` gates the whole layer on `access.feeds?.fires === true`, which
comes from `/health`. A keyless capture — which is every other frame here — can
therefore not photograph fire at all, while `tera.lumbridgecorp.com` answers
`"fires":"cloud1"` and every anonymous visitor to the deployed site does get it.
The site was about to ship an imagery set in which the most visible thing the
engine gained this round did not exist.

`shots.mjs` now has `serveFeed()` and a per-shot `feed` field. It fulfils exactly
two routes and 404s the rest of `/api/v1`, and the body is
`scripts/brand-assets/fires-2026-08-23.json` — the verbatim response from the
live feed on 23 August 2026, kept whole. **Do not trim that file.** `promote()`
counts the rows it refuses and the panel prints that number, so a fixture with
the refused rows deleted would draw the same eight fires under a sentence
claiming a gate that never ran.

Three consequences worth knowing before extending this:

- the synthetic `/health` says `auth.mode: "none"`, which resolves the visitor to
  `member` and keeps **`#tier-adds`** out of the frame. That card is the sign-in
  offer, it is shown only to `anon`, and it is **not** in `FURNITURE.CLUTTER` —
  so the day this pipeline meets a real API it will arrive in the art silently,
  exactly the way `#mode-dock` did;
- `california-fire` is the only shot clocked to **2026-08-23** rather than
  2026-08-06, because the panel prints the body's own `fetchedAt` and how long
  ago that was. A fixture from the 23rd photographed on the 6th reads "fetched
  2026-08-23 · just now";
- the hot-pixel layer draws every FIRMS detection inside `bounds`, and `bounds`
  is a lat/lng **rectangle**. On the state board that puts a handful of cyan
  points over open water and over Baja in the night frame. They are real
  detections and the rectangle is the honest gate the layer has; it is worth
  deciding whether a board wants a land test before the count in the panel is
  ever quoted as a California number.

### The marine layer decided two of the day frames, and it should decide any more

`atmosphere.ts` models the Pacific marine layer on **apparent solar** time, and
the curve is 0.95 before dawn, 0.6 at nine, 0.2 at eleven, 0.4 again by four. Two
shots were on 09:10 and 09:20 — a little after eight solar, where the layer is at
about three quarters — and at a whole-board or 32 km standoff that is not weather
in the picture, it is a white lid over the entire subject. `bay-relief`'s caption
is about eighty-three named hills and four octaves of value noise, none of which
was visible; `peninsula`'s is about twenty miles of city, likewise.

Both moved to the late afternoon (17:00 and 17:10) and both frames are now
legible. The compromise is forced: the layer is thinnest at solar 11-14, which is
**12:07-15:07 on a wristwatch here** — the exact hours a heightfield has no
shadow. Around five is thin enough to see the ground and low enough that the
ranges still throw.

**Any future wide day frame on the Bay board has this constraint.** Close and low
poses do not — `fidi` at 10:10 and `crow-nav` at 09:20 are both clear, because
the camera is under the deck.

⚠️ **A 1x `--preview` does not predict the cloud in the 2x delivery.** The deck
animates on elapsed time and the two runs have different settles (3000 ms against
6000), so the puffs land somewhere else. Framing, pose and light all preview
faithfully; cloud does not. Judge cloud on the delivered frame, which is how
`peninsula`'s note lost a sentence about SFO that was true of the preview and not
of the render.

### Four captions had been falsified by engine changes, and nothing catches that

A caption is prose next to a camera pose. When the engine moves under it, the
frame still renders, still looks deliberate, and still ships. All four of these
were found by opening the delivered frame and reading its own note back:

- **`california-flight` day** ended "and the Pacific past the wingtip". Aerial
  perspective now follows camera altitude, so at 1,413 m the ocean is inside the
  haze rather than beyond it and there is no sea in the frame at all.
- **`california-flight` night** promised "the basin's lit ground in the corner"
  and "the moon's path broken across the water". The frame is over dark coastal
  mountains: no basin, no water. What actually carries it is the aircraft's own
  navigation lights and the corridor's road markings.
- **`lax` day** said "the traffic overhead is the real traffic: registration and
  type come off the same feed". **It cannot be.** Every frame but
  `california-fire` is shot keyless, so `TrafficSource` is the bundled simulator
  and not ADS-B. A caption may not borrow a fact from a deployment the shutter
  never opened on — and this is the failure mode to watch for now that one shot
  *does* have a server.
- **`golden-gate` day** said the towers "throw along the deck". They throw across
  the water beside it, which is checkable and was checked.

### `fidi-day`'s day poster had drifted onto the wrong renderer

`poster: 0.86` was chosen as "a few minutes before sunset: the last of the low
sun down the length of Montgomery". On ACES with the world-space sky dome, 0.86
is 20:09 with the sun at **+0.4°** and the towers are unlit silhouettes — the
frame measured mean luminance **33** out of 255 against the night poster's 21.
That is not a pair, and it matters more than a poster usually would: `Film` ships
`preload="none"`, so the poster is what most readers see for most of the time the
film is on their screen. Now `0.76` — twenty past six, sun twenty degrees up,
towers lit and throwing, mean **87**.

The other three films' posters were checked the same way and are fine
(147/24, 106/21, 155/46 day against night). **Check a poster pair by measuring
both, not by reading the clock**: the fractions were all chosen on an older
renderer and this is the one that had moved.

### Smaller things the re-shoot turned up and did not fix

- **`freeway` and `drive-101` are nearly the same photograph.** Both are the
  chase camera on the 101 chapter at 09:00; the only difference is where on the
  leg they land — 47% against 57%. `freeway` gets there with `settle: 3000`,
  which is the duration-not-position trap `drive-101` exists to document. It has
  landed on 47% on two consecutive runs, so it is reproducible in practice on
  this box, but it is a duration and it is measured from boot. It cannot simply
  be given a `play` block: the 101 chapter is *already* in drive mode, and
  `enterPlay` clicks the dock button, which would toggle it off.
- **`office-desks` slices the hardware card.** The panel is taller than the
  viewport, so `Desk speaker`'s controls are below the fold and its header sits
  on the frame edge. That is the exact condition `shot.hide` was invented for,
  and it cannot be used here because the hardware *is* the subject.
- **`#sky-section` stays in the product stills, deliberately.** It prints "No
  radar feed is configured … that is a fact about this box, not about the sky"
  twice, which reads like a hole — but `tera.lumbridgecorp.com` answers
  `"radar":"none"` and `"birds":"none"` today, so a real visitor sees the same two
  sentences. It is not the `#tier` case (local disagreeing with deployed); it is
  the product's own voice. **If either feed is ever wired, this decision has to be
  taken again**, because then the local frame and the site would disagree.
- **`#sea-section` stays too**, and it is the one that earns it: the harbour is
  modelled identically here and on the deployed site, and its sentence is what
  keeps a frame full of ships honest about their being modelled rather than
  observed.
- **`PROVENANCE.json`'s two card records now name a date they were not made on.**
  `refresh.mjs` re-stamps the share cards narrowly — it substitutes the `sha256`
  in the text and nothing else, which is what keeps that diff two lines instead
  of a reflow — so `intakeDate` still reads `2026-08-22` for files written on the
  23rd. `check-provenance` only validates the format, so nothing catches it. It
  is one line in the one document whose entire job is accuracy.

## The vessel layer has two gaps that belong to other files

Both stated in code comments already; recorded here so they are not only there.

- **Pier 300 and the East Basin take no port calls.** The straight run from the
  nearest point on `Port.channel` to those berths is 1.9 km and 2.9 km and
  crosses Terminal Island, so `BERTH_APPROACH_REACH_METRES` (1100) gates them out
  and they keep a static hull alongside rather than driving a container ship over
  a container yard. The proper fix is an optional per-berth approach path on
  `Berth` — `approach?: LatLng[]`, water-side first — which `socal.ts` could then
  author for Pier 300 as the real route out through Angels Gate and north-east
  across the outer harbour. `harbourCalls` would prefer it to the nearest-point
  projection; about ten lines once the field exists. The field is on
  `src/engine/types.ts`, which the vessel work did not own.
- **The tug's own solid is correct, cheap, and sub-pixel at every SoCal pose we
  ship.** Its payoff is banked for the Bay board and for closer poses rather than
  realised today.
- `engine/nightlights.ts` now imports `craneLights` from `engine/ports.ts`. No
  cycle — `ports.ts` imports only three, terrain, types and world — but the two
  files are coupled and want moving, and re-photographing, together.

## The GPU on this box never leaves 500 MHz, and every frame-time number here is a floor

**This is a machine fault, not a scene fault, and it invalidates frame-time
measurement on amd-server until it is fixed.**

Traced 2026-08-23: `/sys/class/drm/card*/device/pp_dpm_sclk` sampled every two
seconds through eighty seconds of sustained rendering — 2,264,956 triangles at
1440x900 — reports `0: 500Mhz *` on all forty samples. It never ramps.
`power_dpm_force_performance_level` is `auto`. The card's own ceiling is
**2725 MHz core and 1000 MHz memory**, so it is running at roughly 18% of core
and as low as 96 MHz of VRAM clock.

The consequence: `bay-area/desktop` is fragment-bound at exactly this clock and
sits on the vsync deadline. Same build, same geometry, viewport swept —

| viewport | pixels | p95 |
|---|---|---|
| 1440x900 | 1.296 MP | **33.3** |
| 1280x800 | 1.024 MP | 16.7 |
| 1152x720 | 0.829 MP | 16.8 |
| 1024x640 | 0.655 MP | 16.7 |

The cell is ~27% past what this board sustains at 500 MHz. So a build that adds a
trivial amount of fragment work flips it from 16.8 to 33.3 while its geometry is
identical to the digit, and a build that removes some flips it back. **That is
what makes single-run frame-time comparisons on this box worthless**, and it is
how two separate investigations here reached two different wrong conclusions.

**Do not "fix" this by raising a cap.** Geometry is the gate — it is deterministic
and identical across runs. Frame time is advisory until the card clocks properly.

**Two ways this diagnosis was reached wrongly first, both worth remembering.**
One investigation blamed the desktop shadow map: disproved, because 4096, 2048,
1024 and 256 all render the board in 1.21-1.31 ms. Another blamed a camera
`change` listener rebuilding the PMREM environment: disproved three ways — every
field `environmentKey()` fingerprints is bit-identical from 500 m to 67,600 m of
camera altitude; 105 camera-lighting applies produced **0** rebuilds; and the
budget harness runs under `reducedMotion: "reduce"`, so its camera is
**stationary** and fires no `change` events at all during the sample window.
Both wrong conclusions came from comparing a single run against a single run.

To settle it properly: `power_dpm_force_performance_level` needs root. Forcing it
to `high` and re-running the matrix would give the first trustworthy frame-time
numbers this project has had.

## The aeroplane glyph is still larger than the Golden Gate

`GLYPH_MAX_SCALE` in `src/engine/flights.ts` is 52 and that is a mitigation, not
a cure. The glyph has a screen-space *floor* — never smaller than legible — which
scales by the distance to the **aircraft**, when what makes it look wrong is how
far the camera is from **what it is looking at**. At a whole-board pose those are
the same thing; at the Golden Gate chapter the bridge is two units from the camera
and the traffic is two thousand, so the floor fires hard on the aeroplane and not
at all on the bridge.

A ceiling of 52 was chosen because the raw scale at 1,160 units — the far end of
the orbit over the California corridor — is 51.0 at a 60-degree field of view, so
anything lower shrinks aeroplanes at a pose people actually use. (26 was tried
first and put the glyph at 0.0123 of the frame, against the 0.012 at which
`flights.ts` says the wings stop resolving.) 52 takes the worst case from about
two and a half times the bridge's main span down to about one and a half.

The complete fix is to clamp against the camera's focus distance rather than the
aircraft's, so the glyph collapses toward its authored size whenever the viewer
has zoomed in on something near, at any aircraft range. That is a signature change
through `glyphScale`, `tick` and their callers.

The four presets that photograph this clamp used to cover three stand-offs, not
four, and two of them were pictures of a different board — see "The capture
harness was aiming by position" below. They are now `glyph-board` (the whole
state), `glyph-north` (Shasta and the Klamath knot), `glyph-bay` (the Bay Area
oblique) and `glyph-fidi` (downtown San Francisco): four aerial stand-offs over
three orders of magnitude, all four aimed by `data-view`.

## The capture harness was aiming by position, and two presets were lying

Fixed 2026-08-23. Recorded because the *class* of defect is the one this repo
keeps paying for, and because two of the three fixes are guards rather than
changes.

**What was wrong.** `scripts/look.mjs`'s `glyph-la` and `glyph-sf` were
documented as "two chapter closeups on that board" — California — and were
`--click "^LA$"` and `--click "^SF$"`. Chapters 04 and 05 of the state board are
not camera poses: `main.ts:4485` matches them against `CALIFORNIA_DESTINATIONS`
and calls `switchCity()`, so both presets left California and returned frames
byte-comparable to `hero-socal` and `hero-sf`. Two of the four stand-offs the
glyph set claimed to cover were duplicates of two others, and nothing failed.

**What photographing the fix turned up.** California has exactly **two** aerial
poses on it. Of its six chapters, one is the whole board, two are doors, and two
— `la-sf-us-101` and `la-sf-i-5` — call
`requestControlMode("drive")` at `main.ts:4493`, so clicking either lands a chase
camera on a freeway rather than a stand-off. "Two chapter closeups on that board"
was never available. The glyph ladder is spread across boards instead, which is
a better ladder: `glyph-board` → `glyph-north` → `glyph-bay` → `glyph-fidi`.

**The three things that landed with it.**

- `scripts/look.mjs` takes `--chapter <data-view> [--expect <short label>]`, so
  every preset aims at a chapter's **identity** rather than at its position or at
  the text printed on it. Both of the other two have now produced a photograph of
  somewhere else in this repo.
- `scripts/performance-budget.mjs` gives every scene a **signature chapter** —
  one `data-view` that exists on that board and on no other — and refuses to
  become ready until it is on screen; `california-drive` aims at
  `data-view='la-sf-us-101'` instead of at chapter index 1. `?city=` falls back
  to the first board rather than failing, silently, so a `bay-area` cell that had
  quietly measured California would have passed its 2.6M-triangle cap by a factor
  of six and stayed green through any regression a phone would choke on. A budget
  that cannot fail.

  **A signature chapter rather than the pressed board tab, and that is a lesson
  rather than a preference.** It was written against `[data-board]` first and
  broke inside the hour, because the tab strip is chrome and chrome is precisely
  what the continuity work is allowed to change. A chapter id is pack data. Every
  harness in this repo should key on the thing a camera resolves through, not on
  the thing a designer is free to move: `check-chapter-identity.mjs` demotes tab
  drift to a printed note for the same reason, and `switch-cost.mjs` detects
  arrival from `#chapters` rather than from either the tab or the boot card.
- `scripts/check-chapter-identity.mjs` + `scripts/fixtures/chapter-identity.json`
  pin every chapter's `data-view`, position, printed number and short label under
  every `?city=`, and `src/test/chapterIdentity.test.ts` re-states the pack half
  of it in `npm test` with three negative cases that are watched failing.

**The 26 aims that are still index-aimed, and are deliberately left that way.**
`scripts/brand-assets/shots.mjs` (21), `films.mjs` (4) and `capture.mjs` (1, the
Open Graph card) each point at `#chapters .chapter[N]` and assert a short label.
21 of the 26 aim at a board a `?city=` reaches — 12 Bay Area, 6 California, 3
SoCal. Re-pointing them means re-shooting the imagery on lumbridgecorp.com, which
is an owner decision rather than a refactor, so this round landed the check that
*reports* when they would break instead of moving them.
`check-chapter-identity.mjs` reads those three files and prints every aim whose
label no longer matches the board it points at.

The label assertion on its own is **not** enough and the reason is in the data:
"Whole Board" is chapter 01 of both metro boards and "The Valley" is a different
valley on each of them, so a reorder that preserves labels passes all 26 and
shoots the wrong place. Identity is `(board, chapter id, position)` together.

## A board switch is measurable now: `node scripts/switch-cost.mjs`

The pause between one board and the next was the most-complained-about moment in
the product and the one moment nothing measured, because `performance-budget.mjs`
waits `warmup-ms` and throws away exactly the window the switch happens in.

Baseline at `bcac6aa`, desktop 1440x900, `--repeat 3`, medians. `covered` is
click-to-`#boot`-hidden; `blocked` is Long Tasks inside that window; `blank` is
frames that issued zero WebGL draw calls:

| direction | covered | opaque | blocked | longest | frames | blank | live |
|---|---|---|---|---|---|---|---|
| california→sf | 1,715 | 1,415 | 608 | 526 | 69 | 46 | 65% |
| socal→sf | 1,627 | 1,326 | 523 | 459 | 69 | 46 | 68% |
| california→socal | 1,109 | 809 | 400 | 312 | 45 | 22 | 64% |
| sf→socal | 1,027 | 726 | 328 | 245 | 44 | 21 | 68% |
| sf→california | 1,120 | 820 | 340 | 196 | 49 | 26 | 70% |
| socal→california | 1,061 | 761 | 319 | 205 | 47 | 24 | 70% |

Two things to read off it.

**Two thirds of every pause is not work.** 64-70% of the cover is a live,
animating page with an empty world in it, because the outgoing board is disposed
before the incoming one is built. Retaining boards converts a 1.0-1.7 s
full-screen modal into a 0.2-0.6 s freeze of a live picture and costs zero new
triangles.

**Cost belongs to the destination, not to the pair.** The two arrivals at the Bay
Area differ by 5%, the two at California by 6%, and Bay Area against California is
1.6x. Nothing is reused between boards today — `three` refcounts shader programs
per material and deletes them at zero, so disposing a board deletes its programs
and returning relinks them — and this table is what will show that changing.

**And the residual is a separate, nameable defect.** The 526 ms task on an
arrival at the Bay Area is main-thread instance placement for 83,137 buildings.
Retention does not touch it and will make it *more* visible: today it hides
behind a full-screen card with a percentage on it, which reads as busy, and
afterwards it will freeze a live picture, which reads as a hang. Chunking that
placement across frames, or moving it behind the worker, is its own piece of work
with its own payoff.

The command reports `card` and `arrival` beside the durations for exactly that
reason. `card=yes` with two thirds of the frames blank is the disposal model;
`card=no` with `blank` near zero is retention, and the same command measures
both, because arrival is detected from `#chapters` rather than from the card.
A single direction run against the in-flight retention build read
`card=no`, 1 blank frame of 60, and a 508 ms single task — which is the shape
this section predicts: the modal is gone and the hitch is not.

## The quadtree's stated blocker was the wrong one

Recorded 2026-08-23 so the next round does not lose a week re-deriving it. The
terrain quadtree has been deferred repeatedly on the grounds that the synchronous
sampler stands in its way. **It does not, and the code says so in one line.**

- `elevationAt` has exactly **one** production caller: `world.ts:545`, inside
  `computeField`. Nothing in the render path samples it. Everything else reads
  the cached `Field` through `elevationSampled` / `isLandSampled` /
  `inParkSampled` / `groundAt`.
- `world.lattice()` — the thing that hands out flat arrays — has exactly **two**
  callers, both in `terrain.ts` (282 and 404).
- Measured on this box: `elevationAt` is 1.97 µs/call on SF and 11.98 µs on
  California, so a complete permanently-resident base pyramid (levels 0-2, 15
  land tiles, **183 ms** on SF, covering the whole board at 370 m cells) makes
  every point query synchronous by construction — descend-to-finest-resident is
  integer arithmetic against today's *two* binary searches in `cellIndex`. No
  caller ever needs an `await`. The base has to be complete because
  `minimap.ts:407` samples a 160x160 grid across the **whole** board, including
  ground the camera never looks at; level 2 satisfies it exactly.

The API a quadtree must replace is therefore one accessor and two call sites. The
real blockers, none of which had been written down:

1. **Placement drift.** Buildings, landmarks, bridges, airports, ports, markers
   and road ribbons are placed **once** at scene build against `groundAt`. A
   level-3 (185 m) representation sits p95 119 m / max 453 m from level 5, so if
   the resident level under a placed object changes after placement it moves by
   up to **1.15 scene units** on SF — against a lot of 0.42 units. Buildings
   float or sink. This, not the sampler, is what the deferral was groping at.
2. **The shadow caster.** Selected at the shadow map's own texel error a quadtree
   caster is 826,206 triangles against today's 53,806 — 15x. View-distance
   weighted it is 1.3x at the wide pose and 2.9-4.2x at the close ones, i.e.
   +100k to +174k triangles, against 183,824 of headroom on bay-area/mobile and
   39 free draw calls. The caster must be bounded by the *visible* selection
   rather than selected independently, because **never raise a cap**.
3. **Determinism.** `performance-budget.mjs`'s own comment says "geometry here is
   deterministic". A streaming tree makes triangles a function of time and camera
   path. It survives only because the harness runs `reducedMotion: "reduce"` on a
   planted pose and waits 3,000 ms of warmup — and realistic selections settle in
   232-830 ms — but that has to become an explicit `settled()` gate the harness
   waits on, not a hope.

Two more facts worth keeping. A single statewide lattice at SF fidelity is dead —
23.7M points, ~74 s of worker, 142 MB — because `buildAxis` refines per **axis**,
so two focus regions at opposite corners refine nearly the whole board. And the
board that would demonstrate the mechanism paying for itself is **SoCal**, not
SF: 48.4% of SoCal's lattice is the per-axis cross's arms (64,677 of 133,566)
against SF's 31.0%, and SF is the one board where a single hand-drawn rectangle
is exactly right — its 52 districts union to 9.6% of the board and its one
`focusRegion` box is 9.2%.

**And today's SF board is not error-bounded, which nobody had noticed.**
Cell-centre height error against the analytic truth is p99 12.0 m / max 64.9 m in
the fine region and p99 29.1 m / **max 459.3 m** in the coarse one — 27.2 px at
the default whole-board pose, out in the bay. The board is cheap because the pack
declares 90% of it unimportant. That is a defensible trade and it is not the same
thing as being right.

## One California is descendable now, and vertical exaggeration is what stops it

Recorded 2026-08-24, at `e5b9339`, with the photographs that found it. That
commit removed the three things keeping the camera off the merged board — the
orbit floor, the missing metro rungs and the board-switching doors — and what
is left is one cause with two faces.

**`verticalExaggeration` is a per-board constant and this board is not one
scale.** `reconciledExaggeration` derives it so the tallest blended peak fills
`RELIEF_IN_FRAME` of the board span: 15.00 for California against a 5,132 m
blended peak, 5.78 for the Bay Area, 3.41 for the Southland. All three are
correct *for framing that board whole*, and 15 is what makes the Sierra readable
at 1,551 km. The merged board is the first one a visitor can traverse from
1,551 km down to 7.7 km, and the constant that frames the state buries the city:

- **At FiDi (7.7 km) the camera is inside the terrain.** San Francisco's relief
  is drawn 2.6x taller here than on its own board — Twin Peaks' 280 m becomes
  4.2 km of drawn height, which is 2.2 units on a board where the whole descent
  is about 2. The photograph is `/tmp/tera-look/one-fidi.png`: a grey wall, a
  Bay Bridge overhead, and buildings as needles because `world.metres` runs
  building heights through the same exaggeration.
- **At the Bay rung (45 km) the ground is visibly faceted.** 300 m cells
  (`UNIFIED_FINE_METRES`) at 15x turn every hill into angular shards. The cell
  size is the known gap; the exaggeration is what makes it *visible*, because
  vertical error scales with it and horizontal error does not.

So exaggeration has to fall toward 1 as the camera descends, which is what an
orthophoto globe does and why none of them exaggerate at all. **It is not a
constant this file can simply lower**: 15 is load-bearing at the state pose, and
the Sierra at 5.78 is a bruise on a plate.

The blocker is the one `TODO.md` already names for the terrain quadtree, and it
is the same blocker: **everything is placed once at scene build against
`groundAt`.** Buildings, landmarks, bridges, airports, ports, markers and road
ribbons all read a ground height at build time, so an exaggeration that changes
with altitude moves the ground out from under every one of them. Whatever
carries the exaggeration ramp has to carry re-placement with it, and that is the
same machinery a streaming terrain needs. They should be built together or the
second one rewrites the first.

Two smaller things the same photographs turned up, neither of them blocking:

- **Building heights ride the board's exaggeration.** `world.metres(heightM)` is
  `(m / metresPerUnit) * verticalExaggeration`, so a 60 m building on the merged
  board is drawn at 15x while the same building on the Bay board is at 5.78x.
  On a 160 m lot that is a needle. Relief and structures arguably want different
  treatment, and nothing has ever had to decide because no board carried both at
  two scales.
- **The metro lots are still 160 m** (`DETAIL_LOT_METRES`), chosen against the
  Southland's own lot size and explicitly not San Francisco's 40 m because the
  board has to fit one budget. At 45 km that reads as scattered cubes rather
  than as a city. A second, nearer detail tier at 40 m — bounded to the
  districts actually inside a much tighter reach than `DETAIL_REACH_M` — is the
  shape of the fix, and it must be measured rather than assumed: the Bay board's
  83,137 lots are ~1M triangles against this board's whole 400,000 cap, so the
  tier is affordable only because the reach is small. Do not start it before the
  exaggeration ramp, or it will be tuned against relief that is about to change.

## The cap is not what is limiting quality — the terrain is one draw call

Measured 2026-08-24 with the new `scripts/cost-at.mjs`, which costs an arbitrary
pose with `performance-budget.mjs`'s own patched GL counters. The merged board,
seeking to Lumbridge HQ and descending:

| stand-off | triangles | draws |
| --- | --- | --- |
| 400 km | 355,759 | 414 |
| 120 km | 351,019 | 327 |
| 45 km | 332,969 | 265 |
| 20 km | 332,969 | 265 |
| 7.7 km | 330,426 | 339 |
| 2.5 km | 329,392 | 334 |

**Flat, across three orders of magnitude of stand-off.** The Bay Area board at
that last pose draws **2,307,964**. So the merged board is not close to its
400,000 cap and never was: at 2.5 km over a city block it is spending 330,000
triangles, and almost none of them are the city.

`createTerrain` returns **one `THREE.Mesh` with one `BufferGeometry`** covering
the entire state. `three` frustum-culls per *object*, so a single mesh whose
bounding sphere contains California is never culled by anything, at any pose.
The same is true of the anonymous city: one `InstancedMesh`, one bounding
sphere, never culled — `updateBlocksDetail`'s repack bounds it by *authored
district distance*, which is a proxy for the frustum and not the frustum.

This reframes the whole budget question, and the reframing is the finding:

- **Raising the cap buys nothing**, because nothing is pressing against it. The
  cap is a frame-time proxy and frame time is fine; the board is slow to *look
  at*, not slow to draw.
- **The cost does not track the view**, which is the one property every engine
  that draws millions of triangles has and this one does not. A game renders a
  city at 2 km and a continent at 400 km for a similar price because the price
  is set by what is on screen. Here it is set by what exists.
- So the unlock is not a bigger number. It is **making the cost a function of
  the camera**, after which the near-field lot tier and a finer heightfield both
  become affordable inside the cap that already exists.

Two pieces, in this order:

1. **Chunk the terrain.** A grid of meshes over the board instead of one, so
   `three`'s own frustum culling has objects to reject. At 2.5 km that should
   drop the terrain from ~330,000 triangles to the handful of chunks in frame.
   The care is in the shadow caster: `createTerrain` packs caster-only faces
   after the visible set and uses `setDrawRange(0, seen)` to hide them, so each
   chunk has to keep that arrangement or the terrain stops casting. Budget the
   extra draw calls — 415 of a 460 cap is what the whole-board pose already
   spends, so the grid must be coarse (tens, not hundreds) or the cap moves
   before the triangles do.
2. **Then chunk the detail lots spatially** rather than by district, so the same
   culling applies to buildings, and only then raise `DETAIL_LOT_METRES`'s near
   tier to San Francisco's 40 m. The Bay board's 83,137 lots are the proof this
   is affordable *once it is culled*: that board draws 2.3M triangles at 2.5 km
   and holds 60 fps, on the same GPU, with the same renderer.

**Do not do either before the relief ramp is judged**, since both are tuned
against how the ground looks and the ramp changes that. It landed at `d6dc307`.

## One California is the default now — two consumers that follow it

Landed 2026-08-24. A bare `tera.lumbridgecorp.com` is the merged board;
`?city=sf` and `?city=socal` still get the dedicated boards unchanged, because
twenty-nine capture guards and two budget cells assert the `data-board` they land
on and a metro id that silently fell back to the merged board would make them
measure something else. `?one=0` forces the old three-board arrangement.

Both `california` budget cells were pinned to `?one=0` in the same change: their
caps are the plain state board's 440,000, and left alone they would have measured
the merged board — passing, while `california-one` measured the same board and
nobody watched the other.

**The marketing imagery has not followed, and it should.** `shots.mjs` and
`films.mjs` build `?city=${shot.city ?? "california"}&handover=0`, so every shot
that does not name a city now photographs the merged board rather than the plain
state tier. That is the right picture of the product and the wrong thing to have
happen silently — the captions were written against a board with no cities on it,
and `scripts/check-chapter-identity.mjs` does not check prose. Re-shoot with
`npm run refresh`, then read the captions against what came back; `TODO.md`
already records that four captions were falsified by engine changes once before
and that nothing catches it.

The content programme this opens up — what a single cohesive California is still
missing, and in what order — is **`CALIFORNIA.md`**, kept separate because none
of it needs a new mechanism and all of it can be worked in parallel.

## Los Angeles at 7.7 km was over the triangle cap — done, and here is what is left

Found by the review of the culling work, 2026-08-24, and fixed the same day by
`DETAIL_LOT_TIERS`: a detail district is built at six lot sizes and the board
draws whichever of them its *visible* lots fit a 19,000-lot budget at. Measured
with `scripts/cost-at.mjs` on the merged board, 34.05, -118.24:

| seek | before culling | after culling | after the ladder | lot | cap |
| --- | --- | --- | --- | --- | --- |
| 110 km | 341,803 | 310,841 | 314,265 | — | 400,000 |
| 13 km | — | **688,536** | **343,372** | 400 m | 400,000 |
| 12.5 km | — | **678,546** | **343,372** | 400 m | 400,000 |
| 11 km | — | **654,656** | **390,602** | 250 m | 400,000 |
| 9 km | — | **635,806** | **380,762** | 250 m | 400,000 |
| **7.7 km** | **773,282** | **604,332 / 621,866** | **375,196** | 250 m | 400,000 |
| 2.5 km | 761,648 | **415,780** | **345,340** | 200 m | 400,000 |
| 1.2 km | — | 311,564 (160 m either way) | 311,564 | 160 m | 400,000 |
| 0.9 km | — | 297,148 | 363,454 | 40 m | 400,000 |

The 604,332 in the 7.7 km row is the figure this section was written with; the
same pose re-measured on the day of the fix was 621,866, and both are the same
defect. **Six rows were over the cap and only one of them had ever been named.**
2.5 km was over at 415,780, and the whole band from about 9 km up to where
`DETAIL_STANDOFF_M` switches the metros off at 13.3 km was 635,806 to 688,536 —
the frustum there holds essentially the entire Southland. The 400 m rung on the
ladder exists for the top of that band; every other rung is under 260 m and
keeps its street lattice.

The 0.9 km row goes the other way, and is the near-field half of the same fault:
the board was drawing 11,306 lots at 160 m where it had room for 17,929 at 40 m.

**Frustum culling could not fix any of it.** At those stand-offs the lots are
genuinely inside the frustum: the Southland is a basin ninety kilometres across
and you are looking down the length of it. What was wrong was the *lot size*,
which was one number for a board that is no longer looked at from one distance.

### What is left

- **No budget cell stands at any of these poses.** This was the first item in the
  original list and it is still open: `performance-budget.mjs` measures
  `california-one` at `california-overview` and nowhere else, and a cap nobody
  measures is not a cap. The ladder is now the only thing holding these poses
  under 400,000 and nothing in CI would notice if it stopped.
- **The rung change pops.** Each rung is an independent survey — a different
  lattice, different addresses, different heights — so stepping from 250 m to
  200 m replaces the city rather than subdividing it. It lands during camera
  motion and a still board never re-lots, which is what makes it liveable, but
  it is visible if you are watching for it. A cross-fade is the obvious answer
  and it costs a draw call, which this board does not have.
- **The ladder costs about a second of boot.** `createBlocks` on the merged
  board went from 264 ms to 1,236 ms and its store from 4.7 MiB to 18.9 MiB,
  because every rung of every district is walked and kept. The live instance
  buffers got *smaller* — 59,166 instances to 19,000 — so this is boot and
  memory, not frame time. Building the fine rungs lazily, on the first pose that
  asks for one, would move most of it off the boot path.

## What the ground and lot work left open

Recorded 2026-08-24 after both tracks landed, from the integration analysis that
built the actual combination rather than adding up two reports.

**The board boots a second slower, and nobody has signed that off.** Measured,
three interleaved runs each, navigation to `boot.hidden === true`:

| build | boot | heap |
| --- | --- | --- |
| before both tracks | 1,994 / 1,998 / 2,341 ms | 36-37 MB |
| ground only | 2,029 / 2,048 / 2,260 ms | 36-38 MB |
| lots only | 2,932 / 3,003 / 3,238 ms | 47-49 MB |
| both | 3,011 / 3,079 / 3,368 ms | 48-49 MB |

The ground material's texture build is confirmed negligible — ground-only is
base. The whole of it is `createBlocks` walking every district once per rung of
the lot ladder it can reach. **The rungs should be built lazily**: a rung nothing
has asked for is a rung nobody has paid for, and the coarse-fill already means a
district can be missing a rung without breaking a frame. This is the single
biggest thing left on the merged board's boot.

**Triangle headroom at the worst pose is 2.3%, and it is held by one constant.**
`LOT_BUDGET = 19_000` bounds the *city* at 190,000 triangles. It does not bound
the frame, because the non-city cost — terrain, corridor, sky, ports, vessels —
is measured and not enforced, and it reaches 288,365 at LA from 110 km. The worst
combined pose anywhere in either metro's descent is 390,730 at LA 10.5-11.5 km.
`california-one-basin` now stands there, so CI will notice; but the next piece of
work that adds geometry anywhere on this board inherits **9,270 triangles** of
room at that pose, not the 184,000 that SF at 2.5 km suggests.

**Draw calls are effectively gone: 438 of 460 desktop, 434 of 455 mobile.** The
terrain chunk grid spent 17 and neither quality track spent any. Everything in
`CALIFORNIA.md` that adds an object — an airport, a port, a landmark group — is
now constrained by this and not by triangles. The content programme's own note
says draw calls are the scarce resource; they are scarcer than it says. Airports
and ports will have to batch into the existing material roles, or be gated by
stand-off the way metro detail already is.

**Los Angeles at 7.7 km looks coarser than it did**, because the ladder chose a
250 m rung to bring that pose from 621,866 under the cap. That is a real
trade — legality bought with appearance — and it is the correct one while the
alternative is a board 55% over its budget. It gets better when the boot cost
above is fixed and the budget can be spent where it is seen.

Two smaller things, both disclosed by their own tracks and neither blocking:

- **The ground grain reads a little like stucco at a close pose over flat
  ground.** It is isotropic fbm; real terrain has drainage, and an isotropic
  noise field cannot express a watershed. A flow-aligned or ridged term would,
  and would cost nothing extra to sample.
- **The Sierra escarpment's vertical striping is pre-existing and unfixed.** It
  is the 15x exaggeration turning 300 m terrain cells into near-vertical ribs.
  The detail map's slope correction correctly declines to add grain on top of it,
  which is why it neither helps nor hurts. The fix is finer cells there, which is
  terrain level of detail — the piece that still does not exist.

## ~~An unidentified 700 m slab stands on downtown San Francisco~~ — identified and capped

Found 2026-08-24 by photograph; named 2026-08-24 by reading instance matrices
on the merged board at the same pose, not by walking the scene graph.

It was never a mystery mesh. `DETAIL_LOT_TIERS`'s coarsest rung is 400 m, and
tower fill of 1.5–2.2 — written to assemble a Salesforce-shaped site from 40 m
lots — on a 400 m lot is a 600–880 m cube, rotated with `gridAngle`, downtown
palette. The same pose on `?city=sf` uses 40 m lots and does not show it.

The fill is now capped at `NEIGHBOURHOOD_LOT_METRES` (260 m). Fine rungs are
unchanged. `blocksLotLadder.test.ts` holds the cap.

The reproduction still photographs the pose:

```
node scripts/look.mjs slab --url "/" --lat 37.7897 --lng -122.3972 \
  --standoff 2500 --at 2026-08-24T18:30:00Z --wait 11000
```

What the original list correctly ruled out (marker, office glyph, landmark,
corridor, port, airport, office-exterior) stays ruled out. The glyph's window
pane thickness (`0.012` / `0.028` scene units → 23 m / 54 m on this board) is
a separate scene-unit leftover and is not this slab.
