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>
This commit is contained in:
+46
-1
@@ -87,7 +87,10 @@ tera/
|
||||
├── 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/main.ts # the standalone demo app
|
||||
├── 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
|
||||
@@ -101,6 +104,27 @@ 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`.
|
||||
@@ -372,6 +396,27 @@ 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.
|
||||
|
||||
Reference in New Issue
Block a user