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

9.2 KiB

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: stringwhy: 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 []).