0.1.0-0-pre.11-fix.3

This commit is contained in:
2026-09-16 12:46:36 +02:00
parent fea73e898b
commit 69a268712c
5 changed files with 198 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 23 # version: 24
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -17,7 +17,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.1.0-0-pre.11.fix.2" version = "0.1.0-0-pre.11.fix.3"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games" repository = "https://git.sasedev.com/Sasedev/games"

View File

@@ -1,5 +1,5 @@
// file: crates/engines/engine-v1-sdl/src/runtime.rs // file: crates/engines/engine-v1-sdl/src/runtime.rs
// version: 8 // version: 9
/// Minimal SDL3 runtime used by Desktop and Android POC runners. /// Minimal SDL3 runtime used by Desktop and Android POC runners.
pub struct SdlRuntime { pub struct SdlRuntime {
@@ -39,6 +39,7 @@ impl SdlRuntime {
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), 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 runner = engine_v1_common::FixedStepRunner::new(self.frame_duration);
let mut gesture_start: Option<engine_v1_common::PointerState> = None;
let mut pointer = engine_v1_common::PointerState::inactive(); let mut pointer = engine_v1_common::PointerState::inactive();
tracing::info!(title = %self.title, width = self.width, height = self.height, "SDL3 runtime started"); tracing::info!(title = %self.title, width = self.width, height = self.height, "SDL3 runtime started");
'running: loop { 'running: loop {
@@ -61,11 +62,14 @@ impl SdlRuntime {
}, },
sdl3::event::Event::MouseButtonDown { mouse_btn: sdl3::mouse::MouseButton::Left, x, y, .. } => { sdl3::event::Event::MouseButtonDown { mouse_btn: sdl3::mouse::MouseButton::Left, x, y, .. } => {
pointer = normalize_mouse_pointer(&canvas, true, 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 = 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, .. } => { 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); input = input.with_pointer(pointer);
}, },
sdl3::event::Event::MouseMotion { x, y, .. } if pointer.active() => { sdl3::event::Event::MouseMotion { x, y, .. } if pointer.active() => {
@@ -74,14 +78,22 @@ impl SdlRuntime {
}, },
sdl3::event::Event::FingerDown { x, y, .. } => { sdl3::event::Event::FingerDown { x, y, .. } => {
pointer = engine_v1_common::PointerState::normalized(true, 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 = input.with_pointer(pointer).with_action(engine_v1_common::GameAction::Primary, true);
input = apply_pointer_direction(input, pointer);
}, },
sdl3::event::Event::FingerMotion { x, y, .. } => { sdl3::event::Event::FingerMotion { x, y, .. } => {
pointer = engine_v1_common::PointerState::normalized(true, x, y); pointer = engine_v1_common::PointerState::normalized(true, x, y);
input = input.with_pointer(pointer); 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); pointer = engine_v1_common::PointerState::normalized(false, x, y);
input = input.with_pointer(pointer); 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(()); return std::result::Result::Ok(());
} }
fn apply_pointer_direction(mut input: engine_v1_common::InputState, pointer: engine_v1_common::PointerState) -> engine_v1_common::InputState { const SWIPE_DIRECTION_THRESHOLD: f32 = 0.04;
let horizontal = pointer.x() - 0.5;
let vertical = pointer.y() - 0.5; fn apply_swipe_direction(
mut input: engine_v1_common::InputState,
start: Option<engine_v1_common::PointerState>,
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.abs() >= vertical.abs() {
if horizontal < 0.0 { if horizontal < 0.0 {
input = input.with_action(engine_v1_common::GameAction::Left, true); 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; return input;
} }
#[cfg(test)]
#[path = "../unit_tests/swipe.rs"]
mod swipe_tests;

View File

@@ -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));
}

View File

@@ -0,0 +1,117 @@
<!-- file: deltas/0.1.0/0-pre.11.fix.3.md -->
<!-- version: 1 -->
# 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`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-INPUT_AND_CONTROLS.md --> <!-- file: docs/architecture/004-INPUT_AND_CONTROLS.md -->
<!-- version: 4 --> <!-- version: 5 -->
# Abstraction des entrées et contrôles # 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`. 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.