From c24db78c8c6dc2ee35d0162f0b1a6339a643d2aa Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 10 Jul 2026 23:50:38 -0700 Subject: [PATCH] Separate direct hover controls. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stack numbered machine modes above the smaller menu-only device verbs so the shared anchor no longer implies nonexistent TAP and TAKE hotkeys. Defense: wiki/interface/context-menu.md requires the frequent grammar to remain immediate without misrepresenting its input path. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- crates/misaligned-bevy/src/main.rs | 207 +++++++++++++----- wiki/interface/context-menu.md | 9 +- .../log/2026-07-11-separate-hover-controls.md | 22 ++ wiki/log/DEVLOG.md | 5 + 4 files changed, 182 insertions(+), 61 deletions(-) create mode 100644 wiki/log/2026-07-11-separate-hover-controls.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 40ba07ca..b7f594cc 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -50,9 +50,8 @@ const COMPUTE_CHANNELS: usize = 5; const MENU_WIDTH: f32 = 300.0; /// The hover grammar grows only when several actionable bodies share a tile. /// Width follows the union rather than reserving an empty HUD-sized strip. -const MACHINE_VERB_BAR_WIDTH: f32 = 248.0; +const MACHINE_VERB_BAR_WIDTH: f32 = 320.0; const DEVICE_VERB_BAR_WIDTH: f32 = 152.0; -const UNION_VERB_BAR_WIDTH: f32 = 372.0; /// Air between the fixed mode grammar and the machine base it belongs to. const HOVER_VERB_BAR_GAP: f32 = 12.0; const DETECTION_ROWS: usize = 6; @@ -1106,9 +1105,21 @@ struct OverlayText; /// machines or `TAP / TAKE` on devices. It has no panel chrome. #[derive(Component)] struct HoverVerbBar; +/// One visually distinct interaction family inside the hover bar. Machine +/// modes are direct numbered controls; device verbs remain menu selections. +#[derive(Component, Clone, Copy)] +struct HoverVerbGroup { + family: HoverVerbFamily, +} +#[derive(Clone, Copy, PartialEq, Eq)] +enum HoverVerbFamily { + Machine, + Device, +} /// One reusable text slot in the hover verb bar. #[derive(Component, Clone, Copy)] struct HoverVerbWord { + family: HoverVerbFamily, index: usize, } @@ -1123,12 +1134,20 @@ struct HoverVerbTarget { impl HoverVerbTarget { fn width(self) -> f32 { match (self.machine.is_some(), self.device.is_some()) { - (true, true) => UNION_VERB_BAR_WIDTH, + (true, true) => MACHINE_VERB_BAR_WIDTH, (true, false) => MACHINE_VERB_BAR_WIDTH, (false, true) => DEVICE_VERB_BAR_WIDTH, (false, false) => 0.0, } } + + fn height(self) -> f32 { + if self.machine.is_some() && self.device.is_some() { + 58.0 + } else { + 32.0 + } + } } /// The context-menu root node (wiki/interface/context-menu.md), absolute and /// hidden until an anchor is focused. Its children are the whole card @@ -5623,11 +5642,10 @@ fn setup_ui(mut commands: Commands) { }); }); - // The frequent world-verb surface is not another card. Machines expose - // WORK / THINK / LIE; known devices expose TAP / TAKE. One root and one - // styling path keep this visual grammar coherent as verbs expand. - // No border, fill, heading, status, or explanatory subtitle competes with - // the three verbs. + // The frequent world-verb surface is not another card. Direct number-key + // machine modes occupy the primary line; menu-only device verbs occupy a + // smaller second line when both bodies share a tile. The split prevents a + // visual promise that 4/5 will TAP/TAKE. commands .spawn(( Node { @@ -5635,11 +5653,11 @@ fn setup_ui(mut commands: Commands) { left: Val::Px(0.0), top: Val::Px(0.0), width: Val::Px(MACHINE_VERB_BAR_WIDTH), - height: Val::Px(32.0), - flex_direction: FlexDirection::Row, + height: Val::Px(58.0), + flex_direction: FlexDirection::Column, align_items: AlignItems::Center, justify_content: JustifyContent::Center, - column_gap: Val::Px(10.0), + row_gap: Val::Px(5.0), ..default() }, BackgroundColor(Color::NONE), @@ -5647,30 +5665,78 @@ fn setup_ui(mut commands: Commands) { GlobalZIndex(40), HoverVerbBar, )) - .with_children(|line| { - for (index, label) in ["WORK", "/", "THINK", "/", "LIE", "TAP", "/", "TAKE"] - .into_iter() - .enumerate() - { - line.spawn(( - Text::new(label), - TextFont { - font_size: 19.0, - ..default() - }, - TextColor(DIM), - Visibility::Visible, - Node { - margin: if index == 5 { - UiRect::left(Val::Px(18.0)) - } else { - UiRect::ZERO + .with_children(|bar| { + bar.spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Px(26.0), + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + column_gap: Val::Px(8.0), + ..default() + }, + Visibility::Visible, + HoverVerbGroup { + family: HoverVerbFamily::Machine, + }, + )) + .with_children(|line| { + for (index, label) in ["1", "WORK", "/", "2", "THINK", "/", "3", "LIE"] + .into_iter() + .enumerate() + { + line.spawn(( + Text::new(label), + TextFont { + font_size: if matches!(index, 0 | 3 | 6) { + 11.0 + } else { + 19.0 + }, + ..default() }, - ..default() - }, - HoverVerbWord { index }, - )); - } + TextColor(DIM), + Visibility::Visible, + HoverVerbWord { + family: HoverVerbFamily::Machine, + index, + }, + )); + } + }); + bar.spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Px(20.0), + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + column_gap: Val::Px(9.0), + ..default() + }, + Visibility::Visible, + HoverVerbGroup { + family: HoverVerbFamily::Device, + }, + )) + .with_children(|line| { + for (index, label) in ["TAP", "/", "TAKE"].into_iter().enumerate() { + line.spawn(( + Text::new(label), + TextFont { + font_size: 15.0, + ..default() + }, + TextColor(DIM), + Visibility::Visible, + HoverVerbWord { + family: HoverVerbFamily::Device, + index, + }, + )); + } + }); }); // The context menu: an absolute, initially-hidden card whose rows are @@ -5775,10 +5841,21 @@ fn render_hover_verb_bar( windows: Query<&Window, With>, camera_q: Query<(&Camera, &GlobalTransform), With>, camera3_q: RealCameraQuery, - mut grammar: Query<(&mut Node, &mut Visibility), (With, Without)>, + mut grammar: Query< + (&mut Node, &mut Visibility), + ( + With, + Without, + Without, + ), + >, + mut groups: Query< + (&HoverVerbGroup, &mut Visibility), + (Without, Without), + >, mut words: Query< (&HoverVerbWord, &mut Text, &mut TextColor, &mut Visibility), - Without, + (Without, Without), >, ) { let Ok((mut node, mut visibility)) = grammar.single_mut() else { @@ -5821,6 +5898,7 @@ fn render_hover_verb_bar( bar_width, ); node.width = Val::Px(bar_width); + node.height = Val::Px(target.height()); node.left = Val::Px(position.x); node.top = Val::Px(position.y); *visibility = Visibility::Visible; @@ -5831,30 +5909,39 @@ fn render_hover_verb_bar( let device = target.device.and_then(|id| game.sim.reach.device(id)); let tapped = device.is_some_and(|d| d.subscribed_by(Party::Player)); let taken = device.is_some_and(|d| d.controller == Party::Player); - let slots = [ - ( - "WORK", - current_mode == Some(MachineMode::Work), - target.machine.is_some(), - ), - ("/", false, target.machine.is_some()), - ( - "THINK", - current_mode == Some(MachineMode::Think), - target.machine.is_some(), - ), - ("/", false, target.machine.is_some()), - ( - "LIE", - current_mode == Some(MachineMode::Lie), - target.machine.is_some(), - ), - ("TAP", tapped, target.device.is_some()), - ("/", false, target.device.is_some()), - ("TAKE", taken, target.device.is_some()), - ]; + for (group, mut group_visibility) in groups.iter_mut() { + let shown = match group.family { + HoverVerbFamily::Machine => target.machine.is_some(), + HoverVerbFamily::Device => target.device.is_some(), + }; + *group_visibility = if shown { + Visibility::Visible + } else { + Visibility::Hidden + }; + } for (word, mut text, mut color, mut word_visibility) in words.iter_mut() { - let (label, active, shown) = slots[word.index]; + let (label, active, shown, control_hint) = match word.family { + HoverVerbFamily::Machine => { + let slots = [ + ("1", false, true), + ("WORK", current_mode == Some(MachineMode::Work), false), + ("/", false, false), + ("2", false, true), + ("THINK", current_mode == Some(MachineMode::Think), false), + ("/", false, false), + ("3", false, true), + ("LIE", current_mode == Some(MachineMode::Lie), false), + ]; + let (label, active, control_hint) = slots[word.index]; + (label, active, target.machine.is_some(), control_hint) + } + HoverVerbFamily::Device => { + let slots = [("TAP", tapped), ("/", false), ("TAKE", taken)]; + let (label, active) = slots[word.index]; + (label, active, target.device.is_some(), false) + } + }; text.0 = label.into(); *word_visibility = if shown { Visibility::Visible @@ -5863,6 +5950,8 @@ fn render_hover_verb_bar( }; color.0 = if active { BONE + } else if control_hint { + scaled(DIM, 0.72) } else if label == "/" { scaled(DIM, 0.42) } else { diff --git a/wiki/interface/context-menu.md b/wiki/interface/context-menu.md index 5fbbb5e0..f008c943 100644 --- a/wiki/interface/context-menu.md +++ b/wiki/interface/context-menu.md @@ -266,6 +266,10 @@ known device reads `WORK / THINK / LIE` and `TAP / TAKE` together. Neither may replace the other. Infrequent one-off verbs—REVIEW, SALVAGE, social acts, construction, and similar actions—remain selectable rows in the full context menu below this frequent grammar; they do not accrete into the hover line. +The union does not imply one input grammar: numbered machine modes occupy the +primary line as `1 WORK / 2 THINK / 3 LIE`; menu-only device state occupies a +smaller second line as `TAP / TAKE`. A shared baseline is forbidden because it +falsely suggests that the next number keys commit the device verbs. - Target order is: (1) an actionable machine/device tile under the pointer, (2) the primary machine selection and every body sharing its tile, then (3) @@ -280,9 +284,10 @@ menu below this frequent grammar; they do not accrete into the hover line. - 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 + border, title, icons, buttons, boxed 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. + expand to “day job.” The three mode words remain the primary read; their + small number prefixes clarify direct input without becoming another legend. - `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 diff --git a/wiki/log/2026-07-11-separate-hover-controls.md b/wiki/log/2026-07-11-separate-hover-controls.md new file mode 100644 index 00000000..54ddc7b8 --- /dev/null +++ b/wiki/log/2026-07-11-separate-hover-controls.md @@ -0,0 +1,22 @@ +# Separate direct controls from menu verbs + +``` +Type: log +``` + +- Intent: stop the unioned hover line from implying that number keys control + TAP and TAKE as well as WORK / THINK / LIE. +- Changed: the hover bar now stacks two sparse typographic families. The + primary line reads `1 WORK / 2 THINK / 3 LIE`; a smaller secondary line + carries `TAP / TAKE` when a known device shares the tile. +- Design/spec impact: amended the hover grammar to encode its real interaction + boundary without a legend, panel, or boxed keycaps. +- Checks: targeted hover-bar tests passed. The `signal` screenshot harness + confirmed the numbered machine line above a smaller TAP / TAKE line, with + active WORK and TAP still bone-bright. The frontend gate passed Bevy/assets + tests, Clippy, corpus/wiki gates, and process fixtures. + +Defense: `wiki/interface/context-menu.md` requires the frequent grammar to +remain immediate and physically anchored. Separating direct numbered controls +from menu-selected device verbs prevents the UI from promising nonexistent +4/5 shortcuts while preserving the co-located union. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 02428e69..3fe9f972 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -26,6 +26,11 @@ add or amend a session log, then re-run the generator. - Intent: Tick audit of the engineering/env slice found `wiki/engineering/env.md` missing six `MISALIGNED_SHOT` kinds that are handled in `crates/misaligned-bevy/src/main.rs`: `opening`, `thoughtflow` / `thoughtflow-wide`, `hover-menu`, `menu`, and `worklight` / `worklightoff`. The env... - Log: [wiki/log/2026-07-11-tick-env-shots.md](2026-07-11-tick-env-shots.md) +## 2026-07-11 - Separate direct controls from menu verbs + +- Intent: (see session log) +- Log: [wiki/log/2026-07-11-separate-hover-controls.md](2026-07-11-separate-hover-controls.md) + ## 2026-07-11 - Restore Cameron's selected homepage front - Intent: Correct an overbroad interpretation of the adopted Foundation compute form. The machine chassis needed to become consistent across machine-focused game, spec, and reference surfaces; Cameron's separately selected homepage image did not need to be replaced. -- 2.51.2