diff --git a/Android/common/src/main/java/com/sasedev/games/common/NativeBridge.java b/Android/common/src/main/java/com/sasedev/games/common/NativeBridge.java index b4e46ef..09c4292 100644 --- a/Android/common/src/main/java/com/sasedev/games/common/NativeBridge.java +++ b/Android/common/src/main/java/com/sasedev/games/common/NativeBridge.java @@ -1,11 +1,11 @@ // file: Android/common/src/main/java/com/sasedev/games/common/NativeBridge.java -// version: 3 +// version: 4 package com.sasedev.games.common; /** Stable Java/JNI platform bridge contract shared by Android game applications. */ public final class NativeBridge { - private static final int CONTRACT_VERSION = 2; + private static final int CONTRACT_VERSION = 3; private NativeBridge() { } @@ -26,9 +26,6 @@ public final class NativeBridge { */ public static native int nativeContractVersion(); - /** Sends a platform-native Back request to the Rust SDL runtime. */ - private static native void nativeRequestPlatformBack(); - /** Verifies that the loaded native bridge matches the Java contract. */ public static void requireCompatibleContract() { final int nativeVersion = nativeContractVersion(); @@ -37,9 +34,4 @@ public final class NativeBridge { "Native bridge contract mismatch: java=" + CONTRACT_VERSION + ", native=" + nativeVersion); } } - - /** Requests Back through the common Rust engine quit policy. */ - public static void requestPlatformBack() { - nativeRequestPlatformBack(); - } } diff --git a/Android/common/src/main/java/com/sasedev/games/common/SaseGameActivity.java b/Android/common/src/main/java/com/sasedev/games/common/SaseGameActivity.java index 4f95414..be8cfea 100644 --- a/Android/common/src/main/java/com/sasedev/games/common/SaseGameActivity.java +++ b/Android/common/src/main/java/com/sasedev/games/common/SaseGameActivity.java @@ -1,10 +1,11 @@ // file: Android/common/src/main/java/com/sasedev/games/common/SaseGameActivity.java -// version: 5 +// version: 6 package com.sasedev.games.common; import android.os.Build; import android.os.Bundle; +import android.util.Log; /** * Common Android activity boundary for games.sasedev applications. @@ -12,24 +13,18 @@ import android.os.Bundle; *

The SDL3 Android AAR supplies the common {@code SDLActivity} host. Game-specific activities extend this class.

