db074e9cf7
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>
432 lines
23 KiB
Markdown
432 lines
23 KiB
Markdown
# Tera — architecture
|
||
|
||
**Tera** is the map view: California and its cities from above, rendered in three.js, public and
|
||
self-hostable. **Spaces** is the other half — the offices you walk into. They
|
||
are one engine and one asset library, seen from outside and from inside.
|
||
|
||
Lives at `tera.lumbridgecorp.com`. Apache 2.0, on the fleet's own Gitea.
|
||
|
||
## The names
|
||
|
||
Getting these straight early matters, because two of them were nearly overloaded
|
||
onto things that already exist.
|
||
|
||
| name | what it is |
|
||
| --- | --- |
|
||
| **Lumbridge Simulate** | the product line these sit under |
|
||
| **Tera** | the map view. Cities — SF, LA, NYC — from above. Public, no login. |
|
||
| **Spaces** | the interiors product. Offices you enter, host, and self-host. |
|
||
| **Office** | one building's interior, belonging to a tenant. You go *into* an office. |
|
||
| **Space** | a room or zone inside an Office — a desk bay, a meeting room, a lounge. |
|
||
|
||
Two things this avoids. `lumbridge-simulate` is already a **Rust crate** in the
|
||
private monorepo — the Governor's memory-admission simulator — and in its
|
||
vocabulary a **"Scene" is a set of models**, not a three.js scene. This engine
|
||
therefore does not take either word for itself: it is Tera, and its three.js
|
||
scene stays an implementation detail behind `createScene()`.
|
||
|
||
One overload survives on purpose: *Spaces* the product versus *a Space* the
|
||
room. It sits at two different levels and matches how people already talk about
|
||
offices, so it is kept rather than invented around.
|
||
|
||
---
|
||
|
||
## 1. Why this is its own repo, and why it is public
|
||
|
||
Tera began as `/live` inside Workie, the private career portal. That is now
|
||
**deleted**, not deprecated — a 3D city engine has no business living in a job
|
||
tracker, and Workie is a private repo besides.
|
||
|
||
**You cannot cleanly Apache-2.0 a subdirectory of a private repo.** The licence
|
||
attaches to a distribution, and a consumer has to be able to clone the thing the
|
||
licence covers. A folder inside `lumbridgecorp-website` or inside
|
||
`lumbridge-runtime` would mean every release is a manual extraction, and every
|
||
extraction is a chance to ship something private by accident. The point of this
|
||
project — a reusable asset library other people can build on and self-host — needs
|
||
a repo that *is* the open-source artefact.
|
||
|
||
So: **`lumbridge-public/tera` on the fleet's own Gitea.** Not
|
||
GitHub. Self-hosting the forge is the same claim the engine makes: the whole
|
||
thing runs on hardware you own, and nothing about it requires somebody else's
|
||
platform to stay alive.
|
||
|
||
### The 2D world comes down
|
||
|
||
The existing Lumbridge world at `world1.lumbridgecorp.com` is a Phaser 2D
|
||
office, and its tileset is exactly what stops it being open-sourceable. It has
|
||
no real users, so it is being retired rather than migrated — Tera and Spaces
|
||
replace it outright, with original assets, which is the whole point.
|
||
|
||
That reframes this repo: it is not a map that happens to sit next to the
|
||
platform, it is the platform's renderer. The city is the outside of the world,
|
||
offices are the inside, and a tenant moves between them.
|
||
|
||
---
|
||
|
||
## 2. Layout
|
||
|
||
```
|
||
tera/
|
||
├── src/engine/ # renderer. Knows about terrain, blocks, streets, flights.
|
||
│ ├── types.ts # City, District, Hill, Marker, FlightSource
|
||
│ ├── world.ts # projection + cached heightfield, built per city
|
||
│ ├── terrain.ts # relief, coastline, parks
|
||
│ ├── blocks.ts # the built city, on a per-district street lattice
|
||
│ ├── structures.ts # bridges, streets, freeways
|
||
│ ├── markers.ts # pins. Takes Marker[], knows nothing about companies.
|
||
│ ├── flights.ts # FlightSource interface + a simulated implementation
|
||
│ └── scene.ts # lights, sky, camera flights, render loop
|
||
├── src/interiors/ # the inside of the world — floorplans, desks, presence
|
||
├── src/assets/ # the library. Original meshes and materials, Apache 2.0.
|
||
├── src/transport/ # serialisable route packs + renderer-free fixed-step sim
|
||
├── src/cities/ # data packs. Pure geography, no code.
|
||
│ ├── california.ts # state/corridor scale; LA ↔ SF continuity
|
||
│ ├── sf.ts # ~1000 lines of coastline, hills, districts, landmarks
|
||
│ └── socal.ts # LA / OC / Riverside
|
||
├── src/realtime/ # strict game-state wire/client/interpolation adapters
|
||
├── src/media/ # separate screen signaling, ICE and texture lifecycles
|
||
├── src/profile/ # local profile plus ephemeral webcam consent/capture
|
||
├── src/adapters/ # generic HTTP and sample-data boundaries
|
||
├── src/devices/ # smart hardware: types, a fixed-step simulator, the feed seam
|
||
├── src/arena/ # the RL environments, wrapping the same simulators the renderer drives
|
||
├── src/ui/ # every interface decision, and the one module that writes to the page
|
||
└── src/main.ts # the standalone demo app — assembly, and nothing else
|
||
```
|
||
|
||
The split that matters: **`engine` never imports `cities`, and neither imports
|
||
`adapters`.** A city is data handed to the engine. A marker is a `{id, lat, lng,
|
||
label, colorKey}` handed to the engine. The engine has no idea Workie exists,
|
||
which is what makes it publishable, self-hostable by someone with no Lumbridge
|
||
account at all, and free of anyone's private data.
|
||
|
||
`interiors` and `assets` sit beside `engine` rather than inside it: an office
|
||
and a city share the projection, camera, lighting and render loop, and differ
|
||
only in what they put in the scene. That is why `scene.ts` owns the loop and
|
||
knows nothing about terrain specifically.
|
||
|
||
`ui` is the newest of these and the split inside it is the one worth knowing.
|
||
`ui/chromeState.ts` is a **pure function**: hand it a plain object describing what
|
||
the application is, get a plain object back describing what the interface should
|
||
be. `ui/mount.ts` is the only module in the product that writes to the DOM, and it
|
||
writes exactly what that function returned. `main.ts` therefore makes one call —
|
||
`chrome.apply(chromeState(inputs))` — where it used to make about forty
|
||
`element.hidden = someCondition` decisions with the condition and the write on the
|
||
same line, none of which could be reached without a WebGL context. The rule that
|
||
keeps it that way: a new piece of chrome is a field in `ChromeInputs`, a decision
|
||
in `chromeState` and a write in `mount`, and never a fortieth mutation in the app.
|
||
|
||
`devices` follows the same shape one level down, and for the reason §7 gives about
|
||
presence: a `DeviceDeclaration` is **authored** — it says a microphone exists, what
|
||
it can be asked to do and which prop is its hardware — and lives in the office
|
||
pack, public by construction. A `DeviceState` is what that microphone is *hearing*,
|
||
never appears in a pack, and arrives from a route that can refuse an anonymous
|
||
caller. `src/interiors/devices.ts` is the render layer for the first and
|
||
`src/devices/sim.ts` is the state machine behind the second; the arena imports the
|
||
state machine directly, so what a policy is trained against and what a viewer is
|
||
looking at are the same code.
|
||
|
||
`transport` also stays outside the renderer. A `TransportPack` is plain JSON and
|
||
`VehicleSimulation` returns plain geographic poses. Three.js enters only in the
|
||
`roadTraffic` render layer, which projects poses through the active `World`.
|
||
That separation lets the authoritative session service validate and replay the
|
||
same route state without importing a browser or a GPU.
|
||
|
||
### 2.1 Product/control-plane boundary
|
||
|
||
Tera/Spaces is the authoritative renderer and simulation client. The separate
|
||
`lumbridgecorp` service remains the control plane: identity, tenant registry,
|
||
membership, billing, provisioning, and short-lived launch grants. It must not
|
||
grow a second world renderer or own frame-by-frame simulation.
|
||
|
||
The public Tera build remains zero-config and useful without any service. Hosted
|
||
presence, multiplayer and remote media arrive through adapters whose absence
|
||
cannot stop the city, office, traffic, or sky from running. Local profiles and
|
||
webcam faces require no hosted service; webcam pixels never enter the realtime
|
||
or office-screen transports. This is the replacement path for the retired
|
||
Phaser World 1; it is not a migration of that renderer.
|
||
|
||
The first hosted adapter is now concrete: the Fastify API owns short-lived,
|
||
in-memory authoritative presence sessions and streams validated deltas over
|
||
authenticated POST/SSE. `src/realtime` remains renderer- and transport-neutral;
|
||
the browser transport, interpolation buffer, procedural peer renderer and small
|
||
status UI are separate adapters. A city or full-depth office opts into remote
|
||
peers explicitly, while an anonymous/public scene does not even allocate their
|
||
assets. Interest changes clear the old coordinate frame before a new one is
|
||
shown. Game-state deltas never carry webcam or screen media. Webcam faces are
|
||
local-only; office screens use a separate consent, signaling and authorization
|
||
lifecycle.
|
||
|
||
---
|
||
|
||
## 3. Three rules that keep Apache 2.0 honest
|
||
|
||
Apache 2.0 is a promise that everything in the repo is ours to give away. Three
|
||
things would quietly break that promise, and all three are easy to walk into.
|
||
|
||
### 3.1 No trademarks in the repo
|
||
|
||
Company logos are **trademarks**, not code. Salesforce's logo is not ours to
|
||
relicense no matter how the file got here, and an Apache-2.0 repo containing
|
||
`logos/salesforce.svg` is making a claim it cannot back.
|
||
|
||
The NYC atlas ships 47 of these in `public/logos/`. We do not.
|
||
|
||
- The shipped application does not fetch or display third-party logos.
|
||
- The repo carries zero logo files. Self-hoster asset directories are ignored,
|
||
and the provenance/no-binary gates prevent a logo-shaped binary from quietly
|
||
becoming part of the procedural source library.
|
||
- `NOTICE` carries the standard "trademarks are the property of their
|
||
respective owners; their use here is nominative" line.
|
||
|
||
### 3.2 No OSM-derived coordinates in the repo
|
||
|
||
This is the subtle one, and it is the reason the geography in `cities/sf.ts` is
|
||
hand-traced rather than imported.
|
||
|
||
OpenStreetMap data — including anything that comes out of Nominatim geocoding —
|
||
is **ODbL**. ODbL is share-alike: a "derivative database" has to be released
|
||
under ODbL too. Committing a table of company lat/lngs geocoded from Nominatim
|
||
into an Apache-2.0 repo mixes an incompatible share-alike obligation into a
|
||
permissive one. That is exactly the kind of thing that makes a repo unusable for
|
||
the people we want using it.
|
||
|
||
So:
|
||
|
||
- **Geography** (coastlines, hills, districts) is traced by hand from scratch.
|
||
Original expression, ours, Apache 2.0. This is already true for SF.
|
||
- **Elevation**, when we want real terrain, comes from **USGS/SRTM**, which is
|
||
US-government public domain. Not OSM.
|
||
- **Geocoded company coordinates never enter this repo.** They live in a private
|
||
database and arrive over the API at runtime.
|
||
|
||
There is a real result here: the licence constraint and the privacy constraint
|
||
want the same thing. Company positions and pipeline status both stay behind the
|
||
API; the open-source repo holds the city and the renderer. The tempting shortcut
|
||
— "just commit an `sf-companies.json`" — breaks both at once.
|
||
|
||
#### Correction: containment is not discharge
|
||
|
||
An earlier version of this section stopped at the paragraph above, and it was
|
||
**wrong** in a way worth recording rather than quietly fixing.
|
||
|
||
Keeping OSM-derived coordinates out of the repo solves licence *mixing inside
|
||
the repo*. It does not touch ODbL's actual trigger. Serving a snapshot of those
|
||
coordinates from a public endpoint is **Publicly Using a Derivative Database**,
|
||
which brings ODbL §4.3 attribution and §4.4 share-alike onto the served data
|
||
**regardless of where the rows are stored**. Moving the table off-disk hides the
|
||
obligation; it does not discharge it.
|
||
|
||
So the rule is about the *geocoder*, not the storage:
|
||
|
||
- The sanctioned geocoder is the **US Census Geocoder**
|
||
(`geocoding.geo.census.gov`) — a US Government work in the public domain,
|
||
keyless, covering every city planned here.
|
||
- **Google, Mapbox and HERE do not solve this either.** Their terms restrict
|
||
storing and redistributing returned coordinates, which is precisely what a
|
||
public snapshot does. "Not OSM" is not the test; "may be redistributed" is.
|
||
- Every synced row carries a **provenance** field, and the public-shape
|
||
assertion rejects any row whose provenance is not on a non-ODbL allowlist. The
|
||
gate that already checks field *names* now also checks where a coordinate
|
||
came from.
|
||
|
||
Nothing had been built against the old reasoning when this was caught, which is
|
||
the only reason it was cheap.
|
||
|
||
### 3.3 The engine takes no position on what a marker means
|
||
|
||
`markers.ts` renders `Marker[]`. A marker has a `colorKey`, not a `status`. The
|
||
mapping from "rejected" to red lives in the *adapter*, in the consuming app.
|
||
|
||
That is what lets the same engine serve the private Workie build (coloured by
|
||
pipeline state), the public Lumbridge demo (coloured by sector), and whatever
|
||
someone else builds from this repo, without any of them being a fork.
|
||
|
||
---
|
||
|
||
## 4. Flights: FlightRadar24 is the wrong source for this
|
||
|
||
Real aircraft over the Bay is a great idea. FR24 specifically is a problem, and
|
||
it is worth being clear about why before any code is written against it.
|
||
|
||
FlightRadar24's terms prohibit scraping and prohibit redistributing their data;
|
||
their commercial API is licensed per-seat and explicitly does not grant
|
||
redistribution. An Apache-2.0 repo shipping an FR24 client is publishing
|
||
instructions for violating a ToS, and any FR24 data that reached the browser
|
||
could not be relicensed onward. Neither is fatal to a private deployment, but
|
||
both are fatal to the open-source story that is the point of this repo.
|
||
|
||
The engine therefore takes a **`FlightSource` interface**, and the repo ships
|
||
only sources it can actually give away:
|
||
|
||
| source | licence | notes |
|
||
| --- | --- | --- |
|
||
| **`SimulatedFlights`** | ours, Apache 2.0 | great-circle tracks on real SFO/OAK/SJC approach and departure paths. **This is what ships first**, and it is genuinely enough — the map wants convincing motion, not a spotter's log. |
|
||
| **`adsb.lol` / `airplanes.live`** | community, open terms | free, no key, real aircraft. The default "real data" adapter. |
|
||
| **OpenSky Network** | free, non-commercial | fine for a demo, awkward for a commercial product page. |
|
||
| **your own receiver** | *no licence at all* | an RTL-SDR + `dump1090` on any fleet box in the Bay produces first-party ADS-B. Nothing to comply with, and it is very on-brand. |
|
||
| FlightRadar24 | commercial, no redistribution | if it is ever wanted, it is a **private adapter in the Lumbridge deployment**, not in this repo. |
|
||
|
||
Recommendation: build `SimulatedFlights` now, `adsb.lol` next, and treat a
|
||
receiver as the eventual real answer.
|
||
|
||
---
|
||
|
||
## 5. LA is not "SF but more data"
|
||
|
||
SF is 0.20° × 0.36°. At the 45 m cells the SF heightfield uses, that is 336k
|
||
lattice points and a 1.0 s build. (The pack has since grown to the whole Bay
|
||
Area: 0.53M points and about 2.3 s.)
|
||
|
||
LA + Orange County + Riverside is roughly 0.5° × 1.9° — about **14× the area**.
|
||
The same approach would be 4.6M lattice points and a mesh nobody can load. This
|
||
is the one place where the engine as written does not simply extend, and it is
|
||
better to know that before writing `cities/la.ts` than after.
|
||
|
||
Two changes make it work, and both are cheap now:
|
||
|
||
1. **Cell size is per-city, not a constant.** LA's basin gets coarse cells;
|
||
nothing is lost, because LA's relief is mountains at the edges rather than
|
||
SF's hills every four blocks.
|
||
2. **Cities declare `focusRegions`** — a handful of boxes (DTLA, Santa Monica,
|
||
Culver, Irvine, Pasadena) rendered at fine resolution, with the basin between
|
||
them coarse. This is the LOD story, and it is a per-city data declaration
|
||
rather than engine machinery.
|
||
|
||
SF gets one focus region covering the whole city and behaves exactly as it does
|
||
now. LA gets six. NYC, later, gets Manhattan plus the inner boroughs.
|
||
|
||
### 5.1 The heightfield is built off the main thread
|
||
|
||
Both numbers above are seconds during which nothing rendered and nothing
|
||
responded, because the build ran on the thread that paints. It runs in a Worker
|
||
now (`src/engine/terrain.worker.ts`), which is why `createScene` is async and
|
||
takes a `Stage` rather than a canvas.
|
||
|
||
Three consequences worth knowing before touching it:
|
||
|
||
- **The city pack has to stay structured-cloneable.** It is posted to the
|
||
worker as data. A `City` that acquires a method, a class instance or a
|
||
closure stops being sendable, and the fix is to remove it rather than to
|
||
JSON round-trip around it — a city pack containing code is the thing §2
|
||
exists to prevent.
|
||
- **The sampled accessors stay synchronous.** `elevationAt`, `groundAt` and
|
||
`isLand` are called in tight loops by `terrain.ts`, `blocks.ts` and
|
||
`minimap.ts`; the asynchrony is confined to *becoming ready*, and sampling
|
||
before then is a documented error rather than a silent zero.
|
||
- **There is a main-thread fallback and it is not optional.** An environment
|
||
without Workers — a `file://` open, a locked-down browser — still has to
|
||
build and render, because "clone it and it works" has no exception clause.
|
||
|
||
The same reasoning put Spaces behind an `await import()`: the office is a large
|
||
slice of the bundle and most visitors never open it, so it is fetched when
|
||
somebody reaches for the door rather than by everybody at boot.
|
||
|
||
---
|
||
|
||
## 6. Hosted data and authority boundaries
|
||
|
||
Tera has no database requirement. The optional Fastify API supplies bounded
|
||
weather, flight, satellite, marker, office, realtime and screen-signaling
|
||
adapters behind same-origin `/api/v1/*`; a static clone continues with authored
|
||
geography and simulation when that API is absent.
|
||
|
||
Marker rows cross the strict public-shape and provenance allowlist before the
|
||
renderer sees them. The renderer receives only `Marker[]` and never pipeline or
|
||
tenant records. Realtime peers receive opaque page-scoped actor identifiers and
|
||
validated pose state, never auth subjects or profile faces. Screen media is
|
||
peer-to-peer: the API validates exact authored screen bindings and relays bounded
|
||
signaling, but it never receives media bytes, recordings or source locators.
|
||
|
||
The currently implemented hosted authorization is deployment-member plus global
|
||
admin. It is deliberately not described as tenant/office membership. A control-
|
||
plane membership provider must be added before private tenant offices share the
|
||
same deployment authority boundary. TURN is also optional and fail-closed; the
|
||
credential endpoint being implemented does not mean a public relay is deployed
|
||
or approved.
|
||
|
||
---
|
||
|
||
## 7. Interiors — and why the asset library is the real product
|
||
|
||
The existing Lumbridge world is a Phaser 4 2D office, and Karti's read is right:
|
||
its tileset is the thing that stops it being open-sourceable. Redrawing that in
|
||
3D with original assets is not a graphics exercise, it is a **licensing
|
||
unlock** — it is what lets the walkable-office half of Lumbridge ship under
|
||
Apache 2.0 at all.
|
||
|
||
That makes `src/assets` the highest-leverage part of this repo over time: an
|
||
original, consistent, Apache-2.0 library of desks, chairs, partitions, screens,
|
||
doors, lighting rigs, floor and wall materials. Built once, used by every
|
||
Lumbridge world and by anyone else who wants one.
|
||
|
||
Interiors reuse the engine's renderer, camera machinery and render loop, and
|
||
swap the city layer for a floorplan layer. The city view and the office view are
|
||
the outside and the inside of one world, and moving between them is a scene swap
|
||
rather than a different application.
|
||
|
||
Two claims that stood here earlier were wrong, and CONTRACT.md carries the
|
||
resolutions:
|
||
|
||
- **They do not share a `THREE.Scene`.** They cannot. San Francisco's `latScale`
|
||
puts one scene unit at ~94 m with 3.6× vertical exaggeration; an office renders
|
||
at 1 unit = 1 m. There are two scenes over one renderer, and the city scene is
|
||
*retained and paused* when you step inside rather than disposed — rebuilding
|
||
its 0.53M-point heightfield costs about two and a half seconds on the way back
|
||
out.
|
||
- **A person at a desk is not a `Marker`.** A marker is geographic — it has a
|
||
lat/lng. Presence binds to a **seat id** and never to a coordinate, which is
|
||
what lets the office geometry be public and open-source while who is sitting
|
||
in `eng-04` stays private data behind an API. Same shape as the marker rule,
|
||
one level in.
|
||
|
||
Reference point: the per-office spatial products in this space (Simile and
|
||
friends) are the shape to aim at. The difference Spaces is going for is that this
|
||
one is Apache 2.0 and self-hostable — you can run your own world on your own
|
||
hardware, which is the same claim `lumbridge-compute` makes about compute.
|
||
|
||
That phase is now implemented: offices consume the same procedural library and
|
||
retain the city scene behind a metre-scale interior scene swap.
|
||
|
||
---
|
||
|
||
## 8. Current release topology
|
||
|
||
The repository now ships California corridor, Bay Area and Southern California
|
||
boards; walkable offices; procedural actors, vehicles and aircraft; optional
|
||
authoritative realtime; local webcam faces; and separately authorized office
|
||
screen signaling. `BUILD_PLAN.md` is the milestone evidence ledger.
|
||
|
||
Four things landed together and are worth naming, because each crosses a boundary
|
||
this document describes:
|
||
|
||
- **One environment map for the page.** `src/engine/environmentRig.ts` derives a
|
||
sky (city) or a room (office) from the `LightingState` that `Atmosphere` already
|
||
decided, so the eleven metallic material roles and the Model X's clearcoat have
|
||
something to reflect. It sits at the *end* of §4's one-way street: it is handed a
|
||
decision and never makes one, and it constructs no light. One rig is built beside
|
||
the one renderer and shared by every scene; each scene releases itself from it on
|
||
dispose.
|
||
- **The interface is a pure function plus an applier.** See §2 above.
|
||
- **Smart hardware in the studios**, declared in packs and simulated in the tab
|
||
when a deployment has no device source — the same anon-first fallback
|
||
`SimulatedFlights` and `sample.ts` already make.
|
||
- **A Model X on each studio's arrival apron**, driven by
|
||
`src/transport/vehicleTelemetry.ts`, and overhead traffic in a sited office's
|
||
sky drawn from the same `FlightSource` the board outside is polling. The
|
||
aircraft are placed on a dome at their true bearing and elevation rather than at
|
||
their true range: an airliner at twenty kilometres is past a room-scale far
|
||
plane, and a map symbol drawn in 3-D is what §4's traffic has always been.
|
||
|
||
The source code is Apache-2.0. `src/assets/LICENSE-ART` additionally dedicates
|
||
the artistic output of the procedural asset library under CC0-1.0.
|
||
`PROVENANCE.json`, the dependency allowlist and SPDX SBOM gate the distribution.
|
||
|
||
Remaining questions are deployment questions, not missing renderer contracts:
|
||
|
||
1. Which authoritative control-plane provider proves tenant/office membership?
|
||
2. When do operators approve public TURN/DNS/firewall/certificate changes after
|
||
relay-only and abuse-boundary acceptance?
|
||
3. Which Firefox/WebKit versions become supported browser-test targets?
|
||
4. Which additional worlds or optional asset packs enter through the same
|
||
provenance and performance gates?
|