Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ad980a06b | |||
| 0584b01de3 | |||
| 01df07d6af | |||
| 58ecd667b0 | |||
| 19f31082ea | |||
| b605874bea | |||
| 5854ac3d07 | |||
| f770f99a4a | |||
| 76a955bd3d | |||
| e5a4d7fe44 | |||
| d8e7951a04 | |||
| c806490212 | |||
| a36b666d22 | |||
| 4f677dbc77 | |||
| e4b657d0d3 | |||
| 1835464bb3 | |||
| bded27c944 | |||
| 51371c1b24 | |||
| ba718ce4ec | |||
| 778da67e22 | |||
| a9620ef84c | |||
| 45c31d4721 | |||
| 54121215b4 | |||
| fca477eb23 | |||
| ff1ec09d68 | |||
| 3e1153eb8c | |||
| 7f0635ec9d | |||
| 86b8af3b61 | |||
| 023825b89b | |||
| f433e99a12 | |||
| c4b68b8a60 | |||
| c7ccf57723 | |||
| a6a59222d6 | |||
| 6f37fea9ed | |||
| d16648c34c | |||
| 54079accb5 |
@@ -1,9 +1,9 @@
|
||||
<!-- file: Android/README.md -->
|
||||
<!-- version: 8 -->
|
||||
<!-- version: 14 -->
|
||||
|
||||
# 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
|
||||
|
||||
@@ -20,23 +20,47 @@ Chaque module `game-*` est une application Android indépendante ; il dépend du
|
||||
|
||||
## 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` ;
|
||||
- Gradle `9.6.0` attendu par AGP 9.4 ;
|
||||
- JDK `17` ;
|
||||
- Gradle `>= 9.6.0`, résolu depuis l'environnement de développement ;
|
||||
- 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` ;
|
||||
- `targetSdk 36` ;
|
||||
- `minSdk 21` ;
|
||||
- NDK `28.2.13676358` (`r28c`) ;
|
||||
- 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
|
||||
|
||||
L'archive n'est pas vendorizée dans le dépôt.
|
||||
|
||||
Placer :
|
||||
L'archive n'est pas vendorizée dans le dépôt. Placer :
|
||||
|
||||
```text
|
||||
SDL3-3.4.16.aar
|
||||
@@ -48,32 +72,48 @@ dans :
|
||||
Android/libs/
|
||||
```
|
||||
|
||||
Le module `common` consomme cet artefact et `SaseGameActivity` étend désormais `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.
|
||||
Le module `common` compile contre cet artefact et `SaseGameActivity` étend `org.libsdl.app.SDLActivity`.
|
||||
|
||||
## 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
|
||||
rustup target add aarch64-linux-android
|
||||
cargo install cargo-ndk
|
||||
python3 ../scripts/build_android_rust.py reflex
|
||||
python3 ../scripts/build_android_rust.py snake
|
||||
```text
|
||||
arm64-v8a
|
||||
armeabi-v7a
|
||||
x86_64
|
||||
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
|
||||
|
||||
À 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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// file: Android/game-reflex-poc/build.gradle
|
||||
// version: 45
|
||||
// version: 47
|
||||
|
||||
plugins {
|
||||
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 androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
||||
|
||||
@@ -19,6 +23,10 @@ android {
|
||||
targetSdk 36
|
||||
versionCode 2
|
||||
versionName '0.2.0'
|
||||
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -34,3 +42,4 @@ dependencies {
|
||||
|
||||
ext.sasedevGameAssetDirectory = "game-reflex-poc"
|
||||
apply from: rootProject.file("gradle/sasedev-assets.gradle")
|
||||
apply from: rootProject.file("gradle/sasedev-rust-android.gradle")
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// file: Android/game-snake-poc/build.gradle
|
||||
// version: 45
|
||||
// version: 48
|
||||
|
||||
plugins {
|
||||
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 androidNdkVersion = providers.gradleProperty("androidNdkVersion").get()
|
||||
|
||||
@@ -19,6 +23,10 @@ android {
|
||||
targetSdk 36
|
||||
versionCode 2
|
||||
versionName '0.2.0'
|
||||
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -34,3 +42,4 @@ dependencies {
|
||||
|
||||
ext.sasedevGameAssetDirectory = "game-snake-poc"
|
||||
apply from: rootProject.file("gradle/sasedev-assets.gradle")
|
||||
apply from: rootProject.file("gradle/sasedev-rust-android.gradle")
|
||||
|
||||
204
Android/gradle/sasedev-rust-android.gradle
Normal file
204
Android/gradle/sasedev-rust-android.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// file: Android/settings.gradle
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
import org.gradle.util.GradleVersion
|
||||
|
||||
pluginManagement {
|
||||
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 {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,8 +1,71 @@
|
||||
<!-- file: CHANGELOG.md -->
|
||||
<!-- version: 12 -->
|
||||
<!-- version: 18 -->
|
||||
|
||||
# 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 d’un 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 d’extraction Web/Tauri supplémentaire et absence d’AAB 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 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.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 ;
|
||||
- validation du shell Bootstrap 5/Bootswatch + SimpleBar, du Canvas au ratio logique `12 × 20`, des contrôles clavier/pointer, du lifecycle navigateur, des assets et de la provenance runtime ;
|
||||
- validation de la non-régression Desktop SDL3 et des builds release Desktop et Web/WASM ;
|
||||
- promotion mécanique de `0.3.0-3-rc.1` après gate workspace complète, 34 tests réussis et absence de défaut nécessitant un correctif RC ;
|
||||
- préparation de `0.3.1` pour le second host Snake Tauri Android, sans ouvrir ce scope dans la release `0.3.0`.
|
||||
|
||||
Les détails des phases `pre`, `beta`, `rc` et de leurs correctifs restent dans `deltas/0.3.0/` et `history/0.3.0/`.
|
||||
|
||||
## 0.3.0-3-rc.1 — 2026-09-20
|
||||
|
||||
- gel fonctionnel du premier POC Snake Web direct après validation beta et consolidation documentaire ;
|
||||
- gameplay Snake conservé dans `game-snake-poc`, avec adapter `game-snake-poc-wasm` dédié et host navigateur Vite/TypeScript séparé ;
|
||||
- shell Web responsive fondé sur Bootstrap 5/Bootswatch, SimpleBar et Canvas au ratio logique du jeu, avec clavier et contrôles pointer/tactiles ;
|
||||
- lifecycle navigateur fixed-step sans rattrapage massif, resize `devicePixelRatio`, assets `common/` + `game/`, logging frontend structuré et provenance `Web / Browser / Wasm` ;
|
||||
- runner Desktop SDL3 maintenu comme témoin de non-régression et builds release Desktop/Web déjà exercés pendant la beta ;
|
||||
- prompt `0.3.1` préparé pour le second host Snake Tauri Android, avec workflow de session, commandes et validations consolidés.
|
||||
|
||||
La RC n'ouvre aucun nouveau scope. Les détails des phases `pre`, `beta` et de leurs correctifs restent dans `deltas/0.3.0/` et `history/0.3.0/`.
|
||||
|
||||
## 0.2.0 — conception modulaire, POC et Uroburas
|
||||
|
||||
- Gouvernance documentaire renforcée : catégories `ideas/studies/architecture/rules`, immutabilité des deltas, conventions de statuts et workflow de session/version.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# file: Cargo.toml
|
||||
# version: 57
|
||||
# version: 85
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
@@ -16,10 +16,12 @@ members = [
|
||||
"crates/apps/game-android-entrypoint",
|
||||
"crates/apps/game-reflex-poc-tauri",
|
||||
"crates/apps/game-reflex-poc-wasm",
|
||||
"crates/apps/game-snake-poc-wasm",
|
||||
"crates/apps/game-snake-poc-tauri",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.0"
|
||||
version = "0.3.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/games"
|
||||
|
||||
10
README.md
10
README.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: README.md -->
|
||||
<!-- version: 25 -->
|
||||
<!-- version: 53 -->
|
||||
|
||||
# games.sasedev
|
||||
|
||||
@@ -14,6 +14,8 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
|
||||
- assets hors des crates sous `assets/` ;
|
||||
- `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 ;
|
||||
- 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 ;
|
||||
- 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 ;
|
||||
- documentation sous `docs/` ;
|
||||
@@ -23,9 +25,11 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
|
||||
|
||||
## Baseline
|
||||
|
||||
Version stable de référence : `0.1.0`.
|
||||
Version stable de référence : `0.3.3`.
|
||||
|
||||
Version candidate en cours de conception : `0.2.0`.
|
||||
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.
|
||||
|
||||
|
||||
30
ROADMAP.md
30
ROADMAP.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 19 -->
|
||||
<!-- version: 26 -->
|
||||
|
||||
# Roadmap
|
||||
|
||||
@@ -26,11 +26,31 @@ Le détail historique des prereleases `0.1.0-*` reste dans `deltas/0.1.0/` et `h
|
||||
|
||||
- (x) `0.2.0` — fixer la gouvernance documentaire, la nomenclature et le cycle de maturation des idées et décisions.
|
||||
- (x) `0.2.0` — inventorier et réserver les capabilities déjà justifiées sans les implémenter prématurément.
|
||||
- ( ) `0.2.0` — définir les couches, les dépendances autorisées et la frontière entre kernel, capability, plateforme, provider, système de jeu et service.
|
||||
- ( ) `0.2.0` — établir la matrice plateformes et les archétypes de jeux utilisés pour challenger l'architecture.
|
||||
- ( ) `0.2.0` — définir le modèle de composition statique, le manifest produit/jeu et l'architecture cible des crates.
|
||||
- ( ) `0.2.0` — définir la trajectoire d'implémentation progressive des versions suivantes seulement après revue de la conception.
|
||||
- (x) `0.2.0` — définir les couches, les dépendances autorisées et la frontière entre kernel, capability, plateforme, provider, système de jeu et service.
|
||||
- (x) `0.2.0` — établir la matrice plateformes et les archétypes de jeux utilisés pour challenger l'architecture.
|
||||
- (x) `0.2.0` — retenir la composition Rust statique comme direction de référence.
|
||||
- (d) `0.2.0` — figer un manifest produit/jeu et l'architecture physique définitive des crates → target TBD après observation des POC `0.3.x`.
|
||||
- (x) `0.2.0` — définir la trajectoire d'implémentation progressive `0.3.x` POC puis `0.4.x` Uroburas.
|
||||
- (x) `0.2.0` — consolider les études acceptées en architecture/règles durables, notamment ownership, réseau, Uroburas et POC, puis fermer les points encore candidats.
|
||||
- (x) `0.2.0` — formaliser la stratégie réseau : Actix Web pour le Web/API, asset delivery auto-hébergé H2/H3, WebSocket baseline et WebTransport/QUIC candidat.
|
||||
- (x) `0.2.0` — préparer une trajectoire `0.3.x` dédiée aux POC plateforme/réseau avec Snake comme jeu-sonde.
|
||||
- (x) `0.2.0` — préparer la trajectoire `0.4.x` Uroburas Mode 1 et le prompt de session associé après gel de la conception.
|
||||
|
||||
## 0.3.0 — Baseline Snake + premier POC Web direct
|
||||
|
||||
- (x) `0.3.0` — nettoyer la baseline Snake sans introduire de logique Uroburas et maintenir le runner Desktop SDL3 fonctionnel.
|
||||
- (x) `0.3.0` — produire une adaptation WASM dédiée à Snake sans déplacer le gameplay hors de `game-snake-poc`.
|
||||
- (x) `0.3.0` — réaliser un POC navigateur direct Snake couvrant clavier, boutons directionnels tactiles, resize, lifecycle, logging, assets et provenance runtime.
|
||||
- (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 qu’un second consommateur ne justifie pas une extraction commune.
|
||||
|
||||
## Série 0.3.x — trajectoire actuelle après 0.3.1
|
||||
|
||||
- (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.
|
||||
- (d) `0.3.2` — Tauri Desktop + Snake est différé tant qu’une solution de monétisation desktop/WebView, notamment vidéo récompensée, n’est pas démontrée techniquement et contractuellement ; ne pas créer cette distribution seulement pour réutiliser le host Web.
|
||||
- (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.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`.
|
||||
|
||||
La numérotation `0.3.1+` reste révisable à partir des résultats réels ; les lignes ci-dessus décrivent le planning actuel, pas une obligation de créer des versions artificielles.
|
||||
|
||||
7
RULES.md
7
RULES.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: RULES.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# Index normatif games.sasedev
|
||||
|
||||
@@ -17,8 +17,9 @@ Les règles détaillées sont maintenues sous `docs/rules/` et sont cumulatives
|
||||
6. [`docs/rules/VERSION_WORKFLOW.md`](docs/rules/VERSION_WORKFLOW.md) — SemVer, niveaux de maturité, fixes, deltas et livraisons ;
|
||||
7. [`docs/rules/RULES_COMMANDS.md`](docs/rules/RULES_COMMANDS.md) — politique d’exécution des commandes Cargo, audits, runners, Android, Web et Git ;
|
||||
8. [`docs/rules/RULES_VALIDATION_MATRIX.md`](docs/rules/RULES_VALIDATION_MATRIX.md) — matrice évolutive des commandes, dépendances de validation et politiques de nettoyage ;
|
||||
9. [`docs/rules/RULES_SESSION_PLANNING.md`](docs/rules/RULES_SESSION_PLANNING.md) — cadrage `pre.1`, dimensionnement des sessions et contrat des prompts de reprise ;
|
||||
10. [`docs/rules/RULES_SERVER_HOSTING.md`](docs/rules/RULES_SERVER_HOSTING.md) — contraintes durables de portabilité et préférence d'auto-hébergement.
|
||||
9. [`docs/rules/RULES_SESSION_PLANNING.md`](docs/rules/RULES_SESSION_PLANNING.md) — cadrage `pre.1`, dimensionnement des sessions et cycle de transmission ;
|
||||
10. [`docs/rules/PROMPT_STRUCTURE.md`](docs/rules/PROMPT_STRUCTURE.md) — structure minimale des prompts de reprise et rappels de workflow obligatoires ;
|
||||
11. [`docs/rules/RULES_SERVER_HOSTING.md`](docs/rules/RULES_SERVER_HOSTING.md) — contraintes durables de portabilité et préférence d'auto-hébergement.
|
||||
|
||||
## Hiérarchie
|
||||
|
||||
|
||||
118
Web/game-snake-poc/frontend/main.html
Normal file
118
Web/game-snake-poc/frontend/main.html
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- file: Web/game-snake-poc/frontend/main.html -->
|
||||
<!-- version: 4 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport">
|
||||
<meta content="#593196" name="theme-color">
|
||||
<link rel="stylesheet" href="sass/main.scss">
|
||||
<title>Snake POC — Web direct</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="app-header">
|
||||
<nav class="navbar h-100 py-0 bg-light text-dark">
|
||||
<div class="container-fluid px-3 px-md-4 flex-nowrap">
|
||||
<div class="navbar-brand d-flex align-items-center me-3 flex-nowrap min-w-0">
|
||||
<span class="app-logo d-inline-flex align-items-center justify-content-center text-primary" aria-hidden="true">
|
||||
<i class="fa-solid fa-gamepad"></i>
|
||||
</span>
|
||||
<span class="ps-2 fs-4 fw-semibold text-primary text-nowrap">Snake POC</span>
|
||||
<span class="mx-2 fs-5 text-body-secondary d-none d-sm-inline" aria-hidden="true">—</span>
|
||||
<span class="fs-5 text-body text-nowrap d-none d-sm-inline">Web direct</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||
<span class="badge text-bg-light border text-dark d-none d-md-inline">Rust/WASM</span>
|
||||
<span id="runtime-status" class="badge text-bg-secondary" role="status" aria-live="polite">Initialisation…</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="app-main container-fluid p-0">
|
||||
<div class="row g-0 h-100 flex-nowrap">
|
||||
<section class="col app-content app-scrollable h-100" data-simplebar>
|
||||
<div class="container-fluid px-3 px-md-4 py-3 py-md-4">
|
||||
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
||||
<div class="card-body p-3 p-md-4">
|
||||
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-3">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">Snake</h1>
|
||||
<p class="text-body-secondary mb-0">Gameplay Rust portable, adapter WebAssembly et rendu Canvas piloté par TypeScript.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap" aria-label="État de la partie">
|
||||
<output id="score" class="badge text-bg-primary">Score : 0</output>
|
||||
<output id="length" class="badge text-bg-info">Longueur : 0</output>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 g-lg-4 align-items-start">
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="card border-primary-subtle shadow-sm">
|
||||
<div class="card-body p-2 p-sm-3">
|
||||
<div class="game-stage">
|
||||
<canvas id="game" width="640" height="640" tabindex="0" aria-label="Zone de jeu Snake"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="card shadow-sm app-controls-card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between gap-2">
|
||||
<span class="fw-semibold"><i class="fa-solid fa-keyboard me-2" aria-hidden="true"></i>Contrôles</span>
|
||||
<span class="small text-body-secondary">clavier / tactile</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="small text-body-secondary">Utilisez les flèches, WASD, ZQSD ou les boutons directionnels.</p>
|
||||
<div class="direction-pad mx-auto" aria-label="Contrôles directionnels tactiles">
|
||||
<button class="btn btn-outline-primary direction-up" type="button" data-direction="up" aria-label="Haut">
|
||||
<i class="fa-solid fa-arrow-up" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-primary direction-left" type="button" data-direction="left" aria-label="Gauche">
|
||||
<i class="fa-solid fa-arrow-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-primary direction-down" type="button" data-direction="down" aria-label="Bas">
|
||||
<i class="fa-solid fa-arrow-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-primary direction-right" type="button" data-direction="right" aria-label="Droite">
|
||||
<i class="fa-solid fa-arrow-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<dl class="runtime-details small border-top pt-3 mt-3 mb-0">
|
||||
<div class="d-flex justify-content-between gap-3">
|
||||
<dt class="text-body-secondary fw-normal">Provenance</dt>
|
||||
<dd class="mb-2 text-end"><output id="runtime-provenance">web / browser / wasm / unknown / unknown</output></dd>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between gap-3">
|
||||
<dt class="text-body-secondary fw-normal">Assets</dt>
|
||||
<dd class="mb-0 text-end"><output id="asset-status">chargement…</output></dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="alert alert-info small mt-3 mb-0" role="note">
|
||||
Les entrées ne font pas avancer la simulation : elles sont consommées au prochain <code>tick()</code> WASM.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="startup-error" class="alert alert-danger mt-3 mb-0" role="alert" hidden></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="app-footer bg-dark text-light">
|
||||
<div class="container h-100 d-flex align-items-center justify-content-center">
|
||||
<small>© 2026 SASEDEV</small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="ts/main.ts" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
165
Web/game-snake-poc/frontend/sass/_app.scss
Normal file
165
Web/game-snake-poc/frontend/sass/_app.scss
Normal file
@@ -0,0 +1,165 @@
|
||||
// file: Web/game-snake-poc/frontend/sass/_app.scss
|
||||
// version: 4
|
||||
|
||||
$app-header-height: 72px;
|
||||
$app-footer-height: 42px;
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: $gray-100;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
position: fixed;
|
||||
inset: 0 0 auto;
|
||||
height: $app-header-height;
|
||||
z-index: 1020;
|
||||
border-bottom: 2px solid rgba($primary, 0.3);
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
position: fixed;
|
||||
inset: auto 0 0;
|
||||
height: $app-footer-height;
|
||||
z-index: 1020;
|
||||
border-top: 2px solid rgba($primary, 0.3);
|
||||
}
|
||||
|
||||
.app-main {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100vh - $app-header-height - $app-footer-height);
|
||||
margin-top: $app-header-height;
|
||||
margin-bottom: $app-footer-height;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-main > .row {
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-scrollable {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.app-shell-card {
|
||||
width: 100%;
|
||||
max-width: 1180px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.game-stage {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
margin-inline: auto;
|
||||
aspect-ratio: 3 / 5;
|
||||
overflow: hidden;
|
||||
background: $black;
|
||||
border: 1px solid rgba($primary, 0.35);
|
||||
}
|
||||
|
||||
#game {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#game:focus-visible {
|
||||
box-shadow: inset 0 0 0 3px rgba($primary, 0.85);
|
||||
}
|
||||
|
||||
.app-controls-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.runtime-details dd {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.direction-pad {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(64px, 88px));
|
||||
grid-template-rows: repeat(2, minmax(56px, 72px));
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.direction-pad .btn {
|
||||
touch-action: manipulation;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
$mobile-header-height: 64px;
|
||||
|
||||
.app-header {
|
||||
height: $mobile-header-height;
|
||||
}
|
||||
|
||||
.app-main {
|
||||
height: calc(100vh - $mobile-header-height - $app-footer-height);
|
||||
margin-top: $mobile-header-height;
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.direction-pad {
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(3, minmax(72px, 1fr));
|
||||
}
|
||||
}
|
||||
160
Web/game-snake-poc/frontend/sass/_bootswatch.scss
Normal file
160
Web/game-snake-poc/frontend/sass/_bootswatch.scss
Normal file
@@ -0,0 +1,160 @@
|
||||
// file: Web/game-snake-poc/frontend/sass/_bootswatch.scss
|
||||
// version: 1
|
||||
|
||||
// Pulse 5.3.8
|
||||
// Bootswatch
|
||||
|
||||
|
||||
// Variables
|
||||
|
||||
// Buttons
|
||||
|
||||
.btn {
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:active:focus,
|
||||
&.active:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&-secondary {
|
||||
color: $gray-900;
|
||||
background-color: $white;
|
||||
border-color: #ccc;
|
||||
|
||||
&:hover {
|
||||
color: $gray-900;
|
||||
background-color: $gray-300;
|
||||
border-color: $gray-500;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: tint-color($gray-900, 5%);
|
||||
background-color: $white;
|
||||
border-color: tint-color(#ccc, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
&-warning {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&-primary:focus {
|
||||
box-shadow: 0 0 5px tint-color($primary, 10%);
|
||||
}
|
||||
|
||||
&-secondary:focus {
|
||||
box-shadow: 0 0 5px $gray-400;
|
||||
}
|
||||
|
||||
&-success:focus {
|
||||
box-shadow: 0 0 5px tint-color($success, 10%);
|
||||
}
|
||||
|
||||
&-info:focus {
|
||||
box-shadow: 0 0 5px tint-color($info, 10%);
|
||||
}
|
||||
|
||||
&-warning:focus {
|
||||
box-shadow: 0 0 5px tint-color($warning, 10%);
|
||||
}
|
||||
|
||||
&-danger:focus {
|
||||
box-shadow: 0 0 5px tint-color($danger, 10%);
|
||||
}
|
||||
|
||||
&.disabled:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Tables
|
||||
|
||||
.table .thead-dark th {
|
||||
background-color: $secondary;
|
||||
border-color: $table-border-color;
|
||||
}
|
||||
|
||||
.table-primary,
|
||||
.table-secondary,
|
||||
.table-success,
|
||||
.table-warning,
|
||||
.table-danger,
|
||||
.table-info,
|
||||
.table-light {
|
||||
--#{$prefix}table-color: #{$body-color};
|
||||
}
|
||||
|
||||
// Forms
|
||||
|
||||
.form-control:focus {
|
||||
box-shadow: 0 0 5px rgba(100, 65, 164, .4);
|
||||
}
|
||||
|
||||
// Navs
|
||||
|
||||
.nav-tabs {
|
||||
|
||||
.nav-link,
|
||||
.nav-link.active {
|
||||
border-width: 0 0 1px;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.active,
|
||||
.nav-link.active:hover,
|
||||
.nav-link.active:focus {
|
||||
border-bottom: 1px solid $primary;
|
||||
}
|
||||
|
||||
.nav-item+.nav-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
&-item.active {
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
// Indicators
|
||||
|
||||
.badge {
|
||||
&.bg-light {
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bars
|
||||
|
||||
.progress {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
// Containers
|
||||
|
||||
.list-group {
|
||||
&-item {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
|
||||
&.active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 700;
|
||||
|
||||
&:hover {
|
||||
background-color: $list-group-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled:hover {
|
||||
color: $list-group-disabled-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Web/game-snake-poc/frontend/sass/_fontawesome.scss
Normal file
19
Web/game-snake-poc/frontend/sass/_fontawesome.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
// file: Web/game-snake-poc/frontend/sass/_fontawesome.scss
|
||||
// version: 1
|
||||
|
||||
//@use '@fortawesome/fontawesome-free/scss/variables' with (
|
||||
// // customizing $font-path - make sure it points to where your webfonts are stored in your project
|
||||
// $font-path: '../webfonts',
|
||||
//);
|
||||
@use '@fortawesome/fontawesome-free/scss/variables' with (
|
||||
// use fonts from @fortawesome/fontawesome-free
|
||||
$font-path: '@fortawesome/fontawesome-free/webfonts',
|
||||
);
|
||||
// load Font Awesome core
|
||||
@use '@fortawesome/fontawesome-free/scss/fontawesome';
|
||||
|
||||
// load and make available Font Awesome helpers (mixins, functions, and variables)
|
||||
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
|
||||
@use '@fortawesome/fontawesome-free/scss/brands' as fa-brands;
|
||||
@use '@fortawesome/fontawesome-free/scss/regular' as fa-regular;
|
||||
@use '@fortawesome/fontawesome-free/scss/solid' as fa-solid;
|
||||
248
Web/game-snake-poc/frontend/sass/_simplebar.scss
Normal file
248
Web/game-snake-poc/frontend/sass/_simplebar.scss
Normal file
@@ -0,0 +1,248 @@
|
||||
// file: Web/game-snake-poc/frontend/sass/_simplebar.scss
|
||||
// version: 1
|
||||
|
||||
/* Rtl support */
|
||||
[data-simplebar] {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.simplebar-wrapper {
|
||||
overflow: hidden;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
max-width: inherit;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
.simplebar-mask {
|
||||
direction: inherit;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
// z-index: 0;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.simplebar-offset {
|
||||
direction: inherit !important;
|
||||
box-sizing: inherit !important;
|
||||
resize: none !important;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.simplebar-content-wrapper {
|
||||
direction: inherit;
|
||||
box-sizing: border-box !important;
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-hide-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
position: fixed;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.simplebar-content {
|
||||
&:before {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-placeholder {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.simplebar-height-auto-observer-wrapper {
|
||||
box-sizing: inherit !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 1px;
|
||||
position: relative;
|
||||
float: left;
|
||||
max-height: 1px;
|
||||
overflow: hidden;
|
||||
// z-index: -1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
flex-grow: inherit;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.simplebar-height-auto-observer {
|
||||
box-sizing: inherit;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 1000%;
|
||||
width: 1000%;
|
||||
min-height: 1px;
|
||||
min-width: 1px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
// z-index: -1;
|
||||
}
|
||||
|
||||
.simplebar-track {
|
||||
// z-index: 1;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-simplebar].simplebar-dragging {
|
||||
pointer-events: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
.simplebar-content {
|
||||
pointer-events: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.simplebar-track {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-scrollbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
min-height: 10px;
|
||||
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
background: black;
|
||||
border-radius: 7px;
|
||||
left: 2px;
|
||||
right: 2px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s 0.5s linear;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-scrollbar.simplebar-visible {
|
||||
&:before {
|
||||
opacity: 0.5;
|
||||
transition-delay: 0s;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-vertical {
|
||||
top: 0;
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
.simplebar-track.simplebar-horizontal {
|
||||
left: 0;
|
||||
height: 11px;
|
||||
|
||||
.simplebar-scrollbar {
|
||||
right: auto;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
min-height: 0;
|
||||
min-width: 10px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[data-simplebar-direction='rtl'] {
|
||||
.simplebar-track.simplebar-vertical {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-dummy-scrollbar-size {
|
||||
direction: rtl;
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
-ms-overflow-style: scrollbar !important;
|
||||
|
||||
>div {
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.simplebar-hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
95
Web/game-snake-poc/frontend/sass/_variables.scss
Normal file
95
Web/game-snake-poc/frontend/sass/_variables.scss
Normal file
@@ -0,0 +1,95 @@
|
||||
// file: Web/game-snake-poc/frontend/sass/_variables.scss
|
||||
// version: 1
|
||||
|
||||
// Pulse 5.3.8
|
||||
// Bootswatch
|
||||
|
||||
$theme: "pulse" !default;
|
||||
|
||||
//
|
||||
// Color system
|
||||
//
|
||||
|
||||
$white: #fff !default;
|
||||
$gray-100: #fafafa !default;
|
||||
$gray-200: #f9f8fc !default;
|
||||
$gray-300: #ededed !default;
|
||||
$gray-400: #cbc8d0 !default;
|
||||
$gray-500: #adb5bd !default;
|
||||
$gray-600: #868e96 !default;
|
||||
$gray-700: #444 !default;
|
||||
$gray-800: #343a40 !default;
|
||||
$gray-900: #17141f !default;
|
||||
$black: #000 !default;
|
||||
|
||||
$blue: #007bff !default;
|
||||
$indigo: #6610f2 !default;
|
||||
$purple: #593196 !default;
|
||||
$pink: #e83e8c !default;
|
||||
$red: #fc3939 !default;
|
||||
$orange: #fd7e14 !default;
|
||||
$yellow: #efa31d !default;
|
||||
$green: #13b955 !default;
|
||||
$teal: #20c997 !default;
|
||||
$cyan: #009cdc !default;
|
||||
|
||||
$primary: $purple !default;
|
||||
$secondary: #a991d4 !default;
|
||||
$success: $green !default;
|
||||
$info: $cyan !default;
|
||||
$warning: $yellow !default;
|
||||
$danger: $red !default;
|
||||
$light: $gray-200 !default;
|
||||
$dark: $gray-900 !default;
|
||||
|
||||
$min-contrast-ratio: 2.1 !default;
|
||||
|
||||
// Options
|
||||
|
||||
$enable-rounded: false !default;
|
||||
|
||||
// Body
|
||||
|
||||
$body-color: $gray-700 !default;
|
||||
|
||||
// Links
|
||||
|
||||
$link-hover-color: $primary !default;
|
||||
|
||||
// Tables
|
||||
|
||||
$table-color: initial !default;
|
||||
|
||||
$table-border-color: rgba(0, 0, 0, .05) !default;
|
||||
|
||||
// Forms
|
||||
|
||||
$input-focus-border-color: $primary !default;
|
||||
|
||||
// Dropdowns
|
||||
|
||||
$dropdown-link-hover-color: $white !default;
|
||||
$dropdown-link-hover-bg: $primary !default;
|
||||
|
||||
// Navs
|
||||
|
||||
$nav-tabs-border-color: $gray-300 !default;
|
||||
$nav-tabs-link-hover-border-color: $primary !default;
|
||||
|
||||
// Navbar
|
||||
|
||||
$navbar-padding-y: 1.2rem !default;
|
||||
|
||||
// Progress bars
|
||||
|
||||
$progress-bg: $gray-300 !default;
|
||||
$progress-bar-bg: $primary !default;
|
||||
|
||||
// List group
|
||||
|
||||
$list-group-bg: $gray-900 !default;
|
||||
$list-group-border-color: transparent !default;
|
||||
$list-group-hover-bg: lighten($list-group-bg, 10%) !default;
|
||||
$list-group-active-color: $white !default;
|
||||
$list-group-active-bg: $list-group-bg !default;
|
||||
$list-group-disabled-color: lighten($list-group-bg, 30%) !default;
|
||||
10
Web/game-snake-poc/frontend/sass/main.scss
Normal file
10
Web/game-snake-poc/frontend/sass/main.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
// file: Web/game-snake-poc/frontend/sass/main.scss
|
||||
// version: 2
|
||||
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "variables";
|
||||
@import "fontawesome";
|
||||
@import "simplebar";
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
@import "bootswatch";
|
||||
@import "app";
|
||||
54
Web/game-snake-poc/frontend/ts/assets.ts
Normal file
54
Web/game-snake-poc/frontend/ts/assets.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/assets.ts
|
||||
// version: 1
|
||||
|
||||
export interface SnakeWebAssets {
|
||||
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 Web 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 Web Snake.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSnakeWebAssets(): Promise<SnakeWebAssets> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
179
Web/game-snake-poc/frontend/ts/game.ts
Normal file
179
Web/game-snake-poc/frontend/ts/game.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/game.ts
|
||||
// version: 3
|
||||
|
||||
import init, { SnakeWasmGame } from "@snake-wasm";
|
||||
import { webDebug, webInfo } from "./logging";
|
||||
import { BrowserRuntimeProvenance, type SnakeInputSource } from "./provenance";
|
||||
|
||||
const FRAME_MILLIS = 16;
|
||||
const MAX_FRAME_ELAPSED_MILLIS = 250;
|
||||
|
||||
export type SnakeDirection = "left" | "right" | "up" | "down";
|
||||
|
||||
export interface SnakeWebSession {
|
||||
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,
|
||||
): Promise<SnakeWebSession> {
|
||||
await init();
|
||||
webInfo("snake-web-runtime", "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 BrowserRuntimeProvenance(game, provenanceOutput);
|
||||
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;
|
||||
}
|
||||
webDebug("snake-web-runtime", "runtime_paused");
|
||||
}
|
||||
|
||||
function resume(): void {
|
||||
if (running || disposed) {
|
||||
return;
|
||||
}
|
||||
previous = performance.now();
|
||||
accumulator = 0;
|
||||
running = true;
|
||||
schedule();
|
||||
webDebug("snake-web-runtime", "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();
|
||||
},
|
||||
};
|
||||
}
|
||||
61
Web/game-snake-poc/frontend/ts/input.ts
Normal file
61
Web/game-snake-poc/frontend/ts/input.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/input.ts
|
||||
// version: 2
|
||||
|
||||
import type { SnakeDirection, SnakeWebSession } from "./game";
|
||||
import { webDebug } from "./logging";
|
||||
|
||||
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: SnakeWebSession, 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");
|
||||
webDebug("snake-web-input", "direction", { direction, source: "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);
|
||||
webDebug("snake-web-input", "direction", { direction, source });
|
||||
});
|
||||
button.addEventListener("click", event => {
|
||||
if (event.detail !== 0) {
|
||||
return;
|
||||
}
|
||||
session.queueDirection(direction, "keyboard-mouse");
|
||||
webDebug("snake-web-input", "direction", { direction, source: "keyboard-mouse" });
|
||||
});
|
||||
}
|
||||
}
|
||||
58
Web/game-snake-poc/frontend/ts/lifecycle.ts
Normal file
58
Web/game-snake-poc/frontend/ts/lifecycle.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/lifecycle.ts
|
||||
// version: 1
|
||||
|
||||
import type { SnakeWebSession } from "./game";
|
||||
import { webDebug, webInfo } from "./logging";
|
||||
|
||||
export interface SnakeLifecycleBinding {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function bindBrowserLifecycle(session: SnakeWebSession, stage: HTMLElement): SnakeLifecycleBinding {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
session.refreshDeviceClass();
|
||||
session.renderNow();
|
||||
});
|
||||
|
||||
function synchronizeVisibility(): void {
|
||||
if (document.visibilityState === "hidden") {
|
||||
session.pause();
|
||||
webDebug("snake-web-lifecycle", "visibility_hidden");
|
||||
return;
|
||||
}
|
||||
session.resume();
|
||||
session.renderNow();
|
||||
webDebug("snake-web-lifecycle", "visibility_visible");
|
||||
}
|
||||
|
||||
function pageHide(event: PageTransitionEvent): void {
|
||||
if (event.persisted) {
|
||||
session.pause();
|
||||
} else {
|
||||
session.dispose();
|
||||
}
|
||||
webInfo("snake-web-lifecycle", "page_hide", { persisted: event.persisted });
|
||||
}
|
||||
|
||||
function pageShow(event: PageTransitionEvent): void {
|
||||
session.resume();
|
||||
session.renderNow();
|
||||
webInfo("snake-web-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();
|
||||
},
|
||||
};
|
||||
}
|
||||
45
Web/game-snake-poc/frontend/ts/logging.ts
Normal file
45
Web/game-snake-poc/frontend/ts/logging.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/logging.ts
|
||||
// version: 1
|
||||
|
||||
export type WebLogLevel = "debug" | "info" | "warn" | "error";
|
||||
export type WebLogFields = Readonly<Record<string, boolean | number | string | null>>;
|
||||
|
||||
type ConsoleMethod = (...items: unknown[]) => void;
|
||||
|
||||
function consoleMethod(level: WebLogLevel): ConsoleMethod {
|
||||
if (level === "debug") {
|
||||
return console.debug.bind(console);
|
||||
}
|
||||
if (level === "info") {
|
||||
return console.info.bind(console);
|
||||
}
|
||||
if (level === "warn") {
|
||||
return console.warn.bind(console);
|
||||
}
|
||||
return console.error.bind(console);
|
||||
}
|
||||
|
||||
function emit(level: WebLogLevel, target: string, action: string, fields: WebLogFields = {}): void {
|
||||
const event = {
|
||||
target,
|
||||
action,
|
||||
...fields,
|
||||
};
|
||||
consoleMethod(level)(`[games.sasedev][${target}] ${action}`, event);
|
||||
}
|
||||
|
||||
export function webDebug(target: string, action: string, fields: WebLogFields = {}): void {
|
||||
emit("debug", target, action, fields);
|
||||
}
|
||||
|
||||
export function webInfo(target: string, action: string, fields: WebLogFields = {}): void {
|
||||
emit("info", target, action, fields);
|
||||
}
|
||||
|
||||
export function webWarn(target: string, action: string, fields: WebLogFields = {}): void {
|
||||
emit("warn", target, action, fields);
|
||||
}
|
||||
|
||||
export function webError(target: string, action: string, fields: WebLogFields = {}): void {
|
||||
emit("error", target, action, fields);
|
||||
}
|
||||
66
Web/game-snake-poc/frontend/ts/main.ts
Normal file
66
Web/game-snake-poc/frontend/ts/main.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/main.ts
|
||||
// version: 3
|
||||
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
import "simplebar";
|
||||
import { loadSnakeWebAssets } from "./assets";
|
||||
import { startGame } from "./game";
|
||||
import { bindInputs } from "./input";
|
||||
import { bindBrowserLifecycle } from "./lifecycle";
|
||||
import { webError, webInfo } from "./logging";
|
||||
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
|
||||
function requireElement<T extends Element>(selector: string): T {
|
||||
const element = document.querySelector<T>(selector);
|
||||
if (element === null) {
|
||||
throw new Error(`Élément frontend requis absent : ${selector}`);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const canvas = requireElement<HTMLCanvasElement>("#game");
|
||||
const stage = requireElement<HTMLElement>(".game-stage");
|
||||
const score = requireElement<HTMLOutputElement>("#score");
|
||||
const length = requireElement<HTMLOutputElement>("#length");
|
||||
const provenance = requireElement<HTMLOutputElement>("#runtime-provenance");
|
||||
const assets = requireElement<HTMLOutputElement>("#asset-status");
|
||||
const status = requireElement<HTMLSpanElement>("#runtime-status");
|
||||
const error = requireElement<HTMLParagraphElement>("#startup-error");
|
||||
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-direction]"));
|
||||
|
||||
status.textContent = "Chargement assets…";
|
||||
const assetMetadata = await loadSnakeWebAssets();
|
||||
assets.value = `${assetMetadata.gameKind} / engine-v${assetMetadata.engineGeneration}`;
|
||||
webInfo("snake-web-assets", "runtime_assets_loaded", {
|
||||
engineGeneration: assetMetadata.engineGeneration,
|
||||
game: assetMetadata.game,
|
||||
gameKind: assetMetadata.gameKind,
|
||||
});
|
||||
|
||||
status.textContent = "Chargement WASM…";
|
||||
const session = await startGame(canvas, score, length, provenance);
|
||||
bindInputs(session, buttons);
|
||||
bindBrowserLifecycle(session, stage);
|
||||
status.className = "badge text-bg-success";
|
||||
status.textContent = "Prêt";
|
||||
error.hidden = true;
|
||||
canvas.focus();
|
||||
webInfo("snake-web-main", "runtime_ready");
|
||||
}
|
||||
|
||||
void main().catch(caughtError => {
|
||||
const status = document.querySelector<HTMLSpanElement>("#runtime-status");
|
||||
const error = document.querySelector<HTMLParagraphElement>("#startup-error");
|
||||
const message = caughtError instanceof Error ? caughtError.stack ?? caughtError.message : String(caughtError);
|
||||
if (status !== null) {
|
||||
status.className = "badge text-bg-danger";
|
||||
status.textContent = "Erreur";
|
||||
}
|
||||
if (error !== null) {
|
||||
error.textContent = `Impossible de démarrer Snake : ${message}`;
|
||||
error.hidden = false;
|
||||
}
|
||||
webError("snake-web-main", "runtime_start_failed", { message });
|
||||
});
|
||||
98
Web/game-snake-poc/frontend/ts/provenance.ts
Normal file
98
Web/game-snake-poc/frontend/ts/provenance.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
// file: Web/game-snake-poc/frontend/ts/provenance.ts
|
||||
// version: 2
|
||||
|
||||
import type { SnakeWasmGame } from "@snake-wasm";
|
||||
import { webInfo } 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 BrowserRuntimeProvenance {
|
||||
private keyboardMouseObserved = false;
|
||||
private touchObserved = false;
|
||||
private deviceClass: DeviceClass = detectedDeviceClass();
|
||||
|
||||
public constructor(
|
||||
private readonly game: SnakeWasmGame,
|
||||
private readonly output: HTMLOutputElement,
|
||||
) {
|
||||
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("web", "browser", this.deviceClass, profile)) {
|
||||
throw new Error("La provenance navigateur n'a pas pu être configurée dans le bridge WASM.");
|
||||
}
|
||||
this.output.value = displayLabel(this.game);
|
||||
webInfo("snake-web-provenance", "runtime_provenance", {
|
||||
deviceClass: this.game.provenance_device_class(),
|
||||
executionModel: this.game.provenance_execution_model(),
|
||||
inputProfile: this.game.provenance_input_profile(),
|
||||
platformFamily: this.game.provenance_platform_family(),
|
||||
runtimeHost: this.game.provenance_runtime_host(),
|
||||
});
|
||||
}
|
||||
}
|
||||
25
Web/game-snake-poc/package.json
Normal file
25
Web/game-snake-poc/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "game-snake-poc-web",
|
||||
"private": true,
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.3",
|
||||
"bootstrap": "^5.3",
|
||||
"resize-observer-polyfill": "^1.5",
|
||||
"simplebar": "^6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bootstrap": "^5.2",
|
||||
"@types/node": "^26.1",
|
||||
"sass-embedded": "^1.102",
|
||||
"typescript": "^7.0",
|
||||
"vite": "^8.2",
|
||||
"vite-plugin-static-copy": "^4.1"
|
||||
}
|
||||
}
|
||||
36
Web/game-snake-poc/tsconfig.json
Normal file
36
Web/game-snake-poc/tsconfig.json
Normal 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-web/wasm/game_snake_poc_wasm.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"frontend",
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
||||
96
Web/game-snake-poc/vite.config.ts
Normal file
96
Web/game-snake-poc/vite.config.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
// file: Web/game-snake-poc/vite.config.ts
|
||||
// version: 3
|
||||
|
||||
import { NodePackageImporter } from "sass-embedded";
|
||||
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-web"));
|
||||
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"));
|
||||
|
||||
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 (["eot", "otf", "ttf", "woff", "woff2"].includes(extension)) {
|
||||
return "fonts/[name]-[hash][extname]";
|
||||
}
|
||||
if (["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"].includes(extension)) {
|
||||
return "imgs/[name][extname]";
|
||||
}
|
||||
if (extension === "wasm") {
|
||||
return "wasm/[name]-[hash][extname]";
|
||||
}
|
||||
return "otherassets/[name][extname]";
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
quietDeps: true,
|
||||
silenceDeprecations: ["import", "color-functions", "global-builtin"],
|
||||
verbose: false,
|
||||
importers: [new NodePackageImporter()],
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "127.0.0.1",
|
||||
port: 1434,
|
||||
strictPort: true,
|
||||
fs: {
|
||||
allow: [appRoot, repositoryRoot, externalBuildRoot],
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
host: "127.0.0.1",
|
||||
port: 4174,
|
||||
strictPort: true,
|
||||
},
|
||||
});
|
||||
34
crates/apps/game-snake-poc-tauri/Cargo.toml
Normal file
34
crates/apps/game-snake-poc-tauri/Cargo.toml
Normal 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
|
||||
45
crates/apps/game-snake-poc-tauri/README.md
Normal file
45
crates/apps/game-snake-poc-tauri/README.md
Normal 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 qu’une 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 n’enregistre 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 d’architecture et appartiendraient à une UX produit réelle.
|
||||
119
crates/apps/game-snake-poc-tauri/USAGE.md
Normal file
119
crates/apps/game-snake-poc-tauri/USAGE.md
Normal 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 qu’un 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 l’application 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, l’application 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 n’est 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.
|
||||
|
||||
9
crates/apps/game-snake-poc-tauri/build.rs
Normal file
9
crates/apps/game-snake-poc-tauri/build.rs
Normal 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;
|
||||
}
|
||||
12
crates/apps/game-snake-poc-tauri/capabilities/default.json
Normal file
12
crates/apps/game-snake-poc-tauri/capabilities/default.json
Normal 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"
|
||||
]
|
||||
}
|
||||
235
crates/apps/game-snake-poc-tauri/frontend/css/main.css
Normal file
235
crates/apps/game-snake-poc-tauri/frontend/css/main.css
Normal 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;
|
||||
}
|
||||
}
|
||||
59
crates/apps/game-snake-poc-tauri/frontend/main.html
Normal file
59
crates/apps/game-snake-poc-tauri/frontend/main.html
Normal 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>
|
||||
54
crates/apps/game-snake-poc-tauri/frontend/ts/assets.ts
Normal file
54
crates/apps/game-snake-poc-tauri/frontend/ts/assets.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
20
crates/apps/game-snake-poc-tauri/frontend/ts/bridge.ts
Normal file
20
crates/apps/game-snake-poc-tauri/frontend/ts/bridge.ts
Normal 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");
|
||||
}
|
||||
182
crates/apps/game-snake-poc-tauri/frontend/ts/game.ts
Normal file
182
crates/apps/game-snake-poc-tauri/frontend/ts/game.ts
Normal 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");
|
||||
},
|
||||
};
|
||||
}
|
||||
57
crates/apps/game-snake-poc-tauri/frontend/ts/input.ts
Normal file
57
crates/apps/game-snake-poc-tauri/frontend/ts/input.ts
Normal 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");
|
||||
});
|
||||
}
|
||||
}
|
||||
60
crates/apps/game-snake-poc-tauri/frontend/ts/lifecycle.ts
Normal file
60
crates/apps/game-snake-poc-tauri/frontend/ts/lifecycle.ts
Normal 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();
|
||||
},
|
||||
};
|
||||
}
|
||||
16
crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts
Normal file
16
crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts
Normal 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);
|
||||
}
|
||||
65
crates/apps/game-snake-poc-tauri/frontend/ts/main.ts
Normal file
65
crates/apps/game-snake-poc-tauri/frontend/ts/main.ts
Normal 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}`);
|
||||
});
|
||||
97
crates/apps/game-snake-poc-tauri/frontend/ts/provenance.ts
Normal file
97
crates/apps/game-snake-poc-tauri/frontend/ts/provenance.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
BIN
crates/apps/game-snake-poc-tauri/icons/icon.png
Normal file
BIN
crates/apps/game-snake-poc-tauri/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 242 B |
22
crates/apps/game-snake-poc-tauri/package.json
Normal file
22
crates/apps/game-snake-poc-tauri/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
21
crates/apps/game-snake-poc-tauri/src/lib.rs
Normal file
21
crates/apps/game-snake-poc-tauri/src/lib.rs
Normal 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;
|
||||
20
crates/apps/game-snake-poc-tauri/src/main.rs
Normal file
20
crates/apps/game-snake-poc-tauri/src/main.rs
Normal 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
|
||||
},
|
||||
};
|
||||
}
|
||||
87
crates/apps/game-snake-poc-tauri/src/runtime.rs
Normal file
87
crates/apps/game-snake-poc-tauri/src/runtime.rs
Normal 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",
|
||||
};
|
||||
}
|
||||
85
crates/apps/game-snake-poc-tauri/src/tauri.rs
Normal file
85
crates/apps/game-snake-poc-tauri/src/tauri.rs
Normal 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;
|
||||
}
|
||||
41
crates/apps/game-snake-poc-tauri/tauri.conf.json
Normal file
41
crates/apps/game-snake-poc-tauri/tauri.conf.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
36
crates/apps/game-snake-poc-tauri/tsconfig.json
Normal file
36
crates/apps/game-snake-poc-tauri/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
84
crates/apps/game-snake-poc-tauri/vite.config.ts
Normal file
84
crates/apps/game-snake-poc-tauri/vite.config.ts
Normal 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/**"],
|
||||
},
|
||||
},
|
||||
});
|
||||
23
crates/apps/game-snake-poc-wasm/Cargo.toml
Normal file
23
crates/apps/game-snake-poc-wasm/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
# file: crates/apps/game-snake-poc-wasm/Cargo.toml
|
||||
# version: 2
|
||||
|
||||
[package]
|
||||
name = "game-snake-poc-wasm"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
engine-v1-common = { path = "../../engines/engine-v1-common" }
|
||||
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
|
||||
game-snake-poc = { path = "../../games/game-snake-poc" }
|
||||
wasm-bindgen.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
13
crates/apps/game-snake-poc-wasm/src/lib.rs
Normal file
13
crates/apps/game-snake-poc-wasm/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// file: crates/apps/game-snake-poc-wasm/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
//! WebAssembly adapter facade for the Snake POC.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod runtime;
|
||||
|
||||
/// Re-export of the Snake WebAssembly runtime adapter.
|
||||
pub use self::runtime::SnakeWasmGame;
|
||||
330
crates/apps/game-snake-poc-wasm/src/runtime.rs
Normal file
330
crates/apps/game-snake-poc-wasm/src/runtime.rs
Normal file
@@ -0,0 +1,330 @@
|
||||
// file: crates/apps/game-snake-poc-wasm/src/runtime.rs
|
||||
// version: 3
|
||||
|
||||
const FIXED_STEP_MILLIS: u64 = 16;
|
||||
|
||||
/// WebAssembly-owned Snake game state consumed by the direct browser host.
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub struct SnakeWasmGame {
|
||||
pending_input: engine_v1_common::InputState,
|
||||
provenance: engine_v1_platform_api::RuntimeProvenance,
|
||||
runner: engine_v1_common::FixedStepRunner,
|
||||
state: game_snake_poc::SnakeState,
|
||||
}
|
||||
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
impl SnakeWasmGame {
|
||||
/// Creates a fresh Snake POC session using the shared gameplay crate.
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
return Self {
|
||||
pending_input: engine_v1_common::InputState::none(),
|
||||
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::Web,
|
||||
engine_v1_platform_api::RuntimeHost::Browser,
|
||||
),
|
||||
runner: engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(FIXED_STEP_MILLIS)),
|
||||
state: game_snake_poc::SnakeState::new(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Updates host-observed runtime provenance while preserving WebAssembly execution.
|
||||
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) {
|
||||
Some(value) => value,
|
||||
None => return false,
|
||||
};
|
||||
let input_profile = match parse_input_profile(input_profile) {
|
||||
Some(value) => value,
|
||||
None => return false,
|
||||
};
|
||||
self.provenance = engine_v1_platform_api::RuntimeProvenance::new(
|
||||
device_class,
|
||||
engine_v1_platform_api::ExecutionModel::Wasm,
|
||||
input_profile,
|
||||
platform_family,
|
||||
runtime_host,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns the current physical device class label.
|
||||
pub fn provenance_device_class(&self) -> String {
|
||||
return device_class_label(self.provenance.device_class()).to_string();
|
||||
}
|
||||
|
||||
/// Returns the current execution model label.
|
||||
pub fn provenance_execution_model(&self) -> String {
|
||||
return execution_model_label(self.provenance.execution_model()).to_string();
|
||||
}
|
||||
|
||||
/// Returns the current primary input profile label.
|
||||
pub fn provenance_input_profile(&self) -> String {
|
||||
return input_profile_label(self.provenance.input_profile()).to_string();
|
||||
}
|
||||
|
||||
/// Returns the current platform family label.
|
||||
pub fn provenance_platform_family(&self) -> String {
|
||||
return platform_family_label(self.provenance.platform_family()).to_string();
|
||||
}
|
||||
|
||||
/// Returns the current runtime host label.
|
||||
pub fn provenance_runtime_host(&self) -> String {
|
||||
return runtime_host_label(self.provenance.runtime_host()).to_string();
|
||||
}
|
||||
|
||||
/// Queues a logical left direction for the next deterministic update.
|
||||
pub fn left(&mut self) {
|
||||
self.queue_direction(engine_v1_common::GameAction::Left);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Queues a logical right direction for the next deterministic update.
|
||||
pub fn right(&mut self) {
|
||||
self.queue_direction(engine_v1_common::GameAction::Right);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Queues a logical upward direction for the next deterministic update.
|
||||
pub fn up(&mut self) {
|
||||
self.queue_direction(engine_v1_common::GameAction::Up);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Queues a logical downward direction for the next deterministic update.
|
||||
pub fn down(&mut self) {
|
||||
self.queue_direction(engine_v1_common::GameAction::Down);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Advances one deterministic update and consumes the queued direction.
|
||||
pub fn tick(&mut self) {
|
||||
let input = self.pending_input;
|
||||
self.pending_input = engine_v1_common::InputState::none();
|
||||
self.runner.tick(&mut self.state, input);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Returns the current gameplay score.
|
||||
pub fn score(&self) -> u32 {
|
||||
return self.state.score().min(u32::MAX as u64) as u32;
|
||||
}
|
||||
|
||||
/// Returns the current Snake length.
|
||||
pub fn length(&self) -> u32 {
|
||||
return self.state.length().min(u32::MAX as usize) as u32;
|
||||
}
|
||||
|
||||
/// Returns the current scene background red channel.
|
||||
pub fn background_red(&self) -> u8 {
|
||||
return self.scene().background().red();
|
||||
}
|
||||
|
||||
/// Returns the current scene background green channel.
|
||||
pub fn background_green(&self) -> u8 {
|
||||
return self.scene().background().green();
|
||||
}
|
||||
|
||||
/// Returns the current scene background blue channel.
|
||||
pub fn background_blue(&self) -> u8 {
|
||||
return self.scene().background().blue();
|
||||
}
|
||||
|
||||
/// Returns the number of occupied rectangle slots in the current scene.
|
||||
pub fn rectangle_count(&self) -> u32 {
|
||||
let scene = self.scene();
|
||||
let mut count = 0_u32;
|
||||
for slot in scene.rectangles() {
|
||||
if slot.is_some() {
|
||||
count = count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// Returns one rectangle's normalized left coordinate or `-1.0` if absent.
|
||||
pub fn rectangle_x(&self, index: u32) -> f32 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.rect().x(),
|
||||
None => -1.0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one rectangle's normalized top coordinate or `-1.0` if absent.
|
||||
pub fn rectangle_y(&self, index: u32) -> f32 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.rect().y(),
|
||||
None => -1.0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one rectangle's normalized width or `0.0` if absent.
|
||||
pub fn rectangle_width(&self, index: u32) -> f32 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.rect().width(),
|
||||
None => 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one rectangle's normalized height or `0.0` if absent.
|
||||
pub fn rectangle_height(&self, index: u32) -> f32 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.rect().height(),
|
||||
None => 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one rectangle's red channel or zero if absent.
|
||||
pub fn rectangle_red(&self, index: u32) -> u8 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.color().red(),
|
||||
None => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one rectangle's green channel or zero if absent.
|
||||
pub fn rectangle_green(&self, index: u32) -> u8 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.color().green(),
|
||||
None => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns one rectangle's blue channel or zero if absent.
|
||||
pub fn rectangle_blue(&self, index: u32) -> u8 {
|
||||
return match self.rectangle(index) {
|
||||
Some(rectangle) => rectangle.color().blue(),
|
||||
None => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SnakeWasmGame {
|
||||
fn default() -> Self {
|
||||
return Self::new();
|
||||
}
|
||||
}
|
||||
|
||||
impl SnakeWasmGame {
|
||||
fn queue_direction(&mut self, action: engine_v1_common::GameAction) {
|
||||
self.pending_input = engine_v1_common::InputState::none().with_action(action, true);
|
||||
return;
|
||||
}
|
||||
|
||||
fn scene(&self) -> engine_v1_common::EngineScene {
|
||||
return engine_v1_common::EngineGame::scene(&self.state);
|
||||
}
|
||||
|
||||
fn rectangle(&self, requested_index: u32) -> Option<engine_v1_common::RenderRect> {
|
||||
let scene = self.scene();
|
||||
let mut occupied_index = 0_u32;
|
||||
for slot in scene.rectangles() {
|
||||
match slot {
|
||||
Some(rectangle) => {
|
||||
if occupied_index == requested_index {
|
||||
return Some(*rectangle);
|
||||
}
|
||||
occupied_index = occupied_index.saturating_add(1);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
return match label {
|
||||
"desktop" => Some(engine_v1_platform_api::DeviceClass::Desktop),
|
||||
"phone" => Some(engine_v1_platform_api::DeviceClass::Phone),
|
||||
"tablet" => Some(engine_v1_platform_api::DeviceClass::Tablet),
|
||||
"unknown" => Some(engine_v1_platform_api::DeviceClass::Unknown),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_input_profile(label: &str) -> Option<engine_v1_platform_api::InputProfile> {
|
||||
return match label {
|
||||
"gamepad" => Some(engine_v1_platform_api::InputProfile::Gamepad),
|
||||
"keyboard-mouse" => Some(engine_v1_platform_api::InputProfile::KeyboardMouse),
|
||||
"mixed" => Some(engine_v1_platform_api::InputProfile::Mixed),
|
||||
"touch" => Some(engine_v1_platform_api::InputProfile::Touch),
|
||||
"unknown" => Some(engine_v1_platform_api::InputProfile::Unknown),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
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",
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/runtime.rs"]
|
||||
mod tests;
|
||||
63
crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
Normal file
63
crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
// file: crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
fn direction_is_queued_without_advancing_simulation() {
|
||||
let mut game = crate::SnakeWasmGame::new();
|
||||
let before_x = game.rectangle_x(1);
|
||||
let before_y = game.rectangle_y(1);
|
||||
game.up();
|
||||
assert_eq!(game.rectangle_x(1), before_x);
|
||||
assert_eq!(game.rectangle_y(1), before_y);
|
||||
game.tick();
|
||||
assert_eq!(game.rectangle_x(1), before_x);
|
||||
assert!(game.rectangle_y(1) < before_y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scene_and_status_are_exposed_for_canvas_host() {
|
||||
let game = crate::SnakeWasmGame::new();
|
||||
assert_eq!(game.score(), 0);
|
||||
assert_eq!(game.length(), 3);
|
||||
assert_eq!(game.rectangle_count(), 4);
|
||||
assert!(game.rectangle_width(0) > 0.0);
|
||||
assert!(game.rectangle_height(0) > 0.0);
|
||||
assert_eq!(game.rectangle_x(99), -1.0);
|
||||
assert_eq!(game.rectangle_y(99), -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browser_runtime_provenance_preserves_static_and_observed_dimensions() {
|
||||
let mut game = crate::SnakeWasmGame::new();
|
||||
assert_eq!(game.provenance_platform_family(), "web");
|
||||
assert_eq!(game.provenance_execution_model(), "wasm");
|
||||
assert_eq!(game.provenance_runtime_host(), "browser");
|
||||
assert_eq!(game.provenance_device_class(), "unknown");
|
||||
assert_eq!(game.provenance_input_profile(), "unknown");
|
||||
assert!(game.configure_runtime_provenance("web", "browser", "phone", "touch"));
|
||||
assert_eq!(game.provenance_device_class(), "phone");
|
||||
assert_eq!(game.provenance_input_profile(), "touch");
|
||||
assert_eq!(game.provenance_platform_family(), "web");
|
||||
assert_eq!(game.provenance_execution_model(), "wasm");
|
||||
assert_eq!(game.provenance_runtime_host(), "browser");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_runtime_provenance_labels_are_rejected_without_mutation() {
|
||||
let mut game = crate::SnakeWasmGame::new();
|
||||
assert!(game.configure_runtime_provenance("web", "browser", "desktop", "keyboard-mouse"));
|
||||
assert!(!game.configure_runtime_provenance("console", "spaceship", "console", "telepathy"));
|
||||
assert_eq!(game.provenance_device_class(), "desktop");
|
||||
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");
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/engines/engine-v1-platform-api/src/environment.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Broad physical device class relevant to interaction and score provenance.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -56,7 +56,7 @@ pub enum RuntimeHost {
|
||||
Browser,
|
||||
/// Native process managed directly by the operating system.
|
||||
Native,
|
||||
/// Tauri desktop WebView host.
|
||||
/// Tauri WebView host on a supported native platform.
|
||||
TauriWebView,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# file: crates/games/game-reflex-poc/Cargo.toml
|
||||
# version: 3
|
||||
# version: 4
|
||||
|
||||
[package]
|
||||
name = "game-reflex-poc"
|
||||
@@ -12,7 +12,6 @@ publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
engine-v1-common = { path = "../../engines/engine-v1-common" }
|
||||
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
|
||||
|
||||
[dev-dependencies]
|
||||
game-logging-lib = { path = "../../common/game-logging-lib" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# file: crates/games/game-snake-poc/Cargo.toml
|
||||
# version: 3
|
||||
# version: 4
|
||||
|
||||
[package]
|
||||
name = "game-snake-poc"
|
||||
@@ -12,7 +12,6 @@ publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
engine-v1-common = { path = "../../engines/engine-v1-common" }
|
||||
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
|
||||
|
||||
[dev-dependencies]
|
||||
game-logging-lib = { path = "../../common/game-logging-lib" }
|
||||
|
||||
85
deltas/0.3.0/0-pre.1.fix.1.md
Normal file
85
deltas/0.3.0/0-pre.1.fix.1.md
Normal file
@@ -0,0 +1,85 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.1.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.1.fix.1
|
||||
|
||||
## Base
|
||||
|
||||
Base requise : `0.3.0-0-pre.1`.
|
||||
|
||||
Ce correctif reste dans la responsabilité de cadrage de `0-pre.1`. Il ne modifie aucun gameplay, runtime, frontend ou packaging.
|
||||
|
||||
## Objet
|
||||
|
||||
Corriger deux omissions du cadrage initial :
|
||||
|
||||
- créer le plan vivant de `0.3.0` sous `docs/plans/`, avec le découpage prévisionnel souple de toute la progression de la version ;
|
||||
- formaliser les règles associées, notamment la relation plan/ROADMAP/deltas, la cible d'au moins une version complète par session et le traitement des archives fournies depuis un tag.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de `0.3.0-0-pre.1` à `0.3.0-0-pre.1.fix.1`.
|
||||
|
||||
## Plan de version
|
||||
|
||||
Ajout de :
|
||||
|
||||
- `docs/plans/000-README.md` ;
|
||||
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`.
|
||||
|
||||
Le plan conserve le scope et les décisions du cadrage, puis suit une trajectoire souple : baseline Snake portable, adaptation WASM, frontend Web/Vite, intégration E2E, correction/extraction conditionnelle, beta, RC et stable.
|
||||
|
||||
La numérotation est prévisionnelle. Les tranches peuvent être scindées, fusionnées ou décalées en fonction des résultats réels sans forcer une fermeture artificielle.
|
||||
|
||||
## Règles ajoutées ou précisées
|
||||
|
||||
- `0-pre.1` crée ou révise obligatoirement le plan actif de la version sous `docs/plans/` ;
|
||||
- le plan porte le découpage prévisionnel fin, `ROADMAP.md` reste macroscopique et les deltas enregistrent le livré réel ;
|
||||
- une session de développement est planifiée pour livrer au minimum une version concrète complète ; une prerelease est une tranche interne, pas une cible normale de fin de session ;
|
||||
- lorsqu'une archive est fournie comme téléchargement d'un tag du dépôt, elle est la baseline autoritaire de ce tag ; l'absence de `.git` est normale et n'est ni une anomalie ni une validation manquante ;
|
||||
- les contrôles Git nécessitant `.git` ne s'appliquent qu'à un checkout local effectivement fourni.
|
||||
|
||||
Le prompt `002-V0_3_X_START_PROMPT.md` est aligné sur ces règles : il ne demande plus de vérifier un état Git inexistant dans une archive taggée et exige le plan actif dans le résultat de `pre.1`.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
- `docs/plans/000-README.md` ;
|
||||
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md` ;
|
||||
- `deltas/0.3.0/0-pre.1.fix.1.md`.
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
- `Cargo.toml` ;
|
||||
- `README.md` ;
|
||||
- `docs/000-README.md` ;
|
||||
- `docs/rules/FILE_CONTRACTS.md` ;
|
||||
- `docs/rules/RULES_DOCUMENTATION.md` ;
|
||||
- `docs/rules/RULES_SESSION_PLANNING.md` ;
|
||||
- `docs/rules/VERSION_WORKFLOW.md` ;
|
||||
- `docs/rules/RULES_COMMANDS.md` ;
|
||||
- `prompts/002-V0_3_X_START_PROMPT.md`.
|
||||
|
||||
## Suppressions
|
||||
|
||||
Aucune.
|
||||
|
||||
## Validations applicables
|
||||
|
||||
Ce fix modifie uniquement le manifest de version et la documentation/règles. Les audits statiques sont exécutés lors de la préparation du delta.
|
||||
|
||||
Validation utilisateur demandée :
|
||||
|
||||
```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 deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
```
|
||||
|
||||
Clippy/tests ne sont pas rendus nécessaires par ce fix documentaire lui-même ; ils restent ceux prévus par la progression de `0.3.0`.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation de ce fix, poursuivre `0.3.0` selon `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`, en commençant par `0-pre.2` et en visant la fermeture complète de `0.3.0` dans la session plutôt qu'un arrêt planifié sur une prerelease.
|
||||
94
deltas/0.3.0/0-pre.1.fix.2.md
Normal file
94
deltas/0.3.0/0-pre.1.fix.2.md
Normal file
@@ -0,0 +1,94 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.1.fix.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.1.fix.2
|
||||
|
||||
## Base
|
||||
|
||||
Base requise : `0.3.0-0-pre.1.fix.1`.
|
||||
|
||||
Ce correctif reste dans la responsabilité de cadrage de `0-pre.1`. Il ne modifie aucun gameplay, runtime, frontend ou packaging.
|
||||
|
||||
## Objet
|
||||
|
||||
Compléter le plan `0.3.0` sur deux responsabilités qui doivent être visibles dès le cadrage :
|
||||
|
||||
- le POC navigateur doit posséder un véritable shell Web HTML/Vite/TypeScript autour du Canvas/WASM, avec Bootstrap 5 comme baseline de présentation du premier POC Snake ;
|
||||
- la prévision de version doit montrer explicitement la tranche finale de consolidation documentaire et de transmission avant RC/stable, au lieu de laisser cette responsabilité seulement implicite dans les règles générales.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de `0.3.0-0-pre.1.fix.1` à `0.3.0-0-pre.1.fix.2`.
|
||||
|
||||
## Frontend Web
|
||||
|
||||
Le plan retient désormais explicitement pour `0-pre.4` :
|
||||
|
||||
- Vite/TypeScript comme host frontend ;
|
||||
- un document HTML responsive ;
|
||||
- Bootstrap 5 pour le layout, les états UI et les contrôles tactiles ;
|
||||
- un Canvas dédié au rendu du jeu ;
|
||||
- clavier et contrôles directionnels tactiles ;
|
||||
- séparation stricte : Bootstrap/DOM restent côté frontend, le gameplay et l'état du jeu restent dans Rust/WASM.
|
||||
|
||||
La règle durable ne rend pas Bootstrap obligatoire pour tous les futurs hosts Web : elle impose la séparation shell Web / Canvas-WASM / gameplay. Bootstrap 5 est la décision concrète du POC `0.3.0`.
|
||||
|
||||
## Consolidation de fin de version
|
||||
|
||||
Le forecast `0.3.0` ajoute `2-beta.2` comme repère prévisionnel de consolidation :
|
||||
|
||||
- réconciliation de la documentation durable et du plan actif ;
|
||||
- mise à jour de `CHANGELOG.md` ;
|
||||
- mise à jour de `ROADMAP.md` ;
|
||||
- mise à jour de l'historique applicable ;
|
||||
- vérification des deltas réellement livrés ;
|
||||
- préparation et vérification du prompt de la version/session suivante.
|
||||
|
||||
Le numéro reste souple : des fixes ou une beta supplémentaire peuvent le décaler. La responsabilité, elle, ne doit pas disparaître du plan.
|
||||
|
||||
La documentation fonctionnelle reste mise à jour au fil des tranches concernées ; cette consolidation finale sert à fermer la cohérence transversale et la transmission de session.
|
||||
|
||||
## Règles précisées
|
||||
|
||||
- `SESSION-009` exige que le forecast créé en `0-pre.1` rende explicitement visible une tranche de consolidation avant la candidate finale ;
|
||||
- `GAME-PLATFORM-020` fixe la séparation durable du host Web : shell HTML/CSS/TypeScript autour du Canvas/WASM, sans fuite du framework de présentation dans gameplay/WASM ; Bootstrap 5 est la baseline du premier POC Snake.
|
||||
|
||||
Le prompt `002-V0_3_X_START_PROMPT.md` est aligné sur les décisions déjà prises par `pre.1` : Web direct en premier host, adaptation WASM dédiée, shell Bootstrap 5 et tranche `2-beta.2` de consolidation/transmission.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
- `deltas/0.3.0/0-pre.1.fix.2.md`.
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
- `Cargo.toml` ;
|
||||
- `README.md` ;
|
||||
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md` ;
|
||||
- `docs/rules/RULES_PROJECT.md` ;
|
||||
- `docs/rules/RULES_SESSION_PLANNING.md` ;
|
||||
- `prompts/002-V0_3_X_START_PROMPT.md`.
|
||||
|
||||
## Suppressions
|
||||
|
||||
Aucune.
|
||||
|
||||
## Validations applicables
|
||||
|
||||
Ce fix modifie uniquement la version workspace et le cadrage documentaire/normatif. Les audits statiques sont exécutés lors de la préparation du delta.
|
||||
|
||||
Validation utilisateur demandée :
|
||||
|
||||
```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 deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
```
|
||||
|
||||
Aucun build Web n'est demandé par ce fix : Bootstrap 5 et le frontend Snake seront introduits seulement dans leur tranche d'implémentation.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation de ce fix, poursuivre `0.3.0` avec `0-pre.2` selon le plan actif. La session reste planifiée jusqu'à la stable `0.3.0`, avec consolidation documentaire/transmission explicitement réservée avant RC.
|
||||
157
deltas/0.3.0/0-pre.1.md
Normal file
157
deltas/0.3.0/0-pre.1.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.1
|
||||
|
||||
## Base
|
||||
|
||||
Base déclarée : `0.2.0`.
|
||||
|
||||
L'archive de base a été inspectée avant modification. Sa version workspace est `0.2.0`. L'archive ne contient pas `.git`, donc le tag réel et la propreté Git ne peuvent pas être vérifiés dans l'environnement de génération.
|
||||
|
||||
## Objet
|
||||
|
||||
Ouvrir `0.3.0` par le cadrage obligatoire : audit réel de la baseline, revue des règles, requirements Snake, graphe de dépendances, choix du premier POC, sizing corrigé et préparation des validations.
|
||||
|
||||
Aucun gameplay, runtime SDL, Java/JNI, Tauri ou frontend existant n'est modifié dans cette tranche.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de :
|
||||
|
||||
```text
|
||||
0.2.0
|
||||
```
|
||||
|
||||
à :
|
||||
|
||||
```text
|
||||
0.3.0-0-pre.1
|
||||
```
|
||||
|
||||
Les métadonnées Android restent à `0.2.0` dans cette tranche, car le chemin Android natif n'est ni modifié ni construit par le premier POC Web. Elles seront réévaluées dans la version qui réouvre effectivement ce produit Android.
|
||||
|
||||
## Audit de baseline
|
||||
|
||||
Les audits statiques officiels ont été exécutés avant modification et étaient propres :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (4 table(s), 158 file(s))
|
||||
Distribution layout audit: clean (19 required path(s), 1 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
La revue manuelle a néanmoins détecté :
|
||||
|
||||
- README encore positionné sur `0.1.0` stable / `0.2.0` candidate ;
|
||||
- quatre lignes `( )` sous la stable `0.2.0` dans ROADMAP, en violation de `DOC-RMAP-010` ;
|
||||
- la gate `audit_rust_workspace_rules.py` qui créait un cache Python `.pyc` pendant son exécution, alors qu’un audit doit rester en lecture seule ;
|
||||
- `CMD-RC-001` encore spécifique à `0.1.0` ;
|
||||
- contradiction entre `CMD-BUILD-002` (interdiction globale) et `CMD-BUILD-004` (remplacement progressif en `0.3.x`), alors que deux orchestrateurs Python historiques existent encore ;
|
||||
- `CMD-WEB-003` décrivant sans contexte le build Python Tauri/WASM historique ;
|
||||
- dépendances `engine-v1-platform-api` déclarées mais non utilisées par les crates de jeu ;
|
||||
- `RuntimeProvenance` défini mais non branché aux launchers ;
|
||||
- `game-assets-lib` encore sans consommateur runtime ;
|
||||
- deux orchestrateurs Python de build historiques à ne pas réutiliser dans les nouveaux POC.
|
||||
|
||||
Les détails et le graphe complet sont documentés dans `docs/studies/023-V0_3_0_PLATFORM_POC_AUDIT.md`.
|
||||
|
||||
## Corrections de règles et hygiène
|
||||
|
||||
Cette tranche :
|
||||
|
||||
- corrige le README de version ;
|
||||
- ferme correctement le scope `0.2.0` dans ROADMAP ;
|
||||
- distingue ce qui a été livré de ce qui est reporté ;
|
||||
- rend `CMD-RC-001` générique à la version courante ;
|
||||
- rend `CMD-BUILD-002` cohérente avec la migration `0.3.x` : les deux orchestrateurs Python historiques sont gelés jusqu’à réactivation de leur chemin, mais aucun nouveau chemin ne peut les reprendre ;
|
||||
- qualifie explicitement `scripts/build_reflex_tauri_wasm.py` de mécanisme historique non réutilisable comme orchestration `0.3.x` ;
|
||||
- renforce `audit_project_workspace_rules.py` pour détecter le scope `( )` restant sous une version stable présente dans CHANGELOG ;
|
||||
- fait exécuter les sous-audits de `audit_rust_workspace_rules.py` avec l’interpréteur courant et `-B`, afin que la gate standard reste effectivement en lecture seule malgré l’import effectué dans l’audit de complétude des exports.
|
||||
|
||||
## Premier POC retenu
|
||||
|
||||
Le premier host de `0.3.0` devient :
|
||||
|
||||
```text
|
||||
Web navigateur direct + Snake
|
||||
```
|
||||
|
||||
Tauri Android est reporté au second host, actuellement prévu en `0.3.1`.
|
||||
|
||||
Le but est de valider d'abord la frontière Web/WASM et les capabilities directement observables sans cumuler simultanément la généralisation Snake/WASM et la complexité d'un host mobile Tauri.
|
||||
|
||||
## Forecast corrigé
|
||||
|
||||
```text
|
||||
0-pre.1 audit / règles / requirements / sizing
|
||||
0-pre.2 Snake portability baseline
|
||||
0-pre.3 adapter Snake WASM + build natif Web
|
||||
0-pre.4 navigateur direct end-to-end
|
||||
0-pre.5 seulement si un défaut réel est observé
|
||||
2-beta.1 validation large
|
||||
3-rc.1 candidate gelée
|
||||
0.3.0 release mécanique
|
||||
```
|
||||
|
||||
Aucune alpha n'est créée par cérémonial. Elle reste possible si le volume réel de stabilisation le justifie.
|
||||
|
||||
## Scope exclu de 0.3.0
|
||||
|
||||
- Tauri Android ;
|
||||
- Tauri Desktop Snake ;
|
||||
- Android multi-ABI natif ;
|
||||
- réseau realtime ;
|
||||
- WebTransport/QUIC ;
|
||||
- Uroburas ;
|
||||
- ads, billing, auth et leaderboard ;
|
||||
- ECS ou nouveau moteur.
|
||||
|
||||
## Suppressions
|
||||
|
||||
Aucune suppression de fichier appartenant à la baseline `0.2.0` n'est nécessaire.
|
||||
|
||||
## Validations exécutées dans l'environnement de génération
|
||||
|
||||
Après constitution de l'état livré, les audits statiques suivants doivent être réexécutés et leur résultat est enregistré avant packaging :
|
||||
|
||||
```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 deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
```
|
||||
|
||||
Aucun build/test/smoke n'est exécuté par le générateur, conformément à `CMD-GEN-007` et `CMD-BUILD-005`.
|
||||
|
||||
## Validation utilisateur demandée
|
||||
|
||||
Cette tranche ne modifie aucun source Rust/Java/TypeScript ni aucune configuration runtime. Elle ouvre toutefois une nouvelle version `0.3.0` et modifie le manifest workspace ; la gate utilisateur retient donc les audits statiques puis une gate Cargo workspace complète, conformément à la discipline des frontières de version :
|
||||
|
||||
```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 deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test --workspace --all-targets --all-features
|
||||
```
|
||||
|
||||
Aucun build de distribution, smoke Desktop, Tauri ou Android n'est requis par `pre.1`, car aucun runtime ni packaging de ces hosts n'est modifié.
|
||||
|
||||
La revue humaine doit confirmer :
|
||||
|
||||
- le choix Web direct comme premier POC ;
|
||||
- le scope corrigé de `0.3.0` ;
|
||||
- le report de Tauri Android vers le second host ;
|
||||
- le sizing `pre.2` à `pre.4` ;
|
||||
- la fermeture correcte des lignes historiques `0.2.0`.
|
||||
|
||||
## Suite après validation
|
||||
|
||||
Si cette gate est propre et le cadrage accepté, passer automatiquement à `0.3.0-0-pre.2`.
|
||||
|
||||
`pre.2` doit rester une tranche bornée de portabilité Snake et de nettoyage de dépendances ; elle ne doit pas commencer le frontend Web complet.
|
||||
102
deltas/0.3.0/0-pre.2.md
Normal file
102
deltas/0.3.0/0-pre.2.md
Normal file
@@ -0,0 +1,102 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.2
|
||||
|
||||
## Base
|
||||
|
||||
Base requise : `0.3.0-0-pre.1.fix.2`.
|
||||
|
||||
Les validations utilisateur de `0-pre.1.fix.1` et `0-pre.1.fix.2` sont consignées sous `history/0.3.0/` conformément au workflow documentaire.
|
||||
|
||||
## Objet
|
||||
|
||||
Fermer la baseline Snake portable avant l'introduction de l'adapter WASM :
|
||||
|
||||
- retirer des crates de gameplay les dépendances plateforme déclarées mais inutilisées ;
|
||||
- confirmer que le gameplay Snake ne consomme que les contrats moteur indépendants de la plateforme ;
|
||||
- documenter le contrat minimal que le futur host Web devra adapter sans introduire encore de code WASM ou frontend.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de `0.3.0-0-pre.1.fix.2` à `0.3.0-0-pre.2`.
|
||||
|
||||
## Nettoyage des dépendances
|
||||
|
||||
`engine-v1-platform-api` est retiré de :
|
||||
|
||||
- `game-snake-poc` ;
|
||||
- `game-reflex-poc`.
|
||||
|
||||
Ces deux crates n'utilisent aucun symbole de cette dépendance. Les launchers et adapters qui utilisent réellement des capacités plateforme conservent leur dépendance.
|
||||
|
||||
Ce nettoyage ne déplace aucune responsabilité et ne change aucun comportement de gameplay.
|
||||
|
||||
## Contrat Snake portable
|
||||
|
||||
La baseline confirmée pour Snake est :
|
||||
|
||||
- état et update via `EngineGame` ;
|
||||
- entrée via `InputState` ;
|
||||
- actions de gameplay utilisées : `Left`, `Right`, `Up`, `Down` ;
|
||||
- rendu abstrait via `EngineScene` ;
|
||||
- aucune dépendance SDL3, Web/DOM/Canvas ou `engine-v1-platform-api` dans `game-snake-poc`.
|
||||
|
||||
Le host reste responsable de traduire ses périphériques ou widgets en actions logiques. Le futur adapter WASM `0-pre.3` doit réutiliser cette frontière sans créer un modèle Web parallèle.
|
||||
|
||||
## Historique des validations précédentes
|
||||
|
||||
Ajout de :
|
||||
|
||||
- `history/0.3.0/0-pre.1.fix.1.md` ;
|
||||
- `history/0.3.0/0-pre.1.fix.2.md`.
|
||||
|
||||
Ces fichiers enregistrent uniquement les résultats effectivement fournis par l'utilisateur : audits statiques, `cargo fmt --all -- --check` et `cargo check --workspace`. Aucun Clippy, test ou smoke non exécuté n'est déclaré validé.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
- `deltas/0.3.0/0-pre.2.md` ;
|
||||
- `history/0.3.0/0-pre.1.fix.1.md` ;
|
||||
- `history/0.3.0/0-pre.1.fix.2.md`.
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
- `Cargo.toml` ;
|
||||
- `README.md` ;
|
||||
- `crates/games/game-reflex-poc/Cargo.toml` ;
|
||||
- `crates/games/game-snake-poc/Cargo.toml` ;
|
||||
- `docs/architecture/004-INPUT_AND_CONTROLS.md` ;
|
||||
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`.
|
||||
|
||||
## Suppressions
|
||||
|
||||
Aucune.
|
||||
|
||||
## Validations applicables
|
||||
|
||||
Audits statiques de préparation :
|
||||
|
||||
```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 deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
```
|
||||
|
||||
Validation utilisateur demandée :
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
cargo clippy -p game-snake-poc --all-targets --all-features -- -D warnings
|
||||
cargo clippy -p game-reflex-poc --all-targets --all-features -- -D warnings
|
||||
cargo test -p game-snake-poc
|
||||
cargo test -p game-reflex-poc
|
||||
cargo tree -p game-snake-poc --edges normal
|
||||
cargo tree -p game-reflex-poc --edges normal
|
||||
```
|
||||
|
||||
Les deux `cargo tree` doivent confirmer que les crates de gameplay ne tirent plus `engine-v1-platform-api`. Aucun smoke Desktop n'est requis : ni `engine-v1-sdl`, ni le mapping SDL, ni le runner Snake ne sont modifiés dans cette tranche.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation de `0-pre.2`, ouvrir `0-pre.3` pour l'adaptation WASM minimale de Snake conformément au plan actif. Aucun frontend Bootstrap/Vite n'est introduit avant `0-pre.4`.
|
||||
118
deltas/0.3.0/0-pre.3.md
Normal file
118
deltas/0.3.0/0-pre.3.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.3.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.3
|
||||
|
||||
## Base
|
||||
|
||||
Base requise : `0.3.0-0-pre.2`.
|
||||
|
||||
La validation complète de `0-pre.2` est consignée dans `history/0.3.0/0-pre.2.md`.
|
||||
|
||||
## Objet
|
||||
|
||||
Introduire l'adapter WASM minimal de Snake avant tout frontend navigateur :
|
||||
|
||||
- ajouter `game-snake-poc-wasm` comme crate d'application dédiée ;
|
||||
- réutiliser exclusivement `engine-v1-common` et `game-snake-poc` ;
|
||||
- exposer à `wasm-bindgen` les directions logiques et la scène portable ;
|
||||
- préserver la cadence de simulation en séparant événement d'entrée et `tick()` ;
|
||||
- documenter un chemin Cargo + `wasm-bindgen` direct sans orchestrateur Python ;
|
||||
- ne pas introduire Vite, Bootstrap, DOM, Tauri ou SDL3 dans cette tranche.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de `0.3.0-0-pre.2` à `0.3.0-0-pre.3`.
|
||||
|
||||
## Adapter Snake WASM
|
||||
|
||||
La nouvelle crate `crates/apps/game-snake-poc-wasm` contient une façade `lib.rs` et un runtime propriétaire séparé.
|
||||
|
||||
`SnakeWasmGame` possède :
|
||||
|
||||
- un `SnakeState` issu de la crate de gameplay ;
|
||||
- un `FixedStepRunner` à pas déterministe de 16 ms ;
|
||||
- un `InputState` en attente pour la prochaine update.
|
||||
|
||||
Les appels `left()`, `right()`, `up()` et `down()` ne font pas avancer la simulation. Ils remplacent la direction logique en attente. `tick()` consomme ensuite cette entrée une fois, la remet à zéro et avance le runner d'une update. La cadence reste ainsi sous la responsabilité du futur host navigateur.
|
||||
|
||||
L'API expose également le score, la longueur, la couleur de fond et les rectangles normalisés de l'`EngineScene`. Le futur frontend `0-pre.4` peut donc dessiner le Canvas sans accéder à la structure interne de Snake.
|
||||
|
||||
## Frontières conservées
|
||||
|
||||
`game-snake-poc-wasm` ne dépend pas de :
|
||||
|
||||
- `engine-v1-platform-api` ;
|
||||
- `engine-v1-sdl` ;
|
||||
- Tauri ;
|
||||
- DOM/Canvas Rust ;
|
||||
- code frontend TypeScript.
|
||||
|
||||
La duplication limitée des getters de scène avec `game-reflex-poc-wasm` est volontairement conservée à ce stade. Une abstraction commune ne sera extraite que si les POC suivants démontrent un besoin partagé suffisamment stable.
|
||||
|
||||
## Documentation et audits
|
||||
|
||||
L'architecture du workspace mentionne désormais l'adapter Snake WASM et le contrat d'entrée précise la file directionnelle consommée par `tick()`.
|
||||
|
||||
L'audit de distribution exige maintenant `crates/apps/game-snake-poc-wasm/Cargo.toml`, portant le nombre de chemins requis de 19 à 20.
|
||||
|
||||
Le plan actif est réconcilié avec la livraison réelle de `0-pre.3`. `docs/development/009-SNAKE_WEB_WASM_BUILD.md` documente le build natif Cargo + `wasm-bindgen`, avec tous les artefacts générés sous `../builds/sasedev-games/`.
|
||||
|
||||
## Historique
|
||||
|
||||
Ajout de `history/0.3.0/0-pre.2.md` avec les résultats réellement fournis par l'utilisateur : audits, formatage, check workspace, Clippy workspace strict, tests Snake/Reflex et graphes de dépendances.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
- `crates/apps/game-snake-poc-wasm/Cargo.toml` ;
|
||||
- `crates/apps/game-snake-poc-wasm/src/lib.rs` ;
|
||||
- `crates/apps/game-snake-poc-wasm/src/runtime.rs` ;
|
||||
- `crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs` ;
|
||||
- `deltas/0.3.0/0-pre.3.md` ;
|
||||
- `docs/development/009-SNAKE_WEB_WASM_BUILD.md` ;
|
||||
- `history/0.3.0/0-pre.2.md`.
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
- `Cargo.toml` ;
|
||||
- `README.md` ;
|
||||
- `docs/architecture/001-WORKSPACE_ARCHITECTURE.md` ;
|
||||
- `docs/000-README.md` ;
|
||||
- `docs/architecture/004-INPUT_AND_CONTROLS.md` ;
|
||||
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md` ;
|
||||
- `scripts/audit_distribution_layout.py`.
|
||||
|
||||
## Suppressions
|
||||
|
||||
Aucune.
|
||||
|
||||
## Validations applicables
|
||||
|
||||
Audits statiques de préparation :
|
||||
|
||||
```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 deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
```
|
||||
|
||||
Validation utilisateur demandée :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
cargo clippy -p game-snake-poc-wasm --all-targets --all-features -- -D warnings
|
||||
cargo test -p game-snake-poc-wasm
|
||||
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
|
||||
cargo tree -p game-snake-poc-wasm --edges normal
|
||||
```
|
||||
|
||||
Le `cargo tree` doit rester limité à `engine-v1-common`, `game-snake-poc`, `wasm-bindgen` et leurs dépendances nécessaires ; aucune dépendance SDL3, Tauri ou `engine-v1-platform-api` ne doit apparaître.
|
||||
|
||||
La commande `wasm-bindgen` doit produire les bindings navigateur sans écrire d'artefact généré dans le dépôt. Aucun build Vite/Bootstrap et aucun smoke navigateur ne sont requis dans cette tranche, car le frontend appartient à `0-pre.4`.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation de `0-pre.3`, ouvrir `0-pre.4` pour le frontend Web direct Vite/TypeScript, son shell HTML Bootstrap 5, le Canvas et les contrôles clavier/tactiles.
|
||||
56
deltas/0.3.0/0-pre.4.fix.1.md
Normal file
56
deltas/0.3.0/0-pre.4.fix.1.md
Normal file
@@ -0,0 +1,56 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.4.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.4.fix.1
|
||||
|
||||
## Objectif
|
||||
|
||||
Fermer les défauts découverts pendant la validation utilisateur de `0-pre.4` sans ouvrir le scope `0-pre.5`.
|
||||
|
||||
## Corrigé
|
||||
|
||||
- suppression de `compilerOptions.baseUrl`, retiré par TypeScript 7, tout en conservant le mapping `paths` et l'alias Vite `@snake-wasm` ;
|
||||
- réintégration de `simplebar` et `resize-observer-polyfill` depuis le template KSP comme dépendances runtime du shell navigateur ;
|
||||
- ajout du Sass SimpleBar KSP-derived et activation de `data-simplebar` sur `app-main` ;
|
||||
- maintien du header et du footer fixes avec scroll interne de la zone centrale lorsque son contenu dépasse la hauteur disponible ;
|
||||
- retrait des contraintes `h-100` internes qui empêchaient le contenu du shell de prendre sa hauteur naturelle ;
|
||||
- correction du plateau Snake de `1 / 1` vers `3 / 5`, cohérente avec la grille logique `12 × 20`, afin de conserver des cellules visuellement carrées ;
|
||||
- conservation du resize haute densité via `devicePixelRatio` à l'intérieur du ratio CSS corrigé ;
|
||||
- correction de la documentation qui classait à tort SimpleBar et le polyfill parmi les dépendances propres à Tauri ;
|
||||
- ajout d'une règle durable sur le scroll shell Web et extension de l'audit de distribution au Sass SimpleBar ;
|
||||
- consignation sous `history/0.3.0/0-pre.4.md` des validations réussies et de l'échec `TS5102` ayant déclenché ce fix.
|
||||
|
||||
## Non modifié
|
||||
|
||||
- gameplay Snake ;
|
||||
- adapter WASM et cadence `tick()` ;
|
||||
- mappings clavier/pointer ;
|
||||
- lifecycle complet, assets, logging et provenance prévus pour `0-pre.5` ;
|
||||
- route de smoke : `http://127.0.0.1:1434/main.html`.
|
||||
|
||||
## Validation demandée
|
||||
|
||||
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 -p game-snake-poc-wasm --all-targets --all-features -- -D warnings
|
||||
cargo test -p game-snake-poc-wasm
|
||||
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
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Smoke navigateur sur `http://127.0.0.1:1434/main.html` : vérifier clavier, boutons pointer/tactiles, cellules non déformées, scroll SimpleBar lorsque nécessaire et fixation du header/footer.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation du fix, `0-pre.5` peut fermer lifecycle/resize, assets, logging, provenance runtime et non-régression Desktop SDL3 conformément au plan actif.
|
||||
50
deltas/0.3.0/0-pre.4.fix.2.md
Normal file
50
deltas/0.3.0/0-pre.4.fix.2.md
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.4.fix.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.4.fix.2
|
||||
|
||||
## Objectif
|
||||
|
||||
Fermer les deux erreurs TypeScript découvertes pendant la validation utilisateur de `0-pre.4.fix.1`, sans modifier le gameplay ni ouvrir le scope `0-pre.5`.
|
||||
|
||||
## Corrigé
|
||||
|
||||
- `tsconfig.json` résout désormais `@snake-wasm` vers `game_snake_poc_wasm.d.ts`, généré par `wasm-bindgen`, afin que TypeScript consomme les types réels du bridge sans déclaration manuelle dupliquée ;
|
||||
- `vite.config.ts` conserve séparément l'alias runtime vers `game_snake_poc_wasm.js` ;
|
||||
- après validation de `canvas.getContext("2d")`, `game.ts` capture le contexte dans une constante `CanvasRenderingContext2D` non nullable utilisée par le callback de frame ;
|
||||
- consignation sous `history/0.3.0/0-pre.4.fix.1.md` de la gate réellement exécutée et des deux erreurs ayant déclenché ce fix ;
|
||||
- réconciliation du plan actif avec ce second fix de `0-pre.4`.
|
||||
|
||||
## Non modifié
|
||||
|
||||
- gameplay Snake ;
|
||||
- cadence et API du bridge WASM ;
|
||||
- ratio Canvas `3 / 5` ;
|
||||
- SimpleBar, header/footer fixes et shell Bootstrap ;
|
||||
- mappings clavier/pointer ;
|
||||
- lifecycle complet, assets, logging et provenance prévus pour `0-pre.5`.
|
||||
|
||||
## Validation demandée
|
||||
|
||||
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 -p game-snake-poc-wasm
|
||||
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
|
||||
cargo tree -p game-snake-poc-wasm --edges normal
|
||||
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
|
||||
```
|
||||
|
||||
Smoke navigateur sur `http://127.0.0.1:1434/main.html` : vérifier chargement WASM, clavier, boutons pointer/tactiles, ratio du plateau et scroll SimpleBar lorsque nécessaire.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation du fix, `0-pre.5` peut fermer lifecycle/resize, assets, logging, provenance runtime et non-régression Desktop SDL3 conformément au plan actif.
|
||||
64
deltas/0.3.0/0-pre.4.fix.3.md
Normal file
64
deltas/0.3.0/0-pre.4.fix.3.md
Normal file
@@ -0,0 +1,64 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.4.fix.3.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.4.fix.3
|
||||
|
||||
## Objectif
|
||||
|
||||
Aligner le scroll SimpleBar du host Snake Web sur le shell réellement utilisé par les applications Desk KSP après validation réussie du build `0-pre.4.fix.2`.
|
||||
|
||||
## Diagnostic
|
||||
|
||||
La comparaison avec `ksp-app-*-desk` confirme que le package et le Sass SimpleBar étaient présents, mais que le conteneur n'était pas structuré comme la référence :
|
||||
|
||||
- KSP garde `app-main` comme conteneur de hauteur bornée et non scrollable ;
|
||||
- un enfant `row h-100` porte la zone centrale ;
|
||||
- `data-simplebar` est attaché à `app-content app-scrollable h-100` ;
|
||||
- `app-scrollable` et `app-content` ont `height: 100%` / `max-height: 100%` ;
|
||||
- `main.ts` importe `resize-observer-polyfill` puis `simplebar` et installe le polyfill global avant l'initialisation de l'application.
|
||||
|
||||
Le point d'entrée TypeScript Snake suit déjà ce dernier contrat ; le défaut se situe donc dans la structure HTML/Sass.
|
||||
|
||||
## Corrigé
|
||||
|
||||
- `data-simplebar` est déplacé de `app-main` vers `app-content app-scrollable h-100` ;
|
||||
- ajout du conteneur `row g-0 h-100 flex-nowrap` comme dans le shell KSP ;
|
||||
- `app-main` reste borné à la hauteur disponible entre header et footer ;
|
||||
- `app-scrollable` et `app-content` portent explicitement `height: 100%` et `max-height: 100%` ;
|
||||
- la règle `GAME-PLATFORM-023` pérennise ce contrat lorsque le shell KSP/SimpleBar est réutilisé ;
|
||||
- `audit_distribution_layout.py` vérifie statiquement que `data-simplebar` reste sur la zone centrale et que le bootstrap TypeScript/Sass requis est présent ;
|
||||
- la documentation frontend et le plan `0.3.0` sont réconciliés ;
|
||||
- `history/0.3.0/0-pre.4.fix.2.md` consigne la validation réussie du build et le défaut visuel restant.
|
||||
|
||||
## Non modifié
|
||||
|
||||
- gameplay Snake ;
|
||||
- bridge WASM et cadence ;
|
||||
- ratio Canvas `3 / 5` ;
|
||||
- mapping clavier/pointer ;
|
||||
- dépendances npm ;
|
||||
- lifecycle complet, assets, logging et provenance prévus pour `0-pre.5`.
|
||||
|
||||
## Validation demandée
|
||||
|
||||
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 -p game-snake-poc-wasm
|
||||
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 && npm run dev)
|
||||
```
|
||||
|
||||
Smoke navigateur sur `http://127.0.0.1:1434/main.html` : réduire la hauteur de fenêtre ou utiliser un viewport assez bas pour forcer le dépassement vertical, puis vérifier que seule la zone centrale défile avec SimpleBar tandis que header et footer restent fixes. Vérifier aussi que Canvas, clavier et boutons pointer restent fonctionnels.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation du scroll SimpleBar, `0-pre.5` peut ouvrir l'intégration plateforme complète prévue par le plan actif.
|
||||
60
deltas/0.3.0/0-pre.4.md
Normal file
60
deltas/0.3.0/0-pre.4.md
Normal file
@@ -0,0 +1,60 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.4.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.4
|
||||
|
||||
## Objectif
|
||||
|
||||
Introduire le premier host navigateur direct Snake sans élargir encore le scope vers le lifecycle complet, les assets runtime, le logging ou la provenance de `0-pre.5`.
|
||||
|
||||
## Livré
|
||||
|
||||
- création de `Web/game-snake-poc/`, package Vite/TypeScript autonome hors workspace Cargo ;
|
||||
- reprise du template frontend des applications Desk KSP pour la structure `frontend/main.html`, `frontend/sass/`, `frontend/ts/` et le thème Bootstrap/Bootswatch Pulse ;
|
||||
- reprise uniquement des dépendances npm KSP utiles au navigateur direct : Bootstrap 5, Font Awesome, `sass-embedded`, TypeScript, Vite et types associés ;
|
||||
- exclusion volontaire des dépendances Tauri, SimpleBar et tracing spécifiques aux apps Desk ;
|
||||
- consommation des bindings `game-snake-poc-wasm` générés sous `../builds/sasedev-games/game-snake-poc-web/wasm/` ;
|
||||
- Canvas responsive prenant en compte `devicePixelRatio` ;
|
||||
- rendu de l'`EngineScene` normalisée sans dupliquer le gameplay ;
|
||||
- entrées clavier flèches, `WASD`, `ZQSD` et quatre boutons pointer/tactiles ;
|
||||
- état de chargement, score, longueur et retour d'erreur dans le shell ;
|
||||
- cache Vite et distribution frontend maintenus hors dépôt ;
|
||||
- audit de distribution étendu au host Web direct ;
|
||||
- enregistrement sous `history/0.3.0/0-pre.3.md` des validations utilisateur de `0-pre.3`.
|
||||
|
||||
## Hors périmètre
|
||||
|
||||
- lifecycle complet navigateur ;
|
||||
- assets runtime du POC ;
|
||||
- logging structuré Web ;
|
||||
- `RuntimeProvenance` ;
|
||||
- Tauri Snake ;
|
||||
- Android ;
|
||||
- extraction d'une abstraction Web commune non encore justifiée.
|
||||
|
||||
## Validation demandée
|
||||
|
||||
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 -p game-snake-poc-wasm --all-targets --all-features -- -D warnings
|
||||
cargo test -p game-snake-poc-wasm
|
||||
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
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Smoke navigateur : ouvrir `http://127.0.0.1:1434/main.html`, puis vérifier le chargement WASM, le Canvas, les quatre familles d'entrées prévues et le comportement responsive Desktop/mobile.
|
||||
|
||||
## Suite
|
||||
|
||||
`0-pre.5` ferme l'intégration plateforme du POC : lifecycle/resize complet, assets, logging, provenance runtime et non-régression Desktop SDL3.
|
||||
77
deltas/0.3.0/0-pre.5.fix.1.md
Normal file
77
deltas/0.3.0/0-pre.5.fix.1.md
Normal file
@@ -0,0 +1,77 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.5.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.5.fix.1
|
||||
|
||||
## Objectif
|
||||
|
||||
Corriger uniquement l'exposition des assets runtime du host Snake Web en développement, après validation partielle de `0-pre.5`.
|
||||
|
||||
## Défaut confirmé
|
||||
|
||||
`cargo`, WASM, `wasm-bindgen` et le build Vite passent. Sous `vite dev`, le frontend échoue néanmoins avant le démarrage du jeu lors de `fetchJson()` : les routes `common/data/runtime.json` et `game/data/game.json` ne sont pas servies comme attendu.
|
||||
|
||||
Les targets `vite-plugin-static-copy` utilisent des chemins source absolus. Le plugin conserve la structure source par défaut ; le `dest` seul ne constitue donc pas le contrat de route recherché.
|
||||
|
||||
## Correction
|
||||
|
||||
Les deux targets Vite utilisent désormais :
|
||||
|
||||
```text
|
||||
rename: { stripBase: true }
|
||||
```
|
||||
|
||||
Le contrat devient explicitement :
|
||||
|
||||
```text
|
||||
assets/common/data/runtime.json -> /common/data/runtime.json
|
||||
assets/game-snake-poc/data/game.json -> /game/data/game.json
|
||||
```
|
||||
|
||||
Ces routes doivent être identiques sous `vite dev` et dans `dist/`. Aucun asset source n'est dupliqué sous `Web/`.
|
||||
|
||||
## Garde-fou
|
||||
|
||||
`audit_distribution_layout.py` exige désormais `stripBase: true` sur les deux targets, en plus des namespaces `common/data` et `game/data`.
|
||||
|
||||
## Historique
|
||||
|
||||
`history/0.3.0/0-pre.5.md` consigne la validation partielle fournie par l'utilisateur et le défaut runtime exact.
|
||||
|
||||
## Non modifié
|
||||
|
||||
- gameplay Snake ;
|
||||
- adapter WASM/provenance ;
|
||||
- lifecycle/resize ;
|
||||
- logging frontend ;
|
||||
- SimpleBar/Canvas ;
|
||||
- runner Desktop SDL3 ;
|
||||
- ROADMAP et CHANGELOG.
|
||||
|
||||
## Validation demandée
|
||||
|
||||
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 -p engine-v1-platform-api --all-targets --all-features
|
||||
cargo test -p game-snake-poc-wasm --all-targets --all-features
|
||||
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
|
||||
cargo tree -p game-snake-poc-wasm --edges normal
|
||||
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
|
||||
```
|
||||
|
||||
Smoke sur `http://127.0.0.1:1434/main.html` :
|
||||
|
||||
1. `Assets` doit passer de `chargement…` à `snake / engine-v1` ;
|
||||
2. Snake doit démarrer ;
|
||||
3. la provenance doit quitter `unknown / unknown` dès qu'une classe/device et une entrée sont observées ;
|
||||
4. revalider lifecycle/resize/SimpleBar/Canvas/contrôles ;
|
||||
5. terminer par `cargo run -p game-snake-poc-desktop` pour la non-régression SDL3.
|
||||
118
deltas/0.3.0/0-pre.5.md
Normal file
118
deltas/0.3.0/0-pre.5.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- file: deltas/0.3.0/0-pre.5.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-0-pre.5
|
||||
|
||||
## Objectif
|
||||
|
||||
Fermer l'intégration plateforme du premier POC Snake Web direct après validation de `0-pre.4.fix.3` : lifecycle/resize navigateur, assets runtime, logging frontend structuré, provenance `Web/Wasm/Browser` et non-régression du runner Desktop SDL3.
|
||||
|
||||
## État précédent validé
|
||||
|
||||
`0-pre.4.fix.3` a été accepté par l'utilisateur le 2026-09-20 : build Vite propre et smoke visuel/fonctionnel conforme, y compris Canvas portrait, contrôles et scroll SimpleBar du shell KSP-derived. Le résultat est consigné sous `history/0.3.0/0-pre.4.fix.3.md`.
|
||||
|
||||
## Lifecycle et resize
|
||||
|
||||
Le host Web ne laisse plus une boucle `requestAnimationFrame` implicite posséder tout le lifecycle :
|
||||
|
||||
- `SnakeWebSession` expose `pause`, `resume`, `renderNow`, `refreshDeviceClass` et `dispose` ;
|
||||
- `visibilitychange` et `pagehide` suspendent les ticks ;
|
||||
- la reprise réinitialise l'origine temporelle et l'accumulateur afin d'éviter un rattrapage massif après un onglet caché ;
|
||||
- `pageshow` reprend et redessine ;
|
||||
- `ResizeObserver` redessine le Canvas et rafraîchit la classe d'appareil sans faire avancer la simulation ;
|
||||
- la destruction de page libère l'instance WASM.
|
||||
|
||||
Le fixed-step gameplay reste dans Rust/WASM et aucun événement lifecycle n'est ajouté à `game-snake-poc`.
|
||||
|
||||
## Assets Web
|
||||
|
||||
Le package Vite déclare `vite-plugin-static-copy` et package explicitement les deux assets-sondes depuis les sources canoniques :
|
||||
|
||||
```text
|
||||
assets/common/data/runtime.json -> common/data/runtime.json
|
||||
assets/game-snake-poc/data/game.json -> game/data/game.json
|
||||
```
|
||||
|
||||
`frontend/ts/assets.ts` charge et valide les deux JSON avant le démarrage WASM. Le shell affiche ensuite un statut d'assets afin que le smoke confirme le chargement réel. Aucune copie source durable n'est créée sous `Web/`.
|
||||
|
||||
## Logging navigateur
|
||||
|
||||
`frontend/ts/logging.ts` centralise les diagnostics Web sous forme d'événements `level / target / action / fields` vers la console du navigateur.
|
||||
|
||||
Ce logging est spécifique au frontend navigateur direct :
|
||||
|
||||
- il ne dépend pas de Tauri ;
|
||||
- il ne remplace pas `tracing` dans le code Rust ;
|
||||
- les runners natifs continuent d'utiliser `game-logging-lib`.
|
||||
|
||||
## Provenance runtime
|
||||
|
||||
`game-snake-poc-wasm` dépend désormais de `engine-v1-platform-api` et conserve une `RuntimeProvenance` réelle.
|
||||
|
||||
Dimensions fixes :
|
||||
|
||||
```text
|
||||
PlatformFamily = Web
|
||||
ExecutionModel = Wasm
|
||||
RuntimeHost = Browser
|
||||
```
|
||||
|
||||
Le host renseigne ensuite la classe `Desktop/Phone/Tablet/Unknown` et le profil d'entrée `Unknown/KeyboardMouse/Touch/Mixed` selon les capacités et inputs observés. Ces dimensions sont exposées par le bridge WASM et affichées dans le shell.
|
||||
|
||||
La crate de gameplay `game-snake-poc` reste sans dépendance plateforme.
|
||||
|
||||
## Règles et documentation
|
||||
|
||||
- `GAME-ASSET-007` fixe le packaging Web `common/` + `game/` sans copie source ;
|
||||
- `GAME-TRACE-005` distingue logging frontend navigateur et tracing Rust ;
|
||||
- `GAME-PLATFORM-024` fixe pause/reprise sans catch-up massif ;
|
||||
- `GAME-PLATFORM-025` fixe la provenance navigateur via `engine-v1-platform-api` ;
|
||||
- l'architecture assets/provenance, la documentation tracing/frontend et les gates sont réconciliées ;
|
||||
- `audit_distribution_layout.py` vérifie désormais les fichiers et branchements statiques de cette intégration.
|
||||
|
||||
## Non modifié
|
||||
|
||||
- règles de gameplay Snake ;
|
||||
- runner Snake Desktop SDL3 ;
|
||||
- moteur SDL3 ;
|
||||
- Android/Tauri ;
|
||||
- ROADMAP et CHANGELOG, réservés à la consolidation prévue par le plan.
|
||||
|
||||
## Validation demandée
|
||||
|
||||
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 -p engine-v1-platform-api --all-targets --all-features
|
||||
cargo test -p game-snake-poc-wasm --all-targets --all-features
|
||||
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
|
||||
cargo tree -p game-snake-poc-wasm --edges normal
|
||||
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
|
||||
```
|
||||
|
||||
Smoke navigateur sur `http://127.0.0.1:1434/main.html` :
|
||||
|
||||
1. vérifier que `Assets` passe à `snake / engine-v1` avant le statut `Prêt` ;
|
||||
2. vérifier que la provenance affiche `web / browser / wasm / ... / ...` ;
|
||||
3. utiliser clavier/souris puis tactile si disponible et vérifier l'évolution du profil d'entrée (`keyboard-mouse`, `touch` ou `mixed`) ;
|
||||
4. masquer l'onglet quelques secondes puis revenir : Snake ne doit pas accélérer pour rattraper le temps caché ;
|
||||
5. redimensionner la fenêtre : Canvas et provenance device doivent rester cohérents sans saut de gameplay ;
|
||||
6. revalider SimpleBar, clavier et contrôles pointer/tactiles.
|
||||
|
||||
Puis vérifier la non-régression Desktop :
|
||||
|
||||
```bash
|
||||
cargo run -p game-snake-poc-desktop
|
||||
```
|
||||
|
||||
## Suite
|
||||
|
||||
Si cette gate ne révèle ni duplication structurante ni défaut de frontière, la tranche conditionnelle `0-pre.6` est omise et la version peut entrer directement en `2-beta.1`. Sinon, `0-pre.6` ne traite que la correction/extraction prouvée par cette validation.
|
||||
64
deltas/0.3.0/2-beta.1.md
Normal file
64
deltas/0.3.0/2-beta.1.md
Normal file
@@ -0,0 +1,64 @@
|
||||
<!-- file: deltas/0.3.0/2-beta.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-2-beta.1
|
||||
|
||||
## Objectif
|
||||
|
||||
Entrer en beta avec un scope `0.3.0` feature-complete et exécuter une validation large du POC Snake Web direct et de son témoin Desktop SDL3, sans introduire de nouveau comportement.
|
||||
|
||||
## Baseline acceptée
|
||||
|
||||
`0.3.0-0-pre.5.fix.1` est validée : audits, check/Clippy, tests plateforme/WASM, build WASM, build Vite, assets/provenance/inputs dans le navigateur et smoke Desktop SDL3 passent. Aucun défaut structurel ne justifie `0-pre.6`, qui est donc omise.
|
||||
|
||||
## Changements
|
||||
|
||||
- passage de la version workspace à `0.3.0-2-beta.1` ;
|
||||
- passage du package frontend Snake Web au jalon `0.3.0-2-beta.1` ;
|
||||
- ajout de l'historique validé `0-pre.5.fix.1` ;
|
||||
- réconciliation du plan `0.3.0` avec l'omission de `0-pre.6` ;
|
||||
- documentation de la gate beta release Web/Desktop.
|
||||
|
||||
Aucun gameplay, bridge WASM, runtime SDL3, shell HTML/TypeScript ou contrat d'asset n'est modifié par cette tranche.
|
||||
|
||||
## Validation attendue
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Build et smoke Desktop Snake release :
|
||||
|
||||
```bash
|
||||
cargo build -p game-snake-poc-desktop --release
|
||||
../builds/sasedev-games/target/release/game-snake-poc-desktop
|
||||
```
|
||||
|
||||
Build WASM release et frontend production :
|
||||
|
||||
```bash
|
||||
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-web/wasm \
|
||||
--out-name game_snake_poc_wasm
|
||||
|
||||
(cd Web/game-snake-poc && npm install && npm run build && npm run preview)
|
||||
```
|
||||
|
||||
Ouvrir `http://127.0.0.1:4174/main.html` puis vérifier : jeu visible, clavier/pointer, ratio Canvas, SimpleBar, assets `snake / engine-v1`, provenance cohérente, resize et pause/reprise sans rattrapage massif après masquage d'onglet.
|
||||
|
||||
## Après validation
|
||||
|
||||
Si cette gate beta est propre, la tranche suivante est `0.3.0-2-beta.2` pour la consolidation documentaire et la transmission : plan, documentation durable, `CHANGELOG.md`, `ROADMAP.md`, history et prompt de la version/session suivante.
|
||||
67
deltas/0.3.0/2-beta.2.fix.1.md
Normal file
67
deltas/0.3.0/2-beta.2.fix.1.md
Normal file
@@ -0,0 +1,67 @@
|
||||
<!-- file: deltas/0.3.0/2-beta.2.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-2-beta.2.fix.1
|
||||
|
||||
## Base requise
|
||||
|
||||
`0.3.0-2-beta.2` validée mécaniquement par l'utilisateur le 2026-09-20.
|
||||
|
||||
## Objectif
|
||||
|
||||
Corriger la transmission documentaire de `2-beta.2` : rendre le prompt `0.3.1` suffisamment autonome pour limiter les rappels conversationnels et les fixes évitables, sans reproduire l'intégralité des règles KSP ni modifier le code/runtime.
|
||||
|
||||
## Changements
|
||||
|
||||
- enrichissement de `prompts/003-V0_3_1_START_PROMPT.md` avec ordre de lecture, handoff `0.3.0`, cadrage `pre.1`, invariants Tauri/Android/WASM, documentation, commandes, validations, forecast et condition de fin de session ;
|
||||
- ajout de `docs/rules/PROMPT_STRUCTURE.md`, inspiré du principe KSP de prompt autonome mais volontairement plus compact ;
|
||||
- ajout de règles explicites sur le rôle de `deltas/`/`history/`, le timing `ROADMAP`/`CHANGELOG`/prompt suivant et la séparation des validations utilisateur ;
|
||||
- ajout d'une politique `DOC-CRATE-*` pour décider utilement de `README.md`/`USAGE.md` sans créer de fichiers cérémoniels ;
|
||||
- clarification du cycle du prompt suivant : rédaction pendant la consolidation, vérification/complément à la première RC gelée ;
|
||||
- clarification du versionnement des fixes strictement documentaires : le delta porte `.fix.1` mais `workspace.package.version` et les packages runtime restent `0.3.0-2-beta.2` ;
|
||||
- ajout de `history/0.3.0/2-beta.2.md` avec les résultats réellement validés et le motif humain du fix ;
|
||||
- réconciliation du plan `0.3.0` avec ce correctif de transmission.
|
||||
|
||||
`CHANGELOG.md` et `ROADMAP.md` ne sont pas modifiés : le fix ne change ni la publication candidate ni le scope produit.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
docs/rules/PROMPT_STRUCTURE.md
|
||||
history/0.3.0/2-beta.2.md
|
||||
deltas/0.3.0/2-beta.2.fix.1.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
RULES.md
|
||||
docs/000-README.md
|
||||
docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md
|
||||
docs/rules/RULES_DOCUMENTATION.md
|
||||
docs/rules/RULES_SESSION_PLANNING.md
|
||||
docs/rules/VERSION_WORKFLOW.md
|
||||
prompts/003-V0_3_1_START_PROMPT.md
|
||||
```
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
Aucun.
|
||||
|
||||
## Version technique
|
||||
|
||||
Ce correctif est strictement documentaire. Conformément à `VER-DOCFIX-001`, il ne modifie ni `workspace.package.version` ni la version du package Web : la version technique reste `0.3.0-2-beta.2`.
|
||||
|
||||
## Validation attendue
|
||||
|
||||
```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/npm ni smoke n'est requis pour ce fix : aucun fichier consommé par le build/runtime n'est modifié.
|
||||
|
||||
## Après validation
|
||||
|
||||
Si la revue humaine du prompt enrichi est satisfaisante, ouvrir `0.3.0-3-rc.1` et appliquer la matrice RC. La RC vérifie/complète le prompt `0.3.1`, écrit l'entrée RC du `CHANGELOG.md` et ne rouvre pas le scope fonctionnel.
|
||||
66
deltas/0.3.0/2-beta.2.fix.2.md
Normal file
66
deltas/0.3.0/2-beta.2.fix.2.md
Normal file
@@ -0,0 +1,66 @@
|
||||
<!-- file: deltas/0.3.0/2-beta.2.fix.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-2-beta.2.fix.2
|
||||
|
||||
## Base requise
|
||||
|
||||
`0.3.0-2-beta.2.fix.1`.
|
||||
|
||||
## Objectif
|
||||
|
||||
Finaliser la gouvernance des commandes et des validations transmise au prompt `0.3.1`, afin d'éviter les gates surdimensionnées, les logs pollués et les workflows Tauri incorrects.
|
||||
|
||||
Ce correctif est strictement documentaire. Il ne modifie aucun code, manifeste runtime, dépendance, configuration de build ni version technique.
|
||||
|
||||
## Décisions consolidées
|
||||
|
||||
- un fix strictement documentaire ne modifie aucune version Cargo/npm/Gradle/Tauri/Android ou autre version consommée par le runtime/build ;
|
||||
- tout changement de code Rust, manifeste Cargo, feature ou dépendance Rust déclenche `cargo fmt --all`, `cargo fmt --all -- --check`, `cargo check --workspace` puis `cargo clippy --workspace --all-targets --all-features -- -D warnings` ;
|
||||
- les audits Python sont sélectionnés selon les fichiers réellement touchés ;
|
||||
- `cargo test -p <crate> --all-targets --all-features` est la stratégie normale ;
|
||||
- `cargo test --workspace --all-targets --all-features` est réservé à un ou quelques jalons explicitement planifiés ou à une portée transverse incertaine ;
|
||||
- `cargo tree` n'est exécuté que si les dépendances/features changent ou qu'un diagnostic de frontière le justifie ;
|
||||
- toute commande nécessitant un changement de répertoire utilise `(cd <dir> && <commande>)` ;
|
||||
- dans une app Tauri, npm sert à gérer les dépendances frontend ; les smokes passent par `cargo tauri dev` et le packaging final/prefinal par `cargo tauri build`, les hooks Tauri possédant Vite/TypeScript/WASM.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
history/0.3.0/2-beta.2.fix.1.md
|
||||
deltas/0.3.0/2-beta.2.fix.2.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md
|
||||
docs/rules/PROMPT_STRUCTURE.md
|
||||
docs/rules/RULES_COMMANDS.md
|
||||
docs/rules/RULES_SESSION_PLANNING.md
|
||||
docs/rules/RULES_VALIDATION_MATRIX.md
|
||||
docs/rules/VERSION_WORKFLOW.md
|
||||
prompts/003-V0_3_1_START_PROMPT.md
|
||||
```
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
Aucun.
|
||||
|
||||
## Version technique
|
||||
|
||||
Aucune version technique n'est modifiée. La workspace et le package Web restent `0.3.0-2-beta.2`.
|
||||
|
||||
## Validation attendue
|
||||
|
||||
Les seuls fichiers touchés sont Markdown. L'audit applicable est donc :
|
||||
|
||||
```bash
|
||||
python3 scripts/audit_markdown_tables.py docs prompts deltas history
|
||||
```
|
||||
|
||||
Aucun `cargo fmt`, `cargo check`, Clippy, test Cargo, `cargo tree`, audit de distribution, npm, Tauri ou smoke n'est requis pour ce correctif documentaire.
|
||||
|
||||
## Après validation
|
||||
|
||||
Si la revue humaine confirme ces règles, ouvrir `0.3.0-3-rc.1`. La RC appliquera la matrice finale, écrira l'entrée RC du `CHANGELOG.md` et vérifiera une dernière fois le prompt `0.3.1` sans rouvrir le scope fonctionnel.
|
||||
45
deltas/0.3.0/2-beta.2.md
Normal file
45
deltas/0.3.0/2-beta.2.md
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- file: deltas/0.3.0/2-beta.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-2-beta.2
|
||||
|
||||
## Objectif
|
||||
|
||||
Consolider la version feature-complete après validation de `2-beta.1`, préparer le gel RC et transmettre un contexte autonome à `0.3.1`, sans modifier le comportement runtime.
|
||||
|
||||
## Baseline acceptée
|
||||
|
||||
`0.3.0-2-beta.1` est validée : audits, format/check/Clippy, 34 tests workspace, Desktop Snake release et Web/WASM release passent.
|
||||
|
||||
## Changements
|
||||
|
||||
- passage de la version workspace et du package Web à `0.3.0-2-beta.2` ;
|
||||
- ajout de `history/0.3.0/2-beta.1.md` ;
|
||||
- classement des lignes `0.3.0` réellement livrées dans `ROADMAP.md` ;
|
||||
- ajout d'une baseline architecturale consolidée du POC Snake Web direct ;
|
||||
- ajout d'une matrice RC spécifique à `0.3.0` ;
|
||||
- réconciliation de l'étude `pre.1` avec le contrat autoritaire des archives de tag ;
|
||||
- mise à jour du plan actif avec le résultat beta ;
|
||||
- ajout du prompt `003-V0_3_1_START_PROMPT.md` pour le second host Snake Tauri Android.
|
||||
|
||||
`CHANGELOG.md` est revu mais ne reçoit pas d'entrée beta : `DOC-CHG-002` et `DOC-CHG-003` réservent la synthèse publiée aux RC et stables. La matière de la future entrée RC est consolidée dans les documents durables de cette tranche.
|
||||
|
||||
## Validation attendue
|
||||
|
||||
```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
|
||||
(cd Web/game-snake-poc && npm install && npm run build)
|
||||
```
|
||||
|
||||
Aucun test/smoke complet n'est requis par ce delta documentaire + métadonnées : `2-beta.1` vient de valider les artefacts release, et aucun code/runtime n'est modifié.
|
||||
|
||||
## Après validation
|
||||
|
||||
Si cette consolidation est acceptée, produire `0.3.0-3-rc.1` : gel fonctionnel, entrée RC du `CHANGELOG.md`, historique `2-beta.2`, réexécution de la matrice RC et vérification finale du prompt `0.3.1`.
|
||||
88
deltas/0.3.0/3-rc.1.md
Normal file
88
deltas/0.3.0/3-rc.1.md
Normal file
@@ -0,0 +1,88 @@
|
||||
<!-- file: deltas/0.3.0/3-rc.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0-3-rc.1
|
||||
|
||||
## Base requise
|
||||
|
||||
`0.3.0-2-beta.2.fix.2`.
|
||||
|
||||
Cette base a été validée par revue humaine et audit Markdown proportionnel au correctif documentaire.
|
||||
|
||||
## Objectif
|
||||
|
||||
Créer la candidate de publication `0.3.0` sans rouvrir le scope fonctionnel.
|
||||
|
||||
## Gel RC
|
||||
|
||||
Le scope `0.3.0` est gelé. Cette tranche n'ajoute :
|
||||
|
||||
- aucune fonctionnalité ;
|
||||
- aucune capability ;
|
||||
- aucun refactor architectural ;
|
||||
- aucune dépendance ;
|
||||
- aucun changement de gameplay ou de runtime.
|
||||
|
||||
Seuls les correctifs autorisés par `VER-RC-*` pourront suivre sous `3-rc.1.fix.N`.
|
||||
|
||||
## Changements
|
||||
|
||||
- passage de `workspace.package.version` à `0.3.0-3-rc.1` ;
|
||||
- passage du package Web Snake à `0.3.0-3-rc.1` ;
|
||||
- affichage de la candidate courante dans `README.md` ;
|
||||
- création de l'entrée RC `0.3.0-3-rc.1` dans `CHANGELOG.md` ;
|
||||
- enregistrement de la validation `2-beta.2.fix.2` sous `history/0.3.0/` ;
|
||||
- réconciliation du plan `0.3.0` avec l'ouverture de la RC ;
|
||||
- revue finale de `prompts/003-V0_3_1_START_PROMPT.md` : aucune modification supplémentaire requise.
|
||||
|
||||
Les métadonnées Android historiques restent inchangées : `0.3.0` ne livre pas un nouveau host Android. Le package Tauri Reflex historique reste également hors scope.
|
||||
|
||||
## Validation RC
|
||||
|
||||
Appliquer `docs/testing/004-V0_3_0_RC_VALIDATION_MATRIX.md`.
|
||||
|
||||
### Gate workspace
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Le test workspace complet est volontairement exécuté à ce jalon RC final. Aucun `cargo tree` n'est requis puisque les dépendances/features ne changent pas.
|
||||
|
||||
### Desktop Snake release
|
||||
|
||||
```bash
|
||||
cargo build -p game-snake-poc-desktop --release
|
||||
../builds/sasedev-games/target/release/game-snake-poc-desktop
|
||||
```
|
||||
|
||||
Critères : démarrage, rendu, contrôles et arrêt propre.
|
||||
|
||||
### Web/WASM release
|
||||
|
||||
```bash
|
||||
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-web/wasm \
|
||||
--out-name game_snake_poc_wasm
|
||||
|
||||
(cd Web/game-snake-poc && npm install && npm run build && npm run preview)
|
||||
```
|
||||
|
||||
Ouvrir `http://127.0.0.1:4174/main.html` et confirmer Canvas, inputs, SimpleBar, assets, provenance, resize, lifecycle et absence d'erreur runtime bloquante.
|
||||
|
||||
## Après validation
|
||||
|
||||
Si la gate RC est propre et qu'aucun correctif n'est nécessaire, la promotion vers `0.3.0` est mécanique : versions stables, entrée stable du `CHANGELOG.md`, historique RC et delta de release, sans nouveau scope.
|
||||
54
deltas/0.3.0/rel.001.md
Normal file
54
deltas/0.3.0/rel.001.md
Normal file
@@ -0,0 +1,54 @@
|
||||
<!-- file: deltas/0.3.0/rel.001.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.0 — release stable
|
||||
|
||||
## Base
|
||||
|
||||
Base validée : `0.3.0-3-rc.1`.
|
||||
|
||||
## Objet
|
||||
|
||||
Promouvoir mécaniquement la candidate validée vers `0.3.0` sans introduire de nouveau comportement.
|
||||
|
||||
## Changements
|
||||
|
||||
La release stable :
|
||||
|
||||
- passe `workspace.package.version` de `0.3.0-3-rc.1` à `0.3.0` ;
|
||||
- passe le package frontend Web Snake de `0.3.0-3-rc.1` à `0.3.0` ;
|
||||
- positionne `README.md` sur la stable `0.3.0` et indique `0.3.1` comme prochaine version planifiée mais non démarrée ;
|
||||
- ajoute l'entrée stable `0.3.0` dans `CHANGELOG.md` ;
|
||||
- enregistre la validation effective de la RC dans `history/0.3.0/3-rc.1.md` ;
|
||||
- clôt le plan `0.3.0` et réconcilie la baseline architecturale avec l'état effectivement publié.
|
||||
|
||||
Les métadonnées Android et le package frontend Reflex/Tauri ne sont pas modifiés : ils n'appartiennent pas au chemin de distribution ajouté par `0.3.0`.
|
||||
|
||||
Le prompt `prompts/003-V0_3_1_START_PROMPT.md` est conservé tel que validé en RC.
|
||||
|
||||
## Frontière de release
|
||||
|
||||
Aucun changement de gameplay, de Rust source, de dépendance, de build frontend, 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 donc que les gates proportionnelles au changement de version et à la documentation :
|
||||
|
||||
```bash
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md CHANGELOG.md docs deltas history
|
||||
|
||||
cargo check --workspace
|
||||
|
||||
(cd Web/game-snake-poc && npm install && npm run build)
|
||||
```
|
||||
|
||||
`cargo fmt`, Clippy, la suite workspace complète, `cargo tree`, l'audit de distribution et les smokes ne sont pas répétés : cette promotion ne touche ni code Rust, ni dépendance, ni layout de distribution, et ces gates ont déjà été validées sur `0.3.0-3-rc.1` fonctionnellement identique.
|
||||
|
||||
## Publication
|
||||
|
||||
Après validation de ce delta :
|
||||
|
||||
- commit de release ;
|
||||
- tag stable `v0.3.0` ;
|
||||
- prochaine session ouverte uniquement à partir de la stable taggée `v0.3.0` et de `prompts/003-V0_3_1_START_PROMPT.md`.
|
||||
146
deltas/0.3.1/0-pre.1.md
Normal file
146
deltas/0.3.1/0-pre.1.md
Normal 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é.
|
||||
181
deltas/0.3.1/0-pre.2.fix.1.md
Normal file
181
deltas/0.3.1/0-pre.2.fix.1.md
Normal 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.
|
||||
2
deltas/0.3.1/0-pre.2.fix.2.delete.txt
Normal file
2
deltas/0.3.1/0-pre.2.fix.2.delete.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
crates/apps/game-snake-poc-tauri/scripts/prepare-android-gradle.mjs
|
||||
history/0.3.1/0-pre.2.md
|
||||
122
deltas/0.3.1/0-pre.2.fix.2.md
Normal file
122
deltas/0.3.1/0-pre.2.fix.2.md
Normal 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`.
|
||||
128
deltas/0.3.1/0-pre.2.fix.3.md
Normal file
128
deltas/0.3.1/0-pre.2.fix.3.md
Normal 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
161
deltas/0.3.1/0-pre.2.md
Normal 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
152
deltas/0.3.1/0-pre.3.md
Normal 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`.
|
||||
94
deltas/0.3.1/0-pre.4.fix.1.md
Normal file
94
deltas/0.3.1/0-pre.4.fix.1.md
Normal 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
54
deltas/0.3.1/0-pre.4.md
Normal 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
76
deltas/0.3.1/2-beta.1.md
Normal 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
64
deltas/0.3.1/3-rc.1.md
Normal 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
56
deltas/0.3.1/rel.001.md
Normal 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
204
deltas/0.3.3/0-pre.1.md
Normal 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
180
deltas/0.3.3/0-pre.2.md
Normal 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`.
|
||||
4
deltas/0.3.3/0-pre.3.delete.txt
Normal file
4
deltas/0.3.3/0-pre.3.delete.txt
Normal 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
253
deltas/0.3.3/0-pre.3.md
Normal 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
92
deltas/0.3.3/0-pre.4.md
Normal 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`.
|
||||
1
deltas/0.3.3/0-pre.5.delete.txt
Normal file
1
deltas/0.3.3/0-pre.5.delete.txt
Normal file
@@ -0,0 +1 @@
|
||||
scripts/build_android_rust.py
|
||||
126
deltas/0.3.3/0-pre.5.md
Normal file
126
deltas/0.3.3/0-pre.5.md
Normal 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
139
deltas/0.3.3/2-beta.1.md
Normal 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`.
|
||||
82
deltas/0.3.3/3-rc.1.fix.1.md
Normal file
82
deltas/0.3.3/3-rc.1.fix.1.md
Normal 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
66
deltas/0.3.3/3-rc.1.md
Normal 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
59
deltas/0.3.3/rel.001.md
Normal 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.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/000-README.md -->
|
||||
<!-- version: 21 -->
|
||||
<!-- version: 31 -->
|
||||
|
||||
# Documentation games.sasedev
|
||||
|
||||
@@ -12,6 +12,13 @@
|
||||
- [`ideas/000-README.md`](ideas/000-README.md) — rôle des idées non engagées et règles de maturation.
|
||||
- [`studies/000-README.md`](studies/000-README.md) — rôle des études comparatives non normatives.
|
||||
|
||||
## 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/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/001-WORKSPACE_ARCHITECTURE.md`](architecture/001-WORKSPACE_ARCHITECTURE.md) — workspace, générations de moteur, jeux, runners, assets et Android.
|
||||
@@ -51,7 +58,7 @@
|
||||
|
||||
## Règles
|
||||
|
||||
Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](rules/RULES_DOCUMENTATION.md) pour la gouvernance documentaire, [`rules/RULES_COMMANDS.md`](rules/RULES_COMMANDS.md) pour la politique d'exécution, [`rules/RULES_VALIDATION_MATRIX.md`](rules/RULES_VALIDATION_MATRIX.md) pour la matrice des gates, [`rules/RULES_SESSION_PLANNING.md`](rules/RULES_SESSION_PLANNING.md) pour le cadrage des sessions et [`rules/RULES_SERVER_HOSTING.md`](rules/RULES_SERVER_HOSTING.md) pour la portabilité d'auto-hébergement.
|
||||
Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](rules/RULES_DOCUMENTATION.md) pour la gouvernance documentaire, [`rules/RULES_COMMANDS.md`](rules/RULES_COMMANDS.md) pour la politique d'exécution, [`rules/RULES_VALIDATION_MATRIX.md`](rules/RULES_VALIDATION_MATRIX.md) pour la matrice des gates, [`rules/RULES_SESSION_PLANNING.md`](rules/RULES_SESSION_PLANNING.md) pour le cadrage des sessions, [`rules/PROMPT_STRUCTURE.md`](rules/PROMPT_STRUCTURE.md) pour le contrat des prompts de reprise et [`rules/RULES_SERVER_HOSTING.md`](rules/RULES_SERVER_HOSTING.md) pour la portabilité d'auto-hébergement.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -63,11 +70,17 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](ru
|
||||
- [`testing/001-TEST_ARCHITECTURE.md`](testing/001-TEST_ARCHITECTURE.md) — séparation `unit_tests/` / `tests/`, tests ciblés et tracing de test.
|
||||
- [`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/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/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/006-ANDROID_RUST_NATIVE_BUILD.md`](development/006-ANDROID_RUST_NATIVE_BUILD.md) — build `cdylib` Rust Android, cargo-ndk, packaging `jniLibs` et symbole `SDL_main`.
|
||||
- [`development/007-ANDROID_JNI_BRIDGE.md`](development/007-ANDROID_JNI_BRIDGE.md) — frontière Java/JNI minimale, version de contrat et séparation avec l'input SDL3.
|
||||
- [`development/008-WASM_TAURI_POC.md`](development/008-WASM_TAURI_POC.md) — baseline historique Reflex Tauri/WebAssembly et génération WASM associée.
|
||||
- [`development/009-SNAKE_WEB_WASM_BUILD.md`](development/009-SNAKE_WEB_WASM_BUILD.md) — build Cargo + `wasm-bindgen` natif de l'adapter Snake pour le navigateur direct.
|
||||
- [`development/010-SNAKE_WEB_FRONTEND.md`](development/010-SNAKE_WEB_FRONTEND.md) — host Web direct Snake sous Vite/TypeScript, shell Bootstrap 5, Canvas, lifecycle, assets, provenance et contrôles clavier/tactiles.
|
||||
|
||||
## Historique validé
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/architecture/001-WORKSPACE_ARCHITECTURE.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Architecture du workspace
|
||||
|
||||
@@ -22,7 +22,8 @@ games.sasedev/
|
||||
│ ├── game-reflex-poc-desktop/
|
||||
│ ├── game-snake-poc-desktop/
|
||||
│ ├── game-reflex-poc-tauri/
|
||||
│ └── game-reflex-poc-wasm/
|
||||
│ ├── game-reflex-poc-wasm/
|
||||
│ └── game-snake-poc-wasm/
|
||||
├── assets/
|
||||
│ ├── common/
|
||||
│ ├── game-reflex-poc/
|
||||
@@ -31,6 +32,8 @@ games.sasedev/
|
||||
│ ├── common/
|
||||
│ ├── game-reflex-poc/
|
||||
│ └── game-snake-poc/
|
||||
├── Web/
|
||||
│ └── game-snake-poc/
|
||||
├── docs/
|
||||
├── deltas/
|
||||
├── prompts/
|
||||
@@ -69,15 +72,12 @@ Le runtime devra conserver une distinction logique entre ressources communes et
|
||||
|
||||
Chaque crate de jeu reste une bibliothèque. Une crate binaire Desktop séparée sous `crates/apps/` la consomme pour permettre les itérations locales rapides. Cette séparation évite d’introduire `main`, des choix de plateforme ou du code de lancement dans le gameplay réutilisable.
|
||||
|
||||
## Variante Tauri / WebAssembly
|
||||
## Adapters WebAssembly et variante Tauri
|
||||
|
||||
La variante Tauri est distincte du runner SDL3 natif.
|
||||
Les adapters WASM sont distincts des runners SDL3 natifs et ne déplacent aucune règle de gameplay hors des crates de jeu.
|
||||
|
||||
`game-reflex-poc-tauri` porte deux faces du même launcher :
|
||||
`game-reflex-poc-wasm` adapte le POC Reflex à `wasm-bindgen` pour le host Tauri/WebView existant. `game-snake-poc-wasm` adapte séparément Snake pour le premier host navigateur direct de `0.3.0`. Dans les deux cas, l'adapter expose l'état et la scène portable ; il ne possède pas les règles du jeu.
|
||||
|
||||
- une bibliothèque `wasm32-unknown-unknown` qui adapte `game-reflex-poc` à `wasm-bindgen` ;
|
||||
- un binaire natif Tauri qui héberge la WebView locale.
|
||||
`game-reflex-poc-tauri` reste le binaire natif Tauri qui héberge la WebView Reflex locale. Son frontend Vite/TypeScript pilote un Canvas et appelle l'adapter Reflex WASM. La crate Tauri suit le modèle des apps Desk KSP : façade `lib.rs`, pont `tauri.rs`, modules propriétaires séparés et frontend local à la crate.
|
||||
|
||||
Le frontend statique sous `Tauri/game-reflex-poc/frontend/` pilote un Canvas et appelle l'adaptateur WASM. Les règles Reflex restent exclusivement dans `game-reflex-poc`.
|
||||
|
||||
La crate Tauri suit le modèle des apps Desk KSP : façade `lib.rs`, pont `tauri.rs`, modules propriétaires séparés et frontend Vite/TypeScript local à la crate.
|
||||
Le host Web Snake direct réside sous `Web/game-snake-poc/`. Ce package Vite/TypeScript consomme `game-snake-poc-wasm`, reprend la structure HTML/Sass/TypeScript et le thème Bootstrap/Bootswatch des applications Desk KSP sans dépendre de Tauri, traduit clavier/tactile vers les quatre directions logiques et dessine l'`EngineScene` dans un Canvas. Il reste extérieur au workspace Cargo et ne contient aucune règle de gameplay.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user