feat: give the boards a horizon, a sea that reflects, and a state worth flying over
The wide shot, which is what an anonymous visitor actually lands on. **The sea was `MeshLambertMaterial`** — a material with no specular term at all, by construction — on a board where water is half the frame. It is now a low-roughness dielectric that reads `scene.environment`, with a runtime-generated tiling swell normal map sampled twice per fragment at two scales and two headings, so the sun breaks into a moving glitter path instead of a mirror point. An `onBeforeCompile` patch takes the body colour toward the deep value looking straight down and leaves it to the reflection at grazing, and walks roughness up past 1.6 board spans so the far water cannot shimmer. The swell spectrum is 1/k^2 and not 1/k because the first attempt was photographed: at 1/k every component carries the same slope, the shortest wave wins, and the sea renders as hard diagonal corduroy. A test holds it now. **The board no longer ends in a diamond.** The sea plane went from 1.8 board spans to 18, past the fog's far plane from anywhere the orbit reaches, and the sky is a world-space dome rather than a screen-space gradient. That gradient was wrong in a way dusk made obvious: the sunset band was painted along the *bottom* of the picture, under the board, while the true horizon at the top of frame stayed zenith blue. `daylight.ts` pinning the horizon stop to the fog colour to hide the seam was a symptom of it. **Terrain casts shadows.** Left off before because double-sided terrain against a ~16 m-per-texel shadow map gives acne; `shadowSide = BackSide` is the cure, shot at four sun elevations down to +0.0 degrees to confirm no stippling. The caster is a stride-2 decimation appended to the same index buffer and swapped in by `onBeforeShadow`/`onAfterShadow` via `drawRange`: no extra draw call, a quarter of the depth cost, and indistinguishable from the full-resolution caster in a side-by-side crop. Stride 1 was measured at +65,566 triangles and would have missed the budget by ~47,000, so it was not shipped. **California reads as California.** It was a beige kite: the eastern edge one ruled line for five degrees of latitude, the south closing in a diagonal V, the whole south-east a featureless tan wedge. Now the coast runs to the Mexican border with San Diego on it, the eastern edge follows the Colorado and the Nevada diagonal, and the south-east is the Basin and Range — forty parallel desert ridges throwing shadows east, Death Valley as a white pan between the Panamints and the Black Mountains, the Salton Sea the one cool value for two hundred kilometres. The opening pose is retuned to the bigger board; the old 452/392 stand-off left a slab of empty ocean where the state should be. **The aircraft were six pixels.** Measured, by enlarging a screenshot 200% to find one at all — indistinguishable from a dead pixel, on a board whose entire claim is that the sky is live. They are airliners now, with planform and trail, and clicking one raises its card for a signed-out visitor. **The Model X is off the wall.** It stood at floor level outside a studio 188 m up a Transbay tower, reading as a car balanced on a parapet. The apron is now chosen from `site.elevation`, which the pack already carries — not from an office id, which is the bug class this repo already hit once when a door marker gated on `id === "sf"` and would have pinned the Los Angeles building to San Francisco. Also fixed, and nearly shipped: sea z-fighting dithered every flat piece of ground on the Bay Area and SoCal boards. And one test asserted an exact source line for the water material, so the better multi-line implementation failed it — it now asserts the property (dielectric, metalness 0, low roughness) rather than the author's first guess at formatting. Tests 964 -> 1015. California desktop 562/650 draw calls and 728,744/750,000 triangles — 2.8% of triangle headroom left, which is the number the next person should check first. No budget was raised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* The statewide California board — the default one, the first frame an
|
||||
* anonymous visitor sees.
|
||||
*
|
||||
* Every defect this file guards against **typechecked, rendered without a
|
||||
* console error, and met every performance budget.** They were only ever
|
||||
* visible by looking at the board, which is why they survived for so long and
|
||||
* why the assertions below are shaped the way they are: each one is the
|
||||
* cheapest arithmetic statement of something a person found in a screenshot.
|
||||
*
|
||||
* 1. **A self-intersecting coastline.** San Francisco Bay was traced as a
|
||||
* concavity in the landmass, and the polygon's closure edge ran across its
|
||||
* head. `isLand` still answered correctly and the terrain grid still left
|
||||
* the hole, but `ShapeGeometry` triangulated the slit shut and the shore
|
||||
* plate paved the entire bay. Nothing threw. The bay was simply not there.
|
||||
* 2. **A board with no relief.** At an exaggeration of 2.25 a 3,000 m range
|
||||
* stood 1.6 units off a board 284 units tall — five tenths of one percent.
|
||||
* Every hill was in the pack, every hill was in the heightfield, and the
|
||||
* state looked like a beach.
|
||||
* 3. **Cities that produce no buildings.** A district drawn inside a park
|
||||
* envelope emits zero lots, because `createBlocks` skips every lot in a
|
||||
* park. One did, silently, and read as an empty valley.
|
||||
* 4. **A corridor that climbs a mountain nobody meant to put there.** Hill
|
||||
* radii are in degrees and the routes are hand-traced; a range centred
|
||||
* half a degree from US-101 puts a kilometre of climb into the Salinas
|
||||
* Valley and neither the pack nor the renderer has an opinion about it.
|
||||
*
|
||||
* The blocks-scale assertions at the end are here for a different reason: they
|
||||
* are the promise that making the state board legible did not disturb the two
|
||||
* boards that already looked right.
|
||||
*/
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
|
||||
import CALIFORNIA_CITY, { CALIFORNIA_I_5, CALIFORNIA_US_101 } from "../../cities/california.ts";
|
||||
import SF_CITY from "../../cities/sf.ts";
|
||||
import SOCAL_CITY from "../../cities/socal.ts";
|
||||
import { createBlocks } from "../../engine/blocks.ts";
|
||||
import type { City, LatLng } from "../../engine/types.ts";
|
||||
import { World } from "../../engine/world.ts";
|
||||
|
||||
/** Do two closed segments cross, endpoints excluded? */
|
||||
function crosses(a: LatLng, b: LatLng, c: LatLng, d: LatLng): boolean {
|
||||
const side = (p: LatLng, q: LatLng, r: LatLng): number =>
|
||||
Math.sign((q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]));
|
||||
return side(a, b, c) !== side(a, b, d) && side(c, d, a) !== side(c, d, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every pair of non-adjacent edges in a ring, which is quadratic and does not
|
||||
* matter: the biggest ring in the repo is a few hundred vertices and this runs
|
||||
* in single-digit milliseconds.
|
||||
*/
|
||||
function selfIntersections(ring: readonly LatLng[]): Array<[number, number]> {
|
||||
const hits: Array<[number, number]> = [];
|
||||
const n = ring.length;
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
for (let j = i + 2; j < n; j += 1) {
|
||||
if (i === 0 && j === n - 1) continue; // the closing edge touches the first
|
||||
const a = ring[i];
|
||||
const b = ring[(i + 1) % n];
|
||||
const c = ring[j];
|
||||
const d = ring[(j + 1) % n];
|
||||
if (!a || !b || !c || !d) continue;
|
||||
if (crosses(a, b, c, d)) hits.push([i, j]);
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `World` whose heightfield is already built.
|
||||
*
|
||||
* `ready()` waits on a paint that never comes under the Node test runner, so
|
||||
* this takes the documented synchronous path instead: `lattice()` builds the
|
||||
* field on the calling thread when nobody awaited `ready()`, which is the same
|
||||
* fallback a browser with Workers blocked takes.
|
||||
*/
|
||||
function builtWorld(city: City): World {
|
||||
const world = new World(city);
|
||||
world.lattice();
|
||||
return world;
|
||||
}
|
||||
|
||||
describe("California board — geometry that only a picture used to catch", () => {
|
||||
it("traces every coastline as a simple polygon", () => {
|
||||
for (const city of [CALIFORNIA_CITY, SF_CITY, SOCAL_CITY]) {
|
||||
for (const [index, ring] of city.landmasses.entries()) {
|
||||
const hits = selfIntersections(ring);
|
||||
assert.deepEqual(
|
||||
hits,
|
||||
[],
|
||||
`${city.id} landmass ${index} crosses itself at ${JSON.stringify(hits)}; ` +
|
||||
"ShapeGeometry will quietly triangulate the slit shut and pave whatever is inside it",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps San Francisco Bay as water joined to the Pacific", () => {
|
||||
const world = new World(CALIFORNIA_CITY);
|
||||
// Down the middle of the bay, from San Pablo to the south bay, plus the
|
||||
// Golden Gate itself. Every one of these was dry land when the contour
|
||||
// self-intersected.
|
||||
for (const [lat, lng] of [
|
||||
[38.0, -122.35],
|
||||
[37.9, -122.35],
|
||||
[37.8, -122.33],
|
||||
[37.7, -122.25],
|
||||
[37.6, -122.16],
|
||||
[37.5, -122.05],
|
||||
[37.83, -122.5],
|
||||
] as LatLng[]) {
|
||||
assert.equal(world.isLand(lat, lng), false, `${lat},${lng} should be bay`);
|
||||
}
|
||||
// And the two shores are still land, so the bay is a strait and not a hole
|
||||
// punched through the peninsula.
|
||||
assert.equal(world.isLand(37.76, -122.44), true, "San Francisco");
|
||||
assert.equal(world.isLand(37.8, -122.15), true, "the East Bay");
|
||||
});
|
||||
|
||||
it("stands the ranges up far enough to be seen from the state camera", () => {
|
||||
const world = builtWorld(CALIFORNIA_CITY);
|
||||
const { bounds } = CALIFORNIA_CITY;
|
||||
const boardUnits = (bounds.maxLat - bounds.minLat) * CALIFORNIA_CITY.latScale;
|
||||
|
||||
let peak = 0;
|
||||
for (const metres of world.lattice().height) if (metres > peak) peak = metres;
|
||||
const peakUnits = world.metres(peak);
|
||||
|
||||
assert.ok(peak > 4_000, `the highest ground is only ${Math.round(peak)} m`);
|
||||
// 8% of the board's own height. Southern California's San Gabriels clear
|
||||
// this comfortably; the old 2.25 exaggeration put this board at 0.6%.
|
||||
assert.ok(
|
||||
peakUnits / boardUnits > 0.08,
|
||||
`relief is ${((peakUnits / boardUnits) * 100).toFixed(1)}% of the board — flat`,
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves the Central Valley a genuine flat between two ranges", () => {
|
||||
const world = builtWorld(CALIFORNIA_CITY);
|
||||
// A line up the middle of the valley floor, and a matching line along the
|
||||
// Sierra crest. Both axes lean west as they run north, which is why they are
|
||||
// interpolated rather than held at one longitude: the valley at Bakersfield
|
||||
// is at -119.2 and at Stockton it is at -121.4, and a straight line down one
|
||||
// meridian walks out of the valley and up into the foothills.
|
||||
for (let lat = 35.6; lat <= 37.8; lat += 0.2) {
|
||||
const lng = -119.2 - (lat - 35.4) * 0.88;
|
||||
const floor = world.elevationAt(lat, lng);
|
||||
assert.ok(floor < 260, `the valley floor at ${lat.toFixed(1)}N is ${Math.round(floor)} m`);
|
||||
}
|
||||
// The crest is sampled at its own longitudes rather than off a straight
|
||||
// line: the Sierra swings from -118.3 at Whitney to -119.6 at Sonora, and a
|
||||
// meridian drawn through both ends misses the range in the middle.
|
||||
for (const [lat, lng] of [
|
||||
[36.2, -118.28],
|
||||
[36.45, -118.28],
|
||||
[36.62, -118.29],
|
||||
[36.85, -118.38],
|
||||
[37.05, -118.52],
|
||||
[37.25, -118.72],
|
||||
[37.45, -118.92],
|
||||
[37.65, -119.12],
|
||||
] as LatLng[]) {
|
||||
const crest = world.elevationAt(lat, lng);
|
||||
assert.ok(crest > 2_000, `the Sierra at ${lat.toFixed(2)}N is only ${Math.round(crest)} m`);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the two corridors in the valleys, and climbs only where a driver climbs", () => {
|
||||
const world = builtWorld(CALIFORNIA_CITY);
|
||||
/**
|
||||
* The named passes, and nothing else, each with its own reach.
|
||||
*
|
||||
* Newhall is a single notch behind Santa Clarita and the Cuesta Grade is one
|
||||
* climb out of San Luis Obispo, so both are tight. The Grapevine is not a
|
||||
* pass in that sense at all: I-5 leaves the Los Angeles basin at Castaic and
|
||||
* does not come down again until Wheeler Ridge forty kilometres later, over
|
||||
* Gorman and Tejon, and a small circle round the summit would call most of
|
||||
* that ascent an error.
|
||||
*/
|
||||
const passes: Array<{ at: LatLng; reach: number }> = [
|
||||
{ at: [34.3917, -118.5426], reach: 0.16 }, // Newhall
|
||||
{ at: [34.75, -118.8], reach: 0.4 }, // the Grapevine: Castaic to Wheeler Ridge
|
||||
{ at: [35.2828, -120.6596], reach: 0.2 }, // the Cuesta Grade
|
||||
];
|
||||
const nearAPass = (lat: number, lng: number): boolean =>
|
||||
passes.some(({ at, reach }) => Math.hypot(lat - at[0], (lng - at[1]) * 0.81) < reach);
|
||||
|
||||
for (const [name, path] of [["US-101", CALIFORNIA_US_101], ["I-5", CALIFORNIA_I_5]] as const) {
|
||||
for (let index = 0; index < path.length - 1; index += 1) {
|
||||
const from = path[index];
|
||||
const to = path[index + 1];
|
||||
if (!from || !to) continue;
|
||||
for (let step = 0; step <= 20; step += 1) {
|
||||
const t = step / 20;
|
||||
const lat = from[0] + (to[0] - from[0]) * t;
|
||||
const lng = from[1] + (to[1] - from[1]) * t;
|
||||
const metres = world.elevationAt(lat, lng);
|
||||
const cap = nearAPass(lat, lng) ? 1_500 : 700;
|
||||
assert.ok(
|
||||
metres < cap,
|
||||
`${name} climbs to ${Math.round(metres)} m at ${lat.toFixed(2)},${lng.toFixed(2)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("California board — the built state", () => {
|
||||
const world = builtWorld(CALIFORNIA_CITY);
|
||||
const blocks = createBlocks(world);
|
||||
|
||||
it("builds every district it declares", () => {
|
||||
// A district drawn inside a park envelope, or out in the bay, emits nothing
|
||||
// at all and there is no warning anywhere. Each one is rebuilt alone so the
|
||||
// empty one is named rather than hidden in the total.
|
||||
for (const district of CALIFORNIA_CITY.districts) {
|
||||
const alone = new World({ ...CALIFORNIA_CITY, districts: [district] });
|
||||
alone.lattice();
|
||||
const count = createBlocks(alone).count;
|
||||
assert.ok(count > 20, `district "${district.id}" produced ${count} lots`);
|
||||
}
|
||||
});
|
||||
|
||||
it("makes Los Angeles and the Bay Area read as settlements, not as specks", () => {
|
||||
const within = (minLat: number, maxLat: number, minLng: number, maxLng: number): number => {
|
||||
let n = 0;
|
||||
for (const district of CALIFORNIA_CITY.districts) {
|
||||
const inside = district.polygon.every(
|
||||
([lat, lng]) => lat >= minLat && lat <= maxLat && lng >= minLng && lng <= maxLng,
|
||||
);
|
||||
if (!inside) continue;
|
||||
const alone = new World({ ...CALIFORNIA_CITY, districts: [district] });
|
||||
alone.lattice();
|
||||
n += createBlocks(alone).count;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
assert.ok(within(33.4, 34.4, -118.7, -117.6) > 3_000, "the Los Angeles basin is thin");
|
||||
assert.ok(within(37.1, 38.1, -122.6, -121.7) > 1_500, "the Bay Area is thin");
|
||||
});
|
||||
|
||||
it("stays inside the triangle budget it was sized against", () => {
|
||||
// The California board is the tight one: 650 draw calls and 750,000
|
||||
// triangles, shared with the aircraft layer. One instanced box is twelve
|
||||
// triangles, so this ceiling is about 110k of them — roughly a seventh of
|
||||
// the whole board. A district enlarged without checking is the easy way to
|
||||
// blow the budget, and `scripts/performance-budget.mjs` needs a browser and
|
||||
// a minute to say so.
|
||||
assert.ok(blocks.count < 9_200, `${blocks.count} lots is over what the budget was sized for`);
|
||||
assert.ok(blocks.count > 7_000, `${blocks.count} lots is thinner than the board was tuned to`);
|
||||
});
|
||||
|
||||
it("drops the street lattice and the shadow pass only where a lot is a neighbourhood", () => {
|
||||
// The rule in `blocks.ts` is about how much ground a lot covers, not about
|
||||
// which board it is. Stated here as the fact it is derived from, so the two
|
||||
// detailed boards are provably untouched by it.
|
||||
const lotMetres = (city: City): number => 0.42 * (111_320 / city.latScale);
|
||||
assert.ok(lotMetres(SF_CITY) < 260, "San Francisco must keep its street grid");
|
||||
assert.ok(lotMetres(SOCAL_CITY) < 260, "Southern California must keep its street grid");
|
||||
assert.ok(lotMetres(CALIFORNIA_CITY) > 260, "the state board must not draw 800 m streets");
|
||||
|
||||
assert.equal(blocks.castShadow, false, "state-scale lots cost a second pass for one pixel");
|
||||
const socal = builtWorld(SOCAL_CITY);
|
||||
assert.equal(createBlocks(socal).castShadow, true, "Southern California lost its shadows");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* The freeway corridor on the statewide board, which had two problems and only
|
||||
* one of them was visible.
|
||||
*
|
||||
* **It read as a wireframe.** At 1,919 m to the scene unit the whole corridor is
|
||||
* about eleven pixels wide from the default camera, and eleven pixels of flat
|
||||
* mid-grey lying exactly on the ground is a line somebody drew on a map. It is
|
||||
* now a graded crown with two batters, which gives it three value bands and a
|
||||
* normal that is not straight up — see `createFreewayWorld`.
|
||||
*
|
||||
* **It cost a fifth of the board's triangle budget on things nobody can see.**
|
||||
* Guardrails and median walls were tubes at two segments per draped sample on a
|
||||
* corridor already sampled every kilometre, and the reflectors were 2,296 boxes
|
||||
* eighteen millimetres across. Between them: 110,000 triangles on a board with
|
||||
* 75,000 to spare, which is why the state had no mountains and no cities on it.
|
||||
*
|
||||
* The two assertions below are the ones that would have caught the two defects
|
||||
* this cost a rebuild to find:
|
||||
*
|
||||
* - **Every batter faces the sky.** `deck` materials are `DoubleSide` and
|
||||
* three.js negates the shading normal on a back face, so a strip whose two
|
||||
* rails were emitted in the opposite order to its neighbours renders as an
|
||||
* unlit black band. One did, the length of US-101, and it typechecked.
|
||||
* - **The corridor stays under its triangle ceiling.** The real gate is
|
||||
* `scripts/performance-budget.mjs`, which needs a browser and a minute; this
|
||||
* runs in milliseconds and fails on the line that caused the regression.
|
||||
*/
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import * as THREE from "three";
|
||||
|
||||
import CALIFORNIA_CITY from "../../cities/california.ts";
|
||||
import { createFreewayWorld } from "../../engine/structures.ts";
|
||||
import type { World } from "../../engine/world.ts";
|
||||
import CALIFORNIA_TRANSPORT from "../../transport/california.ts";
|
||||
|
||||
/** Flat ground and a linear projection: the corridor's own shape, nothing else. */
|
||||
const flatWorld = {
|
||||
city: CALIFORNIA_CITY,
|
||||
project(lat: number, lng: number): [number, number] {
|
||||
return [(lng + 121) * 47, -(lat - 36) * 58];
|
||||
},
|
||||
groundAt(): number {
|
||||
return 0;
|
||||
},
|
||||
} as unknown as World;
|
||||
|
||||
function meshesIn(group: THREE.Object3D): THREE.Mesh[] {
|
||||
const found: THREE.Mesh[] = [];
|
||||
group.traverse((object) => {
|
||||
if (object instanceof THREE.Mesh) found.push(object);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
function triangles(mesh: THREE.Mesh): number {
|
||||
const geometry = mesh.geometry;
|
||||
const index = geometry.getIndex();
|
||||
const per = index ? index.count / 3 : geometry.getAttribute("position").count / 3;
|
||||
return per * (mesh instanceof THREE.InstancedMesh ? mesh.count : 1);
|
||||
}
|
||||
|
||||
describe("California corridor", () => {
|
||||
const group = createFreewayWorld(flatWorld, CALIFORNIA_TRANSPORT);
|
||||
const all = meshesIn(group);
|
||||
|
||||
it("gives the earthwork a crown and two batters that both face the sky", () => {
|
||||
const embankment = all.find((mesh) => mesh.name === "freeway:embankment");
|
||||
assert.ok(embankment, "the corridor has no embankment; it is a flat ribbon again");
|
||||
|
||||
const normals = embankment.geometry.getAttribute("normal");
|
||||
assert.ok(normals, "the embankment lost the normals mergeGeometries matches on");
|
||||
let tilted = 0;
|
||||
for (let index = 0; index < normals.count; index += 1) {
|
||||
const y = normals.getY(index);
|
||||
assert.ok(y > 0, `embankment normal ${index} points into the ground (y=${y.toFixed(3)})`);
|
||||
if (y < 0.999) tilted += 1;
|
||||
}
|
||||
// And it is a batter, not another flat deck: an untilted strip would pass
|
||||
// the test above and still be the thing this replaced.
|
||||
assert.ok(tilted > normals.count * 0.9, "the embankment is flat; it will not catch the sun");
|
||||
|
||||
// All four spans — two carriageside batters on each of two corridors —
|
||||
// merged into the one mesh. A dropped bucket looks like an efficient one.
|
||||
assert.ok(
|
||||
normals.count > 2_000,
|
||||
`the embankment merged to only ${normals.count} vertices`,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the whole corridor inside the triangle share it was budgeted", () => {
|
||||
const total = all.reduce((sum, mesh) => sum + triangles(mesh), 0);
|
||||
// 142,000 before the reclaim, on a board with a 750,000 cap that was already
|
||||
// measuring 675,000. 95,000 is comfortably above what it emits and low
|
||||
// enough to fail if anyone doubles a tube's tessellation again.
|
||||
assert.ok(total < 95_000, `the corridor is ${Math.round(total)} triangles`);
|
||||
// A floor as well, because the cheapest way to pass the line above is to
|
||||
// stop drawing the corridor.
|
||||
assert.ok(total > 50_000, `the corridor is only ${Math.round(total)} triangles`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user