19 Commits

Author SHA1 Message Date
0ad980a06b 0.3.3 2026-09-21 16:34:14 +02:00
0584b01de3 0.3.3-3-rc.1.fix.1 2026-09-21 16:11:46 +02:00
01df07d6af 0.3.3-3-rc.1 2026-09-21 14:35:57 +02:00
58ecd667b0 0.3.3-2-beta.1 2026-09-21 14:10:30 +02:00
19f31082ea 0.3.3-0-pre.5 2026-09-21 11:17:38 +02:00
b605874bea 0.3.3-0-pre.4 2026-09-21 10:47:57 +02:00
5854ac3d07 0.3.3-0-pre.3 2026-09-21 10:32:31 +02:00
f770f99a4a 0.3.3-0-pre.1 2026-09-21 08:39:57 +02:00
76a955bd3d 0.3.1 2026-09-21 08:22:30 +02:00
e5a4d7fe44 0.3.1-2-beta.1 2026-09-21 08:11:36 +02:00
d8e7951a04 0.3.1-2-beta.1 2026-09-21 07:47:08 +02:00
c806490212 0.3.1-0-pre.4.fix.1 2026-09-21 07:22:50 +02:00
a36b666d22 0.3.1-0-pre.4 2026-09-21 01:21:33 +02:00
4f677dbc77 0.3.1-0-pre.3 2026-09-21 01:13:37 +02:00
e4b657d0d3 0.3.1-0-pre.2.fix.3 2026-09-20 23:23:14 +02:00
1835464bb3 0.3.1-0-pre.2.fix.2 2026-09-20 21:49:23 +02:00
bded27c944 0.3.1-0-pre.2.fix.1 2026-09-20 21:38:21 +02:00
51371c1b24 0.3.1-0-pre.2 2026-09-20 21:30:12 +02:00
ba718ce4ec 0.3.1-0-pre.1 2026-09-20 18:16:32 +02:00
97 changed files with 7678 additions and 309 deletions

View File

@@ -1,9 +1,9 @@
<!-- file: Android/README.md --> <!-- file: Android/README.md -->
<!-- version: 8 --> <!-- version: 14 -->
# Android # Android
Le frontend Android est un projet Gradle multi-module séparé du workspace Cargo. Le frontend Android natif est un projet Gradle multi-module séparé du workspace Cargo.
## Modules ## Modules
@@ -20,23 +20,47 @@ Chaque module `game-*` est une application Android indépendante ; il dépend du
## Toolchain de référence ## Toolchain de référence
Pour la baseline `0.1.0-0-pre.9` : Le pipeline Android natif `0.3.3` utilise :
- Android Gradle Plugin `9.4.0` ; - Android Gradle Plugin `9.4.0` ;
- Gradle `9.6.0` attendu par AGP 9.4 ; - Gradle `>= 9.6.0`, résolu depuis l'environnement de développement ;
- JDK `17` ; - JDK `>= 17` pour exécuter Gradle/AGP, sans `JAVA_HOME` imposé par le projet ;
- niveau Java source/target `17` pour la glue Android ;
- `compileSdk 36` ; - `compileSdk 36` ;
- `targetSdk 36` ; - `targetSdk 36` ;
- `minSdk 21` ; - `minSdk 21` ;
- NDK `28.2.13676358` (`r28c`) ;
- SDL3 Android AAR `3.4.16`. - SDL3 Android AAR `3.4.16`.
Le NDK n'est pas encore utilisé directement dans ce delta. Le prochain jalon introduira la compilation Rust Android `cdylib` et figera la configuration NDK réellement nécessaire. `Android/settings.gradle` déclare `minimumGradleVersion = 9.6.0` et refuse explicitement une version plus ancienne. Une version système plus récente reste autorisée et devient la version réellement utilisée pour le build.
Le JDK n'est pas épinglé par le projet Android natif. Le shell/IDE fournit le JDK courant ; la gate doit afficher `java -version` et `gradle --version` afin de consigner la combinaison réellement validée. Le POC Tauri Android historique conserve ses propres contraintes de JDK et de wrapper sous sa crate ; elles ne pilotent pas le chemin SDL3 natif.
## Commandes Gradle
Depuis la racine du dépôt :
```bash
java -version
(cd Android && gradle --version)
```
Le build natif normal utilise directement le Gradle de l'environnement :
```bash
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
(cd Android && gradle :game-snake-poc:bundleRelease)
(cd Android && gradle :game-reflex-poc:bundleRelease)
```
`bundleRelease` ne requiert aucun secret dans le dépôt. Sans configuration de signature externe, il produit un AAB de validation non destiné à être téléversé tel quel sur un store.
Le dépôt ne versionne pas de Gradle Wrapper pour le projet Android natif. Le minimum est un contrat de compatibilité, pas une version exacte imposée à toutes les machines.
## SDL3 AAR ## SDL3 AAR
L'archive n'est pas vendorizée dans le dépôt. L'archive n'est pas vendorizée dans le dépôt. Placer :
Placer :
```text ```text
SDL3-3.4.16.aar SDL3-3.4.16.aar
@@ -48,32 +72,48 @@ dans :
Android/libs/ Android/libs/
``` ```
Le module `common` consomme cet artefact et `SaseGameActivity` étend désormais `org.libsdl.app.SDLActivity`. Le module `common` compile contre cet artefact et `SaseGameActivity` étend `org.libsdl.app.SDLActivity`.
## Limite volontaire de ce jalon
Les APK peuvent être compilés côté Java/SDL3 une fois l'AAR disponible, mais ne disposent pas encore du `cdylib` Rust contenant le point d'entrée de jeu.
Le lancement fonctionnel sur appareil/émulateur appartient donc au delta suivant.
## Rust Android natif ## Rust Android natif
Le crate `game-android-entrypoint` produit `libgame_android_entrypoint.so`. Il est compilé séparément pour chaque jeu avec une feature Cargo et copié dans le `jniLibs` du module Android concerné. Le crate `game-android-entrypoint` produit `libgame_android_entrypoint.so`. `Android/gradle/sasedev-rust-android.gradle` transfère au graphe Gradle les responsabilités historiques d'extraction SDL3, de résolution NDK, de `cargo ndk`, de sélection de feature et de staging `jniLibs`.
Pour `arm64-v8a` : À `0.3.3-0-pre.5`, Snake et Reflex utilisent tous deux le même pipeline commun avec les quatre ABI Android encore supportées par la toolchain retenue :
```bash ```text
rustup target add aarch64-linux-android arm64-v8a
cargo install cargo-ndk armeabi-v7a
python3 ../scripts/build_android_rust.py reflex x86_64
python3 ../scripts/build_android_rust.py snake x86
``` ```
Le script extrait temporairement `libSDL3.so` de l'AAR uniquement pour fournir le chemin de linkage à `rustc`; l'AAR reste responsable du packaging SDL3 dans l'APK. Les ABI historiques supprimées des toolchains Android modernes, notamment `armeabi`, `mips` et `mips64`, ne font pas partie du contrat. Le support maximal signifie ici toutes les ABI encore supportées simultanément par Android NDK/SDL3/Rust, et non des architectures abandonnées par l'écosystème.
Chaque variante Android déclenche, pour chaque ABI, un build `cargo ndk` avec exactement la feature du module concerné. La variante Debug utilise le profil Cargo `dev`; la variante Release ajoute `--release`. Les outputs sont placés sous :
```text
Android/<game>/build/generated/sasedevNative/<variant>/<abi>/jniLibs/<abi>/
├── libSDL3.so
└── libgame_android_entrypoint.so
```
AGP fusionne ces sources `jniLibs` générées dans l'APK Debug universal ou dans l'AAB Release. Aucune copie durable n'est faite dans `src/main/jniLibs`.
Le builder historique `scripts/build_android_rust.py` est supprimé en `0-pre.5` : toutes ses responsabilités durables (résolution NDK, extraction SDL3, linkage, sélection de feature, `cargo ndk`, profil release et staging) appartiennent désormais au graphe Gradle.
## Compatibilité 16 KB et minSdk
Le plancher Android reste `minSdk 21` / Android 5.0, qui correspond au minimum Android documenté par SDL3. Le niveau Rust transmis à `cargo ndk` reste également `21`.
La baseline utilise AGP `9.4.0` et NDK `r28c`. Pour la contrainte Google Play 16 KB sur appareils 64 bits, les bibliothèques `arm64-v8a` et `x86_64` doivent être compatibles 16 KB ; SDL3 étant fourni sous forme précompilée, `libSDL3.so` est vérifiée au même titre que `libgame_android_entrypoint.so`. La gate `0-pre.5` a confirmé des segments ELF `LOAD` alignés au moins à `2**14` pour ces deux ABI, puis un `zipalign -P 16` réussi sur les APK.
Les ABI 32 bits `armeabi-v7a` et `x86` restent supportées pour compatibilité matérielle ancienne ; leurs segments observés à `2**12` ne contredisent pas l'exigence Play 16 KB, qui s'applique aux appareils 64 bits. L'AAB doit néanmoins contenir les deux bibliothèques pour les quatre ABI sous `base/lib/<abi>/`. Si `bundletool` est disponible dans l'environnement de validation, `bundletool dump config --bundle=<aab>` peut compléter le contrôle du bundle.
La preuve runtime `0-pre.5` couvre également Android 5.0/API 21 sur x86 et Android 15/API 35 x86_64 avec `getconf PAGE_SIZE=16384` sur une image `ps16k`.
## Bridge JNI et tactile ## Bridge JNI et tactile
À partir de `0.1.0-0-pre.9`, `SaseGameActivity` vérifie au démarrage la version du bridge Java/JNI chargé depuis la bibliothèque Rust. `SaseGameActivity` vérifie au démarrage la version du bridge Java/JNI chargé depuis la bibliothèque Rust.
Les touch events standards restent traités via SDL3 et ne transitent pas par JNI. Les touch events standards restent traités via SDL3 et ne transitent pas par JNI.
@@ -96,6 +136,6 @@ Aucune ressource source n'est copiée durablement dans un module Android ou une
`Android/gradle.properties` définit `androidNdkVersion=28.2.13676358`. `Android/gradle.properties` définit `androidNdkVersion=28.2.13676358`.
Gradle utilise cette valeur comme `ndkVersion`. Le script Rust Android résout la même version sous `${ANDROID_HOME}/ndk/` et définit `ANDROID_NDK_HOME` uniquement pour le sous-processus `cargo ndk`. Gradle utilise cette valeur comme `ndkVersion`. La tâche Rust Android résout la même version sous `${ANDROID_HOME}/ndk/` ou `${ANDROID_SDK_ROOT}/ndk/` et définit `ANDROID_NDK_HOME` uniquement pour le sous-processus `cargo ndk`.
Aucun `ANDROID_NDK_HOME` global n'est requis ; plusieurs NDK peuvent rester installés côte à côte. Aucun `ANDROID_NDK_HOME` global n'est requis ; plusieurs NDK peuvent rester installés côte à côte.

View File

@@ -1,10 +1,14 @@
// file: Android/game-reflex-poc/build.gradle // file: Android/game-reflex-poc/build.gradle
// version: 45 // version: 47
plugins { plugins {
id 'com.android.application' id 'com.android.application'
} }
ext.sasedevRustGameFeature = "reflex"
ext.sasedevRustAndroidAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
ext.sasedevRustAndroidApi = 21
def sdl3AarName = providers.gradleProperty("sdl3AarName").get() def sdl3AarName = providers.gradleProperty("sdl3AarName").get()
def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get() def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
@@ -19,6 +23,10 @@ android {
targetSdk 36 targetSdk 36
versionCode 2 versionCode 2
versionName '0.2.0' versionName '0.2.0'
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'
}
} }
compileOptions { compileOptions {
@@ -34,3 +42,4 @@ dependencies {
ext.sasedevGameAssetDirectory = "game-reflex-poc" ext.sasedevGameAssetDirectory = "game-reflex-poc"
apply from: rootProject.file("gradle/sasedev-assets.gradle") apply from: rootProject.file("gradle/sasedev-assets.gradle")
apply from: rootProject.file("gradle/sasedev-rust-android.gradle")

View File

@@ -1,10 +1,14 @@
// file: Android/game-snake-poc/build.gradle // file: Android/game-snake-poc/build.gradle
// version: 45 // version: 48
plugins { plugins {
id 'com.android.application' id 'com.android.application'
} }
ext.sasedevRustGameFeature = "snake"
ext.sasedevRustAndroidAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
ext.sasedevRustAndroidApi = 21
def sdl3AarName = providers.gradleProperty("sdl3AarName").get() def sdl3AarName = providers.gradleProperty("sdl3AarName").get()
def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get() def androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
@@ -19,6 +23,10 @@ android {
targetSdk 36 targetSdk 36
versionCode 2 versionCode 2
versionName '0.2.0' versionName '0.2.0'
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'
}
} }
compileOptions { compileOptions {
@@ -34,3 +42,4 @@ dependencies {
ext.sasedevGameAssetDirectory = "game-snake-poc" ext.sasedevGameAssetDirectory = "game-snake-poc"
apply from: rootProject.file("gradle/sasedev-assets.gradle") apply from: rootProject.file("gradle/sasedev-assets.gradle")
apply from: rootProject.file("gradle/sasedev-rust-android.gradle")

View File

@@ -0,0 +1,204 @@
// file: Android/gradle/sasedev-rust-android.gradle
// version: 2
import java.util.Collections
import java.util.zip.ZipFile
import javax.inject.Inject
import org.gradle.process.ExecOperations
abstract class BuildSasedevRustAndroidTask extends DefaultTask {
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getSdl3Aar()
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
abstract ConfigurableFileCollection getRustInputs()
@Input
abstract Property<String> getGameFeature()
@Input
abstract Property<String> getAbi()
@Input
abstract Property<Integer> getAndroidApi()
@Input
abstract Property<String> getNdkVersion()
@Input
abstract Property<Boolean> getReleaseBuild()
@Internal
abstract DirectoryProperty getRepositoryRoot()
@OutputDirectory
abstract DirectoryProperty getOutputDirectory()
@Inject
abstract ExecOperations getExecOperations()
@TaskAction
void buildNative() {
def selectedAbi = abi.get()
def selectedFeature = gameFeature.get()
def aar = sdl3Aar.get().asFile
def repository = repositoryRoot.get().asFile
def output = outputDirectory.get().asFile
def runtimeDirectory = new File(output, selectedAbi)
def linkDirectory = new File(temporaryDir, "link")
project.delete(output)
project.delete(linkDirectory)
runtimeDirectory.mkdirs()
linkDirectory.mkdirs()
byte[] sdl3Payload
new ZipFile(aar).withCloseable { archive ->
def candidates = [
"prefab/modules/SDL3/libs/android.${selectedAbi}/libSDL3.so",
"jni/${selectedAbi}/libSDL3.so",
]
def entry = candidates.collect { candidate -> archive.getEntry(candidate) }.find { candidate -> candidate != null }
if (entry == null) {
def suffix = "/libs/android.${selectedAbi}/libSDL3.so"
def fallback = Collections.list(archive.entries()).findAll { candidate ->
!candidate.directory && candidate.name.endsWith(suffix)
}
if (fallback.size() != 1) {
throw new GradleException(
"SDL3 shared library for ABI ${selectedAbi} is missing from ${aar}; " +
"expected Prefab member ${candidates[0]}"
)
}
entry = fallback[0]
}
sdl3Payload = archive.getInputStream(entry).withCloseable { stream -> stream.bytes }
}
def linkLibrary = new File(linkDirectory, "libSDL3.so")
def runtimeLibrary = new File(runtimeDirectory, "libSDL3.so")
linkLibrary.bytes = sdl3Payload
runtimeLibrary.bytes = sdl3Payload
def androidHome = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
if (!androidHome) {
throw new GradleException("ANDROID_HOME or ANDROID_SDK_ROOT is required to resolve the project NDK")
}
def configuredNdk = new File(androidHome, "ndk/${ndkVersion.get()}")
if (!configuredNdk.isDirectory()) {
throw new GradleException("configured Android NDK is missing: ${configuredNdk}")
}
def inheritedRustFlags = System.getenv("RUSTFLAGS")?.trim()
def linkFlag = "-Lnative=${linkDirectory}"
def rustFlags = inheritedRustFlags ? "${inheritedRustFlags} ${linkFlag}" : linkFlag
def cargoCommand = [
"cargo",
"ndk",
"-t",
selectedAbi,
"-o",
output.absolutePath,
"build",
"-p",
"game-android-entrypoint",
]
if (releaseBuild.get()) {
cargoCommand.add("--release")
}
cargoCommand.addAll([
"--no-default-features",
"--features",
selectedFeature,
])
execOperations.exec { spec ->
spec.workingDir(repository)
spec.environment("ANDROID_NDK_HOME", configuredNdk.absolutePath)
spec.environment("CARGO_NDK_PLATFORM", androidApi.get().toString())
spec.environment("RUSTFLAGS", rustFlags)
spec.commandLine(cargoCommand)
}
def rustLibrary = new File(runtimeDirectory, "libgame_android_entrypoint.so")
if (!runtimeLibrary.isFile()) {
throw new GradleException("missing staged SDL3 Android library: ${runtimeLibrary}")
}
if (!rustLibrary.isFile()) {
throw new GradleException("missing Rust Android library after cargo-ndk build: ${rustLibrary}")
}
}
}
if (!project.ext.has("sasedevRustGameFeature")) {
throw new GradleException("sasedevRustGameFeature must select exactly one Cargo game feature")
}
if (!project.ext.has("sasedevRustAndroidAbis")) {
throw new GradleException("sasedevRustAndroidAbis must declare at least one Android ABI")
}
if (!project.ext.has("sasedevRustAndroidApi")) {
throw new GradleException("sasedevRustAndroidApi must declare the Rust Android API level")
}
def selectedGameFeature = project.ext.sasedevRustGameFeature as String
def selectedAbis = (project.ext.sasedevRustAndroidAbis as List).collect { configuredAbi -> configuredAbi as String }
def selectedAndroidApi = project.ext.sasedevRustAndroidApi as Integer
def configuredSdl3AarName = providers.gradleProperty("sdl3AarName").get()
def configuredNdkVersion = providers.gradleProperty("androidNdkVersion").get()
def repositoryDirectory = rootProject.projectDir.parentFile
def supportedAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"] as Set
if (selectedAbis.isEmpty()) {
throw new GradleException("sasedevRustAndroidAbis must not be empty")
}
if (selectedAbis.toSet().size() != selectedAbis.size()) {
throw new GradleException("sasedevRustAndroidAbis must not contain duplicates: ${selectedAbis}")
}
def unsupportedAbis = selectedAbis.findAll { configuredAbi -> !supportedAbis.contains(configuredAbi) }
if (!unsupportedAbis.isEmpty()) {
throw new GradleException("unsupported Rust Android ABI(s): ${unsupportedAbis.join(', ')}")
}
androidComponents {
onVariants(selector().all()) { variant ->
def selectedReleaseBuild = variant.buildType == "release"
selectedAbis.each { selectedAbi ->
def variantSuffix = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1)
def abiSuffix = selectedAbi
.split(/[^A-Za-z0-9]+/)
.findAll { segment -> !segment.isEmpty() }
.collect { segment -> segment.substring(0, 1).toUpperCase() + segment.substring(1) }
.join("")
def nativeTask = tasks.register(
"build${variantSuffix}SasedevRust${abiSuffix}",
BuildSasedevRustAndroidTask,
) {
sdl3Aar.set(rootProject.layout.projectDirectory.file("libs/${configuredSdl3AarName}"))
rustInputs.from(rootProject.file("../Cargo.toml"))
rustInputs.from(rootProject.file("../.cargo/config.toml"))
rustInputs.from(rootProject.fileTree("../crates") {
include "**/*.rs"
include "**/Cargo.toml"
include "**/build.rs"
})
gameFeature.set(selectedGameFeature)
abi.set(selectedAbi)
androidApi.set(selectedAndroidApi)
ndkVersion.set(configuredNdkVersion)
releaseBuild.set(selectedReleaseBuild)
repositoryRoot.set(repositoryDirectory)
outputDirectory.set(layout.buildDirectory.dir("generated/sasedevNative/${variant.name}/${selectedAbi}/jniLibs"))
}
if (variant.sources.jniLibs == null) {
throw new GradleException("AGP variant ${variant.name} does not expose jniLibs sources")
}
variant.sources.jniLibs.addGeneratedSourceDirectory(nativeTask) { task ->
task.outputDirectory
}
}
}
}

View File

@@ -1,5 +1,7 @@
// file: Android/settings.gradle // file: Android/settings.gradle
// version: 2 // version: 3
import org.gradle.util.GradleVersion
pluginManagement { pluginManagement {
repositories { repositories {
@@ -9,6 +11,14 @@ pluginManagement {
} }
} }
def minimumGradleVersion = GradleVersion.version("9.6.0")
def currentGradleVersion = GradleVersion.current()
if (currentGradleVersion < minimumGradleVersion) {
throw new GradleException(
"Gradle ${minimumGradleVersion.version} or newer is required; current version is ${currentGradleVersion.version}"
)
}
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {

View File

@@ -1,8 +1,50 @@
<!-- file: CHANGELOG.md --> <!-- file: CHANGELOG.md -->
<!-- version: 14 --> <!-- version: 18 -->
# Changelog # Changelog
## 0.3.3 — 2026-09-21
- publication stable du pipeline Android SDL3 natif multi-ABI possédé par Gradle/Cargo, sans orchestrateur Python de build ni `src/main/jniLibs` généré dans les sources ;
- APK Debug universal et AAB Release produits pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`, avec `minSdk 21` conservé et fumé sur Android 5.0/API 21 x86 ;
- compatibilité pages mémoire 16 KB validée pour les ABI 64 bits : `zipalign -P 16`, segments ELF `align 2**14` et smoke API 35 x86_64 `ps16k` avec `PAGE_SIZE=16384` ;
- smokes finaux Snake et Reflex validés sur AVD API 36 x86_64 et Samsung API 29 ARM64, avec installation réussie, activité lancée, processus vivant et aucune entrée crash remontée ;
- environnement Android natif validé avec JDK courant et Gradle système `>= 9.6.0`, sans wrapper Gradle ni `JAVA_HOME` imposé au projet ;
- préparation de `0.3.4` pour l'API de transport realtime/WebSocket et pour la nouvelle nomenclature prospective `alpha.M / beta.M / rc.M`, sans aucune réécriture des anciens `deltas/`, `history/`, prompts ou entrées historiques.
Les détails des phases `pre`, `beta`, `rc` et de leurs correctifs restent dans `deltas/0.3.3/` et `history/0.3.3/`.
## 0.3.3-3-rc.1 — 2026-09-21
- gel fonctionnel du pipeline Android SDL3 natif désormais possédé par Gradle/Cargo, sans orchestrateur Python de build ni `src/main/jniLibs` généré dans les sources ;
- APK Debug universal et AAB Release validés pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`, avec `minSdk 21` conservé ;
- compatibilité pages mémoire 16 KB validée sur les ABI 64 bits, y compris smoke Android 15/API 35 x86_64 `ps16k` avec `PAGE_SIZE=16384` ;
- smoke Android 5.0/API 21 x86 validé, puis smokes beta Snake et Reflex validés sur AVD API 36 x86_64 et Samsung API 29 ARM64 sans crash immédiat ;
- environnement Android natif validé avec Temurin 25 et Gradle système 9.7.1, le projet imposant seulement `Gradle >= 9.6.0` ;
- prompt `0.3.4` préparé pour l'API de transport realtime/WebSocket et pour la migration de nomenclature à `alpha.M`, `beta.M`, `rc.M` à partir de la prochaine session uniquement ; aucun historique antérieur ne sera renommé ou réécrit.
La RC n'ouvre aucun nouveau scope. Seuls les correctifs de publication autorisés par `VER-RC-*` peuvent produire un `3-rc.1.fix.N`.
## 0.3.1 — 2026-09-21
- publication stable du second host Snake sous Tauri Android, conservé comme POC de référence et non comme voie de production Android ;
- validation du gameplay partagé via `game-snake-poc-wasm`, du Canvas, des contrôles tactiles, des assets, du lifecycle mobile/WebView, du tracing et de la provenance runtime ;
- validation dun APK Debug universal autonome contenant `arm64-v8a` et `x86_64`, installé et lancé sur Galaxy S9+ ARM64 réel et AVD API 36 x86_64 ;
- validation finale de `0.3.1-3-rc.1` sans défaut nécessitant un correctif RC, puis promotion mécanique vers la stable ;
- maintien de `0.3.2` en différé et préparation de `0.3.3` pour Android SDL3 natif multi-ABI, APK universal de test, AAB de distribution et évaluation du `minSdk`.
Les détails des phases `pre`, `beta`, `rc` et de leurs correctifs restent dans `deltas/0.3.1/` et `history/0.3.1/`.
## 0.3.1-3-rc.1 — 2026-09-21
- gel fonctionnel du POC Snake Tauri Android de référence après validation beta du workspace complet et du packaging Android autonome ;
- APK Debug universal construit avec les cibles Rust `aarch64-linux-android` et `x86_64-linux-android`, puis installé et lancé avec succès sur Galaxy S9+ ARM64 et AVD API 36 x86_64 ;
- host Tauri conservé comme POC comparatif : la voie Android productive reste SDL3 natif/Java/JNI ;
- absence dextraction Web/Tauri supplémentaire et absence dAAB Tauri volontaire : les choix de distribution Android productive sont reportés au POC SDL3 natif ;
- `0.3.2` Tauri Desktop reste différée faute de bénéfice produit/monétisation démontré ; la prochaine version active préparée est `0.3.3`, Android SDL3 natif multi-ABI, avec AAB et compatibilité Android ancienne à évaluer.
La RC nouvre aucun nouveau scope. Seuls les correctifs de publication autorisés par `VER-RC-*` peuvent produire un `3-rc.1.fix.N`.
## 0.3.0 — 2026-09-20 ## 0.3.0 — 2026-09-20
- publication stable du premier POC Snake Web direct, construit autour du gameplay Rust partagé, d'un adapter WASM dédié et d'un host navigateur Vite/TypeScript ; - publication stable du premier POC Snake Web direct, construit autour du gameplay Rust partagé, d'un adapter WASM dédié et d'un host navigateur Vite/TypeScript ;

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 69 # version: 85
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -17,10 +17,11 @@ members = [
"crates/apps/game-reflex-poc-tauri", "crates/apps/game-reflex-poc-tauri",
"crates/apps/game-reflex-poc-wasm", "crates/apps/game-reflex-poc-wasm",
"crates/apps/game-snake-poc-wasm", "crates/apps/game-snake-poc-wasm",
"crates/apps/game-snake-poc-tauri",
] ]
[workspace.package] [workspace.package]
version = "0.3.0" version = "0.3.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: README.md --> <!-- file: README.md -->
<!-- version: 40 --> <!-- version: 53 -->
# games.sasedev # games.sasedev
@@ -14,6 +14,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
- assets hors des crates sous `assets/` ; - assets hors des crates sous `assets/` ;
- `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ; - `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ;
- frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ; - frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ;
- Android de production orienté SDL3 natif/Java/JNI ; Tauri Android reste un POC de référence et non un template de jeu ;
- hosts navigateur directs sous `Web/`, avec frontend Vite/TypeScript séparé des adapters Rust/WASM ; - hosts navigateur directs sous `Web/`, avec frontend Vite/TypeScript séparé des adapters Rust/WASM ;
- monétisation optionnelle et spécifique à chaque plateforme/distribution ; - monétisation optionnelle et spécifique à chaque plateforme/distribution ;
- runner Desktop natif SDL3 par défaut, avec variante Tauri uniquement si un besoin futur la justifie ; - runner Desktop natif SDL3 par défaut, avec variante Tauri uniquement si un besoin futur la justifie ;
@@ -24,9 +25,11 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
## Baseline ## Baseline
Version stable de référence : `0.3.0`. Version stable de référence : `0.3.3`.
Version suivante planifiée : `0.3.1` (non démarrée). Prochaine version planifiée : `0.3.4-alpha.1`. `0.3.2` reste différée.
La stable `0.3.3` livre la voie Android SDL3 native multi-ABI : build Gradle/Cargo sans orchestrateur Python, APK Debug universal et AAB Release pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`, `minSdk 21` réellement fumé et compatibilité pages mémoire 16 KB validée sur les ABI 64 bits. `0.3.4-alpha.1` ouvrira la prochaine session avec la migration de nomenclature puis le cadrage de l'API de transport realtime/WebSocket.
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme. Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md --> <!-- file: ROADMAP.md -->
<!-- version: 21 --> <!-- version: 26 -->
# Roadmap # Roadmap
@@ -44,11 +44,11 @@ Le détail historique des prereleases `0.1.0-*` reste dans `deltas/0.1.0/` et `h
- (x) `0.3.0` — utiliser uniquement Cargo, `wasm-bindgen` et Vite/npm pour le chemin de build Web ; aucun script Python ne pilote ce build. - (x) `0.3.0` — utiliser uniquement Cargo, `wasm-bindgen` et Vite/npm pour le chemin de build Web ; aucun script Python ne pilote ce build.
- (x) `0.3.0` — documenter les duplications observées et conserver les adapters Web/WASM spécifiques tant quun second consommateur ne justifie pas une extraction commune. - (x) `0.3.0` — documenter les duplications observées et conserver les adapters Web/WASM spécifiques tant quun second consommateur ne justifie pas une extraction commune.
## Série 0.3.x — trajectoire actuelle après 0.3.0 ## Série 0.3.x — trajectoire actuelle après 0.3.1
- ( ) `0.3.1` second host Snake : Tauri Android, en réutilisant la baseline Web/WASM validée et en remplaçant toute orchestration Python du chemin touché. - (x) `0.3.1` — Snake Tauri Android livré comme POC de référence réutilisant la baseline Web/WASM ; la production Android reste orientée SDL3 natif.
- ( ) `0.3.2` — Tauri Desktop + Snake et factorisation WebView uniquement lorsque deux consommateurs réels justifient l'extraction. - (d) `0.3.2` — Tauri Desktop + Snake est différé tant quune solution de monétisation desktop/WebView, notamment vidéo récompensée, nest pas démontrée techniquement et contractuellement ; ne pas créer cette distribution seulement pour réutiliser le host Web.
- ( ) `0.3.3` — Android SDL natif multi-ABI avec Cargo/Gradle natifs, sans script Python de build. - (x) `0.3.3` — Android SDL3 natif multi-ABI livré avec Cargo/Gradle natifs, APK universal pour tests, AAB comme cible de distribution, `minSdk 21` fumé et compatibilité 16 KB 64 bits validée, sans script Python de build.
- ( ) `0.3.4` — API de transport realtime + WebSocket/tokio-tungstenite baseline, sans serveur Uroburas Mode 3. - ( ) `0.3.4` — API de transport realtime + WebSocket/tokio-tungstenite baseline, sans serveur Uroburas Mode 3.
- ( ) `0.3.5` — POC WebTransport/QUIC sur le même protocole, avec comparaison mesurée et fallback WebSocket. - ( ) `0.3.5` — POC WebTransport/QUIC sur le même protocole, avec comparaison mesurée et fallback WebSocket.
- ( ) `0.3.6` — consolidation des POC plateforme/réseau et préparation de la baseline `0.4.x`. - ( ) `0.3.6` — consolidation des POC plateforme/réseau et préparation de la baseline `0.4.x`.

View File

@@ -1,5 +1,5 @@
// file: Web/game-snake-poc/frontend/ts/provenance.ts // file: Web/game-snake-poc/frontend/ts/provenance.ts
// version: 1 // version: 2
import type { SnakeWasmGame } from "@snake-wasm"; import type { SnakeWasmGame } from "@snake-wasm";
import { webInfo } from "./logging"; import { webInfo } from "./logging";
@@ -83,7 +83,7 @@ export class BrowserRuntimeProvenance {
private synchronize(): void { private synchronize(): void {
const profile = inputProfile(this.keyboardMouseObserved, this.touchObserved); const profile = inputProfile(this.keyboardMouseObserved, this.touchObserved);
if (!this.game.configure_runtime_provenance(this.deviceClass, profile)) { if (!this.game.configure_runtime_provenance("web", "browser", this.deviceClass, profile)) {
throw new Error("La provenance navigateur n'a pas pu être configurée dans le bridge WASM."); throw new Error("La provenance navigateur n'a pas pu être configurée dans le bridge WASM.");
} }
this.output.value = displayLabel(this.game); this.output.value = displayLabel(this.game);

View File

@@ -0,0 +1,34 @@
# file: crates/apps/game-snake-poc-tauri/Cargo.toml
# version: 1
[package]
name = "game-snake-poc-tauri"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
authors.workspace = true
publish.workspace = true
[lib]
name = "game_snake_poc_tauri_lib"
path = "src/lib.rs"
crate-type = ["staticlib", "cdylib", "rlib"]
[[bin]]
name = "game-snake-poc-tauri"
path = "src/main.rs"
[build-dependencies]
tauri-build.workspace = true
[dependencies]
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
game-logging-lib = { path = "../../common/game-logging-lib" }
serde.workspace = true
tauri.workspace = true
tauri-plugin-tracing.workspace = true
tracing.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,45 @@
<!-- file: crates/apps/game-snake-poc-tauri/README.md -->
<!-- version: 9 -->
# game-snake-poc-tauri
Host Tauri Android de référence du POC Snake. Cette application réutilise le gameplay Rust de `game-snake-poc` et l'unique adapter WebAssembly `game-snake-poc-wasm` ; elle ne possède aucune règle de jeu.
## Responsabilités
La crate possède uniquement la frontière Tauri : initialisation du logging Rust, assemblage/plugins/commandes Tauri, contrat minimal de provenance native et frontend Vite/TypeScript exécuté dans la WebView Android.
L'organisation Rust suit le modèle des applications Desk KSP :
- `src/lib.rs` reste une façade et réexporte les éléments partagés au crate-root ;
- `src/tauri.rs` assemble Tauri et fait déléguer les commandes aux modules propriétaires ;
- `src/runtime.rs` possède le petit contrat runtime/provenance ;
- `frontend/` contient le host WebView et consomme les bindings générés de `game-snake-poc-wasm`.
À partir de `0.3.1-0-pre.3`, le frontend fournit aussi le Canvas, la boucle fixed-step, les contrôles clavier/touch et le chargement des assets canoniques. `0-pre.4` ajoute la suspension/reprise explicite, le suivi de visibilité et le resize/orientation.
## Statut du POC
Ce host est conservé comme **POC de référence** et non comme template de production Android. La voie de jeu Android privilégiée reste SDL3 natif/Java/JNI, qui couvre déjà les besoins principaux avec une chaîne de build plus directe.
Tauri Android reste utile pour comparer une WebView/WASM à la voie native et pour tester ultérieurement des besoins spécifiques, par exemple la saisie texte. Une future distribution Tauri Desktop est une décision séparée, conditionnée à un bénéfice produit concret tel quune monétisation WebView exploitable.
## Build
Les hooks `beforeDevCommand` et `beforeBuildCommand` de Tauri possèdent le build WASM et Vite. Aucun orchestrateur Python n'est utilisé. Les bindings WASM, la distribution Vite, les caches, `node_modules/` et le projet Android généré restent hors des sources livrées ou dans des chemins générés ignorés.
Le host ne modifie pas le projet Android généré par Tauri. Pour `0.3.1`, la chaîne Tauri 2.11.x est conservée telle que générée : Gradle `8.14.3` et Android Gradle Plugin `8.11.0`. Le JDK Android de référence du projet est JDK 17, qui correspond au minimum et à la valeur par défaut documentés par AGP 8.11. Le Java 25 global et le JBR 25 de l'Android Studio actuellement installé ne doivent pas exécuter ce wrapper.
Voir [`USAGE.md`](USAGE.md) pour l'initialisation locale et les smokes Android.
## Lifecycle et Back Android
Le host suspend explicitement sa boucle `requestAnimationFrame` lorsque la WebView devient cachée et la reprend en réinitialisant son horloge locale. Une reprise après background ne doit donc pas simuler le temps passé hors écran. Resize et rotation passent par un `ResizeObserver` sur la zone de jeu et réévaluent aussi la provenance device.
Le POC reste mono-page et nenregistre volontairement aucun handler Tauri `onBackButtonPress`. La politique `EngineGame::quit_requested` de Snake est actuellement la politique moteur par défaut `Exit`, cohérente avec le Back Android/Tauri naturel. Un futur jeu retournant `Continue` devra introduire une intégration Back explicite au lieu de modifier ce POC silencieusement.
Le premier smoke ARM64 réel a mis en évidence un abort natif pendant un teardown déclenché par Back : `FORTIFY: pthread_mutex_lock called on a destroyed mutex`. Après correction du tunnel HMR afin que HTTP et WebSocket partagent tous deux `1436`, le smoke `0-pre.4.fix.1` ne l'a pas reproduit. La beta a ensuite validé la construction d'un APK autonome universal et son installation/lancement sur ARM64 réel et AVD x86_64. La RC conserve le teardown/Back dans ses smokes finaux sans ajouter de handler ad hoc ni patcher le scaffold généré.
## Limite ergonomique connue
Le D-pad tactile reste sous le Canvas dans ce POC et peut nécessiter un scroll sur petit écran. Des contrôles superposés ou des gestes swipe seraient techniquement possibles, mais ne sont pas introduits dans `0.3.1` : ils ne changent pas la conclusion darchitecture et appartiendraient à une UX produit réelle.

View File

@@ -0,0 +1,119 @@
<!-- file: crates/apps/game-snake-poc-tauri/USAGE.md -->
<!-- version: 9 -->
# Utilisation — Snake Tauri Android
## Prérequis locaux
Le host utilise Cargo/Rust, `cargo tauri`, Node/npm, `wasm-bindgen`, Java et le SDK Android. Le dépôt attend `ANDROID_HOME` vers le SDK Android et des NDK side-by-side sous `$ANDROID_HOME/ndk/<version>`.
`NDK_HOME` n'est pas un prérequis utilisateur du projet. Tauri sélectionne la version NDK side-by-side appropriée sous `$ANDROID_HOME/ndk/<version>` et peut injecter lui-même `NDK_HOME` dans ses sous-processus Android. Une variable globale ne doit donc pas être ajoutée uniquement pour satisfaire la documentation générique de Tauri.
Pour `0.3.1`, le JDK Android de référence est **JDK 17**. Le template Tauri 2.11.x utilisé par le projet génère Gradle `8.14.3` avec Android Gradle Plugin `8.11.0` : AGP 8.11 exige au minimum JDK 17 et Gradle 8.14.x ne peut pas s'exécuter sur Java 25. JDK 17 fournit donc la baseline reproductible commune aux deux contraintes.
Le Java 25 global de la machine de validation peut rester installé. En revanche, le JBR de l'Android Studio actuellement installé est lui aussi Java 25 et ne doit donc pas être utilisé pour cette gate malgré la recommandation générique de Tauri d'utiliser le JBR d'Android Studio. Le Gradle global installé sur la machine n'est pas utilisé par `gen/android/gradlew` et n'est pas un prérequis du projet.
Avant une commande Tauri Android, sélectionner explicitement un JDK 17 installé et vérifier la version :
```bash
export JAVA_HOME=/chemin/vers/jdk-17
"$JAVA_HOME/bin/java" -version
```
Ne pas modifier `gen/android` pour contourner une incompatibilité de JDK.
Pour les cibles actuellement disponibles, `aarch64-linux-android` permet le smoke sur l'appareil ARM64 et `x86_64-linux-android` celui des AVD x86_64. Les autres ABI restent disponibles pour les validations multi-ABI ultérieures mais ne sont pas nécessaires au smoke courant.
## Initialisation locale
Installer les dépendances frontend dans la crate :
```bash
(cd crates/apps/game-snake-poc-tauri && npm install)
```
Initialiser une fois le projet Android généré par Tauri :
```bash
(cd crates/apps/game-snake-poc-tauri && cargo tauri android init)
```
Le répertoire `gen/` produit par Tauri est généré et ignoré ; il n'appartient pas aux deltas et ne doit pas être patché par des scripts propres au jeu.
Si ce répertoire a été créé ou modifié par `0-pre.2.fix.1`, il est pollué par la tentative abandonnée de migration Gradle 9. Il faut alors le régénérer intégralement avec la CLI Tauri officielle, sous JDK 17 :
```bash
rm -rf crates/apps/game-snake-poc-tauri/gen/android
(cd crates/apps/game-snake-poc-tauri && cargo tauri android init)
```
L'audit de distribution vérifie désormais un `gen/android` présent et refuse un wrapper/AGP qui ne correspondent pas au template Tauri 2.11.x attendu.
## Smoke Android de développement
Lancer ensuite :
```bash
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Choisir l'appareil réel ou l'AVD voulu lorsque la CLI le demande. Le frontend doit afficher le Canvas Snake, les compteurs score/longueur, les contrôles directionnels et les assets `snake / engine-v1`. La provenance doit évoluer depuis `android / tauri-webview / wasm / <device> / unknown` vers un profil d'entrée observé (`touch`, `keyboard-mouse` ou `mixed`) après utilisation des contrôles. Les événements de démarrage doivent apparaître dans le tracing/logcat.
Ne pas utiliser `npm run dev` ou `npm run build` comme gate manuelle : ces scripts sont des hooks internes possédés par Tauri. `cargo tauri android build` reste réservé au jalon de packaging prévu par le plan, sauf diagnostic explicite.
### Appareil Android réel via USB
Sur un appareil physique, Tauri remplace automatiquement le host `localhost` du `devUrl` par une adresse réseau. Pour la gate de référence, le chemin reproductible retenu évite le LAN et utilise le tunnel USB ADB sur le même port HTTP/WebSocket Vite :
```bash
adb -s <serial> reverse --remove-all
adb -s <serial> reverse tcp:1436 tcp:1436
adb -s <serial> reverse --list
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev --host 127.0.0.1)
```
Le `serial` vient de `adb devices -l` et ne doit jamais être enregistré dans les sources. La configuration Vite utilise le port `1436` pour HTTP et WebSocket afin que le même tunnel transporte aussi le HMR. Un port HMR séparé n'appartient pas au contrat de ce POC.
Pour vérifier l'ABI réelle avant le smoke :
```bash
adb -s <serial> shell getprop ro.product.cpu.abi
adb -s <serial> shell getprop ro.product.cpu.abilist
```
`arm64-v8a` correspond à la cible Rust `aarch64-linux-android`, tandis que les AVD utilisés jusqu'ici sont `x86_64-linux-android`.
## Positionnement
`game-snake-poc-tauri` est un POC de référence. Il ne doit pas être utilisé comme base automatique des futurs jeux Android ; la cible Android productive reste SDL3 natif tant quun besoin concret ne justifie pas la WebView Tauri.
## Smoke lifecycle `0-pre.4`
Pendant `cargo tauri android dev` :
1. lancer une partie et effectuer au moins une entrée tactile ;
2. mettre lapplication en arrière-plan puis la ramener au premier plan ;
3. vérifier que le serpent reprend sans accélération ni rattrapage de la durée passée en background ;
4. faire pivoter/redimensionner lémulateur si disponible et vérifier que le Canvas reste rendu ;
5. utiliser Back Android : pour Snake, lapplication doit suivre le comportement système de sortie, sans navigation Web interne ni interception spécifique ;
6. vérifier dans logcat les traces `runtime paused`, `runtime resumed` et les événements `visibility hidden/visible`.
Le D-pad placé sous le Canvas est une limite ergonomique connue du POC de référence et nest pas un critère bloquant de cette gate.
Sur le Galaxy S9+ ARM64 utilisé pour la validation réelle, chargement, WASM, Canvas, assets et touch fonctionnent via le tunnel USB. Un premier smoke a observé un abort natif `FORTIFY: pthread_mutex_lock called on a destroyed mutex` pendant Back/teardown ; après passage de HTTP et HMR sur le même tunnel `1436`, le smoke `0-pre.4.fix.1` ne l'a pas reproduit. La beta a ensuite construit un APK autonome universal et l'a installé/lancé sur le Galaxy S9+ ARM64 ainsi que sur l'AVD x86_64. La RC revalide ces chemins sans modifier `gen/android`.
## Packaging beta Android
À partir de `0.3.1-2-beta.1`, le packaging de validation utilise un APK Debug autonome contenant les deux ABI réellement testées par ce POC : `arm64-v8a` pour l'appareil physique et `x86_64` pour l'AVD. Le build reste possédé par Tauri ; son hook `beforeBuildCommand` construit le WASM release, génère les bindings, exécute TypeScript/Vite et copie les assets.
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
(cd crates/apps/game-snake-poc-tauri && \
cargo tauri android build --debug --apk --target aarch64 --target x86_64)
```
Ne pas lancer `npm run build` séparément pour cette gate. Tauri affiche le chemin de l'APK produit ; les outputs sous `gen/android/**/build/` sont générés et ignorés. Vérifier ensuite que l'APK contient les bibliothèques `arm64-v8a` et `x86_64`, puis installer ce même APK successivement sur l'AVD et l'appareil réel avec `adb install -r`.
Le package autonome ne doit dépendre ni de Vite, ni du port `1436`, ni d'un tunnel ADB. La beta a validé sa construction et son installation/lancement sur les deux architectures de référence. La RC reprend les contrôles fonctionnels lifecycle/Back sur l'état gelé. Aucun AAB Tauri n'est requis : le futur packaging AAB appartient à la voie Android SDL3 native.

View File

@@ -0,0 +1,9 @@
// file: crates/apps/game-snake-poc-tauri/build.rs
// version: 3
//! Build-time integration for the Snake Tauri host.
fn main() {
tauri_build::build();
return;
}

View File

@@ -0,0 +1,12 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default capability for the Snake Tauri Android POC",
"windows": [
"main"
],
"permissions": [
"core:default",
"tracing:default"
]
}

View File

@@ -0,0 +1,235 @@
/* file: crates/apps/game-snake-poc-tauri/frontend/css/main.css */
/* version: 2 */
:root {
font-family: system-ui, sans-serif;
color-scheme: dark;
background: #101418;
color: #f4f7f8;
}
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
min-width: 320px;
min-height: 100%;
margin: 0;
}
body {
min-height: 100vh;
padding: max(0.75rem, env(safe-area-inset-top)) max(0.75rem, env(safe-area-inset-right)) max(0.75rem, env(safe-area-inset-bottom))
max(0.75rem, env(safe-area-inset-left));
background: #101418;
overscroll-behavior: none;
}
.app-header,
.app-footer,
.game-panel,
.control-panel {
border: 1px solid #50606b;
border-radius: 0.75rem;
background: #161c21;
}
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.75rem 1rem;
}
.eyebrow {
margin: 0 0 0.15rem;
color: #9fb0ba;
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1,
h2,
p {
margin-top: 0;
}
h1 {
margin-bottom: 0;
font-size: clamp(1.25rem, 6vw, 1.85rem);
}
h2 {
margin-bottom: 0.35rem;
font-size: 1.1rem;
}
.status-badge,
.game-summary output {
display: inline-flex;
align-items: center;
min-height: 2rem;
padding: 0.35rem 0.65rem;
border-radius: 999px;
background: #29343c;
color: #f4f7f8;
font-size: 0.85rem;
white-space: nowrap;
}
body[data-runtime-state="ready"] .status-badge {
background: #1d5f3d;
}
body[data-runtime-state="error"] .status-badge {
background: #7a2630;
}
.app-main {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 0.75rem;
margin-block: 0.75rem;
}
.game-panel,
.control-panel {
min-width: 0;
padding: 0.75rem;
}
.game-summary {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-bottom: 0.6rem;
}
.game-stage {
width: min(100%, 21rem);
max-height: 58vh;
margin-inline: auto;
aspect-ratio: 3 / 5;
overflow: hidden;
border: 1px solid #6d7f8a;
border-radius: 0.45rem;
background: #000;
}
#game {
display: block;
width: 100%;
height: 100%;
touch-action: none;
outline: none;
}
#game:focus-visible {
box-shadow: inset 0 0 0 3px #8fbfff;
}
.control-panel > div:first-child p {
margin-bottom: 0.75rem;
color: #b9c4ca;
font-size: 0.9rem;
}
.direction-pad {
display: grid;
grid-template-columns: repeat(3, minmax(3.75rem, 5rem));
grid-template-rows: repeat(2, minmax(3.3rem, 4.2rem));
gap: 0.45rem;
justify-content: center;
margin-inline: auto;
user-select: none;
}
.direction-pad button {
border: 1px solid #7892a1;
border-radius: 0.6rem;
background: #202b32;
color: #f4f7f8;
font-size: 1.45rem;
touch-action: manipulation;
}
.direction-pad button:active {
background: #355365;
}
.direction-up {
grid-column: 2;
grid-row: 1;
}
.direction-left {
grid-column: 1;
grid-row: 2;
}
.direction-down {
grid-column: 2;
grid-row: 2;
}
.direction-right {
grid-column: 3;
grid-row: 2;
}
.runtime-details {
margin: 0.9rem 0 0;
padding-top: 0.8rem;
border-top: 1px solid #3a474f;
font-size: 0.78rem;
}
.runtime-details div {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 0.75rem;
margin-top: 0.45rem;
}
.runtime-details dt {
color: #a9b6bd;
}
.runtime-details dd {
min-width: 0;
margin: 0;
overflow-wrap: anywhere;
text-align: right;
}
.startup-error {
margin: 0.9rem 0 0;
padding: 0.65rem;
border: 1px solid #b34b57;
border-radius: 0.5rem;
background: #51232a;
}
.app-footer {
padding: 0.55rem 0.75rem;
color: #a9b6bd;
font-size: 0.75rem;
text-align: center;
}
@media (min-width: 760px) {
.app-main {
grid-template-columns: minmax(18rem, 1fr) minmax(17rem, 22rem);
align-items: start;
}
.game-stage {
width: min(100%, 24rem);
max-height: 70vh;
}
}

View File

@@ -0,0 +1,59 @@
<!-- file: crates/apps/game-snake-poc-tauri/frontend/main.html -->
<!-- version: 2 -->
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#101418" />
<title>Snake POC — Tauri Android</title>
</head>
<body>
<header class="app-header">
<div>
<p class="eyebrow">games.sasedev</p>
<h1>Snake — Tauri Android</h1>
</div>
<span id="runtime-status" class="status-badge" role="status" aria-live="polite">Initialisation…</span>
</header>
<main class="app-main">
<section class="game-panel" aria-label="Partie Snake">
<div class="game-summary">
<output id="score">Score : 0</output>
<output id="length">Longueur : 0</output>
</div>
<div id="game-stage" class="game-stage">
<canvas id="game" width="360" height="600" tabindex="0" aria-label="Zone de jeu Snake"></canvas>
</div>
</section>
<aside class="control-panel" aria-label="Contrôles et runtime">
<div>
<h2>Contrôles</h2>
<p>Flèches, WASD/ZQSD ou boutons tactiles.</p>
</div>
<div class="direction-pad" aria-label="Contrôles directionnels tactiles">
<button class="direction-up" type="button" data-direction="up" aria-label="Haut"></button>
<button class="direction-left" type="button" data-direction="left" aria-label="Gauche"></button>
<button class="direction-down" type="button" data-direction="down" aria-label="Bas"></button>
<button class="direction-right" type="button" data-direction="right" aria-label="Droite"></button>
</div>
<dl class="runtime-details">
<div>
<dt>Provenance</dt>
<dd><output id="runtime-provenance">android / tauri-webview / wasm / unknown / unknown</output></dd>
</div>
<div>
<dt>Assets</dt>
<dd><output id="asset-status">chargement…</output></dd>
</div>
</dl>
<p id="startup-error" class="startup-error" role="alert" hidden></p>
</aside>
</main>
<footer class="app-footer">POC de référence Tauri Android — gameplay Rust partagé</footer>
<script type="module" src="/ts/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/assets.ts
// version: 1
export interface SnakeTauriAssets {
engineGeneration: number;
game: string;
gameKind: string;
}
interface CommonRuntimeAsset {
schema: number;
scope: string;
engine_generation: number;
}
interface GameRuntimeAsset {
schema: number;
game: string;
kind: string;
}
async function fetchJson<T>(relativePath: string): Promise<T> {
const response = await fetch(new URL(relativePath, document.baseURI));
if (!response.ok) {
throw new Error(`Asset runtime indisponible (${response.status}) : ${relativePath}`);
}
return (await response.json()) as T;
}
function validateCommonRuntime(asset: CommonRuntimeAsset): void {
if (asset.schema !== 1 || asset.scope !== "common" || asset.engine_generation !== 1) {
throw new Error("Asset common://data/runtime.json invalide pour le POC Tauri Snake.");
}
}
function validateGameRuntime(asset: GameRuntimeAsset): void {
if (asset.schema !== 1 || asset.game !== "game-snake-poc" || asset.kind !== "snake") {
throw new Error("Asset game://data/game.json invalide pour le POC Tauri Snake.");
}
}
export async function loadSnakeTauriAssets(): Promise<SnakeTauriAssets> {
const [commonRuntime, gameRuntime] = await Promise.all([
fetchJson<CommonRuntimeAsset>("./common/data/runtime.json"),
fetchJson<GameRuntimeAsset>("./game/data/game.json"),
]);
validateCommonRuntime(commonRuntime);
validateGameRuntime(gameRuntime);
return {
engineGeneration: commonRuntime.engine_generation,
game: gameRuntime.game,
gameKind: gameRuntime.kind,
};
}

View File

@@ -0,0 +1,20 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/bridge.ts
// version: 1
import { invoke } from "@tauri-apps/api/core";
export interface RuntimeDescriptor {
deviceClass: string;
executionModel: string;
inputProfile: string;
platformFamily: string;
runtimeHost: string;
}
export async function getRuntimeDescriptor(): Promise<RuntimeDescriptor> {
return await invoke<RuntimeDescriptor>("get_runtime_descriptor");
}
export async function reportFrontendReady(): Promise<void> {
await invoke("frontend_ready");
}

View File

@@ -0,0 +1,182 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/game.ts
// version: 2
import init, { SnakeWasmGame } from "@snake-wasm";
import type { RuntimeDescriptor } from "./bridge";
import { frontendInfo } from "./logging";
import { TauriRuntimeProvenance, type SnakeInputSource } from "./provenance";
const FRAME_MILLIS = 16;
const MAX_FRAME_ELAPSED_MILLIS = 250;
export type SnakeDirection = "left" | "right" | "up" | "down";
export interface SnakeTauriSession {
queueDirection(direction: SnakeDirection, source: SnakeInputSource): void;
pause(): void;
resume(): void;
renderNow(): void;
refreshDeviceClass(): void;
dispose(): void;
}
function color(red: number, green: number, blue: number): string {
return `rgb(${red} ${green} ${blue})`;
}
function resizeCanvas(canvas: HTMLCanvasElement): void {
const ratio = window.devicePixelRatio || 1;
const width = Math.max(1, Math.floor(canvas.clientWidth * ratio));
const height = Math.max(1, Math.floor(canvas.clientHeight * ratio));
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
function render(
game: SnakeWasmGame,
canvas: HTMLCanvasElement,
context: CanvasRenderingContext2D,
score: HTMLOutputElement,
length: HTMLOutputElement,
): void {
resizeCanvas(canvas);
context.fillStyle = color(game.background_red(), game.background_green(), game.background_blue());
context.fillRect(0, 0, canvas.width, canvas.height);
const count = game.rectangle_count();
for (let index = 0; index < count; index += 1) {
context.fillStyle = color(game.rectangle_red(index), game.rectangle_green(index), game.rectangle_blue(index));
context.fillRect(
game.rectangle_x(index) * canvas.width,
game.rectangle_y(index) * canvas.height,
game.rectangle_width(index) * canvas.width,
game.rectangle_height(index) * canvas.height,
);
}
score.value = `Score : ${game.score()}`;
length.value = `Longueur : ${game.length()}`;
}
function queueDirection(game: SnakeWasmGame, direction: SnakeDirection): void {
switch (direction) {
case "left":
game.left();
return;
case "right":
game.right();
return;
case "up":
game.up();
return;
case "down":
game.down();
return;
}
}
export async function startGame(
canvas: HTMLCanvasElement,
score: HTMLOutputElement,
length: HTMLOutputElement,
provenanceOutput: HTMLOutputElement,
descriptor: RuntimeDescriptor,
): Promise<SnakeTauriSession> {
await init();
frontendInfo("Snake Tauri WASM initialized");
const context = canvas.getContext("2d");
if (context === null) {
throw new Error("Le contexte Canvas 2D est indisponible.");
}
const renderingContext: CanvasRenderingContext2D = context;
const game = new SnakeWasmGame();
const provenance = new TauriRuntimeProvenance(game, provenanceOutput, descriptor);
let previous = performance.now();
let accumulator = 0;
let animationFrame: number | null = null;
let running = false;
let disposed = false;
function renderNow(): void {
if (disposed) {
return;
}
render(game, canvas, renderingContext, score, length);
}
function schedule(): void {
if (!running || disposed || animationFrame !== null) {
return;
}
animationFrame = window.requestAnimationFrame(frame);
}
function frame(now: number): void {
animationFrame = null;
if (!running || disposed) {
return;
}
const elapsed = Math.min(Math.max(0, now - previous), MAX_FRAME_ELAPSED_MILLIS);
previous = now;
accumulator += elapsed;
while (accumulator >= FRAME_MILLIS) {
game.tick();
accumulator -= FRAME_MILLIS;
}
renderNow();
schedule();
}
function pause(): void {
if (!running || disposed) {
return;
}
running = false;
accumulator = 0;
if (animationFrame !== null) {
window.cancelAnimationFrame(animationFrame);
animationFrame = null;
}
frontendInfo("Snake Tauri runtime paused");
}
function resume(): void {
if (running || disposed) {
return;
}
previous = performance.now();
accumulator = 0;
running = true;
schedule();
frontendInfo("Snake Tauri runtime resumed");
}
renderNow();
resume();
return {
queueDirection(direction: SnakeDirection, source: SnakeInputSource): void {
if (disposed) {
return;
}
provenance.recordInput(source);
queueDirection(game, direction);
},
pause,
resume,
renderNow,
refreshDeviceClass(): void {
if (!disposed) {
provenance.refreshDeviceClass();
}
},
dispose(): void {
if (disposed) {
return;
}
pause();
disposed = true;
game.free();
frontendInfo("Snake Tauri runtime disposed");
},
};
}

View File

@@ -0,0 +1,57 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/input.ts
// version: 1
import type { SnakeDirection, SnakeTauriSession } from "./game";
const KEY_DIRECTIONS: Readonly<Record<string, SnakeDirection>> = {
arrowleft: "left",
a: "left",
q: "left",
arrowright: "right",
d: "right",
arrowup: "up",
w: "up",
z: "up",
arrowdown: "down",
s: "down",
};
function buttonDirection(button: HTMLButtonElement): SnakeDirection | null {
const direction = button.dataset.direction;
if (direction === "left" || direction === "right" || direction === "up" || direction === "down") {
return direction;
}
return null;
}
export function bindInputs(session: SnakeTauriSession, buttons: readonly HTMLButtonElement[]): void {
window.addEventListener("keydown", event => {
if (event.repeat) {
return;
}
const direction = KEY_DIRECTIONS[event.key.toLowerCase()];
if (direction === undefined) {
return;
}
event.preventDefault();
session.queueDirection(direction, "keyboard-mouse");
});
for (const button of buttons) {
const direction = buttonDirection(button);
if (direction === null) {
continue;
}
button.addEventListener("pointerdown", event => {
event.preventDefault();
const source = event.pointerType === "touch" ? "touch" : "keyboard-mouse";
session.queueDirection(direction, source);
});
button.addEventListener("click", event => {
if (event.detail !== 0) {
return;
}
session.queueDirection(direction, "keyboard-mouse");
});
}
}

View File

@@ -0,0 +1,60 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/lifecycle.ts
// version: 1
import type { SnakeTauriSession } from "./game";
import { frontendInfo } from "./logging";
export interface SnakeTauriLifecycleBinding {
dispose(): void;
}
export function bindTauriLifecycle(session: SnakeTauriSession, stage: HTMLElement): SnakeTauriLifecycleBinding {
const resizeObserver = new ResizeObserver(() => {
session.refreshDeviceClass();
session.renderNow();
});
function synchronizeVisibility(): void {
if (document.visibilityState === "hidden") {
session.pause();
frontendInfo("Snake Tauri lifecycle: visibility hidden");
return;
}
session.resume();
session.refreshDeviceClass();
session.renderNow();
frontendInfo("Snake Tauri lifecycle: visibility visible");
}
function pageHide(event: PageTransitionEvent): void {
if (event.persisted) {
session.pause();
} else {
session.dispose();
}
frontendInfo(`Snake Tauri lifecycle: page hide persisted=${event.persisted}`);
}
function pageShow(event: PageTransitionEvent): void {
session.resume();
session.refreshDeviceClass();
session.renderNow();
frontendInfo(`Snake Tauri lifecycle: page show persisted=${event.persisted}`);
}
resizeObserver.observe(stage);
document.addEventListener("visibilitychange", synchronizeVisibility);
window.addEventListener("pagehide", pageHide);
window.addEventListener("pageshow", pageShow);
synchronizeVisibility();
return {
dispose(): void {
resizeObserver.disconnect();
document.removeEventListener("visibilitychange", synchronizeVisibility);
window.removeEventListener("pagehide", pageHide);
window.removeEventListener("pageshow", pageShow);
session.dispose();
},
};
}

View File

@@ -0,0 +1,16 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts
// version: 1
import { attachConsole, error, info } from "@fltsci/tauri-plugin-tracing";
export async function initializeFrontendTracing(): Promise<void> {
await attachConsole();
}
export function frontendInfo(message: string): void {
info(message);
}
export function frontendError(message: string): void {
error(message);
}

View File

@@ -0,0 +1,65 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/main.ts
// version: 3
import "../css/main.css";
import { loadSnakeTauriAssets } from "./assets";
import { getRuntimeDescriptor, reportFrontendReady } from "./bridge";
import { startGame } from "./game";
import { bindInputs } from "./input";
import { bindTauriLifecycle } from "./lifecycle";
import { frontendError, frontendInfo, initializeFrontendTracing } from "./logging";
function requiredElement<T extends HTMLElement>(identifier: string): T {
const element = document.getElementById(identifier);
if (element === null) {
throw new Error(`Élément frontend requis absent : ${identifier}`);
}
return element as T;
}
async function bootstrap(): Promise<void> {
await initializeFrontendTracing();
frontendInfo("Snake Tauri frontend bootstrap started");
const descriptor = await getRuntimeDescriptor();
const canvas = requiredElement<HTMLCanvasElement>("game");
const stage = requiredElement<HTMLElement>("game-stage");
const score = requiredElement<HTMLOutputElement>("score");
const length = requiredElement<HTMLOutputElement>("length");
const provenance = requiredElement<HTMLOutputElement>("runtime-provenance");
const assets = requiredElement<HTMLOutputElement>("asset-status");
const status = requiredElement<HTMLSpanElement>("runtime-status");
const error = requiredElement<HTMLParagraphElement>("startup-error");
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-direction]"));
status.textContent = "Chargement assets…";
const assetMetadata = await loadSnakeTauriAssets();
assets.value = `${assetMetadata.gameKind} / engine-v${assetMetadata.engineGeneration}`;
frontendInfo(`Snake Tauri assets ready: ${assetMetadata.game} / ${assets.value}`);
status.textContent = "Chargement WASM…";
const session = await startGame(canvas, score, length, provenance, descriptor);
bindInputs(session, buttons);
bindTauriLifecycle(session, stage);
document.body.dataset.runtimeState = "ready";
status.textContent = "Prêt";
error.hidden = true;
canvas.focus();
await reportFrontendReady();
frontendInfo(`Snake Tauri frontend ready: ${provenance.value}`);
}
void bootstrap().catch(reason => {
const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
document.body.dataset.runtimeState = "error";
const status = document.getElementById("runtime-status");
const error = document.getElementById("startup-error");
if (status !== null) {
status.textContent = "Erreur";
}
if (error !== null) {
error.textContent = `Impossible de démarrer Snake : ${message}`;
error.hidden = false;
}
frontendError(`Snake Tauri frontend bootstrap failed: ${message}`);
});

View File

@@ -0,0 +1,97 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/provenance.ts
// version: 1
import type { SnakeWasmGame } from "@snake-wasm";
import type { RuntimeDescriptor } from "./bridge";
import { frontendInfo } from "./logging";
export type SnakeInputSource = "keyboard-mouse" | "touch";
type DeviceClass = "desktop" | "phone" | "tablet" | "unknown";
type InputProfile = "keyboard-mouse" | "mixed" | "touch" | "unknown";
function detectedDeviceClass(): DeviceClass {
const coarsePointer = window.matchMedia("(pointer: coarse)").matches;
const finePointer = window.matchMedia("(pointer: fine)").matches;
if (!coarsePointer || finePointer) {
return "desktop";
}
const shortEdge = Math.min(window.screen.width, window.screen.height);
if (!Number.isFinite(shortEdge) || shortEdge <= 0) {
return "unknown";
}
return shortEdge < 768 ? "phone" : "tablet";
}
function inputProfile(keyboardMouseObserved: boolean, touchObserved: boolean): InputProfile {
if (keyboardMouseObserved && touchObserved) {
return "mixed";
}
if (keyboardMouseObserved) {
return "keyboard-mouse";
}
if (touchObserved) {
return "touch";
}
return "unknown";
}
function displayLabel(game: SnakeWasmGame): string {
return [
game.provenance_platform_family(),
game.provenance_runtime_host(),
game.provenance_execution_model(),
game.provenance_device_class(),
game.provenance_input_profile(),
].join(" / ");
}
export class TauriRuntimeProvenance {
private keyboardMouseObserved = false;
private touchObserved = false;
private deviceClass: DeviceClass = detectedDeviceClass();
public constructor(
private readonly game: SnakeWasmGame,
private readonly output: HTMLOutputElement,
private readonly descriptor: RuntimeDescriptor,
) {
if (descriptor.executionModel !== "wasm") {
throw new Error(`Modèle d'exécution Tauri inattendu : ${descriptor.executionModel}`);
}
this.synchronize();
}
public recordInput(source: SnakeInputSource): void {
if (source === "touch") {
if (this.touchObserved) {
return;
}
this.touchObserved = true;
} else {
if (this.keyboardMouseObserved) {
return;
}
this.keyboardMouseObserved = true;
}
this.synchronize();
}
public refreshDeviceClass(): void {
const nextDeviceClass = detectedDeviceClass();
if (nextDeviceClass === this.deviceClass) {
return;
}
this.deviceClass = nextDeviceClass;
this.synchronize();
}
private synchronize(): void {
const profile = inputProfile(this.keyboardMouseObserved, this.touchObserved);
if (!this.game.configure_runtime_provenance(this.descriptor.platformFamily, this.descriptor.runtimeHost, this.deviceClass, profile)) {
throw new Error("La provenance Tauri Android n'a pas pu être configurée dans le bridge WASM.");
}
this.output.value = displayLabel(this.game);
frontendInfo(`Snake Tauri provenance: ${this.output.value}`);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

View File

@@ -0,0 +1,22 @@
{
"name": "game-snake-poc-tauri",
"private": true,
"version": "0.3.1",
"type": "module",
"scripts": {
"wasm:dev": "cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown && wasm-bindgen ../../../../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../../../../builds/sasedev-games/game-snake-poc-tauri/wasm --out-name game_snake_poc_wasm",
"wasm:build": "cargo build -p game-snake-poc-wasm --release --target wasm32-unknown-unknown && wasm-bindgen ../../../../builds/sasedev-games/target/wasm32-unknown-unknown/release/game_snake_poc_wasm.wasm --target web --out-dir ../../../../builds/sasedev-games/game-snake-poc-tauri/wasm --out-name game_snake_poc_wasm",
"dev": "npm run wasm:dev && vite",
"build": "npm run wasm:build && tsc && vite build"
},
"dependencies": {
"@fltsci/tauri-plugin-tracing": "^0.3",
"@tauri-apps/api": "^2.11"
},
"devDependencies": {
"@types/node": "^26.1",
"typescript": "^7.0",
"vite": "^8.2",
"vite-plugin-static-copy": "^4.1"
}
}

View File

@@ -0,0 +1,21 @@
// file: crates/apps/game-snake-poc-tauri/src/lib.rs
// version: 1
//! Tauri Android application facade for the Snake WebAssembly POC.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
mod runtime;
mod tauri;
/// Runs the Snake Tauri application.
pub use self::tauri::run;
/// Runtime descriptor exposed by the native Tauri bridge.
pub(crate) use self::runtime::RuntimeDescriptorDto;
/// Records that the Tauri frontend completed its minimal startup sequence.
pub(crate) use self::runtime::record_frontend_ready;
/// Returns the canonical runtime descriptor for the Android WebView host.
pub(crate) use self::runtime::runtime_descriptor;

View File

@@ -0,0 +1,20 @@
// file: crates/apps/game-snake-poc-tauri/src/main.rs
// version: 1
//! Binary entry point for host-side Snake Tauri checks.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() -> std::process::ExitCode {
let result = game_snake_poc_tauri_lib::run();
return match result {
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
std::result::Result::Err(error) => {
eprintln!("Snake Tauri application error: {error}");
std::process::ExitCode::FAILURE
},
};
}

View File

@@ -0,0 +1,87 @@
// file: crates/apps/game-snake-poc-tauri/src/runtime.rs
// version: 1
/// Stable runtime dimensions exposed to the Tauri frontend.
#[derive(Clone, Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RuntimeDescriptorDto {
device_class: String,
execution_model: String,
input_profile: String,
platform_family: String,
runtime_host: String,
}
/// Returns the canonical runtime descriptor for the Android WebView host.
pub(crate) fn runtime_descriptor() -> RuntimeDescriptorDto {
let provenance = engine_v1_platform_api::RuntimeProvenance::new(
engine_v1_platform_api::DeviceClass::Unknown,
engine_v1_platform_api::ExecutionModel::Wasm,
engine_v1_platform_api::InputProfile::Unknown,
engine_v1_platform_api::PlatformFamily::Android,
engine_v1_platform_api::RuntimeHost::TauriWebView,
);
return RuntimeDescriptorDto {
device_class: device_class_label(provenance.device_class()).to_string(),
execution_model: execution_model_label(provenance.execution_model()).to_string(),
input_profile: input_profile_label(provenance.input_profile()).to_string(),
platform_family: platform_family_label(provenance.platform_family()).to_string(),
runtime_host: runtime_host_label(provenance.runtime_host()).to_string(),
};
}
/// Records that the Tauri frontend completed its minimal startup sequence.
pub(crate) fn record_frontend_ready() {
let descriptor = crate::runtime_descriptor();
tracing::info!(
target: "games::tauri::snake",
action = "frontend_ready",
platform_family = descriptor.platform_family,
runtime_host = descriptor.runtime_host,
execution_model = descriptor.execution_model,
"Snake Tauri frontend ready"
);
return;
}
fn device_class_label(value: engine_v1_platform_api::DeviceClass) -> &'static str {
return match value {
engine_v1_platform_api::DeviceClass::Desktop => "desktop",
engine_v1_platform_api::DeviceClass::Phone => "phone",
engine_v1_platform_api::DeviceClass::Tablet => "tablet",
engine_v1_platform_api::DeviceClass::Unknown => "unknown",
};
}
fn execution_model_label(value: engine_v1_platform_api::ExecutionModel) -> &'static str {
return match value {
engine_v1_platform_api::ExecutionModel::Native => "native",
engine_v1_platform_api::ExecutionModel::Wasm => "wasm",
};
}
fn input_profile_label(value: engine_v1_platform_api::InputProfile) -> &'static str {
return match value {
engine_v1_platform_api::InputProfile::Gamepad => "gamepad",
engine_v1_platform_api::InputProfile::KeyboardMouse => "keyboard-mouse",
engine_v1_platform_api::InputProfile::Mixed => "mixed",
engine_v1_platform_api::InputProfile::Touch => "touch",
engine_v1_platform_api::InputProfile::Unknown => "unknown",
};
}
fn platform_family_label(value: engine_v1_platform_api::PlatformFamily) -> &'static str {
return match value {
engine_v1_platform_api::PlatformFamily::Android => "android",
engine_v1_platform_api::PlatformFamily::Desktop => "desktop",
engine_v1_platform_api::PlatformFamily::Web => "web",
};
}
fn runtime_host_label(value: engine_v1_platform_api::RuntimeHost) -> &'static str {
return match value {
engine_v1_platform_api::RuntimeHost::Browser => "browser",
engine_v1_platform_api::RuntimeHost::Native => "native",
engine_v1_platform_api::RuntimeHost::TauriWebView => "tauri-webview",
};
}

View File

@@ -0,0 +1,85 @@
// file: crates/apps/game-snake-poc-tauri/src/tauri.rs
// version: 2
//! Tauri runtime assembly and Web/Rust commands for the Snake Android POC.
#[tauri::command]
fn get_runtime_descriptor() -> crate::RuntimeDescriptorDto {
return crate::runtime_descriptor();
}
#[tauri::command]
fn frontend_ready() {
crate::record_frontend_ready();
return;
}
fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
let tracing_plugin = tauri_plugin_tracing::Builder::new().build::<tauri::Wry>();
return builder.plugin(tracing_plugin);
}
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for command dispatch.
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.invoke_handler(tauri::generate_handler![get_runtime_descriptor, frontend_ready]);
}
fn run_runtime() -> std::result::Result<(), String> {
let logging_guard = game_logging_lib::init_console_tracing();
let _logging_guard = match logging_guard {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
tracing::info!(
target: "games::tauri::snake",
action = "runtime_start",
platform_family = "android",
runtime_host = "tauri-webview",
execution_model = "wasm",
"Starting Snake Tauri Android runtime"
);
let builder = tauri::Builder::default();
let builder = configure_plugins(builder);
let builder = configure_commands(builder);
return match builder.run(tauri::generate_context!()) {
std::result::Result::Ok(()) => {
tracing::info!(
target: "games::tauri::snake",
action = "runtime_stop",
"Snake Tauri runtime stopped"
);
std::result::Result::Ok(())
},
std::result::Result::Err(error) => {
tracing::error!(
target: "games::tauri::snake",
action = "runtime_error",
error = error.to_string(),
"Snake Tauri runtime failed"
);
std::result::Result::Err(error.to_string())
},
};
}
/// Runs the Snake Tauri application on a desktop host.
#[cfg(not(mobile))]
pub fn run() -> std::result::Result<(), String> {
return run_runtime();
}
/// Runs the Snake Tauri application from Tauri's mobile entry point.
#[cfg(mobile)]
#[tauri::mobile_entry_point]
pub fn run() {
let result = run_runtime();
if let std::result::Result::Err(error) = result {
tracing::error!(
target: "games::tauri::snake",
action = "mobile_entrypoint_error",
error = error.as_str(),
"Snake Tauri mobile entry point terminated after a runtime error"
);
}
return;
}

View File

@@ -0,0 +1,41 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Snake POC Tauri",
"identifier": "com.sasedev.games.snake.tauri",
"build": {
"beforeDevCommand": {
"script": "npm run dev",
"cwd": "."
},
"devUrl": "http://localhost:1436",
"beforeBuildCommand": {
"script": "npm run build",
"cwd": "."
},
"frontendDist": "../../../../builds/sasedev-games/game-snake-poc-tauri/dist"
},
"app": {
"windows": [
{
"label": "main",
"url": "main.html",
"title": "Snake POC Tauri",
"width": 405,
"height": 720,
"minWidth": 320,
"minHeight": 480,
"resizable": true,
"center": true
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": false,
"icon": [
"icons/icon.png"
]
}
}

View File

@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"types": [
"vite/client",
"node"
],
"paths": {
"@snake-wasm": [
"../../../../builds/sasedev-games/game-snake-poc-tauri/wasm/game_snake_poc_wasm.d.ts"
]
}
},
"include": [
"frontend",
"vite.config.ts"
]
}

View File

@@ -0,0 +1,84 @@
// file: crates/apps/game-snake-poc-tauri/vite.config.ts
// version: 3
import { fileURLToPath } from "node:url";
import { resolve } from "node:path";
import { defineConfig, normalizePath } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
const appRoot = fileURLToPath(new URL(".", import.meta.url));
const repositoryRoot = normalizePath(resolve(appRoot, "../../.."));
const frontendRoot = normalizePath(resolve(appRoot, "frontend"));
const externalBuildRoot = normalizePath(resolve(repositoryRoot, "../builds/sasedev-games/game-snake-poc-tauri"));
const wasmRoot = normalizePath(resolve(externalBuildRoot, "wasm"));
const wasmModule = normalizePath(resolve(wasmRoot, "game_snake_poc_wasm.js"));
const frontendDist = normalizePath(resolve(externalBuildRoot, "dist"));
const viteCacheDir = normalizePath(resolve(externalBuildRoot, "vite-cache"));
const commonRuntimeAsset = normalizePath(resolve(repositoryRoot, "assets/common/data/runtime.json"));
const gameRuntimeAsset = normalizePath(resolve(repositoryRoot, "assets/game-snake-poc/data/game.json"));
const devHost = process.env.TAURI_DEV_HOST;
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [
{ src: commonRuntimeAsset, dest: "common/data", rename: { stripBase: true } },
{ src: gameRuntimeAsset, dest: "game/data", rename: { stripBase: true } },
],
}),
],
base: "./",
cacheDir: viteCacheDir,
clearScreen: false,
root: frontendRoot,
publicDir: false,
input: {
main: normalizePath(resolve(frontendRoot, "main.html")),
},
resolve: {
alias: {
"@snake-wasm": wasmModule,
},
},
build: {
outDir: frontendDist,
emptyOutDir: true,
minify: true,
sourcemap: false,
cssCodeSplit: true,
rolldownOptions: {
output: {
entryFileNames: "js/[name]-[hash].js",
chunkFileNames: "js/chunks/[name]-[hash].js",
assetFileNames: assetInfo => {
const originalName = assetInfo.names[0] ?? "";
const extension = originalName.substring(originalName.lastIndexOf(".") + 1).toLowerCase();
if (extension === "css") {
return "css/[name]-[hash][extname]";
}
if (extension === "wasm") {
return "wasm/[name]-[hash][extname]";
}
return "otherassets/[name][extname]";
},
},
},
},
server: {
port: 1436,
strictPort: true,
host: devHost || false,
fs: {
allow: [appRoot, repositoryRoot, externalBuildRoot],
},
ws: {
protocol: "ws",
host: devHost || "127.0.0.1",
port: 1436,
clientPort: 1436,
},
watch: {
ignored: ["**/src/**"],
},
},
});

View File

@@ -1,5 +1,5 @@
// file: crates/apps/game-snake-poc-wasm/src/runtime.rs // file: crates/apps/game-snake-poc-wasm/src/runtime.rs
// version: 2 // version: 3
const FIXED_STEP_MILLIS: u64 = 16; const FIXED_STEP_MILLIS: u64 = 16;
@@ -31,8 +31,16 @@ impl SnakeWasmGame {
}; };
} }
/// Updates browser-observed device and input dimensions while preserving Web/WASM/Browser provenance. /// Updates host-observed runtime provenance while preserving WebAssembly execution.
pub fn configure_runtime_provenance(&mut self, device_class: &str, input_profile: &str) -> bool { pub fn configure_runtime_provenance(&mut self, platform_family: &str, runtime_host: &str, device_class: &str, input_profile: &str) -> bool {
let platform_family = match parse_platform_family(platform_family) {
Some(value) => value,
None => return false,
};
let runtime_host = match parse_runtime_host(runtime_host) {
Some(value) => value,
None => return false,
};
let device_class = match parse_device_class(device_class) { let device_class = match parse_device_class(device_class) {
Some(value) => value, Some(value) => value,
None => return false, None => return false,
@@ -45,8 +53,8 @@ impl SnakeWasmGame {
device_class, device_class,
engine_v1_platform_api::ExecutionModel::Wasm, engine_v1_platform_api::ExecutionModel::Wasm,
input_profile, input_profile,
engine_v1_platform_api::PlatformFamily::Web, platform_family,
engine_v1_platform_api::RuntimeHost::Browser, runtime_host,
); );
return true; return true;
} }
@@ -236,6 +244,24 @@ impl SnakeWasmGame {
} }
} }
fn parse_platform_family(label: &str) -> Option<engine_v1_platform_api::PlatformFamily> {
return match label {
"android" => Some(engine_v1_platform_api::PlatformFamily::Android),
"desktop" => Some(engine_v1_platform_api::PlatformFamily::Desktop),
"web" => Some(engine_v1_platform_api::PlatformFamily::Web),
_ => None,
};
}
fn parse_runtime_host(label: &str) -> Option<engine_v1_platform_api::RuntimeHost> {
return match label {
"browser" => Some(engine_v1_platform_api::RuntimeHost::Browser),
"native" => Some(engine_v1_platform_api::RuntimeHost::Native),
"tauri-webview" => Some(engine_v1_platform_api::RuntimeHost::TauriWebView),
_ => None,
};
}
fn parse_device_class(label: &str) -> Option<engine_v1_platform_api::DeviceClass> { fn parse_device_class(label: &str) -> Option<engine_v1_platform_api::DeviceClass> {
return match label { return match label {
"desktop" => Some(engine_v1_platform_api::DeviceClass::Desktop), "desktop" => Some(engine_v1_platform_api::DeviceClass::Desktop),

View File

@@ -1,5 +1,5 @@
// file: crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs // file: crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
// version: 2 // version: 3
#[test] #[test]
fn direction_is_queued_without_advancing_simulation() { fn direction_is_queued_without_advancing_simulation() {
@@ -34,7 +34,7 @@ fn browser_runtime_provenance_preserves_static_and_observed_dimensions() {
assert_eq!(game.provenance_runtime_host(), "browser"); assert_eq!(game.provenance_runtime_host(), "browser");
assert_eq!(game.provenance_device_class(), "unknown"); assert_eq!(game.provenance_device_class(), "unknown");
assert_eq!(game.provenance_input_profile(), "unknown"); assert_eq!(game.provenance_input_profile(), "unknown");
assert!(game.configure_runtime_provenance("phone", "touch")); assert!(game.configure_runtime_provenance("web", "browser", "phone", "touch"));
assert_eq!(game.provenance_device_class(), "phone"); assert_eq!(game.provenance_device_class(), "phone");
assert_eq!(game.provenance_input_profile(), "touch"); assert_eq!(game.provenance_input_profile(), "touch");
assert_eq!(game.provenance_platform_family(), "web"); assert_eq!(game.provenance_platform_family(), "web");
@@ -45,8 +45,19 @@ fn browser_runtime_provenance_preserves_static_and_observed_dimensions() {
#[test] #[test]
fn invalid_runtime_provenance_labels_are_rejected_without_mutation() { fn invalid_runtime_provenance_labels_are_rejected_without_mutation() {
let mut game = crate::SnakeWasmGame::new(); let mut game = crate::SnakeWasmGame::new();
assert!(game.configure_runtime_provenance("desktop", "keyboard-mouse")); assert!(game.configure_runtime_provenance("web", "browser", "desktop", "keyboard-mouse"));
assert!(!game.configure_runtime_provenance("console", "telepathy")); assert!(!game.configure_runtime_provenance("console", "spaceship", "console", "telepathy"));
assert_eq!(game.provenance_device_class(), "desktop"); assert_eq!(game.provenance_device_class(), "desktop");
assert_eq!(game.provenance_input_profile(), "keyboard-mouse"); assert_eq!(game.provenance_input_profile(), "keyboard-mouse");
} }
#[test]
fn tauri_android_runtime_provenance_is_supported_by_the_shared_adapter() {
let mut game = crate::SnakeWasmGame::new();
assert!(game.configure_runtime_provenance("android", "tauri-webview", "phone", "touch"));
assert_eq!(game.provenance_platform_family(), "android");
assert_eq!(game.provenance_runtime_host(), "tauri-webview");
assert_eq!(game.provenance_execution_model(), "wasm");
assert_eq!(game.provenance_device_class(), "phone");
assert_eq!(game.provenance_input_profile(), "touch");
}

View File

@@ -1,5 +1,5 @@
// file: crates/engines/engine-v1-platform-api/src/environment.rs // file: crates/engines/engine-v1-platform-api/src/environment.rs
// version: 1 // version: 2
/// Broad physical device class relevant to interaction and score provenance. /// Broad physical device class relevant to interaction and score provenance.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -56,7 +56,7 @@ pub enum RuntimeHost {
Browser, Browser,
/// Native process managed directly by the operating system. /// Native process managed directly by the operating system.
Native, Native,
/// Tauri desktop WebView host. /// Tauri WebView host on a supported native platform.
TauriWebView, TauriWebView,
} }

146
deltas/0.3.1/0-pre.1.md Normal file
View File

@@ -0,0 +1,146 @@
<!-- file: deltas/0.3.1/0-pre.1.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.1
## Base
Base autoritaire : archive téléchargée depuis le tag `v0.3.0` et fournie pour la reprise.
L'archive a été testée et inspectée avant modification. Sa version workspace est `0.3.0`. Elle ne contient pas `.git`, conformément au contrat des archives taggées ; aucun état Git inaccessible n'est inventé.
## Objet
Ouvrir `0.3.1` par son gate obligatoire de cadrage : audit de baseline, revue complète des règles, inventaire de réutilisation Web/WASM, référence Tauri existante, exigences Tauri Android, sizing et planification.
Aucun scaffold `game-snake-poc-tauri`, aucun frontend Tauri Snake et aucun changement de gameplay n'est introduit dans cette tranche.
## Version
La version workspace passe de :
```text
0.3.0
```
à :
```text
0.3.1-0-pre.1
```
Les versions npm des produits existants ne changent pas : cette tranche n'est pas une release de leurs frontends et `VER-TAURI-003`/`VER-TAURI-004` interdisent une synchronisation npm cérémonielle à chaque prerelease Cargo.
## Audit de baseline
Avant modification :
```text
unzip -t : no errors
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 197 file(s))
Distribution layout audit: clean (31 required path(s), 1 forbidden path(s) absent)
```
L'archive ne contient aucun lockfile interdit, `node_modules/`, `dist/`, `target/` ou binding WASM généré.
Le détail est conservé dans `docs/studies/024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md`.
## Corrections documentaires et normatives
Cette tranche :
- ferme l'index du plan `0.3.0` et ouvre le plan actif `0.3.1` ;
- ajoute l'étude de cadrage et le plan vivant `0.3.1` ;
- distingue explicitement les commandes Tauri Desktop des commandes Tauri Android ;
- ajoute `cargo tauri android init`, `cargo tauri android dev` et `cargo tauri android build` à la matrice normative ;
- corrige le prompt de reprise pour utiliser les commandes Android de Tauri au lieu des commandes Desktop ;
- clarifie la différence entre la famille conceptuelle Mobile et le contrat V1 concret `PlatformFamily::Android` ;
- distingue une sortie applicative Tauri soumise à `EngineGame::quit_requested` du Back système Android, qui reste une navigation système ;
- documente la provenance attendue `Android / Wasm / TauriWebView`.
## Réutilisation retenue
Le second host doit :
- conserver `game-snake-poc` comme gameplay unique ;
- réutiliser `game-snake-poc-wasm` plutôt que créer un second adapter ;
- généraliser seulement la provenance de host actuellement figée sur Web/Browser ;
- réutiliser sélectivement Canvas/input/assets/shell du host Web ;
- adapter lifecycle et tracing au WebView Tauri Android ;
- suivre l'organisation Tauri `lib.rs` façade + `tauri.rs` assemblage + modules propriétaires ;
- ne pas reprendre l'orchestrateur Python historique du POC Reflex.
## Référence KSP
L'archive KSP demandée est visible dans la bibliothèque, mais son export binaire vers l'environnement de génération a été refusé par le service de bibliothèque. Aucun audit intégral fictif de cette archive n'est donc déclaré.
Les extraits indexés accessibles confirment néanmoins une organisation Desk avec `tauri.rs` pour l'assemblage/lifecycle et des modules propriétaires séparés tels que `app_state.rs`, `route_runtime.rs` et `frontend_logging.rs`. Les règles locales games.sasedev restent normatives.
## Forecast
```text
0-pre.1 audit / règles / requirements / environnement / plan
0-pre.2 scaffold Tauri Android + pipeline natif + bridge Snake WASM minimal
0-pre.3 Canvas / input / assets / shell
0-pre.4 lifecycle / provenance / tracing / Back + smoke mobile
0-pre.5 conditionnel : extraction commune réellement justifiée
2-beta.1 validation large + packaging Android
2-beta.2 consolidation documentaire conditionnelle
3-rc.1 candidate gelée
0.3.1 release mécanique
```
## Validations exécutées dans l'environnement de génération
Après constitution de l'état livré, exécuter :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
```
Aucun Cargo build/test/smoke final n'est attribué au générateur.
## Validation utilisateur demandée
Le manifest workspace change de version et les règles de commandes/validation sont modifiées. La gate utilisateur est :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Aucun test workspace complet n'est imposé par `pre.1` : aucun code, comportement ni graphe de dépendances n'est modifié. Il reste réservé à un jalon planifié ou à une portée transverse non bornable.
Fournir également l'inventaire d'environnement qui conditionne `0-pre.2` :
```bash
rustc --version
cargo --version
cargo tauri --version
rustup target list --installed
node --version
npm --version
wasm-bindgen --version
java -version
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\nNDK_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME" "$NDK_HOME"
adb version
adb devices -l
emulator -list-avds
```
Cette seconde série est un inventaire de prérequis, pas une gate déclarée réussie à l'avance.
## Suite après validation
Si la gate est propre et le cadrage accepté, passer à `0.3.1-0-pre.2` pour créer le host Tauri Android minimal. Toute absence d'outil indispensable ou incohérence de règle découverte par la gate produit d'abord un `0-pre.1.fix.N` borné.

View File

@@ -0,0 +1,181 @@
<!-- file: deltas/0.3.1/0-pre.2.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.2.fix.1
## Base requise
`0.3.1-0-pre.2`.
Le delta `0-pre.2.md` reste immuable. La gate utilisateur du 2026-09-20 est consignée dans `history/0.3.1/0-pre.2.md`.
## Objectif
Corriger uniquement les défauts qui empêchent la gate de `0-pre.2` :
- warning `missing_docs` du build script, bloquant sous Clippy `-D warnings` ;
- résultat ignoré par le wrapper `tauri::mobile_entry_point` Android ;
- incompatibilité du wrapper Gradle `8.14.3` généré par Tauri CLI `2.11.3` avec Java 25.
Le scope fonctionnel de `0-pre.2` ne change pas. Canvas, contrôles, assets et lifecycle restent réservés aux tranches suivantes.
## Version technique
Le correctif touche du code Rust et le chemin de build Tauri/Android. La version workspace passe donc à :
```text
0.3.1-0-pre.2.fix.1
```
La version frontend locale de `package.json` reste `0.1.0` conformément à `VER-TAURI-003` et `VER-TAURI-004`.
## Correction Rust
`build.rs` possède désormais une rustdoc de crate et reste conforme aux règles Rust du workspace.
Le point d'entrée est séparé en deux niveaux :
- `run_runtime() -> Result<(), String>` possède l'exécution réelle ;
- le host non mobile conserve `run() -> Result<(), String>` ;
- le host mobile expose `#[tauri::mobile_entry_point] pub fn run()` et traite explicitement l'erreur éventuelle au lieu de laisser le macro ignorer un `Result` `must_use`.
Le comportement fonctionnel Tauri n'est pas modifié.
## Correction Gradle 9
Le log utilisateur confirme que Tauri sélectionne lui-même le NDK installé sous `$ANDROID_HOME/ndk/30.0.14904198` puis injecte `NDK_HOME` dans son sous-processus. `NDK_HOME` n'est donc pas ajouté comme prérequis utilisateur.
Le défaut réel est :
```text
Tauri CLI 2.11.3 -> gen/android/gradlew -> Gradle 8.14.3 -> Java 25
Unsupported class file major version 69
```
Le Gradle global de la machine n'est pas consommé par ce chemin ; modifier uniquement le `PATH` ne corrige donc pas le wrapper Tauri.
Le fix ajoute `crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs`. Avant chaque hook `dev`/`build`, ce script adapte idempotemment le projet généré `gen/android` vers la combinaison déjà adoptée par la migration Android upstream de Tauri :
```text
Gradle 9.6.1
Android Gradle Plugin 9.3.1
Kotlin Gradle Plugin 2.2.10
```
Il adapte aussi les ruptures Gradle 9 pertinentes du scaffold Tauri `2.11.x` :
- `Project.exec` devient `ExecOperations` dans `BuildTask.kt` ;
- les tâches Rust ciblées utilisent `tasks.register` et reçoivent explicitement `projectDir` ;
- le DSL Android/Kotlin de l'application est aligné sur le template Gradle 9 upstream ;
- les propriétés de compatibilité AGP 9 sont ajoutées ;
- les anciennes propriétés Android devenues des defaults sont retirées.
Le script est local à la crate Tauri et ne modifie ni le Gradle global ni le cache Cargo.
## Bridge généré par tauri-build
`tauri-build 2.6.3` régénère `gen/android/app/tauri.build.gradle.kts` pendant la compilation Rust, donc un patch effectué uniquement avant Cargo serait écrasé.
`build.rs` applique par conséquent, immédiatement après `tauri_build::build()`, la migration :
```text
val implementation by configurations
```
vers :
```text
val implementation = configurations.getByName("implementation")
```
lorsque le fichier Android généré existe. Le build script reste un no-op pour ce point lors des checks hôte sans `gen/android`.
## Hooks Tauri
`package.json` conserve Tauri comme propriétaire du pipeline :
```text
cargo tauri android dev
-> beforeDevCommand
-> npm run dev
-> android:prepare
-> WASM
-> Vite
```
et de même pour `beforeBuildCommand`.
`npm run dev` et `npm run build` ne deviennent pas des gates manuelles.
## Documentation et garde-fous
- `USAGE.md` retire `NDK_HOME` des prérequis utilisateur et explique la migration Gradle 9 ;
- le plan vivant enregistre le défaut et le fix sans ouvrir `0-pre.3` ;
- `audit_distribution_layout.py` exige désormais le hook Gradle, ses versions épinglées, la migration `ExecOperations` et le bridge `tauri-build` compatible Gradle 9 ;
- le test synthétique du script Node a été exécuté deux fois sur le même fixture pour vérifier son idempotence.
## Fichiers ajoutés
```text
crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs
history/0.3.1/0-pre.2.md
deltas/0.3.1/0-pre.2.fix.1.md
```
## Fichiers modifiés
```text
Cargo.toml
README.md
crates/apps/game-snake-poc-tauri/README.md
crates/apps/game-snake-poc-tauri/USAGE.md
crates/apps/game-snake-poc-tauri/build.rs
crates/apps/game-snake-poc-tauri/package.json
crates/apps/game-snake-poc-tauri/src/tauri.rs
docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md
scripts/audit_distribution_layout.py
```
## Suppressions
Aucune.
## Validation générateur
Les contrôles statiques applicables sont exécutés sur l'état livré. Les commandes Cargo/npm/Tauri/Gradle restent à exécuter côté utilisateur conformément à `CMD-GEN-007` et `CMD-BUILD-005`.
## Gate utilisateur
Depuis la racine :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Le build Web direct était déjà propre dans la gate `0-pre.2` et aucune de ses sources n'est modifiée par ce fix ; il n'est pas nécessaire de répéter sa gate pour ce correctif borné.
Le projet Android généré existe déjà chez l'utilisateur. Ne pas rejouer `cargo tauri android init`. Relancer directement :
```bash
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Le hook doit afficher avant le build Android :
```text
Prepared Tauri Android Gradle 9.6.1 / AGP 9.3.1 / Kotlin 2.2.10.
```
Puis le wrapper doit utiliser Gradle 9.6.1 au lieu de 8.14.3. Le smoke reste celui de `0-pre.2` : démarrage sur l'appareil/AVD, bridge Rust, chargement WASM, provenance `android / tauri-webview / wasm / unknown / unknown` et tracing sans erreur bloquante.
## Suite
Si cette gate est propre, `0-pre.2` est considéré réparé et la progression reprend sur `0.3.1-0-pre.3` conformément au plan vivant.

View File

@@ -0,0 +1,2 @@
crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs
history/0.3.1/0-pre.2.md

View File

@@ -0,0 +1,122 @@
<!-- file: deltas/0.3.1/0-pre.2.fix.2.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.2.fix.2
## Base
Base déclarée : `0.3.1-0-pre.2.fix.1`.
## Objet
Ce fix retire la migration Gradle 9 locale introduite par `fix.1`, revient au chemin Android officiellement généré par Tauri 2 stable et corrige l'audit workspace pour qu'il n'analyse pas le contenu généré sous `gen/` comme du code source du dépôt.
Il corrige également une violation documentaire de `fix.1` : `history/0.3.1/0-pre.2.md` avait été créé alors que `0-pre.2` n'était pas accepté. Aucune entrée `history/` n'est conservée pour ce jalon tant que sa gate n'est pas verte.
## Cause confirmée
Le smoke `0-pre.2` a établi trois faits distincts :
- Tauri sélectionne correctement le NDK side-by-side sous `$ANDROID_HOME/ndk/<version>` ; l'absence de `NDK_HOME` utilisateur n'est pas la cause du défaut ;
- le wrapper Android généré par Tauri CLI `2.11.3` utilise Gradle `8.14.3`, qui ne peut pas s'exécuter avec Java 25 ;
- `cargo tauri android init` crée légitimement `gen/android/app/src/main/assets`, mais l'audit `GAME-ASSET-001` parcourait jusque-là les répertoires générés.
La dernière CLI Tauri 2 stable vérifiée pendant ce fix est `2.11.5`. Son template Android conserve Gradle `8.14.3` et AGP `8.11.0`. La migration Gradle 9 existe upstream mais n'est pas encore publiée dans la branche stable Tauri 2 ; la reproduire par patch local ferait du projet games.sasedev le mainteneur d'un fork implicite du template Tauri.
## Correction Tauri Android
Le host Snake ne modifie plus `gen/android` :
- suppression de `scripts/prepare-android-gradle.mjs` ;
- suppression du hook `android:prepare` dans `package.json` ;
- restauration des hooks Tauri vers le seul pipeline WASM/Vite ;
- `build.rs` délègue uniquement à `tauri_build::build()` ;
- l'audit de distribution interdit désormais la réintroduction d'un patch `gen/android` dans ces deux surfaces.
Pour Tauri 2 stable, le contrat local devient celui recommandé par Tauri : `JAVA_HOME` doit pointer vers un JDK compatible avec le wrapper généré, de préférence le JBR livré avec Android Studio. Le Java 25 global peut rester installé ; il ne doit simplement pas être le JDK utilisé par les commandes Android Tauri tant que le wrapper stable ne le supporte pas.
Le Gradle global installé sous `/opt/gradle` n'est pas utilisé par `gen/android/gradlew` et n'entre pas dans le contrat de build.
## Correction de l'audit workspace
`gen/` est déjà un chemin généré et ignoré par `.gitignore`. `audit_project_workspace_rules.py` exclut désormais ces arbres pour les contrôles qui portent sur la propriété du code source :
- `GAME-ASSET-001` n'interdit plus les `assets` créés par le projet Android Tauri généré ;
- `GAME-ANDROID-001` n'interdit plus les éventuelles sources Java générées sous `gen/`.
Les assets et sources Java réellement versionnés hors des emplacements autorisés restent interdits.
## Historique
`history/0.3.1/0-pre.2.md` est supprimé. `DOC-HIST-002` et `DOC-HIST-003` imposent qu'une entrée d'historique ne soit créée qu'après acceptation effective du jalon. Le prochain delta ne recréera cette entrée que si la gate `0-pre.2.fix.2` est validée.
## Suppressions
Le manifest `deltas/0.3.1/0-pre.2.fix.2.delete.txt` supprime :
- `crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs` ;
- `history/0.3.1/0-pre.2.md`.
Après extraction du delta à la racine du dépôt, appliquer les suppressions avec :
```bash
while IFS= read -r path; do
rm -f -- "$path"
done < deltas/0.3.1/0-pre.2.fix.2.delete.txt
```
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `crates/apps/game-snake-poc-tauri/build.rs` ;
- `crates/apps/game-snake-poc-tauri/package.json` ;
- `crates/apps/game-snake-poc-tauri/README.md` ;
- `crates/apps/game-snake-poc-tauri/USAGE.md` ;
- `docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md` ;
- `scripts/audit_project_workspace_rules.py` ;
- `scripts/audit_distribution_layout.py`.
## Fichiers ajoutés
- `deltas/0.3.1/0-pre.2.fix.2.md` ;
- `deltas/0.3.1/0-pre.2.fix.2.delete.txt`.
## Validation statique du générateur
Le générateur peut exécuter les audits statiques et vérifier explicitement que les chemins générés Tauri ne déclenchent plus `GAME-ASSET-001` / `GAME-ANDROID-001`. Ces contrôles ne remplacent pas les gates build/runtime utilisateur.
## Gate utilisateur
Après application du delta et de son manifest de suppression :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Avant le smoke Android, sélectionner le JBR d'Android Studio dans le shell. Exemple si Android Studio est sous `/opt/android-studio` :
```bash
export JAVA_HOME=/opt/android-studio/jbr
"$JAVA_HOME/bin/java" -version
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Si Android Studio se trouve ailleurs, remplacer `/opt/android-studio/jbr` par son chemin réel. Ne pas exécuter `npm run dev` directement et ne pas rejouer `cargo tauri android init` pour ce fix lorsque `gen/android` existe déjà.
## Critère d'acceptation
Le fix est accepté lorsque :
- les trois audits Python sont propres malgré la présence du `gen/android` local ;
- Cargo/Clippy/tests ciblés sont propres ;
- `cargo tauri android dev` n'échoue plus sur `Unsupported class file major version 69` ;
- le host minimal atteint le smoke Android prévu par `0-pre.2`.

View File

@@ -0,0 +1,128 @@
<!-- file: deltas/0.3.1/0-pre.2.fix.3.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.2.fix.3
## Base
Base déclarée : `0.3.1-0-pre.2.fix.2`.
## Objet
Ce fix corrige la procédure Android après constat que le répertoire local `gen/android` avait été partiellement modifié par la tentative Gradle 9 abandonnée de `0-pre.2.fix.1`.
Aucun patch du projet Android généré n'est réintroduit. Le fix verrouille au contraire le contrat officiel Tauri 2.11.x et rend détectable tout `gen/android` local qui ne correspond plus à ce contrat.
## Diagnostic confirmé
Le smoke `0-pre.2.fix.2` a confirmé :
- les audits Python, Cargo, Clippy et les cinq tests `game-snake-poc-wasm` sont propres ;
- le NDK side-by-side est détecté sans `NDK_HOME` utilisateur ;
- le JBR de l'Android Studio local est Java 25, comme le Java global ;
- `gen/android/gradle/wrapper/gradle-wrapper.properties` utilisait encore Gradle `9.6.1`, trace de `fix.1` ;
- `buildSrc` restait partiellement au format Tauri 2.11.x et échouait donc sous Gradle 9 sur les appels `project.exec`/`exec`.
Le défaut n'est pas dans le host Rust/WASM : l'arbre Android généré local est incohérent et doit être régénéré.
## Toolchain Android retenu pour 0.3.1
Le projet conserve le template officiel Tauri 2.11.x :
```text
Gradle 8.14.3
Android Gradle Plugin 8.11.0
Kotlin Gradle Plugin 1.9.25
JDK de gate 17
```
AGP 8.11 exige JDK 17 au minimum et Gradle 8.14.x prend en charge l'exécution jusqu'à Java 24, pas Java 25. JDK 17 est donc la baseline reproductible retenue pour cette version.
Le Java 25 global et le JBR 25 d'Android Studio restent installables mais ne sont pas utilisés pour `cargo tauri android init/dev/build` dans `0.3.1`.
## Régénération obligatoire du projet Android local
Toute machine ayant exécuté `0-pre.2.fix.1` doit supprimer uniquement l'arbre généré Android Snake puis le recréer officiellement :
```bash
rm -rf crates/apps/game-snake-poc-tauri/gen/android
export JAVA_HOME=/chemin/vers/jdk-17
"$JAVA_HOME/bin/java" -version
(cd crates/apps/game-snake-poc-tauri && cargo tauri android init)
```
Le répertoire `gen/android` est ignoré et n'appartient à aucun delta. Sa suppression n'est donc pas un changement source.
## Audit ajouté
`audit_distribution_layout.py` vérifie désormais un `gen/android` présent sans le considérer comme source versionnée. Pour la tranche courante, il doit contenir les marqueurs du template Tauri 2.11.x : Gradle `8.14.3`, AGP `8.11.0` et Kotlin Gradle Plugin `1.9.25`.
Un arbre incomplet ou divergent provoque `DIST-LAYOUT-041/042` avec instruction de le supprimer puis de relancer `cargo tauri android init` sous JDK 17.
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `crates/apps/game-snake-poc-tauri/README.md` ;
- `crates/apps/game-snake-poc-tauri/USAGE.md` ;
- `docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md` ;
- `docs/validation/001-VALIDATION_GATES.md` ;
- `scripts/audit_distribution_layout.py`.
## Fichier ajouté
- `deltas/0.3.1/0-pre.2.fix.3.md`.
## Gate utilisateur
Après application du delta, commencer par disposer d'un JDK 17. Vérifier les JDK déjà installés avant d'en installer un nouveau :
```bash
find /usr/lib/jvm "$HOME/.jdks" "$HOME/.sdkman/candidates/java" -maxdepth 3 -type f -path '*/bin/java' 2>/dev/null
```
Sélectionner ensuite le JDK 17 réel :
```bash
export JAVA_HOME=/chemin/vers/jdk-17
"$JAVA_HOME/bin/java" -version
```
Nettoyer l'arbre Android pollué par `fix.1`, puis le régénérer avec Tauri :
```bash
rm -rf crates/apps/game-snake-poc-tauri/gen/android
(cd crates/apps/game-snake-poc-tauri && cargo tauri android init)
```
Rejouer ensuite la gate statique/build :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Puis le smoke Android :
```bash
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Ne pas exécuter `npm run dev` directement et ne modifier aucun fichier sous `gen/android` à la main.
## Critère d'acceptation
Le fix est accepté lorsque :
- les audits et gates Rust restent propres ;
- `audit_distribution_layout.py` accepte le `gen/android` fraîchement généré ;
- `gen/android/gradle/wrapper/gradle-wrapper.properties` référence Gradle `8.14.3` ;
- la commande Android utilise JDK 17 ;
- `cargo tauri android dev` assemble, installe et lance le host minimal sans erreur Gradle/Kotlin bloquante.

161
deltas/0.3.1/0-pre.2.md Normal file
View File

@@ -0,0 +1,161 @@
<!-- file: deltas/0.3.1/0-pre.2.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.2
## Base requise
`0.3.1-0-pre.1`, validée par l'utilisateur le 2026-09-20.
## Objectif
Créer le host Tauri Android minimal de Snake sans dupliquer le gameplay, sans créer un second adapter WASM et sans réintroduire de build orchestré par Python.
Cette tranche vise le premier démarrage Android et la validation des frontières Tauri/WASM/tracing. Le Canvas, les contrôles, les assets et le lifecycle complet restent réservés à `0-pre.3` et `0-pre.4` conformément au plan vivant.
## Audit Tauri KSP direct
L'archive fournie `khadhroony-solana-project-v0.3.15.zip` a été extraite et les six crates `ksp-app-*-desk` ont été vérifiées directement. Elles possèdent toutes les frontières structurantes retenues pour games.sasedev :
```text
src/lib.rs
src/tauri.rs
package.json
vite.config.ts
tauri.conf.json
capabilities/default.json
tauri-plugin-tracing côté Rust
@fltsci/tauri-plugin-tracing côté frontend
```
Le nouveau host Snake reprend cette organisation, avec un scope volontairement plus petit. Le Tauri Reflex historique reste seulement une référence de compatibilité : son orchestration Python n'est pas reproduite.
## Changements Rust et WASM
- passage du workspace à `0.3.1-0-pre.2` ;
- ajout de `game-snake-poc-tauri` au workspace ;
- création de la façade `src/lib.rs`, de l'assemblage `src/tauri.rs` et du module propriétaire `src/runtime.rs` ;
- ajout du point d'entrée mobile `#[cfg_attr(mobile, tauri::mobile_entry_point)]` ;
- initialisation du subscriber partagé via `game-logging-lib` avant le premier événement Rust ;
- installation de `tauri-plugin-tracing` ;
- généralisation minimale de `game-snake-poc-wasm::configure_runtime_provenance` afin que le même adapter accepte explicitement la famille et le host observés ;
- conservation de `ExecutionModel::Wasm` dans l'adapter ;
- adaptation du Web direct pour fournir explicitement `web / browser` ;
- ajout d'un test ciblé `android / tauri-webview / wasm` ;
- généralisation de la rustdoc `RuntimeHost::TauriWebView`, qui n'est plus décrite comme Desktop uniquement.
## Frontend et build Tauri
La nouvelle app contient :
- `package.json`, `tsconfig.json` et `vite.config.ts` ;
- un frontend minimal sous `frontend/` ;
- un bridge IPC vers le descriptor runtime Rust ;
- le chargement des bindings produits depuis `game-snake-poc-wasm` ;
- le tracing frontend via `@fltsci/tauri-plugin-tracing` ;
- `tauri.conf.json` et `capabilities/default.json` ;
- un icon POC cohérent avec le host Tauri historique ;
- `README.md` et `USAGE.md` initiaux.
Les hooks Tauri exécutent eux-mêmes le build Cargo WASM, `wasm-bindgen` puis Vite. Les outputs restent sous `../builds/sasedev-games/`. Aucun script Python n'est impliqué dans ce chemin.
`cargo tauri android init` reste une opération locale de la gate : son répertoire `gen/` est généré et ignoré, donc il n'est pas distribué dans le delta.
## NDK et JDK
La machine de validation possède plusieurs NDK sous `$ANDROID_HOME/ndk/`; `NDK_HOME` n'est donc pas imposé globalement par le dépôt. Si la CLI Tauri exige une valeur explicite pendant `android init/dev`, utiliser ponctuellement le chemin exact de la version NDK concernée.
`JAVA_HOME` reste également facultatif tant que `java` est résolu correctement par la CLI dans l'environnement courant. Les deux variables deviennent des outils de diagnostic local, pas des invariants source du projet.
## Audit de distribution
`audit_distribution_layout.py` couvre désormais le host Tauri Snake minimal : fichiers requis, hooks Tauri, outputs WASM externes au dépôt, point d'entrée mobile, bridge tracing, provenance Android/TauriWebView et interdiction de Python/`console.*` dans le chemin touché.
## Fichiers ajoutés
```text
crates/apps/game-snake-poc-tauri/Cargo.toml
crates/apps/game-snake-poc-tauri/README.md
crates/apps/game-snake-poc-tauri/USAGE.md
crates/apps/game-snake-poc-tauri/build.rs
crates/apps/game-snake-poc-tauri/capabilities/default.json
crates/apps/game-snake-poc-tauri/frontend/css/main.css
crates/apps/game-snake-poc-tauri/frontend/main.html
crates/apps/game-snake-poc-tauri/frontend/ts/bridge.ts
crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts
crates/apps/game-snake-poc-tauri/frontend/ts/main.ts
crates/apps/game-snake-poc-tauri/icons/icon.png
crates/apps/game-snake-poc-tauri/package.json
crates/apps/game-snake-poc-tauri/src/lib.rs
crates/apps/game-snake-poc-tauri/src/main.rs
crates/apps/game-snake-poc-tauri/src/runtime.rs
crates/apps/game-snake-poc-tauri/src/tauri.rs
crates/apps/game-snake-poc-tauri/tauri.conf.json
crates/apps/game-snake-poc-tauri/tsconfig.json
crates/apps/game-snake-poc-tauri/vite.config.ts
history/0.3.1/0-pre.1.md
```
## Fichiers modifiés
```text
Cargo.toml
README.md
Web/game-snake-poc/frontend/ts/provenance.ts
crates/apps/game-snake-poc-wasm/src/runtime.rs
crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
crates/engines/engine-v1-platform-api/src/environment.rs
docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md
scripts/audit_distribution_layout.py
```
## Validation générateur
Les audits statiques applicables sont exécutés avant livraison. Aucun `cargo`, `npm`, `wasm-bindgen`, Gradle, Tauri Android ou smoke n'est déclaré validé par le générateur.
## Gate utilisateur
Appliquer d'abord la gate workspace :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Comme l'API de provenance partagée est utilisée par le Web direct, reconstruire ses bindings et son frontend :
```bash
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
(cd Web/game-snake-poc && npm install && npm run build)
```
Puis initialiser et lancer le nouveau host par Tauri, sans exécuter ses scripts npm de build directement :
```bash
(cd crates/apps/game-snake-poc-tauri && npm install)
(cd crates/apps/game-snake-poc-tauri && cargo tauri android init)
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Critères du smoke `android dev` :
- l'app démarre sur l'appareil/AVD choisi ;
- le frontend affiche `android / tauri-webview / wasm / unknown / unknown` ;
- le bridge Rust répond ;
- le frontend charge `game-snake-poc-wasm` ;
- les logs Rust et frontend atteignent le pipeline tracing/logcat ;
- aucune erreur runtime bloquante n'est visible.
`cargo tauri android build` reste reporté au jalon de packaging prévu par le plan.
## Suite après validation
Si la gate est propre, passer automatiquement à `0.3.1-0-pre.3` pour intégrer le Canvas Snake, les inputs et les assets dans le WebView Tauri. Tout défaut source de `0-pre.2` produit d'abord un `0-pre.2.fix.N` borné.

152
deltas/0.3.1/0-pre.3.md Normal file
View File

@@ -0,0 +1,152 @@
<!-- file: deltas/0.3.1/0-pre.3.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.3
## Base
Base déclarée : `0.3.1-0-pre.2.fix.3`, validée par l'utilisateur le 2026-09-20.
## Objet
Cette tranche transforme le host Tauri Android minimal en POC Snake visuellement jouable : Canvas, boucle de simulation fixed-step, contrôles clavier/touch, assets canoniques et shell responsive.
Elle enregistre aussi la décision issue du smoke précédent : Tauri Android est conservé comme POC de référence, mais la voie Android de production reste SDL3 natif tant qu'un besoin fonctionnel explicite ne justifie pas la WebView.
## Frontend Tauri Snake
Le host ajoute des modules propriétaires séparés :
```text
frontend/ts/assets.ts validation des assets common/game
frontend/ts/game.ts projection Canvas + fixed-step WASM
frontend/ts/input.ts clavier + pointer/touch
frontend/ts/provenance.ts device/input observés pour le host Tauri
```
`main.ts` reste l'orchestrateur frontend et ne réimplémente aucune règle Snake. Le gameplay continue de résider dans `game-snake-poc` et le même `game-snake-poc-wasm` reste consommé par le navigateur direct et la WebView Tauri.
Le shell reste volontairement léger : CSS local, aucun Bootstrap/SimpleBar/ResizeObserver ajouté. Le Canvas conserve le ratio logique `3 / 5` du Snake et les boutons directionnels sont utilisables au tactile/pointer.
## Assets
`vite-plugin-static-copy` est ajouté au package Tauri pour exposer exactement les deux assets canoniques :
```text
assets/common/data/runtime.json -> common/data/runtime.json
assets/game-snake-poc/data/game.json -> game/data/game.json
```
Les assets restent hors des crates Rust et aucun duplicat runtime n'est créé sous `frontend/` ou `gen/android`.
## Provenance
Le backend Rust fournit toujours les dimensions statiques `Android / TauriWebView / Wasm`. Le frontend observe désormais la classe de device et les sources d'input pour configurer l'adapter partagé avec `phone/tablet/desktop/unknown` et `touch/keyboard-mouse/mixed/unknown`.
La stabilisation lifecycle/background/foreground appartient encore à `0-pre.4`.
## Vite dev
La configuration WebSocket garde `server.ws`, mécanisme Vite 8 courant. Sur l'émulateur sans `TAURI_DEV_HOST`, le client HMR utilise le même port `1436` que le serveur dev/forwarding Tauri ; avec un host réseau explicite, le port HMR séparé `1437` reste utilisé.
Cette correction vise le warning HMR observé au smoke précédent mais n'est pas un critère fonctionnel du gameplay.
## Positionnement Tauri après le POC minimal
La documentation durable et la roadmap sont réconciliées :
- Tauri Android reste un POC de référence ;
- SDL3 natif reste la cible Android productive par défaut ;
- les futurs jeux ne reçoivent pas automatiquement une app Tauri Android ;
- la saisie texte/WebView reste une hypothèse à comparer aux capacités SDL réelles ;
- Tauri Desktop devient une évaluation conditionnelle, notamment selon la viabilité d'une monétisation publicitaire WebView.
Aucune extraction Web/Tauri commune n'est créée dans cette tranche. Le statut de POC de référence réduit au contraire la valeur d'une abstraction qui n'aurait pas au moins deux consommateurs durablement retenus.
## Historique créé
Conformément à `DOC-HIST-002/003`, la tranche crée :
```text
history/0.3.1/0-pre.2.fix.3.md
```
qui enregistre uniquement les validations réellement fournies pour le jalon précédent.
## Fichiers principaux modifiés
- `Cargo.toml` ;
- `README.md` ;
- `ROADMAP.md` ;
- `docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md` ;
- `docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md` ;
- `crates/apps/game-snake-poc-tauri/README.md` ;
- `crates/apps/game-snake-poc-tauri/USAGE.md` ;
- `crates/apps/game-snake-poc-tauri/frontend/main.html` ;
- `crates/apps/game-snake-poc-tauri/frontend/css/main.css` ;
- `crates/apps/game-snake-poc-tauri/frontend/ts/main.ts` ;
- `crates/apps/game-snake-poc-tauri/package.json` ;
- `crates/apps/game-snake-poc-tauri/vite.config.ts` ;
- `scripts/audit_distribution_layout.py`.
## Fichiers ajoutés
- `crates/apps/game-snake-poc-tauri/frontend/ts/assets.ts` ;
- `crates/apps/game-snake-poc-tauri/frontend/ts/game.ts` ;
- `crates/apps/game-snake-poc-tauri/frontend/ts/input.ts` ;
- `crates/apps/game-snake-poc-tauri/frontend/ts/provenance.ts` ;
- `history/0.3.1/0-pre.2.fix.3.md` ;
- `deltas/0.3.1/0-pre.3.md`.
## Gate utilisateur
Conserver JDK 17 pour les commandes Tauri Android :
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
"$JAVA_HOME/bin/java" -version
```
Le package Tauri possède une nouvelle dépendance frontend ; mettre à jour ses dépendances sans lancer manuellement son build :
```bash
(cd crates/apps/game-snake-poc-tauri && npm install)
```
Puis exécuter :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Le smoke frontend reste possédé par Tauri :
```bash
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Ne pas exécuter `npm run dev` ou `npm run build` manuellement.
## Critère d'acceptation
Sur l'AVD Android :
- le Canvas Snake est visible avec un ratio `3 / 5` ;
- le Snake avance automatiquement ;
- score et longueur sont visibles ;
- les quatre boutons directionnels répondent au pointer/tactile ;
- le clavier fonctionne lorsqu'il est disponible ;
- `Assets` affiche `snake / engine-v1` ;
- la provenance est `android / tauri-webview / wasm / <device> / <input>` et évolue après une entrée ;
- aucune règle gameplay n'est dupliquée dans TypeScript ;
- aucun fichier sous `gen/android` n'est modifié par le projet.
Le background/foreground, la reprise sans catch-up, la rotation consolidée et Back restent les objectifs explicites de `0-pre.4`.

View File

@@ -0,0 +1,94 @@
<!-- file: deltas/0.3.1/0-pre.4.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.4.fix.1
## Base
Base déclarée : `0.3.1-0-pre.4`.
## Objet
Corriger le chemin de développement Tauri Android sur appareil réel après la gate ARM64 et formaliser la limite observée lors du teardown WebView/Activity, sans introduire de fork local de Tauri/Wry ni modifier le projet Android généré.
## Résultats de la gate `0-pre.4`
La gate workspace et le smoke AVD x86_64 sont propres : audits, formatage, `cargo check`, Clippy strict et les cinq tests `game-snake-poc-wasm` passent. Le lifecycle journalise correctement `runtime paused/resumed` et `visibility hidden/visible`.
Le smoke supplémentaire sur appareil Android ARM64 réel a confirmé :
- ABI Android `arm64-v8a` et cible Rust `aarch64-linux-android` ;
- compilation du `cdylib` ARM64 ;
- assemblage, streamed install et démarrage de `MainActivity` ;
- chargement des assets ;
- initialisation WASM ;
- Canvas jouable et touch observé ;
- provenance `android / tauri-webview / wasm / phone / touch` ;
- pause lifecycle avant sortie.
Deux écarts ont été révélés.
Premièrement, le mode `dev` sur téléphone physique remplace automatiquement `localhost` par une adresse LAN. Le tunnel USB ADB `reverse tcp:1436 tcp:1436` associé à `cargo tauri android dev --host 127.0.0.1` rend le chargement HTTP reproductible sans dépendre du LAN.
Deuxièmement, le frontend utilisait encore un WebSocket HMR séparé sur le port `1437`. Le HTML/WASM se chargeait, mais Vite journalisait `failed to connect to websocket`. Ce défaut appartient au projet et est corrigé par ce fix : HTTP et WebSocket Vite partagent désormais le port `1436`, qui est le port tunnelé par ADB.
Enfin, le Back système sur le Galaxy S9+ de validation a provoqué pendant le teardown natif :
```text
FORTIFY: pthread_mutex_lock called on a destroyed mutex
```
L'abort arrive après `runtime paused` / `visibility hidden` et avant `pagehide` / `runtime disposed`. La gate résout Tauri `2.11.5` avec Wry `0.55.1`. Des versions Wry postérieures ont modifié le lifecycle Android et la synchronisation des request handlers, mais le projet ne force pas une version Wry incompatible derrière la pile Tauri stable ni ne patch `gen/android`. Ce crash reste une limite externe du POC à retester avec l'APK autonome de la beta.
## Changements
- version workspace : `0.3.1-0-pre.4.fix.1` ;
- WebSocket Vite/HMR fixé sur le même host de développement et le même port `1436` que HTTP ;
- suppression du port HMR séparé `1437` ;
- procédure de smoke appareil réel documentée avec tunnel ADB USB et `--host 127.0.0.1` ;
- correspondance ABI `arm64-v8a` / `aarch64-linux-android` documentée ;
- crash Back/teardown réel enregistré comme limite du POC et non comme capacité validée ;
- audit de distribution renforcé pour refuser le retour d'un second port WebSocket non tunnelé.
## Invariants
- aucun fichier sous `gen/android` n'est modifié ou livré ;
- aucun handler `onBackButtonPress` n'est ajouté pour masquer le crash de teardown ;
- aucune surcharge manuelle de Wry/Tauri n'est introduite ;
- aucun gameplay ne quitte Rust/WASM ;
- SDL3 natif reste la direction Android de production ;
- l'APK autonome de `2-beta.1` doit distinguer un défaut du mode `dev` d'un défaut général de teardown Tauri/Wry/WebView.
## Gate utilisateur
Gate statique et Rust :
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
```
Smoke appareil réel, avec le `<serial>` fourni par `adb devices -l` :
```bash
adb -s <serial> reverse --remove-all
adb -s <serial> reverse tcp:1436 tcp:1436
adb -s <serial> reverse --list
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev --host 127.0.0.1)
```
Vérifier : Canvas/inputs/assets/WASM/provenance/lifecycle et absence de l'erreur Vite `failed to connect to websocket`. Tester Back une fois et conserver le résultat ; si l'abort natif documenté reste reproductible, il n'est pas contourné dans ce fix et sera retesté sur l'APK autonome en beta.
## Suite prévue
Aucune extraction `0-pre.5` n'est justifiée. Après validation de ce fix, passer directement à `0.3.1-2-beta.1` pour la gate large, le build WASM release, le packaging APK et les smokes package AVD + ARM64 réel.

54
deltas/0.3.1/0-pre.4.md Normal file
View File

@@ -0,0 +1,54 @@
<!-- file: deltas/0.3.1/0-pre.4.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.4
## Objet
Fermer le comportement runtime du POC Snake Tauri Android après validation du Canvas/input/assets de `0-pre.3`, sans élargir la cible vers une distribution Android de production.
## Changements
- version workspace : `0.3.1-0-pre.4` ;
- historique de validation `0-pre.3` ajouté ;
- nouvelle liaison lifecycle Tauri dédiée : `visibilitychange`, `pagehide`, `pageshow`, `ResizeObserver` ;
- session Canvas dotée de `pause()` / `resume()` avec annulation du frame pending et remise à zéro de l'accumulateur ;
- reprise sans simulation du temps passé en background ;
- refresh de la provenance device lors des reprises et resize/rotation ;
- tracing frontend des transitions lifecycle ;
- Back Android volontairement non intercepté pour le Snake mono-page dont la politique moteur est `Exit` ;
- limite ergonomique du D-pad sous le Canvas documentée sans ajouter swipe/overlay hors scope ;
- Tauri Desktop marqué différé tant qu'une voie de monétisation WebView/desktop n'est pas démontrée.
## Invariants
- aucune logique gameplay n'entre dans TypeScript ;
- `game-snake-poc-wasm` reste l'unique adapter WASM Snake ;
- aucun fichier `gen/android` n'est versionné ou patché ;
- aucun handler `onBackButtonPress` n'est installé par ce POC ;
- aucun SDK publicitaire n'est déclaré comme compatible sans POC technique et vérification contractuelle ;
- SDL3 natif reste la direction Android de production.
## Validation demandée
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Smoke manuel : entrée tactile, background/foreground sans rattrapage, rotation/resize, provenance `phone / touch`, traces lifecycle, puis Back Android naturel.
## Suite prévue
Si cette gate est propre, `0-pre.5` est omise sauf duplication durable nouvellement démontrée. La version passe à `0.3.1-2-beta.1` pour validation large et packaging du POC de référence.

76
deltas/0.3.1/2-beta.1.md Normal file
View File

@@ -0,0 +1,76 @@
<!-- file: deltas/0.3.1/2-beta.1.md -->
<!-- version: 1 -->
# Delta 0.3.1-2-beta.1
## Objectif
Entrer en beta avec le POC Snake Tauri Android fonctionnel de bout en bout et exécuter la validation large ainsi que le premier packaging APK autonome, sans ajouter de fonctionnalité.
## Baseline acceptée
`0.3.1-0-pre.4.fix.1` est validée sur AVD x86_64 et appareil ARM64 réel. Les audits/check/Clippy/tests ciblés passent, HTTP et HMR partagent le tunnel `1436`, le frontend atteint `ready` et le touch est observé. L'abort natif Back vu pendant un smoke antérieur n'est pas reproduit lors de la gate `fix.1`; il reste à caractériser sur package autonome.
Aucune extraction `0-pre.5` n'est justifiée : Tauri Android reste un POC de référence et ne devient pas une voie de production.
## Changements
- passage de `workspace.package.version` à `0.3.1-2-beta.1` ;
- passage du package frontend Tauri Snake au jalon `0.3.1-2-beta.1` conformément au workflow Tauri/frontend ;
- ajout de l'historique validé `0-pre.4.fix.1` ;
- réconciliation du plan et des gates avec la validation réelle ARM64 ;
- ajout de la procédure de packaging beta APK Debug x86_64 + ARM64 ;
- maintien explicite de l'absence d'AAB à ce jalon.
Aucun gameplay, adapter WASM, bridge Tauri, lifecycle, input, asset ou scaffold Android généré n'est modifié par cette tranche.
## Validation beta attendue
Depuis la racine :
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
Installer/actualiser les dépendances frontend, puis laisser Tauri posséder entièrement le build WASM release + bindings + TypeScript/Vite :
```bash
(cd crates/apps/game-snake-poc-tauri && npm install)
(cd crates/apps/game-snake-poc-tauri && \
cargo tauri android build --debug --apk --target aarch64 --target x86_64)
```
Ne pas lancer `npm run build` séparément. Le build Tauri doit terminer en affichant le chemin de l'APK produit. Aucun AAB n'est requis pour cette beta.
## Contrôle de l'APK
À partir du chemin `<apk>` affiché par Tauri :
```bash
unzip -l <apk> | grep -E 'lib/(arm64-v8a|x86_64)/libgame_snake_poc_tauri_lib\.so'
```
Les deux ABI doivent être présentes. Le package est ensuite installé successivement sur l'AVD x86_64 et l'appareil ARM64 réel :
```bash
adb -s <serial> install -r <apk>
adb -s <serial> shell am start -n com.sasedev.games.snake.tauri/.MainActivity
```
Le smoke autonome vérifie au minimum : Canvas, assets `snake / engine-v1`, WASM, contrôles, provenance, background/foreground, reprise sans rattrapage, rotation/resize si disponible et plusieurs sorties Back. Le package ne doit dépendre ni de Vite, ni du port `1436`, ni de `adb reverse`.
## Après validation
Si la gate beta et les deux smokes APK sont propres, `2-beta.2` reste omise sauf besoin documentaire substantiel. La tranche suivante est alors `0.3.1-3-rc.1`, avec gel fonctionnel, packaging RC et matrice finale x86_64 + ARM64.

64
deltas/0.3.1/3-rc.1.md Normal file
View File

@@ -0,0 +1,64 @@
<!-- file: deltas/0.3.1/3-rc.1.md -->
<!-- version: 1 -->
# Delta 0.3.1-3-rc.1
## Base requise
`0.3.1-2-beta.1`, validée le 2026-09-21.
## Objectif
Créer la candidate de publication `0.3.1` sans rouvrir le scope fonctionnel du POC Snake Tauri Android.
## Gel RC
Le scope `0.3.1` est gelé. Cette tranche n'ajoute :
- aucune fonctionnalité ;
- aucun nouveau contrôle ;
- aucun framework ou refactor Web/Tauri ;
- aucune dépendance ;
- aucun changement de gameplay ;
- aucun AAB Tauri ;
- aucun patch du scaffold `gen/android`.
Seuls les correctifs autorisés par `VER-RC-*` peuvent suivre sous `3-rc.1.fix.N`.
## Validation beta acquise
`2-beta.1` a validé :
- audits/check/Clippy workspace ;
- `cargo test --workspace --all-targets --all-features` avec 35 tests réussis ;
- build WASM/Vite possédé par Tauri ;
- APK Debug universal ;
- compilation Android `aarch64-linux-android` + `x86_64-linux-android` ;
- installation/lancement du même APK sur Galaxy S9+ ARM64 et AVD API 36 x86_64.
Le contrôle mécanique `unzip -l` des deux entrées ABI n'a pas été transmis et reste dans la gate RC.
## Changements
- passage de `workspace.package.version` à `0.3.1-3-rc.1` ;
- synchronisation du package frontend Tauri Snake à `0.3.1-3-rc.1` ;
- entrée RC dans `CHANGELOG.md` ;
- historique de la beta validée ;
- consolidation du plan `0.3.1` avec omission de `2-beta.2` ;
- création de `docs/testing/005-V0_3_1_RC_VALIDATION_MATRIX.md` ;
- préparation de `prompts/004-V0_3_3_START_PROMPT.md`, puisque `0.3.2` reste différée ;
- mise à jour de la roadmap pour réserver à `0.3.3` le pipeline SDL3 natif multi-ABI, l'AAB et l'évaluation du support d'anciennes versions Android.
## Validation RC
Appliquer intégralement :
```text
docs/testing/005-V0_3_1_RC_VALIDATION_MATRIX.md
```
La gate revalide le workspace complet, reconstruit l'APK autonome universal et le fume sur x86_64 + ARM64. Aucun `npm run build` manuel et aucun AAB ne sont requis.
## Après validation
Si la RC est propre, la promotion vers `0.3.1` est mécanique : versions stables, entrée stable du changelog, historique RC, clôture du plan et delta de release. Aucun nouveau comportement ne doit être introduit.

56
deltas/0.3.1/rel.001.md Normal file
View File

@@ -0,0 +1,56 @@
<!-- file: deltas/0.3.1/rel.001.md -->
<!-- version: 1 -->
# Delta 0.3.1 — release stable
## Base
Base validée : `0.3.1-3-rc.1`.
## Objet
Promouvoir mécaniquement la candidate validée vers `0.3.1` sans introduire de nouveau comportement.
## Changements
La release stable :
- passe `workspace.package.version` de `0.3.1-3-rc.1` à `0.3.1` ;
- passe le package frontend Tauri Snake de `0.3.1-3-rc.1` à `0.3.1` ;
- positionne `README.md` sur la stable `0.3.1` et `0.3.3` comme prochaine version active planifiée, `0.3.2` restant différée ;
- marque `0.3.1` comme terminée dans `ROADMAP.md` ;
- ajoute l'entrée stable `0.3.1` dans `CHANGELOG.md` ;
- enregistre la validation effective de la RC dans `history/0.3.1/3-rc.1.md` ;
- clôt le plan `0.3.1` et confirme que l'AAB/compatibilité Android ancienne appartiennent au POC SDL3 natif `0.3.3`.
Le prompt `prompts/004-V0_3_3_START_PROMPT.md` est conservé tel que préparé et validé en RC.
## Frontière de release
Aucun changement de gameplay, de code Rust, de dépendance, de configuration exécutable, de frontend, de build Android, d'architecture ou de capability n'est introduit après la RC.
## Validation
La gate RC complète a déjà été validée sur un état fonctionnellement identique. La promotion stable ne requiert que les gates proportionnelles aux changements de versions et de documentation :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
(cd crates/apps/game-snake-poc-tauri && npm install)
(cd crates/apps/game-snake-poc-tauri && cargo tauri android build --debug --apk --target aarch64 --target x86_64)
```
Le build Tauri reste la seule validation frontend de release : ne pas lancer `npm run build` manuellement. Les smokes AVD/ARM64, Clippy strict et la suite workspace complète ne sont pas répétés, car aucun code/runtime n'a changé depuis la RC validée.
## Publication
Après validation de ce delta :
- commit de release ;
- tag stable `v0.3.1` ;
- prochaine session ouverte uniquement à partir de la stable taggée `v0.3.1` et de `prompts/004-V0_3_3_START_PROMPT.md` ;
- ne pas créer artificiellement `0.3.2` tant que son critère de réouverture n'est pas satisfait.

204
deltas/0.3.3/0-pre.1.md Normal file
View File

@@ -0,0 +1,204 @@
<!-- file: deltas/0.3.3/0-pre.1.md -->
<!-- version: 1 -->
# Delta 0.3.3-0-pre.1
## Base
Base autoritaire : archive fournie `games-v0.3.1.zip`, téléchargée depuis le lien ZIP du tag Gitea `v0.3.1`.
La version workspace de la base est `0.3.1`. L'archive taggée ne contient pas `.git`; aucun état Git absent n'est inventé et aucune autre branche/version n'est utilisée comme source.
`0.3.2` reste différée conformément au prompt de reprise.
## Objet
Ouvrir `0.3.3` par son gate obligatoire `0-pre.1` : audit complet de la baseline et des règles, audit du pipeline Android SDL3 natif historique, recherche officielle actuelle, choix ABI/API, cadrage APK/AAB, contrainte pages mémoire 16 KB, sizing, risques, validations et plan vivant.
Cette tranche n'introduit volontairement aucune nouvelle tâche Gradle productive et ne modifie pas le comportement Android. La réécriture du chemin de build commence seulement en `0-pre.2`.
## Version
La version workspace passe de :
```text
0.3.1
```
à :
```text
0.3.3-0-pre.1
```
Les versions npm/Tauri et `versionName` Android ne sont pas synchronisées dans cette tranche de cadrage. Les produits concernés n'ont pas encore reçu de changement de packaging et `VER-TAURI-003`/`VER-TAURI-004` interdisent une synchronisation npm cérémonielle.
## Audit de l'archive
Avant modification, l'environnement de génération a obtenu :
```text
unzip -t : no errors
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 222 file(s))
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
```
Le ZIP contient 378 fichiers, aucun lien symbolique, aucun fichier vide et aucun `.git`, `target/`, `node_modules/` ou `gen/android/` généré.
La sortie utilisateur initiale sur son arbre local est également propre pour les trois audits, avec 230 fichiers Markdown et un `cargo check --workspace` réussi. Elle montre ensuite un build Tauri Android Debug universal réussi pour ARM64 et x86_64. Le compte Markdown local supérieur au ZIP taggé est compatible avec un `gen/android/` Tauri déjà présent localement ; le ZIP fourni reste la source autoritaire et aucun fichier généré n'est ajouté au delta.
## Audit des règles
La revue demandée par `prompts/004-V0_3_3_START_PROMPT.md` confirme notamment :
- `0-pre.1` doit contenir le cadrage et le plan avant le développement lourd ;
- une prerelease non-fix synchronise la version workspace ;
- un nouveau chemin de build `0.3.x` ne doit pas être piloté par Python ;
- Gradle doit posséder l'artefact Android final et les outputs générés doivent rester hors sources versionnées ;
- toute modification Rust/Cargo déclenche fmt, audit, check workspace et Clippy strict ;
- les builds, tests et smokes finaux sont attestés côté utilisateur ;
- les tests workspace complets restent des gates rares et planifiées ;
- aucun historique `history/0.3.3/0-pre.1.md` n'est créé avant validation effective de cette tranche.
Aucune contradiction bloquante n'est trouvée entre le prompt, les règles et la baseline `v0.3.1`. L'audit documentaire relève toutefois un paragraphe obsolète dans `Android/README.md`, resté au vocabulaire `0.1.0-0-pre.9` et affirmant que le NDK n'était pas encore utilisé ; cette tranche le réaligne sur la réalité stable `0.3.1` sans changer le build.
## Pipeline Android hérité
La baseline configure :
```text
AGP 9.4.0
Gradle 9.6.0 attendu
JDK 17
compileSdk 36
targetSdk 36
minSdk 21
NDK 28.2.13676358 / r28c
SDL3 AAR 3.4.16
```
`scripts/build_android_rust.py` réalise aujourd'hui l'extraction/linkage SDL3 par ABI, la résolution NDK, `cargo ndk`, la sélection d'une unique feature jeu, le staging dans `src/main/jniLibs` et la vérification de `libgame_android_entrypoint.so`.
Ces responsabilités doivent être transposées dans la logique Gradle commune, avec outputs sous `build/generated/...`, avant suppression du script.
## Recherche et décisions 0-pre.1
Les sources officielles actuelles confirment :
- AGP 9.4.0 : Gradle 9.6.0, JDK 17 et NDK par défaut `28.2.13676358` ;
- SDL3 Android actuel : SDK 35+, NDK r28c+ et API minimale 21 ;
- SDL3 3.4.16 : release stable actuelle de la baseline ;
- Google Play : API 36+ requise depuis le 31 août 2026 pour nouvelles apps/mises à jour téléphone/tablette ;
- application native ciblant API 35+ : compatibilité pages mémoire 16 KB à assurer ; NDK r28+ produit l'alignement 16 KB par défaut ;
- AAB : format de distribution, dont le store dérive des APK optimisés, notamment par ABI.
Les choix retenus sont donc :
```text
minSdk candidat 21
ABI par défaut arm64-v8a + x86_64
armeabi-v7a différée
APK local universal multi-ABI
AAB distribution
splits ABI non par défaut
NDK natif r28c épinglé, distinct du NDK 30 observé dans le POC Tauri
```
Le détail, les sources et les risques sont consignés dans `docs/studies/025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md`.
## Plan créé
`docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md` devient le plan actif avec le forecast révisé :
```text
0-pre.1 audit / règles / ABI-API / minSdk / APK-AAB / 16 KB / plan
0-pre.2 ownership Gradle/Cargo Snake sur arm64-v8a + wrapper Gradle
0-pre.3 arm64-v8a + x86_64 + APK universal + Reflex si factorisation naturelle
0-pre.4 AAB + minSdk/smokes + 16 KB + fermeture du script Python historique
2-beta.1 validation large + packaging final
3-rc.1 gel + reproductibilité + consolidation documentaire
0.3.3 promotion mécanique stable
```
Le scope reste compatible avec une seule version/session tant qu'aucune incompatibilité structurante de Gradle/cargo-ndk/SDL3 n'est découverte.
## Fichiers
Ajoutés :
```text
docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md
docs/studies/025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md
deltas/0.3.3/0-pre.1.md
```
Modifiés :
```text
Android/README.md
Cargo.toml
README.md
docs/000-README.md
docs/plans/000-README.md
docs/studies/000-README.md
```
Aucun fichier Gradle, Rust source, règle normative, roadmap, changelog ou historique n'est modifié dans ce cadrage. `Android/README.md` reçoit uniquement la correction documentaire de baseline décrite ci-dessus.
## Validations exécutées dans l'environnement de génération
Après constitution de l'état livré, exécuter uniquement les audits statiques autorisés au générateur :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
```
Aucun build Cargo/Gradle, test ou smoke final n'est attribué au générateur.
## Validation utilisateur demandée
Le manifest workspace change de version ; la gate minimale est donc :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Aucun test workspace complet n'est demandé : cette tranche ne change aucun code Rust, comportement, dépendance ou configuration Android productive.
La première gate du prompt `004` demande également l'inventaire frais qui conditionne `0-pre.2` :
```bash
rustc --version
cargo --version
cargo ndk --version
rustup target list --installed
java -version
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
adb version
adb devices -l
emulator -list-avds
(cd Android && gradle --version)
printf 'configured NDK: '; sed -n 's/^androidNdkVersion=//p' Android/gradle.properties
ls -ld "$ANDROID_HOME/ndk/28.2.13676358"
test -f Android/libs/SDL3-3.4.16.aar && sha256sum Android/libs/SDL3-3.4.16.aar
```
La sortie déjà fournie confirme JDK 17 sélectionné via `JAVA_HOME`, les targets Tauri ARM64/x86_64 suffisamment opérationnelles pour produire un APK universal et un NDK 30 utilisé par Tauri. Elle ne remplace pas l'inventaire du pipeline Android natif ci-dessus.
## Suite après validation
Si la gate est propre, la tranche suivante crée `history/0.3.3/0-pre.1.md` avec les sorties réellement obtenues puis ouvre `0.3.3-0-pre.2`.
`0-pre.2` doit d'abord introduire le Gradle Wrapper et prouver sur Snake/ARM64 que `:game-snake-poc:assembleDebug` déclenche lui-même le build Rust/SDL3 sans `scripts/build_android_rust.py`. Une défaillance de la gate ou une erreur du cadrage produit d'abord `0-pre.1.fix.N`.

180
deltas/0.3.3/0-pre.2.md Normal file
View File

@@ -0,0 +1,180 @@
<!-- file: deltas/0.3.3/0-pre.2.md -->
<!-- version: 1 -->
# Delta 0.3.3-0-pre.2
## Base et statut précédent
Cette tranche part exclusivement de `0.3.3-0-pre.1` validé par l'utilisateur le 2026-09-21. `history/0.3.3/0-pre.1.md` enregistre la gate propre et l'inventaire Android réellement observé avant ouverture du développement productif.
Aucun fix de `0-pre.1` n'est requis.
## Objectif
Prouver sur Snake Debug et une seule ABI que Gradle possède désormais la chaîne native complète :
```text
:game-snake-poc:assembleDebug
-> source jniLibs générée par AGP Variant API
-> extraction de libSDL3.so depuis l'AAR
-> résolution du NDK projet r28c
-> cargo ndk arm64-v8a
-> feature snake uniquement
-> libSDL3.so + libgame_android_entrypoint.so
-> APK Debug ARM64
```
La commande normale ne dépend plus d'un appel préalable à `scripts/build_android_rust.py snake`.
## Gradle Wrapper reproductible
Le projet Android reçoit le wrapper Gradle et l'épingle à `9.6.0`, version attendue par AGP `9.4.0` dans le cadrage `0-pre.1`.
Contrats de chaîne d'approvisionnement :
```text
Gradle 9.6.0 binary ZIP SHA-256
bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
Gradle Wrapper JAR SHA-256
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
```
`validateDistributionUrl=true` reste activé. L'audit de distribution vérifie statiquement ces contrats.
Le wrapper respecte `JAVA_HOME`; l'environnement utilisateur validé expose JDK 17 via cette variable même si `java` dans le `PATH` pointe vers JDK 25.
## Ownership Gradle/Cargo
`Android/gradle/sasedev-rust-android.gradle` introduit une tâche native incrémentale par ABI. Ses inputs comprennent l'AAR SDL3, les manifests/configurations Cargo utiles, le code Rust, la feature, l'ABI, l'API Android et la version NDK. Son output est un répertoire `jniLibs` généré sous `build/generated/sasedevNative/...`.
La tâche :
1. extrait `libSDL3.so` depuis Prefab ou le fallback AAR compatible ;
2. résout `${ANDROID_HOME}/ndk/28.2.13676358` ou l'équivalent `ANDROID_SDK_ROOT` ;
3. définit `ANDROID_NDK_HOME`, `CARGO_NDK_PLATFORM=21` et le chemin de linkage SDL3 uniquement pour le sous-processus ;
4. lance `cargo ndk` sans defaults avec la seule feature Snake ;
5. vérifie les deux `.so` attendus ;
6. remet l'output à `variant.sources.jniLibs.addGeneratedSourceDirectory`, qui crée la dépendance de build AGP.
Aucun nouvel orchestrateur Python ou shell spécifique au projet n'est introduit.
## Borne mono-ABI
`game-snake-poc` fixe volontairement :
```text
feature snake
ABI arm64-v8a
API Rust 21
```
et `ndk.abiFilters 'arm64-v8a'` empêche ce jalon de masquer une erreur de task wiring derrière le packaging d'autres ABI. `x86_64` reste pour `0-pre.3` après validation réelle de cette preuve ARM64.
Reflex n'est pas modifié dans cette tranche.
## Audit de distribution
`scripts/audit_distribution_layout.py` exige désormais les cinq nouveaux éléments durables du pipeline :
```text
Android/gradlew
Android/gradlew.bat
Android/gradle/wrapper/gradle-wrapper.jar
Android/gradle/wrapper/gradle-wrapper.properties
Android/gradle/sasedev-rust-android.gradle
```
Il contrôle également le checksum du JAR wrapper, la distribution 9.6.0, l'absence de délégation Python dans le nouveau build natif et le contrat mono-ABI Snake.
## Fichiers
Ajoutés :
```text
Android/gradlew
Android/gradlew.bat
Android/gradle/sasedev-rust-android.gradle
Android/gradle/wrapper/gradle-wrapper.jar
Android/gradle/wrapper/gradle-wrapper.properties
deltas/0.3.3/0-pre.2.md
history/0.3.3/0-pre.1.md
```
Modifiés :
```text
Android/README.md
Android/game-snake-poc/build.gradle
Cargo.toml
docs/development/006-ANDROID_RUST_NATIVE_BUILD.md
scripts/audit_distribution_layout.py
```
`scripts/build_android_rust.py`, Reflex, les sources Rust, `CHANGELOG.md` et `ROADMAP.md` restent inchangés.
## Validations exécutées dans l'environnement de génération
Le générateur exécute uniquement les audits statiques autorisés après constitution du delta :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
```
Aucun build Cargo/Gradle, test ou smoke final n'est attribué au générateur.
## Validation utilisateur demandée
Depuis la racine :
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
sha256sum Android/gradle/wrapper/gradle-wrapper.jar
grep -E '^(distributionUrl|distributionSha256Sum|validateDistributionUrl)=' Android/gradle/wrapper/gradle-wrapper.properties
(cd Android && ./gradlew --version)
(cd Android && ./gradlew :game-snake-poc:assembleDebug)
```
Le build Snake doit être lancé directement : ne pas appeler `scripts/build_android_rust.py snake` avant Gradle.
Puis inspecter l'APK produit :
```bash
APK="$(find Android/game-snake-poc/build/outputs/apk/debug -maxdepth 1 -type f -name '*.apk' -print -quit)"
test -n "$APK"
printf 'APK=%s\n' "$APK"
unzip -Z1 "$APK" | grep '^lib/' | sort
unzip -Z1 "$APK" | grep -Fx 'lib/arm64-v8a/libSDL3.so'
unzip -Z1 "$APK" | grep -Fx 'lib/arm64-v8a/libgame_android_entrypoint.so'
if unzip -Z1 "$APK" | grep -Eq '^lib/(x86_64|armeabi-v7a|x86)/'; then
printf '%s\n' 'unexpected non-ARM64 native library in mono-ABI proof' >&2
exit 1
fi
```
Le checksum attendu du wrapper JAR est :
```text
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
```
Aucun smoke appareil n'est requis dans cette tranche ; il reste planifié après établissement du packaging multi-ABI.
## Suite après validation
Si cette gate est propre, `0-pre.3` crée `history/0.3.3/0-pre.2.md`, étend le même ownership à `arm64-v8a + x86_64`, exige un APK Debug universal contenant les deux couples de bibliothèques et migre Reflex uniquement si la factorisation commune reste naturelle.
Une erreur de wrapper, de task wiring, de linkage SDL3, de feature Cargo ou de contenu APK produit d'abord `0-pre.2.fix.N`.

View File

@@ -0,0 +1,4 @@
Android/gradlew
Android/gradlew.bat
Android/gradle/wrapper/gradle-wrapper.jar
Android/gradle/wrapper/gradle-wrapper.properties

253
deltas/0.3.3/0-pre.3.md Normal file
View File

@@ -0,0 +1,253 @@
<!-- file: deltas/0.3.3/0-pre.3.md -->
<!-- version: 1 -->
# Delta 0.3.3-0-pre.3
## Base et statut précédent
Cette tranche part exclusivement de `0.3.3-0-pre.2`, validé par l'utilisateur le 2026-09-21. `history/0.3.3/0-pre.2.md` enregistre la gate complète, le build Gradle réussi et l'APK Snake ARM64 inspecté.
Aucun fix de `0-pre.2` n'est requis.
## Objectif
Cette tranche traite ensemble les deux décisions prévues avant l'AAB :
1. passer d'une version Gradle/JDK imposée pour la preuve à un contrat de minimum compatible avec l'environnement ;
2. élargir le pipeline Gradle/Cargo natif à `arm64-v8a + x86_64` et produire des APK Debug universal.
Snake reste le jeu-sonde principal. Reflex devient le second consommateur de la logique commune puisque sa migration ne demande qu'un mapping de feature et d'ABI, sans nouvelle architecture.
## Politique Gradle minimale
AGP `9.4.0` conserve Gradle `9.6.0` comme minimum. Le projet déclare désormais dans `Android/settings.gradle` :
```text
minimumGradleVersion = 9.6.0
```
Le script compare ce minimum à `GradleVersion.current()` et échoue explicitement si le Gradle système est trop ancien.
Le projet Android natif ne versionne plus de Gradle Wrapper. Une machine avec Gradle `9.6.0`, `9.7.1` ou une version ultérieure compatible utilise directement sa propre version. La version réellement employée est consignée par la gate avec :
```bash
(cd Android && gradle --version)
```
Cette décision concerne uniquement `Android/`. Les wrappers générés ou contraintes historiques propres au POC Tauri restent locaux à la crate Tauri et ne sont pas réinterprétés.
## Politique JDK
Aucun `export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` n'est imposé au chemin SDL3 natif.
Gradle/AGP utilisent le JDK fourni normalement par le shell ou l'IDE. Le minimum de la chaîne reste Java 17, tandis que la gate doit vérifier le JDK réellement sélectionné. Le JDK 25 installé dans l'environnement utilisateur est donc un candidat normal pour cette validation.
La glue Java Android conserve volontairement :
```text
sourceCompatibility = 17
targetCompatibility = 17
```
Le JDK de build et le niveau Java de l'application restent deux contrats distincts.
## Multi-ABI Snake
`game-snake-poc` déclare maintenant :
```text
feature snake
ABI arm64-v8a + x86_64
API Rust 21
```
et :
```text
abiFilters 'arm64-v8a', 'x86_64'
```
La logique commune `sasedev-rust-android.gradle`, déjà prouvée en mono-ABI, enregistre une tâche Rust par ABI et remet les deux répertoires `jniLibs` générés à AGP. L'APK Debug attendu est universal, sans split ABI par défaut.
## Reflex devient second consommateur
`game-reflex-poc` reçoit uniquement la configuration symétrique :
```text
feature reflex
ABI arm64-v8a + x86_64
API Rust 21
```
puis applique la même logique Gradle commune. Aucun nouveau script, type de tâche, bridge JNI ou chemin d'output n'est introduit.
Cette symétrie confirme que la factorisation appartient bien à `Android/gradle/sasedev-rust-android.gradle` et non aux modules de jeu.
## Suppression du wrapper natif
Un ZIP delta n'efface pas les fichiers d'un jalon précédent. Le manifest :
```text
deltas/0.3.3/0-pre.3.delete.txt
```
supprime :
```text
Android/gradlew
Android/gradlew.bat
Android/gradle/wrapper/gradle-wrapper.jar
Android/gradle/wrapper/gradle-wrapper.properties
```
Après extraction du delta à la racine du dépôt, appliquer :
```bash
while IFS= read -r path; do
rm -f -- "$path"
done < deltas/0.3.3/0-pre.3.delete.txt
rmdir --ignore-fail-on-non-empty Android/gradle/wrapper
```
L'audit de distribution traite désormais ces quatre fichiers comme interdits dans le projet Android natif afin d'éviter un retour involontaire à une version Gradle épinglée.
## Règles et documentation
Les règles de commandes Android utilisent désormais `gradle` depuis `Android/` et exigent le minimum déclaré par le projet. `CMD-051` devient :
```text
(cd Android && gradle :<app>:assembleDebug)
```
Le plan `0.3.3`, la documentation Android et le document de build natif distinguent explicitement :
- Gradle système `>= 9.6.0` ;
- JDK runtime fourni par l'environnement ;
- Java source/target 17 ;
- contraintes Tauri historiques séparées ;
- deux ABI natives productives de cette tranche.
## Fichiers
Ajoutés :
```text
deltas/0.3.3/0-pre.3.md
deltas/0.3.3/0-pre.3.delete.txt
history/0.3.3/0-pre.2.md
```
Modifiés :
```text
Android/README.md
Android/game-reflex-poc/build.gradle
Android/game-snake-poc/build.gradle
Android/settings.gradle
Cargo.toml
docs/development/006-ANDROID_RUST_NATIVE_BUILD.md
docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md
docs/rules/RULES_COMMANDS.md
docs/rules/RULES_VALIDATION_MATRIX.md
scripts/audit_distribution_layout.py
```
Supprimés par manifest :
```text
Android/gradlew
Android/gradlew.bat
Android/gradle/wrapper/gradle-wrapper.jar
Android/gradle/wrapper/gradle-wrapper.properties
```
`scripts/build_android_rust.py`, le code Rust, les manifests Android, `CHANGELOG.md` et `ROADMAP.md` restent inchangés.
## Validations exécutées dans l'environnement de génération
Le générateur exécute uniquement les audits statiques autorisés après constitution du delta :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
```
Aucun build Cargo/Gradle, test ou smoke final n'est attribué au générateur.
## Validation utilisateur demandée
Exécuter la gate depuis un shell normal, sans ajouter l'ancien `export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` :
```bash
java -version
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
(cd Android && gradle --version)
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
```
La sortie `gradle --version` doit montrer une version `>= 9.6.0` et le JDK effectivement fourni par votre environnement. Une version supérieure au minimum n'est pas une divergence.
## Inspection des APK universal
Pour chaque jeu :
```bash
for game in game-snake-poc game-reflex-poc; do
APK="$(find "Android/${game}/build/outputs/apk/debug" -maxdepth 1 -type f -name '*.apk' -print -quit)"
test -n "$APK"
printf 'APK=%s\n' "$APK"
unzip -Z1 "$APK" | grep '^lib/' | sort
for abi in arm64-v8a x86_64; do
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libSDL3.so"
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libgame_android_entrypoint.so"
done
if unzip -Z1 "$APK" | grep -Eq '^lib/(armeabi-v7a|x86)/'; then
printf '%s\n' "unexpected 32-bit native library in ${game} universal APK" >&2
exit 1
fi
done
```
L'absence de split est démontrée par un seul APK Debug par module contenant simultanément les bibliothèques ARM64 et x86_64.
## Smoke multi-architecture Snake
Après inspection mécanique, installer le même APK Snake sur l'AVD x86_64 puis sur l'appareil ARM64 réel.
AVD connu :
```bash
APK="Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk"
adb -s emulator-5554 install -r "$APK"
adb -s emulator-5554 shell am start -n com.sasedev.games.snake/.SnakeActivity
```
Puis sur l'appareil réel en remplaçant `<arm64-serial>` par le serial affiché par `adb devices -l` :
```bash
adb -s <arm64-serial> install -r "$APK"
adb -s <arm64-serial> shell am start -n com.sasedev.games.snake/.SnakeActivity
```
Le smoke attendu est borné : lancement sans erreur de chargement JNI/SDL3 et affichage du jeu. La validation gameplay exhaustive reste hors de ce jalon.
## Suite après validation
Si cette gate est propre, `0-pre.4` ouvre le chemin AAB, la vérification 16 KB, la validation du plancher Android disponible et la fermeture de `scripts/build_android_rust.py` si aucune responsabilité n'y subsiste.
Toute erreur de minimum Gradle, exécution JDK, task wiring x86_64, linkage SDL3, packaging universal ou activation Reflex produit d'abord `0-pre.3.fix.1`.

92
deltas/0.3.3/0-pre.4.md Normal file
View File

@@ -0,0 +1,92 @@
<!-- file: deltas/0.3.3/0-pre.4.md -->
<!-- version: 1 -->
# Delta 0.3.3-0-pre.4
## Objet
Étendre le pipeline Android SDL3 natif validé en `0-pre.3` à toutes les ABI encore supportées par la toolchain moderne, sans mélanger cette preuve avec l'AAB/16 KB.
Matrice cible obligatoire :
```text
arm64-v8a
armeabi-v7a
x86_64
x86
```
Les ABI historiques `armeabi`, `mips` et `mips64` restent hors contrat car elles ont été retirées des toolchains Android modernes.
## Base et historique
Base : `0.3.3-0-pre.3`, validée par l'utilisateur le 2026-09-21 avec Temurin 25.0.4.1, Gradle 9.7.1, audits/Cargo/Clippy propres, APK universal ARM64+x86_64 pour Snake et Reflex, puis smoke Snake réussi sur AVD x86_64 et Samsung ARM64 réel.
`history/0.3.3/0-pre.3.md` consigne cette preuve avant les changements du présent delta.
## Changements
- version workspace portée à `0.3.3-0-pre.4` ;
- Snake et Reflex déclarent maintenant les quatre ABI dans `sasedevRustAndroidAbis` et `ndk.abiFilters` ;
- l'audit de distribution exige cette matrice complète pour les deux consommateurs ;
- documentation Android/build mise à jour pour distinguer couverture de packaging et disponibilité de smokes matériels ;
- plan `0.3.3` redécoupé : `0-pre.4` est consacré aux quatre ABI, `0-pre.5` prendra l'AAB, 16 KB, minSdk et la fermeture du script Python historique ;
- l'étude de cadrage conserve sa décision initiale mais ajoute explicitement la révision de scope décidée après `0-pre.3`.
Aucune modification n'est nécessaire dans `Android/gradle/sasedev-rust-android.gradle` : cette logique possédait déjà le mapping des quatre ABI et génère une tâche par ABI configurée.
## Validation attendue
Depuis la racine, sans imposer `JAVA_HOME` :
```bash
java -version
(cd Android && gradle --version)
rustup target list --installed
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
```
Les targets Rust nécessaires sont :
```text
aarch64-linux-android
armv7-linux-androideabi
x86_64-linux-android
i686-linux-android
```
Pour chaque APK :
```bash
for game in game-snake-poc game-reflex-poc; do
APK="$(find "Android/${game}/build/outputs/apk/debug" \
-maxdepth 1 -type f -name '*.apk' -print -quit)"
test -n "$APK"
printf 'APK=%s\n' "$APK"
unzip -Z1 "$APK" | grep '^lib/' | sort
for abi in arm64-v8a armeabi-v7a x86_64 x86; do
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libSDL3.so"
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libgame_android_entrypoint.so"
done
done
```
La gate échoue si une des huit entrées natives par application manque. Aucun smoke 32 bits n'est revendiqué sans appareil/AVD 32 bits disponible. Les smokes déjà validés ARM64/x86_64 ne sont pas invalidés par l'ajout de la matrice 32 bits.
## Suite
Si les deux builds et l'inspection des quatre ABI sont propres, ouvrir `0.3.3-0-pre.5` pour le chemin release/AAB, le contrôle 16 KB, la consolidation minSdk et la suppression de `scripts/build_android_rust.py` lorsque toutes ses responsabilités sont effectivement remplacées.
Toute défaillance spécifique à cette tranche produit `0.3.3-0-pre.4.fix.N` avant `0-pre.5`.

View File

@@ -0,0 +1 @@
scripts/build_android_rust.py

126
deltas/0.3.3/0-pre.5.md Normal file
View File

@@ -0,0 +1,126 @@
<!-- file: deltas/0.3.3/0-pre.5.md -->
<!-- version: 1 -->
# Delta 0.3.3-0-pre.5
## Objet
Fermer le chemin Android natif historique après validation des quatre ABI : étendre l'ownership Gradle aux variantes Release/AAB, consolider `minSdk 21`, préparer la vérification 16 KB et supprimer `scripts/build_android_rust.py`.
## Base
Base : `0.3.3-0-pre.4`, validée par l'utilisateur le 2026-09-21 avec Temurin 25.0.4.1, Gradle 9.7.1, audits/Cargo/Clippy propres et builds Debug Snake/Reflex réussis sur `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
`history/0.3.3/0-pre.4.md` consigne cette preuve.
## Changements
- version workspace portée à `0.3.3-0-pre.5` ;
- `Android/gradle/sasedev-rust-android.gradle` enregistre désormais ses sources `jniLibs` pour toutes les variantes, pas seulement Debug ;
- les variantes Release déclenchent `cargo ndk ... build --release`, tandis que Debug conserve le profil Cargo `dev` ;
- `bundleRelease` de Snake et Reflex devient donc propriétaire de la reconstruction native quatre ABI ;
- `minSdk 21` et `CARGO_NDK_PLATFORM=21` deviennent des invariants audités pour les deux applications ;
- le builder historique `scripts/build_android_rust.py` est supprimé via manifest ;
- l'audit de distribution interdit désormais le retour de ce script et de `src/main/jniLibs` générés ;
- les anciennes commandes RC dépendant du builder Python sont remplacées par les tâches Gradle natives ;
- documentation Android/build/plan/étude actualisée pour AAB, API 21 et contrôle 16 KB.
Aucun secret de signature ni keystore n'est ajouté.
## Référence 16 KB
Le contrôle suit le chemin recommandé par Android : AGP `>= 8.5.1`, NDK `r28+`, vérification ELF par `llvm-objdump`, puis `zipalign -P 16` sur l'APK. La SDL3 de l'AAR est précompilée et doit donc être contrôlée explicitement avec la bibliothèque Rust.
## Validation attendue
Depuis la racine :
```bash
java -version
(cd Android && gradle --version)
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
(cd Android && gradle :game-snake-poc:bundleRelease)
(cd Android && gradle :game-reflex-poc:bundleRelease)
```
Les AAB attendus sont sous `Android/<game>/build/outputs/bundle/release/`. Pour chaque bundle :
```bash
for game in game-snake-poc game-reflex-poc; do
AAB="$(find "Android/${game}/build/outputs/bundle/release" \
-maxdepth 1 -type f -name '*.aab' -print -quit)"
test -n "$AAB"
printf 'AAB=%s\n' "$AAB"
unzip -Z1 "$AAB" | grep '^base/lib/' | sort
for abi in arm64-v8a armeabi-v7a x86_64 x86; do
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libSDL3.so"
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libgame_android_entrypoint.so"
done
done
```
Revalider ensuite l'APK Debug et son alignement ZIP 16 KB avec le `zipalign` des Build Tools installés :
```bash
ZIPALIGN="$(find "$ANDROID_HOME/build-tools" -mindepth 2 -maxdepth 2 -type f -name zipalign -print | sort -V | tail -n 1)"
test -x "$ZIPALIGN"
for game in game-snake-poc game-reflex-poc; do
(cd Android && gradle ":${game}:assembleDebug")
APK="$(find "Android/${game}/build/outputs/apk/debug" -maxdepth 1 -type f -name '*.apk' -print -quit)"
"$ZIPALIGN" -c -P 16 -v 4 "$APK"
done
```
Contrôler les segments ELF des bibliothèques générées et de SDL3. Sur Linux avec le NDK configuré :
```bash
OBJDUMP="$ANDROID_HOME/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump"
test -x "$OBJDUMP"
for game in game-snake-poc game-reflex-poc; do
for variant in debug release; do
for abi in arm64-v8a armeabi-v7a x86_64 x86; do
for so in \
"Android/${game}/build/generated/sasedevNative/${variant}/${abi}/jniLibs/${abi}/libSDL3.so" \
"Android/${game}/build/generated/sasedevNative/${variant}/${abi}/jniLibs/${abi}/libgame_android_entrypoint.so"; do
test -f "$so"
printf '%s\n' "$so"
"$OBJDUMP" -p "$so" | grep 'LOAD'
done
done
done
done
```
Aucun `LOAD` ne doit annoncer un alignement inférieur à `2**14`.
Si `bundletool` est installé dans l'environnement :
```bash
bundletool dump config --bundle="Android/game-snake-poc/build/outputs/bundle/release/game-snake-poc-release.aab" | grep alignment
bundletool dump config --bundle="Android/game-reflex-poc/build/outputs/bundle/release/game-reflex-poc-release.aab" | grep alignment
```
La valeur attendue est `PAGE_ALIGNMENT_16K`. Son absence locale parce que `bundletool` n'est pas installé doit être consignée ; elle ne doit pas être remplacée par une affirmation fictive.
Pour le plancher Android, relever les images disponibles :
```bash
emulator -list-avds
sdkmanager --list_installed | grep 'system-images;android-' || true
```
Si une image API 21 exploitable est déjà disponible ou raisonnablement installable, effectuer un smoke Snake. Sinon, consigner explicitement cette lacune ; le contrat reste API 21 car SDL3 lui-même documente ce niveau comme minimum et le build Rust/manifest l'imposent déjà.
## Suite
Si les AAB quatre ABI et les contrôles 16 KB sont propres, la prochaine tranche est `0.3.3-2-beta.1` conformément au plan révisé. Une défaillance du chemin Release/AAB, de l'alignement ou de la fermeture Python produit d'abord `0.3.3-0-pre.5.fix.N`.

139
deltas/0.3.3/2-beta.1.md Normal file
View File

@@ -0,0 +1,139 @@
<!-- file: deltas/0.3.3/2-beta.1.md -->
<!-- version: 1 -->
# Delta 0.3.3-2-beta.1
## Objectif
Entrer en beta avec le pipeline Android SDL3 natif feature-complete : quatre ABI, APK universal, AAB Release, API 21 prouvée, compatibilité 16 KB 64 bits prouvée et aucun orchestrateur Python de build.
Cette tranche n'ajoute aucune fonctionnalité. Elle synchronise la version technique, consolide les preuves `0-pre.5` et exécute la validation large prévue avant RC.
## Baseline acceptée
`0.3.3-0-pre.5` est validée par l'utilisateur le 2026-09-21.
La baseline acceptée comprend :
- audits Rust/Markdown propres ;
- audit de distribution propre après suppression de deux anciens `src/main/jniLibs` locaux ;
- `cargo check --workspace` et Clippy workspace strict propres ;
- `bundleRelease` réussi pour Snake et Reflex ;
- AAB quatre ABI pour les deux jeux ;
- `zipalign -P 16` réussi sur les APK Debug ;
- ELF `arm64-v8a` et `x86_64` alignés `2**14` pour SDL3 et la bibliothèque Rust ;
- smoke Snake réussi sur Android 5.0/API 21 x86 ;
- smoke Snake réussi sur Android 15/API 35 x86_64 `ps16k` avec `getconf PAGE_SIZE=16384` ;
- suppression du builder `scripts/build_android_rust.py`.
`history/0.3.3/0-pre.5.md` consigne le détail.
## Changements
- passage de `workspace.package.version` à `0.3.3-2-beta.1` ;
- ajout de l'historique validé `0-pre.5` ;
- correction documentaire du périmètre 16 KB : exigence Play portée sur les appareils 64 bits, sans imposer artificiellement `2**14` aux ABI 32 bits ;
- consolidation du plan actif avec les preuves API 21 et `ps16k` réellement obtenues ;
- aucune modification de gameplay, moteur, JNI, Java, logique Gradle, ABI, API Android, dépendance ou asset.
`CHANGELOG.md` et `ROADMAP.md` restent inchangés : la synthèse publiée est réservée à la RC/stable et le scope macro `0.3.3` n'est pas encore clos.
## Validation beta — statique et workspace
Depuis la racine :
```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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
La suite workspace complète est volontairement exécutée ici : `2-beta.1` constitue la gate large planifiée de la version.
## Rebuild Android beta
Nettoyer les outputs Gradle des applications, puis reconstruire les deux formes d'artefact :
```bash
(cd Android && gradle :game-snake-poc:clean :game-reflex-poc:clean)
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
(cd Android && gradle :game-snake-poc:bundleRelease)
(cd Android && gradle :game-reflex-poc:bundleRelease)
```
Pour chaque APK Debug et AAB Release, vérifier les quatre ABI :
```bash
for game in game-snake-poc game-reflex-poc; do
APK="Android/${game}/build/outputs/apk/debug/${game}-debug.apk"
AAB="Android/${game}/build/outputs/bundle/release/${game}-release.aab"
test -f "$APK"
test -f "$AAB"
for abi in arm64-v8a armeabi-v7a x86_64 x86; do
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libSDL3.so"
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libgame_android_entrypoint.so"
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libSDL3.so"
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libgame_android_entrypoint.so"
done
done
```
Revalider le packaging 16 KB :
```bash
ZIPALIGN="$(find "$ANDROID_HOME/build-tools" -mindepth 2 -maxdepth 2 -type f -name zipalign -print | sort -V | tail -n 1)"
test -x "$ZIPALIGN"
"$ZIPALIGN" -c -P 16 -v 4 Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
"$ZIPALIGN" -c -P 16 -v 4 Android/game-reflex-poc/build/outputs/apk/debug/game-reflex-poc-debug.apk
```
Les preuves ELF 64 bits et les smokes de frontière API 21/16 KB viennent d'être établis en `0-pre.5`; ils ne sont pas rejoués mécaniquement dans cette beta tant qu'aucune logique native/Gradle n'a changé.
## Smokes beta de référence
Revalider **Snake et Reflex** sur :
1. l'AVD x86_64/API 36 de référence ;
2. l'appareil ARM64 réel de référence.
Pour chaque jeu et chaque device :
```bash
adb -s <serial> install -r <apk>
adb -s <serial> shell am start -n <package>/<activity>
```
Vérifier au minimum :
- démarrage ;
- rendu SDL3 ;
- contrôles/tactile ;
- assets ;
- absence de crash/panic immédiat ;
- Back système et sortie propre.
Les packages/activities actuels sont :
```text
Snake : com.sasedev.games.snake/.SnakeActivity
Reflex : com.sasedev.games.reflex/.ReflexActivity
```
## Transition
Si la gate workspace, les rebuilds quatre ABI, le packaging et les quatre smokes de référence sont propres, ne pas créer de beta supplémentaire : ouvrir directement `0.3.3-3-rc.1`.
Toute régression imputable au projet produit d'abord `0.3.3-2-beta.1.fix.N`.

View File

@@ -0,0 +1,82 @@
<!-- file: deltas/0.3.3/3-rc.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.3-3-rc.1.fix.1
## Base requise
`0.3.3-3-rc.1`.
## Nature du correctif
Correctif strictement documentaire de la candidate RC. Il ne modifie ni le runtime, ni le pipeline Android, ni Cargo, ni la version technique du workspace.
Conformément à `VER-DOCFIX-001`, `workspace.package.version` reste donc :
```text
0.3.3-3-rc.1
```
L'identité `0.3.3-3-rc.1.fix.1` est portée par le présent delta et son archive.
## Défaut corrigé
Le prompt de reprise `prompts/005-V0_3_4_START_PROMPT.md` était fonctionnel mais trop pauvre comme transmission autonome de session, particulièrement dans son forecast initial :
- chaque tranche n'expliquait pas suffisamment son intention ;
- les livrables probables et critères de sortie n'étaient pas explicités ;
- les points naturels de fusion/scission n'étaient pas indiqués ;
- le rôle conditionnel d'une tranche de robustesse ou d'un demo n'était pas assez clair ;
- la séparation entre forecast initial et plan actif produit par `alpha.1` pouvait être mieux verrouillée ;
- la progression vers beta, RC et stable manquait de critères opératoires.
## Changements
`prompts/005-V0_3_4_START_PROMPT.md` passe en version documentaire 2 et développe son forecast non contraignant avec :
- règles explicites de lecture et de révision du forecast ;
- `alpha.1` détaillée pour la migration de gouvernance, l'audit, le sizing et le contrat transport ;
- `alpha.2` pour l'API async minimale ;
- `alpha.3` pour le backend WebSocket `tokio-tungstenite` et le loopback ;
- `alpha.4` conditionnelle pour robustesse, limites, backpressure et lifecycle ;
- `alpha.5` conditionnelle pour une preuve consommateur/demo uniquement si elle apporte une valeur réelle ;
- une tranche de consolidation de développement explicitement fusionnable ;
- critères de beta large, RC gelée et promotion stable mécanique ;
- branches explicites de redécoupage lorsque les résultats d'`alpha.1` invalident le découpage initial.
Le forecast reste volontairement prévisionnel : `0.3.4-alpha.1` doit le réviser dans `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md`, qui devient ensuite l'autorité prévisionnelle active.
## Immutabilité historique
Ce fix n'applique toujours pas la nouvelle convention aux versions déjà livrées. Aucun fichier historique `deltas/`, `history/`, ancien prompt ou ancienne entrée de changelog n'est renommé ou réécrit pour harmoniser sa nomenclature.
La migration effective des règles prospectives vers :
```text
X.Y.Z-alpha.N
X.Y.Z-alpha.N.fix.M
X.Y.Z-beta.N
X.Y.Z-beta.N.fix.M
X.Y.Z-rc.N
X.Y.Z-rc.N.fix.M
X.Y.Z
```
reste la première action de la prochaine session `0.3.4`.
## État RC connu avant ce fix
La validation utilisateur de `0.3.3-3-rc.1` a déjà confirmé les audits, Cargo check/Clippy/tests workspace, rebuild Android Debug/Release quatre ABI, présence des quatre ABI dans APK/AAB et `zipalign -P 16` sur les deux APK.
Le présent correctif documentaire n'attribue pas de validation future à la RC et ne crée pas `history/0.3.3/3-rc.1.md` avant acceptation complète du jalon.
## Validation du fix
Comme seuls des fichiers Markdown sont ajoutés/modifiés, exécuter :
```bash
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
```
Une fois ce contenu accepté, reprendre la matrice RC là où elle s'était arrêtée. Le correctif ne nécessite aucun rebuild Cargo/Gradle supplémentaire par lui-même.

66
deltas/0.3.3/3-rc.1.md Normal file
View File

@@ -0,0 +1,66 @@
<!-- file: deltas/0.3.3/3-rc.1.md -->
<!-- version: 1 -->
# Delta 0.3.3-3-rc.1
## Base requise
`0.3.3-2-beta.1`, validée le 2026-09-21.
## Objectif
Créer la candidate de publication `0.3.3` sans rouvrir le scope du pipeline Android SDL3 natif multi-ABI.
## Gel RC
Le scope `0.3.3` est gelé. Cette tranche n'ajoute :
- aucune fonctionnalité ;
- aucune ABI ;
- aucune API Android ;
- aucune dépendance ;
- aucune logique Gradle/JNI/Java/Rust ;
- aucun changement de gameplay ou moteur ;
- aucune nouvelle politique de packaging.
Seuls les correctifs autorisés par `VER-RC-*` peuvent suivre sous `3-rc.1.fix.N`.
## Validation beta acquise
`2-beta.1` a validé :
- audits, formatage, check, Clippy et test workspace complet avec 35 tests réussis ;
- rebuild propre Snake/Reflex en Debug et Release ;
- quatre ABI `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86` dans APK et AAB ;
- `zipalign -P 16` sur les deux APK ;
- smokes Snake et Reflex sur AVD API 36 x86_64 avec processus vivant et aucun crash remonté ;
- smokes Snake et Reflex sur Samsung API 29 ARM64 avec processus vivant et aucun crash remonté.
Les preuves de frontière API 21/x86 et API 35/x86_64 `ps16k` avaient déjà été acquises en `0-pre.5`.
## Changements
- passage de `workspace.package.version` à `0.3.3-3-rc.1` ;
- entrée RC dans `CHANGELOG.md` ;
- ajout de `history/0.3.3/2-beta.1.md` ;
- ajout de `docs/testing/006-V0_3_3_RC_VALIDATION_MATRIX.md` et de son index ;
- consolidation du plan `0.3.3` avec la gate beta réellement obtenue ;
- création de `prompts/005-V0_3_4_START_PROMPT.md`.
La nouvelle nomenclature de versions approuvée par l'utilisateur n'est **pas** appliquée dans cette session : `0.3.3` termine sous l'ancien schéma. Le prompt `0.3.4` prépare la migration des règles actives vers `alpha.M / beta.M / rc.M` dès la prochaine session, avec interdiction de renommer ou réécrire les anciens `deltas/`, `history/`, changelog ou prompts historiques.
`ROADMAP.md` reste inchangé : le statut macro de `0.3.3` ne devient terminé qu'à la stable.
## Validation RC
Appliquer intégralement :
```text
docs/testing/006-V0_3_3_RC_VALIDATION_MATRIX.md
```
La gate revalide workspace, APK/AAB quatre ABI, packaging 16 KB 64 bits et les smokes de publication x86_64 + ARM64. Elle exige aussi une observation humaine finale du rendu et des contrôles Snake/Reflex.
## Après validation
Si la RC est propre, la promotion vers `0.3.3` est mécanique : version stable, entrée stable du changelog, historique RC, clôture du plan et delta de release. Aucun nouveau comportement ne doit être introduit.

59
deltas/0.3.3/rel.001.md Normal file
View File

@@ -0,0 +1,59 @@
<!-- file: deltas/0.3.3/rel.001.md -->
<!-- version: 1 -->
# Delta 0.3.3 — release stable
## Base
Base fonctionnelle validée : `0.3.3-3-rc.1`.
Le delta documentaire `0.3.3-3-rc.1.fix.1` est également appliqué et validé ; il n'a pas modifié la version Cargo ni le runtime.
## Objet
Promouvoir mécaniquement la candidate validée vers `0.3.3` sans introduire de nouveau comportement.
## Changements
La release stable :
- passe `workspace.package.version` de `0.3.3-3-rc.1` à `0.3.3` ;
- positionne `README.md` sur `0.3.3` comme stable de référence et `0.3.4-alpha.1` comme prochaine version planifiée ;
- marque `0.3.3` terminée dans `ROADMAP.md` ;
- ajoute l'entrée stable `0.3.3` dans `CHANGELOG.md` ;
- enregistre la validation effective de la RC dans `history/0.3.3/3-rc.1.md` ;
- clôt `docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md` et son entrée d'index ;
- conserve `prompts/005-V0_3_4_START_PROMPT.md` comme transmission de la prochaine session.
## Frontière de release
Aucun changement de gameplay, code Rust, dépendance, Gradle, Android, JNI, Java, ABI, `minSdk`, NDK, SDL3, asset, protocole ou architecture n'est introduit après la RC.
La nouvelle convention de prerelease n'est pas rétroactivement appliquée à `0.3.3`. Elle commencera avec `0.3.4-alpha.1`, qui mettra à jour les règles actives sans renommer ni réécrire l'historique existant.
## Validation proportionnelle
La gate RC complète a déjà été validée sur un état fonctionnellement identique. La promotion stable demande uniquement les contrôles proportionnels aux changements de version/documentation :
```bash
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
```
Les rebuilds Android, Clippy strict, suite workspace complète, contrôles APK/AAB/ELF et smokes runtime ne sont pas répétés puisque la promotion stable ne modifie aucun comportement ni pipeline.
## Publication
Après validation de ce delta :
- commit de release ;
- tag stable unique `v0.3.3` ;
- archive/tag de dépôt utilisée comme base de la prochaine session ;
- démarrage de `0.3.4` uniquement depuis `v0.3.3` avec `prompts/005-V0_3_4_START_PROMPT.md` ;
- première tranche suivante : migration de gouvernance puis `0.3.4-alpha.1` ;
- aucun ancien `deltas/`, `history/`, prompt ou changelog n'est renommé pour harmoniser sa nomenclature.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md --> <!-- file: docs/000-README.md -->
<!-- version: 27 --> <!-- version: 31 -->
# Documentation games.sasedev # Documentation games.sasedev
@@ -15,7 +15,9 @@
## Plans ## Plans
- [`plans/000-README.md`](plans/000-README.md) — plans vivants des versions ; le plan actif porte notamment le découpage prévisionnel souple des prereleases. - [`plans/000-README.md`](plans/000-README.md) — plans vivants des versions ; le plan actif porte notamment le découpage prévisionnel souple des prereleases.
- [`plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`](plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md) — plan actif de `0.3.0`, premier POC Snake Web direct. - [`plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`](plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md) — plan clôturé de `0.3.0`, premier POC Snake Web direct.
- [`plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md`](plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md) — plan clôturé de `0.3.1`, second host Snake Tauri Android.
- [`plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md`](plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md) — plan actif de `0.3.3`, pipeline Android SDL3 natif Gradle/Cargo multi-ABI.
## Architecture ## Architecture
@@ -69,6 +71,8 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](ru
- [`testing/002-BETA_VALIDATION_MATRIX.md`](testing/002-BETA_VALIDATION_MATRIX.md) — matrice beta Desktop SDL3, Tauri/WASM et Android multi-appareils. - [`testing/002-BETA_VALIDATION_MATRIX.md`](testing/002-BETA_VALIDATION_MATRIX.md) — matrice beta Desktop SDL3, Tauri/WASM et Android multi-appareils.
- [`testing/003-RC_VALIDATION_MATRIX.md`](testing/003-RC_VALIDATION_MATRIX.md) — matrice RC de reproductibilité, packaging final et promotion vers `0.1.0`. - [`testing/003-RC_VALIDATION_MATRIX.md`](testing/003-RC_VALIDATION_MATRIX.md) — matrice RC de reproductibilité, packaging final et promotion vers `0.1.0`.
- [`testing/004-V0_3_0_RC_VALIDATION_MATRIX.md`](testing/004-V0_3_0_RC_VALIDATION_MATRIX.md) — gate RC spécifique au POC Snake Web direct et à son témoin Desktop SDL3. - [`testing/004-V0_3_0_RC_VALIDATION_MATRIX.md`](testing/004-V0_3_0_RC_VALIDATION_MATRIX.md) — gate RC spécifique au POC Snake Web direct et à son témoin Desktop SDL3.
- [`testing/005-V0_3_1_RC_VALIDATION_MATRIX.md`](testing/005-V0_3_1_RC_VALIDATION_MATRIX.md) — gate RC du POC Snake Tauri Android de référence, incluant workspace complet et APK universal x86_64 + ARM64.
- [`testing/006-V0_3_3_RC_VALIDATION_MATRIX.md`](testing/006-V0_3_3_RC_VALIDATION_MATRIX.md) — gate RC du pipeline Android SDL3 natif quatre ABI, APK/AAB, 16 KB et smokes x86_64 + ARM64.
- [`development/003-TRACING_AND_DIAGNOSTICS.md`](development/003-TRACING_AND_DIAGNOSTICS.md) — socle `tracing`, subscriber et appender communs. - [`development/003-TRACING_AND_DIAGNOSTICS.md`](development/003-TRACING_AND_DIAGNOSTICS.md) — socle `tracing`, subscriber et appender communs.
- [`development/004-SDL3_DESKTOP_PREREQUISITES.md`](development/004-SDL3_DESKTOP_PREREQUISITES.md) — prérequis SDL3 Desktop, stratégie de liaison système et vérification `pkg-config`. - [`development/004-SDL3_DESKTOP_PREREQUISITES.md`](development/004-SDL3_DESKTOP_PREREQUISITES.md) — prérequis SDL3 Desktop, stratégie de liaison système et vérification `pkg-config`.
- [`development/005-DESKTOP_WINDOW_POLICY.md`](development/005-DESKTOP_WINDOW_POLICY.md) — taille initiale, redimensionnement et séparation future entre fenêtre physique et résolution virtuelle. - [`development/005-DESKTOP_WINDOW_POLICY.md`](development/005-DESKTOP_WINDOW_POLICY.md) — taille initiale, redimensionnement et séparation future entre fenêtre physique et résolution virtuelle.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/010-RUNTIME_PROVENANCE.md --> <!-- file: docs/architecture/010-RUNTIME_PROVENANCE.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Provenance d'environnement d'exécution # Provenance d'environnement d'exécution
@@ -45,7 +45,7 @@ RuntimeHost = Native
InputProfile = KeyboardMouse InputProfile = KeyboardMouse
``` ```
Le futur POC Tauri/WASM : Un host Tauri Desktop/WASM :
```text ```text
PlatformFamily = Desktop PlatformFamily = Desktop
@@ -55,7 +55,17 @@ RuntimeHost = TauriWebView
InputProfile = KeyboardMouse InputProfile = KeyboardMouse
``` ```
Une version Web dans un navigateur de téléphone pourra au contraire être `PlatformFamily = Web`, `DeviceClass = Phone`, `ExecutionModel = Wasm`, `RuntimeHost = Browser`, `InputProfile = Touch`. Le POC Tauri Android de `0.3.1` doit au contraire utiliser :
```text
PlatformFamily = Android
DeviceClass = Phone ou Tablet
ExecutionModel = Wasm
RuntimeHost = TauriWebView
InputProfile = Touch ou Mixed selon les entrées réellement observées
```
Une version Web dans un navigateur de téléphone pourra être `PlatformFamily = Web`, `DeviceClass = Phone`, `ExecutionModel = Wasm`, `RuntimeHost = Browser`, `InputProfile = Touch`.
## Classements et équité ## Classements et équité

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md --> <!-- file: docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md -->
<!-- version: 2 --> <!-- version: 5 -->
# Architecture des POC plateforme et réseau # Architecture des POC plateforme et réseau
@@ -14,12 +14,22 @@ Snake est le jeu-sonde principal.
Première vague, réordonnée à partir du résultat réel de `0.3.0` : Première vague, réordonnée à partir du résultat réel de `0.3.0` :
- Web navigateur direct + Snake — validé par `0.3.0` ; - Web navigateur direct + Snake — validé par `0.3.0` ;
- Tauri Android + Snake — prochain host prévu par `0.3.1` ; - Tauri Android + Snake — POC de référence terminé par `0.3.1`, conservé pour comparaison mais non retenu comme voie de production Android par défaut ;
- Tauri Desktop + Snake — prévu ensuite pour éprouver la factorisation WebView ; - Tauri Desktop + Snake — différé tant quun bénéfice produit explicite nest pas démontré, en particulier une voie de monétisation publicitaire/reward vidéo compatible avec une WebView desktop distribuée ;
- build Android multi-ABI avec outils natifs — tranche dédiée après les POC WebView. - build Android multi-ABI avec outils natifs — tranche dédiée après les POC WebView.
Plateformes supplémentaires lorsque l'environnement existe : Windows SDL natif, macOS SDL natif et iOS SDL natif. Plateformes supplémentaires lorsque l'environnement existe : Windows SDL natif, macOS SDL natif et iOS SDL natif.
## Positionnement Tauri Android après `0.3.1`
Le host Tauri Android reste pertinent comme preuve de portabilité du gameplay Rust/WASM et comme banc dessai WebView. Il nest pas retenu comme distribution Android de jeu par défaut.
La baseline Android productive reste SDL3 natif avec wrapper Java/JNI : elle fournit déjà le rendu et les contrôles nécessaires avec moins de couches doutillage. Tauri Android ne doit être rouvert pour un produit que lorsquun besoin concret apporte une valeur supérieure à ce coût, par exemple si les tests futurs démontrent un avantage net de la WebView pour une saisie texte ou une capacité UI difficile à obtenir proprement via SDL.
Le smoke ARM64 réel de `0.3.1` renforce ce positionnement : le jeu et le touch fonctionnent, mais le mode développement impose un routage `devUrl`/ADB spécifique et le Back système a exposé un abort natif pendant le teardown WebView sur l'appareil de validation. Ce défaut ne justifie pas un fork local de Tauri/Wry pour un POC non retenu comme cible Android productive ; il reste une limite documentée à comparer au package autonome.
Cette décision ninterdit pas Tauri Desktop. Le Desktop reste une distribution distincte et optionnelle ; sa valeur doit être démontrée séparément. À la clôture du POC Android, aucune solution de publicité ou reward vidéo dans une WebView Tauri Desktop nest considérée comme validée. Une approche exigeant une URL Web publique, un chargement distant du jeu ou un SDK tiers spécifique doit faire lobjet dun POC et dune vérification contractuelle avant de rouvrir cette cible.
## POC réseau ## POC réseau
Avant le Mode 3, comparer au minimum WebSocket/tokio-tungstenite, WebTransport/QUIC, fallback automatique, charge, reconnect/resync, backpressure, snapshots/deltas et mobilité réseau. Avant le Mode 3, comparer au minimum WebSocket/tokio-tungstenite, WebTransport/QUIC, fallback automatique, charge, reconnect/resync, backpressure, snapshots/deltas et mobilité réseau.
@@ -38,7 +48,7 @@ Un POC sert à identifier duplication, adapters manquants, capability réellemen
Une abstraction n'est pas extraite avant observation d'un besoin concret. Une abstraction n'est pas extraite avant observation d'un besoin concret.
Le POC Web direct `0.3.0` n'a pas justifié d'extraire prématurément un adapter WASM commun entre Reflex et Snake : les deux adapters spécifiques restent acceptés. Le second host Snake doit d'abord fournir un second besoin réel avant toute factorisation. Le POC Web direct `0.3.0` n'a pas justifié d'extraire prématurément un adapter WASM commun entre Reflex et Snake : les deux adapters spécifiques restent acceptés. Le second host Snake fournit un second consommateur technique, mais son statut de POC Tauri Android de référence réduit encore l'intérêt d'une factorisation durable : une extraction n'est retenue que si elle reste utile à au moins deux hosts réellement conservés.
## Relation avec Uroburas ## Relation avec Uroburas

View File

@@ -1,5 +1,5 @@
<!-- file: docs/development/006-ANDROID_RUST_NATIVE_BUILD.md --> <!-- file: docs/development/006-ANDROID_RUST_NATIVE_BUILD.md -->
<!-- version: 2 --> <!-- version: 7 -->
# Build Rust Android natif # Build Rust Android natif
@@ -12,95 +12,120 @@ Le crate Rust `game-android-entrypoint` est un `cdylib`. Il est compilé avec ex
- `reflex` ; - `reflex` ;
- `snake`. - `snake`.
Le même nom de bibliothèque native peut être utilisé dans les deux APK puisque chaque module Android possède son propre répertoire `jniLibs`. Le même nom de bibliothèque native peut être utilisé dans les deux APK puisque chaque module Android possède son propre ensemble de `jniLibs` générés.
## SDL3 AAR et Prefab ## SDL3 AAR et Prefab
L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab. L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab. Pour chaque ABI, la logique Gradle commune cherche d'abord :
Pour `arm64-v8a`, la structure attendue est notamment :
```text ```text
prefab/modules/SDL3/libs/android.arm64-v8a/libSDL3.so prefab/modules/SDL3/libs/android.<abi>/libSDL3.so
``` ```
Le script `scripts/build_android_rust.py` accepte cette structure officielle et conserve un fallback `jni/<abi>/libSDL3.so` pour compatibilité. Elle accepte aussi `jni/<abi>/libSDL3.so`, puis un fallback Prefab univoque. Pour chaque ABI elle :
Pour chaque application, le script : 1. extrait `libSDL3.so` depuis l'AAR dans un répertoire temporaire de linkage ;
2. stage `libSDL3.so` dans les `jniLibs` générés du variant ;
3. lance `cargo ndk` avec exactement une feature jeu ;
4. stage `libgame_android_entrypoint.so` dans le même arbre généré ;
5. vérifie la présence des deux bibliothèques avant de rendre l'output au variant AGP.
1. extrait `libSDL3.so` depuis l'AAR ; Les `jniLibs` générés sont déclarés à la Variant API via `variant.sources.jniLibs.addGeneratedSourceDirectory`. Gradle possède ainsi la dépendance entre le packaging Android et le build natif au lieu d'exiger une commande préalable séparée.
2. l'utilise comme bibliothèque de liaison pour le build Rust ;
3. copie `libSDL3.so` dans le `jniLibs/<abi>/` du module Android ;
4. lance `cargo ndk` ;
5. vérifie la présence de `libgame_android_entrypoint.so`.
Ainsi, `SDLActivity` dispose au runtime de `libSDL3.so` et de la bibliothèque Rust applicative.
## Toolchain ## Toolchain
SDL Android requiert actuellement SDK 35 ou ultérieur, NDK r28c ou ultérieur et API minimale 21. La baseline projet conserve `compileSdk 36`, `minSdk 21` et l'AAR SDL3 3.4.16. Le contrat projet utilise :
Installer au minimum :
```bash
rustup target add aarch64-linux-android
cargo install cargo-ndk
```
NDK de référence :
```text ```text
r28c = 28.2.13676358 AGP 9.4.0
Gradle minimum 9.6.0
JDK runtime >= 17, fourni par l'environnement
Java source/target 17
compileSdk 36
minSdk 21
NDK 28.2.13676358 / r28c
SDL3 AAR 3.4.16
cargo-ndk requis côté développeur
``` ```
Avec le SDK manager : `Android/settings.gradle` compare `GradleVersion.current()` au minimum projet `9.6.0`. Le projet n'épingle donc pas une distribution Gradle : un Gradle local plus récent est accepté tant qu'il satisfait le minimum.
```bash Le projet Android natif n'impose pas `JAVA_HOME`. Le JDK courant du shell/IDE exécute Gradle ; `java -version` et `gradle --version` sont relevés pendant les gates. Le niveau de bytecode/source Java de la glue reste explicitement `17`, indépendamment du JDK de build.
"${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "ndk;28.2.13676358" "platform-tools"
`cargo-ndk` détecte le NDK désigné pour le sous-processus via `ANDROID_NDK_HOME`. La tâche Gradle résout cette valeur à partir de `ANDROID_HOME` ou `ANDROID_SDK_ROOT` et de `androidNdkVersion`; aucun `ANDROID_NDK_HOME` global n'est requis.
## État 0.3.3-0-pre.5
`0-pre.4` a validé les quatre ABI sur Snake et Reflex : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`. Le même mécanisme Gradle est désormais enregistré pour toutes les variantes Android, et pas seulement Debug.
Pour Debug :
```text
:<game>:assembleDebug
-> buildDebugSasedevRust<Abi>
-> cargo ndk build
-> APK Debug universal
``` ```
`cargo-ndk` détecte un NDK installé par Android Studio ou utilise `ANDROID_NDK_HOME` lorsqu'il est défini. Pour Release/AAB :
Pour utiliser directement `adb` : ```text
:<game>:bundleRelease
```bash -> buildReleaseSasedevRust<Abi>
export PATH="${ANDROID_HOME}/platform-tools:${PATH}" -> cargo ndk build --release
-> AAB Release multi-ABI
``` ```
Cet export peut être ajouté au fichier de configuration shell local de la machine. Chaque arbre généré contient :
## Build ```text
build/generated/sasedevNative/<variant>/<abi>/jniLibs/<abi>/
├── libSDL3.so
└── libgame_android_entrypoint.so
```
Depuis la racine : Depuis la racine :
```bash ```bash
python3 scripts/build_android_rust.py reflex java -version
python3 scripts/build_android_rust.py snake (cd Android && gradle --version)
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
(cd Android && gradle :game-snake-poc:bundleRelease)
(cd Android && gradle :game-reflex-poc:bundleRelease)
``` ```
Puis : Le builder historique `scripts/build_android_rust.py` est retiré : il n'existe plus de chemin natif Android qui stage des bibliothèques dans `src/main/jniLibs`. Les sorties natives sont exclusivement générées sous `build/`.
```bash ### Pages mémoire 16 KB
cd Android
gradle :game-reflex-poc:assembleDebug
gradle :game-snake-poc:assembleDebug
cd ..
```
Les bibliothèques générées sous `Android/*/src/main/jniLibs/` sont des artefacts de build et ne sont pas versionnées. La validation porte sur les deux dimensions requises pour les appareils 64 bits concernés par la contrainte Google Play 16 KB :
1. segments ELF des `.so` `arm64-v8a` et `x86_64` avec un alignement `LOAD` au moins `2**14`, contrôlés par `llvm-objdump -p` depuis le NDK configuré ;
2. alignement ZIP de l'APK avec `zipalign -c -P 16 -v 4`.
AGP `9.4.0` est supérieur au plancher `8.5.1` documenté pour le packaging 16 KB et NDK `r28c` produit les nouvelles bibliothèques 64 bits avec alignement 16 KB par défaut. Cette propriété ne dispense pas de contrôler `libSDL3.so`, qui est précompilée dans l'AAR.
Les ABI 32 bits `armeabi-v7a` et `x86` restent dans le package pour compatibilité ancienne et peuvent conserver des segments ELF `2**12` ; cela ne constitue pas un échec de l'exigence Play 16 KB, explicitement portée sur les appareils 64 bits.
Pour l'AAB, les huit bibliothèques doivent être présentes sous `base/lib/<abi>/`. Lorsque `bundletool` est disponible, `bundletool dump config --bundle=<aab>` peut compléter la vérification du mode d'alignement du bundle.
La preuve runtime utilise une image Android 15/API 35 x86_64 `ps16k` et retient `adb shell getconf PAGE_SIZE` comme contrôle de la taille de page du device. `16384` est le résultat attendu ; une valeur `KernelPageSize` lue dans un VMA particulier de `/proc/<pid>/smaps` n'est pas utilisée comme substitut à ce contrôle.
### Plancher Android
Le contrat est consolidé à Android 5.0 / API 21 :
- `minSdk 21` dans chaque application ;
- `sasedevRustAndroidApi = 21` pour `cargo ndk` ;
- SDL3 documente API 21 comme minimum Android ;
- la gate `0-pre.5` a validé un smoke réel sur Android 5.0/API 21 x86 : installation de l'APK universal et démarrage de `SnakeActivity` réussis.
## Smoke appareil ## Smoke appareil
Préflight : L'APK universal doit être inspecté mécaniquement avant installation. Il doit contenir les deux bibliothèques natives pour chacune des quatre ABI : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
```bash Le smoke réel reste effectué sur les architectures matériellement disponibles : AVD x86_64 et appareil ARM64. Les ABI 32 bits sont néanmoins exigées au build et dans l'APK ; leur exécution réelle dépend de la disponibilité d'un appareil/AVD 32 bits. Le `minSdk 21` est consolidé en `0-pre.5`; les smokes disponibles servent de preuve runtime complémentaire et ne redéfinissent pas ce plancher.
adb version
adb devices
```
Puis installation/lancement des APK si un appareil ou émulateur est disponible.
L'absence de `adb` ou d'appareil n'est pas un défaut fonctionnel du projet ; elle bloque uniquement le smoke appareil.
## Exception FFI Rust ## Exception FFI Rust

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/000-README.md --> <!-- file: docs/plans/000-README.md -->
<!-- version: 1 --> <!-- version: 4 -->
# Plans de versions games.sasedev # Plans de versions games.sasedev
@@ -11,4 +11,6 @@ Le `ROADMAP.md` reste la trajectoire macroscopique du projet ; les deltas décri
## Plans ## Plans
- [`001-V0_3_0_WEB_SNAKE_POC_PLAN.md`](001-V0_3_0_WEB_SNAKE_POC_PLAN.md) — plan actif de `0.3.0`, baseline Snake et premier POC Web direct. - [`001-V0_3_0_WEB_SNAKE_POC_PLAN.md`](001-V0_3_0_WEB_SNAKE_POC_PLAN.md) — plan clôturé de `0.3.0`, baseline Snake et premier POC Web direct.
- [`002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md`](002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md) — plan clôturé de `0.3.1`, second host Snake Tauri Android.
- [`003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md`](003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md) — plan clôturé de `0.3.3`, pipeline Android SDL3 natif Gradle/Cargo multi-ABI.

View File

@@ -0,0 +1,207 @@
<!-- file: docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md -->
<!-- version: 11 -->
# Plan v0.3.1 — second host Snake Tauri Android
## But de la version
`0.3.1` doit terminer un second host Snake réellement exécutable sous Tauri Android en réutilisant la baseline Web/WASM validée par `0.3.0`, puis conserver ce host comme POC de référence plutôt que comme voie de production des jeux Android.
La version ne doit ni déplacer le gameplay hors de `game-snake-poc`, ni créer un second adapter Snake, ni transformer immédiatement les similitudes Web/Tauri en framework générique.
Base stable : `0.3.0`.
Cadrage : [`../studies/024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md`](../studies/024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md).
## Décisions acquises
- la nouvelle crate sera `crates/apps/game-snake-poc-tauri` ;
- `0.3.1` valide uniquement sa cible Android et en fait un POC de référence, pas une distribution jeu Android de production ;
- `game-snake-poc` reste inchangé sauf défaut réellement découvert ;
- `game-snake-poc-wasm` reste l'unique adapter Snake WASM et recevra seulement la généralisation de provenance nécessaire au second host ;
- le frontend Tauri part du comportement réellement validé dans `Web/game-snake-poc`, avec adaptation de lifecycle et logging ;
- `lib.rs` est une façade/reexport et `tauri.rs` reste l'assemblage Tauri ;
- le tracing frontend utilise `@fltsci/tauri-plugin-tracing` ;
- les hooks Tauri possèdent le build frontend/WASM ;
- aucun nouveau chemin de build n'est piloté par Python ;
- `README.md` et `USAGE.md` seront créés avec l'app : architecture pour le premier, prérequis/commandes/smokes pour le second ;
- `cargo tauri android init/dev/build` sont les commandes mobiles de référence ;
- SimpleBar et `resize-observer-polyfill` ne sont pas ajoutés au host Tauri sans besoin démontré ;
- la voie Android de production reste le host natif SDL3/Java/JNI tant quun besoin fonctionnel démontré ne justifie pas Tauri ;
- Tauri Android peut rester utile comme référence comparative, notamment pour évaluer plus tard les saisies texte/WebView, mais ce besoin nest pas considéré comme acquis ;
- une future variante Tauri Desktop reste conditionnée à un avantage explicite, notamment une monétisation WebView/publicitaire techniquement et contractuellement viable.
## Scope
La version couvre :
- création de la crate/app Tauri Snake ;
- initialisation de la cible Android par Tauri ;
- pipeline Rust/WASM + Vite/TypeScript possédé par Tauri sans Python ;
- consommation de `game-snake-poc-wasm` ;
- shell Canvas, contrôles touch et comportement responsive nécessaires au jeu ;
- assets communs et spécifiques Snake ;
- lifecycle mobile/WebView ;
- provenance `Android / Wasm / TauriWebView` ;
- tracing Rust/Tauri/frontend ;
- politique de sortie/Back conforme au moteur ;
- build/package et smoke sur AVD/appareil disponible aux jalons prévus ;
- comparaison du code Web direct/Tauri après fonctionnement réel afin de décider s'il existe une extraction commune justifiée.
Hors scope : Tauri Desktop Snake comme cible validée, iOS, Android SDL3 multi-ABI additionnel, réseau realtime, Uroburas, intégration ads effective, billing, auth et leaderboard.
## Positionnement produit après validation du host minimal
Le smoke `0-pre.2.fix.3` a confirmé que Tauri Android fonctionne techniquement, mais aussi que son outillage ajoute une couche de complexité propre au host : combinaison JDK/Gradle imposée par la version Tauri, projet Android généré et chaîne WebView/WASM supplémentaire alors que le POC Android natif SDL3 fonctionne déjà.
Décision de `0-pre.3` :
- terminer `0.3.1` afin dobtenir un POC Tauri Android jouable et documenté ;
- conserver `game-snake-poc-tauri` comme POC de référence et banc de comparaison ;
- ne pas utiliser Tauri Android comme template de nouvelles applications de jeu ;
- privilégier SDL3 natif pour Android ;
- ne rouvrir la question Tauri Android que pour un besoin concret que SDL3 ne couvre pas proprement, par exemple une saisie texte complexe après test réel des possibilités SDL ;
- évaluer séparément Tauri Desktop seulement si la WebView apporte un avantage produit mesurable, notamment une voie publicitaire exploitable.
Cette décision ne réduit pas le critère de qualité de `0.3.1` : le POC de référence doit rester jouable, reproductible et suffisamment complet pour permettre une comparaison technique utile.
## Prévision souple
### `0-pre.1` — cadrage et règles
Audit complet de l'archive stable, règles, baseline Web/WASM, Tauri Reflex, références Desk KSP accessibles, environnement Android historique et commandes Tauri mobiles actuelles.
Livraison : étude `024`, présent plan, correction des règles Tauri Android, clarification `PlatformFamily`, index documentaires et ouverture de la version technique `0.3.1-0-pre.1`.
Aucun scaffold Tauri Snake n'est créé. Tranche validée par la gate utilisateur du 2026-09-20.
### `0-pre.2` — host Tauri Android minimal
Créer `game-snake-poc-tauri` avec :
- manifest Cargo/build script/config Tauri/capabilities ;
- `lib.rs` façade, `tauri.rs` assemblage et modules propriétaires minimaux ;
- point d'entrée `run()` mobile annoté `#[cfg_attr(mobile, tauri::mobile_entry_point)]` dans le module propriétaire puis réexporté par `lib.rs` ;
- frontend Vite/TypeScript minimal ;
- bridge tracing initialisé ;
- hooks de build natifs, sans Python ;
- cible Android initialisée via Tauri ;
- chargement du même adapter `game-snake-poc-wasm` après généralisation minimale de provenance ;
- `README.md` et `USAGE.md` initiaux.
La tranche vise un premier démarrage Tauri Android minimal, pas encore toute la finition input/lifecycle.
État candidat livré : crate `game-snake-poc-tauri`, bridge Rust/Tauri, hooks WASM/Vite sans Python, frontend minimal, tracing frontend/Rust et provenance partagée `Android / TauriWebView / Wasm`. L'initialisation générée `cargo tauri android init` reste volontairement locale et fait partie de la gate utilisateur, car `gen/` n'est pas un artefact source distribué.
Gate utilisateur : audits, Cargo workspace strict, test ciblé de l'adapter WASM, non-régression du build Web direct, `cargo tauri android init` puis `cargo tauri android dev` sur un AVD ou appareil disponible. `cargo tauri android build` reste reporté aux phases finales sauf besoin diagnostique.
Le premier smoke `0-pre.2` a révélé deux défauts bornés : `build.rs` échouait sous Clippy strict à cause de la rustdoc manquante, et Java 25 ne pouvait pas exécuter le wrapper Gradle `8.14.3` généré par Tauri CLI `2.11.3`. `0-pre.2.fix.1` a tenté de migrer localement le scaffold généré vers Gradle 9 ; cette approche est abandonnée à partir de `0-pre.2.fix.2` car la branche stable Tauri 2.11.x n'a pas encore publié cette migration et le projet ne doit pas maintenir un fork implicite des templates Android Tauri. `0-pre.2.fix.3` fixe JDK 17 comme baseline Android reproductible pour la chaîne officielle Gradle `8.14.3` + AGP `8.11.0` et impose la régénération de tout `gen/android` touché par la tentative `fix.1`. Le Gradle global de la machine ne fait pas partie du contrat reproductible.
### `0-pre.3` — shell Snake, Canvas, input et assets
Réutiliser sélectivement le host Web validé :
- rendu Canvas et session fixed-step ;
- shell responsive pertinent ;
- contrôles directionnels touch/pointer et clavier lorsque disponible ;
- resize/orientation ;
- assets canoniques communs + Snake ;
- absence de dépendances Web-only inutiles.
Aucune extraction commune n'est réalisée uniquement pour éviter quelques fichiers similaires pendant la mise au point.
### `0-pre.4` — lifecycle, provenance, tracing et sortie mobile
Stabiliser :
- background/foreground et reprise sans rattrapage massif ;
- rotation/resize ;
- provenance `Android / Wasm / TauriWebView` + device/input observés ;
- tracing Rust/Tauri/frontend ;
- sorties applicatives conformes à `EngineGame::quit_requested` sans détourner le Back système Android ;
- smokes AVD et, si disponible, appareil réel.
Cette tranche doit rendre le second host fonctionnel de bout en bout et suffisamment stable pour être conservé comme POC de référence.
État candidat `0-pre.4` : la boucle Tauri dispose maintenant de `pause/resume`, remet à zéro son accumulateur à la reprise, suit `visibilitychange`, `pagehide/pageshow` et `ResizeObserver`, et journalise les transitions via le tracing frontend. Le Back Android nest pas intercepté : Snake conserve la politique moteur par défaut `Exit`, identique au comportement système Tauri du host mono-page. Le D-pad sous le Canvas est conservé comme limite ergonomique documentée plutôt que délargir cette version à un système swipe/overlay.
La gate AVD `0-pre.4` est propre. Le smoke supplémentaire sur Galaxy S9+ ARM64 a confirmé compilation `aarch64-linux-android`, installation, Canvas, assets, WASM, touch et lifecycle visible/hidden via un tunnel ADB USB. Deux écarts ont été observés : le HMR utilisait encore un port WebSocket séparé, défaut local corrigé par `0-pre.4.fix.1`, et le Back système a déclenché un abort natif `FORTIFY: pthread_mutex_lock called on a destroyed mutex` pendant le teardown de l'Activity/WebView. Les logs placent cet abort après `visibility hidden` et avant `pagehide`, sans `runtime disposed`.
`0-pre.4.fix.1` ne tente pas de migrer manuellement Tauri/Wry ni de patcher `gen/android`. Il unifie HTTP et HMR sur le port `1436` et sa gate réelle ARM64 est validée : le frontend atteint `ready`, le touch est observé et l'erreur WebSocket Vite a disparu. Le Back n'a pas reproduit l'abort `FORTIFY` du smoke précédent et est allé jusqu'au détachement de la fenêtre ; le défaut reste néanmoins considéré comme potentiellement intermittent jusqu'au smoke de l'APK autonome `2-beta.1`.
### `0-pre.5` — extraction conditionnelle
Créer uniquement si l'état `0-pre.4` montre une duplication réellement durable entre `Web/game-snake-poc` et le frontend Tauri **et** si l'extraction garde une valeur au-delà du seul POC Android de référence.
Une extraction possible doit rester bornée à une responsabilité clairement partagée, par exemple projection/rendu Canvas, mapping input Web ou helpers d'assets. Lifecycle et logging restent host-specific s'ils divergent réellement.
Si aucune extraction n'est justifiée, cette tranche est omise.
### `2-beta.1` — validation large et packaging Android
Tranche validée le 2026-09-21. Aucun `0-pre.5` n'était justifié : aucune extraction Web/Tauri ne conserve assez de valeur maintenant que le host Android Tauri est explicitement un POC de référence.
Résultats acquis :
- audits statiques, `cargo fmt`, `cargo check --workspace` et Clippy workspace strict propres ;
- suite `cargo test --workspace --all-targets --all-features` propre avec 35 tests réussis et aucun échec ;
- hook Tauri `beforeBuildCommand` : WASM release, `wasm-bindgen`, TypeScript/Vite et copie des assets réussis ;
- APK Debug universal généré avec `aarch64-linux-android` et `x86_64-linux-android` ;
- installation et lancement réussis sur Galaxy S9+ ARM64 réel puis AVD API 36 x86_64 ;
- aucun serveur Vite, port `1436` ou tunnel ADB requis pour le package autonome.
Le contrôle `unzip` explicite des deux entrées ABI n'a pas été fourni dans la sortie utilisateur ; la RC le conserve donc comme contrôle mécanique, même si l'installation/lancement du même APK sur ARM64 et x86_64 exerce déjà les deux architectures en pratique.
### `2-beta.2` — omise
Aucune consolidation beta supplémentaire ne justifie une tranche autonome. Les documents durables nécessaires sont consolidés directement à l'ouverture de la RC.
### `3-rc.1` — candidate gelée — validée
Tranche validée le 2026-09-21. Gel fonctionnel strict, synchronisation des versions RC, `CHANGELOG.md`, historique beta, matrice RC spécifique `0.3.1` et préparation du prompt de la prochaine version active. Aucun nouveau framework, host, gameplay ou comportement.
La gate RC a revalidé le workspace complet, reconstruit l'APK autonome universal, vérifié explicitement les entrées `arm64-v8a` et `x86_64`, puis installé et lancé le même APK sur AVD API 36 x86_64 et Galaxy S9+ ARM64. Aucun correctif RC n'est nécessaire.
### `0.3.1` — release stable — publiée
Promotion mécanique de la RC validée : versions stables, changelog, historique RC et clôture du plan. Aucun ajout fonctionnel. L'AAB reste volontairement hors `0.3.1` et appartient à la prochaine voie Android SDL3 native `0.3.3`.
## Inventaire environnement requis avant `0-pre.2`
La gate `0-pre.1` doit recevoir au minimum :
```bash
rustc --version
cargo --version
cargo tauri --version
rustup target list --installed
node --version
npm --version
wasm-bindgen --version
java -version
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
adb version
adb devices -l
emulator -list-avds
```
L'absence d'un outil ne doit pas être masquée. Elle devient un prérequis concret de `0-pre.2` ou motive un fix borné de `0-pre.1`.
La gate `0-pre.1` validée le 2026-09-20 a confirmé Rust `1.94.1`, Tauri CLI `2.11.3`, Node `24.21.0`, npm `11.19.0`, wasm-bindgen `0.2.128`, OpenJDK `25.0.4.1`, `ANDROID_HOME=/home/sinus/DEV/AndroidSDK`, un appareil ARM64 réel et trois AVD disponibles. `JAVA_HOME` était vide et Java 25 était résolu depuis le `PATH`. Les smokes `0-pre.2` ont confirmé que Tauri sélectionne lui-même le NDK side-by-side sous `$ANDROID_HOME/ndk/<version>`. Le JBR de l'Android Studio local est également Java 25 ; il n'est donc pas compatible avec Gradle `8.14.3`. Pour `0.3.1`, `JAVA_HOME` doit pointer vers JDK 17 pendant `cargo tauri android init/dev/build`.
## Critères de fermeture de 0.3.1
La version peut entrer en RC lorsque :
- Snake est jouable dans le WebView Tauri Android sans gameplay dupliqué et le host est explicitement positionné comme POC de référence ;
- le même adapter WASM sert Web direct et Tauri Android avec provenance exacte ;
- lifecycle/input/assets/tracing/Back ont un comportement explicite et validé ;
- le chemin de build ne dépend d'aucun orchestrateur Python ;
- le packaging Tauri Android est reproductible ;
- les smokes prévus sur cible Android ont été exécutés ;
- aucune extraction commune non prouvée n'a été introduite ;
- la documentation opérateur et architecture reflète l'état réel.
## Version suivante
`0.3.2` reste différée : aucune voie de monétisation Desktop/WebView suffisamment démontrée ne justifie son ouverture. La prochaine version active prévue est `0.3.3`, consacrée à Android SDL3 natif multi-ABI. Son `0-pre.1` devra notamment cadrer un AAB de distribution, distinguer compatibilité ABI et compatibilité de version Android, puis déterminer expérimentalement le `minSdk` réellement soutenable avant de promettre le support danciens appareils.

View File

@@ -0,0 +1,239 @@
<!-- file: docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md -->
<!-- version: 7 -->
# Plan 0.3.3 — Android SDL3 natif multi-ABI et packaging Gradle
## Statut
Plan actif créé pendant `0.3.3-0-pre.1` à partir de la stable taggée `v0.3.1`.
`0.3.2` reste volontairement différée. Aucun placeholder, POC Tauri Desktop ou changement cérémoniel n'est introduit pour cette version.
## Mission
`0.3.3` doit rendre le chemin Android SDL3 natif reproductible et possédé par Gradle/Cargo :
```text
Gradle -> Rust natif -> SDL3/JNI -> APK universal de test
Gradle -> Rust natif multi-ABI -> AAB de distribution
```
Le chemin productif Android ne doit dépendre ni de Tauri, ni de WebView, ni de WASM, ni d'un orchestrateur Python de build.
## Baseline et décisions retenues
La baseline `0.3.1` conserve :
- `game-android-entrypoint` comme `cdylib` commun ;
- exactement une feature jeu par application native ;
- Java comme glue Android ;
- `SaseGameActivity`/SDLActivity et un contrat JNI réduit ;
- assets canoniques hors modules Android et staging Gradle déjà basé sur la Variant API ;
- SDL3 AAR non vendorizé ;
- Snake Tauri Android uniquement comme POC de référence ARM64/x86_64.
Les décisions de cadrage `0-pre.1` sont :
```text
AGP 9.4.0
Gradle >= 9.6.0, version système acceptée si supérieure
JDK >= 17, fourni par l'environnement ; JDK 25 candidat validable
compileSdk 36
targetSdk 36
minSdk candidat 21
NDK 28.2.13676358 / r28c
SDL3 3.4.16
ABI par défaut arm64-v8a + armeabi-v7a + x86_64 + x86
ABI historiques pas de armeabi/mips/mips64 : retirées des toolchains modernes
APK test universal multi-ABI
AAB artefact de distribution cible
splits ABI hors chemin par défaut
```
Le `minSdk 21` est fondé sur le plancher Android actuellement documenté par SDL3, pas sur la seule valeur historique du dépôt. Le `targetSdk 36` est maintenu pour la politique Google Play actuelle.
### Révision de toolchain après `0-pre.2`
La preuve mono-ABI a réussi avec le wrapper Gradle `9.6.0` et JDK 17. Après cette validation, la décision projet est de ne pas transformer ces valeurs de preuve en versions exactes imposées au chemin SDL3 natif :
- AGP `9.4.0` conserve Gradle `9.6.0` comme minimum ;
- un Gradle système plus récent est autorisé et doit être affiché dans la gate ;
- le JDK runtime doit satisfaire Gradle/AGP (`>= 17`) mais provient de l'environnement ;
- le niveau Java de la glue Android reste `sourceCompatibility/targetCompatibility = 17` ;
- les contraintes JDK/Gradle du POC Tauri restent locales à ce POC et ne pilotent pas Android SDL3 natif.
Le pipeline devra aussi vérifier la compatibilité 16 KB des bibliothèques natives finales. AGP 9.4 et NDK r28c fournissent le socle requis, mais le prébuild SDL3 doit être contrôlé dans l'APK/AAB produit.
## Ownership cible
La logique durable doit rester répartie ainsi :
- Cargo/Rust : moteur, gameplay et `game-android-entrypoint` ;
- `Android/common` : glue Java/JNI commune durable ;
- `Android/game-*` : identité et configuration applicative par jeu ;
- `Android/gradle/` : logique de build Android commune, y compris orchestration Rust native et staging des sorties générées ;
- `assets/` : sources d'assets canoniques ;
- `Android/<app>/build/generated/` : outputs jetables de packaging ;
- le builder historique `scripts/build_android_rust.py` est supprimé en `0-pre.5` après migration de toutes ses responsabilités vers Gradle ; aucun équivalent Python ne doit être recréé.
Aucune `.so` générée ne doit être écrite durablement dans `src/main/jniLibs/` par le nouveau chemin.
## Contrat de la logique Gradle native
La future logique commune doit couvrir au minimum :
1. résolution du NDK side-by-side exact déclaré dans `Android/gradle.properties` ;
2. validation de la présence de l'AAR SDL3 attendu ;
3. extraction par ABI de `libSDL3.so` depuis Prefab vers un répertoire de build ;
4. linkage Rust contre la SDL extraite ;
5. invocation de `cargo ndk` avec `CARGO_NDK_PLATFORM=21` ;
6. mapping strict de chaque module vers exactement une feature `snake` ou `reflex` ;
7. build des quatre ABI Android encore supportées : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86` ;
8. staging de `libSDL3.so` et `libgame_android_entrypoint.so` dans un répertoire `jniLibs` généré ;
9. branchement de ce répertoire à la variante Android avant packaging ;
10. inputs/outputs Gradle assez précis pour un rebuild incrémental ;
11. diagnostics explicites si outil, target, AAR, NDK ou output manque ;
12. absence de secret de signature dans le dépôt.
Le projet Android natif déclare `minimumGradleVersion=9.6.0` et refuse au chargement une version inférieure. Le Gradle réellement utilisé est celui de l'environnement ; une version plus récente que le minimum est autorisée. Le projet n'épingle pas de wrapper natif.
## ABI et compatibilité OS
Le support CPU et le support de versions Android restent deux axes séparés.
`0-pre.4` révise la politique ABI : le projet vise le maximum raisonnable de compatibilité matérielle, y compris les architectures 32 bits encore supportées. La matrice obligatoire devient `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
`arm64-v8a` reste la cible appareil productive principale et `x86_64` la cible AVD principale, mais elles ne constituent plus à elles seules le contrat de packaging. `armeabi-v7a` et `x86` sont également construites et packagées afin de conserver la compatibilité avec du matériel plus ancien.
Les ABI abandonnées par les toolchains Android modernes (`armeabi`, `mips`, `mips64`) restent hors contrat : les réintroduire exigerait une toolchain historique/forkée et ne correspondrait plus au pipeline Android courant.
Le plancher OS candidat est Android 5.0 / API 21. La version doit tenter un smoke sur API 21 si une image exploitable est raisonnablement disponible. À défaut, elle teste la plus ancienne API localement disponible et consigne explicitement la lacune ; elle ne transforme pas un smoke absent en validation fictive.
## Packaging attendu
L'APK Debug de test doit rester universal : un seul fichier installable contient toutes les ABI retenues. La validation inspecte les entrées `lib/<abi>/...` avant les smokes.
L'AAB est l'artefact de distribution. La validation doit vérifier au minimum :
- présence des bibliothèques natives des ABI retenues ;
- absence de secret/keystore embarqué ;
- configuration d'alignement compatible pages mémoire 16 KB ;
- possibilité d'inspecter le bundle avec les outils Android appropriés ;
- distinction documentée entre bundle de distribution et APK installable.
Les APK split ABI ne sont pas générés par défaut dans `0.3.3`.
## Validation par type de jalon
Pendant les tranches d'implémentation, les validations restent ciblées : audits statiques, gates Rust dès qu'un fichier Rust/Cargo change, puis tâche Gradle du module touché.
Les smokes de packaging deviennent plus larges seulement quand le jalon produit un artefact installable :
- APK universal : inspection des quatre ABI + AVD x86_64 + appareil ARM64 ; les ABI 32 bits sont fumées seulement si un device/AVD compatible est disponible ;
- compatibilité basse : AVD API 21 si raisonnablement disponible, sinon plus ancienne API disponible avec écart documenté ;
- AAB : inspection du bundle et des bibliothèques natives, y compris compatibilité 16 KB ;
- beta/RC : gate workspace large planifiée, rebuild reproductible et smokes finaux.
Les builds, tests et smokes finaux restent exécutés et attestés par l'utilisateur.
## Forecast révisé
### `0-pre.1` — cadrage Android natif
Audit complet de `v0.3.1`, règles, pipeline Python historique, Gradle/AGP/NDK/SDL3, ABI/API, minSdk, APK universal, AAB, pages 16 KB, risques, matrice de validation et plan vivant.
Aucune réécriture Gradle productive dans cette tranche.
### `0-pre.2` — ownership Gradle/Cargo sur Snake mono-ABI
Introduire la logique Gradle commune minimale et prouver que `:game-snake-poc:assembleDebug` possède le build Rust `arm64-v8a`, l'extraction/linkage SDL3 et les `jniLibs` générés, sans appel préalable au script Python. Cette preuve a initialement utilisé un wrapper Gradle 9.6.0 afin d'isoler le task wiring.
La gate utilisateur du 2026-09-21 a validé ce chemin. Après cette preuve, la politique toolchain a été révisée : le projet Android natif utilise désormais un Gradle système `>= 9.6.0` et le JDK courant de l'environnement au lieu d'épinger Gradle/JDK pour ce chemin.
### `0-pre.3` — toolchain minimale, multi-ABI et APK universal
Retirer le wrapper natif introduit pour la preuve `0-pre.2`, déclarer et contrôler `Gradle >= 9.6.0`, ne plus imposer `JAVA_HOME`, puis étendre la logique commune à `arm64-v8a + x86_64`. Produire un APK Debug universal Snake et vérifier mécaniquement les deux couples `libSDL3.so`/`libgame_android_entrypoint.so`.
La factorisation commune étant réduite au mapping `module -> feature`, ajouter Reflex comme second consommateur avec les mêmes deux ABI, sans nouvelle architecture.
### `0-pre.4` — matrice ABI Android complète
Étendre Snake et Reflex aux quatre ABI encore supportées par Android NDK/SDL3/Rust : `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86`. Produire des APK Debug universal et vérifier mécaniquement les huit bibliothèques attendues par application.
Les smokes matériels restent ciblés sur les environnements disponibles ; l'absence d'un device/AVD 32 bits ne doit pas être transformée en fausse preuve runtime.
### `0-pre.5` — AAB, minSdk et fermeture du chemin historique
Étendre l'ownership Gradle aux variantes Release : `bundleRelease` doit reconstruire les quatre ABI en profil Cargo release sans secret embarqué. Inspecter les AAB, les segments ELF et l'alignement APK 16 KB ; consolider `minSdk 21` ; supprimer `scripts/build_android_rust.py` et interdire le retour des `src/main/jniLibs` générés.
Mettre à jour les documents Android/JNI/build durables pour décrire le chemin réel et remplacer les anciennes commandes de validation encore dépendantes du builder Python.
### `2-beta.1` — validation large et packaging final
`0-pre.5` a fermé le scope d'implémentation : AAB quatre ABI, Release Cargo pilotée par Gradle, suppression du builder Python, audit propre après suppression des anciens `src/main/jniLibs`, alignement 16 KB validé sur les ABI 64 bits, smoke API 21/x86 et smoke API 35/x86_64 `ps16k`.
La beta n'ajoute donc aucune capacité. Elle synchronise la version technique, consigne la preuve `0-pre.5`, exécute la gate Rust/workspace large planifiée, reconstruit les APK universal et AAB Snake/Reflex, puis revalide les smokes de référence x86_64 et ARM64. Les preuves de frontière API 21 et 16 KB restent dans l'historique `0-pre.5` et ne sont rejouées que si une modification ultérieure affecte le runtime/package natif.
Corriger uniquement par `2-beta.1.fix.N` si nécessaire ; ne pas ouvrir une beta cérémonielle supplémentaire si la matrice est propre.
### `3-rc.1` — gel et reproductibilité
La beta a passé la gate workspace complète, 35 tests, les rebuilds Debug/Release quatre ABI, l'inspection APK/AAB, `zipalign -P 16` et les smokes Snake/Reflex sur AVD API 36 x86_64 ainsi que Samsung API 29 ARM64. Aucun `2-beta.1.fix.N` n'est requis.
Geler le scope, consolider `CHANGELOG.md`, l'historique beta, la matrice RC et le prompt `0.3.4`. Rejouer uniquement les gates de publication/reproductibilité prévues ; aucune nouvelle capacité.
La convention de prerelease reste inchangée jusqu'à la stable `0.3.3`. La migration approuvée vers `X.Y.Z-alpha.M`, `X.Y.Z-beta.M`, `X.Y.Z-rc.M` et leurs `.fix.N` commence seulement dans la session `0.3.4`. Elle modifie les règles actives et les audits à partir de cette nouvelle version, sans renommer ni réécrire aucun ancien fichier sous `deltas/`, `history/` ou les anciennes entrées du changelog.
### `0.3.3` — stable
Promotion mécanique de la RC validée. Aucun changement fonctionnel ou architectural.
## Statut de clôture
Plan clôturé avec la publication stable `0.3.3` le 2026-09-21.
La RC a confirmé la reproductibilité du workspace, les builds Android Debug/Release quatre ABI, les APK/AAB, `zipalign -P 16`, les segments ELF 64 bits `align 2**14` et les smokes Snake/Reflex sur AVD API 36 x86_64 et appareil ARM64 réel. Les preuves de frontière API 21/x86 et API 35/x86_64 `ps16k` ont été acquises avant beta puis conservées sans régression du pipeline natif.
Aucun comportement nouveau n'est introduit par la promotion stable. La suite appartient à `0.3.4`, qui commencera sous la nouvelle convention `alpha.M / beta.M / rc.M` sans réécrire les jalons historiques de `0.3.3` ou antérieurs.
## Sizing
Le scope reste raisonnable pour une seule version avec cinq prereleases techniques avant beta, la nouvelle `0-pre.4` isolant volontairement l'extension 32 bits du packaging AAB/16 KB. Les zones susceptibles de forcer un split de version sont uniquement une incompatibilité profonde de `cargo-ndk` avec AGP/Gradle, une SDL3 AAR non exploitable par Prefab comme prévu, ou une incompatibilité effective d'une des deux ABI 32 bits encore supportées.
Un simple besoin de `.fix.N` après validation ne remet pas ce sizing en cause.
## Gate avant 0-pre.2
La validation utilisateur de `0-pre.1` doit inclure la gate Rust liée au changement de version workspace et l'inventaire frais suivant :
```bash
rustc --version
cargo --version
cargo ndk --version
rustup target list --installed
java -version
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
adb version
adb devices -l
emulator -list-avds
(cd Android && gradle --version)
printf 'configured NDK: '; sed -n 's/^androidNdkVersion=//p' ls -ld "$ANDROID_HOME/ndk/28.2.13676358"
test -f Android/libs/SDL3-3.4.16.aar && sha256sum Android/libs/SDL3-3.4.16.aar
```
La politique a été révisée après validation de `0-pre.2` : le projet Android natif dépend volontairement d'un Gradle fourni par l'environnement, avec minimum `9.6.0` déclaré et contrôlé par `settings.gradle`. Il n'impose plus de wrapper ni de `JAVA_HOME`; les gates consignent les versions réellement utilisées.
## Critères d'entrée en RC
`0.3.3` peut entrer en RC lorsque :
- un build Gradle Android natif n'exige plus `scripts/build_android_rust.py` ;
- Snake et Reflex produisent un APK universal contenant `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86` par un seul chemin Gradle ;
- l'AAB de distribution est constructible sans secret embarqué ;
- les bibliothèques natives 64 bits finales sont vérifiées pour la contrainte 16 KB et un smoke `ps16k` retourne `PAGE_SIZE=16384` ;
- `minSdk 21` est validé par un smoke réel Android 5.0/API 21 x86 ;
- l'appareil ARM64 réel et l'AVD x86_64 passent le smoke prévu ;
- Reflex est soit branché naturellement au pipeline commun, soit explicitement borné selon les résultats de factorisation ;
- le script Python historique Android est supprimé et l'audit interdit son retour ;
- les documents Android, build, JNI et validation décrivent le chemin réellement livré ;
- les gates workspace/beta applicables sont propres.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/PROMPT_STRUCTURE.md --> <!-- file: docs/rules/PROMPT_STRUCTURE.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Structure des prompts de reprise # Structure des prompts de reprise
@@ -24,7 +24,7 @@ Le prompt rappelle les invariants qui évitent les erreurs de workflow et renvoi
## Commandes avec répertoire et Tauri ## Commandes avec répertoire et Tauri
- **PROMPT-STR-016** — Le prompt rappelle que toute commande nécessitant un `cd` est englobée dans un sous-shell, par exemple `(cd <dir> && <commande>)`, afin de ne pas modifier le répertoire courant pour les commandes suivantes. - **PROMPT-STR-016** — Le prompt rappelle que toute commande nécessitant un `cd` est englobée dans un sous-shell, par exemple `(cd <dir> && <commande>)`, afin de ne pas modifier le répertoire courant pour les commandes suivantes.
- **PROMPT-STR-017** — Lorsqu'une version touche Tauri, le prompt rappelle que `npm` sert à gérer les dépendances frontend mais que les builds/smokes passent par Tauri : `(cd <tauri-app> && cargo tauri dev)` pour le smoke courant et `(cd <tauri-app> && cargo tauri build)` aux phases finales de packaging prévues. Les hooks Tauri possèdent Vite/TypeScript/WASM ; `npm run dev`/`npm run build` ne deviennent pas des gates manuelles Tauri. - **PROMPT-STR-017** — Lorsqu'une version touche Tauri, le prompt rappelle que `npm` sert à gérer les dépendances frontend mais que les builds/smokes passent par Tauri. Il utilise les commandes de la cible concernée : `(cd <tauri-app> && cargo tauri dev)` / `cargo tauri build` pour Desktop, ou `(cd <tauri-app> && cargo tauri android dev)` / `cargo tauri android build` pour Android. Les hooks Tauri possèdent Vite/TypeScript/WASM ; `npm run dev`/`npm run build` ne deviennent pas des gates manuelles Tauri.
## Documentation et traçabilité à rappeler ## Documentation et traçabilité à rappeler

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_COMMANDS.md --> <!-- file: docs/rules/RULES_COMMANDS.md -->
<!-- version: 13 --> <!-- version: 16 -->
# Règles d'exécution des commandes # Règles d'exécution des commandes
@@ -48,11 +48,11 @@
## Android et Gradle ## Android et Gradle
- **CMD-ANDROID-001** — Les commandes Gradle Android sont exécutées depuis `Android/` ou avec un chemin explicite vers le wrapper du projet. - **CMD-ANDROID-001** — Les commandes Gradle du projet Android natif sont exécutées depuis `Android/` avec le `gradle` résolu par lenvironnement ; sa version doit être au moins égale à `minimumGradleVersion` déclaré par le projet.
- **CMD-ANDROID-002** — Les tâches ciblées par application sont préférées, par exemple `./gradlew :game-reflex-poc:assembleDebug`, lorsqu'elles existent. - **CMD-ANDROID-002** — Les tâches ciblées par application sont préférées, par exemple `gradle :game-reflex-poc:assembleDebug`, lorsqu'elles existent.
- **CMD-ANDROID-003** — Un build Android global n'est pas exécuté si la tranche ne touche ni Android ni le contrat natif utilisé par Android. - **CMD-ANDROID-003** — Un build Android global n'est pas exécuté si la tranche ne touche ni Android ni le contrat natif utilisé par Android.
- **CMD-ANDROID-004** — `gradle clean` ou `./gradlew clean` reste un nettoyage Android ciblé ; il n'est pas rendu obligatoire uniquement parce qu'un `cargo clean` est planifié. - **CMD-ANDROID-004** — `gradle clean` reste un nettoyage Android ciblé ; il n'est pas rendu obligatoire uniquement parce qu'un `cargo clean` est planifié.
- **CMD-ANDROID-005** — Les commandes Android réelles ne deviennent des gates qu'après introduction du wrapper Gradle, de l'AGP, du NDK, de SDL3 et des modules exécutables correspondants. - **CMD-ANDROID-005** — Les commandes Android réelles ne deviennent des gates qu'après déclaration d'un minimum Gradle contrôlé, de l'AGP, du NDK, de SDL3 et des modules exécutables correspondants.
## Web ## Web
@@ -61,11 +61,14 @@
- **CMD-WEB-003** — Le POC Tauri/WASM historique `0.1.0` utilise `scripts/build_reflex_tauri_wasm.py` ; ce script reste une référence de baseline mais ne doit pas être réutilisé comme orchestrateur par un POC `0.3.x`, conformément à `CMD-BUILD-004`. - **CMD-WEB-003** — Le POC Tauri/WASM historique `0.1.0` utilise `scripts/build_reflex_tauri_wasm.py` ; ce script reste une référence de baseline mais ne doit pas être réutilisé comme orchestrateur par un POC `0.3.x`, conformément à `CMD-BUILD-004`.
- **CMD-WEB-004** — Les fichiers produits par `wasm-bindgen` sont générés localement et ne sont pas commités. - **CMD-WEB-004** — Les fichiers produits par `wasm-bindgen` sont générés localement et ne sont pas commités.
- **CMD-WEB-005** — Dans une app Tauri, `npm` sert uniquement à gérer les dépendances frontend lorsque nécessaire, par exemple `npm i <package>`, `npm i -D <package>` ou leur opération inverse. Les scripts `npm run dev`, `npm run build` ou équivalents ne sont pas des gates manuelles : Vite/TypeScript/WASM sont déclenchés par les hooks Tauri configurés. - **CMD-WEB-005** — Dans une app Tauri, `npm` sert uniquement à gérer les dépendances frontend lorsque nécessaire, par exemple `npm i <package>`, `npm i -D <package>` ou leur opération inverse. Les scripts `npm run dev`, `npm run build` ou équivalents ne sont pas des gates manuelles : Vite/TypeScript/WASM sont déclenchés par les hooks Tauri configurés.
- **CMD-WEB-006** — Le smoke normal d'une app Tauri est lancé via `(cd <tauri-app> && cargo tauri dev)`. Cette commande possède le serveur Vite et les hooks frontend nécessaires. - **CMD-WEB-006** — Le smoke normal d'une app Tauri Desktop est lancé via `(cd <tauri-app> && cargo tauri dev)`. Cette commande possède le serveur Vite et les hooks frontend nécessaires.
- **CMD-WEB-007** — Le packaging Tauri est validé dans les phases finales prévues par le plan via `(cd <tauri-app> && cargo tauri build)`, typiquement en beta préfinale, RC ou avant release selon le scope. Il n'est pas exécuté après chaque petit delta Tauri. - **CMD-WEB-007** — Le packaging Tauri Desktop est validé dans les phases finales prévues par le plan via `(cd <tauri-app> && cargo tauri build)`, typiquement en beta préfinale, RC ou avant release selon le scope. Il n'est pas exécuté après chaque petit delta Tauri.
- **CMD-WEB-008** — Pour un host Web navigateur direct sous `Web/<game>/`, les commandes npm/Vite peuvent être exécutées directement, toujours dans un sous-shell lorsqu'un changement de répertoire est nécessaire, par exemple `(cd Web/<game> && npm install && npm run build)`. - **CMD-WEB-008** — Pour un host Web navigateur direct sous `Web/<game>/`, les commandes npm/Vite peuvent être exécutées directement, toujours dans un sous-shell lorsqu'un changement de répertoire est nécessaire, par exemple `(cd Web/<game> && npm install && npm run build)`.
- **CMD-WEB-009** — Le build d'un host Web direct qui consomme un adapter `wasm-bindgen` exécute d'abord le build Rust/WASM et la génération des bindings hors dépôt, puis seulement le build TypeScript/Vite. - **CMD-WEB-009** — Le build d'un host Web direct qui consomme un adapter `wasm-bindgen` exécute d'abord le build Rust/WASM et la génération des bindings hors dépôt, puis seulement le build TypeScript/Vite.
- **CMD-WEB-010** — Un smoke navigateur manuel d'un host Web direct vérifie au minimum le chargement WASM, le Canvas, les entrées prévues par la tranche et le comportement responsive concerné. - **CMD-WEB-010** — Un smoke navigateur manuel d'un host Web direct vérifie au minimum le chargement WASM, le Canvas, les entrées prévues par la tranche et le comportement responsive concerné.
- **CMD-WEB-011** — Une cible Tauri Android existante est initialisée depuis sa crate avec `(cd <tauri-app> && cargo tauri android init)` lorsqu'elle ne possède pas encore le projet mobile généré requis ; cette initialisation n'est pas rejouée comme gate ordinaire.
- **CMD-WEB-012** — Le smoke interactif d'une app Tauri Android utilise `(cd <tauri-app> && cargo tauri android dev)` ; il remplace `cargo tauri dev` pour cette cible mobile.
- **CMD-WEB-013** — Le packaging Tauri Android utilise `(cd <tauri-app> && cargo tauri android build)` dans les phases finales prévues par le plan ; `--apk` ou `--aab` est ajouté uniquement lorsque le livrable visé le requiert.
## Git et fichiers générés ## Git et fichiers générés
@@ -78,7 +81,7 @@
- **CMD-BETA-001** — À l'entrée en beta, `scripts/audit_distribution_layout.py` vérifie les frontières statiques nécessaires aux runners et packagings supportés. - **CMD-BETA-001** — À l'entrée en beta, `scripts/audit_distribution_layout.py` vérifie les frontières statiques nécessaires aux runners et packagings supportés.
- **CMD-BETA-002** — La transition alpha vers beta exécute une suite Cargo workspace complète en plus des tests ciblés. - **CMD-BETA-002** — La transition alpha vers beta exécute une suite Cargo workspace complète en plus des tests ciblés.
- **CMD-BETA-003** — Si la version touche le périmètre Tauri, le plan réserve au moins une validation de packaging dans une phase finale appropriée via `(cd <tauri-app> && cargo tauri build)` ; les itérations et smokes courants utilisent `cargo tauri dev`. - **CMD-BETA-003** — Si la version touche le périmètre Tauri, le plan réserve au moins une validation de packaging dans une phase finale appropriée avec la commande Tauri correspondant à la cible : `cargo tauri build` pour Desktop ou `cargo tauri android build` pour Android. Les smokes utilisent de même `cargo tauri dev` ou `cargo tauri android dev` selon la cible.
- **CMD-BETA-004** — Les APK Debug servent à la validation multi-appareils beta ; la signature de publication appartient à la phase RC/stable. - **CMD-BETA-004** — Les APK Debug servent à la validation multi-appareils beta ; la signature de publication appartient à la phase RC/stable.
## RC et release ## RC et release
@@ -86,7 +89,7 @@
- **CMD-RC-001** — L'entrée en RC gèle le périmètre fonctionnel de la version courante ; seuls les correctifs, la reproductibilité des builds, le packaging, la documentation de livraison et les défauts de release sont admis. - **CMD-RC-001** — L'entrée en RC gèle le périmètre fonctionnel de la version courante ; seuls les correctifs, la reproductibilité des builds, le packaging, la documentation de livraison et les défauts de release sont admis.
- **CMD-RC-002** — La validation d'une RC exécute `cargo test --workspace --all-targets --all-features` en plus des audits, du check et de Clippy strict. - **CMD-RC-002** — La validation d'une RC exécute `cargo test --workspace --all-targets --all-features` en plus des audits, du check et de Clippy strict.
- **CMD-RC-003** — Les deux runners Desktop natifs sont construits en `--release` et font l'objet d'un smoke sur les binaires de release. - **CMD-RC-003** — Les deux runners Desktop natifs sont construits en `--release` et font l'objet d'un smoke sur les binaires de release.
- **CMD-RC-004** — Une RC Tauri est construite via `(cd <tauri-app> && cargo tauri build)` ; ses hooks possèdent toujours le build WASM et Vite/TypeScript. Les smokes interactifs restent lancés avec `(cd <tauri-app> && cargo tauri dev)`. - **CMD-RC-004** — Une RC Tauri est construite avec la commande de packaging correspondant à sa cible : `(cd <tauri-app> && cargo tauri build)` pour Desktop ou `(cd <tauri-app> && cargo tauri android build)` pour Android. Ses hooks possèdent toujours le build WASM et Vite/TypeScript ; les smokes utilisent la variante `dev` correspondante.
- **CMD-RC-005** — Android RC revalide au minimum x86_64 sur AVD et ARM64 sur appareil réel avec les APK issus de l'état RC. - **CMD-RC-005** — Android RC revalide au minimum x86_64 sur AVD et ARM64 sur appareil réel avec les APK issus de l'état RC.
- **CMD-RC-006** — Les secrets de signature, keystores et credentials de publication ne sont jamais commités. Leur présence est une condition externe de publication, pas une donnée du dépôt. - **CMD-RC-006** — Les secrets de signature, keystores et credentials de publication ne sont jamais commités. Leur présence est une condition externe de publication, pas une donnée du dépôt.
- **CMD-RC-007** — Une RC n'est promue en stable que si aucun correctif `.fix.N` n'est nécessaire après la gate RC complète. - **CMD-RC-007** — Une RC n'est promue en stable que si aucun correctif `.fix.N` n'est nécessaire après la gate RC complète.
@@ -100,7 +103,7 @@
## Outils de build et scripts d'audit ## Outils de build et scripts d'audit
- **CMD-BUILD-001** — Les scripts Python du dépôt sont autorisés pour les audits, audits complémentaires, validations et validations complémentaires. - **CMD-BUILD-001** — Les scripts Python du dépôt sont autorisés pour les audits, audits complémentaires, validations et validations complémentaires.
- **CMD-BUILD-002** — À partir de `0.3.x`, aucun chemin de build nouveau ou modifié nest piloté par Python. Les orchestrateurs historiques `scripts/build_reflex_tauri_wasm.py` et `scripts/build_android_rust.py` restent tolérés uniquement comme mécanismes gelés de la baseline `0.1.0` jusquà la tranche qui réactive leur chemin ; ils ne sont ni copiés, ni généralisés, ni utilisés pour un nouveau POC. - **CMD-BUILD-002** — À partir de `0.3.x`, aucun chemin de build nouveau ou modifié nest piloté par Python. L'orchestrateur historique `scripts/build_reflex_tauri_wasm.py` reste toléré uniquement comme mécanisme gelé de la baseline `0.1.0` jusquà la tranche qui réactive son chemin ; il n'est ni copié, ni généralisé, ni utilisé pour un nouveau POC. Le builder Android historique `scripts/build_android_rust.py` a été retiré lorsque son chemin a été réactivé en `0.3.3`.
- **CMD-BUILD-003** — Les builds utilisent l'outil natif approprié au périmètre : Cargo pour Rust, Gradle pour Android, Tauri CLI pour Tauri, ou l'outil officiellement retenu par la plateforme concernée. - **CMD-BUILD-003** — Les builds utilisent l'outil natif approprié au périmètre : Cargo pour Rust, Gradle pour Android, Tauri CLI pour Tauri, ou l'outil officiellement retenu par la plateforme concernée.
- **CMD-BUILD-004** — Les POC `0.3.x` doivent remplacer toute orchestration de build Python restante par des procédures explicites, reproductibles et testées avec les outils natifs. - **CMD-BUILD-004** — Les POC `0.3.x` doivent remplacer toute orchestration de build Python restante par des procédures explicites, reproductibles et testées avec les outils natifs.
- **CMD-BUILD-005** — Les builds, tests unitaires, tests d'intégration et smoke tests de validation sont exécutés côté utilisateur ; les scripts d'audit peuvent vérifier statiquement leur préparation mais ne les simulent pas. - **CMD-BUILD-005** — Les builds, tests unitaires, tests d'intégration et smoke tests de validation sont exécutés côté utilisateur ; les scripts d'audit peuvent vérifier statiquement leur préparation mais ne les simulent pas.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_PROJECT.md --> <!-- file: docs/rules/RULES_PROJECT.md -->
<!-- version: 16 --> <!-- version: 17 -->
# Règles spécifiques games.sasedev # Règles spécifiques games.sasedev
@@ -72,7 +72,7 @@
- **GAME-PLATFORM-011** — Les traces frontend Tauri passent par `@fltsci/tauri-plugin-tracing` vers `tauri-plugin-tracing`/`tracing`; les `console.*` applicatifs directs sont interdits hors fallback interne. - **GAME-PLATFORM-011** — Les traces frontend Tauri passent par `@fltsci/tauri-plugin-tracing` vers `tauri-plugin-tracing`/`tracing`; les `console.*` applicatifs directs sont interdits hors fallback interne.
- **GAME-PLATFORM-012** — Le plugin tracing Tauri ne remplace pas l'initialisation du subscriber Rust ; une app Tauri initialise le runtime de logging partagé avant son premier événement `tracing`. - **GAME-PLATFORM-012** — Le plugin tracing Tauri ne remplace pas l'initialisation du subscriber Rust ; une app Tauri initialise le runtime de logging partagé avant son premier événement `tracing`.
- **GAME-PLATFORM-013** — Les demandes de sortie Tauri sont évaluées par la politique `EngineGame::quit_requested` avant toute fermeture native. - **GAME-PLATFORM-013** — Les demandes de sortie applicatives Tauri sont évaluées par la politique `EngineGame::quit_requested` avant toute fermeture native contrôlée par l'application. Cette règle n'impose pas de consommer le Back système Android, qui reste régi par `GAME-PLATFORM-014`.
- **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-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.
@@ -81,7 +81,7 @@
## Réservation de plateformes ## Réservation de plateformes
- **GAME-PLATFORM-016** — Les familles de plateformes réservées sont Desktop, Mobile et Web. Desktop inclut potentiellement Linux, Windows et macOS ; Mobile inclut potentiellement Android et iOS. - **GAME-PLATFORM-016** — Les familles conceptuelles réservées sont Desktop, Mobile et Web. Desktop inclut potentiellement Linux, Windows et macOS ; Mobile inclut potentiellement Android et iOS. Le contrat runtime V1 reste volontairement plus concret : `PlatformFamily` expose actuellement `Android`, `Desktop` et `Web` ; un host Tauri Android utilise donc `PlatformFamily::Android` tant qu'aucune généralisation Mobile/iOS n'est justifiée.
- **GAME-PLATFORM-017** — Téléphone, tablette et futures classes de device sont des dimensions distinctes de l'OS et du backend technique. - **GAME-PLATFORM-017** — Téléphone, tablette et futures classes de device sont des dimensions distinctes de l'OS et du backend technique.
- **GAME-PLATFORM-018** — SDL3 reste le backend natif de référence du POC, mais l'architecture de jeu ne doit pas assimiler une plateforme à SDL ni empêcher un adapter différent lorsque la plateforme l'exige. - **GAME-PLATFORM-018** — SDL3 reste le backend natif de référence du POC, mais l'architecture de jeu ne doit pas assimiler une plateforme à SDL ni empêcher un adapter différent lorsque la plateforme l'exige.
- **GAME-PLATFORM-019** — Une plateforme réservée n'est ni implémentée ni planifiée tant qu'une ligne ROADMAP ou un delta ne l'engage explicitement. - **GAME-PLATFORM-019** — Une plateforme réservée n'est ni implémentée ni planifiée tant qu'une ligne ROADMAP ou un delta ne l'engage explicitement.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_VALIDATION_MATRIX.md --> <!-- file: docs/rules/RULES_VALIDATION_MATRIX.md -->
<!-- version: 4 --> <!-- version: 6 -->
# Matrice normative des commandes et validations # Matrice normative des commandes et validations
@@ -29,13 +29,16 @@ Les commandes ciblées restent la norme pendant l'implémentation ; les gates wo
| `CMD-026` | `cargo tree -p <crate> --edges normal` ou variante ciblée | — | dépendances/features modifiées ou diagnostic | selon portée | | `CMD-026` | `cargo tree -p <crate> --edges normal` ou variante ciblée | — | dépendances/features modifiées ou diagnostic | selon portée |
| `CMD-030` | build Desktop `--release` ciblé | gates Rust applicables | runner/distribution Desktop touché | beta | | `CMD-030` | build Desktop `--release` ciblé | gates Rust applicables | runner/distribution Desktop touché | beta |
| `CMD-031` | smoke Desktop release | `CMD-030` | runtime Desktop touché | beta | | `CMD-031` | smoke Desktop release | `CMD-030` | runtime Desktop touché | beta |
| `CMD-040` | `(cd <tauri-app> && cargo tauri dev)` | gates Rust/frontend applicables | smoke interactif Tauri | `pre`/beta | | `CMD-040` | `(cd <tauri-app> && cargo tauri dev)` | gates Rust/frontend applicables | smoke interactif Tauri Desktop | `pre`/beta |
| `CMD-041` | `(cd <tauri-app> && cargo tauri build)` | gates Rust/frontend applicables | packaging Tauri final/prefinal | beta/RC | | `CMD-041` | `(cd <tauri-app> && cargo tauri build)` | gates Rust/frontend applicables | packaging Tauri Desktop final/prefinal | beta/RC |
| `CMD-042` | build Rust `wasm32-unknown-unknown` + `wasm-bindgen --target web` | gates Rust de l'adapter | adapter WASM/Web direct touché | `pre` | | `CMD-042` | build Rust `wasm32-unknown-unknown` + `wasm-bindgen --target web` | gates Rust de l'adapter | adapter WASM/Web direct touché | `pre` |
| `CMD-043` | `(cd Web/<game> && npm install && npm run build)` | `CMD-042` si frontend avec WASM | frontend Web direct touché | `pre` | | `CMD-043` | `(cd Web/<game> && npm install && npm run build)` | `CMD-042` si frontend avec WASM | frontend Web direct touché | `pre` |
| `CMD-044` | smoke navigateur du host Web direct | `CMD-043` | Canvas/input/responsive Web touchés | `pre`/beta | | `CMD-044` | smoke navigateur du host Web direct | `CMD-043` | Canvas/input/responsive Web touchés | `pre`/beta |
| `CMD-045` | `(cd <tauri-app> && cargo tauri android init)` | environnement Android/Tauri | initialisation unique de la cible Tauri Android | `pre` |
| `CMD-046` | `(cd <tauri-app> && cargo tauri android dev)` | gates Rust/frontend applicables | smoke interactif Tauri Android | `pre`/beta |
| `CMD-047` | `(cd <tauri-app> && cargo tauri android build)` | gates Rust/frontend applicables | packaging Tauri Android final/prefinal | beta/RC |
| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta | | `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta |
| `CMD-051` | `(cd Android && ./gradlew :<app>:assembleDebug)` | `CMD-050` si Rust natif change | Android/app/manifest/Java touché | pre/beta | | `CMD-051` | `(cd Android && gradle :<app>:assembleDebug)` | `CMD-050` si Rust natif change | Android/app/manifest/Java touché | pre/beta |
| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta | | `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta |
| `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC | | `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC |
| `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance | | `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance |
@@ -52,7 +55,7 @@ Dès que du Rust ou une dépendance Cargo change, la séquence minimale obligato
`CMD-026` est ajouté lorsqu'un graphe de dépendances/features a changé ou doit être diagnostiqué ; il est omis des validations ordinaires sans changement de dépendances. `CMD-026` est ajouté lorsqu'un graphe de dépendances/features a changé ou doit être diagnostiqué ; il est omis des validations ordinaires sans changement de dépendances.
Toute commande avec changement de répertoire utilise un sous-shell, comme le montrent `CMD-040`, `CMD-041`, `CMD-043` et `CMD-051`. Toute commande avec changement de répertoire utilise un sous-shell, comme le montrent `CMD-040`, `CMD-041`, `CMD-043`, `CMD-045` à `CMD-047` et `CMD-051`.
## Dépendances entre crates ## Dépendances entre crates

View File

@@ -1,5 +1,5 @@
<!-- file: docs/studies/000-README.md --> <!-- file: docs/studies/000-README.md -->
<!-- version: 9 --> <!-- version: 11 -->
# Études # Études
@@ -57,3 +57,11 @@ Ces documents étudient les POC à réaliser ; ils ne lancent encore aucune impl
## Étude de cadrage 0.3.0 ## Étude de cadrage 0.3.0
- [`023-V0_3_0_PLATFORM_POC_AUDIT.md`](023-V0_3_0_PLATFORM_POC_AUDIT.md) — audit réel de la baseline `0.2.0`, requirements Snake, graphe de dépendances, choix du premier POC et sizing corrigé de `0.3.0`. - [`023-V0_3_0_PLATFORM_POC_AUDIT.md`](023-V0_3_0_PLATFORM_POC_AUDIT.md) — audit réel de la baseline `0.2.0`, requirements Snake, graphe de dépendances, choix du premier POC et sizing corrigé de `0.3.0`.
## Étude de cadrage 0.3.1
- [`024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md`](024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md) — audit de la baseline `0.3.0`, réutilisation Web/WASM, référence Tauri existante, environnement Android et sizing du second host Snake.
## Étude de cadrage 0.3.3
- [`025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md`](025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md) — audit de la baseline `0.3.1`, toolchain Android native actuelle, choix ABI/API, packaging APK/AAB et contraintes 16 KB.

View File

@@ -0,0 +1,223 @@
<!-- file: docs/studies/024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md -->
<!-- version: 1 -->
# Audit v0.3.1 — second host Snake Tauri Android
## Statut
Étude de cadrage `0.3.1-0-pre.1`.
Base autoritaire : archive téléchargée depuis le tag `v0.3.0` et fournie pour cette reprise.
Cette tranche reste un gate d'audit, requirements, sizing et planification. Aucun scaffold Tauri Android Snake, aucun frontend nouveau et aucun changement de gameplay n'y sont introduits.
## Intégrité de la baseline
L'archive `v0.3.0` a été extraite et testée avant modification.
Les contrôles statiques de baseline sont propres :
```text
unzip -t : no errors
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 197 file(s))
Distribution layout audit: clean (31 required path(s), 1 forbidden path(s) absent)
```
La version technique racine est `0.3.0`. Aucun `Cargo.lock`, `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `node_modules/`, `dist/`, `target/` ou binding WASM généré n'est présent dans l'archive source.
L'absence de `.git` est normale pour cette archive taggée et ne permet ni ne requiert d'inventer un état Git.
## État fonctionnel hérité
La release `0.3.0` apporte déjà les briques nécessaires au second host :
- `game-snake-poc` porte le gameplay et reste indépendant du host ;
- `game-snake-poc-wasm` expose le runtime Snake à WebAssembly ;
- `Web/game-snake-poc` valide Canvas, clavier/pointer/touch, resize, lifecycle navigateur, assets, diagnostics frontend et provenance ;
- le runner Desktop SDL3 reste le témoin natif de non-régression ;
- les builds Web nouveaux utilisent Cargo, `wasm-bindgen` et Vite sans orchestration Python.
Le POC Tauri Reflex historique fournit en plus une référence de structure Tauri, mais il conserve des hooks Python gelés qui ne doivent pas être copiés dans un chemin `0.3.x`.
Son manifest contient déjà le triplet `crate-type = ["staticlib", "cdylib", "rlib"]`, compatible avec la préparation mobile Tauri. En revanche son `run()` reste un point d'entrée Desktop et n'est pas annoté `#[cfg_attr(mobile, tauri::mobile_entry_point)]`. La nouvelle app Snake doit donc reprendre la séparation de fichiers, pas ce détail Desktop : le `run()` possédé par `tauri.rs` devra être le point d'entrée mobile et être réexporté par la façade `lib.rs`.
Le manifest Reflex déclare aussi `engine-v1-platform-api`, alors que son code source Tauri ne le consomme pas directement. Cette dépendance ne doit pas être copiée par mimétisme dans la nouvelle app ; la provenance Snake appartient déjà à l'adapter WASM tant qu'aucune responsabilité backend Tauri ne justifie ce lien.
## Référence KSP Desk
L'archive de référence KSP `khadhroony-solana-project-v0.3.15.zip` est bien présente dans la bibliothèque du projet, mais son export binaire vers l'environnement de travail n'est pas autorisé par la bibliothèque. Son contenu intégral n'est donc pas présenté comme audité ici.
Les éléments KSP indexés accessibles confirment néanmoins la convention recherchée :
- `src/tauri.rs` concentre l'assemblage Tauri et le lifecycle de fenêtre ;
- des modules propriétaires tels que `app_state.rs`, `route_runtime.rs` et `frontend_logging.rs` portent leur logique au lieu de gonfler `tauri.rs` ;
- les diagnostics frontend sont routés vers des cibles `tracing` structurées.
Le modèle normatif de `games.sasedev` reste la source de vérité : `lib.rs` façade/reexports, `tauri.rs` assemblage et pont Web/Rust, modules propriétaires séparés.
## Réutilisation attendue du host Web
### Réutilisé sans changement de responsabilité
`game-snake-poc` reste inchangé comme source de vérité du gameplay.
Les assets canoniques sous `assets/common/` et `assets/game-snake-poc/` restent les mêmes ; le host Tauri doit les embarquer ou les copier au build, pas créer une seconde source.
Le contrat sémantique d'entrée du jeu reste le même. Les boutons tactiles et éventuelles entrées clavier du WebView convergent vers les mêmes actions Snake.
### Réutilisé avec adaptation de host
Le Canvas, la session fixed-step, la projection de scène et une grande partie du shell Vite/TypeScript du host Web sont des références directes pour le frontend Tauri.
La détection `DeviceClass` et `InputProfile` peut être reprise, mais les dimensions fixes de provenance changent :
```text
Web direct = Web / Wasm / Browser
Tauri Android = Android / Wasm / TauriWebView
```
Le lifecycle navigateur `visibilitychange/pagehide/pageshow` n'est pas copié comme seul contrat mobile. Le host Tauri Android doit tester le comportement réel lors de pause/reprise, changement d'orientation, background/foreground et fermeture/navigation système.
Le logging frontend Web direct n'est pas réutilisé tel quel : Tauri doit utiliser le bridge `@fltsci/tauri-plugin-tracing` vers `tauri-plugin-tracing`/`tracing`.
### Dépendances frontend à ne pas copier mécaniquement
Bootstrap, Bootswatch, Font Awesome et Sass peuvent être repris si le shell Tauri conserve réellement la même UI.
SimpleBar et `resize-observer-polyfill` restent des dépendances du shell Web direct ; les règles interdisent de les faire devenir des dépendances Tauri par simple copie. Elles ne seront ajoutées que si le layout Android Tauri démontre leur nécessité.
## Point de friction principal : provenance de l'adapter WASM
`game-snake-poc-wasm` est réutilisable pour le gameplay et le rendu, mais son état `0.3.0` fixe actuellement `PlatformFamily::Web` et `RuntimeHost::Browser` dans son constructeur et dans `configure_runtime_provenance`.
Il ne peut donc pas décrire correctement un WebView Tauri Android sans modification minimale.
La direction retenue est de généraliser uniquement la configuration de provenance du host dans l'adapter existant, sans dupliquer le bridge et sans créer une abstraction WebView commune prématurée. Le même adapter doit pouvoir représenter au moins :
```text
Web / Browser / Wasm
Android / TauriWebView / Wasm
```
Le nom `PlatformFamily::Android` est conservé. `Mobile` reste une famille conceptuelle d'architecture et ne justifie pas un renommage de l'API V1 ni l'introduction d'iOS dans `0.3.1`.
## Structure cible de l'application
Le second host sera une nouvelle crate :
```text
crates/apps/game-snake-poc-tauri/
```
La crate est génériquement nommée Tauri afin de pouvoir accueillir ultérieurement le host Desktop prévu en `0.3.2`, mais `0.3.1` n'en valide que la cible Android.
Structure minimale visée :
```text
crates/apps/game-snake-poc-tauri/
├── Cargo.toml
├── build.rs
├── tauri.conf.json
├── capabilities/
├── frontend/
├── src/
│ ├── lib.rs
│ ├── main.rs
│ ├── runtime.rs
│ └── tauri.rs
├── README.md
└── USAGE.md
```
`lib.rs` reste une façade. `tauri.rs` assemble builder/plugins/commands et délègue aux modules propriétaires. Une responsabilité supplémentaire devient un module dédié plutôt qu'une collection de helpers dans `tauri.rs`.
Le `README.md` est requis pour expliquer frontières et architecture. `USAGE.md` est également retenu car le host Android Tauri possède des prérequis SDK/JDK/NDK, des commandes de device/AVD et des smokes non triviaux.
## Build et commandes Tauri Android
La revue des règles a trouvé un défaut de cible : les commandes Tauri existantes `cargo tauri dev/build` correspondent au chemin Desktop alors que le nouveau POC est mobile Android.
La règle est corrigée pour réserver à Android :
```text
cargo tauri android init
cargo tauri android dev
cargo tauri android build
```
Le projet mobile généré par Tauri est traité comme partie du host Android lorsqu'il est requis par l'outil ; son contenu exact sera créé par l'outil Tauri dans la tranche d'implémentation, pas fabriqué à la main pendant le cadrage.
Aucun nouveau script Python de build n'est autorisé. Le frontend Vite/TypeScript et la génération WASM doivent rester possédés par les hooks Tauri et les outils natifs Cargo/`wasm-bindgen`/Tauri.
## Environnement Android : confirmé et à reconfirmer
L'historique du dépôt prouve des validations récentes de l'autre chemin Android SDL3 : NDK, ADB, AVD API 36 et appareil ARM64 réel ont déjà été utilisés avec succès pendant `0.1.0`.
La release `0.3.0` prouve également l'usage récent de Cargo, `wasm32-unknown-unknown`, `wasm-bindgen`, Node/npm et Vite.
Ces validations historiques ne prouvent pas l'état présent du toolchain Tauri Android. Avant `0-pre.2`, l'utilisateur doit fournir un inventaire frais de :
- `cargo tauri --version` ;
- targets Rust Android installés ;
- Java/JDK et `JAVA_HOME` ;
- `ANDROID_HOME` ;
- NDK et `NDK_HOME` ;
- `adb version` et devices visibles ;
- AVD disponibles ;
- Node/npm et `wasm-bindgen`.
Les prérequis Tauri 2 actuels pour Android comprennent Android Studio/tooling SDK, NDK, JDK et les targets Rust Android. Cette tranche ne suppose pas qu'ils sont tous encore configurés seulement parce que l'Android SDL3 historique fonctionnait.
## Risques à tester explicitement
- compatibilité réelle de `tauri-plugin-tracing` et du bridge frontend sur Android ; le package `0.3.x` est bien la référence actuelle du projet, mais le support mobile doit être prouvé par compilation/smoke plutôt que supposé ;
- lifecycle background/foreground et suspension du WebView ;
- distinction entre une sortie applicative Tauri, qui passe par `EngineGame::quit_requested`, et le Back système Android, qui ne doit pas être consommé artificiellement ;
- propagation correcte des dimensions logiques et du `devicePixelRatio` après rotation/resize ;
- chargement des assets embarqués sans dépendre du serveur Vite en build Android ;
- pipeline WASM sans script Python ;
- séparation claire entre Android SDL3 existant sous `Android/` et Tauri Android généré par la crate Tauri ;
- absence de dépendances Web-only recopiées par inertie ;
- output des builds et caches hors racine selon `GAME-BUILD-001`.
## Décisions de cadrage
Retenu pour `0.3.1` :
- une seule nouvelle app `game-snake-poc-tauri` ;
- Android uniquement comme cible validée dans cette version ;
- réutilisation du même `game-snake-poc-wasm` après généralisation minimale de provenance ;
- réutilisation sélective du frontend Web ;
- structure Rust Tauri conforme aux apps Desk : façade, assemblage, modules propriétaires ;
- tracing Tauri de bout en bout ;
- pas de nouveau framework commun tant qu'une duplication réelle entre Web direct et Tauri WebView n'est pas mesurée ;
- aucun orchestrateur Python nouveau ou repris.
Reporté :
- Tauri Desktop Snake comme produit validé, vers `0.3.2` ;
- abstraction WebView commune, sauf duplication réellement prouvée ;
- iOS ;
- Android SDL3 multi-ABI supplémentaire ;
- ads, billing, auth, réseau realtime et Uroburas.
## Sizing
Le scope complet est trop risqué pour être comprimé dans une seule prerelease d'implémentation. Le découpage retenu est :
```text
0-pre.1 audit / règles / requirements / environnement / plan
0-pre.2 scaffold Tauri Android + pipeline natif + bridge minimal vers Snake WASM
0-pre.3 Canvas / input / assets / shell réutilisé du Web
0-pre.4 lifecycle / provenance / tracing / Back + smoke mobile
0-pre.5 conditionnel : extraction commune seulement si duplication réelle
2-beta.1 gate large + packaging Android + AVD/appareil selon disponibilité
2-beta.2 consolidation documentaire si nécessaire
3-rc.1 candidate gelée et reproductibilité
0.3.1 promotion stable mécanique
```
Une tranche peut être scindée si un défaut outillage réel l'impose. `0-pre.5` est supprimé s'il n'existe aucune extraction justifiée.

View File

@@ -0,0 +1,277 @@
<!-- file: docs/studies/025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md -->
<!-- version: 4 -->
# Audit Android SDL3 natif multi-ABI pour 0.3.3
## Statut et objet
Cette étude cadre `0.3.3-0-pre.1` à partir de l'archive taggée `v0.3.1`. Elle reste non normative : les décisions opérationnelles retenues pour la version sont portées par `docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md` et deviendront des responsabilités durables dans les documents Android concernés au moment où l'implémentation les rendra réelles.
L'objectif est de remplacer le chemin historique :
```text
scripts/build_android_rust.py -> cargo ndk -> src/main/jniLibs -> Gradle
```
par un chemin possédé par le build Android :
```text
Gradle -> tâches Rust/cargo-ndk -> jniLibs générés -> APK/AAB
```
sans réintroduire un orchestrateur ad hoc externe.
## Base auditée
L'archive fournie correspond à la stable `0.3.1` et déclare `workspace.package.version = "0.3.1"`.
Avant modification, les contrôles statiques exécutables dans l'environnement de génération donnent :
```text
unzip -t : no errors
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 222 file(s))
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
```
L'archive contient 378 fichiers, aucun lien symbolique, aucun fichier vide, aucun `.git`, `target/`, `node_modules/` ou `gen/android/` généré.
La sortie locale fournie par l'utilisateur annonce 230 fichiers Markdown avant son build Tauri, contre 222 dans le ZIP taggé. La baseline taggée reste autoritaire. L'écart est compatible avec la présence locale d'un arbre Tauri `gen/android/` déjà initialisé, que l'archive de tag n'embarque pas ; il ne constitue pas à lui seul une divergence de source tant que les audits restent propres.
## Règles et frontières vérifiées
La revue de `RULES.md` et des règles référencées par le prompt confirme notamment :
- `0-pre.1` doit rester une tranche de cadrage, sizing, recherche, risques, validations et planification ;
- une prerelease non-fix synchronise la version technique même si elle est principalement documentaire ;
- les builds, tests et smokes finaux restent côté utilisateur ; le générateur peut exécuter les audits statiques ;
- tout chemin de build nouveau ou réactivé en `0.3.x` ne doit pas être piloté par Python ;
- les commandes Android ciblées sont préférées et tout changement de répertoire doit rester dans un sous-shell ;
- les sorties générées ne doivent pas devenir des sources versionnées ;
- les tests workspace complets restent des gates rares et planifiées ;
- l'historique d'une tranche n'est ajouté que par la tranche suivante après validation réelle.
Aucune règle ne justifie de réécrire Gradle dans `0-pre.1`. La tranche doit donc produire les décisions et le plan, puis laisser l'implémentation à `0-pre.2` et suivantes.
## Pipeline Android natif hérité
Le projet Android est un multi-module Gradle séparé du workspace Cargo avec :
```text
Android/common
Android/game-reflex-poc
Android/game-snake-poc
```
La configuration source déclare actuellement :
```text
AGP 9.4.0
compileSdk 36
targetSdk 36
minSdk 21
JDK source 17
NDK 28.2.13676358 (r28c)
SDL3 AAR SDL3-3.4.16.aar
```
Le ZIP ne vend pas l'AAR SDL3, conformément au contrat `Android/libs/README.md`. Il vérifie donc le nom/version attendu, pas les octets de l'AAR présent sur une machine de développement.
L'audit relève aussi que `Android/README.md` portait encore un paragraphe de toolchain daté de `0.1.0-0-pre.9`, contradictoire avec le pipeline natif réellement stabilisé ensuite. `0-pre.1` corrige cette description sans toucher au build.
Le projet Android natif ne contient pas encore de Gradle Wrapper. `Android/README.md` attend Gradle 9.6.0 globalement. Comme la reproductibilité devient une responsabilité centrale de `0.3.3`, l'introduction d'un wrapper Gradle épinglé doit être traitée avec le premier vrai changement de pipeline, pas dans le cadrage documentaire.
## Responsabilités du script historique
`scripts/build_android_rust.py` remplit aujourd'hui plusieurs responsabilités qu'il faut transposer explicitement au build Gradle plutôt que supprimer aveuglément :
1. associer un module Android à exactement une feature jeu Rust ;
2. lire le nom de l'AAR SDL3 et la version NDK depuis `Android/gradle.properties` ;
3. extraire `libSDL3.so` de l'AAR Prefab pour chaque ABI afin de fournir la bibliothèque de liaison à Rust ;
4. rendre `libSDL3.so` disponible au runtime Android ;
5. résoudre le NDK side-by-side exact et transmettre son chemin uniquement au sous-processus natif ;
6. fixer le niveau Android de `cargo-ndk` à 21 ;
7. invoquer `cargo ndk` avec exactement une feature `reflex` ou `snake` ;
8. vérifier que `libgame_android_entrypoint.so` a réellement été produit.
Le script écrit actuellement des bibliothèques sous `Android/<app>/src/main/jniLibs/<abi>/`. Ces fichiers sont ignorés mais restent des artefacts de build dans un répertoire source. La cible `0.3.3` doit plutôt enregistrer un répertoire de `jniLibs` généré sous `Android/<app>/build/generated/...`, sur le même principe que le staging d'assets déjà piloté par la Variant API AGP.
## Toolchain actuelle vérifiée
La documentation Android officielle d'AGP 9.4.0, mise à jour le 18 septembre 2026, fixe :
```text
Gradle minimum/default 9.6.0
JDK minimum/default 17
Build Tools default 36.0.0
NDK default 28.2.13676358
API maximale AGP 37
```
Source officielle : <https://developer.android.com/build/releases/agp-9-4-0-release-notes>.
La baseline `AGP 9.4.0 + Gradle 9.6.0 + JDK 17 + NDK r28c` est donc cohérente et ne nécessite pas de montée de version pour lancer `0.3.3`.
La sortie utilisateur fournie pour le POC Tauri Android montre un NDK local `30.0.14904198`. Cette valeur appartient au pipeline Tauri généré et ne remplace pas la version NDK native explicitement épinglée à `28.2.13676358` dans `Android/gradle.properties`.
## SDL3 et plancher Android
La documentation SDL Android actuelle demande :
```text
Android SDK 35 ou ultérieur
Android NDK r28c ou ultérieur
API minimale 21 / Android 5.0
```
Source officielle : <https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md>.
SDL `3.4.16` est la release stable publiée le 2 septembre 2026 et l'asset Android officiel `SDL3-devel-3.4.16-android.zip` est disponible dans la release.
Source officielle : <https://github.com/libsdl-org/SDL/releases/tag/release-3.4.16>.
Le `minSdk 21` historique n'est donc pas retenu par simple inertie : il correspond au plancher actuel documenté de SDL3. Descendre sous API 21 exigerait de changer une dépendance structurante ou de maintenir un fork, ce qui est hors scope. Le candidat `minSdk` de `0.3.3` est donc `21`, sous réserve d'un smoke sur API 21 ou sur la plus ancienne image raisonnablement disponible avec écart explicitement documenté.
## Target SDK et distribution
Google Play exige depuis le 31 août 2026 qu'une nouvelle application ou mise à jour téléphone/tablette cible Android 16, API 36, ou plus récent.
Source officielle : <https://developer.android.com/google/play/requirements/target-sdk>.
Le `targetSdk 36` de la baseline reste donc approprié pour `0.3.3`. Il ne doit pas être confondu avec `minSdk 21` : l'un décrit la cible comportementale/politique de distribution, l'autre le plancher d'installation.
## Pages mémoire 16 KB
Le projet package du code natif Rust et SDL3. Les appareils Android 15+ peuvent utiliser des pages mémoire de 16 KB et Google Play impose la compatibilité 16 KB aux applications ciblant API 35+ sur appareils 64 bits.
La documentation Android précise qu'AGP 8.5.1+ et NDK r28+ fournissent le chemin standard de packaging/alignement 16 KB, NDK r28 alignant les bibliothèques produites sur 16 KB par défaut. Les bibliothèques précompilées restent néanmoins à vérifier, donc `libSDL3.so` doit faire partie du contrôle de l'artefact final.
Source officielle : <https://developer.android.com/guide/practices/page-sizes>.
Cette contrainte renforce le maintien de NDK r28c. La validation AAB doit inclure un contrôle d'alignement/compatibilité des bibliothèques natives, et un smoke 16 KB peut être ajouté si une image d'émulateur adéquate est raisonnablement disponible.
## ABI retenues pour le chemin par défaut lors du cadrage initial
Les deux ABI de base proposées sont :
```text
arm64-v8a
x86_64
```
Raisons :
- `arm64-v8a` couvre la cible productive minimale sur appareil Android moderne ;
- `x86_64` conserve un chemin AVD local rapide et déjà exercé par le POC Tauri ;
- les deux sont des ABI 64 bits et répondent au besoin de supporter les architectures 64 bits pour une application native ;
- Rust fournit les targets `aarch64-linux-android` et `x86_64-linux-android` ;
- le build Tauri fourni par l'utilisateur vient de réussir pour ces deux targets, ce qui confirme au moins la présence récente de ces cibles Rust sur sa machine.
`armeabi-v7a` est différée par défaut. Le 32 bits n'abaisse pas le plancher OS puisque SDL3 reste à API 21, augmente la matrice de build/test et la taille d'un APK universal, et aucun besoin produit concret n'est actuellement démontré. Elle pourra être rouverte à partir de données d'appareils/utilisateurs ou d'un besoin de distribution mesuré.
`x86` 32 bits n'est pas retenue dans la matrice de base.
### Révision décidée après validation de 0-pre.3
Après la preuve `arm64-v8a + x86_64`, la politique produit a été explicitement élargie : le projet doit tenter de supporter le maximum d'architectures Android encore prises en charge par la toolchain moderne, y compris anciennes. La matrice cible devient donc :
```text
arm64-v8a
armeabi-v7a
x86_64
x86
```
Cette révision ne prétend pas réintroduire `armeabi`, `mips` ou `mips64`, retirées des toolchains Android modernes. Elle augmente la couverture CPU sans changer le plancher OS SDL3, qui reste API 21. La décision normative actualisée est portée par le plan `0.3.3` et les documents Android durables.
## APK universal, AAB et splits
Après la révision de scope, la cible locale est un APK Debug universal contenant les quatre ABI retenues :
```text
lib/arm64-v8a/libSDL3.so
lib/arm64-v8a/libgame_android_entrypoint.so
lib/armeabi-v7a/libSDL3.so
lib/armeabi-v7a/libgame_android_entrypoint.so
lib/x86_64/libSDL3.so
lib/x86_64/libgame_android_entrypoint.so
lib/x86/libSDL3.so
lib/x86/libgame_android_entrypoint.so
```
Aucun split ABI Gradle ne doit être activé par défaut pour cet artefact : un seul APK contient les quatre variantes CPU et doit pouvoir être installé sur chaque architecture compatible. Les smokes effectifs restent limités aux appareils/AVD disponibles.
Pour la distribution, la cible est un AAB. Le format App Bundle laisse au store la génération des APK optimisés par configuration, notamment par ABI ; un AAB n'est pas lui-même un APK installable.
Source officielle : <https://developer.android.com/guide/app-bundle>.
Les APK split ABI restent une option de diagnostic/distribution directe future, pas un artefact par défaut de `0.3.3`.
## Architecture Gradle/Cargo recommandée
La solution retenue pour l'implémentation est une logique Gradle commune sous `Android/gradle/`, consommée par les applications natives. Elle doit :
- déclarer les ABI retenues dans une propriété/configuration centrale explicite ;
- enregistrer des tâches par variante et, si nécessaire, par ABI ;
- déclarer des inputs/outputs Gradle afin de préserver un rebuild incrémental raisonnable ;
- extraire la bibliothèque SDL3 native de l'AAR vers un répertoire de build pour le linkage Rust sans modifier l'AAR source ;
- invoquer `cargo ndk` avec le NDK r28c configuré et `CARGO_NDK_PLATFORM=21` ;
- sélectionner exactement une feature jeu selon le module consommateur ;
- écrire les bibliothèques natives dans un répertoire `build/generated/...` ;
- enregistrer ce répertoire comme `jniLibs` généré de la variante ;
- vérifier explicitement les deux `.so` attendues par ABI avant packaging ;
- échouer avec un diagnostic déterministe si AAR, NDK, cargo-ndk, target Rust ou output manque.
Cette logique n'est pas un nouveau script externe : elle fait partie du build Android qui possède l'artefact final.
## Snake et Reflex
Snake reste le jeu-sonde initial parce que le prompt le demande et que le POC Tauri a déjà prouvé ARM64 + x86_64 pour ce jeu.
Reflex ne doit pas provoquer une seconde architecture. Une fois la factorisation Gradle commune prouvée avec Snake, Reflex peut devenir le second consommateur dans la même version si l'opération se limite essentiellement au mapping `module -> feature`. Si des besoins spécifiques apparaissent, ils doivent être évalués avant d'élargir le scope.
## Risques identifiés
Les principaux risques à fermer pendant les tranches d'implémentation sont :
- absence de Gradle Wrapper dans le projet Android natif ;
- AAR SDL3 absent du ZIP par conception et donc à inventorier sur la machine de validation ;
- `cargo-ndk` présent mais version non encore relevée dans la sortie fraîche fournie ;
- extraction Prefab SDL3 différente de l'hypothèse historique ;
- dépendances Gradle mal ordonnées entre build Rust et merge/package JNI ;
- cache Gradle incorrect si les inputs Rust/assets/toolchain sont sous-déclarés ;
- production accidentelle de deux features jeu dans le même `cdylib` ;
- compatibilité 16 KB d'une bibliothèque précompilée SDL3 non vérifiée dans l'artefact final ;
- confusion entre NDK 30 du POC Tauri et NDK r28c du pipeline Android natif ;
- impossibilité pratique de créer un AVD API 21 récent, auquel cas l'écart de smoke doit rester explicite.
## Inventaire environnement encore à relever côté utilisateur
La sortie locale fournie confirme déjà :
- `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` pour le build présenté ;
- un build Tauri Debug universal réussi pour `aarch64` et `x86_64` ;
- un NDK Tauri local `30.0.14904198` ;
- un `cargo check --workspace` propre sur la stable `0.3.1`.
L'historique validé de `0.3.1` indique Rust/Cargo `1.94.1`, les targets Android ARM64/x86_64, ADB `37.0.1`, un appareil ARM64 réel et plusieurs AVD. Ces valeurs sont utiles comme transmission, mais ne remplacent pas l'inventaire frais demandé par le prompt `004`.
Avant d'ouvrir l'implémentation `0-pre.2`, la gate utilisateur doit donc relever explicitement `rustc`, `cargo`, `cargo ndk`, les targets Rust, Java, `JAVA_HOME`, `ANDROID_HOME`, ADB, appareils, AVD, Gradle natif, NDK r28c installé et AAR SDL3 local.
## Conclusion de cadrage
Le scope tient dans `0.3.3` si chaque tranche reste centrée sur un résultat vertical : ownership Gradle mono-ABI, puis multi-ABI/universal, puis AAB/compatibilité et consolidation. Aucun besoin découvert n'impose aujourd'hui de créer une version supplémentaire avant de commencer l'implémentation.
## Révision après validation de la matrice quatre ABI
La gate `0-pre.4` du 2026-09-21 a confirmé que les quatre targets Rust Android sont installées et que Snake comme Reflex compilent avec succès `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`. Les deux APK Debug universal contiennent les huit bibliothèques attendues.
La fermeture `0-pre.5` a retiré le builder Python historique sans perdre de responsabilité fonctionnelle. Les deux `bundleRelease` ont réussi et les AAB Snake/Reflex contiennent les deux bibliothèques natives pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
La validation 16 KB a affiné le critère initial : l'exigence Google Play vise les appareils 64 bits. Les couples SDL3/Rust `arm64-v8a` et `x86_64`, en Debug comme en Release, ont des segments ELF `LOAD` alignés à `2**14`, et `zipalign -P 16` réussit sur les APK. Les variantes 32 bits `armeabi-v7a` et `x86` restent à `2**12`, ce qui est compatible avec leur rôle d'ABI historiques 32 bits et ne constitue pas un échec de la gate 16 KB 64 bits.
Un AVD Android 15/API 35 x86_64 basé sur l'image `google_apis_playstore_ps16k` a retourné `16384` via `getconf PAGE_SIZE`; l'APK Snake s'y installe, `SnakeActivity` démarre et le processus reste vivant. Une lecture ponctuelle `KernelPageSize: 4 kB` dans `/proc/self/smaps` n'est pas retenue comme mesure du mode de page global : la gate Android utilise `getconf PAGE_SIZE`.
Le plancher `minSdk 21` est désormais prouvé en runtime : un AVD Android 5.0/API 21 x86 a accepté l'APK universal et lancé `SnakeActivity`. La matrice dispose donc à la fois d'une preuve ancienne x86 32 bits et d'une preuve récente x86_64 16 KB.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/testing/003-RC_VALIDATION_MATRIX.md --> <!-- file: docs/testing/003-RC_VALIDATION_MATRIX.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Matrice de validation RC 0.1.0 # Matrice de validation RC 0.1.0
@@ -85,13 +85,8 @@ Critères :
## Android x86_64 / API 36 ## Android x86_64 / API 36
```bash ```bash
python3 scripts/build_android_rust.py reflex --abi x86_64 (cd Android && gradle :game-reflex-poc:assembleDebug)
python3 scripts/build_android_rust.py snake --abi x86_64 (cd Android && gradle :game-snake-poc:assembleDebug)
cd Android
gradle :game-reflex-poc:assembleDebug
gradle :game-snake-poc:assembleDebug
cd ..
``` ```
Déployer les deux APK sur l'AVD de référence et revalider : Déployer les deux APK sur l'AVD de référence et revalider :
@@ -105,13 +100,8 @@ Déployer les deux APK sur l'AVD de référence et revalider :
## Android ARM64 réel ## Android ARM64 réel
```bash ```bash
python3 scripts/build_android_rust.py reflex --abi arm64-v8a (cd Android && gradle :game-reflex-poc:assembleDebug)
python3 scripts/build_android_rust.py snake --abi arm64-v8a (cd Android && gradle :game-snake-poc:assembleDebug)
cd Android
gradle :game-reflex-poc:assembleDebug
gradle :game-snake-poc:assembleDebug
cd ..
``` ```
Déployer sur l'appareil ARM64 de référence et revalider Reflex et Snake. Déployer sur l'appareil ARM64 de référence et revalider Reflex et Snake.

View File

@@ -0,0 +1,96 @@
<!-- file: docs/testing/005-V0_3_1_RC_VALIDATION_MATRIX.md -->
<!-- version: 1 -->
# Matrice de validation RC 0.3.1
## Objectif
`0.3.1-3-rc.1` est une candidate gelée. Elle n'ajoute aucune fonctionnalité au POC Snake Tauri Android ; elle vérifie qu'un état reproductible peut être promu mécaniquement vers `0.3.1`.
Le host Tauri Android reste un POC de référence. La voie Android productive reste SDL3 natif/Java/JNI. Aucun AAB Tauri n'est requis pour cette RC.
## Gel fonctionnel
Pendant `3-rc.*`, seules les corrections autorisées par `VER-RC-*` sont admises. Tout nouveau gameplay, contrôle swipe, refactor Web/Tauri, framework commun, intégration publicitaire ou changement volontaire de comportement est hors scope.
## Gate workspace
Depuis la racine :
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
Cette suite complète est volontairement répétée au jalon RC parce que la version technique et les métadonnées frontend sont promues vers la candidate.
## Packaging Android autonome
Installer/actualiser les dépendances frontend, puis laisser Tauri posséder le build WASM/Vite :
```bash
(cd crates/apps/game-snake-poc-tauri && npm install)
(cd crates/apps/game-snake-poc-tauri && \
cargo tauri android build --debug --apk --target aarch64 --target x86_64)
```
Ne pas lancer `npm run build` manuellement. Le hook `beforeBuildCommand` doit construire le WASM release, générer les bindings, exécuter TypeScript/Vite et copier les assets.
Le package RC de validation reste un APK Debug universal car `0.3.1` ne constitue pas la voie Android de production et ne possède aucun secret/signing de store. L'AAB de production sera étudié dans la future version Android SDL3 native.
## Contrôle ABI
À partir du chemin `<apk>` affiché par Tauri :
```bash
unzip -l <apk> | grep -E 'lib/(arm64-v8a|x86_64)/libgame_snake_poc_tauri_lib\.so'
```
Les deux entrées doivent être présentes.
## Smoke AVD x86_64
Lancer l'AVD de référence dans un terminal séparé :
```bash
emulator -avd sasedev_games_api36
```
Puis :
```bash
adb devices -l
adb -s emulator-5554 install -r <apk>
adb -s emulator-5554 shell am start -n com.sasedev.games.snake.tauri/.MainActivity
```
Vérifier : démarrage, Canvas, assets, WASM, contrôles disponibles, lifecycle background/foreground, reprise sans rattrapage et Back sans crash.
## Smoke ARM64 réel
Avec le Galaxy S9+ ou un autre appareil ARM64 disponible :
```bash
adb -s <serial> shell getprop ro.product.cpu.abi
adb -s <serial> install -r <apk>
adb -s <serial> shell am start -n com.sasedev.games.snake.tauri/.MainActivity
```
Vérifier : provenance `android / tauri-webview / wasm / phone / touch`, contrôles tactiles, background/foreground, reprise sans rattrapage, rotation/resize si disponible et plusieurs Back. Aucun Vite, `adb reverse` ou port `1436` ne doit être nécessaire.
Le crash `FORTIFY: pthread_mutex_lock called on a destroyed mutex` observé une fois en dev reste un point de surveillance. Sa reproduction sur l'APK autonome RC bloque la promotion stable et impose un `3-rc.1.fix.N`; son absence pendant les smokes RC permet de clore ce risque comme non reproductible dans l'état candidat.
## Promotion stable
Si la gate RC est propre et qu'aucun correctif n'est nécessaire, la promotion vers `0.3.1` est mécanique : versions stables, entrée stable du `CHANGELOG.md`, historique RC, clôture du plan et delta de release. Aucun code fonctionnel ne doit changer.

View File

@@ -0,0 +1,151 @@
<!-- file: docs/testing/006-V0_3_3_RC_VALIDATION_MATRIX.md -->
<!-- version: 1 -->
# Matrice de validation RC 0.3.3
## Objectif
`0.3.3-3-rc.1` est une candidate gelée. Elle ne modifie ni gameplay, ni moteur, ni JNI, ni Java, ni logique Gradle : elle revalide qu'un état reproductible du pipeline Android SDL3 natif peut être promu mécaniquement vers `0.3.3`.
## Gel fonctionnel
Pendant `3-rc.*`, seules les corrections autorisées par `VER-RC-*` sont admises. Toute nouvelle ABI, nouvelle API Android, nouvelle capability réseau, nouvelle abstraction, refactor volontaire ou changement de comportement est hors scope.
La future convention de versions `alpha.M / beta.M / rc.M` n'est pas appliquée à `0.3.3`; elle commencera avec la prochaine version et ne doit provoquer aucune réécriture de l'historique.
## Gate workspace
Depuis la racine :
```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 Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
La suite workspace complète est répétée en RC parce que la version Cargo passe à `0.3.3-3-rc.1`.
## Rebuild Android propre
```bash
(cd Android && gradle :game-snake-poc:clean :game-reflex-poc:clean)
(cd Android && gradle :game-snake-poc:assembleDebug)
(cd Android && gradle :game-reflex-poc:assembleDebug)
(cd Android && gradle :game-snake-poc:bundleRelease)
(cd Android && gradle :game-reflex-poc:bundleRelease)
```
Le build doit utiliser le JDK normal de l'environnement et un Gradle système `>= 9.6.0`; aucun `JAVA_HOME` spécifique au projet et aucun wrapper Gradle natif ne sont requis.
## Contrôle quatre ABI
```bash
for game in game-snake-poc game-reflex-poc; do
APK="Android/${game}/build/outputs/apk/debug/${game}-debug.apk"
AAB="Android/${game}/build/outputs/bundle/release/${game}-release.aab"
test -f "$APK"
test -f "$AAB"
for abi in arm64-v8a armeabi-v7a x86_64 x86; do
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libSDL3.so"
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libgame_android_entrypoint.so"
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libSDL3.so"
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libgame_android_entrypoint.so"
done
done
```
## Packaging 16 KB
```bash
ZIPALIGN="$(find "$ANDROID_HOME/build-tools" -mindepth 2 -maxdepth 2 -type f -name zipalign -print | sort -V | tail -n 1)"
test -x "$ZIPALIGN"
"$ZIPALIGN" -c -P 16 -v 4 Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
"$ZIPALIGN" -c -P 16 -v 4 Android/game-reflex-poc/build/outputs/apk/debug/game-reflex-poc-debug.apk
```
Recontrôler les segments ELF 64 bits Release :
```bash
OBJDUMP="$ANDROID_HOME/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump"
test -x "$OBJDUMP"
for game in game-snake-poc game-reflex-poc; do
for abi in arm64-v8a x86_64; do
for so in \
"Android/${game}/build/generated/sasedevNative/release/${abi}/jniLibs/${abi}/libSDL3.so" \
"Android/${game}/build/generated/sasedevNative/release/${abi}/jniLibs/${abi}/libgame_android_entrypoint.so"; do
test -f "$so"
"$OBJDUMP" -p "$so" | grep 'LOAD'
done
done
done
```
Chaque ligne `LOAD` des ABI 64 bits doit rester au minimum à `align 2**14`. Les ABI 32 bits restent supportées mais ne sont pas soumises à ce critère Play 16 KB.
## Smoke AVD API 36 x86_64
Avec l'AVD de référence déjà disponible, utiliser son serial ADB, actuellement `emulator-5554` lorsqu'il est lancé. Pour chaque jeu :
```bash
SERIAL=emulator-5554
APK=Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
adb -s "$SERIAL" install -r "$APK"
adb -s "$SERIAL" shell am force-stop com.sasedev.games.snake
adb -s "$SERIAL" logcat -c
adb -s "$SERIAL" shell am start -W -n com.sasedev.games.snake/.SnakeActivity
sleep 3
adb -s "$SERIAL" shell pidof com.sasedev.games.snake
adb -s "$SERIAL" logcat -d -b crash
```
Puis :
```bash
SERIAL=emulator-5554
APK=Android/game-reflex-poc/build/outputs/apk/debug/game-reflex-poc-debug.apk
adb -s "$SERIAL" install -r "$APK"
adb -s "$SERIAL" shell am force-stop com.sasedev.games.reflex
adb -s "$SERIAL" logcat -c
adb -s "$SERIAL" shell am start -W -n com.sasedev.games.reflex/.ReflexActivity
sleep 3
adb -s "$SERIAL" shell pidof com.sasedev.games.reflex
adb -s "$SERIAL" logcat -d -b crash
```
Vérifier humainement : rendu visible, assets présents, Snake réagit au swipe, Reflex réagit au tap, puis absence de crash immédiat.
## Smoke ARM64 réel
Sur le Samsung ou un autre appareil ARM64 disponible, remplacer `<serial>` :
```bash
SERIAL=<serial>
adb -s "$SERIAL" shell getprop ro.build.version.sdk
adb -s "$SERIAL" shell getprop ro.product.cpu.abi
```
Rejouer les deux blocs Snake/Reflex précédents avec `SERIAL=<serial>`. Vérifier humainement rendu, tactile et sortie/Back.
## Frontières non rejouées par défaut
Les smokes API 21/x86 et API 35/x86_64 `ps16k` ont été établis en `0-pre.5`, puis aucune logique native/Gradle n'a changé. Ils ne sont pas répétés en RC sauf si un correctif RC touche le pipeline Android, le NDK, SDL3, les ABI, `minSdk`, le packaging ou les bibliothèques natives.
## Promotion stable
Si cette gate est propre et qu'aucun correctif n'est nécessaire, la promotion vers `0.3.3` est mécanique : version stable, entrée stable du changelog, historique RC, clôture du plan, delta de release et mise à jour mécanique du prompt `0.3.4` si la base stable doit être rendue certaine. Aucun code fonctionnel ne doit changer.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/001-VALIDATION_GATES.md --> <!-- file: docs/validation/001-VALIDATION_GATES.md -->
<!-- version: 8 --> <!-- version: 11 -->
# Gates de validation # Gates de validation
@@ -56,7 +56,19 @@ Si une commande échoue, ne pas avancer de prerelease. Produire un correctif du
## Android et Web ## Android et Web
Le build Android n'est pas encore une gate : le projet Gradle exécutable et l'intégration SDL3/NDK restent planifiés pour une prerelease dédiée. Dès leur introduction, les tâches ciblées par module sont documentées avant d'être rendues obligatoires. Le build Android SDL3 historique n'est pas encore une gate : le projet Gradle exécutable et l'intégration SDL3/NDK restent planifiés pour une prerelease dédiée. Dès leur introduction, les tâches ciblées par module sont documentées avant d'être rendues obligatoires.
Le host Tauri Android Snake possède en revanche une gate réelle à partir de `0.3.1-0-pre.2`. Pour Tauri 2.11.x, les commandes Android utilisent le projet `gen/android` généré par la CLI officielle, sans patch local. La baseline de validation est JDK 17 avec Gradle `8.14.3` et AGP `8.11.0`. Si `gen/android` provient d'une tentative de migration différente, le supprimer puis rejouer `cargo tauri android init` sous JDK 17 avant le smoke `cargo tauri android dev`.
Sur AVD, le smoke peut utiliser le forwarding ADB géré automatiquement par Tauri. Sur appareil physique, la gate de référence utilise un tunnel USB explicite sur le port unique `1436` afin d'éviter de dépendre du LAN et de transporter à la fois HTTP et HMR :
```bash
adb -s <serial> reverse --remove-all
adb -s <serial> reverse tcp:1436 tcp:1436
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev --host 127.0.0.1)
```
La validation réelle de `0-pre.4` a confirmé le chemin ARM64 `aarch64-linux-android`. Un premier smoke a observé un abort natif lors du Back/teardown WebView ; après correction du tunnel HMR dans `0-pre.4.fix.1`, un nouveau smoke ARM64 n'a pas reproduit cet abort et est allé jusqu'au détachement de la fenêtre. Le risque reste à revalider sur l'APK autonome beta et ne doit pas être masqué par une modification du scaffold `gen/android`.
Le host navigateur direct Snake possède désormais une gate réelle. Lorsqu'il est touché, construire d'abord l'adapter WASM et ses bindings hors dépôt, puis le frontend : Le host navigateur direct Snake possède désormais une gate réelle. Lorsqu'il est touché, construire d'abord l'adapter WASM et ses bindings hors dépôt, puis le frontend :
@@ -73,7 +85,32 @@ npm run build
Le smoke direct utilise ensuite `npm run dev` et `http://127.0.0.1:1434/main.html`. Pour le POC Snake feature-complete, vérifier aussi : assets `common` + `game` chargés avant le statut `Prêt`, provenance `Web/Wasm/Browser` visible et mise à jour par l'input, pause sans rattrapage lors d'un masquage d'onglet, reprise propre, resize sans tick artificiel, SimpleBar, clavier et contrôles pointer/tactiles. Le smoke direct utilise ensuite `npm run dev` et `http://127.0.0.1:1434/main.html`. Pour le POC Snake feature-complete, vérifier aussi : assets `common` + `game` chargés avant le statut `Prêt`, provenance `Web/Wasm/Browser` visible et mise à jour par l'input, pause sans rattrapage lors d'un masquage d'onglet, reprise propre, resize sans tick artificiel, SimpleBar, clavier et contrôles pointer/tactiles.
Lorsque `0-pre.5` touche la frontière plateforme du bridge WASM, exécuter également `cargo test -p engine-v1-platform-api`, `cargo test -p game-snake-poc-wasm`, puis un smoke `cargo run -p game-snake-poc-desktop` pour confirmer la non-régression SDL3. Les frontends Tauri restent pilotés par leurs hooks Tauri et ne reprennent pas cette gate manuelle. Si une future tranche touche de nouveau la frontière plateforme du bridge WASM, exécuter également `cargo test -p engine-v1-platform-api`, `cargo test -p game-snake-poc-wasm`, puis un smoke `cargo run -p game-snake-poc-desktop` pour confirmer la non-régression SDL3. Les frontends Tauri restent pilotés par leurs hooks Tauri et ne reprennent pas cette gate manuelle.
## Gate beta du POC Snake Tauri Android
À l'entrée en `0.3.1-2-beta.1`, la gate ajoute la suite Cargo workspace complète et un packaging Android autonome aux audits/check/Clippy habituels :
```bash
cargo test --workspace --all-targets --all-features
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
(cd crates/apps/game-snake-poc-tauri && npm install)
(cd crates/apps/game-snake-poc-tauri && \
cargo tauri android build --debug --apk --target aarch64 --target x86_64)
```
Le hook Tauri `beforeBuildCommand` possède le build WASM release, `wasm-bindgen`, TypeScript et Vite ; `npm run build` n'est donc pas une gate manuelle. Le build beta vise un APK Debug contenant `arm64-v8a` et `x86_64`; aucun AAB n'est requis à ce jalon.
Après packaging, utiliser le chemin d'APK affiché par Tauri, vérifier son contenu ABI, puis installer le même package sur l'AVD x86_64 et l'appareil ARM64 réel :
```bash
unzip -l <apk> | grep -E 'lib/(arm64-v8a|x86_64)/libgame_snake_poc_tauri_lib\.so'
adb -s <serial> install -r <apk>
adb -s <serial> shell am start -n com.sasedev.games.snake.tauri/.MainActivity
```
Le smoke autonome vérifie Canvas, assets, WASM, input, provenance, background/foreground, reprise sans rattrapage, resize/rotation si disponible et plusieurs sorties Back. Il ne doit utiliser ni serveur Vite ni `adb reverse`.
## Gate beta du POC Snake Web ## Gate beta du POC Snake Web

45
history/0.3.1/0-pre.1.md Normal file
View File

@@ -0,0 +1,45 @@
<!-- file: history/0.3.1/0-pre.1.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.1
## Statut
Cadrage `0.3.1-0-pre.1` validé par l'utilisateur le 2026-09-20. Les audits, le formatage, le check workspace et Clippy strict passent ; aucun `.fix.N` n'est requis avant l'ouverture de `0-pre.2`.
## Gate utilisateur
La gate fournie dans le delta a été exécutée avec succès :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 210 file(s))
Distribution layout audit: clean (31 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
## Environnement confirmé
L'inventaire de transmission vers `0-pre.2` confirme :
- Rust/Cargo `1.94.1` ;
- Tauri CLI `2.11.3` ;
- cibles Rust `aarch64-linux-android`, `x86_64-linux-android`, `wasm32-unknown-unknown` et `x86_64-unknown-linux-gnu` ;
- Node `24.21.0`, npm `11.19.0`, wasm-bindgen `0.2.128` ;
- OpenJDK Temurin `25.0.4.1` disponible via `java` ;
- `ANDROID_HOME=/home/sinus/DEV/AndroidSDK` ;
- `JAVA_HOME` et `NDK_HOME` non définis globalement ;
- Android Debug Bridge `37.0.1` ;
- un Samsung SM-G965F réel connecté ;
- AVD `Medium_Phone`, `Medium_Phone_API_36.0` et `sasedev_games_api36` disponibles.
Le projet ne transforme pas l'absence de `NDK_HOME` en défaut : l'installation locale conserve plusieurs NDK sous `$ANDROID_HOME/ndk/<version>`. Si une commande Tauri demande cette variable, elle sera définie ponctuellement vers une version exacte. De même, `JAVA_HOME` ne devient nécessaire que si la CLI ne résout pas le JDK disponible depuis l'environnement.
## Transmission vers 0-pre.2
L'archive complète `khadhroony-solana-project-v0.3.15.zip` est désormais disponible directement pour la tranche suivante. L'audit de ses six applications `ksp-app-*-desk` confirme le modèle retenu : façade `lib.rs`, assemblage `tauri.rs`, frontend Vite dans la crate, `tauri.conf.json`, capability dédiée et dépendances tracing Rust/TypeScript.

View File

@@ -0,0 +1,64 @@
<!-- file: history/0.3.1/0-pre.2.fix.3.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.2.fix.3
## Statut
Correctif validé par l'utilisateur le 2026-09-20. Le host Tauri Android minimal est assemblé, installé et lancé sur l'AVD x86_64 avec le template Android officiel Tauri 2.11.x sous JDK 17.
Le frontend atteint son état `ready` avec la provenance `Android / TauriWebView / Wasm`. L'écran reste volontairement minimal à ce jalon : Canvas, contrôles et assets appartiennent à `0-pre.3`.
## Toolchain confirmée
La machine possède notamment Temurin 17, 21 et 25. La gate Android a sélectionné explicitement :
```text
JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
OpenJDK 17.0.20.1
NDK sélectionné par Tauri : 30.0.14904198
Gradle wrapper généré : 8.14.3
```
Le projet `gen/android` pollué par la tentative Gradle 9 de `0-pre.2.fix.1` a été supprimé puis régénéré uniquement avec `cargo tauri android init`. L'audit de distribution l'a accepté.
## Gate workspace
Les validations suivantes ont passé :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 217 file(s))
Distribution layout audit: clean (42 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
cargo test -p game-snake-poc-wasm --all-targets --all-features: 5 passed, 0 failed
```
## Smoke Tauri Android
`cargo tauri android dev` a :
- construit l'adapter Snake WASM et le frontend Vite ;
- construit le `cdylib` Android x86_64 ;
- assemblé l'APK avec Gradle 8.14.3 ;
- installé l'application par streamed install ;
- lancé `com.sasedev.games.snake.tauri/.MainActivity` ;
- démarré le runtime Rust avec `platform_family="android"`, `runtime_host="tauri-webview"`, `execution_model="wasm"` ;
- atteint `Snake Tauri frontend ready`.
Les warnings Gradle/Kotlin/Tauri observés sont des dépréciations upstream non bloquantes.
Vite a aussi journalisé un échec de connexion WebSocket HMR. Ce défaut n'empêche ni le chargement du frontend ni l'état `ready` et n'est pas bloquant pour la fermeture du host minimal ; la configuration dev peut être ajustée dans la tranche frontend suivante.
## Décision produit issue du smoke
Le POC démontre la faisabilité de Tauri Android, mais aussi un coût d'outillage supplémentaire par rapport au host Android natif SDL3 déjà fonctionnel. La suite de `0.3.1` termine donc un POC jouable de référence, sans faire de Tauri Android la voie de production des futurs jeux.
## Suite
`0.3.1-0-pre.3` ajoute Canvas, boucle fixed-step, contrôles clavier/touch, assets et shell responsive afin de rendre le POC réellement comparable au host Web direct et au natif SDL3.

55
history/0.3.1/0-pre.3.md Normal file
View File

@@ -0,0 +1,55 @@
<!-- file: history/0.3.1/0-pre.3.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.3
## Statut
Tranche validée par l'utilisateur le 2026-09-21 sur l'AVD Android x86_64. Le POC Tauri Android affiche et exécute réellement Snake via le Canvas et l'adapter WASM partagé.
## Gate workspace
Les validations suivantes ont passé :
```text
OpenJDK Temurin 17.0.20.1 sélectionné par JAVA_HOME
npm install : 0 vulnérabilité
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 219 file(s))
Distribution layout audit: clean (47 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
cargo test -p game-snake-poc-wasm --all-targets --all-features: 5 passed, 0 failed
```
## Smoke Tauri Android
`cargo tauri android dev` a construit le WASM, collecté les deux assets canoniques, assemblé et installé l'APK puis lancé `com.sasedev.games.snake.tauri/.MainActivity`.
Les traces ont confirmé :
```text
Snake Tauri assets ready: game-snake-poc / snake / engine-v1
Snake Tauri WASM initialized
Snake Tauri provenance: android / tauri-webview / wasm / phone / unknown
Snake Tauri frontend ready
Snake Tauri provenance: android / tauri-webview / wasm / phone / touch
```
Le Canvas est visible, le serpent se déplace et les boutons tactiles modifient effectivement sa direction.
## Limite ergonomique observée
Sur le petit écran de l'AVD, les boutons directionnels sont placés sous le Canvas et nécessitent un scroll pour être atteints. Le contrôle fonctionne mais n'est pas adapté à un jeu final. Une UX produit pourrait utiliser des boutons superposés/transparents ou des gestes swipe ; cette optimisation n'est pas ajoutée à `0.3.1`, dont l'objectif reste le POC de référence.
## Décision plateforme confirmée
Le POC confirme que Tauri Android est techniquement viable mais apporte une chaîne supplémentaire JDK/Gradle/Tauri/WebView/WASM sans avantage actuellement démontré sur le host Android SDL3 natif. Les futurs jeux Android restent donc orientés SDL3/Java/JNI.
La variante Tauri Desktop est elle aussi différée tant qu'une solution de monétisation desktop/WebView, notamment reward vidéo, n'est pas démontrée. Les pistes nécessitant une URL Web publique ou un SDK tiers restent des hypothèses à valider, pas des capacités acquises.
## Suite
`0.3.1-0-pre.4` ferme le POC fonctionnel par la gestion explicite background/foreground, resize/rotation, tracing lifecycle et politique Back Android.

View File

@@ -0,0 +1,62 @@
<!-- file: history/0.3.1/0-pre.4.fix.1.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.4.fix.1
## Statut
Correctif validé par l'utilisateur le 2026-09-21. Le POC Tauri Android est jouable sur l'AVD x86_64 et sur un Galaxy S9+ ARM64 réel ; HTTP et HMR utilisent désormais le même tunnel USB `1436` sur appareil physique.
Aucune extraction `0-pre.5` n'est justifiée. La version peut entrer en `2-beta.1` pour la validation large et le packaging APK autonome.
## Gate workspace
Les validations suivantes passent :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 222 file(s))
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
cargo test -p game-snake-poc-wasm --all-targets --all-features: 5 passed, 0 failed
```
## Smoke ARM64 réel
Le téléphone expose `arm64-v8a`; Tauri sélectionne `aarch64-linux-android`, compile `libgame_snake_poc_tauri_lib.so`, assemble puis installe l'application avec succès.
La gate utilise :
```text
adb reverse tcp:1436 tcp:1436
cargo tauri android dev --host 127.0.0.1
```
Les traces confirment :
```text
Snake Tauri frontend bootstrap started
Snake Tauri assets ready: game-snake-poc / snake / engine-v1
Snake Tauri WASM initialized
Snake Tauri provenance: android / tauri-webview / wasm / phone / unknown
Snake Tauri runtime resumed
Snake Tauri frontend ready
Snake Tauri provenance: android / tauri-webview / wasm / phone / touch
```
L'erreur Vite `failed to connect to websocket` observée avant le fix ne réapparaît pas.
## Back et teardown
Le premier smoke ARM64 de `0-pre.4` avait observé un abort `FORTIFY: pthread_mutex_lock called on a destroyed mutex` pendant le teardown. Le smoke `fix.1` ne le reproduit pas : après Back, les traces passent par `runtime paused`, `visibility hidden`, destruction de la surface EGL, `stopped(true)` puis `dispatchDetachedFromWindow`, sans crash observé avant l'arrêt volontaire de la commande.
Le défaut est donc traité comme un risque potentiellement intermittent plutôt que comme une régression reproductible du host. `2-beta.1` doit encore le retester sur un APK autonome, sans Vite ni tunnel ADB.
## Suite
`0.3.1-2-beta.1` exécute la suite Cargo workspace complète, package un APK Debug autonome contenant x86_64 et ARM64, puis le fume sur AVD et appareil réel.

60
history/0.3.1/2-beta.1.md Normal file
View File

@@ -0,0 +1,60 @@
<!-- file: history/0.3.1/2-beta.1.md -->
<!-- version: 1 -->
# Historique 0.3.1-2-beta.1
## Statut
Beta validée par l'utilisateur le 2026-09-21. La version peut entrer en `0.3.1-3-rc.1` sans `2-beta.2`.
## Gate workspace
Les audits Rust/workspace, Markdown et distribution sont propres. `cargo fmt`, `cargo check --workspace` et Clippy workspace strict passent.
La suite :
```text
cargo test --workspace --all-targets --all-features
```
termine avec 35 tests réussis et aucun échec sur les crates effectivement porteuses de tests.
## Build frontend et APK autonome
`npm install` termine sans vulnérabilité signalée. Le hook Tauri `beforeBuildCommand` exécute :
```text
WASM release
wasm-bindgen
TypeScript
Vite production
static copy des deux familles d'assets
```
Le build Android :
```text
cargo tauri android build --debug --apk --target aarch64 --target x86_64
```
compile les cibles `aarch64-linux-android` et `x86_64-linux-android`, puis produit :
```text
gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk
```
Le contrôle `unzip -l` demandé par le delta n'apparaît pas dans la sortie transmise ; il reste donc explicitement dans la gate RC.
## Smoke ARM64 réel
L'APK universal est installé avec succès sur le Galaxy S9+ `arm64-v8a` puis `MainActivity` est lancée via ADB.
## Smoke x86_64 AVD
L'AVD `sasedev_games_api36` utilise une image `x86_64`. Le même APK universal est installé avec succès sur `emulator-5554`, puis `MainActivity` est lancée via ADB.
Ces deux installations/lancements valident le packaging autonome sur les deux architectures de référence sans serveur Vite ni tunnel ADB. Les contrôles lifecycle/Back détaillés restent repris dans la matrice RC afin de fermer explicitement le risque de teardown observé antérieurement en mode dev.
## Décision de phase
Aucun défaut beta ne justifie `2-beta.2`. Le scope fonctionnel est gelé et `3-rc.1` devient la tranche suivante.

54
history/0.3.1/3-rc.1.md Normal file
View File

@@ -0,0 +1,54 @@
<!-- file: history/0.3.1/3-rc.1.md -->
<!-- version: 1 -->
# Historique 0.3.1-3-rc.1
## Statut
RC validée par l'utilisateur le 2026-09-21 puis retenue pour promotion mécanique vers `0.3.1`.
## Gate workspace
La gate RC complète a passé :
- `cargo fmt --all` puis `cargo fmt --all -- --check` ;
- audits Rust général, exports, workspace, Markdown et distribution propres ;
- `cargo check --workspace` ;
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` ;
- `cargo test --workspace --all-targets --all-features` avec 35 tests réussis et aucun échec.
## Build frontend et packaging Android
`npm install` a terminé sans vulnérabilité signalée. Le build Tauri a exécuté son hook `beforeBuildCommand`, comprenant le build WASM release, `wasm-bindgen`, TypeScript, Vite production et la copie des assets.
La commande :
```text
cargo tauri android build --debug --apk --target aarch64 --target x86_64
```
a produit l'APK autonome universal :
```text
gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk
```
Le contrôle `unzip -l` a confirmé les deux bibliothèques natives :
```text
lib/arm64-v8a/libgame_snake_poc_tauri_lib.so
lib/x86_64/libgame_snake_poc_tauri_lib.so
```
## Smokes Android
Le même APK universal a été installé puis lancé avec succès :
- sur l'AVD `sasedev_games_api36` x86_64 ;
- sur le Galaxy S9+ ARM64 réel.
Aucun défaut nécessitant un `3-rc.1.fix.N` n'a été remonté après ces smokes.
## Conclusion
La RC reste fonctionnellement identique au scope gelé de `0.3.1`. La promotion stable peut rester strictement mécanique.

51
history/0.3.3/0-pre.1.md Normal file
View File

@@ -0,0 +1,51 @@
<!-- file: history/0.3.3/0-pre.1.md -->
<!-- version: 1 -->
# Historique 0.3.3-0-pre.1
## Statut
Le cadrage `0.3.3-0-pre.1` a été validé par l'utilisateur le 2026-09-21. Les audits, le formatage, le check workspace et Clippy strict passent ; aucun `0-pre.1.fix.N` n'est requis avant l'ouverture de `0-pre.2`.
## Gate utilisateur
La gate demandée dans le delta a été exécutée avec succès :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 233 file(s))
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
Le compte Markdown local est supérieur à celui du ZIP taggé audité pendant `0-pre.1`; l'audit reste propre et aucune divergence de source n'est déduite de ce seul compteur.
## Environnement Android confirmé
L'inventaire frais transmis pour ouvrir `0-pre.2` confirme :
- Rust et Cargo `1.94.1` ;
- `cargo-ndk 4.1.2` ;
- targets Rust Android installées : `aarch64-linux-android`, `armv7-linux-androideabi`, `i686-linux-android`, `x86_64-linux-android` ;
- target WASM `wasm32-unknown-unknown` et host `x86_64-unknown-linux-gnu` installés ;
- `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` ;
- la commande `java` du `PATH` expose Temurin `25.0.4.1`, tandis que Gradle utilise bien le JDK `17.0.20.1` via `JAVA_HOME` pour son launcher et son daemon ;
- `ANDROID_HOME=/home/sinus/DEV/AndroidSDK` ;
- ADB `37.0.1` ;
- Samsung `SM_G965F` réel connecté ;
- émulateur x86_64 `emulator-5554` connecté ;
- AVD `Medium_Phone`, `Medium_Phone_API_36.0` et `sasedev_games_api36` disponibles ;
- Gradle système `9.7.1` ;
- NDK projet `28.2.13676358` présent ;
- `Android/libs/SDL3-3.4.16.aar` présent avec SHA-256 `03710fc7b49cc070551446841a843840fabf2aaaaa3043cd5739292a55e4e61c`.
La différence entre `java -version` et `JAVA_HOME` n'est pas un défaut de la gate : le pipeline Gradle doit utiliser le wrapper et le JDK désigné par `JAVA_HOME`, pas dépendre implicitement du premier `java` du `PATH`.
## Transmission vers 0-pre.2
Tous les prérequis prévus par le plan sont disponibles pour la preuve mono-ABI Snake : wrapper Gradle épinglable, JDK 17 effectif pour Gradle, r28c, AAR SDL3 attendu, target Rust ARM64 et appareil ARM64 disponible pour une validation ultérieure.

87
history/0.3.3/0-pre.2.md Normal file
View File

@@ -0,0 +1,87 @@
<!-- file: history/0.3.3/0-pre.2.md -->
<!-- version: 1 -->
# Historique 0.3.3-0-pre.2
## Statut
`0.3.3-0-pre.2` a été validé par l'utilisateur le 2026-09-21. La preuve Snake ARM64 confirme que `assembleDebug` possède désormais le build Rust/SDL3 sans exécution préalable de `scripts/build_android_rust.py`.
Aucun `0-pre.2.fix.N` n'est requis ; la suite peut ouvrir `0-pre.3`.
## Gate statique et Rust
La gate fournie est propre :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 235 file(s))
Distribution layout audit: clean (53 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
Le workspace compilé porte bien `0.3.3-0-pre.2` sur les crates concernées.
## Preuve Gradle/JDK du jalon
Le wrapper introduit pour cette preuve a été contrôlé avant build :
```text
Gradle Wrapper JAR SHA-256
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
Gradle distribution
9.6.0
Gradle binary ZIP SHA-256
bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
```
`./gradlew --version` a téléchargé puis exécuté Gradle `9.6.0`. Pour cette gate, le launcher et le daemon utilisaient Temurin `17.0.20.1` via `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64`.
Cette combinaison décrit la preuve `0-pre.2`; elle n'est pas conservée comme contrainte exacte du pipeline natif après la décision de toolchain prise pour `0-pre.3`.
## Build Snake ARM64
La commande :
```text
:game-snake-poc:assembleDebug
```
a déclenché `buildDebugSasedevRustArm64V8a`, compilé `game-android-entrypoint` avec `cargo ndk` pour `aarch64-linux-android`, copié les bibliothèques sous `build/generated/sasedevNative/...` et terminé avec :
```text
BUILD SUCCESSFUL
54 actionable tasks: 54 executed
```
Aucune commande Python de build natif n'a été exécutée avant Gradle.
## Inspection APK
L'artefact produit est :
```text
Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
```
L'inspection confirme les deux bibliothèques ARM64 attendues :
```text
lib/arm64-v8a/libSDL3.so
lib/arm64-v8a/libgame_android_entrypoint.so
```
Aucune bibliothèque `x86_64`, `armeabi-v7a` ou `x86` n'est présente dans cette preuve mono-ABI. Les lignes répétées dans la sortie utilisateur provenaient des deux `grep -Fx` exécutés après le listing initial, pas de doublons internes à l'APK.
## Transmission vers 0-pre.3
Le task wiring, l'extraction SDL3, le linkage Rust, le staging `jniLibs` généré et le packaging ARM64 sont donc prouvés. `0-pre.3` peut élargir ce même contrat à `arm64-v8a + x86_64`.
Après cette validation, la politique toolchain a été révisée explicitement : le projet Android SDL3 natif doit accepter le Gradle système lorsqu'il est `>= 9.6.0` et utiliser le JDK normal de l'environnement, sans `export JAVA_HOME` spécifique au projet. Les contraintes historiques du POC Tauri restent séparées.

66
history/0.3.3/0-pre.3.md Normal file
View File

@@ -0,0 +1,66 @@
<!-- file: history/0.3.3/0-pre.3.md -->
<!-- version: 1 -->
# Historique 0.3.3-0-pre.3
## Statut
`0.3.3-0-pre.3` a été validé par l'utilisateur le 2026-09-21. La tranche prouve le pipeline Gradle système avec JDK courant, le packaging Debug `arm64-v8a + x86_64` de Snake et Reflex, puis l'installation/lancement du même APK Snake sur AVD x86_64 et appareil ARM64 réel.
Aucun `0-pre.3.fix.N` n'est requis. La suite peut ouvrir `0-pre.4`.
## Toolchain réellement validée
Le shell n'impose plus `JAVA_HOME` et utilise Temurin 25 :
```text
OpenJDK / Temurin 25.0.4.1 LTS
JAVA_HOME=
Gradle 9.7.1
Launcher JVM 25.0.4.1
Daemon JVM /usr/lib/jvm/temurin-25-jdk-amd64
```
Cette preuve confirme que le chemin Android SDL3 natif fonctionne avec un Gradle système supérieur au minimum `9.6.0` et avec le JDK courant de l'environnement, sans export JDK 17 spécifique. La glue Java reste compilée avec source/target 17.
## Gates statiques et Rust
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 237 file(s))
Distribution layout audit: clean (50 required path(s), 5 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
## Builds multi-ABI
Snake a exécuté `buildDebugSasedevRustArm64V8a` puis `buildDebugSasedevRustX8664` et terminé avec `BUILD SUCCESSFUL`. Reflex a exécuté les mêmes deux tâches ABI et terminé avec `BUILD SUCCESSFUL`.
Les deux APK Debug universal contiennent exactement les couples observés :
```text
lib/arm64-v8a/libSDL3.so
lib/arm64-v8a/libgame_android_entrypoint.so
lib/x86_64/libSDL3.so
lib/x86_64/libgame_android_entrypoint.so
```
Les lignes répétées de la sortie utilisateur proviennent des `grep -Fx` de contrôle exécutés après le listing initial.
## Smokes runtime
L'APK Snake a été installé puis lancé avec succès sur :
- `emulator-5554`, AVD `sdk_gphone64_x86_64` ;
- appareil physique Samsung `SM_G965F`, ARM64.
Les deux installations retournent `Success` et `SnakeActivity` est démarrée via `am start`.
## Révision de scope après validation
Après cette preuve 64 bits, le besoin produit a été élargi : `0.3.3` ne doit pas se limiter à ARM64/x86_64 mais tenter de supporter toutes les ABI encore prises en charge par la toolchain Android moderne, y compris les 32 bits anciennes. `0-pre.4` porte donc la matrice `arm64-v8a + armeabi-v7a + x86_64 + x86`.

76
history/0.3.3/0-pre.4.md Normal file
View File

@@ -0,0 +1,76 @@
<!-- file: history/0.3.3/0-pre.4.md -->
<!-- version: 1 -->
# Historique 0.3.3-0-pre.4
## Statut
`0.3.3-0-pre.4` a été validé par l'utilisateur le 2026-09-21. La tranche confirme la matrice Android native complète encore supportée par la toolchain moderne pour Snake et Reflex : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
Aucun `0-pre.4.fix.N` n'est requis. La suite peut ouvrir `0-pre.5`.
## Toolchain réellement validée
```text
OpenJDK / Temurin 25.0.4.1 LTS
Gradle 9.7.1
Launcher JVM 25.0.4.1
Daemon JVM /usr/lib/jvm/temurin-25-jdk-amd64
```
Targets Rust Android présents :
```text
aarch64-linux-android
armv7-linux-androideabi
i686-linux-android
x86_64-linux-android
```
## Gates statiques et Rust
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 239 file(s))
Distribution layout audit: clean (50 required path(s), 5 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
## Builds quatre ABI
Snake et Reflex exécutent tous deux avec succès les tâches natives Gradle pour :
```text
arm64-v8a -> aarch64-linux-android
armeabi-v7a -> armv7-linux-androideabi
x86 -> i686-linux-android
x86_64 -> x86_64-linux-android
```
Les deux commandes `assembleDebug` terminent avec `BUILD SUCCESSFUL`.
## Inspection des APK
Les APK Debug universal Snake et Reflex contiennent chacun exactement les couples natifs attendus pour les quatre ABI :
```text
lib/arm64-v8a/libSDL3.so
lib/arm64-v8a/libgame_android_entrypoint.so
lib/armeabi-v7a/libSDL3.so
lib/armeabi-v7a/libgame_android_entrypoint.so
lib/x86/libSDL3.so
lib/x86/libgame_android_entrypoint.so
lib/x86_64/libSDL3.so
lib/x86_64/libgame_android_entrypoint.so
```
Les répétitions visibles dans la sortie utilisateur proviennent des `grep -Fx` de contrôle exécutés après le listing initial.
## Conséquence
La matrice quatre ABI n'est plus seulement une intention documentaire : le même pipeline Gradle/Cargo/SDL3 la compile et la package pour les deux jeux. `0-pre.5` peut donc étendre ce pipeline aux variantes Release/AAB, vérifier la compatibilité 16 KB, consolider API 21 et supprimer le builder Python Android historique.

137
history/0.3.3/0-pre.5.md Normal file
View File

@@ -0,0 +1,137 @@
<!-- file: history/0.3.3/0-pre.5.md -->
<!-- version: 1 -->
# Historique 0.3.3-0-pre.5
## Statut
`0.3.3-0-pre.5` a été validé par l'utilisateur le 2026-09-21. La tranche ferme le chemin Android natif historique : Gradle possède les variantes Debug/Release, les AAB quatre ABI sont produits, `minSdk 21` dispose d'une preuve runtime, la compatibilité 16 KB 64 bits est vérifiée et `scripts/build_android_rust.py` est supprimé.
Aucun `0-pre.5.fix.N` n'est requis. La suite ouvre `0.3.3-2-beta.1`.
## Toolchain réellement validée
```text
OpenJDK / Temurin 25.0.4.1 LTS
Gradle 9.7.1
AGP 9.4.0
NDK 28.2.13676358 / r28c
```
Le pipeline utilise le JDK et le Gradle de l'environnement ; aucun `JAVA_HOME` JDK 17 ni wrapper Gradle natif n'est requis.
## Gates statiques et Rust
La première exécution de l'audit de distribution a détecté deux anciens répertoires générés laissés localement par le chemin historique :
```text
Android/game-reflex-poc/src/main/jniLibs
Android/game-snake-poc/src/main/jniLibs
```
Ils ont été supprimés puis l'audit a été rejoué avec succès :
```text
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
```
Les autres gates fournies sont propres :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 241 file(s))
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
L'échec initial de layout provenait donc de résidus locaux non versionnés, pas d'un retour du builder Python dans le delta.
## Release et AAB quatre ABI
Les commandes suivantes terminent avec `BUILD SUCCESSFUL` :
```text
:game-snake-poc:bundleRelease
:game-reflex-poc:bundleRelease
```
Pour les deux jeux, Gradle exécute les builds Rust Release pour :
```text
arm64-v8a
armeabi-v7a
x86_64
x86
```
Les deux AAB contiennent exactement les couples attendus sous `base/lib/<abi>/` :
```text
libSDL3.so
libgame_android_entrypoint.so
```
pour chacune des quatre ABI.
## Packaging et pages 16 KB
`zipalign -c -P 16 -v 4` réussit sur les APK Debug Snake et Reflex.
Les contrôles ELF Debug et Release montrent :
```text
arm64-v8a -> LOAD align 2**14
x86_64 -> LOAD align 2**14
armeabi-v7a -> LOAD align 2**12
x86 -> LOAD align 2**12
```
pour SDL3 et `libgame_android_entrypoint.so`.
Le critère 16 KB est appliqué aux ABI/appareils 64 bits, conformément à l'exigence Google Play. Les alignements `2**12` des ABI 32 bits ne constituent donc pas un échec.
## Preuve Android 5.0 / API 21
Un AVD basé sur :
```text
system-images/android-21/google_apis/x86/
```
retourne :
```text
API 21
ABI x86
KernelPageSize 4 kB
```
L'APK Snake universal est installé avec succès et `SnakeActivity` démarre. `minSdk 21` dispose donc d'une preuve runtime réelle sur x86 32 bits.
## Preuve Android 15 / 16 KB
Un AVD basé sur :
```text
system-images/android-35/google_apis_playstore_ps16k/x86_64/
```
retourne :
```text
API 35
ABI x86_64
getconf PAGE_SIZE = 16384
```
L'APK Snake s'installe, `SnakeActivity` démarre et `pidof com.sasedev.games.snake` retourne un PID actif.
Une lecture séparée de `/proc/self/smaps` retourne `KernelPageSize: 4 kB` pour le mapping inspecté. Cette valeur n'est pas utilisée comme mesure du mode global du device ; la preuve de taille de page de l'environnement repose sur `getconf PAGE_SIZE=16384`.
## Conséquence
Le scope d'implémentation `0-pre.*` est fermé. La beta peut se concentrer sur la validation large, la reproductibilité du packaging quatre ABI et les smokes de référence sans introduire de nouvelle fonctionnalité.

118
history/0.3.3/2-beta.1.md Normal file
View File

@@ -0,0 +1,118 @@
<!-- file: history/0.3.3/2-beta.1.md -->
<!-- version: 1 -->
# Historique 0.3.3-2-beta.1
## Statut
`0.3.3-2-beta.1` a été validée par l'utilisateur le 2026-09-21. La gate large confirme la reproductibilité du pipeline Android SDL3 natif quatre ABI, les artefacts APK/AAB et les smokes de référence x86_64 + ARM64 pour Snake et Reflex.
Aucun `2-beta.1.fix.N` n'est requis. La version peut entrer directement en `0.3.3-3-rc.1`.
## Gate workspace
Les validations suivantes sont propres :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 251 file(s))
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
cargo test --workspace --all-targets --all-features: 35 passed, 0 failed
```
## Rebuild Android quatre ABI
Après nettoyage Gradle des deux applications, les builds suivants terminent avec succès :
```text
:game-snake-poc:assembleDebug
:game-reflex-poc:assembleDebug
:game-snake-poc:bundleRelease
:game-reflex-poc:bundleRelease
```
Pour Snake et Reflex, les tâches natives ont reconstruit les quatre cibles :
```text
arm64-v8a -> aarch64-linux-android
armeabi-v7a -> armv7-linux-androideabi
x86 -> i686-linux-android
x86_64 -> x86_64-linux-android
```
## Inspection APK et AAB
Chaque APK Debug contient :
```text
lib/<abi>/libSDL3.so
lib/<abi>/libgame_android_entrypoint.so
```
pour les quatre ABI. Chaque AAB Release contient les mêmes couples sous `base/lib/<abi>/`.
`zipalign -c -P 16 -v 4` retourne `Verification successful` pour les APK Debug Snake et Reflex.
## Smokes x86_64/API 36
L'AVD de référence rapporte :
```text
API 36
x86_64
PAGE_SIZE 4096
```
Snake :
```text
install: Success
Status: ok
LaunchState: COLD
pidof: 5192
crash buffer: aucune entrée remontée
```
Reflex :
```text
install: Success
Status: ok
LaunchState: COLD
pidof: 5272
crash buffer: aucune entrée remontée
```
## Smokes ARM64 réels
Le Samsung `SM_G965F` rapporte :
```text
API 29
ro.product.cpu.abi=arm64-v8a
ro.product.cpu.abilist=arm64-v8a,armeabi-v7a,armeabi
```
Snake : installation `Success`, `Status: ok`, lancement COLD et processus vivant (`pidof 30958`).
Reflex : installation `Success`, `Status: ok`, lancement COLD et processus vivant (`pidof 31798`).
Aucune entrée du buffer crash n'a été remontée pendant ces quatre smokes. Les sorties ADB établissent le démarrage et l'absence de crash immédiat ; la matrice RC conserve explicitement la vérification visuelle du rendu et des contrôles comme dernière observation humaine de publication.
## Frontières déjà acquises en 0-pre.5
Aucune logique Android/native n'ayant changé depuis les preuves de frontière, la beta réutilise sans les rejouer systématiquement :
- Android 5.0/API 21 x86 : APK Snake installé et activité démarrée ;
- Android 15/API 35 x86_64 `ps16k` : `getconf PAGE_SIZE=16384`, APK installé, activité démarrée et processus vivant ;
- ELF `arm64-v8a` et `x86_64` alignés `2**14` pour SDL3 et la bibliothèque Rust.
## Conclusion
Le scope d'implémentation reste fermé. `3-rc.1` peut être une candidate strictement gelée, centrée sur la reproductibilité finale, la documentation de publication et la préparation de la session suivante.

100
history/0.3.3/3-rc.1.md Normal file
View File

@@ -0,0 +1,100 @@
<!-- file: history/0.3.3/3-rc.1.md -->
<!-- version: 1 -->
# Historique 0.3.3-3-rc.1
## Statut
`0.3.3-3-rc.1` a été validée par l'utilisateur le 2026-09-21 puis retenue pour promotion mécanique vers la stable `0.3.3`.
Le correctif `3-rc.1.fix.1` est strictement documentaire : il enrichit le prompt `0.3.4` sans changer la version Cargo, le runtime ou le pipeline Android. Ses audits Markdown/distribution ont ensuite été validés.
## Gate workspace et packaging
La gate RC a confirmé :
- audits Rust général, exports, workspace, Markdown et distribution propres ;
- `cargo fmt --all` / `--check`, `cargo check --workspace`, Clippy strict et suite workspace complète avec 35 tests réussis ;
- rebuild propre Snake/Reflex en Debug et Release ;
- quatre ABI `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86` dans les APK Debug et AAB Release ;
- `zipalign -c -P 16 -v 4` avec `Verification successful` sur les deux APK.
## Alignement ELF 64 bits
Les bibliothèques Release `libSDL3.so` et `libgame_android_entrypoint.so` de Snake et Reflex ont été inspectées pour `arm64-v8a` et `x86_64` avec `llvm-objdump` du NDK `28.2.13676358`.
Toutes les lignes `LOAD` observées sont alignées à :
```text
align 2**14
```
Le critère 16 KB 64 bits de la matrice RC est donc satisfait.
## Smokes AVD API 36 x86_64
Snake :
```text
install: Success
Status: ok
LaunchState: COLD
pidof: 4777
crash buffer: aucune entrée remontée
```
Reflex :
```text
install: Success
Status: ok
LaunchState: COLD
pidof: 5545
crash buffer: aucune entrée remontée
```
## Smokes ARM64 réels
Le Samsung de validation rapporte :
```text
API 29
ro.product.cpu.abi=arm64-v8a
```
Snake :
```text
install: Success
Status: ok
LaunchState: COLD
pidof: 21137
crash buffer: aucune entrée remontée
```
Reflex :
```text
install: Success
Status: ok
LaunchState: UNKNOWN (-1)
pidof: 22830
crash buffer: aucune entrée remontée
```
`LaunchState: UNKNOWN (-1)` n'a pas été traité comme un crash : l'activité a terminé la commande `am start -W`, le processus est resté vivant et le buffer crash n'a remonté aucune entrée.
Les observations humaines demandées par la matrice RC ont été exécutées dans le cadre de ces smokes et aucun défaut supplémentaire n'a été signalé avant la promotion stable.
## Frontières déjà acquises
Aucune modification native/Gradle n'étant intervenue depuis les preuves précédentes, la RC conserve aussi :
- Android 5.0/API 21 x86 avec installation et démarrage Snake ;
- Android 15/API 35 x86_64 `ps16k` avec `PAGE_SIZE=16384`, installation, démarrage et processus vivant ;
- support des quatre ABI dans le pipeline Gradle/Cargo ;
- suppression du builder Python Android historique et interdiction des `src/main/jniLibs` générés.
## Conclusion
Aucun défaut nécessitant un `3-rc.1.fix.N` fonctionnel n'a été remonté. La promotion vers `0.3.3` reste strictement mécanique.

View File

@@ -1,5 +1,5 @@
<!-- file: prompts/003-V0_3_1_START_PROMPT.md --> <!-- file: prompts/003-V0_3_1_START_PROMPT.md -->
<!-- version: 3 --> <!-- version: 4 -->
# Prompt de démarrage `0.3.1` — second host Snake Tauri Android # Prompt de démarrage `0.3.1` — second host Snake Tauri Android
@@ -281,13 +281,13 @@ Pour une application Tauri, `npm` sert seulement à gérer les dépendances fron
Le smoke courant utilise : Le smoke courant utilise :
```bash ```bash
(cd <tauri-app> && cargo tauri dev) (cd <tauri-app> && cargo tauri android dev)
``` ```
Le packaging est réservé aux phases finales prévues par le plan : Le packaging est réservé aux phases finales prévues par le plan :
```bash ```bash
(cd <tauri-app> && cargo tauri build) (cd <tauri-app> && cargo tauri android build)
``` ```
Ne pas traiter le host Tauri comme le host navigateur direct de `0.3.0` : le mode de lancement, le packaging, les permissions et les smokes sont différents. Ne pas traiter le host Tauri comme le host navigateur direct de `0.3.0` : le mode de lancement, le packaging, les permissions et les smokes sont différents.
@@ -417,7 +417,7 @@ Toute commande nécessitant un changement de répertoire est englobée dans un s
(cd <répertoire> && <commande>) (cd <répertoire> && <commande>)
``` ```
Pour Tauri, utiliser `cargo tauri dev` pour les smokes et réserver `cargo tauri build` aux phases finales de packaging ; `npm` n'est utilisé directement que pour gérer les dépendances frontend. Pour Tauri, utiliser `cargo tauri android dev` pour les smokes Android et réserver `cargo tauri android build` aux phases finales de packaging Android ; `npm` n'est utilisé directement que pour gérer les dépendances frontend.
## 13. Qui exécute quoi ## 13. Qui exécute quoi
@@ -429,8 +429,8 @@ Les validations de build/runtime restent côté utilisateur :
cargo check workspace + Clippy workspace strict lorsqu'un delta touche Rust/dépendances cargo check workspace + Clippy workspace strict lorsqu'un delta touche Rust/dépendances
tests cargo ciblés par défaut tests cargo ciblés par défaut
test workspace complet seulement aux jalons planifiés/portées incertaines test workspace complet seulement aux jalons planifiés/portées incertaines
(cd <tauri-app> && cargo tauri dev) pour les smokes Tauri (cd <tauri-app> && cargo tauri android dev) pour les smokes Tauri
(cd <tauri-app> && cargo tauri build) pour les validations finales de packaging Tauri (cd <tauri-app> && cargo tauri android build) pour les validations finales de packaging Tauri
Gradle/SDK/NDK lorsque le chemin Tauri les invoque Gradle/SDK/NDK lorsque le chemin Tauri les invoque
installation émulateur/appareil installation émulateur/appareil
smoke mobile smoke mobile
@@ -482,8 +482,8 @@ Sinon omettre la tranche.
### `2-beta.1` — validation large ### `2-beta.1` — validation large
- workspace complet ; - workspace complet ;
- `(cd <tauri-app> && cargo tauri build)` pour le packaging Tauri Android ; - `(cd <tauri-app> && cargo tauri android build)` pour le packaging Tauri Android ;
- smoke via `(cd <tauri-app> && cargo tauri dev)` puis émulateur/appareil disponible ; - smoke via `(cd <tauri-app> && cargo tauri android dev)` puis émulateur/appareil disponible ;
- non-régression Web direct ; - non-régression Web direct ;
- non-régression Desktop SDL3 lorsque nécessaire ; - non-régression Desktop SDL3 lorsque nécessaire ;
- vérification dépendances/package/distribution. - vérification dépendances/package/distribution.

View File

@@ -0,0 +1,287 @@
<!-- file: prompts/004-V0_3_3_START_PROMPT.md -->
<!-- version: 1 -->
# Prompt de démarrage `0.3.3` — Android SDL3 natif multi-ABI et packaging Gradle
## 1. Base exacte et version cible
Partir uniquement de la version stable/taggée :
```text
v0.3.1
```
Si ce prompt est lu avant la publication effective de `0.3.1`, ne pas ouvrir `0.3.3`. Terminer d'abord la RC puis la release stable `0.3.1`.
`0.3.2` est volontairement différée : ne pas créer de placeholder, de version vide ou de POC Tauri Desktop par cérémonial. Sa réouverture dépend d'un bénéfice produit concret, notamment une monétisation Desktop/WebView réellement démontrée.
Version cible active :
```text
0.3.3
```
Première tranche obligatoire :
```text
0.3.3-0-pre.1
```
`0-pre.1` est le gate de cadrage : audit, requirements, recherche ciblée, sizing, risques, matrice de validation et création du plan actif sous `docs/plans/`. Ne pas commencer directement par une réécriture Gradle.
## 2. Sources de vérité
Lire intégralement, dans cet ordre :
```text
RULES.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
```
Puis au minimum :
```text
docs/rules/RULES_GENERAL.md
docs/rules/RULES_RUST.md
docs/rules/RULES_PROJECT.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/FILE_CONTRACTS.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/RULES_COMMANDS.md
docs/rules/RULES_VALIDATION_MATRIX.md
docs/rules/RULES_SESSION_PLANNING.md
docs/rules/PROMPT_STRUCTURE.md
```
Transmission plateforme/Android :
```text
docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md
docs/architecture/002-ANDROID_ARCHITECTURE.md
docs/architecture/003-SDL3_PLATFORM_ABSTRACTION.md
docs/architecture/004-INPUT_AND_CONTROLS.md
docs/architecture/005-ASSET_ARCHITECTURE.md
docs/architecture/010-RUNTIME_PROVENANCE.md
docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md
docs/development/006-ANDROID_RUST_NATIVE_BUILD.md
docs/development/007-ANDROID_JNI_BRIDGE.md
docs/testing/005-V0_3_1_RC_VALIDATION_MATRIX.md
history/0.3.1/
deltas/0.3.1/
```
Auditer ensuite le code réellement concerné :
```text
Android/
crates/apps/game-android-entrypoint/
crates/engines/engine-v1-sdl/
crates/engines/engine-v1-platform-api/
crates/games/game-reflex-poc/
crates/games/game-snake-poc/
assets/
scripts/build_android_rust.py
```
Le script Python historique est une source d'information sur le pipeline existant, pas la cible d'orchestration à conserver.
## 3. État hérité attendu
La stable `0.3.1` doit laisser :
- Desktop SDL3 natif fonctionnel ;
- Android SDL3/Java/JNI historique déjà capable d'exécuter Reflex et Snake ;
- `game-android-entrypoint` comme `cdylib` commun sélectionnant exactement un jeu ;
- SDL3 AAR/Prefab et `cargo-ndk` déjà connus du dépôt ;
- Tauri Android Snake conservé uniquement comme POC de référence ;
- preuve qu'un même package Android peut couvrir ARM64 réel et x86_64 émulateur ;
- décision produit : la voie Android de production est SDL3 native.
Ne pas réutiliser Tauri, WebView ou WASM dans le packaging Android natif uniquement parce qu'ils existent dans `0.3.1`.
## 4. Mission `0.3.3`
Transformer le chemin Android SDL3 natif historique en pipeline multi-ABI reproductible possédé par Cargo/Gradle, sans script Python de build, et préparer une distribution Android réellement adaptée aux futurs jeux.
Le résultat attendu doit permettre au minimum :
```text
Gradle -> build Rust natif -> SDL3/JNI -> APK de test
Gradle -> plusieurs ABI -> APK universal de test
Gradle -> plusieurs ABI pertinentes -> AAB de distribution
```
Le choix final des ABI et du `minSdk` doit être fondé sur les contraintes réellement vérifiées de SDL3, NDK, AGP, Rust et du code projet.
## 5. Multi-ABI et anciennes versions Android : deux sujets distincts
Ne pas confondre :
- **ABI CPU** : `arm64-v8a`, `x86_64`, éventuellement `armeabi-v7a` si elle reste techniquement et produitement justifiée ;
- **version Android / API level** : `minSdk`, `targetSdk`, `compileSdk`.
`arm64-v8a` est la cible appareil productive minimale actuelle ; `x86_64` reste utile pour les AVD. Le support `armeabi-v7a` n'est pas automatiquement requis pour supporter un Android ancien : il doit être évalué séparément.
Le souhait produit est de supporter des versions Android anciennes lorsque cela reste raisonnable. `0-pre.1` doit donc vérifier la documentation officielle actuelle et le pipeline réel avant de fixer un `minSdk`; ne pas recopier aveuglément une valeur historique du dépôt.
## 6. Packaging attendu
Pour les tests locaux, viser un APK universal multi-ABI afin qu'un seul artefact puisse être installé sur les appareils/AVD couverts.
Pour la distribution future, privilégier un Android App Bundle :
```text
app-release.aab
```
contenant les ABI réellement retenues. La signature/keystore de publication reste hors dépôt et ne doit jamais être inventée ou embarquée dans les sources.
La version doit documenter clairement la différence entre :
```text
APK universal -> sideload/tests
AAB -> distribution store
APK split ABI -> option technique, pas artefact par défaut sans besoin
```
## 7. Architecture et ownership
Conserver les décisions suivantes :
- Rust porte gameplay et moteur ;
- SDL3 porte la couche plateforme graphique/input native ;
- Java reste la glue Android préférée ; Kotlin n'est pas introduit sans besoin démontré ;
- le contrat JNI reste petit ;
- `Android/common` porte le commun durable ;
- chaque `Android/game-*` reste une application distincte ;
- assets communs et spécifiques restent canoniques sous `assets/` et sont packagés sans duplication source ;
- les bibliothèques `jniLibs` générées ne sont pas versionnées ;
- aucun build Android productif ne dépend de Tauri.
## 8. Orchestration de build
La cible `0.3.3` est de supprimer le besoin de `scripts/build_android_rust.py` dans le chemin normal de build Android.
Le cadrage doit comparer les options propres disponibles dans Gradle/NDK/Cargo, par exemple l'invocation contrôlée de `cargo ndk` depuis des tâches Gradle, sans créer un nouveau script ad hoc dans un autre langage uniquement pour déplacer le problème.
Le résultat doit garantir :
- choix explicite des ABI ;
- build Rust avec exactement une feature jeu ;
- mise à disposition de `libgame_android_entrypoint.so` et `libSDL3.so` par ABI ;
- dépendances de tâches Gradle correctes ;
- rebuild incrémental raisonnable ;
- erreurs visibles et déterministes ;
- outputs générés hors sources versionnées.
## 9. Scope inclus
À cadrer puis implémenter progressivement :
- pipeline Gradle/Cargo natif multi-ABI ;
- au minimum Snake comme jeu-sonde, puis Reflex si la factorisation commune le rend naturel dans la même version ;
- APK universal de test ;
- AAB de validation de distribution sans secrets ;
- choix documenté des ABI ;
- étude et validation du `minSdk` soutenable ;
- smoke x86_64 AVD et ARM64 réel ;
- si un AVD d'API plus ancienne compatible est disponible ou raisonnablement créable, smoke ciblé du plancher Android retenu ;
- mise à jour des docs Android/Gradle/JNI/validation ;
- suppression du script Python du chemin de build normal, puis suppression du fichier lui-même seulement si aucune autre responsabilité durable ne le justifie.
## 10. Hors scope
Ne pas intégrer dans `0.3.3` sans découverte bloquante :
- Tauri Desktop ;
- nouvelles fonctionnalités de gameplay ;
- réseau realtime ;
- publicité AdMob effective ;
- billing ;
- authentification ;
- Uroburas ;
- iOS ;
- migration vers Kotlin ;
- moteur V2 ;
- optimisation prématurée par ABI sans mesure.
L'AAB est un objectif de packaging, pas une ouverture du scope monétisation/store complet.
## 11. `0-pre.1` obligatoire
Avant tout développement lourd :
1. auditer les modules Gradle existants et leurs versions ;
2. auditer `scripts/build_android_rust.py` et distinguer ce qu'il orchestre réellement ;
3. vérifier la version SDL3/AAR réellement utilisée et sa documentation Android actuelle ;
4. vérifier AGP/Gradle/JDK/NDK et les API Android requises ;
5. inventorier les ABI installables/testables localement ;
6. distinguer support OS ancien et support CPU 32 bits ;
7. proposer le `minSdk` candidat avec justification et stratégie de test ;
8. définir APK universal, AAB et éventuels splits ;
9. définir les gates par jalon ;
10. créer `docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md` avec un forecast souple jusqu'à stable.
Si le scope ne tient pas raisonnablement dans une session/version, le découper dès `0-pre.1` plutôt que d'allonger artificiellement les prereleases.
## 12. Validation et responsabilité
Les audits statiques peuvent être exécutés par le générateur. Les builds Cargo/Gradle, tests et smokes finaux restent côté utilisateur conformément aux règles du dépôt et ne sont jamais déclarés réussis sans sortie réelle.
Dès qu'un fichier Rust/Cargo change, appliquer la gate Rust normale. Dès qu'Android/Gradle change, ajouter les tâches Gradle ciblées prévues par le plan. Les tests workspace complets restent réservés aux jalons larges/finalisation selon la matrice.
Les commandes nécessitant un changement de répertoire utilisent un sous-shell :
```bash
(cd Android && <commande>)
```
## 13. Documentation, deltas et historique
Chaque tranche produit `deltas/0.3.3/<jalon>.md` avec base, scope, fichiers et validations attendues.
`history/0.3.3/<jalon>.md` n'est créé que par la tranche suivante après validation effective du jalon précédent.
`CHANGELOG.md` reste normalement silencieux pendant pre/alpha/beta et est consolidé à partir de la première RC. `ROADMAP.md` reste macroscopique.
Les nouvelles responsabilités Android durables doivent être documentées au moment où elles deviennent réelles, pas repoussées intégralement à la fin.
## 14. Forecast initial non contraignant
Le `0-pre.1` doit réviser ce forecast à partir de l'audit :
```text
0-pre.1 audit + requirements + choix ABI/API + plan
0-pre.2 orchestration Gradle/Cargo native sur une ABI
0-pre.3 multi-ABI + APK universal
0-pre.4 AAB + compatibilité Android/minSdk + smokes
2-beta.1 validation large et packaging final
3-rc.1 gel et reproductibilité
0.3.3 promotion mécanique stable
```
Les numéros peuvent être regroupés/scindés selon les résultats réels. Une version doit néanmoins être menée jusqu'à stable dans la session autant que possible.
## 15. Première gate de reprise
Après lecture/audit et avant la livraison `0-pre.1`, relever au minimum :
```bash
rustc --version
cargo --version
rustup target list --installed
java -version
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
adb version
adb devices -l
emulator -list-avds
(cd Android && gradle --version)
```
Compléter par les versions/chemins NDK, SDK, AGP, SDL3 AAR et `cargo ndk` réellement trouvés dans la baseline.
## 16. Critère de réussite
`0.3.3` réussit lorsque le build Android SDL3 natif n'a plus besoin d'un orchestrateur Python, qu'un artefact de test multi-ABI reproductible est produit, qu'un AAB de distribution peut être construit sans secrets embarqués, que le support Android/ABI est explicitement documenté et testé selon les moyens disponibles, et que Snake reste fonctionnel sans régression du moteur partagé.

View File

@@ -0,0 +1,446 @@
<!-- file: prompts/005-V0_3_4_START_PROMPT.md -->
<!-- version: 2 -->
# Prompt de démarrage `0.3.4` — transport realtime et baseline WebSocket
## 1. Base exacte et cible
Partir uniquement de la version stable/taggée :
```text
v0.3.3
```
Si ce prompt est lu avant la publication effective de `0.3.3`, terminer d'abord la RC puis la release stable `0.3.3`.
Version cible active :
```text
0.3.4
```
Première tranche attendue après migration de gouvernance :
```text
0.3.4-alpha.1
```
`alpha.1` remplace à partir de cette session l'ancien rôle obligatoire de `0-pre.1` : audit, requirements, sizing, risques, validation et création/révision du plan actif avant développement lourd.
## 2. Migration de nomenclature — première action de la session
La décision est acquise depuis la fin de `0.3.3`. La nouvelle convention canonique devient :
```text
X.Y.Z-alpha.N
X.Y.Z-alpha.N.fix.M
X.Y.Z-beta.N
X.Y.Z-beta.N.fix.M
X.Y.Z-rc.N
X.Y.Z-rc.N.fix.M
X.Y.Z
```
Supprimer pour les **nouvelles versions** les préfixes numériques historiques `0-`, `1-`, `2-`, `3-` ainsi que la phase `pre`. `alpha.1` porte désormais le cadrage obligatoire.
Avant de créer le premier delta `0.3.4-alpha.1`, mettre à jour au minimum les règles et contrôles actifs concernés :
```text
RULES.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/RULES_SESSION_PLANNING.md
docs/rules/PROMPT_STRUCTURE.md
docs/rules/RULES_DOCUMENTATION.md
scripts/audit_project_workspace_rules.py
```
Chercher également les références normatives/courantes à l'ancienne convention sous `docs/rules/`, `scripts/` et les README actifs, puis les réconcilier lorsque leur sens est prospectif.
### Immutabilité historique absolue
Ne jamais renommer, déplacer ou réécrire pour cette migration les anciens jalons déjà livrés, notamment :
```text
deltas/0.1.0/ ... deltas/0.3.3/
history/0.1.0/ ... history/0.3.3/
anciennes entrées CHANGELOG décrivant ces versions
anciens prompts qui documentent leur workflow réel
```
Ces fichiers restent des preuves historiques avec leurs identifiants `0-pre`, `1-alpha`, `2-beta`, `3-rc`. Les audits doivent accepter cet historique tout en imposant la nouvelle convention aux versions futures.
Les archives delta futures suivent naturellement :
```text
games-sasedev-X.Y.Z-alpha.N-delta.zip
games-sasedev-X.Y.Z-alpha.N.fix.M-delta.zip
games-sasedev-X.Y.Z-beta.N-delta.zip
games-sasedev-X.Y.Z-rc.N-delta.zip
```
## 3. Sources de vérité
Lire intégralement, dans cet ordre :
```text
RULES.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
```
Puis au minimum :
```text
docs/rules/RULES_GENERAL.md
docs/rules/RULES_RUST.md
docs/rules/RULES_PROJECT.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/FILE_CONTRACTS.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/RULES_COMMANDS.md
docs/rules/RULES_VALIDATION_MATRIX.md
docs/rules/RULES_SESSION_PLANNING.md
docs/rules/PROMPT_STRUCTURE.md
```
Transmission architecture/réseau :
```text
docs/architecture/012-MODULAR_LAYERING_AND_OWNERSHIP.md
docs/architecture/013-NETWORK_AND_SERVER_ARCHITECTURE.md
docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md
docs/studies/006-REALTIME_MULTIPLAYER_SERVER_STUDY.md
docs/studies/009-PLATFORM_POC_CANDIDATES.md
docs/studies/011-PLATFORM_POC_VALIDATION_MATRIX.md
docs/studies/012-PLATFORM_POC_SEQUENCE.md
history/0.3.3/
deltas/0.3.3/
```
Auditer ensuite les crates réelles susceptibles de porter les contrats transport/client/serveur. Ne créer aucune abstraction ou crate uniquement parce que ce prompt mentionne un nom possible : `alpha.1` doit déterminer l'ownership physique à partir de l'architecture actuelle.
## 4. État hérité attendu de 0.3.3
La stable `0.3.3` doit laisser :
- gameplay/moteur Rust partagé inchangé ;
- Desktop SDL3, Web/WASM et POC Tauri existants ;
- voie Android productive SDL3 natif/Java/JNI ;
- build Android possédé par Gradle/Cargo sans orchestrateur Python ;
- APK Debug universal et AAB Release pour `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86` ;
- `minSdk 21` réellement fumé sur x86 ;
- compatibilité 16 KB 64 bits réellement fumée ;
- Gradle système `>= 9.6.0` et JDK courant de l'environnement ;
- aucune dépendance réseau realtime imposée au moteur de rendu ou au gameplay.
Ne pas rouvrir le pipeline Android dans `0.3.4` sauf régression directement provoquée par le nouveau scope.
## 5. Mission 0.3.4
Introduire une **API de transport realtime** et une baseline WebSocket fondée sur Tokio + `tokio-tungstenite`, sans implémenter encore le serveur Uroburas Mode 3 ni figer prématurément tout le protocole multijoueur.
La frontière durable reste conceptuellement :
```text
transport
wire codec
session protocol
synchronization
authoritative simulation
```
`0.3.4` se concentre sur le premier étage et les contrats minimaux nécessaires pour le tester proprement. Une socket ouverte ne doit jamais être confondue avec une session gameplay synchronisée.
## 6. Scope inclus
`alpha.1` doit inventorier et décider au minimum :
- emplacement de l'API de transport realtime et sens des dépendances ;
- séparation client/server et éventuels types communs réellement nécessaires ;
- connexion, fermeture, send/receive, erreurs et cancellation async ;
- ownership Tokio/runtime sans le propager inutilement dans le gameplay ;
- baseline `tokio-tungstenite` WebSocket ;
- stratégie de codec initiale suffisamment simple pour le POC sans figer le protocole final ;
- timeouts, backpressure et limites élémentaires ;
- test loopback/local déterministe ;
- logging/tracing transport séparé des futurs domaines session/sync/simulation ;
- compatibilité future avec un autre transport, notamment le POC WebTransport/QUIC de `0.3.5`, sans abstraction spéculative excessive.
## 7. Hors scope
Ne pas transformer `0.3.4` en serveur de jeu complet. Sont notamment hors scope sauf nécessité démontrée par le POC transport :
- Uroburas Mode 3 ;
- matchmaking complet ;
- simulation authoritative de jeu ;
- snapshots/deltas métier définitifs ;
- prediction/reconciliation ou rollback ;
- Redis/NATS/Kafka ;
- persistence gameplay ;
- auth complète ;
- scaling/sharding/regions ;
- WebTransport/QUIC de production, réservé au POC comparatif suivant ;
- Actix Web comme dépendance obligatoire du data plane realtime.
## 8. Contraintes architecturales
Préserver :
- async-first pour les nouvelles API réseau ;
- aucune dépendance transport dans les crates gameplay ;
- distinction stricte transport/session/sync/simulation ;
- façade `lib.rs` et ownership modulaire conformes aux règles Rust du dépôt ;
- `tracing` pour les diagnostics ;
- erreurs explicites, cancellation et fermeture propres ;
- pas de framework générique massif avant deux consommateurs réels ;
- WebSocket baseline remplaçable/comparable par le futur POC WebTransport sans réécrire le gameplay.
## 9. Cadrage alpha.1 obligatoire
Avant développement lourd :
1. effectuer la migration de nomenclature décrite plus haut ;
2. auditer la stable `0.3.3` et les règles après migration ;
3. réévaluer les études réseau existantes ;
4. vérifier les versions actuelles et contraintes des dépendances réellement envisagées ;
5. décider l'ownership physique des crates/modules ;
6. définir le contrat API minimal et les erreurs ;
7. définir les tests loopback et les gates ;
8. évaluer le besoin éventuel d'un petit demo/CLI uniquement s'il apporte une preuve utile ;
9. créer `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` ;
10. produire un forecast souple jusqu'à stable, avec beta de validation large, RC gelée et release mécanique.
Si le scope dépasse raisonnablement une seule session/version, le scinder dès `alpha.1`.
## 10. Validation et responsabilité
Les audits statiques peuvent être exécutés par le générateur. Les builds, tests et smokes finaux restent côté utilisateur et ne sont jamais déclarés réussis sans sortie réelle.
Dès qu'un fichier Rust/Cargo change :
```bash
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
Les tests restent ciblés par défaut ; `cargo test --workspace --all-targets --all-features` est réservé aux jalons larges/finalisation planifiés.
Toute commande nécessitant un changement de répertoire utilise un sous-shell `(cd ... && ...)`.
## 11. Deltas, historique et changelog
À partir de `0.3.4`, les nouveaux fichiers utilisent uniquement la nouvelle nomenclature :
```text
deltas/0.3.4/alpha.1.md
history/0.3.4/alpha.1.md # créé seulement après validation, par le jalon suivant
```
Un échec de `alpha.1` produit `alpha.1.fix.1`, pas un retour vers l'ancienne convention.
`CHANGELOG.md` reste normalement silencieux avant RC. `ROADMAP.md` reste macroscopique. Aucun ancien delta/history n'est renommé pour homogénéiser l'apparence.
## 12. Forecast initial non contraignant
Ce forecast est une **hypothèse de travail initiale**, pas un contrat de numérotation. Il doit être relu et réécrit dans le plan `0.3.4` pendant `alpha.1` à partir des constats réels de la baseline, des dépendances actuelles, des tests possibles et du sizing obtenu.
Règles de lecture du forecast :
- les jalons ci-dessous donnent un **ordre logique probable**, pas une obligation de produire exactement ce nombre de prereleases ;
- une tranche peut être fusionnée avec la suivante si son contenu devient trivial et reste validable comme une unité cohérente ;
- une tranche doit être scindée si elle dépasse le budget habituel de 1530 minutes, mélange plusieurs responsabilités indépendantes ou nécessite des validations hétérogènes ;
- tout défaut découvert après livraison d'un jalon reste dans `<jalon>.fix.N` tant que son objectif fonctionnel n'est pas rouvert ;
- aucune tranche ne doit être créée uniquement pour respecter la séquence ci-dessous ;
- si l'audit `alpha.1` montre qu'une partie relève plutôt de `0.3.5` ou d'une nouvelle version, la reporter avant développement lourd ;
- le plan actif sous `docs/plans/` devient l'autorité prévisionnelle après `alpha.1` et peut diverger de ce forecast sans réécrire ce prompt historique.
### `0.3.4-alpha.1` — migration de gouvernance + audit + contrat transport
Objectif probable : fermer tout le cadrage avant de figer une API réseau.
Travail attendu :
- appliquer la nouvelle nomenclature aux **règles prospectives uniquement** ;
- adapter les audits qui reconnaissent les prereleases sans modifier l'historique `0.1.x``0.3.3` ;
- vérifier que la baseline stable `0.3.3` est propre ;
- relire les études réseau et confronter leurs hypothèses au code actuel ;
- vérifier les versions actuelles de Tokio, `tokio-tungstenite`, `tungstenite`, TLS éventuel et autres dépendances réellement nécessaires ;
- décider si l'API durable vit dans une crate dédiée, une crate existante ou plusieurs crates ;
- définir les responsabilités client/server communes sans créer un framework abstrait prématuré ;
- définir le modèle d'erreur, fermeture, cancellation, timeout, backpressure et limites ;
- décider le codec de **preuve** initial sans le présenter comme protocole gameplay final ;
- définir la matrice de tests et les gates de chaque tranche ;
- créer `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` avec un forecast révisé jusqu'à stable.
Livrables probables : gouvernance migrée, étude/résultats d'audit si nécessaire, plan actif, ownership décidé, premier contrat API suffisamment précis pour démarrer l'implémentation suivante.
Gate de sortie : aucune ambiguïté majeure sur l'ownership ou le sens des dépendances ; scope `0.3.4` dimensionné pour tenir dans la session ; validations futures identifiées ; aucune dépendance réseau introduite dans les crates gameplay.
### `0.3.4-alpha.2` — API de transport async minimale
Objectif probable : introduire la frontière transport indépendamment de WebSocket.
Travail candidat :
- types de connexion/endpoint strictement nécessaires ;
- contrat async send/receive ou équivalent retenu par l'audit ;
- fermeture locale/distante explicite ;
- cancellation propre ;
- erreurs typées ;
- taille/limites élémentaires des messages ;
- tests unitaires du contrat sans réseau réel lorsque possible ;
- façade `lib.rs` et documentation publique conformes aux règles du dépôt.
À éviter : codec métier complet, session gameplay, heartbeat produit définitif, simulation authoritative ou abstraction multi-transport sophistiquée.
Gate de sortie : API consommable par un backend WebSocket futur et testable indépendamment de celui-ci ; aucun couplage direct au gameplay.
### `0.3.4-alpha.3` — backend WebSocket `tokio-tungstenite`
Objectif probable : fournir la première implémentation concrète de la frontière transport.
Travail candidat :
- connect/listen WebSocket selon l'ownership retenu ;
- mapping frames ↔ messages du transport ;
- fermeture WebSocket propre ;
- propagation des erreurs ;
- timeouts/cancellation minimum nécessaires ;
- tracing du domaine transport ;
- test loopback localhost déterministe client ↔ serveur ;
- absence de dépendance à Axum si elle n'apporte rien à la preuve transport.
Gate de sortie : round-trip local reproductible, fermeture des deux côtés vérifiée, aucune tâche async orpheline connue, erreurs observables.
### `0.3.4-alpha.4` — robustesse : limites, backpressure et lifecycle
Cette tranche est **conditionnelle** : la fusion avec `alpha.3` est préférable si les mécanismes restent petits et cohérents.
Travail candidat :
- bornes de taille et files internes ;
- comportement explicite en cas de consommateur lent ;
- timeout de connexion/lecture/écriture uniquement là où il est justifié ;
- comportement sur fermeture distante, connexion interrompue et cancellation ;
- vérification qu'aucune boucle ne spin ou ne masque une erreur ;
- tests négatifs ciblés ;
- métriques/tracing minimaux permettant de diagnostiquer le transport sans mélanger session/sync/simulation.
Gate de sortie : les principaux modes d'échec du transport sont déterministes, bornés et testés.
### `0.3.4-alpha.5` — intégration consommateur/démo minimale, si utile
Cette tranche n'existe que si une preuve supplémentaire apporte une valeur réelle après `alpha.3/alpha.4`.
Candidats acceptables :
- petit demo/CLI client-server ;
- intégration à un launcher de POC sans logique gameplay réseau ;
- harness de test permettant de mesurer connexion, échange et fermeture.
Ne pas créer d'application ou de crate uniquement pour « montrer » l'API si les tests loopback prouvent déjà tout le contrat utile.
Gate de sortie : preuve reproductible du chemin public du transport, sans introduire la session gameplay.
### `0.3.4-alpha.N` — consolidation de développement
Avant beta, réserver explicitement une tranche de consolidation si les alpha précédentes ont réellement introduit plusieurs composants.
Cette tranche peut couvrir :
- réconciliation docs/API/README/USAGE réellement nécessaires ;
- suppression de duplication ou dette née pendant les alpha, uniquement si bornée ;
- audit du graphe de dépendances ;
- vérification que `transport -> wire codec -> session -> sync -> simulation` reste respecté ;
- confirmation de ce qui est volontairement reporté à `0.3.5` ou `0.4.x` ;
- préparation d'une gate beta large et reproductible.
Elle peut être fusionnée avec la dernière alpha fonctionnelle si aucune consolidation autonome n'est nécessaire.
### `0.3.4-beta.1` — validation large
Objectif probable : valider le produit de `0.3.4` comme baseline transport complète, pas ajouter des fonctionnalités.
Gate candidate :
- audits dépôt applicables ;
- format/check/Clippy workspace ;
- tests ciblés des nouvelles crates/modules ;
- test workspace complet si le plan le confirme ;
- tests loopback client/server ;
- tests de fermeture/cancellation/backpressure retenus ;
- éventuel demo/CLI reproductible si créé ;
- vérification du graphe de dépendances ;
- confirmation qu'aucune crate gameplay ne dépend du backend WebSocket ;
- documentation opérateur/API cohérente avec le comportement réel.
Un défaut trouvé ici produit `beta.1.fix.N` tant que le périmètre reste fermé. Une capacité réseau manquante importante impose de revenir à une alpha suivante au lieu de maquiller une nouvelle fonctionnalité en fix beta.
### `0.3.4-rc.1` — candidate gelée
Objectif probable : candidate de publication sans nouveau comportement.
Travail attendu :
- scope fonctionnel gelé ;
- revalidation des gates importantes de beta ;
- consolidation `CHANGELOG.md` ;
- état macro `ROADMAP.md` cohérent ;
- historique applicable ;
- documentation finale de l'API transport et de la baseline WebSocket ;
- préparation/vérification du prompt `0.3.5` WebTransport/QUIC avec transmission précise de la baseline WebSocket à comparer ;
- aucune optimisation ou abstraction nouvelle sans défaut de release démontré.
Les correctifs strictement nécessaires restent sous `rc.1.fix.N`. Toute réouverture fonctionnelle revient à une phase adaptée avant une nouvelle RC.
### `0.3.4` — promotion stable mécanique
La stable doit normalement se limiter à :
- version stable ;
- historique RC ;
- changelog stable ;
- clôture du plan ;
- statut roadmap si requis ;
- delta/release final ;
- ajustements strictement mécaniques du prompt suivant si une référence de baseline devient certaine.
Aucune nouvelle API, dépendance ou décision architecturale ne doit apparaître pour la première fois dans la stable.
### Branches de redécoupage explicitement admises
Le forecast initial doit être révisé sans hésitation dans les cas suivants :
```text
API transport + backend WebSocket restent très petits
-> fusion possible alpha.2 + alpha.3
backpressure/lifecycle nécessitent un travail autonome
-> conserver alpha.4 ou la scinder
le demo n'apporte aucune preuve supplémentaire
-> supprimer alpha.5
un vrai wire codec réutilisable devient indispensable
-> décider en alpha.1 s'il appartient encore à 0.3.4
ou s'il mérite une version distincte
l'abstraction WebTransport exige déjà des choix QUIC
-> reporter à 0.3.5 ; ne pas polluer 0.3.4
le serveur de jeu/session protocol commence à apparaître
-> arrêter et reporter au scope Uroburas approprié
```
Le critère n'est donc pas « suivre tous les numéros », mais obtenir à la fin de `0.3.4` une frontière de transport claire, un backend WebSocket reproductible et suffisamment robuste pour devenir la baseline comparative de `0.3.5`.
## 13. Condition de fin de session
La session ne vise pas une simple prerelease : elle doit mener `0.3.4` jusqu'à une stable concrète, sauf découverte imposant explicitement un redécoupage de scope vers une version suivante.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# file: scripts/audit_distribution_layout.py # file: scripts/audit_distribution_layout.py
# version: 7 # version: 17
"""Audit the static distribution layout expected by supported POCs.""" """Audit the static distribution layout expected by supported POCs."""
@@ -13,6 +13,13 @@ import sys
FORBIDDEN_PATHS = ( FORBIDDEN_PATHS = (
"builds", "builds",
"Android/gradlew",
"Android/gradlew.bat",
"Android/gradle/wrapper/gradle-wrapper.jar",
"Android/gradle/wrapper/gradle-wrapper.properties",
"Android/game-reflex-poc/src/main/jniLibs",
"Android/game-snake-poc/src/main/jniLibs",
"scripts/build_android_rust.py",
) )
@@ -30,6 +37,23 @@ REQUIRED_PATHS = (
"crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts", "crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts",
"crates/apps/game-reflex-poc-wasm/Cargo.toml", "crates/apps/game-reflex-poc-wasm/Cargo.toml",
"crates/apps/game-snake-poc-wasm/Cargo.toml", "crates/apps/game-snake-poc-wasm/Cargo.toml",
"crates/apps/game-snake-poc-tauri/Cargo.toml",
"crates/apps/game-snake-poc-tauri/README.md",
"crates/apps/game-snake-poc-tauri/USAGE.md",
"crates/apps/game-snake-poc-tauri/package.json",
"crates/apps/game-snake-poc-tauri/tsconfig.json",
"crates/apps/game-snake-poc-tauri/vite.config.ts",
"crates/apps/game-snake-poc-tauri/tauri.conf.json",
"crates/apps/game-snake-poc-tauri/capabilities/default.json",
"crates/apps/game-snake-poc-tauri/icons/icon.png",
"crates/apps/game-snake-poc-tauri/frontend/main.html",
"crates/apps/game-snake-poc-tauri/frontend/css/main.css",
"crates/apps/game-snake-poc-tauri/frontend/ts/assets.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/game.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/input.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/lifecycle.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/main.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/provenance.ts",
"Web/game-snake-poc/package.json", "Web/game-snake-poc/package.json",
"Web/game-snake-poc/tsconfig.json", "Web/game-snake-poc/tsconfig.json",
"Web/game-snake-poc/vite.config.ts", "Web/game-snake-poc/vite.config.ts",
@@ -43,7 +67,8 @@ REQUIRED_PATHS = (
"Web/game-snake-poc/frontend/ts/provenance.ts", "Web/game-snake-poc/frontend/ts/provenance.ts",
"Android/game-reflex-poc/build.gradle", "Android/game-reflex-poc/build.gradle",
"Android/game-snake-poc/build.gradle", "Android/game-snake-poc/build.gradle",
"scripts/build_android_rust.py", "Android/settings.gradle",
"Android/gradle/sasedev-rust-android.gradle",
"scripts/build_reflex_tauri_wasm.py", "scripts/build_reflex_tauri_wasm.py",
"assets/common", "assets/common",
"assets/game-reflex-poc", "assets/game-reflex-poc",
@@ -136,6 +161,225 @@ def audit_snake_web_integration(root: pathlib.Path) -> list[str]:
violations.append(message) violations.append(message)
return violations return violations
def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
"""Validate the playable Snake Tauri Android reference POC boundary."""
violations: list[str] = []
app_root = root / "crates/apps/game-snake-poc-tauri"
cargo_path = app_root / "Cargo.toml"
package_path = app_root / "package.json"
tauri_path = app_root / "tauri.conf.json"
tauri_rust_path = app_root / "src/tauri.rs"
build_script_path = app_root / "build.rs"
html_path = app_root / "frontend/main.html"
main_path = app_root / "frontend/ts/main.ts"
assets_path = app_root / "frontend/ts/assets.ts"
game_path = app_root / "frontend/ts/game.ts"
input_path = app_root / "frontend/ts/input.ts"
provenance_path = app_root / "frontend/ts/provenance.ts"
lifecycle_path = app_root / "frontend/ts/lifecycle.ts"
logging_path = app_root / "frontend/ts/logging.ts"
vite_path = app_root / "vite.config.ts"
wasm_runtime_path = root / "crates/apps/game-snake-poc-wasm/src/runtime.rs"
paths = (
cargo_path,
package_path,
tauri_path,
tauri_rust_path,
build_script_path,
html_path,
main_path,
assets_path,
game_path,
input_path,
provenance_path,
lifecycle_path,
logging_path,
vite_path,
wasm_runtime_path,
)
if not all(path.exists() for path in paths):
return violations
cargo = cargo_path.read_text(encoding="utf-8")
package = package_path.read_text(encoding="utf-8")
tauri = tauri_path.read_text(encoding="utf-8")
tauri_rust = tauri_rust_path.read_text(encoding="utf-8")
build_script = build_script_path.read_text(encoding="utf-8")
html = html_path.read_text(encoding="utf-8")
main = main_path.read_text(encoding="utf-8")
assets = assets_path.read_text(encoding="utf-8")
game = game_path.read_text(encoding="utf-8")
input_module = input_path.read_text(encoding="utf-8")
provenance = provenance_path.read_text(encoding="utf-8")
lifecycle = lifecycle_path.read_text(encoding="utf-8")
logging = logging_path.read_text(encoding="utf-8")
vite = vite_path.read_text(encoding="utf-8")
wasm_runtime = wasm_runtime_path.read_text(encoding="utf-8")
required_fragments = (
(cargo, 'game-logging-lib = { path = "../../common/game-logging-lib" }', "DIST-LAYOUT-021: Snake Tauri host must use shared Rust logging"),
(cargo, 'tauri-plugin-tracing.workspace = true', "DIST-LAYOUT-022: Snake Tauri host must enable the tracing plugin"),
(package, '"wasm:dev": "cargo build -p game-snake-poc-wasm', "DIST-LAYOUT-023: Snake Tauri dev hook must build the shared Snake WASM adapter"),
(package, 'wasm-bindgen ../../../../builds/sasedev-games/', "DIST-LAYOUT-024: Snake Tauri bindings must be generated outside the repository"),
(package, '"dev": "npm run wasm:dev && vite"', "DIST-LAYOUT-036: Snake Tauri dev hook must leave Android generation to Tauri"),
(package, '"build": "npm run wasm:build && tsc && vite build"', "DIST-LAYOUT-037: Snake Tauri build hook must leave Android generation to Tauri"),
(package, '"vite-plugin-static-copy"', "DIST-LAYOUT-043: Snake Tauri package must declare static asset packaging"),
(build_script, "tauri_build::build();", "DIST-LAYOUT-038: Snake Tauri build script must delegate Android integration to tauri-build"),
(tauri, '"script": "npm run dev"', "DIST-LAYOUT-025: Tauri must own the frontend development hook"),
(tauri, '"script": "npm run build"', "DIST-LAYOUT-026: Tauri must own the frontend production hook"),
(tauri_rust, '#[tauri::mobile_entry_point]', "DIST-LAYOUT-027: Snake Tauri run entry point must support Tauri mobile"),
(tauri_rust, 'tauri_plugin_tracing::Builder::new()', "DIST-LAYOUT-028: Snake Tauri runtime must install the tracing plugin"),
(html, '<canvas id="game"', "DIST-LAYOUT-044: Snake Tauri reference POC must expose the Canvas"),
(html, 'data-direction="up"', "DIST-LAYOUT-045: Snake Tauri reference POC must expose touch direction controls"),
(main, 'loadSnakeTauriAssets()', "DIST-LAYOUT-046: Snake Tauri startup must load canonical runtime assets"),
(main, 'startGame(canvas, score, length, provenance, descriptor)', "DIST-LAYOUT-047: Snake Tauri startup must run the shared WASM game through Canvas"),
(main, 'bindInputs(session, buttons)', "DIST-LAYOUT-048: Snake Tauri startup must bind host inputs"),
(main, 'bindTauriLifecycle(session, stage)', "DIST-LAYOUT-059: Snake Tauri startup must bind WebView lifecycle"),
(assets, './common/data/runtime.json', "DIST-LAYOUT-049: Snake Tauri asset probe must load common runtime metadata"),
(assets, './game/data/game.json', "DIST-LAYOUT-050: Snake Tauri asset probe must load Snake metadata"),
(game, 'window.requestAnimationFrame(frame)', "DIST-LAYOUT-051: Snake Tauri Canvas host must schedule its fixed-step session"),
(game, 'game.rectangle_count()', "DIST-LAYOUT-052: Snake Tauri Canvas host must render the shared WASM scene"),
(input_module, '"pointerdown"', "DIST-LAYOUT-053: Snake Tauri input must support pointer/touch controls"),
(input_module, '"keydown"', "DIST-LAYOUT-054: Snake Tauri input must retain keyboard controls when available"),
(provenance, '"touch"', "DIST-LAYOUT-055: Snake Tauri provenance must observe touch input"),
(provenance, '"keyboard-mouse"', "DIST-LAYOUT-056: Snake Tauri provenance must observe keyboard/mouse input"),
(lifecycle, '"visibilitychange"', "DIST-LAYOUT-060: Snake Tauri lifecycle must observe visibility changes"),
(lifecycle, '"pagehide"', "DIST-LAYOUT-061: Snake Tauri lifecycle must observe pagehide"),
(lifecycle, '"pageshow"', "DIST-LAYOUT-062: Snake Tauri lifecycle must observe pageshow"),
(lifecycle, "ResizeObserver", "DIST-LAYOUT-063: Snake Tauri lifecycle must observe stage resize/orientation"),
(game, "pause(): void;", "DIST-LAYOUT-064: Snake Tauri session must expose pause semantics"),
(game, "resume(): void;", "DIST-LAYOUT-065: Snake Tauri session must expose resume semantics"),
(game, "accumulator = 0;", "DIST-LAYOUT-066: Snake Tauri resume/pause must reset accumulated elapsed time"),
(vite, 'dest: "common/data", rename: { stripBase: true }', "DIST-LAYOUT-057: Snake Tauri common assets must retain the common/data namespace"),
(vite, 'dest: "game/data", rename: { stripBase: true }', "DIST-LAYOUT-058: Snake Tauri game assets must retain the game/data namespace"),
(vite, 'host: devHost || "127.0.0.1"', "DIST-LAYOUT-068: Snake Tauri HMR must use a loopback-compatible host for ADB forwarding"),
(vite, 'port: 1436,', "DIST-LAYOUT-069: Snake Tauri Vite WebSocket must share port 1436 with the dev server"),
(vite, 'clientPort: 1436,', "DIST-LAYOUT-070: Snake Tauri HMR client must use forwarded port 1436"),
(logging, '@fltsci/tauri-plugin-tracing', "DIST-LAYOUT-030: Snake Tauri frontend logs must use the tracing bridge"),
(wasm_runtime, '"tauri-webview" => Some(engine_v1_platform_api::RuntimeHost::TauriWebView)', "DIST-LAYOUT-031: Snake WASM adapter must accept the Tauri WebView host"),
(wasm_runtime, '"android" => Some(engine_v1_platform_api::PlatformFamily::Android)', "DIST-LAYOUT-032: Snake WASM adapter must accept Android provenance"),
)
for content, fragment, message in required_fragments:
if fragment not in content:
violations.append(message)
forbidden_fragments = (
(package, "python", "DIST-LAYOUT-033: Snake Tauri build hooks must not use Python"),
(package, "android:prepare", "DIST-LAYOUT-039: Snake Tauri build hooks must not patch generated Android Gradle files"),
(build_script, "gen/android", "DIST-LAYOUT-040: Snake Tauri build script must not mutate generated Android Gradle files"),
(tauri, "python", "DIST-LAYOUT-034: Snake Tauri configuration must not use Python build orchestration"),
(main, "console.", "DIST-LAYOUT-035: Snake Tauri application frontend must not log directly through console.*"),
(main, "onbackbuttonpress", "DIST-LAYOUT-067: Snake Tauri reference POC must leave Android Back to the system while Snake quit policy is Exit"),
(lifecycle, "onbackbuttonpress", "DIST-LAYOUT-067: Snake Tauri reference POC must leave Android Back to the system while Snake quit policy is Exit"),
(vite, "1437", "DIST-LAYOUT-071: Snake Tauri dev/HMR must not require a second unforwarded WebSocket port"),
)
for content, fragment, message in forbidden_fragments:
if fragment in content.lower():
violations.append(message)
return violations
def audit_snake_tauri_generated_android(root: pathlib.Path) -> list[str]:
"""Reject a locally generated Snake Android tree that drifted from Tauri 2.11.x."""
violations: list[str] = []
android_root = root / "crates/apps/game-snake-poc-tauri/gen/android"
if not android_root.exists():
return violations
wrapper_path = android_root / "gradle/wrapper/gradle-wrapper.properties"
build_path = android_root / "build.gradle.kts"
if not wrapper_path.exists() or not build_path.exists():
violations.append(
"DIST-LAYOUT-041: generated Snake Tauri Android tree is incomplete; remove gen/android and rerun cargo tauri android init"
)
return violations
wrapper = wrapper_path.read_text(encoding="utf-8")
build = build_path.read_text(encoding="utf-8")
expected_fragments = (
(wrapper, "gradle-8.14.3-bin.zip", "Gradle 8.14.3"),
(build, 'classpath("com.android.tools.build:gradle:8.11.0")', "Android Gradle Plugin 8.11.0"),
(build, 'classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")', "Kotlin Gradle Plugin 1.9.25"),
)
for content, fragment, label in expected_fragments:
if fragment not in content:
violations.append(
f"DIST-LAYOUT-042: generated Snake Tauri Android tree does not match Tauri 2.11.x {label}; "
"remove gen/android and rerun cargo tauri android init under JDK 17"
)
return violations
def audit_android_native_gradle(root: pathlib.Path) -> list[str]:
"""Validate the minimum Gradle contract and multi-ABI native Rust consumers."""
violations: list[str] = []
settings_path = root / "Android/settings.gradle"
native_script_path = root / "Android/gradle/sasedev-rust-android.gradle"
snake_build_path = root / "Android/game-snake-poc/build.gradle"
reflex_build_path = root / "Android/game-reflex-poc/build.gradle"
required = (settings_path, native_script_path, snake_build_path, reflex_build_path)
if not all(path.exists() for path in required):
return violations
settings = settings_path.read_text(encoding="utf-8")
settings_fragments = (
'import org.gradle.util.GradleVersion',
'GradleVersion.version("9.6.0")',
'GradleVersion.current()',
'currentGradleVersion < minimumGradleVersion',
)
for fragment in settings_fragments:
if fragment not in settings:
violations.append(f"DIST-LAYOUT-072: Android settings missing minimum Gradle gate: {fragment}")
native_script = native_script_path.read_text(encoding="utf-8")
native_fragments = (
'spec.commandLine(cargoCommand)',
'"cargo",',
'"ndk",',
'cargoCommand.add("--release")',
'releaseBuild.set(selectedReleaseBuild)',
'onVariants(selector().all())',
'"--no-default-features",',
'variant.sources.jniLibs.addGeneratedSourceDirectory',
'generated/sasedevNative/',
'libgame_android_entrypoint.so',
'libSDL3.so',
)
for fragment in native_fragments:
if fragment not in native_script:
violations.append(f"DIST-LAYOUT-074: Gradle-owned Rust Android build missing contract: {fragment}")
if "build_android_rust.py" in native_script or "python" in native_script.lower():
violations.append("DIST-LAYOUT-075: Gradle-owned Rust Android build must not delegate orchestration to Python")
consumers = (
(
"Snake",
snake_build_path.read_text(encoding="utf-8"),
'ext.sasedevRustGameFeature = "snake"',
),
(
"Reflex",
reflex_build_path.read_text(encoding="utf-8"),
'ext.sasedevRustGameFeature = "reflex"',
),
)
shared_fragments = (
'ext.sasedevRustAndroidAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]',
'ext.sasedevRustAndroidApi = 21',
'minSdk 21',
"abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'",
'apply from: rootProject.file("gradle/sasedev-rust-android.gradle")',
)
for label, content, feature_fragment in consumers:
for fragment in (feature_fragment, *shared_fragments):
if fragment not in content:
violations.append(f"DIST-LAYOUT-076: {label} Android multi-ABI consumer missing contract: {fragment}")
return violations
def main() -> int: def main() -> int:
"""Validate that every static distribution boundary exists.""" """Validate that every static distribution boundary exists."""
@@ -154,6 +398,9 @@ def main() -> int:
forbidden_present.append(relative) forbidden_present.append(relative)
contract_violations.extend(audit_snake_web_shell(root)) contract_violations.extend(audit_snake_web_shell(root))
contract_violations.extend(audit_snake_web_integration(root)) contract_violations.extend(audit_snake_web_integration(root))
contract_violations.extend(audit_snake_tauri_android_integration(root))
contract_violations.extend(audit_snake_tauri_generated_android(root))
contract_violations.extend(audit_android_native_gradle(root))
if missing: if missing:
for relative in missing: for relative in missing:
print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr) print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# file: scripts/audit_project_workspace_rules.py # file: scripts/audit_project_workspace_rules.py
# version: 6 # version: 7
"""Audit mechanically verifiable games.sasedev workspace boundaries.""" """Audit mechanically verifiable games.sasedev workspace boundaries."""
@@ -15,6 +15,13 @@ import tomllib
SEMVER = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0-pre|1-alpha|2-beta|3-rc)\.[1-9][0-9]*(?:\.fix\.[1-9][0-9]*)?)?$", re.ASCII) SEMVER = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0-pre|1-alpha|2-beta|3-rc)\.[1-9][0-9]*(?:\.fix\.[1-9][0-9]*)?)?$", re.ASCII)
def is_generated_path(path: pathlib.Path, root: pathlib.Path) -> bool:
"""Return whether a path belongs to an ignored generated tree."""
relative = path.relative_to(root)
return "gen" in relative.parts
def main() -> int: def main() -> int:
"""Run project-specific workspace audits.""" """Run project-specific workspace audits."""
@@ -86,10 +93,14 @@ def main() -> int:
errors.append( errors.append(
f"DOC-011: documentary directory entry point must use 000-README.md: {readme_path.relative_to(root).as_posix()}" f"DOC-011: documentary directory entry point must use 000-README.md: {readme_path.relative_to(root).as_posix()}"
) )
forbidden_assets = [path for path in (root / "crates").rglob("assets") if path.is_dir()] forbidden_assets = [
path for path in (root / "crates").rglob("assets") if path.is_dir() and not is_generated_path(path, root)
]
for path in forbidden_assets: for path in forbidden_assets:
errors.append(f"GAME-ASSET-001: assets directory forbidden inside crates: {path.relative_to(root).as_posix()}") errors.append(f"GAME-ASSET-001: assets directory forbidden inside crates: {path.relative_to(root).as_posix()}")
for java_path in sorted(root.rglob("*.java")): for java_path in sorted(root.rglob("*.java")):
if is_generated_path(java_path, root):
continue
if not java_path.is_relative_to(root / "Android"): if not java_path.is_relative_to(root / "Android"):
errors.append(f"GAME-ANDROID-001: Java source outside Android/: {java_path.relative_to(root).as_posix()}") errors.append(f"GAME-ANDROID-001: Java source outside Android/: {java_path.relative_to(root).as_posix()}")

View File

@@ -1,139 +0,0 @@
#!/usr/bin/env python3
# file: scripts/build_android_rust.py
# version: 3
"""Build one Android Rust game entrypoint and stage SDL3 and Rust jniLibs."""
from __future__ import annotations
import argparse
import os
import pathlib
import shutil
import subprocess
import sys
import zipfile
ABI_TO_TRIPLE = {
"arm64-v8a": "aarch64-linux-android",
"armeabi-v7a": "armv7-linux-androideabi",
"x86_64": "x86_64-linux-android",
"x86": "i686-linux-android",
}
GAME_TO_MODULE = {"reflex": "game-reflex-poc", "snake": "game-snake-poc"}
def read_gradle_property(properties_path: pathlib.Path, property_name: str) -> str:
"""Read one required Android Gradle property."""
prefix = f"{property_name}="
for raw_line in properties_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if line.startswith(prefix):
return line.split("=", 1)[1].strip()
raise RuntimeError(f"{property_name} is missing from Android/gradle.properties")
def read_sdl3_aar_name(properties_path: pathlib.Path) -> str:
"""Read the configured SDL3 AAR filename."""
return read_gradle_property(properties_path, "sdl3AarName")
def find_sdl3_member(archive: zipfile.ZipFile, abi: str) -> str:
"""Return the SDL3 shared-library member for one Android ABI."""
candidates = (
f"prefab/modules/SDL3/libs/android.{abi}/libSDL3.so",
f"jni/{abi}/libSDL3.so",
)
members = set(archive.namelist())
for candidate in candidates:
if candidate in members:
return candidate
suffix = f"/libs/android.{abi}/libSDL3.so"
fallback = sorted(member for member in members if member.endswith(suffix))
if len(fallback) == 1:
return fallback[0]
raise RuntimeError(
f"SDL3 shared library for ABI {abi} is missing from the AAR; "
f"expected Prefab member {candidates[0]}"
)
def extract_sdl3_library(
aar_path: pathlib.Path,
abi: str,
link_dir: pathlib.Path,
runtime_dir: pathlib.Path,
) -> pathlib.Path:
"""Extract SDL3 for both Rust linking and APK runtime staging."""
link_dir.mkdir(parents=True, exist_ok=True)
runtime_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(aar_path) as archive:
member = find_sdl3_member(archive, abi)
payload = archive.read(member)
link_target = link_dir / "libSDL3.so"
runtime_target = runtime_dir / "libSDL3.so"
link_target.write_bytes(payload)
runtime_target.write_bytes(payload)
return link_target
def main() -> int:
"""Build the requested game entrypoint for one Android ABI."""
parser = argparse.ArgumentParser()
parser.add_argument("game", choices=sorted(GAME_TO_MODULE))
parser.add_argument("--abi", choices=sorted(ABI_TO_TRIPLE), default="arm64-v8a")
parser.add_argument("--release", action="store_true")
arguments = parser.parse_args()
root = pathlib.Path(__file__).resolve().parent.parent
properties_path = root / "Android" / "gradle.properties"
aar_name = read_sdl3_aar_name(properties_path)
ndk_version = read_gradle_property(properties_path, "androidNdkVersion")
aar_path = root / "Android" / "libs" / aar_name
if not aar_path.is_file():
print(f"missing SDL3 AAR: {aar_path}", file=sys.stderr)
return 2
module = GAME_TO_MODULE[arguments.game]
jni_root = root / "Android" / module / "src" / "main" / "jniLibs"
runtime_dir = jni_root / arguments.abi
link_dir = root / "Android" / "build" / "rust-link" / arguments.abi
try:
extract_sdl3_library(aar_path, arguments.abi, link_dir, runtime_dir)
except (OSError, RuntimeError, zipfile.BadZipFile) as error:
print(str(error), file=sys.stderr)
return 2
environment = os.environ.copy()
android_home = environment.get("ANDROID_HOME")
if not android_home:
print("ANDROID_HOME is required to resolve the project NDK", file=sys.stderr)
return 2
ndk_home = pathlib.Path(android_home) / "ndk" / ndk_version
if not ndk_home.is_dir():
print(f"configured Android NDK is missing: {ndk_home}", file=sys.stderr)
return 2
environment["ANDROID_NDK_HOME"] = str(ndk_home)
rustflags = environment.get("RUSTFLAGS", "").strip()
link_flag = f"-L native={link_dir}"
environment["RUSTFLAGS"] = f"{rustflags} {link_flag}".strip()
environment["CARGO_NDK_PLATFORM"] = "21"
command = [
"cargo",
"ndk",
"-t",
arguments.abi,
"-o",
str(jni_root),
"build",
"-p",
"game-android-entrypoint",
"--no-default-features",
"--features",
arguments.game,
]
if arguments.release:
command.append("--release")
completed = subprocess.run(command, cwd=root, env=environment, check=False)
if completed.returncode != 0:
return completed.returncode
expected = runtime_dir / "libgame_android_entrypoint.so"
if not expected.is_file():
print(f"missing Rust Android library after cargo-ndk build: {expected}", file=sys.stderr)
return 3
return 0
if __name__ == "__main__":
raise SystemExit(main())