1
0

feat: the state becomes California, and the port fills with ships

**Stage 1 of one California.** The owner's complaint had two halves and this is
the first: the state board was a CROPPED SLAB. `california.ts` stopped at 38.05 N,
so the board disagreed with its own minimap about the shape of California in a
single frame, and Bug Fire's 93,733 acres burned off-frame while the panel said
all clear. Bounds now run 32.50-42.05 N / -124.50 to -114.0 W — Cape Mendocino,
the ruled Oregon parallel, the 120th-meridian corner into the Nevada diagonal.

**And it got cheaper.** 391,169 triangles to 375,351, while gaining the North
Coast, the Sacramento Valley, the Klamath knot, the Cascade arc, Shasta at 4,320 m
and Lassen at 3,190 m. Extending the bounds alone would have doubled the lattice
to 168,813 points and blown the mobile cap; coarsening cellLat 0.022 -> 0.0312 and
cellLng 0.027 -> 0.0383 holds it at ~83,800. The cell as a FRACTION of the board
moves 0.0030 -> 0.0033 — unchanged in frame — because the camera retreats to frame
whatever it is given. That argument was already written in the pack's own comment.

The second half — three boards becoming one world you zoom through — is NOT here.
Merging at Bay density would be 34.04M triangles, 13x the highest budget, and
merging at SoCal density would downgrade San Francisco from 40 m lots to 164 m.
Both delete the board every marketing still is shot from. `sf.ts` and `socal.ts`
are untouched by design.

**Aerial perspective, which the state board could not have had before.** The old
fog started at 1.15 board spans = 944 km, on a board whose longest diagonal is
820 km — so no pixel could ever be fogged. Fog now responds to camera altitude,
clamped to the authored pair as a ceiling.

`Atmosphere.aerial(env, view)` is a second pure method returning `{ near, far }`
and **deliberately no colour**. That is structural, not stylistic: it is why a
future camera-dependent term cannot reach `environmentKey()`'s colour fingerprint
and start rebuilding the PMREM cubemap on every camera step. Coarsening the
fingerprint instead would have hidden one instance and armed the mechanism. A
mutation-tested seam guard fails if anyone merges the two paths back together.

**The port.** Terminal Island rendered as a bare tan polygon with generic white
blocks while the chapter text called it the busiest port complex in the
hemisphere. Now six container yards drawn as canvas atlases, 56 gantry cranes at
varied boom angles, the 13 km San Pedro breakwater, the dredged channel. Five
buckets merging ACROSS ports the way airports.ts merges across fields, so a
second complex costs no extra draws: +11 draws and +4,377 triangles for all of it.

At vertical exaggeration 3.4 a 130 m gantry is 1.132 units tall against a 400 m
ship's 1.024 long — the crane is the taller object, and it is what makes a port
read as a port from altitude.

**Ships, and the wake carries the information.** Moored hulls have no foam,
verified at three terminals; a tug under way in the Main Channel trails a clean
Kelvin V. One hull geometry, one InstancedMesh, orientation from the BERTH rather
than the wire. The AIS gate strips sog 102.3, heading 511 and cog 360 — all mean
"not available" — with an explicit test that cog 358.7 SURVIVES, because a naive
range check on cog eats real headings near north.

"Empty or full" is not in AIS position reports and is not invented per ship. The
honest answer is at port level and is a better story: 348,691 of 460,467 boxes
left Los Angeles empty in July 2026, corroborated by FBX01 $7,491 inbound against
FBX02 $347 outbound.

**Radar and birds ship dark, and say why.** California is 0.47% wet and migration
is nocturnal and seasonal, so both layers have nothing to say on most days. The
panel reads "No radar feed is configured, so this board draws no weather. That is
a fact about this box, not about the sky."

Also recorded, and it matters beyond this commit: **the GPU on amd-server never
leaves 500 MHz of a possible 2725**, traced across 80 seconds of sustained load.
`bay-area/desktop` is fragment-bound at that clock and sits on the vsync deadline,
so a trivial change in fragment work flips it between 16.8 and 33.3 with geometry
identical to the digit. Every frame-time number measured on this box is a floor.
Two investigations reached two different wrong conclusions from single-run
comparisons before this was traced. Geometry is the gate; frame time is advisory.
No cap was raised.

