1
0

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:
2026-08-22 18:01:11 -07:00
parent b7f5c41da5
commit b25f217e3e
91 changed files with 15111 additions and 401 deletions
+111
View File
@@ -0,0 +1,111 @@
/**
* The anonymous visitor gets a working studio, and the reason is a *pair* of
* gates rather than one.
*
* This is a regression test for something that shipped. `/etc/tera-api.env` on
* cloud-2 sets `TERA_DEVICES_SOURCE=sim`, so `/api/v1/health` reports
* `sources.devices: "sim"` and `access.feeds.devices` is `true`. `main.ts`
* passed that one fact into `createDeviceSource` as `serverHasDevices` and
* never passed the viewer, so an anonymous visitor took the API strategy
* against a members-only route, was refused with a 401 on every attempt, and
* `apiSource` rendered `atRest()` — a rack of powered-off instruments — for the
* life of the tab, backing off exponentially against a request that could not
* ever pass. The panel next to it says the studio is simulated locally.
*
* Three assertions, and the first two are the interesting ones:
*
* 1. `capabilitiesFor("anon").liveDevices` is false, so the tier has an
* opinion at all — before this it had none.
* 2. The call site in `main.ts` reads *both* facts. Asserted against the
* source text on purpose: the defect was never in `adapter.ts`, which has
* always chosen correctly given what it was told, and a unit test of the
* adapter would have stayed green through the entire outage.
* 3. With both gates applied, an anonymous viewer on a `devices: "sim"`
* deployment gets readings that *change* — the thing "at rest forever"
* was not.
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
import { capabilitiesFor, type Feeds } from "../access.ts";
import { createDeviceSource } from "../devices/adapter.ts";
import type { DeviceDeclaration } from "../devices/types.ts";
/** What cloud-2 reports today: a real device source, wired and named. */
const PRODUCTION_FEEDS: Feeds = {
weather: true,
flights: true,
devices: true,
satellites: false,
markers: true,
fires: true,
};
const MIC: DeviceDeclaration = {
id: "mic-1",
kind: "mic",
label: "Desk mic",
assetId: "tera:device.mic.desk",
anchor: { levelId: "l1", propId: "mic-prop", seatId: "desk-01" },
capabilities: ["power", "mute", "gain", "level"],
provenance: "simulated",
disclosure: "Simulated studio hardware. Demonstration data, never presence data.",
};
/**
* The expression `main.ts` evaluates, restated once so the test can run it.
* Assertion 2 is what keeps the two copies honest.
*/
function serverHasDevicesFor(tier: "anon" | "member" | "god", feeds: Feeds): boolean {
return feeds.devices !== false && capabilitiesFor(tier).liveDevices;
}
describe("the anonymous studio", () => {
it("does not grant an anonymous viewer the members-only device route", () => {
assert.equal(capabilitiesFor("anon").liveDevices, false);
assert.equal(capabilitiesFor("member").liveDevices, true);
assert.equal(capabilitiesFor("god").liveDevices, true);
});
it("asks both questions at the call site in main.ts", () => {
const source = readFileSync(new URL("../main.ts", import.meta.url), "utf8");
const line = source
.split("\n")
.find((l) => l.includes("serverHasDevices:"));
assert.ok(line, "main.ts no longer passes serverHasDevices");
assert.match(line, /access\.feeds\?\.devices !== false/);
assert.match(line, /access\.can\.liveDevices/);
});
it("gives an anon viewer on a devices:'sim' deployment readings that change", () => {
const serverHasDevices = serverHasDevicesFor("anon", PRODUCTION_FEEDS);
assert.equal(serverHasDevices, false, "anon must not take the API strategy");
const source = createDeviceSource({
declarations: [MIC],
// A client is present — this is a real deployment — and it is the tier,
// not its absence, that has to keep us off the route.
client: {
watchDevices: () => ({ current: () => null, refresh: () => {}, stop: () => {} }),
commandDevice: () => Promise.resolve(null),
},
officeId: "mateo-court",
serverHasDevices,
seed: 8731,
fixedStepSeconds: 0.05,
});
const first = JSON.stringify(source.current());
// 200 ms of wall clock, at the simulator's own fixed step.
source.tick(0.2);
const later = JSON.stringify(source.current());
source.stop();
assert.notEqual(
first,
later,
"an anonymous studio must be alive, not a rack of instruments at rest",
);
});
});
+83
View File
@@ -0,0 +1,83 @@
/**
* What an Arena office-nav snapshot may legally contain, now that a walker can
* change storeys.
*
* `src/interiors/walker.ts`'s `restore()` used to refuse any snapshot whose
* `levelId` differed from the spawn level, and `OfficeNavEnvironment` restores
* an episode straight through it. That rule has been relaxed to "a level the
* plan resolves, at a position valid on it" — which is a change to this
* environment's contract and not an internal detail, so it is asserted here
* explicitly rather than left implied by the controller's own unit tests.
*
* Frontier Valley is the pack this environment runs, and it has a mezzanine at
* 4.4 m whose footprint is a *subset* of the ground floor's. That is what makes
* the second assertion below meaningful: `(20, 18)` is a place a walker can
* stand on level 1 and is off the edge of the mezzanine, so "valid on the level
* you are on" and "valid somewhere in this building" are different answers.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { arenaChecksum } from "../../arena/checksum.ts";
import { OfficeNavEnvironment } from "../../arena/officeNav.ts";
/** Re-checksum a hand-edited snapshot so the episode bookkeeping still validates. */
function resign<T extends { checksum: string }>(snapshot: T): T {
const { checksum: _drop, ...core } = snapshot;
return { ...core, checksum: arenaChecksum(core) } as T;
}
describe("arena office-nav snapshots across storeys", () => {
it("restores an episode whose walker is on the mezzanine", () => {
const environment = new OfficeNavEnvironment();
environment.reset(11, "train-hangar-crossing");
const snapshot = environment.snapshot();
assert.equal(snapshot.simulation.walker.levelId, "level-1");
const upstairs = resign({
...snapshot,
simulation: {
...snapshot.simulation,
walker: {
...snapshot.simulation.walker,
levelId: "level-mezz",
position: { x: 45, z: 25 },
},
},
});
const restored = environment.restore(upstairs);
assert.equal(restored.observation.levelId, "level-mezz");
assert.equal(restored.observation.x, 45);
assert.equal(restored.observation.z, 25);
});
it("still refuses a position that is not valid on the level it names", () => {
const environment = new OfficeNavEnvironment();
environment.reset(11, "train-hangar-crossing");
const snapshot = environment.snapshot();
// Inside the hangar, well off the west edge of the mezzanine.
const offTheEdge = resign({
...snapshot,
simulation: {
...snapshot.simulation,
walker: {
...snapshot.simulation.walker,
levelId: "level-mezz",
position: { x: 20, z: 18 },
},
},
});
assert.throws(() => environment.restore(offTheEdge), RangeError);
const unknownLevel = resign({
...snapshot,
simulation: {
...snapshot.simulation,
walker: { ...snapshot.simulation.walker, levelId: "level-9" },
},
});
assert.throws(() => environment.restore(unknownLevel), RangeError);
});
});
+30 -2
View File
@@ -574,11 +574,39 @@ describe("studio-ops wraps the simulators the renderer drives", () => {
assert.ok(speaker, `${definition.id} names a speaker the plan did not resolve`);
assert.equal(mic.kind, "mic");
assert.equal(speaker.kind, "speaker");
assert.equal(mic.provenance, "simulated");
assert.match(mic.disclosure.toLowerCase(), /simulat/);
/**
* Every reading an episode sees is invented here, whatever the *pack*
* says about where its numbers come from in production.
*
* This used to assert `mic.provenance === "simulated"`, which stopped
* being the right question the day `mateo-court`'s two studio instruments
* were promoted to `first-party-sensor`. The environment builds
* `createSimulatedDevices(setup.declarations, …)` unconditionally — there
* is no bridge, no fetch and no network anywhere in this file — so a
* first-party declaration is a description of hardware that this simulator
* then makes up numbers for. What has to hold is that such a declaration
* still carries the sentence that says so, which is exactly the field
* `validateDeviceDeclaration` now requires of it.
*/
const sentence = mic.provenance === "simulated"
? mic.disclosure
: declaredMic(definition.parameters.officeId, definition.parameters.micId)
?.simulatedDisclosure ?? "";
assert.match(sentence.toLowerCase(), /simulat/, definition.id);
}
});
/** The authored declaration behind a resolved device, for the fields `Plan` does not carry. */
function declaredMic(officeId: string, micId: string) {
const pack = officeId === "lumbridge-hq" ? LUMBRIDGE_HQ : MATEO_COURT;
for (const level of pack.levels) {
for (const device of level.floorplan.devices ?? []) {
if (device.id === micId) return device;
}
}
return undefined;
}
it("names the same simulator stack in its manifest as it imports", () => {
assert.equal(STUDIO_OPS_MANIFEST.id, "studio-ops-v1");
for (const fragment of [
+113 -1
View File
@@ -26,7 +26,7 @@ import { describe, it } from "node:test";
import { createTeraClient, describeLiveness } from "../../adapters/http.ts";
import { SAMPLE_MARKERS } from "../../adapters/sample.ts";
import type { SkyRegion } from "../../engine/flights.ts";
import type { DevicesBody, FlightsBody, WeatherBody } from "../../server/wire.ts";
import type { DevicesBody, FiresBody, FlightsBody, WeatherBody } from "../../server/wire.ts";
/** The Bay Area, roughly, and big enough that the fixtures below are inside it. */
const SF: SkyRegion = { center: { lat: 37.77, lng: -122.42 }, radiusNm: 60 };
@@ -556,3 +556,115 @@ describe("devices", () => {
assert.equal(feeds.length, 0);
});
});
/**
* The fire feed, from the browser's side.
*
* Two properties, and both of them are about a board with nothing on it — which
* is the commonest correct answer this feed will ever give, and the one that is
* indistinguishable from a broken feed unless the client is careful.
*/
const FIRES: FiresBody = {
source: "cloud1",
fetchedAt: "2026-08-22T22:22:35.806Z",
latestSeen: "2026-08-22T22:20:06Z",
incidents: [],
detections: [],
detectionWindowHours: 24,
ttlSeconds: 600,
};
describe("fires", () => {
it("asks one unparameterised route, because the answer is the same for everyone", async () => {
const { fetcher, calls } = stubFetch({ "/fires": FIRES });
const body = await createTeraClient({ fetch: fetcher }).fires();
assert.equal(body?.source, "cloud1");
assert.equal(calls.length, 1);
// No `?city=`. The whole state's live set is small, the boards are
// rectangles inside it, and `promote()` has to clip anyway.
assert.ok(!String(calls[0]?.url).includes("?"));
});
it("is null when nobody answered, and never an empty board", async () => {
const { fetcher } = stubFetch({});
assert.equal(await createTeraClient({ fetch: fetcher }).fires(), null);
});
it("refuses a 200 that is the SPA shell rather than a body", async () => {
const { fetcher } = stubFetch({ "/fires": "html" });
assert.equal(await createTeraClient({ fetch: fetcher }).fires(), null);
});
it("republishes an unchanged body, so the age on screen keeps moving", async (t) => {
// Deliberately unlike `watchDevices`, which publishes only on change. An
// unchanged fire body still carries a NEWER `fetchedAt`, and that is the
// field a quiet board is captioned with. Suppressing the republish would
// freeze "4 minutes ago" on screen at the moment of the last change, so a
// feed that died an hour ago would go on claiming to be fresh — the precise
// failure a stated fetch age exists to prevent.
let served = 0;
const { fetcher } = stubFetch({
"/fires": () => {
served += 1;
return { ...FIRES, ttlSeconds: 60 };
},
});
const bodies: (FiresBody | null)[] = [];
const watch = createTeraClient({ fetch: fetcher }).watchFires((body) => bodies.push(body));
t.after(() => watch.stop());
await settle();
assert.equal(bodies.length, 1);
assert.equal(watch.current()?.source, "cloud1");
watch.refresh();
await settle();
await settle();
assert.equal(served, 2);
assert.equal(bodies.length, 2);
});
it("reports a refusal rather than freezing on the last good board", async (t) => {
let up = true;
const { fetcher } = stubFetch({ "/fires": () => (up ? FIRES : undefined) });
const bodies: (FiresBody | null)[] = [];
const watch = createTeraClient({ fetch: fetcher }).watchFires((body) => bodies.push(body));
t.after(() => watch.stop());
await settle();
assert.equal(bodies[0]?.source, "cloud1");
up = false;
watch.refresh();
await settle();
await settle();
// `null`, not the previous board. "California stopped burning" and "I have
// stopped hearing about California" are different facts and only one of
// them may be drawn.
assert.equal(bodies.at(-1), null);
assert.equal(watch.current(), null);
});
it("refuses a body with no incident array, whatever its status was", async (t) => {
const { fetcher } = stubFetch({ "/fires": { source: "cloud1", ttlSeconds: 600 } });
const bodies: (FiresBody | null)[] = [];
const watch = createTeraClient({ fetch: fetcher }).watchFires((body) => bodies.push(body));
t.after(() => watch.stop());
await settle();
// The shape a server one version behind this one sends. Adopting it would
// reach `promote()` as an empty board — a silent all-clear.
assert.equal(bodies.at(-1), null);
});
it("does no work at all once stopped", async () => {
const { fetcher, calls } = stubFetch({ "/fires": FIRES });
const bodies: (FiresBody | null)[] = [];
const watch = createTeraClient({ fetch: fetcher }).watchFires((body) => bodies.push(body));
await settle();
const seen = calls.length;
watch.stop();
watch.refresh();
await settle();
assert.equal(calls.length, seen);
});
});
+112
View File
@@ -30,6 +30,7 @@ import {
DEVICE_PROVENANCE,
DEVICE_RANGES,
deviceKindOfAssetId,
deviceRange,
deviceStateSignature,
hasCapability,
initialDeviceState,
@@ -369,3 +370,114 @@ describe("the change signature", () => {
assert.notEqual(deviceStateSignature([mic, speaker]), deviceStateSignature([speaker, mic]));
});
});
/**
* The three additions real hardware forced, and the reason each is here.
*
* The contract as written could not carry a real room. `gainPct` normalised over
* four different native scales has no honest decibel representation; a
* microphone behind an SSH hop is sometimes unreachable and `powered: false` is
* the wrong word for that; and the anonymous path runs the local simulator under
* a declaration whose own disclosure says the hardware is live.
*/
/** A Blue Yeti Nano: capture level is an ALSA position on a 050 scale. */
const YETI: DeviceDeclaration = {
...DESK_MIC,
id: "la-mic-yeti",
ranges: { gain: { min: 0, max: 100, initial: 68, unit: "%" } },
provenance: "first-party-sensor",
disclosure: "Live reading from the studio's own desk microphone.",
simulatedDisclosure: "Simulated in your browser — no live room is shared with visitors.",
};
describe("a device's own ranges", () => {
it("falls back to the global range when a declaration names none", () => {
assert.deepEqual(deviceRange(DESK_MIC, "gain"), DEVICE_RANGES.gain);
assert.deepEqual(deviceRange(DESK_MIC, "volume"), DEVICE_RANGES.volume);
assert.deepEqual(deviceRange(DESK_MIC, "level"), DEVICE_RANGES.level);
});
it("uses the declared range where there is one, per capability", () => {
assert.deepEqual(deviceRange(YETI, "gain"), { min: 0, max: 100, initial: 68, unit: "%" });
// Partial: `level` was not declared, so it keeps the global answer.
assert.deepEqual(deviceRange(YETI, "level"), DEVICE_RANGES.level);
});
it("ignores a declared range that is not usable", () => {
// A hand-edited pack is entitled to get this wrong, and a slider with NaN on
// both ends is worse than one on the wrong scale.
for (const broken of [
{ min: Number.NaN, max: 10, initial: 1, unit: "%" },
{ min: 10, max: 10, initial: 10, unit: "%" },
{ min: 50, max: 0, initial: 10, unit: "%" },
]) {
const declaration = { ...DESK_MIC, ranges: { gain: broken } } as DeviceDeclaration;
assert.deepEqual(deviceRange(declaration, "gain"), DEVICE_RANGES.gain);
}
});
it("clamps a command into the declared range, not the global one", () => {
// The failure this prevents: 999 clamped to +36 on a device whose scale
// stops at 100, then written to hardware as a percent.
assert.deepEqual(normalizeDeviceCommand(YETI, { deviceId: YETI.id, op: "gain", value: 999 }), {
deviceId: YETI.id,
op: "gain",
value: 100,
});
assert.deepEqual(normalizeDeviceCommand(YETI, { deviceId: YETI.id, op: "gain", value: -5 }), {
deviceId: YETI.id,
op: "gain",
value: 0,
});
});
it("rests where the declaration says it rests", () => {
assert.equal(initialDeviceState(YETI, 1).gainDb, 68);
assert.equal(initialDeviceState(DESK_MIC, 1).gainDb, DEVICE_RANGES.gain.initial);
});
});
describe("the disclosure a simulated fallback shows", () => {
it("requires a simulatedDisclosure on anything that claims real hardware", () => {
const { simulatedDisclosure: _omitted, ...withoutIt } = YETI;
const problems = validateDeviceDeclaration(withoutIt as DeviceDeclaration);
assert.equal(problems.length, 1);
assert.match(problems[0] ?? "", /simulatedDisclosure/);
});
it("requires that sentence to actually say it is simulated", () => {
const problems = validateDeviceDeclaration({
...YETI,
simulatedDisclosure: "Readings shown in this browser.",
});
assert.equal(problems.length, 1);
assert.match(problems[0] ?? "", /does not say it is simulated/);
});
it("asks nothing extra of a declaration that is simulated already", () => {
// Every pack authored before this field existed. Making it structurally
// mandatory would have invalidated them to fix a problem none of them have.
assert.deepEqual(validateDeviceDeclaration(DESK_MIC), []);
assert.deepEqual(validateDeviceDeclaration(YETI), []);
});
});
describe("reachability in the change signature", () => {
it("publishes when a device stops answering", () => {
// Without this the panel would never republish: every other reading holds
// its last value by design when a bridge goes quiet, so the signature would
// be identical forever and a dead room would look like a still one.
const reached: DeviceState = { ...initialDeviceState(YETI, 1), reachable: true };
const lost: DeviceState = { ...reached, reachable: false };
assert.notEqual(deviceStateSignature([reached]), deviceStateSignature([lost]));
});
it("distinguishes absent from present-and-true", () => {
// Absent means the concept does not apply — every simulated device. Present
// and true means somebody asked and got an answer. They are different facts.
const silent = initialDeviceState(YETI, 1);
const reached: DeviceState = { ...silent, reachable: true };
assert.notEqual(deviceStateSignature([silent]), deviceStateSignature([reached]));
});
});
+341
View File
@@ -0,0 +1,341 @@
/**
* The promotion gate, against the day it was written for.
*
* This is the test the whole fire feature is built around, and it is worth being
* blunt about what it is defending. On the day `firesFixture.ts` was captured,
* **twenty-two live incident rows fell inside the SoCal board's bounds**. Every
* single one had `acres: null`. Fifteen were nameless LA County dispatch numbers
* — `LAC-297933`, `LAC-298861`, `LAC-297051`. Nothing was burning in Los
* Angeles. Drawn ungated, that is twenty-two orange marks over a city on an
* ordinary Friday, in a frame that was checked by eye and contains no other warm
* colour at all.
*
* The same body, clipped to the California board, contains five real fires:
* Timber (7,591 ac), Alpaugh (3,600 ac), Carrizo (268 ac), Amber (10 ac) and
* GREEN (10 ac). One rule, one day, two correct answers — and if the second
* number ever moves without the first, the gate has been loosened.
*
* The other half is the de-duplication watermark. The collector upstream writes
* every row it is handed and never deletes, while the merge of CAL FIRE's and
* WFIGS's copies of one fire happens only in the list it *returns* — so a losing
* row keeps its old `lastSeen` forever. There really were two Timber Fires in
* the store, 850 m apart, at 7,591 and 6,669 acres. `GHOST_TIMBER` below is the
* loser, copied out of the store by hand, and a promotion that draws both is a
* promotion that double-counts and under-reports the same fire at once.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
FIRE_TIER_MIN_ACRES,
FIRE_TIER_PLUME_ACRES,
detectionConfidence,
emptyPromotion,
promote,
type FireBounds,
} from "../../server/fires.ts";
import { LIVE_FIRES_BODY } from "./firesFixture.ts";
import type { FireDetection, FireIncident, FiresBody } from "../../server/wire.ts";
/** The three shipped boards, from `src/cities/*.ts`. Restated; those are read-only. */
const SOCAL: FireBounds = { minLat: 33.28, maxLat: 34.36, minLng: -118.88, maxLng: -117.22 };
const CALIFORNIA: FireBounds = { minLat: 32.55, maxLat: 38.05, minLng: -123.05, maxLng: -114.0 };
const BAY_AREA: FireBounds = { minLat: 37.18, maxLat: 38.03, minLng: -122.64, maxLng: -121.75 };
/** The live Timber Fire, as the projection served it. */
const LIVE_TIMBER: FireIncident = {
id: "b7e4a30e-67ab-4964-882e-751da30b44e0",
source: "calfire",
name: "Timber Fire",
lat: 36.224857,
lon: -121.72983,
provenance: "us-gov",
county: "Monterey",
type: "WF",
url: "https://www.fire.ca.gov/incidents/2026/8/8/timber-fire/",
firstSeen: "2026-08-22T21:14:43Z",
lastSeen: "2026-08-22T22:20:06Z",
observedAt: "2026-08-22T21:30:08Z",
acres: 7591,
pctContained: 29,
};
/**
* The same fire, as WFIGS had it, left behind by a de-duplication it lost.
*
* 850 m south-east of the live row, 6,669 acres against 7,591, and a `lastSeen`
* an hour behind. It is still in the upstream table right now and it will be
* there next year.
*/
const GHOST_TIMBER: FireIncident = {
id: "{51374D2C-D96B-40F7-940B-6CB8E0A483A2}",
source: "wfigs",
name: "Timber",
lat: 36.215898858987735,
lon: -121.71752094030687,
provenance: "us-gov",
county: "Monterey",
type: "WF",
url: null,
firstSeen: "2026-08-22T21:14:43Z",
lastSeen: "2026-08-22T21:14:43Z",
observedAt: "2026-08-22T21:14:43Z",
acres: 6669,
pctContained: 29,
};
function bodyOf(incidents: FireIncident[], detections: FireDetection[] = []): FiresBody {
return {
source: "cloud1",
fetchedAt: "2026-08-22T22:22:35.806Z",
latestSeen: incidents.reduce<string | null>(
(latest, i) => (latest === null || i.lastSeen > latest ? i.lastSeen : latest),
null,
),
incidents,
detections,
detectionWindowHours: 24,
ttlSeconds: 600,
};
}
function named(fires: readonly { name: string | null }[]): string[] {
return fires.map((f) => f.name ?? "<unnamed>").sort();
}
describe("the promotion gate, on the day it was captured", () => {
it("draws nothing on the SoCal board, and knows how much it refused", () => {
const promotion = promote(LIVE_FIRES_BODY, SOCAL);
// The number this whole feature exists for.
assert.equal(promotion.drawn.length, 0);
// Not silently: twenty-two live rows are inside those bounds, and the board
// is entitled to say so rather than merely be empty.
assert.equal(promotion.suppressed, 22);
assert.equal(promotion.source, "cloud1");
});
it("draws exactly the five real fires on the California board", () => {
const promotion = promote(LIVE_FIRES_BODY, CALIFORNIA);
assert.equal(promotion.drawn.length, 5);
assert.deepEqual(named(promotion.drawn), [
"Alpaugh Fire",
"Amber Fire",
"Carrizo Fire",
"GREEN",
"Timber Fire",
]);
// Worst first, so a fixed instance buffer that overflows drops the smallest.
assert.deepEqual(
promotion.drawn.map((f) => f.acres),
[7591, 3600, 268.1, 10, 10],
);
});
it("gives a plume only to the fires big enough to have one", () => {
const drawn = promote(LIVE_FIRES_BODY, CALIFORNIA).drawn;
for (const fire of drawn) {
assert.equal(fire.tier, fire.acres >= FIRE_TIER_PLUME_ACRES ? 2 : 1);
}
// The two ten-acre fires are marks and nothing more. A 268-acre fire under a
// forty-kilometre smoke column is a lie told in a truthful-looking medium.
assert.deepEqual(
drawn.filter((f) => f.tier === 1).map((f) => f.name),
["Amber Fire", "GREEN"],
);
});
it("draws nothing on the Bay Area board and refuses nothing there either", () => {
const promotion = promote(LIVE_FIRES_BODY, BAY_AREA);
assert.equal(promotion.drawn.length, 0);
// Zero drawn AND zero suppressed is a different sentence from SoCal's zero
// drawn and twenty-two suppressed, and the caption should be able to tell
// them apart: nothing is happening here at all.
assert.equal(promotion.suppressed, 0);
});
it("names the biggest fires the board cannot show", () => {
const promotion = promote(LIVE_FIRES_BODY, SOCAL);
// Bug Fire is 93,733 acres and 94% contained, so it is off the board AND
// past the containment gate — an all-clear caption that named it would be
// reporting finished news as live.
assert.ok(!named(promotion.offBoard).includes("Bug Fire"));
assert.deepEqual(named(promotion.offBoard), ["Alpaugh Fire", "MP18 Fire", "Timber Fire"]);
});
it("reports the age of the answer, which is what makes an empty board readable", () => {
const at = Date.parse("2026-08-22T22:27:35.806Z");
const promotion = promote(LIVE_FIRES_BODY, SOCAL, at);
assert.equal(promotion.ageMs, 5 * 60_000);
assert.equal(promotion.fetchedAt, LIVE_FIRES_BODY.fetchedAt);
});
});
describe("the de-duplication watermark", () => {
it("drops every row behind the body's latest lastSeen", () => {
const promotion = promote(bodyOf([LIVE_TIMBER, GHOST_TIMBER]), CALIFORNIA);
assert.equal(promotion.drawn.length, 1);
assert.equal(promotion.drawn[0]?.acres, 7591);
assert.equal(promotion.drawn[0]?.source, "calfire");
// The ghost is a refusal like any other, counted rather than vanished.
assert.equal(promotion.suppressed, 1);
});
it("prefers the server's stated watermark over one recomputed from the rows", () => {
// A body that arrived clipped — a bounded read, a truncated array — would
// move its own watermark down to the newest row it happened to contain, and
// re-admit exactly the ghosts this filter exists to drop.
const body = bodyOf([GHOST_TIMBER]);
body.latestSeen = LIVE_TIMBER.lastSeen;
assert.equal(promote(body, CALIFORNIA).drawn.length, 0);
});
it("still filters when the server said nothing, using the rows in hand", () => {
const body = bodyOf([LIVE_TIMBER, GHOST_TIMBER]);
body.latestSeen = null;
assert.equal(promote(body, CALIFORNIA).drawn.length, 1);
});
});
describe("the four independent refusals", () => {
const at = (over: Partial<FireIncident>): FireIncident => ({ ...LIVE_TIMBER, ...over });
it("refuses a row with no acreage at all", () => {
// Not "a small fire". A dispatch record with null acreage is a radio call,
// and fifteen of them were sitting over Los Angeles on the captured day.
assert.equal(promote(bodyOf([at({ acres: null })]), CALIFORNIA).drawn.length, 0);
});
it("refuses a row under the acreage floor and admits one exactly on it", () => {
assert.equal(
promote(bodyOf([at({ acres: FIRE_TIER_MIN_ACRES - 0.1 })]), CALIFORNIA).drawn.length,
0,
);
assert.equal(
promote(bodyOf([at({ acres: FIRE_TIER_MIN_ACRES })]), CALIFORNIA).drawn.length,
1,
);
});
it("refuses a fire that is 80% contained, and admits one at 79", () => {
assert.equal(promote(bodyOf([at({ pctContained: 80 })]), CALIFORNIA).drawn.length, 0);
assert.equal(promote(bodyOf([at({ pctContained: 79 })]), CALIFORNIA).drawn.length, 1);
// Null containment is "the agency has not said", which is not 100%.
assert.equal(promote(bodyOf([at({ pctContained: null })]), CALIFORNIA).drawn.length, 1);
});
it("never draws a prescribed burn as a wildfire", () => {
// Deliberate, scheduled, frequently adjacent to real fire ground, and
// identical to a wildfire under any distance filter.
assert.equal(promote(bodyOf([at({ type: "RX" })]), CALIFORNIA).drawn.length, 0);
assert.equal(promote(bodyOf([at({ type: "rx" })]), CALIFORNIA).drawn.length, 0);
assert.equal(promote(bodyOf([at({ type: "WF" })]), CALIFORNIA).drawn.length, 1);
// An unstated type is not a prescribed burn; refusing it would silently drop
// an agency that stopped populating the column.
assert.equal(promote(bodyOf([at({ type: "" })]), CALIFORNIA).drawn.length, 1);
});
it("refuses a name that describes an exercise rather than an event", () => {
for (const name of ["TRAINING FIRE", "County Drill", "Exercise Ridge", "DO NOT USE"]) {
assert.equal(promote(bodyOf([at({ name })]), CALIFORNIA).drawn.length, 0, name);
}
// Word-boundaried, so a real place is not caught by a substring.
assert.equal(promote(bodyOf([at({ name: "Drilling Creek" })]), CALIFORNIA).drawn.length, 1);
});
it("clips to the board and counts a refusal only where it can be seen", () => {
// Timber is in Monterey — on the California board, nowhere near SoCal. It is
// refused for SoCal by geography, not by the ladder, and must not inflate
// the number a SoCal caption reports.
const promotion = promote(bodyOf([at({ acres: 5 })]), SOCAL);
assert.equal(promotion.drawn.length, 0);
assert.equal(promotion.suppressed, 0);
assert.equal(promotion.offBoard.length, 0);
});
});
describe("hot pixels are evidence, not incidents", () => {
it("splits the known industrial furniture out of the drawn set", () => {
const promotion = promote(LIVE_FIRES_BODY, SOCAL);
// Every drawn pixel is one nothing has seen before at low power.
assert.ok(promotion.detections.every((d) => d.persistent === false));
// And the ones that were dropped are counted, not vanished — a layer that
// silently swallowed them could not say how much of a board's thermal
// activity is a flare stack.
assert.ok(promotion.persistentDetections > 0);
assert.equal(promotion.detectionWindowHours, 24);
});
it("keeps the permanent heat source near the upstream operator's house out", () => {
// Verified in the live store on two consecutive days: a VIIRS pixel at
// ~33.794 / -117.474 burning at FRP ~1.0 with no matching incident. It is an
// industrial site on the I-15 corridor and it will be there tomorrow.
// Drawing it is how a map puts a fire on somebody's house.
const near = (d: FireDetection) =>
Math.abs(d.lat - 33.794) < 0.02 && Math.abs(d.lon - -117.474) < 0.02;
const inFixture = LIVE_FIRES_BODY.detections.filter(near);
assert.ok(inFixture.length > 0, "the fixture should contain the industrial source");
assert.ok(inFixture.every((d) => d.persistent));
assert.equal(promote(LIVE_FIRES_BODY, SOCAL).detections.filter(near).length, 0);
});
it("reads MODIS and VIIRS confidence on their own scales", () => {
const at = (sat: string, confidence: string | null): FireDetection => ({
sat,
acquiredAt: "2026-08-22T17:37:00Z",
lat: 36.26,
lon: -121.71,
frp: 112.9,
confidence,
persistent: false,
persistentDays: 0,
});
// MODIS: an integer 0-100 in the same column VIIRS puts a word in.
assert.equal(detectionConfidence(at("MODIS", "94")), 0.94);
assert.equal(detectionConfidence(at("MODIS", "0")), 0);
// VIIRS: three bands. `low` is the bottom band of a published detection, not
// an absence of confidence, so it is not zero.
assert.equal(detectionConfidence(at("VIIRS-NOAA20", "nominal")), 0.5);
assert.ok((detectionConfidence(at("VIIRS-SNPP", "low")) ?? 0) > 0);
assert.ok(
(detectionConfidence(at("VIIRS-SNPP", "high")) ?? 0) >
(detectionConfidence(at("VIIRS-SNPP", "nominal")) ?? 0),
);
// The failure this branch exists to prevent: `Number("nominal")` is NaN, and
// NaN reaches a shader as a hole.
assert.equal(detectionConfidence(at("VIIRS-NOAA20", "27")), null);
assert.equal(detectionConfidence(at("MODIS", null)), null);
});
});
describe("promote is total", () => {
it("answers with an empty promotion rather than throwing", () => {
// The consumer is a render loop. A body from a server one version behind
// this one, or no body at all, must be a quiet board and never an exception.
for (const bad of [null, undefined, {}, { incidents: "yes" }, { incidents: [null, 7] }]) {
const promotion = promote(bad as unknown as FiresBody, CALIFORNIA);
assert.equal(promotion.drawn.length, 0);
assert.equal(promotion.detections.length, 0);
}
});
it("says it has never been fetched, rather than claiming it just was", () => {
const empty = emptyPromotion();
assert.equal(empty.ageMs, null);
assert.equal(empty.source, "none");
assert.equal(Date.parse(empty.fetchedAt), 0);
});
it("carries no home-relative field through from any body it is handed", () => {
// Belt and braces over a structural guarantee. The four columns never leave
// cloud-1 — but a fixture is exactly the artefact that would immortalise a
// leak, so this asserts on the shape that actually reaches a renderer.
const forbidden = ["distance_km", "bearing_deg", "threat", "distanceKm", "bearingDeg"];
const wire = JSON.stringify(promote(LIVE_FIRES_BODY, CALIFORNIA));
for (const key of forbidden) assert.ok(!wire.includes(key), key);
assert.ok(!JSON.stringify(LIVE_FIRES_BODY).includes("Norco"));
});
});
+291
View File
@@ -0,0 +1,291 @@
/**
* A real `FiresBody`, captured from the live projection.
*
* Captured 2026-08-22 from `GET /api/v1/fires` on a tera-api pointed at cloud-1's
* `/api/fires` projection — which is to say: through the whole wire, not out of
* the database. It is committed verbatim, rows and all, because the tests it
* backs are claims about *this data on this day* and a hand-tidied fixture would
* quietly stop being one.
*
* What makes the day worth keeping is that it is an ordinary one. Twenty-two of
* these rows fall inside the SoCal board's bounds, every single one with
* `acres: null`, and fifteen of them are nameless LA County dispatch numbers —
* `LAC-297933`, `LAC-298861`. Nothing was burning in Los Angeles. The same body,
* clipped to the California board, contains five real fires. That pair is the
* whole test: one rule, one day, two correct answers.
*
* The ghost rows the collector never deletes are **not** in here, because the
* endpoint already filtered them on `last_seen = max(last_seen)` — twenty-two of
* them on the day this was taken. `fires.test.ts` reconstructs one by hand from
* the store (the second Timber Fire, 850 m from the live one at 6,669 acres
* against 7,591) so the client-side half of that filter is exercised too. Both
* halves must hold: the endpoint's is the cheap one and the client's is the one
* that survives an endpoint being replaced.
*
* Nothing in this file is home-relative. The four columns that are —
* `observations.distance_km`, `bearing_deg`, `threat` and
* `detections.distance_km` — never left cloud-1, which is the entire design of
* the feed. A fixture is exactly the artefact that would immortalise such a leak
* in a repo, so it is worth saying that this one was checked.
*/
import type { FiresBody } from "../../server/wire.ts";
export const LIVE_FIRES_BODY: FiresBody = {
source: "cloud1",
fetchedAt: "2026-08-22T22:22:35.806Z",
latestSeen: "2026-08-22T22:20:06Z",
ttlSeconds: 600,
detectionWindowHours: 24,
attribution: ["Incidents from CAL FIRE and NIFC/WFIGS (US Government work, public domain)", "Satellite hot pixels from NASA FIRMS (MODIS, VIIRS)"],
incidents: [
{"id": "ca6b8a6a-12e9-4e87-8f9b-5cb01dd5a25f", "source": "calfire", "name": "Bug Fire", "lat": 39.727395, "lon": -120.0372941, "provenance": "us-gov", "county": "Lassen, Sierra", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/8/bug-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 93733, "pctContained": 94},
{"id": "{6AD80DE9-CD41-40D3-8939-A86CCF775981}", "source": "wfigs", "name": "ELEPHANT", "lat": 39.71083827653158, "lon": -120.19318010997422, "provenance": "us-gov", "county": "Plumas", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 13930, "pctContained": 100},
{"id": "{A7C819CA-FCE9-4E9E-9A7F-C89F2160F102}", "source": "wfigs", "name": "GANN", "lat": 38.10793309380294, "lon": -120.7668100002191, "provenance": "us-gov", "county": "Calaveras", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 10339, "pctContained": 100},
{"id": "a78d23dd-66cb-449d-b955-9d84efedbd51", "source": "calfire", "name": "MP18 Fire", "lat": 41.125604, "lon": -123.684619, "provenance": "us-gov", "county": "Humboldt", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/7/mp18-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 7610, "pctContained": 74},
{"id": "b7e4a30e-67ab-4964-882e-751da30b44e0", "source": "calfire", "name": "Timber Fire", "lat": 36.224857, "lon": -121.72983, "provenance": "us-gov", "county": "Monterey", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/8/timber-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 7591, "pctContained": 29},
{"id": "30df76e5-e4f5-4b4f-b8ff-50f98d6ce14a", "source": "calfire", "name": "Alpaugh Fire", "lat": 35.86609, "lon": -119.487314, "provenance": "us-gov", "county": "Tulare", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/19/alpaugh-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 3600, "pctContained": 30},
{"id": "{BAF0D8B7-48FA-43F7-A249-349644693C63}", "source": "wfigs", "name": "RIDGE", "lat": 34.79108490180015, "lon": -118.83000234743656, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 1134, "pctContained": 100},
{"id": "{69DD692F-9D85-4D69-91E9-AB82AEB5AEB5}", "source": "wfigs", "name": "3-1 PIT", "lat": 40.948838288544295, "lon": -121.26851345578423, "provenance": "us-gov", "county": "Lassen", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 1055, "pctContained": 98},
{"id": "{E53D7A7B-30F7-40CE-B772-2AD3253A9B27}", "source": "wfigs", "name": "BUZZARD", "lat": 34.89972710769351, "lon": -118.92279017234667, "provenance": "us-gov", "county": "Kern", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 902, "pctContained": 100},
{"id": "{5AC0B49D-3F64-4344-826A-5DBB1925E757}", "source": "wfigs", "name": "FELIZ", "lat": 38.992464984350896, "lon": -123.16115347357264, "provenance": "us-gov", "county": "Mendocino", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 872, "pctContained": 100},
{"id": "{20BACABE-DCC0-4B41-A0DA-997AED50D8C4}", "source": "wfigs", "name": "LOOMIS", "lat": 40.96583829952783, "lon": -121.15468044031932, "provenance": "us-gov", "county": "Lassen", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 656, "pctContained": 99},
{"id": "52874353-e4ed-446e-ba8b-71cce3adbdbd", "source": "calfire", "name": "Carrizo Fire", "lat": 35.05302, "lon": -119.91036, "provenance": "us-gov", "county": "San Luis Obispo", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/22/carrizo-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 268.1, "pctContained": null},
{"id": "00dc6f70-642d-463b-b6c1-89851187392c", "source": "calfire", "name": "Holser Fire", "lat": 34.445673, "lon": -118.737863, "provenance": "us-gov", "county": "Ventura", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/8/holser-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 167, "pctContained": 100},
{"id": "{4C9D1FD7-7131-412A-9995-CA7789782346}", "source": "wfigs", "name": "CHUTE", "lat": 39.35154917056113, "lon": -121.15071021515419, "provenance": "us-gov", "county": "Yuba", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 162, "pctContained": 97},
{"id": "f33fd106-2a94-4757-b0ce-beee36a6c967", "source": "calfire", "name": "Sorrento Fire", "lat": 32.91867, "lon": -117.1980091, "provenance": "us-gov", "county": "San Diego", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/16/sorrento-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 138, "pctContained": 91},
{"id": "{147D8C2E-89E9-46C0-B7BB-E30A419816AC}", "source": "wfigs", "name": "WOODS", "lat": 38.40763818886618, "lon": -119.84462988815334, "provenance": "us-gov", "county": "Alpine", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 65.2, "pctContained": 100},
{"id": "72a86965-b906-4da4-8f34-0dae806bd93b", "source": "calfire", "name": "Drum Fire", "lat": 34.721333, "lon": -120.277089, "provenance": "us-gov", "county": "Santa Barbara", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/19/drum-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 40, "pctContained": 85},
{"id": "7ceb6ea0-3cb7-45cb-87ec-2b879ebc6c3a", "source": "calfire", "name": "Ross Fire", "lat": 38.112448, "lon": -120.499796, "provenance": "us-gov", "county": "Calaveras", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/21/ross-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 19.5, "pctContained": 60},
{"id": "5e0f3014-4e8e-4505-94c1-c9afc078989e", "source": "calfire", "name": "Grant Fire", "lat": 38.559162, "lon": -121.187226, "provenance": "us-gov", "county": "Sacramento", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/21/grant-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 18, "pctContained": 95},
{"id": "efbd6a3f-a7b0-4566-9765-e6f93fe299a3", "source": "calfire", "name": "Clover Fire", "lat": 39.13032, "lon": -121.02902, "provenance": "us-gov", "county": "Nevada", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/21/clover-fire/", "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 13.6, "pctContained": 20},
{"id": "aa6ac247-80dd-4c48-b71f-a89f3d7c5bd9", "source": "calfire", "name": "Amber Fire", "lat": 34.1851, "lon": -117.1421, "provenance": "us-gov", "county": "San Bernardino", "type": "WF", "url": "https://www.fire.ca.gov/incidents/2026/8/22/amber-fire/", "firstSeen": "2026-08-22T20:10:06Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 10, "pctContained": null},
{"id": "{406734A4-BE3E-48FB-B5FD-227C8318A12B}", "source": "wfigs", "name": "GREEN", "lat": 34.09944893026948, "lon": -117.0233449828136, "provenance": "us-gov", "county": "San Bernardino", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 10, "pctContained": null},
{"id": "{9F46CEBE-8BF5-414F-BB1C-14AF3A55C52A}", "source": "wfigs", "name": "MURIETTA", "lat": 38.92172222601169, "lon": -119.96247997043604, "provenance": "us-gov", "county": "El Dorado", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:40:06Z", "acres": 0.42, "pctContained": null},
{"id": "{1C53C1FE-E033-403A-8F7A-DC47308A65C8}", "source": "wfigs", "name": "MTZ/RRU/JERRY", "lat": 33.937671905985674, "lon": -117.12384498713448, "provenance": "us-gov", "county": "Riverside", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{46FFBC57-CD61-4ACE-B1E6-3ED9CA748BB2}", "source": "wfigs", "name": "CERCIS", "lat": 38.67381812814398, "lon": -120.96316409954133, "provenance": "us-gov", "county": "El Dorado", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{49D57356-C7DC-400E-800C-748A984585B3}", "source": "wfigs", "name": "Auxiliary", "lat": 35.64458801421802, "lon": -118.46197936400596, "provenance": "us-gov", "county": "Kern", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{6497FA43-184A-4E2E-91F0-A176A9374C00}", "source": "wfigs", "name": "PILOT", "lat": 37.41982210734081, "lon": -119.71026275218291, "provenance": "us-gov", "county": "Mariposa", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{6A197013-C0D9-46B4-A819-F3EA203E6B53}", "source": "wfigs", "name": "HILL", "lat": 34.0669492166621, "lon": -118.96556790829297, "provenance": "us-gov", "county": "Ventura", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{927A24C8-B446-425E-B05E-09921C1F1AB5}", "source": "wfigs", "name": "CRISTO", "lat": 34.34111689550595, "lon": -118.1098921886098, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{E3743DE2-9BE7-4326-B9D4-EC096214029A}", "source": "wfigs", "name": "WHEELER", "lat": 37.42650518160848, "lon": -118.66334558004729, "provenance": "us-gov", "county": "Inyo", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.1, "pctContained": null},
{"id": "{285866F6-E2DB-455A-9A9F-620302FAE83A}", "source": "wfigs", "name": "MTZ/BDC/82B", "lat": 34.791699003601614, "lon": -117.11910605771482, "provenance": "us-gov", "county": "San Bernardino", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.01, "pctContained": null},
{"id": "{4AB4D4A3-9CD6-409D-893E-DB6D56B76568}", "source": "wfigs", "name": "MTZ/SDU/RAINBOW 4", "lat": 33.43083784631518, "lon": -117.13917895005865, "provenance": "us-gov", "county": "San Diego", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.01, "pctContained": null},
{"id": "{9DB428E9-BEE4-4517-922C-BE84BABD764A}", "source": "wfigs", "name": "MTZ/BDC/45A", "lat": 35.749051092479824, "lon": -117.39711819280754, "provenance": "us-gov", "county": "San Bernardino", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.01, "pctContained": null},
{"id": "{B88A5FC8-E82D-4EB5-A01A-2FB700DA066E}", "source": "wfigs", "name": "MEVER", "lat": 35.2215741130807, "lon": -116.10805891793002, "provenance": "us-gov", "county": "San Bernardino", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.01, "pctContained": null},
{"id": "{DE93EFFD-8ED9-496D-A4A2-75ADA69DF6D3}", "source": "wfigs", "name": "WILDFIRE TRAINING", "lat": 37.347195104624284, "lon": -119.65142573458236, "provenance": "us-gov", "county": "Madera", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": 0.01, "pctContained": null},
{"id": "{01F1F104-95DE-4EFC-A721-534F890866CB}", "source": "wfigs", "name": "Convoy", "lat": 32.837489775778494, "lon": -117.1521536082291, "provenance": "us-gov", "county": "San Diego", "type": "WF", "url": null, "firstSeen": "2026-08-22T22:00:12Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T22:00:12Z", "acres": null, "pctContained": null},
{"id": "{020E3272-B789-46E9-BC36-09FF127A0510}", "source": "wfigs", "name": "MESA", "lat": 34.30653487640972, "lon": -118.37525223026576, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{05212731-7BFD-4CEF-A9A8-20F063FEAC5C}", "source": "wfigs", "name": "LAC-297581", "lat": 33.929584836806754, "lon": -118.34391219402642, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{0CE642B7-7433-4934-8804-5BBEDD81B4BA}", "source": "wfigs", "name": "LAC-297948", "lat": 33.98131483884186, "lon": -118.40912220911244, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{1293B230-F8B7-499B-8732-EDC069E935CD}", "source": "wfigs", "name": "LAC-298843", "lat": 34.69007494727988, "lon": -117.88067217956613, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{1B37ADC5-CBBF-44E0-97A4-D6A318C0E484}", "source": "wfigs", "name": "ROVER", "lat": 34.493474902326305, "lon": -118.27991223007649, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{2CE690BA-EA60-4E53-88AF-0455D19D67E3}", "source": "wfigs", "name": "LAC-297151", "lat": 33.852714831851586, "lon": -118.2804121772132, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{3C146CBA-405E-4F06-A6F1-21AD1CD3303E}", "source": "wfigs", "name": "LAC-295619", "lat": 34.718774936726156, "lon": -118.11229222125154, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{432AC518-EFA7-4B33-9894-C252887D6D2E}", "source": "wfigs", "name": "GADDY", "lat": 39.00190501103515, "lon": -122.82892042584398, "provenance": "us-gov", "county": "Lake", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{5AD2E59F-4C9C-4B6D-B18A-E1962BDB1897}", "source": "wfigs", "name": "LAC-295389", "lat": 33.93084484621299, "lon": -118.17871216643361, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{6BF5FAD9-F956-49BB-B998-2B7B534A8579}", "source": "wfigs", "name": "LAC-295363", "lat": 34.01018485983273, "lon": -118.09331215849103, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{6CB773B2-0304-4191-B2E1-0D77C80BE725}", "source": "wfigs", "name": "LAC-298349", "lat": 33.928254839364016, "lon": -118.29571218585188, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{762A3996-EDFC-4D43-94CF-95760038EA91}", "source": "wfigs", "name": "LAC-294910", "lat": 33.814704830321936, "lon": -118.23207216607001, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{7A32142C-F5F0-4D81-8329-EACF43C35C0A}", "source": "wfigs", "name": "EMMA", "lat": 34.50622491828981, "lon": -118.03027218912521, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{88B7C90B-BF20-4773-9F4B-47000559082F}", "source": "wfigs", "name": "Nelson", "lat": 41.075282889162885, "lon": -123.69918054284415, "provenance": "us-gov", "county": "Humboldt", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{8D1138B3-1FF6-442B-89B8-794C5FCDD300}", "source": "wfigs", "name": "LAC-296494", "lat": 34.579444921274494, "lon": -118.1164622099297, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{8F2D0FCF-6394-408A-962D-BB0E113F4A66}", "source": "wfigs", "name": "LAC-296028", "lat": 34.575754926517405, "lon": -118.0201621933547, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{8FB753C6-3866-4716-9EA1-ED5F2F5209F3}", "source": "wfigs", "name": "ALPAUGH", "lat": 36.297505032326384, "lon": -119.21556255273067, "provenance": "us-gov", "county": "Tulare", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{93C238D6-3149-4790-9F1A-669103ACB752}", "source": "wfigs", "name": "LAC-298765", "lat": 34.378674873260664, "lon": -118.5652722680305, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{A2BA63C2-D8C5-4567-A49A-080D828DB65A}", "source": "wfigs", "name": "LAC-297051", "lat": 33.87306484517127, "lon": -118.0822921455627, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{A9D21A74-A235-4083-8F2E-8E25AFFB5480}", "source": "wfigs", "name": "LAC-297330", "lat": 33.97985485021245, "lon": -118.20433217470192, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{B5186422-86F0-4DA4-A5FF-148876D91A8A}", "source": "wfigs", "name": "CUTOFF", "lat": 34.78193791490004, "lon": -118.59422930752102, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{B7E97CB9-9CC6-47A8-B789-19EDD3126FE9}", "source": "wfigs", "name": "LAC-298861", "lat": 34.05186488475675, "lon": -117.73405210105491, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:10:06Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{C3A3DCD7-DD11-46D9-A204-2F8C9CA37280}", "source": "wfigs", "name": "LAC-298044", "lat": 33.95337485635306, "lon": -118.04267214535032, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{C547E30A-A5D1-4940-A47E-027359EC4C61}", "source": "wfigs", "name": "LAC-296084", "lat": 34.68943493214993, "lon": -118.1356222226405, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{C7479B32-7A9E-4DDA-8C11-EA52367D23B7}", "source": "wfigs", "name": "LAC-298013", "lat": 34.6898849367248, "lon": -118.05904220975657, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{C7DD2DD1-3F13-4DA6-8E22-917B35D57311}", "source": "wfigs", "name": "HUGO", "lat": 34.15985489981487, "lon": -117.6825921011304, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{C7EFE75A-EDE7-4910-A1EC-9DCEE4B4FFDA}", "source": "wfigs", "name": "LAC-297484", "lat": 33.98233485661763, "lon": -118.09539215658083, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{C8E5DD4C-A541-4936-AE78-0BAF01271E2E}", "source": "wfigs", "name": "BRIGGS", "lat": 33.966634866173685, "lon": -117.89413212131498, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T15:10:05Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{CB586867-D60D-4257-AA86-3C34D7DFB54D}", "source": "wfigs", "name": "LAC-298474", "lat": 34.55010492809983, "lon": -117.94498217843741, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{CD656240-8F4C-4AB3-B24B-000AD1532F70}", "source": "wfigs", "name": "ARROYO", "lat": 33.55500199628092, "lon": -117.77425999836478, "provenance": "us-gov", "county": "Orange", "type": "WF", "url": null, "firstSeen": "2026-08-22T08:57:56Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{D5653F1A-2600-4862-A522-8B45AE52BE8B}", "source": "wfigs", "name": "LAC-294851", "lat": 34.56558492235531, "lon": -118.07215220126653, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{D68047C5-AA8C-4352-B97B-4A22FEAE4CA8}", "source": "wfigs", "name": "LAC-295003", "lat": 34.10682488159199, "lon": -117.89892213353791, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{D7428E78-C21A-4A24-BB37-0BAA90D48293}", "source": "wfigs", "name": "LAC-296049", "lat": 34.660334928265044, "lon": -118.1477222221619, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{E7B700CF-90A2-458E-B489-DB19CE275CD6}", "source": "wfigs", "name": "LAC-297933", "lat": 34.02620488102593, "lon": -117.74901210151245, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T08:57:56Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{F678F1BE-3E3C-4FD9-81E9-07DC22DAC676}", "source": "wfigs", "name": "LAC-297529", "lat": 34.10959487718198, "lon": -117.98212214784415, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
{"id": "{FD0E23A3-C9D5-4A03-982A-ADC4C708A168}", "source": "wfigs", "name": "Mission", "lat": 33.38725983600112, "lon": -117.23655996352392, "provenance": "us-gov", "county": "San Diego", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:50:05Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:50:05Z", "acres": null, "pctContained": null},
{"id": "{FD2153CB-702B-4FDE-8D29-2CC7927604DD}", "source": "wfigs", "name": "LAC-297317", "lat": 33.927174842421074, "lon": -118.23907217627114, "provenance": "us-gov", "county": "Los Angeles", "type": "WF", "url": null, "firstSeen": "2026-08-22T21:14:43Z", "lastSeen": "2026-08-22T22:20:06Z", "observedAt": "2026-08-22T21:30:08Z", "acres": null, "pctContained": null},
],
detections: [
{"sat": "MODIS", "acquiredAt": "2026-08-22T17:37:00Z", "lat": 36.26633, "lon": -121.71249, "frp": 112.9, "confidence": "94", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T17:37:00Z", "lat": 36.27402, "lon": -121.69181, "frp": 30.99, "confidence": "69", "persistent": false, "persistentDays": 1},
{"sat": "MODIS", "acquiredAt": "2026-08-22T17:37:00Z", "lat": 36.27077, "lon": -121.67509, "frp": 17.59, "confidence": "30", "persistent": false, "persistentDays": 1},
{"sat": "MODIS", "acquiredAt": "2026-08-22T17:37:00Z", "lat": 36.23332, "lon": -121.66344, "frp": 20.83, "confidence": "45", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T12:25:00Z", "lat": 36.2668, "lon": -121.69413, "frp": 19.72, "confidence": "92", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T12:25:00Z", "lat": 36.25437, "lon": -121.68279, "frp": 7.75, "confidence": "26", "persistent": false, "persistentDays": 1},
{"sat": "MODIS", "acquiredAt": "2026-08-22T12:25:00Z", "lat": 36.22919, "lon": -121.66584, "frp": 18.08, "confidence": "88", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 32.53891, "lon": -114.93528, "frp": 7.97, "confidence": "nominal", "persistent": false, "persistentDays": 0},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 32.53988, "lon": -114.93908, "frp": 7.97, "confidence": "nominal", "persistent": false, "persistentDays": 0},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 32.58058, "lon": -115.10069, "frp": 2.61, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.81883, "lon": -118.75208, "frp": 0.42, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 32.49768, "lon": -116.83429, "frp": 6.84, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.42997, "lon": -118.64497, "frp": 0.74, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.33623, "lon": -118.52169, "frp": 0.87, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.33519, "lon": -118.51683, "frp": 0.87, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.60781, "lon": -117.3388, "frp": 1.3, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.35249, "lon": -116.85224, "frp": 1.44, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.8533, "lon": -118.33225, "frp": 1.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.15509, "lon": -118.19353, "frp": 1.08, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.82118, "lon": -118.24605, "frp": 0.85, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 34.03518, "lon": -117.89434, "frp": 1.14, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.49261, "lon": -117.61882, "frp": 0.82, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.49341, "lon": -117.61557, "frp": 0.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.613, "lon": -117.82278, "frp": 0.67, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.71735, "lon": -117.71153, "frp": 0.56, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.7923, "lon": -117.4751, "frp": 0.89, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:15:00Z", "lat": 33.79419, "lon": -117.47487, "frp": 1, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 38.15934, "lon": -122.56444, "frp": 0.42, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 37.94928, "lon": -122.39743, "frp": 1.44, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 38.01608, "lon": -122.11123, "frp": 1.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 38.00313, "lon": -121.93546, "frp": 0.53, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 37.75483, "lon": -121.66117, "frp": 0.48, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 37.45663, "lon": -121.93258, "frp": 1.15, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 37.88346, "lon": -121.18499, "frp": 0.97, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 37.2137, "lon": -121.90205, "frp": 0.33, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 37.18447, "lon": -121.6804, "frp": 0.52, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.71415, "lon": -121.76798, "frp": 0.35, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.2627, "lon": -121.70452, "frp": 3.83, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25572, "lon": -121.70591, "frp": 2.75, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.26178, "lon": -121.69952, "frp": 3.83, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.2583, "lon": -121.70029, "frp": 3.83, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.24885, "lon": -121.70785, "frp": 1.09, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.26436, "lon": -121.69392, "frp": 7.8, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25483, "lon": -121.70107, "frp": 2.75, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.26088, "lon": -121.69467, "frp": 1.84, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.2574, "lon": -121.69542, "frp": 1.84, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.24799, "lon": -121.70325, "frp": 1.09, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.26353, "lon": -121.68942, "frp": 7.8, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25396, "lon": -121.69633, "frp": 1.59, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25055, "lon": -121.69751, "frp": 1.59, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.2627, "lon": -121.68496, "frp": 1.42, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25311, "lon": -121.6918, "frp": 1.59, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25922, "lon": -121.68567, "frp": 1.21, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.2497, "lon": -121.69289, "frp": 1.59, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25573, "lon": -121.6864, "frp": 1.21, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.24631, "lon": -121.69412, "frp": 1.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.26188, "lon": -121.68047, "frp": 1.42, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25228, "lon": -121.68726, "frp": 4.61, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25839, "lon": -121.68121, "frp": 1.21, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.24884, "lon": -121.68823, "frp": 4.61, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25491, "lon": -121.68195, "frp": 1.21, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25142, "lon": -121.68263, "frp": 4.61, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.25404, "lon": -121.67724, "frp": 1.69, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.23105, "lon": -121.68983, "frp": 1.31, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.23013, "lon": -121.68486, "frp": 2.14, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.23254, "lon": -121.67838, "frp": 2.53, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.23502, "lon": -121.67224, "frp": 8.95, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.23161, "lon": -121.6734, "frp": 8.95, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22823, "lon": -121.67467, "frp": 2.25, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22485, "lon": -121.67598, "frp": 2.25, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.23071, "lon": -121.66855, "frp": 8.95, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.21809, "lon": -121.67857, "frp": 2.33, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22731, "lon": -121.66972, "frp": 2.25, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22393, "lon": -121.67102, "frp": 2.25, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22057, "lon": -121.67238, "frp": 2.33, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.2172, "lon": -121.67374, "frp": 2.33, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22642, "lon": -121.66496, "frp": 1.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.22303, "lon": -121.66621, "frp": 1.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.21966, "lon": -121.66756, "frp": 3.48, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T10:13:00Z", "lat": 36.35773, "lon": -114.91074, "frp": 4.1, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 38.00364, "lon": -121.93493, "frp": 0.52, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 37.75442, "lon": -121.65891, "frp": 0.23, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 37.45468, "lon": -121.93169, "frp": 0.96, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 37.88466, "lon": -121.18776, "frp": 0.57, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.24697, "lon": -121.71494, "frp": 1.5, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.26328, "lon": -121.70103, "frp": 4.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25979, "lon": -121.70197, "frp": 4.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25629, "lon": -121.70299, "frp": 2.65, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.26268, "lon": -121.69639, "frp": 4.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25917, "lon": -121.69728, "frp": 4.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.24922, "lon": -121.70427, "frp": 1.04, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.2655, "lon": -121.69025, "frp": 2.68, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25567, "lon": -121.69818, "frp": 2.65, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.26202, "lon": -121.69129, "frp": 2.26, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.24567, "lon": -121.70479, "frp": 1.04, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25213, "lon": -121.69882, "frp": 2.65, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.26134, "lon": -121.68605, "frp": 2.26, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25148, "lon": -121.69376, "frp": 3.13, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.24791, "lon": -121.69415, "frp": 2.68, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25435, "lon": -121.68795, "frp": 3.13, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.26066, "lon": -121.68079, "frp": 1.59, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25083, "lon": -121.68868, "frp": 3.13, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25717, "lon": -121.68177, "frp": 1.59, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.24728, "lon": -121.68927, "frp": 2.68, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25367, "lon": -121.68273, "frp": 2.43, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.24666, "lon": -121.68446, "frp": 0.73, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.25303, "lon": -121.67773, "frp": 2.43, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.22883, "lon": -121.68633, "frp": 2.61, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.23181, "lon": -121.68143, "frp": 2.61, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.23122, "lon": -121.67691, "frp": 6.37, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.23417, "lon": -121.67176, "frp": 5.69, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.23062, "lon": -121.67229, "frp": 6.37, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.22706, "lon": -121.67271, "frp": 6.37, "confidence": "nominal", "persistent": false, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.2235, "lon": -121.67306, "frp": 1.86, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.23, "lon": -121.66749, "frp": 4.04, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.21698, "lon": -121.67848, "frp": 1.86, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.22646, "lon": -121.66796, "frp": 4.04, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.21993, "lon": -121.67339, "frp": 1.86, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.22289, "lon": -121.66832, "frp": 3.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.21636, "lon": -121.67368, "frp": 1.86, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.21932, "lon": -121.66863, "frp": 3.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.22227, "lon": -121.66352, "frp": 3.55, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.98374, "lon": -120.27769, "frp": 1.77, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.98346, "lon": -120.27526, "frp": 1.74, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.69505, "lon": -120.57987, "frp": 0.46, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 36.65004, "lon": -120.58467, "frp": 0.6, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.54894, "lon": -114.31532, "frp": 3.65, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.54527, "lon": -114.31632, "frp": 2.83, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.53598, "lon": -114.93624, "frp": 8.14, "confidence": "nominal", "persistent": false, "persistentDays": 0},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.53967, "lon": -114.93523, "frp": 21.28, "confidence": "nominal", "persistent": false, "persistentDays": 0},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.57935, "lon": -115.09926, "frp": 2.77, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.58301, "lon": -115.09827, "frp": 2.77, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 35.1221, "lon": -118.3668, "frp": 0.67, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.81962, "lon": -118.74895, "frp": 0.74, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.49405, "lon": -116.83299, "frp": 3.13, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.49485, "lon": -116.83748, "frp": 4.57, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.4975, "lon": -116.83227, "frp": 3.13, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.29143, "lon": -118.8039, "frp": 0.23, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.42941, "lon": -118.64447, "frp": 0.66, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.85674, "lon": -117.0289, "frp": 0.74, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 32.85639, "lon": -117.14725, "frp": 0.92, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.3344, "lon": -118.5206, "frp": 1.31, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.62182, "lon": -117.09929, "frp": 2.37, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.24366, "lon": -118.38096, "frp": 0.35, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.60677, "lon": -117.33517, "frp": 1.69, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.35355, "lon": -116.85136, "frp": 0.88, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.85204, "lon": -118.33392, "frp": 1.08, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.15232, "lon": -118.19386, "frp": 1.23, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.81985, "lon": -118.24242, "frp": 1.09, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.0358, "lon": -118.10721, "frp": 0.65, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.11464, "lon": -117.9248, "frp": 0.6, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.87611, "lon": -116.99934, "frp": 0.48, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.03679, "lon": -117.89153, "frp": 1.03, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 34.14304, "lon": -117.42785, "frp": 0.62, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.49009, "lon": -117.61714, "frp": 0.53, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.49343, "lon": -117.61636, "frp": 0.53, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.61327, "lon": -117.82159, "frp": 0.72, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.71708, "lon": -117.71082, "frp": 0.77, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-SNPP", "acquiredAt": "2026-08-22T09:56:00Z", "lat": 33.79279, "lon": -117.47366, "frp": 1.13, "confidence": "nominal", "persistent": true, "persistentDays": 2},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T08:35:00Z", "lat": 34.54817, "lon": -114.32118, "frp": 11.34, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T08:35:00Z", "lat": 34.54764, "lon": -114.32278, "frp": 7.42, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T08:35:00Z", "lat": 32.58179, "lon": -115.10446, "frp": 4.39, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T08:35:00Z", "lat": 32.42354, "lon": -116.92715, "frp": 0.67, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T08:35:00Z", "lat": 32.49521, "lon": -116.8389, "frp": 7.53, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "VIIRS-NOAA20", "acquiredAt": "2026-08-22T08:35:00Z", "lat": 32.50074, "lon": -116.83313, "frp": 6.89, "confidence": "nominal", "persistent": false, "persistentDays": 1},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.25913, "lon": -121.69695, "frp": 90.2, "confidence": "100", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.24763, "lon": -121.69332, "frp": 65.46, "confidence": "100", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.25262, "lon": -121.68853, "frp": 100.42, "confidence": "100", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.2637, "lon": -121.67416, "frp": 53.89, "confidence": "100", "persistent": false, "persistentDays": 1},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.23001, "lon": -121.67924, "frp": 34.43, "confidence": "96", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.21877, "lon": -121.67432, "frp": 72.74, "confidence": "100", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-22T04:00:00Z", "lat": 36.22232, "lon": -121.65631, "frp": 15.33, "confidence": "30", "persistent": false, "persistentDays": 2},
{"sat": "MODIS", "acquiredAt": "2026-08-21T22:46:00Z", "lat": 32.42723, "lon": -115.25581, "frp": 11.32, "confidence": "0", "persistent": false, "persistentDays": 0},
],
};
+65
View File
@@ -251,6 +251,71 @@ describe("the seam", () => {
assert.equal(JSON.stringify(source.current().states), before);
});
it("gives an anonymous visitor the simulator, on a box that HAS devices", async (t) => {
// The shipping bug this fixes, in one test. cloud-2 runs
// `TERA_DEVICES_SOURCE=sim`, so `/health` reports a device source and
// `serverHasDevices` is true — but `routes/devices.ts` refuses an anonymous
// read, correctly, because the readings describe a room somebody is
// standing in. Without the tier, the API strategy was chosen anyway: the GET
// 401s, the feed maps to `live: false`, and every visitor to a studio was
// shown permanently powered-off instruments while the poll backed off
// exponentially against a 401 it could never pass. Three file headers
// promised them a living simulated studio instead.
let asked = 0;
const client = createTeraClient({
fetch: deployment({
"/devices": () => {
asked += 1;
return new Response("no", { status: 401 });
},
}),
});
const source = createDeviceSource({
declarations: [DECLARATION],
client,
officeId: "hq",
serverHasDevices: true,
viewerTier: "anon",
});
t.after(() => source.stop());
await new Promise((resolve) => setTimeout(resolve, 0));
// Not one request spent on a refusal that was knowable in advance.
assert.equal(asked, 0);
assert.equal(source.current().source, "sim");
assert.equal(source.current().live, false);
assert.equal(source.current().synthetic, true);
// And alive: two different readings a fifth of a second apart, which is what
// "living simulated studio" has to mean for it to be worth anything.
const before = JSON.stringify(source.current().states);
source.tick(0.2);
assert.notEqual(JSON.stringify(source.current().states), before);
});
it("keeps the API for a signed-in viewer on the same deployment", async (t) => {
const body: DevicesBody = {
officeId: "hq",
devices: [{ id: "mic-1", kind: "mic", powered: true, observedAt: 1, synthetic: true }],
observedAt: 1,
source: "sim",
synthetic: true,
ttlSeconds: 5,
};
const client = createTeraClient({ fetch: deployment({ "/devices": () => json(body) }) });
const source = createDeviceSource({
declarations: [DECLARATION],
client,
officeId: "hq",
serverHasDevices: true,
viewerTier: "member",
});
t.after(() => source.stop());
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(source.current().live, true);
});
it("skips the API entirely when /health said this box has no devices", async (t) => {
let asked = 0;
const client = createTeraClient({
+97 -1
View File
@@ -17,7 +17,7 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { intoBuildingFrame, officeDaylight } from "../interiors/daylight.ts";
import { intoBuildingFrame, officeDaylight, smokeCaption } from "../interiors/daylight.ts";
import type { LightingState } from "../engine/types.ts";
import type { OfficeSite } from "../interiors/types.ts";
@@ -152,3 +152,99 @@ describe("moving the weather outdoors", () => {
assert.equal(JSON.stringify(cityState), before);
});
});
// ---- Smoke ----------------------------------------------------------------
/**
* The haze the fires drive, and the one thing it must never do.
*
* `smokeLoad` reaches this function from a fire layer reading a feed off another
* machine, so the zero case is the case that matters: on a day when nothing is
* burning which, on the SoCal board, is most days and was today the office
* has to be bit-identical to what it was before any of this existed. A haze that
* creeps in at a load of nothing is the same failure as an orange glyph over a
* city where nothing is on fire, one layer further in.
*/
describe("office daylight under smoke", () => {
const site: OfficeSite = { lat: 34.0395, lng: -118.2288, elevation: 79, heading: 36 };
const clear: LightingState = {
sun: { direction: [0.3, 0.8, -0.5], color: 0xfff3e0, intensity: 2.4 },
hemisphere: { sky: 0x8899aa, ground: 0x404040, intensity: 1 },
ambient: { color: 0xffffff, intensity: 0.3 },
sky: { top: 0x223344, horizon: 0x99aabb },
fog: { color: 0xaabbcc, near: 900, far: 60000 },
};
it("is bit-identical to a clear day at a load of zero", () => {
const base = officeDaylight(clear, site);
// A non-finite load is *no* smoke rather than maximum smoke. It can only be
// a bug upstream, and the safe rendering of a bug is the ordinary sky.
for (const nothing of [0, -1, Number.NaN, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]) {
const out = officeDaylight(clear, site, nothing);
assert.equal(out.fog?.color, base.fog?.color, `${nothing}`);
assert.equal(out.fog?.near, base.fog?.near, `${nothing}`);
assert.equal(out.fog?.far, base.fog?.far, `${nothing}`);
assert.equal(out.sun.color, base.sun.color, `${nothing}`);
assert.equal(out.sun.intensity, base.sun.intensity, `${nothing}`);
// And the answer it always gave, restated so a changed default is caught
// here rather than in a screenshot.
assert.equal(out.fog?.color, 0xaabbcc);
assert.equal(out.fog?.near, 150);
assert.equal(out.sun.intensity, 2.4);
}
assert.equal(smokeCaption(0), null);
assert.equal(smokeCaption(Number.NaN), null);
});
it("moves the haze colour, the haze distance and the sun together, monotonically", () => {
const loads = [0, 0.25, 0.5, 0.75, 1];
const near: number[] = [];
const sunIntensity: number[] = [];
const fogWarmth: number[] = [];
const sunBlue: number[] = [];
for (const load of loads) {
const out = officeDaylight(clear, site, load);
near.push(out.fog!.near);
sunIntensity.push(out.sun.intensity);
// Brown is warm, not dark: what rises is red *against* blue, and the base
// fog is a cool grey-blue whose red channel falls as it browns.
fogWarmth.push(((out.fog!.color >> 16) & 0xff) - (out.fog!.color & 0xff));
sunBlue.push(out.sun.color & 0xff);
}
for (let index = 1; index < loads.length; index += 1) {
// The haze comes closer.
assert.ok(near[index]! < near[index - 1]!, `near ${near.join()}`);
// The sun dims.
assert.ok(sunIntensity[index]! < sunIntensity[index - 1]!, `sun ${sunIntensity.join()}`);
// The air goes browner.
assert.ok(fogWarmth[index]! > fogWarmth[index - 1]!, `fog ${fogWarmth.join()}`);
// ...and less blue in the sun, which is the half that reads as fire.
assert.ok(sunBlue[index]! < sunBlue[index - 1]!, `sun blue ${sunBlue.join()}`);
}
// Capped rather than saturating: a full load is a bad day, not a blackout.
assert.ok(sunIntensity[4]! > clear.sun.intensity * 0.5, `${sunIntensity[4]}`);
assert.ok(near[4]! > 40, `${near[4]}`);
});
it("clamps a load above one rather than running past the cap", () => {
const full = officeDaylight(clear, site, 1);
for (const over of [1.0000001, 1.5, 40, 1e9]) {
assert.deepEqual(officeDaylight(clear, site, over).fog, full.fog, `${over}`);
assert.deepEqual(officeDaylight(clear, site, over).sun, full.sun, `${over}`);
}
});
it("never draws a brown sky without a sentence under it", () => {
for (const load of [0.05, 0.4, 0.9, 1]) {
const caption = smokeCaption(load);
assert.ok(caption, `${load}`);
// It must say the number is not a measurement of this building's air.
assert.match(caption, /not a measurement/i);
assert.match(caption, /fires currently on the board/i);
}
});
it("leaves a fogless rig fogless, smoke or no smoke", () => {
assert.equal(officeDaylight({ ...clear, fog: null }, site, 1).fog, null);
});
});
+63
View File
@@ -0,0 +1,63 @@
/**
* The one place the fire wire and the fire renderer meet, checked from both
* sides.
*
* `src/server/fires.ts` owns `promote()` and the shape it returns.
* `src/engine/scene.ts` owns `SceneHandle.setFires` and declares the *minimum*
* a renderer needs deliberately its own copy, so the engine never imports a
* wire module and neither file has to exist for the other to compile. The cost
* of that decision is exactly one thing: nothing stops the two drifting.
*
* This is that one thing. The assignment below is the whole test if
* `FirePromotion` stops satisfying `FireView`, `tsc` fails here rather than in
* a render loop three weeks later and the runtime assertions state the same
* contract for a reader who is not running the compiler.
*
* The risk it guards is named in the round's own notes: cloud-1's `fires.py`
* was modified the day this landed and nothing in this repo's test suite covers
* it, so a renamed column has to fail loudly somewhere. This is the somewhere on
* the client side.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { emptyPromotion, promote, type FirePromotion } from "../server/fires.ts";
import type { FireView } from "../engine/scene.ts";
/**
* The compile-time half. A `FirePromotion` must be usable wherever the scene
* asks for a `FireView`, with no mapping step in between because there is no
* mapping step in `main.ts` either.
*/
const _assignable: (p: FirePromotion) => FireView = (p) => p;
const SOCAL = { minLat: 32.5, maxLat: 34.9, minLng: -119.5, maxLng: -117.2 };
describe("the fire seam", () => {
it("hands promote() output straight to the scene with no adapter", () => {
const view: FireView = _assignable(emptyPromotion());
assert.deepEqual(view.drawn, []);
assert.deepEqual(view.detections, []);
// Epoch zero rather than "now": nothing has answered, and saying so with a
// real timestamp would be the lie this field exists to prevent.
assert.equal(view.fetchedAt, new Date(0).toISOString());
assert.equal(view.ageMs, null);
});
it("survives a body it did not build, rather than throwing into a render loop", () => {
for (const body of [null, undefined, {} as never]) {
const view: FireView = _assignable(promote(body, SOCAL, 1_000));
assert.deepEqual(view.drawn, []);
assert.deepEqual(view.detections, []);
}
});
it("keeps the field names the renderer reads", () => {
// Named explicitly rather than inferred, so a rename on either side lands
// here as a failing assertion with the old name printed in it.
const view: FireView = _assignable(emptyPromotion());
for (const key of ["drawn", "detections", "fetchedAt", "ageMs"]) {
assert.ok(key in view, `FireView lost ${key}`);
}
});
});
+100
View File
@@ -0,0 +1,100 @@
/**
* The opening move, held to the two things it must never do.
*
* Neither of these is a look-at-the-picture question those were answered with
* `scripts/look.mjs` and cannot be asserted but both are the kind of thing
* that would break silently and be noticed months later in a screenshot.
*
* 1. **The hero seat only ever brings a camera down.** A board's whole-board
* shot is authored between 32 and 41 degrees above the ground and is much
* better seen from lower; a *room's* viewpoint is often eye height, and
* lifting a camera that is standing inside a building to twenty-nine
* degrees is a ceiling shot of somebody's desk. `Math.min` is the whole
* guard and it is one character from being wrong.
* 2. **Neither pose is allowed past the orbit's ceiling.** `setPose` hands the
* camera to `OrbitControls`, which clamps to `maxDistance` on its next
* update, so a pose beyond it is not a wider shot it is a move that
* starts wherever the clamp happened to land.
*
* Plus the invariant that makes the move a move: the start stands further off
* and higher than the rest, and looks at the same point.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { Vector3 } from "three";
import { arrivalStart, heroPose } from "../engine/scene.ts";
/** Elevation above the horizontal, in degrees, of a pose about its target. */
function elevationDeg(pose: { position: Vector3; target: Vector3 }): number {
const d = pose.position.clone().sub(pose.target);
return (Math.asin(d.y / d.length()) * 180) / Math.PI;
}
function reach(pose: { position: Vector3; target: Vector3 }): number {
return pose.position.distanceTo(pose.target);
}
/** A pose at `elevation` degrees and `distance` units from the origin. */
function pose(elevationDegrees: number, distance: number) {
const e = (elevationDegrees * Math.PI) / 180;
return {
target: new Vector3(10, 4, -6),
position: new Vector3(
10 + Math.cos(e) * distance * 0.6,
4 + Math.sin(e) * distance,
-6 + Math.cos(e) * distance * 0.8,
),
};
}
describe("the opening move", () => {
it("brings a high board pose down and leaves its target alone", () => {
// California's chapter 01: 408 out, 352 up, so 40.8 degrees.
const authored = pose(40.8, 538);
const hero = heroPose(authored, 1_000);
assert.ok(elevationDeg(hero) < elevationDeg(authored) - 8, elevationDeg(hero).toFixed(1));
assert.ok(elevationDeg(hero) > 20, elevationDeg(hero).toFixed(1));
assert.deepEqual(hero.target.toArray(), authored.target.toArray());
});
it("gives the three boards one answer, not three", () => {
// 40.8 California, 34.7 San Francisco, 32.1 Southern California.
const elevations = [40.8, 34.7, 32.1].map((deg) =>
elevationDeg(heroPose(pose(deg, 400), 1_000)),
);
for (const e of elevations) assert.ok(Math.abs(e - elevations[0]!) < 0.001, String(e));
});
it("never raises a camera that is already low — an office keeps its seat", () => {
const eyeLevel = pose(7, 12);
const hero = heroPose(eyeLevel, 40);
assert.ok(
elevationDeg(hero) <= elevationDeg(eyeLevel) + 0.001,
`${elevationDeg(hero)} vs ${elevationDeg(eyeLevel)}`,
);
});
it("stands further off and higher than the pose it will land on", () => {
const rest = heroPose(pose(40.8, 538), 5_000);
const start = arrivalStart(rest, 5_000);
assert.ok(reach(start) > reach(rest));
assert.ok(elevationDeg(start) > elevationDeg(rest));
assert.deepEqual(start.target.toArray(), rest.target.toArray());
});
it("keeps both poses inside the orbit's own ceiling", () => {
const ceiling = 300;
const authored = pose(40.8, 538);
const rest = heroPose(authored, ceiling);
assert.ok(reach(rest) <= ceiling, String(reach(rest)));
assert.ok(reach(arrivalStart(rest, ceiling)) <= ceiling, String(reach(arrivalStart(rest, ceiling))));
});
it("is a no-op on a degenerate pose rather than a NaN", () => {
const degenerate = { target: new Vector3(1, 2, 3), position: new Vector3(1, 2, 3) };
const hero = heroPose(degenerate, 100);
assert.deepEqual(hero.position.toArray(), [1, 2, 3]);
assert.deepEqual(hero.target.toArray(), [1, 2, 3]);
});
});
+190
View File
@@ -195,3 +195,193 @@ describe("office walker actor adapter", () => {
actor.dispose();
});
});
// ---- The climb -------------------------------------------------------------
/**
* Two storeys and one dog-leg stair between them, sized so that the numbers in
* the assertions are readable: 4 m of rise, two 3 m flights and a flat landing.
*
* The upper footprint deliberately sits at the *edge* of the upper floor, which
* is what it does in `mateo-court`: the way up arrives through a gap in a
* balustrade, and covering that gap is the thing that stops a walker strolling
* out of it.
*/
function stairPlan(): Plan {
const room = (id: string, outline: Room["outline"]): Room =>
({ id, name: id, floor: "floor" as never, outline });
const lower: Level = {
id: "level-1",
name: "Ground",
elevation: 0,
wallHeight: 3,
wallThickness: 0.1,
floorplan: { rooms: [room("l1", FLOOR.outline)], walls: [] },
};
const upper: Level = {
id: "level-2",
name: "Upper",
elevation: 4,
wallHeight: 3,
wallThickness: 0.1,
floorplan: { rooms: [room("l2", FLOOR.outline)], walls: [] },
};
const office: Office = {
id: "stair-test",
name: "Stair Test",
levels: [lower, upper],
viewpoints: [],
transitions: [{
id: "stair",
kind: "stair",
lower: {
levelId: "level-1",
footprint: [{ x: 1, z: 1 }, { x: 3, z: 1 }, { x: 3, z: 3 }, { x: 1, z: 3 }],
landing: { x: 2, z: 2 },
},
upper: {
levelId: "level-2",
footprint: [{ x: 7, z: 1 }, { x: 9, z: 1 }, { x: 9, z: 3 }, { x: 7, z: 3 }],
landing: { x: 8, z: 2 },
},
legs: [
{ to: { x: 5, z: 2 }, rise: 1 },
{ to: { x: 5, z: 5 }, rise: 0 },
{ to: { x: 8, z: 2 }, rise: 1 },
],
}],
};
return new Plan(office, { warn: false });
}
function climber(plan: Plan) {
const actor = createOfficeWalker(plan, {
levelId: "level-1",
position: { x: 6, z: 2 },
speed: 2,
fixedStep: 0.05,
active: true,
});
return actor;
}
/** Walk west into the foot of the stair and return once the crossing has begun. */
function walkIntoTheStair(actor: ReturnType<typeof climber>): void {
actor.setAction({ x: -1, z: 0 });
for (let step = 0; step < 60 && actor.state().crossing === null; step += 1) actor.tick(0.05);
}
describe("office walker crossings", () => {
it("climbs a stair, changes storey once, and keeps the odometer running", () => {
const actor = climber(stairPlan());
const start = actor.state();
assert.equal(start.levelId, "level-1");
assert.equal(actor.root.position.y, 0);
walkIntoTheStair(actor);
const walked = actor.state().distance;
assert.equal(actor.state().crossing, "stair");
assert.ok(walked > 2.5, `${walked}`);
// Half way up: still on the lower storey by state, visibly between floors.
const seen: number[] = [];
for (let step = 0; step < 4; step += 1) {
actor.tick(0.1);
seen.push(actor.root.position.y);
}
assert.ok(seen[seen.length - 1]! > 0, `${seen.join()}`);
assert.ok(seen[seen.length - 1]! < 4, `${seen.join()}`);
// The camera rides the actor up rather than cutting to a floor height.
assert.ok(Math.abs(actor.followPose().position.y - (actor.root.position.y + 2.25)) < 1e-9);
for (let step = 0; step < 200 && actor.state().crossing !== null; step += 1) actor.tick(0.05);
const top = actor.state();
assert.equal(top.crossing, null);
assert.equal(top.levelId, "level-2");
assert.deepEqual(top.position, { x: 8, z: 2 });
assert.equal(actor.root.position.y, 4);
// `reset()` was not the transition path: the odometer survived the climb.
assert.equal(top.distance, walked);
// Every height on the way up was between the two floors, in order.
for (let index = 1; index < seen.length; index += 1) {
assert.ok(seen[index]! >= seen[index - 1]!, `${seen.join()}`);
}
actor.dispose();
});
it("does not immediately fall back down the stair it just came up", () => {
const actor = climber(stairPlan());
walkIntoTheStair(actor);
for (let step = 0; step < 200 && actor.state().crossing !== null; step += 1) actor.tick(0.05);
assert.equal(actor.state().levelId, "level-2");
// Still inside the upper footprint, still holding a direction. The latch is
// what stops this being an infinite loop between two floors.
actor.setAction({ x: 1, z: 0 });
for (let step = 0; step < 10; step += 1) actor.tick(0.05);
assert.equal(actor.state().crossing, null);
assert.equal(actor.state().levelId, "level-2");
actor.dispose();
});
it("does not start a crossing from standing still, or while inactive", () => {
const actor = createOfficeWalker(stairPlan(), {
levelId: "level-1",
position: { x: 2, z: 2 },
speed: 2,
fixedStep: 0.05,
active: true,
});
// Standing on the foot of the flight, holding nothing.
for (let step = 0; step < 20; step += 1) actor.tick(0.05);
assert.equal(actor.state().crossing, null);
assert.equal(actor.state().levelId, "level-1");
actor.setActive(false);
actor.setAction({ x: 1, z: 0 });
for (let step = 0; step < 20; step += 1) actor.tick(0.05);
assert.equal(actor.state().crossing, null);
assert.equal(actor.state().levelId, "level-1");
actor.dispose();
});
it("lands on one storey when a crossing is interrupted", () => {
const inactive = climber(stairPlan());
walkIntoTheStair(inactive);
inactive.tick(0.1);
assert.equal(inactive.state().crossing, "stair");
inactive.setActive(false);
assert.equal(inactive.state().crossing, null);
assert.equal(inactive.state().levelId, "level-2");
assert.equal(inactive.root.position.y, 4);
inactive.dispose();
// `reset` is a teleport to a known place, so it abandons the climb rather
// than completing it — but it still lands on a storey, never between two.
const restarted = climber(stairPlan());
walkIntoTheStair(restarted);
restarted.tick(0.1);
restarted.reset();
assert.equal(restarted.state().crossing, null);
assert.equal(restarted.state().levelId, "level-1");
assert.deepEqual(restarted.state().position, { x: 6, z: 2 });
assert.equal(restarted.root.position.y, 0);
restarted.dispose();
});
it("walks normally in a pack that authors no transitions", () => {
const actor = createOfficeWalker(makePlan(), {
levelId: "ground",
position: { x: 2, z: 2 },
speed: 1,
fixedStep: 0.1,
active: true,
});
actor.setAction({ x: 1, z: 0 });
for (let step = 0; step < 10; step += 1) actor.tick(0.1);
assert.equal(actor.state().crossing, null);
assert.ok(actor.state().position.x > 2.9);
actor.dispose();
});
});
+157 -11
View File
@@ -26,7 +26,10 @@ import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
CANONICAL_CAPABILITIES,
DEVICE_CAPABILITIES,
DEVICE_RANGES,
deviceKindOfAssetId,
deviceRange,
validateDeviceDeclaration,
type DeviceDeclaration,
type DeviceKind,
@@ -81,28 +84,56 @@ describe("both studios declare the hardware the product promises", () => {
}
});
it(`${pack.id} says its readings are simulated, in words`, () => {
it(`${pack.id} says where every reading comes from, in words`, () => {
for (const device of declarations) {
// The library check — provenance, disclosure wording, capability
// vocabulary — restated here against the shipped packs rather than
// against a fixture, because it is the shipped packs that get edited.
assert.deepEqual(validateDeviceDeclaration(device), [], device.id);
assert.equal(device.provenance, "simulated");
assert.match(device.disclosure, /simulat/i);
assert.ok(
device.provenance === "simulated" || device.provenance === "first-party-sensor",
`${device.id} has provenance "${device.provenance}"`,
);
if (device.provenance === "simulated") {
assert.match(device.disclosure, /simulat/i, device.id);
return;
}
// A live device says the opposite, and then has to carry a *second*
// sentence for the path where the readings are not live after all: an
// anonymous visitor cannot read the device route, so they get the local
// simulator running this declaration. Without it the panel would print
// "live, LA Studio" under a number invented a millisecond ago.
assert.doesNotMatch(device.disclosure, /simulat/i, device.id);
assert.match(device.simulatedDisclosure ?? "", /simulat/i, device.id);
}
});
it(`${pack.id} describes each instrument with the canonical capabilities`, () => {
it(`${pack.id} describes every instrument in a vocabulary the system knows`, () => {
// Not style: the panel builds its controls by walking this array and the
// arena's observation width is the sum of them, so two studios authored
// months apart disagreeing about what a mic can do changes the shape of an
// RL observation without anybody editing the arena.
//
// The list is no longer required to *equal* `CANONICAL_CAPABILITIES`,
// because the studio instruments now differ from it deliberately — see
// "the LA studio's two live instruments" below. What every device still
// owes is that each capability it claims is one the whole system knows and
// one its own kind could plausibly have.
for (const device of declarations) {
assert.deepEqual(
[...device.capabilities],
[...CANONICAL_CAPABILITIES[device.kind]],
device.id,
);
assert.ok(device.capabilities.length > 0, device.id);
assert.equal(new Set(device.capabilities).size, device.capabilities.length, device.id);
for (const capability of device.capabilities) {
assert.ok(
DEVICE_CAPABILITIES.includes(capability),
`${device.id} declares unknown capability "${capability}"`,
);
assert.ok(
// `mute` is the one addition: canonical omits it on a speaker and
// both real and simulated speakers plainly have one.
capability === "mute" || CANONICAL_CAPABILITIES[device.kind].includes(capability),
`${device.id} claims "${capability}", which a ${device.kind} does not have`,
);
}
}
});
@@ -151,8 +182,12 @@ describe("Plan resolves a device onto its anchor prop", () => {
[
"la-front-mic@lobby/front-01",
"la-front-speaker@lobby/front-01",
"la-studio-mic@press/media-01",
"la-studio-speaker@press/media-02",
// The two studio instruments carry the *upstream's* ids, because
// `server/src/devices/firstParty.ts` binds a reading to a declaration by
// id and nothing else. A prettier id here is a device that declares
// itself live and is permanently unreachable.
"mic-yeti@press/media-01",
"speaker@press/media-02",
],
);
});
@@ -177,6 +212,117 @@ describe("Plan resolves a device onto its anchor prop", () => {
});
});
/**
* The LA studio is the first pack in this repo whose readings are not invented,
* and every one of these assertions is a refusal rather than a feature.
*
* The upstream's own `GET /api/la-studio/state` reports, per microphone,
* `{ muted, gainPct, gainRaw, reachable, error, checkedAt }` no level. A level
* needs `POST /levels`, which records one and a half to three seconds of audio
* per microphone to measure it. And `gainPct` is a percentage of four different
* native mixer travels, which is not any number of decibels. So this file pins
* the two omissions, because they are the sort of thing somebody adds back in
* good faith to make a room feel alive.
*/
describe("the LA studio's two live instruments", () => {
const live = declarationsOf(MATEO_COURT).filter(
(device) => device.provenance === "first-party-sensor",
);
it("promotes exactly the two studio instruments, by their upstream ids", () => {
assert.deepEqual(live.map((device) => device.id), ["mic-yeti", "speaker"]);
// The other two are reception's, they are invented, and they stay that way.
const simulated = declarationsOf(MATEO_COURT).filter(
(device) => device.provenance === "simulated",
);
assert.deepEqual(simulated.map((device) => device.id), ["la-front-mic", "la-front-speaker"]);
});
it("declares no level meter on a microphone in a room with people in it", () => {
for (const device of live.filter((entry) => entry.kind === "mic")) {
assert.ok(!device.capabilities.includes("level"), device.id);
}
});
it("states its gain in the unit the upstream actually speaks", () => {
const mic = live.find((device) => device.kind === "mic");
assert.ok(mic);
// Fail-closed and checked on both sides: without a declared non-decibel
// range the bridge deliberately emits no gain at all, because the global
// default is decibels and 68 % of a Yeti's travel is not any number of them.
assert.deepEqual(mic.ranges?.gain, { min: 0, max: 100, initial: 60, unit: "%" });
assert.equal(deviceRange(mic, "gain").unit, "%");
assert.notEqual(deviceRange(mic, "gain").unit, DEVICE_RANGES.gain.unit);
});
it("names the real hardware in one sentence and the simulator in another", () => {
for (const device of live) {
assert.match(device.disclosure, /live/i, device.id);
assert.match(device.disclosure, /LA Studio/, device.id);
assert.doesNotMatch(device.disclosure, /simulat/i, device.id);
assert.match(device.simulatedDisclosure ?? "", /simulat/i, device.id);
// Neither sentence may become a coordinate. The upstream store is centred
// on somebody's home and the words that describe it are not ours to
// publish; "the LA Studio" is the endpoint's own name for itself.
assert.doesNotMatch(device.disclosure, /bedroom|bed\b|asleep|address/i, device.id);
assert.doesNotMatch(device.simulatedDisclosure ?? "", /bedroom|asleep/i, device.id);
}
});
});
/**
* SF has no hardware anywhere, and the pack says so in the sentence a viewer
* reads rather than by omission.
*/
describe("the SF studio stays simulated and says what it is", () => {
const declarations = declarationsOf(LUMBRIDGE_HQ);
it("declares nothing live", () => {
for (const device of declarations) assert.equal(device.provenance, "simulated");
});
it("says out loud that it is a room nobody is standing in", () => {
for (const device of declarations) {
assert.match(device.disclosure, /simulat/i, device.id);
assert.match(device.disclosure, /not a room anybody is standing in/i, device.id);
assert.match(device.disclosure, /no hardware in San Francisco/i, device.id);
}
});
/**
* The two *studio* instruments in each pack describe themselves identically,
* so a viewer comparing SF with LA is comparing where the numbers come from
* and nothing else. `mateo-court`'s reception desk is not a studio and keeps
* the canonical list including the level meter its simulator is free to
* invent, which is the contrast the disclosure sentences are there to explain.
*/
it("uses the same capability vocabulary as the studio that is real", () => {
const shape = (devices: readonly DeviceDeclaration[]) =>
devices
.map((device) => `${device.kind}:${[...device.capabilities].sort().join("+")}`)
.sort();
const sf = shape(declarationsOf(LUMBRIDGE_HQ));
const la = shape(
declarationsOf(MATEO_COURT).filter((device) => device.provenance === "first-party-sensor"),
);
assert.deepEqual(sf, la);
assert.deepEqual(sf, ["mic:gain+mute+power", "speaker:mute+playback+power+volume"]);
});
it("states its gain in decibels, because a simulated preamp really is decibels", () => {
const mic = declarationsOf(LUMBRIDGE_HQ).find((device) => device.kind === "mic");
assert.ok(mic);
assert.equal(deviceRange(mic, "gain").unit, DEVICE_RANGES.gain.unit);
assert.deepEqual(mic.ranges?.gain, DEVICE_RANGES.gain);
});
it("declares no level meter either, though nothing stops it inventing one", () => {
for (const device of declarationsOf(LUMBRIDGE_HQ)) {
assert.ok(!device.capabilities.includes("level"), device.id);
}
});
});
describe("a broken device costs one device", () => {
const CASES: readonly [string, (device: DeviceDeclaration) => DeviceDeclaration, RegExp][] = [
[
+140
View File
@@ -0,0 +1,140 @@
/**
* The handshake between Tera's office packs and the room cloud-1 actually
* measures.
*
* `GET /api/la-studio/tera/seats` was built so that a pack could be diffed
* against the real room, seat id by seat id. Nothing has ever diffed against it.
* A handshake nobody shakes is a comment, and this file is the difference: from
* here on, every seat the upstream publishes is either **resolved by a shipped
* pack** or **explicitly listed as unshipped, with a reason**. There is no third
* state, which is what makes silent drift impossible the day somebody authors
* the pack, the list empties itself; the day the upstream adds a seat, this
* fails and somebody has to decide what it is.
*
* ### Why all three are currently unshipped, and why that is the right answer
*
* The room is 3.9624 m square. Mateo Court is a 36 x 26 m courtyard block: 936 m²
* against 15.7 m², sixty times the floor area, with a lobby, a press room and a
* robotics lab that do not exist there. The two are not the same place and
* cannot be made into each other, which is why this round twinned the two studio
* *devices* where the reading is measured and the placement is authored, and
* the disclosure says which is which and did not twin the room.
*
* Authoring the room itself is a separate decision, and it is the owner's rather
* than an engineer's: `bed-01` is not a figure of speech. That is recorded here
* rather than in a ticket, because this is the file somebody reads on the day
* they decide to build it.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { Plan } from "../../interiors/plan.ts";
import FRONTIER_VALLEY from "../../offices/frontier-valley.ts";
import LUMBRIDGE_HQ from "../../offices/lumbridge-hq.ts";
import MATEO_COURT from "../../offices/mateo-court.ts";
import { LA_STUDIO_SEATS } from "./laStudioSeats.ts";
/** Every pack this build ships, at full depth. */
const SHIPPED = [LUMBRIDGE_HQ, MATEO_COURT, FRONTIER_VALLEY].map(
(pack) => new Plan(pack, { warn: false }),
);
/**
* Seats the upstream publishes that no shipped pack resolves, and why.
*
* Every entry here is a promise that somebody looked at it. Deleting one means
* a pack now carries that seat; adding one means a new seat appeared upstream
* and somebody decided, on purpose, not to ship it yet.
*/
const UNSHIPPED: Readonly<Record<string, string>> = {
"desk-01":
"the LA Studio's own desk. No shipped pack is that room — Mateo Court is a " +
"936 m² courtyard block and the studio is 15.7 m² — so binding this id to " +
"one of its desks would put a measured address on an invented piece of " +
"furniture.",
"bed-01":
"the room has a bed in it, and it is somebody's. Authoring a walkable, " +
"publicly reachable twin of it is the owner's decision and has not been " +
"made. Nothing here should make it by accident.",
"floor-01":
"the standing mark in the middle of the same room. Unshipped for the same " +
"reason as the other two: the room is not authored.",
};
describe("the LA Studio seat handshake", () => {
it("captured a body with the shape the upstream documents", () => {
assert.equal(LA_STUDIO_SEATS.officeId, "la-studio");
assert.ok(Number.isInteger(LA_STUDIO_SEATS.frameRev));
assert.ok(LA_STUDIO_SEATS.seats.length > 0);
// The slab is the strongest claim in the whole body and the reason a twin of
// this room would be worth building: it was measured, not typed.
assert.equal(LA_STUDIO_SEATS.slab.provenance, "measured");
assert.ok(LA_STUDIO_SEATS.slab.widthM > 0 && LA_STUDIO_SEATS.slab.depthM > 0);
assert.ok(!Number.isNaN(Date.parse(LA_STUDIO_SEATS.slab.measuredAt)));
for (const seat of LA_STUDIO_SEATS.seats) {
assert.ok(seat.id.length > 0);
assert.ok(Number.isFinite(seat.position.x) && Number.isFinite(seat.position.z));
assert.ok(seat.position.x >= 0 && seat.position.x <= LA_STUDIO_SEATS.slab.widthM, seat.id);
assert.ok(seat.position.z >= 0 && seat.position.z <= LA_STUDIO_SEATS.slab.depthM, seat.id);
assert.ok(seat.pose === "sit" || seat.pose === "stand", seat.id);
}
});
it("carries no coordinate, no address and nothing home-relative", () => {
// The upstream store this room belongs to is centred on somebody's home, and
// several of its columns invert to a distance from it. None of them is on
// this endpoint and none may ever be captured into this repository.
const text = JSON.stringify(LA_STUDIO_SEATS);
for (const forbidden of [
"lat", "lng", "longitude", "latitude", "bearing", "distanceKm", "distance_km",
"threat", "address", "street",
]) {
assert.ok(!text.includes(forbidden), `the seat fixture carries "${forbidden}"`);
}
});
it("accounts for every upstream seat: shipped, or listed as unshipped", () => {
for (const seat of LA_STUDIO_SEATS.seats) {
const resolved = SHIPPED.some((plan) => plan.seat(seat.id) !== null);
const excused = Object.prototype.hasOwnProperty.call(UNSHIPPED, seat.id);
assert.ok(
resolved || excused,
`upstream publishes seat "${seat.id}" and no shipped pack resolves it, and ` +
"nothing in UNSHIPPED says why. Author it, or write down the reason.",
);
assert.ok(
!(resolved && excused),
`seat "${seat.id}" is both shipped and listed as unshipped — delete the ` +
"UNSHIPPED entry, it is now a lie.",
);
}
});
it("keeps the unshipped list honest in both directions", () => {
const published = new Set(LA_STUDIO_SEATS.seats.map((seat) => seat.id));
for (const [id, reason] of Object.entries(UNSHIPPED)) {
assert.ok(
published.has(id),
`UNSHIPPED excuses "${id}", which the upstream no longer publishes`,
);
// A reason, not a placeholder. This is the field that stops the list
// becoming a way of silencing the check.
assert.ok(reason.length > 40, `UNSHIPPED["${id}"] does not say anything`);
}
});
it("does not let a shipped pack quietly reuse an upstream seat id", () => {
// The inverse drift: a pack author picks `desk-01` for a desk in Mateo Court
// and, the day a presence feed is wired up, the LA Studio's occupants appear
// in a fictional lobby. Seat ids are what `Presence` binds on.
for (const plan of SHIPPED) {
for (const seat of LA_STUDIO_SEATS.seats) {
const collision = plan.seat(seat.id);
assert.ok(
collision === null,
`${plan.office.id} declares seat "${seat.id}", which is an LA Studio id`,
);
}
}
});
});
+69
View File
@@ -0,0 +1,69 @@
/**
* The LA Studio's seat handshake, captured.
*
* `GET /api/la-studio/tera/seats` on cloud-1 exists for exactly one purpose: to
* let a Tera office pack be *diffed* against the room it claims to be a twin of.
* It has existed for a while and nothing has ever diffed against it, which is
* how a handshake becomes decoration. This is the response, captured on
* 2026-08-22 at `frameRev` 2, so that the next person to author that pack finds
* a test already waiting rather than a guess.
*
* ### It is a fixture, not a pack
*
* Nothing outside `src/test/` imports this and nothing should. It is not in the
* bundle, it is not an `Office`, and it is deliberately not one: authoring the
* room this describes is a decision about a private space that has not been
* made see `src/test/packs/laStudioHandshake.test.ts`, which is the whole
* reason the file exists.
*
* ### What is in it, and what is deliberately not
*
* Three seats and a slab, in the room's own coordinates, with the origin at a
* floor corner. There is no latitude, no longitude, no address, no bearing to
* anywhere and nothing home-relative in it. The upstream also serves an
* occupancy endpoint and a live camera frame; neither is captured here and
* neither belongs in a repository.
*
* `slab.provenance` is the upstream's own word and it is the interesting field:
* this room was **measured**, on the date it says, which is a stronger claim
* than any shipped pack makes about its own dimensions and the reason a twin of
* it would be worth building.
*/
export interface LaStudioSeatFixture {
id: string;
position: { x: number; z: number };
facing: number;
pose: "sit" | "stand";
}
export interface LaStudioSeatsBody {
frameRev: number;
officeId: string;
slab: {
widthM: number;
depthM: number;
ceilingM: number;
provenance: string;
measuredAt: string;
};
seats: LaStudioSeatFixture[];
}
/** Captured verbatim from `GET /api/la-studio/tera/seats`, 2026-08-22. */
export const LA_STUDIO_SEATS: LaStudioSeatsBody = {
frameRev: 2,
officeId: "la-studio",
slab: {
widthM: 3.9624,
depthM: 3.9624,
ceilingM: 2.44,
provenance: "measured",
measuredAt: "2026-08-21T21:57:05-07:00",
},
seats: [
{ id: "desk-01", position: { x: 2.1, z: 0.8 }, facing: 0, pose: "sit" },
{ id: "bed-01", position: { x: 3.55, z: 2.5 }, facing: 1.5707963267948966, pose: "sit" },
{ id: "floor-01", position: { x: 1.9812, z: 1.9812 }, facing: 3.141592653589793, pose: "stand" },
],
};
+127
View File
@@ -377,3 +377,130 @@ describe("the pinned ids survive a content pass", () => {
]) assert.ok(plan.prop(id), `robot station anchor ${id} is gone`);
});
});
/**
* The upper floor, and whether anybody can get to it.
*
* Level 2 of this pack the Model Loft, the Model Bay, the Materials Room, two
* desk banks, twenty-four props and six viewpoints was authored and then
* unreachable on foot for as long as it has existed, because the walk controller
* refused any state on a storey other than the one it spawned on. `README.md`
* carried a warning telling pack authors not to bother modelling a staircase.
*
* The engine change is tested in `walker.test.ts` and `officeWalker.test.ts`.
* What is tested *here* is the thing those two cannot see: that this building's
* way up actually resolves, and that both ends of it are places a walker can
* reach on foot. A transition that resolves onto a landing nobody can walk to is
* a staircase in a locked room, and every test in the engine would still pass.
*/
describe("mateo-court's way upstairs", () => {
const stair = plan.transition("stair");
it("resolves, with no problems and both storeys under it", () => {
assert.deepEqual(plan.problems.filter((p) => p.where.startsWith("transitions")), []);
assert.ok(stair, "the pack authors no way between its two storeys");
assert.equal(stair.kind, "stair");
assert.equal(stair.lower.levelId, "level-1");
assert.equal(stair.upper.levelId, "level-2");
// Floor to floor, which is the storey height plus the timber between.
assert.ok(Math.abs(stair.rise - 5) < 1e-9, `${stair.rise}`);
});
it("is a dog-leg with a flat half landing, and the treads follow it", () => {
assert.ok(stair);
assert.equal(stair.path.length, 4);
const [foot, turn, landing, head] = stair.path;
assert.equal(foot!.y, 0);
assert.equal(head!.y, 5);
// The half landing is flat: two flights of equal rise with a level between.
assert.equal(turn!.y, landing!.y);
assert.ok(Math.abs(turn!.y - 2.5) < 1e-9, `${turn!.y}`);
// The first flight runs east, the landing turns south, the second runs west
// and arrives through the gap in the balustrade.
assert.ok(turn!.x > foot!.x, "the first flight does not run east");
assert.ok(landing!.z > turn!.z, "the half landing does not turn");
assert.ok(head!.x < landing!.x, "the second flight does not come back west");
});
it("arrives through the gap in the balustrade and nowhere else", () => {
assert.ok(stair);
// The two rail segments leave 1.2 m of nothing between them at z 9.8-11.0 on
// the yard's west line, and the upper footprint covers it. That is what stops
// a walker who reaches the loggia strolling off a 5 m drop onto pavers.
assert.ok(stair.upper.bounds.minZ > 9.8, `${stair.upper.bounds.minZ}`);
assert.ok(stair.upper.bounds.maxZ < 11, `${stair.upper.bounds.maxZ}`);
assert.ok(stair.upper.bounds.minX < 9.6 && stair.upper.bounds.maxX > 9.6);
// And standing there is what triggers it, from either side.
assert.ok(plan.transitionAt("level-2", stair.upper.landing));
assert.ok(plan.transitionAt("level-1", stair.lower.landing));
// The middle of the courtyard is not a way anywhere.
assert.equal(plan.transitionAt("level-1", { x: 20, z: 13 }), null);
assert.equal(plan.transitionAt("level-2", { x: 3, z: 3 }), null);
});
it("puts both landings somewhere a walker can actually stand", () => {
assert.ok(stair);
for (const end of [stair.lower, stair.upper]) {
assert.equal(
plan.blocked(end.levelId, end.landing, end.landing, 0.3),
false,
`${end.levelId} landing is inside a wall`,
);
}
});
/**
* The check the engine cannot do for itself: a route.
*
* A flood fill on a 0.15 m grid with the walker's own radius, using the same
* `Plan.blocked` the controller sweeps against. It is slow and it is worth it
* the failure it catches is a staircase behind a wall, which looks perfect
* in every unit test and in every screenshot of the courtyard.
*/
it("can be walked to from the arrival spawn, and leads to the Model Loft", () => {
assert.ok(stair);
const spawn = MATEO_COURT.viewpoints[0]!;
assert.equal(spawn.levelId, "level-1", "the walk spawn moved off the ground floor");
assert.ok(reachable("level-1", spawn.focus.at, stair.lower.landing), "the stair foot");
// And from the head of it, the room this whole exercise was for.
assert.ok(reachable("level-2", stair.upper.landing, { x: 13, z: 3 }), "the Model Loft");
assert.ok(reachable("level-2", stair.upper.landing, { x: 3.6, z: 14.4 }), "the Materials Room");
});
});
/** Flood fill over one storey at the default walker radius. */
function reachable(
levelId: string,
from: { x: number; z: number },
to: { x: number; z: number },
): boolean {
const level = plan.level(levelId);
if (!level) return false;
const step = 0.15;
const radius = 0.3;
const key = (x: number, z: number) => `${Math.round(x / step)},${Math.round(z / step)}`;
const queue: { x: number; z: number }[] = [{ ...from }];
const seen = new Set([key(from.x, from.z)]);
let visited = 0;
while (queue.length > 0) {
const at = queue.shift()!;
visited += 1;
if (Math.hypot(at.x - to.x, at.z - to.z) < 0.35) return true;
// A courtyard block is about 60,000 cells; this is a guard against a bug
// here, not against the building.
if (visited > 200_000) return false;
for (const [dx, dz] of [[step, 0], [-step, 0], [0, step], [0, -step]] as const) {
const next = { x: at.x + dx, z: at.z + dz };
if (
next.x < level.bounds.minX + radius || next.x > level.bounds.maxX - radius ||
next.z < level.bounds.minZ + radius || next.z > level.bounds.maxZ - radius
) continue;
const k = key(next.x, next.z);
if (seen.has(k)) continue;
if (plan.blocked(levelId, at, next, radius)) continue;
seen.add(k);
queue.push(next);
}
}
return false;
}
+244 -1
View File
@@ -27,7 +27,8 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { Plan } from "../interiors/plan.ts";
import type { Level, Office, Room, Wall } from "../interiors/types.ts";
import { DEFAULT_WALKER_RADIUS } from "../interiors/walker.ts";
import type { Level, Office, Room, Transition, Wall } from "../interiors/types.ts";
/** Silent: these packs are wrong on purpose and the warnings are the point, not noise. */
const QUIET = { warn: false } as const;
@@ -306,3 +307,245 @@ describe("queries", () => {
assert.equal(plan.level("l2")?.runs[0]?.bottom, 4.2);
});
});
// ---- Transitions -----------------------------------------------------------
/**
* A two-storey pack whose transition is whatever the case is about.
*
* Both levels are the same 10 x 6 room so that a footprint failing on one and
* passing on the other is always the *transition's* doing rather than the
* building's except in the one case that deliberately shrinks the upper floor.
*/
function twoStorey(
transitions: Transition[],
upperRoom: Room["outline"] = SQUARE,
lowerWalls: Wall[] = [],
): Plan {
const lower = level({
id: "level-1",
floorplan: { rooms: [{ id: "l1", name: "Ground", outline: SQUARE, floor: "floor" as never }], walls: lowerWalls },
});
const upper = level({
id: "level-2",
name: "Level 2",
elevation: 4,
floorplan: { rooms: [{ id: "l2", name: "Upper", outline: upperRoom, floor: "floor" as never }], walls: [] },
});
return new Plan(
{ id: "test", name: "Test", levels: [lower, upper], viewpoints: [], transitions },
QUIET,
);
}
/** A 2 x 2 patch of floor with its own corner at (x, z). */
function patch(x: number, z: number, size = 2): Room["outline"] {
return [
{ x, z },
{ x: x + size, z },
{ x: x + size, z: z + size },
{ x, z: z + size },
];
}
const STAIR: Transition = {
id: "stair",
kind: "stair",
lower: { levelId: "level-1", footprint: patch(1, 1), landing: { x: 2, z: 2 } },
upper: { levelId: "level-2", footprint: patch(6, 1), landing: { x: 7, z: 2 } },
};
describe("Plan transitions", () => {
it("resolves both ends, adds each level's elevation once, and answers transitionAt", () => {
const plan = twoStorey([STAIR]);
assert.deepEqual(plan.problems, []);
assert.equal(plan.transitions.length, 1);
const resolved = plan.transition("stair")!;
assert.equal(resolved.rise, 4);
assert.equal(resolved.lower.floorY, 0);
assert.equal(resolved.upper.floorY, 4);
assert.equal(resolved.width, 1.2);
// Absent legs mean one straight flight, foot to head, with the office-world
// height at both ends.
assert.deepEqual(resolved.path, [
{ x: 2, y: 0, z: 2 },
{ x: 7, y: 4, z: 2 },
]);
const up = plan.transitionAt("level-1", { x: 2, z: 2 })!;
assert.equal(up.ascending, true);
assert.equal(up.to.levelId, "level-2");
assert.deepEqual(up.to.landing, { x: 7, z: 2 });
const down = plan.transitionAt("level-2", { x: 7, z: 2 })!;
assert.equal(down.ascending, false);
assert.equal(down.to.levelId, "level-1");
// The other end's footprint is not a way up on the level you are standing on.
assert.equal(plan.transitionAt("level-1", { x: 7, z: 2 }), null);
assert.equal(plan.transitionAt("level-2", { x: 2, z: 2 }), null);
assert.equal(plan.transitionAt("level-1", { x: 9, z: 5 }), null);
});
it("normalises leg rise shares and gives every point a height", () => {
const plan = twoStorey([{
...STAIR,
// A dog-leg whose author's shares do not add up, which is the ordinary
// case: two flights and a flat landing between them.
legs: [
{ to: { x: 5, z: 2 }, rise: 2 },
{ to: { x: 5, z: 4 }, rise: 0 },
{ to: { x: 7, z: 2 }, rise: 2 },
],
}]);
assert.deepEqual(plan.problems, []);
const path = plan.transition("stair")!.path;
assert.equal(path.length, 4);
assert.deepEqual(path[0], { x: 2, y: 0, z: 2 });
assert.deepEqual(path[1], { x: 5, y: 2, z: 2 });
// The landing is flat: same height as the foot of the second flight.
assert.deepEqual(path[2], { x: 5, y: 2, z: 4 });
assert.deepEqual(path[3], { x: 7, y: 4, z: 2 });
});
it("drops a transition WHOLE when only one of its two ends resolves", () => {
// The upper floor is a 4 x 6 strip in the west; the upper footprint at x = 6
// is off the end of it. The lower end is impeccable and must not survive.
const plan = twoStorey([STAIR], [
{ x: 0, z: 0 },
{ x: 4, z: 0 },
{ x: 4, z: 6 },
{ x: 0, z: 6 },
]);
assert.equal(plan.transitions.length, 0);
assert.equal(plan.transition("stair"), null);
assert.equal(plan.transitionAt("level-1", { x: 2, z: 2 }), null);
assert.equal(plan.transitionAt("level-2", { x: 7, z: 2 }), null);
const problem = plan.problems.find((p) => p.where === "transitions[0].upper");
assert.ok(problem, JSON.stringify(plan.problems));
assert.equal(problem.action, "dropped");
assert.match(problem.message, /footprint outside level "level-2"/);
});
it("refuses a footprint with a wall running through it", () => {
const plan = twoStorey([STAIR], SQUARE, [
{ id: "divider", from: { x: 2, z: 0 }, to: { x: 2, z: 6 } },
]);
assert.equal(plan.transitions.length, 0);
const problem = plan.problems.find((p) => p.where === "transitions[0].lower");
assert.ok(problem, JSON.stringify(plan.problems));
assert.match(problem.message, /wall "divider" running through its footprint/);
});
it("accepts a footprint that merely runs along a wall", () => {
// Stairs go against walls. A wall on the footprint's own edge is the
// ordinary case and must not be refused, or no stair is authorable.
const plan = twoStorey([STAIR], SQUARE, [
{ id: "edge", from: { x: 1, z: 0 }, to: { x: 1, z: 6 } },
]);
assert.deepEqual(plan.problems, []);
assert.equal(plan.transitions.length, 1);
});
it("refuses a landing outside its own footprint, or inside a wall", () => {
const outside = twoStorey([{
...STAIR,
lower: { levelId: "level-1", footprint: patch(1, 1), landing: { x: 8, z: 5 } },
}]);
assert.equal(outside.transitions.length, 0);
assert.match(
outside.problems.find((p) => p.where === "transitions[0].lower")!.message,
/landing outside its own footprint/,
);
// A wall along the footprint's edge is fine; a landing pressed against it is
// not, because that is a place nobody can stand.
const buried = twoStorey([{
...STAIR,
lower: { levelId: "level-1", footprint: patch(1, 1), landing: { x: 1.05, z: 2 } },
}], SQUARE, [{ id: "edge", from: { x: 1, z: 0 }, to: { x: 1, z: 6 } }]);
assert.equal(buried.transitions.length, 0);
assert.match(
buried.problems.find((p) => p.where === "transitions[0].lower")!.message,
/a walker cannot stand on/,
);
});
it("refuses a transition that does not go up, names a level twice, or repeats an id", () => {
const flat = twoStorey([{
...STAIR,
upper: { levelId: "level-1", footprint: patch(6, 1), landing: { x: 7, z: 2 } },
}]);
assert.equal(flat.transitions.length, 0);
assert.match(flat.problems[0]!.message, /joins level "level-1" to itself/);
const backwards = twoStorey([{
...STAIR,
lower: { levelId: "level-2", footprint: patch(6, 1), landing: { x: 7, z: 2 } },
upper: { levelId: "level-1", footprint: patch(1, 1), landing: { x: 2, z: 2 } },
}]);
assert.equal(backwards.transitions.length, 0);
assert.match(backwards.problems[0]!.message, /does not rise/);
const twice = twoStorey([STAIR, { ...STAIR }]);
assert.equal(twice.transitions.length, 1);
assert.match(twice.problems[0]!.message, /duplicate transition id "stair"/);
const unknown = twoStorey([{ ...STAIR, kind: "escalator" as never }]);
assert.equal(unknown.transitions.length, 0);
assert.match(unknown.problems[0]!.message, /unknown kind "escalator"/);
});
it("skips a private transition at public depth without calling it a problem", () => {
const pack: Office = {
id: "test",
name: "Test",
levels: [
level({ id: "level-1", floorplan: { rooms: [{ id: "l1", name: "Ground", outline: SQUARE, floor: "floor" as never }], walls: [] } }),
level({ id: "level-2", name: "Level 2", elevation: 4, floorplan: { rooms: [{ id: "l2", name: "Upper", outline: SQUARE, floor: "floor" as never }], walls: [] } }),
],
viewpoints: [],
transitions: [{ ...STAIR, audience: "private" }],
};
const publicBuild = new Plan(pack, { ...QUIET, depth: "public" });
assert.equal(publicBuild.transitions.length, 0);
assert.deepEqual(publicBuild.problems, []);
assert.equal(new Plan(pack, QUIET).transitions.length, 1);
});
it("checks a landing against the same radius the walker uses", () => {
// `plan.ts` restates `DEFAULT_WALKER_RADIUS` rather than importing it, so
// that `walker.ts` stays this file's dependent and not the other way round.
// A restated constant is one that drifts, so it is pinned here.
const buried = twoStorey([{
...STAIR,
lower: {
levelId: "level-1",
footprint: patch(1, 1),
// Exactly `DEFAULT_WALKER_RADIUS + thickness / 2` from the wall would be
// clear; a hair inside it is not, and this asserts the boundary is that
// number rather than some other one.
landing: { x: 1 + DEFAULT_WALKER_RADIUS + 0.05 - 0.01, z: 2 },
},
}], SQUARE, [{ id: "edge", from: { x: 1, z: 0 }, to: { x: 1, z: 6 }, thickness: 0.1 }]);
assert.equal(buried.transitions.length, 0);
const clear = twoStorey([{
...STAIR,
lower: {
levelId: "level-1",
footprint: patch(1, 1),
landing: { x: 1 + DEFAULT_WALKER_RADIUS + 0.05 + 0.01, z: 2 },
},
}], SQUARE, [{ id: "edge", from: { x: 1, z: 0 }, to: { x: 1, z: 6 }, thickness: 0.1 }]);
assert.equal(clear.transitions.length, 1);
});
it("has no transitions when a pack authors none", () => {
assert.deepEqual(withWalls([]).transitions, []);
assert.equal(withWalls([]).transitionAt("level-1", { x: 1, z: 1 }), null);
});
});
+219
View File
@@ -0,0 +1,219 @@
/**
* The plume layer, held to the two properties that make it honest and the one
* that makes it cheap.
*
* **Plume count costs no draw calls.** Eight plumes and one plume are the same
* single `THREE.Mesh`; the only thing that moves is `instanceCount`. That is the
* whole reason a plume is affordable at all, and it is easy to lose to a
* well-meaning refactor that gives each fire its own mesh "for clarity".
*
* **The wind blows the right way.** `fromDeg` is the bearing the wind comes
* *from*, so smoke travels toward `fromDeg + 180`, and scene north is Z with
* +X east. Getting that wrong points every plume into the wind, which looks
* completely plausible on a still frame and is wrong on every one of them. The
* assertion below is on the uniform, because there is no picture that catches it.
*
* **An empty layer is not visited.** `visible = false` rather than an
* instance count of zero, because a mesh the renderer walks is a draw call paid
* on a board where nothing is happening which is most boards, most days.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import { createFireSmoke, MAX_PLUMES, type SmokePlume } from "../../engine/fireSmoke.ts";
import type { LightingState } from "../../engine/types.ts";
const SPAN = 428;
function plume(id: string, over: Partial<SmokePlume> = {}): SmokePlume {
return { id, x: 0, y: 0, z: 0, length: 12, width: 1.2, density: 0.5, lift: 0, ...over };
}
/** The default layer: full density, so the instance counts are predictable. */
function layer() {
return createFireSmoke({ span: SPAN, density: 1 });
}
function mesh(root: THREE.Object3D): THREE.Mesh {
const found = root.getObjectByName("fire-smoke-puffs");
assert.ok(found, "the puff mesh must exist");
return found as THREE.Mesh;
}
const NOON: LightingState = {
sun: { direction: [0, 1, 0], color: 0xfff2e0, intensity: 2.6 },
hemisphere: { sky: 0x8fb6e8, ground: 0x6b6153, intensity: 0.6 },
ambient: { color: 0xffffff, intensity: 0.2 },
sky: { top: 0x3f7fd0, horizon: 0xcfe0f2 },
fog: { color: 0xc9d8e8, near: 400, far: 1600 },
};
describe("the smoke layer", () => {
it("is one mesh whatever the plume count", () => {
const smoke = layer();
const meshes: string[] = [];
smoke.group.traverse((object) => {
if ((object as THREE.Mesh).isMesh === true) meshes.push(object.name);
});
assert.deepEqual(meshes, ["fire-smoke-puffs"]);
smoke.setPlumes([plume("a")]);
const one = (mesh(smoke.group).geometry as THREE.InstancedBufferGeometry).instanceCount;
smoke.setPlumes([plume("a"), plume("b"), plume("c")]);
const three = (mesh(smoke.group).geometry as THREE.InstancedBufferGeometry).instanceCount;
assert.equal(three, one * 3, "instances scale with plumes");
const stillOne: string[] = [];
smoke.group.traverse((object) => {
if ((object as THREE.Mesh).isMesh === true) stillOne.push(object.name);
});
assert.deepEqual(stillOne, ["fire-smoke-puffs"], "and objects do not");
smoke.dispose();
});
it("is invisible with no plumes rather than empty and visited", () => {
const smoke = layer();
assert.equal(mesh(smoke.group).visible, false);
smoke.setPlumes([plume("a")]);
assert.equal(mesh(smoke.group).visible, true);
smoke.setPlumes([]);
assert.equal(mesh(smoke.group).visible, false);
assert.equal(smoke.plumeCount(), 0);
smoke.dispose();
});
it("caps at MAX_PLUMES and keeps the ones it was handed first", () => {
const smoke = layer();
smoke.setPlumes(Array.from({ length: 40 }, (_, i) => plume(`f${i}`)));
assert.equal(smoke.plumeCount(), MAX_PLUMES);
smoke.dispose();
});
it("respects a smaller cap and a lower density", () => {
const smoke = createFireSmoke({ span: SPAN, maxPlumes: 2, puffsPerPlume: 20, density: 1 });
smoke.setPlumes([plume("a"), plume("b"), plume("c")]);
assert.equal(smoke.plumeCount(), 2);
assert.equal((mesh(smoke.group).geometry as THREE.InstancedBufferGeometry).instanceCount, 40);
smoke.dispose();
});
it("blows downwind, not upwind", () => {
const smoke = layer();
const material = mesh(smoke.group).material as THREE.ShaderMaterial;
const wind = material.uniforms.uWind?.value as THREE.Vector2;
// Wind *from* the north travels south, and scene south is +Z.
smoke.setWind(20, 0);
assert.ok(Math.abs(wind.x) < 1e-6);
assert.ok(wind.y > 0.99, `north wind must travel +Z, got ${wind.y}`);
// Wind *from* the west travels east, and scene east is +X.
smoke.setWind(20, 270);
assert.ok(wind.x > 0.99, `west wind must travel +X, got ${wind.x}`);
assert.ok(Math.abs(wind.y) < 1e-6);
smoke.dispose();
});
it("falls back to a light breeze rather than standing still", () => {
const smoke = layer();
const material = mesh(smoke.group).material as THREE.ShaderMaterial;
const wind = material.uniforms.uWind?.value as THREE.Vector2;
smoke.setWind(null, null);
assert.ok(Math.hypot(wind.x, wind.y) > 0.99, "a null wind must still have a direction");
smoke.setWind(Number.NaN, Number.NaN);
assert.ok(Number.isFinite(wind.x) && Number.isFinite(wind.y));
smoke.dispose();
});
it("takes the fog and the key off a rig it did not compute", () => {
const smoke = layer();
const material = mesh(smoke.group).material as THREE.ShaderMaterial;
smoke.setLighting(NOON);
assert.equal(material.uniforms.uFogNear?.value, 400);
assert.equal(material.uniforms.uFogFar?.value, 1600);
assert.ok((material.uniforms.uKey?.value as number) > 0.5);
const dusk: LightingState = { ...NOON, sun: { ...NOON.sun, intensity: 0.2 } };
smoke.setLighting(dusk);
assert.ok((material.uniforms.uKey?.value as number) < 0.2);
smoke.dispose();
});
it("constructs no light — CONTRACT.md §4", () => {
const smoke = layer();
smoke.setPlumes([plume("a")]);
smoke.setLighting(NOON);
const lights: string[] = [];
smoke.group.traverse((object) => {
if ((object as THREE.Light).isLight === true) lights.push(object.type);
});
assert.deepEqual(lights, []);
smoke.dispose();
});
it("writes each plume's own shape into its own instances", () => {
const smoke = createFireSmoke({ span: SPAN, maxPlumes: 3, puffsPerPlume: 10, density: 1 });
smoke.setPlumes([
plume("a", { x: 5, y: 1, z: -7, length: 30, width: 2, density: 0.7, lift: 0.9 }),
plume("b", { x: -11, y: 2, z: 3, length: 6, width: 0.5, density: 0.3, lift: 0 }),
]);
const geometry = mesh(smoke.group).geometry as THREE.InstancedBufferGeometry;
const origin = geometry.getAttribute("iOrigin");
const shape = geometry.getAttribute("iShape");
assert.equal(origin.getX(0), 5);
assert.equal(origin.getZ(9), -7);
assert.equal(shape.getX(0), 30);
assert.ok(Math.abs(shape.getW(0) - 0.9) < 1e-6);
assert.equal(origin.getX(10), -11);
assert.equal(shape.getX(19), 6);
assert.equal(shape.getW(19), 0);
smoke.dispose();
});
it("clamps a density and a lift that arrived out of range", () => {
const smoke = createFireSmoke({ span: SPAN, maxPlumes: 1, puffsPerPlume: 4, density: 1 });
smoke.setPlumes([plume("a", { density: 9, lift: -3 })]);
const shape = (mesh(smoke.group).geometry as THREE.InstancedBufferGeometry).getAttribute(
"iShape",
);
assert.equal(shape.getZ(0), 1);
assert.equal(shape.getW(0), 0);
smoke.dispose();
});
it("stays hidden while it is switched off, however many plumes arrive", () => {
const smoke = layer();
smoke.setVisible(false);
smoke.setPlumes([plume("a"), plume("b")]);
assert.equal(mesh(smoke.group).visible, false);
smoke.setVisible(true);
assert.equal(mesh(smoke.group).visible, true);
smoke.dispose();
});
it("advances its own clock and never on a bad delta", () => {
const smoke = layer();
const material = mesh(smoke.group).material as THREE.ShaderMaterial;
smoke.tick(0.5);
const after = material.uniforms.uTime?.value as number;
assert.ok(after > 0);
smoke.tick(Number.NaN);
assert.equal(material.uniforms.uTime?.value, after, "a NaN delta must not poison the clock");
smoke.dispose();
});
it("casts no shadow and is never frustum culled", () => {
const smoke = layer();
const puffs = mesh(smoke.group);
// The bounding sphere is the unit quad's, so the CPU thinks this object is
// two units across; culling on it would cull the plume from almost anywhere.
assert.equal(puffs.frustumCulled, false);
assert.equal(puffs.castShadow, false);
assert.equal(puffs.receiveShadow, false);
smoke.dispose();
});
});
+672
View File
@@ -0,0 +1,672 @@
/**
* The fire layer, held to the four things a screenshot cannot check.
*
* **It draws nothing on a quiet board.** This is the whole round in one
* assertion. The fixture is a real body captured through the whole wire on an
* ordinary day: clipped to the SoCal board it contains twenty-two live incident
* records, every single one with `acres: null`, fifteen of them nameless LA
* County dispatch numbers. The correct picture is an empty one, and "empty"
* means `instanceCount === 0` and a mesh the renderer never visits not a mesh
* drawing twenty-two zero-sized glyphs.
*
* **It constructs no light.** CONTRACT.md §4 gives `Atmosphere` sole ownership
* of the rig, and a wildfire is the most tempting exception in the codebase. The
* build spec's `grep` catches the letter; this catches the spirit, by walking
* the whole subtree and asserting nothing in it is a `THREE.Light`.
*
* **It reads two confidence scales out of one column.** MODIS publishes an
* integer 0100 and VIIRS publishes `low`/`nominal`/`high` in the same field. A
* renderer that maps the raw value to an opacity is wrong for one of the two on
* every frame, and wrong in the direction that puts `NaN` in an alpha.
*
* **Momentum brightens and lengthens a fire that was already drawn, and never
* draws one.** Across all 665 observations in the upstream store's life not one
* incident has ever recorded two different acreage values, so this code path's
* first real execution will be in production during a fire. It is therefore
* asserted against a synthetic series here or it is not shipped.
*
* The world below is a real board projection rather than a tidy 1:1 fake
* California's `latScale: 58`, which puts one scene unit at 1,919 m. A 1:1 fake
* would pass while every plume was a thousand times too long.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import {
createFireLayer,
createMomentumTracker,
extentRadiusKm,
fireHeat,
growthPerHour,
hotPixelStyle,
markGlow,
momentumOf,
plumeLengthKm,
plumeWidthKm,
smokeLoad,
type DrawnFireMark,
type FireView,
} from "../../engine/fires.ts";
import { promote, type FirePromotion } from "../../server/fires.ts";
import type { FireLayerFactory, FireView as SceneFireView } from "../../engine/scene.ts";
import { LIVE_FIRES_BODY } from "../data/firesFixture.ts";
import type { World } from "../../engine/world.ts";
// ---- The boards, exactly as the packs declare them -------------------------
const CALIFORNIA = { minLat: 32.55, maxLat: 38.05, minLng: -123.05, maxLng: -114.0 };
const SOCAL = { minLat: 33.28, maxLat: 34.36, minLng: -118.88, maxLng: -117.22 };
/** `california.ts`: centre 35.3/-118.55, `latScale: 58` — 1,919 m to the unit. */
function board(
latScale: number,
centre: { lat: number; lng: number },
ground: (lat: number, lng: number) => number = () => 0,
): World {
const lngScale = latScale * Math.cos((centre.lat * Math.PI) / 180);
return {
project(lat: number, lng: number): [number, number] {
return [(lng - centre.lng) * lngScale, -(lat - centre.lat) * latScale];
},
groundAt: ground,
metresPerUnit: 111_320 / latScale,
metres(value: number): number {
return value / (111_320 / latScale);
},
} as unknown as World;
}
const californiaWorld = () => board(58, { lat: 35.3, lng: -118.55 });
const socalWorld = () => board(285, { lat: 33.82, lng: -118.05 });
/** The larger projected extent of the pack's bounds — what `scene.ts` passes. */
const CALIFORNIA_SPAN = 428;
const SOCAL_SPAN = 393;
const NOW = Date.parse("2026-08-22T22:26:00Z");
function promoteFor(bounds: typeof CALIFORNIA): FirePromotion {
return promote(LIVE_FIRES_BODY, bounds, NOW);
}
/**
* `FirePromotion` must flow into the layer with no adapter at all. Asserted at
* compile time, which is the only place it can be: if `promote()` ever renames a
* field the renderer reads, this line stops the build instead of quietly
* drawing an empty board.
*/
const _assignable: (p: FirePromotion) => FireView = (p) => p;
void _assignable;
/**
* And `createFireLayer` must be a `FireLayerFactory` the one seam `scene.ts`
* constructs through.
*
* A type-only import, so nothing about `scene.ts` is pulled into this test at
* runtime, and `engine/fires.ts` still imports nothing from it. That is the
* point of the arrangement: the two modules each declare their own copy of the
* shape and are checked against each other *here* and at `SceneOptions.fires`,
* rather than one of them depending on the other. Without this line the first
* time anyone found out the two had drifted would be the moment WS5 wired them.
*/
const _factory: FireLayerFactory = createFireLayer;
void _factory;
/** And the view `scene.ts` forwards must be one this layer accepts. */
const _viewIn: (v: SceneFireView) => FireView = (v) => v;
void _viewIn;
function meshNamed(root: THREE.Object3D, name: string): THREE.Mesh | THREE.Points {
const found = root.getObjectByName(name);
assert.ok(found, `no object named ${name}`);
return found as THREE.Mesh | THREE.Points;
}
function instanceCount(root: THREE.Object3D, name: string): number {
const mesh = meshNamed(root, name);
const geometry = mesh.geometry as THREE.InstancedBufferGeometry;
return geometry.instanceCount;
}
// ---- The quiet board ------------------------------------------------------
describe("the fire layer on a quiet board", () => {
it("draws nothing at all for twenty-two live records with no acreage", () => {
const gated = promoteFor(SOCAL);
// The fixture is the claim: this is a real day, not a constructed one.
assert.equal(gated.drawn.length, 0, "the gate must refuse every SoCal row today");
assert.equal(gated.suppressed, 22, "and there must be twenty-two of them to refuse");
const layer = createFireLayer(socalWorld(), { span: SOCAL_SPAN, reducedMotion: true });
layer.setFires(gated);
assert.equal(layer.markCount(), 0);
assert.equal(layer.plumeCount(), 0);
assert.equal(instanceCount(layer.group, "fire-marks"), 0);
// Invisible, not merely empty. An empty-but-visible mesh is a draw call the
// renderer still pays for on a board where nothing is happening.
assert.equal(meshNamed(layer.group, "fire-marks").visible, false);
assert.equal(meshNamed(layer.group, "fire-smoke-puffs").visible, false);
layer.dispose();
});
it("clears back to empty when the feed goes away", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
layer.setFires(promoteFor(CALIFORNIA));
assert.ok(layer.markCount() > 0, "California must draw something to begin with");
layer.setFires(null);
assert.equal(layer.markCount(), 0);
assert.equal(layer.detectionCount(), 0);
assert.equal(meshNamed(layer.group, "fire-marks").visible, false);
assert.equal(meshNamed(layer.group, "fire-hot-pixels").visible, false);
layer.dispose();
});
it("survives a null, an undefined and a body full of nonsense", () => {
const layer = createFireLayer(socalWorld(), { span: SOCAL_SPAN, reducedMotion: true });
layer.setFires(null);
layer.setFires(undefined as unknown as FireView);
layer.setFires({
drawn: [null, { id: "x" }] as unknown as readonly DrawnFireMark[],
detections: [null, { sat: "MODIS" }] as unknown as FireView["detections"],
fetchedAt: "not a date",
ageMs: null,
});
// The consumer is a render loop. A throw here is a black page, so the only
// acceptable behaviour for a shape we did not build is to draw less.
assert.ok(layer.markCount() <= 1);
layer.dispose();
});
});
// ---- The truthful full board ----------------------------------------------
describe("the fire layer on the California board", () => {
it("draws exactly the five fires the gate admitted, worst first", () => {
const gated = promoteFor(CALIFORNIA);
assert.deepEqual(
gated.drawn.map((fire) => fire.name),
["Timber Fire", "Alpaugh Fire", "Carrizo Fire", "Amber Fire", "GREEN"],
);
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
layer.setFires(gated);
assert.equal(layer.markCount(), 5);
assert.equal(instanceCount(layer.group, "fire-marks"), 5);
assert.equal(meshNamed(layer.group, "fire-marks").visible, true);
layer.dispose();
});
it("gives a plume to every tier-2 fire and to no tier-1 fire", () => {
const gated = promoteFor(CALIFORNIA);
const tier2 = gated.drawn.filter((fire) => fire.tier === 2);
assert.deepEqual(
tier2.map((fire) => fire.name),
["Timber Fire", "Alpaugh Fire", "Carrizo Fire"],
"tier is `promote()`'s decision and this layer must not re-derive it",
);
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
layer.setFires(gated);
assert.equal(layer.plumeCount(), 3);
for (const fire of gated.drawn) {
const state = layer.inspect(fire.id);
assert.ok(state, `${fire.name} must be inspectable`);
if (fire.tier === 2) assert.ok(state.plumeUnits > 0, `${fire.name} must have a plume`);
else assert.equal(state.plumeUnits, 0, `${fire.name} must not have a plume`);
}
layer.dispose();
});
it("gives the biggest fire the longest plume, and caps it", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
const gated = promoteFor(CALIFORNIA);
layer.setFires(gated);
const timber = layer.inspect(gated.drawn[0]?.id ?? "");
const carrizo = layer.inspect(gated.drawn[2]?.id ?? "");
assert.ok(timber && carrizo);
assert.ok(timber.plumeUnits > carrizo.plumeUnits);
// 40 km is the hard cap, and California's board is 1,919 m to the unit.
assert.ok(timber.plumeUnits <= (40 * 1000) / (111_320 / 58) + 1e-6);
layer.dispose();
});
it("draws the hot pixels the gate handed it, and not one more", () => {
const gated = promoteFor(CALIFORNIA);
assert.ok(gated.detections.length > 0);
assert.ok(gated.persistentDetections > 0, "the fixture must contain known furniture");
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
layer.setFires(gated);
assert.equal(layer.detectionCount(), gated.detections.length);
assert.equal(meshNamed(layer.group, "fire-hot-pixels").visible, true);
layer.dispose();
});
});
// ---- CONTRACT §4 ----------------------------------------------------------
describe("the fire layer and the light rig", () => {
it("constructs no THREE.Light anywhere in its subtree", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
layer.setFires(promoteFor(CALIFORNIA));
layer.setSolarElevation(-12);
const lights: string[] = [];
layer.group.traverse((object) => {
if ((object as THREE.Light).isLight === true) lights.push(object.type);
});
assert.deepEqual(lights, [], "Atmosphere is the sole light owner — CONTRACT.md §4");
layer.dispose();
});
it("takes night from the solar elevation seam and nothing else", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
const material = (meshNamed(layer.group, "fire-marks") as THREE.Mesh)
.material as THREE.ShaderMaterial;
layer.setSolarElevation(40);
assert.equal(material.uniforms.uNight?.value, 0, "noon is not night");
layer.setSolarElevation(-12);
assert.equal(material.uniforms.uNight?.value, 1, "well after sunset is fully night");
layer.setSolarElevation(Number.NaN);
assert.equal(material.uniforms.uNight?.value, 1, "a bad number must change nothing");
layer.dispose();
});
});
// ---- Hot pixels are evidence ----------------------------------------------
describe("hot-pixel styling", () => {
const base = { lat: 36.2, lon: -121.7, acquiredAt: "2026-08-22T21:00:00Z", frp: 12 };
it("reads VIIRS confidence off the string scale", () => {
const low = hotPixelStyle({ ...base, sat: "VIIRS-NOAA20", confidence: "low", persistent: false });
const nominal = hotPixelStyle({
...base,
sat: "VIIRS-SNPP",
confidence: "nominal",
persistent: false,
});
const high = hotPixelStyle({ ...base, sat: "VIIRS-NOAA20", confidence: "high", persistent: false });
assert.ok(low.opacity < nominal.opacity);
assert.ok(nominal.opacity < high.opacity);
// The failure this exists to stop: `Number("nominal")` is NaN, and NaN in an
// alpha is a hole in the frame rather than a dim dot.
for (const style of [low, nominal, high]) {
assert.ok(Number.isFinite(style.opacity), "a VIIRS string must never become NaN");
}
});
it("reads MODIS confidence off the 0-100 integer scale", () => {
const weak = hotPixelStyle({ ...base, sat: "MODIS", confidence: "26", persistent: false });
const strong = hotPixelStyle({ ...base, sat: "MODIS", confidence: "100", persistent: false });
assert.ok(weak.opacity < strong.opacity);
// The same literal means opposite things on the two instruments, which is
// the entire reason the branch exists: "100" is full confidence on MODIS and
// is not a VIIRS value at all.
const viirs = hotPixelStyle({ ...base, sat: "VIIRS-SNPP", confidence: "100", persistent: false });
assert.notEqual(viirs.opacity, strong.opacity);
});
it("sizes by fire radiative power, so a cluster has structure in it", () => {
const faint = hotPixelStyle({ ...base, frp: 1, sat: "MODIS", confidence: "80", persistent: false });
const fierce = hotPixelStyle({
...base,
frp: 112.9,
sat: "MODIS",
confidence: "80",
persistent: false,
});
assert.ok(fierce.size > faint.size);
});
it("visibly demotes a persistent source rather than dropping it", () => {
const fresh = hotPixelStyle({
...base,
frp: 1.0,
sat: "VIIRS-NOAA20",
confidence: "nominal",
persistent: false,
});
// The industrial heat source 4.7 km from the upstream operator's house: FRP
// ~1.0, "nominal", on every pass on every day the store holds, with no
// incident behind it.
const furniture = hotPixelStyle({
...base,
frp: 1.0,
sat: "VIIRS-NOAA20",
confidence: "nominal",
persistent: true,
});
assert.ok(furniture.opacity < fresh.opacity * 0.5, "a flare stack must not read as a fire");
assert.ok(furniture.size < fresh.size);
assert.notEqual(furniture.color, fresh.color);
assert.ok(furniture.opacity > 0, "counted and drawn faintly, never silently dropped");
});
it("survives a missing confidence and a missing FRP", () => {
const style = hotPixelStyle({ ...base, frp: null, sat: "MODIS", confidence: null, persistent: false });
assert.ok(Number.isFinite(style.opacity) && style.opacity > 0);
assert.ok(Number.isFinite(style.size) && style.size > 0);
});
});
// ---- Momentum -------------------------------------------------------------
describe("momentum", () => {
it("is zero for a fire that has never restated its acreage", () => {
// Which is every fire the upstream store has ever held: across all 665
// observations in its life, no incident has recorded two different acreages.
const tracker = createMomentumTracker();
for (let i = 0; i < 6; i++) tracker.observe("f", NOW + i * 600_000, 7591);
assert.equal(tracker.rateFor("f"), 0);
assert.equal(momentumOf(tracker.rateFor("f")), 0);
});
it("ignores a repeated observation, so one restatement is not a spike", () => {
const tracker = createMomentumTracker();
tracker.observe("f", NOW, 100);
for (let i = 0; i < 20; i++) tracker.observe("f", NOW, 100);
tracker.observe("f", NOW + 3_600_000, 150);
assert.equal(tracker.samples("f").length, 2);
assert.ok(Math.abs(tracker.rateFor("f") - 0.5) < 1e-9, "50 % in an hour is a rate of 0.5");
});
it("forgets a fire the gate has dropped", () => {
const tracker = createMomentumTracker();
tracker.observe("a", NOW, 100);
tracker.observe("b", NOW, 100);
tracker.retain(["a"]);
assert.equal(tracker.samples("a").length, 1);
assert.equal(tracker.samples("b").length, 0);
});
it("brightens and lengthens without changing the drawn set", () => {
/**
* Six samples over five hours, four times, each ending at **the same**
* acreage and starting lower. So the only thing that differs between the
* runs is the growth *rate* the acreage the plume is sized from is
* identical, which is what makes this a test of momentum rather than of
* arithmetic on `acres`.
*/
const finalAcres = 500;
const starts = [500, 460, 380, 240];
const world = californiaWorld();
const lengths: number[] = [];
const glows: number[] = [];
const drawnSets: string[][] = [];
for (const start of starts) {
const layer = createFireLayer(world, { span: CALIFORNIA_SPAN, reducedMotion: true });
for (let i = 0; i < 6; i++) {
const acres = start + ((finalAcres - start) * i) / 5;
const at = new Date(NOW + i * 3_600_000).toISOString();
layer.setFires({
drawn: [
{
id: "synthetic",
name: "Synthetic Fire",
lat: 36.0,
lon: -120.0,
acres,
pctContained: 20,
tier: 2,
observedAt: at,
},
],
detections: [],
fetchedAt: at,
ageMs: 0,
});
}
const state = layer.inspect("synthetic");
assert.ok(state, "the synthetic fire must be drawn in every run");
lengths.push(state.plumeUnits);
glows.push(markGlow(state.heat, state.momentum, 1));
drawnSets.push([...Array(layer.markCount()).keys()].map(() => "synthetic"));
assert.equal(layer.plumeCount(), 1);
layer.dispose();
}
for (let i = 1; i < lengths.length; i++) {
assert.ok(
(lengths[i] ?? 0) > (lengths[i - 1] ?? 0),
`plume length must rise with growth rate (${lengths.join(", ")})`,
);
assert.ok(
(glows[i] ?? 0) > (glows[i - 1] ?? 0),
`emissive must rise with growth rate (${glows.join(", ")})`,
);
assert.deepEqual(
drawnSets[i],
drawnSets[i - 1],
"momentum modifies a fire that already passed the gate — it never promotes one",
);
}
});
it("caps: a fire that trebles in an hour is not drawn twice as long", () => {
assert.equal(momentumOf(0.35), 1);
assert.equal(momentumOf(3.0), 1);
assert.equal(momentumOf(-1), 0);
assert.equal(momentumOf(Number.NaN), 0);
});
it("refuses a series it cannot read", () => {
assert.equal(growthPerHour([]), 0);
assert.equal(growthPerHour([{ atMs: NOW, acres: 10 }]), 0);
assert.equal(growthPerHour([{ atMs: NOW, acres: 0 }, { atMs: NOW + 3_600_000, acres: 10 }]), 0);
assert.equal(growthPerHour([{ atMs: NOW, acres: 10 }, { atMs: NOW, acres: 20 }]), 0);
// Shrinking is not negative momentum. An agency revising an estimate down is
// not a fire going out, and a plume that got shorter would say it was.
assert.equal(growthPerHour([{ atMs: NOW, acres: 20 }, { atMs: NOW + 3_600_000, acres: 10 }]), 0);
});
});
// ---- The arithmetic -------------------------------------------------------
describe("fire arithmetic", () => {
it("turns acreage into the radius of an equal-area disc", () => {
// Bug Fire: 93,733 acres is 379 km², an 11 km radius, a disc 22 km across.
assert.ok(Math.abs(extentRadiusKm(93_733) - 11.0) < 0.15);
assert.ok(Math.abs(extentRadiusKm(10) - 0.113) < 0.005);
assert.equal(extentRadiusKm(0), 0);
assert.equal(extentRadiusKm(Number.NaN), 0);
});
it("keeps a small fire's plume small", () => {
// The design's own warning: "a 268-acre fire with a 40 km plume is a lie
// told in a medium that reads as truthful".
const carrizo = plumeLengthKm(268.1, 14);
assert.ok(carrizo > 2 && carrizo < 8, `Carrizo got ${carrizo} km`);
const timber = plumeLengthKm(7591, 14);
assert.ok(timber > 20 && timber <= 40, `Timber got ${timber} km`);
assert.equal(plumeLengthKm(1e9, 200), 40, "the cap is hard");
assert.equal(plumeLengthKm(0, 14), 0);
});
it("lengthens a plume in the wind and widens it with the fire", () => {
assert.ok(plumeLengthKm(500, 40) > plumeLengthKm(500, 4));
assert.ok(plumeWidthKm(7591) > plumeWidthKm(268));
assert.ok(plumeWidthKm(1e9) <= 3.5);
});
it("reads heat off acreage and containment, and never off nothing", () => {
assert.ok(fireHeat(7591, 29) > fireHeat(268, 29));
assert.ok(fireHeat(500, 0) > fireHeat(500, 70), "a fire being beaten is cooler");
// `null` containment means "the agency has not said", which is not zero and
// must not read as a fire nobody has touched.
assert.equal(fireHeat(500, null), fireHeat(500, 0));
for (const acres of [10, 100, 1000, 100_000]) {
const heat = fireHeat(acres, 50);
assert.ok(heat >= 0 && heat <= 1);
}
});
it("mirrors the shader's glow term and rises with both inputs", () => {
assert.ok(markGlow(1, 0, 1) > markGlow(0, 0, 1));
assert.ok(markGlow(0.5, 1, 1) > markGlow(0.5, 0, 1));
assert.ok(markGlow(0.5, 0, 1) > markGlow(0.5, 0, 0), "night is what makes it glow");
});
});
// ---- The scalar the office reads ------------------------------------------
describe("smoke load", () => {
const fire: DrawnFireMark = {
id: "f",
name: "Test",
lat: 34.3,
lon: -118.0,
acres: 5000,
pctContained: 10,
tier: 2,
observedAt: null,
};
it("is zero with nothing burning", () => {
assert.equal(smokeLoad([], 34.0, -118.2, 250), 0);
});
it("is zero beyond the reach, however big the fire", () => {
assert.equal(smokeLoad([{ ...fire, acres: 1e6 }], 40.0, -118.0, null), 0);
});
it("is higher downwind than upwind of the same fire, at the same distance", () => {
// Wind from the north (0°) blows smoke south, so a point south of the fire
// is in it and a point the same distance north is not.
const south = smokeLoad([fire], fire.lat - 0.3, fire.lon, 0);
const north = smokeLoad([fire], fire.lat + 0.3, fire.lon, 0);
assert.ok(south > north, `downwind ${south} must beat upwind ${north}`);
assert.ok(north > 0, "an upwind floor exists because wind is a ten-minute average");
});
it("falls off with distance and stays inside 0..1", () => {
const near = smokeLoad([fire], fire.lat - 0.05, fire.lon, 0);
const far = smokeLoad([fire], fire.lat - 0.6, fire.lon, 0);
assert.ok(near > far);
const many = smokeLoad(
Array.from({ length: 12 }, (_, i) => ({ ...fire, id: `f${i}`, acres: 90_000 })),
fire.lat - 0.05,
fire.lon,
0,
);
assert.ok(many <= 1 && many > 0.5);
});
it("is what the layer reports, wind and all", () => {
const layer = createFireLayer(socalWorld(), { span: SOCAL_SPAN, reducedMotion: true });
layer.setWind(20, 0);
layer.setFires({ drawn: [fire], detections: [], fetchedAt: "2026-08-22T22:20:00Z", ageMs: 0 });
assert.ok(layer.smokeLoadAt(fire.lat - 0.3, fire.lon) > 0);
layer.setFires(null);
assert.equal(layer.smokeLoadAt(fire.lat - 0.3, fire.lon), 0);
layer.dispose();
});
});
// ---- Cost -----------------------------------------------------------------
describe("what the fire layer costs", () => {
it("is three objects, whatever is burning", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
layer.setFires(promoteFor(CALIFORNIA));
const drawable: string[] = [];
layer.group.traverse((object) => {
if ((object as THREE.Mesh).isMesh === true || (object as THREE.Points).isPoints === true) {
drawable.push(object.name);
}
});
// One instanced mesh for the marks and their ground extent, one Points for
// the hot pixels, one instanced mesh for every plume there will ever be.
assert.deepEqual(drawable.sort(), ["fire-hot-pixels", "fire-marks", "fire-smoke-puffs"]);
layer.dispose();
});
it("packs the ground extent into the mark's own geometry", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
reducedMotion: true,
});
const geometry = meshNamed(layer.group, "fire-marks").geometry;
const index = geometry.getIndex();
assert.ok(index);
// Six for the ember, two for the disc. Two triangles a fire and no second
// draw call is the whole reason the extent can exist at all.
assert.equal(index.count / 3, 8);
assert.ok(geometry.getAttribute("aPart"), "the per-vertex part selector must be there");
layer.dispose();
});
it("never exceeds its instance capacity, whatever an upstream sends", () => {
const layer = createFireLayer(californiaWorld(), {
span: CALIFORNIA_SPAN,
maxFires: 8,
maxDetections: 16,
reducedMotion: true,
});
layer.setFires({
drawn: Array.from({ length: 400 }, (_, i) => ({
id: `f${i}`,
name: null,
lat: 35 + (i % 20) * 0.05,
lon: -119 + (i % 17) * 0.05,
acres: 200,
pctContained: null,
tier: 2 as const,
observedAt: null,
})),
detections: Array.from({ length: 900 }, (_, i) => ({
sat: "MODIS",
lat: 35 + (i % 30) * 0.02,
lon: -119 + (i % 23) * 0.02,
frp: 5,
confidence: "60",
persistent: false,
acquiredAt: "2026-08-22T21:00:00Z",
})),
fetchedAt: "2026-08-22T22:20:00Z",
ageMs: 0,
});
assert.equal(layer.markCount(), 8);
assert.equal(layer.detectionCount(), 16);
assert.ok(layer.plumeCount() <= 8);
layer.dispose();
});
});
+84
View File
@@ -99,3 +99,87 @@ describe("glyph scale", () => {
}
});
});
/*
* The focus-distance clamp the complete fix the ceiling above was a mitigation
* for.
*
* Every assertion in the suite above is left exactly as it was, and that is the
* point of the first test here: the third parameter is optional and omitting it
* has to reproduce the previous answer to the bit, not to a rounding. A ceiling
* that moved by a hair under this change would be silent visual drift in the one
* function whose entire job is to prevent silent visual drift.
*/
describe("glyph scale, clamped against what the camera is looking at", () => {
it("reproduces the two-argument answer exactly when no focus distance is given", () => {
for (const fov of [42, 50, 60]) {
for (const d of [0.5, 5, 34, 200, 400, 1160, 2280, 4000, 1e9]) {
assert.equal(
glyphScale(d, fov, undefined),
glyphScale(d, fov),
`omitting the focus distance changed the answer at ${d} units and ${fov} degrees`,
);
}
}
});
it("ignores a focus distance that is not a usable number", () => {
for (const bad of [0, -1, NaN, Infinity]) {
assert.equal(
glyphScale(2280, 50, bad),
glyphScale(2280, 50),
`a focus distance of ${bad} must fall back to the flat ceiling`,
);
}
});
it("leaves the whole-board pose alone, where the floor is right about everything", () => {
/*
* At a standoff the camera's target and the aircraft are the same distance
* away to within a third, so the clamp must not bind. 1,160 units is the far
* end of the California orbit; the aircraft on that board run from about 900
* to about 1,400.
*/
for (const fov of [42, 50, 60]) {
for (const aircraft of [900, 1160, 1400]) {
assert.equal(
glyphScale(aircraft, fov, 1160),
glyphScale(aircraft, fov),
`the clamp bound at a whole-board pose (${aircraft} units, ${fov} degrees)`,
);
}
}
});
it("collapses the Golden Gate case, which the flat ceiling only softened", () => {
// The measured chapter: the bridge fifteen units off the camera, the traffic
// over the Pacific two thousand two hundred and eighty away.
const flat = glyphScale(2280, 50);
const focused = glyphScale(2280, 50, 15);
assert.ok(focused < flat / 5, `focus clamp barely moved the worst case: ${focused} vs ${flat}`);
assert.ok(focused >= 1, "and it must never shrink the glyph below its authored size");
});
it("is monotonic in the focus distance: looking further away never shrinks the glyph", () => {
let previous = 0;
for (const focus of [1, 5, 15, 34, 100, 300, 1160, 5000]) {
const s = glyphScale(2280, 50, focus);
assert.ok(s >= previous, `scale fell as the camera focused further out, at ${focus}`);
previous = s;
}
assert.equal(
previous,
glyphScale(2280, 50),
"and at a focus distance past the backstop it must equal the unclamped answer",
);
});
it("never exceeds the absolute backstop, whatever focus distance it is handed", () => {
for (const focus of [1e6, 1e9]) {
assert.ok(
glyphScale(1e9, 50, focus) <= glyphScale(1e9, 50),
"the focus clamp must be a ceiling on top of the old one, never a lift",
);
}
});
});
+223
View File
@@ -0,0 +1,223 @@
/**
* The infrastructure that switches itself on after dark, and the two things
* about it a picture cannot check.
*
* A screenshot tells you a bridge is lit. It does not tell you the lamps are on
* the deck *this* build drew rather than on a deck computed a second time and
* a metre out, and it does not tell you they will be in the same place in the
* 2x render as they were in the 1x preview which is the property the capture
* scripts rely on when they shoot the same frame at two resolutions and compare
* them. Both are asserted here.
*
* The world below is San Francisco's real projection: one scene unit is 94.34 m
* and heights carry the pack's 3.6x exaggeration. A tidy 1:1 fake would pass
* while a lamp sat 3.6 times too high over the roadway, which is precisely the
* class of bug `bridges.ts` already has a comment about.
*/
import assert from "node:assert/strict";
import test from "node:test";
import { bridgeLights, planBridge } from "../../engine/bridges.ts";
import { MODEL_X_METRICS, buildHeadlampPool } from "../../assets/vehicles/modelX.ts";
import type { Bridge } from "../../engine/types.ts";
import type { World } from "../../engine/world.ts";
const LAT_SCALE = 1180;
const CENTRE = { lat: 37.7749, lng: -122.4194 };
const METRES_PER_UNIT = 111_320 / LAT_SCALE;
const EXAGGERATION = 3.6;
function board(latScale: number, exaggeration: number, ground = () => 0): World {
const metresPerUnit = 111_320 / latScale;
const lngScale = latScale * Math.cos((CENTRE.lat * Math.PI) / 180);
return {
project(lat: number, lng: number): [number, number] {
return [(lng - CENTRE.lng) * lngScale, -(lat - CENTRE.lat) * latScale];
},
groundAt: ground,
metres(value: number): number {
return (value / metresPerUnit) * exaggeration;
},
metresPerUnit,
} as unknown as World;
}
const bayWorld = () => board(LAT_SCALE, EXAGGERATION);
/** The pack's Golden Gate, coordinate for coordinate. */
const GOLDEN_GATE: Bridge = {
name: "Golden Gate Bridge",
path: [
[37.8025, -122.4752],
[37.8106, -122.4775],
[37.8155, -122.4783],
[37.825, -122.479],
[37.8325, -122.4798],
[37.8375, -122.4806],
],
towers: [
[37.8155, -122.4783],
[37.825, -122.479],
],
towerHeight: 227,
deckHeight: 67,
sag: 0.55,
color: 0xc0442c,
};
/** Metres, matching the constants `bridgeLights` is written against. */
const DECK_LAMP_SPACING_M = 50;
const DECK_LAMP_HEIGHT_M = 12;
const HEAD_LIGHT_CLEARANCE_M = 6;
function triples(flat: readonly number[]): [number, number, number][] {
const out: [number, number, number][] = [];
for (let i = 0; i + 2 < flat.length; i += 3) {
out.push([flat[i] as number, flat[i + 1] as number, flat[i + 2] as number]);
}
return out;
}
// ---- The deck run ----------------------------------------------------------
test("the deck lamps stand on the deck, at lamp height, in pairs", () => {
const world = bayWorld();
const lit = bridgeLights(world, GOLDEN_GATE);
const lamps = triples(lit.deck);
assert.ok(lamps.length >= 80, `a 2.7 km crossing wants a real run of lamps, got ${lamps.length}`);
assert.equal(lamps.length % 2, 0, "lamps come one per kerb, so the count is even");
// The deck is flat at `deckHeight` everywhere except the ramps at each end,
// so the great majority of lamps sit at exactly deck + lamp height. That is
// the assertion that fails the moment somebody recomputes the deck profile
// somewhere else and the two answers drift.
const deckY = world.metres(GOLDEN_GATE.deckHeight);
const expected = deckY + world.metres(DECK_LAMP_HEIGHT_M);
const onTheFlat = lamps.filter(([, y]) => Math.abs(y - expected) < 1e-6);
assert.ok(
onTheFlat.length > lamps.length * 0.6,
`most lamps belong on the flat deck at ${expected.toFixed(4)}; ${onTheFlat.length} of ${lamps.length} were`,
);
// The rest are on the ramps at each end, where the deck comes down onto the
// shore — so the run is bounded above by the flat deck's lamp height and below
// by a lamp standing on the landing. Neither bound is decoration: the first
// catches a lamp that has floated up to the cable, the second one that has
// sunk into the roadway.
const lift = world.metres(DECK_LAMP_HEIGHT_M);
for (const [, y] of lamps) {
assert.ok(y <= expected + 1e-6, `a deck lamp never rises above the deck run (${y})`);
assert.ok(y >= lift - 1e-6, `a deck lamp is above whatever the deck is resting on (${y})`);
assert.ok(y < world.metres(GOLDEN_GATE.towerHeight), "a deck lamp is not a tower light");
}
});
test("the lamp run is evenly spaced along the whole crossing", () => {
const world = bayWorld();
const lamps = triples(bridgeLights(world, GOLDEN_GATE).deck);
const spacing = DECK_LAMP_SPACING_M / METRES_PER_UNIT;
// One pair per station along the deck: walk the left kerb only.
const left = lamps.filter((_, index) => index % 2 === 0);
const plan = planBridge(world, GOLDEN_GATE);
const covered = (left.length - 1) * spacing;
assert.ok(
covered > plan.deckLength * 0.9,
`the run should reach both ends: covered ${covered.toFixed(2)} of ${plan.deckLength.toFixed(2)} units`,
);
for (let i = 1; i < left.length; i += 1) {
const a = left[i - 1];
const b = left[i];
if (!a || !b) continue;
const step = Math.hypot(b[0] - a[0], b[2] - a[2]);
assert.ok(
step > spacing * 0.5 && step < spacing * 1.6,
`lamp ${i} is ${step.toFixed(3)} units from the last, against a ${spacing.toFixed(3)} pitch`,
);
}
});
// ---- The tower heads -------------------------------------------------------
test("each tower carries two obstruction lights, over the saddle", () => {
const world = bayWorld();
const lit = bridgeLights(world, GOLDEN_GATE);
const heads = triples(lit.heads);
assert.equal(heads.length, GOLDEN_GATE.towers.length * 2, "one light per tower leg");
const expected = world.metres(GOLDEN_GATE.towerHeight) + world.metres(HEAD_LIGHT_CLEARANCE_M);
for (const [, y] of heads) {
assert.ok(Math.abs(y - expected) < 1e-6, `a head light belongs at ${expected.toFixed(4)}, got ${y}`);
}
// The pair straddles the deck: two legs, `deckHalf` either side of the centre.
const [a, b] = heads;
assert.ok(a && b);
const across = Math.hypot(b[0] - a[0], b[2] - a[2]);
assert.ok(
Math.abs(across - lit.scale * 2) < 1e-6,
`the legs are a deck apart: ${across.toFixed(4)} against ${(lit.scale * 2).toFixed(4)}`,
);
});
// ---- Determinism -----------------------------------------------------------
test("the same bridge lights identically twice — the capture scripts rely on it", () => {
const world = bayWorld();
const first = bridgeLights(world, GOLDEN_GATE);
const second = bridgeLights(world, GOLDEN_GATE);
assert.deepEqual(first.deck, second.deck);
assert.deepEqual(first.heads, second.heads);
assert.equal(first.scale, second.scale);
});
// ---- Board scale -----------------------------------------------------------
test("a coarser board gets a smaller lamp, because it draws a smaller bridge", () => {
// San Francisco at 94 m to the unit against Los Angeles at 391. A sprite size
// in scene units tuned on the first would be several times the width of the
// deck on the second, which is the bug `socal.ts` asked the kit to stop having.
const fine = bridgeLights(board(1180, 3.6), GOLDEN_GATE);
const coarse = bridgeLights(board(285, 2.2), GOLDEN_GATE);
assert.ok(coarse.scale < fine.scale, "a coarser board draws a narrower deck");
assert.ok(coarse.scale >= 0.09, "…but never below the legibility floor");
// The run still reaches both ends of the crossing; it is the pitch floor that
// keeps a 391 m/unit board from putting four lamps on a whole bridge.
assert.ok(triples(coarse.deck).length >= 8, "a coarse board still gets a run, not a handful");
});
// ---- The headlamp pool -----------------------------------------------------
test("the headlamp pool lies ahead of the nose, and is off until night", () => {
const pool = buildHeadlampPool({ reachM: 26 });
pool.mesh.geometry.computeBoundingBox();
const box = pool.mesh.geometry.boundingBox;
assert.ok(box);
// The nose is at -Z. The pool starts at the bumper and runs forward from it,
// so every vertex is in front of the car and none is behind the rear axle.
const nose = -MODEL_X_METRICS.length / 2;
assert.ok(box.max.z <= nose + 1e-6, `the pool starts at the bumper (${box.max.z} vs ${nose})`);
assert.ok(Math.abs(box.min.z - (nose - 26)) < 1e-6, "…and reaches the asked-for range");
// Flat on the road, not a wall.
assert.ok(Math.abs(box.max.y - box.min.y) < 1e-6, "the pool is flat");
assert.equal(pool.mesh.visible, false, "nothing is drawn before a night level arrives");
pool.setIntensity(0);
assert.equal(pool.mesh.visible, false, "…and nothing at broad daylight either");
pool.dispose();
});
test("the pool draws nothing at all with no DOM to draw its beam pattern into", () => {
// Every bit of shape this thing has is in the texture's alpha, so a pool built
// without one would be a hard-edged glowing rectangle across the carriageway.
// Headless is exactly where that would go unseen, which is why it is asserted
// here rather than left to a screenshot.
assert.equal(typeof document, "undefined", "this test only means anything headless");
const pool = buildHeadlampPool();
pool.setIntensity(1);
assert.equal(pool.mesh.visible, false);
pool.dispose();
});
+313
View File
@@ -0,0 +1,313 @@
/**
* The night sky: the cloud deck's level, and the moon as a drawn object.
*
* Both of these are judged by a picture and neither can be asserted from one, so
* what is pinned here is the arithmetic the picture turns on. The defect this
* suite is downstream of is worth restating, because it is the reason a test
* that only checks "the layer exists" would have passed on the broken build:
*
* > At 22:30 PDT over San Francisco, on a keyless clone, the cloud deck lifted a
* > night frame's mean luminance from 25.3 to 44.3 out of 255 **without adding a
* > single readable shape**. Its shaded flank was measurably brighter than its
* > moonward one. Every existing assertion about that layer passed.
*
* So the three things asserted are the three things that were wrong:
*
* 1. The deck's level after dark is a fraction of what it is at noon, rather
* than being inflated by a hemisphere intensity that `atmosphere.ts`
* deliberately *raises* at night to keep the world off black.
* 2. The moonward flank of a cloud top is brighter than the flank facing away,
* which is what "modelled" means and what a flat wash is missing.
* 3. Daylight did not move. The day was never the defect and a fix that
* changed it would be a different regression in the same file.
*
* And the moon is asserted where it is computed rather than where it is drawn:
* a disc in a sky-dome fragment shader cannot be inspected without a GL context,
* but the record `atmosphere.ts` hands the shader is a plain object with a
* position, a phase and a bright-limb axis in it, and every way that record can
* be wrong is visible from the outside.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import {
createAtmosphere,
observe,
PACIFIC_MARINE_LAYER,
nightFactor,
} from "../../engine/atmosphere.ts";
import type { LightingMoon } from "../../engine/types.ts";
/** San Francisco, which is the board the marine layer was written for. */
const SF = { lat: 37.7749, lng: -122.4194 };
/** Rec.709 luminance, the same one `clouds.ts` derives its levels with. */
function luminance(hex: number): number {
const c = new THREE.Color().setHex(hex);
return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
}
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
const smoothstep = (e0: number, e1: number, x: number) => {
const t = clamp((x - e0) / (e1 - e0), 0, 1);
return t * t * (3 - 2 * t);
};
/*
* `clouds.ts`'s own arithmetic, restated.
*
* Restated rather than imported because `createCloudLayer` needs a `World`, a
* WebGL context and a canvas to hand back a `setLighting` at all, and none of
* those has an opinion about the numbers. What is under test is the *shape* of
* the relationship night is a fraction of day, lit beats shade and both
* halves would have to be edited together for this suite to go quiet while the
* picture went wrong, which is the bar a restatement has to clear.
*/
const SKY_DAY_REFERENCE = 0.838;
const SKY_DAY_GAIN = 0.772;
const NIGHT_SKY_LOW = 0.09;
const NIGHT_SKY_HIGH = 0.42;
const NIGHT_TOP_FLOOR = 0.01;
const NIGHT_TOP_KEY = 0.155;
const NIGHT_SHADE_SKY = 0.1;
const NIGHT_SHADE_AMBIENT = 0.1;
interface Deck {
/** Sky-dome luminance, the term the whole normalisation now rides on. */
skyLum: number;
night: number;
/** Luminance of the moonward (or sunward) flank. */
lit: number;
/** Luminance of the flank facing away. */
shade: number;
}
function deckAt(when: Date, marine = PACIFIC_MARINE_LAYER): Deck {
const atmosphere = createAtmosphere({
lng: SF.lng,
metresPerUnit: 94,
marineLayer: marine,
});
const state = atmosphere.apply(observe(SF.lat, SF.lng, when));
const skyLum = luminance(state.hemisphere.sky);
const day = clamp(skyLum / SKY_DAY_REFERENCE, 0, 1);
const night = 1 - smoothstep(NIGHT_SKY_LOW, NIGHT_SKY_HIGH, skyLum);
const key = clamp(state.sun.intensity / 2.1, 0, 1);
const litScale =
(1 - night) * (0.06 + SKY_DAY_GAIN * day) + night * (NIGHT_TOP_FLOOR + NIGHT_TOP_KEY * key);
const lit = luminance(state.sun.color) * litScale;
const shade =
skyLum * state.hemisphere.intensity * ((1 - night) * 0.55 + night * NIGHT_SHADE_SKY) +
luminance(state.ambient.color) *
state.ambient.intensity *
((1 - night) * 0.7 + night * NIGHT_SHADE_AMBIENT);
return { skyLum, night, lit, shade };
}
/** 22:30 PDT on 23 August 2026 — the exact frame the defect was measured in. */
const THE_NIGHT = new Date("2026-08-23T05:30:00Z");
/** 13:00 PDT the same day. */
const THE_NOON = new Date("2026-08-23T20:00:00Z");
describe("the cloud deck after dark", () => {
it("is a small fraction of its daylight level, not most of it", () => {
const night = deckAt(THE_NIGHT);
const noon = deckAt(THE_NOON);
assert.ok(night.night > 0.99, "the test's own night frame must actually be night");
assert.ok(
night.lit < noon.lit * 0.05,
`a night cloud top must be a small fraction of a noon one: ${night.lit} vs ${noon.lit}`,
);
assert.ok(
night.shade < noon.shade * 0.1,
`and so must its shaded flank: ${night.shade} vs ${noon.shade}`,
);
});
it("has a light direction in it — the moonward flank beats the one facing away", () => {
/*
* This is the assertion that would have failed on the old build. Measured
* there: lit 0.072 against shade 0.084, i.e. the side facing the moon was
* *darker* than the side facing away, which is not a lighting bug so much as
* the absence of lighting. A deck with no direction in it is the flat milky
* wash the file header calls blue soup.
*/
const night = deckAt(THE_NIGHT);
assert.ok(
night.lit > night.shade,
`the moonward flank must be the brighter one: lit ${night.lit}, shade ${night.shade}`,
);
});
it("still lands above black on a moonless overcast night", () => {
/*
* The opposite failure, and the one the file's own comment warns about:
* `0.06 is not black`. `NIGHT_TOP_FLOOR` exists so that a deck with no moon
* on it is dark rather than absent, and a layer that vanishes is a different
* bug report about the same file.
*/
const night = deckAt(THE_NIGHT);
assert.ok(night.lit > 0, "a night deck must still be drawn");
assert.ok(night.shade > 0, "and its shaded flank must still be drawn");
});
it("leaves daylight where it was, across the whole daylit range", () => {
/*
* The previous normalisation, verbatim, against the new one. The day was
* right and the change was aimed entirely at the other side of
* `NIGHT_SKY_HIGH`; 0.05 is the tolerance the two constants were fitted to
* over four places and two seasons, on a multiplier that runs 0.06 to 0.86.
*/
const atmosphere = createAtmosphere({
lng: SF.lng,
metresPerUnit: 94,
marineLayer: PACIFIC_MARINE_LAYER,
});
let worst = 0;
for (let i = 0; i < 96; i++) {
const when = new Date(Date.UTC(2026, 7, 23) + i * 900_000);
const state = atmosphere.apply(observe(SF.lat, SF.lng, when));
const skyLum = luminance(state.hemisphere.sky);
const night = 1 - smoothstep(NIGHT_SKY_LOW, NIGHT_SKY_HIGH, skyLum);
if (night > 0.02) continue; // the night path is meant to differ; that is the point
const before = 0.06 + 0.92 * clamp((skyLum * state.hemisphere.intensity) / 0.96, 0, 1);
const after = 0.06 + SKY_DAY_GAIN * clamp(skyLum / SKY_DAY_REFERENCE, 0, 1);
worst = Math.max(worst, Math.abs(before - after));
}
assert.ok(worst < 0.05, `daylight moved by ${worst}, which is more than a fit's worth`);
});
it("hands over at dusk rather than at a threshold", () => {
// Three quarters of an hour either side of the handover must be different
// numbers, or the deck steps rather than fades and the step is visible.
const levels = [];
for (const hour of [2, 3, 4, 5]) {
levels.push(deckAt(new Date(Date.UTC(2026, 7, 23, hour, 30))).night);
}
for (let i = 1; i < levels.length; i++) {
assert.ok(levels[i]! >= levels[i - 1]!, "the night term must rise monotonically through dusk");
}
assert.ok(levels[0]! < 0.9 && levels[3]! > 0.99, "and it must actually traverse the range");
});
});
describe("the moon, as a thing to draw", () => {
const atmosphere = createAtmosphere({
lng: SF.lng,
metresPerUnit: 94,
marineLayer: PACIFIC_MARINE_LAYER,
});
const moonAt = (when: Date): LightingMoon | null =>
atmosphere.apply(observe(SF.lat, SF.lng, when)).moon ?? null;
it("is absent while it is below the horizon, rather than drawn underground", () => {
/*
* Over one synodic month the moon is below the horizon for about half of
* every day, so a scan that never returns `null` would mean the horizon test
* is not running at all and a moon drawn under the board is the failure
* `satellites.ts` records under HORIZON_FADE_DEG, one layer over.
*/
let down = 0;
let up = 0;
for (let i = 0; i < 24 * 30; i++) {
const when = new Date(Date.UTC(2026, 7, 1) + i * 3_600_000);
const moon = moonAt(when);
if (moon === null) down += 1;
else up += 1;
}
assert.ok(down > 200, `the moon must set: only ${down} of 720 hours had it down`);
assert.ok(up > 200, `and rise: only ${up} of 720 hours had it up`);
});
it("hands over a unit direction and a bright limb perpendicular to it", () => {
/*
* Both are consumed by a fragment shader that projects a view direction onto
* them, and a non-unit or non-orthogonal pair puts the terminator at the
* wrong place on the disc rather than throwing anywhere. This is the failure
* that cannot be seen except in a picture of a crescent facing the wrong way.
*/
let checked = 0;
for (let i = 0; i < 24 * 30; i++) {
const moon = moonAt(new Date(Date.UTC(2026, 7, 1) + i * 3_600_000));
if (moon === null) continue;
checked += 1;
const d = new THREE.Vector3().fromArray(moon.direction);
const limb = new THREE.Vector3().fromArray(moon.brightLimb);
assert.ok(Math.abs(d.length() - 1) < 1e-9, `direction was not a unit vector: ${d.length()}`);
assert.ok(Math.abs(limb.length() - 1) < 1e-9, `bright limb was not a unit vector`);
assert.ok(Math.abs(d.dot(limb)) < 1e-9, `bright limb was not perpendicular: ${d.dot(limb)}`);
assert.ok(Number.isFinite(moon.angularRadius) && moon.angularRadius > 0);
assert.ok(moon.illuminated >= 0 && moon.illuminated <= 1);
assert.ok(moon.visibility >= 0 && moon.visibility <= 1);
}
assert.ok(checked > 200, "the scan must have found a moon to check");
});
it("points its lit limb away from the sun's own direction, never at it", () => {
/*
* The one error anybody who has ever looked up will spot instantly. The
* bright limb is the sun projected onto the plane of the disc, so its dot
* with the sun's true direction has to be positive a crescent whose horns
* point toward the sun is the picture of a mistake.
*/
let checked = 0;
for (let i = 0; i < 24 * 30; i++) {
const when = new Date(Date.UTC(2026, 7, 1) + i * 3_600_000);
const env = observe(SF.lat, SF.lng, when);
const moon = moonAt(when);
if (moon === null) continue;
// The sun in the same scene frame: azimuth clockwise from north, north -Z.
const el = (env.sun.elevation * Math.PI) / 180;
const az = (env.sun.azimuth * Math.PI) / 180;
const sun = new THREE.Vector3(
Math.cos(el) * Math.sin(az),
Math.sin(el),
-Math.cos(el) * Math.cos(az),
);
const limb = new THREE.Vector3().fromArray(moon.brightLimb);
// Skip the two degenerate instants — full and new — where the projection
// has no length and any axis is as right as any other.
if (moon.illuminated > 0.995 || moon.illuminated < 0.005) continue;
checked += 1;
assert.ok(
limb.dot(sun) > -1e-9,
`the lit limb faced away from the sun at ${when.toISOString()}`,
);
}
assert.ok(checked > 200, "the scan must have found a phase to check");
});
it("is faint in daylight and full-strength at night, without ever disappearing while up", () => {
let sawDaylight = false;
let sawNight = false;
for (let i = 0; i < 24 * 30; i++) {
const when = new Date(Date.UTC(2026, 7, 1) + i * 3_600_000);
const env = observe(SF.lat, SF.lng, when);
const moon = moonAt(when);
if (moon === null || env.moon.elevation < 20) continue;
if (nightFactor(env.sun.elevation) === 0) {
sawDaylight = true;
assert.ok(moon.visibility > 0, "a daytime moon well up must still be drawn, faintly");
assert.ok(moon.visibility < 0.35, `and faintly: ${moon.visibility}`);
}
if (nightFactor(env.sun.elevation) > 0.99) sawNight = true;
}
assert.ok(sawDaylight, "the scan must have found a daytime moon");
assert.ok(sawNight, "and a night one");
});
it("is absent from a rig that models no sky at all", () => {
/*
* An office has walls and no ephemeris, and `LightingMoon` is optional
* precisely so that a hand-built rig is not obliged to invent one. The
* consumer reads `state.moon ?? null` and draws nothing, which is the same
* answer as a moon below the horizon.
*/
const bare = { sun: { direction: [0, 1, 0], color: 0xffffff, intensity: 1 } };
assert.equal((bare as { moon?: unknown }).moon ?? null, null);
});
});
+45
View File
@@ -0,0 +1,45 @@
/**
* The two places a flight's riser count is decided, pinned to each other.
*
* `src/interiors/shell.ts` divides a climbing leg into whole 178 mm risers and
* lays a tread on each; `src/engine/officeMinimap.ts` draws one tick per riser
* on the floor plan. Neither imports the other the minimap builds no meshes
* and has no business importing the file that does, and the shell has no
* business knowing a widget exists so the constant is stated twice.
*
* A constant stated twice is a constant that drifts, and the drift here is
* quiet: a plan showing eleven ticks on a flight of fourteen looks fine. So the
* two are read out of the source and compared, which is the cheapest thing that
* actually catches it. Reading source text rather than exporting the constants
* is deliberate: neither is part of either module's interface, and widening an
* interface to make a test easier is how a private number becomes an API.
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
function constantIn(path: string, name: string): number {
const source = readFileSync(new URL(path, import.meta.url), "utf8");
const match = new RegExp(`const ${name} = ([0-9.]+);`).exec(source);
assert.ok(match, `${name} is not declared in ${path}`);
const value = Number(match[1]);
assert.ok(Number.isFinite(value) && value > 0, `${name} is ${match[1]}`);
return value;
}
describe("a drawn flight and a drawn plan agree about how many risers it has", () => {
it("uses one riser height in the shell and in the minimap", () => {
const shell = constantIn("../../interiors/shell.ts", "TARGET_RISER_M");
const minimap = constantIn("../../engine/officeMinimap.ts", "MINIMAP_RISER_M");
assert.equal(minimap, shell);
});
it("keeps that height inside what a person can climb", () => {
const riser = constantIn("../../interiors/shell.ts", "TARGET_RISER_M");
// Commercial stairs run about 150-190 mm. Outside that a flight stops
// reading as a flight: too shallow and it is a ramp with lines on it, too
// steep and the actor's feet visibly miss the treads.
assert.ok(riser >= 0.15 && riser <= 0.19, `${riser} m`);
});
});
+67 -1
View File
@@ -23,7 +23,7 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { SatelliteCatalogue, type SatelliteElements } from "../engine/satellites.ts";
import { dotPixels, SatelliteCatalogue, type SatelliteElements } from "../engine/satellites.ts";
/** San Francisco, which is `SAN_FRANCISCO.center` and is the default board. */
const SF = { lat: 37.7749, lng: -122.4194 };
@@ -198,3 +198,69 @@ describe("the observer", () => {
assert.ok(disagreed, "the observer coordinate made no difference to the answer");
});
});
/*
* The sizing rule, which is the whole of "satellites are objects and not a
* lattice".
*
* The layer itself is still not tested see the file header but this is not
* the layer, it is the one piece of arithmetic between a fix and how large it is
* drawn, and it is the piece that regresses silently. Every dot used to be
* exactly 3.5 pixels and exactly square; a few hundred identical squares on a
* sphere sample the pixel grid as a regular lattice, which is the moire a
* photograph of production at dusk showed. If this collapses back to a constant
* nothing fails, nothing warns, and the sky quietly becomes graph paper again.
*/
describe("how large a satellite is drawn", () => {
const lit = (rangeKm: number) => dotPixels({ rangeKm, shadow: 0 });
it("is not one number: a near object is drawn larger than a far one", () => {
// A station at 400 km, a Starlink overhead at 550, one low on the horizon at
// 2,000, and a navigation satellite at 20,000. The spread is the point.
assert.ok(lit(400) > lit(550), "a low pass must be larger than a Starlink overhead");
assert.ok(lit(550) > lit(2000), "and one overhead larger than one near the horizon");
assert.ok(lit(2000) > lit(20_000), "and a low-orbit object larger than a navigation bird");
});
it("stays inside a range a naked-eye pass could plausibly occupy", () => {
for (const range of [200, 400, 550, 800, 1500, 2400, 20_000, 36_000]) {
const size = lit(range);
assert.ok(size >= 2.1 && size <= 6, `${range} km came out at ${size} pixels`);
}
});
it("is monotonic in range, so nothing grows as it recedes", () => {
let previous = Infinity;
for (let km = 200; km <= 40_000; km += 200) {
const size = lit(km);
assert.ok(size <= previous + 1e-12, `size grew with range at ${km} km`);
previous = size;
}
});
it("shrinks an eclipsed object, which is the same fact its alpha already states", () => {
/*
* A point source at the threshold of vision blooms: a bright one occupies
* more of a sensor than a faint one at the same true angular size. So the
* shadow term reinforces `SHADOW_ALPHA` rather than repeating it an object
* in the earth's shadow is both fainter and smaller, which is how the end of
* a Starlink train reads as fading out rather than as switching off.
*/
assert.ok(dotPixels({ rangeKm: 550, shadow: 1 }) < dotPixels({ rangeKm: 550, shadow: 0 }));
const penumbra = dotPixels({ rangeKm: 550, shadow: 0.5 });
assert.ok(penumbra < dotPixels({ rangeKm: 550, shadow: 0 }));
assert.ok(penumbra > dotPixels({ rangeKm: 550, shadow: 1 }));
});
it("never returns something a vertex shader cannot use", () => {
for (const fix of [
{ rangeKm: 0, shadow: 0 },
{ rangeKm: -1, shadow: 0 },
{ rangeKm: 550, shadow: -5 },
{ rangeKm: 550, shadow: 9 },
]) {
const size = dotPixels(fix);
assert.ok(Number.isFinite(size) && size > 0, `${JSON.stringify(fix)} gave ${size}`);
}
});
});
+126
View File
@@ -231,3 +231,129 @@ describe("the device panel", () => {
assert.deepEqual(commands, [], "a disposed panel must not still be sending commands");
});
});
/**
* What the panel has to do once a device is real.
*
* Three additions, and each closes a way the panel could tell a viewer something
* untrue about hardware in a room somebody is standing in.
*/
/** A microphone whose gain is a mixer position, not a preamp measurement. */
const YETI: DeviceDeclaration = {
id: "la-mic-yeti",
kind: "mic",
label: "Blue Yeti Nano",
assetId: "tera:device.mic.desk",
anchor: { levelId: "l1", propId: "desk-01" },
capabilities: ["power", "mute", "gain", "level"],
ranges: { gain: { min: 0, max: 100, initial: 68, unit: "%" } },
provenance: "first-party-sensor",
disclosure: "Live reading from the studio's own desk microphone, north wall.",
simulatedDisclosure:
"Simulated in your browser. This deployment does not share the live room with visitors.",
};
describe("a device that declares its own range", () => {
it("draws the slider between the declared bounds, not the global ones", () => {
const { root } = setup([YETI]);
const slider = row(root, YETI.id, "gain").querySelector("input");
assert.ok(slider);
assert.equal(slider.min, "0");
assert.equal(slider.max, "100");
// The global default is 12…+36 dB. A Yeti Nano's capture level is an ALSA
// position on a 050 scale normalised to percent; drawing it on the dB
// scale would peg the handle at the far right and label it "+36 dB".
assert.notEqual(slider.max, String(DEVICE_RANGES.gain.max));
});
it("labels the reading in the declared unit and never invents decibels", () => {
const { panel, root } = setup([YETI]);
panel.apply([
{ id: YETI.id, kind: "mic", powered: true, gainDb: 68, observedAt: 1, synthetic: false },
]);
const value = row(root, YETI.id, "gain").querySelector(".tera-device__value");
assert.equal(value?.textContent, "68%");
// "+68 dB" would be a guess wearing the typography of a measurement, and it
// would look completely plausible next to a photograph of a desk.
assert.ok(!String(value?.textContent).includes("dB"));
});
it("still reads the global range for a declaration that names none", () => {
const { panel, root } = setup([MIC]);
panel.apply([initialDeviceState(MIC, 1)]);
const value = row(root, MIC.id, "gain").querySelector(".tera-device__value");
assert.equal(value?.textContent, `+${DEVICE_RANGES.gain.initial.toFixed(0)} dB`);
});
});
describe("which disclosure a viewer is actually reading", () => {
it("prints the simulated sentence when a live declaration is being simulated", () => {
// The hole the twin creates. An anonymous visitor cannot read the device
// route, so they are handed the local simulator running THIS declaration —
// and `disclosure` on a live device says the room is live.
const { panel, root } = setup([YETI]);
panel.apply([
{ id: YETI.id, kind: "mic", powered: true, gainDb: 68, observedAt: 1, synthetic: true },
]);
const line = card(root, YETI.id).querySelector(".tera-device__disclosure");
assert.equal(line?.textContent, YETI.simulatedDisclosure);
});
it("prints the live sentence once a reading is actually observed", () => {
const { panel, root } = setup([YETI]);
panel.apply([
{ id: YETI.id, kind: "mic", powered: true, gainDb: 68, observedAt: 1, synthetic: false },
]);
const line = card(root, YETI.id).querySelector(".tera-device__disclosure");
assert.equal(line?.textContent, YETI.disclosure);
});
it("leaves a simulated declaration's one sentence alone", () => {
const { panel, root } = setup([MIC]);
panel.apply([initialDeviceState(MIC, 1)]);
assert.equal(
card(root, MIC.id).querySelector(".tera-device__disclosure")?.textContent,
MIC.disclosure,
);
});
});
describe("a device nobody could reach", () => {
it("says so, and says how old the reading it is still showing is", () => {
const { panel, root } = setup([YETI]);
const observedAt = Date.now() - 5 * 60_000;
panel.apply([
{
id: YETI.id,
kind: "mic",
powered: false,
muted: true,
reachable: false,
observedAt,
synthetic: false,
},
]);
const status = card(root, YETI.id).querySelector(".tera-device__status");
assert.equal(status?.attributes.get("data-reachable"), "false");
assert.match(String(status?.textContent), /Not reached/);
assert.match(String(status?.textContent), /5 min ago/);
// The mute reading it last reported still stands. `reachable: false` is not
// `powered: false` — a microphone on a machine that is asleep is not a
// switched-off microphone, and the panel must not redraw it as one.
assert.equal(
row(root, YETI.id, "mute").querySelector(".tera-device__value")?.textContent,
"Muted",
);
});
it("says nothing at all when reachability does not apply", () => {
// Every simulated device. A state machine in this tab is never unreachable,
// and a line claiming it was reached would be a fact about nothing.
const { panel, root } = setup([MIC]);
panel.apply([initialDeviceState(MIC, 1)]);
const status = card(root, MIC.id).querySelector(".tera-device__status");
assert.equal(status?.textContent, "");
assert.equal(status?.attributes.get("data-reachable"), undefined);
});
});
+395
View File
@@ -0,0 +1,395 @@
/**
* The fire panel, and the three empty states it must never collapse.
*
* A silent board and a dead feed look identical. That is the whole problem this
* panel exists to solve, and it is why "the board is empty" is not one sentence
* but three:
*
* - nothing has ever answered a **fault**;
* - nothing is drawn but *n* live records were refused a **finding**, and
* the number is the finding;
* - nothing is drawn and nothing was refused a different finding.
*
* The real body underneath these tests is `LIVE_FIRES_BODY`, captured through
* the whole wire on an ordinary day. Clipped to the SoCal board it yields the
* second case exactly: zero drawn, twenty-two suppressed. Clipped to California
* it yields five fires and names the largest one the frame cannot show, because
* `california.ts` caps at 38.05 N and the state does not.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
evidenceCaption,
fireHeadline,
formatAcres,
mountFirePanel,
offBoardDirection,
relativeAge,
suppressedNote,
type FirePanelView,
} from "../../ui/firePanel.ts";
import { promote, type FirePromotion } from "../../server/fires.ts";
import { LIVE_FIRES_BODY } from "../data/firesFixture.ts";
import { FakeDocument, type FakeElement } from "./fakeDom.ts";
const CALIFORNIA = { minLat: 32.55, maxLat: 38.05, minLng: -123.05, maxLng: -114.0 };
const SOCAL = { minLat: 33.28, maxLat: 34.36, minLng: -118.88, maxLng: -117.22 };
/** Four minutes after the fixture's own `fetchedAt`, so the copy reads literally. */
const NOW = Date.parse("2026-08-22T22:26:35.806Z");
/**
* `FirePromotion` must reach the panel with no adapter, exactly as it reaches
* the renderer. Asserted at compile time: a renamed field on the gate stops the
* build rather than quietly emptying a caption.
*/
const _assignable: (p: FirePromotion) => FirePanelView = (p) => p;
void _assignable;
function setup(bounds?: typeof CALIFORNIA) {
const doc = new FakeDocument();
const host = doc.createElement("div");
doc.body.append(host);
const panel = mountFirePanel(host as unknown as HTMLElement, { bounds });
return { doc, panel, root: panel.root as unknown as FakeElement };
}
function part(root: FakeElement, role: string): FakeElement {
const found = root.querySelector(`[data-role=${role}]`);
assert.ok(found, `no element for role ${role}`);
return found;
}
function text(root: FakeElement, role: string): string {
return part(root, role).textContent ?? "";
}
// ---- The three empty states -----------------------------------------------
describe("the empty board", () => {
it("calls a feed that has never answered a fault, not a calm day", () => {
const { panel, root } = setup();
panel.apply({ fetchedAt: new Date(0).toISOString(), ageMs: null, drawn: [], suppressed: 0 });
assert.equal(root.getAttribute("data-state"), "fault");
assert.match(text(root, "headline"), /no fire feed configured/i);
assert.doesNotMatch(
text(root, "headline"),
/no active fire/i,
"a dead feed must never report an all-clear",
);
// No age, because there is no fetch to be aged. A "1970" stamp would be a
// number that looks like an answer.
assert.equal(text(root, "fetched"), "");
panel.dispose();
});
it("states the quiet SoCal board with its fetch age and the number refused", () => {
const gated = promote(LIVE_FIRES_BODY, SOCAL, NOW);
assert.equal(gated.drawn.length, 0);
assert.equal(gated.suppressed, 22);
const { panel, root } = setup(SOCAL);
panel.apply(gated);
assert.equal(root.getAttribute("data-state"), "quiet");
assert.equal(
text(root, "headline"),
"No active fire on this board — CAL FIRE and WFIGS, 4 minutes ago.",
);
assert.match(text(root, "suppressed"), /^22 live records inside this frame/);
assert.match(text(root, "suppressed"), /prescribed burn/);
// The machine-readable instant is on the page beside the human one.
assert.equal(part(root, "fetched").getAttribute("datetime"), LIVE_FIRES_BODY.fetchedAt);
assert.ok(text(root, "fetched").includes(LIVE_FIRES_BODY.fetchedAt));
assert.equal(part(root, "drawn").children.length, 0);
panel.dispose();
});
it("does not print a suppressed count when nothing was suppressed", () => {
const { panel, root } = setup();
panel.apply({
fetchedAt: "2026-08-22T22:22:35.806Z",
ageMs: 240_000,
drawn: [],
suppressed: 0,
});
assert.match(text(root, "headline"), /No active fire on this board/);
assert.equal(text(root, "suppressed"), "", "nothing refused is a different fact from 22");
panel.dispose();
});
it("renders the fault sentence before anything has been applied", () => {
const { panel, root } = setup();
assert.match(text(root, "headline"), /no fire feed configured/i);
panel.dispose();
});
});
// ---- The truthful full board ----------------------------------------------
describe("the California board", () => {
it("names the five fires the gate admitted, worst first", () => {
const gated = promote(LIVE_FIRES_BODY, CALIFORNIA, NOW);
const { panel, root } = setup(CALIFORNIA);
panel.apply(gated);
assert.equal(root.getAttribute("data-state"), "active");
assert.match(text(root, "headline"), /^5 active fires on this board/);
const rows = part(root, "drawn").children;
assert.equal(rows.length, 5);
const names = rows.map((row) => row.querySelector(".tera-fire__name")?.textContent);
assert.deepEqual(names, [
"Timber Fire",
"Alpaugh Fire",
"Carrizo Fire",
"Amber Fire",
"GREEN",
]);
const timber = rows[0];
assert.ok(timber);
assert.equal(timber.getAttribute("data-tier"), "2");
const facts = timber.querySelector(".tera-fire__facts")?.textContent ?? "";
assert.ok(facts.includes("7,591 acres"), facts);
assert.ok(facts.includes("29% contained"), facts);
panel.dispose();
});
it("says an agency has not reported containment rather than saying zero", () => {
const gated = promote(LIVE_FIRES_BODY, CALIFORNIA, NOW);
const { panel, root } = setup(CALIFORNIA);
panel.apply(gated);
// Carrizo, Amber and GREEN all carry `pctContained: null` on this day.
const carrizo = part(root, "drawn").children[2];
assert.ok(carrizo);
const facts = carrizo.querySelector(".tera-fire__facts")?.textContent ?? "";
assert.ok(facts.includes("containment not reported"), facts);
assert.doesNotMatch(facts, /0% contained/);
panel.dispose();
});
it("names what the frame cannot show, and says it is not drawn", () => {
// `california.ts` caps at 38.05 N. MP18 is at 41.13 N with 7,610 acres.
const gated = promote(LIVE_FIRES_BODY, CALIFORNIA, NOW);
assert.equal(gated.offBoard[0]?.name, "MP18 Fire");
const { panel, root } = setup(CALIFORNIA);
panel.apply(gated);
assert.match(text(root, "off-board-heading"), /outside this frame/i);
const rows = part(root, "off-board").children;
assert.ok(rows.length > 0 && rows.length <= 3, "capped upstream at three");
const first = rows[0]?.textContent ?? "";
assert.ok(first.includes("MP18 Fire"), first);
assert.ok(first.includes("7,610 acres"), first);
assert.match(first, /\d+ km north of this frame/);
assert.match(first, /Listed, not drawn/);
panel.dispose();
});
it("does not name Bug Fire, which is 93,733 acres and 94 % contained", () => {
// The off-board list is gated by the same ladder as the drawn set, or it
// reintroduces exactly the noise the ladder exists to remove.
const gated = promote(LIVE_FIRES_BODY, CALIFORNIA, NOW);
const { panel, root } = setup(CALIFORNIA);
panel.apply(gated);
assert.doesNotMatch(text(root, "off-board"), /Bug Fire/);
panel.dispose();
});
it("leaves the off-board section empty when the gate found nothing outside", () => {
const { panel, root } = setup(CALIFORNIA);
panel.apply({
fetchedAt: "2026-08-22T22:22:35.806Z",
ageMs: 240_000,
drawn: [],
offBoard: [],
suppressed: 3,
});
assert.equal(text(root, "off-board-heading"), "");
assert.equal(part(root, "off-board").children.length, 0);
panel.dispose();
});
});
// ---- Hot pixels are captioned as evidence ---------------------------------
describe("the hot-pixel caption", () => {
it("always says evidence, not incidents", () => {
const gated = promote(LIVE_FIRES_BODY, CALIFORNIA, NOW);
const { panel, root } = setup(CALIFORNIA);
panel.apply(gated);
const caption = text(root, "evidence");
assert.match(caption, /evidence, not incidents/);
assert.match(caption, /last 24 h/);
assert.match(caption, /persistent sources/);
assert.ok(caption.includes(String(gated.persistentDetections)), caption);
panel.dispose();
});
it("says so when there are none, rather than saying nothing", () => {
assert.match(
evidenceCaption({ fetchedAt: "", ageMs: 0, drawn: [], detections: [] }),
/No satellite hot pixels/,
);
});
it("omits the persistent clause when there is no furniture to report", () => {
const caption = evidenceCaption({
fetchedAt: "",
ageMs: 0,
drawn: [],
detections: [{}, {}],
persistentDetections: 0,
detectionWindowHours: 24,
});
assert.match(caption, /2 satellite hot pixels, last 24 h — evidence, not incidents\./);
assert.doesNotMatch(caption, /persistent/);
});
});
// ---- The pure copy --------------------------------------------------------
describe("the panel's copy", () => {
it("ages a fetch coarsely, because the feed polls every ten minutes", () => {
assert.equal(relativeAge(null), "never");
assert.equal(relativeAge(1_000), "just now");
assert.equal(relativeAge(60_000), "1 minute ago");
assert.equal(relativeAge(240_000), "4 minutes ago");
assert.equal(relativeAge(3 * 3_600_000), "3 hours ago");
assert.equal(relativeAge(4 * 86_400_000), "4 days ago");
});
it("groups acres and keeps a small fire's tenths", () => {
assert.equal(formatAcres(93_733), "93,733");
assert.equal(formatAcres(7_591), "7,591");
assert.equal(formatAcres(268.1), "268");
assert.equal(formatAcres(19.5), "19.5");
assert.equal(formatAcres(Number.NaN), "—");
});
it("keeps the three headline cases apart", () => {
const base = { fetchedAt: "2026-08-22T22:22:35.806Z", drawn: [] };
assert.equal(fireHeadline({ ...base, ageMs: null }).state, "fault");
assert.equal(fireHeadline({ ...base, ageMs: 0, suppressed: 22 }).state, "quiet");
assert.equal(
fireHeadline({
...base,
ageMs: 0,
drawn: [{ id: "a", name: "A", acres: 500, pctContained: null, lat: 36, lon: -120 }],
}).state,
"active",
);
});
it("prints one fire in the singular", () => {
const headline = fireHeadline({
fetchedAt: "2026-08-22T22:22:35.806Z",
ageMs: 60_000,
drawn: [{ id: "a", name: "A", acres: 500, pctContained: null, lat: 36, lon: -120 }],
});
assert.equal(headline.text, "1 active fire on this board — CAL FIRE and WFIGS, 1 minute ago.");
});
it("keeps the suppressed note off an active board", () => {
assert.equal(
suppressedNote({
fetchedAt: "",
ageMs: 0,
suppressed: 40,
drawn: [{ id: "a", name: "A", acres: 500, pctContained: null, lat: 36, lon: -120 }],
}),
null,
"the number behind a calm board is not a number about a burning one",
);
assert.equal(
suppressedNote({ fetchedAt: "", ageMs: null, suppressed: 40, drawn: [] }),
null,
"a feed that never answered has suppressed nothing",
);
});
it("picks the direction a fire actually lies in, longitude squashed", () => {
assert.equal(offBoardDirection({ lat: 41.13, lon: -123.68 }), null, "no bounds, no claim");
assert.match(
offBoardDirection({ lat: 41.13, lon: -123.68 }, CALIFORNIA) ?? "",
/^34[0-9] km north of this frame$/,
);
assert.match(
offBoardDirection({ lat: 33.9, lon: -117.1 }, SOCAL) ?? "",
/km east of this frame$/,
);
assert.equal(
offBoardDirection({ lat: 34.0, lon: -118.0 }, SOCAL),
null,
"a point inside the bounds is not outside them",
);
});
});
// ---- Lifecycle ------------------------------------------------------------
describe("the panel's lifecycle", () => {
it("mounts one stylesheet per document, not one per board", () => {
const doc = new FakeDocument();
const a = doc.createElement("div");
const b = doc.createElement("div");
doc.body.append(a);
doc.body.append(b);
const first = mountFirePanel(a as unknown as HTMLElement);
const second = mountFirePanel(b as unknown as HTMLElement);
assert.equal(doc.head.querySelectorAll("style").length, 1);
first.dispose();
second.dispose();
});
it("goes quiet after dispose rather than throwing", () => {
const { panel, root } = setup();
panel.dispose();
panel.apply({ fetchedAt: "2026-08-22T22:22:35.806Z", ageMs: 0, drawn: [], suppressed: 1 });
assert.match(text(root, "headline"), /no fire feed configured/i);
});
it("falls back to the fault sentence on a null view", () => {
const gated = promote(LIVE_FIRES_BODY, CALIFORNIA, NOW);
const { panel, root } = setup(CALIFORNIA);
panel.apply(gated);
assert.equal(part(root, "drawn").children.length, 5);
panel.apply(null);
assert.equal(part(root, "drawn").children.length, 0);
assert.match(text(root, "headline"), /no fire feed configured/i);
assert.equal(text(root, "evidence"), "");
panel.dispose();
});
it("never prints an unnamed incident's id as its name", () => {
const { panel, root } = setup();
panel.apply({
fetchedAt: "2026-08-22T22:22:35.806Z",
ageMs: 0,
drawn: [
{
id: "{6AD80DE9-CD41-40D3-8939-A86CCF775981}",
name: null,
acres: 500,
pctContained: null,
lat: 36,
lon: -120,
},
],
});
const row = part(root, "drawn").children[0];
assert.ok(row);
assert.equal(row.querySelector(".tera-fire__name")?.textContent, "Unnamed incident");
panel.dispose();
});
it("writes no raw z-index", async () => {
const { FIRE_PANEL_CSS } = await import("../../ui/firePanel.ts");
assert.deepEqual(FIRE_PANEL_CSS.match(/z-index:\s*\d/g) ?? [], []);
});
});
+8 -1
View File
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
import { DEVICE_PANEL_CSS } from "../../ui/devicePanel.ts";
import { FIRE_PANEL_CSS } from "../../ui/firePanel.ts";
import { ONBOARDING_CSS } from "../../ui/onboarding.ts";
import { TOUCH_TARGET_PX } from "../../ui/tokens.ts";
@@ -40,7 +41,7 @@ describe("the stylesheet", () => {
);
});
it("writes no raw z-index in any of the four injected stylesheets", () => {
it("writes no raw z-index in any of the injected stylesheets", () => {
// These four modules mount a `<style>` at runtime and each used to carry its
// own stacking literal, invisible to every other file on the page. Two of
// them collided with rules in index.html.
@@ -56,6 +57,11 @@ describe("the stylesheet", () => {
for (const [name, css] of [
["devicePanel", DEVICE_PANEL_CSS],
["onboarding", ONBOARDING_CSS],
// The third, added when the fire panel landed. It injects one
// `<style>` per document exactly as `devicePanel` does, so it is subject
// to exactly the same rule, and this is the shared gate rather than its
// own file's copy of the assertion.
["firePanel", FIRE_PANEL_CSS],
] as const) {
assert.deepEqual(css.match(RAW_Z_INDEX) ?? [], [], `${name} writes a raw z-index literal`);
}
@@ -177,6 +183,7 @@ describe("the stylesheet", () => {
"title", "subtitle", "clock", "cities", "boards-title",
"enter", "walk", "fly", "screens", "devices",
"device-section", "device-host", "office-invite", "office-note",
"fire-section", "fire-host",
"chapters", "blurb",
"topright", "tier", "tier-label", "tier-who", "tier-signin", "tier-character",
"tier-adds", "presence-host", "webcam-face-indicator", "corner", "minimap",
+135
View File
@@ -138,3 +138,138 @@ describe("walker guardrails", () => {
assert.throws(() => walker(plan, { position: { x: 11, z: 2 } }), RangeError);
});
});
// ---- Cross-level -----------------------------------------------------------
/**
* Two storeys, hand-authored, with different footprints on purpose.
*
* Level 2 is deliberately *smaller* than level 1 and offset from it, so "valid
* on the level you are on" is a different question from "valid anywhere in the
* building" and a test can tell the two apart. Nothing here is a stair: the
* controller knows nothing about transitions, only about being told to be
* somewhere else.
*/
function twoLevelPlan(): Plan {
const lower: Level = {
id: "level-1",
name: "Ground",
elevation: 0,
wallHeight: 3,
wallThickness: 0.1,
floorplan: { rooms: [ROOM], walls: [] },
};
const upper: Level = {
id: "level-2",
name: "Upper",
elevation: 4,
wallHeight: 3,
wallThickness: 0.1,
floorplan: {
rooms: [{
id: "loft",
name: "Loft",
floor: "floor" as never,
outline: [
{ x: 0, z: 0 },
{ x: 6, z: 0 },
{ x: 6, z: 8 },
{ x: 0, z: 8 },
],
}],
walls: [],
},
};
const office: Office = {
id: "two-level-test",
name: "Two Level Test",
levels: [lower, upper],
viewpoints: [],
};
return new Plan(office, { warn: false });
}
describe("walker levels", () => {
it("crosses to another storey without zeroing the odometer", () => {
const plan = twoLevelPlan();
const controller = walker(plan, { levelId: "level-1", position: { x: 2, z: 2 }, speed: 1 });
for (let index = 0; index < 10; index += 1) controller.tick(0.1, { x: 1, z: 0 });
const before = controller.state();
assert.equal(before.levelId, "level-1");
assert.ok(Math.abs(before.distance - 1) < 1e-9, `${before.distance}`);
// The transition path. `reset` would have been the easy way to write this
// and it is the wrong one: it zeroes `distance` and restores the authored
// facing, so every flight of stairs would silently reset the odometer.
const crossed = controller.enterLevel("level-2", { x: 3, z: 2 });
assert.equal(crossed.levelId, "level-2");
assert.deepEqual(crossed.position, { x: 3, z: 2 });
assert.equal(crossed.distance, before.distance);
assert.deepEqual(crossed.facing, before.facing);
for (let index = 0; index < 10; index += 1) controller.tick(0.1, { x: 1, z: 0 });
const after = controller.state();
assert.equal(after.levelId, "level-2");
assert.ok(after.distance > before.distance, `${after.distance}`);
assert.ok(Math.abs(after.distance - 2) < 1e-9, `${after.distance}`);
// And the upper storey's own bounds hold: level 2 stops at x = 6 where
// level 1 runs to 10, so walking east on the loft stops sooner.
for (let index = 0; index < 60; index += 1) controller.tick(0.1, { x: 1, z: 0 });
assert.ok(controller.state().position.x <= 6 - 0.3 + 1e-9, `${controller.state().position.x}`);
});
it("refuses a crossing to an unknown level or an invalid position on a known one", () => {
const controller = walker(twoLevelPlan(), { levelId: "level-1", position: { x: 2, z: 2 } });
assert.throws(() => controller.enterLevel("level-3", { x: 2, z: 2 }), RangeError);
// Inside level 1 and outside level 2: the check is per level, not per building.
assert.throws(() => controller.enterLevel("level-2", { x: 8, z: 2 }), RangeError);
assert.throws(() => controller.enterLevel("level-2", { x: Number.NaN, z: 2 }), RangeError);
assert.equal(controller.state().levelId, "level-1");
assert.deepEqual(controller.state().position, { x: 2, z: 2 });
});
it("restores a snapshot from another storey, and still refuses an invalid one", () => {
const plan = twoLevelPlan();
const controller = walker(plan, { levelId: "level-1", position: { x: 2, z: 2 } });
// The rule is "a level this plan resolves, at a position valid on it" — not
// "the level you spawned on". `src/arena/officeNav.ts` restores through this.
const restored = controller.restore({
levelId: "level-2",
position: { x: 4, z: 5 },
facing: { x: 0, z: 1 },
distance: 12.5,
});
assert.equal(restored.levelId, "level-2");
assert.deepEqual(restored.position, { x: 4, z: 5 });
assert.equal(restored.distance, 12.5);
// Valid on level 1, outside level 2. Relaxing the level check must not have
// relaxed the position check with it.
assert.throws(() => controller.restore({
levelId: "level-2",
position: { x: 8, z: 5 },
facing: { x: 0, z: 1 },
distance: 1,
}), RangeError);
assert.throws(() => controller.restore({
levelId: "level-9",
position: { x: 4, z: 5 },
facing: { x: 0, z: 1 },
distance: 1,
}), RangeError);
assert.throws(() => controller.restore({
levelId: "level-2",
position: { x: 4, z: 5 },
facing: { x: 0, z: 1 },
distance: -1,
}), RangeError);
assert.equal(controller.state().levelId, "level-2");
// `reset()` still returns to the configured spawn, which is on level 1 —
// a crossing changes where you are, never where the episode began.
assert.equal(controller.reset().levelId, "level-1");
});
});