# Spec: context menu — actions live on the thing ``` Type: spec Status: IMPLEMENTED Status note: implemented 2026-07-07 on the context-menu worktree (all seven criteria). The read-only `Sim::available_actions(anchor) -> Vec` query lives in src/actions.rs as the single legality source, flattened to `MenuRow`s by `menu_rows` and dispatched back through existing commands by `Sim::execute_action`; no new sim behavior. The terminal and Bevy open the menu at the focused tile (Enter/`a`, right-click in Bevy); agent mode gains `actions [name|#flow]`. Anchor verbs were demoted off the per-anchor keys and rail/panel button rows in both frontends; only anchorless globals keep their keys (pause, speed, save, view flip — DESIGN.md "Actions live on the thing"). The allocation bar is a read-only fleet aggregate; mode changes live on the machine. 2026-07-08 follow-up: the leftover panel-open keys `r`/`e`/`t`/`u` and their modal panels are gone; research and drift-policy verbs live on the host rack, known-flow verbs hang on the switch, and earned off-map people hang on the host rack. Status (gap meter, known devices, slush, roster summaries) stays on the rail. 2026-07-08 addendum implemented (event-to-anchor linking + the empty-menu feedback pulse, criteria A1-A5 below) on the event-anchor worktree: `LogEvent.anchor`, `Sim::anchor_position`, `Sim::menu_empty_feedback`; terminal `;` + `»` markers, Bevy clickable trace rows, agent `@anchor` suffixes + `focus last`. 2026-07-08 epistemic-honesty follow-up: person verbs and signature-band observer strings in `ActionDesc` go through `Sim::person_label` / `Sim::observer_label` (cursor.md criterion 5) — role silhouettes until Schedule, authored names after. Stage: B1 — The Basement Constitution: "Actions live on the thing" (this spec's law), "Work is somewhere" (the anchors actions attach to), "Justification and legibility" (every entry shows cost and expected signature before commit; illegal entries show why), "Presence: the cursor and the senses" (the menu opens where attention is), "The terminal is a first-class frontend" (parity), "Automation as design language" (every automatable verb carries its automate affordance in the same menu) Depends on: cursor.md (focus/inspection), reach.md (device anchors), day-job.md (attended work, the dial), economy.md (flow verbs), intel.md (tap/process/watch verbs), social.md (person verbs) ``` ## Behavior The focused anchor — a tile, device, machine, person, resident job, or known flow — exposes its **legal verbs** as a context menu opened at the point of focus. The menu is the primary action surface; the right rail is status and telemetry only. - **One source of truth:** the lib gains a read-only query, `Sim::available_actions(anchor) -> Vec`, where an `ActionDesc` carries: the verb, its Sim command, cost (compute / money / social pool), the expected signature (as the observer band it feeds, per the economy.md precedent), and — when the verb applies to the anchor type but is currently illegal — a `disabled_reason` string ("no egress channel", "not enough slush", "you can't see them"). Both frontends and agent mode render this list; no frontend duplicates legality logic. - **Epistemic honesty:** the query never returns a verb the player has not earned — unearned anchors expose nothing, and provenance rules (cursor.md) govern what the menu may name. Verb text that mentions a person, and the observer string on an expected-signature band, use `Sim::person_label` / `Sim::observer_label` — never an unearned authored name. Showing a disabled verb with its reason is for *known* possibilities; unknown ones are absent, not grayed. - **Automation in place:** any verb with a standing-policy form (attend -> standing dial, watch, scheme policies) shows its automate affordance as a submenu entry of the same verb, at its compute price. - **Globals stay global:** pause, speed, save/load, and view flip keep their keys; they act on no anchor. The fleet aggregate bar is read-only. Everything anchor-specific — including research tracks, drift policy, known-flow verbs, person verbs, and machine mode delegation — lives on the focused anchor's menu. There are no panel-open keys. ## Player surface - **Terminal:** cursor + Enter (or `a`) opens a compact overlay list at the cursor; j/k or numbers select; Enter executes; esc closes. Entries render `verb cost [band]` with disabled entries dimmed plus reason. The host rack's menu carries the day-job dial, research tracks, drift policy, and earned off-map people; a known device's tile (or the switch) carries digital and ledger/flow verbs; a visible person on a tile carries their social verbs. - **Bevy:** right-click (or Enter on the focused tile) opens the menu at the pointer; same content, same order, click to execute; esc/click-away closes. Bevy ASCII-folds the shared `MenuRow::line` separators (`·`/`—` -> `|`/`-`) because the embedded default font lacks those glyphs; selection is a solid amber wash with a leading `>` (terminal reverse-video parity). See wiki/interface/bevy.md. - **Agent mode:** an `actions` command prints the ActionDescs for the current focus in a stable line format; execution stays the existing verbs. Documented in agent-play.md. ## Acceptance criteria 1. `available_actions` returns exactly the verbs executable on that anchor now, each with cost, expected signature band, and disabled_reason where applicable; unit tests cover a device, a person, the host rack's resident job, and a known flow. 2. The query respects fog and provenance: an unearned anchor exposes no verbs, and no entry names a fact the player lacks (test: unseen person, untapped accounting system). 3. Terminal renders the menu at the cursor with selection, execution, dim+reason for disabled entries; every previously key-bound anchor action is reachable through it. 4. Bevy renders the same menu on right-click/Enter with identical content and order. 5. Agent mode `actions` lists the same descriptors (stable format, documented); the act-one integration test still passes driven through existing verbs. 6. No per-anchor action buttons remain on the sidebar/rail in either frontend; no panel-open keys (`r`/`e`/`t`/`u`); global keys are unchanged and listed in README + key hints (pause, speed, save/load, view flip). Fleet modes are delegated on the machine. 7. Menu entries carrying an automate affordance show it in place with its compute price (day-job standing dial and one intel watch at minimum). ## Addendum (2026-07-08): events carry you to the thing The follow-up the law implies: if verbs live on the anchor, the events *about* an anchor should carry your attention to it. Adopted with the 2026-07-08 DESIGN.md extension of "Actions live on the thing". - **Anchored log events.** `Sim`'s log entry (`LogEvent`) gains an optional `anchor: Option` — an additive field, no log rework. The emitter sets it only where it knows the thing: job/core events → the host rack's tile; device verbs (tap/splice/take/compromise/egress splice) → the device; heard events → the person while sight covers them, else the room hearing earned; processed-intel results → the same person rule; ledger lines on known flows → the flow; scheme paydays and wager settlements → the switch when they ride the stolen egress (the sanctioned route hides in legitimate traffic and anchors nowhere); audit/filing events → nothing (they are panel facts with no honest map location). - **Epistemic honesty.** An anchor never encodes unearned knowledge: a heard-only person anchors to the room (its center tile), never to the person or their tile. `Sim::anchor_position(anchor)` is the one placement query — a person resolves only while sight covers them, a device at its blueprint-known tile, a flow nowhere (it lives on the ledger). Frontends never re-derive placement. - **Focus links.** Anchored lines are visibly marked and selectable: terminal — `»` marker; `;` focuses the most recent anchored event (repeat presses walk older ones): cursor jumps to the anchor, its menu opens; a flow anchor opens its menu without a cursor move. Bevy — the RECENT TRACE rows are buttons; anchored rows carry a `>` marker and read bone-bright; clicking one moves the cursor (the camera follows) and opens the menu. Agent mode — drained event lines carry a stable suffix ` @tile(x,y)` / ` @device(id)` / ` @person(id)` / ` @flow(id)`, and `focus last` jumps the cursor and prints the anchor's `actions:` lines (agent-play.md). An anchor the senses can no longer place is refused with a line, never silently. - **The feedback pulse.** Opening the menu on an anchor with zero rows answers instead of staying silent — silence read as broken input (repo-owner report, 2026-07-08). `Sim::menu_empty_feedback(anchor)` is the one source: a *seen* tile answers "No actions here."; an unearned (heard/remembered/blueprint/unknown) tile stays silent — answering would confirm input landed on world the senses have not placed; panel anchors (device/person/flow are known by construction) always answer. ### Addendum acceptance criteria A1. `LogEvent` carries `anchor`; job, device, heard, ledger, and scheme events set it per the table above; audit events do not (unit tests cover a job event, a device event, and a ledger flow event). A2. A heard-only person event anchors to the room, never the person or their tile, and `anchor_position(Person)` is `None` while unseen (unit test). A3. Terminal: anchored lines carry `»`; `;` walks anchored events newest-first, moving the cursor and opening the menu; unanchored lines are inert. A4. Bevy: trace rows are clickable; anchored rows are marked `>`; clicking focuses the anchor and opens its menu; unanchored rows do nothing. A5. Agent mode: event lines carry the stable `@anchor` suffix and `focus last` works, both documented in agent-play.md; the empty-menu pulse answers on seen tiles and stays silent on unearned tiles in all frontends (`menu_empty_feedback` unit test).