diff --git a/scripts/brand-assets/shots.mjs b/scripts/brand-assets/shots.mjs index da6474f..ef5fd2f 100644 --- a/scripts/brand-assets/shots.mjs +++ b/scripts/brand-assets/shots.mjs @@ -182,7 +182,7 @@ const SHOTS = [ expect: "The Floor", place: "Lumbridge HQ", caption: - "The same renderer from inside: thirty-four metres by eighteen, one storey, glazed along the north edge. An Office is one building's interior and a Space is a room within it — and this cannot share a THREE.Scene with the city, because San Francisco puts one scene unit at about 94 metres and this is one unit to the metre.", + "The same renderer from inside: forty-eight metres by eighteen, two storeys held a long way apart, glazed along the north edge and open to the roof at the east end. An Office is one building's interior and a Space is a room within it — and this cannot share a THREE.Scene with the city, because San Francisco puts one scene unit at about 94 metres and this is one unit to the metre.", day: { at: "2026-08-06T11:20:00-07:00", note: "Twenty past eleven in the morning.", @@ -190,8 +190,8 @@ const SHOTS = [ }, night: { at: "2026-08-06T21:30:00-07:00", - note: "Half past nine. The office keeps its own light after the sun has gone, which is the difference between a room and a landscape.", - alt: "The same office model at night, lit from within against a dark ground.", + note: "Half past nine. The building has switched its own lights on, because the sun it is following has gone down over the real San Francisco — and a fitting brightens as somebody walks under it.", + alt: "The same office model at night, lit from within by its ceiling fittings against a dark ground.", }, }, { @@ -201,7 +201,7 @@ const SHOTS = [ expect: "Desks", place: "Lumbridge HQ", caption: - "Thirty-six seats in four benches. Stepping in here does not throw the city away — the city scene is retained and paused rather than disposed, because rebuilding the Bay's heightfield costs about 2.3 seconds and nobody should pay that to walk back out of a door.", + "Thirty-six seats in four benches, and how many of them are taken depends on the hour you are reading this. Stepping in here does not throw the city away — the city scene is retained and paused rather than disposed, because rebuilding the Bay's heightfield costs about 2.3 seconds and nobody should pay that to walk back out of a door.", day: { at: "2026-08-06T11:20:00-07:00", note: "Twenty past eleven, daylight through the glazing on the north edge.", @@ -209,10 +209,49 @@ const SHOTS = [ }, night: { at: "2026-08-06T21:30:00-07:00", - note: "Half past nine, and the windows have stopped being the light source.", + note: "Half past nine, and the windows have stopped being the light source. The fittings have taken over, and two humanoids are walking the floor.", alt: "The same desks at night, lit by the office's own lighting with darkness beyond the windows.", }, }, + { + id: "office-commons", + door: "office", + chapter: 5, + expect: "Commons", + place: "Lumbridge HQ", + caption: + "Fourteen metres of floor open all the way to the roof, with a gallery cutting across one side of it. The two storeys are held apart by a fourteen-metre interstitial — not architecture, a deliberate exaggeration of the service void, so that the space between the slabs is somewhere things can hang. Everything you can see is composed at runtime from cached unit primitives; no mesh in this repo was ever a file.", + day: { + at: "2026-08-06T11:20:00-07:00", + note: "Twenty past eleven, and the sky through the glazing is the real sky over this coordinate.", + alt: "A tall interior atrium seen from floor level: pale terrazzo, trees, lounge seating, with a balcony crossing high above and daylight beyond the glass.", + }, + night: { + at: "2026-08-06T21:30:00-07:00", + note: "Half past nine, with the pendants hanging nineteen metres into the void.", + alt: "The same atrium at night, lit by pendants high in the volume.", + }, + }, + { + id: "office-hangar", + door: "office", + office: "Frontier Valley", + chapter: 0, + expect: "Hangar", + place: "Frontier Valley, Alameda Point", + caption: + "A second building, and the argument for the format being a format: one room, fifty-four metres by thirty, nine to the trusses, in a hangar on the old naval air station across the estuary. Same engine, same clock, same sun — and it feels nothing like the tower, because an office pack now says where on the earth it stands and how it is turned, and four metres above an airfield is not a hundred and eighty-eight above Transbay.", + day: { + at: "2026-08-06T11:20:00-07:00", + note: "Twenty past eleven. North light through the clerestory, which is the light a shed actually wants.", + alt: "A large open hangar interior rendered in 3D: benches of desks down the middle, freestanding meeting boxes at one end, a mezzanine deck at the other, and a twelve-metre door.", + }, + night: { + at: "2026-08-06T21:30:00-07:00", + note: "Half past nine, and the trusses are carrying the lights.", + alt: "The same hangar at night, lit from the roof trusses.", + }, + }, ]; /** The two frames every shot is taken in, in the order they are rendered. */ @@ -378,6 +417,40 @@ async function shoot(browser, shot, frame) { ); // Boot hiding means the scene exists, not that it has drawn a full frame. await page.waitForTimeout(preview ? 3000 : 6000); + /** + * Which building, before which view of it. + * + * The office door opens on the first entry in `OFFICES` and there are two + * of them now, so a shot of the hangar has to press the picker first — and + * has to do it *before* choosing a chapter, because switching buildings + * disposes the scene and rebuilds it, which would strand a chapter click + * against a legend that no longer exists. + * + * Selected by visible label rather than by index, and asserted, for the same + * reason `chapter()` asserts its `shortLabel`: a reordered `OFFICES` table + * would otherwise silently photograph the wrong building and caption it with + * the other one's name. + */ + if (shot.office) { + const button = page.locator("#cities button", { hasText: shot.office }); + const count = await button.count(); + if (count === 0) { + throw new Error( + `no office picker button matching "${shot.office}" — the OFFICES table ` + + `changed, so the shot list and its captions need re-checking`, + ); + } + await button.first().click(); + // A building switch tears the scene down and builds another. Wait on the + // app again rather than on a clock, exactly as the first boot does. + await page.waitForFunction( + () => + document.getElementById("boot")?.hidden === true && + document.querySelectorAll("#chapters .chapter").length > 0, + { timeout: 180_000 }, + ); + await page.waitForTimeout(preview ? 3000 : 6000); + } await chapter(page, shot.chapter, shot.expect, { click: shot.chapter > 0 }); if (shot.chapter > 0) await page.waitForTimeout(2500); await hide(page, [...FURNITURE.transient, ...FURNITURE.CLUTTER]); diff --git a/vite.config.ts b/vite.config.ts index 96972e6..c5abaad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -37,7 +37,7 @@ function twoDoors(): Plugin {