1
0

The city points at its own buildings, and the sky stops depending on an API

**Clouds were invisible to everyone who had not wired up NWS.** The layer
took `currentWeather()?.cloudCover ?? 0`, and `currentWeather()` is null on
any deployment without a weather source — which is the default, and the
exact configuration this repo is held to: a stranger clones it, runs one
command, and gets a city with no account and no key. Their sky was
permanently, silently empty. `atmosphere.ts` already models a sky when
nobody has observed one; it now models cover too, an observed reading
still wins outright, and the clouds are there on a bare clone.

**Both offices are pins on the city, and clicking one walks you in.** Each
pack has carried a real `site` since the sun needed one, and that
coordinate was known to the lighting and to nothing else — a visitor
looking at the board had no way to tell that two of those buildings are
ones they can go inside. The coordinates move to a tiny eagerly-imported
`offices/sites.ts` that the packs import *from*, because a pack is a 25 kB
lazy chunk and the board wants its pins long before anybody opens a door.
A test asserts the pack and the table hold the **same object**, not merely
equal values: a drifted coordinate would put the marker on one building
and the sun on another and both would look entirely plausible.

**Aircraft bank into their turns.** The roll channel existed and was never
written, so every turn was flat. Bank comes from the coordinated-turn
relation against the measured turn rate, damped by a first-order lag so it
settles rather than oscillates, and clamped at 30° like a real limiter.
Six regression tests, because roll is the one channel that feeds itself —
position and heading are recomputed from the last two observations and
wash out a bad value, while a NaN in the roll would persist for the life
of the track.

That fed straight into a real defect: `AdsbFlights` substituted
`heading: 0` for records with no `track` field, which is harmless for a
symmetrical dart and is a **sustained full-scale artefact** once aircraft
bank — a target whose real heading is 200° reported as 0° reads as a 160°
turn and pins the roll at its limiter for as long as it is in the feed.
Those records are dropped now. An aeroplane the feed will not give a
heading for is one this layer cannot draw honestly.

**The office empties out overnight.** A full complement of seated people
at one in the morning, under house lights that came on because the sun is
down, was the least believable thing left in the room once the clock
became real. A live roster always wins — an API that says the building is
empty is telling the truth about the building.

**Robots go somewhere.** They pick real addresses — a seat, a room — and
turn to face the seat when they arrive, rather than stopping at a random
angle. Godmode gets an office section: house lights forced on or off or
following the sun, robots and ceilings toggled, with a readout.

**The bundle is split.** Entry chunk 758 kB to 208 kB, with three.js and
satellite.js in a vendor chunk that survives an app deploy instead of
being re-downloaded on every one. Rollup's 500 kB warning still fires and
should — it now points at three.js, where it is true, instead of at our
code, where it was pointing at three.js all along.

Reviewers caught two false geography claims in the new prose ("both
shipped buildings stand in San Francisco" — one is across the estuary at
Alameda Point) and several miscounted figures. Fixed. In a codebase where
the comments are the design record, those are defects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-07 04:18:00 -07:00
parent 51979feea0
commit 2d87d9f354
14 changed files with 2304 additions and 72 deletions
+246 -1
View File
@@ -33,6 +33,13 @@
* camera — a paste-ready `Chapter.focus`, both bodies' az/el, the marine
* strength, and the fog the atmosphere actually installed.
*
* Two more sections belong to whatever board is up rather than to the panel, and
* are absent when there is nothing to point them at: **sky** (`GodmodeSky`) and
* **office** (`GodmodeOffice`). Neither is a fifth instrument in the sense above
* — they are handles onto one scene's dials, they arrive with it and they mean
* nothing without it, which is why both are optional interfaces rather than
* fields on `GodmodeOptions`.
*
* ## It is not shipped to anyone else
*
* `access.ts` is explicit that `debug` is a *drawing* decision and not a
@@ -156,6 +163,103 @@ export interface GodmodeSky {
};
}
/**
* Where the office's house lights take their level from.
*
* `"sun"` is the building's own behaviour and the position everything boots in;
* the other two are a hand on the switch, and they stay where they were put
* until somebody moves them — across a walk out to the city and back, and across
* the rebuild a city switch performs.
*/
export type GodmodeHouseLights = "sun" | "on" | "off";
/**
* The office, when the stage is showing one.
*
* The same shape and the same argument as `GodmodeSky` — optional as a unit,
* methods that reach the room through the caller's live handle rather than
* through one captured at construction — with the one difference that the sky
* does not have: a city is simply there, and an office is a room you walk into
* and out of. So `read()` may answer `null` at any moment and the section takes
* itself off screen when it does. A ceiling switch pointed at a building nobody
* is looking at is worse than no switch, and greying it out would only put the
* dead panel back in a paler colour.
*
* The panel owns the three switch positions and re-asserts all three the moment
* `read().id` changes. It has to: in this app an office handle does not survive
* a city switch — it is disposed and the next one is built at its own defaults,
* lids off and robots drawn — so a panel whose chips still claimed "ceilings"
* over a freshly built building would be lying about the only thing it does.
* Re-asserting is three idempotent calls on an event that happens when somebody
* changes metro, which is not a budget anybody has to think about.
*/
export interface GodmodeOffice {
/**
* Where the house lights take their level from.
*
* This is the control the section is worth building for. The fittings ramp on
* against the sun's height — fully on at or below 0°, fully off at or above
* +6°, as `LIGHTS_ON_BELOW_DEG` and `LIGHTS_OFF_ABOVE_DEG` in
* `interiors/luminaires.ts` have it — so until now the only way to see the lit
* building was to scrub the clock into the evening, which also moves the sun,
* the sky behind the glazing, the fog and every shadow. You end up comparing
* two pictures that differ in five ways and learning nothing about any of
* them. Forcing the level moves the fittings and the interior term and
* nothing else: `withHouseLights` in `interiors/daylight.ts` lifts the ambient
* and hemisphere terms and returns the rest of the rig untouched, so the sun
* outside is still the clock's and the two pictures differ in the one thing
* that was asked about.
*/
onHouseLights(mode: GodmodeHouseLights): void;
/**
* Draw the robots, or do not — they keep walking either way, and that is the
* useful half rather than a caveat.
*
* A fitting responds to where a robot *is* and not to whether it is drawn:
* the luminaires hold the live array the robot layer mutates in place, and
* hiding a mesh does not move a position. So this is how you watch the ceiling
* follow somebody across an empty floor with nothing else in the frame, which
* is the one view that shows the occupancy response on its own.
*/
onRobotsVisible(visible: boolean): void;
/**
* Put the lids back on. `OfficeScene.setCeilingsVisible`, which has existed
* since the shell did and which nothing in the app has ever called — the
* ceilings are off by default because looking down into the floor plate is the
* entire view, and that is exactly why being able to close it is worth a chip:
* a fitting you cannot see the underside of is a fitting you cannot check.
*/
onCeilingsVisible(visible: boolean): void;
/**
* The office on the stage, or `null` when the stage is showing something else.
*
* Polled on the panel's own refresh rather than pushed, like
* `GodmodeSky.read`, and here for a stronger reason than either of the sky's:
* `houseLevel` is a ramp against the sun and climbs on its own all through
* dusk while nobody touches a control, which is precisely the moment somebody
* has this section open.
*/
read(): {
/**
* Which building. Printed, and watched for the rebuild described on this
* interface — so it must name the *building*, and must change when the room
* on the stage is a different one.
*/
id: string;
/**
* `"public"` is the stranger's building: same shell, same plan, same
* furniture, and no presence layer at all. Worth a word on screen, because
* an empty office is otherwise indistinguishable from an occupancy feed that
* never landed.
*/
depth: "full" | "public";
/** `OfficeScene.houseLevel()`, 0..1, after the last solar elevation it was given. */
houseLevel: number;
/** How many robots this pack asked for. Zero is a normal answer, not a fault. */
robots: number;
} | null;
}
export interface GodmodeOptions {
/**
* Where to mount. The root positions *itself* — bottom centre, over the map,
@@ -172,6 +276,12 @@ export interface GodmodeOptions {
onWeatherOverride(w: WeatherObservation | null): void;
/** Traffic and satellites, when there is a board to point them at. */
sky?: GodmodeSky;
/**
* House lights, robots and ceilings, when this deployment has an office at
* all. Absent leaves the section unbuilt; present but `read()`ing `null`
* leaves it off screen until somebody walks into the building.
*/
office?: GodmodeOffice;
/** Start with the drawer open. Default `false`: the tab, and nothing else. */
open?: boolean;
/**
@@ -686,6 +796,69 @@ export function createGodmode(options: GodmodeOptions): Godmode {
satelliteNote,
);
// ---- Office ---------------------------------------------------------------
/**
* Built unconditionally and appended only with `options.office`, exactly as
* the sky block above is, and then hidden whenever `read()` says the stage is
* not showing a room. The `hidden` attribute rather than a detach: the section
* is a grid item, `[hidden]` is `display: none` here in a rule that outranks
* the grid, and taking a node out and putting it back would also have to
* remember where in the column order it belonged.
*/
const officeSection = section("office");
const officeLine = el("div", "gm-stamp");
const houseLine = el("div", "gm-line");
const houseChips = el("div", "gm-chips");
let houseLights: GodmodeHouseLights = "sun";
const houseButtons: { mode: GodmodeHouseLights; el: HTMLButtonElement }[] = [];
for (const spec of [
{ mode: "sun", label: "sun" },
{ mode: "on", label: "force on" },
{ mode: "off", label: "force off" },
] as const) {
const b = button("gm-chip", spec.label, () => {
houseLights = spec.mode;
options.office?.onHouseLights(spec.mode);
refresh();
});
houseButtons.push({ mode: spec.mode, el: b });
houseChips.append(b);
}
const houseNote = el("div", "gm-hint");
houseNote.textContent =
"the fittings and the interior term only — the sun outside the glazing is still the clock's";
const robotLine = el("div", "gm-line");
const showChips = el("div", "gm-chips");
let robotsOn = true;
const robotChip = button("gm-chip", "robots", () => {
robotsOn = !robotsOn;
options.office?.onRobotsVisible(robotsOn);
refresh();
});
let ceilingsOn = false;
const ceilingChip = button("gm-chip", "ceilings", () => {
ceilingsOn = !ceilingsOn;
options.office?.onCeilingsVisible(ceilingsOn);
refresh();
});
showChips.append(robotChip, ceilingChip);
const officeNote = el("div", "gm-hint");
officeNote.textContent =
"hidden robots keep walking and the fittings above them still come up; " +
"the lids are off by default, which is why putting them back is a control";
officeSection.body.append(
officeLine,
houseLine,
labelled("lights", houseChips),
houseNote,
robotLine,
labelled("show", showChips),
officeNote,
);
// ---- Performance ----------------------------------------------------------
const perfSection = section("performance");
@@ -732,6 +905,7 @@ export function createGodmode(options: GodmodeOptions): Godmode {
timeSection.el,
weatherSection.el,
...(options.sky ? [skySection.el] : []),
...(options.office ? [officeSection.el] : []),
perfSection.el,
overlaySection.el,
);
@@ -824,7 +998,16 @@ export function createGodmode(options: GodmodeOptions): Godmode {
if (now - lastRefresh < REFRESH_MS) return;
lastRefresh = now;
if (open) refreshLive();
// The office rides the fast path with the counters rather than waiting for
// the next `refresh()`. Everything else on this panel changes because
// somebody moved something or because `main.ts` announced a new instant —
// and `main.ts` announces one once a minute. The house level is neither: it
// ramps against the sun's height, so at dusk it is a number that visibly
// moves, and it is the number this whole section exists to explain.
if (open) {
refreshLive();
refreshOffice();
}
if (!hud.hidden) refreshHud();
}
raf = requestAnimationFrame(frame);
@@ -880,6 +1063,7 @@ export function createGodmode(options: GodmodeOptions): Godmode {
refreshTime();
refreshWeather();
refreshSky();
refreshOffice();
refreshLive();
}
refreshHud();
@@ -919,6 +1103,67 @@ export function createGodmode(options: GodmodeOptions): Godmode {
satelliteChip.setAttribute("aria-pressed", String(satellitesOn && state.satellites !== null));
}
/**
* The building this office handle last described, or `null` for "no room on
* the stage". See `GodmodeOffice` for what a change in it means and why the
* switches are pushed back down when it happens.
*/
let knownOffice: string | null = null;
function refreshOffice() {
const office = options.office;
if (!office) return;
const state = office.read();
officeSection.el.hidden = state === null;
if (!state) {
// Walking out to the city forgets the building rather than the switches,
// so walking back in re-asserts them onto whatever handle is there now —
// which may not be the one that was there when they were set.
knownOffice = null;
return;
}
if (state.id !== knownOffice) {
// Written before the calls, not after. None of the three re-enters this
// panel today, and a debug control that could loop the render loop by
// being wired slightly differently tomorrow is not worth the two lines it
// saves.
knownOffice = state.id;
office.onHouseLights(houseLights);
office.onRobotsVisible(robotsOn);
office.onCeilingsVisible(ceilingsOn);
}
setText(
officeLine,
`${state.id} · ${state.depth === "full" ? "full" : "public — no presence layer"}`,
);
setText(
houseLine,
`house ${Math.round(state.houseLevel * 100)}% · ` +
(houseLights === "sun" ? "following the sun" : `forced ${houseLights}`),
);
for (const entry of houseButtons) {
entry.el.setAttribute("aria-pressed", String(entry.mode === houseLights));
}
// Three states again, and the middle one is again the one worth saying out
// loud: a pack that asked for no robots is not a pack whose robots are
// hidden, and the chip that would toggle nothing says so by going dead
// rather than by appearing to work.
if (state.robots === 0) {
setText(robotLine, "robots none — this pack asked for none");
} else {
setText(
robotLine,
`robots ${state.robots} walking${robotsOn ? "" : ", hidden"}`,
);
}
robotChip.disabled = state.robots === 0;
robotChip.setAttribute("aria-pressed", String(robotsOn && state.robots > 0));
ceilingChip.setAttribute("aria-pressed", String(ceilingsOn));
}
function refreshBanner() {
const parts: string[] = [];
if (override) parts.push(`time ${fmtStamp(override)}`);
+7 -1
View File
@@ -32,4 +32,10 @@
*/
export { createGodmode } from "./godmode.ts";
export type { Godmode, GodmodeOptions, GodmodePlace } from "./godmode.ts";
export type {
Godmode,
GodmodeHouseLights,
GodmodeOffice,
GodmodeOptions,
GodmodePlace,
} from "./godmode.ts";