b7f5c41da5
Three pictures on /arena — the chase camera behind the EV, the crow in flight, the
aircraft over the board — were the only imagery on the site with no generator
script anywhere in either repo. They were made by hand, and so they were a single
daylight frame served to dark-mode readers too, with no pair to choose from.
`shots.mjs` gains a `play` capability beside `aim`: a shot names a control mode,
the script clicks the app's own mode dock, and only then shoots — with the HUD
deliberately left in frame, because on these three the body and its readout ARE
the subject. `#mode-dock` and `#play-hud` are in FURNITURE.BARE rather than
CLUTTER for that reason, and there is a comment at the `hide()` call so a later
tidy-up does not crop the subject out.
Three guards, each verified by deliberately making it fire:
- the mode must be OFFERED, not merely present — asking for `drive` on the Bay
Area board now says `"drive" is not offered here — the dock is showing
[overview, actor]`;
- `aria-pressed="true"` AND `#play-hud` unhidden, the same pair
`performance-budget.mjs` waits on;
- the HUD's own body chip, so asking for `Explore` when an anonymous visitor
gets a crow says `the play HUD says "Crow", not "Explore"`.
The body guard runs twice, at the click and again at the shutter, because a
completed route or a bound ends a mode mid-wait and hands back an orbit wide shot
under the right filename.
`untilProgress` exists because the first delivery drifted: `run: 9000` gave 55% of
the leg in a 1x preview and 69% in the 2x render — a different stretch of road,
with a town's edge across the top of the frame. Road traffic runs from page load,
so a duration is really "boot plus a bit". Waiting on the app's own route
percentage lands preview and delivery on the same frame; overshoot throws rather
than shooting. The crow and the aircraft are built inert, so a duration is exact
for them and only the car needed the gate — measured, not assumed.
The run log now echoes the HUD (`[Drive] 56 mph · US-101 | assisted · 55% ·
chase`), because "shot drive-101-day" is equally true of a parked car, a finished
route and a correct frame.
The crow moved from the state board to the Bay Area. On California a one-metre
bird stands against ground drawn at 1,919 m to the unit with an oak the size of a
hill behind it, and the night frame would have been a black rectangle with a bird
in it. `crow-nav-v1`'s `source` said "Tera / California" and that was wrong on the
environment's own terms as well — `crowNav.ts` runs a ±120 m box with no terrain
at all.
One frame is honestly weak and is recorded rather than dressed up: `drive-101`
at night. From behind, on an unlit corridor, the EV is two tail lamps and a roof
strip. Four hours were shot between 21:15 and 20:05 and the corridor's ground is
equally black at all of them, so the hour is not the lever; both real fixes are in
the engine and are in TODO.md. It is still the honest night frame.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
194 lines
11 KiB
Markdown
194 lines
11 KiB
Markdown
# Open work on Tera
|
||
|
||
Short, and only things that are decided but not done. Anything speculative belongs
|
||
in an issue, not here.
|
||
|
||
## 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, and it is the product not the capture
|
||
|
||
Canvas-region mean luminance, 1600w: `crow-nav` 23.2, `california-flight` 13.6,
|
||
`drive-101` **7.8** — against `fidi` 16.0, `golden-gate` 8.2 and the `bay-bridge`
|
||
frame this file already calls out, 8.3. Nothing in it needs a lift to be legible —
|
||
the road, the markings, the panel, the HUD and the dock all read at native
|
||
exposure — but the *subject* nearly disappears: seen from behind on an unlit
|
||
corridor the EV is two tail lamps and a roof strip, and the hour is not the lever.
|
||
Shot at 21:15, 20:45, 20:20 and 20:05 (sun −15.7° to −3.1°) the corridor's ground
|
||
is equally black, and by 20:05 the plan view has flipped to its daylight styling
|
||
while the scene has not, which is worse. The two candidate fixes are both in the
|
||
engine: give the EV a headlamp spill on the road ahead, or let the corridor's
|
||
terrain keep some moonlight the way the city boards' ground does.
|
||
|
||
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.
|
||
|
||
## Re-shoot the time-lapse films
|
||
|
||
The 28 product stills and both share cards were re-shot on 2026-08-22 at the
|
||
airports-and-bridges commit and are current. **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 Bay Area board drops a frame in twenty, on desktop only
|
||
|
||
`bay-area.desktop.p95FrameIntervalMs` carries a **33.4 ms allowance and that is a
|
||
recorded defect, not a target.** The board renders a median frame in 16.7 ms and
|
||
drops roughly one frame in twenty: p50 16.7, p95 33.3, and 443–456 frame samples
|
||
in a window where every other cell returns 480.
|
||
|
||
It is desktop-only, and it is **not fill rate**: the mobile cell runs the *same*
|
||
2.26 M triangles at a comparable pixel count — 1.32 MP against desktop's 1.30 —
|
||
and holds 16.7 ms flat. The obvious suspect is the shadow map, which `stage.ts`
|
||
sizes **2048 on desktop and 1024 on handheld**, over what is now the heaviest
|
||
shadow-casting scene in the product.
|
||
|
||
**It is not a regression, and this was checked rather than assumed.** Measured at
|
||
the commit before the airports and bridges landed, with the same harness:
|
||
p95 33.3, p50 16.7, 443 samples, **2,771,606 triangles**. After that work:
|
||
p95 33.3, p50 16.7, 456 samples, **2,265,056 triangles** — the board got
|
||
506,550 triangles *lighter* while gaining SFO, both bridges and a surfaced
|
||
freeway. The stutter was simply invisible until `bay-area` became a measured
|
||
cell, which it had never been.
|
||
|
||
The allowance is there so the cell still guards the numbers that are healthy —
|
||
triangles, draw calls, and the mobile frame time — rather than sitting
|
||
permanently red and therefore permanently ignored. **Fix the stutter and put the
|
||
cap back to 16.7.** Start with the desktop shadow-map size and the shadow
|
||
frustum over the SF board; a 2048 map over 2.26 M triangles of casters is the
|
||
first thing to rule in or out.
|
||
|
||
## 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.
|