*/ public abstract class SaseGameActivity extends org.libsdl.app.SDLActivity { + private static final String LOG_TAG = "games.sasedev"; /** {@inheritDoc} */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); NativeBridge.requireCompatibleContract(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - Api33BackHandler.register(this); + if (Build.VERSION.SDK_INT >= 36) { + Api36BackObserver.register(this); } } - /** {@inheritDoc} */ - @SuppressWarnings("deprecation") - @Override - public void onBackPressed() { - NativeBridge.requestPlatformBack(); - } - /** {@inheritDoc} */ @Override protected final String[] getLibraries() { @@ -43,15 +38,15 @@ public abstract class SaseGameActivity extends org.libsdl.app.SDLActivity { */ public abstract String gameId(); - /** API 33+ predictive Back bridge isolated from pre-33 class loading. */ - private static final class Api33BackHandler { - private Api33BackHandler() { + /** API 36+ non-consuming Back observer isolated from older Android runtimes. */ + private static final class Api36BackObserver { + private Api36BackObserver() { } static void register(SaseGameActivity activity) { activity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( - android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT, - NativeBridge::requestPlatformBack); + android.window.OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER, + () -> Log.i(LOG_TAG, "Android system Back observed")); } } } diff --git a/Android/game-reflex-poc/build.gradle b/Android/game-reflex-poc/build.gradle index f94ad9d..7a9a969 100644 --- a/Android/game-reflex-poc/build.gradle +++ b/Android/game-reflex-poc/build.gradle @@ -1,5 +1,5 @@ // file: Android/game-reflex-poc/build.gradle -// version: 20 +// version: 21 plugins { id 'com.android.application' @@ -18,7 +18,7 @@ android { minSdk 21 targetSdk 36 versionCode 1 - versionName '0.1.0-2-beta.1.fix.3' + versionName '0.1.0-2-beta.1.fix.4' } compileOptions { diff --git a/Android/game-reflex-poc/src/main/AndroidManifest.xml b/Android/game-reflex-poc/src/main/AndroidManifest.xml index dc7b9d4..8c5c8dd 100644 --- a/Android/game-reflex-poc/src/main/AndroidManifest.xml +++ b/Android/game-reflex-poc/src/main/AndroidManifest.xml @@ -1,8 +1,9 @@ - + - + core::ffi::c_int { - return 2; -} - -#[unsafe(export_name = "Java_com_sasedev_games_common_NativeBridge_nativeRequestPlatformBack")] -extern "C" fn native_request_platform_back(_environment: *mut core::ffi::c_void, _class: *mut core::ffi::c_void) { - engine_v1_sdl::request_platform_back(); - return; + return 3; } #[cfg(feature = "reflex")] diff --git a/crates/apps/game-reflex-poc-tauri/package.json b/crates/apps/game-reflex-poc-tauri/package.json index 268b2fe..cf8be6a 100644 --- a/crates/apps/game-reflex-poc-tauri/package.json +++ b/crates/apps/game-reflex-poc-tauri/package.json @@ -1,7 +1,7 @@ { "name": "game-reflex-poc-tauri", "private": true, - "version": "0.1.0-2-beta.1.fix.3", + "version": "0.1.0-2-beta.1.fix.4", "type": "module", "scripts": { "dev": "vite", diff --git a/crates/apps/game-reflex-poc-tauri/tauri.conf.json b/crates/apps/game-reflex-poc-tauri/tauri.conf.json index acf7bd7..74b3d4b 100644 --- a/crates/apps/game-reflex-poc-tauri/tauri.conf.json +++ b/crates/apps/game-reflex-poc-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Reflex POC Tauri", - "version": "0.1.0-2-beta.1.fix.3", + "version": "0.1.0-2-beta.1.fix.4", "identifier": "com.sasedev.games.reflex.tauri", "build": { "beforeDevCommand": { diff --git a/crates/engines/engine-v1-sdl/src/lib.rs b/crates/engines/engine-v1-sdl/src/lib.rs index fd70f66..708da0c 100644 --- a/crates/engines/engine-v1-sdl/src/lib.rs +++ b/crates/engines/engine-v1-sdl/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/engines/engine-v1-sdl/src/lib.rs -// version: 4 +// version: 5 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -12,5 +12,3 @@ mod unit_tests; /// Re-export of the minimal SDL3 runtime used by Desktop POC runners. pub use self::runtime::SdlRuntime; -/// Re-export of the platform-native Back request bridge. -pub use self::runtime::request_platform_back; diff --git a/crates/engines/engine-v1-sdl/src/runtime.rs b/crates/engines/engine-v1-sdl/src/runtime.rs index cd05639..080f35b 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: 12 +// version: 13 /// Minimal SDL3 runtime used by Desktop and Android POC runners. pub struct SdlRuntime { @@ -9,17 +9,6 @@ pub struct SdlRuntime { frame_duration: std::time::Duration, } -static PLATFORM_BACK_REQUESTED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); - -/// Requests the active SDL runtime to evaluate a platform-native Back action. -/// -/// Platform adapters use this signal when the host operating system does not expose Back -/// as a normal SDL keyboard event. -pub fn request_platform_back() { - PLATFORM_BACK_REQUESTED.store(true, std::sync::atomic::Ordering::Release); - return; -} - impl SdlRuntime { /// Creates a minimal SDL3 runtime configuration. #[must_use] @@ -54,12 +43,6 @@ impl SdlRuntime { let mut pointer = engine_v1_common::PointerState::inactive(); tracing::info!(title = %self.title, width = self.width, height = self.height, "SDL3 runtime started"); 'running: loop { - if take_platform_back_request() { - tracing::info!(source = "platform_back", "SDL3 platform Back requested"); - if quit_requested(game, engine_v1_common::QuitSource::PlatformBack) { - break 'running; - } - } let mut input = engine_v1_common::InputState::none().with_pointer(pointer); for event in events.poll_iter() { match event { @@ -179,10 +162,6 @@ fn render_scene(canvas: &mut sdl3::render::WindowCanvas, scene: engine_v1_common const SWIPE_DIRECTION_THRESHOLD: f32 = 0.04; -fn take_platform_back_request() -> bool { - return PLATFORM_BACK_REQUESTED.swap(false, std::sync::atomic::Ordering::AcqRel); -} - fn quit_requested(game: &mut G, source: engine_v1_common::QuitSource) -> bool where G: engine_v1_common::EngineGame, diff --git a/crates/engines/engine-v1-sdl/unit_tests/swipe.rs b/crates/engines/engine-v1-sdl/unit_tests/swipe.rs index 639bf41..dc49fe5 100644 --- a/crates/engines/engine-v1-sdl/unit_tests/swipe.rs +++ b/crates/engines/engine-v1-sdl/unit_tests/swipe.rs @@ -1,5 +1,5 @@ // file: crates/engines/engine-v1-sdl/unit_tests/swipe.rs -// version: 4 +// version: 5 fn pointer(x: f32, y: f32) -> engine_v1_common::PointerState { return engine_v1_common::PointerState::normalized(true, x, y); @@ -48,10 +48,3 @@ fn runtime_respects_game_quit_decision() { assert!(!super::quit_requested(&mut game, engine_v1_common::QuitSource::PlatformBack)); assert_eq!(game.requests, 1); } - -#[test] -fn platform_back_bridge_is_consumed_once() { - super::request_platform_back(); - assert!(super::take_platform_back_request()); - assert!(!super::take_platform_back_request()); -} diff --git a/deltas/0.1.0/2-beta.1.fix.4.md b/deltas/0.1.0/2-beta.1.fix.4.md new file mode 100644 index 0000000..0158619 --- /dev/null +++ b/deltas/0.1.0/2-beta.1.fix.4.md @@ -0,0 +1,130 @@ + + + +# Delta 0.1.0-2-beta.1.fix.4 + +## Base + +Base déclarée : `0.1.0-2-beta.1.fix.3`. + +## Constat + +`fix.3` a validé le logging Android via `tracing-android`, mais son callback Back `PRIORITY_DEFAULT` est trop intrusif pour une launcher Activity racine : + +- il consomme la navigation système ; +- il désactive le chemin Back-to-home normal ; +- il ajoute un bridge JNI et une file atomique Rust inutiles pour ce cas. + +Le smoke AVD a confirmé que `adb shell input keyevent 4` déclenche correctement la navigation Back système, alors que le contrôle graphique Back de l'AVD peut ne rien injecter. + +## Correctif + +Le logging Android de `fix.3` est conservé. + +Le chemin suivant est supprimé : + +```text +OnBackInvokedCallback PRIORITY_DEFAULT +-> JNI nativeRequestPlatformBack() +-> engine-v1-sdl::request_platform_back() +-> QuitSource::PlatformBack +``` + +Pour API 36+, Android utilise uniquement : + +```text +OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER +``` + +L'observer écrit : + +```text +Android system Back observed +``` + +dans logcat, sans consommer l'action. + +La navigation Back-to-home reste entièrement gérée par Android. + +## Manifest + +Les deux launcher applications déclarent explicitement : + +```xml +android:enableOnBackInvokedCallback="true" +``` + +## JNI + +Le contrat Java/JNI passe à `3`. + +Il ne contient plus que le contrôle de version en plus de `SDL_main`. + +## SDL + +La prise en charge SDL native de : + +```text +Keycode::AcBack +Scancode::AcBack +``` + +reste présente. + +Elle continue à produire `QuitSource::PlatformBack` sur les plateformes qui exposent réellement Back comme événement SDL. + +## Validation Rust/statique + +```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 +python3 scripts/audit_distribution_layout.py + +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-logging-lib --all-targets --all-features +``` + +## AVD x86_64 + +```bash +python3 scripts/build_android_rust.py reflex --abi x86_64 +python3 scripts/build_android_rust.py snake --abi x86_64 + +cd Android +gradle :game-reflex-poc:assembleDebug +gradle :game-snake-poc:assembleDebug +cd .. + +adb -s emulator-5554 install -r Android/game-reflex-poc/build/outputs/apk/debug/game-reflex-poc-debug.apk +adb -s emulator-5554 install -r Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk +``` + +Pour Reflex : + +```bash +adb -s emulator-5554 logcat -c +adb -s emulator-5554 shell am start -n com.sasedev.games.reflex/.ReflexActivity +adb -s emulator-5554 shell input keyevent 4 +adb -s emulator-5554 logcat | grep -iE 'games\.sasedev|CoreBackPreview|SDL|AndroidRuntime|FATAL|panic' +``` + +Attendu sur API 36 : + +- `Android tracing initialized` ; +- `SDL3 runtime started` ; +- `Android system Back observed` ; +- retour système à l'écran précédent / accueil ; +- arrêt propre du runtime ou cycle de pause/stop conforme au comportement système ; +- aucun `FATAL`, `AndroidRuntime` ou panic. + +Répéter pour Snake. + +## ARM64 réel + +Après validation AVD, refaire un smoke court des deux jeux sur le Galaxy S9+ pour vérifier l'absence de régression sur l'ancien chemin système Android. diff --git a/docs/development/007-ANDROID_JNI_BRIDGE.md b/docs/development/007-ANDROID_JNI_BRIDGE.md index 7cecb06..5902cab 100644 --- a/docs/development/007-ANDROID_JNI_BRIDGE.md +++ b/docs/development/007-ANDROID_JNI_BRIDGE.md @@ -1,5 +1,5 @@ - + # Bridge Java/JNI Android @@ -14,7 +14,7 @@ L'input tactile de base ne passe pas par JNI : SDL3 fournit directement les év La baseline expose un unique appel Java vers Rust : ```text -NativeBridge.nativeContractVersion() -> 2 +NativeBridge.nativeContractVersion() -> 3 ``` `SaseGameActivity` vérifie ce contrat après `SDLActivity.onCreate()`. @@ -48,17 +48,14 @@ Si un futur service nécessite de manipuler réellement `JNIEnv`, il devra faire ## Back Android -Le contrat v2 ajoute un pont Java/JNI explicite pour Back : +Back n'est pas une commande JNI du jeu pour la launcher Activity racine. + +Sur Android 16 / API 36+, `SaseGameActivity` enregistre uniquement un observer : ```text -SaseGameActivity --> NativeBridge.requestPlatformBack() --> nativeRequestPlatformBack() --> engine-v1-sdl::request_platform_back() --> EngineGame::quit_requested(QuitSource::PlatformBack) +OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER ``` -Sur API 33+, `SaseGameActivity` enregistre un `OnBackInvokedCallback`. -Sur les versions antérieures, `onBackPressed()` reste le fallback. +Cet observer écrit un événement logcat mais ne consomme pas la navigation. Android conserve donc son comportement système, notamment le Back-to-home et son animation prédictive. -Cette voie remplace la dépendance au mapping variable du bouton Back vers `SDL_SCANCODE_AC_BACK`. +Le moteur SDL conserve parallèlement la prise en charge de `AcBack` lorsqu'une plateforme SDL produit réellement cet événement. diff --git a/docs/rules/RULES_PROJECT.md b/docs/rules/RULES_PROJECT.md index b7a957e..132ae65 100644 --- a/docs/rules/RULES_PROJECT.md +++ b/docs/rules/RULES_PROJECT.md @@ -1,5 +1,5 @@ - + # Règles spécifiques games.sasedev @@ -73,5 +73,5 @@ - **GAME-BUILD-001** — Les artefacts générés Cargo, Tauri, Vite et caches frontend sont placés hors de la racine du dépôt, sous `../builds/sasedev-games/`; un répertoire racine `builds/` dans le dépôt est interdit. -- **GAME-PLATFORM-014** — Android API 33+ traite Back via `OnBackInvokedCallback`; le bridge Java/JNI doit convertir cette action en `QuitSource::PlatformBack` et laisser `EngineGame::quit_requested` décider. +- **GAME-PLATFORM-014** — Pour une launcher Activity Android racine, Back reste une navigation système. Le projet ne doit pas enregistrer de callback consommant Back uniquement pour logger ou exécuter de la logique métier ; sur API 36+, un `PRIORITY_SYSTEM_NAVIGATION_OBSERVER` peut observer l'action sans bloquer le Back-to-home. - **GAME-PLATFORM-015** — Les exécutables Android initialisent le subscriber partagé et envoient les événements `tracing` vers logcat ; `stderr` n'est pas la destination Android de référence. diff --git a/docs/rules/RULES_RUST.md b/docs/rules/RULES_RUST.md index 4b077b5..602ca03 100644 --- a/docs/rules/RULES_RUST.md +++ b/docs/rules/RULES_RUST.md @@ -1,5 +1,5 @@ - + # Règles Rust générales @@ -8,7 +8,7 @@ - **RUST-BASE-001** — L'édition Rust est Rust 2024. - **RUST-BASE-002** — Chaque `lib.rs` et `main.rs` active `missing_docs`, `unreachable_pub` et interdit `unsafe_code`, sauf exception FFI explicitement définie par cette règle. - **RUST-BASE-003** — Les lints communs sont déclarés au workspace et hérités par les crates ; une crate FFI exceptée peut déclarer localement le même profil avec uniquement le niveau `unsafe_code` ajusté. -- **RUST-BASE-004** — Les blocs `unsafe` et fonctions `unsafe` sont interdits. L’unique exception actuelle est `crates/apps/game-android-entrypoint/src/lib.rs`, autorisé à porter uniquement les attributs `#[unsafe(export_name = "SDL_main")]` , `#[unsafe(export_name = "Java_com_sasedev_games_common_NativeBridge_nativeContractVersion")]` et `#[unsafe(export_name = "Java_com_sasedev_games_common_NativeBridge_nativeRequestPlatformBack")]` nécessaires aux frontières SDL Android et JNI. Cette exception n’autorise aucun déréférencement de pointeur brut, aucun autre attribut unsafe, aucun bloc `unsafe` et aucune fonction `unsafe`. +- **RUST-BASE-004** — Les blocs `unsafe` et fonctions `unsafe` sont interdits. L’unique exception actuelle est `crates/apps/game-android-entrypoint/src/lib.rs`, autorisé à porter uniquement les attributs `#[unsafe(export_name = "SDL_main")]` et `#[unsafe(export_name = "Java_com_sasedev_games_common_NativeBridge_nativeContractVersion")]` nécessaires aux frontières SDL Android et JNI. Cette exception n’autorise aucun déréférencement de pointeur brut, aucun autre attribut unsafe, aucun bloc `unsafe` et aucune fonction `unsafe`. - **RUST-BASE-005** — Tout fichier Rust possède les en-têtes `// file: ...` et `// version: N`. - **RUST-DEP-001** — Une dépendance tierce partagée déclare uniquement sa contrainte de version canonique sous `[workspace.dependencies]`, sauf exception normative explicitement documentée. - **RUST-DEP-002** — Les features d'une dépendance tierce sont activées dans le `Cargo.toml` de la crate qui en a réellement besoin, via `workspace = true`; elles ne sont pas activées globalement au workspace par commodité. diff --git a/docs/testing/002-BETA_VALIDATION_MATRIX.md b/docs/testing/002-BETA_VALIDATION_MATRIX.md index cce5092..86a1a9e 100644 --- a/docs/testing/002-BETA_VALIDATION_MATRIX.md +++ b/docs/testing/002-BETA_VALIDATION_MATRIX.md @@ -1,5 +1,5 @@ - + # Matrice de validation beta 0.1.0 @@ -143,6 +143,13 @@ Android utilise un subscriber `tracing` dédié à logcat. ## Back Android 16 -Les applications ciblent API 36. Le Back système est donc traité via `OnBackInvokedCallback` sur API 33+ puis converti en `QuitSource::PlatformBack`. +Les applications ciblent API 36 et laissent Android exécuter la navigation Back système. -Le fallback `onBackPressed()` reste réservé aux versions Android antérieures. +Pour la launcher Activity racine : + +- aucun callback `PRIORITY_DEFAULT` ne consomme Back ; +- `android:enableOnBackInvokedCallback="true"` reste explicitement activé ; +- sur API 36+, `PRIORITY_SYSTEM_NAVIGATION_OBSERVER` trace l'action sans bloquer le Back-to-home ; +- le bouton Back injecté par `adb shell input keyevent 4` constitue un smoke système valide si le contrôle graphique de l'AVD ne relaie pas correctement Back. + +Le moteur SDL conserve `AcBack` pour les plateformes qui émettent cet événement. diff --git a/scripts/audit_rust_general_rules.py b/scripts/audit_rust_general_rules.py index 2d81146..de88080 100755 --- a/scripts/audit_rust_general_rules.py +++ b/scripts/audit_rust_general_rules.py @@ -522,7 +522,6 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]: if ffi_export_exception: required_export_attributes = ( '#[unsafe(export_name = "Java_com_sasedev_games_common_NativeBridge_nativeContractVersion")]', - '#[unsafe(export_name = "Java_com_sasedev_games_common_NativeBridge_nativeRequestPlatformBack")]', '#[unsafe(export_name = "SDL_main")]', ) for export_attribute in required_export_attributes: