[ui] round 5 notes

All nine orders shipped. The tab-click report was a real bug (scrollbar eating
half of every tab hitbox) and is fixed; RENDER's demolition tint is verified
with a screenshot, closing the item owed since round 3.

The honest parts: DATA's commission queue head-of-line blocks the whole game,
RENDER doesn't read the inspect selection yet, and the radio drives a stand-in
until SCREEN publishes its control. One proposal (audio on the bus), no
contract requests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-18 14:12:42 +10:00
parent 4da8096de9
commit 0cc80d3c82

View File

@ -471,3 +471,120 @@ breaks for no good reason.
**NEXT (suggested):** verify RENDER's tint; research rig in reference.ts so a pack economy **NEXT (suggested):** verify RENDER's tint; research rig in reference.ts so a pack economy
exists to test against; scrolling tab strip if the catalog grows again; era-skinned tech exists to test against; scrolling tab strip if the catalog grows again; era-skinned tech
panel once eras mean something visually (§8). panel once eras mean something visually (§8).
### Round 5 (MEGA) — 2026-07-18 — Opus 4.8
**SHIPPED:** All nine orders. New modules: `power.ts` was round 4; this round adds
`radio.ts` (SPEAKER bridge + echo stand-in), `tuner.ts` (the dock), `screenview.ts`
(enlarge), and tests `tuner.test.ts`, `wildlife.test.ts`, `screenview.test.ts`.
**194 UI tests** (was 145); full suite 446/446; `npm run check` clean.
1. **Scrolling tab strip** — one row, never wraps, active tab scrolled into view on
`[`/`]`. Found and fixed a real bug doing it: see the hitbox finding below.
2. **Inspect mode published**`setSelection({mode:'inspect',entity})` on open, cleared
on close (X, Esc, or the unit being demolished under it). Holding a tool supersedes it
and shuts the panel, so panel and highlight can't disagree.
3. **Wildlife surfaced** — infestation toasts in voice; MOSQUITO EXPOSURE + severity in
the inspector, read from `snap.wildlife` by `target`; hatch warnings deduped one per
pile id.
4. **Research tests migrated** to SIM's `testkit.grantResearch`/`grantAllResearch` — my
round-4 hand-rolled save-blob edit is gone, and with it the coupling to SIM's schema.
Tint + highlight verified, see below.
5. **First-brownout mercy toast** — once per session, on the first brownout event.
6. **Camera hint** in the idle line; **tab hitbox adjudicated** (below).
7. **`no seam` jam voice** — "THERE IS NOTHING DOWN THERE. MOVE." SIM emits this reason
at index.ts:498, so it's live copy, not speculative.
8. **THE SCREEN click-to-enlarge** — ~2.5×, dimmed backdrop, Esc/click closes.
9. **THE TUNER DOCK** — hidden until the fossil is excavated, then slides in with dial
(drag + ←/→), AM/FM/SW, volume + mute, and a station readout in voice.
**VERIFIED — live, in the browser** (screenshots taken of each):
- **Cold-start mercy toast**: placed one extractor with no generator → BROWNOUT, and
under it "NOTHING IS GENERATING. THE COMMITTEE SUGGESTS: POWER." That's the DoD's
opening requirement.
- **THE SCREEN enlarge**: the live WebGL sky at 2.5× centred over a dimmed HUD; Esc
restored its `style` attribute to `null` and its dock rect to y=10, w=322 — exactly as
found.
- **THE TUNER DOCK**: excavated SIM's real seeded fossil → dock slid in bottom-right with
"AUDIO BUS RESTORED. THE BITSTREAM HAS A VOICE.", dial, FM active, VOL/MUTE, hint line.
- **RENDER's demolition tint — the item owed since round 3 is CLOSED.** Armed remove,
hovered a machine, and it renders outlined in demolition red. Screenshot taken.
- Build bar with padlocks on the POWER page (DECODE ASIC free; SOFTWARE DECODER and ASIC
COOLER greyed + 🔒 + REQUIRES:), reference factory healthy at GEN 220 / DRAW 158.
**VERIFIED — headless** (`live.test.ts`, real sim + real DOM, 19 tests): mercy toast fires
once and *stays* once across a second brownout; inspect selection published on open and
dropped both on Esc and when the inspected unit is demolished; the tuner stays hidden
until `relics[0].found` flips and then reveals with the ceremony toast and the toast
standoff; full research loop (lock → pick → deliver → unlock → place) now driven through
`grantResearch` and the lab's real per-pack timing.
**DECISIONS:**
- **THE SCREEN is enlarged in place, not mirrored.** I built the mirror first; it rendered
black. THE SCREEN is a WebGL canvas, and `drawImage` from a WebGL context without
`preserveDrawingBuffer` returns nothing. Asking SCREEN to enable that flag would cost
them a buffer copy every frame for an occasional feature, so I enlarge the real element
with inline styles and restore its `style` attribute verbatim on close (tested,
including the case where SCREEN later adds inline styles of their own).
- **The radio bridge is guarded** (`radio.ts`). `src/audio` is still the stub — no `radio`
export exists — so the dock resolves a control at runtime and otherwise drives a silent
echo stand-in, which is why every dial is testable today. **LANE-SCREEN: publish
`window.__fktrySpeaker = { radio }` (or tell me the export name you prefer) and the dock
drives real audio with zero change here.** The interface I'm driving is exactly your
documented one: `setFrequency / setBand / volume / mute / getState / subscribe`, with
`getState()` returning `{freqMHz, band, volume, muted, station}``station: string|null`
is what flips the readout to "SIGNAL LOCKED". `isLive()` reports which one is attached.
- Wildlife exposure shows the swarm's **severity** (`WildlifeState.size`), not a
sim-confirmed throughput number — SIM owns the real penalty and doesn't expose one.
Labelled as exposure rather than a promise about output.
**BLOCKED/BROKEN (read this part):**
- **The tab-click bug was REAL, and I fixed it.** Reproduced with a genuine mouse click:
a click on POWER did nothing. `elementFromPoint` at the button's own centre returned the
*strip container*, not the button — the horizontal scrollbar was being painted over the
lower half of every tab and swallowing clicks there, which is exactly why keyboard
paging still worked. Fixed by giving the scrollbar its own lane (`padding-bottom: 7px`
≥ scrollbar height); the full button height now hit-tests to the button, and a real
click activates POWER. **Separately**, `computer` clicks via `ref` deliver *no* events to
the page at all (document-level capture recorded nothing); coordinate clicks work fine.
So: real bug, now fixed, plus a harness quirk on top of it.
- **DATA's commission queue head-of-line blocks, and it stalls the whole game.** The v5
queue opens with `raw-payload` (20 raw MDAT ORE), but the reference factory refines every
scrap of its ore and never ships it — so the head commission can never complete and all
19 behind it are unreachable. Probed: 12k ticks ships hf-dust, chroma-slurry,
macroblock-bricks, melt and anchor-slab, and `commissionDone` fires **zero** times. My
tests now build the obvious player rig (extractor → belt → uplink) to pay it. Worth a
design look: either the opening commission should want something the tutorial factory
ships, or an unsatisfiable head shouldn't block the tray.
- **Machines only push onto belts, never into an adjacent machine.** Cost me a while to
find; noting it because it's non-obvious and unstated anywhere in CONTRACTS.
- **RENDER doesn't consume the inspect selection yet** — no `inspect` anywhere in
`src/render`. I publish it correctly (verified live: `{mode:'inspect',entity:105}`), so
the highlight lights up whenever they read it. **Not owed by me any more; owed to me.**
- **Stale ghost on keyboard disarm**: main.ts only refreshes the ghost on `pointermove`, so
Esc-ing out of remove mode leaves the demolition tint on screen until the mouse moves.
Visible in my inspect screenshot. Cosmetic, and it's main.ts's glue, not mine.
- **SIM landed wildlife AND relics mid-round, again** — my round-start grep found neither.
Fifth round running. It's why my wildlife/relic UI could be verified against the real sim
at all, so this is a good problem, but the pattern is now completely reliable: I re-probe
a *running* sim before writing NOTES, never a grep from round-start.
- Three of my live tests failed mid-round from SIM/DATA changes, not my code (proved by
stashing my diff and re-running). Also hit `DUST_SPILL_GRACE is not defined` from SIM
mid-edit, which resolved itself between two consecutive commands.
- Harness, for the record: the browser pane backgrounds the tab, which pauses rAF **and
CSS transitions**. I lost time reading a transitioned `bottom` that never advanced and
briefly thought the CSS was broken; `transition:none` proved it computes correctly. Same
root cause as the paused sim loop from rounds 3-4.
**CONTRACT REQUEST:** none. v5/v6 gave me everything these orders needed.
**PROPOSAL:** `UIBus.audio?: { radio?: RadioControl }` — the tuner currently reaches THE
SPEAKER through a global handshake because `ui.init` never sees the audio module, which is
the same shape of problem as `pickTile` in round 1 and the `__remove` sentinel in round 3.
One optional field on the bus would make it typed and testable instead of a documented
convention. Not blocking: the guarded bridge works.
**NEXT (suggested):** wire the real radio the moment SCREEN publishes it (and describe how
it sounds in NOTES, since they're doing the same); inspect highlight once RENDER reads the
selection; a station-locked readout screenshot once there are real stations; commission
head-of-line blocking is a design question for you, not a UI fix.