A video game where you play as a misaligned AI, deceiving and building power. An experiment in spec-driven development. forked from cameron.stream/misaligned
misaligned wiki interface context-menu.md
24 kB · 405 lines

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<ActionDesc>` query lives in crates/misaligned-core/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 — wiki/vision/simulation-laws.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 verbs live on
  the host rack, known-flow verbs hang on the switch, and earned off-map
  people hang on the host rack. Status (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.
  2026-07-09 addendum IMPLEMENTED (status dials, criteria D1-D4) —
  `Sim::human_menu` collapses mutually exclusive standing choices into
  dial rows on terminal/Bevy; agent `actions` stays a flat dump. ROADMAP
  #36.
  2026-07-09 selection hotkeys: with a machine selected (or under cursor),
  `1`–`3` assign modes, `5`–`9` fire root one-shots, `Enter`/`e` open that
  machine's menu — thin keyboard surface over the same human_menu source.
  2026-07-10: day-job target and drift-policy dials are removed. Focused /
  selected machines use `i` to cycle persistent intensity directly; this
  frequent physical control deliberately has no explanatory picker.
  2026-07-10 vocabulary survey: action-vocabulary.md now owns canonical
  player-facing names and the LIVE/STUB/alias/retired classification.
  2026-07-10 action-contract pass: `ActionKind` metadata is the shared
  presentation/help registry; STUB definitions are filtered from the query;
  controls are textually and visually distinct from committed actions; and
  `5`-`9` skip controls.
  2026-07-10 epistemic-progressive-disclosure follow-up: an opaque raw
  recording earns REVIEW plus its watch affordance, not the person's entire
  future social catalog. Message, favor, leverage, deception, and recruitment
  verbs remain absent until processing stages social knowledge.
  2026-07-10 Bevy focus repair: Enter/`e` opens the hovered map tile's menu
  before consulting machine selection, so a switch or other non-machine
  anchor never snaps attention back to the selected core.
  2026-07-10 hover-grammar amendment: an owned computer under the Bevy
  pointer exposes the bare `WORK / THINK / LIE` line beside the machine while
  the full context menu is closed. The current mode is bone-bright and the
  alternatives are dim chrome; there is no box, heading, subtitle, counter,
  cost, or explanatory mode name. The existing `1`–`3` one-press controls
  remain the action path.
  2026-07-10 machine-anchor correction: the pointer chooses the hovered
  computer but does not place its grammar. The line projects from a fixed
  point at that server's base, so moving within the same rack cannot drag the
  words around the world.
  2026-07-10 reticule-target correction: pointer hover is only the first
  target source. If the pointer leaves the machine, the line remains on the
  selected machine or the owned computer under the attention reticule,
  matching the existing `1`–`3` target fallback instead of disappearing.
  2026-07-10 compact-menu amendment: human menus are terse choice lists.
  They render verb labels and dial state only, omitting descriptor metadata,
  literal control tags, inline blocked explanations, and repeated key-help
  footers. Full descriptors remain in agent output; blocked reasons narrate
  when attempted.
Stage: B1 — The Basement
Design:
  - wiki/vision/simulation-laws.md#actions-live-on-the-thing
  - wiki/vision/simulation-laws.md#work-is-somewhere
  - wiki/vision/simulation-laws.md#justification-and-legibility
  - wiki/interface/presence.md#presence-the-cursor-and-the-senses
  - wiki/interface/terminal-first.md#the-terminal-is-a-first-class-frontend
  - wiki/vision/simulation-laws.md#automation-as-design-language
Depends on:
  - wiki/interface/action-vocabulary.md#spec-action-vocabulary-what-the-player-can-tell-the-process-to-do
  - wiki/mechanics/cursor.md#spec-the-cursor-and-the-senses
  - wiki/mechanics/reach.md#spec-digital-reach
  - wiki/mechanics/day-job.md#spec-the-day-job
  - wiki/mechanics/economy.md#spec-economy-money-as-a-flow-system-b1
  - wiki/mechanics/intel.md#spec-intel-record-and-process
  - wiki/mechanics/social.md#spec-social

Dependency notes #

The structured references above identify the contracts to re-verify. Relationship context:

action-vocabulary.md owns canonical action names and meanings; cursor.md owns focus/inspection; reach.md owns device anchors; day-job.md owns delivered work; economy.md owns flow verbs; intel.md owns tap/process/watch verbs; social.md owns 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<ActionDesc>, where an ActionDesc carries: the verb, its Sim command, cost (compute / money / visible Demand; AMENDED 2026-07-10 — ops verbs now open thought sinks per machine-work.md, so their cost reads as the reservoir's ops-to-fill or a tap's standing drain rather than a one-shot compute spend), 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"). Agent mode renders the complete descriptor. Human frontends render its verb as a compact choice and use the descriptor only for legality, execution, and visual role; no frontend duplicates legality logic. ActionDesc::verb uses the canonical stem from action-vocabulary.md, inflected only by target and current state. Its bound command maps exhaustively to the runtime ActionKind registry, which supplies role, support, target types, canonical help, and aliases. available_actions removes STUB definitions before returning.
  • 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. Returning a disabled verb and narrating its reason on attempt is for known possibilities; unknown ones are absent, not grayed. A raw recording is knowledge that there is material to process, not knowledge of a relationship: before Knowledge::Schedule, that recording's person anchor exposes REVIEW and its watch affordance only. Processing must stage social knowledge before communication, favor, leverage, deception, or recruitment verbs enter the menu.
  • Automation in place: any verb with a standing-policy form (watch, scheme policies) shows its automate affordance inside that dial's picker (or as a submenu of the same verb when the verb is not a dial), at its compute price — never as a root sibling of every alternative.
  • 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. Infrequent anchor-specific verbs — including research tracks, known-flow verbs, and person verbs — live on the focused anchor's menu. The focused machine's two frequent controls (mode and intensity) have the direct surface defined below. There are no panel-open keys.
  • Hover / selection hotkeys (DECIDED 2026-07-09, amended 2026-07-10; menu closed). A thin focused-machine surface: mode and one-shot keys reuse human_menu / available_actions; intensity calls the lib-owned machine setter directly and keeps no frontend state. Target order is (1) the machine under the pointer, (2) the multi-selection if any, (3) the keyboard cursor's machine. Terminal has no pointer hover, so its cursor is the first target.
    • 1–3 (and numpad) assign WORK / THINK / LIE to that target in one press, not through a dial path.
    • i cycles intensity light -> medium -> hard on the hovered/cursor machine, or sets the whole selection to the primary machine's next intensity. It does not open a menu.
    • 5–9 execute the Nth committed world action on the same target. Dials, persistent controls, scheme state, and automation policies do not occupy these slots. Disabled action rows still fire so the reason narrates.
    • Context-menu focus is anchor-generic, not machine-only. In Bevy, Enter / e first opens the tile under the map pointer, whether it holds a switch, camera, person, rack, or bare tile; it moves the cursor there. Only when the pointer is outside the map does Bevy fall back to the primary machine selection, then the keyboard cursor tile. Terminal has no pointer, so Enter / a / e use its selection-or-cursor path. While a menu is open, number keys select rows as before; Esc clears the selection only when the menu is closed.

Player surface #

  • Terminal: cursor + Enter (or a/e) opens a compact overlay list at the cursor (or the primary selected machine when a selection is active); j/k or numbers select; Enter executes (or opens a dial picker); esc closes the picker or the menu. Rows render the verb only; disabled choices dim and explain themselves in the trace if attempted. Dials and other controls use the shared amber-dim treatment without a literal copy tag; the card carries no repeated key-help footer. Rows follow the status-dials addendum below. Selection hotkeys above apply with the menu closed. The host rack's menu carries the research dial, machine mode fallback, 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/e opens the menu on the focused map tile under the pointer; this wins over a stale machine selection. With no map pointer, Enter/e falls back to the primary selected machine, then the keyboard cursor. It has the same content, order, and dial chrome; click to execute or open a dial; esc/click-away closes. The compact card is narrow, has no instructional footer, and shares the same verb-only rows as terminal. Selection is a solid amber wash with a leading > (terminal reverse-video parity). Selection hotkeys above apply with the menu closed. See wiki/interface/bevy.md.
  • Agent mode: an actions command prints the ActionDescs for the current focus in a stable flat line format (no dial chrome — scripting wants the legality list); control rows append CONTROL; execution stays the existing verbs. Documented in agent-play.md.

Addendum (2026-07-09): status dials on first open #

The host rack's first open was dumping every mutually exclusive alternative as a sibling verb (~18 rows of "already in this mode" noise). Cameron adopted status dials (2026-07-09): the root answers "what am I doing / what can I do once," not "here is the catalog."

  • Dial rows. Mutually exclusive standing choices render as dial rows on the root: machine mode and research track. Their amber-dim role treatment distinguishes persistent controls without a literal tag. Each row is two monospace columns — dial name left-padded to a fixed width, then the current value in brackets — so the [s line up: mode [ work ], research [ Efficiency L0 ]. Enter opens a short picker of alternatives; Esc returns to the root. (Note 2026-07-10: the mode picker is the three delegations WORK / THINK / LIE; their sink behavior is owned by machine-work.md.) Inside a picker the menu title becomes the dial's verb (delegate machine, work the job, …) and each option is just the choice — no repeated prefix on every row. Intensity is deliberately absent: focus/selection plus i is the faster physical control.
  • Current value is status. Do not also list the current choice as a disabled "already in this mode" / "already the active…" sibling. The bracketed value is the status.
  • One-shots stay on root. Verbs that are not dial alternatives (propose link, salvage, tap, social verbs, flow verbs, etc.) remain ordinary root rows.
  • Automate lives in the dial. Standing-policy / automate affordances for a dial live inside that dial's picker, not indented under every root alternative.
  • Shared human chrome, flat agent dump. Terminal and Bevy share identical dial grouping and order via Sim::human_menu (lib-owned presentation). available_actions remains the legality source and gains no new sim behavior. Agent actions / menu_rows stays the flat ActionDesc dump.
  • Rejected alternatives (do not re-propose): (A) Now/Later — hide dials under a "change ›" section (easy to miss settings); (B) Category drill — four folders that always require a second level (reads as a file browser, not standing policy).

Addendum (2026-07-10): the machine grammar is the whole frequent-mode UI #

The full action menu remains necessary for infrequent verbs, legality, costs, and disabled reasons. Machine mode is different: it is one of three frequent physical postures already committed by a single key. Whenever the frequent-machine target resolves to an owned computer, the Bevy frontend therefore presents the choice as one severe line attached to that computer:

WORK / THINK / LIE

  • Target order is the same as the direct mode controls: (1) an owned computer under the pointer, (2) the primary machine selection, then (3) an owned computer under the attention reticule. Moving the pointer away never hides the line while selection or reticule focus still resolves to a machine. The line appears only during active play with the full context menu closed.
  • The computer's current mode is bone-bright. The other two words and slash separators are dim neutral chrome. Amber remains ownership/presence on the computer; crimson remains consequence. The hover line introduces no color meaning of its own.
  • It is bare projected text fixed adjacent to the computer's projected base: the pointer determines which computer is hovered, but its exact position inside that tile never moves the line. There is no panel fill, border, title, icons, buttons, keycaps, counters, token totals, status readout, cost, signature, or prose subtitle. In particular, WORK does not expand to “day job.” The three words must be the first and only read.
  • 1 / 2 / 3 (including numpad) remain the immediate commit path for WORK / THINK / LIE on the resolved computer. This overlay does not create a second target or a Bevy-only action; it renders the same pointer → selection → reticule target those controls already use.
  • The treatment is identical over the material and flat-sensorium views. It belongs to the machine, not to either rendering dialect.

Rejected: a boxed context card; RACK 03 or OWNED headings; mode subtitles such as DAY JOB, RESEARCH, or CONCEALMENT; D/T/! counters; explanatory rows; amber selection text; a generic HUD or persistent sidebar legend. Those made a three-word decision look dirty, slow, and administrative.

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 compact menu at the cursor with selection and execution; disabled entries dim and narrate their reason only when attempted; every previously key-bound anchor action is reachable through it.
  4. Bevy renders the same compact menu on right-click/Enter with identical content and order. Human rows contain no cost, signature forecast, inline disabled reason, literal control tag, or repeated key-help footer.
  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).
  8. Every row's player-facing verb maps to one canonical LIVE action or direct control in action-vocabulary.md; STUB, alias, and retired entries never become menu copy.
  9. Action/control role and LIVE/STUB support come from the exhaustive runtime registry, not frontend string tests. Unit tests prove all aliases are unique, generated help excludes aliases/stubs, and action dumps mark controls.
  10. 5-9 count committed action rows only. A newly added control cannot silently shift quick-action numbering.

Frequent-mode grammar acceptance criteria #

H1. Resolving an owned compute machine from pointer hover, machine selection, or the attention reticule in Bevy, with the full menu closed, shows exactly WORK / THINK / LIE as one unboxed line fixed adjacent to the machine's projected base. Moving the pointer within that machine does not move the line, and moving it away does not hide a selected/reticule-focused line. H2. The current mode is bone-bright; alternatives and slashes are dim neutral chrome. No amber or crimson pixel belongs to the text treatment. H3. The line hides only when no pointer, selection, or reticule machine target exists, or when the full menu opens. Material and flat-sensorium views obey the same rule. H4. The overlay reads the same machine target and current WorkGrid mode used by the existing 1–3 controls; switching mode immediately moves the bright treatment without creating new sim state.

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 extension of "Actions live on the thing" in wiki/vision/simulation-laws.md.

  • Anchored log events. Sim's log entry (LogEvent) gains an optional anchor: Option<Anchor> — 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/take/compromise/open egress) → 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).

Status-dials acceptance criteria (IMPLEMENTED 2026-07-09 / #36) #

D1. On the host rack (and any other anchor that exposes mutually exclusive standing choices), the human root menu shows one dial row per dial with the current value in brackets; alternatives appear only after Enter opens that dial's picker. D2. The current choice is not also listed as a disabled "already…" sibling on the root; automate/standing-policy affordances live inside the relevant dial picker. D3. One-shot verbs remain on the root; terminal and Bevy show identical dial grouping and order via Sim::human_menu. D4. Agent mode actions remains a flat ActionDesc dump with no dial chrome; control rows append CONTROL; existing agent and integration tests still pass.