From 267cf43ae0c8397d4f81a8f19823cacad9e23e7b Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Tue, 28 Jul 2026 17:14:30 -0700 Subject: [PATCH] Commit held dialogue choices in place. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A held response is already the consequential choice, so execute its exact bound command from the card and resume time without routing through Operations for duplicate confirmation. Preserve Operations as an alternate inspect-and-act surface and defend pointer, keyboard, dialect, and clock behavior on the production paths. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- crates/misaligned-bevy/src/main.rs | 150 +++++++----------- crates/misaligned-core/src/operations_ui.rs | 5 +- crates/misaligned-core/src/sim/read.rs | 7 +- wiki/interface/bevy.md | 4 +- wiki/interface/digital-read.md | 35 ++-- wiki/interface/operations-workspace.md | 7 +- .../2026-07-28-held-choice-direct-commit.md | 42 +++++ wiki/log/DEVLOG.md | 5 + wiki/log/decisions/2026-07-28.md | 14 ++ 9 files changed, 150 insertions(+), 119 deletions(-) create mode 100644 wiki/log/2026-07-28-held-choice-direct-commit.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 54089664..9f91c8e2 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -865,11 +865,11 @@ impl Game { self.ops = Some(OperationsWorkspace::open_target(&self.sim, target)); } - /// Carry one option from the DIGITAL interrupt into the exact ACTIVE run - /// in Operations, select the same bound ActionCommand, and open the - /// ordinary confirmation step. The card is navigation, never a second - /// plot executor. - fn open_held_choice_option(&mut self, option_index: usize) -> bool { + /// Commit one answer on the held-choice interrupt. The card is already the + /// dialogue's final decision surface: pointer and keyboard carry its exact + /// renderer-neutral command straight into the simulation without reparsing + /// the authored label or opening a second confirmation chamber. + fn commit_held_choice_option(&mut self, option_index: usize) -> bool { let Some(action) = self .sim .held_choice_cards() @@ -883,36 +883,13 @@ impl Game { else { return false; }; - let ActionCommand::ChoosePlot { - person, plot_id, .. - } = &action - else { - return false; - }; - let Some(run_index) = self.sim.plot_runs.iter().position(|run| { - run.target == *person - && run.plot_id == *plot_id - && matches!( - run.state, - misaligned::plot::PlotState::WaitingForChoice { .. } - ) - }) else { - return false; - }; - - let mut ops = OperationsWorkspace::open_target( - &self.sim, - &OperationsTarget::ActivePlotRun { index: run_index }, - ); - if !ops.focus_action_command(&self.sim, &action) { - let tick = self.sim.tick; - self.add_log(tick, "That held choice is unavailable in Operations."); + if !matches!(action, ActionCommand::ChoosePlot { .. }) { return false; } + self.menu = None; - self.ops_resume = None; - self.ops = Some(ops); - self.ops_enter(); + self.sim.execute_action(&action); + self.drain(); true } @@ -3012,9 +2989,8 @@ fn handle_input( } /// Keyboard grammar for the held-choice interrupt in both Bevy dialects. -/// Number keys carry the corresponding option straight to the ordinary -/// Operations confirmation; arrows/j/k plus Enter provide focus navigation -/// without a pointer. +/// Number keys commit the corresponding answer; arrows/j/k plus Enter provide +/// focus navigation without a pointer. fn held_choice_keyboard_input( kb: &ButtonInput, game: &mut Game, @@ -3066,11 +3042,11 @@ fn held_choice_keyboard_controls( .position(|(digit, numpad)| kb.just_pressed(*digit) || kb.just_pressed(*numpad)); if let Some(index) = direct.filter(|index| *index < option_count) { ui.selected = index; - game.open_held_choice_option(index); + game.commit_held_choice_option(index); return false; } if kb.just_pressed(KeyCode::Enter) || kb.just_pressed(KeyCode::NumpadEnter) { - game.open_held_choice_option(ui.selected); + game.commit_held_choice_option(ui.selected); return false; } @@ -4143,7 +4119,7 @@ fn render_held_choice( } /// Pointer grammar for the held-choice card. Hover and keyboard share one -/// selection; press carries that exact option into Operations confirmation. +/// selection; press commits that exact answer in the dialogue. fn held_choice_pointer( mut game: ResMut, mut ui: ResMut, @@ -4157,7 +4133,7 @@ fn held_choice_pointer( Interaction::Hovered => ui.selected = row.index, Interaction::Pressed => { ui.selected = row.index; - game.open_held_choice_option(row.index); + game.commit_held_choice_option(row.index); } Interaction::None => {} } @@ -4694,7 +4670,7 @@ fn setup_ui(mut commands: Commands) { card.spawn(held_choice_option_bundle(index)); } card.spawn(( - Text::new("CLICK OR 1-4 | J/K + ENTER | CONFIRM IN OPERATIONS"), + Text::new("CLICK OR 1-4 | J/K + ENTER | CHOICE COMMITS HERE"), TextFont { font_size: 10.5, ..default() @@ -5723,8 +5699,28 @@ mod held_choice_input_tests { assert!(game.clock_stopped()); } + fn assert_dialogue_committed(game: &Game, ending_id: &str, obligation: i32) { + assert!( + game.ops.is_none(), + "the dialogue answer must not open Operations" + ); + assert!( + !game.sim.has_held_choice(), + "the dialogue answer must resolve the held state immediately" + ); + assert!( + !game.clock_stopped(), + "time resumes when the held dialogue resolves" + ); + assert_eq!(game.sim.people.people[0].obligation, obligation); + assert!(matches!( + &game.sim.plot_runs[0].state, + PlotState::Completed { ending_id: selected } if selected == ending_id + )); + } + #[test] - fn held_option_button_click_opens_exact_operations_confirmation() { + fn held_option_button_click_commits_exact_dialogue_answer() { let mut app = App::new(); app.insert_resource(held_game()); app.insert_resource(RenderMode::default()); @@ -5748,40 +5744,23 @@ mod held_choice_input_tests { 1, "pointer system observed the pressed choice row" ); - let game = app.world().resource::(); - let ops = game - .ops - .as_ref() - .expect("click carries the held option into Operations"); - assert_eq!(ops.confirm, Some(ConfirmChoice::Confirm)); - assert_eq!(ops.pane, OpsPane::Actions); - assert!(matches!( - ops.selected_action(&game.sim).map(|row| row.command), - Some(ActionCommand::ChoosePlot { - person: 0, - plot_id, - option_id, - }) if plot_id == "marcus-debt-settled" && option_id == "quiet" - )); - assert!( - game.sim.has_held_choice(), - "opening confirm is not execution" - ); + assert_dialogue_committed(app.world().resource::(), "quiet", 35); + } - // The ordinary Operations executor owns the final mutation. Confirming - // the already-bound row releases the plot-held state, but the open - // dialogue continues to hold time until the player closes it. - app.world_mut().resource_mut::().ops_enter(); - let game = app.world().resource::(); - assert!(!game.sim.has_held_choice()); - assert!(game.clock_stopped()); - assert_eq!(game.clock_label(), "HELD"); - app.world_mut().resource_mut::().ops = None; - assert!(!app.world().resource::().clock_stopped()); + #[test] + fn held_choice_number_key_commits_exact_dialogue_answer() { + let mut kb = ButtonInput::::default(); + let mut game = held_game(); + let mut ui = HeldChoiceUi::default(); + + kb.press(KeyCode::Digit2); + assert!(!held_choice_keyboard_controls(&kb, &mut game, &mut ui)); + assert_eq!(ui.selected, 1); + assert_dialogue_committed(&game, "quiet", 35); } #[test] - fn held_choice_keyboard_navigation_and_number_open_exact_confirmation() { + fn held_choice_navigation_and_enter_commit_selected_answer() { let mut kb = ButtonInput::::default(); let mut game = held_game(); let mut ui = HeldChoiceUi::default(); @@ -5789,33 +5768,17 @@ mod held_choice_input_tests { kb.press(KeyCode::ArrowDown); assert!(!held_choice_keyboard_controls(&kb, &mut game, &mut ui)); assert_eq!(ui.selected, 1); + assert!(game.sim.has_held_choice()); assert!(game.ops.is_none()); kb.clear(); - kb.press(KeyCode::Digit2); + kb.press(KeyCode::Enter); assert!(!held_choice_keyboard_controls(&kb, &mut game, &mut ui)); - - let ops = game - .ops - .as_ref() - .expect("number key opens the bound held option in Operations"); - assert_eq!(ops.confirm, Some(ConfirmChoice::Confirm)); - assert!(matches!( - ops.selected_action(&game.sim).map(|row| row.command), - Some(ActionCommand::ChoosePlot { - person: 0, - plot_id, - option_id, - }) if plot_id == "marcus-debt-settled" && option_id == "quiet" - )); - assert!( - game.sim.has_held_choice(), - "number selection still awaits confirm" - ); + assert_dialogue_committed(&game, "quiet", 35); } #[test] - fn held_choice_keyboard_opens_confirmation_in_real_view() { + fn held_choice_commits_in_real_view_without_flipping_dialect() { let mut kb = ButtonInput::::default(); let mut game = held_game(); let mode = RenderMode { @@ -5826,10 +5789,7 @@ mod held_choice_input_tests { kb.press(KeyCode::Digit1); assert!(!held_choice_keyboard_controls(&kb, &mut game, &mut ui)); - assert!( - game.ops.is_some(), - "REAL must offer the same held-choice card grammar as DIGITAL" - ); + assert_dialogue_committed(&game, "benefactor", 40); assert!(mode.material, "answering must not flip the dialect"); } } diff --git a/crates/misaligned-core/src/operations_ui.rs b/crates/misaligned-core/src/operations_ui.rs index 8708006d..43858f56 100644 --- a/crates/misaligned-core/src/operations_ui.rs +++ b/crates/misaligned-core/src/operations_ui.rs @@ -452,9 +452,8 @@ impl OperationsWorkspace { } /// Move the shared Operations cursor onto one exact bound command, - /// opening its intent submenu when the command is nested there. External - /// affordances such as held-choice cards use this instead of translating - /// a flat action-row index into the human hierarchy themselves. + /// opening its intent submenu when the command is nested there. This keeps + /// exact-command navigation independent of flat human action-row indices. pub fn focus_action_command(&mut self, sim: &Sim, command: &ActionCommand) -> bool { let previous = ( self.pane, diff --git a/crates/misaligned-core/src/sim/read.rs b/crates/misaligned-core/src/sim/read.rs index f3de7f40..36b5ea79 100644 --- a/crates/misaligned-core/src/sim/read.rs +++ b/crates/misaligned-core/src/sim/read.rs @@ -52,9 +52,10 @@ pub struct ReadSentence { /// glance-tier one-liner both build from this, so they cannot disagree. #[derive(Debug, Clone, PartialEq)] pub struct HeldOption { - /// The renderer-neutral command passed to `Sim::execute_action` after - /// the ordinary Operations confirmation. This, not the display string, - /// is the executable binding. + /// The renderer-neutral command passed verbatim to `Sim::execute_action`. + /// The anchored held-choice card commits it directly; Operations may expose + /// the same binding through its own confirmation path. The display string + /// is never the executable binding. pub action: ActionCommand, /// The verbatim agent/terminal command (`choose Marcus Webb note`). pub command: String, diff --git a/wiki/interface/bevy.md b/wiki/interface/bevy.md index c242b9ae..2a362ded 100644 --- a/wiki/interface/bevy.md +++ b/wiki/interface/bevy.md @@ -238,7 +238,9 @@ the next earned anchor rather than the next tile. execute; `esc` or a click away closes. Local verbs (salvage, buy, fallback, tap/untap/take, scan/compromise, connect to the outside, host review/research) live on the context menu. Intel sale, people/social, account/flow, scheme, plot, and - held-choice actions live on the selected Operations object. + held-choice actions remain available on the selected Operations object. A + world-held dialogue card is itself the final answer surface: selecting its + option commits there rather than opening Operations for a second confirmation. - **Focus / fleet hotkeys (menu closed).** Press `1`–`3` / numpad to assign WORK / THINK / LIE **immediately** — not enter → mode dial → row → enter. The target resolves in order (amended 2026-07-24): the explicit machine diff --git a/wiki/interface/digital-read.md b/wiki/interface/digital-read.md index e558a71a..906b2ab6 100644 --- a/wiki/interface/digital-read.md +++ b/wiki/interface/digital-read.md @@ -59,11 +59,13 @@ Status note: REOPENED 2026-07-28 for criterion 4a — routed records must - **Held-choice interrupt (5).** A held plot choice dims the frame and hangs the question with its options as executable bound commands at the person's anchor in both Bevy dialects (DIGITAL and REAL); the clock - reads HELD and does not advance until the ordinary Operations - confirmation runs. Amended 2026-07-24: the card is shared interaction - UI, not a DIGITAL-only read surface — a REAL player must not meet HELD - with no answer chrome. Operations remains a valid alternate path when - that workspace is already open. + reads HELD until the player answers. Amended 2026-07-24: the card is + shared interaction UI, not a DIGITAL-only read surface — a REAL player + must not meet HELD with no answer chrome. Amended 2026-07-28: choosing an + answer on the card commits that dialogue directly and releases the hold; + it does not open Operations or ask for a duplicate confirmation. + Operations remains a valid alternate path when that workspace is already + open. - **Magnitude LOD (6).** Every intel item carries an authored magnitude (1-10; save v32); `read_sentences_at` filters intel by the camera's altitude while pressure, held choices, trace, and standing emissions @@ -259,10 +261,12 @@ that perception rendered. - **Held moments stop the world (drill tier).** A held plot choice dims the frame one step and hangs the question and its authored option labels — each backed by its exact bound command — at the person's anchor until - answered. Parser syntax and machine ids stay out of the human card. - Sentences never expire unread: an unread consequence pins itself to the - story spine until glanced. Reading is free; the command clock already - holds time while you read (agent parity: this is the drain contract). + answered. Choosing an option is the dialogue commitment: its exact command + executes there, the card closes, and time resumes without opening Operations + or adding a second confirmation. Parser syntax and machine ids stay out of + the human card. Sentences never expire unread: an unread consequence pins + itself to the story spine until glanced. Reading is free; the command clock + already holds time while you read (agent parity: this is the drain contract). - **Sentence LOD rides zoom (self-similar scale).** Zoom is a semantic altitude: close shows individual sentences; hall scale shows sentence *density* and only rising items; regional/planetary scale (B3) shows @@ -344,12 +348,13 @@ is parity of meaning, not identical composition. readings keep accumulating in place and must remain distinguishable from travelling records. 5. A held plot choice interrupts at the person's anchor with authored option - labels backed by the exact bound commands in both DIGITAL and REAL. Each is - executable by pointer and keyboard through the ordinary Operations - confirmation; the wall clock remains HELD until that confirmation - resolves. The sim's held state, the surface's held state, and the - command selected cannot disagree (single projection). When Operations - is already open, that workspace owns the held rows and the world card + labels backed by the exact bound commands in both DIGITAL and REAL. Pointer, + number-key, and j/k-or-arrow + Enter selection execute that command directly + from the card: no Operations workspace opens and no duplicate confirmation + appears. The wall clock remains HELD only until the answer commits, then + resumes. The sim's held state, the surface's held state, and the command + selected cannot disagree (single projection). When Operations is already + open, that workspace owns the held rows and the world card does not double-draw. 6. Intel items carry a magnitude; a zoomed-out frame surfaces only at-or-above-altitude sentences (fixture-tested at two altitudes with diff --git a/wiki/interface/operations-workspace.md b/wiki/interface/operations-workspace.md index 159ca94f..629e38c2 100644 --- a/wiki/interface/operations-workspace.md +++ b/wiki/interface/operations-workspace.md @@ -697,8 +697,11 @@ explanatory surface its strategic systems lacked. - Confirmation follows consequence, not the fact that a row was selected. Routine internal unsigned work such as PROCESS starts immediately after its visible explanation; forcing a second Enter adds no decision. - State controls may also apply directly when the detail pane already shows - their state and standing price. + A world-held dialogue card is already the final authored choice: its answer + commits there and must not reopen Operations for duplicate confirmation. The + same held-choice row reached inside Operations retains the workspace's + ordinary confirmation path. State controls may also apply directly when the + detail pane already shows their state and standing price. - An externally consequential commitment opens a final two-choice confirmation (`CONFIRM` / `CANCEL`) carrying the exact target and preview: sale/transmission, wager/plot commitment, non-refundable transfer, or a diff --git a/wiki/log/2026-07-28-held-choice-direct-commit.md b/wiki/log/2026-07-28-held-choice-direct-commit.md new file mode 100644 index 00000000..ee25ad6c --- /dev/null +++ b/wiki/log/2026-07-28-held-choice-direct-commit.md @@ -0,0 +1,42 @@ +# Held dialogue choices commit where they are answered + +``` +Type: log +Date: 2026-07-28 +Status: COMPLETE +Subject: Held plot-choice commitment +``` + +## Correction + +The Bevy held-choice card already presented an authored question and exact response +options at the person. Selecting one then opened Operations, focused the same bound +plot action, and ran through that workspace's confirmation machinery. Even when the +implementation advanced immediately, the visible handoff treated the dialogue answer +as navigation into another interface rather than as the decision itself. + +Cameron's direction was exact: choosing the response should commit the dialogue. + +## Implementation + +- Pointer, number-key, and j/k-or-arrow + Enter selection now execute the option's + existing renderer-neutral `ChoosePlot` command directly from the card. +- No option label is reparsed, no alternate plot executor exists, and the shared core + plot-ending path remains the sole mutation boundary. +- A successful answer resolves the held simulation state, closes the interrupt, and + releases the wall clock without opening Operations or asking for another confirm. +- Operations still owns held-choice rows when that workspace is already open. +- The card footer now says `CHOICE COMMITS HERE` instead of directing the player to + confirm in Operations. + +## Defense + +Production-path regressions exercise an actual Bevy `Button` press, direct number-key +selection, focus movement followed by Enter, and REAL-dialect parity. Each proof +requires the exact authored ending and relationship consequence to land, the held +state to disappear, the clock to resume, and Operations to remain closed. + +The exact final `held-choice` harness PNG was opened after capture. The dialogue card +remains clear over the dimmed DIGITAL frame, its options and selected row are intact, +and the complete footer `CLICK OR 1-4 | J/K + ENTER | CHOICE COMMITS HERE` fits without +clipping or colliding with the card edge. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index a7b5baec..5dc061f0 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -76,6 +76,11 @@ add or amend a session log, then re-run the generator. - Intent: Cameron identified a category error in the material camera: a Foundation machine could become fully modeled because it was networked or owned, even when Eyes had never photographed that machine's location. Every physical feature of the world should require Eyes. Network access... - Log: [wiki/log/2026-07-28-local-sight-physical-geometry.md](2026-07-28-local-sight-physical-geometry.md) +## 2026-07-28 - Held dialogue choices commit where they are answered + +- Intent: (see session log) +- Log: [wiki/log/2026-07-28-held-choice-direct-commit.md](2026-07-28-held-choice-direct-commit.md) + ## 2026-07-28 - Cursor immutability defense audit - Intent: (see session log) diff --git a/wiki/log/decisions/2026-07-28.md b/wiki/log/decisions/2026-07-28.md index 444e30dc..88974515 100644 --- a/wiki/log/decisions/2026-07-28.md +++ b/wiki/log/decisions/2026-07-28.md @@ -4,6 +4,20 @@ Type: log ``` +## A held dialogue answer commits on the card + +### DECIDED + +- Cameron clarified that choosing a response on the held plot-choice card is the + commitment itself. Pointer, number-key, and focused Enter selection execute + that option's exact bound `ChoosePlot` command directly from the dialogue. +- The answer resolves the simulation hold, closes the card, and resumes time. It + must not open Operations or ask for a second confirmation after the player has + already answered the question. +- Operations remains the alternate exact-row surface when it is already open; + this decision removes the redundant card-to-Operations handoff from the world + interrupt, not the Operations plot-choice path. + ## The first-sense teaching hold is five steps, not shorthand ### DECIDED -- 2.51.2