[ui] round 3 notes

Shipped 1-4, skipped the tech panel (tech.json is still []). The honest parts:
the reference factory can't ship anchor slabs so the SANITISING premise didn't
hold; live pressure verification is impractical in the browser pane (paused rAF
+ HMR churn) so it's headless against the real sim; and I nearly reported
commissionQueue as unpublished before a probe caught SIM landing it mid-round.
Two contract requests (setGhostMode to kill the __remove magic string,
MachineDef.accent) and a ?uidemo proposal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-17 18:34:40 +10:00
parent 9f3a254d85
commit 34e8b39fed

View File

@ -243,3 +243,123 @@ closes the last build-verb gap.
**NEXT (suggested):** remove-by-mouse; heat/scram + SANITISING confirmation once a chain
can be fed; commission queue UI becomes real once requests 1-2 land; tech panel when
`tech.json` fills (still `[]`).
### Round 3 — 2026-07-17 — Opus 4.8
**SHIPPED:** Orders 1-4. Order 5 (tech panel) skipped — see BLOCKED. New: `live.test.ts`
(real sim + real HUD), `chips.test.ts`, `hotkeys.dom.test.ts`, `fax.test.ts`. **105 UI
tests** (was 57); full suite 238/238; `npm run check` clean.
1. **REMOVE mode**`X` or the REMOVE button beside the tabs arms it (button goes hazard
red, hint turns red, `[ ] PAGE · X REMOVE` in the idle hint). Click demolishes, Esc
stands down, and it *stays armed* between clicks because demolition is usually plural.
Toast: "UNIT RECLAIMED. THE FLOOR REMEMBERS." Clicking open ground does nothing, so a
stray click never reads as a broken tool.
2. **NEXT IN TRAY reads truth**`snap.commissionQueue[1]`. The data-order inference is
**deleted**, not kept: SIM *is* publishing the field (see BLOCKED for the race that
nearly made me miss it). No queue → no peek. A fax that lies quietly is worse than one
that says nothing.
3. **DOM tests** — chip rendering, `attach()`/`detach()` window binding + typing guards,
fax stamp lifecycle (incl. back-to-back stamps not clearing early), all under
happy-dom.
4. **Live pressure verification** — see VERIFIED.
**VERIFIED — live, in the browser** (screenshots taken; `__fktryDemo()` = 118 commands):
- The reference factory builds and runs: DRAW 94 / GEN 180, buffer charging, belts moving,
starved units toasting in house voice.
- **REMOVE end-to-end:** armed the mode (button red, `bus._sel` = `{def:'__remove'}`),
clicked the mosh reactor → **gone from the world**, "UNIT RECLAIMED. THE FLOOR
REMEMBERS." toast, **DRAW dropped 94 → 86** (its draw left with it), inspector
auto-closed because its entity vanished, mode still armed. That's the round's DoD.
- Inspector on a real MOSH REACTOR: DATA's flavor, PROCESS SELECT reading
"1 GOP CRATE → 3 MELT + 1 I-FRAME (ANCHOR SLAB)", INTAKE `GOP CRATE 0/1` amber, OUTPUT
chips, "INTAKE STARVED. NOTHING IS ARRIVING."
- Paging tabs + NEXT IN TRAY (DUST ALLOWANCE) + REMOVE all legible in one bar.
**VERIFIED — headless, against the REAL sim** (`live.test.ts`; boots `createSim()` +
`createUI()` on the real `data/*.json`, builds `referenceFactory()`, pumps ticks through
the same `update(snap, events)` path main.ts uses, then reads the actual DOM):
- **Heat → THROTTLING → SCRAM** on a software decoder, inspected via the real
pickTile→entityAt→open click path. Also pinned: the unit **still reads SCRAM while
cooling back below 1.0** — the exact case `heat >= 1` got wrong, now read off
`EntityState.scrammed` per contracts v3.
- **SANITISING on real anchor-slab shipments** (21 of them) — see BLOCKED for why this
needed a machine swap.
- **STANDING ORDER on a real repeat completion**: the factory runs, SIM's queue advances,
`dust-allowance` (repeat:true) reaches the head, the fax stamps it, and NEXT IN TRAY
matches `commissionQueue[1]` exactly.
- **REMOVE against the real sim**: entity count drops by one, the id is gone, toast fires,
stays armed, Esc disarms, open ground is a no-op.
**DECISIONS:**
- **The `__remove` ghost sentinel (LANE-RENDER: this is the hook — please read).** The UI
has no Renderer reference; the only channel to the ghost is `bus.selectedBuild()`, which
main.ts feeds to `setGhost`. So while remove is armed, `_sel` reads
**`{ def: '__remove', dir: 0 }`** — key your demolition tint off that def id. It is safe
because sim's place handler is `const def = defs.get(cmd.def); if (def) ...`, so main.ts's
click-to-place fires a `place` for `__remove`, matches no machine, and does nothing; the
real removal is dispatched by the UI. Exported as `REMOVE_DEF` from `src/ui/selection.ts`
and pinned by a test. A magic string is not how I'd design this — CONTRACT REQUEST 1.
- **Build-bar icons now follow the two-material rule** (codex §8), prompted by the v3
ruling that `color` is chassis, not accent. DATA's chassis values are all desaturated
neutrals — correct for the world, but 21 icons in `#4a4e52`/`#3f4348` is an unreadable
grey wall. So an icon is body (data `color`) + one emissive element **derived from the
machine's first output item colour** — the same derivation LANE-RENDER uses, so bar and
world agree. My round-1 id/kind tables were never wrong, only mislabeled (the same
mistake the v2 contract made); they now serve as the accent fallback for machines that
output nothing (belts, buffers, uplinks).
- Inspector reads `EntityState.scrammed`, not `heat >= 1`. Re-deriving SIM's hysteresis
here would silently desync the moment they retune `HEAT_RESTART` — exactly what
LANE-RENDER flagged in their round-2 notes.
**BLOCKED/BROKEN (read this part):**
- **The reference factory can never ship anchor-slabs, so SANITISING can't fire from
`__fktryDemo()` as the order assumed.** It recycles them on purpose — reference.ts:
*"Recovered anchor slabs come back out of the reactor. Rather than sell them, feed the
i-only assembler."* Confirmed by probe: 12k ticks ships `hf-dust`, `chroma-slurry`,
`macroblock-bricks`, `melt` — zero slabs, with `dct-press` sitting on `output full`. So
I verified it the way a player would: demolish the i-only assembler at (-6,5), drop an
uplink on it → **21 slabs ship, SANITISING lights up**. That's what `live.test.ts` does.
Nobody's bug; the order's premise just didn't hold.
- **Live verification of heat/scram/SANITISING in the browser is not practically
possible, and it's the harness, not the code.** The browser pane keeps the tab
backgrounded (`document.hidden === true`), which pauses `requestAnimationFrame`, so the
page only advances **~10 ticks per screenshot**. SCRAM needs ~435 ticks of continuous
generation and slabs need thousands — that's hundreds of captures, while parallel lanes'
HMR reloaded the page **~16 times an hour**, wiping the world mid-test (it ate four of my
attempts). Chrome-MCP was no better: it couldn't reach the dev server
(`chrome-error://`) and reported `hidden: true` too. Hence `live.test.ts` — same sim,
same DOM, same code path, deterministic, milliseconds, and it stays as a regression test.
I'd rather hand you that than a screenshot I had to fight the harness for.
- **I nearly reported `commissionQueue` as unpublished.** My first grep for
`snap.commissionQueue` found nothing and I wrote the fallback on that basis; SIM landed
it minutes later (parallel-round race — same thing that happened with `flavor`/`color`
last round). A probe against the real sim caught it. Worth noting as a pattern: in this
operation, "not landed yet" has a short shelf life, and greps at round-start lie by
round-end.
- **Order 5 (tech panel) skipped.** `tech.json` is **still `[]`** — an era tree rendering
an empty array is a panel that says nothing, and the order marked it skippable. It's
~30 lines whenever DATA fills the file.
- The `?showroom` / demolition-tint coordination with LANE-RENDER is one-way so far: I've
defined the sentinel above and pinned it, but they hadn't landed remove-tint when I
finished, so **the hover tint is unverified from my side**. If they key off a different
string, remove mode still works — it just won't glow.
**CONTRACT REQUEST:**
1. **`UIBus.setGhostMode(mode: 'build' | 'remove' | null)`** (or let `setGhost` take a
mode) — *why:* remove-mode hover currently rides a magic def id through
`selectedBuild()`, and main.ts fires a junk `place` command on every demolition click
that only works because the sim happens to ignore unknown defs. It's two lanes agreeing
on a string in a NOTES file. One typed field kills it.
2. **`MachineDef.accent?: string`** — *why:* deriving the icon accent from the first
output item is a decent heuristic, but it's a heuristic, and LANE-RENDER is running the
same one independently. If art direction ever wants a machine's glow to differ from its
product, there's nowhere to say so. (LANE-RENDER asked for this in round 2 as well.)
**PROPOSAL:** `?uidemo` — a URL param that runs `__fktryDemo()` on boot. Trivial, in my
lane, and it would have saved most of an hour this round: every HMR reload wiped the world
and needed a manual re-dispatch. Say the word.
**NEXT (suggested):** tech panel the moment `tech.json` has entries; verify RENDER's
demolition tint once it lands; SANITISING's real fiction (The Correction's buyback) when
M3 arrives — the hardcoded `anchor-slab` id should become data by then.