feat: real fire on the boards, the LA office as a twin, and a night sky worth reading
The world stops being a simulation of California and starts being California. **THE PROMOTION GATE WAS THE FIRST COMMIT, BEFORE ANY ORANGE PIXEL EXISTED.** On today's live store the SoCal board contains 22 incidents. Every one has NULL acreage and fifteen are nameless LA County dispatch numbers. Drawn naively that is 22 orange marks over Los Angeles on a day nothing is burning — in a frame that contains no other warm colour, so one glyph would be the most salient object on the board and twenty-two would spend its credibility permanently. `acres >= 10 AND contained < 80 AND type != 'RX' AND last_seen = max(last_seen)` returns 0 on SoCal, exactly 5 on California, 0 on the Bay — same body, same day, three correct answers. The empty board is a deliverable, not a fallback: it says "No active fire on this board — CAL FIRE and WFIGS, just now", states that 21 records were gated and why, lists the largest fires burning OUTSIDE the frame with distances, and counts the hot pixels it is deliberately not drawing. **The privacy leak is structurally impossible rather than carefully avoided.** cloud-1 serves a projection; the four home-relative columns never leave that box. `observations.threat` was the one that nearly got through — it is `(16/distance)^2 x log10(acres) x momentum x containment x wind-alignment`, so with acreage and containment public it inverts to a distance circle around a house and three fires give an intersection. A grep of the built bundle for distance_km, bearing_deg, threat, 7762 and the street name returns nothing. **Deliberately not used, and both would have produced a confident wrong answer:** the store's `air` table retains only the last parameter of each poll, so all 78 rows read "Good" while the live feed reports ozone 101 "Unhealthy for Sensitive Groups" — haze driven off it would clear the sky during a smoke event. And `weather` is written only inside the NWS alerts loop, so a quiet day stores no wind at all. Tera's own per-region NWS wind is already correct and already what the clouds drift on. Satellite detections are drawn as evidence and never as incidents. The permanent industrial heat source 4.7 km from the owner's house is flagged persistent and dropped, asserted by a test that first proves it is present in the fixture. MODIS integer confidence and VIIRS string confidence are branched on `sat`. **The LA office is a twin.** Its entire authored second storey — Model Loft, Model Bay, The Materials Room, 430 lines nobody had ever stood in — is reachable on foot: a walker crosses level-1 to level-2 in 73 fixed steps, floorY 0 to 5, verified against the real pack rather than a synthetic plan. Its two studio devices read real hardware through a field-allowlisted bridge: mute, volume and reachability only. Never level, because there is no passive level upstream and obtaining one would record a room with people in it. Never dB, because upstream is gainPct across four different native scales. The bridge refuses all writes. Fixed at its root: an anonymous visitor was getting permanently at-rest instruments backing off against a 401. The tier moves into `createDeviceSource`, so anon gets the living simulator three file headers already promised. **Item 8 is closed, not fixed, and the correction is the point.** The Bay Area "stutter" was GPU power management — the card sat at 500 MHz of 2725 through every run that reproduced it, 4096/2048/1024/256 shadow maps all render in 1.21-1.31 ms, and two consecutive runs over a byte-identical dist gave 33.4 then 16.7. The allowance is removed and the cell is back to 16.7. Geometry is the gate; frame time is advisory. Item 7 was re-scoped after measuring: 1,069,006 of the Bay Area's 2,265,056 triangles were the second submission of the same buildings into the shadow pass. Mobile now has its own triangle caps and bay-area mobile draws 1,266,096. Also: bridges and the freeway corridor light up at night as emission, not lights — 1,614 deck lamps and 18 tower heads on the Bay in two draw calls. The single change that made US-101 legible was moving its edge lines from the lit material to the unlit one: retroreflective paint, the argument the SFO night frame already makes. California went 21,991 lamps to 4,051, clustered at the 17 town districts, because a rural interurban corridor genuinely is unlit. Tests 1137 -> 1340, server 280. All ten budget cells pass on first attempt with no cap raised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+275
@@ -429,3 +429,278 @@ Remaining questions are deployment questions, not missing renderer contracts:
|
||||
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?
|
||||
|
||||
---
|
||||
|
||||
## 9. Fire — and why the privacy filter lives on cloud-1
|
||||
|
||||
Tera can draw the wildfires that are actually burning in California. The data
|
||||
behind that is collected by `fleet-tools/fires` on **cloud-1**, into a sqlite
|
||||
store that is centred on the owner's *house*. That single fact decides the whole
|
||||
architecture of this feature, and it is worth stating before any of the shapes.
|
||||
|
||||
### 9.1 A projection, not a copy
|
||||
|
||||
cloud-1 serves `GET /api/fires/incidents` and `/api/fires/detections` as a
|
||||
**projection**: a hand-written column list that a home-relative column is not in.
|
||||
`tera-api` consumes those over `createUpstream` exactly the way it consumes
|
||||
satellites, and never opens the sqlite. The store is not copied, mirrored,
|
||||
exported on a cron, or replicated to cloud-2.
|
||||
|
||||
The reason is not convenience. These columns are computed against the owner's
|
||||
home and must never reach a public wire:
|
||||
|
||||
| Column | Table |
|
||||
|---|---|
|
||||
| `distance_km` | `observations`, `detections`, `evac_zones` |
|
||||
| `bearing_deg` | `observations` |
|
||||
| `threat` | `observations` |
|
||||
|
||||
`threat` is the one that is easy to miss. It is
|
||||
`(16.0 / distance_to_house)^2 x log10(acres) x momentum x containment x
|
||||
wind-alignment` — so with acreage and containment public, publishing `threat`
|
||||
**solves for the distance**. One circle around the house per fire; three fires
|
||||
give an intersection. Two more are the coordinate with extra steps: `air.area`
|
||||
is the literal string "Norco and Corona", a named two-town area next to the
|
||||
house, and the `fires near` CLI prints the full street address in its header, so
|
||||
no design may proxy that stdout.
|
||||
|
||||
If `tera-api` pulled the sqlite, every one of those would be sitting on cloud-2
|
||||
one careless `SELECT *` away from a public route. Serving a projection makes the
|
||||
leak structurally impossible rather than merely avoided — and it costs this repo
|
||||
nothing, because `adapters/http.ts` and `createUpstream` already are exactly this
|
||||
shape. It also refuses the failure mode this store exists to prevent: a cron
|
||||
export that silently stops looks identical to "no fires", and `BACKUP.md` already
|
||||
records what a timer reporting success is worth.
|
||||
|
||||
Note also that `acres`, `pct_contained` and the rest live in `observations`, not
|
||||
in `incidents` — the projection is a join to the latest observation per incident,
|
||||
and the join's `SELECT` list is precisely where a lazy `o.*` would put the
|
||||
home-relative columns on the wire.
|
||||
|
||||
### 9.2 The promotion gate is the feature
|
||||
|
||||
`promote()` in `src/server/fires.ts` decides what is worth drawing:
|
||||
|
||||
```
|
||||
acres >= 10
|
||||
AND coalesce(pct_contained, 0) < 80
|
||||
AND type != 'RX'
|
||||
AND last_seen = (SELECT max(last_seen) FROM incidents)
|
||||
```
|
||||
|
||||
Verified against the live store on a quiet day: **zero** rows inside the SoCal
|
||||
board bounds, and exactly **five** on the California board. Same data, same day,
|
||||
both answers correct. Ungated, SoCal draws twenty-two orange marks over Los
|
||||
Angeles on a day nothing is burning — every one with a NULL acreage, fifteen of
|
||||
them nameless LA County dispatch numbers — in a frame that contains no other warm
|
||||
colour, so each one is the most salient object on the board.
|
||||
|
||||
Three of the four clauses are there for a specific, verified reason:
|
||||
|
||||
- **`last_seen = max(last_seen)`** — `persist()` writes every row and never
|
||||
deletes, so a deduplication loser keeps its old `last_seen` forever. 21 of 95
|
||||
rows are stale ghosts; MP18 and Timber each appear twice, 850 m apart, with
|
||||
different acreage.
|
||||
- **`type != 'RX'`** — a prescribed burn is deliberate, scheduled, frequently
|
||||
adjacent to a real one, and indistinguishable from a wildfire under a distance
|
||||
filter. It must never render as one.
|
||||
- **`acres >= 10`** — an untested judgement, stated as such. The store has never
|
||||
held a SoCal fire between 1 and 100 acres, so this boundary has never been
|
||||
exercised against the case it exists for, and a genuinely dangerous 5-acre fire
|
||||
in Griffith Park would be invisible. It is accepted because WFIGS's own LA
|
||||
County records make the low-acreage false-positive rate overwhelming, and the
|
||||
constant is written where it is obvious and one edit away.
|
||||
|
||||
An empty board is therefore a **result**, not a gap, and the panel says so with
|
||||
the age of the fetch: *"No active fire on this board — CAL FIRE and WFIGS, 4
|
||||
minutes ago."* A silent board and a dead feed are indistinguishable without a
|
||||
timestamp, which is the same argument `health.ts` already makes for `degraded[]`.
|
||||
|
||||
### 9.3 A detection is evidence, not an incident
|
||||
|
||||
Satellite thermal detections are a **separate, visually weaker layer** and are
|
||||
never promoted into an incident client-side. There is a permanent industrial heat
|
||||
source 4.7 km from the owner's house at FRP ~1.0 that appears on every pass, on
|
||||
every day in the store, with no matching incident: drawn as a fire, it draws a
|
||||
fire on his house. Two more properties of that table have bitten already —
|
||||
`confidence` carries incompatible scales in one column (MODIS is an integer
|
||||
0-100, VIIRS is `low`/`nominal`/`high`, so read `sat` first), and the
|
||||
highest-FRP detections in the store are in **Nevada**, because `CA_BBOX` is a
|
||||
rectangle and `in_ca()` gates incidents only.
|
||||
|
||||
### 9.4 Two tables that must not be used
|
||||
|
||||
- **`air`** — `observed_at` is the primary key and `fires.py` does `INSERT OR
|
||||
REPLACE` once per parameter per poll, so only the last parameter survives.
|
||||
Every row says NO2 / AQI 20 / "Good" while the live feed reports ozone 101 and
|
||||
PM2.5 55 at the same instant. Haze driven off it *clears the sky during a smoke
|
||||
event*.
|
||||
- **`weather`** — every row is `zone='point'`, one grid sample at the owner's
|
||||
house, written only inside the NWS alerts loop, so on a quiet day no wind is
|
||||
stored at all. Tera's own per-region `WeatherBody.windDirDeg`/`windKph` is
|
||||
already fetched, already correct for the board, and is already what the clouds
|
||||
drift on. The plumes drift on the same one, because a plume leaning on a
|
||||
different wind from the cloud beside it would be two opinions about one sky.
|
||||
|
||||
### 9.5 Where it attaches in the engine
|
||||
|
||||
`scene.ts` owns the wiring and constructs no fire geometry itself. The seam is
|
||||
`SceneOptions.fires`, a factory with the same shape `createCloudLayer` has, and
|
||||
five forwarders on `SceneHandle`:
|
||||
|
||||
```ts
|
||||
type FireLayerFactory = (world: World, options: { span: number }) => FireLayer;
|
||||
|
||||
interface SceneHandle {
|
||||
setFires(view: FireView | null): void; // null = nothing has answered yet
|
||||
setFireSmoke(visible: boolean): void; // plumes on/off; the marks stay
|
||||
fireSmokeLoadAt(lat: number, lng: number): number; // 0..1, for haze elsewhere
|
||||
// and, already present: setLighting, setSolarElevation, setWind
|
||||
}
|
||||
```
|
||||
|
||||
`FireView`, `DrawnFireMark` and `FireDetectionMark` are declared in `scene.ts`
|
||||
as the **minimum a renderer needs**, and `FirePromotion` — what `promote()` in
|
||||
`src/server/fires.ts` actually returns — is assignable to `FireView` with no
|
||||
mapping step. The duplication is deliberate and is what keeps the engine from
|
||||
importing a wire module: the same rule that keeps `Marker` in
|
||||
`engine/types.ts` and the marker row on the server. `src/test/fireSeam.test.ts`
|
||||
is the one thing standing between that decision and silent drift — it assigns a
|
||||
`FirePromotion` to a `FireView` at compile time, so a renamed column fails there
|
||||
rather than in a render loop.
|
||||
|
||||
`fireSmokeLoadAt` is the one read-back, and it is what couples the LA courtyard
|
||||
to the real sky. A fire sixty kilometres away in the San Gabriels is not a flame
|
||||
seen from a courtyard: it is a brown horizon, a dimmed orange sun, and air that
|
||||
stops being clear closer in. That is one scalar into `daylight.ts`, and zero
|
||||
geometry.
|
||||
|
||||
`ATMOSPHERE IS THE SOLE LIGHT OWNER` still holds (CONTRACT §4). The night glow of
|
||||
a fire is emissive material and one additive ground quad — the same two
|
||||
mechanisms `nightlights.ts` uses to draw San Francisco's 12,038 street lamps in
|
||||
one draw call — and not a `THREE.PointLight`.
|
||||
|
||||
---
|
||||
|
||||
## 10. First-party devices, and the anonymous visitor
|
||||
|
||||
A studio's instruments reach the renderer by one of two strategies, chosen once
|
||||
at construction in `createDeviceSource`: the deployment's device route, or the
|
||||
fixed-step simulator bundled in the tab. There is no runtime failover, because a
|
||||
strategy that silently swapped a real bridge for a simulator mid-session would be
|
||||
the `first-party-sensor`/`simulated` confusion `DeviceProvenance` exists to
|
||||
prevent, arriving without a word in the interface.
|
||||
|
||||
**That choice needs two facts and used to be made from one.** `Feeds.devices`
|
||||
says the *deployment* has a device source; `Capabilities.liveDevices` says the
|
||||
*viewer* may read it. On cloud-2 the first is true — `/health` reports
|
||||
`devices: "sim"` — and for an anonymous visitor the second is false, because the
|
||||
route is members-only and answers 401. Passing only the deployment's answer sent
|
||||
every anonymous visitor down the API strategy to be refused, whereupon
|
||||
`apiSource` rendered `atRest()` — a rack of powered-off instruments, forever,
|
||||
backing off exponentially against a request that could never pass — beside a
|
||||
panel describing a studio that runs locally. Both gates are now applied at the
|
||||
one call site in `main.ts`, and the fallback is the simulator that was always
|
||||
meant to serve this case.
|
||||
|
||||
What a first-party studio may honestly mirror is bounded by what the upstream
|
||||
actually measures, and two refusals are load-bearing:
|
||||
|
||||
- **No microphone level.** There is no passive level upstream; `POST /levels`
|
||||
*calls* `measureMic` and records 1.5–3 s per mic. "Just add the level meter to
|
||||
make it feel alive" builds a continuously-recording microphone and it looks
|
||||
like a feature while doing it. No level capability, no JPEG from
|
||||
`/cameras/:id/live.jpg` (which *captures* on demand), no `/sleep`, no
|
||||
`/automations` — at any tier, behind any flag.
|
||||
- **No decibels.** Upstream speaks `gainPct`, normalised over four different
|
||||
native scales (Yeti max 50, SMY18 and Anker 100, ThinkPad 63). Rendering 68% as
|
||||
"20.6 dB" would look completely plausible and would be a guess presented as a
|
||||
measurement — the exact failure this product names elsewhere. Without a
|
||||
declared `DeviceDeclaration.ranges`, the gain row is not mirrored at all.
|
||||
|
||||
---
|
||||
|
||||
## 11. How the fire reaches the board, and where it stops
|
||||
|
||||
Sections 9 and 10 describe the two feeds. This one is the wiring, because every
|
||||
decision in it is about *which* board and *which* viewer, and those are made in
|
||||
exactly one file each.
|
||||
|
||||
### 11.1 Three gates, and none of them is a permission
|
||||
|
||||
`main.ts` polls `/api/v1/fires` only when all three hold, and each is about a
|
||||
different thing:
|
||||
|
||||
| Gate | Question | Where |
|
||||
|---|---|---|
|
||||
| `FIRE_BOARDS.has(id)` | does fire happen on this rectangle? | `main.ts` |
|
||||
| `access.feeds.fires` | has this deployment got a projection? | `access.ts`, from `/health` |
|
||||
| — | may this viewer see it? | **there is no third gate** |
|
||||
|
||||
A wildfire is a public agency record. CAL FIRE publishes every one of these on
|
||||
its own website, so there is no `Capabilities` twin to `feeds.fires` and there
|
||||
must never be one: the fires are exactly as public as the weather and the
|
||||
aeroplanes, and the anon-first rule that governs those governs this.
|
||||
|
||||
The board gate is the one people will want to remove. It is not laziness — the
|
||||
Bay Area rectangle has held zero incidents on every day the upstream store has
|
||||
existed, and it is the board already carrying the largest frame-time allowance
|
||||
in the product. `SceneOptions.fires` is *withheld* rather than passed and left
|
||||
empty, so on San Francisco there is no group, no material and no draw call.
|
||||
|
||||
### 11.2 One body, every board
|
||||
|
||||
`/fires` takes no query and is not per-region: the whole state's live incident
|
||||
set is small, and the clip is `promote()`, which the client has to run anyway to
|
||||
apply the tier ladder. That has a visible dividend and `main.ts` spends it —
|
||||
`firesBody` is held for the **page** rather than for the board, so switching
|
||||
from California to the Southland re-clips the answer already in hand and draws
|
||||
the correct, and correctly empty, board in the frame of the switch. Only the
|
||||
first board of a session ever shows the "nothing has answered" sentence.
|
||||
|
||||
The three states are distinct and the panel says which one it is in:
|
||||
|
||||
- `ageMs === null` — nothing has ever answered. **A fault sentence, never an
|
||||
all-clear.** A dead feed and a quiet day are indistinguishable without a
|
||||
timestamp, which is the same argument `health.ts` makes for `degraded[]`.
|
||||
- `drawn.length === 0, suppressed > 0` — the board is quiet and *n* live records
|
||||
were refused by the gate. The Southland today: nothing drawn, twenty-one
|
||||
refused, and the panel names the threshold that refused them.
|
||||
- `drawn.length === 0, suppressed === 0` — nothing is happening on this
|
||||
rectangle at all.
|
||||
|
||||
### 11.3 Where the panel is, and where it goes away
|
||||
|
||||
`#fire-section` and `#fire-host` belong to `main.ts`, not to `mount.ts` — the
|
||||
same arrangement `#presence-host` has, and the reason `main.ts`'s "write to no
|
||||
chrome node" rule survives. The panel is rebuilt per board because its `bounds`
|
||||
are the board's, and one rectangle feeds both `promote()` and the caption so the
|
||||
picture and the sentence cannot disagree about what is off-frame.
|
||||
|
||||
**Inside a building the panel appears only when the fires are in that building's
|
||||
sky.** Outside, it is always up on a board that draws fire, because "nothing is
|
||||
burning here" is the fact worth stating. Inside, a five-item list of incidents
|
||||
three hundred kilometres away pushed the room's own controls below the fold on
|
||||
the first frame a visitor sees of it. When the courtyard actually goes brown the
|
||||
list is the explanation for what is on screen and it belongs there;
|
||||
`smokeCaption` says the same thing in one sentence either way, and it sits with
|
||||
the room's other disclosures rather than in the fire panel, because it is a
|
||||
claim about *this room's picture*: the sky in here is derived from incidents on
|
||||
the map and is not a measurement of the air at this address.
|
||||
|
||||
### 11.4 The office coupling is one argument
|
||||
|
||||
`officeDaylight(state, site, smokeLoad)` — third argument, clamped, and
|
||||
bit-identical to no argument at zero. The scalar is
|
||||
`SceneHandle.fireSmokeLoadAt(site.lat, site.lng)`, and the adaptation happens
|
||||
once, in `main.ts`, where the rest of the office's rig adaptation already
|
||||
happens. A room may not reach into the fire layer and form a second opinion
|
||||
about its own sky (CONTRACT §4).
|
||||
|
||||
One consequence is worth stating rather than hiding: the load is computed from
|
||||
the **board you came in from**, so the same building is very slightly hazier
|
||||
entered from California than from the Southland, because the two rectangles
|
||||
contain different fires. `smokeCaption`'s sentence is worded for exactly that —
|
||||
"drawn from the fires currently on the board" — and the alternative, a statewide
|
||||
query per building, is a second source of truth about the same sky.
|
||||
|
||||
Reference in New Issue
Block a user