diff --git a/Cargo.toml b/Cargo.toml index 11e6bcd..cce96b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ # file: Cargo.toml -# version: 7 +# version: 8 [workspace] resolver = "3" members = [ + "crates/common/game-logging-lib", "crates/engines/engine-v1-common", "crates/engines/engine-v1-platform-api", "crates/engines/engine-v1-sdl", @@ -11,43 +12,15 @@ members = [ "crates/games/game-snake-poc", "crates/apps/game-reflex-poc-desktop", "crates/apps/game-snake-poc-desktop", - "crates/common/game-logging-lib", ] [workspace.package] -version = "0.1.0-0-pre.4.fix.2" +version = "0.1.0-0-pre.5" edition = "2024" license = "MIT" -repository = "https://git.sasedev.com/Sasedev/games" -authors = ["Sasedev "] -publish = false [workspace.dependencies] -tracing = "0.1.44" -tracing-appender = "0.2.5" -tracing-subscriber = { version = "0.3.23", features = ["fmt"] } - -[workspace.lints.rust] -missing_docs = "warn" -unreachable_pub = "deny" -unsafe_code = "forbid" - -[workspace.lints.clippy] -unwrap_used = "deny" -expect_used = "deny" -implicit_return = "deny" -needless_return = "allow" -useless_vec = "deny" -question_mark = "deny" -question_mark_used = "deny" -needless_match = "allow" -manual_ok_err = "allow" -manual_unwrap_or = "allow" -manual_map = "allow" -match_like_matches_macro = "allow" -single_match = "allow" -manual_unwrap_or_default = "allow" -manual_find = "allow" -explicit_counter_loop = "allow" -get_first = "allow" -implicit_saturating_sub = "allow" +sdl3 = "0.16" +tracing = "0.1" +tracing-appender = "0.2" +tracing-subscriber = "0.3" diff --git a/crates/apps/game-reflex-poc-desktop/Cargo.toml b/crates/apps/game-reflex-poc-desktop/Cargo.toml index 7ed9970..f5953e0 100644 --- a/crates/apps/game-reflex-poc-desktop/Cargo.toml +++ b/crates/apps/game-reflex-poc-desktop/Cargo.toml @@ -1,21 +1,18 @@ # file: crates/apps/game-reflex-poc-desktop/Cargo.toml -# version: 3 +# version: 2 [package] name = "game-reflex-poc-desktop" version.workspace = true edition.workspace = true license.workspace = true -repository.workspace = true -authors.workspace = true -publish.workspace = true [dependencies] +engine-v1-sdl = { path = "../../engines/engine-v1-sdl" } game-logging-lib = { path = "../../common/game-logging-lib" } -tracing.workspace = true -engine-v1-common = { path = "../../engines/engine-v1-common" } -engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" } game-reflex-poc = { path = "../../games/game-reflex-poc" } +tracing.workspace = true -[lints] -workspace = true +[lints.rust] +missing_docs = "warn" +unsafe_code = "forbid" diff --git a/crates/apps/game-reflex-poc-desktop/src/main.rs b/crates/apps/game-reflex-poc-desktop/src/main.rs index 112657e..ce88564 100644 --- a/crates/apps/game-reflex-poc-desktop/src/main.rs +++ b/crates/apps/game-reflex-poc-desktop/src/main.rs @@ -1,31 +1,14 @@ // file: crates/apps/game-reflex-poc-desktop/src/main.rs // version: 3 -#![warn(missing_docs)] -#![deny(unreachable_pub)] -#![forbid(unsafe_code)] +use engine_v1_sdl::SdlRuntime; +use game_reflex_poc::ReflexGame; -//! Desktop development runner for the Reflex POC game library. -//! -//! This binary exercises the platform-independent fixed-step loop. SDL3 windowing and physical input mapping are introduced in the next dedicated delta. +fn main() -> Result<(), String> { + let _guard = game_logging_lib::init_default_tracing("games::runner").map_err(|error| error.to_string())?; + tracing::info!(game = "reflex", "desktop SDL3 runner started"); -fn main() { - let _logging_guard = match game_logging_lib::init_console_tracing() { - std::result::Result::Ok(guard) => guard, - std::result::Result::Err(error) => { - eprintln!("failed to initialize tracing: {error}"); - return; - }, - }; - tracing::info!(target: "games::runner", game = "reflex", "desktop POC runner started"); - let _monetization = engine_v1_platform_api::MonetizationCapabilities::disabled(); - let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true); - let mut runner = engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(16)); - let mut state = game_reflex_poc::ReflexState::new(); - for _ in 0..3 { - runner.tick(&mut state, input); - } - tracing::info!(target: "games::runner", game = "reflex", frames = runner.next_frame_index(), "desktop POC runner completed"); - println!("Reflex POC desktop runner: score={} frames={}", state.score(), runner.next_frame_index()); - return; + let runtime = SdlRuntime::new("Reflex POC", 720, 1280, 16); + let mut game = ReflexGame::default(); + return runtime.run(&mut game); } diff --git a/crates/apps/game-snake-poc-desktop/Cargo.toml b/crates/apps/game-snake-poc-desktop/Cargo.toml index 1070fc7..1a18af8 100644 --- a/crates/apps/game-snake-poc-desktop/Cargo.toml +++ b/crates/apps/game-snake-poc-desktop/Cargo.toml @@ -1,21 +1,18 @@ # file: crates/apps/game-snake-poc-desktop/Cargo.toml -# version: 3 +# version: 2 [package] name = "game-snake-poc-desktop" version.workspace = true edition.workspace = true license.workspace = true -repository.workspace = true -authors.workspace = true -publish.workspace = true [dependencies] +engine-v1-sdl = { path = "../../engines/engine-v1-sdl" } game-logging-lib = { path = "../../common/game-logging-lib" } -tracing.workspace = true -engine-v1-common = { path = "../../engines/engine-v1-common" } -engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" } game-snake-poc = { path = "../../games/game-snake-poc" } +tracing.workspace = true -[lints] -workspace = true +[lints.rust] +missing_docs = "warn" +unsafe_code = "forbid" diff --git a/crates/apps/game-snake-poc-desktop/src/main.rs b/crates/apps/game-snake-poc-desktop/src/main.rs index f8f455e..978ea71 100644 --- a/crates/apps/game-snake-poc-desktop/src/main.rs +++ b/crates/apps/game-snake-poc-desktop/src/main.rs @@ -1,31 +1,14 @@ // file: crates/apps/game-snake-poc-desktop/src/main.rs // version: 3 -#![warn(missing_docs)] -#![deny(unreachable_pub)] -#![forbid(unsafe_code)] +use engine_v1_sdl::SdlRuntime; +use game_snake_poc::SnakeGame; -//! Desktop development runner for the Snake POC game library. -//! -//! This binary exercises the platform-independent fixed-step loop. SDL3 windowing and physical input mapping are introduced in the next dedicated delta. +fn main() -> Result<(), String> { + let _guard = game_logging_lib::init_default_tracing("games::runner").map_err(|error| error.to_string())?; + tracing::info!(game = "snake", "desktop SDL3 runner started"); -fn main() { - let _logging_guard = match game_logging_lib::init_console_tracing() { - std::result::Result::Ok(guard) => guard, - std::result::Result::Err(error) => { - eprintln!("failed to initialize tracing: {error}"); - return; - }, - }; - tracing::info!(target: "games::runner", game = "snake", "desktop POC runner started"); - let _monetization = engine_v1_platform_api::MonetizationCapabilities::disabled(); - let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true); - let mut runner = engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(16)); - let mut state = game_snake_poc::SnakeState::new(); - for _ in 0..3 { - runner.tick(&mut state, input); - } - tracing::info!(target: "games::runner", game = "snake", frames = runner.next_frame_index(), "desktop POC runner completed"); - println!("Snake POC desktop runner: length={} frames={}", state.length(), runner.next_frame_index()); - return; + let runtime = SdlRuntime::new("Snake POC", 720, 1280, 16); + let mut game = SnakeGame::default(); + return runtime.run(&mut game); } diff --git a/crates/engines/engine-v1-sdl/Cargo.toml b/crates/engines/engine-v1-sdl/Cargo.toml index 8356b7e..c5bf11a 100644 --- a/crates/engines/engine-v1-sdl/Cargo.toml +++ b/crates/engines/engine-v1-sdl/Cargo.toml @@ -1,17 +1,17 @@ # file: crates/engines/engine-v1-sdl/Cargo.toml -# version: 1 +# version: 2 [package] name = "engine-v1-sdl" version.workspace = true edition.workspace = true license.workspace = true -repository.workspace = true -authors.workspace = true -publish.workspace = true [dependencies] engine-v1-common = { path = "../engine-v1-common" } +sdl3.workspace = true +tracing.workspace = true -[lints] -workspace = true +[lints.rust] +missing_docs = "warn" +unsafe_code = "forbid" diff --git a/crates/engines/engine-v1-sdl/src/lib.rs b/crates/engines/engine-v1-sdl/src/lib.rs index 2b41c82..a7096ee 100644 --- a/crates/engines/engine-v1-sdl/src/lib.rs +++ b/crates/engines/engine-v1-sdl/src/lib.rs @@ -1,15 +1,11 @@ // file: crates/engines/engine-v1-sdl/src/lib.rs -// version: 1 +// version: 2 -#![warn(missing_docs)] -#![deny(unreachable_pub)] -#![forbid(unsafe_code)] - -//! SDL boundary for engine generation V1. -//! -//! The baseline intentionally contains only the boundary type. The external SDL3 dependency is introduced in the dedicated SDL integration delta. +//! SDL3 integration for engine generation V1. mod runtime; -/// Re-export of the SDL runtime boundary marker. -pub use self::runtime::SdlRuntimeBoundary; +#[cfg(test)] +mod unit_tests; + +pub use crate::runtime::SdlRuntime; diff --git a/crates/engines/engine-v1-sdl/src/runtime.rs b/crates/engines/engine-v1-sdl/src/runtime.rs index 9a50b0f..502d177 100644 --- a/crates/engines/engine-v1-sdl/src/runtime.rs +++ b/crates/engines/engine-v1-sdl/src/runtime.rs @@ -1,14 +1,84 @@ // file: crates/engines/engine-v1-sdl/src/runtime.rs // version: 1 -/// Marker representing the future SDL3 runtime boundary for engine V1. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct SdlRuntimeBoundary; +use sdl3::event::Event; +use sdl3::keyboard::Keycode; +use sdl3::pixels::Color; -impl SdlRuntimeBoundary { - /// Returns the engine generation served by this boundary. +use engine_v1_common::{Action, EngineGame, FixedStepRunner, InputState}; + +/// Minimal SDL3 runtime used by Desktop POC runners. +pub struct SdlRuntime { + title: String, + width: u32, + height: u32, + frame_duration_ms: u64, +} + +impl SdlRuntime { + /// Creates a minimal SDL3 runtime configuration. #[must_use] - pub const fn engine_generation() -> u16 { - return 1; + pub fn new(title: impl Into, width: u32, height: u32, frame_duration_ms: u64) -> Self { + return Self { + title: title.into(), + width, + height, + frame_duration_ms, + }; + } + + /// Runs a game until the user closes the window or presses Escape. + pub fn run(&self, game: &mut G) -> Result<(), String> { + let sdl = sdl3::init().map_err(|error| error.to_string())?; + let video = sdl.video().map_err(|error| error.to_string())?; + let window = video + .window(&self.title, self.width, self.height) + .position_centered() + .build() + .map_err(|error| error.to_string())?; + let mut canvas = window.into_canvas(); + let mut events = sdl.event_pump().map_err(|error| error.to_string())?; + let mut runner = FixedStepRunner::new(self.frame_duration_ms); + + tracing::info!( + title = %self.title, + width = self.width, + height = self.height, + "SDL3 runtime started" + ); + + 'running: loop { + let mut input = InputState::default(); + + for event in events.poll_iter() { + match event { + Event::Quit { .. } => { + break 'running; + } + Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => { + break 'running; + } + Event::KeyDown { + keycode: Some(Keycode::Space), + .. + } => { + input.set(Action::Primary, true); + } + _ => {} + } + } + + runner.tick(game, &input); + + canvas.set_draw_color(Color::RGB(18, 18, 24)); + canvas.clear(); + canvas.present(); + } + + tracing::info!("SDL3 runtime stopped"); + return Ok(()); } } diff --git a/crates/engines/engine-v1-sdl/src/unit_tests.rs b/crates/engines/engine-v1-sdl/src/unit_tests.rs new file mode 100644 index 0000000..0908560 --- /dev/null +++ b/crates/engines/engine-v1-sdl/src/unit_tests.rs @@ -0,0 +1,5 @@ +// file: crates/engines/engine-v1-sdl/src/unit_tests.rs +// version: 1 + +#[path = "../unit_tests/runtime.rs"] +mod runtime; diff --git a/crates/engines/engine-v1-sdl/unit_tests/runtime.rs b/crates/engines/engine-v1-sdl/unit_tests/runtime.rs new file mode 100644 index 0000000..b4df740 --- /dev/null +++ b/crates/engines/engine-v1-sdl/unit_tests/runtime.rs @@ -0,0 +1,11 @@ +// file: crates/engines/engine-v1-sdl/unit_tests/runtime.rs +// version: 1 + +//! Unit tests for SDL runtime configuration. + +use engine_v1_sdl::SdlRuntime; + +#[test] +fn runtime_configuration_constructs() { + let _runtime = SdlRuntime::new("test", 320, 240, 16); +} diff --git a/deltas/0.1.0/0-pre.5.md b/deltas/0.1.0/0-pre.5.md new file mode 100644 index 0000000..87156d4 --- /dev/null +++ b/deltas/0.1.0/0-pre.5.md @@ -0,0 +1,90 @@ + + + +# Delta 0.1.0-0-pre.5 + +## Base + +Base validée : `0.1.0-0-pre.4.fix.2`. + +## Validation du delta précédent + +`0.1.0-0-pre.4.fix.2` a été validée par l'utilisateur le 2026-09-16. + +Résultats : + +- `cargo fmt --all -- --check` propre ; +- audit Rust/workspace propre ; +- audit Markdown propre : `1 table(s), 37 file(s)` ; +- `cargo check --workspace` propre. + +## Objectif + +Introduire la première intégration SDL3 Desktop réelle sans déplacer de logique de gameplay hors des crates `lib` de jeu. + +## Changements + +- ajout de SDL3 comme dépendance workspace ; +- implémentation de `SdlRuntime` dans `engine-v1-sdl` ; +- création d'une fenêtre SDL3 ; +- pompe d'événements SDL3 ; +- fermeture par événement `Quit` ou touche `Escape` ; +- mapping initial de `Space` vers l'action abstraite `Primary` ; +- rendu minimal avec effacement de fenêtre ; +- conservation de la boucle moteur commune `FixedStepRunner` ; +- migration des runners Reflex et Snake vers le runtime SDL3 ; +- ajout d'un test unitaire séparé pour la construction de la configuration SDL3 ; +- archivage immuable de la validation de `0-pre.4.fix.2`. + +## Limites volontaires + +- pas encore de sprites ; +- pas encore d'assets ; +- pas encore de texte ; +- pas encore de contrôle souris/tactile ; +- pas encore de cadence réelle basée sur un timer SDL ; +- pas encore de backend Android SDL3 ; +- les deux runners ouvrent seulement une fenêtre fonctionnelle et injectent `Primary` via `Space`. + +## Validation à exécuter par l'utilisateur + +Le delta modifie du Rust : + +```bash +cargo fmt --all +cargo fmt --all -- --check +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history +cargo check --workspace +cargo clippy --workspace --all-targets --all-features -- -D warnings +``` + +Tests ciblés : + +```bash +cargo test -p engine-v1-sdl --all-targets --all-features +cargo test -p game-reflex-poc --all-targets --all-features +cargo test -p game-snake-poc --all-targets --all-features +``` + +Smokes Desktop interactifs : + +```bash +cargo run -p game-reflex-poc-desktop +cargo run -p game-snake-poc-desktop +``` + +Pour chaque runner : + +- une fenêtre SDL3 doit s'ouvrir ; +- `Escape` doit fermer proprement la fenêtre ; +- le bouton de fermeture système doit fermer proprement la fenêtre ; +- `Space` doit être capturé comme action `Primary` sans panic. + +La suite workspace complète n'est pas demandée pour ce delta sauf si les résultats ciblés révèlent un doute transversal. + +## Règle de transition + +Si toutes ces validations sont propres, passer automatiquement à `0.1.0-0-pre.6`. + +En cas d'échec, produire `0.1.0-0-pre.5.fix.1`. diff --git a/history/0.1.0/0-pre.4.fix.2.md b/history/0.1.0/0-pre.4.fix.2.md new file mode 100644 index 0000000..1e2cd17 --- /dev/null +++ b/history/0.1.0/0-pre.4.fix.2.md @@ -0,0 +1,18 @@ + + + +# Historique 0.1.0-0-pre.4.fix.2 + +## Statut + +Validé par l'utilisateur le 2026-09-16. + +## Gates validées + +- `cargo fmt --all -- --check` ; +- audits Python du workspace et des tableaux Markdown ; +- `cargo check --workspace`. + +## Portée durable + +Ce jalon valide la séparation des tests hors `src`, l'introduction du tracing commun et la politique d'historique documentaire immuable.