1
0

feat: land on a real place, and let a wheel notch cross the seam

The owner has now asked four times why there are three boards, and the last
answer missed the point: reconciling the packs made them draw one California,
but you still *arrived* on the coarsest tier the product owns and still changed
boards by picking a name off a list. This changes both.

**The behaviour was already built and behind a second flag defaulted off.**
`handover()` in `ladder.ts` — promote/demote by camera stand-off, hysteresis at
0.9/1.15, a drag guard so a board never swaps under a finger — was pure, tested
and shipped a round ago, with `?handover=1` as the only way to see it. It is on
now, `?handover=0` turns it off. Measured on the deployed bundle first:
promotion into the Bay Area fired at the sixteenth wheel notch in from the state
pose, arrival clean, no boot card, no tab, no click on a name.

**And it did not work, for a reason worth writing down.** Landing on the Bay
with the flag on bounced straight back to the state board with no input at all.
Two wrong diagnoses on the way, both from reasoning instead of measuring:

  1. "sf's ceiling equals its own widest pose, so the band is too tight." It is
     not — `chapterStandoffMetres` puts the resting pose at 71.2 km against a
     demote threshold of 81.9 km.
  2. "the arrival flight carries the camera through the threshold, so guard on
     `arriving()`." Right about the cause, wrong about the mechanism: the guard
     went in and the bounce survived it.

A trajectory log settled it in one run. The handover tick arrived *before* the
first `arriving=true` sample, at 108,316 m — 1.5x sf's resting stand-off, which
is `arrivalStart`'s own offset. `arrive()` read:

    kit.setPose(from);
    arrival = { from, to: rest, elapsed: 0 };

`setPose` drives `OrbitControls`, which fires `change` **synchronously**, and
`main.ts` listens on that event. So the listener ran on the line *between* those
two statements: camera already 1.5x out, `arriving()` still false. A one-frame
ordering race, and the guard could not fire because the flag it reads was set one
statement too late. The assignment now goes first.

The guard stays, because the inequality behind it is structural rather than
incidental: `ARRIVAL_STANDOFF` is 1.5 and `DEMOTE` is 1.15, both global, so the
opening frame of *every* board sits outside that board's own retention band.
`handoverArrivalGuard.test.ts` asserts that relation and drives the real rule
through the opening stand-off to watch it demote, so neither the guard nor either
constant can be quietly simplified.

**The landing board is `DEFAULT_CITY_ID`, and it is the Bay Area.** It was
`CITIES[0]` in three places, which meant the state tier: seventeen districts over
1063x930 km at 1,919 m per unit, no city legible, and a left column whose first
offer is somewhere else to go. The detailed boards carry 52 and 47 districts at
94 and 391 m per unit. With free handover on, the state tier stops being a
destination and becomes what you get when you pull back — the role it is good at,
since it is the only board drawing 97.4% of California. A named constant rather
than reordering `CITIES`, because that array's order is the `?city=` fallback and
is read positionally by other consumers.

**Which caught a silent break in the capture harness, and this is the part that
would have cost a week.** `shots.mjs` and `films.mjs` built `?city=` only when a
shot declared one — and 4 of 21 shots and 1 of 4 films declare none, so they
inherited the app's default. Moving that default would have re-pointed five
pieces of marketing imagery at a different place while every filename, caption
and alt text stayed as it was. Both harnesses now name `california` themselves.
`performance-budget.mjs`'s `california` and `california-drive` cells had no query
at all for the same reason; `signature` would have failed them loudly rather than
mismeasuring, which is the harness working, but a harness that depends on an app
default reports someone else's change as its own flake.

Every harness also pins `handover=0`. A planted pose wider than a board's
retention band would otherwise demote to the coarser tier while the shutter is
open, and the frame that comes back is a real photograph of the wrong board.

