1
0

feat: the boards stop being three products

The owner asked twice why there are still three separate boards. The honest
answer, and what this round executes: **it feels like three boards, but not
because the scale jumps 20x — because the three packs draw three different
Californias, and the loudest difference is that the mountains are four times
taller on one of them.**

**THE 20x HORIZONTAL SCALE JUMP IS INVISIBLE**, and measuring that collapsed the
cost of this whole round. `World.project` is a uniform scale in x/z with no
vertical term, and a uniform scale leaves a perspective image identical — so a
camera carried across the seam on matched true-metre offsets draws a
pixel-identical horizontal frame. 1,919 -> 94 m/unit costs nothing to look at.
Rescaling was never the problem. A boot card, a tab strip and a 4.17x vertical
deflation were.

**THE PAUSE WAS MOSTLY FAKE.** A switch covered the screen for 1,715 ms but only
608 ms blocked the main thread; the page drew 46 of 69 frames with nothing to
show, because the outgoing board had already been disposed. `mountCity` now
retains it: the incoming board builds BEHIND a live, interactive picture, and
`stage.setScene` fires only on completion. Measured across all six directions,
three runs each — boot card yes -> **no**, opaque cover 726-1,415 ms -> **0**,
blank frames 21-46 -> **exactly 1**, wall clock down 12-29%, blocked main thread
down 15-47%. A return to a board already seen links **zero** shader programs and
blocks **zero** milliseconds: 298-312 ms of camera flight where it was ~1,600 ms
behind a card. Disposal had been throwing away the shader cache too — linkProgram
ran 38, 59, 78, 109, 127 across five mounts and never reused one.

**The transition is a fog dip, not a crossfade**, through the `setAerialFog` seam
built last round. Every both-boards-live crossfade breaks a budget — ca+sf is
2,640,307 triangles against bay-area's 2,600,000 cap — and a fade never lands
inside the harness's sample window, which is the "a cap you do not measure is a
cap you do not have" failure this repo already argues against. The dip costs zero
triangles and zero draw calls, and it hides the 4.17x deflation, the 4,025 m
projection disagreement and the vanishing 2 km freeway symbols at once, because
all three happen at maximum obscuration. It is also diegetic: a descent through
haze.

The first dip was wrong and the photograph caught it: collapsing to 6% of board
SPAN turned the whole night frame into one flat field — the exact "turns the map
off" failure the risk list named. Re-anchored to 70% of camera STAND-OFF, so the
coastline survives and only the relief melts.

**One ladder, one places list.** 26 authored chapters become 24 rungs sorted
descending by STAND-OFF, not altitude — by altitude they interleave badly and
altitude cannot tell a low oblique from a high plan. The three-board tab strip is
off by default; the left column is now one scrolling list of all 24 rungs under
three region headings that does not change when the board does. Only which row is
lit changes. Label collisions are resolved in the ladder and never in a pack, so
the 29 index-aimed capture guards are untouched.

The minimap stops turning through 90 degrees between boards: every board is
pinned to a rectangle with California's proportions.

**SF and SoCal are not regressed**, and that was the acceptance that mattered:
95.9-98.8% of board pixels are delta-0 against a baseline hash-verified identical
to what the live site serves, and every one of the 34-70 surviving pixels per
frame is an aircraft or a hull.

**A real defect found only by photograph:** `minimap.setMarkers()` had zero call
sites. Every marker on every board was gone — the LA studio's door dot, the Bay
Area's eight company markers — dropped when the minimap went per-board.
Typecheck, tests, budgets and the console were all green with that bug in.

Also fixed: two capture presets that lied. `look.mjs`'s `glyph-la` and `glyph-sf`
claimed California chapter closeups and returned SoCal and Bay Area frames,
because they aimed by chapter index and the indices had moved. Aiming is now by
identity, with a guard test.

NOT SHIPPED, DELIBERATELY: the pack merge. At Bay density it is 34.04M triangles,
13x the highest budget — dead, not a trade. At SoCal density it is 1.99M and fits
today, and the price is San Francisco rendering at 164 m lots instead of 40 m,
i.e. SF looking the way SoCal looks now. SF and SoCal carry every marketing still
on the site. That is the owner's decision and it is worthless as an argument and
decisive as a photograph, so it ships as a measurement artifact with a
side-by-side still and is wired into nothing. The four data reconciliations that
would make one world honest — one exaggeration rule, roads in metres, one
projection centre, one coastline convention — are behind TERA_RECONCILE, default
OFF.

