diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 2d6d1fc4..79b2de76 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -47,6 +47,10 @@ const COMPUTE_CHANNELS: usize = 5; /// the embedded font cannot wrap mid-glyph cleanly, so prefer width over /// truncation (wiki/interface/context-menu.md). const MENU_WIDTH: f32 = 460.0; +/// The hover grammar is one clean line, not a panel. This width leaves the +/// three words readable without letting the affordance become a HUD card +/// (wiki/interface/context-menu.md, hover machine grammar). +const HOVER_MODE_WIDTH: f32 = 248.0; const DETECTION_ROWS: usize = 6; const DETECTION_CELLS: usize = 4; @@ -353,7 +357,8 @@ impl Default for RenderMode { } /// Dev screenshot harness (env `MISALIGNED_SHOT=flat|opening|wide|close|dark| -/// zoomin|zoomout|intel|tokens|thoughtflow|signal|ears|eyes-white|eyes-form|worklight|worklightoff`, path via +/// zoomin|zoomout|intel|tokens|thoughtflow|signal|ears|eyes-white|eyes-form| +/// hover-menu|worklight|worklightoff`, path via /// `MISALIGNED_SHOT_PATH`): stages a scenario, /// waits for /// assets, runs the fog audit, saves one screenshot, exits. Not a player @@ -363,6 +368,8 @@ struct ShotHarness { path: String, frames: u32, taken: bool, + /// Pointer-free deterministic hover target for UI evidence. + hover_tile: Option<(i32, i32)>, } // ─── The wake (world/story/opening.md beat 1 — glow choreography) ──────────── @@ -1046,6 +1053,16 @@ struct DetectionCell { } #[derive(Component)] struct OverlayText; +/// Root for the menu-closed machine grammar: bare `WORK / THINK / LIE` +/// text adjacent to the hovered owned rack. It has no panel chrome. +#[derive(Component)] +struct HoverModeGrammar; +/// One word or separator in the hover grammar. Mode words restyle from the +/// hovered machine's current delegation; separators stay quiet chrome. +#[derive(Component, Clone, Copy)] +struct HoverModeWord { + mode: Option, +} /// The context-menu root node (wiki/interface/context-menu.md), absolute and /// hidden until an anchor is focused. Its children are the whole card /// (title, rows, footer); rebuilds clear them via `despawn_related::` @@ -1125,10 +1142,18 @@ fn main() { let path = std::env::var("MISALIGNED_SHOT_PATH") .unwrap_or_else(|_| format!("misaligned_shot_{kind}.png")); dev_shot_scenario(&mut game, &mut mode, &kind); + let hover_tile = (kind == "hover-menu").then(|| { + let core = game.sim.core_position(); + game.sim + .set_machine_mode(game.sim.core.host_machine, MachineMode::Work); + game.set_cursor(core.0, core.1); + core + }); ShotHarness { path, frames: 150, taken: false, + hover_tile, } }); @@ -1250,6 +1275,7 @@ fn main() { render_noise_field_flat, scroll_sidebar, render_ui, + render_hover_mode_grammar, menu_pointer, log_row_pointer, manage_menu_ui, @@ -5085,6 +5111,49 @@ fn setup_ui(mut commands: Commands) { }); }); + // The frequent machine-mode surface is not another card. Hovering an + // owned rack reveals one bare, high-contrast line beside the pointer; + // 1/2/3 remain the one-press controls. No border, fill, heading, status, + // or explanatory subtitle competes with the three verbs. + commands + .spawn(( + Node { + position_type: PositionType::Absolute, + left: Val::Px(0.0), + top: Val::Px(0.0), + width: Val::Px(HOVER_MODE_WIDTH), + height: Val::Px(32.0), + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + column_gap: Val::Px(10.0), + ..default() + }, + BackgroundColor(Color::NONE), + Visibility::Hidden, + GlobalZIndex(40), + HoverModeGrammar, + )) + .with_children(|line| { + for (label, mode) in [ + ("WORK", Some(MachineMode::Work)), + ("/", None), + ("THINK", Some(MachineMode::Think)), + ("/", None), + ("LIE", Some(MachineMode::Lie)), + ] { + line.spawn(( + Text::new(label), + TextFont { + font_size: 19.0, + ..default() + }, + TextColor(DIM), + HoverModeWord { mode }, + )); + } + }); + // The context menu: an absolute, initially-hidden card whose rows are // (re)built by `manage_menu_ui` (wiki/interface/context-menu.md). Placed // at the pointer/cursor by that system. @@ -5108,6 +5177,68 @@ fn setup_ui(mut commands: Commands) { )); } +/// Keep the three machine verbs physically adjacent to the hovered rack. +/// The pointer already determines the target for the one-press 1/2/3 input, +/// so this is presentation over that exact same target rather than a second +/// focus model. Screenshot evidence supplies a deterministic synthetic hover. +#[allow(clippy::too_many_arguments)] +fn render_hover_mode_grammar( + game: Res, + mode: Res, + harness: Option>, + windows: Query<&Window, With>, + camera_q: Query<(&Camera, &GlobalTransform), With>, + camera3_q: RealCameraQuery, + mut grammar: Query<(&mut Node, &mut Visibility), With>, + mut words: Query<(&HoverModeWord, &mut TextColor)>, +) { + let Ok((mut node, mut visibility)) = grammar.single_mut() else { + return; + }; + let Ok(window) = windows.single() else { + *visibility = Visibility::Hidden; + return; + }; + + let synthetic_hover = harness.as_ref().and_then(|h| h.hover_tile); + let pointer = window.cursor_position(); + let hover = synthetic_hover.or_else(|| { + if mode.material { + mouse_grid_real(&windows, &camera3_q) + } else { + mouse_grid(&windows, &camera_q) + } + }); + let Some((machine_id, _, _)) = hover.and_then(|(x, y)| game.machine_at(x, y)) else { + *visibility = Visibility::Hidden; + return; + }; + if game.screen != Screen::Playing || game.menu.is_some() { + *visibility = Visibility::Hidden; + return; + } + + let anchor = pointer.unwrap_or(Vec2::new( + (window.width() - SIDEBAR_WIDTH) * 0.5, + window.height() * 0.42, + )); + let world_width = (window.width() - SIDEBAR_WIDTH).max(HOVER_MODE_WIDTH + 24.0); + node.left = Val::Px( + (anchor.x - HOVER_MODE_WIDTH * 0.5).clamp(12.0, world_width - HOVER_MODE_WIDTH - 12.0), + ); + node.top = Val::Px((anchor.y - 58.0).clamp(12.0, window.height() - 44.0)); + *visibility = Visibility::Visible; + + let current = game.sim.work_grid.mode(machine_id); + for (word, mut color) in words.iter_mut() { + color.0 = match word.mode { + Some(word_mode) if Some(word_mode) == current => BONE, + Some(_) => scaled(DIM, 0.58), + None => scaled(DIM, 0.42), + }; + } +} + /// Fold shared menu / log wording into glyphs Bevy's embedded default font /// can draw. Terminal keeps the typographic separators (`·`, `—`, `»`); /// Bevy must not — missing glyphs render as empty boxes (wiki/interface/bevy.md). diff --git a/wiki/interface/bevy.md b/wiki/interface/bevy.md index ba4b90c0..f074f959 100644 --- a/wiki/interface/bevy.md +++ b/wiki/interface/bevy.md @@ -177,7 +177,12 @@ controls table), plus zoom, sidebar scrolling, and the render flip: context menu, not on keys. - **Hover / fleet hotkeys (menu closed).** Point at a rack (hover — no click) and press `1`–`3` / numpad to assign WORK / THINK / LIE - **immediately** — not enter → mode dial → row → enter. `i` cycles light / + **immediately** — not enter → mode dial → row → enter. While the pointer is + on an owned rack, one bare `WORK / THINK / LIE` line floats beside it; the + current mode is bone-bright and the alternatives/slashes are dim chrome. + It has no panel, heading, subtitle, counters, or extra state, and disappears + when the pointer leaves or the full menu opens + ([context-menu.md](context-menu.md), hover-grammar addendum). `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 the hovered tile's menu. Drag a @@ -197,3 +202,6 @@ for Bevy-visible changes. Machine-work final-swallow motion has deterministic bottom Demand cube pulls into the WORK chassis, and the ivory Thought bead contracts into the passive core draw. Both are driven by `Sim::work_consumptions`, not frontend queue differences. +`MISALIGNED_SHOT=hover-menu` captures the clean hover grammar over the owned +host with WORK current; it is the visual acceptance frame for context-menu.md +H1–H4. diff --git a/wiki/interface/context-menu.md b/wiki/interface/context-menu.md index c0844a65..e2fb3484 100644 --- a/wiki/interface/context-menu.md +++ b/wiki/interface/context-menu.md @@ -51,6 +51,12 @@ Status note: implemented 2026-07-07 on the context-menu worktree (all 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. Stage: B1 — The Basement Design: - wiki/vision/simulation-laws.md#actions-live-on-the-thing @@ -216,6 +222,39 @@ noise). Cameron adopted **status dials** (2026-07-09): the root answers Category drill — four folders that always require a second level (reads as a file browser, not standing policy). +## Addendum (2026-07-10): the hover 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. On hover, the +Bevy frontend therefore presents the choice as one severe line attached to +the computer: + +`WORK / THINK / LIE` + +- The line appears only while the pointer is over an owned computer, play is + active, and the full context menu is closed. Leaving the computer, moving + over the sidebar, or opening the full menu removes it. +- 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 adjacent to the pointer/computer: 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 hovered computer. This overlay does not create a + second target or a Bevy-only action; it renders the same hover 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 @@ -250,6 +289,19 @@ noise). Cameron adopted **status dials** (2026-07-09): the root answers 10. `5`-`9` count committed action rows only. A newly added control cannot silently shift quick-action numbering. +### Hover-grammar acceptance criteria + +H1. Hovering any owned compute machine in Bevy, with the full menu closed, + shows exactly `WORK / THINK / LIE` as one unboxed line adjacent to the + machine/pointer; no other hover copy or chrome appears. +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. Leaving the machine, crossing into the sidebar, or opening the full menu + hides the line. 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 hover + `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 diff --git a/wiki/log/2026-07-10-hover-machine-grammar.md b/wiki/log/2026-07-10-hover-machine-grammar.md new file mode 100644 index 00000000..2734ba0f --- /dev/null +++ b/wiki/log/2026-07-10-hover-machine-grammar.md @@ -0,0 +1,10 @@ +# Hovering a machine reveals only the three verbs + +``` +Type: log +``` + +- Intent: Turn the adopted clean UI study into the binding and playable Bevy machine-hover surface. +- Changed: The context-menu spec now defines the unboxed `WORK / THINK / LIE` hover grammar, the Bevy frontend renders it over the existing hover target with the current WorkGrid mode bright, and the deterministic screenshot harness gains `MISALIGNED_SHOT=hover-menu`. +- Design/spec impact: Machine mode remains a direct `1`–`3` control. The hover affordance adds no sim state and deliberately excludes every explanatory or status element from the first read. +- Checks: `cargo check -p misaligned-bevy`; observed `MISALIGNED_SHOT=hover-menu` capture at 2560×1440 — one unboxed, unclipped line above the owned host, WORK pixels at bone white (`219/217/209`) and THINK/LIE at dim chrome (`81/81/77`), with no sidebar or machine overlap; `./tools/check.sh --frontend`. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 22ce09ad..decb8ccd 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -186,6 +186,11 @@ add or amend a session log, then re-run the generator. - Intent: Cameron: mode change should be hover over a rack and press 1-4 immediately — not enter + enter + select dial row + enter. - Log: [wiki/log/2026-07-10-hover-mode-keys.md](2026-07-10-hover-mode-keys.md) +## 2026-07-10 - Hovering a machine reveals only the three verbs + +- Intent: (see session log) +- Log: [wiki/log/2026-07-10-hover-machine-grammar.md](2026-07-10-hover-machine-grammar.md) + ## 2026-07-10 - Homepage names the machine grammar - Intent: (see session log) diff --git a/wiki/log/decisions/2026-07-10.md b/wiki/log/decisions/2026-07-10.md index 40e9b34d..c8b4c20d 100644 --- a/wiki/log/decisions/2026-07-10.md +++ b/wiki/log/decisions/2026-07-10.md @@ -423,3 +423,17 @@ Type: log passive core draw), never frontend-differenced queue snapshots. Exact costs and the x1.50 multiplier remain `[TUNE]` after play. Owners: wiki/mechanics/machine-work.md and wiki/mechanics/research.md. +- **2026-07-10 — Machine hover is only WORK / THINK / LIE.** Cameron adopted + the clean generated interface study over the first dense proposal. DECIDED: + hovering an owned computer in Bevy reveals one bare projected line, + `WORK / THINK / LIE`, adjacent to the machine. The current mode is + bone-bright; alternatives and slash separators are dim neutral chrome. The + line has no panel, border, title, rack identity, subtitles, counters, costs, + signatures, token readout, amber selection, or explanatory prose. It + disappears off-machine and while the full action menu is open. Existing + hover + `1`/`2`/`3` remains the commit path and the overlay reads that same + target and WorkGrid mode; it adds no sim state. Rejected: the first dirty + card treatment (`RACK 03 · OWNED`, `DAY JOB` / `RESEARCH` / + `CONCEALMENT`, D/T/! totals), and any UI where the player must parse more + than the three verbs. Owner: wiki/interface/context-menu.md; synchronized + surface: wiki/interface/bevy.md.