Verified: bare URL lands on the Bay Area and stays there; `?city=california` and
`?city=socal` still deep-link; `?handover=0` stays put; zooming out from the Bay
demotes to the state tier at the second notch. 1,694 tests pass. All ten budget
cells pass with no cap raised, and every cell now measures the board it names.

Still true and now a decision rather than a doubt: 97.4% of California has no
board below 242 km of stand-off, so zooming into the middle of the state lands on
coarse ground. That picture has been looked at. Authoring Sacramento, Fresno and
the Central Valley is what retires it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-24 00:13:23 -07:00
parent 6fde2f0c97
commit 6fc0b2b60e
7 changed files with 285 additions and 14 deletions
+39 -2
View File
@@ -602,6 +602,22 @@ export interface SceneHandle {
* `AERIAL_SUBJECT_CLEARANCE`.
*/
cameraStandoffMetres(): number;
/**
* Whether the unrequested opening move is still in the air.
*
* Exposed for one caller and one reason: `main.ts`'s free-camera handover must
* not read a stand-off that the *arrival* is still changing. `arrivalStart`
* begins the move at `ARRIVAL_STANDOFF` — 1.5x the resting stand-off — and the
* ladder demotes a board above `DEMOTE`, which is 1.15x its handover ceiling.
* 1.5 is greater than 1.15 for every board there will ever be, so the opening
* frame of *any* board is outside that board's own retention band and a
* handover that ran during it would immediately throw the visitor back to the
* coarser tier they came from. Which is exactly what it did.
*
* This is the same class of guard as the drag guard: not "the camera is
* somewhere odd" but "nobody has asked for this camera position yet".
*/
arriving(): boolean;
/** The reflectivity raster, or `null`. A no-op without a precipitation layer. */
setPrecip(field: RadarField | null): void;
/** Tonight's migration, or `null`. A no-op without a migration layer. */
@@ -1382,8 +1398,26 @@ export async function createScene(
}
const rest = heroPose(openingPose, orbitMaxDistance);
const from = arrivalStart(rest, orbitMaxDistance);
kit.setPose(from);
/*
* **Record the move before making it.** `kit.setPose` drives
* `OrbitControls`, which fires `change` *synchronously*, and `main.ts`
* listens on that event to decide whether the camera has left this board.
* Written the obvious way round — pose first, then bookkeeping — that
* listener runs on the line between them, sees a camera 1.5x further out
* than this board's own resting stand-off and an `arriving()` that is
* still false, and hands the visitor back to the coarser tier before the
* opening move has drawn a single frame.
*
* That is precisely what shipped: landing on the Bay Area bounced to the
* state board with no input at all, and the guard written to prevent it
* could not fire because the flag it reads was set one statement too late.
* The trajectory log that found it showed the handover tick arriving
* *before* the first `arriving=true` sample, at the same stand-off.
*
* So the assignment goes first. Nothing else in this function changed.
*/
arrival = { from, to: rest, elapsed: 0 };
kit.setPose(from);
},
setLighting: (state) => {
lighting = state;
@@ -1429,6 +1463,9 @@ export async function createScene(
},
setFires: (view) => fireLayer?.setFires(view),
setFireSmoke: (visible) => fireLayer?.setSmokeVisible(visible),
arriving() {
return arrival !== null;
},
cameraStandoffMetres() {
// `metresPerUnit` and NOT `unitsToMetres`, which is the vertical
// conversion and divides the exaggeration back out. A stand-off is a
@@ -1661,7 +1698,7 @@ const HERO_ELEVATION_DEG = 29;
const HERO_DISTANCE = 0.9;
/** How much further out the camera stands before the move, as a multiple. */
const ARRIVAL_STANDOFF = 1.5;
export const ARRIVAL_STANDOFF = 1.5;
/** How much higher, as a multiple. Larger than the stand-off: the move descends. */
const ARRIVAL_LIFT = 2.2;
/** How far round the board it swings, in radians. Negative is anticlockwise. */