1
0

feat: the crane grows a mast, the harbour works a shift, and the site is re-shot

**The Asset Factory verdict, and it mostly went against the vote.** Nine
candidates were thumbed up. One was taken.

TOOK the STS crane. Rebuilt in `ports.ts` from 5 unit boxes to 11 — an A-frame
mast and apex cap, a forestay to the boom, a backstay to the tail, a sill, the
truck-lane portal beam, a machinery house — still exactly ONE InstancedMesh.
What was missing is the thing that makes a gantry a gantry: on a real STS the
tallest part of a WORKING crane is the A-frame apex, not the boom, and a parked
raised boom clears its own apex by only 15-25%. Before, 56 gantries read from
altitude as 56 crosses — two coincident verticals with one bar through them and
nothing above it — so a berth flattened into a picket fence.

Proportions came from both upvoted candidates agreeing independently (hinge ~58 m
under an apex at 99-104 m), taken conservatively because Tera's packs already
author an 82 m hinge against a real 55-60.

The apex beacon came across as EMISSION: `craneLights()` returns bare positions,
`nightlights.ts` turns them into one additive Points cloud, 56 points, one draw
call, night only, no THREE.Light anywhere. 0.09 units was invisible against the
port's own cream emissive; 0.17 — half a bridge head light — is right, and the
screenshot at 0.09 is what condemned it.

REJECTED all three bridges, city-lights and both aircraft: the incumbents won on
the picture, decisively for the bridge.

TWO PARTS WERE BUILT FROM THE APPROVED CANDIDATES, PHOTOGRAPHED, AND CUT. Four
legs: 14 m of quay spacing is 0.036 units at 391 m/unit against a 0.032 member
floor, so 90% overlap. A portal X-brace: the bay is 0.115 wide by 0.38 tall, so
both diagonals come out near-vertical and add a lump at mid-leg. Both are among
the best things about the factory cranes AT THE FACTORY'S FRAMING. Neither
survives at board scale. That gap is the whole reason a factory asset is
reference geometry and not a drop-in.

Fixed a defect the rebuild exposed: the backreach started a full rail-gauge
behind the hinge, leaving a gap over the portal with the beam floating below it.
One unbroken girder now. And every inclined member goes through a `strut()` that
takes two points in the (distance-along-boom, height) plane, so the
vertical-exaggeration bug the module header warns about is no longer reachable —
it needs a length and an angle, and there is now no way to start from those.

**The harbour works a shift.** It was a frozen tableau: 19 hulls placed from the
pack's berths that never changed. Vessels now arrive through the channel, are met
by a tug, berth, work and depart — seeded, so two people see the same harbour and
a capture script shoots the same frame twice. A ship loses its wake when it ties
up, because the wake is the information.

**Every still and film re-shot.** The site was showing a Tera that no longer
existed — SHOTS_COMMIT b7f5c41, FILMS_COMMIT 2aa4049, against an engine that has
since gained fires, the whole state, ports, ships and night infrastructure. Two
frames were bad and are fixed by moving the hour, not by retouching:
`bay-relief-day` and `peninsula-day` were white lids of marine layer. Four
captions described a Tera that no longer existed and are rewritten to the
delivered frame. `california-relief-night` is measurably brighter than the frame
it replaces (canvas mean 7.91 -> 10.57) despite the state being 30% larger.

Ten budget cells pass, run twice. socal 1,422,025 -> 1,429,993 triangles against
1,700,000, 218 draws against 320. The measured delta is double the geometry
because the crane mesh casts shadow, so renderer.info counts it in both passes —
worth knowing before anyone reads that number as geometry.

