1
0
This repository has been archived on 2026-08-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tera/TODO.md
T
karti acf4d1a510 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>
2026-08-22 23:35:09 -07:00

396 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.5042.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.** Every
`city: "california"` shot in `scripts/brand-assets/shots.mjs` was photographed
at 428 units across with a ruled crop along 38.05 N. Re-shoot with
`npm run refresh`. The `chapter` indices are all still correct — the north
chapter was added at the **end**, index 5, precisely so nothing re-points — but
`california-relief`'s caption says "428 units across at 1,919 metres to the
unit" and that number is now 554. The caption is the only edit needed.
- **`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 one seam is still coarse
Fog now follows camera altitude (`aerialReach` in `engine/atmosphere.ts`, §12.2).
`main.ts` recomputes the rig on the controls' `change` event, throttled at 2% of
the current altitude, which collapses a chapter flight to a few dozen
recomputations instead of sixty a second.
What that does **not** cover is a camera moved by something other than
`OrbitControls`: the drive, actor and aircraft follow-cameras set the pose
directly each frame and do not fire `change`. Those poses fall back to the
one-hertz clock tick, so a fast descent under a follow camera steps the fog up
to four times instead of easing it. It is not visible on the boards that exist —
the follow cameras sit low and stay there — but a continuous state-to-city
descent would need this on the frame loop rather than on an event.
### 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.
## Re-shoot the time-lapse films
All 38 product stills were re-shot on 2026-08-22 against the night-infrastructure
work, so the manifest records `SHOTS_COMMIT b7f5c41` with **`SHOTS_DIRTY true`** —
that tree was not committed when the shutter opened, and the manifest says so
rather than naming a commit that would render something else. Re-run
`shots.mjs --manifest-only` once it is committed. The two **share cards** were not
re-shot and did not need to be: `capture.mjs` shoots both at 11:20 and 17:30, so
nothing that changed after dark is in them. **The films were not** — they were
last shot on 2026-08-07 at `9c9e78f`. `films.ts` in lumbridge-v4 records the tera
commit each reel was shot at, so a stale reel is visually stale and no caption edit
fixes it — check that commit against tera HEAD before assuming a reel is current.
What the reels predate: ACES tone mapping and the runtime PMREM environment rig; a
sea that reflects, with a swell normal map, Fresnel and sun glitter, in place of a
flat `MeshLambertMaterial` that had no specular term at all; a world-space sky dome
replacing the screen-space gradient, which at dusk painted the sunset band along the
*bottom* of the picture, under the board; terrain that casts shadows; a California
board with the Basin and Range, Death Valley, the Salton Sea and San Diego on it;
SFO, LAX, both bridges and a freeway that reads as a road; aircraft that move,
carrying registration and type; a rebuilt LA studio; the studio device hardware; and
a new UI chrome with a first-run flow.
```sh
npm run refresh -- --films-only --rough # rough cut first: ~20 s a reel, and it is
# the cheapest way to find out a camera is
# pointed at the wrong thing
npm run refresh -- --films-only # ~90 s per 180-frame film on the Radeon
```
Worth knowing before starting (the rest is in `~/.claude/skills/tera-capture`):
- Check the run prints the Radeon and not SwiftShader. The software path is ~15x
slower — 22 minutes a film rather than 90 seconds — and it reports success.
- `hero-soma` is a different animal: `chrome: "bare"`, midnight to midnight, and a
two-stage encode (a CRF 18 master, then 1440w CRF 28 and 960w CRF 30 delivered).
- **The hero camera is worth reconsidering.** Whole-board was tried and rejected
because the edge of the terrain plate showed against the sky. That seam no longer
exists: the sea now runs eighteen board spans and ends past the fog's far plane.
- After tuning the hero, run `npm run check:hero` in lumbridge-v4.
- `#onboarding-host` is in `FURNITURE.CLUTTER` now, and `#mode-dock` and
`#play-hud` are in `FURNITURE.BARE`. Every *new* overlay has to be added to one
of those two lists on the day it lands, because a frame with a stray card in it
still renders and still looks deliberate. The mode dock proved it: it shipped
inside the tera share card for a fortnight and nobody noticed.
- **The two capture scripts both serve on port 5210.** Two of them at once is
`EADDRINUSE` a minute into a run, so do not start a film while a stills pass is
going — or give one of them `--site`-style isolation first.
- Commit tera first, then re-run, so the manifest records a clean sha.
## 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.