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>
Tera
The map view of Lumbridge Simulate — California from above, in three.js. Its other half, Spaces, is the offices you walk into: one engine and one asset library, seen from outside and from inside.
Apache 2.0. Runs at tera.lumbridgecorp.com.
What it is
An engine plus data packs. The default board joins Los Angeles and San Francisco with live deterministic traffic on US-101 and on the honest I-5 → I-580 → I-80 approach. Choose either route chapter to follow the procedural black Model X.
The engine renders terrain, coastline, built cities on authored street grids, bridges, roads, markers, road traffic and air traffic. A city pack is pure data — coastlines, hills, districts, landmarks, camera chapters — so adding a city is a data contribution anyone can review, not a fork.
California, the detailed Bay Area, and Los Angeles / Orange County / Riverside ship today. The corridor is intentionally sparse; detailed cities remain their own boards rather than forcing a 600 km world into one full-resolution mesh.
A plan view sits top right: the board drawn flat, with the footprint of the camera's own frustum on it, so you can see where you are looking from outside the shot. Click or drag it to move the camera; scroll it to dolly. It is a 2D canvas rather than a second WebGL context, drawn from the same city pack, and it follows the sun into the night along with everything else.
Who sees what
Three tiers, resolved once at boot by src/access.ts:
| anonymous | signed in | admin | |
|---|---|---|---|
| the map, the plan view, the named chapters | ✅ | ✅ | ✅ |
| observed weather and live aircraft | ✅ | ✅ | ✅ |
| the office | public depth — shell, furniture, viewpoints, nobody home | full depth, with presence | full depth |
| the marker feed | per TERA_MARKERS_ACCESS |
✅ | ✅ |
the godmode panel (G) — date, season, weather override, counters, pose editor |
— | — | ✅ |
The sky is public on purpose. Cloud cover over San Francisco is a government sensor reading, and the aircraft are broadcasting their positions unencrypted to anyone with a receiver; neither is something an account can grant you access to. Gating them cost the only moment that makes this project land — real fog rolling off the Pacific onto a city you recognise, at the real time of day, on a first visit.
The markers are the one feed that can carry something private, so the server
decides. TERA_MARKERS_ACCESS is members by default and an operator has to
say public out loud, which /api/v1/health then announces in degraded[].
The default is the safe answer rather than the common one, because the failure
mode is silent: nothing errors, nothing looks broken, the data is just readable
by the internet.
These are drawing decisions, not a security boundary, and src/access.ts
says so at length. Live data and office presence are withheld by the API, from
a caller it does not recognise; the client tier stops the app asking for
something it will not get. Admin is granted only by TERA_ADMIN_SUBJECTS on the
server — never inferred in the browser, and never from an API that failed to
answer. A deployment with no API at all is open, because "clone it and it works"
is the promise; it is not "clone it and you are an administrator".
Quick start
npm install
npm run dev
Play controls
The bottom mode dock is the local-player source of truth: View, Drive, Explore,
Fly, or office Walk. A transition clears stale held input and atomically hands
the follow camera to one subsystem. WASD is movement; Q/E is vertical or
yaw, I/K pitches the crow, Space is the primary action, G glides, P
resumes assistance, R resets, and C switches the driving camera. A standard
gamepad maps both sticks, triggers, shoulders, and rising-edge action buttons.
Touch play uses a pointer-ID analogue stick at lower left and only the actions that apply to the current mode at lower right. The Map button remains available during possession. Touch, keyboard, and gamepad state are independent, so a released or cancelled finger cannot clear another source that is still held. The UI and follow camera are presentation adapters only; they never enter Arena observations, rewards, snapshots, traces, or simulator hashes.
Headless RL environments
Tera also exports a versioned, renderer-independent Arena contract with five deterministic environments: US-101/I-5 driving, Frontier Valley office navigation, seeded SF/LA office robot jobs, crow waypoint flight, and California electric-aircraft flight. They share the client controllers and office plan, but require no canvas, DOM, Three.js scene, network service, or new runtime dependency.
Import them from @lumbridge/tera/arena. Seeded train/dev scenarios,
component rewards, safety terminals, maximum steps, snapshots, checksummed
traces, exact replay and executable inaction/scripted baseline proofs are
documented in ARENA.md.
The visible SF and LA office robots use that same fixed-step job state. Their patrol, parcel, inspection, and charging loops are authored demonstration scenarios—not presence, telemetry, or evidence of real company work—and the UI labels them as a seeded simulation.
Using the engine
import { createScene } from "@lumbridge/tera/engine/scene.ts";
import { createStage } from "@lumbridge/tera/engine/stage.ts";
import SAN_FRANCISCO from "@lumbridge/tera/cities/sf.ts";
// One stage per canvas, for the life of the page. Cities are put on it and
// taken off again; a renderer per city leaks its shadow map on every switch.
const stage = createStage(canvas);
const scene = await createScene(stage, {
city: SAN_FRANCISCO,
markerPalette: { hiring: 0x4ade80, closed: 0xef4444 },
});
scene?.setMarkers([
{ id: "1", lat: 37.7765, lng: -122.4241, label: "Somewhere", colorKey: "hiring" },
]);
createScene is async because the heightfield is built in a Worker — half a
million samples, about 730 ms on the Bay Area, and not on the main thread. It
resolves to null if the build was abandoned through options.signal, which is
what makes switching city mid-build cheap.
The engine renders Marker[] and looks colours up by colorKey in a palette
you supply. It does not know what your markers mean — that mapping lives in
your adapter. This is what lets one renderer serve a private map coloured by
one scheme and a public map coloured by another, without either being a fork.
Adding a city
Write src/cities/<id>.ts exporting a City. Trace the coastline and parks by
hand, place hills as radial peaks, and give each district its street bearing.
Two rules, and they are not stylistic:
- Do not import geometry from OpenStreetMap. OSM and Nominatim output is ODbL — share-alike, and incompatible with this repo's licence.
- Do not commit logos or brand assets. They are trademarks, not code.
See ARCHITECTURE.md §3 for the full reasoning, and
NOTICE for the attribution and data-provenance statement, and
PROVENANCE.json for the machine-checked shipped-artifact and
original procedural-lineage ledger. Run npm run provenance, npm run licenses,
and npm run sbom before accepting assets or dependencies.
Aircraft
The engine takes a FlightSource. Two ship here: SimulatedFlights (original,
flies real approach and departure corridors) and AdsbFlights (open community
ADS-B feeds such as adsb.lol).
FlightRadar24 is deliberately absent — their terms forbid scraping and forbid redistributing their data, so a client for it cannot live in an Apache-2.0 repository. Commercial sources belong in private deployments. The best long-term answer is an RTL-SDR receiver: first-party data with nothing to comply with.
Layout
src/engine/ renderer — terrain, blocks, structures, markers, flights, scene, minimap
src/cities/ data packs — pure geography, no code
src/transport/ serializable route packs and renderer-independent simulation
src/arena/ versioned headless RL contract, scenarios, traces and environments
src/assets/ original procedural asset library
src/adapters/ where outside data plugs in
src/tools/ instruments — god-only, dynamically imported, never statically
engine never imports cities; neither imports adapters.
Nothing under src/tools/ may be reached by a static import from the app. It is
loaded by one await import() behind access.can.debug, so a visitor who is
not an admin does not download the code at all — which is the strongest
available reading of "nothing here runs for a non-god visitor": not a hidden
panel, not a disabled panel, no panel. src/tools/index.ts states the rule and
what silently undoes it.
Licence
Apache License 2.0 — see LICENSE and NOTICE.
The ordered build plan and parallel work lanes live in BUILD_PLAN.md.
