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 b7f5c41da5 feat(brand): a shot can take a body, so the last hand-made stills are generated
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>
2026-08-22 13:30:23 -07:00

11 KiB
Raw Blame History

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

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.

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 443456 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.