1
0
Commit Graph

5 Commits

Author SHA1 Message Date
karti 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>
2026-08-21 23:43:14 -07:00
karti fe58290728 California gets roads, traffic, and a car to follow 2026-08-11 18:24:53 -07:00
karti e41c90fe8d Real weather, real aircraft, a heightfield off the main thread, and instruments
Three things that were built and never connected, connected.

**The weather was already there.** `observe()` has always taken a
`WeatherObservation` and `main.ts` has always passed null, so the cloud,
precipitation, visibility and marine-layer paths in atmosphere.ts had never run
outside a test. The server already shipped NWS, met.no and Open-Meteo, all
configured off. What was actually missing was that a single TERA_ORIGIN_LAT/LNG
served one metro and lied to the other — so weather and traffic are per-region
now, derived from the city's own bounds, and the Bay Area gets its fog while
Long Beach gets its own sky. The route takes ?city= or a validated ?lat=&lng=
and refuses to become an open geocoding proxy for the planet.

**The heightfield moved to a Worker.** 2.3 s of blocked main thread at boot, and
another ~950 ms of point-in-polygon on top of it: the park mask is filled in the
worker now, and block placement samples four corners and only runs the exact
test on a cell that straddles an edge — 8 buildings differ out of 185,036.
createScene is async and takes a Stage as a consequence, and there is a
main-thread fallback because "clone it and it works" has no exception clause.

**Spaces is a chunk you fetch when you reach for the door**, not one everybody
downloads. Same for the godmode tools. The entry chunk is 722 kB rather than
772; three.js is most of what is left and splitting it is a different job.

**Godmode is an instrument panel now** rather than one slider: the date and the
season, not just the hour, so the Meeus moon and the sun's seasonal arc become
visible instead of merely correct; a weather override that says on screen when
it is lying; a frame-time and draw-call readout; and a pose editor that emits a
paste-ready Chapter block, which is the thing that makes adding New York cheap.

Two blockers the review caught:

  - Every city switch leaked 8 GPU textures — one of them a 2048x2048 shadow map
    — and ~10.5 shader programs, and deleteTexture had never been called once in
    the app's lifetime. The renderer was being built per scene; it belongs to the
    canvas, for the life of the page.
  - An upstream fetch that threw rather than returning null skipped the cache
    stamp, so the TTL — the only rate limit on outbound calls — collapsed to one
    upstream request per inbound request, and the caller got a 500.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 03:25:31 -07:00
Karti Tripathi 44c5a79424 SoCal, the whole bay, a moon, and gates that actually run
Six agents in parallel, and the two city packs independently reported the same
blocker: `focusRegions` and `coarseFactor` existed on the `City` type and
nothing implemented them. Uniform lattices would have been 2.9M points for
Southern California and 3.7M for the expanded bay. Both packs were unloadable
as written.

`buildAxis` is the answer, and it is honest about its limits: refinement is per
axis, not per rectangle, so a focus region sharpens its whole row *and* its
whole column. Two regions at opposite corners refine nearly everything between
them. Measured, not guessed — the bay went 0.53M points with one region and
1.64M with three, for detail nobody is looking at from a board this wide. One
region each, coarse factor ten, and the builds land at 3.8 s and 2.3 s.

Then three things that were only ever right because San Francisco was the only
city. `maxDistance: 340` and a 170-unit shadow box were constants tuned for a
230-unit board; the bay is 1003 units across and the camera physically could
not retreat far enough to frame it. Fog distances were scene units pinned to
the same assumption. And `minVisibilityM` defaulted to 4.5 km of honest
weather, which over ninety-four kilometres of bay correctly hides three
quarters of it — the night view was a black rectangle for a completely
reasonable reason. All three now derive from the board.

The moon is a real ephemeris and its light is a deliberate lie: 1.15, against a
physical ratio of one to four hundred thousand. What is being reproduced is
what a moonlit night looks like on a screen in a lit room.

The CI gate caught itself, which is the part worth keeping. Port 8431 was
already held by a server from an earlier session, so the boot check polled a
healthy stranger while the process it started died on EADDRINUSE. It now
refuses to run rather than pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 03:13:32 -07:00
Karti Tripathi 67f8df8d53 Lumbridge Simulate Engine — the city, and the licence it can actually ship under
LSE is the third of the three, beside lumbridge-compute and lumbridge-bench: a
3D engine for walkable places. This first commit is the outside of the world —
San Francisco — plus the seams the inside will attach to.

The engine renders a City and a list of Markers and knows nothing else. It does
not know markers are usually companies and it will never learn that "rejected"
is red; that mapping lives in an adapter. Which is what lets one renderer serve
a private map, a public one, and a self-hoster with no Lumbridge account, none
of them a fork of the others.

Three things were designed around the licence rather than discovered after it,
because each one is a promise Apache 2.0 makes that is easy to break by
accident. No trademarks in the repo — logos are fetched at runtime, and
public/logos/ is gitignored. No OpenStreetMap-derived coordinates, which is why
every coastline in cities/sf.ts was traced by hand: Nominatim output is ODbL,
share-alike, and would attach to the whole pack. And no FlightRadar24 client —
their terms forbid scraping and redistribution, so flights are an interface
with a simulator and open community ADS-B behind it.

The privacy constraint and the licence constraint turned out to want the same
thing. Geocoded company positions and pipeline status both stay behind Workie's
API; the open repo holds the city and the renderer. The tempting shortcut —
commit an sf-companies.json — breaks both at once.

Ported out of Workie, where a 3D city engine had no business living. Workie's
/live is deleted rather than deprecated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:49:08 -07:00