1db868ad70
`liveData` was one flag meaning two unrelated things, and it was set to `tier !== "anon"`. That reasoning does not survive asking what the data actually is. Cloud cover over San Francisco is a reading from a government sensor. The aircraft are broadcasting their positions, unencrypted, to anyone within range who owns a forty-dollar receiver. Neither is withheld from anybody by anybody, so neither is a thing an account can grant access to — and putting them behind a sign-in 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. On an SSO-gated deployment it cost that moment for every visitor there currently is. So `liveData` splits. `liveEnvironment` is public and unconditional. `liveMarkers` is asked for by everyone and granted by the server, because the marker set is the one feed here that can carry something private — a company's pipeline, a person's job search — and whether it is public is a property of the deployment, not of a file in this repo. `TERA_MARKERS_ACCESS` is therefore a server switch and its default is `members`, which is the safe answer rather than the common one. The failure mode of getting this wrong is silent: nothing throws, nothing looks broken, the data is simply readable by the internet. An operator who wires real markers up gets the shut door without having chosen it and has to say `public` out loud — and saying it appends a line to `degraded[]`, so `/api/v1/health` reports that this box is publishing its map without anyone having to go and read the env file. Same reasoning as `TERA_ADMIN_SUBJECTS=*`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
147 lines
6.1 KiB
Markdown
147 lines
6.1 KiB
Markdown
# Tera
|
|
|
|
The map view of **Lumbridge Simulate** — cities 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](https://tera.lumbridgecorp.com).
|
|
|
|

|
|
|
|
---
|
|
|
|
## What it is
|
|
|
|
An engine plus data packs. The engine renders terrain, coastline, a built city
|
|
on real street grids, bridges, roads, markers 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.
|
|
|
|
San Francisco ships today. Los Angeles / Orange County / Riverside is next; New
|
|
York after that.
|
|
|
|
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
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
## Using the engine
|
|
|
|
```ts
|
|
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](ARCHITECTURE.md) §3 for the full reasoning, and
|
|
[NOTICE](NOTICE) for the attribution and data-provenance statement.
|
|
|
|
## 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/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](LICENSE) and [NOTICE](NOTICE).
|