068d4d71e59106105e0a912d58273e292c387e9d
91 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
068d4d71e5 |
feat: the merged board's cities are lotted like cities, and drawn one city at a time
Three findings, each of which changed the next.
**1. The metros on the merged board were lotted at 806 m.** `LOT` is fixed in
scene units — 0.42 — and `blocks.ts`'s own note already spelled out what that
means: a lot is 40 m on San Francisco's board, 164 m on the Southland's and
**806 m** on the statewide one. Put San Francisco's fifty-two districts *on* the
statewide board and they inherit 806 m. Lots go as the inverse square of their
size, so the Bay Area's ~84,000 buildings became about **two hundred**, and a
city rendered as a handful of grey slabs. That is the whole reason the merged
board's cities looked wrong.
A detail district now measures its lot in metres. 160 m, chosen against a
shipping reference rather than a feeling: it is what Southern California's own
board already builds itself at (0.42 x 390.6), so a metro on the merged board is
lotted about as finely as the Southland lots itself. Measured in the browser:
**59,166 building instances, of which 2,839 are the state's own and 56,327 the
two metros'.** Against roughly two hundred before.
**2. An all-or-nothing LOD could not carry them.** `InstancedMesh.count` draws
the first N instances, so "base first, detail last" can say "no cities" and
"every city" and nothing between. Driving into Los Angeles therefore revealed
San Francisco's fifty-two districts as well, four hundred kilometres away and
behind the camera: `california-drive` measured **925,854 triangles against a
430,000 cap**. Shrinking the lot to fit would have needed about 478 m, which
gives back the entire gain.
So `updateBlocksDetail` re-packs by district. Each detail district's lots and its
centre are recorded at build time; when the visible *set* changes — a handful of
times in a journey across California, not per frame — the visible districts are
copied into the front of the instance buffers and `count` moves. Three
attributes move together and must: the matrix, the colour and
`FACADE_ATTRIBUTE`, which is per-instance and decides which windows are lit;
moving matrices alone would light a tower's windows on a warehouse.
`california-drive` fell 925,854 -> **368,962**.
**3. Two bugs in that, both mine, both found by measuring rather than reading.**
- `DetailStore.key` remembers which districts the buffers hold, and started as
`""` — which is also the key an *empty* visible set produces. The first call,
at the whole-board pose where nothing should draw, compared equal, took the
early return and did nothing.
- `InstancedMesh`'s constructor sets `count` to capacity, so between
`createBlocks` returning and the scene's first `applyDetailLod` the mesh drew
every detail lot. The budget reports `maxTriangles` — a max over the whole
sample window — and caught that as **911,541 triangles while the steady
state was a correct 348,271**. A level of detail that is right on every frame
but the first is not a level of detail. The mesh is now born packed.
The second one is the instructive one: an instrumented log showed `count=2839,
want=false` at exactly the pose the budget was failing, which is what turned a
"the LOD is broken" hypothesis into "the LOD is right and the first frame is
not".
Result on the merged board, all triangles green: california 348,271 of 440,000 —
**lower than the state board's own 372,415** while carrying 56,327 more
buildings — and california-drive 368,962 of 430,000.
Still red, and unchanged from before this commit: `california-drive` mobile draw
calls, 315 against 280. Triangles there are now fine. That cap was set when the
corridor had no cities on it, and re-deriving it is an owner's call rather than a
number to quietly raise.
1,704 tests pass, including a regression test for the empty-key bug that reads
the source, because building a real `InstancedMesh` needs a GPU.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
64618cb47d |
perf: one lake mesh per board, and the merged board's metro layers get an LOD
Closing the merged board's red budget cells, and one of the two fixes turned out to be a win for every board in the product rather than for this one. **Every lake on a board is now one mesh.** `createWater` built a `Mesh` and a `MeshStandardMaterial` per inland-water polygon, so a board paid a draw call per lake. It went unnoticed while the boards were separate — California draws one, the Salton Sea — and a scene census on a phone found it the moment `unify.ts` folded three packs together: **water=20 against water=1**, nineteen of the fifty-eight draw calls that put the merged board over the mobile cap. They differ in nothing but shape, so the geometries merge and the material is shared. The 0.05 lift is baked in before the merge, since afterwards there is no per-lake mesh to carry it. Measured on the shipping default, unrelated to any flag: socal 218 -> 205 draw calls, bay-area 208 -> 202, california 374 -> 373, california-drive 233 -> 232. It was never free anywhere. **The metro-scale layers now follow the same reveal as the buildings.** Bridges, airports, ports and vessels are handed to `applyDetailLod` and hidden with the detail districts. That every one of them is metro detail on a merged board is measured rather than assumed: the same census showed all seven named bridges, all eight airport layers and every port mesh going from *zero* on the state pack to one on the merged board — `california.ts` authors none of them. Together they were the remaining 27 of those 58 draws, for structures sub-pixel at 1,919 m to the unit. Result on the merged board: california desktop 348,271 triangles of 440,000 and 372 draws of 415 on mobile, both green where mobile was 428 before. **`DETAIL_REACH_M` is 60 km, down from 150.** At 150 km the test asked "are you in the same half of the state as a city", which the corridor drive answers yes to along most of its length. 60 km asks "are you looking at a city", which is what the flag is for — and driving up US-101 the Southland now appears as you come into it rather than while you are still in the Salinas Valley. **The metro chapters were merged, photographed and reverted, and the reason is recorded rather than the attempt deleted.** Converting a metro rung onto this board is easy and was done correctly — `focus.lat/lng` is absolute, `distance` is horizontal in the owning board's units and `height` has already been through that board's exaggeration, so each keeps its true metres. The camera arrives exactly where it should. What is *at* that range is the problem: two scene units from the target on a 551-unit board, and the corridor there is a deliberate 4.7 km-wide atlas glyph, because DRIVE mode has to be able to drive down it on a board where a real freeway is a fifth of a pixel. The delivered frame is a black slab across San Francisco. A rung that flies you into that is worse than no rung. They come back when the corridor is drawn at true width, which is `createFreewayWorld` and the `roads` rule, not `unify.ts`. `airportsAndCard.test.ts` asserted a literal one-liner that is now two statements — the airport group is captured so the LOD can hide it. Split into the two facts it was ever about: the `?? []` call exists, and the result is added. 1,701 tests pass. The full budget passes on the shipping default. Still open on the merged board: `california-drive` mobile peaks at 315 draws against 280. The steady state is +3 drawables over the state board; the peak is the car driving into a metro and revealing it, against a cap set when the corridor had no cities on it. That is a cap to re-derive for a board that now has them, and it is an owner's call rather than a number to quietly raise — which is why this is still behind `?one=1`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
15d7accdbb |
feat: one California as one pack, behind ?one=1
The nesting preview is gone and this replaces it. That one drew each metro's
built scene inside the state board's under a similarity transform; the transform
was right and two independently built terrains then occupied the same ground and
interpenetrated. `cities/unify.ts` is the version with no seam to stitch,
because the way to not have two grids is to not have two grids: one `City`
carrying the state's landform and both metros' detail, so the engine builds one
world, one lattice and one terrain mesh.
**The merge is mostly a union and twice it must not be.** Everything that
describes a place in a `City` is absolute lat/lng, so hills, districts,
landmarks, bridges, ports and airports concatenate. Two do not:
- `World.elevationAt` *blends* every hill covering a point, so leaving the
state's coarse hills under a metro's fine ones adds their heights and lifts
the whole Bay. 25 state hills are dropped where a metro replaces them.
- The state's 17 districts include coarse stand-ins for both metros against
the metros' 99 real ones, and both drawn is two cities in one place. 9 go.
Filtering is by centre-in-bounds, which is honest and not exact, and the file
says so rather than hiding it.
**The lattice was the whole question and the answer was measured, twice.**
`TODO.md` records a statewide lattice at metro fidelity as 23.7M points and
dead. That is true at San Francisco's own 45 m cell, because `buildAxis` refines
per *axis* — a plus, not a box — so two metros at opposite corners refine most
of the state. The blowup is a function of the fine cell and is gentle until 45 m,
so the first pass took 150 m / x8, or 1.23M points.
That rendered **1,114,226 triangles at the whole-board pose against a 440,000
cap**, and the useful part is where it did *not* come from: with every metro
building hidden, triangles fell by 43,000 and draw calls by 111. The buildings
were never the cost. Terrain was. So the fine cell is what a statewide board can
afford without terrain LOD, and that is **300 m / x16** — 0.18M points, 11.6x
finer than the state pack's 3,473 m cell and 6.7x coarser than San Francisco's
own board. Measured at that size: california 392,862 triangles of 440,000 and
california-drive 405,124 of 430,000, both green on desktop.
**And the detail LOD is the cheapest kind there is.** `createBlocks` now orders
every `detail` district's lots *last*, so hiding 99 districts' buildings is one
assignment to `InstancedMesh.count` — no second mesh, no second draw call, no
allocation, and nothing for `nightlights.ts` to learn, since it holds the same
mesh either way. Landmarks are one mesh each so theirs is a `visible` flag.
The reveal is two tests and needed both. Stand-off alone passes for the drive
chapter, whose camera sits a few tens of metres off the ground half way up the
state while being two hundred kilometres from the nearest building this governs
— measured, it failed `california-drive` on triangles for lots nobody could see.
So detail draws only when the camera is inside `DETAIL_STANDOFF_M` *and* its
**target** is within `DETAIL_REACH_M` of a detail district. The target rather
than the camera, because the target is what is being looked at.
`src/test/unify.test.ts` pins the two non-union merges, the detail marking and
the lattice size. Writing it turned up something worth keeping: **six hills are
named in both the state pack and a metro pack** — Marin Headlands, San Bruno
Mountain, Mount Diablo, Bolinas Ridge, Mount Gleason, Mount Wilson — so the
first version failed on San Francisco's own Marin Headlands, identified as a
state hill by its name. `ladder.ts` hit this with "Whole Board" on two boards.
A name is not an identity; the test keys on the object.
Known and not fixed: mobile draw calls are 428 of 415 and 315 of 280 on the
merged board, from the port, vessel and bridge layers that the state board never
carried before and that no LOD hides yet. That is why this is still a flag.
Default is untouched and the full budget passes with it off.
1,700 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
122497088c |
feat(preview): one full California, behind ?nest=1
Not finished, off by default, and shipped anyway — for the reason `reconcile` was shipped behind a flag: the owner is choosing between two shapes of the product and an argument about how a board looks is settled by looking at it. `?nest=1` draws San Francisco's and Southern California's ground inside the state board instead of beside it. The question it answers is whether one full California needs a terrain quadtree, or whether the three packs already are the detail tiers of one map and only ever needed drawing in one scene. **They are, and the placement is a similarity transform and nothing more.** `World.project` is a uniform scale in x/z with no vertical term, so a metro enters the state board's space by scaling x/z by the ratio of metres-per-unit, scaling y by the ratio of `exaggeration / metresPerUnit` — separately, because the two boards stretch height differently — and translating to where the state board projects the metro's own centre. Measured: sf is xz 0.04915, y 0.12764 at (-146.2, -27.5) on a 551-unit board; socal is xz 0.20351, y 0.89640 at (55.4, 201.8). Both land where they belong at the size they should be, and the close pose shows real towers, blocks and a freeway on the state board. **And the statewide lattice is not the blocker TODO.md says it is.** That file records a statewide lattice at metro fidelity as 23.7M points and dead. That is true of `buildAxis`, which refines per *axis* — a focus region refines its whole row and its whole column, a plus rather than a box — so two metros at opposite corners refine most of the state. Measured against the packs' real focus regions (sf's is 22x40 km; socal's two are 9x12 and 8x13), per-*rectangle* refinement is **2.93M points and about 26 MB** at a 720 m coarse cell, or 1.13M at 1439 m, against 0.75M for all three boards as they stand today. The verdict in TODO is a fact about the lattice's shape, not about one California. **What is broken in the preview, precisely.** Two terrains occupy the same ground: the state's coarse mounds are still drawn under the metro's fine surface and they interpenetrate, so a block sits on a smooth hill in one place and is buried in it three hundred metres away. That is nested-grid stitching — cut a hole in the coarse lattice over each fine rectangle and sew the seam — and it is the whole of the remaining work. Nothing about the transform is implicated. Also not done: no LOD decides which ground draws, the metros are lit by a rig that belongs to another board, and triangles are additive rather than traded. `water` and `sea` are deliberately not lifted. The state board already has an ocean at y=0 and a second one inside it is a z-fight rather than a sea — the same defect the terrain still has, and the one place it was cheap to avoid. Inert without the flag, verified: `?city=california` with and without `?nest=1` both boot clean with zero page errors, and 1,694 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6fc0b2b60e |
feat: land on a real place, and let a wheel notch cross the seam
The owner has now asked four times why there are three boards, and the last
answer missed the point: reconciling the packs made them draw one California,
but you still *arrived* on the coarsest tier the product owns and still changed
boards by picking a name off a list. This changes both.
**The behaviour was already built and behind a second flag defaulted off.**
`handover()` in `ladder.ts` — promote/demote by camera stand-off, hysteresis at
0.9/1.15, a drag guard so a board never swaps under a finger — was pure, tested
and shipped a round ago, with `?handover=1` as the only way to see it. It is on
now, `?handover=0` turns it off. Measured on the deployed bundle first:
promotion into the Bay Area fired at the sixteenth wheel notch in from the state
pose, arrival clean, no boot card, no tab, no click on a name.
**And it did not work, for a reason worth writing down.** Landing on the Bay
with the flag on bounced straight back to the state board with no input at all.
Two wrong diagnoses on the way, both from reasoning instead of measuring:
1. "sf's ceiling equals its own widest pose, so the band is too tight." It is
not — `chapterStandoffMetres` puts the resting pose at 71.2 km against a
demote threshold of 81.9 km.
2. "the arrival flight carries the camera through the threshold, so guard on
`arriving()`." Right about the cause, wrong about the mechanism: the guard
went in and the bounce survived it.
A trajectory log settled it in one run. The handover tick arrived *before* the
first `arriving=true` sample, at 108,316 m — 1.5x sf's resting stand-off, which
is `arrivalStart`'s own offset. `arrive()` read:
kit.setPose(from);
arrival = { from, to: rest, elapsed: 0 };
`setPose` drives `OrbitControls`, which fires `change` **synchronously**, and
`main.ts` listens on that event. So the listener ran on the line *between* those
two statements: camera already 1.5x out, `arriving()` still false. A one-frame
ordering race, and the guard could not fire because the flag it reads was set one
statement too late. The assignment now goes first.
The guard stays, because the inequality behind it is structural rather than
incidental: `ARRIVAL_STANDOFF` is 1.5 and `DEMOTE` is 1.15, both global, so the
opening frame of *every* board sits outside that board's own retention band.
`handoverArrivalGuard.test.ts` asserts that relation and drives the real rule
through the opening stand-off to watch it demote, so neither the guard nor either
constant can be quietly simplified.
**The landing board is `DEFAULT_CITY_ID`, and it is the Bay Area.** It was
`CITIES[0]` in three places, which meant the state tier: seventeen districts over
1063x930 km at 1,919 m per unit, no city legible, and a left column whose first
offer is somewhere else to go. The detailed boards carry 52 and 47 districts at
94 and 391 m per unit. With free handover on, the state tier stops being a
destination and becomes what you get when you pull back — the role it is good at,
since it is the only board drawing 97.4% of California. A named constant rather
than reordering `CITIES`, because that array's order is the `?city=` fallback and
is read positionally by other consumers.
**Which caught a silent break in the capture harness, and this is the part that
would have cost a week.** `shots.mjs` and `films.mjs` built `?city=` only when a
shot declared one — and 4 of 21 shots and 1 of 4 films declare none, so they
inherited the app's default. Moving that default would have re-pointed five
pieces of marketing imagery at a different place while every filename, caption
and alt text stayed as it was. Both harnesses now name `california` themselves.
`performance-budget.mjs`'s `california` and `california-drive` cells had no query
at all for the same reason; `signature` would have failed them loudly rather than
mismeasuring, which is the harness working, but a harness that depends on an app
default reports someone else's change as its own flake.
Every harness also pins `handover=0`. A planted pose wider than a board's
retention band would otherwise demote to the coarser tier while the shutter is
open, and the frame that comes back is a real photograph of the wrong board.
Verified: bare URL lands on the Bay Area and stays there; `?city=california` and
`?city=socal` still deep-link; `?handover=0` stays put; zooming out from the Bay
demotes to the state tier at the second notch. 1,694 tests pass. All ten budget
cells pass with no cap raised, and every cell now measures the board it names.
Still true and now a decision rather than a doubt: 97.4% of California has no
board below 242 km of stand-off, so zooming into the middle of the state lands on
coarse ground. That picture has been looked at. Authoring Sacramento, Fresno and
the Central Valley is what retires it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
6fde2f0c97 |
feat: one California by default, and a box ship you can see
Two complaints, and the measured cause of each turned out to be a number already written down in this repo rather than anything that needed inventing. **"I see three different maps."** `reconcile.ts` has existed for a round with the four rules that make three packs draw one California, behind a flag whose whole purpose was that the owner could choose from a photograph — and `DEFAULT_RULES` was empty, so nobody ever saw the reconciled board without typing a query string. The photographs are now taken, at HEAD, at a matched 17:10 and a matched pose against the live API, and two of the four rules are on: - `exaggeration`. Relief in frame is 7.24% on California, 6.92% on the Southland and **4.51%** on the Bay: two boards were already on one number and one was not. The rule moves SF and only SF, 3.60 -> 5.78, and leaves the two boards every marketing still was tuned on untouched to the digit. Unreconciled, the Bay draws Tamalpais, the Berkeley hills and Mount Diablo as low smeared mounds; at 5.78 they stand up, and the chapter's own caption — "the Diablo range closing the east" — is true of the picture for the first time. - `projection`. Each pack squashed longitude by the cosine of its *own* centre, so California and the Southland disagreed about Riverside by 3,435 m and about their shared corner by 4,025 m: 3% of the frame at a 109 km stand-off, and a visible sideways slide in any transition showing both. One canvas cannot hold two answers for one place. `ground` stays off: its palette mix pushes the Southland's flats yellow-olive inland, because that pack's `flats` is off the one-parameter family in a direction no mix can reach. Its own comment calls it the riskiest of the four and the photograph agrees. `roads` stays off for the reason argued in the file — it rewrites a column nothing reads on the one board where the complaint shows. **"I don't see boats on our ports."** True, and by construction. `Crane`'s own doc note already had the cause: at SoCal's 3.4x a 130 m gantry stands 1.13 units while a 400 m ship is 1.02 units *long*. Exaggeration multiplies heights and a length takes 1x, so a hull is the one object on this coast that the board flattens while stretching everything around it — and eleven of fifteen hulls in a working harbour are alongside a berth, where a moored hull has no wake by design and so had nothing to be seen by. `FREEBOARD_RATIO` had already named the fix: a laden ULCV "stands about thirty metres out of the water **before you count the container stack, and the stack is what makes the silhouette**". The stack was never drawn. `containerHull` draws it — three bays with the forward one a tier down, eight tiers of high-cube at 0.52 of keel-to-deck depth — which buys height and nothing else. No length, no beam: a new test holds the container solid inside the merchant hull's bounding box in x and z, so a hull still cannot overhang the berth it lies in, and `vesselScale.test.ts` is green to the digit. It arrived through the seam `HULL_SHAPES` said it would — one entry, one case in `hullGeometry`, one arm in `hullShape` — which is the only evidence that seam was real rather than merely claimed. **The band is a modulation map, and the first pass of it was not.** Painted at `PORT_PALETTE`'s real box values under a container ship's own 0x93aac4 tint, the stack photographed near black — darker than the hull under it. `deckAtlas`'s own header had predicted that exact failure two paragraphs above where I was editing. So hue comes from the tint like every other part of every other hull, and the band carries relative value and *structure*: at the 25 px a stack draws at, individual box hues average to one tone and what survives is the shadow between two boxes. Adding a fourth band also had to move the other three, since they occupied 0.02..0.98 with nothing to append to, so band edges are now computed from one layout rather than typed as six literals — and the four hardcoded `v` values in the bow triangles read from it too. Three tests were pinned rather than changed: they measure packs as authored or synthetic one-hill fixtures, and each had an implicit dependency on the flag being off. Following `roadWidth.test.ts`, which already did this. Verified: 1,690 tests pass. All ten performance-budget cells pass with no cap raised — bay-area desktop 2,306,424 triangles of 2,600,000 (the taller board keeping more through the height guard), socal +284 triangles and +1 draw call for the container mesh, of 1,700,000 and 320. Frame time is not claimed either way; this box's GPU never leaves 500 MHz of 2,725. ⚠ This changes the default, so lumbridgecorp.com's 21 stills and 4 films are now pictures of a board the product no longer draws. `npm run refresh` is the whole of that work and it is not optional. `?reconcile=0` asks for the old board. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fea09c70d4 | merge: boards load before you ask for them | ||
|
|
85be0b13f8 |
feat: boards load before you ask for them
The owner asked for one map, with detail arriving in the background rather than
behind a wait. The interface already said one place (
|
||
|
|
2ff11a385e |
fix: the sky meets the sea, and the places rail admits it scrolls
Two defects a photograph found and no gate could. **The sky seam.** A phone held upright drew a hard horizontal step across the top 10.5% of the frame — a flat duller slab above a warm gradient, meeting in one pixel. Painting the sky dome red proved the slab was the dome itself and everything below the line was sea; so this was never missing geometry, it was two halves of one join disagreeing about a colour. The sea is a flat plane, so however wide it is made the camera's own far plane cuts it at an elevation *below* the horizontal — about 12 degrees in a plan view of the state. atmosphere.ts has fogged it to exactly the horizon colour by then, which is the entire mechanism by which water dissolves into sky instead of ending in an edge. The dome at that same angle has to be the same number, and it was not: the below-horizon darkening ramped to its full 0.82 over the first 11.5 degrees. The sea arrived at the horizon colour and met a sky already a fifth darker. Measured 155,107,99 against 127,88,81 across one pixel. Holding the horizon's own colour for the first 30 degrees costs nothing the darkening was for — a floor or an ocean covers that band whenever there is one, and where there is not, matching is the whole point. Measured after: the worst single-row step anywhere in the sky falls from 15/255 to 8, and the 8 is water texture rather than a line. render/skyHorizonSeam.test.ts parses the shader text, which is a weaker instrument than a frame and the strongest one available with no GL context. It asserts the one number the picture proved wrong — how far down the dome still paints the horizon colour — and it caught a real off-by-a-hair while being written: 0.42 is sin(24.8 deg), just inside the 25 deg the sea can reach. It also guards the backtick-in-a-template-literal trap that has now broken this same shader three times. **The places rail.** Its cap is min(22rem, 42vh) and rows are variable height, so it always landed mid-row, and a row sliced through its own text reads as a button that failed to draw rather than as more list. markOverflow writes data-overflow and the stylesheet fades whichever end has list behind it. Every measurement in it is guarded, because ui/mount.ts is deliberately tested with no DOM at all: no geometry must mean no attribute, not data-overflow=NaN. A pixel of slack at each end, because fractional layout reports a fully-scrolled list a fraction short and a fade that never switches off dims a row over nothing. stylesheet.test.ts asserts the two files still speak the same four words — nothing else connects them and the failure is silent. 1,664 + 295 tests, all gates, budget 779,789 tri / 150 draws / p95 16.7 ms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4bd8481be1 |
feat: the boards stop being three products
The owner asked twice why there are still three separate boards. The honest answer, and what this round executes: **it feels like three boards, but not because the scale jumps 20x — because the three packs draw three different Californias, and the loudest difference is that the mountains are four times taller on one of them.** **THE 20x HORIZONTAL SCALE JUMP IS INVISIBLE**, and measuring that collapsed the cost of this whole round. `World.project` is a uniform scale in x/z with no vertical term, and a uniform scale leaves a perspective image identical — so a camera carried across the seam on matched true-metre offsets draws a pixel-identical horizontal frame. 1,919 -> 94 m/unit costs nothing to look at. Rescaling was never the problem. A boot card, a tab strip and a 4.17x vertical deflation were. **THE PAUSE WAS MOSTLY FAKE.** A switch covered the screen for 1,715 ms but only 608 ms blocked the main thread; the page drew 46 of 69 frames with nothing to show, because the outgoing board had already been disposed. `mountCity` now retains it: the incoming board builds BEHIND a live, interactive picture, and `stage.setScene` fires only on completion. Measured across all six directions, three runs each — boot card yes -> **no**, opaque cover 726-1,415 ms -> **0**, blank frames 21-46 -> **exactly 1**, wall clock down 12-29%, blocked main thread down 15-47%. A return to a board already seen links **zero** shader programs and blocks **zero** milliseconds: 298-312 ms of camera flight where it was ~1,600 ms behind a card. Disposal had been throwing away the shader cache too — linkProgram ran 38, 59, 78, 109, 127 across five mounts and never reused one. **The transition is a fog dip, not a crossfade**, through the `setAerialFog` seam built last round. Every both-boards-live crossfade breaks a budget — ca+sf is 2,640,307 triangles against bay-area's 2,600,000 cap — and a fade never lands inside the harness's sample window, which is the "a cap you do not measure is a cap you do not have" failure this repo already argues against. The dip costs zero triangles and zero draw calls, and it hides the 4.17x deflation, the 4,025 m projection disagreement and the vanishing 2 km freeway symbols at once, because all three happen at maximum obscuration. It is also diegetic: a descent through haze. The first dip was wrong and the photograph caught it: collapsing to 6% of board SPAN turned the whole night frame into one flat field — the exact "turns the map off" failure the risk list named. Re-anchored to 70% of camera STAND-OFF, so the coastline survives and only the relief melts. **One ladder, one places list.** 26 authored chapters become 24 rungs sorted descending by STAND-OFF, not altitude — by altitude they interleave badly and altitude cannot tell a low oblique from a high plan. The three-board tab strip is off by default; the left column is now one scrolling list of all 24 rungs under three region headings that does not change when the board does. Only which row is lit changes. Label collisions are resolved in the ladder and never in a pack, so the 29 index-aimed capture guards are untouched. The minimap stops turning through 90 degrees between boards: every board is pinned to a rectangle with California's proportions. **SF and SoCal are not regressed**, and that was the acceptance that mattered: 95.9-98.8% of board pixels are delta-0 against a baseline hash-verified identical to what the live site serves, and every one of the 34-70 surviving pixels per frame is an aircraft or a hull. **A real defect found only by photograph:** `minimap.setMarkers()` had zero call sites. Every marker on every board was gone — the LA studio's door dot, the Bay Area's eight company markers — dropped when the minimap went per-board. Typecheck, tests, budgets and the console were all green with that bug in. Also fixed: two capture presets that lied. `look.mjs`'s `glyph-la` and `glyph-sf` claimed California chapter closeups and returned SoCal and Bay Area frames, because they aimed by chapter index and the indices had moved. Aiming is now by identity, with a guard test. NOT SHIPPED, DELIBERATELY: the pack merge. At Bay density it is 34.04M triangles, 13x the highest budget — dead, not a trade. At SoCal density it is 1.99M and fits today, and the price is San Francisco rendering at 164 m lots instead of 40 m, i.e. SF looking the way SoCal looks now. SF and SoCal carry every marketing still on the site. That is the owner's decision and it is worthless as an argument and decisive as a photograph, so it ships as a measurement artifact with a side-by-side still and is wired into nothing. The four data reconciliations that would make one world honest — one exaggeration rule, roads in metres, one projection centre, one coastline convention — are behind TERA_RECONCILE, default OFF. Tests 1,570 -> 1,651, server 295. All ten budget cells pass, no cap raised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bcac6aa41a |
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 |
||
|
|
e7a8aa028b |
fix(satellites): declare uPixelRatio, which was breaking the layer in production
`ShaderMaterial` prepends three's own built-ins — projectionMatrix,
modelViewMatrix, position — but it does NOT prepend the custom uniforms declared
in the `uniforms` object. `uPixelRatio` was in that object and never declared in
the GLSL, so the vertex program failed to compile with
ERROR: 0:79: 'uPixelRatio' : undeclared identifier
and the whole satellite dome silently drew nothing on the deployed site while
every other layer carried on. The only symptom was a console error nobody was
reading — which is exactly why the deploy check now opens the page and greps the
console rather than trusting a 200.
Found by screenshotting the LIVE site after deploying, not by any test: no test
in this repo compiles a shader, `npm run build` cannot, and `ui-smoke` passed
because the page boots fine with one layer missing.
Verified after the fix at two hours on two boards: zero shader errors, where the
deployed build had one on every load.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
acf4d1a510 |
feat: the state becomes California, and the port fills with ships
**Stage 1 of one California.** The owner's complaint had two halves and this is
the first: the state board was a CROPPED SLAB. `california.ts` stopped at 38.05 N,
so the board disagreed with its own minimap about the shape of California in a
single frame, and Bug Fire's 93,733 acres burned off-frame while the panel said
all clear. Bounds now run 32.50-42.05 N / -124.50 to -114.0 W — Cape Mendocino,
the ruled Oregon parallel, the 120th-meridian corner into the Nevada diagonal.
**And it got cheaper.** 391,169 triangles to 375,351, while gaining the North
Coast, the Sacramento Valley, the Klamath knot, the Cascade arc, Shasta at 4,320 m
and Lassen at 3,190 m. Extending the bounds alone would have doubled the lattice
to 168,813 points and blown the mobile cap; coarsening cellLat 0.022 -> 0.0312 and
cellLng 0.027 -> 0.0383 holds it at ~83,800. The cell as a FRACTION of the board
moves 0.0030 -> 0.0033 — unchanged in frame — because the camera retreats to frame
whatever it is given. That argument was already written in the pack's own comment.
The second half — three boards becoming one world you zoom through — is NOT here.
Merging at Bay density would be 34.04M triangles, 13x the highest budget, and
merging at SoCal density would downgrade San Francisco from 40 m lots to 164 m.
Both delete the board every marketing still is shot from. `sf.ts` and `socal.ts`
are untouched by design.
**Aerial perspective, which the state board could not have had before.** The old
fog started at 1.15 board spans = 944 km, on a board whose longest diagonal is
820 km — so no pixel could ever be fogged. Fog now responds to camera altitude,
clamped to the authored pair as a ceiling.
`Atmosphere.aerial(env, view)` is a second pure method returning `{ near, far }`
and **deliberately no colour**. That is structural, not stylistic: it is why a
future camera-dependent term cannot reach `environmentKey()`'s colour fingerprint
and start rebuilding the PMREM cubemap on every camera step. Coarsening the
fingerprint instead would have hidden one instance and armed the mechanism. A
mutation-tested seam guard fails if anyone merges the two paths back together.
**The port.** Terminal Island rendered as a bare tan polygon with generic white
blocks while the chapter text called it the busiest port complex in the
hemisphere. Now six container yards drawn as canvas atlases, 56 gantry cranes at
varied boom angles, the 13 km San Pedro breakwater, the dredged channel. Five
buckets merging ACROSS ports the way airports.ts merges across fields, so a
second complex costs no extra draws: +11 draws and +4,377 triangles for all of it.
At vertical exaggeration 3.4 a 130 m gantry is 1.132 units tall against a 400 m
ship's 1.024 long — the crane is the taller object, and it is what makes a port
read as a port from altitude.
**Ships, and the wake carries the information.** Moored hulls have no foam,
verified at three terminals; a tug under way in the Main Channel trails a clean
Kelvin V. One hull geometry, one InstancedMesh, orientation from the BERTH rather
than the wire. The AIS gate strips sog 102.3, heading 511 and cog 360 — all mean
"not available" — with an explicit test that cog 358.7 SURVIVES, because a naive
range check on cog eats real headings near north.
"Empty or full" is not in AIS position reports and is not invented per ship. The
honest answer is at port level and is a better story: 348,691 of 460,467 boxes
left Los Angeles empty in July 2026, corroborated by FBX01 $7,491 inbound against
FBX02 $347 outbound.
**Radar and birds ship dark, and say why.** California is 0.47% wet and migration
is nocturnal and seasonal, so both layers have nothing to say on most days. The
panel reads "No radar feed is configured, so this board draws no weather. That is
a fact about this box, not about the sky."
Also recorded, and it matters beyond this commit: **the GPU on amd-server never
leaves 500 MHz of a possible 2725**, traced across 80 seconds of sustained load.
`bay-area/desktop` is fragment-bound at that clock and sits on the vsync deadline,
so a trivial change in fragment work flips it between 16.8 and 33.3 with geometry
identical to the digit. Every frame-time number measured on this box is a floor.
Two investigations reached two different wrong conclusions from single-run
comparisons before this was traced. Geometry is the gate; frame time is advisory.
No cap was raised.
Tests 1,340 -> 1,540, server 280 -> 295.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
b25f217e3e |
feat: real fire on the boards, the LA office as a twin, and a night sky worth reading
The world stops being a simulation of California and starts being California. **THE PROMOTION GATE WAS THE FIRST COMMIT, BEFORE ANY ORANGE PIXEL EXISTED.** On today's live store the SoCal board contains 22 incidents. Every one has NULL acreage and fifteen are nameless LA County dispatch numbers. Drawn naively that is 22 orange marks over Los Angeles on a day nothing is burning — in a frame that contains no other warm colour, so one glyph would be the most salient object on the board and twenty-two would spend its credibility permanently. `acres >= 10 AND contained < 80 AND type != 'RX' AND last_seen = max(last_seen)` returns 0 on SoCal, exactly 5 on California, 0 on the Bay — same body, same day, three correct answers. The empty board is a deliverable, not a fallback: it says "No active fire on this board — CAL FIRE and WFIGS, just now", states that 21 records were gated and why, lists the largest fires burning OUTSIDE the frame with distances, and counts the hot pixels it is deliberately not drawing. **The privacy leak is structurally impossible rather than carefully avoided.** cloud-1 serves a projection; the four home-relative columns never leave that box. `observations.threat` was the one that nearly got through — it is `(16/distance)^2 x log10(acres) x momentum x containment x wind-alignment`, so with acreage and containment public it inverts to a distance circle around a house and three fires give an intersection. A grep of the built bundle for distance_km, bearing_deg, threat, 7762 and the street name returns nothing. **Deliberately not used, and both would have produced a confident wrong answer:** the store's `air` table retains only the last parameter of each poll, so all 78 rows read "Good" while the live feed reports ozone 101 "Unhealthy for Sensitive Groups" — haze driven off it would clear the sky during a smoke event. And `weather` is written only inside the NWS alerts loop, so a quiet day stores no wind at all. Tera's own per-region NWS wind is already correct and already what the clouds drift on. Satellite detections are drawn as evidence and never as incidents. The permanent industrial heat source 4.7 km from the owner's house is flagged persistent and dropped, asserted by a test that first proves it is present in the fixture. MODIS integer confidence and VIIRS string confidence are branched on `sat`. **The LA office is a twin.** Its entire authored second storey — Model Loft, Model Bay, The Materials Room, 430 lines nobody had ever stood in — is reachable on foot: a walker crosses level-1 to level-2 in 73 fixed steps, floorY 0 to 5, verified against the real pack rather than a synthetic plan. Its two studio devices read real hardware through a field-allowlisted bridge: mute, volume and reachability only. Never level, because there is no passive level upstream and obtaining one would record a room with people in it. Never dB, because upstream is gainPct across four different native scales. The bridge refuses all writes. Fixed at its root: an anonymous visitor was getting permanently at-rest instruments backing off against a 401. The tier moves into `createDeviceSource`, so anon gets the living simulator three file headers already promised. **Item 8 is closed, not fixed, and the correction is the point.** The Bay Area "stutter" was GPU power management — the card sat at 500 MHz of 2725 through every run that reproduced it, 4096/2048/1024/256 shadow maps all render in 1.21-1.31 ms, and two consecutive runs over a byte-identical dist gave 33.4 then 16.7. The allowance is removed and the cell is back to 16.7. Geometry is the gate; frame time is advisory. Item 7 was re-scoped after measuring: 1,069,006 of the Bay Area's 2,265,056 triangles were the second submission of the same buildings into the shadow pass. Mobile now has its own triangle caps and bay-area mobile draws 1,266,096. Also: bridges and the freeway corridor light up at night as emission, not lights — 1,614 deck lamps and 18 tower heads on the Bay in two draw calls. The single change that made US-101 legible was moving its edge lines from the lit material to the unlit one: retroreflective paint, the argument the SFO night frame already makes. California went 21,991 lamps to 4,051, clustered at the 17 town districts, because a rural interurban corridor genuinely is unlit. Tests 1137 -> 1340, server 280. All ten budget cells pass on first attempt with no cap raised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b7f5c41da5 |
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>
|
||
|
|
2aa4049258 |
feat(brand): re-shoot everything at the new engine, and make it one command
**Seven new shots**, because the world grew the most photogenic things in it
after the last pass: `sfo`, `lax`, `golden-gate`, `bay-bridge`, `freeway`,
`california-relief` and `pacific-sea`. All nine existing ids are unchanged — the
manifest emits a `ShotId` union that lumbridge-v4 imports, so ids are added and
never renamed.
**A shot can now aim itself.** The chapter list has no camera for SFO, LAX, the
bridges, the freeway or the open sea, and the engine has no `?pose=` back door,
so a shot points itself by driving the app's own inputs: a click on the plan view
slides the orbit target to a lat/lng while keeping the chapter's stance, wheel
notches set the standoff, and a drag sets azimuth and elevation. The plan view's
pixel-to-coordinate map is solved at runtime from three hovers of
`#minimap-readout` rather than hard-coded, so it survives a board resize or a
restyle of the widget.
**The tera share card was a picture of the wrong thing, and had been.** Its art
came from `keyboard.press("2")`, which had landed on the California board's drive
mode once the default board changed — so the card under the headline "Cities from
above." was a chase camera on US-101, showing metre-scale cars driving between
kilometre-wide buildings, with the DRIVE readout and the mode pill baked into the
art. It renders, it looks deliberate, and it is why an unguarded key press has no
place in a capture script. `capture.mjs` now clicks an indexed chapter and
asserts its `shortLabel` the way `shots.mjs` does, waits on `#boot` and
`#chapters` instead of sleeping twenty seconds, and gives each card its own hour.
**`npm run refresh` is the durable half.** One command: build, stills, cards,
films, both manifests, and a hashed before/after diff of every deliverable. It
fails loudly and specifically on the two conditions that otherwise produce
confident wrong output — the renderer coming up as SwiftShader, and a chapter
`expect` guard firing. `--stills-only` / `--cards-only` / `--films-only` compose,
`--dry-run` lists the plan without opening a browser, and
`shots.mjs --list` prints the whole shot plan — board, chapter, expect, aim, both
hours — which is what to run first when a guard does fire.
It also re-stamps `PROVENANCE.json`, narrowly: only entries whose origin is
`repository-generated` and whose `generator` names a script the run actually
executed, by literal hash substitution rather than re-serialising the file.
Without that, every legitimate card re-shoot leaves `npm run provenance` red.
**Every film re-shot.** They were at `9c9e78f`, captured 2026-08-07, and predated
the tone mapping, the reflective sea, the sky dome, terrain shadows, the rebuilt
California board, SFO, LAX, both bridges and the moving aircraft.
Tests 1137, typecheck, build, eight budget cells and every provenance and licence
check pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
f81d5218d4 |
feat: SFO, LAX, both bridges, a road that reads as a road, and aeroplanes that move
**The aeroplanes were stuck because the wire could not describe motion.** `WireAircraft` carried position, altitude and heading and nothing else, so the client could only interpolate between the last two observations: every aircraft replayed a segment it had already flown, arrived at the newest known point, and sat still until the next poll landed five to fifteen seconds later. The feed had the missing numbers the whole time and the server threw them away. Sampled live from `api.adsb.lol/v2/point` while writing this — `gs` ground speed, `track`, `baro_rate`, plus `r` registration and `t` type designator. They are on the wire now in SI, aircraft dead-reckon along their own track and correct toward the truth when a fix lands, and the click card an anonymous visitor gets says "B739 · N68834". That last part is the enrichment FR24 was wanted for, obtained from an ODbL feed we may actually republish. **SFO and LAX exist.** A new `engine/airports.ts` composes an airport from runways, taxiways, aprons and terminal masses, with markings drawn on a canvas rather than modelled; the pattern of the runways is what the eye recognises from altitude, long before any building does. SFO is the two crossing pairs on the bay fill; LAX is the four parallels either side of the terminal horseshoe, plus the Southland fields under the traffic that actually flies there. **The Golden Gate and the Bay Bridge are those bridges.** One kit in `engine/bridges.ts`, because a suspension bridge is a repeated tower, a catenary main cable, a series of hangers and a deck — so both are configurations rather than two private implementations. The Bay Bridge carries the real 2013 topology: two suspension towers west of Yerba Buena, one east, then the piered causeway. The freeway stopped being a wireframe overlay and became a road, with shoulders, a median, and lane markings as texture. **And the board got faster while all of that landed.** California went from 728,744 triangles and 562 draw calls to 391,169 and 371 — headroom from 2.8% to 47.8%. The Bay Area board is 506,550 triangles lighter than before this work. Two things paid for it: - `transmission: 0.08` on the aircraft cockpit glass. three.js runs a full transmission backdrop pass whenever any rendered material has transmission above zero, re-drawing the entire opaque scene into a second target every frame — so the city was rendering terrain, every block and every freeway piece TWICE. Measured by patching only that number in a copy of the built bundle: 703,267 tris / 562 draws with it, 398,608 / 371 without. The material was already `transparent: true, opacity: 0.86`, so it was buying nothing. - Flatness-adaptive terrain LOD, which collapses runs of lattice cells wherever the height and colour agree with the quad replacing them. The coastline is provably untouched — a patch collapses only when every point is on land and agrees about `park` — and a test asserts the drawn footprint matches the cell-by-cell area to 1e-6. `createTerrain` got *faster*: the vertices it stops emitting cost more than the flatness scan costs to run. **The budget now watches the boards this was built on.** There was no `bay-area` or `socal` cell — so SFO, LAX and both bridges all landed in frames nothing measured, which is how a cap you do not have looks from the inside. Both are in the matrix now with caps set from measurement, and the rationale lives in the harness because JSON cannot hold a comment. Two known defects ship with this, both recorded in TODO.md rather than hidden: - `bay-area.desktop` drops about one frame in twenty (p50 16.7, p95 33.3). It is desktop-only and not fill rate — mobile runs the same 2.26 M triangles at a comparable pixel count and holds 16.7 flat — which points at the 2048 shadow map desktop uses against handheld's 1024. Measured at the commit before this work with the same harness: identical p95 33.3. Pre-existing, and invisible until the cell existed. - The aeroplane glyph is still about 1.5x the Golden Gate's main span at chapter zoom, down from 2.5x. `GLYPH_MAX_SCALE` is 52 because the raw scale at the far end of the California orbit is 51.0 at a 60-degree field of view, and 26 — tried first — put the glyph at 0.0123 of the frame against the 0.012 where the wings stop resolving. The real fix is to clamp against the camera's focus distance rather than the aircraft's, which is a signature change. Tests 1020 -> 1137. Typecheck, build, eight budget cells, no-binaries, provenance, zero-config boot, dependency licences, arena source hashes and the UI smoke across two viewports and two access tiers all pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d9f8baf171 |
docs: track the film re-shoot, which the stills pass did not cover
The stills and both share cards were re-shot at
|
||
|
|
2b2dd5cb64 |
chore(brand): re-shoot the cards, and stop describing a building that is gone
The share cards were stale in the two ways that survive a code fix, because both of them end up baked into a picture. **"Two buildings" was wrong in two files.** The words on the card live in `og.html`; the words a crawler reads live in `vite.config.ts`. Fixing either one alone leaves the other saying something else — and there are three packs now, one of which is a courtyard block in Los Angeles that neither file mentioned. Both now say three, name the LA block, and mention the microphone on the desk. Lumbridge HQ also stopped being "a tower floor" some hours ago and is a live/work studio. **The tera card was cropping around a defect that no longer exists.** Its framing was pinned to `56% 88%` with a comment explaining that low was necessary to keep the far edge of the board — where the terrain stopped and the sky began in a hard diagonal — out of shot, because a card with that seam in it read as a rendering fault rather than as a map. The sea now runs eighteen board spans and ends past the fog's far plane, and the sky is a world-space dome, so there is no seam to hide from. `56% 62%` puts the framing where the picture is best instead. Also a per-shot `hide` in `shots.mjs`. The studio-hardware card is a `.panel-group` inside a scrolling card, so when it is taller than the viewport the last control is below the fold — correct on a screen, where a reader scrolls, and wrong in a photograph, where the frame edge cuts a gain slider in half and anyone who does not know the card scrolls reads it as a broken render. It is also a question of subject: `office-desks` is a picture *of* that hardware and keeps it, while the LA courtyard is a picture of a courtyard and does not want a column of audio controls down one side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3c4649d078 |
fix: make the app boot on an insecure origin, and re-aim the capture harness
Three things, all found by trying to re-shoot the product imagery and failing.
**`crypto.randomUUID` is secure-context only.** `main.ts` called it at module top
level for its three wire identities, so on any origin that is not HTTPS and not
`localhost` the call threw before the scene was built and the app stopped at
"Starting up" with one TypeError and no other symptom. Every way a developer
normally opens this app is a secure context — `vite dev` and `vite preview` serve
localhost, the deployed site is HTTPS — which is why this survived since
|
||
|
|
8fb85cd2e5 |
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> |
||
|
|
655848746d |
fix(ui): give the rail its own short labels instead of the sheet's sentences
The corner rail rendered `Shortcut.meaning`, which is written for the `?` sheet where a full sentence is exactly right because somebody is reading a reference. The rail is a glance over a moving 3D board with room for one line, and it is a `flex-wrap` container: the sentence wrapped, and the wrapped line drew straight through the chip beneath it. On the deployed build "Fly to a chapter of the tour, or to a viewpoint inside a studio" and "Walk into the studio, or step back out to the city" overlapped and neither could be read, over a bright sky with no background to hide it. Nothing errored — it just looked broken, on every screen. Two labels for two jobs. `Shortcut.rail` carries the two-or-three-word form for the nine ids `railHints` can select; `meaning` keeps the sentence for the sheet. `.hint` also had no `line-height`, so it inherited the body's and a wrapped chip had nowhere to go, and no `max-width`, so the rail grew leftward until it met the mode pill. Chips are now `white-space: nowrap`: wrapping belongs between chips, which the flex container already does, not inside one. Tested, because this is a defect a test can hold: every id the rail can return must have a `rail` label, it must fit on a line, and it must not contain a clause. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
75908774f7 |
fix(flights): identify this service to adsb.lol, which refuses anonymous callers
The real reason the sky was fake, and it was not the timeout. `api.adsb.lol` answers `403` with the body `"User-Agent too generic; include valid contact info."` to any request that does not say who is making it. Node's `fetch` sends nothing useful, so every live flight fetch was refused — in 268 ms, not at the 6 s timeout, which is why the previous commit's longer budget changed nothing. `http.ts` already exported `userAgent()` and `weather/metno.ts` already used it; flights simply never did. `config.flights.contact` now follows the pattern satellites already established: `TERA_FLIGHTS_CONTACT`, falling back to `TERA_WEATHER_CONTACT`, because an operator has one contact address and not four. This failed in the way worth being angry about. A `403` became `null` in `getJson`, `null` means "serve the simulated plan" in `flights/index.ts`, and both of those are correct in isolation. So visitors got fabricated aircraft while `/health` reported `flights: adsb` — the source *was* configured and the host *was* reachable — and `degraded[]` stayed empty because nothing had degraded at boot. Every signal the service publishes about itself said it was fine. `server/src/test/adsbUserAgent.test.ts` asserts the header carries the contact, that the URL is still the region asked for, and that a non-allowlisted host is refused before a single byte reaches the network. A load-bearing header with no test is a header that comes off in a refactor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ab41302993 |
fix(flights): give adsb.lol a timeout its tail actually fits in
The sky over California has been intermittently fake and nothing said so. `http.ts` gives every upstream 6 s. Five consecutive calls to `api.adsb.lol/v2/point` from the box that serves this deployment came back in 1.2 s, 5.7 s, 24.3 s, 3.5 s and 4.2 s — all `200`, all correct, and three of the five past the shared default. The feed is a volunteer aggregator answering a geographic query and its tail is long in a way an NWS observation's is not. The resulting failure was invisible in the worst way. The fetch did not error, it timed out, `getJson` returned `null` as designed, and `flights/index.ts` did the right thing with a `null` — served the simulated plan. So visitors got fabricated aircraft, `/health` still reported `flights: adsb` because the source was configured and reachable, and `degraded[]` stayed empty because nothing had degraded at boot. Every indicator this service has said it was fine. 25 s, above the slowest measured answer, and only for this upstream. It costs nothing when the feed is quick: the call runs behind `createUpstream`'s TTL, not on any visitor's request path. On a board whose entire claim is that the aircraft are live, a slow real answer beats a fast invented one. Not a regression from the studio build — `http.ts` is untouched by it; this was already happening. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
db074e9cf7 |
feat: tone-mapped render rig, studio devices, LA fidelity pass, UI overhaul
The build the studios needed, across eight workstreams and one strict file partition. **The render rig was the quality ceiling.** The renderer ran three's NoToneMapping default while atmosphere drove the sun to 2.35 and assets set emissives to 3.2, so every value above 1.0 hard-clipped to flat white — which is why walls blew out and every fitting looked like a white rectangle. ACES filmic tone mapping and an explicit output colour space land in `stage.ts`, and the atmosphere intensity table and palette headroom are re-tuned against the new curve rather than left tuned for the clipping we removed. `engine/environmentRig.ts` builds a PMREM environment at runtime, procedurally, so nothing binary is committed. There was no environment map anywhere before, so every `metalness > 0` role had nothing to reflect and rendered dull grey — a defect the code already documented against itself in `office/optimus.ts`, where a whole material role was abandoned over it, and worked around in `modelX.ts` with a fake emissive that this change deletes. Atmosphere remains the sole light owner; the rig derives from the `LightingState` it already produced. **Studio hardware exists.** There was no device concept anywhere in the product: no type, no route, no state. `devices/types.ts` fixes a declaration/state/ capability/command contract that a smart light, a thermostat, a door sensor and a charger all fit without a schema change, and both studios now carry a desk mic and a computer speaker with deterministic simulated behaviour behind an adapter seam a real API can occupy later. Reads are the demo and are open; commands are a signed-in action and are kept off the read body entirely, because a shared cache replaying a GET that turned a microphone on is exactly what the fail-closed cache default exists to prevent. **The ADS-B licence hole is closed.** `TERA_ADSB_ENDPOINT` accepted any URL, the response was served publicly cacheable, and the attribution hardcoded adsb.lol regardless of where the endpoint pointed — one env var away from republishing non-redistributable data under an open-terms credit. The host is now allowlisted, the credit is derived from the host actually configured, public cacheability is conditional on redistributability, and a refused endpoint demotes to simulated flights and says so in `degraded[]`. The gate is on the source, not the feature: live aircraft and their detail cards stay open to anonymous visitors. **The LA studio was never the smaller pack** — 16 rooms and 248 props against SF's 4 and 28. Its deficit was fidelity per square metre: 98 of those props were ceiling troffers, it bound no props to seats, placed none of the habitat kit, and 12 of its 16 rooms had no viewpoint. Density comes from new asset kinds rather than more instances, because `furnish.ts` draws once per kind and folds colour into the batch key, so repeat instances add nothing the eye can read. **The interface stops being forty imperative mutations.** Every visibility decision moves into a pure, tested `ui/chromeState.ts` and one applier, so the chrome has coverage for the first time. Deleted: ~100 lines of CSS and two bindings targeting elements that no longer exist, and a `body:has()` rule that shifted the desktop layout by 160px for touch controls hidden there. Fixed: the office picker tabs that drew their label and their badge on top of each other. Added: a first-run flow, because the product is two verbs and neither was ever stated on screen. Mobile is designed on its own terms instead of being the desktop with things hidden — the plan view comes back, and the keyboard-only shortcuts button is replaced by touch controls. `arena/studioOps.ts` frames the whole thing as the multi-variable environment it is, wrapping the same simulators the renderer drives rather than a headless copy. Also removed `input/vehicle.ts`, which nothing but its own test imported. Tests 385 -> 961, all passing. Typecheck, build, performance budgets across six matrix cells, no-binaries, provenance, dependency licences, zero-config boot and arena source hashes all green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8738367258 | docs: make static deploy cache-safe | ||
|
|
20deae2a0f | feat(actors): build articulated dog v2 | ||
|
|
264daaab61 | feat: unify life-sim play controls | ||
|
|
120eac878a | fix: align robot jobs with active HQ plans | ||
|
|
e378a03740 | feat: add deterministic office robot jobs | ||
|
|
0557a26e6b | feat: add active SF and LA HQ environments | ||
|
|
afa285bd5c | chore(arena): pin integrated simulator source hashes | ||
|
|
ddf814a657 | feat: build deterministic freeway and Lumbridge EV v2 | ||
|
|
94de2d8bee | feat: build articulated crow flight v2 | ||
|
|
747744fa6c | feat(arena): add deterministic headless RL environments | ||
|
|
79793faed2 | feat: upgrade electric aircraft fidelity and flight model | ||
|
|
dd18c6775d | fix: close California play and asset acceptance gaps | ||
|
|
d4859b33c6 | feat: add ephemeral webcam faces and adaptive screens | ||
|
|
3326d2e6d0 | feat: complete actor handoff and piloted aircraft presence | ||
|
|
655c383061 | test: enforce simulation and render budgets | ||
|
|
19f022f71a | security: bind TURN grants to screen sessions | ||
|
|
5ca214e4bb | feat: stream private office screens | ||
|
|
d841575315 | feat: add private office media signaling | ||
|
|
fc1f500019 | perf: lazy load authenticated office peers | ||
|
|
92ebf8abb5 | feat: add authenticated realtime presence | ||
|
|
16dc85a6f8 | feat: add playable flight and office screen sharing | ||
|
|
c0c7fcf974 | feat: add character studio and aircraft foundation | ||
|
|
1c37ef8f3e | feat: add private profile media and realtime contracts | ||
|
|
a2a52bfdae | feat: add playable actors and seamless journey state | ||
|
|
4e313c4a79 | Keep route controls on the California board |