# 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 only two 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. 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. ## Re-shoot the time-lapse films The 25 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.