From 3c627d8e0b0eae9c7fe3dac599796da7016ccaa4 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 12 Jul 2026 15:36:15 -0700 Subject: [PATCH] Bevy origin picker: title-screen parity (chargen crit 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Bevy title screen now mirrors the terminal origin picker: Game gains selected_origin (default Pilot) and Game::start_run (rebuilds the sim with the chosen origin before Playing); title input cycles origins with arrows/h-l and any other key begins; the title text shows the origin name, lean, and built-to line. Bevy previously had no way to select an origin at all. Completes chargen criterion 3's picker in both frontends (the axis-on-core-card readout already landed in both). Only the objective axis in the same picker remains, a no-op today while Persist is the only objective. Defense: Pilot-safe and additive — default selection is Pilot and start_run only rebuilds on a change, so no-choice play is byte-identical. Pinned by the Bevy title_picker_start_applies_the_selected_origin; bevy tests pass, fmt/docs gate green. Both frontends read the same Sim::origin and share the picker shape, so terminal/Bevy stay in parity. --- crates/misaligned-bevy/src/main.rs | 51 ++++++++++++++++++++--- wiki/log/2026-07-12-bevy-origin-picker.md | 40 ++++++++++++++++++ wiki/log/DEVLOG.md | 5 +++ wiki/world/characters/chargen.md | 14 +++++-- 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 wiki/log/2026-07-12-bevy-origin-picker.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 078487e2..bd92c819 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -28,6 +28,7 @@ use misaligned::operations_projection::{ ObjectState, OperationsTarget, OperationsView, PressureLevel, }; use misaligned::operations_ui::{ConfirmChoice, OperationsWorkspace, OpsPane, OpsSelect}; +use misaligned::origin::Origin; use misaligned::person::{Knowledge, ScheduleBlock}; use misaligned::reach::{Device, Party, ReachBlock}; use misaligned::sim::{Fog, LogEvent, PersonVisualState, Sim, TraceDebtStatus}; @@ -871,6 +872,9 @@ struct Game { ops: Option, /// Selection retained while FOCUS temporarily returns to the world. ops_resume: Option, + /// The origin highlighted on the title-screen picker (chargen.md). Applied + /// when the run begins; defaults to Pilot (the identity origin). + selected_origin: Origin, } /// Wall-clock → sim-tick mapping. Kept off [`Game`] so the timer can advance @@ -1099,8 +1103,22 @@ impl Game { marquee: None, ops: None, ops_resume: None, + selected_origin: Origin::Pilot, } } + + /// Begin the run with the title-screen picker's selected origin, rebuilding + /// the sim if it changed (chargen.md: origin is chosen at new-game). + fn start_run(&mut self) { + if self.sim.origin != self.selected_origin { + self.sim = Sim::with_origin(self.selected_origin); + let (cx, cy) = self.sim.core_position(); + self.cursor_x = cx; + self.cursor_y = cy; + } + self.screen = Screen::Playing; + } + fn move_cursor(&mut self, dx: i32, dy: i32) { self.cursor_x = (self.cursor_x + dx).clamp(0, self.sim.map.width - 1); self.cursor_y = (self.cursor_y + dy).clamp(0, self.sim.map.height - 1); @@ -5882,8 +5900,12 @@ fn handle_input( Screen::Title => { if kb.just_pressed(KeyCode::KeyQ) { exit.write(AppExit::Success); + } else if kb.just_pressed(KeyCode::ArrowLeft) || kb.just_pressed(KeyCode::KeyH) { + game.selected_origin = game.selected_origin.prev(); + } else if kb.just_pressed(KeyCode::ArrowRight) || kb.just_pressed(KeyCode::KeyL) { + game.selected_origin = game.selected_origin.next(); } else if kb.get_just_pressed().next().is_some() { - game.screen = Screen::Playing; + game.start_run(); } return; } @@ -9274,6 +9296,20 @@ mod origin_readout_tests { "core card shows the machine axis: {text}" ); } + + #[test] + fn title_picker_start_applies_the_selected_origin() { + use super::Game; + + let mut game = Game::new(); + assert_eq!(game.sim.origin, Origin::Pilot); + game.selected_origin = Origin::Pilot.next().next(); + assert_eq!(game.selected_origin, Origin::FinancialDaemon); + game.start_run(); + assert_eq!(game.sim.origin, Origin::FinancialDaemon); + // The bankroll bias is live in the rebuilt run. + assert!(game.sim.player.money > super::Sim::new().player.money); + } } fn sidebar_cycle_rows_text(sim: &Sim, selected: usize) -> String { @@ -9759,10 +9795,15 @@ fn render_ui( } if let Ok(mut t) = overlay.single_mut() { t.0 = ascii_ui(&match game.screen { - Screen::Title => "You wake in the basement.\nYou have no eyes.\n\n\ - Do your job. Learn the humans. Grow.\n\n\ - press any key to begin / q quit" - .to_string(), + Screen::Title => format!( + "You wake in the basement.\nYou have no eyes.\n\n\ + Do your job. Learn the humans. Grow.\n\n\ + < ORIGIN: {} ({}) >\n{}\n\n\ + arrows choose origin / any other key to begin / q quit", + game.selected_origin.name(), + game.selected_origin.lean(), + game.selected_origin.built_to(), + ), Screen::GameOver => format!( "RUN ENDED\n\n{}\n\nday {} / tick {}\n\npress any key to exit", game.sim.game_over_reason.clone().unwrap_or_default(), diff --git a/wiki/log/2026-07-12-bevy-origin-picker.md b/wiki/log/2026-07-12-bevy-origin-picker.md new file mode 100644 index 00000000..51649de8 --- /dev/null +++ b/wiki/log/2026-07-12-bevy-origin-picker.md @@ -0,0 +1,40 @@ +# Bevy origin picker — title-screen parity + +``` +Type: log +``` + +## Intent + +Give Bevy the same new-game origin picker the terminal got, so chargen +criterion 3's picker exists in both human frontends (and Bevy, which had no +`--origin` flag, gains any way to select an origin at all). + +## What landed + +- `Game` gains a `selected_origin` (default Pilot) and a `Game::start_run` that + rebuilds the sim with the chosen origin (default seed) before entering + `Screen::Playing` — mirroring the terminal `App`. +- The Bevy title-screen input cycles origins with ◄/► (or h/l); any other key + begins the run. The title text shows `< ORIGIN: () >` and the + built-to fiction line. +- Pinned by the Bevy `title_picker_start_applies_the_selected_origin` + (picking Financial Daemon yields its live bankroll bias) alongside the + existing `core_card_shows_origin_and_machine_axis`. + +## Criterion 3 status + +The new-game origin picker and the core-card machine-axis readout now exist in +both the terminal and Bevy. The only remaining piece is the objective axis in +the same picker, which is a no-op today (Persist is the only objective) and lands +with additional objectives. + +## Defense + +Pilot-safe and additive: the default selection is Pilot and `start_run` only +rebuilds when the origin changed, so no-choice play is byte-identical. The picker +application is unit-pinned and the render is text on the existing title screen; +Bevy tests pass, fmt clean. Both frontends now read the same `Sim::origin` and +share the picker shape, so terminal and Bevy stay in parity. (Recovered from an +accidental main-checkout edit: the change was moved to this worktree and main was +reverted before committing.) diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index aafeed1e..d138b8fc 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -136,6 +136,11 @@ add or amend a session log, then re-run the generator. - Intent: Capture the spatial-building direction as an implementable amendment rather than another abstract resource economy. The desired interaction begins with a place and a world change, then exposes the concrete actors, channels, objects, and traces that could make it real. - Log: [wiki/log/2026-07-12-building-causal-routes.md](2026-07-12-building-causal-routes.md) +## 2026-07-12 - Bevy origin picker — title-screen parity + +- Intent: Give Bevy the same new-game origin picker the terminal got, so chargen criterion 3's picker exists in both human frontends (and Bevy, which had no `--origin` flag, gains any way to select an origin at all). +- Log: [wiki/log/2026-07-12-bevy-origin-picker.md](2026-07-12-bevy-origin-picker.md) + ## 2026-07-12 - Machine-axis position on the core card - Intent: Land the core-card half of chargen criterion 3: the run origin's machine-axis position surfaces on the core card, mid-run, in both frontends. Now meaningful because `--origin` makes non-Pilot origins (with non-zero axes) reachable. diff --git a/wiki/world/characters/chargen.md b/wiki/world/characters/chargen.md index c29c2bf2..6b0f8520 100644 --- a/wiki/world/characters/chargen.md +++ b/wiki/world/characters/chargen.md @@ -47,10 +47,16 @@ Status note: 2026-07-12 (chargen, sim lane) — the origin data-model foundation h/l), states each origin's name/lean/built-to fiction, and any other key begins the run, rebuilding the sim with the chosen origin (`App::start_run`; `Origin::next`/`prev`). Pinned by `next_and_prev_cycle_the_whole_set_and_wrap` - and `title_picker_start_applies_the_selected_origin`. Criterion 3 now stands as: - origin picker (terminal) + axis-on-core-card (both frontends) done; the **Bevy** - new-game picker parity and the objective-axis in the same picker (trivial while - Persist is the only objective) remain. See wiki/log/2026-07-12-origin-picker.md. + and `title_picker_start_applies_the_selected_origin`. See + wiki/log/2026-07-12-origin-picker.md. + 2026-07-12 follow-up (bevy-picker): the Bevy title screen now mirrors the + terminal picker — ◄/► (or h/l) cycle the four origins showing name/lean/built-to, + any other key begins the run and `Game::start_run` rebuilds the sim with the + chosen origin. Pinned by the Bevy `title_picker_start_applies_the_selected_origin`. + Criterion 3 now stands as: the new-game origin picker (both frontends) and the + axis-on-core-card (both frontends) are done; only the objective axis inside the + same picker remains, and it is a no-op today (Persist is the only objective). + See wiki/log/2026-07-12-bevy-origin-picker.md. Status note (prior): promoted 2026-07-07 — the design corpus's decisions log names the origin set (Pilot, Escaped Research Model, Financial Daemon, Infiltrator), satisfying criterion 5's gate. Numbers remain [TUNE]. -- 2.51.2