glytch/fktry/lanes/LANE-UI.md
type-two 86d765f90b [orchestrator] round 1 review + contracts v2 + round 2 orders
Review: all 5 lanes pass — 70/70 tests, tsc clean, ownership audit clean,
integration verified live (sim -> events -> UI ticker + SCREEN corruption).

Contracts v2 (granted from lane requests): UIBus.pickTile (wired), BeltItem.id?,
MachineDef.{color,flavor,heatPerTick,bufferCap}?, CommissionDef.repeat?,
SimEvent 'scram'. main.ts: non-left pointerdown no longer places. happy-dom added.

Rulings: canon melt chain IS M1 (masterplan updated); rank-routing stays, splitters
next; anchor-slab sanitizing -> Correction buyback fiction; build-bar paging approved;
per-lane dev ports 8151-8155. Round 2 orders written in all lane docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:42:58 +10:00

149 lines
9.2 KiB
Markdown

# LANE-UI — HUD, build tools, and the voice of the game
You are an Opus 4.8 executor on the UI lane of FKTRY. Read `../MASTERPLAN.md` and
`../CONTRACTS.md` first; the world bible is `../../docs/FKTRY_LORE.md`. The game's
voice is §1's "OSHA-poster deadpan" — you write most of the words the player reads.
Every label, tooltip, and error is an opportunity; "Inventory full" is a crime when
"OUTPUT BUFFER AT CAPACITY. THIS IS A YOU PROBLEM." is available.
**Mission:** everything DOM. Build bar, inspector, resource readouts, commission fax,
pause/speed, hotkeys. Plain TypeScript + DOM — no framework, no CSS libs.
**Owned paths:** `src/ui/**` (styles included — inject a `<style>` tag or import a css
file from your dir).
**Never touch:** contracts, main.ts, other lanes. You never mutate snapshots; you
dispatch Commands via the bus and read state from `update(snap, events)`.
**Standing rules (all rounds):**
- `#ui` root has `pointer-events:none`; every interactive child you create re-enables
it. Clicks must fall through to the game world everywhere else — test this.
- Keyboard map lives in one file (`hotkeys.ts`) — it will grow.
- Terminal-industrial aesthetic: monospace, `#d8ffd8` on dark, hot accents `#ff3fd4` /
`#3fffe0` (match GLYTCH's palette; see `../../index.html` for reference). No rounded
corners. This is machinery, not a startup.
---
## CURRENT ORDERS — Round 2 (goal: finish M1 interaction, open M2 "PRESSURE")
Round 1 review: strong, and the honesty section was the best in the fleet. Verdicts:
CONTRACT REQUEST 1 GRANTED — `UIBus.pickTile` is live, wired to the renderer in
main.ts. Req 2 GRANTED (`MachineDef.color?`). Req 3 GRANTED as `MachineDef.flavor?`
LANE-DATA fills it this round; read data first, keep voice.ts as fallback. Req 4
GRANTED — `happy-dom` is installed; use `// @vitest-environment happy-dom` per test
file (no root config change needed). Build-bar paging PROPOSAL: APPROVED.
1. **Click-to-inspect** via `bus.pickTile`. Delete the TAB-cycle fallback and
`?uifixture` — the sim is real now; drive a brownout by building.
2. **Build bar paging** (your design): kind tabs, `[`/`]` to page, 1-9 within the
active page — every machine hotkey-reachable at any catalog size.
3. **Inspector upgrades**: recipe picker (`setRecipe` finally gets its UI), heat bar
with THROTTLING/SCRAM states, and starved-vs-backed-up wording (sim distinguishes
`'starved'` from `'output full'` this round).
4. **Commission queue UI**: current fax + a "NEXT IN TRAY" peek; `repeat:true`
commissions get a STANDING ORDER stamp. When anchor-slabs ship, show a SANITISING
chip (hardcoding the id is fine this round — the fiction becomes The Correction's
buyback in M3).
5. **Tests, minimum 10**: chips, roster/footprint hit-testing, selection, hotkey map —
under happy-dom.
**Definition of done:** the reference factory (SIM ships `src/sim/reference.ts` this
round) is inspectable end to end by mouse alone; every machine reachable by hotkey;
tests green; fixture deleted.
---
## NOTES (append-only log, newest at bottom)
```
### Round N — YYYY-MM-DD — [your model]
SHIPPED: / DECISIONS: / BLOCKED/BROKEN: / CONTRACT REQUEST: / PROPOSAL: / NEXT:
```
### Round 1 — 2026-07-17 — Opus 4.8
**SHIPPED:** All five ordered surfaces, in `src/ui/**`, verified in a browser (screenshots
taken of each). Modules: `index.ts` (root/wiring), `style.ts` (injected CSS), `voice.ts`
(all player-facing copy), `hotkeys.ts` (the keymap, one file as ordered), `buildbar.ts`,
`topstrip.ts`, `inspector.ts`, `fax.ts`, `toasts.ts`, `chips.ts` (shared item chip),
`selection.ts` (writes `bus._sel`), `palette.ts`, `pick.ts`, `dom.ts`, `fixture.ts` (dev).
1. Build bar — one button per machine, hotkeys 1-9, R rotates, Esc/right-click clears,
selected button goes hot magenta, hint line shows facing + verbs.
2. Top strip — bandwidth meter (cyan → amber ≥85% → red + flashing BROWNOUT over 100%),
buffer, shipped ticker + per-item chips, TICK / HALTED.
3. Inspector — name, codex flavor, INTAKE/OUTPUT chips w/ live buffers, process bar,
heat readout w/ THROTTLING, jam reason in amber. Esc or X closes.
4. Commission fax — fax-paper card under THE SCREEN, wants as chips w/ `1/1` progress,
reward line, and a FAX SENT rubber stamp on `commissionDone`.
5. Toasts — max 4, 6s TTL, colored per event kind, house voice.
**VERIFIED:** `npm run check` clean; `npm test` 70/70 (other lanes' suites). Dev server on
:8151 (8150 was another lane's session). Confirmed with own eyes: hotkey 1 → `bus._sel`
`{seam-extractor,0}`; R → dir 1 → 2; Esc → null; TAB → inspector paints SEAM EXTRACTOR
with flavor + MDAT ORE chip + process bar; brownout → red border/BROWNOUT/buffer 0 + both
toasts; FAX SENT stamp + its toast; pointer fall-through (`#ui` computes `pointer-events:
none`, empty world hits CANVAS, panels are solid). No console errors.
**DECISIONS:**
- All player-facing strings live in `voice.ts` so the OSHA-deadpan register stays in one
place and isn't re-invented per panel.
- The UI keeps its own flattened `roster` (pick.ts) instead of holding the snapshot:
CONTRACTS says buffers may be reused between snapshots, and pointer/key handlers fire
*between* frames. The backing array is reused so 500 entities don't allocate per frame.
- Esc is overloaded deliberately: drops the pending build first, closes the inspector
second — dropping the tool is what a player expects from the first press.
**BLOCKED/BROKEN (read this part):**
- **Click-to-inspect does not work.** `ui.init(root, data, bus)` never sees the Renderer,
and `UIBus` has only `dispatch`/`selectedBuild` — so the UI cannot reach
`renderer.pickTile`, which is the only thing that knows the camera. LANE-RENDER has a
working `pickTile` this round; I just can't call it. `pick.ts` opportunistically probes
`window.__fktryRenderer.pickTile` (nobody sets it) and otherwise falls back to **TAB
cycles the unit manifest**, which is how the inspector is reachable with no console
today. Tile→entity hit-testing (incl. footprint rotation) is written and ready; it is
one contract line away from working. See CONTRACT REQUEST 1.
- **LANE-DATA shipped 21 machines; hotkeys only cover 9.** Slots 10-21 are click-only,
and that includes MOSH REACTOR, DECODE ASIC and SCREEN UPLINK — three of the seven M1
chain machines. The M1 chain is fully placeable by *mouse*, and 1-9 works, but "the M1
chain on hotkeys" is not literally true. The bar now wraps to 3 rows so nothing is off
the viewport (it ran off both edges at 21). See PROPOSAL.
- **No DOM tests for `src/ui`.** No `jsdom`/`happy-dom` installed and I may not touch root
config, so this lane has zero unit tests — everything above was verified by hand in the
browser. See CONTRACT REQUEST 4.
- `?uifixture` is dev scaffolding I wrote (`fixture.ts`) because the sim stub reported an
empty world at the time — a fake snapshot on a wall clock so every surface could be seen
working. It is inert without the query param and dispatches nothing. LANE-SIM is real
now; this can be deleted once the real sim can be driven to a brownout by hand.
- Harness note, not a bug: the browser-automation `key: space` delivers an empty
`event.key`, so pause could only be exercised via a dispatched `' '` KeyboardEvent
(works: HALTED renders) and via `dispatch({setPaused})` (works). Real spacebar sends
`' '` per spec; the handler matches that.
**CONTRACT REQUEST:**
1. **`UIBus.pickTile(clientX, clientY): Vec2 | null`** (or pass the `Renderer` to
`ui.init`) — *why:* without it the inspector cannot be opened by clicking a machine,
which is the core interaction of order #3. Renderer already implements it; only the
plumbing is missing. This is my only real blocker.
2. **`MachineDef.color: string`** — *why:* `ItemDef` has `color`, `MachineDef` doesn't, so
the build bar has nothing data-driven to tint icons with. LANE-RENDER's orders say to
tint placeholder boxes "per codex color" — same gap. Right now `palette.ts` and their
renderer each hand-maintain a color table, which is how the build bar and the world end
up disagreeing about what a quantizer looks like. Data should own it once.
3. **`MachineDef.flavor: string`** (codex one-liner) — *why:* orders told me to hard-code
a lookup; I did (`voice.ts`), but only 7 of 21 machines have one and the other 14 fall
back to generic text. This is LANE-DATA's material, not mine.
4. **`jsdom` (or `happy-dom`) devDependency + `environment: 'jsdom'`** — *why:* the UI lane
currently cannot write a single test. Chips/roster/footprint/selection logic are all
pure and testable the moment there's a DOM env.
**PROPOSAL:** build bar paging — group the 21 machines into kind tabs (EXTRACT /
LOGISTICS / REFINE / POWER / SHIP), `[`/`]` to page, 1-9 within the active page. Makes
every machine hotkey-reachable and scales as LANE-DATA keeps adding. Not done this round:
the orders said "one button per machine, hotkeys 1-9" and redesigning the bar unasked is
exactly what MASTERPLAN says not to do. Say the word and it's a small round-2 change.
**NEXT (suggested):** wire click-to-inspect the moment request 1 lands and delete the TAB
fallback; recipe picker in the inspector (`setRecipe` exists in Command and has no UI);
build bar paging; tech/research panel once `tech.json` is populated (it's `[]`).