Tests 1,340 -> 1,540, server 280 -> 295.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 23:35:09 -07:00
parent b25f217e3e
commit acf4d1a510
52 changed files with 14436 additions and 142 deletions
+259
View File
@@ -704,3 +704,262 @@ entered from California than from the Southland, because the two rectangles
contain different fires. `smokeCaption`'s sentence is worded for exactly that —
"drawn from the fires currently on the board" — and the alternative, a statewide
query per building, is a second source of truth about the same sky.
---
## 12. The state board is the state, and what that cost
The default board — the first frame an anonymous visitor sees — used to stop at
38.05 N. The minimap in the corner of the same frame drew the whole of
California from the same pack, so one screenshot contained a picture of the
state and a picture of two thirds of the state, disagreeing about the shape of
the one silhouette in this product that everybody already knows.
`bounds` is now 32.5042.05 N and -124.50 to -114.0 W: the Mexican border, the
Oregon line, Cape Mendocino with eight kilometres of ocean west of it, and the
Colorado past Parker. All four edges are the state's own.
### 12.1 The price was paid in the cell, not in the bounds
Extending north and west at the old 0.022° × 0.027° spacing takes the lattice
from **84,924 points to 168,813** — 2.02× — and the terrain mesh with it,
against a mobile budget that had 70,000 triangles spare and two more layers
landing on the same board in the same round. Extending the bounds alone does not
fit and was never going to.
Coarsening the cell by 1.42× in each axis puts it back. The argument is the one
`california.ts` already made when the board grew east and it is worth stating
once more here, because it is counterintuitive: **what the eye sees is the cell
as a fraction of the board, not the cell in metres**, because the camera
retreats to frame whatever it is given.
| board | cell | span | cell / span |
| --- | --- | --- | --- |
| the corridor, before the east | 0.020° | 284 u | 0.0041 |
| the southern two thirds | 0.022° | 428 u | 0.0030 |
| the whole state | 0.0312° | 554 u | 0.0033 |
The ground cell went from 2,449 m to 3,473 m — 42% coarser on the earth — and
*finer* in the frame than the board two revisions ago. Measured after: 85,008
lattice points against 84,924, and 71,876 terrain triangles against 87,074.
`verticalExaggeration` went 13 → 15 in the same change, and for the same kind of
reason. Relief in the frame is peak-units over board *span*: at 13 the extended
board is 6.4% where the old one was 8.3%, and 15 puts it at 7.4%, which is
Southern California's 7.5% almost exactly. Buildings share the exaggeration and
get *smaller* in frame, not larger — a 250 m tower is 0.0035 of a span where it
was 0.0040 — so the "bed of nails" objection the old comment raises about
pushing to 20 does not apply at 15 on a board this size.
### 12.2 Fog is a function of camera altitude. Almost nothing else became metric
An investigation proposed converting every span-derived constant in the engine
to physical metres, on the grounds that the three boards disagree by 8× once
converted and San Francisco is the one that is right. **The diagnosis is
correct and the prescription is not**, and the arithmetic is worth keeping:
86 km of clear-day visibility is 0.91 spans on the Bay Area's 1,003-unit board
and **0.085 spans on the extended state board**, where the camera orbits out to
1,108 units. Literal metre fog buries California in haze 47 units from the lens
and there is no pose on that board from which the state is visible at all.
So exactly two things changed, and the rule for deciding which is stated here so
the next person does not have to re-derive it:
- **Fog near and far** are now a function of camera altitude —
`aerialReach()` in `atmosphere.ts` — clamped so the pair the board was
authored with is the ceiling and is never exceeded. It saturates at any
whole-board pose, which is what leaves every existing marketing still
untouched *by construction rather than by measurement*: the Bay Area's opening
chapter sits 40.6 km up and the curve reaches its ceiling by 6.7 km.
- **Cloud base altitude** was checked and did not move. It was already
`BASE_ALTITUDE_M = 1350` passed through `world.metres()`, which is a physical
metre constant respecting each pack's own exaggeration. Nothing to convert.
Everything else stays a span, and the distinction is not "physical vs lazy" — it
is **whether the constant describes the air or describes the composition**:
| constant | stays a span, because |
| --- | --- |
| `clouds.ts` `TILE_SPANS` (2.2) and the 14×14 cell layout | tuned over 190 photographed frames; a cell-ownership chain, not a distance |
| `satellites.ts` dome radius | the file says outright that it is arbitrary — it is where the dome looks right |
| `fires.ts` `MARK_SPAN_FRACTION` | a glyph sized to be legible, like the aeroplane's 0.42-unit clamp |
| `terrain.ts` `SWELL_TILE_SPANS` (1/34) | a physically-scaled sea has no visible surface at all; the file argues it at length |
| camera `far`, `shadowExtent`, orbit limits, minimap layout | properties of a board, not of an atmosphere |
The one number a physical model got wrong on its own is worth recording, because
it was found by taking a photograph rather than by thinking. A camera parked
223 km from a mountain and 8 km above the ground is, physically, looking at
something it cannot see — the air supports 231 km and the subject is at 223 of
it — and the frame came back as a white rectangle with a faint cone in it. A
board is a **map**, and a map is looked at from outside the atmosphere it
depicts, which is the same argument `minVisibilityM` was already making. So the
fog is floored at **6× the camera's own stand-off** (`AERIAL_SUBJECT_CLEARANCE`),
which with `main.ts`'s 1.15/3.9 ratio puts the near plane at 1.77 stand-offs:
nothing closer than about 1.8× the camera's distance to its subject is fogged at
all, and everything past that hazes out toward six.
Six rather than four is set by one measurement, not by taste, and it is what
makes "leaves the existing boards untouched by construction" true rather than
approximately true. California's opening pose is 67.6 km up and Southern
California's 17.0 km, so both saturate on the air term alone. The Bay Area's is
11.3 km up against a 12.4 km saturation altitude — 324 km of air against 369 km
of authored reach — and it is the stand-off term that carries it over. At a
clearance of 2.4 that board renders at **88%** of its authored reach and the far
corner picks up haze it has never had, which would have been a silent change to
every still shot from the board that carries most of the product's imagery.
`aerialPerspective.test.ts` asserts all three against the deployed formula, not
against `cityDaylight`'s narrower one, because the narrower one is not what any
board on the page uses.
### 12.3 What the north is made of
The added land is authored, not permitted. Hand-traced by house rule (§3.2): the
North Coast from Bodega Head to the Oregon line with Cape Mendocino at -124.41,
the 120th meridian and the corner at Lake Tahoe, the Klamath knot as four chains
at four bearings because the Klamaths genuinely do not run north-south, the
southern Cascades, the northern Sierra tapering out into the Cascade arc, the
Warner Mountains alone in the north-east corner, and the Sacramento Valley as a
chain rather than a circle.
Two failures were made and photographed on the way, both of them re-runs of
mistakes this pack already documents:
- **The northern Sierra shipped as a row of separate domes.** Six hand-placed
bells 0.35° apart on a 0.240.26 radius is 1.4 radii, and a chain only reads
as a ridge below about 0.9. `ridge()` computes the spacing; the peaks that
are written by hand are the ones somebody has checked.
- **Mount Shasta rendered as a 4,320 m cone of pine.** `groundColor` answers
`inPark` **first**, so a park envelope paints its ground green at any altitude
and the `alpine` stop is unreachable inside one. The Cascade forest is now two
polygons with the mountain in the gap between them — which is also true on the
ground, since the Shasta Valley west of the cone is treeless grassland.
No new inland water. Clear Lake, Shasta Lake and Lake Tahoe are all legible at
this scale and all three were rejected for the same reason: `createWater` puts
every `inlandWater` polygon at y = 0.05, which is sea level, and those three sit
at 400 m, 320 m and 1,897 m. At an exaggeration of 15 a lake plate at sea level
under ground standing 3 to 15 units above it is a hole, not a lake. The Salton
Sea works because it really is below sea level. Each polygon is also its own
draw call, and the California mobile cell is the tightest in the project.
## 13. Four layers, one seam, and two gates each
The port kit, the ships, the reflectivity raster and the migration field arrived
as four independent workstreams and reach the board through one seam:
`SceneOptions` carries four optional factories — `ports`, `vessels`, `precip`,
`migration` — and `createScene` builds, lights, ticks and disposes each of them
exactly as it does `fires`. Every one of those five touch points is a line
somebody can delete without breaking a compile, so
`src/test/integration/layerSeams.test.ts` asserts all five from the source, at
both ends of the seam: the factory is supplied by `main.ts` and it is consumed by
`scene.ts`.
**A layer that is not supplied costs nothing.** Not "draws nothing" — is never
visited: no group, no material, no geometry, no draw call. That is the whole
reason the slots are optional rather than a `visible` flag, and it is what the
budget shows. The California mobile cell measures **370 draws and 373,825
triangles** with the two sky layers wired and no feed behind them, which is the
number it measured before they existed.
### 13.1 The two gates
Each layer answers two questions, and they are different questions:
| Layer | Board gate | Deployment gate |
|---|---|---|
| `ports` | the pack declares `city.ports` | none — a quay is authored, not fetched |
| `vessels` | the pack declares `city.ports` | none this round; the hulls are modelled |
| `precip` | `boardCarriesRaster(bounds)` | `/health` `sources.radar` |
| `migration` | `boardCarriesRaster(bounds)` | `/health` `sources.birds` |
The **board** gate is geometry. `boardCarriesRaster` asks whether a rectangle can
be described by a quarter-degree cell — twelve cells on the short side, which is
three degrees — and it answers true for California (9.55° × 10.50°) and false for
the Southland (1.08° × 1.66°) and the Bay (0.85° × 0.89°). That is the same
answer an `id === "california"` would have given and it is a fact about the map
rather than about a name. It gates the birds as well as the rain because both
feeds are statewide instruments quantised to the same cell: NEXRAD's composite
and BirdCast's fifty-eight counties. A metro board would show three county discs
and part of a fourth, which is a picture of the sampling grid rather than of the
migration.
The **deployment** gate is `access.feeds`, read once from `/health`'s `sources`
block at boot — the same field `drawsFire` reads. `sources.radar` and
`sources.birds` are published by `server/src/routes/health.ts` off the config, so
"why is there no rain on this board" is answered by the box rather than by its
env file. Both are **optional** on `Feeds`, unlike every other feed there, because
they are newer than some servers this client will meet and `undefined` has to
mean *do not ask*.
### 13.2 The quiet day is the designed case
This repo's default is `none` for radar and `none` for birds, and no AIS licence
has been read, so **the empty state is what nearly every visitor sees**. It is
never blank. Each gate owns a sentence, each sentence is always complete, and
each says what is being withheld as well as what is being shown:
- no feed at all — *"No radar feed is configured, so this board draws no
weather. That is a fact about this box, not about the sky."*
- a feed that answered with nothing — *"Nothing is falling on this board. All 16
radars reporting. Last scan 5 minutes old."*
- an instrument that does not measure in daylight — *"Nothing is aloft. BirdCast
measures migration only after dark. Last night 393,290 birds crossed
California heading south-east…"*
- a harbour with modelled hulls — *"…19 hulls: 6 making way, 13 at rest, 16
alongside a berth. No hull is labelled laden or in ballast: that is a port
figure, not a ship one."*
They are printed into `#sea-section` and `#sky-section`, which are `index.html`
elements written by `main.ts` rather than by `mount.ts` — the arrangement
`#fire-section` and `#presence-host` already have, and for the same reason: the
content is a *board's* instrument reading, it is rebuilt per board, and the
section is hidden outright on a board that has neither a harbour nor a statewide
raster. Both sit below **Go**, so the studio's own call to action stays above the
fold on a phone.
### 13.3 The harbour is refreshed as a new fix, never nudged
`modelHarbour` stands in for a feed this deployment does not have, and the feed's
shape sets the cadence. Upstream listens for thirty seconds every fifteen
minutes; a hull under way moves about five kilometres between two reports;
`engine/vessels.ts` dead-reckons along the reported course for exactly 900
seconds and then stops. `main.ts` rebuilds the modelled body on that same
interval off the once-a-minute clock, which is not a smoothing trick — it is the
feed's own behaviour, and the small jump a moving hull makes when a new body
lands is the jump a real fix makes. Nothing anywhere splines between two fixes,
because the chord between them is not a path anything took.
Two clocks, deliberately different:
- the **modelled harbour** is built for `currentInstant()`, so a scrubbed sky and
the ships under it describe the same moment, and `promoteVessels` is handed the
same number so the body's age is what it really is: zero;
- the **sky promotions** take `Date.now()`, because `fetchedAt` is a real
timestamp and "last scan 5 minutes old" has to stay true when somebody drags
the clock to midnight.
The migration layer is given `env.sun.elevation` — the same number the rig is
lit by — rather than an ephemeris of its own. `LightingState.hemisphere.intensity`
is **not** a day/night signal: it measures 1.33 at 21° below the horizon against
0.95 at noon, because `atmosphere.ts` raises the fill to compensate a moonlit
scene. A layer that read it that way drew nothing at midnight while passing a
test written against invented numbers.
### 13.4 What the boards cost after wiring
Measured on this box, against the pre-round baseline:
| Cell | Before | After | Cap |
|---|---|---|---|
| california · mobile | 368 draws / 389,843 tri | 370 / 373,825 | 415 / 430,000 |
| socal · desktop | 205 / 1,417,648 | 216 / 1,422,025 | 320 / 1,700,000 |
| socal · mobile | 137 / 765,596 | 148 / 771,797 | 170 / 900,000 |
| bay-area · desktop | 206 / 2,264,956 | 209 / 2,264,956 | 320 / 2,600,000 |
The whole port complex and its fleet — two ports, a thirteen-kilometre
breakwater, six container yards, fifty-six gantries and nineteen hulls with
wakes — costs **eleven draw calls and four to six thousand triangles**, and the
same eleven on both viewports. That is what
instancing across buckets buys, and it is the `airports.ts` argument repeated:
the draw count is a function of how many *kinds* of surface exist, not of how
many terminals.