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: