Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e24b7840c | |||
| bf9ead329a | |||
| f270ed8f86 | |||
| 881d5afacd | |||
| 74cdb177e9 | |||
| fa17ea0ca5 | |||
| bbecae5e0d | |||
| 4fb521f54e | |||
| 4e2be3cc9a | |||
| 27978c3782 | |||
| 0ad980a06b | |||
| 0584b01de3 | |||
| 01df07d6af | |||
| 58ecd667b0 | |||
| 19f31082ea | |||
| b605874bea | |||
| 5854ac3d07 | |||
| f770f99a4a |
@@ -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 {
|
||||
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,8 +1,53 @@
|
||||
<!-- file: CHANGELOG.md -->
|
||||
<!-- version: 16 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# Changelog
|
||||
|
||||
## 0.3.4 — 2026-09-21
|
||||
|
||||
- publication stable de la première baseline realtime, avec `game-realtime-transport-lib` comme contrat binaire transport-neutral et `game-realtime-websocket-lib` comme backend Tokio/tokio-tungstenite ;
|
||||
- client `ws://`, listener serveur, split send/receive, close propre, limites message/frame/write-buffer, deadlines connect/send/close et erreurs transport-neutral validés sans runtime ni task backend privé ;
|
||||
- robustesse validée par tests loopback et négatifs : payloads hors limite, Text interdit, peer drop abrupt, handshake silencieux borné et mappings capacité/backpressure ;
|
||||
- smoke runtime `game-realtime-websocket-smoke: PASS` validé hors harness de test sur localhost avec port éphémère, échange binaire bidirectionnel et fermeture propre ;
|
||||
- `0.3.4-beta.1` a validé le workspace complet avec 53 tests et l'arbre inverse a confirmé que seul le launcher de smoke dépend du backend WebSocket ; `0.3.4-rc.1` a ensuite repassé audits, check/Clippy, 18 tests realtime ciblés, smoke et frontière de dépendances sans défaut ;
|
||||
- documentation durable des deux crates realtime livrée et prompt `0.3.5` préparé pour évaluer WebTransport/QUIC sur la même frontière avec WebSocket comme baseline/fallback ;
|
||||
- aucun TLS direct, wire codec final, protocole session, synchronisation gameplay, simulation authoritative ou serveur Uroburas n'est introduit dans `0.3.4`.
|
||||
|
||||
Les détails des phases `alpha`, `beta`, `rc` et du correctif `alpha.3.fix.1` restent dans `deltas/0.3.4/` et `history/0.3.4/`.
|
||||
|
||||
## 0.3.4-rc.1 — 2026-09-21
|
||||
|
||||
- gel fonctionnel de la baseline realtime : `game-realtime-transport-lib` porte le contrat binaire transport-neutral et `game-realtime-websocket-lib` son backend Tokio/tokio-tungstenite, sans dépendance transport dans les moteurs ou le gameplay ;
|
||||
- client `ws://`, listener serveur, split send/receive, close propre, erreurs transport-neutral, limites de message/frame/write-buffer et deadlines connect/send/close validés sans runtime ni task backend privé ;
|
||||
- robustesse validée sur localhost : round-trip binaire, payload hors limite, Text interdit, peer drop abrupt, handshake silencieux borné, mappings capacité/backpressure et smoke runtime public `game-realtime-websocket-smoke: PASS` ;
|
||||
- `0.3.4-beta.1` validée avec audits propres, `cargo check`, Clippy strict et `cargo test --workspace --all-targets --all-features` : 53 tests réussis, puis smoke runtime séparé et arbre inverse confirmant que seul le launcher de smoke dépend du backend WebSocket ;
|
||||
- consolidation de la documentation durable des deux crates realtime et préparation du prompt `0.3.5` pour évaluer WebTransport/QUIC sur la même frontière, avec WebSocket conservé comme fallback de référence ;
|
||||
- aucun TLS direct, protocole de session, synchronisation gameplay, simulation authoritative ou serveur Uroburas n'est introduit dans `0.3.4`.
|
||||
|
||||
La RC n'ouvre aucun nouveau scope. Seuls les correctifs nécessaires à la publication selon `VER-RC-*` peuvent produire `0.3.4-rc.1.fix.N`.
|
||||
|
||||
## 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 ;
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -1,5 +1,5 @@
|
||||
# file: Cargo.toml
|
||||
# version: 80
|
||||
# version: 95
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
@@ -9,10 +9,13 @@ members = [
|
||||
"crates/engines/engine-v1-sdl",
|
||||
"crates/games/game-reflex-poc",
|
||||
"crates/games/game-snake-poc",
|
||||
"crates/apps/game-realtime-websocket-smoke",
|
||||
"crates/apps/game-reflex-poc-desktop",
|
||||
"crates/apps/game-snake-poc-desktop",
|
||||
"crates/common/game-assets-lib",
|
||||
"crates/common/game-logging-lib",
|
||||
"crates/common/game-realtime-transport-lib",
|
||||
"crates/common/game-realtime-websocket-lib",
|
||||
"crates/apps/game-android-entrypoint",
|
||||
"crates/apps/game-reflex-poc-tauri",
|
||||
"crates/apps/game-reflex-poc-wasm",
|
||||
@@ -21,7 +24,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.1"
|
||||
version = "0.3.5-alpha.1"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/games"
|
||||
@@ -29,6 +32,7 @@ authors = ["Sasedev <games@sasedev.com>"]
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
futures-util = { version = "0.3.34", default-features = false }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sdl3 = "^0.20"
|
||||
tracing = "0.1.44"
|
||||
@@ -38,6 +42,8 @@ tracing-subscriber = "0.3.23"
|
||||
tauri = "2"
|
||||
tauri-build = "2"
|
||||
tauri-plugin-tracing = "^0.3"
|
||||
tokio = "1.53.1"
|
||||
tokio-tungstenite = { version = "0.30.0", default-features = false }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
10
README.md
10
README.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: README.md -->
|
||||
<!-- version: 51 -->
|
||||
<!-- version: 63 -->
|
||||
|
||||
# games.sasedev
|
||||
|
||||
@@ -25,11 +25,11 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
|
||||
|
||||
## Baseline
|
||||
|
||||
Version stable de référence : `0.3.1`.
|
||||
Version stable de référence : `0.3.4`.
|
||||
|
||||
Version suivante active planifiée : `0.3.3` (non démarrée). `0.3.2` reste différée.
|
||||
Version active : `0.3.5-alpha.1`. `0.3.2` reste différée.
|
||||
|
||||
La stable `0.3.1` conserve Snake Tauri Android comme POC de référence validé sur AVD x86_64 et appareil ARM64 réel. La prochaine version active `0.3.3` est centrée sur Android SDL3 natif multi-ABI, le packaging Gradle/AAB et l’évaluation explicite de la compatibilité avec des versions Android plus anciennes.
|
||||
La stable `0.3.4` livre la première baseline realtime : contrat binaire transport-neutral, backend WebSocket Tokio/tokio-tungstenite, limites et deadlines, tests loopback/robustesse, smoke runtime localhost public et frontières de dépendances empêchant moteurs et gameplay de dépendre d'un backend concret. `0.3.5-alpha.1` ouvre le POC WebTransport/QUIC par un cadrage sans backend nouveau : audit de la stable, étude de l'écosystème actuel, choix de la famille `web-transport`, stratégie TLS/fallback, smoke tests et découpage fin avant implémentation.
|
||||
|
||||
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.
|
||||
|
||||
@@ -45,4 +45,4 @@ Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-sna
|
||||
|
||||
## Diagnostics et tests
|
||||
|
||||
Les socles transverses `crates/common/game-assets-lib` et `crates/common/game-logging-lib` fournissent respectivement la résolution logique des assets et le tracing commun. Les tests unitaires résident hors `src/` sous `unit_tests/`; les tests d’intégration/environnement résident sous `tests/`.
|
||||
Les socles transverses `crates/common/game-assets-lib` et `crates/common/game-logging-lib` fournissent respectivement la résolution logique des assets et le tracing commun. Le realtime est séparé entre `game-realtime-transport-lib`, contrat binaire transport-neutral, et `game-realtime-websocket-lib`, backend Tokio/tokio-tungstenite ; leurs responsabilités et leur consommation sont documentées dans leurs README/USAGE locaux. `game-realtime-websocket-smoke` fournit la preuve runtime localhost hors harness de test. Les tests unitaires résident hors `src/` sous `unit_tests/`; les tests d’intégration/environnement résident sous `tests/`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 25 -->
|
||||
<!-- version: 27 -->
|
||||
|
||||
# Roadmap
|
||||
|
||||
@@ -48,8 +48,8 @@ Le détail historique des prereleases `0.1.0-*` reste dans `deltas/0.1.0/` et `h
|
||||
|
||||
- (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.
|
||||
- ( ) `0.3.3` — Android SDL3 natif multi-ABI avec Cargo/Gradle natifs, APK universal pour tests, AAB comme cible de distribution et évaluation du `minSdk`/support d’anciennes versions Android, sans script Python de build.
|
||||
- ( ) `0.3.4` — API de transport realtime + WebSocket/tokio-tungstenite baseline, sans serveur Uroburas Mode 3.
|
||||
- (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.
|
||||
- (x) `0.3.4` — API de transport realtime + WebSocket/tokio-tungstenite baseline livrée, 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`.
|
||||
|
||||
|
||||
4
RULES.md
4
RULES.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: RULES.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Index normatif games.sasedev
|
||||
|
||||
@@ -17,7 +17,7 @@ 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 cycle de transmission ;
|
||||
9. [`docs/rules/RULES_SESSION_PLANNING.md`](docs/rules/RULES_SESSION_PLANNING.md) — cadrage `alpha.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.
|
||||
|
||||
|
||||
21
crates/apps/game-realtime-websocket-smoke/Cargo.toml
Normal file
21
crates/apps/game-realtime-websocket-smoke/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
# file: crates/apps/game-realtime-websocket-smoke/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "game-realtime-websocket-smoke"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
game-logging-lib = { path = "../../common/game-logging-lib" }
|
||||
game-realtime-transport-lib = { path = "../../common/game-realtime-transport-lib" }
|
||||
game-realtime-websocket-lib = { path = "../../common/game-realtime-websocket-lib" }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "time"] }
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
108
crates/apps/game-realtime-websocket-smoke/src/main.rs
Normal file
108
crates/apps/game-realtime-websocket-smoke/src/main.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
// file: crates/apps/game-realtime-websocket-smoke/src/main.rs
|
||||
// version: 1
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Executable localhost smoke for the public games.sasedev WebSocket realtime transport path.
|
||||
|
||||
use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import
|
||||
|
||||
const CLIENT_PAYLOAD: &[u8] = b"games.sasedev-client-smoke";
|
||||
const SERVER_PAYLOAD: &[u8] = b"games.sasedev-server-smoke";
|
||||
const SMOKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
const TRACING_TARGET: &str = "games::realtime::websocket::smoke";
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> std::process::ExitCode {
|
||||
let _logging_guard = match game_logging_lib::init_console_tracing() {
|
||||
std::result::Result::Ok(guard) => guard,
|
||||
std::result::Result::Err(error) => {
|
||||
eprintln!("failed to initialize realtime WebSocket smoke tracing: {error}");
|
||||
return std::process::ExitCode::FAILURE;
|
||||
},
|
||||
};
|
||||
tracing::info!(target: TRACING_TARGET, "realtime WebSocket smoke started");
|
||||
let result = tokio::time::timeout(SMOKE_TIMEOUT, run_smoke()).await;
|
||||
return match result {
|
||||
std::result::Result::Ok(std::result::Result::Ok(())) => {
|
||||
tracing::info!(target: TRACING_TARGET, "realtime WebSocket smoke passed");
|
||||
println!("game-realtime-websocket-smoke: PASS");
|
||||
std::process::ExitCode::SUCCESS
|
||||
},
|
||||
std::result::Result::Ok(std::result::Result::Err(error)) => {
|
||||
tracing::error!(target: TRACING_TARGET, detail = error.as_str(), "realtime WebSocket smoke failed");
|
||||
eprintln!("game-realtime-websocket-smoke: FAIL: {error}");
|
||||
std::process::ExitCode::FAILURE
|
||||
},
|
||||
std::result::Result::Err(_) => {
|
||||
tracing::error!(target: TRACING_TARGET, timeout_ms = SMOKE_TIMEOUT.as_millis(), "realtime WebSocket smoke timed out");
|
||||
eprintln!("game-realtime-websocket-smoke: FAIL: smoke timed out");
|
||||
std::process::ExitCode::FAILURE
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async fn run_smoke() -> std::result::Result<(), String> {
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind(bind_address).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(format!("listener bind failed: {error}")),
|
||||
};
|
||||
let endpoint = format!("ws://{}/", listener.local_addr());
|
||||
tracing::info!(target: TRACING_TARGET, endpoint = endpoint.as_str(), "loopback endpoint bound");
|
||||
let (server_result, client_result) = tokio::join!(listener.accept(), game_realtime_websocket_lib::connect(endpoint.as_str()));
|
||||
let server_connection = match server_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(format!("server accept failed: {error}")),
|
||||
};
|
||||
let client_connection = match client_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(format!("client connect failed: {error}")),
|
||||
};
|
||||
let (mut server_sender, mut server_receiver) = server_connection.split();
|
||||
let (mut client_sender, mut client_receiver) = client_connection.split();
|
||||
let client_message = game_realtime_transport_lib::TransportMessage::new(CLIENT_PAYLOAD.to_vec());
|
||||
if let std::result::Result::Err(error) = client_sender.send(client_message).await {
|
||||
return std::result::Result::Err(format!("client send failed: {error}"));
|
||||
}
|
||||
let server_received = match server_receiver.receive().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(format!("server receive failed: {error}")),
|
||||
};
|
||||
if !receive_matches(server_received, CLIENT_PAYLOAD) {
|
||||
return std::result::Result::Err(String::from("server did not receive the expected client payload"));
|
||||
}
|
||||
let server_message = game_realtime_transport_lib::TransportMessage::new(SERVER_PAYLOAD.to_vec());
|
||||
if let std::result::Result::Err(error) = server_sender.send(server_message).await {
|
||||
return std::result::Result::Err(format!("server send failed: {error}"));
|
||||
}
|
||||
let client_received = match client_receiver.receive().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(format!("client receive failed: {error}")),
|
||||
};
|
||||
if !receive_matches(client_received, SERVER_PAYLOAD) {
|
||||
return std::result::Result::Err(String::from("client did not receive the expected server payload"));
|
||||
}
|
||||
if let std::result::Result::Err(error) = client_sender.close().await {
|
||||
return std::result::Result::Err(format!("client close failed: {error}"));
|
||||
}
|
||||
let server_close = match server_receiver.receive().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(format!("server close observation failed: {error}")),
|
||||
};
|
||||
if server_close != game_realtime_transport_lib::TransportReceive::Closed {
|
||||
return std::result::Result::Err(String::from("server did not observe the client close handshake"));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn receive_matches(receive: game_realtime_transport_lib::TransportReceive, expected: &[u8]) -> bool {
|
||||
return match receive {
|
||||
game_realtime_transport_lib::TransportReceive::Message(message) => message.as_bytes() == expected,
|
||||
game_realtime_transport_lib::TransportReceive::Closed => false,
|
||||
};
|
||||
}
|
||||
14
crates/common/game-realtime-transport-lib/Cargo.toml
Normal file
14
crates/common/game-realtime-transport-lib/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
# file: crates/common/game-realtime-transport-lib/Cargo.toml
|
||||
# version: 1
|
||||
|
||||
[package]
|
||||
name = "game-realtime-transport-lib"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
42
crates/common/game-realtime-transport-lib/README.md
Normal file
42
crates/common/game-realtime-transport-lib/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- file: crates/common/game-realtime-transport-lib/README.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# game-realtime-transport-lib
|
||||
|
||||
Contrat realtime transport-neutral de `games.sasedev`. La crate définit uniquement la forme minimale d'une connexion établie, de ses moitiés d'envoi/réception, des payloads binaires opaques et des erreurs stables visibles par les couches supérieures.
|
||||
|
||||
## Responsabilité
|
||||
|
||||
La crate possède :
|
||||
|
||||
- `TransportMessage`, buffer binaire possédé ;
|
||||
- `TransportReceive`, qui distingue message reçu et fermeture distante propre ;
|
||||
- `RealtimeConnection`, `RealtimeSender` et `RealtimeReceiver` ;
|
||||
- `TransportError` et `TransportErrorKind` comme catégories backend-neutral.
|
||||
|
||||
Elle ne possède pas :
|
||||
|
||||
- l'établissement d'une connexion réseau ;
|
||||
- Tokio ou un autre runtime ;
|
||||
- WebSocket, WebTransport, QUIC, HTTP ou TLS ;
|
||||
- un codec wire ;
|
||||
- une session joueur/room ;
|
||||
- la synchronisation gameplay ou la simulation authoritative.
|
||||
|
||||
## Contrat async
|
||||
|
||||
`RealtimeConnection::split()` consomme une connexion établie et retourne des moitiés d'envoi et de réception indépendantes. Les opérations async sont exposées par des futures associées GAT plutôt que par `async-trait` ou `Box<dyn Future>`.
|
||||
|
||||
Le contrat n'impose volontairement aucune borne `Send` aux futures. Un backend natif peut fournir des futures `Send`, tandis qu'un futur backend navigateur/WASM ne doit pas être exclu par une contrainte de threading qui ne relève pas de l'abstraction transport.
|
||||
|
||||
## Sémantique
|
||||
|
||||
Les payloads sont toujours binaires et opaques. Une couche supérieure pourra ultérieurement leur appliquer un codec wire ou un protocole de session sans modifier cette crate.
|
||||
|
||||
Une fermeture distante propre est représentée par `TransportReceive::Closed`. Elle n'est pas convertie en erreur I/O générique. Les erreurs utilisent une catégorie stable (`Timeout`, `MessageTooLarge`, `Backpressure`, `Protocol`, etc.) et un détail de diagnostic, sans exposer le type d'erreur du backend concret.
|
||||
|
||||
## Dépendances et sens d'ownership
|
||||
|
||||
Cette crate ne dépend d'aucun backend realtime. Les implémentations concrètes dépendent d'elle, jamais l'inverse.
|
||||
|
||||
Le premier backend de référence est `game-realtime-websocket-lib`. Le POC WebTransport/QUIC prévu ensuite doit d'abord challenger cette même frontière avant toute généralisation supplémentaire.
|
||||
42
crates/common/game-realtime-transport-lib/src/connection.rs
Normal file
42
crates/common/game-realtime-transport-lib/src/connection.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
// file: crates/common/game-realtime-transport-lib/src/connection.rs
|
||||
// version: 1
|
||||
|
||||
/// Established realtime connection that owns independent send and receive halves.
|
||||
pub trait RealtimeConnection {
|
||||
/// Send half produced when this connection is split.
|
||||
type Sender: crate::RealtimeSender;
|
||||
/// Receive half produced when this connection is split.
|
||||
type Receiver: crate::RealtimeReceiver;
|
||||
|
||||
/// Consumes the connection and returns independent send and receive halves.
|
||||
fn split(self) -> (Self::Sender, Self::Receiver);
|
||||
}
|
||||
|
||||
/// Send half of an established realtime connection.
|
||||
pub trait RealtimeSender {
|
||||
/// Future returned by [`RealtimeSender::send`].
|
||||
type SendFuture<'a>: core::future::Future<Output = Result<(), crate::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
/// Future returned by [`RealtimeSender::close`].
|
||||
type CloseFuture<'a>: core::future::Future<Output = Result<(), crate::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
/// Sends one owned opaque binary message, respecting backend backpressure.
|
||||
fn send(&mut self, message: crate::TransportMessage) -> Self::SendFuture<'_>;
|
||||
|
||||
/// Initiates a clean local transport-level close.
|
||||
fn close(&mut self) -> Self::CloseFuture<'_>;
|
||||
}
|
||||
|
||||
/// Receive half of an established realtime connection.
|
||||
pub trait RealtimeReceiver {
|
||||
/// Future returned by [`RealtimeReceiver::receive`].
|
||||
type ReceiveFuture<'a>: core::future::Future<Output = Result<crate::TransportReceive, crate::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
/// Receives one binary message or observes a clean remote close.
|
||||
fn receive(&mut self) -> Self::ReceiveFuture<'_>;
|
||||
}
|
||||
82
crates/common/game-realtime-transport-lib/src/error.rs
Normal file
82
crates/common/game-realtime-transport-lib/src/error.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
// file: crates/common/game-realtime-transport-lib/src/error.rs
|
||||
// version: 1
|
||||
|
||||
/// Stable transport-neutral category for a realtime operation failure.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum TransportErrorKind {
|
||||
/// An endpoint or transport configuration is invalid.
|
||||
InvalidConfiguration,
|
||||
/// A client connection could not be established.
|
||||
Connect,
|
||||
/// A server endpoint could not be bound.
|
||||
Bind,
|
||||
/// A server could not accept an incoming connection.
|
||||
Accept,
|
||||
/// An operation exceeded its configured deadline.
|
||||
Timeout,
|
||||
/// A payload exceeds the configured transport bound.
|
||||
MessageTooLarge,
|
||||
/// The transport cannot currently accept more outbound data within its configured bounds.
|
||||
Backpressure,
|
||||
/// The requested operation requires a connection that is already closed.
|
||||
Closed,
|
||||
/// An underlying I/O operation failed.
|
||||
Io,
|
||||
/// The backend reported a protocol-level or transport-specific failure.
|
||||
Protocol,
|
||||
/// The operation was explicitly aborted or cancelled when that distinction is observable.
|
||||
Aborted,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for TransportErrorKind {
|
||||
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
return match self {
|
||||
crate::TransportErrorKind::InvalidConfiguration => formatter.write_str("invalid configuration"),
|
||||
crate::TransportErrorKind::Connect => formatter.write_str("connect failure"),
|
||||
crate::TransportErrorKind::Bind => formatter.write_str("bind failure"),
|
||||
crate::TransportErrorKind::Accept => formatter.write_str("accept failure"),
|
||||
crate::TransportErrorKind::Timeout => formatter.write_str("operation timed out"),
|
||||
crate::TransportErrorKind::MessageTooLarge => formatter.write_str("message too large"),
|
||||
crate::TransportErrorKind::Backpressure => formatter.write_str("transport backpressure"),
|
||||
crate::TransportErrorKind::Closed => formatter.write_str("connection closed"),
|
||||
crate::TransportErrorKind::Io => formatter.write_str("I/O failure"),
|
||||
crate::TransportErrorKind::Protocol => formatter.write_str("protocol failure"),
|
||||
crate::TransportErrorKind::Aborted => formatter.write_str("operation aborted"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Transport-neutral operation failure with a stable category and diagnostic detail.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TransportError {
|
||||
detail: String,
|
||||
kind: crate::TransportErrorKind,
|
||||
}
|
||||
|
||||
impl TransportError {
|
||||
/// Creates a transport failure from a stable category and backend-neutral diagnostic detail.
|
||||
#[must_use]
|
||||
pub fn new(kind: crate::TransportErrorKind, detail: impl Into<String>) -> Self {
|
||||
return Self { detail: detail.into(), kind };
|
||||
}
|
||||
|
||||
/// Borrows the diagnostic detail associated with the failure.
|
||||
#[must_use]
|
||||
pub fn detail(&self) -> &str {
|
||||
return self.detail.as_str();
|
||||
}
|
||||
|
||||
/// Returns the stable transport-neutral failure category.
|
||||
#[must_use]
|
||||
pub fn kind(&self) -> crate::TransportErrorKind {
|
||||
return self.kind;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for TransportError {
|
||||
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
return write!(formatter, "{}: {}", self.kind, self.detail);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for TransportError {}
|
||||
31
crates/common/game-realtime-transport-lib/src/lib.rs
Normal file
31
crates/common/game-realtime-transport-lib/src/lib.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
// file: crates/common/game-realtime-transport-lib/src/lib.rs
|
||||
// version: 1
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Transport-neutral realtime connection contracts for games.sasedev.
|
||||
|
||||
mod connection;
|
||||
mod error;
|
||||
mod message;
|
||||
|
||||
/// Re-export of an established realtime connection that can be split into independent halves.
|
||||
pub use self::connection::RealtimeConnection;
|
||||
/// Re-export of the receive half of an established realtime connection.
|
||||
pub use self::connection::RealtimeReceiver;
|
||||
/// Re-export of the send half of an established realtime connection.
|
||||
pub use self::connection::RealtimeSender;
|
||||
/// Re-export of a transport-neutral operation failure.
|
||||
pub use self::error::TransportError;
|
||||
/// Re-export of transport-neutral error categories.
|
||||
pub use self::error::TransportErrorKind;
|
||||
/// Re-export of an owned opaque binary transport message.
|
||||
pub use self::message::TransportMessage;
|
||||
/// Re-export of the result of one receive operation.
|
||||
pub use self::message::TransportReceive;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/contract.rs"]
|
||||
mod tests;
|
||||
49
crates/common/game-realtime-transport-lib/src/message.rs
Normal file
49
crates/common/game-realtime-transport-lib/src/message.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
// file: crates/common/game-realtime-transport-lib/src/message.rs
|
||||
// version: 1
|
||||
|
||||
/// Owned opaque binary payload carried by a realtime transport.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TransportMessage {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TransportMessage {
|
||||
/// Creates a transport message from owned bytes.
|
||||
#[must_use]
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
return Self { bytes };
|
||||
}
|
||||
|
||||
/// Borrows the opaque payload bytes.
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
return self.bytes.as_slice();
|
||||
}
|
||||
|
||||
/// Consumes the message and returns its owned payload bytes.
|
||||
#[must_use]
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
return self.bytes;
|
||||
}
|
||||
|
||||
/// Returns whether the payload is empty.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
return self.bytes.is_empty();
|
||||
}
|
||||
|
||||
/// Returns the payload length in bytes.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
return self.bytes.len();
|
||||
}
|
||||
}
|
||||
|
||||
/// Outcome of one successful transport receive operation.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TransportReceive {
|
||||
/// One opaque binary message was received.
|
||||
Message(crate::TransportMessage),
|
||||
/// The remote peer completed a clean transport-level close.
|
||||
Closed,
|
||||
}
|
||||
131
crates/common/game-realtime-transport-lib/unit_tests/contract.rs
Normal file
131
crates/common/game-realtime-transport-lib/unit_tests/contract.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
// file: crates/common/game-realtime-transport-lib/unit_tests/contract.rs
|
||||
// version: 1
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct TestSender {
|
||||
closed: bool,
|
||||
sent_bytes: usize,
|
||||
}
|
||||
|
||||
impl crate::RealtimeSender for TestSender {
|
||||
type SendFuture<'a>
|
||||
= std::future::Ready<Result<(), crate::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
type CloseFuture<'a>
|
||||
= std::future::Ready<Result<(), crate::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn send(&mut self, message: crate::TransportMessage) -> Self::SendFuture<'_> {
|
||||
self.sent_bytes = self.sent_bytes.saturating_add(message.len());
|
||||
return std::future::ready(Ok(()));
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Self::CloseFuture<'_> {
|
||||
self.closed = true;
|
||||
return std::future::ready(Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct TestReceiver;
|
||||
|
||||
impl crate::RealtimeReceiver for TestReceiver {
|
||||
type ReceiveFuture<'a>
|
||||
= std::future::Ready<Result<crate::TransportReceive, crate::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn receive(&mut self) -> Self::ReceiveFuture<'_> {
|
||||
return std::future::ready(Ok(crate::TransportReceive::Closed));
|
||||
}
|
||||
}
|
||||
|
||||
struct TestConnection;
|
||||
|
||||
impl crate::RealtimeConnection for TestConnection {
|
||||
type Sender = TestSender;
|
||||
type Receiver = TestReceiver;
|
||||
|
||||
fn split(self) -> (Self::Sender, Self::Receiver) {
|
||||
return (TestSender { closed: false, sent_bytes: 0 }, TestReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_ready<T>(future: impl core::future::Future<Output = T>) -> T {
|
||||
let mut future = std::pin::pin!(future);
|
||||
let waker = std::task::Waker::noop();
|
||||
let mut context = std::task::Context::from_waker(waker);
|
||||
return match core::future::Future::poll(future.as_mut(), &mut context) {
|
||||
std::task::Poll::Ready(value) => value,
|
||||
std::task::Poll::Pending => unreachable!("test future unexpectedly remained pending"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_preserves_owned_binary_payload() {
|
||||
let message = crate::TransportMessage::new(vec![0, 1, 2, 255]);
|
||||
assert_eq!(message.as_bytes(), &[0, 1, 2, 255]);
|
||||
assert_eq!(message.len(), 4);
|
||||
assert!(!message.is_empty());
|
||||
assert_eq!(message.into_bytes(), vec![0, 1, 2, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_message_remains_a_valid_transport_payload() {
|
||||
let message = crate::TransportMessage::new(Vec::new());
|
||||
assert!(message.is_empty());
|
||||
assert_eq!(message.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_remote_close_is_distinct_from_transport_error() {
|
||||
let mut receiver = TestReceiver;
|
||||
let received = poll_ready(crate::RealtimeReceiver::receive(&mut receiver));
|
||||
assert_eq!(received, Ok(crate::TransportReceive::Closed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_split_produces_independent_contract_halves() {
|
||||
let (mut sender, mut receiver) = crate::RealtimeConnection::split(TestConnection);
|
||||
assert_eq!(poll_ready(crate::RealtimeSender::send(&mut sender, crate::TransportMessage::new(vec![1, 2, 3]))), Ok(()));
|
||||
assert_eq!(sender.sent_bytes, 3);
|
||||
assert_eq!(poll_ready(crate::RealtimeReceiver::receive(&mut receiver)), Ok(crate::TransportReceive::Closed));
|
||||
assert_eq!(poll_ready(crate::RealtimeSender::close(&mut sender)), Ok(()));
|
||||
assert!(sender.closed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_receive_variant_preserves_payload() {
|
||||
let received = crate::TransportReceive::Message(crate::TransportMessage::new(vec![4, 5, 6]));
|
||||
assert_eq!(received, crate::TransportReceive::Message(crate::TransportMessage::new(vec![4, 5, 6])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transport_error_preserves_stable_kind_and_diagnostic_detail() {
|
||||
let error = crate::TransportError::new(crate::TransportErrorKind::Timeout, "send deadline exceeded");
|
||||
assert_eq!(error.kind(), crate::TransportErrorKind::Timeout);
|
||||
assert_eq!(error.detail(), "send deadline exceeded");
|
||||
assert_eq!(error.to_string(), "operation timed out: send deadline exceeded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transport_error_kinds_have_stable_human_readable_labels() {
|
||||
let cases = [
|
||||
(crate::TransportErrorKind::InvalidConfiguration, "invalid configuration"),
|
||||
(crate::TransportErrorKind::Connect, "connect failure"),
|
||||
(crate::TransportErrorKind::Bind, "bind failure"),
|
||||
(crate::TransportErrorKind::Accept, "accept failure"),
|
||||
(crate::TransportErrorKind::Timeout, "operation timed out"),
|
||||
(crate::TransportErrorKind::MessageTooLarge, "message too large"),
|
||||
(crate::TransportErrorKind::Backpressure, "transport backpressure"),
|
||||
(crate::TransportErrorKind::Closed, "connection closed"),
|
||||
(crate::TransportErrorKind::Io, "I/O failure"),
|
||||
(crate::TransportErrorKind::Protocol, "protocol failure"),
|
||||
(crate::TransportErrorKind::Aborted, "operation aborted"),
|
||||
];
|
||||
for (kind, expected) in cases {
|
||||
assert_eq!(kind.to_string(), expected);
|
||||
}
|
||||
}
|
||||
24
crates/common/game-realtime-websocket-lib/Cargo.toml
Normal file
24
crates/common/game-realtime-websocket-lib/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
# file: crates/common/game-realtime-websocket-lib/Cargo.toml
|
||||
# version: 3
|
||||
|
||||
[package]
|
||||
name = "game-realtime-websocket-lib"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures-util = { workspace = true, features = ["sink", "std"] }
|
||||
game-realtime-transport-lib = { path = "../game-realtime-transport-lib" }
|
||||
tokio = { workspace = true, features = ["net", "time"] }
|
||||
tokio-tungstenite = { workspace = true, features = ["connect", "handshake"] }
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["macros", "rt", "time"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
49
crates/common/game-realtime-websocket-lib/README.md
Normal file
49
crates/common/game-realtime-websocket-lib/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
<!-- file: crates/common/game-realtime-websocket-lib/README.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# game-realtime-websocket-lib
|
||||
|
||||
Backend WebSocket de référence pour le contrat `game-realtime-transport-lib`, fondé sur Tokio et `tokio-tungstenite`.
|
||||
|
||||
## Responsabilité
|
||||
|
||||
La crate possède :
|
||||
|
||||
- connexion client `ws://` ;
|
||||
- listener serveur TCP + upgrade WebSocket ;
|
||||
- types concrets `WebSocketConnection`, `WebSocketSender` et `WebSocketReceiver` ;
|
||||
- mapping WebSocket vers le contrat binaire transport-neutral ;
|
||||
- limites de message/frame/write-buffer ;
|
||||
- deadlines connect/handshake, send et close ;
|
||||
- tracing du domaine `games::realtime::websocket` ;
|
||||
- mapping des erreurs Tungstenite vers `TransportErrorKind`.
|
||||
|
||||
Elle ne possède pas le runtime Tokio : le consommateur crée et exécute son runtime. La crate ne lance ni runtime global, ni thread runtime privé, ni task backend détachée pour une connexion de base.
|
||||
|
||||
## Frontières
|
||||
|
||||
Le backend transporte des octets opaques. Il ne connaît ni joueur, ni room, ni tick, ni snapshot, ni codec wire, ni protocole de session.
|
||||
|
||||
Les messages Text ne font pas partie du contrat et sont rejetés comme erreur de protocole. Ping/Pong reste un détail WebSocket. Une fermeture distante propre devient `TransportReceive::Closed`.
|
||||
|
||||
La baseline active utilise `ws://`. Aucune feature TLS de `tokio-tungstenite` n'est activée ; une politique `wss://` directe n'est pas introduite tant qu'un besoin produit et une stratégie de certificats ne sont pas démontrés.
|
||||
|
||||
## Configuration
|
||||
|
||||
`WebSocketConfig::default()` fournit des bornes produit explicites :
|
||||
|
||||
```text
|
||||
message maximum 1 MiB
|
||||
frame maximum 1 MiB
|
||||
write buffer target 64 KiB
|
||||
write buffer maximum 2 MiB
|
||||
connect/handshake 10 s
|
||||
send 5 s
|
||||
close 2 s
|
||||
```
|
||||
|
||||
Les builders `with_*` permettent d'adapter ces limites avant `connect_with_config` ou `WebSocketListener::bind_with_config`. `validate()` refuse les configurations incohérentes avant l'établissement réseau.
|
||||
|
||||
## Utilisation
|
||||
|
||||
Voir [`USAGE.md`](USAGE.md) pour les points d'entrée client/serveur, la configuration et le smoke de référence.
|
||||
78
crates/common/game-realtime-websocket-lib/USAGE.md
Normal file
78
crates/common/game-realtime-websocket-lib/USAGE.md
Normal file
@@ -0,0 +1,78 @@
|
||||
<!-- file: crates/common/game-realtime-websocket-lib/USAGE.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Utilisation — backend realtime WebSocket
|
||||
|
||||
## Préconditions
|
||||
|
||||
Le consommateur possède le runtime Tokio. `game-realtime-websocket-lib` utilise Tokio pour les sockets et deadlines mais ne crée jamais son propre runtime.
|
||||
|
||||
Le contrat transport-neutral est fourni par `game-realtime-transport-lib`. Pour appeler `split`, `send`, `receive` et `close`, importer les traits correspondants dans le module consommateur selon les règles Rust du dépôt.
|
||||
|
||||
## Client
|
||||
|
||||
Le point d'entrée simple est :
|
||||
|
||||
```text
|
||||
game_realtime_websocket_lib::connect("ws://host:port/path")
|
||||
```
|
||||
|
||||
La fonction retourne une `WebSocketConnection`. La connexion est ensuite consommée par `RealtimeConnection::split()` pour obtenir un sender et un receiver indépendants.
|
||||
|
||||
Pour une configuration spécifique, construire `WebSocketConfig`, valider ses invariants puis utiliser :
|
||||
|
||||
```text
|
||||
game_realtime_websocket_lib::connect_with_config(endpoint, config)
|
||||
```
|
||||
|
||||
## Serveur
|
||||
|
||||
Créer d'abord une adresse `SocketAddr`, puis binder :
|
||||
|
||||
```text
|
||||
WebSocketListener::bind(address)
|
||||
```
|
||||
|
||||
ou :
|
||||
|
||||
```text
|
||||
WebSocketListener::bind_with_config(address, config)
|
||||
```
|
||||
|
||||
`local_addr()` permet de connaître l'adresse réellement allouée, notamment après bind sur `127.0.0.1:0`. `accept().await` attend ensuite un peer TCP et effectue l'upgrade WebSocket dans la deadline configurée.
|
||||
|
||||
## Émission et réception
|
||||
|
||||
Un message applicatif est encapsulé dans `TransportMessage::new(Vec<u8>)`. Le sender refuse localement un payload dépassant les bornes configurées avant écriture.
|
||||
|
||||
`receive()` retourne :
|
||||
|
||||
- `TransportReceive::Message` pour un payload binaire ;
|
||||
- `TransportReceive::Closed` pour une fermeture distante propre ;
|
||||
- `TransportError` pour une erreur réseau/protocole, un timeout ou une limite dépassée.
|
||||
|
||||
Une frame Text reçue est une erreur de protocole : elle n'est jamais convertie en bytes métier.
|
||||
|
||||
## Fermeture
|
||||
|
||||
`RealtimeSender::close()` initie un close WebSocket propre et applique la deadline de fermeture configurée. Une task/future annulée n'est pas assimilée à un close handshake réussi.
|
||||
|
||||
## Smoke local
|
||||
|
||||
Le launcher de validation public utilise uniquement les API exposées par les deux crates realtime :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
Il bind `127.0.0.1:0`, connecte un client réel, échange un payload binaire dans les deux sens, initie un close propre et doit terminer avec :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke: PASS
|
||||
```
|
||||
|
||||
Le smoke ne dépend ni d'Internet, ni d'un port fixe, ni d'un serveur externe.
|
||||
|
||||
## Limites intentionnelles
|
||||
|
||||
Cette crate n'est pas un protocole multijoueur. Authentification, versionnement wire, session joueur/room, heartbeat métier, resynchronisation, snapshots/deltas et simulation authoritative appartiennent aux couches supérieures et restent hors de son contrat.
|
||||
167
crates/common/game-realtime-websocket-lib/src/config.rs
Normal file
167
crates/common/game-realtime-websocket-lib/src/config.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/src/config.rs
|
||||
// version: 1
|
||||
|
||||
const DEFAULT_CLOSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
|
||||
const DEFAULT_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
const DEFAULT_MAX_FRAME_SIZE: usize = 1024 * 1024;
|
||||
const DEFAULT_MAX_MESSAGE_SIZE: usize = 1024 * 1024;
|
||||
const DEFAULT_MAX_WRITE_BUFFER_SIZE: usize = 2 * 1024 * 1024;
|
||||
const DEFAULT_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
const DEFAULT_WRITE_BUFFER_SIZE: usize = 64 * 1024;
|
||||
|
||||
/// Product-facing limits and operation deadlines for one WebSocket connection.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct WebSocketConfig {
|
||||
max_message_size: usize,
|
||||
max_frame_size: usize,
|
||||
write_buffer_size: usize,
|
||||
max_write_buffer_size: usize,
|
||||
connect_timeout: std::time::Duration,
|
||||
send_timeout: std::time::Duration,
|
||||
close_timeout: std::time::Duration,
|
||||
}
|
||||
|
||||
impl Default for WebSocketConfig {
|
||||
fn default() -> Self {
|
||||
return Self {
|
||||
max_message_size: DEFAULT_MAX_MESSAGE_SIZE,
|
||||
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
|
||||
write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE,
|
||||
max_write_buffer_size: DEFAULT_MAX_WRITE_BUFFER_SIZE,
|
||||
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
|
||||
send_timeout: DEFAULT_SEND_TIMEOUT,
|
||||
close_timeout: DEFAULT_CLOSE_TIMEOUT,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl WebSocketConfig {
|
||||
/// Returns a copy with a different maximum binary message size.
|
||||
#[must_use]
|
||||
pub fn with_max_message_size(mut self, value: usize) -> Self {
|
||||
self.max_message_size = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a copy with a different maximum WebSocket frame payload size.
|
||||
#[must_use]
|
||||
pub fn with_max_frame_size(mut self, value: usize) -> Self {
|
||||
self.max_frame_size = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a copy with a different Tungstenite write-buffer target.
|
||||
#[must_use]
|
||||
pub fn with_write_buffer_size(mut self, value: usize) -> Self {
|
||||
self.write_buffer_size = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a copy with a different hard maximum for the Tungstenite write buffer.
|
||||
#[must_use]
|
||||
pub fn with_max_write_buffer_size(mut self, value: usize) -> Self {
|
||||
self.max_write_buffer_size = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a copy with a different connection or handshake deadline.
|
||||
#[must_use]
|
||||
pub fn with_connect_timeout(mut self, value: std::time::Duration) -> Self {
|
||||
self.connect_timeout = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a copy with a different deadline for one send operation.
|
||||
#[must_use]
|
||||
pub fn with_send_timeout(mut self, value: std::time::Duration) -> Self {
|
||||
self.send_timeout = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns a copy with a different deadline for a clean local close operation.
|
||||
#[must_use]
|
||||
pub fn with_close_timeout(mut self, value: std::time::Duration) -> Self {
|
||||
self.close_timeout = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns the configured maximum binary message size.
|
||||
#[must_use]
|
||||
pub fn max_message_size(&self) -> usize {
|
||||
return self.max_message_size;
|
||||
}
|
||||
|
||||
/// Returns the configured maximum WebSocket frame payload size.
|
||||
#[must_use]
|
||||
pub fn max_frame_size(&self) -> usize {
|
||||
return self.max_frame_size;
|
||||
}
|
||||
|
||||
/// Returns the configured Tungstenite write-buffer target.
|
||||
#[must_use]
|
||||
pub fn write_buffer_size(&self) -> usize {
|
||||
return self.write_buffer_size;
|
||||
}
|
||||
|
||||
/// Returns the configured hard maximum for the Tungstenite write buffer.
|
||||
#[must_use]
|
||||
pub fn max_write_buffer_size(&self) -> usize {
|
||||
return self.max_write_buffer_size;
|
||||
}
|
||||
|
||||
/// Returns the configured connection or handshake deadline.
|
||||
#[must_use]
|
||||
pub fn connect_timeout(&self) -> std::time::Duration {
|
||||
return self.connect_timeout;
|
||||
}
|
||||
|
||||
/// Returns the configured deadline for one send operation.
|
||||
#[must_use]
|
||||
pub fn send_timeout(&self) -> std::time::Duration {
|
||||
return self.send_timeout;
|
||||
}
|
||||
|
||||
/// Returns the configured deadline for a clean local close operation.
|
||||
#[must_use]
|
||||
pub fn close_timeout(&self) -> std::time::Duration {
|
||||
return self.close_timeout;
|
||||
}
|
||||
|
||||
/// Validates all invariants required before creating a WebSocket endpoint or connection.
|
||||
pub fn validate(&self) -> Result<(), game_realtime_transport_lib::TransportError> {
|
||||
if self.max_message_size == 0 {
|
||||
return Err(invalid_configuration("max_message_size must be greater than zero"));
|
||||
}
|
||||
if self.max_frame_size == 0 {
|
||||
return Err(invalid_configuration("max_frame_size must be greater than zero"));
|
||||
}
|
||||
if self.max_frame_size > self.max_message_size {
|
||||
return Err(invalid_configuration("max_frame_size must not exceed max_message_size"));
|
||||
}
|
||||
let minimum_max_write_buffer_size = match self.write_buffer_size.checked_add(self.max_message_size) {
|
||||
Some(value) => value,
|
||||
None => return Err(invalid_configuration("write_buffer_size + max_message_size overflows usize")),
|
||||
};
|
||||
if self.max_write_buffer_size < minimum_max_write_buffer_size {
|
||||
return Err(invalid_configuration("max_write_buffer_size must fit write_buffer_size plus one maximum-sized message"));
|
||||
}
|
||||
if self.connect_timeout.is_zero() {
|
||||
return Err(invalid_configuration("connect_timeout must be greater than zero"));
|
||||
}
|
||||
if self.send_timeout.is_zero() {
|
||||
return Err(invalid_configuration("send_timeout must be greater than zero"));
|
||||
}
|
||||
if self.close_timeout.is_zero() {
|
||||
return Err(invalid_configuration("close_timeout must be greater than zero"));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_configuration(detail: &str) -> game_realtime_transport_lib::TransportError {
|
||||
return game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration, detail);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/config.rs"]
|
||||
mod tests;
|
||||
26
crates/common/game-realtime-websocket-lib/src/lib.rs
Normal file
26
crates/common/game-realtime-websocket-lib/src/lib.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/src/lib.rs
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Tokio/tokio-tungstenite WebSocket backend for the games.sasedev realtime transport contract.
|
||||
|
||||
mod config;
|
||||
mod websocket;
|
||||
|
||||
/// Re-export of product-facing WebSocket limits and operation deadlines.
|
||||
pub use self::config::WebSocketConfig;
|
||||
/// Re-export of an established WebSocket transport connection.
|
||||
pub use self::websocket::WebSocketConnection;
|
||||
/// Re-export of a bound WebSocket server listener.
|
||||
pub use self::websocket::WebSocketListener;
|
||||
/// Re-export of the receive half of an established WebSocket connection.
|
||||
pub use self::websocket::WebSocketReceiver;
|
||||
/// Re-export of the send half of an established WebSocket connection.
|
||||
pub use self::websocket::WebSocketSender;
|
||||
/// Re-export of the WebSocket client connection constructor using baseline defaults.
|
||||
pub use self::websocket::connect;
|
||||
/// Re-export of the configurable WebSocket client connection constructor.
|
||||
pub use self::websocket::connect_with_config;
|
||||
411
crates/common/game-realtime-websocket-lib/src/websocket.rs
Normal file
411
crates/common/game-realtime-websocket-lib/src/websocket.rs
Normal file
@@ -0,0 +1,411 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/src/websocket.rs
|
||||
// version: 2
|
||||
|
||||
use futures_util::SinkExt; // rust-rules: trait-import
|
||||
use futures_util::StreamExt; // rust-rules: trait-import
|
||||
|
||||
const TRACING_TARGET: &str = "games::realtime::websocket";
|
||||
|
||||
type ClientStream = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
|
||||
type ServerStream = tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>;
|
||||
type ClientSink = futures_util::stream::SplitSink<ClientStream, tokio_tungstenite::tungstenite::Message>;
|
||||
type ServerSink = futures_util::stream::SplitSink<ServerStream, tokio_tungstenite::tungstenite::Message>;
|
||||
type ClientReceiver = futures_util::stream::SplitStream<ClientStream>;
|
||||
type ServerReceiver = futures_util::stream::SplitStream<ServerStream>;
|
||||
|
||||
enum WebSocketStreamKind {
|
||||
Client(ClientStream),
|
||||
Server(ServerStream),
|
||||
}
|
||||
|
||||
enum WebSocketSinkKind {
|
||||
Client(ClientSink),
|
||||
Server(ServerSink),
|
||||
}
|
||||
|
||||
enum WebSocketReceiverKind {
|
||||
Client(ClientReceiver),
|
||||
Server(ServerReceiver),
|
||||
}
|
||||
|
||||
/// Established WebSocket connection implementing the transport-neutral realtime contract.
|
||||
pub struct WebSocketConnection {
|
||||
inner: WebSocketStreamKind,
|
||||
config: crate::WebSocketConfig,
|
||||
}
|
||||
|
||||
impl WebSocketConnection {
|
||||
fn from_client(stream: ClientStream, config: crate::WebSocketConfig) -> Self {
|
||||
return Self { inner: WebSocketStreamKind::Client(stream), config };
|
||||
}
|
||||
|
||||
fn from_server(stream: ServerStream, config: crate::WebSocketConfig) -> Self {
|
||||
return Self { inner: WebSocketStreamKind::Server(stream), config };
|
||||
}
|
||||
}
|
||||
|
||||
impl game_realtime_transport_lib::RealtimeConnection for WebSocketConnection {
|
||||
type Sender = crate::WebSocketSender;
|
||||
type Receiver = crate::WebSocketReceiver;
|
||||
|
||||
fn split(self) -> (Self::Sender, Self::Receiver) {
|
||||
let max_frame_size = self.config.max_frame_size();
|
||||
let max_message_size = self.config.max_message_size();
|
||||
let send_timeout = self.config.send_timeout();
|
||||
let close_timeout = self.config.close_timeout();
|
||||
return match self.inner {
|
||||
WebSocketStreamKind::Client(stream) => {
|
||||
let (sender, receiver) = stream.split();
|
||||
(
|
||||
crate::WebSocketSender {
|
||||
inner: WebSocketSinkKind::Client(sender),
|
||||
max_frame_size,
|
||||
max_message_size,
|
||||
send_timeout,
|
||||
close_timeout,
|
||||
send_timed_out: false,
|
||||
},
|
||||
crate::WebSocketReceiver { inner: WebSocketReceiverKind::Client(receiver) },
|
||||
)
|
||||
},
|
||||
WebSocketStreamKind::Server(stream) => {
|
||||
let (sender, receiver) = stream.split();
|
||||
(
|
||||
crate::WebSocketSender {
|
||||
inner: WebSocketSinkKind::Server(sender),
|
||||
max_frame_size,
|
||||
max_message_size,
|
||||
send_timeout,
|
||||
close_timeout,
|
||||
send_timed_out: false,
|
||||
},
|
||||
crate::WebSocketReceiver { inner: WebSocketReceiverKind::Server(receiver) },
|
||||
)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Send half of an established WebSocket transport connection.
|
||||
pub struct WebSocketSender {
|
||||
inner: WebSocketSinkKind,
|
||||
max_frame_size: usize,
|
||||
max_message_size: usize,
|
||||
send_timeout: std::time::Duration,
|
||||
close_timeout: std::time::Duration,
|
||||
send_timed_out: bool,
|
||||
}
|
||||
|
||||
impl game_realtime_transport_lib::RealtimeSender for WebSocketSender {
|
||||
type SendFuture<'a>
|
||||
= futures_util::future::LocalBoxFuture<'a, Result<(), game_realtime_transport_lib::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
type CloseFuture<'a>
|
||||
= futures_util::future::LocalBoxFuture<'a, Result<(), game_realtime_transport_lib::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn send(&mut self, message: game_realtime_transport_lib::TransportMessage) -> Self::SendFuture<'_> {
|
||||
return Box::pin(async move {
|
||||
if self.send_timed_out {
|
||||
return Err(game_realtime_transport_lib::TransportError::new(
|
||||
game_realtime_transport_lib::TransportErrorKind::Aborted,
|
||||
"sender is unavailable after a previous send timeout",
|
||||
));
|
||||
}
|
||||
let payload_len = message.len();
|
||||
if payload_len > self.max_message_size || payload_len > self.max_frame_size {
|
||||
let error = game_realtime_transport_lib::TransportError::new(
|
||||
game_realtime_transport_lib::TransportErrorKind::MessageTooLarge,
|
||||
format!("binary payload size {payload_len} exceeds configured message/frame maxima {}/{}", self.max_message_size, self.max_frame_size),
|
||||
);
|
||||
tracing::warn!(
|
||||
target: TRACING_TARGET,
|
||||
payload_len = payload_len,
|
||||
max_message_size = self.max_message_size,
|
||||
max_frame_size = self.max_frame_size,
|
||||
"outbound WebSocket payload rejected"
|
||||
);
|
||||
return Err(error);
|
||||
}
|
||||
let websocket_message = tokio_tungstenite::tungstenite::Message::Binary(message.into_bytes().into());
|
||||
let send_timeout = self.send_timeout;
|
||||
let send = async {
|
||||
return match &mut self.inner {
|
||||
WebSocketSinkKind::Client(sender) => sender.send(websocket_message).await,
|
||||
WebSocketSinkKind::Server(sender) => sender.send(websocket_message).await,
|
||||
};
|
||||
};
|
||||
let result = tokio::time::timeout(send_timeout, send).await;
|
||||
return match result {
|
||||
Ok(Ok(())) => {
|
||||
tracing::trace!(target: TRACING_TARGET, payload_len = payload_len, "binary WebSocket payload sent");
|
||||
Ok(())
|
||||
},
|
||||
Ok(Err(error)) => {
|
||||
let mapped = map_stream_error(error);
|
||||
tracing::warn!(target: TRACING_TARGET, kind = %mapped.kind(), detail = mapped.detail(), "WebSocket send failed");
|
||||
Err(mapped)
|
||||
},
|
||||
Err(_) => {
|
||||
self.send_timed_out = true;
|
||||
let error = timeout_error("WebSocket send", send_timeout);
|
||||
tracing::warn!(target: TRACING_TARGET, timeout_ms = duration_millis(send_timeout), "WebSocket send timed out");
|
||||
Err(error)
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Self::CloseFuture<'_> {
|
||||
return Box::pin(async move {
|
||||
let close_timeout = self.close_timeout;
|
||||
let close = async {
|
||||
return match &mut self.inner {
|
||||
WebSocketSinkKind::Client(sender) => sender.close().await,
|
||||
WebSocketSinkKind::Server(sender) => sender.close().await,
|
||||
};
|
||||
};
|
||||
let result = tokio::time::timeout(close_timeout, close).await;
|
||||
return match result {
|
||||
Ok(Ok(())) => {
|
||||
tracing::debug!(target: TRACING_TARGET, "local WebSocket close initiated");
|
||||
Ok(())
|
||||
},
|
||||
Ok(Err(error)) => {
|
||||
let mapped = map_stream_error(error);
|
||||
tracing::warn!(target: TRACING_TARGET, kind = %mapped.kind(), detail = mapped.detail(), "WebSocket close failed");
|
||||
Err(mapped)
|
||||
},
|
||||
Err(_) => {
|
||||
let error = timeout_error("WebSocket close", close_timeout);
|
||||
tracing::warn!(target: TRACING_TARGET, timeout_ms = duration_millis(close_timeout), "WebSocket close timed out");
|
||||
Err(error)
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive half of an established WebSocket transport connection.
|
||||
pub struct WebSocketReceiver {
|
||||
inner: WebSocketReceiverKind,
|
||||
}
|
||||
|
||||
impl game_realtime_transport_lib::RealtimeReceiver for WebSocketReceiver {
|
||||
type ReceiveFuture<'a>
|
||||
= futures_util::future::LocalBoxFuture<'a, Result<game_realtime_transport_lib::TransportReceive, game_realtime_transport_lib::TransportError>>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn receive(&mut self) -> Self::ReceiveFuture<'_> {
|
||||
return Box::pin(async move {
|
||||
loop {
|
||||
let next_message = match &mut self.inner {
|
||||
WebSocketReceiverKind::Client(receiver) => receiver.next().await,
|
||||
WebSocketReceiverKind::Server(receiver) => receiver.next().await,
|
||||
};
|
||||
match next_message {
|
||||
Some(Ok(tokio_tungstenite::tungstenite::Message::Binary(bytes))) => {
|
||||
tracing::trace!(target: TRACING_TARGET, payload_len = bytes.len(), "binary WebSocket payload received");
|
||||
return Ok(game_realtime_transport_lib::TransportReceive::Message(game_realtime_transport_lib::TransportMessage::new(bytes.to_vec())));
|
||||
},
|
||||
Some(Ok(tokio_tungstenite::tungstenite::Message::Close(_))) => {
|
||||
tracing::debug!(target: TRACING_TARGET, "remote WebSocket close observed");
|
||||
return Ok(game_realtime_transport_lib::TransportReceive::Closed);
|
||||
},
|
||||
Some(Ok(tokio_tungstenite::tungstenite::Message::Ping(_))) | Some(Ok(tokio_tungstenite::tungstenite::Message::Pong(_))) => {},
|
||||
Some(Ok(tokio_tungstenite::tungstenite::Message::Text(_))) => {
|
||||
let error = game_realtime_transport_lib::TransportError::new(
|
||||
game_realtime_transport_lib::TransportErrorKind::Protocol,
|
||||
"text WebSocket messages are not part of the binary transport contract",
|
||||
);
|
||||
tracing::warn!(target: TRACING_TARGET, kind = %error.kind(), "unsupported WebSocket text message received");
|
||||
return Err(error);
|
||||
},
|
||||
Some(Ok(tokio_tungstenite::tungstenite::Message::Frame(_))) => {
|
||||
let error = game_realtime_transport_lib::TransportError::new(
|
||||
game_realtime_transport_lib::TransportErrorKind::Protocol,
|
||||
"unexpected raw WebSocket frame surfaced by the backend",
|
||||
);
|
||||
tracing::warn!(target: TRACING_TARGET, kind = %error.kind(), "unexpected raw WebSocket frame received");
|
||||
return Err(error);
|
||||
},
|
||||
Some(Err(error)) => {
|
||||
let mapped = map_stream_error(error);
|
||||
tracing::warn!(target: TRACING_TARGET, kind = %mapped.kind(), detail = mapped.detail(), "WebSocket receive failed");
|
||||
return Err(mapped);
|
||||
},
|
||||
None => {
|
||||
tracing::debug!(target: TRACING_TARGET, "WebSocket stream ended");
|
||||
return Ok(game_realtime_transport_lib::TransportReceive::Closed);
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Bound TCP listener that upgrades accepted peers to WebSocket connections.
|
||||
pub struct WebSocketListener {
|
||||
listener: tokio::net::TcpListener,
|
||||
local_addr: std::net::SocketAddr,
|
||||
config: crate::WebSocketConfig,
|
||||
}
|
||||
|
||||
impl WebSocketListener {
|
||||
/// Binds a WebSocket listener with the baseline configuration.
|
||||
pub async fn bind(address: std::net::SocketAddr) -> Result<Self, game_realtime_transport_lib::TransportError> {
|
||||
return Self::bind_with_config(address, crate::WebSocketConfig::default()).await;
|
||||
}
|
||||
|
||||
/// Binds a WebSocket listener with explicit product-facing limits and deadlines.
|
||||
pub async fn bind_with_config(address: std::net::SocketAddr, config: crate::WebSocketConfig) -> Result<Self, game_realtime_transport_lib::TransportError> {
|
||||
if let Err(error) = config.validate() {
|
||||
return Err(error);
|
||||
}
|
||||
let listener = match tokio::net::TcpListener::bind(address).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
let mapped = game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::Bind, error.to_string());
|
||||
tracing::warn!(target: TRACING_TARGET, address = %address, detail = mapped.detail(), "WebSocket listener bind failed");
|
||||
return Err(mapped);
|
||||
},
|
||||
};
|
||||
let local_addr = match listener.local_addr() {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
let mapped = game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::Bind, error.to_string());
|
||||
tracing::warn!(target: TRACING_TARGET, detail = mapped.detail(), "bound WebSocket listener address lookup failed");
|
||||
return Err(mapped);
|
||||
},
|
||||
};
|
||||
tracing::info!(target: TRACING_TARGET, address = %local_addr, "WebSocket listener bound");
|
||||
return Ok(Self { listener, local_addr, config });
|
||||
}
|
||||
|
||||
/// Returns the concrete local socket address, including an ephemeral port selected by the OS.
|
||||
#[must_use]
|
||||
pub fn local_addr(&self) -> std::net::SocketAddr {
|
||||
return self.local_addr;
|
||||
}
|
||||
|
||||
/// Accepts one TCP peer and completes a bounded server-side WebSocket handshake.
|
||||
pub async fn accept(&self) -> Result<crate::WebSocketConnection, game_realtime_transport_lib::TransportError> {
|
||||
let (stream, peer_addr) = match self.listener.accept().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
let mapped = game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::Accept, error.to_string());
|
||||
tracing::warn!(target: TRACING_TARGET, detail = mapped.detail(), "WebSocket TCP accept failed");
|
||||
return Err(mapped);
|
||||
},
|
||||
};
|
||||
let handshake = tokio_tungstenite::accept_async_with_config(stream, Some(tungstenite_config(&self.config)));
|
||||
let result = tokio::time::timeout(self.config.connect_timeout(), handshake).await;
|
||||
let websocket = match result {
|
||||
Ok(Ok(value)) => value,
|
||||
Ok(Err(error)) => {
|
||||
let mapped = game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::Accept, error.to_string());
|
||||
tracing::warn!(target: TRACING_TARGET, peer = %peer_addr, detail = mapped.detail(), "WebSocket server handshake failed");
|
||||
return Err(mapped);
|
||||
},
|
||||
Err(_) => {
|
||||
let error = timeout_error("WebSocket server handshake", self.config.connect_timeout());
|
||||
tracing::warn!(target: TRACING_TARGET, peer = %peer_addr, timeout_ms = duration_millis(self.config.connect_timeout()), "WebSocket server handshake timed out");
|
||||
return Err(error);
|
||||
},
|
||||
};
|
||||
tracing::info!(target: TRACING_TARGET, peer = %peer_addr, "WebSocket peer accepted");
|
||||
return Ok(crate::WebSocketConnection::from_server(websocket, self.config));
|
||||
}
|
||||
}
|
||||
|
||||
/// Connects a client to one plain `ws://` endpoint with the baseline configuration.
|
||||
pub async fn connect(endpoint: &str) -> Result<crate::WebSocketConnection, game_realtime_transport_lib::TransportError> {
|
||||
return crate::connect_with_config(endpoint, crate::WebSocketConfig::default()).await;
|
||||
}
|
||||
|
||||
/// Connects a client to one plain `ws://` endpoint with explicit limits and deadlines.
|
||||
pub async fn connect_with_config(
|
||||
endpoint: &str,
|
||||
config: crate::WebSocketConfig,
|
||||
) -> Result<crate::WebSocketConnection, game_realtime_transport_lib::TransportError> {
|
||||
if !endpoint.starts_with("ws://") {
|
||||
return Err(game_realtime_transport_lib::TransportError::new(
|
||||
game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration,
|
||||
"the baseline WebSocket backend accepts only ws:// endpoints",
|
||||
));
|
||||
}
|
||||
if let Err(error) = config.validate() {
|
||||
return Err(error);
|
||||
}
|
||||
tracing::debug!(target: TRACING_TARGET, endpoint = endpoint, "connecting WebSocket client");
|
||||
let handshake = tokio_tungstenite::connect_async_with_config(endpoint, Some(tungstenite_config(&config)), false);
|
||||
let result = tokio::time::timeout(config.connect_timeout(), handshake).await;
|
||||
let (stream, _) = match result {
|
||||
Ok(Ok(value)) => value,
|
||||
Ok(Err(error)) => {
|
||||
let mapped = map_connect_error(error);
|
||||
tracing::warn!(
|
||||
target: TRACING_TARGET,
|
||||
endpoint = endpoint,
|
||||
kind = %mapped.kind(),
|
||||
detail = mapped.detail(),
|
||||
"WebSocket client connect failed"
|
||||
);
|
||||
return Err(mapped);
|
||||
},
|
||||
Err(_) => {
|
||||
let error = timeout_error("WebSocket client connect", config.connect_timeout());
|
||||
tracing::warn!(target: TRACING_TARGET, endpoint = endpoint, timeout_ms = duration_millis(config.connect_timeout()), "WebSocket client connect timed out");
|
||||
return Err(error);
|
||||
},
|
||||
};
|
||||
tracing::info!(target: TRACING_TARGET, endpoint = endpoint, "WebSocket client connected");
|
||||
return Ok(crate::WebSocketConnection::from_client(stream, config));
|
||||
}
|
||||
|
||||
fn tungstenite_config(config: &crate::WebSocketConfig) -> tokio_tungstenite::tungstenite::protocol::WebSocketConfig {
|
||||
return tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
|
||||
.write_buffer_size(config.write_buffer_size())
|
||||
.max_write_buffer_size(config.max_write_buffer_size())
|
||||
.max_message_size(Some(config.max_message_size()))
|
||||
.max_frame_size(Some(config.max_frame_size()));
|
||||
}
|
||||
|
||||
fn timeout_error(operation: &str, timeout: std::time::Duration) -> game_realtime_transport_lib::TransportError {
|
||||
return game_realtime_transport_lib::TransportError::new(
|
||||
game_realtime_transport_lib::TransportErrorKind::Timeout,
|
||||
format!("{operation} exceeded configured deadline of {} ms", duration_millis(timeout)),
|
||||
);
|
||||
}
|
||||
|
||||
fn duration_millis(duration: std::time::Duration) -> u128 {
|
||||
return duration.as_millis();
|
||||
}
|
||||
|
||||
fn map_connect_error(error: tokio_tungstenite::tungstenite::Error) -> game_realtime_transport_lib::TransportError {
|
||||
let kind = match &error {
|
||||
tokio_tungstenite::tungstenite::Error::Url(_) => game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration,
|
||||
_ => game_realtime_transport_lib::TransportErrorKind::Connect,
|
||||
};
|
||||
return game_realtime_transport_lib::TransportError::new(kind, error.to_string());
|
||||
}
|
||||
|
||||
fn map_stream_error(error: tokio_tungstenite::tungstenite::Error) -> game_realtime_transport_lib::TransportError {
|
||||
let kind = match &error {
|
||||
tokio_tungstenite::tungstenite::Error::ConnectionClosed | tokio_tungstenite::tungstenite::Error::AlreadyClosed => {
|
||||
game_realtime_transport_lib::TransportErrorKind::Closed
|
||||
},
|
||||
tokio_tungstenite::tungstenite::Error::Io(_) => game_realtime_transport_lib::TransportErrorKind::Io,
|
||||
tokio_tungstenite::tungstenite::Error::Capacity(_) => game_realtime_transport_lib::TransportErrorKind::MessageTooLarge,
|
||||
tokio_tungstenite::tungstenite::Error::WriteBufferFull(_) => game_realtime_transport_lib::TransportErrorKind::Backpressure,
|
||||
_ => game_realtime_transport_lib::TransportErrorKind::Protocol,
|
||||
};
|
||||
return game_realtime_transport_lib::TransportError::new(kind, error.to_string());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/websocket.rs"]
|
||||
mod tests;
|
||||
62
crates/common/game-realtime-websocket-lib/tests/loopback.rs
Normal file
62
crates/common/game-realtime-websocket-lib/tests/loopback.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/tests/loopback.rs
|
||||
// version: 1
|
||||
|
||||
//! Deterministic localhost proof for the Tokio/tokio-tungstenite backend.
|
||||
|
||||
use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import
|
||||
|
||||
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn binary_round_trip_and_clean_close_work_on_loopback() {
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind(bind_address).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("ws://{}/", listener.local_addr());
|
||||
let pair = tokio::time::timeout(TEST_TIMEOUT, async {
|
||||
return tokio::join!(listener.accept(), game_realtime_websocket_lib::connect(endpoint.as_str()));
|
||||
})
|
||||
.await;
|
||||
let (server_connection, client_connection) = match pair {
|
||||
Ok((Ok(server), Ok(client))) => (server, client),
|
||||
Ok((_server, _client)) => panic!("loopback connection establishment failed"),
|
||||
Err(_) => panic!("loopback connection establishment timed out"),
|
||||
};
|
||||
let (mut server_sender, mut server_receiver) = server_connection.split();
|
||||
let (mut client_sender, mut client_receiver) = client_connection.split();
|
||||
let client_payload = game_realtime_transport_lib::TransportMessage::new(vec![0, 1, 2, 3, 255]);
|
||||
let client_send = tokio::time::timeout(TEST_TIMEOUT, client_sender.send(client_payload)).await;
|
||||
assert!(matches!(client_send, Ok(Ok(()))));
|
||||
let server_receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match server_receive {
|
||||
Ok(Ok(received)) => assert_eq!(
|
||||
received,
|
||||
game_realtime_transport_lib::TransportReceive::Message(game_realtime_transport_lib::TransportMessage::new(vec![0, 1, 2, 3, 255]))
|
||||
),
|
||||
Ok(Err(error)) => panic!("server receive failed: {error}"),
|
||||
Err(_) => panic!("server receive timed out"),
|
||||
}
|
||||
let server_payload = game_realtime_transport_lib::TransportMessage::new(vec![9, 8, 7, 6]);
|
||||
let server_send = tokio::time::timeout(TEST_TIMEOUT, server_sender.send(server_payload)).await;
|
||||
assert!(matches!(server_send, Ok(Ok(()))));
|
||||
let client_receive = tokio::time::timeout(TEST_TIMEOUT, client_receiver.receive()).await;
|
||||
match client_receive {
|
||||
Ok(Ok(received)) => {
|
||||
assert_eq!(received, game_realtime_transport_lib::TransportReceive::Message(game_realtime_transport_lib::TransportMessage::new(vec![9, 8, 7, 6])))
|
||||
},
|
||||
Ok(Err(error)) => panic!("client receive failed: {error}"),
|
||||
Err(_) => panic!("client receive timed out"),
|
||||
}
|
||||
let client_close = tokio::time::timeout(TEST_TIMEOUT, client_sender.close()).await;
|
||||
assert!(matches!(client_close, Ok(Ok(()))));
|
||||
let server_close_observation = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match server_close_observation {
|
||||
Ok(Ok(received)) => assert_eq!(received, game_realtime_transport_lib::TransportReceive::Closed),
|
||||
Ok(Err(error)) => panic!("server close observation failed: {error}"),
|
||||
Err(_) => panic!("server close observation timed out"),
|
||||
};
|
||||
}
|
||||
134
crates/common/game-realtime-websocket-lib/tests/robustness.rs
Normal file
134
crates/common/game-realtime-websocket-lib/tests/robustness.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/tests/robustness.rs
|
||||
// version: 1
|
||||
|
||||
//! Negative and bounded lifecycle tests for the WebSocket transport backend.
|
||||
|
||||
use futures_util::SinkExt; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import
|
||||
use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import
|
||||
|
||||
const SMALL_MESSAGE_LIMIT: usize = 32;
|
||||
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
|
||||
type RawClient = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn outbound_payload_over_the_configured_limit_is_rejected_before_write() {
|
||||
let config = small_message_config();
|
||||
let (server_connection, client_connection) = establish_backend_pair(config).await;
|
||||
let (_server_sender, _server_receiver) = server_connection.split();
|
||||
let (mut client_sender, _client_receiver) = client_connection.split();
|
||||
let oversized = game_realtime_transport_lib::TransportMessage::new(vec![7; SMALL_MESSAGE_LIMIT + 1]);
|
||||
let result = client_sender.send(oversized).await;
|
||||
match result {
|
||||
Ok(()) => panic!("oversized outbound payload was accepted"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn inbound_payload_over_the_configured_limit_is_rejected_by_tungstenite() {
|
||||
let config = small_message_config();
|
||||
let (server_connection, mut raw_client) = establish_backend_server_with_raw_client(config).await;
|
||||
let (_server_sender, mut server_receiver) = server_connection.split();
|
||||
let send = raw_client.send(tokio_tungstenite::tungstenite::Message::binary(vec![3; SMALL_MESSAGE_LIMIT + 1])).await;
|
||||
assert!(send.is_ok());
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("oversized inbound payload produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
Err(_) => panic!("oversized inbound payload did not complete within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn text_message_is_rejected_by_the_binary_transport_contract() {
|
||||
let (server_connection, mut raw_client) = establish_backend_server_with_raw_client(game_realtime_websocket_lib::WebSocketConfig::default()).await;
|
||||
let (_server_sender, mut server_receiver) = server_connection.split();
|
||||
let send = raw_client.send(tokio_tungstenite::tungstenite::Message::text("text is outside the transport contract")).await;
|
||||
assert!(send.is_ok());
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("text message produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol),
|
||||
Err(_) => panic!("text-message rejection did not complete within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn peer_drop_without_close_handshake_is_reported_as_protocol_failure() {
|
||||
let (server_connection, raw_client) = establish_backend_server_with_raw_client(game_realtime_websocket_lib::WebSocketConfig::default()).await;
|
||||
let (_server_sender, mut server_receiver) = server_connection.split();
|
||||
drop(raw_client);
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, server_receiver.receive()).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("abrupt peer drop was reported as a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Protocol),
|
||||
Err(_) => panic!("abrupt peer drop was not observed within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn silent_tcp_peer_hits_the_server_handshake_deadline() {
|
||||
let config = game_realtime_websocket_lib::WebSocketConfig::default().with_connect_timeout(std::time::Duration::from_millis(50));
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind_with_config(bind_address, config).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("bounded listener bind failed: {error}"),
|
||||
};
|
||||
let _silent_peer = match tokio::net::TcpStream::connect(listener.local_addr()).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("silent TCP peer connection failed: {error}"),
|
||||
};
|
||||
let accept = tokio::time::timeout(TEST_TIMEOUT, listener.accept()).await;
|
||||
match accept {
|
||||
Ok(Ok(_connection)) => panic!("silent TCP peer unexpectedly completed a WebSocket handshake"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Timeout),
|
||||
Err(_) => panic!("server handshake timeout did not fire within the outer test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
fn small_message_config() -> game_realtime_websocket_lib::WebSocketConfig {
|
||||
return game_realtime_websocket_lib::WebSocketConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT).with_max_frame_size(SMALL_MESSAGE_LIMIT);
|
||||
}
|
||||
|
||||
async fn establish_backend_pair(
|
||||
config: game_realtime_websocket_lib::WebSocketConfig,
|
||||
) -> (game_realtime_websocket_lib::WebSocketConnection, game_realtime_websocket_lib::WebSocketConnection) {
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind_with_config(bind_address, config).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("ws://{}/", listener.local_addr());
|
||||
let pair = tokio::time::timeout(TEST_TIMEOUT, async {
|
||||
return tokio::join!(listener.accept(), game_realtime_websocket_lib::connect_with_config(endpoint.as_str(), config));
|
||||
})
|
||||
.await;
|
||||
return match pair {
|
||||
Ok((Ok(server), Ok(client))) => (server, client),
|
||||
Ok((_server, _client)) => panic!("loopback connection establishment failed"),
|
||||
Err(_) => panic!("loopback connection establishment timed out"),
|
||||
};
|
||||
}
|
||||
|
||||
async fn establish_backend_server_with_raw_client(
|
||||
config: game_realtime_websocket_lib::WebSocketConfig,
|
||||
) -> (game_realtime_websocket_lib::WebSocketConnection, RawClient) {
|
||||
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
|
||||
let listener = match game_realtime_websocket_lib::WebSocketListener::bind_with_config(bind_address, config).await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("ws://{}/", listener.local_addr());
|
||||
let pair = tokio::time::timeout(TEST_TIMEOUT, async {
|
||||
return tokio::join!(listener.accept(), tokio_tungstenite::connect_async(endpoint.as_str()));
|
||||
})
|
||||
.await;
|
||||
return match pair {
|
||||
Ok((Ok(server), Ok((client, _response)))) => (server, client),
|
||||
Ok((_server, _client)) => panic!("raw-client loopback connection establishment failed"),
|
||||
Err(_) => panic!("raw-client loopback connection establishment timed out"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/unit_tests/config.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn default_configuration_matches_the_product_baseline() {
|
||||
let config = crate::WebSocketConfig::default();
|
||||
assert_eq!(config.max_message_size(), 1024 * 1024);
|
||||
assert_eq!(config.max_frame_size(), 1024 * 1024);
|
||||
assert_eq!(config.write_buffer_size(), 64 * 1024);
|
||||
assert_eq!(config.max_write_buffer_size(), 2 * 1024 * 1024);
|
||||
assert_eq!(config.connect_timeout(), std::time::Duration::from_secs(10));
|
||||
assert_eq!(config.send_timeout(), std::time::Duration::from_secs(5));
|
||||
assert_eq!(config.close_timeout(), std::time::Duration::from_secs(2));
|
||||
assert!(config.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_message_and_write_buffer_bounds_are_rejected() {
|
||||
let zero_message = crate::WebSocketConfig::default().with_max_message_size(0);
|
||||
assert_invalid_configuration(zero_message);
|
||||
let oversized_frame = crate::WebSocketConfig::default().with_max_frame_size(2 * 1024 * 1024);
|
||||
assert_invalid_configuration(oversized_frame);
|
||||
let insufficient_write_buffer = crate::WebSocketConfig::default().with_max_write_buffer_size(1024 * 1024);
|
||||
assert_invalid_configuration(insufficient_write_buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_operation_deadlines_are_rejected() {
|
||||
assert_invalid_configuration(crate::WebSocketConfig::default().with_connect_timeout(std::time::Duration::ZERO));
|
||||
assert_invalid_configuration(crate::WebSocketConfig::default().with_send_timeout(std::time::Duration::ZERO));
|
||||
assert_invalid_configuration(crate::WebSocketConfig::default().with_close_timeout(std::time::Duration::ZERO));
|
||||
}
|
||||
|
||||
fn assert_invalid_configuration(config: crate::WebSocketConfig) {
|
||||
let result = config.validate();
|
||||
match result {
|
||||
Ok(()) => panic!("invalid WebSocket configuration was accepted"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// file: crates/common/game-realtime-websocket-lib/unit_tests/websocket.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn tungstenite_backpressure_maps_to_transport_backpressure() {
|
||||
let message = tokio_tungstenite::tungstenite::Message::Binary(vec![1, 2, 3].into());
|
||||
let backend_error = tokio_tungstenite::tungstenite::Error::WriteBufferFull(Box::new(message));
|
||||
let mapped = super::map_stream_error(backend_error);
|
||||
assert_eq!(mapped.kind(), game_realtime_transport_lib::TransportErrorKind::Backpressure);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tungstenite_capacity_maps_to_message_too_large() {
|
||||
let capacity = tokio_tungstenite::tungstenite::error::CapacityError::MessageTooLong { size: 65, max_size: 64 };
|
||||
let backend_error = tokio_tungstenite::tungstenite::Error::Capacity(capacity);
|
||||
let mapped = super::map_stream_error(backend_error);
|
||||
assert_eq!(mapped.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge);
|
||||
}
|
||||
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.
|
||||
246
deltas/0.3.4/alpha.1.md
Normal file
246
deltas/0.3.4/alpha.1.md
Normal file
@@ -0,0 +1,246 @@
|
||||
<!-- file: deltas/0.3.4/alpha.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-alpha.1
|
||||
|
||||
## Base
|
||||
|
||||
Base autoritaire : archive fournie `games-v0.3.3.zip`, téléchargée depuis le lien ZIP du tag Gitea `v0.3.3`.
|
||||
|
||||
La version workspace de la base est `0.3.3`. L'archive taggée est utilisée telle quelle conformément à `CMD-GIT-003` et `CMD-GIT-004`; aucun fichier local absent du ZIP n'est inventé ou réinjecté.
|
||||
|
||||
`0.3.2` reste différée conformément à la roadmap.
|
||||
|
||||
## Objet
|
||||
|
||||
Ouvrir `0.3.4` par le nouveau gate obligatoire `alpha.1` : migrer la nomenclature prospective de version, auditer complètement la stable et les règles, réévaluer les études réseau, vérifier les dépendances amont envisagées, décider l'ownership physique du transport realtime, fermer le contrat minimal WebSocket et créer le plan vivant jusqu'à stable.
|
||||
|
||||
Cette tranche n'ajoute volontairement aucune crate réseau, aucune dépendance Tokio/WebSocket et aucun comportement runtime. L'implémentation commence seulement après validation de ce cadrage.
|
||||
|
||||
## Migration de nomenclature
|
||||
|
||||
À partir de `0.3.4`, les nouvelles prereleases suivent exclusivement :
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
`alpha.1` remplace le rôle historique de `0-pre.1`.
|
||||
|
||||
La migration réconcilie les règles prospectives et les README/index actifs concernés. Les anciens deltas, historiques, prompts et entrées de changelog restent inchangés avec leurs identifiants `0-pre`, `1-alpha`, `2-beta` et `3-rc`.
|
||||
|
||||
L'audit workspace sépare désormais explicitement :
|
||||
|
||||
- la convention courante, exigée pour le workspace et les versions explicites des crates ;
|
||||
- la convention historique, acceptée uniquement pour les répertoires de versions déjà livrés avant `0.3.4`.
|
||||
|
||||
Ainsi un nouveau jalon `0.3.4-0-pre.*` est refusé sans casser la lecture de l'historique existant.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de :
|
||||
|
||||
```text
|
||||
0.3.3
|
||||
```
|
||||
|
||||
à :
|
||||
|
||||
```text
|
||||
0.3.4-alpha.1
|
||||
```
|
||||
|
||||
Aucune version npm/Tauri/Android n'est synchronisée : cette tranche ne change aucun produit packagé.
|
||||
|
||||
## 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), 242 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
L'inventaire indépendant du ZIP confirme :
|
||||
|
||||
```text
|
||||
400 fichiers
|
||||
246 fichiers Markdown
|
||||
55 fichiers Rust
|
||||
15 Cargo.toml, dont le manifest racine
|
||||
14 membres workspace
|
||||
workspace.package.version = 0.3.3
|
||||
0 symlink
|
||||
0 erreur de chemin/archive détectée
|
||||
```
|
||||
|
||||
Aucune arborescence générée `target/`, `node_modules/`, `gen/android/` ou `build/` n'est livrée dans l'archive.
|
||||
|
||||
Le log utilisateur fourni à l'ouverture de session confirme également sur son checkout `0.3.3` : format Cargo propre, audits propres, `cargo check --workspace` et Clippy workspace strict réussis. Son audit Markdown annonce `258` fichiers, soit davantage que le ZIP taggé fourni. Cette différence n'est pas masquée : le delta est construit exclusivement depuis l'archive autoritaire reçue.
|
||||
|
||||
## Audit des règles
|
||||
|
||||
La lecture intégrale demandée par `prompts/005-V0_3_4_START_PROMPT.md` confirme notamment :
|
||||
|
||||
- `alpha.1` porte le cadrage, le sizing, les risques, les validations et le plan avant développement lourd ;
|
||||
- les archives taggées peuvent être utilisées sans `.git` et constituent la baseline de travail déclarée ;
|
||||
- les historiques validés sont immuables ;
|
||||
- le transport doit rester séparé du wire codec, de la session, de la synchronisation et de la simulation authoritative ;
|
||||
- aucune dépendance réseau concrète ne doit remonter dans le gameplay ;
|
||||
- les builds, tests et smokes finaux sont attestés côté utilisateur ;
|
||||
- une modification Cargo impose format/check/Clippy workspace côté utilisateur ;
|
||||
- `CHANGELOG.md` reste silencieux avant RC et `ROADMAP.md` reste macroscopique.
|
||||
|
||||
La recherche prospective a trouvé des références à l'ancienne nomenclature au-delà des six fichiers minimum du prompt. `RULES_VALIDATION_MATRIX.md`, `docs/plans/000-README.md`, `history/000-README.md` et le `README.md` racine sont donc également réconciliés lorsqu'ils décrivent le workflow courant. Les références historiques restent intactes.
|
||||
|
||||
Aucune contradiction bloquante n'est trouvée entre le prompt, les règles, la roadmap, les études réseau et la baseline `v0.3.3`.
|
||||
|
||||
## Vérification des dépendances envisagées
|
||||
|
||||
État amont vérifié le 2026-09-21 pour préparer les tranches d'implémentation :
|
||||
|
||||
```text
|
||||
Tokio 1.53.1
|
||||
futures-util 0.3.34
|
||||
tokio-tungstenite 0.30.0
|
||||
tungstenite 0.30.0
|
||||
```
|
||||
|
||||
Constats retenus :
|
||||
|
||||
- Tokio `1.53.1` annonce un MSRV `1.71` ;
|
||||
- `tokio-tungstenite 0.30.0` et `tungstenite 0.30.0` annoncent un MSRV `1.85` ;
|
||||
- `tokio-tungstenite` fournit `connect`/`handshake` par défaut mais pas de backend TLS obligatoire ;
|
||||
- les features `native-tls` et `rustls-*` restent optionnelles ;
|
||||
- Tungstenite expose déjà des limites configurables de message, frame et write buffer.
|
||||
|
||||
Aucune de ces dépendances n'est ajoutée dans `alpha.1`. Le plan les introduira uniquement dans la crate backend qui les consomme.
|
||||
|
||||
## Ownership et contrat retenus
|
||||
|
||||
Le plan `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` retient deux frontières physiques :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-transport-lib
|
||||
crates/common/game-realtime-websocket-lib
|
||||
```
|
||||
|
||||
`game-realtime-transport-lib` portera uniquement le contrat transport-neutral : payload binaire opaque, send/receive/close, fermeture distante, erreurs transport-neutral et split concurrent. Elle ne dépendra ni de Tokio ni de Tungstenite.
|
||||
|
||||
`game-realtime-websocket-lib` portera l'établissement client/server, Tokio, `tokio-tungstenite`, le mapping des frames, les limites, timeouts, close handshake, tracing et tests loopback localhost.
|
||||
|
||||
Décisions structurantes :
|
||||
|
||||
- runtime Tokio possédé par l'application/service/test consommateur, jamais créé globalement par le backend ;
|
||||
- aucune tâche backend détachée nécessaire au chemin de base ;
|
||||
- aucun codec métier dans `0.3.4` : le transport véhicule des octets opaques ;
|
||||
- aucune abstraction générique `Connector`/`Provider`/runtime avant besoin démontré ;
|
||||
- baseline locale en `ws://`, sans choix TLS prématuré ;
|
||||
- aucune file interne non bornée ;
|
||||
- tests loopback sur `127.0.0.1:0`, sans Internet ni port fixe ;
|
||||
- aucune dépendance `tokio-tungstenite` dans `crates/games/` ou `crates/engines/`.
|
||||
|
||||
Les limites et timeouts candidats, le modèle d'erreur, le lifecycle, le tracing et la matrice de tests sont détaillés dans le plan actif. Les signatures Rust exactes restent volontairement à fermer dans `alpha.2`, après validation de ce cadrage.
|
||||
|
||||
## Forecast révisé
|
||||
|
||||
Le plan actif retient :
|
||||
|
||||
```text
|
||||
0.3.4-alpha.1 gouvernance + audit + ownership + contrat
|
||||
0.3.4-alpha.2 API game-realtime-transport-lib
|
||||
0.3.4-alpha.3 backend game-realtime-websocket-lib + loopback
|
||||
0.3.4-alpha.4 robustesse + limites + lifecycle + consolidation
|
||||
0.3.4-alpha.5 uniquement si un demo/consolidation autonome est réellement utile
|
||||
0.3.4-beta.1 validation large
|
||||
0.3.4-rc.1 candidate gelée + publication documentaire
|
||||
0.3.4 promotion mécanique stable
|
||||
```
|
||||
|
||||
Le scope reste compatible avec une seule version/session tant qu'il ne dérive pas vers le wire codec, la session multijoueur, TLS/PKI produit ou WebTransport/QUIC.
|
||||
|
||||
## Fichiers
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
deltas/0.3.4/alpha.1.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
RULES.md
|
||||
docs/000-README.md
|
||||
docs/plans/000-README.md
|
||||
docs/rules/PROMPT_STRUCTURE.md
|
||||
docs/rules/RULES_DOCUMENTATION.md
|
||||
docs/rules/RULES_SESSION_PLANNING.md
|
||||
docs/rules/RULES_VALIDATION_MATRIX.md
|
||||
docs/rules/VERSION_WORKFLOW.md
|
||||
history/000-README.md
|
||||
scripts/audit_project_workspace_rules.py
|
||||
```
|
||||
|
||||
`ROADMAP.md`, `CHANGELOG.md`, les anciens prompts, anciens deltas et anciens historiques restent inchangés.
|
||||
|
||||
## Validations exécutées dans l'environnement de génération
|
||||
|
||||
Après constitution de l'état livré, le générateur a exécuté uniquement les audits statiques autorisés :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 244 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
Le contrôle ciblé de politique de version a également obtenu :
|
||||
|
||||
```text
|
||||
history/0.3.4/0-pre.1.md -> DOC-010, rejet attendu
|
||||
history/0.3.4/alpha.1.md -> accepté
|
||||
targeted version-policy probe: clean
|
||||
```
|
||||
|
||||
L'historique antérieur à `0.3.4` reste accepté par l'audit workspace normal.
|
||||
|
||||
Aucun `cargo check`, Clippy, test ou smoke post-delta n'est attribué au générateur.
|
||||
|
||||
## Validation utilisateur demandée
|
||||
|
||||
Le manifest Cargo et l'audit Python changent. 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
|
||||
```
|
||||
|
||||
Aucun test workspace complet n'est demandé dans `alpha.1` : aucun code Rust, aucune dépendance réseau et aucun comportement runtime ne changent.
|
||||
|
||||
## Suite après validation
|
||||
|
||||
Si cette gate est propre, `alpha.2` crée `history/0.3.4/alpha.1.md` avec les sorties réellement obtenues, puis introduit `game-realtime-transport-lib` sans Tokio/Tungstenite.
|
||||
|
||||
Une erreur de migration, d'audit, de plan ou de versionnement produit d'abord `0.3.4-alpha.1.fix.N`.
|
||||
133
deltas/0.3.4/alpha.2.md
Normal file
133
deltas/0.3.4/alpha.2.md
Normal file
@@ -0,0 +1,133 @@
|
||||
<!-- file: deltas/0.3.4/alpha.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-alpha.2
|
||||
|
||||
## Base
|
||||
|
||||
Base : `0.3.4-alpha.1` validée par l'utilisateur le 2026-09-21.
|
||||
|
||||
Cette tranche matérialise uniquement la frontière transport-neutral prévue par le plan `0.3.4`. Elle n'ajoute encore ni Tokio, ni Tungstenite, ni socket, ni protocole de session/gameplay.
|
||||
|
||||
## Historique fermé
|
||||
|
||||
Ajout de :
|
||||
|
||||
```text
|
||||
history/0.3.4/alpha.1.md
|
||||
```
|
||||
|
||||
L'entrée enregistre les gates effectivement fournies par l'utilisateur pour `alpha.1`, notamment le rebuild complet après `cargo clean`, les audits propres, `cargo check --workspace` et Clippy workspace strict.
|
||||
|
||||
## Nouvelle crate `game-realtime-transport-lib`
|
||||
|
||||
Ajout de :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-transport-lib/Cargo.toml
|
||||
crates/common/game-realtime-transport-lib/src/lib.rs
|
||||
crates/common/game-realtime-transport-lib/src/connection.rs
|
||||
crates/common/game-realtime-transport-lib/src/error.rs
|
||||
crates/common/game-realtime-transport-lib/src/message.rs
|
||||
crates/common/game-realtime-transport-lib/unit_tests/contract.rs
|
||||
```
|
||||
|
||||
La crate appartient à `crates/common/` car le contrat realtime est indépendant d'une génération de moteur et d'un backend réseau concret.
|
||||
|
||||
Elle ne possède aucune dépendance tierce.
|
||||
|
||||
## Contrat public
|
||||
|
||||
Le payload transport est `TransportMessage`, un buffer binaire possédé sans sémantique gameplay ni codec.
|
||||
|
||||
`TransportReceive` distingue explicitement :
|
||||
|
||||
```text
|
||||
Message(TransportMessage)
|
||||
Closed
|
||||
```
|
||||
|
||||
Une fermeture distante propre n'est donc pas confondue avec une erreur I/O.
|
||||
|
||||
Les contrats sont :
|
||||
|
||||
```text
|
||||
RealtimeConnection
|
||||
RealtimeSender
|
||||
RealtimeReceiver
|
||||
```
|
||||
|
||||
`RealtimeConnection::split()` produit les deux moitiés indépendantes. Les opérations `send`, `close` et `receive` restent async sans imposer un runtime particulier.
|
||||
|
||||
L'implémentation du contrat utilise des futures associées GAT. Cette forme évite une allocation `Box<dyn Future>`, conserve le dispatch statique et n'impose pas de borne `Send` au niveau transport-neutral. Un backend natif reste libre de fournir des futures `Send`; un backend navigateur/WASM futur n'est pas exclu artificiellement.
|
||||
|
||||
## Erreurs
|
||||
|
||||
`TransportError` conserve une catégorie stable `TransportErrorKind` et un détail diagnostic backend-neutral.
|
||||
|
||||
Les catégories de baseline sont :
|
||||
|
||||
```text
|
||||
InvalidConfiguration
|
||||
Connect
|
||||
Bind
|
||||
Accept
|
||||
Timeout
|
||||
MessageTooLarge
|
||||
Backpressure
|
||||
Closed
|
||||
Io
|
||||
Protocol
|
||||
Aborted
|
||||
```
|
||||
|
||||
Les futurs types d'erreur Tungstenite ne sont pas exposés par cette crate.
|
||||
|
||||
## Tests
|
||||
|
||||
Les tests unitaires couvrent :
|
||||
|
||||
- conservation du payload binaire possédé, y compris le payload vide ;
|
||||
- distinction message/fermeture propre ;
|
||||
- split sender/receiver ;
|
||||
- futures associées de send/receive/close avec une implémentation test sans runtime ;
|
||||
- catégorie, détail et libellés des erreurs.
|
||||
|
||||
Aucun test réseau n'existe dans cette tranche puisqu'aucun backend réseau n'existe encore.
|
||||
|
||||
## Documentation locale
|
||||
|
||||
Aucun `README.md` ni `USAGE.md` local n'est ajouté pendant `alpha.2`. La crate est volontairement petite, son contrat est documenté par rustdoc et le plan central couvre encore ses frontières ; créer un guide local maintenant dupliquerait ces sources sans valeur durable supplémentaire. Ce choix sera réévalué pendant la consolidation finale conformément à `DOC-CRATE-001`.
|
||||
|
||||
## Fichiers existants modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
`Cargo.toml` ajoute le membre workspace et passe la version technique à `0.3.4-alpha.2`.
|
||||
|
||||
Le README racine annonce la candidate active. Le plan enregistre la validation de `alpha.1` et ferme le choix des futures associées GAT pour le contrat commun.
|
||||
|
||||
`ROADMAP.md` et `CHANGELOG.md` ne changent pas : le scope macroscopique de `0.3.4` est inchangé et une alpha n'ajoute normalement pas d'entrée de changelog.
|
||||
|
||||
## Validation attendue
|
||||
|
||||
Appliquer d'abord le formatage canonique, puis exécuter :
|
||||
|
||||
```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-realtime-transport-lib --all-targets --all-features
|
||||
```
|
||||
|
||||
Aucune gate Android, Web/Tauri ou réseau n'est requise : cette tranche n'affecte ni leurs contrats ni un backend socket.
|
||||
87
deltas/0.3.4/alpha.3.fix.1.md
Normal file
87
deltas/0.3.4/alpha.3.fix.1.md
Normal file
@@ -0,0 +1,87 @@
|
||||
<!-- file: deltas/0.3.4/alpha.3.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-alpha.3.fix.1
|
||||
|
||||
## Cause
|
||||
|
||||
La gate utilisateur de `0.3.4-alpha.3` s'est arrêtée avant compilation lors du chargement des manifests Cargo :
|
||||
|
||||
```text
|
||||
error inheriting `futures-util` from workspace root manifest's `workspace.dependencies.futures-util`
|
||||
|
||||
Caused by:
|
||||
`default-features = false` cannot override workspace's `default-features`
|
||||
```
|
||||
|
||||
Le même défaut était présent sur `tokio-tungstenite` mais n'était pas encore affiché parce que Cargo s'arrêtait sur la première dépendance invalide.
|
||||
|
||||
`alpha.3` n'est donc pas historisée comme validée et aucune tranche `alpha.4` n'est ouverte avant fermeture de ce défaut.
|
||||
|
||||
## Correction
|
||||
|
||||
Le workspace passe à :
|
||||
|
||||
```text
|
||||
0.3.4-alpha.3.fix.1
|
||||
```
|
||||
|
||||
Les deux dépendances concernées portent désormais la désactivation de leurs features par défaut directement dans `[workspace.dependencies]` :
|
||||
|
||||
```toml
|
||||
futures-util = { version = "0.3.34", default-features = false }
|
||||
tokio-tungstenite = { version = "0.30.0", default-features = false }
|
||||
```
|
||||
|
||||
La crate `game-realtime-websocket-lib` hérite ensuite de ces réglages et ajoute uniquement les features dont elle a besoin :
|
||||
|
||||
```toml
|
||||
futures-util = { workspace = true, features = ["sink", "std"] }
|
||||
tokio-tungstenite = { workspace = true, features = ["connect", "handshake"] }
|
||||
```
|
||||
|
||||
Les features Tokio restent inchangées. Aucun TLS n'est ajouté et aucun code Rust/API/comportement transport n'est modifié.
|
||||
|
||||
## Pourquoi ce placement
|
||||
|
||||
Cargo autorise une crate membre à enrichir les `features` d'une dépendance héritée du workspace, mais pas à remplacer localement la valeur `default-features` définie par l'héritage. La politique `default-features = false` doit donc être possédée par la déclaration workspace lorsqu'elle est commune à l'utilisation héritée.
|
||||
|
||||
Ce fix corrige les deux dépendances concernées immédiatement afin d'éviter qu'une seconde erreur identique apparaisse après correction de `futures-util` seulement.
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
crates/common/game-realtime-websocket-lib/Cargo.toml
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
Nouveau fichier :
|
||||
|
||||
```text
|
||||
deltas/0.3.4/alpha.3.fix.1.md
|
||||
```
|
||||
|
||||
Aucun fichier `history/0.3.4/alpha.3.md` n'est créé : une entrée `history/` n'existe qu'après validation réussie du jalon qu'elle décrit.
|
||||
|
||||
## 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
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
```
|
||||
|
||||
La validation recommence depuis le début de la gate, car `alpha.3` n'a jamais atteint une compilation Cargo valide.
|
||||
144
deltas/0.3.4/alpha.3.md
Normal file
144
deltas/0.3.4/alpha.3.md
Normal file
@@ -0,0 +1,144 @@
|
||||
<!-- file: deltas/0.3.4/alpha.3.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-alpha.3
|
||||
|
||||
## Mission
|
||||
|
||||
Introduire le premier backend concret de la frontière realtime validée en `alpha.2` : WebSocket async natif avec Tokio + `tokio-tungstenite`, sans TLS, sans protocole session/gameplay et sans dépendance backend dans les moteurs ou jeux.
|
||||
|
||||
## Historique fermé
|
||||
|
||||
`history/0.3.4/alpha.2.md` enregistre la validation utilisateur : audits propres, workspace/Clippy strict propres et `7` tests sur `game-realtime-transport-lib` passés.
|
||||
|
||||
Aucun `alpha.2.fix.N` n'est requis.
|
||||
|
||||
## Version et dépendances
|
||||
|
||||
Le workspace passe à :
|
||||
|
||||
```text
|
||||
0.3.4-alpha.3
|
||||
```
|
||||
|
||||
Dépendances tierces centralisées au workspace :
|
||||
|
||||
```text
|
||||
futures-util 0.3.34
|
||||
tokio 1.53.1
|
||||
tokio-tungstenite 0.30.0
|
||||
```
|
||||
|
||||
`game-realtime-websocket-lib` active uniquement les features dont elle a besoin :
|
||||
|
||||
```text
|
||||
futures-util: default-features=false, sink, std
|
||||
tokio: net
|
||||
tokio-tungstenite: default-features=false, connect, handshake
|
||||
```
|
||||
|
||||
Le harness de test ajoute localement les features Tokio `macros`, `rt` et `time`. Aucune feature TLS n'est activée.
|
||||
|
||||
## Nouvelle crate WebSocket
|
||||
|
||||
Ajout :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-websocket-lib/Cargo.toml
|
||||
crates/common/game-realtime-websocket-lib/src/lib.rs
|
||||
crates/common/game-realtime-websocket-lib/src/websocket.rs
|
||||
crates/common/game-realtime-websocket-lib/tests/loopback.rs
|
||||
```
|
||||
|
||||
La crate implémente le contrat `game-realtime-transport-lib` sans en modifier l'API.
|
||||
|
||||
### Client
|
||||
|
||||
`connect(&str)` établit une connexion WebSocket cliente à partir d'un endpoint `ws://`. Un endpoint hors baseline, notamment `wss://`, est rejeté comme `InvalidConfiguration` au lieu d'activer implicitement un backend TLS.
|
||||
|
||||
### Serveur
|
||||
|
||||
`WebSocketListener::bind(SocketAddr)` bind un `tokio::net::TcpListener`; `127.0.0.1:0` permet au système de choisir un port de test éphémère. `accept()` accepte le TCP puis effectue le handshake WebSocket serveur.
|
||||
|
||||
### Connexion
|
||||
|
||||
Client et serveur retournent le même `WebSocketConnection` public. Les représentations `tokio_tungstenite::WebSocketStream<...>` restent privées.
|
||||
|
||||
`RealtimeConnection::split()` produit `WebSocketSender` et `WebSocketReceiver`. Les futures concrètes sont boxées uniquement dans ce backend afin d'implémenter les associated futures GAT du contrat. Les représentations WebSocket/Tungstenite restent privées ; l'alias public `LocalBoxFuture` de `futures-util` sert uniquement de représentation concrète des associated futures du backend.
|
||||
|
||||
### Mapping WebSocket
|
||||
|
||||
```text
|
||||
Binary -> TransportReceive::Message
|
||||
Close -> TransportReceive::Closed
|
||||
Ping / Pong -> détail de contrôle backend, non remonté au gameplay
|
||||
Text -> TransportErrorKind::Protocol
|
||||
Frame -> TransportErrorKind::Protocol
|
||||
WriteBufferFull -> TransportErrorKind::Backpressure
|
||||
Capacity -> TransportErrorKind::MessageTooLarge
|
||||
I/O -> TransportErrorKind::Io
|
||||
closed/already -> TransportErrorKind::Closed
|
||||
```
|
||||
|
||||
Les erreurs de connect/bind/accept restent catégorisées au niveau de l'opération qui échoue et aucun `tungstenite::Error` ne fuit dans l'API publique.
|
||||
|
||||
## Tracing
|
||||
|
||||
Le target backend est :
|
||||
|
||||
```text
|
||||
games::realtime::websocket
|
||||
```
|
||||
|
||||
Les événements couvrent connect, bind, accept, send, receive, close et erreurs. Le contenu brut des payloads n'est jamais loggué ; seule leur longueur peut apparaître au niveau trace.
|
||||
|
||||
La crate ne configure aucun subscriber global.
|
||||
|
||||
## Preuve loopback
|
||||
|
||||
Le test d'intégration public :
|
||||
|
||||
```text
|
||||
binary_round_trip_and_clean_close_work_on_loopback
|
||||
```
|
||||
|
||||
utilise `127.0.0.1:0`, établit client et serveur sans Internet ni port fixe, puis vérifie :
|
||||
|
||||
- payload binaire client vers serveur ;
|
||||
- payload binaire serveur vers client ;
|
||||
- fermeture locale cliente observée comme fermeture distante propre côté serveur.
|
||||
|
||||
Une borne de trois secondes entoure les étapes asynchrones uniquement pour rendre le test déterministe en cas de régression ; elle ne constitue pas encore le timeout de transport produit, réservé à `alpha.4`.
|
||||
|
||||
## Documentation locale
|
||||
|
||||
Aucun `README.md` ou `USAGE.md` local n'est ajouté pour cette nouvelle crate pendant `alpha.3`. Le contrat reste petit, la rustdoc décrit l'API et le plan central porte les décisions d'architecture. Cette décision sera réévaluée pendant la consolidation finale conformément à `DOC-CRATE-*`.
|
||||
|
||||
## Fichiers existants modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
`ROADMAP.md` et `CHANGELOG.md` restent inchangés : le scope macro de `0.3.4` n'a pas changé et une alpha n'ajoute pas encore de changelog de release.
|
||||
|
||||
## 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
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
```
|
||||
|
||||
`cargo test --workspace --all-targets --all-features` reste réservé à la beta conformément au plan.
|
||||
157
deltas/0.3.4/alpha.4.md
Normal file
157
deltas/0.3.4/alpha.4.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<!-- file: deltas/0.3.4/alpha.4.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-alpha.4
|
||||
|
||||
## Base
|
||||
|
||||
Base directe : `0.3.4-alpha.3.fix.1`, validée par l'utilisateur le 2026-09-21.
|
||||
|
||||
Cette tranche n'élargit pas le contrat transport-neutral et n'introduit ni session gameplay, ni wire codec, ni TLS. Elle ferme les limites, deadlines et principaux cas négatifs du backend WebSocket avant la validation large.
|
||||
|
||||
## Historique du jalon précédent
|
||||
|
||||
Ajout de :
|
||||
|
||||
```text
|
||||
history/0.3.4/alpha.3.fix.1.md
|
||||
```
|
||||
|
||||
L'historique enregistre la gate utilisateur réellement fournie : audits propres, workspace/Clippy propres, sept tests transport, loopback WebSocket propre et graphe normal sans pile TLS.
|
||||
|
||||
## Configuration WebSocket produit
|
||||
|
||||
Ajout de `WebSocketConfig`, réexportée au crate-root de `game-realtime-websocket-lib`.
|
||||
|
||||
Valeurs par défaut :
|
||||
|
||||
```text
|
||||
max message size 1 MiB
|
||||
max frame size 1 MiB
|
||||
write buffer target 64 KiB
|
||||
max write buffer 2 MiB
|
||||
connect/handshake 10 s
|
||||
send 5 s
|
||||
close 2 s
|
||||
receive idle timeout aucun
|
||||
```
|
||||
|
||||
La configuration peut être ajustée par builders `with_*` puis validée. Les invariants refusent notamment :
|
||||
|
||||
- message/frame de taille nulle ;
|
||||
- frame maximale supérieure au message maximal ;
|
||||
- write buffer maximal incapable de contenir le target plus un message maximal ;
|
||||
- overflow de ce calcul ;
|
||||
- deadline connect/send/close nulle.
|
||||
|
||||
Les wrappers `connect()` et `WebSocketListener::bind()` restent disponibles avec la configuration par défaut. Les nouveaux chemins configurables sont :
|
||||
|
||||
```text
|
||||
connect_with_config(endpoint, config)
|
||||
WebSocketListener::bind_with_config(address, config)
|
||||
```
|
||||
|
||||
## Limites et backpressure
|
||||
|
||||
Les limites message/frame/write-buffer sont transmises à `tungstenite::protocol::WebSocketConfig` aussi bien côté client que côté serveur.
|
||||
|
||||
Le send vérifie également `max_message_size` et `max_frame_size` avant l'écriture afin que le dépassement sortant soit déterministe et remonte `TransportErrorKind::MessageTooLarge` sans dépendre d'un comportement réseau.
|
||||
|
||||
`max_write_buffer_size` est borné à `2 MiB` par défaut. `tungstenite::Error::WriteBufferFull` continue d'être mappé vers `TransportErrorKind::Backpressure` et ce mapping dispose désormais d'un test unitaire dédié. Aucun test loopback ne fabrique une saturation artificielle : le write buffer Tungstenite ne grossit au-delà de son target que lors d'échecs d'écriture sous-jacents, ce qui rendrait ce scénario réseau local non déterministe.
|
||||
|
||||
## Timeouts et lifecycle
|
||||
|
||||
La feature Tokio `time` devient une dépendance de production uniquement pour `game-realtime-websocket-lib`.
|
||||
|
||||
Les deadlines sont appliquées ainsi :
|
||||
|
||||
- connexion client complète : `connect_timeout` ;
|
||||
- handshake serveur après accept TCP : `connect_timeout` ;
|
||||
- émission d'un message : `send_timeout` ;
|
||||
- fermeture locale du sink : `close_timeout`.
|
||||
|
||||
L'attente d'un nouveau peer sur `TcpListener::accept()` n'est volontairement pas bornée par la configuration d'une connexion. `receive()` ne reçoit aucun idle timeout : heartbeat et inactivité appartiennent aux futures couches session/synchronisation.
|
||||
|
||||
Si un send expire, la moitié émission mémorise cet état et refuse un nouvel envoi avec `TransportErrorKind::Aborted`. Le send interrompu peut avoir progressé partiellement ; il ne doit donc jamais être rejoué implicitement comme s'il n'avait rien produit. Une fermeture explicite reste néanmoins tentable avec sa propre deadline.
|
||||
|
||||
Le backend continue de ne créer aucun runtime, thread ou task détachée.
|
||||
|
||||
## Tests ajoutés
|
||||
|
||||
Tests unitaires de configuration :
|
||||
|
||||
- defaults produit exacts ;
|
||||
- limites message/frame/write-buffer invalides ;
|
||||
- deadlines nulles invalides.
|
||||
|
||||
Tests unitaires de mapping backend :
|
||||
|
||||
- `WriteBufferFull -> Backpressure` ;
|
||||
- `Capacity -> MessageTooLarge`.
|
||||
|
||||
Nouveau `tests/robustness.rs` :
|
||||
|
||||
1. payload sortant hors limite rejeté avant écriture ;
|
||||
2. payload entrant hors limite rejeté par Tungstenite ;
|
||||
3. frame Text rejetée par le contrat binaire ;
|
||||
4. peer drop sans close handshake remonté comme erreur de protocole ;
|
||||
5. peer TCP silencieux borné par le timeout de handshake serveur.
|
||||
|
||||
Tous les scénarios réseau utilisent uniquement `127.0.0.1:0` et une borne externe de test de trois secondes.
|
||||
|
||||
## Documentation
|
||||
|
||||
Le plan actif `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` est mis à jour avec :
|
||||
|
||||
- validation effective de `alpha.3.fix.1` ;
|
||||
- configuration et deadlines retenues ;
|
||||
- raison de l'absence d'idle timeout transport ;
|
||||
- stratégie backpressure testable sans test réseau flaky ;
|
||||
- orientation vers beta ou courte consolidation selon le résultat réel de cette gate.
|
||||
|
||||
`CHANGELOG.md` et `ROADMAP.md` restent inchangés : la version est encore en alpha et le scope macroscopique `0.3.4` ne change pas.
|
||||
|
||||
## Fichiers
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
crates/common/game-realtime-websocket-lib/Cargo.toml
|
||||
crates/common/game-realtime-websocket-lib/src/lib.rs
|
||||
crates/common/game-realtime-websocket-lib/src/websocket.rs
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-websocket-lib/src/config.rs
|
||||
crates/common/game-realtime-websocket-lib/unit_tests/config.rs
|
||||
crates/common/game-realtime-websocket-lib/unit_tests/websocket.rs
|
||||
crates/common/game-realtime-websocket-lib/tests/robustness.rs
|
||||
history/0.3.4/alpha.3.fix.1.md
|
||||
deltas/0.3.4/alpha.4.md
|
||||
```
|
||||
|
||||
## 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
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
```
|
||||
|
||||
La validation utilisateur reste autoritaire pour la compilation, Clippy et les tests runtime. Les audits statiques exécutables dans l'environnement de génération doivent être propres avant livraison du delta.
|
||||
127
deltas/0.3.4/alpha.5.md
Normal file
127
deltas/0.3.4/alpha.5.md
Normal file
@@ -0,0 +1,127 @@
|
||||
<!-- file: deltas/0.3.4/alpha.5.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-alpha.5
|
||||
|
||||
## Base
|
||||
|
||||
Base directe : `0.3.4-alpha.4`, validée par l'utilisateur le 2026-09-21.
|
||||
|
||||
La gate `alpha.4` est entièrement propre, mais la revue post-validation a identifié une omission de processus : les preuves runtime réseau restent sous le harness de test Cargo. Cette tranche ajoute le smoke réellement exécutable avant toute promotion en beta.
|
||||
|
||||
## Historique du jalon précédent
|
||||
|
||||
Ajout de :
|
||||
|
||||
```text
|
||||
history/0.3.4/alpha.4.md
|
||||
```
|
||||
|
||||
L'historique enregistre les audits propres, check/Clippy propres, sept tests transport, onze tests WebSocket/robustesse et le graphe de dépendances sans TLS fournis par l'utilisateur.
|
||||
|
||||
## Launcher de smoke runtime
|
||||
|
||||
Ajout de la crate binaire :
|
||||
|
||||
```text
|
||||
crates/apps/game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
Ce launcher n'introduit aucune logique session/gameplay et n'ajoute aucune dépendance tierce nouvelle. Il consomme uniquement :
|
||||
|
||||
- `game-realtime-websocket-lib` ;
|
||||
- `game-realtime-transport-lib` ;
|
||||
- le logging commun ;
|
||||
- Tokio pour posséder le runtime du processus de smoke.
|
||||
|
||||
Le backend reste donc conforme à la décision d'ownership : `game-realtime-websocket-lib` ne crée toujours ni runtime ni thread privé.
|
||||
|
||||
## Scénario fumé
|
||||
|
||||
La commande :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
exécute hors harness `#[test]` le scénario suivant :
|
||||
|
||||
1. initialise le tracing applicatif commun ;
|
||||
2. bind un listener WebSocket sur `127.0.0.1:0` ;
|
||||
3. construit l'endpoint `ws://` à partir du port réellement alloué ;
|
||||
4. accepte un serveur et connecte un client via les API publiques ;
|
||||
5. envoie un payload binaire client → serveur et vérifie son contenu ;
|
||||
6. envoie un payload binaire serveur → client et vérifie son contenu ;
|
||||
7. initie un close propre côté client ;
|
||||
8. vérifie que le serveur observe `TransportReceive::Closed` ;
|
||||
9. affiche `game-realtime-websocket-smoke: PASS` et sort avec le code zéro.
|
||||
|
||||
Une deadline globale de cinq secondes empêche le smoke de rester bloqué en cas de régression. Toute erreur de bind/connect/send/receive/close, payload inattendu ou timeout produit un code de sortie non nul.
|
||||
|
||||
Le smoke ne dépend ni d'Internet, ni d'un service externe, ni d'un port fixe.
|
||||
|
||||
## Documentation locale
|
||||
|
||||
`DOC-CRATE-*` est réévalué pour le nouveau launcher. Aucun `README.md` ou `USAGE.md` local n'est ajouté : le binaire n'a aucune configuration, aucun argument et un unique workflow opérateur (`cargo run -p game-realtime-websocket-smoke`), entièrement documenté par le plan actif et le présent delta.
|
||||
|
||||
## Plan et version
|
||||
|
||||
L'audit de distribution exige désormais aussi la présence du manifeste du launcher de smoke afin que cette preuve runtime ne puisse pas disparaître silencieusement d'une archive future.
|
||||
|
||||
Le workspace passe à :
|
||||
|
||||
```text
|
||||
0.3.4-alpha.5
|
||||
```
|
||||
|
||||
Le plan `0.3.4` est corrigé pour distinguer explicitement les tests automatisés du smoke runtime et rendre ce dernier obligatoire avant l'entrée en beta.
|
||||
|
||||
`CHANGELOG.md` et `ROADMAP.md` restent inchangés : aucun scope macro ou jalon RC/stable n'est modifié.
|
||||
|
||||
## Fichiers
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
scripts/audit_distribution_layout.py
|
||||
```
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
crates/apps/game-realtime-websocket-smoke/Cargo.toml
|
||||
crates/apps/game-realtime-websocket-smoke/src/main.rs
|
||||
history/0.3.4/alpha.4.md
|
||||
deltas/0.3.4/alpha.5.md
|
||||
```
|
||||
|
||||
## 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
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
cargo tree -p game-realtime-websocket-smoke --edges normal
|
||||
```
|
||||
|
||||
La validation attendue du smoke est explicitement :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke: PASS
|
||||
```
|
||||
|
||||
avec code de sortie zéro. La compilation, les tests et le smoke restent exécutés côté utilisateur conformément à `CMD-BUILD-005`.
|
||||
106
deltas/0.3.4/beta.1.md
Normal file
106
deltas/0.3.4/beta.1.md
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- file: deltas/0.3.4/beta.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-beta.1
|
||||
|
||||
## Objectif
|
||||
|
||||
Entrer en beta après fermeture de la phase alpha : contrat transport-neutral, backend WebSocket, robustesse, tests localhost et smoke runtime public sont tous validés.
|
||||
|
||||
Cette tranche n'ajoute aucune fonctionnalité. Elle synchronise la version technique, historise `alpha.5` et ouvre la validation large prévue avant la consolidation de publication.
|
||||
|
||||
## Baseline acceptée
|
||||
|
||||
`0.3.4-alpha.5` est validée par l'utilisateur le 2026-09-21.
|
||||
|
||||
La baseline acceptée comprend :
|
||||
|
||||
- audits Rust/workspace, Markdown et distribution propres ;
|
||||
- `cargo check --workspace` et Clippy workspace strict propres ;
|
||||
- sept tests du contrat transport-neutral ;
|
||||
- cinq tests unitaires WebSocket ;
|
||||
- un test loopback WebSocket ;
|
||||
- cinq tests de robustesse WebSocket ;
|
||||
- smoke runtime hors harness `#[test]` avec bind `127.0.0.1:0`, échange binaire bidirectionnel, close propre et sortie `game-realtime-websocket-smoke: PASS` ;
|
||||
- graphe normal du launcher conforme aux frontières prévues.
|
||||
|
||||
`history/0.3.4/alpha.5.md` consigne le détail.
|
||||
|
||||
## Changements
|
||||
|
||||
- passage de `workspace.package.version` à `0.3.4-beta.1` ;
|
||||
- ajout de l'historique validé `alpha.5` ;
|
||||
- promotion du plan actif vers la phase beta ;
|
||||
- précision de la gate beta : test workspace complet, smoke runtime séparé et arbres de dépendances direct/inverse ;
|
||||
- aucune modification de code réseau, contrat transport, gameplay, moteur, SDL, Android, Tauri, WASM, asset ou dépendance tierce.
|
||||
|
||||
`CHANGELOG.md` et `ROADMAP.md` restent inchangés : la consolidation publiée et le gel de publication sont réservés aux tranches suivantes.
|
||||
|
||||
## Fichiers
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
history/0.3.4/alpha.5.md
|
||||
deltas/0.3.4/beta.1.md
|
||||
```
|
||||
|
||||
## 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 : `beta.1` constitue la première gate transverse de `0.3.4`.
|
||||
|
||||
## Smoke beta
|
||||
|
||||
Le smoke runtime reste une gate distincte du test workspace :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
Résultat attendu :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke: PASS
|
||||
```
|
||||
|
||||
avec code de sortie zéro.
|
||||
|
||||
## Frontières et dépendances
|
||||
|
||||
Revalider le graphe direct du backend puis ses consommateurs workspace :
|
||||
|
||||
```bash
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
cargo tree -i game-realtime-websocket-lib --workspace --edges normal
|
||||
```
|
||||
|
||||
L'arbre inverse ne doit révéler aucune dépendance depuis `crates/games/` ni `crates/engines/`. Le launcher de smoke est un consommateur attendu.
|
||||
|
||||
## Transition
|
||||
|
||||
Si la gate workspace, le smoke et les graphes sont propres, aucune beta supplémentaire n'est créée par cérémonie. La tranche suivante doit effectuer la consolidation finale exigée par `VER-PHASE-010` : documentation durable, `CHANGELOG.md`, `ROADMAP.md`, prompt de la version/session suivante et préparation du gel RC.
|
||||
|
||||
Toute régression imputable au projet produit d'abord `0.3.4-beta.1.fix.N`. Une capacité fonctionnelle manquante réouvre une alpha.
|
||||
151
deltas/0.3.4/rc.1.md
Normal file
151
deltas/0.3.4/rc.1.md
Normal file
@@ -0,0 +1,151 @@
|
||||
<!-- file: deltas/0.3.4/rc.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4-rc.1
|
||||
|
||||
## Base requise
|
||||
|
||||
`0.3.4-beta.1`, validée par l'utilisateur le 2026-09-21.
|
||||
|
||||
## Objectif
|
||||
|
||||
Créer la candidate de publication `0.3.4` après validation large de la baseline realtime, sans rouvrir le comportement réseau.
|
||||
|
||||
Cette tranche ferme `VER-PHASE-010` : historique beta, documentation durable, changelog, prompt de la version suivante et gel RC.
|
||||
|
||||
## Gel fonctionnel
|
||||
|
||||
`0.3.4-rc.1` n'ajoute :
|
||||
|
||||
- aucun transport ;
|
||||
- aucune API réseau ;
|
||||
- aucune dépendance tierce ;
|
||||
- aucun TLS ;
|
||||
- aucun wire codec ;
|
||||
- aucun protocole session/joueur/room ;
|
||||
- aucune synchronisation gameplay ;
|
||||
- aucune simulation authoritative ;
|
||||
- aucun changement moteur, jeu, SDL, Android, Tauri ou WASM.
|
||||
|
||||
Les seules modifications techniques sont la promotion de `workspace.package.version` vers `0.3.4-rc.1`. Le code Rust realtime validé en beta reste inchangé.
|
||||
|
||||
Tout défaut de publication fermé relève de `0.3.4-rc.1.fix.N` selon `VER-RC-*`. Une réouverture fonctionnelle invalide la RC.
|
||||
|
||||
## Validation beta acquise
|
||||
|
||||
`history/0.3.4/beta.1.md` consigne la gate réelle :
|
||||
|
||||
- audits Rust/workspace, Markdown et distribution propres ;
|
||||
- `cargo check --workspace` propre ;
|
||||
- Clippy workspace strict propre ;
|
||||
- `cargo test --workspace --all-targets --all-features` : 53 tests réussis, 0 échec ;
|
||||
- smoke runtime `game-realtime-websocket-smoke: PASS` ;
|
||||
- backend sur Tokio `1.53.1`, tokio-tungstenite/Tungstenite `0.30.0`, sans pile TLS ;
|
||||
- arbre inverse limité à `game-realtime-websocket-smoke`, sans dépendance backend dans `crates/games/` ou `crates/engines/`.
|
||||
|
||||
Aucune beta supplémentaire n'est créée uniquement par cérémonie.
|
||||
|
||||
## Consolidation documentaire
|
||||
|
||||
La revue `DOC-CRATE-*` conclut :
|
||||
|
||||
- `game-realtime-transport-lib` reçoit un `README.md` durable décrivant son contrat, ses frontières et le sens des dépendances ; son API est assez petite pour ne pas justifier un `USAGE.md` séparé ;
|
||||
- `game-realtime-websocket-lib` reçoit un `README.md` durable et un `USAGE.md`, car configuration, client/listener, limites, deadlines et smoke constituent un workflow de consommation non trivial ;
|
||||
- `game-realtime-websocket-smoke` reste sans document local supplémentaire : la commande Cargo unique, le plan et le delta couvrent entièrement son rôle.
|
||||
|
||||
Les documents locaux ne contiennent aucun journal de prerelease.
|
||||
|
||||
## Changelog et roadmap
|
||||
|
||||
`CHANGELOG.md` reçoit l'entrée `0.3.4-rc.1` avec le scope réellement validé.
|
||||
|
||||
`ROADMAP.md` est relue mais reste inchangée : `0.3.4` ne devient `(x)` qu'après validation de la RC et promotion stable. `0.3.5` reste le POC WebTransport/QUIC prévu ensuite.
|
||||
|
||||
## Prompt suivant
|
||||
|
||||
Ajout de :
|
||||
|
||||
```text
|
||||
prompts/006-V0_3_5_START_PROMPT.md
|
||||
```
|
||||
|
||||
Le prompt part de la future stable/taggée `v0.3.4`, rappelle que la RC/stable doit être terminée si nécessaire, et cadre `0.3.5-alpha.1` autour de :
|
||||
|
||||
- recherche actuelle des stacks WebTransport/QUIC ;
|
||||
- challenge du contrat transport-neutral livré par `0.3.4` ;
|
||||
- plateformes navigateur/native/Android réellement testables ;
|
||||
- exigences TLS/certificats/HTTP3/UDP ;
|
||||
- fallback WebSocket au niveau d'ownership approprié ;
|
||||
- comparaison mesurée sans présumer que WebTransport doit être retenu ;
|
||||
- maintien hors scope des couches session/synchronisation/simulation.
|
||||
|
||||
Le prompt ne présente aucune validation future de la RC ou de la stable comme acquise.
|
||||
|
||||
## Fichiers
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
docs/000-README.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
```
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-transport-lib/README.md
|
||||
crates/common/game-realtime-websocket-lib/README.md
|
||||
crates/common/game-realtime-websocket-lib/USAGE.md
|
||||
history/0.3.4/beta.1.md
|
||||
prompts/006-V0_3_5_START_PROMPT.md
|
||||
deltas/0.3.4/rc.1.md
|
||||
```
|
||||
|
||||
## Validation RC
|
||||
|
||||
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-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
cargo tree -i game-realtime-websocket-lib --workspace --edges normal
|
||||
```
|
||||
|
||||
Le test workspace complet n'est pas répété par cérémonie : aucun Rust, dépendance ou contrat transverse n'a changé depuis la gate beta qui l'a déjà validé. Il redevient obligatoire si un fix RC touche ces éléments.
|
||||
|
||||
Le smoke doit afficher :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke: PASS
|
||||
```
|
||||
|
||||
L'arbre inverse doit continuer à montrer uniquement le launcher de smoke comme consommateur du backend WebSocket.
|
||||
|
||||
## Après validation
|
||||
|
||||
Si la RC est propre, la promotion vers `0.3.4` est mécanique :
|
||||
|
||||
- `workspace.package.version = 0.3.4` ;
|
||||
- ajout de `history/0.3.4/rc.1.md` ;
|
||||
- entrée stable dans `CHANGELOG.md` ;
|
||||
- `ROADMAP.md` passe `0.3.4` en `(x)` ;
|
||||
- clôture du plan `004` et de son index ;
|
||||
- ajustement mécanique de `prompts/006-V0_3_5_START_PROMPT.md` si une référence devient certaine ;
|
||||
- `deltas/0.3.4/rel.001.md`.
|
||||
|
||||
Aucun nouveau comportement ne doit apparaître dans cette promotion.
|
||||
64
deltas/0.3.4/rel.001.md
Normal file
64
deltas/0.3.4/rel.001.md
Normal file
@@ -0,0 +1,64 @@
|
||||
<!-- file: deltas/0.3.4/rel.001.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.3.4 — release stable
|
||||
|
||||
## Base
|
||||
|
||||
Base validée : `0.3.4-rc.1`.
|
||||
|
||||
## Objet
|
||||
|
||||
Promouvoir mécaniquement la candidate validée vers `0.3.4` sans introduire de nouveau comportement.
|
||||
|
||||
## Changements
|
||||
|
||||
La release stable :
|
||||
|
||||
- passe `workspace.package.version` de `0.3.4-rc.1` à `0.3.4` ;
|
||||
- positionne `README.md` sur `0.3.4` comme stable de référence et `0.3.5-alpha.1` comme prochaine version planifiée ;
|
||||
- marque `0.3.4` terminée dans `ROADMAP.md` ;
|
||||
- ajoute l'entrée stable `0.3.4` dans `CHANGELOG.md` ;
|
||||
- enregistre la validation effective de la RC dans `history/0.3.4/rc.1.md` ;
|
||||
- clôt `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` et ses deux entrées d'index ;
|
||||
- ajuste mécaniquement `prompts/006-V0_3_5_START_PROMPT.md` afin qu'il parte explicitement de la stable taggée `v0.3.4` et des validations réellement acquises.
|
||||
|
||||
## Frontière de release
|
||||
|
||||
Aucun fichier Rust, test Rust, manifeste de crate realtime, dépendance tierce, frontend, Android, gameplay, moteur, comportement WebSocket, limite, timeout ou contrat transport n'est modifié après la RC.
|
||||
|
||||
La stable conserve notamment :
|
||||
|
||||
- `game-realtime-transport-lib` transport-neutral ;
|
||||
- `game-realtime-websocket-lib` comme backend WebSocket de référence ;
|
||||
- `game-realtime-websocket-smoke` comme preuve runtime localhost ;
|
||||
- l'absence de dépendance du backend dans `crates/games/` et `crates/engines/` ;
|
||||
- WebTransport/QUIC, TLS produit, wire/session, synchronisation et simulation authoritative hors scope de `0.3.4`.
|
||||
|
||||
## Validation proportionnelle
|
||||
|
||||
La gate RC complète a déjà été validée sur un état fonctionnellement identique : audits propres, check/Clippy, 18 tests realtime ciblés, smoke runtime `PASS` et arbre inverse conforme. `beta.1` avait en outre validé `cargo test --workspace --all-targets --all-features` avec 53 tests réussis.
|
||||
|
||||
La promotion stable demande uniquement les contrôles proportionnels aux changements de version et de 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
|
||||
```
|
||||
|
||||
Clippy strict, suites de tests et smoke runtime ne sont pas répétés par cérémonie puisque la promotion stable ne modifie aucun code ni comportement. Tout échec de la gate ci-dessus doit néanmoins être corrigé avant publication.
|
||||
|
||||
## Publication
|
||||
|
||||
Après validation de ce delta :
|
||||
|
||||
- commit de release ;
|
||||
- tag stable unique `v0.3.4` ;
|
||||
- archive ZIP du tag utilisée comme baseline autoritaire de la session suivante ;
|
||||
- démarrage de `0.3.5` uniquement depuis `v0.3.4` avec `prompts/006-V0_3_5_START_PROMPT.md` ;
|
||||
- première tranche suivante : `0.3.5-alpha.1`, audit/recherche WebTransport/QUIC et création du plan `docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md`.
|
||||
296
deltas/0.3.5/alpha.1.md
Normal file
296
deltas/0.3.5/alpha.1.md
Normal file
@@ -0,0 +1,296 @@
|
||||
<!-- file: deltas/0.3.5/alpha.1.md -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Delta 0.3.5-alpha.1
|
||||
|
||||
## Base
|
||||
|
||||
Base autoritaire : archive fournie `games-v0.3.4.zip`, annoncée comme téléchargement ZIP du tag Gitea `v0.3.4`.
|
||||
|
||||
La version workspace de la base est :
|
||||
|
||||
```text
|
||||
0.3.4
|
||||
```
|
||||
|
||||
L'archive taggée est utilisée telle quelle conformément à `CMD-GIT-003` et `CMD-GIT-004`. L'absence de `.git` est normale et aucun fichier local absent du ZIP n'est inventé.
|
||||
|
||||
## Objet
|
||||
|
||||
Ouvrir `0.3.5` par le gate obligatoire `alpha.1` : auditer la stable, relire les preuves `0.3.4`, rechercher l'écosystème WebTransport/QUIC actuel, sélectionner la stack POC primaire, challenger le contrat commun, fermer la stratégie TLS/fallback, ajouter les smoke tests nécessaires et redécouper la version afin que chaque delta reste normalement dans la cible 15 à 30 minutes.
|
||||
|
||||
Cette tranche n'ajoute aucune crate WebTransport, aucune dépendance QUIC/Rustls et aucun comportement runtime.
|
||||
|
||||
## Version
|
||||
|
||||
La version workspace passe de :
|
||||
|
||||
```text
|
||||
0.3.4
|
||||
```
|
||||
|
||||
à :
|
||||
|
||||
```text
|
||||
0.3.5-alpha.1
|
||||
```
|
||||
|
||||
Conformément à `VER-DOCFIX-002`, une prerelease non-fix synchronise sa version technique même si le contenu de cette tranche est principalement documentaire.
|
||||
|
||||
Aucune version npm/Tauri/Android n'est modifiée : aucun package de ces plateformes n'est touché.
|
||||
|
||||
## 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), 263 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
Inventaire indépendant :
|
||||
|
||||
```text
|
||||
437 fichiers dans le ZIP
|
||||
267 fichiers Markdown extraits
|
||||
68 fichiers Rust
|
||||
18 Cargo.toml, dont le manifest racine
|
||||
17 membres workspace
|
||||
workspace.package.version = 0.3.4
|
||||
0 symlink
|
||||
0 chemin absolu/traversal
|
||||
0 target/, node_modules/, build/ ou gen/android/ généré
|
||||
```
|
||||
|
||||
Le log utilisateur fourni à l'ouverture de session confirme également :
|
||||
|
||||
```text
|
||||
cargo fmt --all -- --check : clean
|
||||
audits Rust/Markdown/distribution : clean
|
||||
cargo check --workspace : clean
|
||||
```
|
||||
|
||||
Son audit Markdown annonce `279` fichiers contre `263` dans le scan de l'archive fournie. Le delta reste construit exclusivement depuis le ZIP autoritaire reçu et enregistre cet écart sans l'extrapoler.
|
||||
|
||||
## Audit des règles et du workflow
|
||||
|
||||
La lecture du prompt `006`, de `RULES.md`, des règles détaillées, des documents réseau/plateforme et des preuves `0.3.4` confirme :
|
||||
|
||||
- `alpha.1` doit produire le plan actif avant développement lourd ;
|
||||
- un delta vise environ 15 à 30 minutes de travail effectif ;
|
||||
- une tranche clairement trop lourde doit être scindée avant exécution ;
|
||||
- le full test workspace doit être rare et explicitement planifié ;
|
||||
- une tranche de consolidation doit apparaître avant la candidate finale ;
|
||||
- les builds/tests/smokes de validation sont attestés côté utilisateur ;
|
||||
- les audits statiques peuvent être exécutés dans l'environnement de génération ;
|
||||
- `history/` n'est créé qu'après validation réelle d'un jalon ;
|
||||
- `CHANGELOG.md` reste normalement silencieux avant la phase de consolidation/RC ;
|
||||
- `ROADMAP.md` reste macroscopique et n'a pas besoin de changer pour ce cadrage.
|
||||
|
||||
Le forecast initial du prompt est fonctionnellement correct mais trop agrégé sur trois risques : backend natif, browser/TLS et fallback/mesures. Le plan actif les sépare et ajoute `beta.2` comme consolidation explicite pré-RC.
|
||||
|
||||
## Recherche WebTransport/QUIC actuelle
|
||||
|
||||
État vérifié au 2026-09-21 :
|
||||
|
||||
```text
|
||||
web-transport 0.12.0
|
||||
web-transport-quinn 0.12.1
|
||||
web-transport-wasm 0.6.0
|
||||
wtransport 0.7.2
|
||||
```
|
||||
|
||||
Constats structurants :
|
||||
|
||||
- `web-transport` fournit une façade native + WASM, avec Quinn côté natif et API navigateur côté WASM ;
|
||||
- la documentation amont traite explicitement la différence `Send`/`!Send` entre natif et WASM, cohérente avec la frontière `0.3.4` ;
|
||||
- le build WASM nécessite actuellement `--cfg=web_sys_unstable_apis` ;
|
||||
- `wtransport` reste un candidat natif robuste et documenté, notamment pour certificats/hash W3C, mais ne fournit pas la même façade Rust WASM ;
|
||||
- MDN classe WebTransport « Baseline 2026 » depuis mars 2026 sur navigateurs récents et impose un contexte sécurisé ;
|
||||
- WebTransport/HTTP3 reste `draft-ietf-webtrans-http3-16`, Internet-Draft en WG Last Call, donc pas encore un RFC final.
|
||||
|
||||
L'étude détaillée est ajoutée dans :
|
||||
|
||||
```text
|
||||
docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md
|
||||
```
|
||||
|
||||
## Stack primaire retenue
|
||||
|
||||
Le POC commence avec la famille :
|
||||
|
||||
```text
|
||||
web-transport 0.12.x
|
||||
```
|
||||
|
||||
`wtransport 0.7.x` reste le candidat de repli si une exigence concrète échoue.
|
||||
|
||||
Aucune dépendance n'est ajoutée dans `alpha.1`; la résolution Cargo exacte et le choix des features crypto sont fermés dans `alpha.2` avec `cargo tree`.
|
||||
|
||||
## Contrat transport et framing
|
||||
|
||||
Le contrat `game-realtime-transport-lib` reste inchangé.
|
||||
|
||||
Pour préserver sa sémantique fiable/ordonnée/message-oriented, le backend WebTransport utilisera un stream bidirectionnel principal et un framing privé :
|
||||
|
||||
```text
|
||||
u32 big-endian length + payload bytes
|
||||
```
|
||||
|
||||
La taille est bornée avant allocation/écriture. Ce framing n'est pas un wire codec métier et ne contient aucun joueur, room, tick ou version de protocole gameplay.
|
||||
|
||||
Les datagrams restent hors du contrat commun parce qu'ils sont non fiables/non ordonnés. Ils seront challengés séparément sans créer une capability commune prématurée.
|
||||
|
||||
## TLS et navigateur
|
||||
|
||||
Le POC retient une stratégie de développement compatible avec les contraintes W3C :
|
||||
|
||||
- certificat self-signed court ;
|
||||
- ECDSA P-256 ;
|
||||
- validité inférieure à deux semaines ;
|
||||
- hash SHA-256 épinglé côté client ;
|
||||
- aucune clé privée durable dans le dépôt ;
|
||||
- aucune désactivation permanente de validation TLS.
|
||||
|
||||
Le smoke navigateur est désormais une preuve explicitement planifiée et distincte du simple build WASM.
|
||||
|
||||
## Fallback
|
||||
|
||||
Le fallback WebSocket est possédé par la composition/application du POC :
|
||||
|
||||
```text
|
||||
attempt WebTransport
|
||||
success -> WebTransport
|
||||
classified unavailable/establishment failure -> WebSocket
|
||||
```
|
||||
|
||||
Le plan exige une branche WebTransport forcée, une branche fallback forcée et une erreur non-fallback visible. Aucun `TransportManager` ou registry n'est créé dans ce cadrage.
|
||||
|
||||
## Forecast révisé
|
||||
|
||||
Le plan actif retient désormais :
|
||||
|
||||
```text
|
||||
0.3.5-alpha.1 audit + recherche + design + plan
|
||||
0.3.5-alpha.2 crate/dépendances + établissement natif/TLS pinning
|
||||
0.3.5-alpha.3 stream fiable + framing + contrat commun
|
||||
0.3.5-alpha.4 robustesse/lifecycle/limites/timeouts
|
||||
0.3.5-alpha.5 smoke WebTransport natif + graphe
|
||||
0.3.5-alpha.6 chemin Rust WASM compilable
|
||||
0.3.5-alpha.7 smoke navigateur + TLS local réel
|
||||
0.3.5-alpha.8 fallback WebSocket au niveau composition
|
||||
0.3.5-alpha.9 datagram POC conditionnel et isolé
|
||||
0.3.5-alpha.10 mesures WebSocket vs WebTransport
|
||||
0.3.5-beta.1 validation large + full workspace + smokes
|
||||
0.3.5-beta.2 consolidation durable + conclusion + prompt 0.3.6
|
||||
0.3.5-rc.1 candidate gelée + gates de publication
|
||||
0.3.5 promotion stable mécanique
|
||||
```
|
||||
|
||||
Les numéros restent souples : une tranche peut être fusionnée si elle devient micro-scopique ou scindée avant exécution si elle dépasse clairement 30 minutes.
|
||||
|
||||
Le point important est que `alpha.6` et `alpha.7` sont séparées : compiler le client Rust WASM et faire fonctionner un navigateur avec certificat/UDP/serveur sont deux risques différents. De même, la consolidation n'est plus reportée dans la RC.
|
||||
|
||||
## Full tests et smoke tests planifiés
|
||||
|
||||
`cargo test --workspace --all-targets --all-features` est explicitement réservé à :
|
||||
|
||||
```text
|
||||
0.3.5-beta.1
|
||||
0.3.5-rc.1
|
||||
```
|
||||
|
||||
sauf changement transverse inattendu imposant une gate supplémentaire.
|
||||
|
||||
Smokes prévus :
|
||||
|
||||
```text
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
cargo run -p game-realtime-webtransport-smoke
|
||||
browser/WASM WebTransport smoke (workflow fixé en alpha.7)
|
||||
fallback branch smoke/proof (intégré à un launcher existant si possible)
|
||||
```
|
||||
|
||||
Le benchmark reste un outil borné, pas une infrastructure permanente disproportionnée.
|
||||
|
||||
## Fichiers
|
||||
|
||||
Ajoutés :
|
||||
|
||||
```text
|
||||
deltas/0.3.5/alpha.1.md
|
||||
docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md
|
||||
docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md
|
||||
```
|
||||
|
||||
Modifiés :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
README.md
|
||||
docs/000-README.md
|
||||
docs/plans/000-README.md
|
||||
docs/studies/000-README.md
|
||||
```
|
||||
|
||||
Inchangés volontairement :
|
||||
|
||||
```text
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
prompts/006-V0_3_5_START_PROMPT.md
|
||||
crates/**
|
||||
Android/**
|
||||
Web/**
|
||||
```
|
||||
|
||||
## Validations exécutées dans l'environnement de génération
|
||||
|
||||
Après constitution de l'état livré, le générateur a exécuté uniquement les audits statiques autorisés :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 266 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
|
||||
alpha.1 targeted consistency: clean
|
||||
```
|
||||
|
||||
Le contrôle ciblé confirme en particulier `workspace.package.version = 0.3.5-alpha.1`, la présence du plan/étude/delta, l'absence volontaire de `history/0.3.5/alpha.1.md` avant validation et l'absence d'entrée `0.3.5-alpha.1` dans `CHANGELOG.md`.
|
||||
|
||||
Aucun `cargo check`, Clippy, test, benchmark ou smoke post-delta n'est attribué au générateur.
|
||||
|
||||
## Validation utilisateur demandée
|
||||
|
||||
`Cargo.toml` change uniquement pour la version workspace et les fichiers Markdown changent. Aucune dépendance ni source Rust n'est encore ajoutée.
|
||||
|
||||
Depuis la racine :
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Clippy et tests ne sont pas requis par cérémonie dans `alpha.1` car aucun code Rust, feature ou dépendance fonctionnelle n'est modifié. Le log d'ouverture a déjà confirmé `cargo check --workspace` propre sur la stable `0.3.4`; la commande ci-dessus valide la synchronisation de version du nouvel état.
|
||||
|
||||
## Suite après validation
|
||||
|
||||
Si cette gate est propre, `alpha.2` :
|
||||
|
||||
- crée `history/0.3.5/alpha.1.md` à partir de la sortie réelle ;
|
||||
- ajoute `game-realtime-webtransport-lib` ;
|
||||
- introduit la stack primaire avec features minimales ;
|
||||
- ferme la configuration TLS/hash et l'établissement natif ;
|
||||
- ne cherche pas encore à absorber framing, robustesse, browser smoke et fallback dans la même tranche.
|
||||
|
||||
Un échec du cadrage, des audits ou de la synchronisation de version produit d'abord `0.3.5-alpha.1.fix.N`.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/000-README.md -->
|
||||
<!-- version: 29 -->
|
||||
<!-- version: 35 -->
|
||||
|
||||
# Documentation games.sasedev
|
||||
|
||||
@@ -11,12 +11,16 @@
|
||||
|
||||
- [`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.
|
||||
- [`studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md`](studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md) — audit `0.3.5` des stacks WebTransport/QUIC, du contrat transport, de TLS et des plateformes.
|
||||
|
||||
## 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 actif de `0.3.1`, second host Snake Tauri Android.
|
||||
- [`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 clôturé de `0.3.3`, pipeline Android SDL3 natif Gradle/Cargo multi-ABI.
|
||||
- [`plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md`](plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md) — plan clôturé de `0.3.4`, API de transport realtime et baseline WebSocket Tokio/tokio-tungstenite.
|
||||
- [`plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md`](plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md) — plan actif de `0.3.5`, POC WebTransport/QUIC, navigateur, fallback WebSocket et mesures comparatives.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -71,6 +75,7 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](ru
|
||||
- [`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.
|
||||
@@ -79,6 +84,9 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](ru
|
||||
- [`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.
|
||||
- [`../crates/common/game-realtime-transport-lib/README.md`](../crates/common/game-realtime-transport-lib/README.md) — contrat realtime transport-neutral, payload binaire, split et erreurs stables.
|
||||
- [`../crates/common/game-realtime-websocket-lib/README.md`](../crates/common/game-realtime-websocket-lib/README.md) — backend WebSocket Tokio/tokio-tungstenite, frontières et configuration produit.
|
||||
- [`../crates/common/game-realtime-websocket-lib/USAGE.md`](../crates/common/game-realtime-websocket-lib/USAGE.md) — connexion client, listener serveur, limites, deadlines et smoke localhost.
|
||||
|
||||
## Historique validé
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/development/006-ANDROID_RUST_NATIVE_BUILD.md -->
|
||||
<!-- version: 2 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Build Rust Android natif
|
||||
|
||||
@@ -12,95 +12,120 @@ Le crate Rust `game-android-entrypoint` est un `cdylib`. Il est compilé avec ex
|
||||
- `reflex` ;
|
||||
- `snake`.
|
||||
|
||||
Le même nom de bibliothèque native peut être utilisé dans les deux APK puisque chaque module Android possède son propre répertoire `jniLibs`.
|
||||
Le même nom de bibliothèque native peut être utilisé dans les deux APK puisque chaque module Android possède son propre ensemble de `jniLibs` générés.
|
||||
|
||||
## SDL3 AAR et Prefab
|
||||
|
||||
L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab.
|
||||
|
||||
Pour `arm64-v8a`, la structure attendue est notamment :
|
||||
L'AAR officiel SDL3 expose ses bibliothèques natives avec Prefab. Pour chaque ABI, la logique Gradle commune cherche d'abord :
|
||||
|
||||
```text
|
||||
prefab/modules/SDL3/libs/android.arm64-v8a/libSDL3.so
|
||||
prefab/modules/SDL3/libs/android.<abi>/libSDL3.so
|
||||
```
|
||||
|
||||
Le script `scripts/build_android_rust.py` accepte cette structure officielle et conserve un fallback `jni/<abi>/libSDL3.so` pour compatibilité.
|
||||
Elle accepte aussi `jni/<abi>/libSDL3.so`, puis un fallback Prefab univoque. Pour chaque ABI elle :
|
||||
|
||||
Pour chaque application, le script :
|
||||
1. extrait `libSDL3.so` depuis l'AAR dans un répertoire temporaire de linkage ;
|
||||
2. stage `libSDL3.so` dans les `jniLibs` générés du variant ;
|
||||
3. lance `cargo ndk` avec exactement une feature jeu ;
|
||||
4. stage `libgame_android_entrypoint.so` dans le même arbre généré ;
|
||||
5. vérifie la présence des deux bibliothèques avant de rendre l'output au variant AGP.
|
||||
|
||||
1. extrait `libSDL3.so` depuis l'AAR ;
|
||||
2. l'utilise comme bibliothèque de liaison pour le build Rust ;
|
||||
3. copie `libSDL3.so` dans le `jniLibs/<abi>/` du module Android ;
|
||||
4. lance `cargo ndk` ;
|
||||
5. vérifie la présence de `libgame_android_entrypoint.so`.
|
||||
|
||||
Ainsi, `SDLActivity` dispose au runtime de `libSDL3.so` et de la bibliothèque Rust applicative.
|
||||
Les `jniLibs` générés sont déclarés à la Variant API via `variant.sources.jniLibs.addGeneratedSourceDirectory`. Gradle possède ainsi la dépendance entre le packaging Android et le build natif au lieu d'exiger une commande préalable séparée.
|
||||
|
||||
## Toolchain
|
||||
|
||||
SDL Android requiert actuellement SDK 35 ou ultérieur, NDK r28c ou ultérieur et API minimale 21. La baseline projet conserve `compileSdk 36`, `minSdk 21` et l'AAR SDL3 3.4.16.
|
||||
|
||||
Installer au minimum :
|
||||
|
||||
```bash
|
||||
rustup target add aarch64-linux-android
|
||||
cargo install cargo-ndk
|
||||
```
|
||||
|
||||
NDK de référence :
|
||||
Le contrat projet utilise :
|
||||
|
||||
```text
|
||||
r28c = 28.2.13676358
|
||||
AGP 9.4.0
|
||||
Gradle minimum 9.6.0
|
||||
JDK runtime >= 17, fourni par l'environnement
|
||||
Java source/target 17
|
||||
compileSdk 36
|
||||
minSdk 21
|
||||
NDK 28.2.13676358 / r28c
|
||||
SDL3 AAR 3.4.16
|
||||
cargo-ndk requis côté développeur
|
||||
```
|
||||
|
||||
Avec le SDK manager :
|
||||
`Android/settings.gradle` compare `GradleVersion.current()` au minimum projet `9.6.0`. Le projet n'épingle donc pas une distribution Gradle : un Gradle local plus récent est accepté tant qu'il satisfait le minimum.
|
||||
|
||||
```bash
|
||||
"${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" "ndk;28.2.13676358" "platform-tools"
|
||||
Le projet Android natif n'impose pas `JAVA_HOME`. Le JDK courant du shell/IDE exécute Gradle ; `java -version` et `gradle --version` sont relevés pendant les gates. Le niveau de bytecode/source Java de la glue reste explicitement `17`, indépendamment du JDK de build.
|
||||
|
||||
`cargo-ndk` détecte le NDK désigné pour le sous-processus via `ANDROID_NDK_HOME`. La tâche Gradle résout cette valeur à partir de `ANDROID_HOME` ou `ANDROID_SDK_ROOT` et de `androidNdkVersion`; aucun `ANDROID_NDK_HOME` global n'est requis.
|
||||
|
||||
## État 0.3.3-0-pre.5
|
||||
|
||||
`0-pre.4` a validé les quatre ABI sur Snake et Reflex : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`. Le même mécanisme Gradle est désormais enregistré pour toutes les variantes Android, et pas seulement Debug.
|
||||
|
||||
Pour Debug :
|
||||
|
||||
```text
|
||||
:<game>:assembleDebug
|
||||
-> buildDebugSasedevRust<Abi>
|
||||
-> cargo ndk build
|
||||
-> APK Debug universal
|
||||
```
|
||||
|
||||
`cargo-ndk` détecte un NDK installé par Android Studio ou utilise `ANDROID_NDK_HOME` lorsqu'il est défini.
|
||||
Pour Release/AAB :
|
||||
|
||||
Pour utiliser directement `adb` :
|
||||
|
||||
```bash
|
||||
export PATH="${ANDROID_HOME}/platform-tools:${PATH}"
|
||||
```text
|
||||
:<game>:bundleRelease
|
||||
-> buildReleaseSasedevRust<Abi>
|
||||
-> cargo ndk build --release
|
||||
-> AAB Release multi-ABI
|
||||
```
|
||||
|
||||
Cet export peut être ajouté au fichier de configuration shell local de la machine.
|
||||
Chaque arbre généré contient :
|
||||
|
||||
## Build
|
||||
```text
|
||||
build/generated/sasedevNative/<variant>/<abi>/jniLibs/<abi>/
|
||||
├── libSDL3.so
|
||||
└── libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
Depuis la racine :
|
||||
|
||||
```bash
|
||||
python3 scripts/build_android_rust.py reflex
|
||||
python3 scripts/build_android_rust.py snake
|
||||
java -version
|
||||
(cd Android && gradle --version)
|
||||
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||
(cd Android && gradle :game-snake-poc:bundleRelease)
|
||||
(cd Android && gradle :game-reflex-poc:bundleRelease)
|
||||
```
|
||||
|
||||
Puis :
|
||||
Le builder historique `scripts/build_android_rust.py` est retiré : il n'existe plus de chemin natif Android qui stage des bibliothèques dans `src/main/jniLibs`. Les sorties natives sont exclusivement générées sous `build/`.
|
||||
|
||||
```bash
|
||||
cd Android
|
||||
gradle :game-reflex-poc:assembleDebug
|
||||
gradle :game-snake-poc:assembleDebug
|
||||
cd ..
|
||||
```
|
||||
### Pages mémoire 16 KB
|
||||
|
||||
Les bibliothèques générées sous `Android/*/src/main/jniLibs/` sont des artefacts de build et ne sont pas versionnées.
|
||||
La validation porte sur les deux dimensions requises pour les appareils 64 bits concernés par la contrainte Google Play 16 KB :
|
||||
|
||||
1. segments ELF des `.so` `arm64-v8a` et `x86_64` avec un alignement `LOAD` au moins `2**14`, contrôlés par `llvm-objdump -p` depuis le NDK configuré ;
|
||||
2. alignement ZIP de l'APK avec `zipalign -c -P 16 -v 4`.
|
||||
|
||||
AGP `9.4.0` est supérieur au plancher `8.5.1` documenté pour le packaging 16 KB et NDK `r28c` produit les nouvelles bibliothèques 64 bits avec alignement 16 KB par défaut. Cette propriété ne dispense pas de contrôler `libSDL3.so`, qui est précompilée dans l'AAR.
|
||||
|
||||
Les ABI 32 bits `armeabi-v7a` et `x86` restent dans le package pour compatibilité ancienne et peuvent conserver des segments ELF `2**12` ; cela ne constitue pas un échec de l'exigence Play 16 KB, explicitement portée sur les appareils 64 bits.
|
||||
|
||||
Pour l'AAB, les huit bibliothèques doivent être présentes sous `base/lib/<abi>/`. Lorsque `bundletool` est disponible, `bundletool dump config --bundle=<aab>` peut compléter la vérification du mode d'alignement du bundle.
|
||||
|
||||
La preuve runtime utilise une image Android 15/API 35 x86_64 `ps16k` et retient `adb shell getconf PAGE_SIZE` comme contrôle de la taille de page du device. `16384` est le résultat attendu ; une valeur `KernelPageSize` lue dans un VMA particulier de `/proc/<pid>/smaps` n'est pas utilisée comme substitut à ce contrôle.
|
||||
|
||||
### Plancher Android
|
||||
|
||||
Le contrat est consolidé à Android 5.0 / API 21 :
|
||||
|
||||
- `minSdk 21` dans chaque application ;
|
||||
- `sasedevRustAndroidApi = 21` pour `cargo ndk` ;
|
||||
- SDL3 documente API 21 comme minimum Android ;
|
||||
- la gate `0-pre.5` a validé un smoke réel sur Android 5.0/API 21 x86 : installation de l'APK universal et démarrage de `SnakeActivity` réussis.
|
||||
|
||||
## Smoke appareil
|
||||
|
||||
Préflight :
|
||||
L'APK universal doit être inspecté mécaniquement avant installation. Il doit contenir les deux bibliothèques natives pour chacune des quatre ABI : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
|
||||
|
||||
```bash
|
||||
adb version
|
||||
adb devices
|
||||
```
|
||||
|
||||
Puis installation/lancement des APK si un appareil ou émulateur est disponible.
|
||||
|
||||
L'absence de `adb` ou d'appareil n'est pas un défaut fonctionnel du projet ; elle bloque uniquement le smoke appareil.
|
||||
Le smoke réel reste effectué sur les architectures matériellement disponibles : AVD x86_64 et appareil ARM64. Les ABI 32 bits sont néanmoins exigées au build et dans l'APK ; leur exécution réelle dépend de la disponibilité d'un appareil/AVD 32 bits. Le `minSdk 21` est consolidé en `0-pre.5`; les smokes disponibles servent de preuve runtime complémentaire et ne redéfinissent pas ce plancher.
|
||||
|
||||
## Exception FFI Rust
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<!-- file: docs/plans/000-README.md -->
|
||||
<!-- version: 2 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Plans de versions games.sasedev
|
||||
|
||||
Ce répertoire contient les plans vivants des versions concrètes.
|
||||
|
||||
Un plan est créé ou révisé pendant `0-pre.1`. Il conserve le scope, les décisions utiles, les validations et surtout le découpage prévisionnel souple des tranches jusqu'à la release. Il peut évoluer lorsque les audits ou validations imposent de scinder, fusionner, reporter ou corriger une tranche.
|
||||
Un plan est créé ou révisé pendant `alpha.1`. Il conserve le scope, les décisions utiles, les validations et surtout le découpage prévisionnel souple des tranches jusqu'à la release. Il peut évoluer lorsque les audits ou validations imposent de scinder, fusionner, reporter ou corriger une tranche.
|
||||
|
||||
Le `ROADMAP.md` reste la trajectoire macroscopique du projet ; les deltas décrivent ce qui a réellement été livré. Le plan se situe entre les deux et sert au suivi de la version en cours.
|
||||
|
||||
## Plans
|
||||
|
||||
- [`001-V0_3_0_WEB_SNAKE_POC_PLAN.md`](001-V0_3_0_WEB_SNAKE_POC_PLAN.md) — plan clôturé de `0.3.0`, baseline Snake et premier POC Web direct.
|
||||
- [`002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md`](002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md) — plan actif de `0.3.1`, second host Snake Tauri Android.
|
||||
- [`002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md`](002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md) — plan clôturé de `0.3.1`, second host Snake Tauri Android.
|
||||
- [`003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md`](003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md) — plan clôturé de `0.3.3`, pipeline Android SDL3 natif Gradle/Cargo multi-ABI.
|
||||
- [`004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md`](004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md) — plan clôturé de `0.3.4`, API de transport realtime et baseline WebSocket Tokio/tokio-tungstenite.
|
||||
- [`005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md`](005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md) — plan actif de `0.3.5`, POC WebTransport/QUIC, navigateur, fallback WebSocket et comparaison mesurée.
|
||||
|
||||
239
docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md
Normal file
239
docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md
Normal file
@@ -0,0 +1,239 @@
|
||||
<!-- file: docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Plan 0.3.3 — Android SDL3 natif multi-ABI et packaging Gradle
|
||||
|
||||
## Statut
|
||||
|
||||
Plan actif créé pendant `0.3.3-0-pre.1` à partir de la stable taggée `v0.3.1`.
|
||||
|
||||
`0.3.2` reste volontairement différée. Aucun placeholder, POC Tauri Desktop ou changement cérémoniel n'est introduit pour cette version.
|
||||
|
||||
## Mission
|
||||
|
||||
`0.3.3` doit rendre le chemin Android SDL3 natif reproductible et possédé par Gradle/Cargo :
|
||||
|
||||
```text
|
||||
Gradle -> Rust natif -> SDL3/JNI -> APK universal de test
|
||||
Gradle -> Rust natif multi-ABI -> AAB de distribution
|
||||
```
|
||||
|
||||
Le chemin productif Android ne doit dépendre ni de Tauri, ni de WebView, ni de WASM, ni d'un orchestrateur Python de build.
|
||||
|
||||
## Baseline et décisions retenues
|
||||
|
||||
La baseline `0.3.1` conserve :
|
||||
|
||||
- `game-android-entrypoint` comme `cdylib` commun ;
|
||||
- exactement une feature jeu par application native ;
|
||||
- Java comme glue Android ;
|
||||
- `SaseGameActivity`/SDLActivity et un contrat JNI réduit ;
|
||||
- assets canoniques hors modules Android et staging Gradle déjà basé sur la Variant API ;
|
||||
- SDL3 AAR non vendorizé ;
|
||||
- Snake Tauri Android uniquement comme POC de référence ARM64/x86_64.
|
||||
|
||||
Les décisions de cadrage `0-pre.1` sont :
|
||||
|
||||
```text
|
||||
AGP 9.4.0
|
||||
Gradle >= 9.6.0, version système acceptée si supérieure
|
||||
JDK >= 17, fourni par l'environnement ; JDK 25 candidat validable
|
||||
compileSdk 36
|
||||
targetSdk 36
|
||||
minSdk candidat 21
|
||||
NDK 28.2.13676358 / r28c
|
||||
SDL3 3.4.16
|
||||
ABI par défaut arm64-v8a + armeabi-v7a + x86_64 + x86
|
||||
ABI historiques pas de armeabi/mips/mips64 : retirées des toolchains modernes
|
||||
APK test universal multi-ABI
|
||||
AAB artefact de distribution cible
|
||||
splits ABI hors chemin par défaut
|
||||
```
|
||||
|
||||
Le `minSdk 21` est fondé sur le plancher Android actuellement documenté par SDL3, pas sur la seule valeur historique du dépôt. Le `targetSdk 36` est maintenu pour la politique Google Play actuelle.
|
||||
|
||||
### Révision de toolchain après `0-pre.2`
|
||||
|
||||
La preuve mono-ABI a réussi avec le wrapper Gradle `9.6.0` et JDK 17. Après cette validation, la décision projet est de ne pas transformer ces valeurs de preuve en versions exactes imposées au chemin SDL3 natif :
|
||||
|
||||
- AGP `9.4.0` conserve Gradle `9.6.0` comme minimum ;
|
||||
- un Gradle système plus récent est autorisé et doit être affiché dans la gate ;
|
||||
- le JDK runtime doit satisfaire Gradle/AGP (`>= 17`) mais provient de l'environnement ;
|
||||
- le niveau Java de la glue Android reste `sourceCompatibility/targetCompatibility = 17` ;
|
||||
- les contraintes JDK/Gradle du POC Tauri restent locales à ce POC et ne pilotent pas Android SDL3 natif.
|
||||
|
||||
Le pipeline devra aussi vérifier la compatibilité 16 KB des bibliothèques natives finales. AGP 9.4 et NDK r28c fournissent le socle requis, mais le prébuild SDL3 doit être contrôlé dans l'APK/AAB produit.
|
||||
|
||||
## Ownership cible
|
||||
|
||||
La logique durable doit rester répartie ainsi :
|
||||
|
||||
- Cargo/Rust : moteur, gameplay et `game-android-entrypoint` ;
|
||||
- `Android/common` : glue Java/JNI commune durable ;
|
||||
- `Android/game-*` : identité et configuration applicative par jeu ;
|
||||
- `Android/gradle/` : logique de build Android commune, y compris orchestration Rust native et staging des sorties générées ;
|
||||
- `assets/` : sources d'assets canoniques ;
|
||||
- `Android/<app>/build/generated/` : outputs jetables de packaging ;
|
||||
- le builder historique `scripts/build_android_rust.py` est supprimé en `0-pre.5` après migration de toutes ses responsabilités vers Gradle ; aucun équivalent Python ne doit être recréé.
|
||||
|
||||
Aucune `.so` générée ne doit être écrite durablement dans `src/main/jniLibs/` par le nouveau chemin.
|
||||
|
||||
## Contrat de la logique Gradle native
|
||||
|
||||
La future logique commune doit couvrir au minimum :
|
||||
|
||||
1. résolution du NDK side-by-side exact déclaré dans `Android/gradle.properties` ;
|
||||
2. validation de la présence de l'AAR SDL3 attendu ;
|
||||
3. extraction par ABI de `libSDL3.so` depuis Prefab vers un répertoire de build ;
|
||||
4. linkage Rust contre la SDL extraite ;
|
||||
5. invocation de `cargo ndk` avec `CARGO_NDK_PLATFORM=21` ;
|
||||
6. mapping strict de chaque module vers exactement une feature `snake` ou `reflex` ;
|
||||
7. build des quatre ABI Android encore supportées : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86` ;
|
||||
8. staging de `libSDL3.so` et `libgame_android_entrypoint.so` dans un répertoire `jniLibs` généré ;
|
||||
9. branchement de ce répertoire à la variante Android avant packaging ;
|
||||
10. inputs/outputs Gradle assez précis pour un rebuild incrémental ;
|
||||
11. diagnostics explicites si outil, target, AAR, NDK ou output manque ;
|
||||
12. absence de secret de signature dans le dépôt.
|
||||
|
||||
Le projet Android natif déclare `minimumGradleVersion=9.6.0` et refuse au chargement une version inférieure. Le Gradle réellement utilisé est celui de l'environnement ; une version plus récente que le minimum est autorisée. Le projet n'épingle pas de wrapper natif.
|
||||
|
||||
## ABI et compatibilité OS
|
||||
|
||||
Le support CPU et le support de versions Android restent deux axes séparés.
|
||||
|
||||
`0-pre.4` révise la politique ABI : le projet vise le maximum raisonnable de compatibilité matérielle, y compris les architectures 32 bits encore supportées. La matrice obligatoire devient `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
|
||||
|
||||
`arm64-v8a` reste la cible appareil productive principale et `x86_64` la cible AVD principale, mais elles ne constituent plus à elles seules le contrat de packaging. `armeabi-v7a` et `x86` sont également construites et packagées afin de conserver la compatibilité avec du matériel plus ancien.
|
||||
|
||||
Les ABI abandonnées par les toolchains Android modernes (`armeabi`, `mips`, `mips64`) restent hors contrat : les réintroduire exigerait une toolchain historique/forkée et ne correspondrait plus au pipeline Android courant.
|
||||
|
||||
Le plancher OS candidat est Android 5.0 / API 21. La version doit tenter un smoke sur API 21 si une image exploitable est raisonnablement disponible. À défaut, elle teste la plus ancienne API localement disponible et consigne explicitement la lacune ; elle ne transforme pas un smoke absent en validation fictive.
|
||||
|
||||
## Packaging attendu
|
||||
|
||||
L'APK Debug de test doit rester universal : un seul fichier installable contient toutes les ABI retenues. La validation inspecte les entrées `lib/<abi>/...` avant les smokes.
|
||||
|
||||
L'AAB est l'artefact de distribution. La validation doit vérifier au minimum :
|
||||
|
||||
- présence des bibliothèques natives des ABI retenues ;
|
||||
- absence de secret/keystore embarqué ;
|
||||
- configuration d'alignement compatible pages mémoire 16 KB ;
|
||||
- possibilité d'inspecter le bundle avec les outils Android appropriés ;
|
||||
- distinction documentée entre bundle de distribution et APK installable.
|
||||
|
||||
Les APK split ABI ne sont pas générés par défaut dans `0.3.3`.
|
||||
|
||||
## Validation par type de jalon
|
||||
|
||||
Pendant les tranches d'implémentation, les validations restent ciblées : audits statiques, gates Rust dès qu'un fichier Rust/Cargo change, puis tâche Gradle du module touché.
|
||||
|
||||
Les smokes de packaging deviennent plus larges seulement quand le jalon produit un artefact installable :
|
||||
|
||||
- APK universal : inspection des quatre ABI + AVD x86_64 + appareil ARM64 ; les ABI 32 bits sont fumées seulement si un device/AVD compatible est disponible ;
|
||||
- compatibilité basse : AVD API 21 si raisonnablement disponible, sinon plus ancienne API disponible avec écart documenté ;
|
||||
- AAB : inspection du bundle et des bibliothèques natives, y compris compatibilité 16 KB ;
|
||||
- beta/RC : gate workspace large planifiée, rebuild reproductible et smokes finaux.
|
||||
|
||||
Les builds, tests et smokes finaux restent exécutés et attestés par l'utilisateur.
|
||||
|
||||
## Forecast révisé
|
||||
|
||||
### `0-pre.1` — cadrage Android natif
|
||||
|
||||
Audit complet de `v0.3.1`, règles, pipeline Python historique, Gradle/AGP/NDK/SDL3, ABI/API, minSdk, APK universal, AAB, pages 16 KB, risques, matrice de validation et plan vivant.
|
||||
|
||||
Aucune réécriture Gradle productive dans cette tranche.
|
||||
|
||||
### `0-pre.2` — ownership Gradle/Cargo sur Snake mono-ABI
|
||||
|
||||
Introduire la logique Gradle commune minimale et prouver que `:game-snake-poc:assembleDebug` possède le build Rust `arm64-v8a`, l'extraction/linkage SDL3 et les `jniLibs` générés, sans appel préalable au script Python. Cette preuve a initialement utilisé un wrapper Gradle 9.6.0 afin d'isoler le task wiring.
|
||||
|
||||
La gate utilisateur du 2026-09-21 a validé ce chemin. Après cette preuve, la politique toolchain a été révisée : le projet Android natif utilise désormais un Gradle système `>= 9.6.0` et le JDK courant de l'environnement au lieu d'épinger Gradle/JDK pour ce chemin.
|
||||
|
||||
### `0-pre.3` — toolchain minimale, multi-ABI et APK universal
|
||||
|
||||
Retirer le wrapper natif introduit pour la preuve `0-pre.2`, déclarer et contrôler `Gradle >= 9.6.0`, ne plus imposer `JAVA_HOME`, puis étendre la logique commune à `arm64-v8a + x86_64`. Produire un APK Debug universal Snake et vérifier mécaniquement les deux couples `libSDL3.so`/`libgame_android_entrypoint.so`.
|
||||
|
||||
La factorisation commune étant réduite au mapping `module -> feature`, ajouter Reflex comme second consommateur avec les mêmes deux ABI, sans nouvelle architecture.
|
||||
|
||||
### `0-pre.4` — matrice ABI Android complète
|
||||
|
||||
Étendre Snake et Reflex aux quatre ABI encore supportées par Android NDK/SDL3/Rust : `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86`. Produire des APK Debug universal et vérifier mécaniquement les huit bibliothèques attendues par application.
|
||||
|
||||
Les smokes matériels restent ciblés sur les environnements disponibles ; l'absence d'un device/AVD 32 bits ne doit pas être transformée en fausse preuve runtime.
|
||||
|
||||
### `0-pre.5` — AAB, minSdk et fermeture du chemin historique
|
||||
|
||||
Étendre l'ownership Gradle aux variantes Release : `bundleRelease` doit reconstruire les quatre ABI en profil Cargo release sans secret embarqué. Inspecter les AAB, les segments ELF et l'alignement APK 16 KB ; consolider `minSdk 21` ; supprimer `scripts/build_android_rust.py` et interdire le retour des `src/main/jniLibs` générés.
|
||||
|
||||
Mettre à jour les documents Android/JNI/build durables pour décrire le chemin réel et remplacer les anciennes commandes de validation encore dépendantes du builder Python.
|
||||
|
||||
### `2-beta.1` — validation large et packaging final
|
||||
|
||||
`0-pre.5` a fermé le scope d'implémentation : AAB quatre ABI, Release Cargo pilotée par Gradle, suppression du builder Python, audit propre après suppression des anciens `src/main/jniLibs`, alignement 16 KB validé sur les ABI 64 bits, smoke API 21/x86 et smoke API 35/x86_64 `ps16k`.
|
||||
|
||||
La beta n'ajoute donc aucune capacité. Elle synchronise la version technique, consigne la preuve `0-pre.5`, exécute la gate Rust/workspace large planifiée, reconstruit les APK universal et AAB Snake/Reflex, puis revalide les smokes de référence x86_64 et ARM64. Les preuves de frontière API 21 et 16 KB restent dans l'historique `0-pre.5` et ne sont rejouées que si une modification ultérieure affecte le runtime/package natif.
|
||||
|
||||
Corriger uniquement par `2-beta.1.fix.N` si nécessaire ; ne pas ouvrir une beta cérémonielle supplémentaire si la matrice est propre.
|
||||
|
||||
### `3-rc.1` — gel et reproductibilité
|
||||
|
||||
La beta a passé la gate workspace complète, 35 tests, les rebuilds Debug/Release quatre ABI, l'inspection APK/AAB, `zipalign -P 16` et les smokes Snake/Reflex sur AVD API 36 x86_64 ainsi que Samsung API 29 ARM64. Aucun `2-beta.1.fix.N` n'est requis.
|
||||
|
||||
Geler le scope, consolider `CHANGELOG.md`, l'historique beta, la matrice RC et le prompt `0.3.4`. Rejouer uniquement les gates de publication/reproductibilité prévues ; aucune nouvelle capacité.
|
||||
|
||||
La convention de prerelease reste inchangée jusqu'à la stable `0.3.3`. La migration approuvée vers `X.Y.Z-alpha.M`, `X.Y.Z-beta.M`, `X.Y.Z-rc.M` et leurs `.fix.N` commence seulement dans la session `0.3.4`. Elle modifie les règles actives et les audits à partir de cette nouvelle version, sans renommer ni réécrire aucun ancien fichier sous `deltas/`, `history/` ou les anciennes entrées du changelog.
|
||||
|
||||
### `0.3.3` — stable
|
||||
|
||||
Promotion mécanique de la RC validée. Aucun changement fonctionnel ou architectural.
|
||||
|
||||
## Statut de clôture
|
||||
|
||||
Plan clôturé avec la publication stable `0.3.3` le 2026-09-21.
|
||||
|
||||
La RC a confirmé la reproductibilité du workspace, les builds Android Debug/Release quatre ABI, les APK/AAB, `zipalign -P 16`, les segments ELF 64 bits `align 2**14` et les smokes Snake/Reflex sur AVD API 36 x86_64 et appareil ARM64 réel. Les preuves de frontière API 21/x86 et API 35/x86_64 `ps16k` ont été acquises avant beta puis conservées sans régression du pipeline natif.
|
||||
|
||||
Aucun comportement nouveau n'est introduit par la promotion stable. La suite appartient à `0.3.4`, qui commencera sous la nouvelle convention `alpha.M / beta.M / rc.M` sans réécrire les jalons historiques de `0.3.3` ou antérieurs.
|
||||
|
||||
## Sizing
|
||||
|
||||
Le scope reste raisonnable pour une seule version avec cinq prereleases techniques avant beta, la nouvelle `0-pre.4` isolant volontairement l'extension 32 bits du packaging AAB/16 KB. Les zones susceptibles de forcer un split de version sont uniquement une incompatibilité profonde de `cargo-ndk` avec AGP/Gradle, une SDL3 AAR non exploitable par Prefab comme prévu, ou une incompatibilité effective d'une des deux ABI 32 bits encore supportées.
|
||||
|
||||
Un simple besoin de `.fix.N` après validation ne remet pas ce sizing en cause.
|
||||
|
||||
## Gate avant 0-pre.2
|
||||
|
||||
La validation utilisateur de `0-pre.1` doit inclure la gate Rust liée au changement de version workspace et l'inventaire frais suivant :
|
||||
|
||||
```bash
|
||||
rustc --version
|
||||
cargo --version
|
||||
cargo ndk --version
|
||||
rustup target list --installed
|
||||
java -version
|
||||
printf 'JAVA_HOME=%s\nANDROID_HOME=%s\n' "$JAVA_HOME" "$ANDROID_HOME"
|
||||
adb version
|
||||
adb devices -l
|
||||
emulator -list-avds
|
||||
(cd Android && gradle --version)
|
||||
printf 'configured NDK: '; sed -n 's/^androidNdkVersion=//p' ls -ld "$ANDROID_HOME/ndk/28.2.13676358"
|
||||
test -f Android/libs/SDL3-3.4.16.aar && sha256sum Android/libs/SDL3-3.4.16.aar
|
||||
```
|
||||
|
||||
La politique a été révisée après validation de `0-pre.2` : le projet Android natif dépend volontairement d'un Gradle fourni par l'environnement, avec minimum `9.6.0` déclaré et contrôlé par `settings.gradle`. Il n'impose plus de wrapper ni de `JAVA_HOME`; les gates consignent les versions réellement utilisées.
|
||||
|
||||
## Critères d'entrée en RC
|
||||
|
||||
`0.3.3` peut entrer en RC lorsque :
|
||||
|
||||
- un build Gradle Android natif n'exige plus `scripts/build_android_rust.py` ;
|
||||
- Snake et Reflex produisent un APK universal contenant `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86` par un seul chemin Gradle ;
|
||||
- l'AAB de distribution est constructible sans secret embarqué ;
|
||||
- les bibliothèques natives 64 bits finales sont vérifiées pour la contrainte 16 KB et un smoke `ps16k` retourne `PAGE_SIZE=16384` ;
|
||||
- `minSdk 21` est validé par un smoke réel Android 5.0/API 21 x86 ;
|
||||
- l'appareil ARM64 réel et l'AVD x86_64 passent le smoke prévu ;
|
||||
- Reflex est soit branché naturellement au pipeline commun, soit explicitement borné selon les résultats de factorisation ;
|
||||
- le script Python historique Android est supprimé et l'audit interdit son retour ;
|
||||
- les documents Android, build, JNI et validation décrivent le chemin réellement livré ;
|
||||
- les gates workspace/beta applicables sont propres.
|
||||
611
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
Normal file
611
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
Normal file
@@ -0,0 +1,611 @@
|
||||
<!-- file: docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# Plan 0.3.4 — transport realtime et baseline WebSocket
|
||||
|
||||
## Statut
|
||||
|
||||
Plan clôturé avec la publication stable `0.3.4` le 2026-09-21. Il a été créé pendant `0.3.4-alpha.1` à partir de l'archive taggée `v0.3.3` et reste désormais une référence historique.
|
||||
|
||||
Les tranches `alpha.1`, `alpha.2`, `alpha.3.fix.1`, `alpha.4`, `alpha.5`, `beta.1` et `rc.1` ont été validées le 2026-09-21. `alpha.3` avait été rejetée avant compilation à cause d'un héritage Cargo invalide de `default-features`; son fix a ensuite validé le backend WebSocket, le round-trip localhost et le graphe sans TLS. `alpha.4` a fermé les limites, timeouts, cas négatifs et lifecycle, `alpha.5` a fourni le smoke runtime hors harness de test, puis `beta.1` a validé le workspace complet avec 53 tests et les frontières de dépendances. `rc.1` a repassé les gates de publication ciblées, 18 tests realtime, le smoke `PASS` et l'arbre inverse sans défaut. La stable est une promotion mécanique de cette RC gelée.
|
||||
|
||||
## Mission
|
||||
|
||||
`0.3.4` doit introduire une frontière de transport realtime async et une première implémentation WebSocket fondée sur Tokio + `tokio-tungstenite`, sans implémenter le protocole de session, la synchronisation gameplay ni la simulation authoritative.
|
||||
|
||||
La frontière reste :
|
||||
|
||||
```text
|
||||
transport
|
||||
↓
|
||||
wire codec
|
||||
↓
|
||||
session protocol
|
||||
↓
|
||||
synchronization
|
||||
↓
|
||||
authoritative simulation
|
||||
```
|
||||
|
||||
Le transport de `0.3.4` transporte des octets opaques. Il ne connaît ni joueur, ni room, ni tick, ni snapshot, ni delta métier.
|
||||
|
||||
## Baseline auditée
|
||||
|
||||
L'archive fournie comme téléchargement du tag `v0.3.3` est traitée comme autoritaire conformément à `CMD-GIT-003` et `CMD-GIT-004`.
|
||||
|
||||
L'audit local de l'archive confirme :
|
||||
|
||||
- archive ZIP intègre, sans chemin traversant ni symlink ;
|
||||
- `400` fichiers, `14` membres Cargo et `55` fichiers Rust ;
|
||||
- `workspace.package.version = 0.3.3` avant migration ;
|
||||
- audits Rust/workspace, Markdown et distribution propres sur l'archive ;
|
||||
- aucune arborescence générée `target/`, `node_modules/`, `gen/android` ou `build/` livrée ;
|
||||
- gameplay et moteurs sans dépendance réseau realtime ;
|
||||
- baseline Android `0.3.3` conservée et hors scope de cette version.
|
||||
|
||||
Le log utilisateur fourni à l'ouverture de session confirme également `cargo fmt`, `cargo check --workspace` et Clippy workspace strict sur son checkout `0.3.3`. Son audit Markdown annonce davantage de fichiers que l'archive taggée fournie ; la présente version reste néanmoins construite exclusivement depuis l'archive autoritaire reçue et n'invente aucun fichier absent de celle-ci.
|
||||
|
||||
## Migration de gouvernance
|
||||
|
||||
À partir de `0.3.4`, les seules formes de prerelease nouvelles sont :
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
`alpha.1` remplace l'ancien rôle de cadrage `0-pre.1`.
|
||||
|
||||
Les anciens labels `0-pre`, `1-alpha`, `2-beta` et `3-rc` restent valides uniquement comme preuves historiques déjà livrées. Les audits distinguent donc la convention courante de la compatibilité historique et n'autorisent plus l'ancien schéma pour un nouveau workspace ou un nouvel historique `0.3.4+`.
|
||||
|
||||
## Dépendances vérifiées au 2026-09-21
|
||||
|
||||
Versions amont retenues et introduites par `alpha.3` :
|
||||
|
||||
```text
|
||||
Tokio 1.53.1
|
||||
futures-util 0.3.34
|
||||
tokio-tungstenite 0.30.0
|
||||
tungstenite 0.30.0, via réexport tokio-tungstenite
|
||||
```
|
||||
|
||||
Contraintes utiles :
|
||||
|
||||
- Tokio `1.53.1` annonce un MSRV `1.71` ;
|
||||
- `tokio-tungstenite 0.30.0` et `tungstenite 0.30.0` annoncent un MSRV `1.85` ;
|
||||
- `tokio-tungstenite` dépend déjà de Tokio `1.x` et de Tungstenite `0.30.0` ;
|
||||
- ses features par défaut couvrent `connect` et `handshake`, sans TLS ;
|
||||
- `native-tls` et les variantes `rustls-*` sont optionnelles ;
|
||||
- Tungstenite expose déjà des limites de message/frame et de write buffer configurables.
|
||||
|
||||
`alpha.3.fix.1` centralise désormais aussi `default-features = false` sous `[workspace.dependencies]`, car Cargo interdit à une dépendance membre héritée de remplacer cette option. `game-realtime-websocket-lib` ajoute seulement les features locales `sink + std` pour `futures-util` et `connect + handshake` pour `tokio-tungstenite`; aucune feature TLS n'est activée. La gate utilisateur du fix confirme Rust `1.94.1`, Tokio `1.53.1`, tokio-tungstenite/Tungstenite `0.30.0` et un graphe normal sans pile TLS. `alpha.4` ajoute uniquement la feature Tokio `time` au backend afin d'appliquer les deadlines produit.
|
||||
|
||||
## Ownership physique retenu
|
||||
|
||||
### `game-realtime-transport-lib`
|
||||
|
||||
Créer sous :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-transport-lib
|
||||
```
|
||||
|
||||
Responsabilités :
|
||||
|
||||
- contrat transport-neutral ;
|
||||
- payload binaire opaque ;
|
||||
- distinction send/receive/close ;
|
||||
- fermeture distante explicite ;
|
||||
- catégories d'erreur transport-neutral ;
|
||||
- contrat de split permettant lecture et écriture concurrentes ;
|
||||
- aucune dépendance à Tokio, Tungstenite, HTTP, TLS, SDL, Tauri, WASM, moteur ou gameplay.
|
||||
|
||||
Cette crate est une capability technique commune indépendante d'une génération de moteur. Elle n'est pas placée dans `engine-v1-*`.
|
||||
|
||||
### `game-realtime-websocket-lib`
|
||||
|
||||
Créer sous :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-websocket-lib
|
||||
```
|
||||
|
||||
Responsabilités :
|
||||
|
||||
- implémentation WebSocket du contrat transport ;
|
||||
- `tokio-tungstenite` et Tokio réseau/temps ;
|
||||
- connexion client WebSocket ;
|
||||
- bind/accept serveur local ;
|
||||
- mapping des frames WebSocket vers le contrat binaire ;
|
||||
- close handshake, erreurs, timeouts, limites et tracing spécifiques au backend ;
|
||||
- tests loopback localhost.
|
||||
|
||||
Le backend dépend de `game-realtime-transport-lib`. L'inverse est interdit.
|
||||
|
||||
### Dépendances interdites
|
||||
|
||||
Aucune crate sous :
|
||||
|
||||
```text
|
||||
crates/games/
|
||||
crates/engines/
|
||||
```
|
||||
|
||||
doit dépendre de `tokio-tungstenite` ou du backend WebSocket pendant `0.3.4`.
|
||||
|
||||
Aucun contrat gameplay n'est déplacé vers les crates transport pour fabriquer artificiellement un consommateur.
|
||||
|
||||
## Contrat transport minimal
|
||||
|
||||
Le contrat public doit rester suffisamment petit pour être implémentable par WebSocket puis confronté au POC WebTransport de `0.3.5`.
|
||||
|
||||
### Payload
|
||||
|
||||
Le message transport est un buffer binaire possédé, conceptuellement `Vec<u8>`.
|
||||
|
||||
Décisions :
|
||||
|
||||
- aucune variante texte dans l'API transport-neutral ;
|
||||
- aucun JSON, Serde, Protobuf ou codec gameplay dans `0.3.4` ;
|
||||
- Ping/Pong WebSocket reste un détail du backend ;
|
||||
- une frame Text reçue par le backend n'est pas promue en message métier et doit produire un comportement explicite, testé et tracé ;
|
||||
- le futur wire codec consommera/produira ces octets au-dessus du transport.
|
||||
|
||||
### Connexion et split
|
||||
|
||||
Le contrat doit permettre conceptuellement :
|
||||
|
||||
```text
|
||||
connection.split() -> sender + receiver
|
||||
sender.send(bytes) -> async Result
|
||||
sender.close() -> async Result
|
||||
receiver.receive() -> async Result<Message | Closed>
|
||||
```
|
||||
|
||||
`alpha.2` retient une API statiquement dispatchée fondée sur des associated types : `RealtimeConnection` produit un `Sender` et un `Receiver`, puis les opérations async exposent des futures associées GAT (`SendFuture`, `CloseFuture`, `ReceiveFuture`). Le contrat commun n'utilise ni `async-trait`, ni `Box<dyn Future>`, ni runtime concret.
|
||||
|
||||
Aucune borne `Send` n'est imposée aux futures par le contrat transport-neutral : un backend natif peut naturellement fournir des futures `Send`, tandis qu'un futur backend navigateur/WASM ne doit pas être rendu impossible par une contrainte de threading qui ne relève pas du transport abstrait.
|
||||
|
||||
Le contrat doit supporter lecture et écriture concurrentes après split. Il n'expose pas de runtime Tokio et ne crée aucun executor.
|
||||
|
||||
### Fermeture
|
||||
|
||||
La fermeture distante n'est pas une erreur I/O générique. `receive()` doit pouvoir distinguer au minimum :
|
||||
|
||||
- message binaire reçu ;
|
||||
- fermeture distante propre ;
|
||||
- erreur de transport.
|
||||
|
||||
La fermeture locale est explicite via `close()`.
|
||||
|
||||
Une cancellation de task/future n'est pas assimilée à une fermeture WebSocket propre. Abandonner le propriétaire de la connexion doit néanmoins libérer les ressources sans tâche backend détachée.
|
||||
|
||||
### Erreurs
|
||||
|
||||
Le contrat transport-neutral doit représenter au minimum les catégories suivantes sans exposer les types d'erreur de Tungstenite :
|
||||
|
||||
```text
|
||||
invalid endpoint/configuration
|
||||
connect/bind/accept failure
|
||||
timeout
|
||||
message too large
|
||||
backpressure/write buffer full
|
||||
connection closed
|
||||
I/O failure
|
||||
protocol/backend failure
|
||||
cancelled/operation aborted lorsque cette distinction est réellement observable
|
||||
```
|
||||
|
||||
Les détails backend peuvent être conservés pour `Display`/source/tracing sans faire remonter `tungstenite::Error` dans l'API commune.
|
||||
|
||||
## Ownership Tokio/runtime
|
||||
|
||||
Le runtime est possédé par l'application, le service ou le test consommateur.
|
||||
|
||||
`game-realtime-websocket-lib` :
|
||||
|
||||
- utilise les primitives Tokio nécessaires ;
|
||||
- ne crée pas de runtime global ;
|
||||
- ne lance pas de thread runtime privé ;
|
||||
- évite les tâches détachées pour la connexion de base ;
|
||||
- ne propage aucune dépendance Tokio dans le gameplay.
|
||||
|
||||
Les tests peuvent utiliser `#[tokio::test]` comme harness de validation, sans transformer la macro en contrat public.
|
||||
|
||||
## Client et serveur
|
||||
|
||||
La séparation retenue est asymétrique et minimale :
|
||||
|
||||
- le contrat commun modélise une connexion déjà établie et ses deux moitiés ;
|
||||
- le backend WebSocket possède les constructeurs concrets client/server ;
|
||||
- aucun trait générique `Connector`, `Listener`, `Server`, `Provider` ou `Runtime` n'est créé dans `alpha.2` sans besoin démontré ;
|
||||
- le client peut exposer une connexion à partir d'un endpoint WebSocket ;
|
||||
- le serveur peut binder une adresse socket puis accepter une connexion ;
|
||||
- le futur backend WebTransport pourra proposer ses propres étapes d'établissement tout en produisant une connexion compatible lorsque cela reste pertinent.
|
||||
|
||||
Cette décision évite de forcer aujourd'hui WebSocket et QUIC à partager artificiellement des opérations d'établissement différentes.
|
||||
|
||||
## TLS
|
||||
|
||||
La baseline `0.3.4` est d'abord un POC transport local reproductible en `ws://`.
|
||||
|
||||
Aucune feature TLS de `tokio-tungstenite` n'est activée par défaut dans la première implémentation. `wss://` pourra être ajouté si un consommateur direct ou la stratégie de terminaison TLS le justifie réellement.
|
||||
|
||||
Cette décision :
|
||||
|
||||
- réduit les dépendances initiales ;
|
||||
- évite de choisir prématurément `native-tls` contre `rustls` ;
|
||||
- n'interdit pas une terminaison TLS future en reverse proxy ;
|
||||
- ne présente pas le WebSocket local de test comme configuration de production Internet.
|
||||
|
||||
## Timeouts et backpressure
|
||||
|
||||
### Principes
|
||||
|
||||
- aucune file interne non bornée ;
|
||||
- `send()` attend la capacité du sink au lieu d'accumuler des messages arbitrairement ;
|
||||
- les limites Tungstenite sont configurées explicitement et ne restent pas implicitement aux maxima amont ;
|
||||
- l'absence de message reçu n'est pas un timeout de transport automatique : heartbeat/idle/session timeout appartient à la couche supérieure ;
|
||||
- connect, send et fermeture propre disposent de bornes configurables afin qu'une opération locale ne reste pas bloquée indéfiniment.
|
||||
|
||||
### Valeurs de baseline proposées
|
||||
|
||||
Valeurs initiales, configurables et non assimilées à un protocole gameplay final :
|
||||
|
||||
```text
|
||||
max message size 1 MiB
|
||||
max frame size 1 MiB
|
||||
write buffer target 64 KiB
|
||||
max write buffer 2 MiB
|
||||
connect timeout 10 s
|
||||
send timeout 5 s
|
||||
close timeout 2 s
|
||||
receive idle timeout aucun au niveau transport
|
||||
```
|
||||
|
||||
Le choix `2 MiB` pour le write buffer maximum laisse au moins la place au buffer cible plus un message maximal. Les tests négatifs doivent vérifier le comportement limite plutôt que supposer que ces constantes resteront éternellement inchangées.
|
||||
|
||||
## Tracing
|
||||
|
||||
Domaines proposés :
|
||||
|
||||
```text
|
||||
games::realtime::transport
|
||||
games::realtime::websocket
|
||||
```
|
||||
|
||||
Le tracing doit couvrir au minimum connexion, accept, fermeture, timeout, rejet de message hors limite et erreurs backend.
|
||||
|
||||
Ne pas logguer par défaut le contenu brut des payloads de transport.
|
||||
|
||||
Les futurs domaines session/sync/simulation utilisent des targets distinctes.
|
||||
|
||||
## Décisions matérialisées dans `alpha.3`
|
||||
|
||||
Le backend concret reste sous `crates/common/game-realtime-websocket-lib` et dépend uniquement du contrat commun plus des briques réseau/tracing nécessaires.
|
||||
|
||||
L'API publique expose :
|
||||
|
||||
```text
|
||||
connect(endpoint)
|
||||
WebSocketListener::bind(address)
|
||||
WebSocketListener::accept()
|
||||
WebSocketConnection
|
||||
WebSocketSender
|
||||
WebSocketReceiver
|
||||
```
|
||||
|
||||
Les types `WebSocketStream`, `SplitSink`, `SplitStream`, `MaybeTlsStream` et `tungstenite::Error` restent privés. Client et serveur produisent le même `WebSocketConnection` public grâce à une représentation interne à deux variantes ; le contrat commun reste donc la seule frontière partagée par les consommateurs.
|
||||
|
||||
`connect()` accepte volontairement uniquement `ws://` dans cette baseline. Le TLS direct reste différé. Les frames binaires deviennent `TransportMessage`; Close devient `TransportReceive::Closed`; Ping/Pong sont traités comme contrôle backend ; Text et raw Frame sont explicitement rejetés comme erreurs de protocole. Les tests négatifs exhaustifs correspondants restent planifiés pour `alpha.4`.
|
||||
|
||||
Le backend ne crée aucun runtime, thread ni task détachée. Le harness d'intégration utilise un runtime Tokio de test courant, un bind `127.0.0.1:0` et une borne temporelle uniquement pour empêcher un test loopback défectueux de rester suspendu.
|
||||
|
||||
Aucun `README.md`/`USAGE.md` local n'est ajouté pendant `alpha.3` : la crate reste petite, son API publique est documentée par rustdoc et le présent plan porte encore les décisions durables. Ce choix sera réévalué pendant la consolidation finale conformément à `DOC-CRATE-*`.
|
||||
|
||||
## Décisions matérialisées dans `alpha.4`
|
||||
|
||||
`game-realtime-websocket-lib` expose désormais `WebSocketConfig` et les variantes configurables `connect_with_config()` et `WebSocketListener::bind_with_config()`. Les wrappers historiques `connect()` et `bind()` conservent des valeurs de baseline explicites :
|
||||
|
||||
```text
|
||||
max message size 1 MiB
|
||||
max frame size 1 MiB
|
||||
write buffer target 64 KiB
|
||||
max write buffer 2 MiB
|
||||
connect/handshake 10 s
|
||||
send 5 s
|
||||
close 2 s
|
||||
receive idle timeout aucun
|
||||
```
|
||||
|
||||
La configuration refuse une limite nulle, une frame plus grande que le message, un write buffer maximum incapable de contenir le target plus un message maximal et une deadline nulle. Les limites Tungstenite sont transmises aux handshakes client et serveur ; les tailles message/frame sortantes sont également vérifiées avant l'appel au sink afin que le rejet soit déterministe.
|
||||
|
||||
`connect_timeout` borne la connexion client et la phase de handshake serveur après accept TCP. Le listener reste volontairement capable d'attendre indéfiniment un nouveau peer : ce temps d'attente appartient au service consommateur, pas à une connexion déjà en établissement. `send_timeout` et `close_timeout` bornent leurs opérations respectives. Après expiration d'un `send`, la moitié émission refuse un nouvel envoi avec `Aborted`, car l'appel interrompu peut avoir progressé partiellement et ne doit pas être rejoué implicitement.
|
||||
|
||||
Aucun idle timeout n'est ajouté à `receive()`. Heartbeat, inactivité joueur et politique de session restent au-dessus du transport.
|
||||
|
||||
Les tests négatifs `alpha.4` couvrent :
|
||||
|
||||
- rejet sortant d'un payload hors limite avant écriture ;
|
||||
- rejet entrant d'un message/frame hors limite par Tungstenite ;
|
||||
- rejet d'une frame Text par le contrat binaire ;
|
||||
- drop TCP/WebSocket sans close handshake, attendu comme erreur de protocole ;
|
||||
- timeout de handshake serveur avec un peer TCP silencieux ;
|
||||
- mapping déterministe de `WriteBufferFull` vers `Backpressure` et de `Capacity` vers `MessageTooLarge`.
|
||||
|
||||
Un test réseau artificiel de saturation `WriteBufferFull` n'est pas retenu : Tungstenite documente que son write buffer ne dépasse le target que lorsque les écritures sous-jacentes échouent, ce qui rendrait une saturation loopback normale non représentative et potentiellement flaky. La borne finie est néanmoins configurée, le mapping backend est testé unitairement et le timeout d'émission borne l'attente du sink.
|
||||
|
||||
## Tests retenus
|
||||
|
||||
### Contrat commun
|
||||
|
||||
Tests unitaires ciblés :
|
||||
|
||||
- types d'erreur et affichage ;
|
||||
- invariants de configuration réellement portés par la crate commune ;
|
||||
- aucune dépendance au backend concret.
|
||||
|
||||
### Backend WebSocket
|
||||
|
||||
Tests d'intégration localhost déterministes avec `127.0.0.1:0` :
|
||||
|
||||
1. bind serveur sur port éphémère ;
|
||||
2. connexion client ;
|
||||
3. client → serveur : payload binaire ;
|
||||
4. serveur → client : réponse binaire ;
|
||||
5. fermeture propre initiée d'un côté et observée de l'autre ;
|
||||
6. dépassement de taille rejeté ;
|
||||
7. comportement explicite sur frame Text ;
|
||||
8. connexion interrompue/peer drop ;
|
||||
9. timeout d'opération retenu ;
|
||||
10. backpressure/write-buffer error provoquée par un test borné si elle est reproductible sans test flaky.
|
||||
|
||||
Les tests ne dépendent ni d'Internet, ni d'un serveur externe, ni d'un port fixe.
|
||||
|
||||
Un petit launcher de smoke n'était pas prévu par défaut tant que les tests publics semblaient suffire. La revue après validation de `alpha.4` a distingué correctement test automatisé et smoke runtime : `alpha.5` ajoute donc `game-realtime-websocket-smoke` sous `crates/apps/`. Ce binaire reste un harness de validation, pas un produit ni un protocole applicatif.
|
||||
|
||||
## Gates par jalon
|
||||
|
||||
### `alpha.1`
|
||||
|
||||
Changements : gouvernance, audit Python, version workspace, plan et delta.
|
||||
|
||||
Gate utilisateur :
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Aucun test runtime réseau n'est requis avant l'existence du code réseau.
|
||||
|
||||
### `alpha.2`
|
||||
|
||||
Ajouter `game-realtime-transport-lib`, ses tests ciblés et sa documentation publique.
|
||||
|
||||
Gate minimale supplémentaire :
|
||||
|
||||
```bash
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
```
|
||||
|
||||
### `alpha.3`
|
||||
|
||||
Ajouter `game-realtime-websocket-lib`, les dépendances backend et le loopback principal.
|
||||
|
||||
Gates ciblées :
|
||||
|
||||
```bash
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
```
|
||||
|
||||
### `alpha.4`
|
||||
|
||||
Fermer robustesse, limites, timeouts, close/cancellation et cas négatifs. Réexécuter les tests des deux crates. Un demo n'est ajouté que si une preuve manque réellement.
|
||||
|
||||
Gates ciblées :
|
||||
|
||||
```bash
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
```
|
||||
|
||||
Le test backend doit maintenant couvrir le loopback positif, les invariants de configuration, les mappings de capacité/backpressure et les cinq scénarios négatifs déterministes de `robustness.rs`.
|
||||
|
||||
### `alpha.5`
|
||||
|
||||
Ajouter le smoke runtime public avant promotion de maturité.
|
||||
|
||||
Gates ciblées :
|
||||
|
||||
```bash
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
cargo tree -p game-realtime-websocket-smoke --edges normal
|
||||
```
|
||||
|
||||
Le smoke doit binder `127.0.0.1:0`, connecter un client via l'API publique, échanger un payload binaire dans chaque sens, effectuer un close propre, afficher `game-realtime-websocket-smoke: PASS` et sortir avec le code zéro. Il ne dépend ni d'Internet, ni d'un port fixe, ni d'un harness `#[test]`.
|
||||
|
||||
### Beta
|
||||
|
||||
La beta est le jalon large retenu pour :
|
||||
|
||||
```bash
|
||||
cargo test --workspace --all-targets --all-features
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
cargo tree -p game-realtime-websocket-lib --edges normal
|
||||
cargo tree -i game-realtime-websocket-lib --workspace --edges normal
|
||||
```
|
||||
|
||||
Elle revalide également audits, format/check et Clippy. Le test workspace couvre les suites transport/WebSocket et leurs loopbacks ; le smoke reste exécuté séparément car un binaire runtime n'est pas remplacé par le harness `#[test]`. L'arbre inverse doit confirmer qu'aucune crate sous `crates/games/` ou `crates/engines/` ne dépend du backend WebSocket.
|
||||
|
||||
`0.3.4-beta.1` a satisfait cette gate le 2026-09-21 : 53 tests workspace réussis, smoke runtime `PASS`, graphe backend sans TLS et arbre inverse limité à `game-realtime-websocket-smoke`. Aucun fix beta n'est requis.
|
||||
|
||||
### RC
|
||||
|
||||
La RC gèle le comportement et rejoue les gates de publication utiles. Comme aucun Rust, dépendance tierce ou contrat transport n'a changé depuis la beta, le test workspace complet n'est pas répété par cérémonie. La gate RC retenue est :
|
||||
|
||||
```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-realtime-transport-lib --all-targets --all-features
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
cargo tree -i game-realtime-websocket-lib --workspace --edges normal
|
||||
```
|
||||
|
||||
Le smoke doit encore afficher `game-realtime-websocket-smoke: PASS`. Tout défaut de publication fermé produit `rc.1.fix.N`; toute réouverture fonctionnelle abandonne la RC conformément à `VER-RC-004`.
|
||||
|
||||
## Forecast révisé
|
||||
|
||||
### `0.3.4-alpha.1` — gouvernance, audit et contrat
|
||||
|
||||
- migrer les règles prospectives ;
|
||||
- adapter l'audit de version sans casser l'historique ;
|
||||
- synchroniser la version workspace ;
|
||||
- vérifier dépendances et contraintes amont ;
|
||||
- décider ownership, contrat, erreurs, lifecycle, limites et tests ;
|
||||
- créer le présent plan.
|
||||
|
||||
Aucune dépendance réseau n'est ajoutée.
|
||||
|
||||
### `0.3.4-alpha.2` — API transport-neutral
|
||||
|
||||
- créer `game-realtime-transport-lib` ;
|
||||
- implémenter message, receive/close, erreurs et split ;
|
||||
- conserver l'API sans Tokio/Tungstenite ;
|
||||
- retenir des futures associées GAT afin de préserver le dispatch statique sans box ni contrainte `Send` imposée au contrat ;
|
||||
- tests unitaires ciblés ;
|
||||
- README/USAGE uniquement si une valeur durable est démontrée par `DOC-CRATE-*`.
|
||||
|
||||
### `0.3.4-alpha.3` — backend WebSocket et loopback
|
||||
|
||||
- créer `game-realtime-websocket-lib` ;
|
||||
- ajouter Tokio, `tokio-tungstenite` et `futures-util` avec features minimales ;
|
||||
- client connect + serveur bind/accept ;
|
||||
- mapping binaire, tracing, close ;
|
||||
- round-trip localhost déterministe.
|
||||
|
||||
### `0.3.4-alpha.4` — robustesse et consolidation technique
|
||||
|
||||
- `WebSocketConfig` avec limites et deadlines explicites ;
|
||||
- application symétrique de la configuration aux handshakes client/serveur ;
|
||||
- timeout connect/handshake, send et close, sans idle timeout transport ;
|
||||
- fermeture distante propre conservée et abrupt drop distingué ;
|
||||
- cas Text non supporté ;
|
||||
- message/frame hors limite ;
|
||||
- write buffer maximum borné et mapping `Backpressure` testé sans fabriquer un test réseau flaky ;
|
||||
- lifecycle sans runtime/task backend privé ;
|
||||
- documentation API/backend et audit du graphe.
|
||||
|
||||
La gate `alpha.4` est propre, mais elle ne constitue pas à elle seule un smoke runtime : tous ses scénarios restent exécutés sous le harness de test Cargo.
|
||||
|
||||
### `0.3.4-alpha.5` — smoke runtime end-to-end
|
||||
|
||||
- créer `game-realtime-websocket-smoke` comme launcher minimal sous `crates/apps/` ;
|
||||
- utiliser uniquement les API publiques `game-realtime-websocket-lib` et `game-realtime-transport-lib` ;
|
||||
- binder un listener loopback sur port éphémère et connecter un client réel ;
|
||||
- vérifier client → serveur puis serveur → client ;
|
||||
- initier et observer un close propre ;
|
||||
- borner le smoke global et retourner un code de sortie non nul au moindre défaut ;
|
||||
- ne créer ni protocole session/gameplay, ni serveur externe, ni port fixe.
|
||||
|
||||
`DOC-CRATE-*` est réévalué pour ce launcher : le workflow se résume à une commande Cargo unique et le plan central décrit entièrement sa responsabilité, donc aucun `README.md`/`USAGE.md` local n'est ajouté.
|
||||
|
||||
### `0.3.4-beta.1` — validation large
|
||||
|
||||
- aucun nouveau scope ;
|
||||
- test workspace complet planifié ;
|
||||
- tests loopback/negatifs ;
|
||||
- dépendances et frontières vérifiées ;
|
||||
- confirmation qu'aucun gameplay/engine ne dépend du backend WebSocket.
|
||||
|
||||
Un défaut fermé produit `beta.1.fix.N`. Une capacité manquante réouvre une alpha.
|
||||
|
||||
### `0.3.4-rc.1` — candidate gelée
|
||||
|
||||
- scope fonctionnel gelé après validation complète de `beta.1` ;
|
||||
- entrée RC dans `CHANGELOG.md` ;
|
||||
- `ROADMAP.md` relue mais conservée non cochée jusqu'à la stable ;
|
||||
- historique `beta.1` ;
|
||||
- README durable de `game-realtime-transport-lib` ;
|
||||
- README + USAGE durables de `game-realtime-websocket-lib` ;
|
||||
- prompt `0.3.5` préparant le POC WebTransport/QUIC sur la même frontière, sans prétendre qu'un backend ou une stack QUIC est déjà validé ;
|
||||
- aucune abstraction nouvelle ni modification du comportement réseau sans défaut de release.
|
||||
|
||||
### `0.3.4` — stable
|
||||
|
||||
Promotion mécanique réalisée : version `0.3.4`, historique RC, changelog stable, roadmap clôturée, plan clôturé, delta `rel.001` et prompt `0.3.5` ajusté sur la stable effectivement publiée.
|
||||
|
||||
## Consolidation documentaire RC
|
||||
|
||||
La revue `DOC-CRATE-*` conclut :
|
||||
|
||||
- `game-realtime-transport-lib` est une capability durable et réutilisable : un `README.md` local documente responsabilité, frontière, sémantique et sens des dépendances ; son API publique reste suffisamment petite pour ne pas justifier un `USAGE.md` séparé ;
|
||||
- `game-realtime-websocket-lib` est un backend durable avec configuration, client, listener et lifecycle opératoire : il reçoit un `README.md` et un `USAGE.md` ;
|
||||
- `game-realtime-websocket-smoke` reste un launcher de validation minimal : le plan et la commande Cargo couvrent entièrement son usage, donc aucun document local supplémentaire n'est créé.
|
||||
|
||||
Cette documentation est durable et ne contient pas de journal de prerelease. Les détails de validation restent dans `deltas/`, `history/` et `CHANGELOG.md`.
|
||||
|
||||
## Sizing
|
||||
|
||||
Le scope reste compatible avec une seule version/session : deux petites crates techniques, un seul backend concret et des tests locaux. Le POC WebTransport/QUIC, la session multijoueur et la simulation authoritative sont explicitement exclus.
|
||||
|
||||
Un split vers une autre version est requis si l'une des conditions suivantes apparaît :
|
||||
|
||||
- besoin d'un vrai wire codec partagé pour prouver le transport ;
|
||||
- nécessité de concevoir le protocole session/joueur/room ;
|
||||
- TLS direct nécessitant une politique de certificats/PKI produit ;
|
||||
- abstraction commune WebSocket/QUIC exigeant déjà des concepts spécifiques à QUIC ;
|
||||
- demo/app devenant un produit autonome plutôt qu'un harness de preuve.
|
||||
|
||||
## Hors scope confirmé
|
||||
|
||||
- Uroburas Mode 3 ;
|
||||
- matchmaking ;
|
||||
- auth complète ;
|
||||
- snapshots/deltas métier ;
|
||||
- prediction/reconciliation ;
|
||||
- rollback ;
|
||||
- persistence gameplay ;
|
||||
- Redis/NATS/Kafka ;
|
||||
- scaling/sharding/regions ;
|
||||
- WebTransport/QUIC productif ;
|
||||
- Actix Web dans le data plane realtime ;
|
||||
- modification du pipeline Android `0.3.3` sans régression causée par `0.3.4`.
|
||||
|
||||
## Critères d'entrée en beta
|
||||
|
||||
`0.3.4` peut entrer en beta lorsque :
|
||||
|
||||
- l'API commune ne dépend ni de Tokio ni de WebSocket ;
|
||||
- le backend WebSocket implémente le contrat public sans fuite de types Tungstenite ;
|
||||
- client et serveur round-tripent des payloads binaires sur localhost ;
|
||||
- close local/distant est observable ;
|
||||
- limites et timeouts retenus sont testés ;
|
||||
- aucun queueing non borné ni runtime privé n'est introduit ;
|
||||
- les targets tracing transport/WebSocket sont distinctes des futurs domaines session/sync/simulation ;
|
||||
- aucune crate gameplay ou engine ne dépend de `tokio-tungstenite` ;
|
||||
- les audits et tests ciblés sont propres ;
|
||||
- le smoke `cargo run -p game-realtime-websocket-smoke` passe réellement côté utilisateur hors harness de test ;
|
||||
- la documentation décrit ce qui est réellement implémenté et ce qui reste reporté.
|
||||
555
docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md
Normal file
555
docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md
Normal file
@@ -0,0 +1,555 @@
|
||||
<!-- file: docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Plan 0.3.5 — POC WebTransport/QUIC et fallback WebSocket
|
||||
|
||||
## Statut
|
||||
|
||||
Plan actif créé pendant `0.3.5-alpha.1` à partir de l'archive taggée `v0.3.4`.
|
||||
|
||||
Le cadrage détaillé et la comparaison des stacks actuelles sont conservés dans `docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md`. Le présent document porte les décisions opérationnelles, le scope, les gates et le forecast vivant de la version.
|
||||
|
||||
## Mission
|
||||
|
||||
`0.3.5` doit déterminer par des preuves reproductibles si WebTransport/QUIC mérite de devenir un second backend realtime aux côtés de WebSocket.
|
||||
|
||||
La version doit :
|
||||
|
||||
- conserver `game-realtime-transport-lib` comme frontière transport-neutral tant qu'aucun besoin commun ne justifie son évolution ;
|
||||
- implémenter un chemin WebTransport fiable compatible avec `TransportMessage` ;
|
||||
- prouver client/server natifs en loopback ;
|
||||
- prouver un chemin navigateur/WASM réel si la stack retenue reste viable ;
|
||||
- prouver le traitement des certificats de développement sans désactivation permanente de TLS ;
|
||||
- exercer le fallback WebSocket au niveau composition ;
|
||||
- comparer WebSocket et WebTransport avec des mesures locales bornées ;
|
||||
- challenger séparément les datagrams sans les forcer dans le contrat fiable ;
|
||||
- conclure `retained`, `deferred` ou `rejected` pour la trajectoire produit.
|
||||
|
||||
La frontière reste :
|
||||
|
||||
```text
|
||||
transport
|
||||
↓
|
||||
wire codec
|
||||
↓
|
||||
session protocol
|
||||
↓
|
||||
synchronization
|
||||
↓
|
||||
authoritative simulation
|
||||
```
|
||||
|
||||
Aucune couche supérieure n'est introduite dans `0.3.5`.
|
||||
|
||||
## Décisions acquises en alpha.1
|
||||
|
||||
### Stack POC primaire
|
||||
|
||||
Famille retenue :
|
||||
|
||||
```text
|
||||
web-transport 0.12.x
|
||||
native -> web-transport-quinn 0.12.x
|
||||
wasm32 -> web-transport-wasm 0.6.x
|
||||
```
|
||||
|
||||
La version exacte résolue par Cargo sera enregistrée dans le delta qui introduit les dépendances. Les contraintes restent centralisées sous `[workspace.dependencies]` conformément à `RUST-DEP-001` et les features sont choisies localement par la crate consommatrice.
|
||||
|
||||
`wtransport 0.7.x` reste le candidat de repli prioritaire si une exigence concrète bloque la famille primaire. Quinn/H3 brut n'est pas le premier choix.
|
||||
|
||||
### Ownership physique
|
||||
|
||||
Nouveau backend durable candidat :
|
||||
|
||||
```text
|
||||
crates/common/game-realtime-webtransport-lib
|
||||
```
|
||||
|
||||
Responsabilités :
|
||||
|
||||
- établissement WebTransport natif et, si viable, client WASM ;
|
||||
- adaptation du chemin fiable vers `game-realtime-transport-lib` ;
|
||||
- framing binaire privé du stream principal ;
|
||||
- TLS/certificat/hash nécessaires au transport ;
|
||||
- limites, timeouts, close/reset/cancellation et mapping d'erreurs ;
|
||||
- tracing `games::realtime::webtransport` ;
|
||||
- tests natifs déterministes ;
|
||||
- aucune notion joueur/room/tick/snapshot.
|
||||
|
||||
Launchers techniques candidats :
|
||||
|
||||
```text
|
||||
crates/apps/game-realtime-webtransport-smoke
|
||||
crates/apps/game-realtime-transport-benchmark
|
||||
```
|
||||
|
||||
Un launcher séparé de fallback n'est créé que si le smoke WebTransport ou le benchmark ne peut pas porter proprement cette preuve. Ne pas créer plusieurs exécutables uniquement pour refléter chaque alpha.
|
||||
|
||||
Pour le navigateur, le frontend technique exact est décidé au moment de `alpha.7` après validation du chemin WASM. S'il faut un host Vite direct, il doit rester explicitement technique et ne pas contaminer `Web/game-snake-poc` ou le gameplay Snake.
|
||||
|
||||
### Contrat commun
|
||||
|
||||
`game-realtime-transport-lib` reste inchangé dans le plan initial.
|
||||
|
||||
Le chemin fiable utilise :
|
||||
|
||||
```text
|
||||
one WebTransport session
|
||||
-> one primary bidirectional reliable stream
|
||||
-> u32 big-endian length
|
||||
-> payload bytes
|
||||
```
|
||||
|
||||
Le framing est privé au backend, borné avant allocation et distinct du futur wire codec.
|
||||
|
||||
Le client ouvre le stream principal ; le serveur l'accepte avant de retourner une connexion utilisable. Le split commun mappe ensuite write/read du stream principal vers `RealtimeSender`/`RealtimeReceiver`.
|
||||
|
||||
Toute modification du contrat commun requiert un besoin impossible à satisfaire proprement par WebSocket et WebTransport autrement ; elle n'est pas autorisée pour harmoniser les noms d'API.
|
||||
|
||||
### Datagrams
|
||||
|
||||
Les datagrams restent hors `RealtimeConnection` parce qu'ils sont non fiables et non ordonnés.
|
||||
|
||||
Le POC les exerce uniquement comme capacité backend-spécifique/mesure. Aucune API commune durable n'est créée sans second consommateur réel.
|
||||
|
||||
### TLS de développement
|
||||
|
||||
Le POC privilégie :
|
||||
|
||||
- certificat self-signed X.509v3 court ;
|
||||
- ECDSA P-256 ;
|
||||
- validité totale inférieure à deux semaines ;
|
||||
- pin SHA-256 côté client natif et navigateur ;
|
||||
- aucune clé privée durable versionnée ;
|
||||
- aucune désactivation globale de validation TLS.
|
||||
|
||||
Le mode PKI publique, ACME et reverse proxy HTTP/3 restent hors scope de `0.3.5`.
|
||||
|
||||
### Fallback
|
||||
|
||||
Le fallback vit au niveau composition/application :
|
||||
|
||||
```text
|
||||
WebTransport attempt
|
||||
-> success: WebTransport
|
||||
-> classified unavailable/establishment failure: WebSocket
|
||||
```
|
||||
|
||||
Le test doit pouvoir forcer les deux branches.
|
||||
|
||||
Pas de `TransportManager`, registry de plugins ou sélection dynamique générique dans `game-realtime-transport-lib`.
|
||||
|
||||
## Plateformes et preuves
|
||||
|
||||
### Linux natif — obligatoire
|
||||
|
||||
- compilation backend ;
|
||||
- client/server loopback sur adresse/port éphémère ;
|
||||
- round-trip binaire ;
|
||||
- limites/timeouts/close/reset ;
|
||||
- smoke runtime hors `#[test]` ;
|
||||
- mesures locales WebSocket vs WebTransport.
|
||||
|
||||
### WASM/navigateur — obligatoire si la stack primaire reste viable
|
||||
|
||||
Deux gates distinctes :
|
||||
|
||||
1. build/check réel `wasm32-unknown-unknown` du chemin WebTransport Rust ;
|
||||
2. smoke runtime dans un navigateur récent vers le serveur Rust local.
|
||||
|
||||
Le cfg `web_sys_unstable_apis` doit être ciblé sur WASM uniquement.
|
||||
|
||||
### Android — trajectoire, pas intégration produit
|
||||
|
||||
- vérifier la compatibilité des dépendances et, si raisonnable, compiler le backend pour au moins `aarch64-linux-android` ;
|
||||
- ne modifier ni Java, ni Gradle, ni JNI sans besoin concret découvert ;
|
||||
- ne pas rejouer APK/AAB quatre ABI si aucun chemin Android produit n'est touché.
|
||||
|
||||
### Apple — documentation uniquement
|
||||
|
||||
macOS/iOS restent non validés sans environnement Apple. La version documente la dépendance supposée mais n'attribue aucun smoke.
|
||||
|
||||
## Métriques
|
||||
|
||||
Mesures obligatoires si les deux transports fonctionnent :
|
||||
|
||||
```text
|
||||
establishment latency
|
||||
RTT: small payload
|
||||
throughput: bounded medium payload
|
||||
several messages in flight
|
||||
```
|
||||
|
||||
Jeu d'essai candidat :
|
||||
|
||||
```text
|
||||
32 B
|
||||
256 B
|
||||
1 KiB
|
||||
16 KiB
|
||||
```
|
||||
|
||||
La taille exacte peut évoluer selon les limites observées, mais reste identique entre transports.
|
||||
|
||||
Les résultats doivent inclure au minimum nombre d'itérations et une statistique robuste simple, par exemple médiane et p95. Aucun benchmark loopback ne conclut à lui seul sur Internet/mobile.
|
||||
|
||||
CPU/mémoire sont optionnels si la mesure n'est pas stable/reproductible dans la session.
|
||||
|
||||
## Smoke tests prévus
|
||||
|
||||
### Smoke WebSocket hérité
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
Reste le témoin de fallback et doit être rejoué aux jalons larges où le fallback est concerné.
|
||||
|
||||
### Smoke WebTransport natif
|
||||
|
||||
Cible prévue :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-webtransport-smoke
|
||||
```
|
||||
|
||||
Il doit :
|
||||
|
||||
- générer/charger uniquement l'identité de développement nécessaire ;
|
||||
- binder localement sans port fixe ;
|
||||
- établir un client WebTransport ;
|
||||
- échanger au moins un payload dans chaque sens ;
|
||||
- fermer proprement ;
|
||||
- afficher un résultat `PASS` déterministe ;
|
||||
- ne dépendre ni d'Internet ni d'un secret versionné.
|
||||
|
||||
### Smoke navigateur
|
||||
|
||||
Le smoke navigateur doit prouver réellement :
|
||||
|
||||
- chargement du client WASM retenu ;
|
||||
- création WebTransport vers le serveur Rust ;
|
||||
- hash certificat transmis explicitement ;
|
||||
- round-trip binaire ;
|
||||
- close propre ;
|
||||
- absence de fallback silencieux vers WebSocket pendant la preuve WebTransport.
|
||||
|
||||
Le workflow exact est fixé dans `alpha.7` après la preuve de compilation WASM.
|
||||
|
||||
### Smoke fallback
|
||||
|
||||
La preuve de fallback doit couvrir :
|
||||
|
||||
- WebTransport disponible -> chemin WebTransport ;
|
||||
- WebTransport volontairement indisponible/endpoint invalide -> WebSocket ;
|
||||
- erreur non classée comme fallback -> erreur visible, pas masquée.
|
||||
|
||||
Cette preuve peut être intégrée à un launcher technique existant si cela garde le code plus petit et plus clair.
|
||||
|
||||
## Tests automatisés prévus
|
||||
|
||||
### Backend natif
|
||||
|
||||
Au minimum :
|
||||
|
||||
- configuration valide/invalide ;
|
||||
- établissement loopback ;
|
||||
- payload vide si le contrat l'autorise ;
|
||||
- payload binaire normal ;
|
||||
- plusieurs messages ordonnés ;
|
||||
- taille maximale et dépassement ;
|
||||
- longueur de frame malformée/overflow impossible ;
|
||||
- timeout d'établissement ;
|
||||
- timeout send/receive lorsque reproductible ;
|
||||
- fermeture locale ;
|
||||
- fermeture distante ;
|
||||
- reset/abort du stream principal ;
|
||||
- drop/cancellation sans task détachée ;
|
||||
- mapping des erreurs sans fuite des types amont dans le contrat commun.
|
||||
|
||||
### WASM
|
||||
|
||||
Les tests unitaires qui n'exigent pas de navigateur restent ciblés. L'interop navigateur est un smoke runtime distinct et ne doit pas être simulée par un test natif.
|
||||
|
||||
### Fallback
|
||||
|
||||
Tester la classification et la décision de composition sans créer une abstraction runtime générique.
|
||||
|
||||
## Graphe de dépendances attendu
|
||||
|
||||
```text
|
||||
game-realtime-transport-lib
|
||||
↑
|
||||
├── game-realtime-websocket-lib
|
||||
└── game-realtime-webtransport-lib
|
||||
```
|
||||
|
||||
Aucune crate sous :
|
||||
|
||||
```text
|
||||
crates/engines/
|
||||
crates/games/
|
||||
```
|
||||
|
||||
doit dépendre de `web-transport`, Quinn, Rustls ou d'un backend concret.
|
||||
|
||||
Les launchers techniques peuvent dépendre des backends nécessaires à leur preuve.
|
||||
|
||||
## Hors scope
|
||||
|
||||
- wire codec définitif ;
|
||||
- session joueur/room ;
|
||||
- matchmaking/auth ;
|
||||
- snapshots/deltas gameplay ;
|
||||
- prediction/reconciliation/rollback ;
|
||||
- simulation authoritative ;
|
||||
- Uroburas Mode 3 ;
|
||||
- persistence gameplay ;
|
||||
- cluster/sharding/regions ;
|
||||
- Redis/NATS/Kafka ;
|
||||
- PKI/ACME produit ;
|
||||
- reverse proxy HTTP/3 définitif ;
|
||||
- CDN/edge ;
|
||||
- framework générique multi-transport ;
|
||||
- réécriture Actix Web ;
|
||||
- modification du gameplay pour sélectionner un transport.
|
||||
|
||||
## Risques et critères de replanification
|
||||
|
||||
La version est replanifiée avant exécution lorsqu'une tranche dépasse clairement 30 minutes de scope attendu.
|
||||
|
||||
Déclencheurs explicites :
|
||||
|
||||
- compilation de la stack primaire impossible avec les règles Rust du dépôt ;
|
||||
- besoin d'un fork amont ;
|
||||
- browser smoke exigeant une infrastructure externe ou PKI disproportionnée ;
|
||||
- dépendance crypto rendant Android ou Linux non praticable ;
|
||||
- évolution du contrat commun requise ;
|
||||
- framing fiable beaucoup plus complexe que prévu ;
|
||||
- fallback nécessitant une abstraction partagée nouvelle ;
|
||||
- benchmark devenant un sous-projet de performance.
|
||||
|
||||
Dans ces cas, le delta courant reste fermé sur sa preuve et le plan est révisé avant la tranche suivante.
|
||||
|
||||
## Forecast révisé
|
||||
|
||||
Chaque tranche vise environ 15 à 30 minutes de travail effectif et une preuve indépendante. Les numéros restent prévisionnels conformément à `SESSION-007`.
|
||||
|
||||
### `0.3.5-alpha.1` — cadrage, audit et plan
|
||||
|
||||
- audit archive/règles/historique `0.3.4` ;
|
||||
- recherche stacks WebTransport/QUIC ;
|
||||
- choix primaire + fallback technique ;
|
||||
- décision contrat/framing/datagram ;
|
||||
- matrice TLS/plateforme ;
|
||||
- smoke/tests/metrics ;
|
||||
- plan vivant.
|
||||
|
||||
Aucun backend ni dépendance WebTransport ajouté.
|
||||
|
||||
### `0.3.5-alpha.2` — crate backend, dépendances et établissement natif
|
||||
|
||||
- créer `game-realtime-webtransport-lib` ;
|
||||
- ajouter uniquement les dépendances/features nécessaires ;
|
||||
- config native client/server ;
|
||||
- génération ou injection d'identité de test ;
|
||||
- hash pinning ;
|
||||
- établir une session native client/server ;
|
||||
- tests d'établissement/configuration ;
|
||||
- README initial de responsabilité/frontières.
|
||||
|
||||
Pas encore d'implémentation complète `RealtimeConnection` si cela rend la tranche trop lourde.
|
||||
|
||||
### `0.3.5-alpha.3` — stream fiable et contrat commun
|
||||
|
||||
- stream bidirectionnel principal ;
|
||||
- framing `u32 + payload` borné ;
|
||||
- `RealtimeConnection`, sender et receiver ;
|
||||
- round-trip ordonné multi-message ;
|
||||
- fermeture distante de base ;
|
||||
- tests loopback ciblés ;
|
||||
- USAGE si l'API/configuration devient non triviale.
|
||||
|
||||
### `0.3.5-alpha.4` — robustesse et lifecycle
|
||||
|
||||
- limites ;
|
||||
- deadlines ;
|
||||
- backpressure/erreurs observables ;
|
||||
- close/reset/abort ;
|
||||
- cancellation/drop ;
|
||||
- cas négatifs de framing ;
|
||||
- mapping d'erreurs ;
|
||||
- tests de robustesse ciblés.
|
||||
|
||||
### `0.3.5-alpha.5` — smoke natif hors harness
|
||||
|
||||
- créer ou finaliser `game-realtime-webtransport-smoke` ;
|
||||
- round-trip réel localhost ;
|
||||
- certificat/hash de développement reproductible ;
|
||||
- `PASS` déterministe ;
|
||||
- contrôle du graphe de dépendances.
|
||||
|
||||
Cette tranche reste distincte pour ne pas mélanger robustesse de bibliothèque et preuve runtime publique.
|
||||
|
||||
### `0.3.5-alpha.6` — chemin WASM compilable
|
||||
|
||||
- activer `web_sys_unstable_apis` uniquement pour `wasm32-unknown-unknown` ;
|
||||
- implémenter/adapter le client WASM ;
|
||||
- prouver le build/check WASM ciblé ;
|
||||
- ne pas modifier Snake ou Reflex ;
|
||||
- documenter les différences native/WASM réellement rencontrées.
|
||||
|
||||
Si la famille `web-transport` échoue ici, évaluer `wtransport` serveur + API navigateur directe avant de conclure.
|
||||
|
||||
### `0.3.5-alpha.7` — interop navigateur et TLS local
|
||||
|
||||
- host technique minimal si nécessaire ;
|
||||
- navigateur récent -> serveur Rust WebTransport ;
|
||||
- hash certificat W3C ;
|
||||
- round-trip binaire ;
|
||||
- close propre ;
|
||||
- smoke documenté et reproductible ;
|
||||
- aucune désactivation permanente de TLS.
|
||||
|
||||
Cette tranche est volontairement séparée de `alpha.6` car certificat, serveur local et browser tooling constituent un risque opérationnel distinct.
|
||||
|
||||
### `0.3.5-alpha.8` — fallback WebSocket au niveau composition
|
||||
|
||||
- tentative WebTransport ;
|
||||
- fallback WebSocket uniquement sur erreurs classifiées ;
|
||||
- branche WebTransport forcée ;
|
||||
- branche fallback forcée ;
|
||||
- erreur non-fallback visible ;
|
||||
- aucun couplage gameplay ;
|
||||
- pas de registry/framework générique.
|
||||
|
||||
### `0.3.5-alpha.9` — datagram POC isolé
|
||||
|
||||
Uniquement si le backend fiable et le navigateur sont suffisamment stables :
|
||||
|
||||
- émission/réception datagram ;
|
||||
- pertes/ordre non garantis documentés ;
|
||||
- preuve locale bornée ;
|
||||
- aucune modification du contrat commun ;
|
||||
- décision explicite : utile pour future capability ou simple constat.
|
||||
|
||||
Si la valeur est déjà claire sans API supplémentaire, cette tranche peut être fusionnée avec la mesure ou supprimée.
|
||||
|
||||
### `0.3.5-alpha.10` — mesures comparatives bornées
|
||||
|
||||
- launcher/outil minimal de mesure ;
|
||||
- WebSocket vs WebTransport fiable ;
|
||||
- établissement, RTT, throughput, messages en vol ;
|
||||
- datagram seulement s'il existe réellement ;
|
||||
- résultats et limites de méthode documentés ;
|
||||
- conclusion technique provisoire `retain/defer/reject`.
|
||||
|
||||
### `0.3.5-beta.1` — validation large
|
||||
|
||||
Jalon rare de full workspace :
|
||||
|
||||
```bash
|
||||
cargo test --workspace --all-targets --all-features
|
||||
```
|
||||
|
||||
Plus :
|
||||
|
||||
- fmt/check/Clippy/audits ;
|
||||
- tests ciblés realtime ;
|
||||
- smoke WebSocket ;
|
||||
- smoke WebTransport natif ;
|
||||
- smoke navigateur si retenu ;
|
||||
- preuve fallback ;
|
||||
- `cargo tree` direct/inverse des deux backends ;
|
||||
- vérification qu'aucun gameplay/engine ne dépend d'un backend concret.
|
||||
|
||||
Toute capacité fonctionnelle majeure manquante réouvre une alpha ; un défaut fermé produit `beta.1.fix.N`.
|
||||
|
||||
### `0.3.5-beta.2` — consolidation pré-RC
|
||||
|
||||
Tranche explicitement réservée par `SESSION-009` / `VER-PHASE-010` :
|
||||
|
||||
- réconcilier README/USAGE et docs durables ;
|
||||
- figer la conclusion WebTransport ;
|
||||
- documenter plateformes réellement validées/non validées ;
|
||||
- mettre à jour `CHANGELOG.md` si la transition vers RC est préparée ;
|
||||
- réconcilier `ROADMAP.md` seulement si le statut macro change ;
|
||||
- écrire le prompt `0.3.6` ;
|
||||
- enregistrer l'historique `beta.1` après validation ;
|
||||
- aucune nouvelle fonctionnalité majeure.
|
||||
|
||||
### `0.3.5-rc.1` — candidate gelée
|
||||
|
||||
- aucun comportement volontaire nouveau ;
|
||||
- full workspace conformément à `CMD-RC-002` ;
|
||||
- gates realtime de publication ;
|
||||
- smokes retenus ;
|
||||
- graphe de dépendances ;
|
||||
- vérification du prompt `0.3.6` ;
|
||||
- corrections uniquement selon `VER-RC-*`.
|
||||
|
||||
### `0.3.5` — stable
|
||||
|
||||
Promotion mécanique autant que possible :
|
||||
|
||||
- version stable ;
|
||||
- historique RC ;
|
||||
- changelog stable ;
|
||||
- clôture du plan ;
|
||||
- roadmap si nécessaire ;
|
||||
- delta final ;
|
||||
- ajustement mécanique du prompt `0.3.6`.
|
||||
|
||||
## Gates Cargo planifiées
|
||||
|
||||
### Tranches Rust ordinaires
|
||||
|
||||
Dès qu'un fichier Rust/Cargo change :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
```
|
||||
|
||||
Puis tests ciblés des crates touchées/consommateurs affectés.
|
||||
|
||||
### Full workspace
|
||||
|
||||
`cargo test --workspace --all-targets --all-features` est réservé explicitement à :
|
||||
|
||||
- `beta.1` ;
|
||||
- `rc.1` ;
|
||||
- éventuellement une tranche plus tôt uniquement si un changement transverse rend les tests ciblés insuffisants.
|
||||
|
||||
Il n'est pas répété à chaque alpha.
|
||||
|
||||
### Cargo tree
|
||||
|
||||
À exécuter :
|
||||
|
||||
- après introduction/changement de dépendances WebTransport ;
|
||||
- à `alpha.5` pour prouver la frontière ;
|
||||
- à `beta.1` et `rc.1` pour les graphes de publication.
|
||||
|
||||
## Nettoyage Cargo
|
||||
|
||||
Aucun `cargo clean` n'est requis dans `alpha.1`.
|
||||
|
||||
Le plan réserve un nettoyage complet au plus tard avant la validation RC si l'accumulation du target-dir ou un doute de reproductibilité le justifie. Un nettoyage ciblé reste préférable pendant les alphas.
|
||||
|
||||
## Critère de réussite de 0.3.5
|
||||
|
||||
La version est réussie si elle fournit une conclusion reproductible parmi :
|
||||
|
||||
```text
|
||||
retained -> second backend utile, conserver pour 0.4.x
|
||||
deferred -> viable mais bénéfice/portabilité/infrastructure insuffisants aujourd'hui
|
||||
rejected -> coût ou incompatibilité disproportionnés pour la trajectoire actuelle
|
||||
```
|
||||
|
||||
Aucune conclusion n'est imposée à l'avance.
|
||||
|
||||
Même en cas de report/rejet, la baseline WebSocket `0.3.4` reste fonctionnelle et le POC ne doit pas laisser une abstraction commune artificiellement déformée.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/PROMPT_STRUCTURE.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# Structure des prompts de reprise
|
||||
|
||||
@@ -15,7 +15,7 @@ Le prompt rappelle les invariants qui évitent les erreurs de workflow et renvoi
|
||||
- **PROMPT-STR-002** — Le prompt fournit un ordre de lecture court des sources de vérité : `RULES.md`, `ROADMAP.md`, `CHANGELOG.md`, `docs/000-README.md`, règles directement pertinentes, plan/historique de la version précédente et documents d'architecture concernés.
|
||||
- **PROMPT-STR-003** — Le prompt distingue explicitement l'état déjà validé hérité de la baseline des validations qui devront être exécutées dans la nouvelle version.
|
||||
- **PROMPT-STR-004** — Le prompt décrit la mission, le résultat attendu, le scope inclus, le hors-périmètre et les invariants architecturaux gelés.
|
||||
- **PROMPT-STR-005** — Le prompt rappelle que `0-pre.1` est le gate de cadrage : audit, requirements, sizing, risques, validations prévues et création/révision du plan sous `docs/plans/` avant développement lourd.
|
||||
- **PROMPT-STR-005** — Le prompt rappelle que `alpha.1` est le gate de cadrage : audit, requirements, sizing, risques, validations prévues et création/révision du plan sous `docs/plans/` avant développement lourd.
|
||||
- **PROMPT-STR-006** — Le prompt contient un forecast souple jusqu'à la stable. Il réserve les responsabilités de développement, validation large, consolidation documentaire, préparation de publication/RC et release mécanique sans rendre les numéros immuables.
|
||||
- **PROMPT-STR-007** — Le prompt rappelle où se trouve la définition des commandes : `docs/rules/RULES_COMMANDS.md` pour la politique d'exécution et `docs/rules/RULES_VALIDATION_MATRIX.md` pour les IDs, dépendances et déclencheurs. Il ne recopie que les commandes indispensables à la reprise ou au premier gate.
|
||||
- **PROMPT-STR-008** — Le prompt rappelle la séparation utilisateur/générateur : les audits statiques peuvent être exécutés par le générateur, mais les builds, tests et smokes finaux restent côté utilisateur conformément à `CMD-BUILD-005` et ne sont jamais déclarés réussis sans sortie réelle.
|
||||
@@ -30,7 +30,7 @@ Le prompt rappelle les invariants qui évitent les erreurs de workflow et renvoi
|
||||
|
||||
- **PROMPT-STR-010** — Le prompt rappelle que `deltas/` décrit la livraison candidate et ses validations attendues, alors que `history/` enregistre uniquement le résultat d'un jalon effectivement accepté.
|
||||
- **PROMPT-STR-011** — Le prompt rappelle qu'une entrée `history/<X.Y.Z>/<jalon>.md` est créée par le delta suivant ou le fix suivant après validation, jamais avant la validation qu'elle décrit.
|
||||
- **PROMPT-STR-012** — Le prompt rappelle que `CHANGELOG.md` n'est normalement mis à jour qu'à partir de la RC puis à la stable ; les détails `pre`/`beta` restent dans `deltas/` et `history/`.
|
||||
- **PROMPT-STR-012** — Le prompt rappelle que `CHANGELOG.md` n'est normalement mis à jour qu'à partir de la RC puis à la stable ; les détails `alpha`/`beta` restent dans `deltas/` et `history/`.
|
||||
- **PROMPT-STR-013** — Le prompt rappelle que `ROADMAP.md` reste macroscopique et n'est modifié que lorsque le scope, son ordre ou son statut évolue réellement ; le plan de version porte le découpage fin.
|
||||
- **PROMPT-STR-014** — Le prompt rappelle que la documentation propre à une fonctionnalité évolue avec la tranche qui l'introduit ; la consolidation finale réconcilie l'ensemble mais ne sert pas à repousser toute documentation à la fin.
|
||||
- **PROMPT-STR-015** — Le prompt mentionne explicitement la politique `README.md`/`USAGE.md` lorsque la version crée ou finalise une crate, une application ou un package : appliquer `DOC-CRATE-*` et décider dans le plan quels fichiers ont une valeur durable réelle.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_COMMANDS.md -->
|
||||
<!-- version: 14 -->
|
||||
<!-- version: 16 -->
|
||||
|
||||
# Règles d'exécution des commandes
|
||||
|
||||
@@ -48,11 +48,11 @@
|
||||
|
||||
## Android et Gradle
|
||||
|
||||
- **CMD-ANDROID-001** — Les commandes Gradle Android sont exécutées depuis `Android/` ou avec un chemin explicite vers le wrapper du projet.
|
||||
- **CMD-ANDROID-002** — Les tâches ciblées par application sont préférées, par exemple `./gradlew :game-reflex-poc:assembleDebug`, lorsqu'elles existent.
|
||||
- **CMD-ANDROID-001** — Les commandes Gradle du projet Android natif sont exécutées depuis `Android/` avec le `gradle` résolu par l’environnement ; sa version doit être au moins égale à `minimumGradleVersion` déclaré par le projet.
|
||||
- **CMD-ANDROID-002** — Les tâches ciblées par application sont préférées, par exemple `gradle :game-reflex-poc:assembleDebug`, lorsqu'elles existent.
|
||||
- **CMD-ANDROID-003** — Un build Android global n'est pas exécuté si la tranche ne touche ni Android ni le contrat natif utilisé par Android.
|
||||
- **CMD-ANDROID-004** — `gradle clean` ou `./gradlew clean` reste un nettoyage Android ciblé ; il n'est pas rendu obligatoire uniquement parce qu'un `cargo clean` est planifié.
|
||||
- **CMD-ANDROID-005** — Les commandes Android réelles ne deviennent des gates qu'après introduction du wrapper Gradle, de l'AGP, du NDK, de SDL3 et des modules exécutables correspondants.
|
||||
- **CMD-ANDROID-004** — `gradle clean` reste un nettoyage Android ciblé ; il n'est pas rendu obligatoire uniquement parce qu'un `cargo clean` est planifié.
|
||||
- **CMD-ANDROID-005** — Les commandes Android réelles ne deviennent des gates qu'après déclaration d'un minimum Gradle contrôlé, de l'AGP, du NDK, de SDL3 et des modules exécutables correspondants.
|
||||
|
||||
## Web
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
## Outils de build et scripts d'audit
|
||||
|
||||
- **CMD-BUILD-001** — Les scripts Python du dépôt sont autorisés pour les audits, audits complémentaires, validations et validations complémentaires.
|
||||
- **CMD-BUILD-002** — À partir de `0.3.x`, aucun chemin de build nouveau ou modifié n’est piloté par Python. Les orchestrateurs historiques `scripts/build_reflex_tauri_wasm.py` et `scripts/build_android_rust.py` restent tolérés uniquement comme mécanismes gelés de la baseline `0.1.0` jusqu’à la tranche qui réactive leur chemin ; ils ne sont ni copiés, ni généralisés, ni utilisés pour un nouveau POC.
|
||||
- **CMD-BUILD-002** — À partir de `0.3.x`, aucun chemin de build nouveau ou modifié n’est piloté par Python. L'orchestrateur historique `scripts/build_reflex_tauri_wasm.py` reste toléré uniquement comme mécanisme gelé de la baseline `0.1.0` jusqu’à la tranche qui réactive son chemin ; il n'est ni copié, ni généralisé, ni utilisé pour un nouveau POC. Le builder Android historique `scripts/build_android_rust.py` a été retiré lorsque son chemin a été réactivé en `0.3.3`.
|
||||
- **CMD-BUILD-003** — Les builds utilisent l'outil natif approprié au périmètre : Cargo pour Rust, Gradle pour Android, Tauri CLI pour Tauri, ou l'outil officiellement retenu par la plateforme concernée.
|
||||
- **CMD-BUILD-004** — Les POC `0.3.x` doivent remplacer toute orchestration de build Python restante par des procédures explicites, reproductibles et testées avec les outils natifs.
|
||||
- **CMD-BUILD-005** — Les builds, tests unitaires, tests d'intégration et smoke tests de validation sont exécutés côté utilisateur ; les scripts d'audit peuvent vérifier statiquement leur préparation mais ne les simulent pas.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_DOCUMENTATION.md -->
|
||||
<!-- version: 8 -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# Règles de documentation
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
## CHANGELOG
|
||||
|
||||
- **DOC-CHG-001** — `CHANGELOG.md` est une synthèse de publication, pas un journal de développement.
|
||||
- **DOC-CHG-002** — Les `pre.*`, `alpha.*`, `beta.*` et leurs `.fix.*` ne créent normalement aucune entrée de changelog.
|
||||
- **DOC-CHG-002** — Les `alpha.*`, `beta.*` et leurs `.fix.*` ne créent normalement aucune entrée de changelog.
|
||||
- **DOC-CHG-003** — Le changelog est mis à jour à partir des jalons `rc.*` et pour chaque release stable.
|
||||
- **DOC-CHG-004** — Une entrée RC résume l'état candidat à publication ; l'entrée stable résume le résultat effectivement publié.
|
||||
- **DOC-CHG-005** — Les détails intermédiaires de construction, corrections et validations restent dans `deltas/` et `history/`.
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
- **DOC-VAL-001** — Une gate Markdown ou un audit syntaxique valide la forme des documents, jamais leur exactitude fonctionnelle, leur exhaustivité ni leur acceptation.
|
||||
- **DOC-VAL-002** — Une prerelease principalement documentaire reste candidate tant que son contenu n'a pas été relu et accepté humainement.
|
||||
- **DOC-VAL-003** — Une version de conception peut utiliser plusieurs `pre.N` successives uniquement pour permettre revue, correction, complément et maturation documentaire.
|
||||
- **DOC-VAL-003** — Une version de conception peut utiliser plusieurs `alpha.N` successives uniquement pour permettre revue, correction, complément et maturation documentaire.
|
||||
- **DOC-VAL-004** — Cargo, Gradle, packaging et smoke tests ne sont requis pour une prerelease documentaire que si le delta modifie du code, une configuration de build/runtime ou un contrat susceptible de les affecter.
|
||||
- **DOC-VAL-005** — Le document de delta énumère les validations applicables ; l'absence volontaire d'une gate technique doit découler du scope réel, pas d'un raccourci.
|
||||
- **DOC-VAL-006** — Une version documentaire n'est promue en `rc` ou stable qu'après validation explicite de son contenu, même si tous les audits automatisés sont propres.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_SESSION_PLANNING.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Règles de cadrage des versions, sessions et prompts
|
||||
|
||||
@@ -7,26 +7,26 @@
|
||||
|
||||
Ces règles imposent un découpage suffisamment petit pour qu'une version puisse être développée complètement dans une seule session et reprise sans ambiguïté.
|
||||
|
||||
## `pre.1` — cadrage obligatoire
|
||||
## `alpha.1` — cadrage obligatoire
|
||||
|
||||
- **SESSION-001** — Toute nouvelle version commence par une `0-pre.1` de cadrage.
|
||||
- **SESSION-001** — Toute nouvelle version commence par une `alpha.1` de cadrage.
|
||||
- **SESSION-002** — Cette tranche couvre au minimum l'audit de la base, le brainstorming/recherche de requirements, le sizing, les dépendances, les validations prévues et le découpage prévisionnel.
|
||||
- **SESSION-003** — Une première implémentation peut être incluse dans `0-pre.1` uniquement si elle est petite, cohérente et n'empêche pas le cadrage d'être terminé.
|
||||
- **SESSION-003** — Une première implémentation peut être incluse dans `alpha.1` uniquement si elle est petite, cohérente et n'empêche pas le cadrage d'être terminé.
|
||||
- **SESSION-004** — Si le sizing montre que l'objectif global ne peut raisonnablement pas être terminé dans la session, il est scindé en plusieurs versions avant le développement lourd.
|
||||
- **SESSION-005** — `0-pre.1` crée ou révise obligatoirement le plan de la version sous `docs/plans/`. Ce plan est un livrable du cadrage, pas une note optionnelle.
|
||||
- **SESSION-005** — `alpha.1` crée ou révise obligatoirement le plan de la version sous `docs/plans/`. Ce plan est un livrable du cadrage, pas une note optionnelle.
|
||||
- **SESSION-006** — Le plan de version contient au minimum l'objectif et le scope, les décisions acquises, les dépendances/risques utiles, les validations attendues, les hors-périmètre et une prévision souple des tranches jusqu'à la release stable.
|
||||
- **SESSION-007** — La prévision du plan n'est pas un calendrier figé : une tranche peut être scindée, fusionnée, déplacée ou complétée par un fix lorsque les résultats réels le justifient. Le plan actif est alors réconcilié et le delta explique le changement.
|
||||
- **SESSION-008** — `ROADMAP.md` reste macroscopique, le plan porte le découpage prévisionnel fin de la version et les deltas enregistrent ce qui a réellement été livré.
|
||||
- **SESSION-009** — Le forecast créé en `0-pre.1` rend explicitement visible une tranche de consolidation avant la candidate finale ; cette tranche couvre au minimum la réconciliation de la documentation durable, `CHANGELOG.md`, `ROADMAP.md`, l'historique applicable et le prompt de la version/session suivante, même si son numéro exact reste prévisionnel.
|
||||
- **SESSION-009** — Le forecast créé en `alpha.1` rend explicitement visible une tranche de consolidation avant la candidate finale ; cette tranche couvre au minimum la réconciliation de la documentation durable, `CHANGELOG.md`, `ROADMAP.md`, l'historique applicable et le prompt de la version/session suivante, même si son numéro exact reste prévisionnel.
|
||||
|
||||
## Taille des tranches
|
||||
|
||||
- **SESSION-010** — Une tranche `pre.N`, `alpha.N`, `beta.N`, `rc.N` ou `fix.N` vise normalement un delta correspondant à environ 15 à 30 minutes de travail effectif.
|
||||
- **SESSION-010** — Une tranche `alpha.N`, `beta.N`, `rc.N` ou `fix.N` vise normalement un delta correspondant à environ 15 à 30 minutes de travail effectif.
|
||||
- **SESSION-011** — Une tranche clairement plus lourde est scindée avant exécution.
|
||||
- **SESSION-012** — Plusieurs micro-tranches sans valeur de validation indépendante peuvent être regroupées.
|
||||
- **SESSION-013** — Le découpage suit des unités fonctionnelles complètes et validables ; une fonctionnalité ne doit pas être volontairement coupée au milieu uniquement pour respecter un numéro de prerelease.
|
||||
- **SESSION-014** — Chaque tranche livre son delta et ses validations proportionnelles avant la tranche suivante.
|
||||
- **SESSION-015** — Le plan créé en `0-pre.1` identifie explicitement le ou les rares jalons où `cargo test --workspace --all-targets --all-features` apporte une valeur globale (initial si nécessaire, préfinal/final ou changement transverse). Les autres tranches privilégient les tests `cargo test -p ...` ciblés.
|
||||
- **SESSION-015** — Le plan créé en `alpha.1` identifie explicitement le ou les rares jalons où `cargo test --workspace --all-targets --all-features` apporte une valeur globale (initial si nécessaire, préfinal/final ou changement transverse). Les autres tranches privilégient les tests `cargo test -p ...` ciblés.
|
||||
|
||||
## Une version par session
|
||||
|
||||
@@ -49,7 +49,7 @@ Ces règles imposent un découpage suffisamment petit pour qu'une version puisse
|
||||
- **PROMPT-005** — Le prompt rappelle les invariants essentiels mais renvoie aux RULES pour les détails normatifs au lieu de les recopier intégralement.
|
||||
- **PROMPT-006** — Le prompt contient suffisamment de contexte pour reprendre la version sans dépendre de la mémoire conversationnelle ni relire toute l'histoire du dépôt.
|
||||
- **PROMPT-007** — Le prompt précise la condition de fin de session et les livrables attendus.
|
||||
- **PROMPT-008** — Si `pre.1` invalide le sizing prévu par le prompt, le nouveau découpage est documenté immédiatement avant le développement lourd.
|
||||
- **PROMPT-008** — Si `alpha.1` invalide le sizing prévu par le prompt, le nouveau découpage est documenté immédiatement avant le développement lourd.
|
||||
- **PROMPT-009** — Tout nouveau prompt de version applique `docs/rules/PROMPT_STRUCTURE.md`; le présent document fixe le cycle de session tandis que `PROMPT_STRUCTURE.md` fixe le contenu opératoire à rappeler.
|
||||
|
||||
## Relation avec VERSION_WORKFLOW
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_VALIDATION_MATRIX.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Matrice normative des commandes et validations
|
||||
|
||||
@@ -15,30 +15,30 @@ Les commandes ciblées restent la norme pendant l'implémentation ; les gates wo
|
||||
|
||||
| ID | Commande / action | Dépend de | Déclencheur principal | Phase minimale typique |
|
||||
|-----------|------------------------------------------------------------------------------------|---------------------------------|----------------------------------------------------|------------------------|
|
||||
| `CMD-001` | `cargo fmt --all` | — | Rust ou dépendance Cargo modifiée | `pre` |
|
||||
| `CMD-002` | `cargo fmt --all -- --check` | `CMD-001` | Rust ou dépendance Cargo modifiée | `pre` |
|
||||
| `CMD-010` | `python3 scripts/audit_rust_workspace_rules.py` | — | Rust/Cargo/workspace/règles Rust concernés | `pre` |
|
||||
| `CMD-011` | `python3 scripts/audit_markdown_tables.py ...` | — | Markdown concerné | `pre` |
|
||||
| `CMD-012` | `python3 scripts/audit_distribution_layout.py` | — | layout/build/distribution concerné | `pre` |
|
||||
| `CMD-020` | `cargo check -p <crate>` | audits applicables | diagnostic ciblé facultatif | `pre` |
|
||||
| `CMD-021` | `cargo test -p <crate> --all-targets --all-features` | `CMD-023`, `CMD-024` | comportement/API crate | `pre` |
|
||||
| `CMD-022` | tests ciblés des crates consommatrices impactées | `CMD-021` | API publique/contrat partagé modifié | `pre` |
|
||||
| `CMD-023` | `cargo check --workspace` | `CMD-002`, audits applicables | Rust ou dépendance Cargo modifiée | `pre` |
|
||||
| `CMD-024` | `cargo clippy --workspace --all-targets --all-features -- -D warnings` | `CMD-023` | Rust ou dépendance Cargo modifiée | `pre` |
|
||||
| `CMD-001` | `cargo fmt --all` | — | Rust ou dépendance Cargo modifiée | `alpha` |
|
||||
| `CMD-002` | `cargo fmt --all -- --check` | `CMD-001` | Rust ou dépendance Cargo modifiée | `alpha` |
|
||||
| `CMD-010` | `python3 scripts/audit_rust_workspace_rules.py` | — | Rust/Cargo/workspace/règles Rust concernés | `alpha` |
|
||||
| `CMD-011` | `python3 scripts/audit_markdown_tables.py ...` | — | Markdown concerné | `alpha` |
|
||||
| `CMD-012` | `python3 scripts/audit_distribution_layout.py` | — | layout/build/distribution concerné | `alpha` |
|
||||
| `CMD-020` | `cargo check -p <crate>` | audits applicables | diagnostic ciblé facultatif | `alpha` |
|
||||
| `CMD-021` | `cargo test -p <crate> --all-targets --all-features` | `CMD-023`, `CMD-024` | comportement/API crate | `alpha` |
|
||||
| `CMD-022` | tests ciblés des crates consommatrices impactées | `CMD-021` | API publique/contrat partagé modifié | `alpha` |
|
||||
| `CMD-023` | `cargo check --workspace` | `CMD-002`, audits applicables | Rust ou dépendance Cargo modifiée | `alpha` |
|
||||
| `CMD-024` | `cargo clippy --workspace --all-targets --all-features -- -D warnings` | `CMD-023` | Rust ou dépendance Cargo modifiée | `alpha` |
|
||||
| `CMD-025` | `cargo test --workspace --all-targets --all-features` | `CMD-024` | gate rare planifiée / portée transverse incertaine | selon plan |
|
||||
| `CMD-026` | `cargo tree -p <crate> --edges normal` ou variante ciblée | — | dépendances/features modifiées ou diagnostic | selon portée |
|
||||
| `CMD-030` | build Desktop `--release` ciblé | gates Rust applicables | runner/distribution Desktop touché | beta |
|
||||
| `CMD-031` | smoke Desktop release | `CMD-030` | runtime Desktop touché | beta |
|
||||
| `CMD-040` | `(cd <tauri-app> && cargo tauri dev)` | gates Rust/frontend applicables | smoke interactif Tauri Desktop | `pre`/beta |
|
||||
| `CMD-040` | `(cd <tauri-app> && cargo tauri dev)` | gates Rust/frontend applicables | smoke interactif Tauri Desktop | alpha/beta |
|
||||
| `CMD-041` | `(cd <tauri-app> && cargo tauri build)` | gates Rust/frontend applicables | packaging Tauri Desktop final/prefinal | beta/RC |
|
||||
| `CMD-042` | build Rust `wasm32-unknown-unknown` + `wasm-bindgen --target web` | gates Rust de l'adapter | adapter WASM/Web direct touché | `pre` |
|
||||
| `CMD-043` | `(cd Web/<game> && npm install && npm run build)` | `CMD-042` si frontend avec WASM | frontend Web direct touché | `pre` |
|
||||
| `CMD-044` | smoke navigateur du host Web direct | `CMD-043` | Canvas/input/responsive Web touchés | `pre`/beta |
|
||||
| `CMD-045` | `(cd <tauri-app> && cargo tauri android init)` | environnement Android/Tauri | initialisation unique de la cible Tauri Android | `pre` |
|
||||
| `CMD-046` | `(cd <tauri-app> && cargo tauri android dev)` | gates Rust/frontend applicables | smoke interactif Tauri Android | `pre`/beta |
|
||||
| `CMD-042` | build Rust `wasm32-unknown-unknown` + `wasm-bindgen --target web` | gates Rust de l'adapter | adapter WASM/Web direct touché | `alpha` |
|
||||
| `CMD-043` | `(cd Web/<game> && npm install && npm run build)` | `CMD-042` si frontend avec WASM | frontend Web direct touché | `alpha` |
|
||||
| `CMD-044` | smoke navigateur du host Web direct | `CMD-043` | Canvas/input/responsive Web touchés | alpha/beta |
|
||||
| `CMD-045` | `(cd <tauri-app> && cargo tauri android init)` | environnement Android/Tauri | initialisation unique de la cible Tauri Android | `alpha` |
|
||||
| `CMD-046` | `(cd <tauri-app> && cargo tauri android dev)` | gates Rust/frontend applicables | smoke interactif Tauri Android | alpha/beta |
|
||||
| `CMD-047` | `(cd <tauri-app> && cargo tauri android build)` | gates Rust/frontend applicables | packaging Tauri Android final/prefinal | beta/RC |
|
||||
| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta |
|
||||
| `CMD-051` | `(cd Android && ./gradlew :<app>:assembleDebug)` | `CMD-050` si Rust natif change | Android/app/manifest/Java touché | pre/beta |
|
||||
| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | alpha/beta |
|
||||
| `CMD-051` | `(cd Android && gradle :<app>:assembleDebug)` | `CMD-050` si Rust natif change | Android/app/manifest/Java touché | alpha/beta |
|
||||
| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta |
|
||||
| `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC |
|
||||
| `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance |
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
<!-- file: docs/rules/VERSION_WORKFLOW.md -->
|
||||
<!-- version: 6 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# Versionnement, maturité et livraisons
|
||||
|
||||
## SemVer canonique
|
||||
|
||||
Le projet utilise SemVer et les labels de maturité normalisés suivants :
|
||||
À partir de `0.3.4`, le projet utilise SemVer et les labels de maturité normalisés suivants :
|
||||
|
||||
```text
|
||||
X.Y.Z-0-pre.N
|
||||
X.Y.Z-0-pre.N.fix.M
|
||||
X.Y.Z-1-alpha.N
|
||||
X.Y.Z-1-alpha.N.fix.M
|
||||
X.Y.Z-2-beta.N
|
||||
X.Y.Z-2-beta.N.fix.M
|
||||
X.Y.Z-3-rc.N
|
||||
X.Y.Z-3-rc.N.fix.M
|
||||
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
|
||||
```
|
||||
|
||||
`N` et `M` sont des entiers positifs sans zéro initial.
|
||||
|
||||
Les anciens labels `0-pre.N`, `1-alpha.N`, `2-beta.N` et `3-rc.N` appartiennent uniquement aux jalons livrés avant cette migration. Les fichiers historiques concernés restent immuables et leurs identifiants ne sont jamais normalisés rétroactivement.
|
||||
|
||||
## Sens des niveaux
|
||||
|
||||
- `0-pre.N` : construction initiale, architecture et fonctionnalités encore très mouvantes ;
|
||||
- `1-alpha.N` : périmètre fonctionnel principal établi mais encore incomplet ou instable ;
|
||||
- `2-beta.N` : fonctionnalités attendues largement présentes, priorité à la stabilisation et aux tests ;
|
||||
- `3-rc.N` : candidat de publication, aucune évolution non indispensable ;
|
||||
- `alpha.N` : construction, architecture et fonctionnalités encore susceptibles d'évoluer ; `alpha.1` porte obligatoirement le cadrage de la version ;
|
||||
- `beta.N` : fonctionnalités attendues largement présentes, priorité à la stabilisation et aux tests ;
|
||||
- `rc.N` : candidat de publication, aucune évolution non indispensable ;
|
||||
- `X.Y.Z` : version stable.
|
||||
|
||||
## Correctifs
|
||||
@@ -34,13 +33,13 @@ X.Y.Z
|
||||
Un suffixe `.fix.M` corrige la prerelease immédiatement précédente sans changer son objectif fonctionnel. Exemple :
|
||||
|
||||
```text
|
||||
0.1.0-0-pre.4
|
||||
0.1.0-0-pre.4.fix.1
|
||||
0.1.0-0-pre.4.fix.2
|
||||
0.1.0-0-pre.5
|
||||
0.3.4-alpha.2
|
||||
0.3.4-alpha.2.fix.1
|
||||
0.3.4-alpha.2.fix.2
|
||||
0.3.4-alpha.3
|
||||
```
|
||||
|
||||
Après une version stable, un correctif produit normalement un nouveau patch SemVer, par exemple `0.1.1`, et non `0.1.0.fix.1`.
|
||||
Après une version stable, un correctif produit normalement un nouveau patch SemVer, par exemple `0.3.5`, et non `0.3.4.fix.1`.
|
||||
|
||||
## Version workspace et versions autonomes
|
||||
|
||||
@@ -64,19 +63,21 @@ Les documents sont rangés sous :
|
||||
deltas/X.Y.Z/<prerelease-or-rel>.md
|
||||
```
|
||||
|
||||
Exemples :
|
||||
Exemples canoniques pour les nouvelles versions :
|
||||
|
||||
```text
|
||||
deltas/0.1.0/0-pre.1.md
|
||||
deltas/0.1.0/0-pre.1.fix.1.md
|
||||
deltas/0.1.0/1-alpha.1.md
|
||||
deltas/0.1.0/3-rc.2.md
|
||||
deltas/0.1.0/rel.md
|
||||
deltas/0.3.4/alpha.1.md
|
||||
deltas/0.3.4/alpha.1.fix.1.md
|
||||
deltas/0.3.4/beta.1.md
|
||||
deltas/0.3.4/rc.1.md
|
||||
deltas/0.3.4/rel.001.md
|
||||
```
|
||||
|
||||
Les anciens chemins déjà livrés, par exemple `deltas/0.3.3/0-pre.1.md` ou `deltas/0.3.3/3-rc.1.md`, restent des preuves historiques valides et immuables.
|
||||
|
||||
## Versions principalement documentaires
|
||||
|
||||
Une version de conception suit le même SemVer que les autres versions et peut utiliser plusieurs `0-pre.N` pour permettre une revue humaine progressive.
|
||||
Une version de conception suit le même SemVer que les autres versions et peut utiliser plusieurs `alpha.N` pour permettre une revue humaine progressive.
|
||||
|
||||
Les audits Markdown et de règles valident la cohérence mécanique mais ne valent jamais acceptation du fond documentaire. Une prerelease documentaire reste candidate jusqu'à revue explicite de son contenu.
|
||||
|
||||
@@ -93,7 +94,7 @@ La promotion `rc` puis stable d'une version de conception exige une validation h
|
||||
|
||||
- **VER-RC-001** — Une RC est fonctionnellement gelée. Les nouvelles fonctionnalités, nouvelles capabilities, refactors architecturaux non indispensables et changements volontaires de comportement sont interdits.
|
||||
- **VER-RC-002** — Les modifications de code restent autorisées en RC lorsqu'elles corrigent un bug, un test erroné, un défaut de packaging, un problème de sécurité, une incompatibilité de release ou un défaut strictement nécessaire à la publication.
|
||||
- **VER-RC-003** — Un correctif conforme à `VER-RC-002` produit `3-rc.N.fix.M` et n'impose pas un retour automatique en beta.
|
||||
- **VER-RC-003** — Un correctif conforme à `VER-RC-002` produit `rc.N.fix.M` et n'impose pas un retour automatique en beta.
|
||||
- **VER-RC-004** — Si le périmètre fonctionnel est rouvert pendant une RC, la candidate est abandonnée et le développement revient à une phase adaptée, normalement beta, avant une nouvelle RC.
|
||||
|
||||
## Prompt de la version suivante
|
||||
@@ -106,7 +107,7 @@ La promotion `rc` puis stable d'une version de conception exige une validation h
|
||||
## Correctifs strictement documentaires
|
||||
|
||||
- **VER-DOCFIX-001** — Un `.fix.N` limité à la documentation, aux prompts, aux deltas, à `history/` ou aux règles non consommées par le build/runtime ne modifie aucune version technique : ni `workspace.package.version`/`Cargo.toml`, ni `package.json`, ni Gradle/Android, ni `tauri.conf.json`, ni autre métadonnée de version consommée par un build ou une distribution. L'identité du correctif est portée uniquement par le delta et son archive.
|
||||
- **VER-DOCFIX-002** — Une prerelease non-fix (`pre.N`, `alpha.N`, `beta.N`, `rc.N`) synchronise sa version technique selon le workflow de phase même lorsque son contenu est principalement documentaire.
|
||||
- **VER-DOCFIX-002** — Une prerelease non-fix (`alpha.N`, `beta.N`, `rc.N`) synchronise sa version technique selon le workflow de phase même lorsque son contenu est principalement documentaire.
|
||||
- **VER-DOCFIX-003** — Dès qu'un correctif touche du code, une configuration exécutable, un manifeste consommé par le build/runtime ou un artefact distribué, la version technique suit l'identifiant `.fix.N`.
|
||||
|
||||
## Phases de développement
|
||||
@@ -118,11 +119,11 @@ PLAN → IMPLEMENT → INTEGRATE → VALIDATE → CANDIDATE → RELEASE
|
||||
```
|
||||
|
||||
- **VER-PHASE-001** — Ces phases structurent le travail mais n'imposent pas une prerelease distincte pour chacune.
|
||||
- **VER-PHASE-002** — `0-pre.1` est obligatoirement la tranche de cadrage de la version : audit de la base, brainstorming/recherche de requirements, sizing, planification, dépendances, validations attendues et création/révision du plan actif sous `docs/plans/` avec son découpage prévisionnel souple.
|
||||
- **VER-PHASE-003** — `0-pre.1` peut aussi contenir une première implémentation strictement bornée si le cadrage montre qu'elle tient naturellement dans la même tranche, mais le cadrage ne doit jamais être sauté.
|
||||
- **VER-PHASE-002** — `alpha.1` est obligatoirement la tranche de cadrage de la version : audit de la base, brainstorming/recherche de requirements, sizing, planification, dépendances, validations attendues et création/révision du plan actif sous `docs/plans/` avec son découpage prévisionnel souple.
|
||||
- **VER-PHASE-003** — `alpha.1` peut aussi contenir une première implémentation strictement bornée si le cadrage montre qu'elle tient naturellement dans la même tranche, mais le cadrage ne doit jamais être sauté.
|
||||
- **VER-PHASE-004** — Une version doit être dimensionnée pour que l'ensemble de son développement puisse être terminé dans une seule session de travail. Si ce n'est pas réaliste, son objectif est découpé en plusieurs versions/sessions avant le développement lourd.
|
||||
- **VER-PHASE-005** — Le plan établi en `0-pre.1` est vivant : il peut regrouper, scinder, reporter ou reclasser des tranches lorsque l'information réelle le justifie, sans réécrire les deltas déjà livrés. Il reste la référence de suivi prévisionnel de la version jusqu'à sa clôture.
|
||||
- **VER-PHASE-006** — Une tranche `pre.N`, `alpha.N`, `beta.N`, `rc.N` ou leur fix vise normalement un delta réalisable en environ 15 à 30 minutes. Une tranche sensiblement plus lourde est découpée avant exécution ; une tranche trop petite peut être regroupée avec une tranche adjacente cohérente.
|
||||
- **VER-PHASE-005** — Le plan établi en `alpha.1` est vivant : il peut regrouper, scinder, reporter ou reclasser des tranches lorsque l'information réelle le justifie, sans réécrire les deltas déjà livrés. Il reste la référence de suivi prévisionnel de la version jusqu'à sa clôture.
|
||||
- **VER-PHASE-006** — Une tranche `alpha.N`, `beta.N`, `rc.N` ou leur fix vise normalement un delta réalisable en environ 15 à 30 minutes. Une tranche sensiblement plus lourde est découpée avant exécution ; une tranche trop petite peut être regroupée avec une tranche adjacente cohérente.
|
||||
- **VER-PHASE-007** — Le découpage privilégie des unités fonctionnelles complètes et validables, pas des coupures arbitraires au milieu d'une fonctionnalité.
|
||||
- **VER-PHASE-008** — Chaque tranche ferme son propre scope, produit son delta et ses validations proportionnelles avant l'ouverture de la tranche suivante.
|
||||
- **VER-PHASE-009** — Alpha, beta et RC sont utilisées proportionnellement au risque et à la maturité ; elles ne sont pas créées uniquement pour satisfaire une séquence cérémonielle.
|
||||
@@ -133,7 +134,7 @@ PLAN → IMPLEMENT → INTEGRATE → VALIDATE → CANDIDATE → RELEASE
|
||||
|
||||
- **VER-TAURI-001** — Pour une app Tauri Rust du workspace, la version produit canonique est la version Cargo.
|
||||
- **VER-TAURI-002** — `tauri.conf.json` omet `version` lorsque Tauri peut hériter de la version `Cargo.toml`.
|
||||
- **VER-TAURI-003** — La `version` de `package.json` décrit le package frontend local et n'est pas synchronisée à chaque `pre.N` ou `.fix.N`.
|
||||
- **VER-TAURI-003** — La `version` de `package.json` décrit le package frontend local et n'est pas synchronisée à chaque prerelease ou `.fix.N`.
|
||||
- **VER-TAURI-004** — Tant que le frontend n'est pas publié comme package npm, sa version est mise à jour uniquement aux jalons significatifs retenus par le projet, au minimum lorsque cela est nécessaire pour alpha, beta, RC ou stable.
|
||||
|
||||
## Corrections des deltas déjà livrés
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/studies/000-README.md -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 12 -->
|
||||
|
||||
# Études
|
||||
|
||||
@@ -61,3 +61,11 @@ Ces documents étudient les POC à réaliser ; ils ne lancent encore aucune impl
|
||||
## Étude de cadrage 0.3.1
|
||||
|
||||
- [`024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md`](024-V0_3_1_TAURI_ANDROID_SNAKE_AUDIT.md) — audit de la baseline `0.3.0`, réutilisation Web/WASM, référence Tauri existante, environnement Android et sizing du second host Snake.
|
||||
|
||||
## Étude de cadrage 0.3.3
|
||||
|
||||
- [`025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md`](025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md) — audit de la baseline `0.3.1`, toolchain Android native actuelle, choix ABI/API, packaging APK/AAB et contraintes 16 KB.
|
||||
|
||||
## Étude de cadrage 0.3.5
|
||||
|
||||
- [`026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md`](026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md) — audit de la stable `0.3.4`, comparaison des stacks WebTransport/QUIC actuelles, compatibilité du contrat realtime, TLS, navigateurs et sizing de `0.3.5`.
|
||||
|
||||
277
docs/studies/025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md
Normal file
277
docs/studies/025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md
Normal file
@@ -0,0 +1,277 @@
|
||||
<!-- file: docs/studies/025-V0_3_3_ANDROID_NATIVE_MULTI_ABI_AUDIT.md -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# Audit Android SDL3 natif multi-ABI pour 0.3.3
|
||||
|
||||
## Statut et objet
|
||||
|
||||
Cette étude cadre `0.3.3-0-pre.1` à partir de l'archive taggée `v0.3.1`. Elle reste non normative : les décisions opérationnelles retenues pour la version sont portées par `docs/plans/003-V0_3_3_ANDROID_NATIVE_MULTI_ABI_PLAN.md` et deviendront des responsabilités durables dans les documents Android concernés au moment où l'implémentation les rendra réelles.
|
||||
|
||||
L'objectif est de remplacer le chemin historique :
|
||||
|
||||
```text
|
||||
scripts/build_android_rust.py -> cargo ndk -> src/main/jniLibs -> Gradle
|
||||
```
|
||||
|
||||
par un chemin possédé par le build Android :
|
||||
|
||||
```text
|
||||
Gradle -> tâches Rust/cargo-ndk -> jniLibs générés -> APK/AAB
|
||||
```
|
||||
|
||||
sans réintroduire un orchestrateur ad hoc externe.
|
||||
|
||||
## Base auditée
|
||||
|
||||
L'archive fournie correspond à la stable `0.3.1` et déclare `workspace.package.version = "0.3.1"`.
|
||||
|
||||
Avant modification, les contrôles statiques exécutables dans l'environnement de génération donnent :
|
||||
|
||||
```text
|
||||
unzip -t : no errors
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 222 file(s))
|
||||
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
L'archive contient 378 fichiers, aucun lien symbolique, aucun fichier vide, aucun `.git`, `target/`, `node_modules/` ou `gen/android/` généré.
|
||||
|
||||
La sortie locale fournie par l'utilisateur annonce 230 fichiers Markdown avant son build Tauri, contre 222 dans le ZIP taggé. La baseline taggée reste autoritaire. L'écart est compatible avec la présence locale d'un arbre Tauri `gen/android/` déjà initialisé, que l'archive de tag n'embarque pas ; il ne constitue pas à lui seul une divergence de source tant que les audits restent propres.
|
||||
|
||||
## Règles et frontières vérifiées
|
||||
|
||||
La revue de `RULES.md` et des règles référencées par le prompt confirme notamment :
|
||||
|
||||
- `0-pre.1` doit rester une tranche de cadrage, sizing, recherche, risques, validations et planification ;
|
||||
- une prerelease non-fix synchronise la version technique même si elle est principalement documentaire ;
|
||||
- les builds, tests et smokes finaux restent côté utilisateur ; le générateur peut exécuter les audits statiques ;
|
||||
- tout chemin de build nouveau ou réactivé en `0.3.x` ne doit pas être piloté par Python ;
|
||||
- les commandes Android ciblées sont préférées et tout changement de répertoire doit rester dans un sous-shell ;
|
||||
- les sorties générées ne doivent pas devenir des sources versionnées ;
|
||||
- les tests workspace complets restent des gates rares et planifiées ;
|
||||
- l'historique d'une tranche n'est ajouté que par la tranche suivante après validation réelle.
|
||||
|
||||
Aucune règle ne justifie de réécrire Gradle dans `0-pre.1`. La tranche doit donc produire les décisions et le plan, puis laisser l'implémentation à `0-pre.2` et suivantes.
|
||||
|
||||
## Pipeline Android natif hérité
|
||||
|
||||
Le projet Android est un multi-module Gradle séparé du workspace Cargo avec :
|
||||
|
||||
```text
|
||||
Android/common
|
||||
Android/game-reflex-poc
|
||||
Android/game-snake-poc
|
||||
```
|
||||
|
||||
La configuration source déclare actuellement :
|
||||
|
||||
```text
|
||||
AGP 9.4.0
|
||||
compileSdk 36
|
||||
targetSdk 36
|
||||
minSdk 21
|
||||
JDK source 17
|
||||
NDK 28.2.13676358 (r28c)
|
||||
SDL3 AAR SDL3-3.4.16.aar
|
||||
```
|
||||
|
||||
Le ZIP ne vend pas l'AAR SDL3, conformément au contrat `Android/libs/README.md`. Il vérifie donc le nom/version attendu, pas les octets de l'AAR présent sur une machine de développement.
|
||||
|
||||
L'audit relève aussi que `Android/README.md` portait encore un paragraphe de toolchain daté de `0.1.0-0-pre.9`, contradictoire avec le pipeline natif réellement stabilisé ensuite. `0-pre.1` corrige cette description sans toucher au build.
|
||||
|
||||
Le projet Android natif ne contient pas encore de Gradle Wrapper. `Android/README.md` attend Gradle 9.6.0 globalement. Comme la reproductibilité devient une responsabilité centrale de `0.3.3`, l'introduction d'un wrapper Gradle épinglé doit être traitée avec le premier vrai changement de pipeline, pas dans le cadrage documentaire.
|
||||
|
||||
## Responsabilités du script historique
|
||||
|
||||
`scripts/build_android_rust.py` remplit aujourd'hui plusieurs responsabilités qu'il faut transposer explicitement au build Gradle plutôt que supprimer aveuglément :
|
||||
|
||||
1. associer un module Android à exactement une feature jeu Rust ;
|
||||
2. lire le nom de l'AAR SDL3 et la version NDK depuis `Android/gradle.properties` ;
|
||||
3. extraire `libSDL3.so` de l'AAR Prefab pour chaque ABI afin de fournir la bibliothèque de liaison à Rust ;
|
||||
4. rendre `libSDL3.so` disponible au runtime Android ;
|
||||
5. résoudre le NDK side-by-side exact et transmettre son chemin uniquement au sous-processus natif ;
|
||||
6. fixer le niveau Android de `cargo-ndk` à 21 ;
|
||||
7. invoquer `cargo ndk` avec exactement une feature `reflex` ou `snake` ;
|
||||
8. vérifier que `libgame_android_entrypoint.so` a réellement été produit.
|
||||
|
||||
Le script écrit actuellement des bibliothèques sous `Android/<app>/src/main/jniLibs/<abi>/`. Ces fichiers sont ignorés mais restent des artefacts de build dans un répertoire source. La cible `0.3.3` doit plutôt enregistrer un répertoire de `jniLibs` généré sous `Android/<app>/build/generated/...`, sur le même principe que le staging d'assets déjà piloté par la Variant API AGP.
|
||||
|
||||
## Toolchain actuelle vérifiée
|
||||
|
||||
La documentation Android officielle d'AGP 9.4.0, mise à jour le 18 septembre 2026, fixe :
|
||||
|
||||
```text
|
||||
Gradle minimum/default 9.6.0
|
||||
JDK minimum/default 17
|
||||
Build Tools default 36.0.0
|
||||
NDK default 28.2.13676358
|
||||
API maximale AGP 37
|
||||
```
|
||||
|
||||
Source officielle : <https://developer.android.com/build/releases/agp-9-4-0-release-notes>.
|
||||
|
||||
La baseline `AGP 9.4.0 + Gradle 9.6.0 + JDK 17 + NDK r28c` est donc cohérente et ne nécessite pas de montée de version pour lancer `0.3.3`.
|
||||
|
||||
La sortie utilisateur fournie pour le POC Tauri Android montre un NDK local `30.0.14904198`. Cette valeur appartient au pipeline Tauri généré et ne remplace pas la version NDK native explicitement épinglée à `28.2.13676358` dans `Android/gradle.properties`.
|
||||
|
||||
## SDL3 et plancher Android
|
||||
|
||||
La documentation SDL Android actuelle demande :
|
||||
|
||||
```text
|
||||
Android SDK 35 ou ultérieur
|
||||
Android NDK r28c ou ultérieur
|
||||
API minimale 21 / Android 5.0
|
||||
```
|
||||
|
||||
Source officielle : <https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md>.
|
||||
|
||||
SDL `3.4.16` est la release stable publiée le 2 septembre 2026 et l'asset Android officiel `SDL3-devel-3.4.16-android.zip` est disponible dans la release.
|
||||
|
||||
Source officielle : <https://github.com/libsdl-org/SDL/releases/tag/release-3.4.16>.
|
||||
|
||||
Le `minSdk 21` historique n'est donc pas retenu par simple inertie : il correspond au plancher actuel documenté de SDL3. Descendre sous API 21 exigerait de changer une dépendance structurante ou de maintenir un fork, ce qui est hors scope. Le candidat `minSdk` de `0.3.3` est donc `21`, sous réserve d'un smoke sur API 21 ou sur la plus ancienne image raisonnablement disponible avec écart explicitement documenté.
|
||||
|
||||
## Target SDK et distribution
|
||||
|
||||
Google Play exige depuis le 31 août 2026 qu'une nouvelle application ou mise à jour téléphone/tablette cible Android 16, API 36, ou plus récent.
|
||||
|
||||
Source officielle : <https://developer.android.com/google/play/requirements/target-sdk>.
|
||||
|
||||
Le `targetSdk 36` de la baseline reste donc approprié pour `0.3.3`. Il ne doit pas être confondu avec `minSdk 21` : l'un décrit la cible comportementale/politique de distribution, l'autre le plancher d'installation.
|
||||
|
||||
## Pages mémoire 16 KB
|
||||
|
||||
Le projet package du code natif Rust et SDL3. Les appareils Android 15+ peuvent utiliser des pages mémoire de 16 KB et Google Play impose la compatibilité 16 KB aux applications ciblant API 35+ sur appareils 64 bits.
|
||||
|
||||
La documentation Android précise qu'AGP 8.5.1+ et NDK r28+ fournissent le chemin standard de packaging/alignement 16 KB, NDK r28 alignant les bibliothèques produites sur 16 KB par défaut. Les bibliothèques précompilées restent néanmoins à vérifier, donc `libSDL3.so` doit faire partie du contrôle de l'artefact final.
|
||||
|
||||
Source officielle : <https://developer.android.com/guide/practices/page-sizes>.
|
||||
|
||||
Cette contrainte renforce le maintien de NDK r28c. La validation AAB doit inclure un contrôle d'alignement/compatibilité des bibliothèques natives, et un smoke 16 KB peut être ajouté si une image d'émulateur adéquate est raisonnablement disponible.
|
||||
|
||||
## ABI retenues pour le chemin par défaut lors du cadrage initial
|
||||
|
||||
Les deux ABI de base proposées sont :
|
||||
|
||||
```text
|
||||
arm64-v8a
|
||||
x86_64
|
||||
```
|
||||
|
||||
Raisons :
|
||||
|
||||
- `arm64-v8a` couvre la cible productive minimale sur appareil Android moderne ;
|
||||
- `x86_64` conserve un chemin AVD local rapide et déjà exercé par le POC Tauri ;
|
||||
- les deux sont des ABI 64 bits et répondent au besoin de supporter les architectures 64 bits pour une application native ;
|
||||
- Rust fournit les targets `aarch64-linux-android` et `x86_64-linux-android` ;
|
||||
- le build Tauri fourni par l'utilisateur vient de réussir pour ces deux targets, ce qui confirme au moins la présence récente de ces cibles Rust sur sa machine.
|
||||
|
||||
`armeabi-v7a` est différée par défaut. Le 32 bits n'abaisse pas le plancher OS puisque SDL3 reste à API 21, augmente la matrice de build/test et la taille d'un APK universal, et aucun besoin produit concret n'est actuellement démontré. Elle pourra être rouverte à partir de données d'appareils/utilisateurs ou d'un besoin de distribution mesuré.
|
||||
|
||||
`x86` 32 bits n'est pas retenue dans la matrice de base.
|
||||
|
||||
### Révision décidée après validation de 0-pre.3
|
||||
|
||||
Après la preuve `arm64-v8a + x86_64`, la politique produit a été explicitement élargie : le projet doit tenter de supporter le maximum d'architectures Android encore prises en charge par la toolchain moderne, y compris anciennes. La matrice cible devient donc :
|
||||
|
||||
```text
|
||||
arm64-v8a
|
||||
armeabi-v7a
|
||||
x86_64
|
||||
x86
|
||||
```
|
||||
|
||||
Cette révision ne prétend pas réintroduire `armeabi`, `mips` ou `mips64`, retirées des toolchains Android modernes. Elle augmente la couverture CPU sans changer le plancher OS SDL3, qui reste API 21. La décision normative actualisée est portée par le plan `0.3.3` et les documents Android durables.
|
||||
|
||||
## APK universal, AAB et splits
|
||||
|
||||
Après la révision de scope, la cible locale est un APK Debug universal contenant les quatre ABI retenues :
|
||||
|
||||
```text
|
||||
lib/arm64-v8a/libSDL3.so
|
||||
lib/arm64-v8a/libgame_android_entrypoint.so
|
||||
lib/armeabi-v7a/libSDL3.so
|
||||
lib/armeabi-v7a/libgame_android_entrypoint.so
|
||||
lib/x86_64/libSDL3.so
|
||||
lib/x86_64/libgame_android_entrypoint.so
|
||||
lib/x86/libSDL3.so
|
||||
lib/x86/libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
Aucun split ABI Gradle ne doit être activé par défaut pour cet artefact : un seul APK contient les quatre variantes CPU et doit pouvoir être installé sur chaque architecture compatible. Les smokes effectifs restent limités aux appareils/AVD disponibles.
|
||||
|
||||
Pour la distribution, la cible est un AAB. Le format App Bundle laisse au store la génération des APK optimisés par configuration, notamment par ABI ; un AAB n'est pas lui-même un APK installable.
|
||||
|
||||
Source officielle : <https://developer.android.com/guide/app-bundle>.
|
||||
|
||||
Les APK split ABI restent une option de diagnostic/distribution directe future, pas un artefact par défaut de `0.3.3`.
|
||||
|
||||
## Architecture Gradle/Cargo recommandée
|
||||
|
||||
La solution retenue pour l'implémentation est une logique Gradle commune sous `Android/gradle/`, consommée par les applications natives. Elle doit :
|
||||
|
||||
- déclarer les ABI retenues dans une propriété/configuration centrale explicite ;
|
||||
- enregistrer des tâches par variante et, si nécessaire, par ABI ;
|
||||
- déclarer des inputs/outputs Gradle afin de préserver un rebuild incrémental raisonnable ;
|
||||
- extraire la bibliothèque SDL3 native de l'AAR vers un répertoire de build pour le linkage Rust sans modifier l'AAR source ;
|
||||
- invoquer `cargo ndk` avec le NDK r28c configuré et `CARGO_NDK_PLATFORM=21` ;
|
||||
- sélectionner exactement une feature jeu selon le module consommateur ;
|
||||
- écrire les bibliothèques natives dans un répertoire `build/generated/...` ;
|
||||
- enregistrer ce répertoire comme `jniLibs` généré de la variante ;
|
||||
- vérifier explicitement les deux `.so` attendues par ABI avant packaging ;
|
||||
- échouer avec un diagnostic déterministe si AAR, NDK, cargo-ndk, target Rust ou output manque.
|
||||
|
||||
Cette logique n'est pas un nouveau script externe : elle fait partie du build Android qui possède l'artefact final.
|
||||
|
||||
## Snake et Reflex
|
||||
|
||||
Snake reste le jeu-sonde initial parce que le prompt le demande et que le POC Tauri a déjà prouvé ARM64 + x86_64 pour ce jeu.
|
||||
|
||||
Reflex ne doit pas provoquer une seconde architecture. Une fois la factorisation Gradle commune prouvée avec Snake, Reflex peut devenir le second consommateur dans la même version si l'opération se limite essentiellement au mapping `module -> feature`. Si des besoins spécifiques apparaissent, ils doivent être évalués avant d'élargir le scope.
|
||||
|
||||
## Risques identifiés
|
||||
|
||||
Les principaux risques à fermer pendant les tranches d'implémentation sont :
|
||||
|
||||
- absence de Gradle Wrapper dans le projet Android natif ;
|
||||
- AAR SDL3 absent du ZIP par conception et donc à inventorier sur la machine de validation ;
|
||||
- `cargo-ndk` présent mais version non encore relevée dans la sortie fraîche fournie ;
|
||||
- extraction Prefab SDL3 différente de l'hypothèse historique ;
|
||||
- dépendances Gradle mal ordonnées entre build Rust et merge/package JNI ;
|
||||
- cache Gradle incorrect si les inputs Rust/assets/toolchain sont sous-déclarés ;
|
||||
- production accidentelle de deux features jeu dans le même `cdylib` ;
|
||||
- compatibilité 16 KB d'une bibliothèque précompilée SDL3 non vérifiée dans l'artefact final ;
|
||||
- confusion entre NDK 30 du POC Tauri et NDK r28c du pipeline Android natif ;
|
||||
- impossibilité pratique de créer un AVD API 21 récent, auquel cas l'écart de smoke doit rester explicite.
|
||||
|
||||
## Inventaire environnement encore à relever côté utilisateur
|
||||
|
||||
La sortie locale fournie confirme déjà :
|
||||
|
||||
- `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` pour le build présenté ;
|
||||
- un build Tauri Debug universal réussi pour `aarch64` et `x86_64` ;
|
||||
- un NDK Tauri local `30.0.14904198` ;
|
||||
- un `cargo check --workspace` propre sur la stable `0.3.1`.
|
||||
|
||||
L'historique validé de `0.3.1` indique Rust/Cargo `1.94.1`, les targets Android ARM64/x86_64, ADB `37.0.1`, un appareil ARM64 réel et plusieurs AVD. Ces valeurs sont utiles comme transmission, mais ne remplacent pas l'inventaire frais demandé par le prompt `004`.
|
||||
|
||||
Avant d'ouvrir l'implémentation `0-pre.2`, la gate utilisateur doit donc relever explicitement `rustc`, `cargo`, `cargo ndk`, les targets Rust, Java, `JAVA_HOME`, `ANDROID_HOME`, ADB, appareils, AVD, Gradle natif, NDK r28c installé et AAR SDL3 local.
|
||||
|
||||
## Conclusion de cadrage
|
||||
|
||||
Le scope tient dans `0.3.3` si chaque tranche reste centrée sur un résultat vertical : ownership Gradle mono-ABI, puis multi-ABI/universal, puis AAB/compatibilité et consolidation. Aucun besoin découvert n'impose aujourd'hui de créer une version supplémentaire avant de commencer l'implémentation.
|
||||
|
||||
## Révision après validation de la matrice quatre ABI
|
||||
|
||||
La gate `0-pre.4` du 2026-09-21 a confirmé que les quatre targets Rust Android sont installées et que Snake comme Reflex compilent avec succès `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`. Les deux APK Debug universal contiennent les huit bibliothèques attendues.
|
||||
|
||||
La fermeture `0-pre.5` a retiré le builder Python historique sans perdre de responsabilité fonctionnelle. Les deux `bundleRelease` ont réussi et les AAB Snake/Reflex contiennent les deux bibliothèques natives pour `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
|
||||
|
||||
La validation 16 KB a affiné le critère initial : l'exigence Google Play vise les appareils 64 bits. Les couples SDL3/Rust `arm64-v8a` et `x86_64`, en Debug comme en Release, ont des segments ELF `LOAD` alignés à `2**14`, et `zipalign -P 16` réussit sur les APK. Les variantes 32 bits `armeabi-v7a` et `x86` restent à `2**12`, ce qui est compatible avec leur rôle d'ABI historiques 32 bits et ne constitue pas un échec de la gate 16 KB 64 bits.
|
||||
|
||||
Un AVD Android 15/API 35 x86_64 basé sur l'image `google_apis_playstore_ps16k` a retourné `16384` via `getconf PAGE_SIZE`; l'APK Snake s'y installe, `SnakeActivity` démarre et le processus reste vivant. Une lecture ponctuelle `KernelPageSize: 4 kB` dans `/proc/self/smaps` n'est pas retenue comme mesure du mode de page global : la gate Android utilise `getconf PAGE_SIZE`.
|
||||
|
||||
Le plancher `minSdk 21` est désormais prouvé en runtime : un AVD Android 5.0/API 21 x86 a accepté l'APK universal et lancé `SnakeActivity`. La matrice dispose donc à la fois d'une preuve ancienne x86 32 bits et d'une preuve récente x86_64 16 KB.
|
||||
300
docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md
Normal file
300
docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md
Normal file
@@ -0,0 +1,300 @@
|
||||
<!-- file: docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Audit WebTransport/QUIC pour 0.3.5
|
||||
|
||||
## Statut et objet
|
||||
|
||||
Cette étude cadre `0.3.5-alpha.1` à partir de l'archive taggée `v0.3.4`. Elle reste non normative : les décisions opérationnelles retenues pour la version sont portées par `docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md`.
|
||||
|
||||
L'objectif est de déterminer, sur l'état réel de l'écosystème au 2026-09-21, quelle pile WebTransport/QUIC mérite un POC, comment elle se confronte au contrat `game-realtime-transport-lib` livré en `0.3.4`, quelles preuves navigateur/TLS sont réalistes et où doit vivre le fallback WebSocket.
|
||||
|
||||
## Base auditée
|
||||
|
||||
L'archive fournie est annoncée comme le téléchargement ZIP du tag Gitea `v0.3.4` et déclare :
|
||||
|
||||
```text
|
||||
workspace.package.version = 0.3.4
|
||||
17 membres workspace
|
||||
```
|
||||
|
||||
Avant modification, les contrôles statiques exécutables dans l'environnement de génération donnent :
|
||||
|
||||
```text
|
||||
unzip -t : no errors
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 263 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
L'inventaire indépendant confirme :
|
||||
|
||||
```text
|
||||
437 fichiers dans le ZIP
|
||||
267 fichiers Markdown extraits
|
||||
68 fichiers Rust
|
||||
18 Cargo.toml, dont le manifest racine
|
||||
17 membres workspace
|
||||
0 symlink
|
||||
0 chemin absolu ou traversal détecté
|
||||
0 target/, node_modules/, build/ ou gen/android/ généré
|
||||
```
|
||||
|
||||
La sortie utilisateur fournie à l'ouverture de session confirme également `cargo fmt --all -- --check`, les trois audits et `cargo check --workspace` propres sur son checkout `0.3.4`. Son audit Markdown annonce `279` fichiers contre `263` dans le périmètre scanné du ZIP fourni. La baseline taggée reçue reste l'autorité de génération conformément à `CMD-GIT-003` et `CMD-GIT-004`; l'écart est enregistré sans inventer les fichiers locaux absents de l'archive.
|
||||
|
||||
## État hérité de 0.3.4
|
||||
|
||||
Les preuves de `history/0.3.4/` et `deltas/0.3.4/rel.001.md` confirment :
|
||||
|
||||
- `game-realtime-transport-lib` sans Tokio, WebSocket, QUIC, HTTP ni TLS ;
|
||||
- `TransportMessage` binaire opaque et contrat message-oriented ;
|
||||
- split `RealtimeConnection -> Sender + Receiver` ;
|
||||
- fermeture distante propre distincte d'une erreur ;
|
||||
- erreurs transport-neutral ;
|
||||
- backend `game-realtime-websocket-lib` Tokio/tokio-tungstenite ;
|
||||
- tests loopback et robustesse ;
|
||||
- smoke runtime `game-realtime-websocket-smoke` hors harness `#[test]` ;
|
||||
- `beta.1` validée avec 53 tests workspace ;
|
||||
- `rc.1` validée avec 18 tests realtime ciblés, smoke `PASS` et arbre inverse où seul le launcher de smoke consomme le backend WebSocket.
|
||||
|
||||
Cette frontière constitue la baseline de comparaison. `0.3.5` n'a pas besoin de la redessiner avant d'avoir une preuve WebTransport concrète.
|
||||
|
||||
## État du protocole au 2026-09-21
|
||||
|
||||
WebTransport côté navigateur est désormais classé « Baseline 2026 » par MDN : l'API fonctionne sur les versions récentes des principaux navigateurs depuis mars 2026, avec la réserve habituelle sur les versions anciennes et certaines sous-capacités. L'API exige un contexte sécurisé et expose streams bidirectionnels/unidirectionnels fiables ainsi que datagrams non fiables.
|
||||
|
||||
Sources :
|
||||
|
||||
- <https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API>
|
||||
- <https://developer.mozilla.org/en-US/docs/Web/API/WebTransport>
|
||||
|
||||
Le binding WebTransport over HTTP/3 n'est cependant pas encore un RFC final. `draft-ietf-webtrans-http3-16`, daté du 2026-07-06, est toujours un Internet-Draft en WG Last Call avec statut visé Proposed Standard.
|
||||
|
||||
Source : <https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/>
|
||||
|
||||
Conséquence pour le POC : l'interopérabilité navigateur est suffisamment réelle pour être testée, mais la version ne doit pas présenter le protocole ni une crate comme une dépendance produit définitivement stabilisée.
|
||||
|
||||
## Candidats Rust actuels
|
||||
|
||||
### Famille `web-transport`
|
||||
|
||||
État observé :
|
||||
|
||||
```text
|
||||
web-transport 0.12.0 (2026-08-20)
|
||||
web-transport-quinn 0.12.1 (2026-08-20)
|
||||
web-transport-wasm 0.6.0 (2026-08)
|
||||
```
|
||||
|
||||
`web-transport` fournit une API générique qui sélectionne :
|
||||
|
||||
```text
|
||||
native -> web-transport-quinn
|
||||
wasm32 -> web-transport-wasm
|
||||
```
|
||||
|
||||
La crate native s'appuie sur Quinn, Rustls, Tokio et expose streams + datagrams. La crate WASM enveloppe l'API WebTransport du navigateur. Le projet amont est sous licence `MIT OR Apache-2.0`.
|
||||
|
||||
Sources :
|
||||
|
||||
- <https://docs.rs/crate/web-transport/latest>
|
||||
- <https://docs.rs/crate/web-transport-quinn/latest>
|
||||
- <https://docs.rs/crate/web-transport-wasm/latest>
|
||||
- <https://github.com/moq-dev/web-transport>
|
||||
|
||||
Point particulièrement pertinent pour `games.sasedev` : la documentation `web-transport` explique explicitement le problème `Send` entre natif et WASM et contourne ce problème par sélection de l'implémentation selon la cible. Cela rejoint la décision prise en `0.3.4` de ne pas imposer `Send` aux futures du contrat transport-neutral.
|
||||
|
||||
La voie WASM impose actuellement :
|
||||
|
||||
```text
|
||||
--cfg=web_sys_unstable_apis
|
||||
```
|
||||
|
||||
car les bindings WebTransport de `web-sys` restent derrière ce cfg. Ce flag doit être fourni par le build final et ne peut pas être activé par une dépendance. Une éventuelle modification `.cargo/config.toml` devra donc être ciblée sur `wasm32-unknown-unknown`, pas appliquée globalement au workspace.
|
||||
|
||||
### `wtransport`
|
||||
|
||||
État observé :
|
||||
|
||||
```text
|
||||
wtransport 0.7.2 (2026-08-11)
|
||||
```
|
||||
|
||||
La crate fournit une implémentation WebTransport/HTTP3 pure Rust, client et serveur natifs, fondée notamment sur Quinn, Rustls et Tokio. Sa documentation est plus riche et elle fournit des helpers explicites pour certificat self-signed, hash SHA-256 et contraintes W3C. La branche `0.7.x` annonce un MSRV au moins Rust 1.88 dans la metadata consultée de `0.7.1`; la compilation exacte de `0.7.2` reste à prouver sur le toolchain du projet.
|
||||
|
||||
Sources :
|
||||
|
||||
- <https://docs.rs/crate/wtransport/latest>
|
||||
- <https://docs.rs/wtransport/latest/wtransport/>
|
||||
|
||||
Sa limite pour ce projet est l'absence d'une façade Rust WASM équivalente : l'intégration navigateur documentée utilise directement l'API JavaScript `WebTransport`. Cela reste viable pour un serveur Rust, mais apporte moins de valeur au POC si l'objectif est aussi de challenger le contrat Rust commun sur `wasm32`.
|
||||
|
||||
### Quinn / H3 de plus bas niveau
|
||||
|
||||
Quinn est la base QUIC commune à plusieurs stacks, mais l'utiliser directement imposerait de réimplémenter le binding WebTransport/HTTP3, la négociation et les détails de session déjà possédés par les crates spécialisées.
|
||||
|
||||
Cette voie reste un recours si les wrappers retenus bloquent une exigence essentielle ; elle n'est pas retenue comme premier POC, conformément au principe d'éviter une infrastructure disproportionnée.
|
||||
|
||||
## Choix de POC retenu
|
||||
|
||||
La famille `web-transport` est retenue en premier pour `0.3.5`, avec :
|
||||
|
||||
```text
|
||||
game-realtime-webtransport-lib
|
||||
-> web-transport
|
||||
-> native: web-transport-quinn
|
||||
-> wasm32: web-transport-wasm
|
||||
```
|
||||
|
||||
Raisons :
|
||||
|
||||
- façade native + WASM déjà pensée par l'amont ;
|
||||
- alignement direct avec la contrainte `!Send` possible du contrat `0.3.4` ;
|
||||
- serveur natif et client natif/WASM disponibles dans la même famille ;
|
||||
- streams et datagrams disponibles pour le POC ;
|
||||
- Quinn/Rustls/Tokio restent contenus dans le backend concret ;
|
||||
- licence compatible avec le dépôt ;
|
||||
- activité amont récente en août 2026.
|
||||
|
||||
`wtransport` reste le candidat de repli technique prioritaire si `web-transport` échoue sur une exigence concrète de `alpha.2` à `alpha.4`, notamment configuration TLS, lifecycle ou interop navigateur. Un échec d'implémentation ne justifie pas de basculer silencieusement : le plan et le delta doivent enregistrer la raison.
|
||||
|
||||
## Compatibilité avec le contrat 0.3.4
|
||||
|
||||
### Chemin fiable
|
||||
|
||||
Le contrat commun est message-oriented alors qu'un stream QUIC/WebTransport est un flux d'octets fiable, ordonné et flow-controlled. La différence de sémantique est réelle mais ne nécessite pas de modifier `RealtimeConnection`.
|
||||
|
||||
Le POC retiendra un stream bidirectionnel principal par connexion logique :
|
||||
|
||||
```text
|
||||
WebTransport session
|
||||
-> one primary bidirectional reliable stream
|
||||
-> bounded backend framing
|
||||
-> TransportMessage
|
||||
```
|
||||
|
||||
Le framing interne candidat est :
|
||||
|
||||
```text
|
||||
u32 big-endian payload length
|
||||
payload bytes
|
||||
```
|
||||
|
||||
avec une limite produit vérifiée avant allocation/écriture. Ce framing sert uniquement à reconstruire les frontières `TransportMessage` sur un byte stream ; il n'est pas le futur wire codec métier, ne porte aucune version de protocole joueur/room et reste privé au backend.
|
||||
|
||||
Le client ouvre le stream principal ; le serveur l'accepte avant de considérer la `RealtimeConnection` établie. Ce choix conserve l'ordre des messages et permet au split commun de mapper naturellement le côté write/read du même stream.
|
||||
|
||||
### Datagrams
|
||||
|
||||
Les datagrams WebTransport sont non fiables, non ordonnés et non flow-controlled. Ils ne satisfont donc pas le contrat fiable/ordonné livré en `0.3.4`.
|
||||
|
||||
Ils seront exercés séparément dans le POC, sans élargir `RealtimeConnection` et sans créer une capability commune tant qu'un second consommateur réel et un besoin sémantique durable ne sont pas démontrés.
|
||||
|
||||
Une mesure backend-spécifique peut utiliser la session amont directement ou une surface expérimentale locale au launcher de mesure. Elle ne doit pas devenir une API durable uniquement pour permettre le benchmark.
|
||||
|
||||
## TLS et certificats de développement
|
||||
|
||||
Le navigateur impose une URL `https://` vers le serveur WebTransport. L'option `serverCertificateHashes` permet de faire confiance à un certificat connu sans PKI publique lorsque la connexion est dédiée.
|
||||
|
||||
La documentation MDN impose notamment pour ce mode :
|
||||
|
||||
- hash SHA-256 ;
|
||||
- certificat X.509v3 ;
|
||||
- validité totale inférieure à deux semaines ;
|
||||
- date courante dans la période de validité ;
|
||||
- ECDSA P-256 comme choix interopérable minimal.
|
||||
|
||||
Source : <https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport>
|
||||
|
||||
`web-transport-quinn` fournit `ClientBuilder::with_server_certificate_hashes`, ce qui permet de reprendre la même stratégie de pinning côté client natif sans désactiver la validation TLS.
|
||||
|
||||
Source : <https://docs.rs/web-transport-quinn/latest/web_transport_quinn/struct.ClientBuilder.html>
|
||||
|
||||
Le POC doit donc privilégier une identité self-signed courte générée pour localhost et un pin de hash explicite. Une API « dangerous/no certificate verification » ne devient pas le chemin normal du smoke.
|
||||
|
||||
Aucun certificat privé, clé privée durable ou secret ne doit être commité.
|
||||
|
||||
## Plateformes
|
||||
|
||||
### Linux natif
|
||||
|
||||
Cible de référence pour :
|
||||
|
||||
- serveur local UDP/QUIC ;
|
||||
- client natif ;
|
||||
- tests loopback déterministes ;
|
||||
- smoke runtime hors harness ;
|
||||
- benchmark local borné.
|
||||
|
||||
### Navigateur / WASM
|
||||
|
||||
Cible obligatoire du POC parce que WebTransport apporte une valeur spécifique au navigateur. Deux preuves distinctes sont nécessaires :
|
||||
|
||||
1. compilation `wasm32-unknown-unknown` du chemin Rust choisi ;
|
||||
2. interop runtime d'un navigateur récent avec le serveur Rust local.
|
||||
|
||||
Le smoke navigateur ne doit pas être confondu avec un simple `cargo check` WASM.
|
||||
|
||||
### Android
|
||||
|
||||
La pile native Quinn/Rustls rend Android plausible, mais `0.3.5-alpha.1` ne le déclare pas validé. Le plan réserve au minimum un contrôle de compilation ciblé si la dépendance choisie ne force pas une réouverture disproportionnée du pipeline Android.
|
||||
|
||||
Aucun changement Java/Gradle/JNI n'est prévu pour le POC ; la matrice quatre ABI `0.3.3` n'est donc pas rejouée par cérémonie.
|
||||
|
||||
### macOS / iOS
|
||||
|
||||
La trajectoire est documentée mais non validée en l'absence d'environnement Apple. Une compatibilité supposée depuis Quinn/Rustls ne doit pas être présentée comme un smoke réel.
|
||||
|
||||
## Fallback WebSocket
|
||||
|
||||
Le fallback est retenu au niveau composition/application du POC, pas dans le gameplay et pas dans `game-realtime-transport-lib`.
|
||||
|
||||
La preuve visée est :
|
||||
|
||||
```text
|
||||
attempt WebTransport
|
||||
success -> use WebTransport path
|
||||
classified establishment failure / unsupported path -> WebSocket baseline
|
||||
```
|
||||
|
||||
Le POC doit également permettre de forcer chaque branche afin de vérifier le fallback de façon déterministe. Il ne doit pas créer de registry dynamique, `TransportManager` générique ni système de plugins.
|
||||
|
||||
Aucune règle n'impose encore que cette logique devienne une crate réutilisable : un launcher technique suffit tant qu'un second consommateur réel ne justifie pas l'extraction.
|
||||
|
||||
## Mesures retenues
|
||||
|
||||
Les mesures minimales sont :
|
||||
|
||||
- temps d'établissement ;
|
||||
- RTT de petits payloads ;
|
||||
- débit sur payloads bornés ;
|
||||
- plusieurs messages en vol ;
|
||||
- comparaison WebSocket vs stream WebTransport fiable ;
|
||||
- comparaison datagram uniquement si la preuve datagram est effectivement réalisée ;
|
||||
- coût opérationnel observé : certificat, UDP, port, debug et build WASM.
|
||||
|
||||
Les résultats loopback sont décrits comme des mesures locales contrôlées. Ils ne seront jamais extrapolés en gains Internet/mobile sans test réseau correspondant.
|
||||
|
||||
CPU/mémoire peuvent être relevés si la méthode est stable, mais ne sont pas une gate de release.
|
||||
|
||||
## Risques principaux
|
||||
|
||||
- protocole HTTP/3 WebTransport encore en Internet-Draft ;
|
||||
- APIs amont actives mais encore susceptibles de casser ;
|
||||
- `web_sys_unstable_apis` imposé au build WASM ;
|
||||
- contraintes de certificats plus lourdes que la baseline `ws://` ;
|
||||
- UDP parfois bloqué par réseau, firewall ou infrastructure ;
|
||||
- browser smoke nécessitant plusieurs processus/outils locaux ;
|
||||
- framing message interne nécessaire sur le stream fiable ;
|
||||
- différences de close/reset entre session WebTransport et stream principal ;
|
||||
- dépendances crypto natives pouvant compliquer certains targets ;
|
||||
- Android plausible mais non prouvé ;
|
||||
- benchmark loopback trop bruité pour porter seul une décision produit.
|
||||
|
||||
## Conclusion de cadrage
|
||||
|
||||
Le POC est viable et justifie `0.3.5`, mais le forecast initial du prompt est trop grossier pour la règle de 15 à 30 minutes par delta. En particulier, backend natif, robustesse, smoke runtime, WASM, interop navigateur/TLS, fallback et mesures ne doivent pas être agrégés en deux grosses alpha.
|
||||
|
||||
Le plan actif découpe ces preuves en tranches verticales indépendantes et ajoute une consolidation explicite avant RC, conformément à `SESSION-009` et `VER-PHASE-010`.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/testing/003-RC_VALIDATION_MATRIX.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Matrice de validation RC 0.1.0
|
||||
|
||||
@@ -85,13 +85,8 @@ Critères :
|
||||
## Android x86_64 / API 36
|
||||
|
||||
```bash
|
||||
python3 scripts/build_android_rust.py reflex --abi x86_64
|
||||
python3 scripts/build_android_rust.py snake --abi x86_64
|
||||
|
||||
cd Android
|
||||
gradle :game-reflex-poc:assembleDebug
|
||||
gradle :game-snake-poc:assembleDebug
|
||||
cd ..
|
||||
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||
```
|
||||
|
||||
Déployer les deux APK sur l'AVD de référence et revalider :
|
||||
@@ -105,13 +100,8 @@ Déployer les deux APK sur l'AVD de référence et revalider :
|
||||
## Android ARM64 réel
|
||||
|
||||
```bash
|
||||
python3 scripts/build_android_rust.py reflex --abi arm64-v8a
|
||||
python3 scripts/build_android_rust.py snake --abi arm64-v8a
|
||||
|
||||
cd Android
|
||||
gradle :game-reflex-poc:assembleDebug
|
||||
gradle :game-snake-poc:assembleDebug
|
||||
cd ..
|
||||
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||
```
|
||||
|
||||
Déployer sur l'appareil ARM64 de référence et revalider Reflex et Snake.
|
||||
|
||||
151
docs/testing/006-V0_3_3_RC_VALIDATION_MATRIX.md
Normal file
151
docs/testing/006-V0_3_3_RC_VALIDATION_MATRIX.md
Normal file
@@ -0,0 +1,151 @@
|
||||
<!-- file: docs/testing/006-V0_3_3_RC_VALIDATION_MATRIX.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Matrice de validation RC 0.3.3
|
||||
|
||||
## Objectif
|
||||
|
||||
`0.3.3-3-rc.1` est une candidate gelée. Elle ne modifie ni gameplay, ni moteur, ni JNI, ni Java, ni logique Gradle : elle revalide qu'un état reproductible du pipeline Android SDL3 natif peut être promu mécaniquement vers `0.3.3`.
|
||||
|
||||
## Gel fonctionnel
|
||||
|
||||
Pendant `3-rc.*`, seules les corrections autorisées par `VER-RC-*` sont admises. Toute nouvelle ABI, nouvelle API Android, nouvelle capability réseau, nouvelle abstraction, refactor volontaire ou changement de comportement est hors scope.
|
||||
|
||||
La future convention de versions `alpha.M / beta.M / rc.M` n'est pas appliquée à `0.3.3`; elle commencera avec la prochaine version et ne doit provoquer aucune réécriture de l'historique.
|
||||
|
||||
## Gate workspace
|
||||
|
||||
Depuis la racine :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
|
||||
python3 scripts/audit_distribution_layout.py
|
||||
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test --workspace --all-targets --all-features
|
||||
```
|
||||
|
||||
La suite workspace complète est répétée en RC parce que la version Cargo passe à `0.3.3-3-rc.1`.
|
||||
|
||||
## Rebuild Android propre
|
||||
|
||||
```bash
|
||||
(cd Android && gradle :game-snake-poc:clean :game-reflex-poc:clean)
|
||||
|
||||
(cd Android && gradle :game-snake-poc:assembleDebug)
|
||||
(cd Android && gradle :game-reflex-poc:assembleDebug)
|
||||
|
||||
(cd Android && gradle :game-snake-poc:bundleRelease)
|
||||
(cd Android && gradle :game-reflex-poc:bundleRelease)
|
||||
```
|
||||
|
||||
Le build doit utiliser le JDK normal de l'environnement et un Gradle système `>= 9.6.0`; aucun `JAVA_HOME` spécifique au projet et aucun wrapper Gradle natif ne sont requis.
|
||||
|
||||
## Contrôle quatre ABI
|
||||
|
||||
```bash
|
||||
for game in game-snake-poc game-reflex-poc; do
|
||||
APK="Android/${game}/build/outputs/apk/debug/${game}-debug.apk"
|
||||
AAB="Android/${game}/build/outputs/bundle/release/${game}-release.aab"
|
||||
|
||||
test -f "$APK"
|
||||
test -f "$AAB"
|
||||
|
||||
for abi in arm64-v8a armeabi-v7a x86_64 x86; do
|
||||
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libSDL3.so"
|
||||
unzip -Z1 "$APK" | grep -Fx "lib/${abi}/libgame_android_entrypoint.so"
|
||||
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libSDL3.so"
|
||||
unzip -Z1 "$AAB" | grep -Fx "base/lib/${abi}/libgame_android_entrypoint.so"
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
## Packaging 16 KB
|
||||
|
||||
```bash
|
||||
ZIPALIGN="$(find "$ANDROID_HOME/build-tools" -mindepth 2 -maxdepth 2 -type f -name zipalign -print | sort -V | tail -n 1)"
|
||||
test -x "$ZIPALIGN"
|
||||
|
||||
"$ZIPALIGN" -c -P 16 -v 4 Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
|
||||
"$ZIPALIGN" -c -P 16 -v 4 Android/game-reflex-poc/build/outputs/apk/debug/game-reflex-poc-debug.apk
|
||||
```
|
||||
|
||||
Recontrôler les segments ELF 64 bits Release :
|
||||
|
||||
```bash
|
||||
OBJDUMP="$ANDROID_HOME/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump"
|
||||
test -x "$OBJDUMP"
|
||||
|
||||
for game in game-snake-poc game-reflex-poc; do
|
||||
for abi in arm64-v8a x86_64; do
|
||||
for so in \
|
||||
"Android/${game}/build/generated/sasedevNative/release/${abi}/jniLibs/${abi}/libSDL3.so" \
|
||||
"Android/${game}/build/generated/sasedevNative/release/${abi}/jniLibs/${abi}/libgame_android_entrypoint.so"; do
|
||||
test -f "$so"
|
||||
"$OBJDUMP" -p "$so" | grep 'LOAD'
|
||||
done
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
Chaque ligne `LOAD` des ABI 64 bits doit rester au minimum à `align 2**14`. Les ABI 32 bits restent supportées mais ne sont pas soumises à ce critère Play 16 KB.
|
||||
|
||||
## Smoke AVD API 36 x86_64
|
||||
|
||||
Avec l'AVD de référence déjà disponible, utiliser son serial ADB, actuellement `emulator-5554` lorsqu'il est lancé. Pour chaque jeu :
|
||||
|
||||
```bash
|
||||
SERIAL=emulator-5554
|
||||
APK=Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
|
||||
|
||||
adb -s "$SERIAL" install -r "$APK"
|
||||
adb -s "$SERIAL" shell am force-stop com.sasedev.games.snake
|
||||
adb -s "$SERIAL" logcat -c
|
||||
adb -s "$SERIAL" shell am start -W -n com.sasedev.games.snake/.SnakeActivity
|
||||
sleep 3
|
||||
adb -s "$SERIAL" shell pidof com.sasedev.games.snake
|
||||
adb -s "$SERIAL" logcat -d -b crash
|
||||
```
|
||||
|
||||
Puis :
|
||||
|
||||
```bash
|
||||
SERIAL=emulator-5554
|
||||
APK=Android/game-reflex-poc/build/outputs/apk/debug/game-reflex-poc-debug.apk
|
||||
|
||||
adb -s "$SERIAL" install -r "$APK"
|
||||
adb -s "$SERIAL" shell am force-stop com.sasedev.games.reflex
|
||||
adb -s "$SERIAL" logcat -c
|
||||
adb -s "$SERIAL" shell am start -W -n com.sasedev.games.reflex/.ReflexActivity
|
||||
sleep 3
|
||||
adb -s "$SERIAL" shell pidof com.sasedev.games.reflex
|
||||
adb -s "$SERIAL" logcat -d -b crash
|
||||
```
|
||||
|
||||
Vérifier humainement : rendu visible, assets présents, Snake réagit au swipe, Reflex réagit au tap, puis absence de crash immédiat.
|
||||
|
||||
## Smoke ARM64 réel
|
||||
|
||||
Sur le Samsung ou un autre appareil ARM64 disponible, remplacer `<serial>` :
|
||||
|
||||
```bash
|
||||
SERIAL=<serial>
|
||||
|
||||
adb -s "$SERIAL" shell getprop ro.build.version.sdk
|
||||
adb -s "$SERIAL" shell getprop ro.product.cpu.abi
|
||||
```
|
||||
|
||||
Rejouer les deux blocs Snake/Reflex précédents avec `SERIAL=<serial>`. Vérifier humainement rendu, tactile et sortie/Back.
|
||||
|
||||
## Frontières non rejouées par défaut
|
||||
|
||||
Les smokes API 21/x86 et API 35/x86_64 `ps16k` ont été établis en `0-pre.5`, puis aucune logique native/Gradle n'a changé. Ils ne sont pas répétés en RC sauf si un correctif RC touche le pipeline Android, le NDK, SDL3, les ABI, `minSdk`, le packaging ou les bibliothèques natives.
|
||||
|
||||
## Promotion stable
|
||||
|
||||
Si cette gate est propre et qu'aucun correctif n'est nécessaire, la promotion vers `0.3.3` est mécanique : version stable, entrée stable du changelog, historique RC, clôture du plan, delta de release et mise à jour mécanique du prompt `0.3.4` si la base stable doit être rendue certaine. Aucun code fonctionnel ne doit changer.
|
||||
51
history/0.3.3/0-pre.1.md
Normal file
51
history/0.3.3/0-pre.1.md
Normal file
@@ -0,0 +1,51 @@
|
||||
<!-- file: history/0.3.3/0-pre.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-0-pre.1
|
||||
|
||||
## Statut
|
||||
|
||||
Le cadrage `0.3.3-0-pre.1` a été validé par l'utilisateur le 2026-09-21. Les audits, le formatage, le check workspace et Clippy strict passent ; aucun `0-pre.1.fix.N` n'est requis avant l'ouverture de `0-pre.2`.
|
||||
|
||||
## Gate utilisateur
|
||||
|
||||
La gate demandée dans le delta a été exécutée avec succès :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 233 file(s))
|
||||
Distribution layout audit: clean (48 required path(s), 1 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le compte Markdown local est supérieur à celui du ZIP taggé audité pendant `0-pre.1`; l'audit reste propre et aucune divergence de source n'est déduite de ce seul compteur.
|
||||
|
||||
## Environnement Android confirmé
|
||||
|
||||
L'inventaire frais transmis pour ouvrir `0-pre.2` confirme :
|
||||
|
||||
- Rust et Cargo `1.94.1` ;
|
||||
- `cargo-ndk 4.1.2` ;
|
||||
- targets Rust Android installées : `aarch64-linux-android`, `armv7-linux-androideabi`, `i686-linux-android`, `x86_64-linux-android` ;
|
||||
- target WASM `wasm32-unknown-unknown` et host `x86_64-unknown-linux-gnu` installés ;
|
||||
- `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64` ;
|
||||
- la commande `java` du `PATH` expose Temurin `25.0.4.1`, tandis que Gradle utilise bien le JDK `17.0.20.1` via `JAVA_HOME` pour son launcher et son daemon ;
|
||||
- `ANDROID_HOME=/home/sinus/DEV/AndroidSDK` ;
|
||||
- ADB `37.0.1` ;
|
||||
- Samsung `SM_G965F` réel connecté ;
|
||||
- émulateur x86_64 `emulator-5554` connecté ;
|
||||
- AVD `Medium_Phone`, `Medium_Phone_API_36.0` et `sasedev_games_api36` disponibles ;
|
||||
- Gradle système `9.7.1` ;
|
||||
- NDK projet `28.2.13676358` présent ;
|
||||
- `Android/libs/SDL3-3.4.16.aar` présent avec SHA-256 `03710fc7b49cc070551446841a843840fabf2aaaaa3043cd5739292a55e4e61c`.
|
||||
|
||||
La différence entre `java -version` et `JAVA_HOME` n'est pas un défaut de la gate : le pipeline Gradle doit utiliser le wrapper et le JDK désigné par `JAVA_HOME`, pas dépendre implicitement du premier `java` du `PATH`.
|
||||
|
||||
## Transmission vers 0-pre.2
|
||||
|
||||
Tous les prérequis prévus par le plan sont disponibles pour la preuve mono-ABI Snake : wrapper Gradle épinglable, JDK 17 effectif pour Gradle, r28c, AAR SDL3 attendu, target Rust ARM64 et appareil ARM64 disponible pour une validation ultérieure.
|
||||
87
history/0.3.3/0-pre.2.md
Normal file
87
history/0.3.3/0-pre.2.md
Normal file
@@ -0,0 +1,87 @@
|
||||
<!-- file: history/0.3.3/0-pre.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-0-pre.2
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.3-0-pre.2` a été validé par l'utilisateur le 2026-09-21. La preuve Snake ARM64 confirme que `assembleDebug` possède désormais le build Rust/SDL3 sans exécution préalable de `scripts/build_android_rust.py`.
|
||||
|
||||
Aucun `0-pre.2.fix.N` n'est requis ; la suite peut ouvrir `0-pre.3`.
|
||||
|
||||
## Gate statique et Rust
|
||||
|
||||
La gate fournie est propre :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 235 file(s))
|
||||
Distribution layout audit: clean (53 required path(s), 1 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compilé porte bien `0.3.3-0-pre.2` sur les crates concernées.
|
||||
|
||||
## Preuve Gradle/JDK du jalon
|
||||
|
||||
Le wrapper introduit pour cette preuve a été contrôlé avant build :
|
||||
|
||||
```text
|
||||
Gradle Wrapper JAR SHA-256
|
||||
497c8c2a7e5031f6aa847f88104aa80a93532ec32ee17bdb8d1d2f67a194a9c7
|
||||
|
||||
Gradle distribution
|
||||
9.6.0
|
||||
|
||||
Gradle binary ZIP SHA-256
|
||||
bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
|
||||
```
|
||||
|
||||
`./gradlew --version` a téléchargé puis exécuté Gradle `9.6.0`. Pour cette gate, le launcher et le daemon utilisaient Temurin `17.0.20.1` via `JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64`.
|
||||
|
||||
Cette combinaison décrit la preuve `0-pre.2`; elle n'est pas conservée comme contrainte exacte du pipeline natif après la décision de toolchain prise pour `0-pre.3`.
|
||||
|
||||
## Build Snake ARM64
|
||||
|
||||
La commande :
|
||||
|
||||
```text
|
||||
:game-snake-poc:assembleDebug
|
||||
```
|
||||
|
||||
a déclenché `buildDebugSasedevRustArm64V8a`, compilé `game-android-entrypoint` avec `cargo ndk` pour `aarch64-linux-android`, copié les bibliothèques sous `build/generated/sasedevNative/...` et terminé avec :
|
||||
|
||||
```text
|
||||
BUILD SUCCESSFUL
|
||||
54 actionable tasks: 54 executed
|
||||
```
|
||||
|
||||
Aucune commande Python de build natif n'a été exécutée avant Gradle.
|
||||
|
||||
## Inspection APK
|
||||
|
||||
L'artefact produit est :
|
||||
|
||||
```text
|
||||
Android/game-snake-poc/build/outputs/apk/debug/game-snake-poc-debug.apk
|
||||
```
|
||||
|
||||
L'inspection confirme les deux bibliothèques ARM64 attendues :
|
||||
|
||||
```text
|
||||
lib/arm64-v8a/libSDL3.so
|
||||
lib/arm64-v8a/libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
Aucune bibliothèque `x86_64`, `armeabi-v7a` ou `x86` n'est présente dans cette preuve mono-ABI. Les lignes répétées dans la sortie utilisateur provenaient des deux `grep -Fx` exécutés après le listing initial, pas de doublons internes à l'APK.
|
||||
|
||||
## Transmission vers 0-pre.3
|
||||
|
||||
Le task wiring, l'extraction SDL3, le linkage Rust, le staging `jniLibs` généré et le packaging ARM64 sont donc prouvés. `0-pre.3` peut élargir ce même contrat à `arm64-v8a + x86_64`.
|
||||
|
||||
Après cette validation, la politique toolchain a été révisée explicitement : le projet Android SDL3 natif doit accepter le Gradle système lorsqu'il est `>= 9.6.0` et utiliser le JDK normal de l'environnement, sans `export JAVA_HOME` spécifique au projet. Les contraintes historiques du POC Tauri restent séparées.
|
||||
66
history/0.3.3/0-pre.3.md
Normal file
66
history/0.3.3/0-pre.3.md
Normal file
@@ -0,0 +1,66 @@
|
||||
<!-- file: history/0.3.3/0-pre.3.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-0-pre.3
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.3-0-pre.3` a été validé par l'utilisateur le 2026-09-21. La tranche prouve le pipeline Gradle système avec JDK courant, le packaging Debug `arm64-v8a + x86_64` de Snake et Reflex, puis l'installation/lancement du même APK Snake sur AVD x86_64 et appareil ARM64 réel.
|
||||
|
||||
Aucun `0-pre.3.fix.N` n'est requis. La suite peut ouvrir `0-pre.4`.
|
||||
|
||||
## Toolchain réellement validée
|
||||
|
||||
Le shell n'impose plus `JAVA_HOME` et utilise Temurin 25 :
|
||||
|
||||
```text
|
||||
OpenJDK / Temurin 25.0.4.1 LTS
|
||||
JAVA_HOME=
|
||||
Gradle 9.7.1
|
||||
Launcher JVM 25.0.4.1
|
||||
Daemon JVM /usr/lib/jvm/temurin-25-jdk-amd64
|
||||
```
|
||||
|
||||
Cette preuve confirme que le chemin Android SDL3 natif fonctionne avec un Gradle système supérieur au minimum `9.6.0` et avec le JDK courant de l'environnement, sans export JDK 17 spécifique. La glue Java reste compilée avec source/target 17.
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 237 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 5 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
## Builds multi-ABI
|
||||
|
||||
Snake a exécuté `buildDebugSasedevRustArm64V8a` puis `buildDebugSasedevRustX8664` et terminé avec `BUILD SUCCESSFUL`. Reflex a exécuté les mêmes deux tâches ABI et terminé avec `BUILD SUCCESSFUL`.
|
||||
|
||||
Les deux APK Debug universal contiennent exactement les couples observés :
|
||||
|
||||
```text
|
||||
lib/arm64-v8a/libSDL3.so
|
||||
lib/arm64-v8a/libgame_android_entrypoint.so
|
||||
lib/x86_64/libSDL3.so
|
||||
lib/x86_64/libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
Les lignes répétées de la sortie utilisateur proviennent des `grep -Fx` de contrôle exécutés après le listing initial.
|
||||
|
||||
## Smokes runtime
|
||||
|
||||
L'APK Snake a été installé puis lancé avec succès sur :
|
||||
|
||||
- `emulator-5554`, AVD `sdk_gphone64_x86_64` ;
|
||||
- appareil physique Samsung `SM_G965F`, ARM64.
|
||||
|
||||
Les deux installations retournent `Success` et `SnakeActivity` est démarrée via `am start`.
|
||||
|
||||
## Révision de scope après validation
|
||||
|
||||
Après cette preuve 64 bits, le besoin produit a été élargi : `0.3.3` ne doit pas se limiter à ARM64/x86_64 mais tenter de supporter toutes les ABI encore prises en charge par la toolchain Android moderne, y compris les 32 bits anciennes. `0-pre.4` porte donc la matrice `arm64-v8a + armeabi-v7a + x86_64 + x86`.
|
||||
76
history/0.3.3/0-pre.4.md
Normal file
76
history/0.3.3/0-pre.4.md
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- file: history/0.3.3/0-pre.4.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-0-pre.4
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.3-0-pre.4` a été validé par l'utilisateur le 2026-09-21. La tranche confirme la matrice Android native complète encore supportée par la toolchain moderne pour Snake et Reflex : `arm64-v8a`, `armeabi-v7a`, `x86_64` et `x86`.
|
||||
|
||||
Aucun `0-pre.4.fix.N` n'est requis. La suite peut ouvrir `0-pre.5`.
|
||||
|
||||
## Toolchain réellement validée
|
||||
|
||||
```text
|
||||
OpenJDK / Temurin 25.0.4.1 LTS
|
||||
Gradle 9.7.1
|
||||
Launcher JVM 25.0.4.1
|
||||
Daemon JVM /usr/lib/jvm/temurin-25-jdk-amd64
|
||||
```
|
||||
|
||||
Targets Rust Android présents :
|
||||
|
||||
```text
|
||||
aarch64-linux-android
|
||||
armv7-linux-androideabi
|
||||
i686-linux-android
|
||||
x86_64-linux-android
|
||||
```
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 239 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 5 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
## Builds quatre ABI
|
||||
|
||||
Snake et Reflex exécutent tous deux avec succès les tâches natives Gradle pour :
|
||||
|
||||
```text
|
||||
arm64-v8a -> aarch64-linux-android
|
||||
armeabi-v7a -> armv7-linux-androideabi
|
||||
x86 -> i686-linux-android
|
||||
x86_64 -> x86_64-linux-android
|
||||
```
|
||||
|
||||
Les deux commandes `assembleDebug` terminent avec `BUILD SUCCESSFUL`.
|
||||
|
||||
## Inspection des APK
|
||||
|
||||
Les APK Debug universal Snake et Reflex contiennent chacun exactement les couples natifs attendus pour les quatre ABI :
|
||||
|
||||
```text
|
||||
lib/arm64-v8a/libSDL3.so
|
||||
lib/arm64-v8a/libgame_android_entrypoint.so
|
||||
lib/armeabi-v7a/libSDL3.so
|
||||
lib/armeabi-v7a/libgame_android_entrypoint.so
|
||||
lib/x86/libSDL3.so
|
||||
lib/x86/libgame_android_entrypoint.so
|
||||
lib/x86_64/libSDL3.so
|
||||
lib/x86_64/libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
Les répétitions visibles dans la sortie utilisateur proviennent des `grep -Fx` de contrôle exécutés après le listing initial.
|
||||
|
||||
## Conséquence
|
||||
|
||||
La matrice quatre ABI n'est plus seulement une intention documentaire : le même pipeline Gradle/Cargo/SDL3 la compile et la package pour les deux jeux. `0-pre.5` peut donc étendre ce pipeline aux variantes Release/AAB, vérifier la compatibilité 16 KB, consolider API 21 et supprimer le builder Python Android historique.
|
||||
137
history/0.3.3/0-pre.5.md
Normal file
137
history/0.3.3/0-pre.5.md
Normal file
@@ -0,0 +1,137 @@
|
||||
<!-- file: history/0.3.3/0-pre.5.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-0-pre.5
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.3-0-pre.5` a été validé par l'utilisateur le 2026-09-21. La tranche ferme le chemin Android natif historique : Gradle possède les variantes Debug/Release, les AAB quatre ABI sont produits, `minSdk 21` dispose d'une preuve runtime, la compatibilité 16 KB 64 bits est vérifiée et `scripts/build_android_rust.py` est supprimé.
|
||||
|
||||
Aucun `0-pre.5.fix.N` n'est requis. La suite ouvre `0.3.3-2-beta.1`.
|
||||
|
||||
## Toolchain réellement validée
|
||||
|
||||
```text
|
||||
OpenJDK / Temurin 25.0.4.1 LTS
|
||||
Gradle 9.7.1
|
||||
AGP 9.4.0
|
||||
NDK 28.2.13676358 / r28c
|
||||
```
|
||||
|
||||
Le pipeline utilise le JDK et le Gradle de l'environnement ; aucun `JAVA_HOME` JDK 17 ni wrapper Gradle natif n'est requis.
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
La première exécution de l'audit de distribution a détecté deux anciens répertoires générés laissés localement par le chemin historique :
|
||||
|
||||
```text
|
||||
Android/game-reflex-poc/src/main/jniLibs
|
||||
Android/game-snake-poc/src/main/jniLibs
|
||||
```
|
||||
|
||||
Ils ont été supprimés puis l'audit a été rejoué avec succès :
|
||||
|
||||
```text
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
```
|
||||
|
||||
Les autres gates fournies sont propres :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 241 file(s))
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
L'échec initial de layout provenait donc de résidus locaux non versionnés, pas d'un retour du builder Python dans le delta.
|
||||
|
||||
## Release et AAB quatre ABI
|
||||
|
||||
Les commandes suivantes terminent avec `BUILD SUCCESSFUL` :
|
||||
|
||||
```text
|
||||
:game-snake-poc:bundleRelease
|
||||
:game-reflex-poc:bundleRelease
|
||||
```
|
||||
|
||||
Pour les deux jeux, Gradle exécute les builds Rust Release pour :
|
||||
|
||||
```text
|
||||
arm64-v8a
|
||||
armeabi-v7a
|
||||
x86_64
|
||||
x86
|
||||
```
|
||||
|
||||
Les deux AAB contiennent exactement les couples attendus sous `base/lib/<abi>/` :
|
||||
|
||||
```text
|
||||
libSDL3.so
|
||||
libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
pour chacune des quatre ABI.
|
||||
|
||||
## Packaging et pages 16 KB
|
||||
|
||||
`zipalign -c -P 16 -v 4` réussit sur les APK Debug Snake et Reflex.
|
||||
|
||||
Les contrôles ELF Debug et Release montrent :
|
||||
|
||||
```text
|
||||
arm64-v8a -> LOAD align 2**14
|
||||
x86_64 -> LOAD align 2**14
|
||||
armeabi-v7a -> LOAD align 2**12
|
||||
x86 -> LOAD align 2**12
|
||||
```
|
||||
|
||||
pour SDL3 et `libgame_android_entrypoint.so`.
|
||||
|
||||
Le critère 16 KB est appliqué aux ABI/appareils 64 bits, conformément à l'exigence Google Play. Les alignements `2**12` des ABI 32 bits ne constituent donc pas un échec.
|
||||
|
||||
## Preuve Android 5.0 / API 21
|
||||
|
||||
Un AVD basé sur :
|
||||
|
||||
```text
|
||||
system-images/android-21/google_apis/x86/
|
||||
```
|
||||
|
||||
retourne :
|
||||
|
||||
```text
|
||||
API 21
|
||||
ABI x86
|
||||
KernelPageSize 4 kB
|
||||
```
|
||||
|
||||
L'APK Snake universal est installé avec succès et `SnakeActivity` démarre. `minSdk 21` dispose donc d'une preuve runtime réelle sur x86 32 bits.
|
||||
|
||||
## Preuve Android 15 / 16 KB
|
||||
|
||||
Un AVD basé sur :
|
||||
|
||||
```text
|
||||
system-images/android-35/google_apis_playstore_ps16k/x86_64/
|
||||
```
|
||||
|
||||
retourne :
|
||||
|
||||
```text
|
||||
API 35
|
||||
ABI x86_64
|
||||
getconf PAGE_SIZE = 16384
|
||||
```
|
||||
|
||||
L'APK Snake s'installe, `SnakeActivity` démarre et `pidof com.sasedev.games.snake` retourne un PID actif.
|
||||
|
||||
Une lecture séparée de `/proc/self/smaps` retourne `KernelPageSize: 4 kB` pour le mapping inspecté. Cette valeur n'est pas utilisée comme mesure du mode global du device ; la preuve de taille de page de l'environnement repose sur `getconf PAGE_SIZE=16384`.
|
||||
|
||||
## Conséquence
|
||||
|
||||
Le scope d'implémentation `0-pre.*` est fermé. La beta peut se concentrer sur la validation large, la reproductibilité du packaging quatre ABI et les smokes de référence sans introduire de nouvelle fonctionnalité.
|
||||
118
history/0.3.3/2-beta.1.md
Normal file
118
history/0.3.3/2-beta.1.md
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- file: history/0.3.3/2-beta.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-2-beta.1
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.3-2-beta.1` a été validée par l'utilisateur le 2026-09-21. La gate large confirme la reproductibilité du pipeline Android SDL3 natif quatre ABI, les artefacts APK/AAB et les smokes de référence x86_64 + ARM64 pour Snake et Reflex.
|
||||
|
||||
Aucun `2-beta.1.fix.N` n'est requis. La version peut entrer directement en `0.3.3-3-rc.1`.
|
||||
|
||||
## Gate workspace
|
||||
|
||||
Les validations suivantes sont propres :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 251 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
cargo test --workspace --all-targets --all-features: 35 passed, 0 failed
|
||||
```
|
||||
|
||||
## Rebuild Android quatre ABI
|
||||
|
||||
Après nettoyage Gradle des deux applications, les builds suivants terminent avec succès :
|
||||
|
||||
```text
|
||||
:game-snake-poc:assembleDebug
|
||||
:game-reflex-poc:assembleDebug
|
||||
:game-snake-poc:bundleRelease
|
||||
:game-reflex-poc:bundleRelease
|
||||
```
|
||||
|
||||
Pour Snake et Reflex, les tâches natives ont reconstruit les quatre cibles :
|
||||
|
||||
```text
|
||||
arm64-v8a -> aarch64-linux-android
|
||||
armeabi-v7a -> armv7-linux-androideabi
|
||||
x86 -> i686-linux-android
|
||||
x86_64 -> x86_64-linux-android
|
||||
```
|
||||
|
||||
## Inspection APK et AAB
|
||||
|
||||
Chaque APK Debug contient :
|
||||
|
||||
```text
|
||||
lib/<abi>/libSDL3.so
|
||||
lib/<abi>/libgame_android_entrypoint.so
|
||||
```
|
||||
|
||||
pour les quatre ABI. Chaque AAB Release contient les mêmes couples sous `base/lib/<abi>/`.
|
||||
|
||||
`zipalign -c -P 16 -v 4` retourne `Verification successful` pour les APK Debug Snake et Reflex.
|
||||
|
||||
## Smokes x86_64/API 36
|
||||
|
||||
L'AVD de référence rapporte :
|
||||
|
||||
```text
|
||||
API 36
|
||||
x86_64
|
||||
PAGE_SIZE 4096
|
||||
```
|
||||
|
||||
Snake :
|
||||
|
||||
```text
|
||||
install: Success
|
||||
Status: ok
|
||||
LaunchState: COLD
|
||||
pidof: 5192
|
||||
crash buffer: aucune entrée remontée
|
||||
```
|
||||
|
||||
Reflex :
|
||||
|
||||
```text
|
||||
install: Success
|
||||
Status: ok
|
||||
LaunchState: COLD
|
||||
pidof: 5272
|
||||
crash buffer: aucune entrée remontée
|
||||
```
|
||||
|
||||
## Smokes ARM64 réels
|
||||
|
||||
Le Samsung `SM_G965F` rapporte :
|
||||
|
||||
```text
|
||||
API 29
|
||||
ro.product.cpu.abi=arm64-v8a
|
||||
ro.product.cpu.abilist=arm64-v8a,armeabi-v7a,armeabi
|
||||
```
|
||||
|
||||
Snake : installation `Success`, `Status: ok`, lancement COLD et processus vivant (`pidof 30958`).
|
||||
|
||||
Reflex : installation `Success`, `Status: ok`, lancement COLD et processus vivant (`pidof 31798`).
|
||||
|
||||
Aucune entrée du buffer crash n'a été remontée pendant ces quatre smokes. Les sorties ADB établissent le démarrage et l'absence de crash immédiat ; la matrice RC conserve explicitement la vérification visuelle du rendu et des contrôles comme dernière observation humaine de publication.
|
||||
|
||||
## Frontières déjà acquises en 0-pre.5
|
||||
|
||||
Aucune logique Android/native n'ayant changé depuis les preuves de frontière, la beta réutilise sans les rejouer systématiquement :
|
||||
|
||||
- Android 5.0/API 21 x86 : APK Snake installé et activité démarrée ;
|
||||
- Android 15/API 35 x86_64 `ps16k` : `getconf PAGE_SIZE=16384`, APK installé, activité démarrée et processus vivant ;
|
||||
- ELF `arm64-v8a` et `x86_64` alignés `2**14` pour SDL3 et la bibliothèque Rust.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Le scope d'implémentation reste fermé. `3-rc.1` peut être une candidate strictement gelée, centrée sur la reproductibilité finale, la documentation de publication et la préparation de la session suivante.
|
||||
100
history/0.3.3/3-rc.1.md
Normal file
100
history/0.3.3/3-rc.1.md
Normal file
@@ -0,0 +1,100 @@
|
||||
<!-- file: history/0.3.3/3-rc.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.3-3-rc.1
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.3-3-rc.1` a été validée par l'utilisateur le 2026-09-21 puis retenue pour promotion mécanique vers la stable `0.3.3`.
|
||||
|
||||
Le correctif `3-rc.1.fix.1` est strictement documentaire : il enrichit le prompt `0.3.4` sans changer la version Cargo, le runtime ou le pipeline Android. Ses audits Markdown/distribution ont ensuite été validés.
|
||||
|
||||
## Gate workspace et packaging
|
||||
|
||||
La gate RC a confirmé :
|
||||
|
||||
- audits Rust général, exports, workspace, Markdown et distribution propres ;
|
||||
- `cargo fmt --all` / `--check`, `cargo check --workspace`, Clippy strict et suite workspace complète avec 35 tests réussis ;
|
||||
- rebuild propre Snake/Reflex en Debug et Release ;
|
||||
- quatre ABI `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86` dans les APK Debug et AAB Release ;
|
||||
- `zipalign -c -P 16 -v 4` avec `Verification successful` sur les deux APK.
|
||||
|
||||
## Alignement ELF 64 bits
|
||||
|
||||
Les bibliothèques Release `libSDL3.so` et `libgame_android_entrypoint.so` de Snake et Reflex ont été inspectées pour `arm64-v8a` et `x86_64` avec `llvm-objdump` du NDK `28.2.13676358`.
|
||||
|
||||
Toutes les lignes `LOAD` observées sont alignées à :
|
||||
|
||||
```text
|
||||
align 2**14
|
||||
```
|
||||
|
||||
Le critère 16 KB 64 bits de la matrice RC est donc satisfait.
|
||||
|
||||
## Smokes AVD API 36 x86_64
|
||||
|
||||
Snake :
|
||||
|
||||
```text
|
||||
install: Success
|
||||
Status: ok
|
||||
LaunchState: COLD
|
||||
pidof: 4777
|
||||
crash buffer: aucune entrée remontée
|
||||
```
|
||||
|
||||
Reflex :
|
||||
|
||||
```text
|
||||
install: Success
|
||||
Status: ok
|
||||
LaunchState: COLD
|
||||
pidof: 5545
|
||||
crash buffer: aucune entrée remontée
|
||||
```
|
||||
|
||||
## Smokes ARM64 réels
|
||||
|
||||
Le Samsung de validation rapporte :
|
||||
|
||||
```text
|
||||
API 29
|
||||
ro.product.cpu.abi=arm64-v8a
|
||||
```
|
||||
|
||||
Snake :
|
||||
|
||||
```text
|
||||
install: Success
|
||||
Status: ok
|
||||
LaunchState: COLD
|
||||
pidof: 21137
|
||||
crash buffer: aucune entrée remontée
|
||||
```
|
||||
|
||||
Reflex :
|
||||
|
||||
```text
|
||||
install: Success
|
||||
Status: ok
|
||||
LaunchState: UNKNOWN (-1)
|
||||
pidof: 22830
|
||||
crash buffer: aucune entrée remontée
|
||||
```
|
||||
|
||||
`LaunchState: UNKNOWN (-1)` n'a pas été traité comme un crash : l'activité a terminé la commande `am start -W`, le processus est resté vivant et le buffer crash n'a remonté aucune entrée.
|
||||
|
||||
Les observations humaines demandées par la matrice RC ont été exécutées dans le cadre de ces smokes et aucun défaut supplémentaire n'a été signalé avant la promotion stable.
|
||||
|
||||
## Frontières déjà acquises
|
||||
|
||||
Aucune modification native/Gradle n'étant intervenue depuis les preuves précédentes, la RC conserve aussi :
|
||||
|
||||
- Android 5.0/API 21 x86 avec installation et démarrage Snake ;
|
||||
- Android 15/API 35 x86_64 `ps16k` avec `PAGE_SIZE=16384`, installation, démarrage et processus vivant ;
|
||||
- support des quatre ABI dans le pipeline Gradle/Cargo ;
|
||||
- suppression du builder Python Android historique et interdiction des `src/main/jniLibs` générés.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Aucun défaut nécessitant un `3-rc.1.fix.N` fonctionnel n'a été remonté. La promotion vers `0.3.3` reste strictement mécanique.
|
||||
39
history/0.3.4/alpha.1.md
Normal file
39
history/0.3.4/alpha.1.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<!-- file: history/0.3.4/alpha.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-alpha.1
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-alpha.1` a été validée par l'utilisateur le 2026-09-21 après application du delta sur son checkout issu de la stable `0.3.3`.
|
||||
|
||||
Aucun `alpha.1.fix.N` n'est requis. La suite peut ouvrir `0.3.4-alpha.2` et matérialiser l'API transport-neutral planifiée.
|
||||
|
||||
## Nettoyage et gates statiques
|
||||
|
||||
L'utilisateur a d'abord exécuté :
|
||||
|
||||
```text
|
||||
cargo clean
|
||||
Removed 57247 files, 28.5GiB total
|
||||
```
|
||||
|
||||
Puis les audits et gates Rust ont tous terminé avec succès :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 260 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
La reconstruction complète après `cargo clean` confirme notamment le workspace à `0.3.4-alpha.1` sur les moteurs, jeux, adapters Desktop/Android/WASM/Tauri et bibliothèques communes.
|
||||
|
||||
## Conséquence
|
||||
|
||||
La migration de gouvernance et le cadrage de `0.3.4` sont acceptés. `alpha.2` peut ajouter uniquement le contrat commun realtime, sans Tokio, Tungstenite ni backend WebSocket ; ces dépendances restent réservées à la tranche suivante.
|
||||
48
history/0.3.4/alpha.2.md
Normal file
48
history/0.3.4/alpha.2.md
Normal file
@@ -0,0 +1,48 @@
|
||||
<!-- file: history/0.3.4/alpha.2.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-alpha.2
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-alpha.2` a été validée par l'utilisateur le 2026-09-21. Le contrat transport-neutral compile dans le workspace, passe Clippy strict et ses sept tests ciblés sont propres.
|
||||
|
||||
Aucun `alpha.2.fix.N` n'est requis. La suite peut ouvrir `0.3.4-alpha.3` et introduire le backend WebSocket Tokio/tokio-tungstenite planifié.
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
La gate fournie est propre :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 262 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compilé porte `0.3.4-alpha.2`, y compris `game-realtime-transport-lib`.
|
||||
|
||||
## Tests du contrat transport
|
||||
|
||||
La commande :
|
||||
|
||||
```text
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
```
|
||||
|
||||
termine avec :
|
||||
|
||||
```text
|
||||
7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
Les tests couvrent le payload binaire possédé, le payload vide, la distinction message/fermeture propre, le split sender/receiver, les futures associées de send/receive/close et les catégories d'erreur transport-neutral.
|
||||
|
||||
## Conséquence
|
||||
|
||||
La frontière commune est acceptée sans Tokio, Tungstenite ni runtime concret. `alpha.3` peut ajouter une crate backend séparée qui implémente ce contrat, tout en conservant le gameplay et les moteurs indépendants du backend WebSocket.
|
||||
60
history/0.3.4/alpha.3.fix.1.md
Normal file
60
history/0.3.4/alpha.3.fix.1.md
Normal file
@@ -0,0 +1,60 @@
|
||||
<!-- file: history/0.3.4/alpha.3.fix.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-alpha.3.fix.1
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-alpha.3.fix.1` a été validée par l'utilisateur le 2026-09-21. Le correctif de manifeste ferme le défaut de `alpha.3` et valide effectivement le backend WebSocket prévu par cette tranche.
|
||||
|
||||
Aucun nouveau fix de `alpha.3` n'est requis. La suite peut ouvrir `0.3.4-alpha.4` pour les limites, timeouts et cas négatifs planifiés.
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
La gate fournie est propre :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 265 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compilé porte `0.3.4-alpha.3.fix.1`, y compris `game-realtime-transport-lib` et `game-realtime-websocket-lib`.
|
||||
|
||||
## Tests
|
||||
|
||||
Le contrat transport-neutral reste propre :
|
||||
|
||||
```text
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
Le backend WebSocket compile et son test d'intégration localhost passe :
|
||||
|
||||
```text
|
||||
cargo test -p game-realtime-websocket-lib --all-targets --all-features
|
||||
unit tests: 0 passed; 0 failed
|
||||
loopback: 1 passed; 0 failed
|
||||
```
|
||||
|
||||
Le loopback couvre le round-trip binaire dans les deux sens et la fermeture propre distante.
|
||||
|
||||
## Graphe de dépendances
|
||||
|
||||
`cargo tree -p game-realtime-websocket-lib --edges normal` confirme la frontière attendue :
|
||||
|
||||
- `game-realtime-websocket-lib` dépend directement de `game-realtime-transport-lib`, `futures-util 0.3.34`, `tokio 1.53.1`, `tokio-tungstenite 0.30.0` et `tracing 0.1.44` ;
|
||||
- `tokio-tungstenite 0.30.0` apporte `tungstenite 0.30.0` ;
|
||||
- aucune pile TLS `native-tls`/`rustls` n'apparaît dans le graphe normal ;
|
||||
- aucune crate gameplay ou moteur n'est introduite comme dépendance du backend.
|
||||
|
||||
## Conséquence
|
||||
|
||||
La baseline WebSocket de `alpha.3` est désormais réellement validée après son fix de manifeste. `alpha.4` peut ajouter la robustesse produit sans rouvrir l'API transport-neutral ni introduire de protocole session/gameplay.
|
||||
62
history/0.3.4/alpha.4.md
Normal file
62
history/0.3.4/alpha.4.md
Normal file
@@ -0,0 +1,62 @@
|
||||
<!-- file: history/0.3.4/alpha.4.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-alpha.4
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-alpha.4` a été validée par l'utilisateur le 2026-09-21. Les limites, deadlines et cas négatifs du backend WebSocket compilent dans le workspace, passent Clippy strict et tous les tests ciblés sont propres.
|
||||
|
||||
Aucun `alpha.4.fix.N` n'est requis. La revue suivant cette gate a néanmoins identifié qu'aucun smoke runtime distinct du harness de test n'avait encore été exécuté. La suite ouvre donc `0.3.4-alpha.5` pour cette preuve avant la beta.
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
La gate fournie est propre :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 267 file(s))
|
||||
Distribution layout audit: clean (49 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compilé porte `0.3.4-alpha.4`, y compris `game-realtime-transport-lib` et `game-realtime-websocket-lib`.
|
||||
|
||||
## Tests transport et WebSocket
|
||||
|
||||
Le contrat transport-neutral reste propre :
|
||||
|
||||
```text
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
Le backend WebSocket valide ensuite :
|
||||
|
||||
```text
|
||||
unit tests: 5 passed; 0 failed
|
||||
loopback: 1 passed; 0 failed
|
||||
robustness: 5 passed; 0 failed
|
||||
```
|
||||
|
||||
Les onze tests WebSocket couvrent notamment les defaults et invariants de configuration, les mappings capacité/backpressure, le round-trip binaire et close propre, les tailles hors limite, Text interdit, peer drop abrupt et timeout de handshake silencieux.
|
||||
|
||||
## Graphe de dépendances
|
||||
|
||||
`cargo tree -p game-realtime-websocket-lib --edges normal` confirme toujours la frontière attendue :
|
||||
|
||||
- `game-realtime-websocket-lib` dépend directement de `game-realtime-transport-lib`, `futures-util 0.3.34`, `tokio 1.53.1`, `tokio-tungstenite 0.30.0` et `tracing 0.1.44` ;
|
||||
- `tokio-tungstenite 0.30.0` apporte `tungstenite 0.30.0` ;
|
||||
- aucune pile TLS `native-tls`/`rustls` n'apparaît dans le graphe normal ;
|
||||
- aucune crate gameplay ou moteur ne dépend du backend WebSocket.
|
||||
|
||||
## Limite de la preuve
|
||||
|
||||
Cette gate est suffisante pour valider techniquement `alpha.4`, mais elle reste composée d'audits, compilation, lint et tests Cargo. Même le round-trip localhost est exécuté sous `#[test]`.
|
||||
|
||||
La validation runtime opérateur manque encore. `alpha.5` doit donc fournir un exécutable de smoke séparé qui utilise uniquement l'API publique sur un vrai socket loopback et dont le code de sortie atteste le résultat.
|
||||
85
history/0.3.4/alpha.5.md
Normal file
85
history/0.3.4/alpha.5.md
Normal file
@@ -0,0 +1,85 @@
|
||||
<!-- file: history/0.3.4/alpha.5.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-alpha.5
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-alpha.5` a été validée par l'utilisateur le 2026-09-21. La gate complète est propre et le smoke runtime WebSocket distinct du harness de test affiche explicitement `game-realtime-websocket-smoke: PASS` avec sortie zéro.
|
||||
|
||||
Aucun `alpha.5.fix.N` n'est requis. Les critères d'entrée en beta sont satisfaits ; la suite ouvre `0.3.4-beta.1` sans nouveau scope fonctionnel.
|
||||
|
||||
## Gates statiques et Rust
|
||||
|
||||
La gate fournie est propre :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 269 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compilé porte `0.3.4-alpha.5`, y compris le launcher `game-realtime-websocket-smoke`.
|
||||
|
||||
## Tests transport et WebSocket
|
||||
|
||||
Le contrat transport-neutral reste propre :
|
||||
|
||||
```text
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
Le backend WebSocket valide :
|
||||
|
||||
```text
|
||||
unit tests: 5 passed; 0 failed
|
||||
loopback: 1 passed; 0 failed
|
||||
robustness: 5 passed; 0 failed
|
||||
```
|
||||
|
||||
Les scénarios couvrent notamment les invariants de configuration, les mappings capacité/backpressure, le round-trip binaire, le close propre, les tailles hors limite, Text interdit, peer drop abrupt et timeout de handshake silencieux.
|
||||
|
||||
## Smoke runtime
|
||||
|
||||
La commande :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
s'exécute avec succès hors harness `#[test]` sur un port loopback éphémère. Le log fourni montre :
|
||||
|
||||
```text
|
||||
realtime WebSocket smoke started
|
||||
WebSocket listener bound address=127.0.0.1:42419
|
||||
loopback endpoint bound endpoint="ws://127.0.0.1:42419/"
|
||||
WebSocket peer accepted peer=127.0.0.1:42388
|
||||
WebSocket client connected endpoint="ws://127.0.0.1:42419/"
|
||||
game-realtime-websocket-smoke: PASS
|
||||
realtime WebSocket smoke passed
|
||||
```
|
||||
|
||||
Le port est alloué dynamiquement ; les valeurs ci-dessus décrivent uniquement cette exécution de validation.
|
||||
|
||||
## Graphe du smoke
|
||||
|
||||
`cargo tree -p game-realtime-websocket-smoke --edges normal` confirme que le launcher dépend directement de :
|
||||
|
||||
- `game-logging-lib` ;
|
||||
- `game-realtime-transport-lib` ;
|
||||
- `game-realtime-websocket-lib` ;
|
||||
- Tokio ;
|
||||
- tracing.
|
||||
|
||||
Le backend WebSocket reste sur Tokio `1.53.1`, `tokio-tungstenite 0.30.0` et Tungstenite `0.30.0`. Aucune pile TLS n'apparaît dans le graphe normal fourni.
|
||||
|
||||
## Conclusion alpha
|
||||
|
||||
La phase alpha a désormais produit et validé le contrat transport-neutral, le backend WebSocket, la robustesse, les cas négatifs et la preuve runtime publique. La beta peut se limiter à la validation large et à la recherche de régressions, conformément au plan actif.
|
||||
100
history/0.3.4/beta.1.md
Normal file
100
history/0.3.4/beta.1.md
Normal file
@@ -0,0 +1,100 @@
|
||||
<!-- file: history/0.3.4/beta.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-beta.1
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-beta.1` a été validée par l'utilisateur le 2026-09-21. La gate large est entièrement propre : audits statiques, formatage, check, Clippy strict, test workspace complet, smoke runtime WebSocket et graphes de dépendances.
|
||||
|
||||
Aucun `beta.1.fix.N` n'est requis. Le comportement peut être gelé en `0.3.4-rc.1` sans créer de beta supplémentaire artificielle.
|
||||
|
||||
## Gates statiques et compilation
|
||||
|
||||
La sortie utilisateur confirme :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 271 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compile intégralement sous `0.3.4-beta.1`, y compris les deux crates realtime et `game-realtime-websocket-smoke`.
|
||||
|
||||
## Test workspace complet
|
||||
|
||||
La commande :
|
||||
|
||||
```bash
|
||||
cargo test --workspace --all-targets --all-features
|
||||
```
|
||||
|
||||
est propre avec **53 tests réussis, 0 échec** sur l'ensemble des suites exécutées.
|
||||
|
||||
La gate couvre notamment :
|
||||
|
||||
- `engine-v1-common` : 8 tests ;
|
||||
- `engine-v1-platform-api` : 3 tests ;
|
||||
- `engine-v1-sdl` : 5 tests ;
|
||||
- `game-android-entrypoint` : 2 tests ;
|
||||
- `game-assets-lib` : 2 tests ;
|
||||
- `game-logging-lib` : 1 test d'intégration ;
|
||||
- `game-realtime-transport-lib` : 7 tests ;
|
||||
- `game-realtime-websocket-lib` : 5 tests unitaires, 1 loopback et 5 tests de robustesse ;
|
||||
- `game-reflex-poc` : 4 tests ;
|
||||
- `game-snake-poc` : 5 tests ;
|
||||
- `game-snake-poc-wasm` : 5 tests.
|
||||
|
||||
Les autres targets bin/lib de runners et hosts concernés s'exécutent également sans échec avec zéro test lorsqu'ils ne possèdent pas de suite propre.
|
||||
|
||||
## Smoke runtime
|
||||
|
||||
La commande :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
passe hors harness `#[test]` et affiche :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke: PASS
|
||||
```
|
||||
|
||||
Le log de validation montre un bind éphémère `127.0.0.1:39077`, une connexion cliente réelle, l'accept serveur et la fermeture propre. Ce numéro de port décrit uniquement cette exécution ; le smoke continue d'utiliser `127.0.0.1:0`.
|
||||
|
||||
## Graphe de dépendances
|
||||
|
||||
`cargo tree -p game-realtime-websocket-lib --edges normal` confirme le backend attendu :
|
||||
|
||||
- `futures-util 0.3.34` ;
|
||||
- `game-realtime-transport-lib` ;
|
||||
- Tokio `1.53.1` ;
|
||||
- `tokio-tungstenite 0.30.0` / Tungstenite `0.30.0` ;
|
||||
- tracing.
|
||||
|
||||
Aucune pile TLS n'apparaît dans le graphe normal fourni.
|
||||
|
||||
L'arbre inverse :
|
||||
|
||||
```bash
|
||||
cargo tree -i game-realtime-websocket-lib --workspace --edges normal
|
||||
```
|
||||
|
||||
ne contient qu'un consommateur workspace :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
Aucune crate sous `crates/games/` ou `crates/engines/` ne dépend donc du backend WebSocket.
|
||||
|
||||
## Conclusion beta
|
||||
|
||||
La beta valide la baseline fonctionnelle et ses frontières sans régression transverse. La RC peut se limiter au gel de publication, à la documentation durable, au changelog, à l'historique et à la préparation du prompt `0.3.5`.
|
||||
83
history/0.3.4/rc.1.md
Normal file
83
history/0.3.4/rc.1.md
Normal file
@@ -0,0 +1,83 @@
|
||||
<!-- file: history/0.3.4/rc.1.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Historique 0.3.4-rc.1
|
||||
|
||||
## Statut
|
||||
|
||||
`0.3.4-rc.1` a été validée par l'utilisateur le 2026-09-21. La candidate gelée passe les audits, la compilation workspace, Clippy strict, les suites realtime ciblées, le smoke runtime et le contrôle inverse des dépendances sans défaut de publication.
|
||||
|
||||
Aucun `rc.1.fix.N` n'est requis. La candidate peut être promue mécaniquement vers la stable `0.3.4`.
|
||||
|
||||
## Gates statiques et compilation
|
||||
|
||||
La sortie utilisateur confirme :
|
||||
|
||||
```text
|
||||
General Rust rule audit: clean
|
||||
Rust export completeness audit: 0 candidate(s)
|
||||
games.sasedev workspace audit: clean
|
||||
Markdown table audit: clean (5 table(s), 277 file(s))
|
||||
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
|
||||
cargo fmt --all: clean
|
||||
cargo fmt --all -- --check: clean
|
||||
cargo check --workspace: clean
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
|
||||
```
|
||||
|
||||
Le workspace compilé porte `0.3.4-rc.1`, y compris les deux crates realtime et le launcher de smoke.
|
||||
|
||||
## Tests realtime
|
||||
|
||||
Le contrat transport-neutral passe :
|
||||
|
||||
```text
|
||||
cargo test -p game-realtime-transport-lib --all-targets --all-features
|
||||
7 passed; 0 failed
|
||||
```
|
||||
|
||||
Le backend WebSocket passe ses trois ensembles de tests :
|
||||
|
||||
```text
|
||||
unit tests: 5 passed; 0 failed
|
||||
loopback: 1 passed; 0 failed
|
||||
robustness: 5 passed; 0 failed
|
||||
```
|
||||
|
||||
La RC valide donc **18 tests realtime ciblés, 0 échec**. Le full workspace de 53 tests avait déjà été validé en `beta.1` et n'a pas été répété puisque la RC n'a modifié aucun code Rust ni dépendance fonctionnelle.
|
||||
|
||||
## Smoke runtime
|
||||
|
||||
La commande :
|
||||
|
||||
```bash
|
||||
cargo run -p game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
passe hors harness `#[test]` et affiche :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke: PASS
|
||||
```
|
||||
|
||||
Le log fourni montre un bind loopback éphémère, l'accept serveur, la connexion cliente, l'échange prévu et la fermeture propre. Le port observé `46581` est propre à cette exécution ; le smoke continue de binder `127.0.0.1:0`.
|
||||
|
||||
## Frontière de dépendances
|
||||
|
||||
L'arbre inverse :
|
||||
|
||||
```bash
|
||||
cargo tree -i game-realtime-websocket-lib --workspace --edges normal
|
||||
```
|
||||
|
||||
ne contient qu'un consommateur workspace :
|
||||
|
||||
```text
|
||||
game-realtime-websocket-smoke
|
||||
```
|
||||
|
||||
Aucune crate moteur ou gameplay ne dépend donc du backend WebSocket.
|
||||
|
||||
## Conclusion RC
|
||||
|
||||
Le comportement realtime et sa documentation de publication sont gelés et validés. La stable peut se limiter aux changements mécaniques de version, changelog, roadmap, clôture du plan, historique RC et delta final.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: history/000-README.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Historique transitoire validé
|
||||
|
||||
@@ -10,13 +10,14 @@ Cette arborescence conserve l'historique durable des jalons transitoires effecti
|
||||
```text
|
||||
history/
|
||||
└── X.Y.Z/
|
||||
├── 0-pre.N.md
|
||||
├── 0-pre.N.fix.M.md
|
||||
├── 1-alpha.N.md
|
||||
├── 2-beta.N.md
|
||||
└── 3-rc.N.md
|
||||
├── alpha.N.md
|
||||
├── alpha.N.fix.M.md
|
||||
├── beta.N.md
|
||||
├── beta.N.fix.M.md
|
||||
├── rc.N.md
|
||||
└── rc.N.fix.M.md
|
||||
```
|
||||
|
||||
Un fichier n'est créé qu'après validation du jalon qu'il décrit et devient ensuite immuable.
|
||||
Un fichier n'est créé qu'après validation du jalon qu'il décrit et devient ensuite immuable. Les répertoires historiques antérieurs à `0.3.4` conservent volontairement leurs anciens labels `0-pre.N`, `1-alpha.N`, `2-beta.N` et `3-rc.N` ; ils ne sont ni renommés ni réécrits.
|
||||
|
||||
Le fichier delta correspondant décrit la livraison candidate et les commandes attendues ; le fichier `history/` décrit le résultat validé.
|
||||
|
||||
446
prompts/005-V0_3_4_START_PROMPT.md
Normal file
446
prompts/005-V0_3_4_START_PROMPT.md
Normal file
@@ -0,0 +1,446 @@
|
||||
<!-- file: prompts/005-V0_3_4_START_PROMPT.md -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Prompt de démarrage `0.3.4` — transport realtime et baseline WebSocket
|
||||
|
||||
## 1. Base exacte et cible
|
||||
|
||||
Partir uniquement de la version stable/taggée :
|
||||
|
||||
```text
|
||||
v0.3.3
|
||||
```
|
||||
|
||||
Si ce prompt est lu avant la publication effective de `0.3.3`, terminer d'abord la RC puis la release stable `0.3.3`.
|
||||
|
||||
Version cible active :
|
||||
|
||||
```text
|
||||
0.3.4
|
||||
```
|
||||
|
||||
Première tranche attendue après migration de gouvernance :
|
||||
|
||||
```text
|
||||
0.3.4-alpha.1
|
||||
```
|
||||
|
||||
`alpha.1` remplace à partir de cette session l'ancien rôle obligatoire de `0-pre.1` : audit, requirements, sizing, risques, validation et création/révision du plan actif avant développement lourd.
|
||||
|
||||
## 2. Migration de nomenclature — première action de la session
|
||||
|
||||
La décision est acquise depuis la fin de `0.3.3`. La nouvelle convention canonique devient :
|
||||
|
||||
```text
|
||||
X.Y.Z-alpha.N
|
||||
X.Y.Z-alpha.N.fix.M
|
||||
X.Y.Z-beta.N
|
||||
X.Y.Z-beta.N.fix.M
|
||||
X.Y.Z-rc.N
|
||||
X.Y.Z-rc.N.fix.M
|
||||
X.Y.Z
|
||||
```
|
||||
|
||||
Supprimer pour les **nouvelles versions** les préfixes numériques historiques `0-`, `1-`, `2-`, `3-` ainsi que la phase `pre`. `alpha.1` porte désormais le cadrage obligatoire.
|
||||
|
||||
Avant de créer le premier delta `0.3.4-alpha.1`, mettre à jour au minimum les règles et contrôles actifs concernés :
|
||||
|
||||
```text
|
||||
RULES.md
|
||||
docs/rules/VERSION_WORKFLOW.md
|
||||
docs/rules/RULES_SESSION_PLANNING.md
|
||||
docs/rules/PROMPT_STRUCTURE.md
|
||||
docs/rules/RULES_DOCUMENTATION.md
|
||||
scripts/audit_project_workspace_rules.py
|
||||
```
|
||||
|
||||
Chercher également les références normatives/courantes à l'ancienne convention sous `docs/rules/`, `scripts/` et les README actifs, puis les réconcilier lorsque leur sens est prospectif.
|
||||
|
||||
### Immutabilité historique absolue
|
||||
|
||||
Ne jamais renommer, déplacer ou réécrire pour cette migration les anciens jalons déjà livrés, notamment :
|
||||
|
||||
```text
|
||||
deltas/0.1.0/ ... deltas/0.3.3/
|
||||
history/0.1.0/ ... history/0.3.3/
|
||||
anciennes entrées CHANGELOG décrivant ces versions
|
||||
anciens prompts qui documentent leur workflow réel
|
||||
```
|
||||
|
||||
Ces fichiers restent des preuves historiques avec leurs identifiants `0-pre`, `1-alpha`, `2-beta`, `3-rc`. Les audits doivent accepter cet historique tout en imposant la nouvelle convention aux versions futures.
|
||||
|
||||
Les archives delta futures suivent naturellement :
|
||||
|
||||
```text
|
||||
games-sasedev-X.Y.Z-alpha.N-delta.zip
|
||||
games-sasedev-X.Y.Z-alpha.N.fix.M-delta.zip
|
||||
games-sasedev-X.Y.Z-beta.N-delta.zip
|
||||
games-sasedev-X.Y.Z-rc.N-delta.zip
|
||||
```
|
||||
|
||||
## 3. Sources de vérité
|
||||
|
||||
Lire intégralement, dans cet ordre :
|
||||
|
||||
```text
|
||||
RULES.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
docs/000-README.md
|
||||
```
|
||||
|
||||
Puis au minimum :
|
||||
|
||||
```text
|
||||
docs/rules/RULES_GENERAL.md
|
||||
docs/rules/RULES_RUST.md
|
||||
docs/rules/RULES_PROJECT.md
|
||||
docs/rules/RULES_DOCUMENTATION.md
|
||||
docs/rules/FILE_CONTRACTS.md
|
||||
docs/rules/VERSION_WORKFLOW.md
|
||||
docs/rules/RULES_COMMANDS.md
|
||||
docs/rules/RULES_VALIDATION_MATRIX.md
|
||||
docs/rules/RULES_SESSION_PLANNING.md
|
||||
docs/rules/PROMPT_STRUCTURE.md
|
||||
```
|
||||
|
||||
Transmission architecture/réseau :
|
||||
|
||||
```text
|
||||
docs/architecture/012-MODULAR_LAYERING_AND_OWNERSHIP.md
|
||||
docs/architecture/013-NETWORK_AND_SERVER_ARCHITECTURE.md
|
||||
docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md
|
||||
docs/studies/006-REALTIME_MULTIPLAYER_SERVER_STUDY.md
|
||||
docs/studies/009-PLATFORM_POC_CANDIDATES.md
|
||||
docs/studies/011-PLATFORM_POC_VALIDATION_MATRIX.md
|
||||
docs/studies/012-PLATFORM_POC_SEQUENCE.md
|
||||
history/0.3.3/
|
||||
deltas/0.3.3/
|
||||
```
|
||||
|
||||
Auditer ensuite les crates réelles susceptibles de porter les contrats transport/client/serveur. Ne créer aucune abstraction ou crate uniquement parce que ce prompt mentionne un nom possible : `alpha.1` doit déterminer l'ownership physique à partir de l'architecture actuelle.
|
||||
|
||||
## 4. État hérité attendu de 0.3.3
|
||||
|
||||
La stable `0.3.3` doit laisser :
|
||||
|
||||
- gameplay/moteur Rust partagé inchangé ;
|
||||
- Desktop SDL3, Web/WASM et POC Tauri existants ;
|
||||
- voie Android productive SDL3 natif/Java/JNI ;
|
||||
- build Android possédé par Gradle/Cargo sans orchestrateur Python ;
|
||||
- APK Debug universal et AAB Release pour `arm64-v8a`, `armeabi-v7a`, `x86_64`, `x86` ;
|
||||
- `minSdk 21` réellement fumé sur x86 ;
|
||||
- compatibilité 16 KB 64 bits réellement fumée ;
|
||||
- Gradle système `>= 9.6.0` et JDK courant de l'environnement ;
|
||||
- aucune dépendance réseau realtime imposée au moteur de rendu ou au gameplay.
|
||||
|
||||
Ne pas rouvrir le pipeline Android dans `0.3.4` sauf régression directement provoquée par le nouveau scope.
|
||||
|
||||
## 5. Mission 0.3.4
|
||||
|
||||
Introduire une **API de transport realtime** et une baseline WebSocket fondée sur Tokio + `tokio-tungstenite`, sans implémenter encore le serveur Uroburas Mode 3 ni figer prématurément tout le protocole multijoueur.
|
||||
|
||||
La frontière durable reste conceptuellement :
|
||||
|
||||
```text
|
||||
transport
|
||||
↓
|
||||
wire codec
|
||||
↓
|
||||
session protocol
|
||||
↓
|
||||
synchronization
|
||||
↓
|
||||
authoritative simulation
|
||||
```
|
||||
|
||||
`0.3.4` se concentre sur le premier étage et les contrats minimaux nécessaires pour le tester proprement. Une socket ouverte ne doit jamais être confondue avec une session gameplay synchronisée.
|
||||
|
||||
## 6. Scope inclus
|
||||
|
||||
`alpha.1` doit inventorier et décider au minimum :
|
||||
|
||||
- emplacement de l'API de transport realtime et sens des dépendances ;
|
||||
- séparation client/server et éventuels types communs réellement nécessaires ;
|
||||
- connexion, fermeture, send/receive, erreurs et cancellation async ;
|
||||
- ownership Tokio/runtime sans le propager inutilement dans le gameplay ;
|
||||
- baseline `tokio-tungstenite` WebSocket ;
|
||||
- stratégie de codec initiale suffisamment simple pour le POC sans figer le protocole final ;
|
||||
- timeouts, backpressure et limites élémentaires ;
|
||||
- test loopback/local déterministe ;
|
||||
- logging/tracing transport séparé des futurs domaines session/sync/simulation ;
|
||||
- compatibilité future avec un autre transport, notamment le POC WebTransport/QUIC de `0.3.5`, sans abstraction spéculative excessive.
|
||||
|
||||
## 7. Hors scope
|
||||
|
||||
Ne pas transformer `0.3.4` en serveur de jeu complet. Sont notamment hors scope sauf nécessité démontrée par le POC transport :
|
||||
|
||||
- Uroburas Mode 3 ;
|
||||
- matchmaking complet ;
|
||||
- simulation authoritative de jeu ;
|
||||
- snapshots/deltas métier définitifs ;
|
||||
- prediction/reconciliation ou rollback ;
|
||||
- Redis/NATS/Kafka ;
|
||||
- persistence gameplay ;
|
||||
- auth complète ;
|
||||
- scaling/sharding/regions ;
|
||||
- WebTransport/QUIC de production, réservé au POC comparatif suivant ;
|
||||
- Actix Web comme dépendance obligatoire du data plane realtime.
|
||||
|
||||
## 8. Contraintes architecturales
|
||||
|
||||
Préserver :
|
||||
|
||||
- async-first pour les nouvelles API réseau ;
|
||||
- aucune dépendance transport dans les crates gameplay ;
|
||||
- distinction stricte transport/session/sync/simulation ;
|
||||
- façade `lib.rs` et ownership modulaire conformes aux règles Rust du dépôt ;
|
||||
- `tracing` pour les diagnostics ;
|
||||
- erreurs explicites, cancellation et fermeture propres ;
|
||||
- pas de framework générique massif avant deux consommateurs réels ;
|
||||
- WebSocket baseline remplaçable/comparable par le futur POC WebTransport sans réécrire le gameplay.
|
||||
|
||||
## 9. Cadrage alpha.1 obligatoire
|
||||
|
||||
Avant développement lourd :
|
||||
|
||||
1. effectuer la migration de nomenclature décrite plus haut ;
|
||||
2. auditer la stable `0.3.3` et les règles après migration ;
|
||||
3. réévaluer les études réseau existantes ;
|
||||
4. vérifier les versions actuelles et contraintes des dépendances réellement envisagées ;
|
||||
5. décider l'ownership physique des crates/modules ;
|
||||
6. définir le contrat API minimal et les erreurs ;
|
||||
7. définir les tests loopback et les gates ;
|
||||
8. évaluer le besoin éventuel d'un petit demo/CLI uniquement s'il apporte une preuve utile ;
|
||||
9. créer `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` ;
|
||||
10. produire un forecast souple jusqu'à stable, avec beta de validation large, RC gelée et release mécanique.
|
||||
|
||||
Si le scope dépasse raisonnablement une seule session/version, le scinder dès `alpha.1`.
|
||||
|
||||
## 10. Validation et responsabilité
|
||||
|
||||
Les audits statiques peuvent être exécutés par le générateur. Les builds, tests et smokes finaux restent côté utilisateur et ne sont jamais déclarés réussis sans sortie réelle.
|
||||
|
||||
Dès qu'un fichier Rust/Cargo change :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
```
|
||||
|
||||
Les tests restent ciblés par défaut ; `cargo test --workspace --all-targets --all-features` est réservé aux jalons larges/finalisation planifiés.
|
||||
|
||||
Toute commande nécessitant un changement de répertoire utilise un sous-shell `(cd ... && ...)`.
|
||||
|
||||
## 11. Deltas, historique et changelog
|
||||
|
||||
À partir de `0.3.4`, les nouveaux fichiers utilisent uniquement la nouvelle nomenclature :
|
||||
|
||||
```text
|
||||
deltas/0.3.4/alpha.1.md
|
||||
history/0.3.4/alpha.1.md # créé seulement après validation, par le jalon suivant
|
||||
```
|
||||
|
||||
Un échec de `alpha.1` produit `alpha.1.fix.1`, pas un retour vers l'ancienne convention.
|
||||
|
||||
`CHANGELOG.md` reste normalement silencieux avant RC. `ROADMAP.md` reste macroscopique. Aucun ancien delta/history n'est renommé pour homogénéiser l'apparence.
|
||||
|
||||
## 12. Forecast initial non contraignant
|
||||
|
||||
Ce forecast est une **hypothèse de travail initiale**, pas un contrat de numérotation. Il doit être relu et réécrit dans le plan `0.3.4` pendant `alpha.1` à partir des constats réels de la baseline, des dépendances actuelles, des tests possibles et du sizing obtenu.
|
||||
|
||||
Règles de lecture du forecast :
|
||||
|
||||
- les jalons ci-dessous donnent un **ordre logique probable**, pas une obligation de produire exactement ce nombre de prereleases ;
|
||||
- une tranche peut être fusionnée avec la suivante si son contenu devient trivial et reste validable comme une unité cohérente ;
|
||||
- une tranche doit être scindée si elle dépasse le budget habituel de 15–30 minutes, mélange plusieurs responsabilités indépendantes ou nécessite des validations hétérogènes ;
|
||||
- tout défaut découvert après livraison d'un jalon reste dans `<jalon>.fix.N` tant que son objectif fonctionnel n'est pas rouvert ;
|
||||
- aucune tranche ne doit être créée uniquement pour respecter la séquence ci-dessous ;
|
||||
- si l'audit `alpha.1` montre qu'une partie relève plutôt de `0.3.5` ou d'une nouvelle version, la reporter avant développement lourd ;
|
||||
- le plan actif sous `docs/plans/` devient l'autorité prévisionnelle après `alpha.1` et peut diverger de ce forecast sans réécrire ce prompt historique.
|
||||
|
||||
### `0.3.4-alpha.1` — migration de gouvernance + audit + contrat transport
|
||||
|
||||
Objectif probable : fermer tout le cadrage avant de figer une API réseau.
|
||||
|
||||
Travail attendu :
|
||||
|
||||
- appliquer la nouvelle nomenclature aux **règles prospectives uniquement** ;
|
||||
- adapter les audits qui reconnaissent les prereleases sans modifier l'historique `0.1.x`–`0.3.3` ;
|
||||
- vérifier que la baseline stable `0.3.3` est propre ;
|
||||
- relire les études réseau et confronter leurs hypothèses au code actuel ;
|
||||
- vérifier les versions actuelles de Tokio, `tokio-tungstenite`, `tungstenite`, TLS éventuel et autres dépendances réellement nécessaires ;
|
||||
- décider si l'API durable vit dans une crate dédiée, une crate existante ou plusieurs crates ;
|
||||
- définir les responsabilités client/server communes sans créer un framework abstrait prématuré ;
|
||||
- définir le modèle d'erreur, fermeture, cancellation, timeout, backpressure et limites ;
|
||||
- décider le codec de **preuve** initial sans le présenter comme protocole gameplay final ;
|
||||
- définir la matrice de tests et les gates de chaque tranche ;
|
||||
- créer `docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md` avec un forecast révisé jusqu'à stable.
|
||||
|
||||
Livrables probables : gouvernance migrée, étude/résultats d'audit si nécessaire, plan actif, ownership décidé, premier contrat API suffisamment précis pour démarrer l'implémentation suivante.
|
||||
|
||||
Gate de sortie : aucune ambiguïté majeure sur l'ownership ou le sens des dépendances ; scope `0.3.4` dimensionné pour tenir dans la session ; validations futures identifiées ; aucune dépendance réseau introduite dans les crates gameplay.
|
||||
|
||||
### `0.3.4-alpha.2` — API de transport async minimale
|
||||
|
||||
Objectif probable : introduire la frontière transport indépendamment de WebSocket.
|
||||
|
||||
Travail candidat :
|
||||
|
||||
- types de connexion/endpoint strictement nécessaires ;
|
||||
- contrat async send/receive ou équivalent retenu par l'audit ;
|
||||
- fermeture locale/distante explicite ;
|
||||
- cancellation propre ;
|
||||
- erreurs typées ;
|
||||
- taille/limites élémentaires des messages ;
|
||||
- tests unitaires du contrat sans réseau réel lorsque possible ;
|
||||
- façade `lib.rs` et documentation publique conformes aux règles du dépôt.
|
||||
|
||||
À éviter : codec métier complet, session gameplay, heartbeat produit définitif, simulation authoritative ou abstraction multi-transport sophistiquée.
|
||||
|
||||
Gate de sortie : API consommable par un backend WebSocket futur et testable indépendamment de celui-ci ; aucun couplage direct au gameplay.
|
||||
|
||||
### `0.3.4-alpha.3` — backend WebSocket `tokio-tungstenite`
|
||||
|
||||
Objectif probable : fournir la première implémentation concrète de la frontière transport.
|
||||
|
||||
Travail candidat :
|
||||
|
||||
- connect/listen WebSocket selon l'ownership retenu ;
|
||||
- mapping frames ↔ messages du transport ;
|
||||
- fermeture WebSocket propre ;
|
||||
- propagation des erreurs ;
|
||||
- timeouts/cancellation minimum nécessaires ;
|
||||
- tracing du domaine transport ;
|
||||
- test loopback localhost déterministe client ↔ serveur ;
|
||||
- absence de dépendance à Axum si elle n'apporte rien à la preuve transport.
|
||||
|
||||
Gate de sortie : round-trip local reproductible, fermeture des deux côtés vérifiée, aucune tâche async orpheline connue, erreurs observables.
|
||||
|
||||
### `0.3.4-alpha.4` — robustesse : limites, backpressure et lifecycle
|
||||
|
||||
Cette tranche est **conditionnelle** : la fusion avec `alpha.3` est préférable si les mécanismes restent petits et cohérents.
|
||||
|
||||
Travail candidat :
|
||||
|
||||
- bornes de taille et files internes ;
|
||||
- comportement explicite en cas de consommateur lent ;
|
||||
- timeout de connexion/lecture/écriture uniquement là où il est justifié ;
|
||||
- comportement sur fermeture distante, connexion interrompue et cancellation ;
|
||||
- vérification qu'aucune boucle ne spin ou ne masque une erreur ;
|
||||
- tests négatifs ciblés ;
|
||||
- métriques/tracing minimaux permettant de diagnostiquer le transport sans mélanger session/sync/simulation.
|
||||
|
||||
Gate de sortie : les principaux modes d'échec du transport sont déterministes, bornés et testés.
|
||||
|
||||
### `0.3.4-alpha.5` — intégration consommateur/démo minimale, si utile
|
||||
|
||||
Cette tranche n'existe que si une preuve supplémentaire apporte une valeur réelle après `alpha.3/alpha.4`.
|
||||
|
||||
Candidats acceptables :
|
||||
|
||||
- petit demo/CLI client-server ;
|
||||
- intégration à un launcher de POC sans logique gameplay réseau ;
|
||||
- harness de test permettant de mesurer connexion, échange et fermeture.
|
||||
|
||||
Ne pas créer d'application ou de crate uniquement pour « montrer » l'API si les tests loopback prouvent déjà tout le contrat utile.
|
||||
|
||||
Gate de sortie : preuve reproductible du chemin public du transport, sans introduire la session gameplay.
|
||||
|
||||
### `0.3.4-alpha.N` — consolidation de développement
|
||||
|
||||
Avant beta, réserver explicitement une tranche de consolidation si les alpha précédentes ont réellement introduit plusieurs composants.
|
||||
|
||||
Cette tranche peut couvrir :
|
||||
|
||||
- réconciliation docs/API/README/USAGE réellement nécessaires ;
|
||||
- suppression de duplication ou dette née pendant les alpha, uniquement si bornée ;
|
||||
- audit du graphe de dépendances ;
|
||||
- vérification que `transport -> wire codec -> session -> sync -> simulation` reste respecté ;
|
||||
- confirmation de ce qui est volontairement reporté à `0.3.5` ou `0.4.x` ;
|
||||
- préparation d'une gate beta large et reproductible.
|
||||
|
||||
Elle peut être fusionnée avec la dernière alpha fonctionnelle si aucune consolidation autonome n'est nécessaire.
|
||||
|
||||
### `0.3.4-beta.1` — validation large
|
||||
|
||||
Objectif probable : valider le produit de `0.3.4` comme baseline transport complète, pas ajouter des fonctionnalités.
|
||||
|
||||
Gate candidate :
|
||||
|
||||
- audits dépôt applicables ;
|
||||
- format/check/Clippy workspace ;
|
||||
- tests ciblés des nouvelles crates/modules ;
|
||||
- test workspace complet si le plan le confirme ;
|
||||
- tests loopback client/server ;
|
||||
- tests de fermeture/cancellation/backpressure retenus ;
|
||||
- éventuel demo/CLI reproductible si créé ;
|
||||
- vérification du graphe de dépendances ;
|
||||
- confirmation qu'aucune crate gameplay ne dépend du backend WebSocket ;
|
||||
- documentation opérateur/API cohérente avec le comportement réel.
|
||||
|
||||
Un défaut trouvé ici produit `beta.1.fix.N` tant que le périmètre reste fermé. Une capacité réseau manquante importante impose de revenir à une alpha suivante au lieu de maquiller une nouvelle fonctionnalité en fix beta.
|
||||
|
||||
### `0.3.4-rc.1` — candidate gelée
|
||||
|
||||
Objectif probable : candidate de publication sans nouveau comportement.
|
||||
|
||||
Travail attendu :
|
||||
|
||||
- scope fonctionnel gelé ;
|
||||
- revalidation des gates importantes de beta ;
|
||||
- consolidation `CHANGELOG.md` ;
|
||||
- état macro `ROADMAP.md` cohérent ;
|
||||
- historique applicable ;
|
||||
- documentation finale de l'API transport et de la baseline WebSocket ;
|
||||
- préparation/vérification du prompt `0.3.5` WebTransport/QUIC avec transmission précise de la baseline WebSocket à comparer ;
|
||||
- aucune optimisation ou abstraction nouvelle sans défaut de release démontré.
|
||||
|
||||
Les correctifs strictement nécessaires restent sous `rc.1.fix.N`. Toute réouverture fonctionnelle revient à une phase adaptée avant une nouvelle RC.
|
||||
|
||||
### `0.3.4` — promotion stable mécanique
|
||||
|
||||
La stable doit normalement se limiter à :
|
||||
|
||||
- version stable ;
|
||||
- historique RC ;
|
||||
- changelog stable ;
|
||||
- clôture du plan ;
|
||||
- statut roadmap si requis ;
|
||||
- delta/release final ;
|
||||
- ajustements strictement mécaniques du prompt suivant si une référence de baseline devient certaine.
|
||||
|
||||
Aucune nouvelle API, dépendance ou décision architecturale ne doit apparaître pour la première fois dans la stable.
|
||||
|
||||
### Branches de redécoupage explicitement admises
|
||||
|
||||
Le forecast initial doit être révisé sans hésitation dans les cas suivants :
|
||||
|
||||
```text
|
||||
API transport + backend WebSocket restent très petits
|
||||
-> fusion possible alpha.2 + alpha.3
|
||||
|
||||
backpressure/lifecycle nécessitent un travail autonome
|
||||
-> conserver alpha.4 ou la scinder
|
||||
|
||||
le demo n'apporte aucune preuve supplémentaire
|
||||
-> supprimer alpha.5
|
||||
|
||||
un vrai wire codec réutilisable devient indispensable
|
||||
-> décider en alpha.1 s'il appartient encore à 0.3.4
|
||||
ou s'il mérite une version distincte
|
||||
|
||||
l'abstraction WebTransport exige déjà des choix QUIC
|
||||
-> reporter à 0.3.5 ; ne pas polluer 0.3.4
|
||||
|
||||
le serveur de jeu/session protocol commence à apparaître
|
||||
-> arrêter et reporter au scope Uroburas approprié
|
||||
```
|
||||
|
||||
Le critère n'est donc pas « suivre tous les numéros », mais obtenir à la fin de `0.3.4` une frontière de transport claire, un backend WebSocket reproductible et suffisamment robuste pour devenir la baseline comparative de `0.3.5`.
|
||||
|
||||
## 13. Condition de fin de session
|
||||
|
||||
La session ne vise pas une simple prerelease : elle doit mener `0.3.4` jusqu'à une stable concrète, sauf découverte imposant explicitement un redécoupage de scope vers une version suivante.
|
||||
404
prompts/006-V0_3_5_START_PROMPT.md
Normal file
404
prompts/006-V0_3_5_START_PROMPT.md
Normal file
@@ -0,0 +1,404 @@
|
||||
<!-- file: prompts/006-V0_3_5_START_PROMPT.md -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Prompt de démarrage `0.3.5` — POC WebTransport/QUIC et fallback WebSocket
|
||||
|
||||
## 1. Base exacte et cible
|
||||
|
||||
Partir uniquement de la version stable/taggée :
|
||||
|
||||
```text
|
||||
v0.3.4
|
||||
```
|
||||
|
||||
Ce prompt est destiné à être utilisé depuis la stable taggée `v0.3.4`. Une archive annoncée comme téléchargement ZIP du tag Gitea est autoritaire conformément à `CMD-GIT-003` et `CMD-GIT-004` ; l'absence de `.git` n'est pas un défaut.
|
||||
|
||||
Version cible :
|
||||
|
||||
```text
|
||||
0.3.5
|
||||
```
|
||||
|
||||
Première tranche attendue :
|
||||
|
||||
```text
|
||||
0.3.5-alpha.1
|
||||
```
|
||||
|
||||
`alpha.1` est obligatoirement le gate de cadrage : audit de la stable, recherche des stacks WebTransport/QUIC actuelles, requirements, sizing, risques, matrice plateforme, validations et création du plan actif avant développement lourd.
|
||||
|
||||
## 2. Sources de vérité
|
||||
|
||||
Lire intégralement, dans cet ordre :
|
||||
|
||||
```text
|
||||
RULES.md
|
||||
ROADMAP.md
|
||||
CHANGELOG.md
|
||||
docs/000-README.md
|
||||
```
|
||||
|
||||
Puis au minimum :
|
||||
|
||||
```text
|
||||
docs/rules/RULES_GENERAL.md
|
||||
docs/rules/RULES_RUST.md
|
||||
docs/rules/RULES_PROJECT.md
|
||||
docs/rules/RULES_DOCUMENTATION.md
|
||||
docs/rules/FILE_CONTRACTS.md
|
||||
docs/rules/VERSION_WORKFLOW.md
|
||||
docs/rules/RULES_COMMANDS.md
|
||||
docs/rules/RULES_VALIDATION_MATRIX.md
|
||||
docs/rules/RULES_SESSION_PLANNING.md
|
||||
docs/rules/PROMPT_STRUCTURE.md
|
||||
```
|
||||
|
||||
Transmission réseau et plateforme :
|
||||
|
||||
```text
|
||||
docs/architecture/012-MODULAR_LAYERING_AND_OWNERSHIP.md
|
||||
docs/architecture/013-NETWORK_AND_SERVER_ARCHITECTURE.md
|
||||
docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md
|
||||
docs/studies/006-REALTIME_MULTIPLAYER_SERVER_STUDY.md
|
||||
docs/studies/009-PLATFORM_POC_CANDIDATES.md
|
||||
docs/studies/011-PLATFORM_POC_VALIDATION_MATRIX.md
|
||||
docs/studies/012-PLATFORM_POC_SEQUENCE.md
|
||||
docs/plans/004-V0_3_4_REALTIME_TRANSPORT_WEBSOCKET_PLAN.md
|
||||
history/0.3.4/
|
||||
deltas/0.3.4/
|
||||
crates/common/game-realtime-transport-lib/README.md
|
||||
crates/common/game-realtime-websocket-lib/README.md
|
||||
crates/common/game-realtime-websocket-lib/USAGE.md
|
||||
```
|
||||
|
||||
Le plan `0.3.4` est historique une fois la stable publiée ; `0.3.5-alpha.1` crée `docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md`, qui devient alors l'autorité prévisionnelle active.
|
||||
|
||||
## 3. État hérité attendu de 0.3.4
|
||||
|
||||
La stable `0.3.4` doit laisser une frontière realtime déjà testée :
|
||||
|
||||
```text
|
||||
transport-neutral contract
|
||||
↓
|
||||
WebSocket backend
|
||||
↓
|
||||
future wire/session layers
|
||||
```
|
||||
|
||||
État attendu :
|
||||
|
||||
- `game-realtime-transport-lib` sans Tokio, WebSocket, QUIC, HTTP ou TLS ;
|
||||
- payload binaire opaque `TransportMessage` ;
|
||||
- split `RealtimeConnection -> Sender + Receiver` ;
|
||||
- fermeture distante propre distinguée des erreurs ;
|
||||
- catégories d'erreur transport-neutral ;
|
||||
- `game-realtime-websocket-lib` comme backend de référence Tokio/tokio-tungstenite ;
|
||||
- client `ws://`, listener serveur, close propre, limites, deadlines et tracing ;
|
||||
- aucun runtime Tokio privé ni task backend détachée ;
|
||||
- aucun moteur ou gameplay dépendant du backend WebSocket ;
|
||||
- `game-realtime-websocket-smoke` comme preuve runtime localhost hors harness `#[test]` ;
|
||||
- aucune politique TLS directe, aucun wire codec final, aucune session joueur/room et aucune simulation authoritative.
|
||||
|
||||
La stable `0.3.4` hérite des validations réellement obtenues pendant `beta.1` et `rc.1` : workspace complet validé en beta, gates de publication realtime ciblées validées en RC, smoke runtime `PASS` et frontière de dépendances confirmée. Relire `history/0.3.4/` et `deltas/0.3.4/rel.001.md` comme preuves détaillées au lieu d'inventer ou d'extrapoler des validations supplémentaires.
|
||||
|
||||
## 4. Mission 0.3.5
|
||||
|
||||
Évaluer puis prototyper **WebTransport/QUIC** comme second transport realtime en conservant WebSocket comme baseline/fallback de référence.
|
||||
|
||||
Le résultat recherché n'est pas « remplacer WebSocket parce que QUIC est plus moderne ». `0.3.5` doit répondre avec des preuves :
|
||||
|
||||
- un backend WebTransport/QUIC est-il viable sur les plateformes réellement visées ?
|
||||
- peut-il consommer la frontière `game-realtime-transport-lib` sans la déformer artificiellement ?
|
||||
- quelles différences de sémantique existent entre WebSocket et WebTransport : streams, datagrams, ordering, reliability, fermeture, backpressure ?
|
||||
- le bénéfice mesuré justifie-t-il la complexité TLS/HTTP3/QUIC et les contraintes navigateur/infrastructure ?
|
||||
- quel fallback WebSocket est réellement nécessaire et à quel niveau doit-il être choisi ?
|
||||
|
||||
La frontière conceptuelle reste :
|
||||
|
||||
```text
|
||||
transport
|
||||
↓
|
||||
wire codec
|
||||
↓
|
||||
session protocol
|
||||
↓
|
||||
synchronization
|
||||
↓
|
||||
authoritative simulation
|
||||
```
|
||||
|
||||
`0.3.5` reste concentrée sur le transport et la comparaison. Elle ne doit pas profiter du POC QUIC pour concevoir prématurément les couches supérieures.
|
||||
|
||||
## 5. Recherche alpha.1 obligatoire
|
||||
|
||||
Avant de choisir une crate ou d'écrire un backend, vérifier sur les sources actuelles :
|
||||
|
||||
- état et versions des crates Rust candidates WebTransport/QUIC ;
|
||||
- maintenance, MSRV, Tokio/runtime, licence et dépendances ;
|
||||
- support client et serveur natif ;
|
||||
- possibilité réelle côté navigateur/WASM et relation éventuelle avec l'API WebTransport du navigateur ;
|
||||
- support Android natif pertinent pour la trajectoire du projet ;
|
||||
- exigences TLS/certificats et contraintes de développement localhost ;
|
||||
- HTTP/3/QUIC sous-jacent et besoins UDP ;
|
||||
- compatibilité Debian Stable pour un serveur auto-hébergé ;
|
||||
- interaction avec reverse proxy/edge lorsqu'elle est réellement nécessaire ;
|
||||
- support des streams bidirectionnels/unidirectionnels et des datagrams ;
|
||||
- comportement de backpressure, limites, fermeture et cancellation ;
|
||||
- maturité des APIs de test loopback/local ;
|
||||
- disponibilité et stabilité des APIs navigateur sur les cibles réellement testables.
|
||||
|
||||
Ne pas figer dans ce prompt un nom de crate WebTransport/QUIC : `alpha.1` doit comparer l'état actuel de l'écosystème au moment du développement.
|
||||
|
||||
## 6. Question centrale : compatibilité du contrat 0.3.4
|
||||
|
||||
Le contrat `game-realtime-transport-lib` a été volontairement minimal et orienté « messages binaires fiables/ordonnés » parce que WebSocket était le premier backend.
|
||||
|
||||
`alpha.1` doit déterminer si :
|
||||
|
||||
1. WebTransport peut implémenter ce contrat proprement via un stream fiable sans perte significative ;
|
||||
2. les datagrams WebTransport apportent une capability réellement utile qui ne rentre pas dans ce contrat ;
|
||||
3. une capability supplémentaire doit être séparée au lieu d'élargir `RealtimeConnection` ;
|
||||
4. le POC doit garder deux chemins distincts afin de comparer avant toute extraction commune.
|
||||
|
||||
Interdiction de modifier `game-realtime-transport-lib` uniquement pour rendre les APIs WebSocket et QUIC esthétiquement identiques. Toute évolution du contrat commun doit être motivée par au moins deux consommateurs réels et un besoin sémantique partagé.
|
||||
|
||||
## 7. Scope inclus
|
||||
|
||||
Le scope candidat comprend :
|
||||
|
||||
- audit/recherche WebTransport/QUIC ;
|
||||
- plan `0.3.5` et matrice de preuves ;
|
||||
- second backend transport si la stack retenue est suffisamment viable ;
|
||||
- preuve client/serveur locale ou équivalent reproductible ;
|
||||
- payload binaire opaque réutilisant le contrat commun lorsque cela est sémantiquement correct ;
|
||||
- fermeture/cancellation, limites, timeouts et erreurs du nouveau backend ;
|
||||
- tracing séparé ;
|
||||
- preuve du fallback WebSocket ou d'un choix de transport au niveau approprié, uniquement si le POC le justifie ;
|
||||
- comparaison mesurée WebSocket vs WebTransport/QUIC ;
|
||||
- documentation des contraintes plateforme/TLS/infrastructure ;
|
||||
- smoke runtime distinct du harness de test si nécessaire pour prouver le nouveau chemin.
|
||||
|
||||
## 8. Hors scope
|
||||
|
||||
Sauf nécessité strictement démontrée par le POC transport, ne pas introduire :
|
||||
|
||||
- Uroburas Mode 3 ;
|
||||
- matchmaking ;
|
||||
- auth complète ;
|
||||
- protocole session joueur/room ;
|
||||
- codec wire définitif ;
|
||||
- snapshot/delta gameplay ;
|
||||
- prediction/reconciliation ou rollback ;
|
||||
- simulation authoritative ;
|
||||
- persistence gameplay ;
|
||||
- Redis/NATS/Kafka ;
|
||||
- sharding/regions ;
|
||||
- CDN/edge complexe ;
|
||||
- orchestration Kubernetes ;
|
||||
- remplacement de la stack Web/API Actix ;
|
||||
- framework générique multi-transport massif.
|
||||
|
||||
## 9. Mesures et comparaison
|
||||
|
||||
Le POC doit définir avant mesure les métriques réellement utiles. Candidats :
|
||||
|
||||
- temps d'établissement ;
|
||||
- latence aller-retour de petits payloads ;
|
||||
- débit sur payloads bornés ;
|
||||
- overhead observable ;
|
||||
- comportement sous plusieurs messages en vol ;
|
||||
- coût CPU/mémoire si la mesure est suffisamment reproductible ;
|
||||
- différence fiable/ordonnée vs datagram lorsque ce dernier est réellement disponible ;
|
||||
- complexité opérationnelle : certificats, UDP, ports, reverse proxy et debugging.
|
||||
|
||||
Un benchmark loopback ne suffit pas à conclure sur Internet/mobile. Il sert à vérifier l'implémentation et à comparer des overheads locaux contrôlés. Toute conclusion produit doit distinguer mesure locale, comportement protocolaire connu et test réseau externe réellement effectué.
|
||||
|
||||
Les benchmarks doivent rester bornés, reproductibles et ne pas devenir une infrastructure de performance disproportionnée pour un POC.
|
||||
|
||||
## 10. Fallback WebSocket
|
||||
|
||||
WebSocket reste la baseline fonctionnelle jusqu'à preuve contraire.
|
||||
|
||||
Le fallback ne doit pas être codé dans le gameplay. `alpha.1` doit décider l'ownership du choix de transport parmi :
|
||||
|
||||
- composition/application ;
|
||||
- client/platform adapter ;
|
||||
- petit sélecteur technique dédié si deux backends réels le justifient.
|
||||
|
||||
Ne pas créer un `TransportManager`, registry dynamique ou système de plugins uniquement pour choisir entre deux transports POC.
|
||||
|
||||
## 11. Plateformes à challenger
|
||||
|
||||
Le plan `alpha.1` doit expliciter ce qui est réellement testable dans la session :
|
||||
|
||||
- Linux natif : client/server local de référence ;
|
||||
- navigateur/WASM : important pour WebTransport, mais seulement avec une chaîne de test réaliste ;
|
||||
- Android : vérifier la trajectoire et la compatibilité, sans rouvrir le pipeline multi-ABI `0.3.3` si aucun code Android n'est touché ;
|
||||
- iOS/macOS : documenter les contraintes connues mais ne pas bloquer la version en l'absence d'environnement Apple.
|
||||
|
||||
Une plateforme non testée ne doit pas être présentée comme validée.
|
||||
|
||||
## 12. TLS, certificats et sécurité transport
|
||||
|
||||
Contrairement à la baseline locale `ws://`, WebTransport navigateur peut imposer des contraintes de sécurité/certificats. `alpha.1` doit vérifier les exigences actuelles au lieu de les supposer.
|
||||
|
||||
La version peut introduire uniquement la politique minimale nécessaire au POC. Ne pas transformer `0.3.5` en projet PKI complet. Les certificats de développement, s'ils sont nécessaires, restent hors secrets du dépôt et leur génération/installation doit être documentée de façon reproductible.
|
||||
|
||||
Aucune désactivation dangereuse de validation TLS ne devient un chemin produit permanent uniquement pour simplifier un smoke.
|
||||
|
||||
## 13. Documentation README/USAGE
|
||||
|
||||
Appliquer `DOC-CRATE-*` à tout nouveau composant :
|
||||
|
||||
- backend WebTransport/QUIC durable : README probable ;
|
||||
- configuration/workflow/certificats non triviaux : USAGE probablement utile ;
|
||||
- launcher de benchmark/smoke très petit : documentation locale seulement si le plan central ne couvre pas suffisamment son rôle ;
|
||||
- ne jamais créer des fichiers vides ou redondants par cérémonie.
|
||||
|
||||
La documentation propre à la fonctionnalité évolue avec la tranche qui l'introduit ; la RC ne doit pas être utilisée pour reporter toute documentation à la fin.
|
||||
|
||||
## 14. Validation et responsabilité
|
||||
|
||||
Les audits statiques peuvent être exécutés par le générateur. Les builds, tests, benchmarks et smokes finaux restent côté utilisateur conformément à `CMD-BUILD-005` et ne sont jamais déclarés réussis sans sortie réelle.
|
||||
|
||||
Dès qu'un fichier Rust ou Cargo change :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
```
|
||||
|
||||
Les tests restent ciblés pendant l'implémentation. `cargo test --workspace --all-targets --all-features` est réservé aux jalons larges planifiés ou lorsqu'une évolution transverse le justifie.
|
||||
|
||||
`cargo tree` est utilisé lorsque les dépendances changent ou lorsqu'une frontière doit être prouvée, pas comme cérémonie à chaque tranche.
|
||||
|
||||
Toute commande nécessitant un `cd` doit être englobée dans un sous-shell `(cd ... && ...)`.
|
||||
|
||||
## 15. Deltas, history, changelog et roadmap
|
||||
|
||||
À partir de `0.3.5`, conserver la nomenclature :
|
||||
|
||||
```text
|
||||
0.3.5-alpha.N
|
||||
0.3.5-alpha.N.fix.M
|
||||
0.3.5-beta.N
|
||||
0.3.5-beta.N.fix.M
|
||||
0.3.5-rc.N
|
||||
0.3.5-rc.N.fix.M
|
||||
0.3.5
|
||||
```
|
||||
|
||||
Chaque delta indique sa base, son scope, les fichiers touchés et les validations attendues.
|
||||
|
||||
`history/0.3.5/<jalon>.md` est créé uniquement par la tranche suivante ou son fix après validation réelle du jalon précédent.
|
||||
|
||||
`CHANGELOG.md` reste normalement silencieux avant la RC. `ROADMAP.md` reste macroscopique et ne change que si le scope, l'ordre ou le statut évolue réellement.
|
||||
|
||||
Un échec fermé d'une tranche produit `<jalon>.fix.N`. Une validation propre permet de poursuivre automatiquement vers la tranche planifiée suivante. Une session doit fermer au minimum une version concrète jusqu'à sa stable ; ne pas s'arrêter volontairement sur une prerelease.
|
||||
|
||||
## 16. Cadrage alpha.1 obligatoire
|
||||
|
||||
Avant développement lourd :
|
||||
|
||||
1. auditer la stable `v0.3.4` et vérifier ses gates/historique ;
|
||||
2. relire le contrat transport et le backend WebSocket réellement livrés ;
|
||||
3. rechercher l'écosystème WebTransport/QUIC actuel ;
|
||||
4. établir une matrice plateforme/runtime/TLS ;
|
||||
5. comparer les stacks candidates et justifier le choix ;
|
||||
6. décider si le contrat transport-neutral reste inchangé ;
|
||||
7. décider ownership physique des nouvelles crates/outils ;
|
||||
8. définir les preuves locales, navigateur éventuel, benchmarks et smokes ;
|
||||
9. définir précisément ce que signifie « fallback WebSocket » dans ce POC ;
|
||||
10. créer `docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md` ;
|
||||
11. produire un forecast révisé jusqu'à stable et redécouper la version si le scope excède une session raisonnable.
|
||||
|
||||
## 17. Forecast initial non contraignant
|
||||
|
||||
Le forecast ci-dessous est une hypothèse de départ. `alpha.1` doit le réviser à partir des résultats réels et le plan actif devient ensuite l'autorité prévisionnelle.
|
||||
|
||||
### `0.3.5-alpha.1` — audit WebTransport/QUIC et design du POC
|
||||
|
||||
- audit stable `0.3.4` ;
|
||||
- recherche des stacks actuelles ;
|
||||
- matrice plateforme/TLS/runtime ;
|
||||
- challenge du contrat commun ;
|
||||
- stratégie fallback ;
|
||||
- métriques et gates ;
|
||||
- plan actif.
|
||||
|
||||
Aucun backend lourd n'est créé avant fermeture de ces décisions.
|
||||
|
||||
### `0.3.5-alpha.2` — backend minimal et loopback
|
||||
|
||||
Si `alpha.1` confirme la viabilité :
|
||||
|
||||
- créer le backend retenu avec dépendances minimales ;
|
||||
- établir client/server local ;
|
||||
- transporter un payload binaire ;
|
||||
- close/cancellation et erreurs de base ;
|
||||
- tests loopback déterministes ;
|
||||
- tracing dédié.
|
||||
|
||||
Si le contrat commun ne convient pas, documenter le besoin avant de le modifier.
|
||||
|
||||
### `0.3.5-alpha.3` — plateforme/fallback réel
|
||||
|
||||
Selon les résultats :
|
||||
|
||||
- preuve navigateur/WASM si elle est techniquement et opérationnellement réalisable ;
|
||||
- ou preuve native plus complète si le navigateur exige une infrastructure disproportionnée à cette tranche ;
|
||||
- fallback WebSocket au niveau d'ownership retenu ;
|
||||
- tests des deux chemins sans couplage au gameplay.
|
||||
|
||||
Cette tranche peut être fusionnée ou scindée selon TLS/certificats et tooling réellement nécessaires.
|
||||
|
||||
### `0.3.5-alpha.4` — mesures et robustesse conditionnelles
|
||||
|
||||
Si nécessaire :
|
||||
|
||||
- mesures WebSocket vs WebTransport/QUIC ;
|
||||
- streams vs datagrams lorsque réellement supportés ;
|
||||
- limites, timeouts, backpressure et peer drop ;
|
||||
- smoke runtime/benchmark borné ;
|
||||
- décision documentée sur la valeur réelle du second transport.
|
||||
|
||||
Ne pas créer cette tranche si `alpha.2/alpha.3` ferment déjà ces preuves proprement.
|
||||
|
||||
### `0.3.5-beta.1` — validation large
|
||||
|
||||
- workspace complet ;
|
||||
- smokes réellement retenus ;
|
||||
- graphes de dépendances ;
|
||||
- vérification qu'aucun gameplay/engine ne dépend d'un backend concret ;
|
||||
- comparaison et limitations documentées.
|
||||
|
||||
Un défaut fermé produit `beta.1.fix.N`. Une capacité majeure manquante réouvre une alpha.
|
||||
|
||||
### `0.3.5-rc.1` — candidate gelée
|
||||
|
||||
- aucun nouveau comportement volontaire ;
|
||||
- consolidation durable ;
|
||||
- `CHANGELOG.md` ;
|
||||
- `ROADMAP.md` si statut macro à réconcilier ;
|
||||
- historique beta ;
|
||||
- prompt `0.3.6` préparant la consolidation des POC plateforme/réseau ;
|
||||
- gates de publication proportionnelles aux changements depuis beta.
|
||||
|
||||
### `0.3.5` — stable
|
||||
|
||||
Promotion autant que possible mécanique : version stable, historique RC, changelog stable, clôture du plan, roadmap, delta final et ajustement mécanique du prompt `0.3.6`.
|
||||
|
||||
## 18. Critère de réussite produit de 0.3.5
|
||||
|
||||
La version est réussie même si la conclusion est de **ne pas adopter WebTransport immédiatement**, à condition que le POC fournisse une réponse technique solide et reproductible.
|
||||
|
||||
La décision finale doit pouvoir distinguer clairement :
|
||||
|
||||
- WebTransport/QUIC retenu comme second backend utile ;
|
||||
- viable mais reporté faute de bénéfice suffisant ou de contraintes plateforme/infrastructure ;
|
||||
- non viable pour la trajectoire actuelle ;
|
||||
- besoin d'une étude complémentaire explicitement reportée.
|
||||
|
||||
Le résultat ne doit jamais être forcé pour justifier le temps investi dans le POC.
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_distribution_layout.py
|
||||
# version: 14
|
||||
# version: 18
|
||||
|
||||
"""Audit the static distribution layout expected by supported POCs."""
|
||||
|
||||
@@ -13,10 +13,18 @@ import sys
|
||||
|
||||
FORBIDDEN_PATHS = (
|
||||
"builds",
|
||||
"Android/gradlew",
|
||||
"Android/gradlew.bat",
|
||||
"Android/gradle/wrapper/gradle-wrapper.jar",
|
||||
"Android/gradle/wrapper/gradle-wrapper.properties",
|
||||
"Android/game-reflex-poc/src/main/jniLibs",
|
||||
"Android/game-snake-poc/src/main/jniLibs",
|
||||
"scripts/build_android_rust.py",
|
||||
)
|
||||
|
||||
|
||||
REQUIRED_PATHS = (
|
||||
"crates/apps/game-realtime-websocket-smoke/Cargo.toml",
|
||||
"crates/apps/game-reflex-poc-desktop/Cargo.toml",
|
||||
"crates/apps/game-snake-poc-desktop/Cargo.toml",
|
||||
"crates/apps/game-reflex-poc-tauri/Cargo.toml",
|
||||
@@ -60,7 +68,8 @@ REQUIRED_PATHS = (
|
||||
"Web/game-snake-poc/frontend/ts/provenance.ts",
|
||||
"Android/game-reflex-poc/build.gradle",
|
||||
"Android/game-snake-poc/build.gradle",
|
||||
"scripts/build_android_rust.py",
|
||||
"Android/settings.gradle",
|
||||
"Android/gradle/sasedev-rust-android.gradle",
|
||||
"scripts/build_reflex_tauri_wasm.py",
|
||||
"assets/common",
|
||||
"assets/game-reflex-poc",
|
||||
@@ -302,6 +311,76 @@ def audit_snake_tauri_generated_android(root: pathlib.Path) -> list[str]:
|
||||
return violations
|
||||
|
||||
|
||||
|
||||
def audit_android_native_gradle(root: pathlib.Path) -> list[str]:
|
||||
"""Validate the minimum Gradle contract and multi-ABI native Rust consumers."""
|
||||
|
||||
violations: list[str] = []
|
||||
settings_path = root / "Android/settings.gradle"
|
||||
native_script_path = root / "Android/gradle/sasedev-rust-android.gradle"
|
||||
snake_build_path = root / "Android/game-snake-poc/build.gradle"
|
||||
reflex_build_path = root / "Android/game-reflex-poc/build.gradle"
|
||||
required = (settings_path, native_script_path, snake_build_path, reflex_build_path)
|
||||
if not all(path.exists() for path in required):
|
||||
return violations
|
||||
|
||||
settings = settings_path.read_text(encoding="utf-8")
|
||||
settings_fragments = (
|
||||
'import org.gradle.util.GradleVersion',
|
||||
'GradleVersion.version("9.6.0")',
|
||||
'GradleVersion.current()',
|
||||
'currentGradleVersion < minimumGradleVersion',
|
||||
)
|
||||
for fragment in settings_fragments:
|
||||
if fragment not in settings:
|
||||
violations.append(f"DIST-LAYOUT-072: Android settings missing minimum Gradle gate: {fragment}")
|
||||
|
||||
native_script = native_script_path.read_text(encoding="utf-8")
|
||||
native_fragments = (
|
||||
'spec.commandLine(cargoCommand)',
|
||||
'"cargo",',
|
||||
'"ndk",',
|
||||
'cargoCommand.add("--release")',
|
||||
'releaseBuild.set(selectedReleaseBuild)',
|
||||
'onVariants(selector().all())',
|
||||
'"--no-default-features",',
|
||||
'variant.sources.jniLibs.addGeneratedSourceDirectory',
|
||||
'generated/sasedevNative/',
|
||||
'libgame_android_entrypoint.so',
|
||||
'libSDL3.so',
|
||||
)
|
||||
for fragment in native_fragments:
|
||||
if fragment not in native_script:
|
||||
violations.append(f"DIST-LAYOUT-074: Gradle-owned Rust Android build missing contract: {fragment}")
|
||||
if "build_android_rust.py" in native_script or "python" in native_script.lower():
|
||||
violations.append("DIST-LAYOUT-075: Gradle-owned Rust Android build must not delegate orchestration to Python")
|
||||
|
||||
consumers = (
|
||||
(
|
||||
"Snake",
|
||||
snake_build_path.read_text(encoding="utf-8"),
|
||||
'ext.sasedevRustGameFeature = "snake"',
|
||||
),
|
||||
(
|
||||
"Reflex",
|
||||
reflex_build_path.read_text(encoding="utf-8"),
|
||||
'ext.sasedevRustGameFeature = "reflex"',
|
||||
),
|
||||
)
|
||||
shared_fragments = (
|
||||
'ext.sasedevRustAndroidAbis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]',
|
||||
'ext.sasedevRustAndroidApi = 21',
|
||||
'minSdk 21',
|
||||
"abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'",
|
||||
'apply from: rootProject.file("gradle/sasedev-rust-android.gradle")',
|
||||
)
|
||||
for label, content, feature_fragment in consumers:
|
||||
for fragment in (feature_fragment, *shared_fragments):
|
||||
if fragment not in content:
|
||||
violations.append(f"DIST-LAYOUT-076: {label} Android multi-ABI consumer missing contract: {fragment}")
|
||||
return violations
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Validate that every static distribution boundary exists."""
|
||||
|
||||
@@ -322,6 +401,7 @@ def main() -> int:
|
||||
contract_violations.extend(audit_snake_web_integration(root))
|
||||
contract_violations.extend(audit_snake_tauri_android_integration(root))
|
||||
contract_violations.extend(audit_snake_tauri_generated_android(root))
|
||||
contract_violations.extend(audit_android_native_gradle(root))
|
||||
if missing:
|
||||
for relative in missing:
|
||||
print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_project_workspace_rules.py
|
||||
# version: 7
|
||||
# version: 8
|
||||
|
||||
"""Audit mechanically verifiable games.sasedev workspace boundaries."""
|
||||
|
||||
@@ -12,7 +12,15 @@ import re
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
SEMVER = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0-pre|1-alpha|2-beta|3-rc)\.[1-9][0-9]*(?:\.fix\.[1-9][0-9]*)?)?$", re.ASCII)
|
||||
CURRENT_SEMVER = re.compile(
|
||||
r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:alpha|beta|rc)\.[1-9][0-9]*(?:\.fix\.[1-9][0-9]*)?)?$",
|
||||
re.ASCII,
|
||||
)
|
||||
LEGACY_MILESTONE_SEMVER = re.compile(
|
||||
r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-(?:0-pre|1-alpha|2-beta|3-rc)\.[1-9][0-9]*(?:\.fix\.[1-9][0-9]*)?$",
|
||||
re.ASCII,
|
||||
)
|
||||
LEGACY_HISTORY_BASE_VERSIONS = frozenset({"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.3.3"})
|
||||
|
||||
|
||||
def is_generated_path(path: pathlib.Path, root: pathlib.Path) -> bool:
|
||||
@@ -32,7 +40,7 @@ def main() -> int:
|
||||
errors: list[str] = []
|
||||
manifest = tomllib.loads((root / "Cargo.toml").read_text(encoding="utf-8"))
|
||||
workspace_version = manifest.get("workspace", {}).get("package", {}).get("version")
|
||||
if not isinstance(workspace_version, str) or SEMVER.fullmatch(workspace_version) is None:
|
||||
if not isinstance(workspace_version, str) or CURRENT_SEMVER.fullmatch(workspace_version) is None:
|
||||
errors.append("VERSION-001: workspace.package.version does not follow the canonical games.sasedev SemVer scheme")
|
||||
members = manifest.get("workspace", {}).get("members", [])
|
||||
for member in members:
|
||||
@@ -47,7 +55,7 @@ def main() -> int:
|
||||
if version.get("workspace") is not True:
|
||||
errors.append(f"GAME-WS-004: {relative}: table version must use workspace = true")
|
||||
elif isinstance(version, str):
|
||||
if SEMVER.fullmatch(version) is None:
|
||||
if CURRENT_SEMVER.fullmatch(version) is None:
|
||||
errors.append(f"GAME-WS-005: {relative}: explicit crate version does not follow the canonical games.sasedev SemVer scheme")
|
||||
else:
|
||||
errors.append(f"GAME-WS-004: {relative}: crate must inherit or explicitly own a version")
|
||||
@@ -84,7 +92,9 @@ def main() -> int:
|
||||
errors.append(f"DOC-010: invalid history base version directory: {history_path.relative_to(root).as_posix()}")
|
||||
label = filename[:-3]
|
||||
candidate = f"{base_version}-{label}"
|
||||
if SEMVER.fullmatch(candidate) is None:
|
||||
is_current = CURRENT_SEMVER.fullmatch(candidate) is not None
|
||||
is_legacy = base_version in LEGACY_HISTORY_BASE_VERSIONS and LEGACY_MILESTONE_SEMVER.fullmatch(candidate) is not None
|
||||
if not is_current and not is_legacy:
|
||||
errors.append(f"DOC-010: invalid history milestone filename: {history_path.relative_to(root).as_posix()}")
|
||||
|
||||
docs_root = root / "docs"
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/build_android_rust.py
|
||||
# version: 3
|
||||
|
||||
"""Build one Android Rust game entrypoint and stage SDL3 and Rust jniLibs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
ABI_TO_TRIPLE = {
|
||||
"arm64-v8a": "aarch64-linux-android",
|
||||
"armeabi-v7a": "armv7-linux-androideabi",
|
||||
"x86_64": "x86_64-linux-android",
|
||||
"x86": "i686-linux-android",
|
||||
}
|
||||
GAME_TO_MODULE = {"reflex": "game-reflex-poc", "snake": "game-snake-poc"}
|
||||
|
||||
def read_gradle_property(properties_path: pathlib.Path, property_name: str) -> str:
|
||||
"""Read one required Android Gradle property."""
|
||||
prefix = f"{property_name}="
|
||||
for raw_line in properties_path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if line.startswith(prefix):
|
||||
return line.split("=", 1)[1].strip()
|
||||
raise RuntimeError(f"{property_name} is missing from Android/gradle.properties")
|
||||
|
||||
def read_sdl3_aar_name(properties_path: pathlib.Path) -> str:
|
||||
"""Read the configured SDL3 AAR filename."""
|
||||
return read_gradle_property(properties_path, "sdl3AarName")
|
||||
|
||||
def find_sdl3_member(archive: zipfile.ZipFile, abi: str) -> str:
|
||||
"""Return the SDL3 shared-library member for one Android ABI."""
|
||||
candidates = (
|
||||
f"prefab/modules/SDL3/libs/android.{abi}/libSDL3.so",
|
||||
f"jni/{abi}/libSDL3.so",
|
||||
)
|
||||
members = set(archive.namelist())
|
||||
for candidate in candidates:
|
||||
if candidate in members:
|
||||
return candidate
|
||||
suffix = f"/libs/android.{abi}/libSDL3.so"
|
||||
fallback = sorted(member for member in members if member.endswith(suffix))
|
||||
if len(fallback) == 1:
|
||||
return fallback[0]
|
||||
raise RuntimeError(
|
||||
f"SDL3 shared library for ABI {abi} is missing from the AAR; "
|
||||
f"expected Prefab member {candidates[0]}"
|
||||
)
|
||||
|
||||
def extract_sdl3_library(
|
||||
aar_path: pathlib.Path,
|
||||
abi: str,
|
||||
link_dir: pathlib.Path,
|
||||
runtime_dir: pathlib.Path,
|
||||
) -> pathlib.Path:
|
||||
"""Extract SDL3 for both Rust linking and APK runtime staging."""
|
||||
link_dir.mkdir(parents=True, exist_ok=True)
|
||||
runtime_dir.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(aar_path) as archive:
|
||||
member = find_sdl3_member(archive, abi)
|
||||
payload = archive.read(member)
|
||||
link_target = link_dir / "libSDL3.so"
|
||||
runtime_target = runtime_dir / "libSDL3.so"
|
||||
link_target.write_bytes(payload)
|
||||
runtime_target.write_bytes(payload)
|
||||
return link_target
|
||||
|
||||
def main() -> int:
|
||||
"""Build the requested game entrypoint for one Android ABI."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("game", choices=sorted(GAME_TO_MODULE))
|
||||
parser.add_argument("--abi", choices=sorted(ABI_TO_TRIPLE), default="arm64-v8a")
|
||||
parser.add_argument("--release", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
root = pathlib.Path(__file__).resolve().parent.parent
|
||||
properties_path = root / "Android" / "gradle.properties"
|
||||
aar_name = read_sdl3_aar_name(properties_path)
|
||||
ndk_version = read_gradle_property(properties_path, "androidNdkVersion")
|
||||
aar_path = root / "Android" / "libs" / aar_name
|
||||
if not aar_path.is_file():
|
||||
print(f"missing SDL3 AAR: {aar_path}", file=sys.stderr)
|
||||
return 2
|
||||
module = GAME_TO_MODULE[arguments.game]
|
||||
jni_root = root / "Android" / module / "src" / "main" / "jniLibs"
|
||||
runtime_dir = jni_root / arguments.abi
|
||||
link_dir = root / "Android" / "build" / "rust-link" / arguments.abi
|
||||
try:
|
||||
extract_sdl3_library(aar_path, arguments.abi, link_dir, runtime_dir)
|
||||
except (OSError, RuntimeError, zipfile.BadZipFile) as error:
|
||||
print(str(error), file=sys.stderr)
|
||||
return 2
|
||||
environment = os.environ.copy()
|
||||
android_home = environment.get("ANDROID_HOME")
|
||||
if not android_home:
|
||||
print("ANDROID_HOME is required to resolve the project NDK", file=sys.stderr)
|
||||
return 2
|
||||
ndk_home = pathlib.Path(android_home) / "ndk" / ndk_version
|
||||
if not ndk_home.is_dir():
|
||||
print(f"configured Android NDK is missing: {ndk_home}", file=sys.stderr)
|
||||
return 2
|
||||
environment["ANDROID_NDK_HOME"] = str(ndk_home)
|
||||
rustflags = environment.get("RUSTFLAGS", "").strip()
|
||||
link_flag = f"-L native={link_dir}"
|
||||
environment["RUSTFLAGS"] = f"{rustflags} {link_flag}".strip()
|
||||
environment["CARGO_NDK_PLATFORM"] = "21"
|
||||
command = [
|
||||
"cargo",
|
||||
"ndk",
|
||||
"-t",
|
||||
arguments.abi,
|
||||
"-o",
|
||||
str(jni_root),
|
||||
"build",
|
||||
"-p",
|
||||
"game-android-entrypoint",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
arguments.game,
|
||||
]
|
||||
if arguments.release:
|
||||
command.append("--release")
|
||||
completed = subprocess.run(command, cwd=root, env=environment, check=False)
|
||||
if completed.returncode != 0:
|
||||
return completed.returncode
|
||||
expected = runtime_dir / "libgame_android_entrypoint.so"
|
||||
if not expected.is_file():
|
||||
print(f"missing Rust Android library after cargo-ndk build: {expected}", file=sys.stderr)
|
||||
return 3
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user