1
0

Every shot is taken twice, and the site shows you the other one

Each of the seven cameras now fires at two clocks — one daylight frame, one
around sixteen degrees below the horizon — and lumbridgecorp.com loads whichever
is the opposite of the reader's theme. A night render on that site's dark page
is a dark rectangle on a dark page: the edges dissolve and the picture stops
being an object. A daylit one on the white page has the same problem in reverse,
and the hazier frames wash out entirely.

It is also the cheapest demonstration available of the claim those pages lean on
hardest. The sun is computed from a real time rather than themed, and a reader
who doubts it can hit the light/dark toggle and watch the same city change hour
with nothing else about the geometry moving.

Shot ids lose their time of day — `soma-afternoon` is `soma`, `fidi-night` is
`fidi`, `peninsula-morning` is `peninsula` — because none of them names one hour
any more. The manifest gains a `Frame` type: `at`, `src`, `alt` and a `note`
belong to an hour, while `place` and `caption` are true of both, so a caption
cannot end up describing light that is not in the frame under it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 15:33:19 -07:00
parent d71e844703
commit d04a75b9bd
+153 -62
View File
@@ -59,10 +59,21 @@ const ROOT = join(HERE, "..", "..");
* reordered pack silently reframes every picture on the site and captions them
* with the old names.
*
* Every shot is taken **twice from the same camera**, once by day and once at
* night, and the site loads whichever suits the theme the reader is in — a dark
* page gets the daylit frame and a light page gets the night one, because a
* night render on a dark page dissolves into it and a hazy noon render on a
* white page washes out. One camera and two clocks also happens to be the
* cheapest possible demonstration of the thing these pages claim hardest: the
* sun is computed from a real time, not painted on.
*
* Times are local to the city and are chosen, not defaulted. Midday is the
* worst light a heightfield ever gets — the sun is behind the camera, nothing
* casts, and 83k buildings read as gravel. The relief shots are taken in the
* morning and the city shots at dusk for the same reason a photographer would.
* casts, and 83k buildings read as gravel. The daylight frames are taken in the
* morning or late afternoon for the same reason a photographer would; the night
* frames sit around sixteen degrees below the horizon, which is dark enough for
* the window lights to carry the picture and not so dark that the ground has
* gone.
*/
const SHOTS = [
{
@@ -71,47 +82,79 @@ const SHOTS = [
city: "sf",
chapter: 0,
expect: "Whole Board",
at: "2026-08-06T07:40:00-07:00",
place: "San Francisco Bay Area",
caption:
"The whole region at twenty to eight in the morning, sun fifteen degrees up. 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.",
alt: "The San Francisco Bay Area seen from above in low morning light, the peninsula and the East Bay hills raking shadows across the water.",
"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-06T07:40:00-07:00",
note: "Twenty to eight, sun fifteen degrees up: low enough that the hills rake shadow across the flats and the relief reads as relief.",
alt: "The San Francisco Bay Area seen from above in low morning light, the peninsula and the East Bay hills raking shadows across the water.",
},
night: {
at: "2026-08-06T21:35:00-07:00",
note: "Twenty-five to ten, and the same board is a scatter of lit ground between two dark ranges — the shape of the region is now where the cities are, not where the hills are.",
alt: "The same view of the Bay Area at night, the built-up ground picked out in clusters of light between dark hills and darker water.",
},
},
{
id: "soma-afternoon",
id: "soma",
door: "tera",
city: "sf",
chapter: 2,
expect: "SoMa",
at: "2026-08-06T17:20:00-07:00",
place: "SoMa, San Francisco",
caption:
"South of Market at twenty past five, where the grid turns forty-six degrees and the blocks get long. About 83,000 buildings are on screen here, drawn as instanced geometry on street bearings the city pack declares per district.",
alt: "Downtown San Francisco south of Market in afternoon light, towers clustered on a street grid turned at an angle to the rest of the city.",
"South of Market, where the grid turns forty-six degrees and the blocks get long. About 83,000 buildings are on screen here, drawn as instanced geometry on street bearings the city pack declares per district.",
day: {
at: "2026-08-06T17:20:00-07:00",
note: "Twenty past five.",
alt: "Downtown San Francisco south of Market in afternoon light, towers clustered on a street grid turned at an angle to the rest of the city.",
},
night: {
at: "2026-08-06T21:35:00-07:00",
note: "Twenty-five to ten. Nothing about the geometry changed — only the hour the page was asked for.",
alt: "The same towers south of Market at night, windows lit in a scatter across every face.",
},
},
{
id: "fidi-night",
id: "fidi",
door: "tera",
city: "sf",
chapter: 3,
expect: "FiDi",
at: "2026-08-06T21:40:00-07:00",
place: "Financial District, San Francisco",
caption:
"The same city at twenty to ten, sun sixteen degrees below the horizon. The lights are not a night theme — they are emissive window faces chosen from a seeded RNG as the computed sun goes down. The moonlight carrying the ground is about five orders of magnitude brighter than the real moon-to-sun ratio, deliberately: the honest number renders a black rectangle.",
alt: "The San Francisco financial district at night, tower windows lit from within, the Bay Bridge running off to the right and the ground reading dark blue rather than black.",
"The old money and the venture money, stacked between Montgomery and the Embarcadero under the pyramid, with the Bay Bridge running out to the right.",
day: {
at: "2026-08-06T10:10:00-07:00",
note: "Ten past ten in the morning, sun forty-four degrees up.",
alt: "The San Francisco financial district from above in mid-morning light, the pyramid among the towers and the Bay Bridge crossing to the right.",
},
night: {
at: "2026-08-06T21:40:00-07:00",
note: "Twenty to ten, sun sixteen degrees below the horizon. The lights are not a night theme — they are emissive window faces chosen from a seeded RNG as the computed sun goes down. The moonlight carrying the ground is about five orders of magnitude brighter than the real moon-to-sun ratio, deliberately: the honest number renders a black rectangle.",
alt: "The San Francisco financial district at night, tower windows lit from within and the ground reading dark blue rather than black.",
},
},
{
id: "peninsula-morning",
id: "peninsula",
door: "tera",
city: "sf",
chapter: 6,
expect: "Peninsula",
at: "2026-08-06T09:20:00-07:00",
place: "The Peninsula",
caption:
"Twenty miles of city between the water and the ridge, never more than four wide, with an aircraft on approach drawing the line across the bay. 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.",
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.",
"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.",
},
night: {
at: "2026-08-06T21:35:00-07:00",
note: "Twenty-five to ten, and the ridge is legible only as the edge the lights stop at.",
alt: "The San Francisco peninsula at night, a band of lit ground between dark water and a darker mountain ridge.",
},
},
{
id: "socal-dtla",
@@ -119,36 +162,63 @@ const SHOTS = [
city: "socal",
chapter: 1,
expect: "DTLA",
at: "2026-08-06T18:10:00-07:00",
place: "Downtown Los Angeles",
caption:
"The second city pack, on the same engine and the same renderer. A city is pure data — coastlines, hills, district street bearings, landmarks and camera chapters in one file with no code in it — so adding one is a reviewable contribution rather than a fork. The camera is square to downtown's grid, which is why everything else in the frame looks crooked.",
alt: "Downtown Los Angeles from above in late afternoon light, the river running past the tower cluster and the San Gabriel mountains standing behind the basin.",
day: {
at: "2026-08-06T18:10:00-07:00",
note: "Ten past six, the San Gabriels standing behind the basin.",
alt: "Downtown Los Angeles from above in late afternoon light, the river running past the tower cluster and the San Gabriel mountains behind the basin.",
},
night: {
at: "2026-08-06T21:20:00-07:00",
note: "Twenty past nine. The basin is the one place on either board where the lit ground runs to the edge of the frame in every direction.",
alt: "Downtown Los Angeles at night, the tower cluster lit and the surrounding basin covered in light to the horizon.",
},
},
{
id: "office-floor",
door: "office",
chapter: 0,
expect: "The Floor",
at: "2026-08-06T11:20:00-07:00",
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.",
alt: "A whole office interior rendered in 3D as an open-topped model, walls, desks and meeting rooms laid out across one floor, with its plan drawn beside it.",
day: {
at: "2026-08-06T11:20:00-07:00",
note: "Twenty past eleven in the morning.",
alt: "A whole office interior rendered in 3D as an open-topped model, walls, desks and meeting rooms laid out across one floor, with its plan drawn beside it.",
},
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.",
},
},
{
id: "office-desks",
door: "office",
chapter: 2,
expect: "Desks",
at: "2026-08-06T11:20:00-07:00",
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.",
alt: "A closer view inside the office: rows of desks with chairs and monitors, people seated at them, bookshelves and planting behind a partition.",
day: {
at: "2026-08-06T11:20:00-07:00",
note: "Twenty past eleven, daylight through the glazing on the north edge.",
alt: "A closer view inside the office: rows of desks with chairs and monitors, people seated at them, bookshelves and planting behind a partition.",
},
night: {
at: "2026-08-06T21:30:00-07:00",
note: "Half past nine, and the windows have stopped being the light source.",
alt: "The same desks at night, lit by the office's own lighting with darkness beyond the windows.",
},
},
];
/** The two frames every shot is taken in, in the order they are rendered. */
const VARIANTS = ["day", "night"];
// ---- Sizes ------------------------------------------------------------------
/**
@@ -214,7 +284,7 @@ const commit = git("rev-parse", "--short", "HEAD");
* render something else.
*/
const dirty = git("status", "--porcelain").length > 0;
const day = new Date().toISOString().slice(0, 10);
const today = new Date().toISOString().slice(0, 10);
// ---- Capture ----------------------------------------------------------------
@@ -268,7 +338,7 @@ async function chapter(page, index, expect, { click = false } = {}) {
}
}
async function shoot(browser, shot) {
async function shoot(browser, shot, frame) {
const host = shot.door === "office" ? "office.lumbridgecorp.com" : "tera.lumbridgecorp.com";
const query = shot.city ? `?city=${shot.city}` : "";
const page = await browser.newPage({
@@ -292,7 +362,7 @@ async function shoot(browser, shot) {
const problems = [];
page.on("pageerror", (e) => problems.push(String(e)));
try {
await page.addInitScript(clockShim(shot.at));
await page.addInitScript(clockShim(frame.at));
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.
@@ -355,33 +425,36 @@ try {
const codec = await browser.newPage();
await codec.goto("about:blank");
const archiveDir = join(archiveRoot, `${day}-${commit}${dirty ? "-dirty" : ""}`);
const archiveDir = join(archiveRoot, `${today}-${commit}${dirty ? "-dirty" : ""}`);
if (!preview) await mkdir(archiveDir, { recursive: true });
for (const shot of wanted) {
const png = await shoot(browser, shot);
if (preview) {
await mkdir(join(ROOT, "shots", "preview"), { recursive: true });
await writeFile(join(ROOT, "shots", "preview", `${shot.id}.png`), png);
console.log("preview", shot.id);
continue;
}
for (const variant of VARIANTS) {
const stem = `${shot.id}-${variant}`;
const png = await shoot(browser, shot, shot[variant]);
if (preview) {
await mkdir(join(ROOT, "shots", "preview"), { recursive: true });
await writeFile(join(ROOT, "shots", "preview", `${stem}.png`), png);
console.log("preview", stem);
continue;
}
await writeFile(
join(archiveDir, `${shot.id}.webp`),
await encode(codec, png, VIEWPORT.width * 2, ARCHIVE_QUALITY),
);
await writeFile(
join(archiveDir, `${stem}.webp`),
await encode(codec, png, VIEWPORT.width * 2, ARCHIVE_QUALITY),
);
const sizes = {};
for (const size of SIZES) {
sizes[size.suffix] = await encode(codec, png, size.width, size.quality);
const sizes = {};
for (const size of SIZES) {
sizes[size.suffix] = await encode(codec, png, size.width, size.quality);
}
results.push({ stem, sizes });
console.log(
"shot ",
stem.padEnd(22),
SIZES.map((s) => `${s.suffix}w ${Math.round(sizes[s.suffix].length / 1024)}kB`).join(" "),
);
}
results.push({ shot, sizes });
console.log(
"shot ",
shot.id.padEnd(16),
SIZES.map((s) => `${s.suffix}w ${Math.round(sizes[s.suffix].length / 1024)}kB`).join(" "),
);
}
if (preview) {
@@ -394,19 +467,19 @@ try {
} else {
await mkdir(publicDir, { recursive: true });
await mkdir(dataDir, { recursive: true });
for (const { shot, sizes } of results) {
for (const { stem, sizes } of results) {
for (const size of SIZES) {
await writeFile(join(publicDir, `${shot.id}-${size.suffix}.webp`), sizes[size.suffix]);
await writeFile(join(publicDir, `${stem}-${size.suffix}.webp`), sizes[size.suffix]);
}
}
// Regenerating the manifest from a partial run would drop every shot the
// run did not take, so `--only` writes images and leaves the manifest be.
if (only) {
console.log(`site ${results.length} image set(s)${publicDir}`);
console.log(`site ${results.length} frames${publicDir}`);
console.log(" manifest left alone (partial run; re-run without --only to rewrite it)");
} else {
await writeFile(join(dataDir, "shots.ts"), manifest());
console.log(`site ${results.length} image sets → ${publicDir}`);
console.log(`site ${results.length} frames → ${publicDir}`);
console.log(` manifest → ${join(dataDir, "shots.ts")}`);
}
}
@@ -420,18 +493,26 @@ try {
function manifest() {
const aspect = VIEWPORT.width / VIEWPORT.height;
const frame = (id, variant) => {
const f = SHOTS.find((s) => s.id === id)[variant];
return `{
at: ${JSON.stringify(f.at)},
src: ${JSON.stringify(`/shots/${id}-${variant}-1600.webp`)},
srcSmall: ${JSON.stringify(`/shots/${id}-${variant}-800.webp`)},
alt: ${JSON.stringify(f.alt)},
note: ${JSON.stringify(f.note)},
}`;
};
const entries = SHOTS.map(
(s) => ` {
id: ${JSON.stringify(s.id)},
door: ${JSON.stringify(s.door)},
place: ${JSON.stringify(s.place)},
at: ${JSON.stringify(s.at)},
src: ${JSON.stringify(`/shots/${s.id}-1600.webp`)},
srcSmall: ${JSON.stringify(`/shots/${s.id}-800.webp`)},
width: ${SIZES[0].width},
height: ${Math.round(SIZES[0].width / aspect)},
alt: ${JSON.stringify(s.alt)},
caption: ${JSON.stringify(s.caption)},
day: ${frame(s.id, "day")},
night: ${frame(s.id, "night")},
},`,
).join("\n");
@@ -441,7 +522,8 @@ function manifest() {
* poses that framed them so the two cannot drift apart.
*
* To change a picture or its caption: edit \`SHOTS\` in that script, run
* \`node scripts/brand-assets/shots.mjs\`, and commit both repos.
* \`node scripts/brand-assets/shots.mjs\`, and commit both repos. A caption-only
* change can use \`--manifest-only\` and skip the render entirely.
*
* Every image is a screenshot of tera's built \`dist/\` at the commit named
* below. Nothing here is an illustration.
@@ -455,20 +537,29 @@ function manifest() {
export type ShotId =
${SHOTS.map((s) => ` | ${JSON.stringify(s.id)}`).join("\n")};
/** One camera, one clock. Every shot has two of these and differs only in the hour. */
export interface Frame {
/** The instant the engine's own clock was set to. The sun follows from it. */
at: string;
src: string;
srcSmall: string;
alt: string;
/** What this hour in particular is showing, appended to the shot's caption. */
note: string;
}
export interface Shot {
id: ShotId;
/** Which door the engine was showing: the city, or an office interior. */
door: "tera" | "office";
/** Where this is, in words a reader would use. */
place: string;
/** The instant the engine's own clock was set to. The sun follows from it. */
at: string;
src: string;
srcSmall: string;
width: number;
height: number;
alt: string;
/** True of both frames — the place and the mechanism, never the light. */
caption: string;
day: Frame;
night: Frame;
}
/** The tera commit these were rendered from. */
@@ -476,7 +567,7 @@ export const SHOTS_COMMIT = ${JSON.stringify(commit)};
/** True if that commit is not the whole story — the tree had uncommitted work. */
export const SHOTS_DIRTY = ${dirty};
/** The day the shutter opened, ISO. */
export const SHOTS_CAPTURED = ${JSON.stringify(day)};
export const SHOTS_CAPTURED = ${JSON.stringify(today)};
export const SHOTS: Shot[] = [
${entries}