From 69a268712c6d732682972e47435a9dcf5eb3460d Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Wed, 16 Sep 2026 12:46:36 +0200 Subject: [PATCH] 0.1.0-0-pre.11-fix.3 --- Cargo.toml | 4 +- crates/engines/engine-v1-sdl/src/runtime.rs | 46 +++++-- .../engines/engine-v1-sdl/unit_tests/swipe.rs | 27 ++++ deltas/0.1.0/0-pre.11.fix.3.md | 117 ++++++++++++++++++ docs/architecture/004-INPUT_AND_CONTROLS.md | 16 ++- 5 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 crates/engines/engine-v1-sdl/unit_tests/swipe.rs create mode 100644 deltas/0.1.0/0-pre.11.fix.3.md diff --git a/Cargo.toml b/Cargo.toml index b33ccf9..7cac5d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 23 +# version: 24 [workspace] resolver = "3" @@ -17,7 +17,7 @@ members = [ ] [workspace.package] -version = "0.1.0-0-pre.11.fix.2" +version = "0.1.0-0-pre.11.fix.3" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/games" diff --git a/crates/engines/engine-v1-sdl/src/runtime.rs b/crates/engines/engine-v1-sdl/src/runtime.rs index 5c3131e..9e0cbec 100644 --- a/crates/engines/engine-v1-sdl/src/runtime.rs +++ b/crates/engines/engine-v1-sdl/src/runtime.rs @@ -1,5 +1,5 @@ // file: crates/engines/engine-v1-sdl/src/runtime.rs -// version: 8 +// version: 9 /// Minimal SDL3 runtime used by Desktop and Android POC runners. pub struct SdlRuntime { @@ -39,6 +39,7 @@ impl SdlRuntime { std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), }; let mut runner = engine_v1_common::FixedStepRunner::new(self.frame_duration); + let mut gesture_start: Option = None; let mut pointer = engine_v1_common::PointerState::inactive(); tracing::info!(title = %self.title, width = self.width, height = self.height, "SDL3 runtime started"); 'running: loop { @@ -61,11 +62,14 @@ impl SdlRuntime { }, sdl3::event::Event::MouseButtonDown { mouse_btn: sdl3::mouse::MouseButton::Left, x, y, .. } => { pointer = normalize_mouse_pointer(&canvas, true, x, y); + gesture_start = Some(pointer); input = input.with_pointer(pointer).with_action(engine_v1_common::GameAction::Primary, true); - input = apply_pointer_direction(input, pointer); }, sdl3::event::Event::MouseButtonUp { mouse_btn: sdl3::mouse::MouseButton::Left, x, y, .. } => { - pointer = normalize_mouse_pointer(&canvas, false, x, y); + let released = normalize_mouse_pointer(&canvas, false, x, y); + input = apply_swipe_direction(input, gesture_start, released); + gesture_start = None; + pointer = released; input = input.with_pointer(pointer); }, sdl3::event::Event::MouseMotion { x, y, .. } if pointer.active() => { @@ -74,14 +78,22 @@ impl SdlRuntime { }, sdl3::event::Event::FingerDown { x, y, .. } => { pointer = engine_v1_common::PointerState::normalized(true, x, y); + gesture_start = Some(pointer); input = input.with_pointer(pointer).with_action(engine_v1_common::GameAction::Primary, true); - input = apply_pointer_direction(input, pointer); }, sdl3::event::Event::FingerMotion { x, y, .. } => { pointer = engine_v1_common::PointerState::normalized(true, x, y); input = input.with_pointer(pointer); }, - sdl3::event::Event::FingerUp { x, y, .. } | sdl3::event::Event::FingerCanceled { x, y, .. } => { + sdl3::event::Event::FingerUp { x, y, .. } => { + let released = engine_v1_common::PointerState::normalized(false, x, y); + input = apply_swipe_direction(input, gesture_start, released); + gesture_start = None; + pointer = released; + input = input.with_pointer(pointer); + }, + sdl3::event::Event::FingerCanceled { x, y, .. } => { + gesture_start = None; pointer = engine_v1_common::PointerState::normalized(false, x, y); input = input.with_pointer(pointer); }, @@ -134,9 +146,23 @@ fn render_scene(canvas: &mut sdl3::render::WindowCanvas, scene: engine_v1_common return std::result::Result::Ok(()); } -fn apply_pointer_direction(mut input: engine_v1_common::InputState, pointer: engine_v1_common::PointerState) -> engine_v1_common::InputState { - let horizontal = pointer.x() - 0.5; - let vertical = pointer.y() - 0.5; +const SWIPE_DIRECTION_THRESHOLD: f32 = 0.04; + +fn apply_swipe_direction( + mut input: engine_v1_common::InputState, + start: Option, + end: engine_v1_common::PointerState, +) -> engine_v1_common::InputState { + let start = match start { + Some(value) => value, + None => return input, + }; + let horizontal = end.x() - start.x(); + let vertical = end.y() - start.y(); + let dominant = horizontal.abs().max(vertical.abs()); + if dominant < SWIPE_DIRECTION_THRESHOLD { + return input; + } if horizontal.abs() >= vertical.abs() { if horizontal < 0.0 { input = input.with_action(engine_v1_common::GameAction::Left, true); @@ -150,3 +176,7 @@ fn apply_pointer_direction(mut input: engine_v1_common::InputState, pointer: eng } return input; } + +#[cfg(test)] +#[path = "../unit_tests/swipe.rs"] +mod swipe_tests; diff --git a/crates/engines/engine-v1-sdl/unit_tests/swipe.rs b/crates/engines/engine-v1-sdl/unit_tests/swipe.rs new file mode 100644 index 0000000..fca972e --- /dev/null +++ b/crates/engines/engine-v1-sdl/unit_tests/swipe.rs @@ -0,0 +1,27 @@ +// file: crates/engines/engine-v1-sdl/unit_tests/swipe.rs +// version: 1 + +fn pointer(x: f32, y: f32) -> engine_v1_common::PointerState { + return engine_v1_common::PointerState::normalized(true, x, y); +} + +#[test] +fn short_gesture_does_not_emit_direction() { + let input = super::apply_swipe_direction(engine_v1_common::InputState::none(), Some(pointer(0.5, 0.5)), pointer(0.52, 0.51)); + assert!(!input.is_active(engine_v1_common::GameAction::Left)); + assert!(!input.is_active(engine_v1_common::GameAction::Right)); + assert!(!input.is_active(engine_v1_common::GameAction::Up)); + assert!(!input.is_active(engine_v1_common::GameAction::Down)); +} + +#[test] +fn horizontal_swipe_uses_dominant_axis() { + let input = super::apply_swipe_direction(engine_v1_common::InputState::none(), Some(pointer(0.7, 0.5)), pointer(0.2, 0.55)); + assert!(input.is_active(engine_v1_common::GameAction::Left)); +} + +#[test] +fn vertical_swipe_uses_dominant_axis() { + let input = super::apply_swipe_direction(engine_v1_common::InputState::none(), Some(pointer(0.5, 0.8)), pointer(0.55, 0.2)); + assert!(input.is_active(engine_v1_common::GameAction::Up)); +} diff --git a/deltas/0.1.0/0-pre.11.fix.3.md b/deltas/0.1.0/0-pre.11.fix.3.md new file mode 100644 index 0000000..8b981bb --- /dev/null +++ b/deltas/0.1.0/0-pre.11.fix.3.md @@ -0,0 +1,117 @@ + + + +# Delta 0.1.0-0-pre.11.fix.3 + +## Base + +Base déclarée : `0.1.0-0-pre.11.fix.2`, techniquement propre mais contrôle tactile Snake jugé insuffisant lors du smoke Android. + +## Problème observé + +Le mapping directionnel basé sur la position absolue du tap par rapport au centre est fonctionnel mais peu naturel pour Snake. + +Un tap peut facilement produire une direction involontaire et le joueur doit viser une zone plutôt qu'exprimer directement la direction souhaitée. + +## Correctif + +Snake utilise désormais un geste de swipe reconnu dans `engine-v1-sdl`. + +### Début + +`MouseButtonDown` ou `FingerDown` mémorise l'origine du geste. + +L'impulsion `Primary` reste émise sur `Down`, afin que Reflex conserve son comportement. + +### Mouvement + +`MouseMotion` et `FingerMotion` mettent à jour `PointerState` sans émettre de direction. + +### Fin + +`MouseButtonUp` ou `FingerUp` calcule le déplacement normalisé depuis l'origine. + +La direction est produite uniquement si l'amplitude dominante atteint : + +```text +0.04 +``` + +L'axe dominant sélectionne : + +```text +Left +Right +Up +Down +``` + +Un tap ou micro-mouvement ne change donc pas la direction. + +`FingerCanceled` annule le geste sans produire d'action. + +## Tests + +Trois tests ciblés vérifient : + +- qu'un geste trop court n'émet aucune direction ; +- qu'un swipe horizontal choisit l'axe horizontal ; +- qu'un swipe vertical choisit l'axe vertical. + +## Validation + +```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 + +cargo test -p engine-v1-sdl --all-targets --all-features +cargo test -p game-snake-poc --all-targets --all-features +``` + +Smoke Desktop : + +```bash +cargo run -p game-snake-poc-desktop +``` + +Les flèches restent disponibles. Un drag souris suffisamment long peut également changer la direction. + +Android x86_64 : + +```bash +python3 scripts/build_android_rust.py snake --abi x86_64 + +cd Android +gradle :game-snake-poc:assembleDebug +cd .. +``` + +Puis : + +```bash +android emulator start sasedev_games_api36 +adb devices + +android run \ + --apks=Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk \ + --device=emulator-5554 +``` + +Critères : + +- swipe gauche/droite/haut/bas naturel ; +- tap simple sans changement de direction ; +- demi-tour immédiat toujours refusé par le gameplay ; +- aucun impact sur Reflex `Primary`. + +## Transition + +Si les gates et le smoke Android passent, `0.1.0-0-pre.11.fix.3` valide la fin de la phase `0-pre.*`. + +En cas d'échec imputable au projet, produire `0.1.0-0-pre.11.fix.4`. diff --git a/docs/architecture/004-INPUT_AND_CONTROLS.md b/docs/architecture/004-INPUT_AND_CONTROLS.md index af0fc0e..7d31508 100644 --- a/docs/architecture/004-INPUT_AND_CONTROLS.md +++ b/docs/architecture/004-INPUT_AND_CONTROLS.md @@ -1,5 +1,5 @@ - + # Abstraction des entrées et contrôles @@ -83,4 +83,16 @@ Cette séparation empêche un contact maintenu de produire un succès par frame. Les flèches clavier produisent `Left`, `Right`, `Up` et `Down`. -Lors d'un clic/tap, la position normalisée produit également une direction selon l'axe dominant depuis le centre. Reflex ignore ces directions ; Snake les consomme. Le backend SDL3 reste ainsi commun aux jeux. +Snake utilise désormais un geste directionnel et non la position absolue du tap. + +Pour souris et tactile : + +- `Down` mémorise l'origine normalisée ; +- les mouvements mettent à jour le pointeur sans produire de direction ; +- `Up` compare l'origine et la position finale ; +- le déplacement doit dépasser un seuil normalisé minimal ; +- l'axe dominant détermine `Left`, `Right`, `Up` ou `Down` ; +- un simple tap ou un micro-mouvement ne produit aucune direction ; +- `Canceled` annule le geste. + +Reflex conserve son impulsion `Primary` sur `Down` et reste donc indépendant de la reconnaissance du swipe.