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>
This commit is contained in:
@@ -21,7 +21,7 @@ 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
|
||||
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
|
||||
@@ -29,15 +29,77 @@ that otherwise produce confident wrong output:
|
||||
- **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 25 product stills and both share cards were re-shot on 2026-08-22 at the
|
||||
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
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
* So: one command, and the parts you can skip are flags rather than the memory
|
||||
* of which script does what.
|
||||
*
|
||||
* ### The two failures this must never report as success
|
||||
* ### The three failures this must never report as success
|
||||
*
|
||||
* Both of them produce output that looks deliberate, which is why they are
|
||||
* All three produce output that looks deliberate, which is why they are
|
||||
* checked here rather than left to the eye:
|
||||
*
|
||||
* 1. **SwiftShader.** `launch()` in `harness.mjs` asks for `--use-angle=vulkan`
|
||||
@@ -46,6 +46,14 @@
|
||||
* surfaces it as its own headline rather than as one line of a stack trace
|
||||
* fifty lines up the log.
|
||||
*
|
||||
* 3. **A `play` guard firing.** Three stills take control of a body before the
|
||||
* shutter opens — the EV on US-101, the crow over the Bay, the aircraft on the
|
||||
* corridor — and `enterPlay()` in `shots.mjs` refuses to shoot when the dock
|
||||
* did not hand over the mode, when the HUD names a different body, or when the
|
||||
* route ran out during the wait. Every one of those otherwise delivers an orbit
|
||||
* wide shot under a filename that says an agent is driving. Same treatment as
|
||||
* the chapter guard, and for the same reason: re-running cannot fix it.
|
||||
*
|
||||
* ### It re-stamps the provenance manifest, narrowly
|
||||
*
|
||||
* `PROVENANCE.json` records a SHA-256 for every tracked binary and
|
||||
@@ -154,6 +162,19 @@ async function fingerprint() {
|
||||
const SOFTWARE_RENDERER = /GPU:.*(SwiftShader|llvmpipe)|no GPU \(/i;
|
||||
/** `chapter()` in `shots.mjs`, and the office picker's equivalent beside it. */
|
||||
const PACK_REORDERED = /a city pack was reordered|the OFFICES table\s*\n?\s*changed|is "[^"]*", not "/;
|
||||
/**
|
||||
* `enterPlay()` in `shots.mjs` refusing to shoot a body it did not get.
|
||||
*
|
||||
* The third headline, and here for the same reason the second one is: three of
|
||||
* the stills are pictures of an agent driving, flying and gliding, and every way
|
||||
* that can go wrong produces a frame that still renders, still looks deliberate
|
||||
* and is of the wrong thing — an overview wide shot with `drive-101-day` written
|
||||
* on it. Re-running does not fix any of them. Either the dock stopped offering
|
||||
* the mode, or the body is not the one the caption describes, or the leg ran out
|
||||
* from under the wait.
|
||||
*/
|
||||
const BODY_REFUSED =
|
||||
/is not offered here|the mode dock has no|the dock did not take|the play HUD says|did not survive the wait|was already \d+% done|carries no percentage/;
|
||||
|
||||
class StepFailure extends Error {
|
||||
constructor(step, headline, detail) {
|
||||
@@ -217,6 +238,23 @@ function run(step, command, args, { cwd = ROOT } = {}) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (code !== 0 && BODY_REFUSED.test(log)) {
|
||||
rejectRun(
|
||||
new StepFailure(
|
||||
step,
|
||||
"a play guard fired — a shot did not get the body it asked for",
|
||||
"Three stills take control of something before the shutter opens: the EV on US-101, the\n" +
|
||||
"crow over the Bay, the aircraft on the California corridor. Each asks the mode dock for a\n" +
|
||||
"mode, waits for `aria-pressed` and the play HUD, and asserts which body the HUD names.\n" +
|
||||
"One of those checks refused, and re-running will not fix it: the mode is unavailable on\n" +
|
||||
"that board, or the body changed, or the route ran out during the wait. A frame shot past\n" +
|
||||
"any of those is an orbit wide shot with the right filename.\n" +
|
||||
"`node scripts/brand-assets/shots.mjs --list` prints what each shot asks for. Do not\n" +
|
||||
"weaken the guard.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (code !== 0) {
|
||||
rejectRun(new StepFailure(step, `${step} failed (exit ${code})`, "The child's own output is above."));
|
||||
return;
|
||||
|
||||
@@ -83,6 +83,12 @@ const ROOT = join(HERE, "..", "..");
|
||||
* See the long note beside `aimAt` for how each number reaches the engine, and
|
||||
* for why the chapter under an aimed shot is chosen so that the blurb still in
|
||||
* the panel is true of the frame.
|
||||
*
|
||||
* `play` is the third camera, and the only one that is not a camera at all: it
|
||||
* asks the app for a **body**. The three shots that carry it are pictures of an
|
||||
* agent doing something — the chase camera behind the EV, the crow in the air,
|
||||
* the aircraft on the corridor — and in those three the play HUD is the subject
|
||||
* rather than clutter. See the long note beside `enterPlay`.
|
||||
*/
|
||||
const SHOTS = [
|
||||
{
|
||||
@@ -342,6 +348,129 @@ const SHOTS = [
|
||||
alt: "The same airport at night, its dark field surrounded by the lights of the Los Angeles basin running to the horizon.",
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ---- The three that take a body ----------------------------------------
|
||||
*
|
||||
* Everything above is a camera looking at the world. These three are the
|
||||
* world being *driven*, and they exist because /arena needs a picture of each
|
||||
* spatial environment actually running: `drive-101-v1`, `crow-nav-v1` and
|
||||
* `california-flight-v1` are scored against Tera's vehicle, actor and
|
||||
* aircraft controllers, and the play HUD across the top of each frame is the
|
||||
* observation those environments are built out of.
|
||||
*
|
||||
* They replace three hand-made screenshots. Those were the last pictures on
|
||||
* lumbridgecorp.com that no script could reproduce, which meant they were also
|
||||
* the last pictures nobody could re-shoot when the engine moved: all three
|
||||
* still showed a left column with "Explore as your crow →" in it, a "FULL
|
||||
* VIEW" badge no visitor to the site ever sees, and a single daylight frame
|
||||
* served to dark-mode readers as well. Being a script is the point of them.
|
||||
*/
|
||||
{
|
||||
id: "drive-101",
|
||||
door: "tera",
|
||||
city: "california",
|
||||
chapter: 1,
|
||||
expect: "101",
|
||||
/*
|
||||
* A place on the leg, not a number of seconds, and `freeway`'s `settle: 3000`
|
||||
* next door is the argument for it: the traffic simulation has been running
|
||||
* since page load, so a duration is only ever "boot plus a bit" and boot is
|
||||
* not a constant. The same nine seconds put this car at 55% of the leg in a
|
||||
* 1x preview and 69% in the 2x render, which is a different stretch of road
|
||||
* — the second one had the edge of a town across the top of the frame, where
|
||||
* buildings drawn at 1,919 m to the unit stand beside a road built at one.
|
||||
*
|
||||
* 55% is open oak country with both carriageways in the frame and other
|
||||
* traffic on them, which is the picture: an agent alone on a road is not
|
||||
* being asked anything.
|
||||
*/
|
||||
play: { mode: "drive", expect: "Drive", untilProgress: 0.55 },
|
||||
place: "US-101, at the wheel",
|
||||
caption:
|
||||
"Taking the body is an input the app already accepts: the Drive button on the dock hands the vehicle controller the held keys and the follow camera, and the strip across the top is that controller reporting itself — speed, which road, whether the assist or a person is steering, how far along the leg, which camera. It is the same state Arena's drive-101 environment builds its observation out of, drawn for a human rather than serialised for a policy, which is the whole claim these pictures exist to make: the environment is this, running, and not a diagram of it. The unbadged Lumbridge EV is on the asphalt of the one corridor built at a metre to the metre, on a board that is otherwise 1,919 metres to the unit.",
|
||||
day: {
|
||||
at: "2026-08-06T09:00:00-07:00",
|
||||
note: "Nine in the morning, the sun across the carriageway rather than down it, so the median and the shoulders keep an edge under the car. Everything on the readout is the simulation's own number — 56 mph is what the controller is doing, not a figure somebody typed into a mock-up.",
|
||||
alt: "A chase camera above and behind a dark car driving a divided freeway through dry oak country, another vehicle further down the same carriageway, and a heads-up strip across the top of the frame reading its speed, the road name, the assist state and the route progress.",
|
||||
},
|
||||
night: {
|
||||
at: "2026-08-06T21:15:00-07:00",
|
||||
note: "Quarter past nine, and this corridor at night is what a car on an unlit road actually is: two tail lamps, the markings taking the light back, and a second set of lamps up the carriageway. Nothing here is lit that would not be — the hills either side of US-101 have nothing on them to switch on, which is exactly the argument for shooting the crow over a city instead.",
|
||||
alt: "The same chase camera at night, the car reduced to its tail lamps with the lane markings and edge lines catching what light there is against black hills, and the same heads-up strip along the top.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "crow-nav",
|
||||
door: "tera",
|
||||
city: "sf",
|
||||
chapter: 0,
|
||||
expect: "Whole Board",
|
||||
/*
|
||||
* The Bay Area board rather than California, which is where the hand-made
|
||||
* original was taken. Two reasons, and the second is the one that decides it.
|
||||
*
|
||||
* The crow is authored at about a metre. On the state board a metre is
|
||||
* 0.0005 of a scene unit and the frame around it is bare ground drawn at
|
||||
* 1,919 m to the unit, so the original is a bird in front of a featureless
|
||||
* sand-coloured wall with one oak the size of a hill behind it — the same
|
||||
* scale mismatch the `freeway` note warns about, and it reads as a rendering
|
||||
* fault. On the Bay Area board a metre is a hundredth of a unit and there is
|
||||
* a city under the wings.
|
||||
*
|
||||
* And the night frame has to stand on its own. It is served to every
|
||||
* dark-mode reader on the page and it is the only frame they get; a crow
|
||||
* over unlit valley floor at half past nine is a black rectangle with a bird
|
||||
* in it. Over San Francisco the ground carries itself.
|
||||
*/
|
||||
play: { mode: "actor", expect: "Crow", run: 5000 },
|
||||
place: "Over San Francisco, as a crow",
|
||||
caption:
|
||||
"The same dock, a different body. Explore hands the held keys to an articulated crow, and the wings are not a clip being played: the controller carries a pose — flap, glide, bank, tuck, perch — and a phase whose rate rises with throttle and falls as the reserve goes, so what the bird is doing is a readout of the same numbers the HUD is showing. Airspeed and altitude in place of a speedometer, and the flight energy that beating spends and gliding does not, which is why Arena's crow-nav environment can price powered circling: it costs the one quantity the goal also needs.",
|
||||
day: {
|
||||
at: "2026-08-06T09:20:00-07:00",
|
||||
note: "Twenty past nine, over the downtown towers with the bay, a crossing and the far shore stacked up behind them — the whole board is in one frame because the body is above all of it, which is the one camera the chapter list has no button for.",
|
||||
alt: "An articulated crow seen from behind, wings spread, flying over the towers of downtown San Francisco in morning light with the bay, a crossing and the far shore beyond, and a heads-up strip along the top reading its airspeed, altitude, pose and flight energy.",
|
||||
},
|
||||
night: {
|
||||
at: "2026-08-06T21:35:00-07:00",
|
||||
note: "Twenty-five to ten, and the frame has inverted: by day the bird is the dark thing on a pale board, and at night it is the dark thing on a board made of windows. Same body, same altitude, same pose — only the hour was asked for.",
|
||||
alt: "The same crow at night, silhouetted against the lit windows of the San Francisco towers below and the dark bay beyond, with the same heads-up strip along the top.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "california-flight",
|
||||
door: "tera",
|
||||
city: "california",
|
||||
chapter: 0,
|
||||
expect: "State",
|
||||
/*
|
||||
* Chapter 0 rather than 1, and it matters for the same reason an aimed shot
|
||||
* picks its chapter: the blurb left in the panel is a caption inside the
|
||||
* photograph. "State" says the two cities are joined as one route board,
|
||||
* which is exactly what the aircraft in the frame is flying between; "101"
|
||||
* would be talking about a road the frame does not contain.
|
||||
*
|
||||
* The aircraft is on the California board and nowhere else — `main.ts`
|
||||
* builds it only for that pack — so this shot cannot move to another board
|
||||
* the way the crow did. Five seconds from the LA waypoint is far enough that
|
||||
* it is over ground rather than sitting on its start, and near enough that
|
||||
* the basin is still what is underneath at night.
|
||||
*/
|
||||
play: { mode: "aircraft", expect: "Flight", run: 5000 },
|
||||
place: "The California corridor, flying",
|
||||
caption:
|
||||
"The third body and the only one with a published route: the distributed-electric aircraft starts at Los Angeles on a three-waypoint corridor to San Francisco, and Fly takes it off its own autopilot and gives you the yoke. The HUD is in knots, metres and watt-hours because that is what an aircraft has, and the battery is the reason the environment is not a pure altitude race — Arena's california-flight scores waypoint progress against energy, and climbing is the cheapest way to farm a number that is not the goal.",
|
||||
day: {
|
||||
at: "2026-08-06T18:00:00-07:00",
|
||||
note: "Six in the evening, five seconds off the Los Angeles waypoint on a heading of 320°, with the coast ranges throwing across the corridor and the Pacific past the wingtip. The altitude on the readout is above sea level, not above the ridge under it — the controller keeps both, and the one that terminates an episode is the clearance.",
|
||||
alt: "A distributed-electric aircraft seen from behind and above, flying north-west over green coastal mountains in late afternoon light with a freeway threading the valley below and the Pacific beyond, and a heads-up strip along the top reading its airspeed, altitude, flight mode and battery.",
|
||||
},
|
||||
night: {
|
||||
at: "2026-08-06T21:20:00-07:00",
|
||||
note: "Twenty past nine, and three things are carrying a frame that has no daylight in it: the basin's lit ground in the corner, the moon's path broken across the water, and the aircraft's own lights — red on the port wingtip, green on the starboard, white strobes and a tail light — which are unlit materials, so they are the only part of this picture that does not care what the sun is doing.",
|
||||
alt: "The same aircraft at night over the lights of the Los Angeles basin, the same heads-up strip along the top.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "office-floor",
|
||||
hide: ["#device-section"],
|
||||
@@ -634,6 +763,203 @@ async function aimAt(page, rect, toPx, aim) {
|
||||
await page.waitForTimeout(900);
|
||||
}
|
||||
|
||||
/**
|
||||
* ---- Taking a body ----------------------------------------------------------
|
||||
*
|
||||
* `aim` points a camera at the world. `play` asks the app to hand over a *body*
|
||||
* — the car, the crow, the aircraft — and then photographs the world moving
|
||||
* under it, with the play HUD deliberately left in the frame.
|
||||
*
|
||||
* It is the same kind of input `aim` is, and for the same reason: the engine has
|
||||
* no back door. There is no `?mode=drive`, and `main.ts` keeps the control mode
|
||||
* in a module-scoped `ControlModeState` that nothing outside it can write. What
|
||||
* it does have is the dock — `<nav id="mode-dock">`, five buttons carrying
|
||||
* `data-control-mode` — which is the control a reader uses, so it is the control
|
||||
* a shot uses.
|
||||
*
|
||||
* ### Three things this asserts, and why each one is not ceremony
|
||||
*
|
||||
* **1. The button is offered before it is pressed.** A mode exists only where
|
||||
* the pack builds a body for it: `availableControlModes()` in `main.ts` puts the
|
||||
* EV and the aircraft on the California board *alone*, and gates driving further
|
||||
* on the current route being `la-sf-us-101` or `la-sf-i-5` — so "Drive" is not
|
||||
* merely unpressed on the Bay Area board, it is `hidden`. Clicking a hidden
|
||||
* button is a no-op that leaves the camera in `overview` and the run cheerfully
|
||||
* shooting a wide shot. The failure below names what the board *did* offer,
|
||||
* because that is the sentence that tells you whether the shot list is wrong or
|
||||
* the pack is.
|
||||
*
|
||||
* **2. The mode is confirmed off the app, not off the click.** Exactly as
|
||||
* `performance-budget.mjs` does for its `california-drive` scene: wait for
|
||||
* `[data-control-mode='…'][aria-pressed='true']` **and** `#play-hud` unhidden.
|
||||
* `transitionControlMode` can silently substitute the fallback mode for a
|
||||
* request it will not honour, so a click that "worked" and a mode that changed
|
||||
* are two different facts. Pressing a key and hoping is how the tera share card
|
||||
* ended up being a chase camera on US-101 under the headline "Cities from
|
||||
* above".
|
||||
*
|
||||
* **3. The HUD says which body, at the shutter and not only at the click.**
|
||||
* `expect` here is the chapter guard's twin: `#play-hud-mode` reads "Drive",
|
||||
* "Crow", "Flight" or "Studio", and the actor mode in particular reports *which*
|
||||
* actor — a signed-in visitor gets a humanoid and the chip reads "Explore"
|
||||
* instead. The check is repeated after `run` because a body can lose the mode
|
||||
* while the wait is going: a completed route, a bound, an envelope contact. A
|
||||
* frame shot two seconds after the app dropped back to `overview` is a wide shot
|
||||
* with a correct-looking filename, which is precisely the class of failure the
|
||||
* hand-made originals could not be checked for.
|
||||
*
|
||||
* ### `run` for a body that was asleep, `untilProgress` for one that was not
|
||||
*
|
||||
* `run` is a duration, and it is honest for two of the three bodies: the crow
|
||||
* and the aircraft are built inert — `createSceneAircraft` takes `active: false`
|
||||
* so that "construction never steals input or camera ownership" — so neither of
|
||||
* them has moved a metre before the dock hands it over, and n milliseconds after
|
||||
* that is the same place every time. Measured: the crow reports 9.9 m/s at
|
||||
* 1,202 m and the aircraft 120 kt at 1,403 m, identically at `deviceScaleFactor`
|
||||
* 1 and 2.
|
||||
*
|
||||
* The car is different, and the difference cost a delivered frame. Road traffic
|
||||
* runs from page load, so a duration measured from the mode change is a duration
|
||||
* measured from *boot*, and boot is not a constant: the same `run: 9000` put the
|
||||
* EV at 55% of the leg in a 1x preview and at 69% in the 2x render — a different
|
||||
* stretch of road, in a frame with the edge of a town across the top of it. A
|
||||
* preview that does not predict the delivery is worse than no preview.
|
||||
*
|
||||
* So `untilProgress` waits on the app's own number instead, which is trap three
|
||||
* of this file applied to the simulation rather than to boot. Route progress is
|
||||
* the one monotonic quantity any of these HUDs publishes; it is a position along
|
||||
* the leg, so the same fraction is the same place whatever the machine was
|
||||
* doing. It is read out of `#play-hud-status` because that is where
|
||||
* `formatPlayHud` puts it and the engine offers no other channel — a formatted
|
||||
* string is a poor interface, and the alternative was a picture that moves.
|
||||
*
|
||||
* Overshoot is a failure and not a wait. If the leg is already past the target
|
||||
* when the mode is taken, the shot cannot get there by waiting and the frame
|
||||
* would be of somewhere the caption does not describe, so this throws rather
|
||||
* than shooting. On `la-sf-us-101` there is also a ceiling: at 100% the EV
|
||||
* reaches San Francisco, the app swaps to the Bay Area board, and the shot is of
|
||||
* a different product entirely.
|
||||
*/
|
||||
async function enterPlay(page, play) {
|
||||
const read = () =>
|
||||
page.evaluate((wanted) => {
|
||||
const buttons = [...document.querySelectorAll("#mode-dock [data-control-mode]")];
|
||||
const button = buttons.find((b) => b.getAttribute("data-control-mode") === wanted) ?? null;
|
||||
const hud = document.getElementById("play-hud");
|
||||
const text = (id) => document.getElementById(id)?.textContent?.trim() ?? "";
|
||||
const status = text("play-hud-status");
|
||||
const percent = /(\d+)%/.exec(status);
|
||||
return {
|
||||
present: button !== null,
|
||||
hidden: button === null || button.hidden,
|
||||
pressed: button?.getAttribute("aria-pressed") === "true",
|
||||
hudVisible: hud !== null && hud.hidden === false,
|
||||
mode: text("play-hud-mode"),
|
||||
primary: text("play-hud-primary"),
|
||||
status,
|
||||
progress: percent ? Number(percent[1]) / 100 : null,
|
||||
offered: buttons
|
||||
.filter((b) => !b.hidden)
|
||||
.map((b) => b.getAttribute("data-control-mode")),
|
||||
};
|
||||
}, play.mode);
|
||||
|
||||
const before = await read();
|
||||
if (!before.present) {
|
||||
throw new Error(
|
||||
`the mode dock has no "${play.mode}" button — index.html's dock changed under the ` +
|
||||
"shot list, so a shot carrying `play` cannot take a body",
|
||||
);
|
||||
}
|
||||
if (before.hidden) {
|
||||
throw new Error(
|
||||
`"${play.mode}" is not offered here — the dock is showing ` +
|
||||
`[${before.offered.join(", ") || "nothing"}]. A body exists only where the pack ` +
|
||||
"builds one: the EV and the aircraft are on the California board alone, and " +
|
||||
"driving additionally needs US-101 or I-5 to be the current chapter",
|
||||
);
|
||||
}
|
||||
// Already pressed is the normal case for `drive`: choosing the US-101 chapter
|
||||
// enters drive mode by itself, so the shot asserts the mode it inherited
|
||||
// rather than toggling it back off by clicking a button that is already on.
|
||||
if (!before.pressed) {
|
||||
await page.click(`#mode-dock [data-control-mode="${play.mode}"]`);
|
||||
await page.waitForFunction(
|
||||
(wanted) => {
|
||||
const button = document.querySelector(`#mode-dock [data-control-mode="${wanted}"]`);
|
||||
const hud = document.getElementById("play-hud");
|
||||
return button?.getAttribute("aria-pressed") === "true" && hud?.hidden === false;
|
||||
},
|
||||
play.mode,
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
}
|
||||
|
||||
const entered = await read();
|
||||
if (!entered.pressed || !entered.hudVisible) {
|
||||
throw new Error(
|
||||
`the dock did not take "${play.mode}" — it is showing [${entered.offered.join(", ")}] ` +
|
||||
`and the HUD is ${entered.hudVisible ? "up" : "down"}. \`transitionControlMode\` ` +
|
||||
"substitutes the fallback mode for a request it will not honour, so this is a " +
|
||||
"refusal and not a slow transition",
|
||||
);
|
||||
}
|
||||
if (entered.mode !== play.expect) {
|
||||
throw new Error(
|
||||
`the play HUD says "${entered.mode}", not "${play.expect}" — the mode was entered ` +
|
||||
"but the body is not the one this shot's caption describes (an actor is a crow " +
|
||||
"for an anonymous visitor and a humanoid for a signed-in one, and the chip says " +
|
||||
"which). Fix the shot list before anything ships",
|
||||
);
|
||||
}
|
||||
|
||||
if (play.untilProgress !== undefined) {
|
||||
if (entered.progress === null) {
|
||||
throw new Error(
|
||||
`\`untilProgress\` was asked of the "${play.expect}" HUD, whose status reads ` +
|
||||
`"${entered.status}" and carries no percentage. Only the drive HUD publishes a ` +
|
||||
"route progress; the other bodies are inert until the dock hands them over, so " +
|
||||
"`run` is exact for them",
|
||||
);
|
||||
}
|
||||
if (entered.progress > play.untilProgress + 0.02) {
|
||||
throw new Error(
|
||||
`the leg was already ${Math.round(entered.progress * 100)}% done when "${play.mode}" ` +
|
||||
`was taken, past this shot's ${Math.round(play.untilProgress * 100)}% — waiting ` +
|
||||
"cannot go backwards, so the frame would be of a stretch of road the caption does " +
|
||||
"not describe. Boot got slower; raise `untilProgress` and re-check the framing",
|
||||
);
|
||||
}
|
||||
await page.waitForFunction(
|
||||
(target) => {
|
||||
const found = /(\d+)%/.exec(
|
||||
document.getElementById("play-hud-status")?.textContent ?? "",
|
||||
);
|
||||
return found !== null && Number(found[1]) / 100 >= target;
|
||||
},
|
||||
play.untilProgress,
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
}
|
||||
|
||||
if (play.run) await page.waitForTimeout(play.run);
|
||||
|
||||
// The shutter guard. A body can lose its mode during `run` — a route that
|
||||
// completed, a bound, an envelope contact — and the frame that comes back is a
|
||||
// wide shot with the right filename.
|
||||
const shutter = await read();
|
||||
if (!shutter.pressed || !shutter.hudVisible || shutter.mode !== play.expect) {
|
||||
throw new Error(
|
||||
`"${play.mode}" did not survive the wait — the dock now reads ` +
|
||||
`${shutter.pressed ? "pressed" : "released"}, the HUD is ` +
|
||||
`${shutter.hudVisible ? `up saying "${shutter.mode}"` : "down"}. A completed route, ` +
|
||||
"a bound or an envelope contact ends a mode, and the frame that comes back is a " +
|
||||
"wide shot with the right filename. Shorten the wait",
|
||||
);
|
||||
}
|
||||
return shutter;
|
||||
}
|
||||
|
||||
// ---- Arguments --------------------------------------------------------------
|
||||
|
||||
function flag(name, fallback = null) {
|
||||
@@ -862,6 +1188,15 @@ async function shoot(browser, shot, frame) {
|
||||
* reproducible on another: boot time varies, and the car with it.
|
||||
*/
|
||||
if (shot.settle) await page.waitForTimeout(shot.settle);
|
||||
/*
|
||||
* `shot.play` asks the app for a body and lets it move before the shutter
|
||||
* opens. It comes last of the three camera steps because it *takes the
|
||||
* camera away* — the follow camera owns the pose in every play mode, so a
|
||||
* chapter chosen underneath it survives only as the blurb left in the panel,
|
||||
* and an `aim` under it would be overwritten. See the long note beside
|
||||
* `enterPlay` for what it asserts and why each assertion is load-bearing.
|
||||
*/
|
||||
const hud = shot.play ? await enterPlay(page, shot.play) : null;
|
||||
/*
|
||||
* `shot.hide` is the per-frame escape hatch, and the studio-hardware card is
|
||||
* why it exists.
|
||||
@@ -877,12 +1212,19 @@ async function shoot(browser, shot, frame) {
|
||||
* courtyard is a picture of a courtyard, and a column of audio controls down
|
||||
* one side of it is answering a question nobody asked.
|
||||
*/
|
||||
/*
|
||||
* `#mode-dock` and `#play-hud` are **not** in `CLUTTER`, and for these three
|
||||
* shots that is the point rather than an oversight — the HUD is what the
|
||||
* picture is of. They are in `BARE`, which is the share card's list, because
|
||||
* a card supplies its own typography; a product still of an agent driving
|
||||
* with its instruments cropped off is a photograph of a road.
|
||||
*/
|
||||
await hide(page, [...FURNITURE.transient, ...FURNITURE.CLUTTER, ...(shot.hide ?? [])]);
|
||||
const png = await page.screenshot({ timeout: 120_000, animations: "disabled" });
|
||||
if (problems.length) {
|
||||
throw new Error(`the page threw while being photographed: ${problems[0]}`);
|
||||
}
|
||||
return png;
|
||||
return { png, hud };
|
||||
} finally {
|
||||
await page.close();
|
||||
}
|
||||
@@ -905,7 +1247,20 @@ if (listOnly) {
|
||||
for (const shot of wanted) {
|
||||
const where = shot.door === "office" ? `office/${shot.office ?? "SF HQ"}` : `tera/${shot.city ?? "california"}`;
|
||||
const aimed = shot.aim ? ` aimed at ${shot.aim.lat}, ${shot.aim.lng}` : "";
|
||||
console.log(` ${shot.id.padEnd(20)} ${where.padEnd(22)} chapter ${shot.chapter} "${shot.expect}"${aimed}`);
|
||||
/*
|
||||
* `--list` is how `refresh.mjs --dry-run` says what a run would do without
|
||||
* opening a browser, and it is what the chapter guard's failure message
|
||||
* tells you to run. A capability the listing does not mention is a
|
||||
* capability a reader of the plan cannot see, so `play` prints the mode it
|
||||
* asks for, the HUD chip it demands back, and how long it lets the world run.
|
||||
*/
|
||||
const waited = shot.play?.untilProgress !== undefined
|
||||
? `to ${Math.round(shot.play.untilProgress * 100)}% of the leg`
|
||||
: `for ${shot.play?.run ?? 0} ms`;
|
||||
const played = shot.play
|
||||
? ` playing ${shot.play.mode} → HUD "${shot.play.expect}", ${waited}`
|
||||
: "";
|
||||
console.log(` ${shot.id.padEnd(20)} ${where.padEnd(22)} chapter ${shot.chapter} "${shot.expect}"${aimed}${played}`);
|
||||
for (const variant of VARIANTS) console.log(` ${variant.padEnd(6)} ${shot[variant].at}`);
|
||||
}
|
||||
process.exit(0);
|
||||
@@ -938,11 +1293,18 @@ try {
|
||||
for (const shot of wanted) {
|
||||
for (const variant of VARIANTS) {
|
||||
const stem = `${shot.id}-${variant}`;
|
||||
const png = await shoot(browser, shot, shot[variant]);
|
||||
const { png, hud } = await shoot(browser, shot, shot[variant]);
|
||||
/*
|
||||
* The HUD is echoed into the log because it is the only evidence in the
|
||||
* run that the body was doing something. "shot drive-101-day" is true of a
|
||||
* parked car, a finished route and a correct frame alike; `56 mph ·
|
||||
* US-101 | assisted · 41% · chase` is not.
|
||||
*/
|
||||
const readout = hud ? ` [${hud.mode}] ${hud.primary} | ${hud.status}` : "";
|
||||
if (preview) {
|
||||
await mkdir(join(ROOT, "shots", "preview"), { recursive: true });
|
||||
await writeFile(join(ROOT, "shots", "preview", `${stem}.png`), png);
|
||||
console.log("preview", stem);
|
||||
console.log("preview", stem, readout);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -961,6 +1323,7 @@ try {
|
||||
stem.padEnd(22),
|
||||
SIZES.map((s) => `${s.suffix}w ${Math.round(sizes[s.suffix].length / 1024)}kB`).join(" "),
|
||||
shot.aim ? ` aim ×${(ZOOM_NOTCH ** -(shot.aim.zoom ?? 0)).toFixed(4)} standoff` : "",
|
||||
readout,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user