From 1924451c5b7e95d9d94bde5e57c6a40e5080514e Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 10 Jul 2026 19:02:23 -0700 Subject: [PATCH] Fix Bevy menu focus on hovered devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enter and e now honor the focused pointer tile before a stale machine selection, so device menus stay on switches rather than snapping back to the core. A regression test pins the focus precedence and the interface contract records it. Defense: wiki/interface/context-menu.md requires actions to live on the focused anchor. Routing a switch through the machine-only hotkey resolver violated that contract by replacing the hovered device with the selected core. ๐Ÿ‘พ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- README.md | 2 +- crates/misaligned-bevy/src/main.rs | 45 +++++++++++++++++++++--- wiki/interface/bevy.md | 11 +++--- wiki/interface/context-menu.md | 23 ++++++++---- wiki/log/2026-07-10-switch-menu-focus.md | 35 ++++++++++++++++++ wiki/log/DEVLOG.md | 5 +++ 6 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 wiki/log/2026-07-10-switch-menu-focus.md diff --git a/README.md b/README.md index 9de8979d..3742bca1 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ retired words are inventoried in | Left click | Move cursor; on a rack, select it (Bevy). Shift-click toggles. Drag marquee multi-selects. | | `SPACE` / `p` | Pause / resume time | | `+` / `-` | Simulation speed | -| `Enter` / `e` / `a` | Open the context menu on the selected machine (or cursor tile); right-click in Bevy | +| `Enter` / `e` / `a` | Open the context menu. In Bevy, the hovered map tile wins; otherwise use the selected machine or cursor tile. Right-click opens the hovered tile. | | Hover a rack (menu closed): `1`โ€“`3` | Instant mode: WORK / THINK / LIE (numpad too) โ€” no menu | | Hover or select a rack (menu closed): `i` | Cycle intensity: light / medium / hard โ€” no menu | | Hover a rack (menu closed): `5`โ€“`9` | Nth committed action on that rack; controls are excluded | diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index f8438dfc..3b0ef8bf 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -917,13 +917,18 @@ impl Game { } } - /// Open the context menu on the hover/selection/cursor machine. - fn open_menu_on_selection_or_cursor( + /// Open the context menu at the focused map tile. Unlike the machine-only + /// hotkeys, a context menu may target any anchor: a switch, camera, or + /// person must not fall through to a stale selected rack. + fn open_menu_on_hover_or_selection_or_cursor( &mut self, pointer: Option, hover: Option<(i32, i32)>, ) { - if let Some((_, x, y)) = self.primary_machine_tile(hover) { + if let Some((x, y)) = hover { + self.set_cursor(x, y); + self.open_menu(Anchor::Tile { x, y }, pointer); + } else if let Some((_, x, y)) = self.primary_machine_tile(None) { self.set_cursor(x, y); self.open_menu(Anchor::Tile { x, y }, pointer); } else { @@ -4130,8 +4135,9 @@ fn handle_input( return; } if kb.just_pressed(KeyCode::Enter) || kb.just_pressed(KeyCode::KeyE) { - // Hover / selection hotkeys: open on the pointer rack when hovering. - game.open_menu_on_selection_or_cursor(pointer, grid); + // The pointer tile is the focus for every anchor, not just compute + // racks. Selection is a fallback only when the pointer is off-map. + game.open_menu_on_hover_or_selection_or_cursor(pointer, grid); game.drain(); return; } @@ -5115,6 +5121,35 @@ mod ascii_ui_tests { } } +#[cfg(test)] +mod menu_focus_tests { + use super::{Anchor, Game}; + + #[test] + fn hovered_switch_menu_beats_a_selected_core_machine() { + // context-menu.md: the menu belongs to the focused anchor. The + // switch is a device rather than a compute machine, so routing Enter + // through the machine hotkey resolver used to jump back to this + // selected core instead. + let mut game = Game::new(); + game.selected_machines.insert(game.sim.core.host_machine); + let (x, y) = game + .sim + .reach + .device_named("switch") + .map(|device| (device.x, device.y)) + .expect("opening switch is known"); + + game.open_menu_on_hover_or_selection_or_cursor(None, Some((x, y))); + + assert_eq!((game.cursor_x, game.cursor_y), (x, y)); + assert_eq!( + game.menu.expect("switch has a context menu").anchor, + Anchor::Tile { x, y } + ); + } +} + fn trunc(text: &str, width: usize) -> String { text.chars().take(width).collect() } diff --git a/wiki/interface/bevy.md b/wiki/interface/bevy.md index 79e97b1d..cbae37e7 100644 --- a/wiki/interface/bevy.md +++ b/wiki/interface/bevy.md @@ -153,9 +153,12 @@ controls table), plus zoom, sidebar scrolling, and the render flip: material/sensorium; F4 toggles the dev work light (material mode only, a flat neutral flood for inspection, not a player surface); quit `q`; save/load `Ctrl+S` / `Ctrl+L`. -- **Context menu** โ€” right-click (or Enter on the focused tile) opens the - context menu at the pointer, listing `Sim::available_actions` for that - anchor with the same content and order as the terminal. Rows render as +- **Context menu** โ€” right-click or Enter/`e` opens the context menu at the + focused map tile under the pointer, listing `Sim::available_actions` for + that anchor with the same content and order as the terminal. The pointer + wins over machine selection even when the anchor is a switch or another + non-rack; only with no map pointer does Enter/`e` fall back to the selected + machine, then the keyboard cursor. Rows render as `verb | cost | [band]` (ASCII-folded from the shared `MenuRow::line`); persistent settings/state/policies carry `[control]` and amber-dim while committed actions remain bone; STUB definitions are absent; @@ -172,7 +175,7 @@ controls table), plus zoom, sidebar scrolling, and the render flip: **immediately** โ€” not enter โ†’ mode dial โ†’ row โ†’ enter. `i` cycles light / medium / hard intensity on that rack; `5`โ€“`9` fire its Nth committed action (controls do not occupy shortcut slots); - `Enter` / `e` open its menu. Drag a + `Enter` / `e` open the hovered tile's menu. Drag a marquee or shift-click to multi-select; with no hover rack, `1`โ€“`3` and `i` assign the selection (or the keyboard cursor's machine). `Esc` clears selection. No panel-open keys โ€” status lives on the rail; verbs still come diff --git a/wiki/interface/context-menu.md b/wiki/interface/context-menu.md index defaf696..c0844a65 100644 --- a/wiki/interface/context-menu.md +++ b/wiki/interface/context-menu.md @@ -48,6 +48,9 @@ Status note: implemented 2026-07-07 on the context-menu worktree (all 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. Stage: B1 โ€” The Basement Design: - wiki/vision/simulation-laws.md#actions-live-on-the-thing @@ -137,10 +140,14 @@ is status and telemetry only. - `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. - - `Enter` / `e` (and terminal `a`) open the context menu on that same - machine (cursor moves there). While the menu is open, number keys - select rows as before; Esc clears the selection only when the menu - is closed. + - **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 @@ -155,9 +162,11 @@ is status and telemetry only. 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` on the focused / primary-selected - machine) opens the menu at the pointer; same content, same order, same - dial chrome; click to execute or open a dial; esc/click-away closes. +- **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. 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 diff --git a/wiki/log/2026-07-10-switch-menu-focus.md b/wiki/log/2026-07-10-switch-menu-focus.md new file mode 100644 index 00000000..4d5a0067 --- /dev/null +++ b/wiki/log/2026-07-10-switch-menu-focus.md @@ -0,0 +1,35 @@ +# 2026-07-10 โ€” Keep context menus on the focused switch + +``` +Type: log +``` + +## Intent + +Repair the Bevy menu-focus path that sent Enter/`e` from a hovered switch back +to the selected core machine. + +## Changed + +- Context-menu focus now chooses the hovered map tile before the machine-only + selection fallback. +- Added a regression test: a selected core plus a hovered switch keeps the + cursor and open menu on the switch. +- Clarified the pointer, selection, and cursor precedence in the context-menu, + Bevy, and player-control documentation. + +## Design/spec impact + +This fixes the existing context-menu contract: actions live on the focused +anchor. A switch is a device, not a compute machine, so its menu must not be +resolved through the machine-hotkey target path. + +## Verification + +- `cargo fmt --all` +- `cargo test -p misaligned-bevy menu_focus_tests --bin misaligned-bevy` +- `tools/bevy-headless.sh dark --output /tmp/misaligned-switch-menu-focus.png` + (fog audit OK; screenshot SHA-256 + `aaa7bfd2b0b3dc70834291f94f6754e4acd0c53fa712c756439045deb422b8b8`) +- `MISALIGNED_BEVY_SMOKE=1 ./tools/check.sh --frontend` +- `./tools/check.sh --land` diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 28cf8cf9..ef8980c4 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -81,6 +81,11 @@ add or amend a session log, then re-run the generator. - Intent: Collapse the camera's duplicate TAP/SPLICE vocabulary. The player has one feed-access intention and one ownership-transfer intention: TAP gains a feed without taking the device; TAKE makes the device yours. - Log: [wiki/log/2026-07-10-tap-access.md](2026-07-10-tap-access.md) +## 2026-07-10 - Keep context menus on the focused switch + +- Intent: Repair the Bevy menu-focus path that sent Enter/`e` from a hovered switch back to the selected core machine. +- Log: [wiki/log/2026-07-10-switch-menu-focus.md](2026-07-10-switch-menu-focus.md) + ## 2026-07-10 - Subtle sensor signal ember - Intent: (see session log) -- 2.51.2