Tests 1,570 -> 1,651, server 295. All ten budget cells pass, no cap raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-23 06:20:18 -07:00
parent bcac6aa41a
commit 4bd8481be1
32 changed files with 6628 additions and 264 deletions
+148 -4
View File
@@ -424,6 +424,58 @@
.num { font-size: 9px; letter-spacing: 0.1em; opacity: 0.55; font-variant-numeric: tabular-nums; }
#blurb { margin: 0; color: var(--ink-3); font-size: 10px; line-height: 1.6; }
/* ---- Places -----------------------------------------------------------
The same row as a chapter, so the two lists cannot look like two
different kinds of thing, plus a region heading and a stand-off readout.
Capped and scrollable: twenty-four rungs is a long column on a phone,
where the panel is already a bottom sheet. */
.places {
display: flex;
flex-direction: column;
gap: 1px;
max-height: min(22rem, 42vh);
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
}
.places__region {
margin: var(--s2) 0 var(--s1);
font-size: 9px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--ink-4);
}
.places__region:first-child { margin-top: 0; }
.place {
display: flex;
align-items: baseline;
gap: var(--s2);
padding: var(--s2);
background: none;
border: 0;
border-radius: var(--r-sm);
cursor: pointer;
text-align: left;
font: inherit;
font-size: 12px;
color: var(--ink-2);
transition: background var(--t), color var(--t);
}
@media (hover: hover) {
.place:hover { background: rgba(255, 255, 255, 0.09); color: var(--ink); }
}
.place[aria-pressed="true"] { background: rgba(242, 177, 52, 0.2); color: var(--amber-ink); }
.place__name { flex: 1 1 auto; min-width: 0; }
/* The ladder's own coordinate, in the row it orders. Tabular so the column
of numbers reads as a scale rather than as ragged text. */
.place__standoff {
flex: 0 0 auto;
font-size: 9px;
letter-spacing: 0.06em;
opacity: 0.5;
font-variant-numeric: tabular-nums;
}
/* The panel toggle only exists on a narrow screen, where it doubles as the
header — hence the board name in it. */
.panel-toggle {
@@ -932,6 +984,48 @@
min-height: 1.4em; }
.boot-note { margin: var(--s4) 0 0; font-size: 10px; line-height: 1.6; color: var(--ink-4); }
/* ---- The switch pill --------------------------------------------------
Bottom centre, above the mode dock's own row on a desktop and out of the
way of the left column on a phone. Deliberately small, deliberately not
modal, and it never takes a pointer event: the whole point of it is that
the board underneath stays usable while the next one builds. */
.switching {
position: fixed;
left: 50%;
bottom: calc(var(--s6) + env(safe-area-inset-bottom));
transform: translateX(-50%);
z-index: var(--z-boot);
display: flex;
align-items: center;
gap: var(--s2);
padding: var(--s2) var(--s4);
border-radius: 999px;
border: 1px solid var(--hairline);
background: var(--glass-strong);
backdrop-filter: var(--blur);
box-shadow: var(--shadow);
font-size: 10px;
letter-spacing: 0.06em;
color: var(--ink-2);
pointer-events: none;
white-space: nowrap;
}
.switching[hidden] { display: none; }
.switching-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--amber);
animation: switching-pulse 1100ms ease-in-out infinite;
}
@keyframes switching-pulse {
0%, 100% { opacity: 0.25; }
50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.switching-dot { animation: none; opacity: 1; }
}
/* ---- The play HUD and the mode dock ----------------------------------- */
.play-hud {
position: fixed;
@@ -1213,12 +1307,18 @@
.act,
.board,
.chapter,
.place,
.help,
.detail-close,
.sheet-close {
min-height: var(--tap);
}
.chapter { align-items: center; }
.chapter,
.place { align-items: center; }
/* Twenty-four rungs at a thumb's height each is taller than the sheet.
Shorter here, not shorter everywhere: the list is the navigation on a
phone and scrolling past it to reach the door is the failure. */
.places { max-height: 38vh; }
.help,
.detail-close {
display: inline-flex;
@@ -1321,7 +1421,14 @@
<p id="clock" class="clock" aria-live="polite"></p>
</header>
<section class="panel-group">
<!-- The board strip, which is now the *studio* picker and, outside a
building, a developer's control rather than a visitor's.
Three tabs naming three modes is the single most direct way to tell
somebody they are looking at three products; the places list below
is what replaced it. Kept alive, and reachable with `?boards=1` or
godmode, because it is still the only control that addresses a board
by id. -->
<section class="panel-group" id="boards-section">
<p id="boards-title" class="group-title t-caps">Boards</p>
<nav id="cities" class="boards" aria-label="Board"></nav>
</section>
@@ -1379,10 +1486,36 @@
<p id="birds-note" class="note" hidden></p>
</section>
<section class="panel-group">
<!-- One list of places, under three region headings, ordered by how far
the camera stands back. It is the whole tour of all three boards in
one column, and the rung the camera is standing on lights up and
scrolls itself into view as you descend — so the list reads as an
altimeter rather than as a menu, and crossing from the state into
the Bay is a click on the next line down rather than a change of
mode. See `src/engine/ladder.ts`. -->
<section class="panel-group" id="places-section">
<p class="group-title t-caps">Places</p>
<nav id="places" class="places" aria-label="Places"></nav>
</section>
<!-- The per-board chapter list, unchanged and unchangeable.
Twenty-nine capture guards in `scripts/brand-assets` reach this by
`document.querySelectorAll("#chapters .chapter")[i].click()` and
assert the button's short label, and every still on
lumbridgecorp.com is aimed that way. So the ids, the order, the
short labels, `data-view` and `data-view-index` are byte-identical
to what they were, and the list stays in the document with real
buttons in it — a DOM `.click()` does not care whether an element is
displayed. Outside a building it is `hidden`, because the places
list above is the same journey said better; inside one it is the
viewpoint list and is the visible control. -->
<section class="panel-group" id="chapters-section">
<p class="group-title t-caps">Chapters</p>
<nav id="chapters" class="chapters" aria-label="Chapters"></nav>
<p id="blurb" hidden></p>
</section>
<section class="panel-group" id="blurb-section" hidden>
<p id="blurb"></p>
</section>
</div>
</div>
@@ -1485,6 +1618,17 @@
</div>
</div>
<!-- The switch pill: what a board change says now that it no longer covers
the screen. The board you are looking at stays up, live and draggable,
for the whole of the next one's build, so the only thing left to say is
that something is happening — and it says it in a corner rather than
over the top of the picture. The opaque card below is the first mount of
a session and nothing else. -->
<div class="switching" id="switching" hidden aria-live="polite">
<span class="switching-dot" aria-hidden="true"></span>
<span id="switching-step">Building…</span>
</div>
<div class="boot" id="boot">
<div class="boot-inner">
<p class="boot-mark">Tera</p>