Tests 1,540 -> 1,570, server 295.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-23 03:29:58 -07:00
parent e7a8aa028b
commit bcac6aa41a
15 changed files with 2429 additions and 304 deletions
+250 -17
View File
@@ -42,7 +42,7 @@
import { fileURLToPath } from "node:url";
import { dirname, join, resolve } from "node:path";
import { mkdir, writeFile, access } from "node:fs/promises";
import { mkdir, writeFile, readFile, access } from "node:fs/promises";
import { execFileSync } from "node:child_process";
import { serve, launch, clockShim, FURNITURE, hide } from "./harness.mjs";
@@ -101,9 +101,25 @@ const SHOTS = [
caption:
"Everything else in the engine stands on this heightfield: 83 named hills, four octaves of value noise, and overlapping peaks combined as tallest-plus-35%-of-the-rest — the one rule that makes a run of summits read as a ridge instead of stacking into a fictional mountain.",
day: {
at: "2026-08-06T09:10:00-07:00",
note: "Ten past nine, sun thirty degrees up: high enough that the marine layer has burned back off the bay, low enough that the East Bay hills still rake shadow west across the flats.",
alt: "The San Francisco Bay Area seen from above in mid-morning light, the peninsula and the East Bay hills casting shadows west, scattered cloud over the water.",
at: "2026-08-06T17:00:00-07:00",
/*
* Five in the afternoon, and it used to be ten past nine — which was the
* right instinct about shadow and the wrong one about cloud. `atmosphere.ts`
* models the Pacific marine layer on *apparent solar* time, and its diurnal
* curve is 0.95 before dawn, 0.6 at nine, 0.2 at eleven and back to 0.4 by
* four. At 09:10 PDT — a little after eight solar — the layer is at about
* three quarters, and the frame it produced was a white lid with a board
* somewhere under it: the caption underneath was about eighty-three named
* hills and four octaves of value noise, none of which was visible.
*
* This is the compromise the curve allows. Physically the layer is thinnest
* at solar eleven to fourteen, which is 12:07-15:07 on a wristwatch here —
* the exact hours a heightfield has no shadow at all. 17:00 PDT is solar
* 15.9, thin enough that the board is out from under it, and low enough at
* 36.6 degrees that both ranges still throw.
*/
note: "Five in the afternoon, which on this coast is a decision about cloud before it is one about shadow. The marine layer here is modelled rather than painted — thickest before dawn, burnt back through the late morning, on its way in again by six, all of it on apparent solar time — so at nine this board is a white lid, and at five it is out from under it with both ranges throwing.",
alt: "The San Francisco Bay Area seen from above in late afternoon light: the peninsula between the Pacific and the bay, the East Bay hills beyond, both crossings over the water, and scattered cloud out over the ocean.",
},
night: {
at: "2026-08-06T21:35:00-07:00",
@@ -161,9 +177,17 @@ const SHOTS = [
caption:
"Twenty miles of city between the water and the ridge, never more than four wide. The camera is about 32 km back — the city scene's orbit limit — which is why the region ships as five chapters rather than one impossible wide shot.",
day: {
at: "2026-08-06T09:20:00-07:00",
note: "Twenty past nine, with an aircraft on approach drawing the line across the bay.",
alt: "The San Francisco peninsula from above in morning light, the Santa Cruz mountains on one side, the bay on the other, and an aircraft trail crossing the water.",
at: "2026-08-06T17:10:00-07:00",
/*
* Moved off 09:20 for `bay-relief`'s reason, and this shot is the sharper
* case of it: at a thirty-two kilometre standoff the modelled marine layer
* is not weather in the picture, it is a lid over the whole subject, and
* the twenty miles of city this caption is about were simply not in the
* frame. Ten past five is thin enough to see the ground and low enough to
* model the ridge.
*/
note: "Ten past five, the sun down the length of the bay and low enough that the Santa Cruz ridge has a lit side and a dark one. Nothing here is a fixed number of miles from anything: the standoff is the city scene's own orbit limit, which is why the region ships as five chapters and not as one impossible wide shot.",
alt: "The San Francisco peninsula from above in late afternoon light: the Santa Cruz mountains green along one side, the built ground between them and the bay, sun glitter across the water and haze closing the far end of the bay.",
},
night: {
at: "2026-08-06T21:35:00-07:00",
@@ -224,7 +248,7 @@ const SHOTS = [
"One kit draws both of the Bay's crossings, because a suspension bridge is arithmetic over a path and a list of towers: a main cable hanging between the tower tops, a regular series of hangers down to the deck, and piers under every metre that hangs from nothing. Here that resolves to two cross-braced towers 227 m over the strait, one main span, and international orange. Every reach is classified before it is drawn — the previous builder hung a catenary between every pair of towers, which is how the Bay Bridge ended up with a two-and-a-half kilometre span over dry land.",
day: {
at: "2026-08-06T17:40:00-07:00",
note: "Twenty to six, raking down the strait, so the towers throw along the deck and the bridge puts its own shadow on the water. The road does not stop at the anchorages: both ends run onto land, because started at the waterline the deck appeared to float with its approaches missing.",
note: "Twenty to six, raking down the strait, so both towers lay their own shadows out across the water beside the deck. The road does not stop at the anchorages: both ends run onto land, because started at the waterline the deck appeared to float with its approaches missing.",
alt: "The Golden Gate Bridge from above in late afternoon light, international orange, its two towers cross-braced and the main cable sagging to mid-span between them, with the Marin headlands on one side and the Presidio on the other.",
},
night: {
@@ -320,11 +344,11 @@ const SHOTS = [
expect: "State",
place: "California",
caption:
"A third board, and the first that is a state rather than a city: 428 units across at 1,919 metres to the unit, which is the same heightfield code the Bay Area runs at 94. The Central Valley is the flat green band between the Sierra crest and the Coast Ranges; the Basin and Range steps away east of the Sierra, Death Valley is the pale pan below sea level inside it, and the Salton Sea is the water in the south-east corner. Adding a board is a data contribution — coastlines, ranges, roads, landmarks and chapters in one file with no code in it.",
"A third board, and the first that is a state rather than a city: the whole of California, Mexico to Oregon, 554 units across at 1,919 metres to the unit the same heightfield code the Bay Area runs at 94. The Central Valley is the flat green band between the Sierra crest and the Coast Ranges, running the length of the board; the Basin and Range steps away east of the Sierra, Death Valley is the pale pan below sea level inside it, the Salton Sea is the water in the south-east corner, and in the north the Sierra hands over to the Cascade arc with the Klamath knot west of it. Adding a board is a data contribution — coastlines, ranges, roads, landmarks and chapters in one file with no code in it.",
day: {
at: "2026-08-06T17:30:00-07:00",
note: "Half past five, and every ridge in the Basin and Range is throwing its shadow east. That is the hour the steps between them separate at all: at midday the sun is behind the camera and the whole state is one sheet of sand.",
alt: "The state of California rendered from above as a relief board in late afternoon light, the Sierra Nevada and the Coast Ranges either side of the Central Valley, the Basin and Range stepping east into shadow, and the Salton Sea in the south.",
alt: "The whole state of California rendered from above as a relief board in late afternoon light, the Sierra Nevada and the Coast Ranges either side of the Central Valley, the Basin and Range stepping east into shadow, the Salton Sea in the south and the Cascade and Klamath country closing the board in the north.",
},
night: {
at: "2026-08-06T21:10:00-07:00",
@@ -332,6 +356,49 @@ const SHOTS = [
alt: "The same board at night, the land dark and the built-up ground picked out around Los Angeles and San Francisco.",
},
},
{
id: "california-fire",
door: "tera",
city: "california",
chapter: 0,
expect: "State",
/*
* The one shot with a server behind it. See the long note beside
* `serveFeed`: the fire layer is gated on `/health` naming a fire source, so
* a keyless capture cannot photograph it at all, while every real visitor to
* tera.lumbridgecorp.com gets it. The body is a fixture in this repo rather
* than the live feed, so the picture is reproducible at the commit the
* manifest names.
*
* The whole board rather than a close pass over one fire, and that is the
* feature's own argument rather than a framing preference. There are no
* perimeters in the store — every fire in it is a point — so the mark is
* drawn as extent *without* shape, a radial gradient with no edge anywhere,
* because a crisp circle would claim a boundary that would be wrong at every
* point on it. That reads as designed at board scale, where it is a mark on a
* map sized by acreage; close up it is a blur claiming nothing, and the eight
* marks and the list that names them stop being one picture.
*
* The clock is 23 August rather than 6 August, unlike every other frame here,
* because the panel prints the body's own `fetchedAt` and how long ago that
* was. A fixture from the 23rd photographed on the 6th reads "fetched
* 2026-08-23 · just now", which looks like a bug and is one.
*/
feed: "fires",
place: "California",
caption:
"The same board with a feed behind it. Eight fires are drawn here out of seventy-three live rows, and the gate that refused the other sixty-five is the whole feature: under ten acres or no acreage at all is not a small fire, it is nobody having said it is a fire; eighty per cent contained is finished; a prescribed burn is not a wildfire; and a row the collector stopped seeing but never deleted is a ghost. That decision is made once, in server code with no GL context in it, and this layer draws what it is handed and never one thing more. There are no perimeters in the store — every incident is a point — so a mark carries its acreage as extent without shape, a radial gradient with no edge, rather than a circle that would claim a boundary wrong at every point on it. The hot pixels underneath are evidence and not incidents, and the ninety-seven known flare stacks, kilns and landfills among them are counted and not drawn.",
day: {
at: "2026-08-23T09:40:00-07:00",
note: "Twenty to ten in the morning, eight hours after the collector last answered — which the panel says out loud, because a board with no feed behind it and a board with nothing burning on it are indistinguishable without a timestamp. Every mark is sized by its own acreage, from the Timber Fire's 8,665 in Monterey down to a ten-acre start in San Bernardino, and the soft ground under each one is that acreage drawn as extent.",
alt: "The whole state of California as a relief board in morning light with eight orange fire marks on it, each sitting in a soft glow sized to its acreage, and a panel down the left listing the incidents by name, acreage, containment and county.",
},
night: {
at: "2026-08-23T21:10:00-07:00",
note: "Ten past nine, and the marks are the only warm thing left on a board whose relief has gone. Nothing here is a THREE light — a wildfire is the most tempting exception in the codebase and a point light is one line — so the glow is emission in an over-range colour taken through ACES, which is both cheaper than a rig and a better picture of it.",
alt: "The same board after dark, the land unlit and the eight fire marks glowing orange against it, with the lit ground of the two city clusters and the same incident list down the left.",
},
},
{
id: "freeway",
door: "tera",
@@ -376,8 +443,17 @@ const SHOTS = [
"The same airport kit, on the second city pack, producing an airport nobody would mistake for the first: four parallels, two either side of the terminal horseshoe, instead of two crossing pairs. The property is a cardinal rectangle — Westchester Parkway north, Imperial Highway south, Aviation Boulevard east, the dunes above Dockweiler west — with the runway pattern set about seven degrees off it, and that disagreement between the fence and the pavement is the thing that reads as LAX from altitude.",
day: {
at: "2026-08-06T17:50:00-07:00",
note: "Ten to six, with the basin's grid running out to the San Gabriels behind and the Santa Monica Bay in front. The traffic overhead is the real traffic: registration and type come off the same feed.",
alt: "Los Angeles International Airport from above in late afternoon light: four parallel runways either side of a horseshoe of terminals, the street grid of the basin all around it and the Pacific beyond.",
/*
* This note used to end "the traffic overhead is the real traffic:
* registration and type come off the same feed", and that sentence cannot
* be true of this picture. Every frame here but `california-fire` is shot
* keyless — no `/health`, so `access.feeds` is `null`, so `TrafficSource`
* is the bundled simulator and not ADS-B. The deployed site does have the
* feed; this photograph does not, and a caption may not borrow a fact from
* a deployment the shutter never opened on.
*/
note: "Ten to six, and the disagreement in this frame is the subject: the property is a cardinal rectangle, the pavement inside it is set about seven degrees off, and the basin's street grid outside the fence goes back to the cardinal. That is what reads as LAX from altitude — long before any building does.",
alt: "Los Angeles International Airport from above in late afternoon light: four parallel runways either side of a horseshoe of terminals, set at a slight angle inside a rectangular property, with the street grid of the basin all around it and the Pacific beyond.",
},
night: {
at: "2026-08-06T21:20:00-07:00",
@@ -385,6 +461,44 @@ const SHOTS = [
alt: "The same airport at night, its dark field surrounded by the lights of the Los Angeles basin running to the horizon.",
},
},
{
id: "harbour",
door: "tera",
city: "socal",
chapter: 5,
expect: "Harbour",
/*
* The chapter's own stance is a wide plan of the whole bight from about
* forty units up, which puts Terminal Island in the middle of a lot of
* water and makes the cranes a fringe. The subject is the cranes, so this
* comes down three notches to about the closest standoff the board allows
* and drags forty-five degrees of azimuth to look across the island rather
* than down at it — which is what stands the gantries up against the water
* instead of laying them flat on the yard.
*
* The seek is a point in the West Basin rather than the middle of the
* island, for `bay-bridge`'s reason: the panel eats the left 310 px, so the
* midpoint of *what has to fit* is west and north of the midpoint of the
* thing. From here the Vincent Thomas crosses the Main Channel in the lower
* left with the channel running out under it, all four of the Los Angeles
* crane rails are in frame at their four different bearings, and San Pedro
* closes the bottom of the picture so the harbour has a town on it.
*/
aim: { lat: 33.7405, lng: -118.2565, zoom: -3, azimuth: 45, elevation: 4 },
place: "San Pedro Bay",
caption:
"Two ports around one basin — Los Angeles and Long Beach — behind thirteen kilometres of federal breakwater. The gantry crane is the hero of this kit and vertical exaggeration is why: at 3.4× on a board of 390 metres to the scene unit a 130 m crane stands 1.13 units tall while an entire 400 m container ship is 1.02 units long, so the crane is the taller object and it is what makes a port read as a port from altitude. All fifty-six gantries on the board are one InstancedMesh with a boom angle per instance — lowered over a berth reads as working, raised to near-vertical reads as stood down — and the four rails are set at four different bearings, because a port where every boom points the same way reads as a fence. The yards are the opposite call: a forty-foot box is one and a half pixels from the closest pose this board offers, so a terminal is one painted quad indexing one canvas atlas rather than ten thousand instanced boxes, banded at exactly the empty share the data claims.",
day: {
at: "2026-08-06T17:50:00-07:00",
note: "Ten to six, low enough that every boom throws its own shadow back across the yard it stands on and the four rails read as four different bearings. The hulls alongside are modelled from this board's own berths and channels rather than observed — anonymous, no names and no MMSIs — because the live AIS feed is not configured and a panel that said otherwise would be the only dishonest thing in the frame.",
alt: "The Los Angeles and Long Beach port complex from above in late afternoon light: container terminals on Terminal Island with rows of white gantry cranes along their quays, stacked container yards beside them, ships lying alongside and one under way in the Main Channel, the green Vincent Thomas Bridge crossing the channel and the breakwater running across the outer harbour.",
},
night: {
at: "2026-08-06T21:20:00-07:00",
note: "Twenty past nine, and nothing in a container terminal has a window to switch on. What marks one from the air after dark is the red obstruction light at the apex of every gantry — one per crane, the same fixture and the same red the bridge tower heads carry, drawn as points in the board's one additive cloud rather than as fifty-six lights nobody could afford.",
alt: "The same harbour after dark: the terminals reading as pale decks against black water, a red light standing at the top of each gantry crane, the Vincent Thomas Bridge picked out by the lamps down its deck, and the lit ground of San Pedro and Long Beach around the basin.",
},
},
/*
* ---- The three that take a body ----------------------------------------
*
@@ -499,13 +613,20 @@ const SHOTS = [
"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.",
/*
* This note used to end "and the Pacific past the wingtip", and the sea has
* since gone out of the frame — not because the aircraft moved but because
* the air did. Fog now follows camera altitude, so at 1,413 m the far ridges
* grey out in the order they recede and the ocean behind them is inside the
* haze rather than beyond it. Rewritten to the frame that is delivered.
*/
note: "Six in the evening, five seconds off the Los Angeles waypoint on a heading of 320°, low over the Coast Ranges with the corridor threading the valley underneath. The ridges grey out in the order they recede rather than all at once, because the fog follows the camera's own altitude now — aerial perspective that only exists once something is flying through it. 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, a freeway threading the valley below and the far ridges fading one behind another into haze, with 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.",
note: "Twenty past nine, and there is no daylight anywhere in this frame. Two things carry it. The aircraft's own lights — red on the port wingtip, green on the starboard, white strobes out along the leading edge and a light under the tail — are unlit materials, so they are the only part of this picture that does not care what the sun is doing. And the corridor under the wing, where the road's edge lines and cat's eyes are now the brightest thing left on the ground.",
alt: "The same aircraft at night over dark coastal mountains, its red, green and white navigation lights the brightest thing in the air, the freeway below picked out by its own lane markings, and the same heads-up strip along the top.",
},
},
{
@@ -1119,6 +1240,110 @@ async function chapter(page, index, expect, { click = false } = {}) {
}
}
/**
* ---- The one shot that needs a server behind it ----------------------------
*
* Every other frame here is the **keyless** experience, and that is the right
* default rather than a limitation. `harness.mjs` serves `dist/` and nothing
* else, `/api/v1/health` 404s, `access.ts` resolves with `feeds: null`, and what
* is photographed is what CONTRACT §0's stranger sees on a box with no API.
* Almost all of this product is that picture.
*
* Fire is the exception, and it is not a small one. `drawsFire()` in `main.ts`
* gates the entire layer on `access.feeds?.fires === true`, which comes from
* `/health`'s `sources` block — so a keyless capture cannot photograph it at
* all, while `tera.lumbridgecorp.com` answers `"fires":"cloud1"` and every
* anonymous visitor to the deployed site does get it. Without this the site
* would have shipped an imagery set in which the most visible thing the engine
* gained this round did not exist, and the pictures would have looked entirely
* deliberate while doing it.
*
* ### A fixture in this repo, not a proxy to the live feed
*
* `scripts/look.mjs --api` points a browser at a running server, which is the
* right tool for judging a layer against real data. It is the wrong tool for
* *delivering* a picture. This script's whole claim is that anything on the site
* can be reproduced by re-running it at the commit the manifest names, and a
* frame whose content is whatever California is doing today does not have that
* property: it re-renders with different fires, a different count and a
* different list under an unchanged caption, and nothing says so.
*
* So `fires-2026-08-23.json` is the verbatim body `GET /api/v1/fires` returned
* on 23 August 2026, kept whole — seventy-three incident rows, three hundred and
* sixteen hot pixels, both attribution lines, nothing trimmed. Trimming it would
* have been the tempting thing and would have quietly falsified the panel:
* `promote()` in `server/fires.ts` counts the rows it *refuses* and the panel
* prints that number, so a fixture with the refused rows removed would draw the
* same eight fires under a sentence claiming a gate that never ran.
*
* ### The health body says as little as it can
*
* Synthetic, and deliberately minimal. Every source but `fires` is `"none"`, so
* exactly one thing changes in the frame and nothing else in the picture becomes
* a claim about a feed this capture does not have. `auth.mode` is `"none"`,
* which `access.ts` resolves to `member` — the self-host-with-the-door-open
* case — and that keeps `#tier-adds` out of the shot. That card is the sign-in
* offer, it is shown only to `anon`, and it is **not** in `FURNITURE.CLUTTER`:
* it would have arrived in this frame silently, the way `#mode-dock` arrived in
* the share card and stayed for a fortnight.
*
* Anything under `/api/v1` that is not these two answers 404, which is the state
* every watcher in the app is already written for.
*/
const FEEDS = {
fires: {
fixture: "fires-2026-08-23.json",
route: "/api/v1/fires",
sources: {
weather: "none",
flights: "none",
satellites: "none",
markers: "none",
devices: "none",
fires: "cloud1",
radar: "none",
birds: "none",
},
},
};
/**
* Put one fixture behind `/api/v1`, before the page is navigated.
*
* Installed per page rather than per context, so a shot without `feed` cannot
* inherit it — `shoot()` opens a fresh page for every frame, which makes the
* isolation free.
*
* `route.fulfill` rather than a redirect to a second server, for `look.mjs`'s
* reason: the response is same-origin as far as the page is concerned, so no
* CORS header has to exist anywhere for a screenshot to work.
*/
async function serveFeed(page, name) {
const feed = FEEDS[name];
if (feed === undefined) throw new Error(`no such feed fixture: ${name}`);
const body = await readFile(join(HERE, feed.fixture), "utf8");
const health = JSON.stringify({
ok: true,
service: "tera-api",
version: `fixture:${feed.fixture}`,
uptimeSeconds: 0,
sources: feed.sources,
auth: { mode: "none" },
regions: [],
degraded: [],
});
await page.route("**/api/v1/**", async (route) => {
const path = new URL(route.request().url()).pathname;
const answer = path.endsWith("/health") ? health : path === feed.route ? body : null;
await route.fulfill(
answer === null
? { status: 404, contentType: "application/json", body: "{}" }
: { status: 200, contentType: "application/json", body: answer },
);
});
}
async function shoot(browser, shot, frame) {
const host = shot.door === "office" ? "office.lumbridgecorp.com" : "tera.lumbridgecorp.com";
const query = shot.city ? `?city=${shot.city}` : "";
@@ -1144,6 +1369,9 @@ async function shoot(browser, shot, frame) {
page.on("pageerror", (e) => problems.push(String(e)));
try {
await page.addInitScript(clockShim(frame.at));
// Before the navigation: `resolveAccess()` asks `/health` during boot, and a
// route installed after `goto` would miss it and photograph a keyless board.
if (shot.feed) await serveFeed(page, shot.feed);
await page.goto(`http://${host}:5210/${query}`, { waitUntil: "networkidle" });
/**
* Wait for the app to say it is up rather than for a number of seconds.
@@ -1297,7 +1525,12 @@ if (listOnly) {
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}`);
// Same argument as `play` above: a shot that is *not* the keyless
// experience has to say so in the plan, because "this frame had a server
// behind it" is the single fact that most changes what it is a photograph
// of, and `refresh --dry-run` is where somebody reads the plan.
const fed = shot.feed ? ` against the ${shot.feed} fixture ${FEEDS[shot.feed].fixture}` : "";
console.log(` ${shot.id.padEnd(20)} ${where.padEnd(22)} chapter ${shot.chapter} "${shot.expect}"${aimed}${played}${fed}`);
for (const variant of VARIANTS) console.log(` ${variant.padEnd(6)} ${shot[variant].at}`);
}
process.exit(0);