28 Commits

Author SHA1 Message Date
76a955bd3d 0.3.1 2026-09-21 08:22:30 +02:00
e5a4d7fe44 0.3.1-2-beta.1 2026-09-21 08:11:36 +02:00
d8e7951a04 0.3.1-2-beta.1 2026-09-21 07:47:08 +02:00
c806490212 0.3.1-0-pre.4.fix.1 2026-09-21 07:22:50 +02:00
a36b666d22 0.3.1-0-pre.4 2026-09-21 01:21:33 +02:00
4f677dbc77 0.3.1-0-pre.3 2026-09-21 01:13:37 +02:00
e4b657d0d3 0.3.1-0-pre.2.fix.3 2026-09-20 23:23:14 +02:00
1835464bb3 0.3.1-0-pre.2.fix.2 2026-09-20 21:49:23 +02:00
bded27c944 0.3.1-0-pre.2.fix.1 2026-09-20 21:38:21 +02:00
51371c1b24 0.3.1-0-pre.2 2026-09-20 21:30:12 +02:00
ba718ce4ec 0.3.1-0-pre.1 2026-09-20 18:16:32 +02:00
778da67e22 0.3.0 2026-09-20 15:32:00 +02:00
a9620ef84c 0.3.0-3-rc.1 2026-09-20 15:25:55 +02:00
45c31d4721 0.3.0-2-beta.2.fix.2 2026-09-20 15:07:25 +02:00
54121215b4 0.3.0-2-beta.2.fix.1 2026-09-20 14:52:37 +02:00
fca477eb23 0.3.0-2-beta.2 2026-09-20 14:30:55 +02:00
ff1ec09d68 0.3.0-2-beta.1 2026-09-20 14:20:07 +02:00
3e1153eb8c 0.3.0-0-pre.5-fix.1 2026-09-20 14:03:04 +02:00
7f0635ec9d 0.3.0-0-pre.5 2026-09-20 13:58:13 +02:00
86b8af3b61 0.3.0-0-pre.4-fix.3 2026-09-20 13:40:19 +02:00
023825b89b 0.3.0-0-pre.4-fix.2 2026-09-20 13:33:39 +02:00
f433e99a12 0.3.0-0-pre.4-fix.1 2026-09-20 13:26:07 +02:00
c4b68b8a60 0.3.0-0-pre.4 2026-09-20 13:14:40 +02:00
c7ccf57723 0.3.0-0-pre.3 2026-09-20 07:03:34 +02:00
a6a59222d6 0.3.0-0-pre.2 2026-09-20 06:53:40 +02:00
6f37fea9ed 0.3.0-0-pre.1-fix.2 2026-09-20 06:43:07 +02:00
d16648c34c 0.3.0-0-pre.1-fix.1 2026-09-20 06:28:37 +02:00
54079accb5 0.3.0-0-pre.1 2026-09-20 06:15:50 +02:00
136 changed files with 10122 additions and 154 deletions

View File

@@ -1,8 +1,49 @@
<!-- file: CHANGELOG.md -->
<!-- version: 12 -->
<!-- version: 16 -->
# Changelog
## 0.3.1 — 2026-09-21
- publication stable du second host Snake sous Tauri Android, conservé comme POC de référence et non comme voie de production Android ;
- validation du gameplay partagé via `game-snake-poc-wasm`, du Canvas, des contrôles tactiles, des assets, du lifecycle mobile/WebView, du tracing et de la provenance runtime ;
- validation dun APK Debug universal autonome contenant `arm64-v8a` et `x86_64`, installé et lancé sur Galaxy S9+ ARM64 réel et AVD API 36 x86_64 ;
- validation finale de `0.3.1-3-rc.1` sans défaut nécessitant un correctif RC, puis promotion mécanique vers la stable ;
- maintien de `0.3.2` en différé et préparation de `0.3.3` pour Android SDL3 natif multi-ABI, APK universal de test, AAB de distribution et évaluation du `minSdk`.
Les détails des phases `pre`, `beta`, `rc` et de leurs correctifs restent dans `deltas/0.3.1/` et `history/0.3.1/`.
## 0.3.1-3-rc.1 — 2026-09-21
- gel fonctionnel du POC Snake Tauri Android de référence après validation beta du workspace complet et du packaging Android autonome ;
- APK Debug universal construit avec les cibles Rust `aarch64-linux-android` et `x86_64-linux-android`, puis installé et lancé avec succès sur Galaxy S9+ ARM64 et AVD API 36 x86_64 ;
- host Tauri conservé comme POC comparatif : la voie Android productive reste SDL3 natif/Java/JNI ;
- absence dextraction Web/Tauri supplémentaire et absence dAAB Tauri volontaire : les choix de distribution Android productive sont reportés au POC SDL3 natif ;
- `0.3.2` Tauri Desktop reste différée faute de bénéfice produit/monétisation démontré ; la prochaine version active préparée est `0.3.3`, Android SDL3 natif multi-ABI, avec AAB et compatibilité Android ancienne à évaluer.
La RC nouvre aucun nouveau scope. Seuls les correctifs de publication autorisés par `VER-RC-*` peuvent produire un `3-rc.1.fix.N`.
## 0.3.0 — 2026-09-20
- publication stable du premier POC Snake Web direct, construit autour du gameplay Rust partagé, d'un adapter WASM dédié et d'un host navigateur Vite/TypeScript ;
- validation du shell Bootstrap 5/Bootswatch + SimpleBar, du Canvas au ratio logique `12 × 20`, des contrôles clavier/pointer, du lifecycle navigateur, des assets et de la provenance runtime ;
- validation de la non-régression Desktop SDL3 et des builds release Desktop et Web/WASM ;
- promotion mécanique de `0.3.0-3-rc.1` après gate workspace complète, 34 tests réussis et absence de défaut nécessitant un correctif RC ;
- préparation de `0.3.1` pour le second host Snake Tauri Android, sans ouvrir ce scope dans la release `0.3.0`.
Les détails des phases `pre`, `beta`, `rc` et de leurs correctifs restent dans `deltas/0.3.0/` et `history/0.3.0/`.
## 0.3.0-3-rc.1 — 2026-09-20
- gel fonctionnel du premier POC Snake Web direct après validation beta et consolidation documentaire ;
- gameplay Snake conservé dans `game-snake-poc`, avec adapter `game-snake-poc-wasm` dédié et host navigateur Vite/TypeScript séparé ;
- shell Web responsive fondé sur Bootstrap 5/Bootswatch, SimpleBar et Canvas au ratio logique du jeu, avec clavier et contrôles pointer/tactiles ;
- lifecycle navigateur fixed-step sans rattrapage massif, resize `devicePixelRatio`, assets `common/` + `game/`, logging frontend structuré et provenance `Web / Browser / Wasm` ;
- runner Desktop SDL3 maintenu comme témoin de non-régression et builds release Desktop/Web déjà exercés pendant la beta ;
- prompt `0.3.1` préparé pour le second host Snake Tauri Android, avec workflow de session, commandes et validations consolidés.
La RC n'ouvre aucun nouveau scope. Les détails des phases `pre`, `beta` et de leurs correctifs restent dans `deltas/0.3.0/` et `history/0.3.0/`.
## 0.2.0 — conception modulaire, POC et Uroburas
- Gouvernance documentaire renforcée : catégories `ideas/studies/architecture/rules`, immutabilité des deltas, conventions de statuts et workflow de session/version.

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 57
# version: 80
[workspace]
resolver = "3"
@@ -16,10 +16,12 @@ members = [
"crates/apps/game-android-entrypoint",
"crates/apps/game-reflex-poc-tauri",
"crates/apps/game-reflex-poc-wasm",
"crates/apps/game-snake-poc-wasm",
"crates/apps/game-snake-poc-tauri",
]
[workspace.package]
version = "0.2.0"
version = "0.3.1"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 25 -->
<!-- version: 51 -->
# games.sasedev
@@ -14,6 +14,8 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
- assets hors des crates sous `assets/` ;
- `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ;
- frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ;
- Android de production orienté SDL3 natif/Java/JNI ; Tauri Android reste un POC de référence et non un template de jeu ;
- hosts navigateur directs sous `Web/`, avec frontend Vite/TypeScript séparé des adapters Rust/WASM ;
- monétisation optionnelle et spécifique à chaque plateforme/distribution ;
- runner Desktop natif SDL3 par défaut, avec variante Tauri uniquement si un besoin futur la justifie ;
- documentation sous `docs/` ;
@@ -23,9 +25,11 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
## Baseline
Version stable de référence : `0.1.0`.
Version stable de référence : `0.3.1`.
Version candidate en cours de conception : `0.2.0`.
Version suivante active planifiée : `0.3.3` (non démarrée). `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.
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md -->
<!-- version: 19 -->
<!-- version: 25 -->
# Roadmap
@@ -26,11 +26,31 @@ Le détail historique des prereleases `0.1.0-*` reste dans `deltas/0.1.0/` et `h
- (x) `0.2.0` — fixer la gouvernance documentaire, la nomenclature et le cycle de maturation des idées et décisions.
- (x) `0.2.0` — inventorier et réserver les capabilities déjà justifiées sans les implémenter prématurément.
- ( ) `0.2.0` — définir les couches, les dépendances autorisées et la frontière entre kernel, capability, plateforme, provider, système de jeu et service.
- ( ) `0.2.0` — établir la matrice plateformes et les archétypes de jeux utilisés pour challenger l'architecture.
- ( ) `0.2.0`définir le modèle de composition statique, le manifest produit/jeu et l'architecture cible des crates.
- ( ) `0.2.0`définir la trajectoire d'implémentation progressive des versions suivantes seulement après revue de la conception.
- (x) `0.2.0` — définir les couches, les dépendances autorisées et la frontière entre kernel, capability, plateforme, provider, système de jeu et service.
- (x) `0.2.0` — établir la matrice plateformes et les archétypes de jeux utilisés pour challenger l'architecture.
- (x) `0.2.0`retenir la composition Rust statique comme direction de référence.
- (d) `0.2.0`figer un manifest produit/jeu et l'architecture physique définitive des crates → target TBD après observation des POC `0.3.x`.
- (x) `0.2.0` — définir la trajectoire d'implémentation progressive `0.3.x` POC puis `0.4.x` Uroburas.
- (x) `0.2.0` — consolider les études acceptées en architecture/règles durables, notamment ownership, réseau, Uroburas et POC, puis fermer les points encore candidats.
- (x) `0.2.0` — formaliser la stratégie réseau : Actix Web pour le Web/API, asset delivery auto-hébergé H2/H3, WebSocket baseline et WebTransport/QUIC candidat.
- (x) `0.2.0` — préparer une trajectoire `0.3.x` dédiée aux POC plateforme/réseau avec Snake comme jeu-sonde.
- (x) `0.2.0` — préparer la trajectoire `0.4.x` Uroburas Mode 1 et le prompt de session associé après gel de la conception.
## 0.3.0 — Baseline Snake + premier POC Web direct
- (x) `0.3.0` — nettoyer la baseline Snake sans introduire de logique Uroburas et maintenir le runner Desktop SDL3 fonctionnel.
- (x) `0.3.0` — produire une adaptation WASM dédiée à Snake sans déplacer le gameplay hors de `game-snake-poc`.
- (x) `0.3.0` — réaliser un POC navigateur direct Snake couvrant clavier, boutons directionnels tactiles, resize, lifecycle, logging, assets et provenance runtime.
- (x) `0.3.0` — utiliser uniquement Cargo, `wasm-bindgen` et Vite/npm pour le chemin de build Web ; aucun script Python ne pilote ce build.
- (x) `0.3.0` — documenter les duplications observées et conserver les adapters Web/WASM spécifiques tant quun second consommateur ne justifie pas une extraction commune.
## Série 0.3.x — trajectoire actuelle après 0.3.1
- (x) `0.3.1` — Snake Tauri Android livré comme POC de référence réutilisant la baseline Web/WASM ; la production Android reste orientée SDL3 natif.
- (d) `0.3.2` — Tauri Desktop + Snake est différé tant quune solution de monétisation desktop/WebView, notamment vidéo récompensée, nest pas démontrée techniquement et contractuellement ; ne pas créer cette distribution seulement pour réutiliser le host Web.
- ( ) `0.3.3` — Android SDL3 natif multi-ABI avec Cargo/Gradle natifs, APK universal pour tests, AAB comme cible de distribution et évaluation du `minSdk`/support danciennes versions Android, sans script Python de build.
- ( ) `0.3.4` — API de transport realtime + WebSocket/tokio-tungstenite baseline, sans serveur Uroburas Mode 3.
- ( ) `0.3.5` — POC WebTransport/QUIC sur le même protocole, avec comparaison mesurée et fallback WebSocket.
- ( ) `0.3.6` — consolidation des POC plateforme/réseau et préparation de la baseline `0.4.x`.
La numérotation `0.3.1+` reste révisable à partir des résultats réels ; les lignes ci-dessus décrivent le planning actuel, pas une obligation de créer des versions artificielles.

View File

@@ -1,5 +1,5 @@
<!-- file: RULES.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# Index normatif games.sasedev
@@ -17,8 +17,9 @@ Les règles détaillées sont maintenues sous `docs/rules/` et sont cumulatives
6. [`docs/rules/VERSION_WORKFLOW.md`](docs/rules/VERSION_WORKFLOW.md) — SemVer, niveaux de maturité, fixes, deltas et livraisons ;
7. [`docs/rules/RULES_COMMANDS.md`](docs/rules/RULES_COMMANDS.md) — politique dexécution des commandes Cargo, audits, runners, Android, Web et Git ;
8. [`docs/rules/RULES_VALIDATION_MATRIX.md`](docs/rules/RULES_VALIDATION_MATRIX.md) — matrice évolutive des commandes, dépendances de validation et politiques de nettoyage ;
9. [`docs/rules/RULES_SESSION_PLANNING.md`](docs/rules/RULES_SESSION_PLANNING.md) — cadrage `pre.1`, dimensionnement des sessions et contrat des prompts de reprise ;
10. [`docs/rules/RULES_SERVER_HOSTING.md`](docs/rules/RULES_SERVER_HOSTING.md) — contraintes durables de portabilité et préférence d'auto-hébergement.
9. [`docs/rules/RULES_SESSION_PLANNING.md`](docs/rules/RULES_SESSION_PLANNING.md) — cadrage `pre.1`, dimensionnement des sessions et cycle de transmission ;
10. [`docs/rules/PROMPT_STRUCTURE.md`](docs/rules/PROMPT_STRUCTURE.md) — structure minimale des prompts de reprise et rappels de workflow obligatoires ;
11. [`docs/rules/RULES_SERVER_HOSTING.md`](docs/rules/RULES_SERVER_HOSTING.md) — contraintes durables de portabilité et préférence d'auto-hébergement.
## Hiérarchie

View File

@@ -0,0 +1,118 @@
<!-- file: Web/game-snake-poc/frontend/main.html -->
<!-- version: 4 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport">
<meta content="#593196" name="theme-color">
<link rel="stylesheet" href="sass/main.scss">
<title>Snake POC — Web direct</title>
</head>
<body>
<header class="app-header">
<nav class="navbar h-100 py-0 bg-light text-dark">
<div class="container-fluid px-3 px-md-4 flex-nowrap">
<div class="navbar-brand d-flex align-items-center me-3 flex-nowrap min-w-0">
<span class="app-logo d-inline-flex align-items-center justify-content-center text-primary" aria-hidden="true">
<i class="fa-solid fa-gamepad"></i>
</span>
<span class="ps-2 fs-4 fw-semibold text-primary text-nowrap">Snake POC</span>
<span class="mx-2 fs-5 text-body-secondary d-none d-sm-inline" aria-hidden="true"></span>
<span class="fs-5 text-body text-nowrap d-none d-sm-inline">Web direct</span>
</div>
<div class="d-flex align-items-center gap-2 ms-auto">
<span class="badge text-bg-light border text-dark d-none d-md-inline">Rust/WASM</span>
<span id="runtime-status" class="badge text-bg-secondary" role="status" aria-live="polite">Initialisation…</span>
</div>
</div>
</nav>
</header>
<main class="app-main container-fluid p-0">
<div class="row g-0 h-100 flex-nowrap">
<section class="col app-content app-scrollable h-100" data-simplebar>
<div class="container-fluid px-3 px-md-4 py-3 py-md-4">
<div class="card app-shell-card mx-auto shadow-sm border-0">
<div class="card-body p-3 p-md-4">
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-3">
<div>
<h1 class="h3 mb-1">Snake</h1>
<p class="text-body-secondary mb-0">Gameplay Rust portable, adapter WebAssembly et rendu Canvas piloté par TypeScript.</p>
</div>
<div class="d-flex gap-2 flex-wrap" aria-label="État de la partie">
<output id="score" class="badge text-bg-primary">Score : 0</output>
<output id="length" class="badge text-bg-info">Longueur : 0</output>
</div>
</div>
<div class="row g-3 g-lg-4 align-items-start">
<div class="col-12 col-lg-8">
<div class="card border-primary-subtle shadow-sm">
<div class="card-body p-2 p-sm-3">
<div class="game-stage">
<canvas id="game" width="640" height="640" tabindex="0" aria-label="Zone de jeu Snake"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-4">
<div class="card shadow-sm app-controls-card">
<div class="card-header d-flex align-items-center justify-content-between gap-2">
<span class="fw-semibold"><i class="fa-solid fa-keyboard me-2" aria-hidden="true"></i>Contrôles</span>
<span class="small text-body-secondary">clavier / tactile</span>
</div>
<div class="card-body">
<p class="small text-body-secondary">Utilisez les flèches, WASD, ZQSD ou les boutons directionnels.</p>
<div class="direction-pad mx-auto" aria-label="Contrôles directionnels tactiles">
<button class="btn btn-outline-primary direction-up" type="button" data-direction="up" aria-label="Haut">
<i class="fa-solid fa-arrow-up" aria-hidden="true"></i>
</button>
<button class="btn btn-outline-primary direction-left" type="button" data-direction="left" aria-label="Gauche">
<i class="fa-solid fa-arrow-left" aria-hidden="true"></i>
</button>
<button class="btn btn-outline-primary direction-down" type="button" data-direction="down" aria-label="Bas">
<i class="fa-solid fa-arrow-down" aria-hidden="true"></i>
</button>
<button class="btn btn-outline-primary direction-right" type="button" data-direction="right" aria-label="Droite">
<i class="fa-solid fa-arrow-right" aria-hidden="true"></i>
</button>
</div>
<dl class="runtime-details small border-top pt-3 mt-3 mb-0">
<div class="d-flex justify-content-between gap-3">
<dt class="text-body-secondary fw-normal">Provenance</dt>
<dd class="mb-2 text-end"><output id="runtime-provenance">web / browser / wasm / unknown / unknown</output></dd>
</div>
<div class="d-flex justify-content-between gap-3">
<dt class="text-body-secondary fw-normal">Assets</dt>
<dd class="mb-0 text-end"><output id="asset-status">chargement…</output></dd>
</div>
</dl>
<div class="alert alert-info small mt-3 mb-0" role="note">
Les entrées ne font pas avancer la simulation : elles sont consommées au prochain <code>tick()</code> WASM.
</div>
</div>
</div>
</div>
</div>
<p id="startup-error" class="alert alert-danger mt-3 mb-0" role="alert" hidden></p>
</div>
</div>
</div>
</section>
</div>
</main>
<footer class="app-footer bg-dark text-light">
<div class="container h-100 d-flex align-items-center justify-content-center">
<small>&copy; 2026 SASEDEV</small>
</div>
</footer>
<script type="module" src="ts/main.ts" defer></script>
</body>
</html>

View File

@@ -0,0 +1,165 @@
// file: Web/game-snake-poc/frontend/sass/_app.scss
// version: 4
$app-header-height: 72px;
$app-footer-height: 42px;
html,
body {
width: 100%;
min-width: 320px;
height: 100%;
}
body {
margin: 0;
overflow: hidden;
background: $gray-100;
overscroll-behavior: none;
}
.min-w-0 {
min-width: 0;
}
.app-header {
position: fixed;
inset: 0 0 auto;
height: $app-header-height;
z-index: 1020;
border-bottom: 2px solid rgba($primary, 0.3);
}
.app-footer {
position: fixed;
inset: auto 0 0;
height: $app-footer-height;
z-index: 1020;
border-top: 2px solid rgba($primary, 0.3);
}
.app-main {
position: relative;
width: 100%;
height: calc(100vh - $app-header-height - $app-footer-height);
margin-top: $app-header-height;
margin-bottom: $app-footer-height;
overflow: hidden;
}
.app-main > .row {
flex-wrap: nowrap;
min-width: 0;
}
.app-scrollable {
height: 100%;
max-height: 100%;
}
.app-content {
min-width: 0;
height: 100%;
max-height: 100%;
}
.app-logo {
width: 42px;
height: 42px;
font-size: 1.75rem;
}
.app-shell-card {
width: 100%;
max-width: 1180px;
min-width: 0;
}
.game-stage {
width: 100%;
max-width: 520px;
margin-inline: auto;
aspect-ratio: 3 / 5;
overflow: hidden;
background: $black;
border: 1px solid rgba($primary, 0.35);
}
#game {
display: block;
width: 100%;
height: 100%;
touch-action: none;
outline: none;
}
#game:focus-visible {
box-shadow: inset 0 0 0 3px rgba($primary, 0.85);
}
.app-controls-card {
min-width: 0;
}
.runtime-details dd {
min-width: 0;
overflow-wrap: anywhere;
}
.direction-pad {
display: grid;
grid-template-columns: repeat(3, minmax(64px, 88px));
grid-template-rows: repeat(2, minmax(56px, 72px));
gap: 0.5rem;
justify-content: center;
user-select: none;
}
.direction-pad .btn {
touch-action: manipulation;
font-size: 1.25rem;
}
.direction-up {
grid-column: 2;
grid-row: 1;
}
.direction-left {
grid-column: 1;
grid-row: 2;
}
.direction-down {
grid-column: 2;
grid-row: 2;
}
.direction-right {
grid-column: 3;
grid-row: 2;
}
@media (max-width: 575.98px) {
$mobile-header-height: 64px;
.app-header {
height: $mobile-header-height;
}
.app-main {
height: calc(100vh - $mobile-header-height - $app-footer-height);
margin-top: $mobile-header-height;
}
.app-logo {
width: 34px;
height: 34px;
font-size: 1.4rem;
}
.direction-pad {
width: 100%;
grid-template-columns: repeat(3, minmax(72px, 1fr));
}
}

View File

@@ -0,0 +1,160 @@
// file: Web/game-snake-poc/frontend/sass/_bootswatch.scss
// version: 1
// Pulse 5.3.8
// Bootswatch
// Variables
// Buttons
.btn {
&:focus,
&:active,
&:active:focus,
&.active:focus {
outline: none;
}
&-secondary {
color: $gray-900;
background-color: $white;
border-color: #ccc;
&:hover {
color: $gray-900;
background-color: $gray-300;
border-color: $gray-500;
}
&.disabled {
color: tint-color($gray-900, 5%);
background-color: $white;
border-color: tint-color(#ccc, 5%);
}
}
&-warning {
color: $white;
}
&-primary:focus {
box-shadow: 0 0 5px tint-color($primary, 10%);
}
&-secondary:focus {
box-shadow: 0 0 5px $gray-400;
}
&-success:focus {
box-shadow: 0 0 5px tint-color($success, 10%);
}
&-info:focus {
box-shadow: 0 0 5px tint-color($info, 10%);
}
&-warning:focus {
box-shadow: 0 0 5px tint-color($warning, 10%);
}
&-danger:focus {
box-shadow: 0 0 5px tint-color($danger, 10%);
}
&.disabled:focus {
box-shadow: none;
}
}
// Tables
.table .thead-dark th {
background-color: $secondary;
border-color: $table-border-color;
}
.table-primary,
.table-secondary,
.table-success,
.table-warning,
.table-danger,
.table-info,
.table-light {
--#{$prefix}table-color: #{$body-color};
}
// Forms
.form-control:focus {
box-shadow: 0 0 5px rgba(100, 65, 164, .4);
}
// Navs
.nav-tabs {
.nav-link,
.nav-link.active {
border-width: 0 0 1px;
}
.nav-link:hover,
.nav-link.active,
.nav-link.active:hover,
.nav-link.active:focus {
border-bottom: 1px solid $primary;
}
.nav-item+.nav-item {
margin-left: 0;
}
}
.breadcrumb {
&-item.active {
color: $gray-700;
}
}
// Indicators
.badge {
&.bg-light {
color: $dark;
}
}
// Progress bars
.progress {
height: 8px;
}
// Containers
.list-group {
&-item {
color: rgba(255, 255, 255, .8);
&.active,
&:hover,
&:focus {
color: $white;
}
&.active {
font-weight: 700;
&:hover {
background-color: $list-group-hover-bg;
}
}
&.disabled:hover {
color: $list-group-disabled-color;
}
}
}

View File

@@ -0,0 +1,19 @@
// file: Web/game-snake-poc/frontend/sass/_fontawesome.scss
// version: 1
//@use '@fortawesome/fontawesome-free/scss/variables' with (
// // customizing $font-path - make sure it points to where your webfonts are stored in your project
// $font-path: '../webfonts',
//);
@use '@fortawesome/fontawesome-free/scss/variables' with (
// use fonts from @fortawesome/fontawesome-free
$font-path: '@fortawesome/fontawesome-free/webfonts',
);
// load Font Awesome core
@use '@fortawesome/fontawesome-free/scss/fontawesome';
// load and make available Font Awesome helpers (mixins, functions, and variables)
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
@use '@fortawesome/fontawesome-free/scss/brands' as fa-brands;
@use '@fortawesome/fontawesome-free/scss/regular' as fa-regular;
@use '@fortawesome/fontawesome-free/scss/solid' as fa-solid;

View File

@@ -0,0 +1,248 @@
// file: Web/game-snake-poc/frontend/sass/_simplebar.scss
// version: 1
/* Rtl support */
[data-simplebar] {
position: relative;
flex-direction: column;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
align-items: flex-start;
}
.simplebar-wrapper {
overflow: hidden;
width: inherit;
height: inherit;
max-width: inherit;
max-height: inherit;
}
.simplebar-mask {
direction: inherit;
position: absolute;
overflow: hidden;
padding: 0;
margin: 0;
left: 0;
top: 0;
bottom: 0;
right: 0;
width: auto !important;
height: auto !important;
// z-index: 0;
inset: 0;
}
.simplebar-offset {
direction: inherit !important;
box-sizing: inherit !important;
resize: none !important;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 0;
margin: 0;
-webkit-overflow-scrolling: touch;
inset: 0;
}
.simplebar-content-wrapper {
direction: inherit;
box-sizing: border-box !important;
position: relative;
display: block;
height: 100%;
width: auto;
max-width: 100%;
max-height: 100%;
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
.simplebar-hide-scrollbar {
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
position: fixed;
left: 0;
visibility: hidden;
overflow-y: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
}
.simplebar-content {
&:before {
content: ' ';
display: table;
}
&:after {
content: ' ';
display: table;
}
}
.simplebar-placeholder {
max-height: 100%;
max-width: 100%;
width: 100%;
pointer-events: none;
}
.simplebar-height-auto-observer-wrapper {
box-sizing: inherit !important;
height: 100%;
width: 100%;
max-width: 1px;
position: relative;
float: left;
max-height: 1px;
overflow: hidden;
// z-index: -1;
padding: 0;
margin: 0;
pointer-events: none;
flex-grow: inherit;
flex-shrink: 0;
flex-basis: 0;
}
.simplebar-height-auto-observer {
box-sizing: inherit;
display: block;
opacity: 0;
position: absolute;
top: 0;
left: 0;
height: 1000%;
width: 1000%;
min-height: 1px;
min-width: 1px;
overflow: hidden;
pointer-events: none;
// z-index: -1;
}
.simplebar-track {
// z-index: 1;
position: absolute;
right: 0;
bottom: 0;
pointer-events: none;
overflow: hidden;
}
[data-simplebar].simplebar-dragging {
pointer-events: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.simplebar-content {
pointer-events: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.simplebar-track {
pointer-events: all;
}
}
.simplebar-scrollbar {
position: absolute;
left: 0;
right: 0;
min-height: 10px;
&:before {
position: absolute;
content: '';
background: black;
border-radius: 7px;
left: 2px;
right: 2px;
opacity: 0;
transition: opacity 0.2s 0.5s linear;
top: 2px;
bottom: 2px;
}
}
.simplebar-scrollbar.simplebar-visible {
&:before {
opacity: 0.5;
transition-delay: 0s;
transition-duration: 0s;
}
}
.simplebar-track.simplebar-vertical {
top: 0;
width: 11px;
}
.simplebar-track.simplebar-horizontal {
left: 0;
height: 11px;
.simplebar-scrollbar {
right: auto;
left: 0;
top: 0;
bottom: 0;
min-height: 0;
min-width: 10px;
width: auto;
}
}
[data-simplebar-direction='rtl'] {
.simplebar-track.simplebar-vertical {
right: auto;
left: 0;
}
}
.simplebar-dummy-scrollbar-size {
direction: rtl;
position: fixed;
opacity: 0;
visibility: hidden;
height: 500px;
width: 500px;
overflow-y: hidden;
overflow-x: scroll;
-ms-overflow-style: scrollbar !important;
>div {
width: 200%;
height: 200%;
margin: 10px 0;
}
}
.simplebar-hover {
cursor: pointer;
}

View File

@@ -0,0 +1,95 @@
// file: Web/game-snake-poc/frontend/sass/_variables.scss
// version: 1
// Pulse 5.3.8
// Bootswatch
$theme: "pulse" !default;
//
// Color system
//
$white: #fff !default;
$gray-100: #fafafa !default;
$gray-200: #f9f8fc !default;
$gray-300: #ededed !default;
$gray-400: #cbc8d0 !default;
$gray-500: #adb5bd !default;
$gray-600: #868e96 !default;
$gray-700: #444 !default;
$gray-800: #343a40 !default;
$gray-900: #17141f !default;
$black: #000 !default;
$blue: #007bff !default;
$indigo: #6610f2 !default;
$purple: #593196 !default;
$pink: #e83e8c !default;
$red: #fc3939 !default;
$orange: #fd7e14 !default;
$yellow: #efa31d !default;
$green: #13b955 !default;
$teal: #20c997 !default;
$cyan: #009cdc !default;
$primary: $purple !default;
$secondary: #a991d4 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-200 !default;
$dark: $gray-900 !default;
$min-contrast-ratio: 2.1 !default;
// Options
$enable-rounded: false !default;
// Body
$body-color: $gray-700 !default;
// Links
$link-hover-color: $primary !default;
// Tables
$table-color: initial !default;
$table-border-color: rgba(0, 0, 0, .05) !default;
// Forms
$input-focus-border-color: $primary !default;
// Dropdowns
$dropdown-link-hover-color: $white !default;
$dropdown-link-hover-bg: $primary !default;
// Navs
$nav-tabs-border-color: $gray-300 !default;
$nav-tabs-link-hover-border-color: $primary !default;
// Navbar
$navbar-padding-y: 1.2rem !default;
// Progress bars
$progress-bg: $gray-300 !default;
$progress-bar-bg: $primary !default;
// List group
$list-group-bg: $gray-900 !default;
$list-group-border-color: transparent !default;
$list-group-hover-bg: lighten($list-group-bg, 10%) !default;
$list-group-active-color: $white !default;
$list-group-active-bg: $list-group-bg !default;
$list-group-disabled-color: lighten($list-group-bg, 30%) !default;

View File

@@ -0,0 +1,10 @@
// file: Web/game-snake-poc/frontend/sass/main.scss
// version: 2
@import "bootstrap/scss/functions";
@import "variables";
@import "fontawesome";
@import "simplebar";
@import "bootstrap/scss/bootstrap";
@import "bootswatch";
@import "app";

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
// file: Web/game-snake-poc/frontend/ts/lifecycle.ts
// version: 1
import type { SnakeWebSession } from "./game";
import { webDebug, webInfo } from "./logging";
export interface SnakeLifecycleBinding {
dispose(): void;
}
export function bindBrowserLifecycle(session: SnakeWebSession, stage: HTMLElement): SnakeLifecycleBinding {
const resizeObserver = new ResizeObserver(() => {
session.refreshDeviceClass();
session.renderNow();
});
function synchronizeVisibility(): void {
if (document.visibilityState === "hidden") {
session.pause();
webDebug("snake-web-lifecycle", "visibility_hidden");
return;
}
session.resume();
session.renderNow();
webDebug("snake-web-lifecycle", "visibility_visible");
}
function pageHide(event: PageTransitionEvent): void {
if (event.persisted) {
session.pause();
} else {
session.dispose();
}
webInfo("snake-web-lifecycle", "page_hide", { persisted: event.persisted });
}
function pageShow(event: PageTransitionEvent): void {
session.resume();
session.renderNow();
webInfo("snake-web-lifecycle", "page_show", { persisted: event.persisted });
}
resizeObserver.observe(stage);
document.addEventListener("visibilitychange", synchronizeVisibility);
window.addEventListener("pagehide", pageHide);
window.addEventListener("pageshow", pageShow);
synchronizeVisibility();
return {
dispose(): void {
resizeObserver.disconnect();
document.removeEventListener("visibilitychange", synchronizeVisibility);
window.removeEventListener("pagehide", pageHide);
window.removeEventListener("pageshow", pageShow);
session.dispose();
},
};
}

View File

@@ -0,0 +1,45 @@
// file: Web/game-snake-poc/frontend/ts/logging.ts
// version: 1
export type WebLogLevel = "debug" | "info" | "warn" | "error";
export type WebLogFields = Readonly<Record<string, boolean | number | string | null>>;
type ConsoleMethod = (...items: unknown[]) => void;
function consoleMethod(level: WebLogLevel): ConsoleMethod {
if (level === "debug") {
return console.debug.bind(console);
}
if (level === "info") {
return console.info.bind(console);
}
if (level === "warn") {
return console.warn.bind(console);
}
return console.error.bind(console);
}
function emit(level: WebLogLevel, target: string, action: string, fields: WebLogFields = {}): void {
const event = {
target,
action,
...fields,
};
consoleMethod(level)(`[games.sasedev][${target}] ${action}`, event);
}
export function webDebug(target: string, action: string, fields: WebLogFields = {}): void {
emit("debug", target, action, fields);
}
export function webInfo(target: string, action: string, fields: WebLogFields = {}): void {
emit("info", target, action, fields);
}
export function webWarn(target: string, action: string, fields: WebLogFields = {}): void {
emit("warn", target, action, fields);
}
export function webError(target: string, action: string, fields: WebLogFields = {}): void {
emit("error", target, action, fields);
}

View File

@@ -0,0 +1,66 @@
// file: Web/game-snake-poc/frontend/ts/main.ts
// version: 3
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import { loadSnakeWebAssets } from "./assets";
import { startGame } from "./game";
import { bindInputs } from "./input";
import { bindBrowserLifecycle } from "./lifecycle";
import { webError, webInfo } from "./logging";
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
function requireElement<T extends Element>(selector: string): T {
const element = document.querySelector<T>(selector);
if (element === null) {
throw new Error(`Élément frontend requis absent : ${selector}`);
}
return element;
}
async function main(): Promise<void> {
const canvas = requireElement<HTMLCanvasElement>("#game");
const stage = requireElement<HTMLElement>(".game-stage");
const score = requireElement<HTMLOutputElement>("#score");
const length = requireElement<HTMLOutputElement>("#length");
const provenance = requireElement<HTMLOutputElement>("#runtime-provenance");
const assets = requireElement<HTMLOutputElement>("#asset-status");
const status = requireElement<HTMLSpanElement>("#runtime-status");
const error = requireElement<HTMLParagraphElement>("#startup-error");
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-direction]"));
status.textContent = "Chargement assets…";
const assetMetadata = await loadSnakeWebAssets();
assets.value = `${assetMetadata.gameKind} / engine-v${assetMetadata.engineGeneration}`;
webInfo("snake-web-assets", "runtime_assets_loaded", {
engineGeneration: assetMetadata.engineGeneration,
game: assetMetadata.game,
gameKind: assetMetadata.gameKind,
});
status.textContent = "Chargement WASM…";
const session = await startGame(canvas, score, length, provenance);
bindInputs(session, buttons);
bindBrowserLifecycle(session, stage);
status.className = "badge text-bg-success";
status.textContent = "Prêt";
error.hidden = true;
canvas.focus();
webInfo("snake-web-main", "runtime_ready");
}
void main().catch(caughtError => {
const status = document.querySelector<HTMLSpanElement>("#runtime-status");
const error = document.querySelector<HTMLParagraphElement>("#startup-error");
const message = caughtError instanceof Error ? caughtError.stack ?? caughtError.message : String(caughtError);
if (status !== null) {
status.className = "badge text-bg-danger";
status.textContent = "Erreur";
}
if (error !== null) {
error.textContent = `Impossible de démarrer Snake : ${message}`;
error.hidden = false;
}
webError("snake-web-main", "runtime_start_failed", { message });
});

View File

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

View File

@@ -0,0 +1,25 @@
{
"name": "game-snake-poc-web",
"private": true,
"version": "0.3.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.3",
"bootstrap": "^5.3",
"resize-observer-polyfill": "^1.5",
"simplebar": "^6.3"
},
"devDependencies": {
"@types/bootstrap": "^5.2",
"@types/node": "^26.1",
"sass-embedded": "^1.102",
"typescript": "^7.0",
"vite": "^8.2",
"vite-plugin-static-copy": "^4.1"
}
}

View File

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

View File

@@ -0,0 +1,96 @@
// file: Web/game-snake-poc/vite.config.ts
// version: 3
import { NodePackageImporter } from "sass-embedded";
import { fileURLToPath } from "node:url";
import { resolve } from "node:path";
import { defineConfig, normalizePath } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
const appRoot = fileURLToPath(new URL(".", import.meta.url));
const repositoryRoot = normalizePath(resolve(appRoot, "../.."));
const frontendRoot = normalizePath(resolve(appRoot, "frontend"));
const externalBuildRoot = normalizePath(resolve(repositoryRoot, "../builds/sasedev-games/game-snake-poc-web"));
const wasmRoot = normalizePath(resolve(externalBuildRoot, "wasm"));
const wasmModule = normalizePath(resolve(wasmRoot, "game_snake_poc_wasm.js"));
const frontendDist = normalizePath(resolve(externalBuildRoot, "dist"));
const viteCacheDir = normalizePath(resolve(externalBuildRoot, "vite-cache"));
const commonRuntimeAsset = normalizePath(resolve(repositoryRoot, "assets/common/data/runtime.json"));
const gameRuntimeAsset = normalizePath(resolve(repositoryRoot, "assets/game-snake-poc/data/game.json"));
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [
{ src: commonRuntimeAsset, dest: "common/data", rename: { stripBase: true } },
{ src: gameRuntimeAsset, dest: "game/data", rename: { stripBase: true } },
],
}),
],
base: "./",
cacheDir: viteCacheDir,
clearScreen: false,
root: frontendRoot,
publicDir: false,
input: {
main: normalizePath(resolve(frontendRoot, "main.html")),
},
resolve: {
alias: {
"@snake-wasm": wasmModule,
},
},
build: {
outDir: frontendDist,
emptyOutDir: true,
minify: true,
sourcemap: false,
cssCodeSplit: true,
rolldownOptions: {
output: {
entryFileNames: "js/[name]-[hash].js",
chunkFileNames: "js/chunks/[name]-[hash].js",
assetFileNames: assetInfo => {
const originalName = assetInfo.names[0] ?? "";
const extension = originalName.substring(originalName.lastIndexOf(".") + 1).toLowerCase();
if (extension === "css") {
return "css/[name]-[hash][extname]";
}
if (["eot", "otf", "ttf", "woff", "woff2"].includes(extension)) {
return "fonts/[name]-[hash][extname]";
}
if (["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"].includes(extension)) {
return "imgs/[name][extname]";
}
if (extension === "wasm") {
return "wasm/[name]-[hash][extname]";
}
return "otherassets/[name][extname]";
},
},
},
},
css: {
preprocessorOptions: {
scss: {
quietDeps: true,
silenceDeprecations: ["import", "color-functions", "global-builtin"],
verbose: false,
importers: [new NodePackageImporter()],
},
},
},
server: {
host: "127.0.0.1",
port: 1434,
strictPort: true,
fs: {
allow: [appRoot, repositoryRoot, externalBuildRoot],
},
},
preview: {
host: "127.0.0.1",
port: 4174,
strictPort: true,
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
# file: crates/apps/game-snake-poc-wasm/Cargo.toml
# version: 2
[package]
name = "game-snake-poc-wasm"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
authors.workspace = true
publish.workspace = true
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
engine-v1-common = { path = "../../engines/engine-v1-common" }
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
game-snake-poc = { path = "../../games/game-snake-poc" }
wasm-bindgen.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,13 @@
// file: crates/apps/game-snake-poc-wasm/src/lib.rs
// version: 1
//! WebAssembly adapter facade for the Snake POC.
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
mod runtime;
/// Re-export of the Snake WebAssembly runtime adapter.
pub use self::runtime::SnakeWasmGame;

View File

@@ -0,0 +1,330 @@
// file: crates/apps/game-snake-poc-wasm/src/runtime.rs
// version: 3
const FIXED_STEP_MILLIS: u64 = 16;
/// WebAssembly-owned Snake game state consumed by the direct browser host.
#[wasm_bindgen::prelude::wasm_bindgen]
pub struct SnakeWasmGame {
pending_input: engine_v1_common::InputState,
provenance: engine_v1_platform_api::RuntimeProvenance,
runner: engine_v1_common::FixedStepRunner,
state: game_snake_poc::SnakeState,
}
#[wasm_bindgen::prelude::wasm_bindgen]
impl SnakeWasmGame {
/// Creates a fresh Snake POC session using the shared gameplay crate.
#[wasm_bindgen::prelude::wasm_bindgen(constructor)]
pub fn new() -> Self {
return Self {
pending_input: engine_v1_common::InputState::none(),
provenance: engine_v1_platform_api::RuntimeProvenance::new(
engine_v1_platform_api::DeviceClass::Unknown,
engine_v1_platform_api::ExecutionModel::Wasm,
engine_v1_platform_api::InputProfile::Unknown,
engine_v1_platform_api::PlatformFamily::Web,
engine_v1_platform_api::RuntimeHost::Browser,
),
runner: engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(FIXED_STEP_MILLIS)),
state: game_snake_poc::SnakeState::new(),
};
}
/// Updates host-observed runtime provenance while preserving WebAssembly execution.
pub fn configure_runtime_provenance(&mut self, platform_family: &str, runtime_host: &str, device_class: &str, input_profile: &str) -> bool {
let platform_family = match parse_platform_family(platform_family) {
Some(value) => value,
None => return false,
};
let runtime_host = match parse_runtime_host(runtime_host) {
Some(value) => value,
None => return false,
};
let device_class = match parse_device_class(device_class) {
Some(value) => value,
None => return false,
};
let input_profile = match parse_input_profile(input_profile) {
Some(value) => value,
None => return false,
};
self.provenance = engine_v1_platform_api::RuntimeProvenance::new(
device_class,
engine_v1_platform_api::ExecutionModel::Wasm,
input_profile,
platform_family,
runtime_host,
);
return true;
}
/// Returns the current physical device class label.
pub fn provenance_device_class(&self) -> String {
return device_class_label(self.provenance.device_class()).to_string();
}
/// Returns the current execution model label.
pub fn provenance_execution_model(&self) -> String {
return execution_model_label(self.provenance.execution_model()).to_string();
}
/// Returns the current primary input profile label.
pub fn provenance_input_profile(&self) -> String {
return input_profile_label(self.provenance.input_profile()).to_string();
}
/// Returns the current platform family label.
pub fn provenance_platform_family(&self) -> String {
return platform_family_label(self.provenance.platform_family()).to_string();
}
/// Returns the current runtime host label.
pub fn provenance_runtime_host(&self) -> String {
return runtime_host_label(self.provenance.runtime_host()).to_string();
}
/// Queues a logical left direction for the next deterministic update.
pub fn left(&mut self) {
self.queue_direction(engine_v1_common::GameAction::Left);
return;
}
/// Queues a logical right direction for the next deterministic update.
pub fn right(&mut self) {
self.queue_direction(engine_v1_common::GameAction::Right);
return;
}
/// Queues a logical upward direction for the next deterministic update.
pub fn up(&mut self) {
self.queue_direction(engine_v1_common::GameAction::Up);
return;
}
/// Queues a logical downward direction for the next deterministic update.
pub fn down(&mut self) {
self.queue_direction(engine_v1_common::GameAction::Down);
return;
}
/// Advances one deterministic update and consumes the queued direction.
pub fn tick(&mut self) {
let input = self.pending_input;
self.pending_input = engine_v1_common::InputState::none();
self.runner.tick(&mut self.state, input);
return;
}
/// Returns the current gameplay score.
pub fn score(&self) -> u32 {
return self.state.score().min(u32::MAX as u64) as u32;
}
/// Returns the current Snake length.
pub fn length(&self) -> u32 {
return self.state.length().min(u32::MAX as usize) as u32;
}
/// Returns the current scene background red channel.
pub fn background_red(&self) -> u8 {
return self.scene().background().red();
}
/// Returns the current scene background green channel.
pub fn background_green(&self) -> u8 {
return self.scene().background().green();
}
/// Returns the current scene background blue channel.
pub fn background_blue(&self) -> u8 {
return self.scene().background().blue();
}
/// Returns the number of occupied rectangle slots in the current scene.
pub fn rectangle_count(&self) -> u32 {
let scene = self.scene();
let mut count = 0_u32;
for slot in scene.rectangles() {
if slot.is_some() {
count = count.saturating_add(1);
}
}
return count;
}
/// Returns one rectangle's normalized left coordinate or `-1.0` if absent.
pub fn rectangle_x(&self, index: u32) -> f32 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.rect().x(),
None => -1.0,
};
}
/// Returns one rectangle's normalized top coordinate or `-1.0` if absent.
pub fn rectangle_y(&self, index: u32) -> f32 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.rect().y(),
None => -1.0,
};
}
/// Returns one rectangle's normalized width or `0.0` if absent.
pub fn rectangle_width(&self, index: u32) -> f32 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.rect().width(),
None => 0.0,
};
}
/// Returns one rectangle's normalized height or `0.0` if absent.
pub fn rectangle_height(&self, index: u32) -> f32 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.rect().height(),
None => 0.0,
};
}
/// Returns one rectangle's red channel or zero if absent.
pub fn rectangle_red(&self, index: u32) -> u8 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.color().red(),
None => 0,
};
}
/// Returns one rectangle's green channel or zero if absent.
pub fn rectangle_green(&self, index: u32) -> u8 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.color().green(),
None => 0,
};
}
/// Returns one rectangle's blue channel or zero if absent.
pub fn rectangle_blue(&self, index: u32) -> u8 {
return match self.rectangle(index) {
Some(rectangle) => rectangle.color().blue(),
None => 0,
};
}
}
impl Default for SnakeWasmGame {
fn default() -> Self {
return Self::new();
}
}
impl SnakeWasmGame {
fn queue_direction(&mut self, action: engine_v1_common::GameAction) {
self.pending_input = engine_v1_common::InputState::none().with_action(action, true);
return;
}
fn scene(&self) -> engine_v1_common::EngineScene {
return engine_v1_common::EngineGame::scene(&self.state);
}
fn rectangle(&self, requested_index: u32) -> Option<engine_v1_common::RenderRect> {
let scene = self.scene();
let mut occupied_index = 0_u32;
for slot in scene.rectangles() {
match slot {
Some(rectangle) => {
if occupied_index == requested_index {
return Some(*rectangle);
}
occupied_index = occupied_index.saturating_add(1);
},
None => {},
}
}
return None;
}
}
fn parse_platform_family(label: &str) -> Option<engine_v1_platform_api::PlatformFamily> {
return match label {
"android" => Some(engine_v1_platform_api::PlatformFamily::Android),
"desktop" => Some(engine_v1_platform_api::PlatformFamily::Desktop),
"web" => Some(engine_v1_platform_api::PlatformFamily::Web),
_ => None,
};
}
fn parse_runtime_host(label: &str) -> Option<engine_v1_platform_api::RuntimeHost> {
return match label {
"browser" => Some(engine_v1_platform_api::RuntimeHost::Browser),
"native" => Some(engine_v1_platform_api::RuntimeHost::Native),
"tauri-webview" => Some(engine_v1_platform_api::RuntimeHost::TauriWebView),
_ => None,
};
}
fn parse_device_class(label: &str) -> Option<engine_v1_platform_api::DeviceClass> {
return match label {
"desktop" => Some(engine_v1_platform_api::DeviceClass::Desktop),
"phone" => Some(engine_v1_platform_api::DeviceClass::Phone),
"tablet" => Some(engine_v1_platform_api::DeviceClass::Tablet),
"unknown" => Some(engine_v1_platform_api::DeviceClass::Unknown),
_ => None,
};
}
fn parse_input_profile(label: &str) -> Option<engine_v1_platform_api::InputProfile> {
return match label {
"gamepad" => Some(engine_v1_platform_api::InputProfile::Gamepad),
"keyboard-mouse" => Some(engine_v1_platform_api::InputProfile::KeyboardMouse),
"mixed" => Some(engine_v1_platform_api::InputProfile::Mixed),
"touch" => Some(engine_v1_platform_api::InputProfile::Touch),
"unknown" => Some(engine_v1_platform_api::InputProfile::Unknown),
_ => None,
};
}
fn device_class_label(value: engine_v1_platform_api::DeviceClass) -> &'static str {
return match value {
engine_v1_platform_api::DeviceClass::Desktop => "desktop",
engine_v1_platform_api::DeviceClass::Phone => "phone",
engine_v1_platform_api::DeviceClass::Tablet => "tablet",
engine_v1_platform_api::DeviceClass::Unknown => "unknown",
};
}
fn execution_model_label(value: engine_v1_platform_api::ExecutionModel) -> &'static str {
return match value {
engine_v1_platform_api::ExecutionModel::Native => "native",
engine_v1_platform_api::ExecutionModel::Wasm => "wasm",
};
}
fn input_profile_label(value: engine_v1_platform_api::InputProfile) -> &'static str {
return match value {
engine_v1_platform_api::InputProfile::Gamepad => "gamepad",
engine_v1_platform_api::InputProfile::KeyboardMouse => "keyboard-mouse",
engine_v1_platform_api::InputProfile::Mixed => "mixed",
engine_v1_platform_api::InputProfile::Touch => "touch",
engine_v1_platform_api::InputProfile::Unknown => "unknown",
};
}
fn platform_family_label(value: engine_v1_platform_api::PlatformFamily) -> &'static str {
return match value {
engine_v1_platform_api::PlatformFamily::Android => "android",
engine_v1_platform_api::PlatformFamily::Desktop => "desktop",
engine_v1_platform_api::PlatformFamily::Web => "web",
};
}
fn runtime_host_label(value: engine_v1_platform_api::RuntimeHost) -> &'static str {
return match value {
engine_v1_platform_api::RuntimeHost::Browser => "browser",
engine_v1_platform_api::RuntimeHost::Native => "native",
engine_v1_platform_api::RuntimeHost::TauriWebView => "tauri-webview",
};
}
#[cfg(test)]
#[path = "../unit_tests/runtime.rs"]
mod tests;

View File

@@ -0,0 +1,63 @@
// file: crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
// version: 3
#[test]
fn direction_is_queued_without_advancing_simulation() {
let mut game = crate::SnakeWasmGame::new();
let before_x = game.rectangle_x(1);
let before_y = game.rectangle_y(1);
game.up();
assert_eq!(game.rectangle_x(1), before_x);
assert_eq!(game.rectangle_y(1), before_y);
game.tick();
assert_eq!(game.rectangle_x(1), before_x);
assert!(game.rectangle_y(1) < before_y);
}
#[test]
fn scene_and_status_are_exposed_for_canvas_host() {
let game = crate::SnakeWasmGame::new();
assert_eq!(game.score(), 0);
assert_eq!(game.length(), 3);
assert_eq!(game.rectangle_count(), 4);
assert!(game.rectangle_width(0) > 0.0);
assert!(game.rectangle_height(0) > 0.0);
assert_eq!(game.rectangle_x(99), -1.0);
assert_eq!(game.rectangle_y(99), -1.0);
}
#[test]
fn browser_runtime_provenance_preserves_static_and_observed_dimensions() {
let mut game = crate::SnakeWasmGame::new();
assert_eq!(game.provenance_platform_family(), "web");
assert_eq!(game.provenance_execution_model(), "wasm");
assert_eq!(game.provenance_runtime_host(), "browser");
assert_eq!(game.provenance_device_class(), "unknown");
assert_eq!(game.provenance_input_profile(), "unknown");
assert!(game.configure_runtime_provenance("web", "browser", "phone", "touch"));
assert_eq!(game.provenance_device_class(), "phone");
assert_eq!(game.provenance_input_profile(), "touch");
assert_eq!(game.provenance_platform_family(), "web");
assert_eq!(game.provenance_execution_model(), "wasm");
assert_eq!(game.provenance_runtime_host(), "browser");
}
#[test]
fn invalid_runtime_provenance_labels_are_rejected_without_mutation() {
let mut game = crate::SnakeWasmGame::new();
assert!(game.configure_runtime_provenance("web", "browser", "desktop", "keyboard-mouse"));
assert!(!game.configure_runtime_provenance("console", "spaceship", "console", "telepathy"));
assert_eq!(game.provenance_device_class(), "desktop");
assert_eq!(game.provenance_input_profile(), "keyboard-mouse");
}
#[test]
fn tauri_android_runtime_provenance_is_supported_by_the_shared_adapter() {
let mut game = crate::SnakeWasmGame::new();
assert!(game.configure_runtime_provenance("android", "tauri-webview", "phone", "touch"));
assert_eq!(game.provenance_platform_family(), "android");
assert_eq!(game.provenance_runtime_host(), "tauri-webview");
assert_eq!(game.provenance_execution_model(), "wasm");
assert_eq!(game.provenance_device_class(), "phone");
assert_eq!(game.provenance_input_profile(), "touch");
}

View File

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

View File

@@ -1,5 +1,5 @@
# file: crates/games/game-reflex-poc/Cargo.toml
# version: 3
# version: 4
[package]
name = "game-reflex-poc"
@@ -12,7 +12,6 @@ publish.workspace = true
[dependencies]
engine-v1-common = { path = "../../engines/engine-v1-common" }
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
[dev-dependencies]
game-logging-lib = { path = "../../common/game-logging-lib" }

View File

@@ -1,5 +1,5 @@
# file: crates/games/game-snake-poc/Cargo.toml
# version: 3
# version: 4
[package]
name = "game-snake-poc"
@@ -12,7 +12,6 @@ publish.workspace = true
[dependencies]
engine-v1-common = { path = "../../engines/engine-v1-common" }
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
[dev-dependencies]
game-logging-lib = { path = "../../common/game-logging-lib" }

View File

@@ -0,0 +1,85 @@
<!-- file: deltas/0.3.0/0-pre.1.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.1.fix.1
## Base
Base requise : `0.3.0-0-pre.1`.
Ce correctif reste dans la responsabilité de cadrage de `0-pre.1`. Il ne modifie aucun gameplay, runtime, frontend ou packaging.
## Objet
Corriger deux omissions du cadrage initial :
- créer le plan vivant de `0.3.0` sous `docs/plans/`, avec le découpage prévisionnel souple de toute la progression de la version ;
- formaliser les règles associées, notamment la relation plan/ROADMAP/deltas, la cible d'au moins une version complète par session et le traitement des archives fournies depuis un tag.
## Version
La version workspace passe de `0.3.0-0-pre.1` à `0.3.0-0-pre.1.fix.1`.
## Plan de version
Ajout de :
- `docs/plans/000-README.md` ;
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`.
Le plan conserve le scope et les décisions du cadrage, puis suit une trajectoire souple : baseline Snake portable, adaptation WASM, frontend Web/Vite, intégration E2E, correction/extraction conditionnelle, beta, RC et stable.
La numérotation est prévisionnelle. Les tranches peuvent être scindées, fusionnées ou décalées en fonction des résultats réels sans forcer une fermeture artificielle.
## Règles ajoutées ou précisées
- `0-pre.1` crée ou révise obligatoirement le plan actif de la version sous `docs/plans/` ;
- le plan porte le découpage prévisionnel fin, `ROADMAP.md` reste macroscopique et les deltas enregistrent le livré réel ;
- une session de développement est planifiée pour livrer au minimum une version concrète complète ; une prerelease est une tranche interne, pas une cible normale de fin de session ;
- lorsqu'une archive est fournie comme téléchargement d'un tag du dépôt, elle est la baseline autoritaire de ce tag ; l'absence de `.git` est normale et n'est ni une anomalie ni une validation manquante ;
- les contrôles Git nécessitant `.git` ne s'appliquent qu'à un checkout local effectivement fourni.
Le prompt `002-V0_3_X_START_PROMPT.md` est aligné sur ces règles : il ne demande plus de vérifier un état Git inexistant dans une archive taggée et exige le plan actif dans le résultat de `pre.1`.
## Fichiers ajoutés
- `docs/plans/000-README.md` ;
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md` ;
- `deltas/0.3.0/0-pre.1.fix.1.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `docs/000-README.md` ;
- `docs/rules/FILE_CONTRACTS.md` ;
- `docs/rules/RULES_DOCUMENTATION.md` ;
- `docs/rules/RULES_SESSION_PLANNING.md` ;
- `docs/rules/VERSION_WORKFLOW.md` ;
- `docs/rules/RULES_COMMANDS.md` ;
- `prompts/002-V0_3_X_START_PROMPT.md`.
## Suppressions
Aucune.
## Validations applicables
Ce fix modifie uniquement le manifest de version et la documentation/règles. Les audits statiques sont exécutés lors de la préparation du delta.
Validation utilisateur demandée :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all -- --check
cargo check --workspace
```
Clippy/tests ne sont pas rendus nécessaires par ce fix documentaire lui-même ; ils restent ceux prévus par la progression de `0.3.0`.
## Suite
Après validation de ce fix, poursuivre `0.3.0` selon `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`, en commençant par `0-pre.2` et en visant la fermeture complète de `0.3.0` dans la session plutôt qu'un arrêt planifié sur une prerelease.

View File

@@ -0,0 +1,94 @@
<!-- file: deltas/0.3.0/0-pre.1.fix.2.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.1.fix.2
## Base
Base requise : `0.3.0-0-pre.1.fix.1`.
Ce correctif reste dans la responsabilité de cadrage de `0-pre.1`. Il ne modifie aucun gameplay, runtime, frontend ou packaging.
## Objet
Compléter le plan `0.3.0` sur deux responsabilités qui doivent être visibles dès le cadrage :
- le POC navigateur doit posséder un véritable shell Web HTML/Vite/TypeScript autour du Canvas/WASM, avec Bootstrap 5 comme baseline de présentation du premier POC Snake ;
- la prévision de version doit montrer explicitement la tranche finale de consolidation documentaire et de transmission avant RC/stable, au lieu de laisser cette responsabilité seulement implicite dans les règles générales.
## Version
La version workspace passe de `0.3.0-0-pre.1.fix.1` à `0.3.0-0-pre.1.fix.2`.
## Frontend Web
Le plan retient désormais explicitement pour `0-pre.4` :
- Vite/TypeScript comme host frontend ;
- un document HTML responsive ;
- Bootstrap 5 pour le layout, les états UI et les contrôles tactiles ;
- un Canvas dédié au rendu du jeu ;
- clavier et contrôles directionnels tactiles ;
- séparation stricte : Bootstrap/DOM restent côté frontend, le gameplay et l'état du jeu restent dans Rust/WASM.
La règle durable ne rend pas Bootstrap obligatoire pour tous les futurs hosts Web : elle impose la séparation shell Web / Canvas-WASM / gameplay. Bootstrap 5 est la décision concrète du POC `0.3.0`.
## Consolidation de fin de version
Le forecast `0.3.0` ajoute `2-beta.2` comme repère prévisionnel de consolidation :
- réconciliation de la documentation durable et du plan actif ;
- mise à jour de `CHANGELOG.md` ;
- mise à jour de `ROADMAP.md` ;
- mise à jour de l'historique applicable ;
- vérification des deltas réellement livrés ;
- préparation et vérification du prompt de la version/session suivante.
Le numéro reste souple : des fixes ou une beta supplémentaire peuvent le décaler. La responsabilité, elle, ne doit pas disparaître du plan.
La documentation fonctionnelle reste mise à jour au fil des tranches concernées ; cette consolidation finale sert à fermer la cohérence transversale et la transmission de session.
## Règles précisées
- `SESSION-009` exige que le forecast créé en `0-pre.1` rende explicitement visible une tranche de consolidation avant la candidate finale ;
- `GAME-PLATFORM-020` fixe la séparation durable du host Web : shell HTML/CSS/TypeScript autour du Canvas/WASM, sans fuite du framework de présentation dans gameplay/WASM ; Bootstrap 5 est la baseline du premier POC Snake.
Le prompt `002-V0_3_X_START_PROMPT.md` est aligné sur les décisions déjà prises par `pre.1` : Web direct en premier host, adaptation WASM dédiée, shell Bootstrap 5 et tranche `2-beta.2` de consolidation/transmission.
## Fichiers ajoutés
- `deltas/0.3.0/0-pre.1.fix.2.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md` ;
- `docs/rules/RULES_PROJECT.md` ;
- `docs/rules/RULES_SESSION_PLANNING.md` ;
- `prompts/002-V0_3_X_START_PROMPT.md`.
## Suppressions
Aucune.
## Validations applicables
Ce fix modifie uniquement la version workspace et le cadrage documentaire/normatif. Les audits statiques sont exécutés lors de la préparation du delta.
Validation utilisateur demandée :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all -- --check
cargo check --workspace
```
Aucun build Web n'est demandé par ce fix : Bootstrap 5 et le frontend Snake seront introduits seulement dans leur tranche d'implémentation.
## Suite
Après validation de ce fix, poursuivre `0.3.0` avec `0-pre.2` selon le plan actif. La session reste planifiée jusqu'à la stable `0.3.0`, avec consolidation documentaire/transmission explicitement réservée avant RC.

157
deltas/0.3.0/0-pre.1.md Normal file
View File

@@ -0,0 +1,157 @@
<!-- file: deltas/0.3.0/0-pre.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.1
## Base
Base déclarée : `0.2.0`.
L'archive de base a été inspectée avant modification. Sa version workspace est `0.2.0`. L'archive ne contient pas `.git`, donc le tag réel et la propreté Git ne peuvent pas être vérifiés dans l'environnement de génération.
## Objet
Ouvrir `0.3.0` par le cadrage obligatoire : audit réel de la baseline, revue des règles, requirements Snake, graphe de dépendances, choix du premier POC, sizing corrigé et préparation des validations.
Aucun gameplay, runtime SDL, Java/JNI, Tauri ou frontend existant n'est modifié dans cette tranche.
## Version
La version workspace passe de :
```text
0.2.0
```
à :
```text
0.3.0-0-pre.1
```
Les métadonnées Android restent à `0.2.0` dans cette tranche, car le chemin Android natif n'est ni modifié ni construit par le premier POC Web. Elles seront réévaluées dans la version qui réouvre effectivement ce produit Android.
## Audit de baseline
Les audits statiques officiels ont été exécutés avant modification et étaient propres :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (4 table(s), 158 file(s))
Distribution layout audit: clean (19 required path(s), 1 forbidden path(s) absent)
```
La revue manuelle a néanmoins détecté :
- README encore positionné sur `0.1.0` stable / `0.2.0` candidate ;
- quatre lignes `( )` sous la stable `0.2.0` dans ROADMAP, en violation de `DOC-RMAP-010` ;
- la gate `audit_rust_workspace_rules.py` qui créait un cache Python `.pyc` pendant son exécution, alors quun audit doit rester en lecture seule ;
- `CMD-RC-001` encore spécifique à `0.1.0` ;
- contradiction entre `CMD-BUILD-002` (interdiction globale) et `CMD-BUILD-004` (remplacement progressif en `0.3.x`), alors que deux orchestrateurs Python historiques existent encore ;
- `CMD-WEB-003` décrivant sans contexte le build Python Tauri/WASM historique ;
- dépendances `engine-v1-platform-api` déclarées mais non utilisées par les crates de jeu ;
- `RuntimeProvenance` défini mais non branché aux launchers ;
- `game-assets-lib` encore sans consommateur runtime ;
- deux orchestrateurs Python de build historiques à ne pas réutiliser dans les nouveaux POC.
Les détails et le graphe complet sont documentés dans `docs/studies/023-V0_3_0_PLATFORM_POC_AUDIT.md`.
## Corrections de règles et hygiène
Cette tranche :
- corrige le README de version ;
- ferme correctement le scope `0.2.0` dans ROADMAP ;
- distingue ce qui a été livré de ce qui est reporté ;
- rend `CMD-RC-001` générique à la version courante ;
- rend `CMD-BUILD-002` cohérente avec la migration `0.3.x` : les deux orchestrateurs Python historiques sont gelés jusquà réactivation de leur chemin, mais aucun nouveau chemin ne peut les reprendre ;
- qualifie explicitement `scripts/build_reflex_tauri_wasm.py` de mécanisme historique non réutilisable comme orchestration `0.3.x` ;
- renforce `audit_project_workspace_rules.py` pour détecter le scope `( )` restant sous une version stable présente dans CHANGELOG ;
- fait exécuter les sous-audits de `audit_rust_workspace_rules.py` avec linterpréteur courant et `-B`, afin que la gate standard reste effectivement en lecture seule malgré limport effectué dans laudit de complétude des exports.
## Premier POC retenu
Le premier host de `0.3.0` devient :
```text
Web navigateur direct + Snake
```
Tauri Android est reporté au second host, actuellement prévu en `0.3.1`.
Le but est de valider d'abord la frontière Web/WASM et les capabilities directement observables sans cumuler simultanément la généralisation Snake/WASM et la complexité d'un host mobile Tauri.
## Forecast corrigé
```text
0-pre.1 audit / règles / requirements / sizing
0-pre.2 Snake portability baseline
0-pre.3 adapter Snake WASM + build natif Web
0-pre.4 navigateur direct end-to-end
0-pre.5 seulement si un défaut réel est observé
2-beta.1 validation large
3-rc.1 candidate gelée
0.3.0 release mécanique
```
Aucune alpha n'est créée par cérémonial. Elle reste possible si le volume réel de stabilisation le justifie.
## Scope exclu de 0.3.0
- Tauri Android ;
- Tauri Desktop Snake ;
- Android multi-ABI natif ;
- réseau realtime ;
- WebTransport/QUIC ;
- Uroburas ;
- ads, billing, auth et leaderboard ;
- ECS ou nouveau moteur.
## Suppressions
Aucune suppression de fichier appartenant à la baseline `0.2.0` n'est nécessaire.
## Validations exécutées dans l'environnement de génération
Après constitution de l'état livré, les audits statiques suivants doivent être réexécutés et leur résultat est enregistré avant packaging :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history
python3 scripts/audit_distribution_layout.py
```
Aucun build/test/smoke n'est exécuté par le générateur, conformément à `CMD-GEN-007` et `CMD-BUILD-005`.
## Validation utilisateur demandée
Cette tranche ne modifie aucun source Rust/Java/TypeScript ni aucune configuration runtime. Elle ouvre toutefois une nouvelle version `0.3.0` et modifie le manifest workspace ; la gate utilisateur retient donc les audits statiques puis une gate Cargo workspace complète, conformément à la discipline des frontières de version :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
Aucun build de distribution, smoke Desktop, Tauri ou Android n'est requis par `pre.1`, car aucun runtime ni packaging de ces hosts n'est modifié.
La revue humaine doit confirmer :
- le choix Web direct comme premier POC ;
- le scope corrigé de `0.3.0` ;
- le report de Tauri Android vers le second host ;
- le sizing `pre.2` à `pre.4` ;
- la fermeture correcte des lignes historiques `0.2.0`.
## Suite après validation
Si cette gate est propre et le cadrage accepté, passer automatiquement à `0.3.0-0-pre.2`.
`pre.2` doit rester une tranche bornée de portabilité Snake et de nettoyage de dépendances ; elle ne doit pas commencer le frontend Web complet.

102
deltas/0.3.0/0-pre.2.md Normal file
View File

@@ -0,0 +1,102 @@
<!-- file: deltas/0.3.0/0-pre.2.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.2
## Base
Base requise : `0.3.0-0-pre.1.fix.2`.
Les validations utilisateur de `0-pre.1.fix.1` et `0-pre.1.fix.2` sont consignées sous `history/0.3.0/` conformément au workflow documentaire.
## Objet
Fermer la baseline Snake portable avant l'introduction de l'adapter WASM :
- retirer des crates de gameplay les dépendances plateforme déclarées mais inutilisées ;
- confirmer que le gameplay Snake ne consomme que les contrats moteur indépendants de la plateforme ;
- documenter le contrat minimal que le futur host Web devra adapter sans introduire encore de code WASM ou frontend.
## Version
La version workspace passe de `0.3.0-0-pre.1.fix.2` à `0.3.0-0-pre.2`.
## Nettoyage des dépendances
`engine-v1-platform-api` est retiré de :
- `game-snake-poc` ;
- `game-reflex-poc`.
Ces deux crates n'utilisent aucun symbole de cette dépendance. Les launchers et adapters qui utilisent réellement des capacités plateforme conservent leur dépendance.
Ce nettoyage ne déplace aucune responsabilité et ne change aucun comportement de gameplay.
## Contrat Snake portable
La baseline confirmée pour Snake est :
- état et update via `EngineGame` ;
- entrée via `InputState` ;
- actions de gameplay utilisées : `Left`, `Right`, `Up`, `Down` ;
- rendu abstrait via `EngineScene` ;
- aucune dépendance SDL3, Web/DOM/Canvas ou `engine-v1-platform-api` dans `game-snake-poc`.
Le host reste responsable de traduire ses périphériques ou widgets en actions logiques. Le futur adapter WASM `0-pre.3` doit réutiliser cette frontière sans créer un modèle Web parallèle.
## Historique des validations précédentes
Ajout de :
- `history/0.3.0/0-pre.1.fix.1.md` ;
- `history/0.3.0/0-pre.1.fix.2.md`.
Ces fichiers enregistrent uniquement les résultats effectivement fournis par l'utilisateur : audits statiques, `cargo fmt --all -- --check` et `cargo check --workspace`. Aucun Clippy, test ou smoke non exécuté n'est déclaré validé.
## Fichiers ajoutés
- `deltas/0.3.0/0-pre.2.md` ;
- `history/0.3.0/0-pre.1.fix.1.md` ;
- `history/0.3.0/0-pre.1.fix.2.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `crates/games/game-reflex-poc/Cargo.toml` ;
- `crates/games/game-snake-poc/Cargo.toml` ;
- `docs/architecture/004-INPUT_AND_CONTROLS.md` ;
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`.
## Suppressions
Aucune.
## Validations applicables
Audits statiques de préparation :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history
python3 scripts/audit_distribution_layout.py
```
Validation utilisateur demandée :
```bash
cargo fmt --all -- --check
cargo check --workspace
cargo clippy -p game-snake-poc --all-targets --all-features -- -D warnings
cargo clippy -p game-reflex-poc --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc
cargo test -p game-reflex-poc
cargo tree -p game-snake-poc --edges normal
cargo tree -p game-reflex-poc --edges normal
```
Les deux `cargo tree` doivent confirmer que les crates de gameplay ne tirent plus `engine-v1-platform-api`. Aucun smoke Desktop n'est requis : ni `engine-v1-sdl`, ni le mapping SDL, ni le runner Snake ne sont modifiés dans cette tranche.
## Suite
Après validation de `0-pre.2`, ouvrir `0-pre.3` pour l'adaptation WASM minimale de Snake conformément au plan actif. Aucun frontend Bootstrap/Vite n'est introduit avant `0-pre.4`.

118
deltas/0.3.0/0-pre.3.md Normal file
View File

@@ -0,0 +1,118 @@
<!-- file: deltas/0.3.0/0-pre.3.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.3
## Base
Base requise : `0.3.0-0-pre.2`.
La validation complète de `0-pre.2` est consignée dans `history/0.3.0/0-pre.2.md`.
## Objet
Introduire l'adapter WASM minimal de Snake avant tout frontend navigateur :
- ajouter `game-snake-poc-wasm` comme crate d'application dédiée ;
- réutiliser exclusivement `engine-v1-common` et `game-snake-poc` ;
- exposer à `wasm-bindgen` les directions logiques et la scène portable ;
- préserver la cadence de simulation en séparant événement d'entrée et `tick()` ;
- documenter un chemin Cargo + `wasm-bindgen` direct sans orchestrateur Python ;
- ne pas introduire Vite, Bootstrap, DOM, Tauri ou SDL3 dans cette tranche.
## Version
La version workspace passe de `0.3.0-0-pre.2` à `0.3.0-0-pre.3`.
## Adapter Snake WASM
La nouvelle crate `crates/apps/game-snake-poc-wasm` contient une façade `lib.rs` et un runtime propriétaire séparé.
`SnakeWasmGame` possède :
- un `SnakeState` issu de la crate de gameplay ;
- un `FixedStepRunner` à pas déterministe de 16 ms ;
- un `InputState` en attente pour la prochaine update.
Les appels `left()`, `right()`, `up()` et `down()` ne font pas avancer la simulation. Ils remplacent la direction logique en attente. `tick()` consomme ensuite cette entrée une fois, la remet à zéro et avance le runner d'une update. La cadence reste ainsi sous la responsabilité du futur host navigateur.
L'API expose également le score, la longueur, la couleur de fond et les rectangles normalisés de l'`EngineScene`. Le futur frontend `0-pre.4` peut donc dessiner le Canvas sans accéder à la structure interne de Snake.
## Frontières conservées
`game-snake-poc-wasm` ne dépend pas de :
- `engine-v1-platform-api` ;
- `engine-v1-sdl` ;
- Tauri ;
- DOM/Canvas Rust ;
- code frontend TypeScript.
La duplication limitée des getters de scène avec `game-reflex-poc-wasm` est volontairement conservée à ce stade. Une abstraction commune ne sera extraite que si les POC suivants démontrent un besoin partagé suffisamment stable.
## Documentation et audits
L'architecture du workspace mentionne désormais l'adapter Snake WASM et le contrat d'entrée précise la file directionnelle consommée par `tick()`.
L'audit de distribution exige maintenant `crates/apps/game-snake-poc-wasm/Cargo.toml`, portant le nombre de chemins requis de 19 à 20.
Le plan actif est réconcilié avec la livraison réelle de `0-pre.3`. `docs/development/009-SNAKE_WEB_WASM_BUILD.md` documente le build natif Cargo + `wasm-bindgen`, avec tous les artefacts générés sous `../builds/sasedev-games/`.
## Historique
Ajout de `history/0.3.0/0-pre.2.md` avec les résultats réellement fournis par l'utilisateur : audits, formatage, check workspace, Clippy workspace strict, tests Snake/Reflex et graphes de dépendances.
## Fichiers ajoutés
- `crates/apps/game-snake-poc-wasm/Cargo.toml` ;
- `crates/apps/game-snake-poc-wasm/src/lib.rs` ;
- `crates/apps/game-snake-poc-wasm/src/runtime.rs` ;
- `crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs` ;
- `deltas/0.3.0/0-pre.3.md` ;
- `docs/development/009-SNAKE_WEB_WASM_BUILD.md` ;
- `history/0.3.0/0-pre.2.md`.
## Fichiers modifiés
- `Cargo.toml` ;
- `README.md` ;
- `docs/architecture/001-WORKSPACE_ARCHITECTURE.md` ;
- `docs/000-README.md` ;
- `docs/architecture/004-INPUT_AND_CONTROLS.md` ;
- `docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md` ;
- `scripts/audit_distribution_layout.py`.
## Suppressions
Aucune.
## Validations applicables
Audits statiques de préparation :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history
python3 scripts/audit_distribution_layout.py
```
Validation utilisateur demandée :
```bash
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy -p game-snake-poc-wasm --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
cargo tree -p game-snake-poc-wasm --edges normal
```
Le `cargo tree` doit rester limité à `engine-v1-common`, `game-snake-poc`, `wasm-bindgen` et leurs dépendances nécessaires ; aucune dépendance SDL3, Tauri ou `engine-v1-platform-api` ne doit apparaître.
La commande `wasm-bindgen` doit produire les bindings navigateur sans écrire d'artefact généré dans le dépôt. Aucun build Vite/Bootstrap et aucun smoke navigateur ne sont requis dans cette tranche, car le frontend appartient à `0-pre.4`.
## Suite
Après validation de `0-pre.3`, ouvrir `0-pre.4` pour le frontend Web direct Vite/TypeScript, son shell HTML Bootstrap 5, le Canvas et les contrôles clavier/tactiles.

View File

@@ -0,0 +1,56 @@
<!-- file: deltas/0.3.0/0-pre.4.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.4.fix.1
## Objectif
Fermer les défauts découverts pendant la validation utilisateur de `0-pre.4` sans ouvrir le scope `0-pre.5`.
## Corrigé
- suppression de `compilerOptions.baseUrl`, retiré par TypeScript 7, tout en conservant le mapping `paths` et l'alias Vite `@snake-wasm` ;
- réintégration de `simplebar` et `resize-observer-polyfill` depuis le template KSP comme dépendances runtime du shell navigateur ;
- ajout du Sass SimpleBar KSP-derived et activation de `data-simplebar` sur `app-main` ;
- maintien du header et du footer fixes avec scroll interne de la zone centrale lorsque son contenu dépasse la hauteur disponible ;
- retrait des contraintes `h-100` internes qui empêchaient le contenu du shell de prendre sa hauteur naturelle ;
- correction du plateau Snake de `1 / 1` vers `3 / 5`, cohérente avec la grille logique `12 × 20`, afin de conserver des cellules visuellement carrées ;
- conservation du resize haute densité via `devicePixelRatio` à l'intérieur du ratio CSS corrigé ;
- correction de la documentation qui classait à tort SimpleBar et le polyfill parmi les dépendances propres à Tauri ;
- ajout d'une règle durable sur le scroll shell Web et extension de l'audit de distribution au Sass SimpleBar ;
- consignation sous `history/0.3.0/0-pre.4.md` des validations réussies et de l'échec `TS5102` ayant déclenché ce fix.
## Non modifié
- gameplay Snake ;
- adapter WASM et cadence `tick()` ;
- mappings clavier/pointer ;
- lifecycle complet, assets, logging et provenance prévus pour `0-pre.5` ;
- route de smoke : `http://127.0.0.1:1434/main.html`.
## Validation demandée
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy -p game-snake-poc-wasm --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
cd Web/game-snake-poc
npm install
npm run build
npm run dev
```
Smoke navigateur sur `http://127.0.0.1:1434/main.html` : vérifier clavier, boutons pointer/tactiles, cellules non déformées, scroll SimpleBar lorsque nécessaire et fixation du header/footer.
## Suite
Après validation du fix, `0-pre.5` peut fermer lifecycle/resize, assets, logging, provenance runtime et non-régression Desktop SDL3 conformément au plan actif.

View File

@@ -0,0 +1,50 @@
<!-- file: deltas/0.3.0/0-pre.4.fix.2.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.4.fix.2
## Objectif
Fermer les deux erreurs TypeScript découvertes pendant la validation utilisateur de `0-pre.4.fix.1`, sans modifier le gameplay ni ouvrir le scope `0-pre.5`.
## Corrigé
- `tsconfig.json` résout désormais `@snake-wasm` vers `game_snake_poc_wasm.d.ts`, généré par `wasm-bindgen`, afin que TypeScript consomme les types réels du bridge sans déclaration manuelle dupliquée ;
- `vite.config.ts` conserve séparément l'alias runtime vers `game_snake_poc_wasm.js` ;
- après validation de `canvas.getContext("2d")`, `game.ts` capture le contexte dans une constante `CanvasRenderingContext2D` non nullable utilisée par le callback de frame ;
- consignation sous `history/0.3.0/0-pre.4.fix.1.md` de la gate réellement exécutée et des deux erreurs ayant déclenché ce fix ;
- réconciliation du plan actif avec ce second fix de `0-pre.4`.
## Non modifié
- gameplay Snake ;
- cadence et API du bridge WASM ;
- ratio Canvas `3 / 5` ;
- SimpleBar, header/footer fixes et shell Bootstrap ;
- mappings clavier/pointer ;
- lifecycle complet, assets, logging et provenance prévus pour `0-pre.5`.
## Validation demandée
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
cargo tree -p game-snake-poc-wasm --edges normal
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
```
Smoke navigateur sur `http://127.0.0.1:1434/main.html` : vérifier chargement WASM, clavier, boutons pointer/tactiles, ratio du plateau et scroll SimpleBar lorsque nécessaire.
## Suite
Après validation du fix, `0-pre.5` peut fermer lifecycle/resize, assets, logging, provenance runtime et non-régression Desktop SDL3 conformément au plan actif.

View File

@@ -0,0 +1,64 @@
<!-- file: deltas/0.3.0/0-pre.4.fix.3.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.4.fix.3
## Objectif
Aligner le scroll SimpleBar du host Snake Web sur le shell réellement utilisé par les applications Desk KSP après validation réussie du build `0-pre.4.fix.2`.
## Diagnostic
La comparaison avec `ksp-app-*-desk` confirme que le package et le Sass SimpleBar étaient présents, mais que le conteneur n'était pas structuré comme la référence :
- KSP garde `app-main` comme conteneur de hauteur bornée et non scrollable ;
- un enfant `row h-100` porte la zone centrale ;
- `data-simplebar` est attaché à `app-content app-scrollable h-100` ;
- `app-scrollable` et `app-content` ont `height: 100%` / `max-height: 100%` ;
- `main.ts` importe `resize-observer-polyfill` puis `simplebar` et installe le polyfill global avant l'initialisation de l'application.
Le point d'entrée TypeScript Snake suit déjà ce dernier contrat ; le défaut se situe donc dans la structure HTML/Sass.
## Corrigé
- `data-simplebar` est déplacé de `app-main` vers `app-content app-scrollable h-100` ;
- ajout du conteneur `row g-0 h-100 flex-nowrap` comme dans le shell KSP ;
- `app-main` reste borné à la hauteur disponible entre header et footer ;
- `app-scrollable` et `app-content` portent explicitement `height: 100%` et `max-height: 100%` ;
- la règle `GAME-PLATFORM-023` pérennise ce contrat lorsque le shell KSP/SimpleBar est réutilisé ;
- `audit_distribution_layout.py` vérifie statiquement que `data-simplebar` reste sur la zone centrale et que le bootstrap TypeScript/Sass requis est présent ;
- la documentation frontend et le plan `0.3.0` sont réconciliés ;
- `history/0.3.0/0-pre.4.fix.2.md` consigne la validation réussie du build et le défaut visuel restant.
## Non modifié
- gameplay Snake ;
- bridge WASM et cadence ;
- ratio Canvas `3 / 5` ;
- mapping clavier/pointer ;
- dépendances npm ;
- lifecycle complet, assets, logging et provenance prévus pour `0-pre.5`.
## Validation demandée
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
```
Smoke navigateur sur `http://127.0.0.1:1434/main.html` : réduire la hauteur de fenêtre ou utiliser un viewport assez bas pour forcer le dépassement vertical, puis vérifier que seule la zone centrale défile avec SimpleBar tandis que header et footer restent fixes. Vérifier aussi que Canvas, clavier et boutons pointer restent fonctionnels.
## Suite
Après validation du scroll SimpleBar, `0-pre.5` peut ouvrir l'intégration plateforme complète prévue par le plan actif.

60
deltas/0.3.0/0-pre.4.md Normal file
View File

@@ -0,0 +1,60 @@
<!-- file: deltas/0.3.0/0-pre.4.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.4
## Objectif
Introduire le premier host navigateur direct Snake sans élargir encore le scope vers le lifecycle complet, les assets runtime, le logging ou la provenance de `0-pre.5`.
## Livré
- création de `Web/game-snake-poc/`, package Vite/TypeScript autonome hors workspace Cargo ;
- reprise du template frontend des applications Desk KSP pour la structure `frontend/main.html`, `frontend/sass/`, `frontend/ts/` et le thème Bootstrap/Bootswatch Pulse ;
- reprise uniquement des dépendances npm KSP utiles au navigateur direct : Bootstrap 5, Font Awesome, `sass-embedded`, TypeScript, Vite et types associés ;
- exclusion volontaire des dépendances Tauri, SimpleBar et tracing spécifiques aux apps Desk ;
- consommation des bindings `game-snake-poc-wasm` générés sous `../builds/sasedev-games/game-snake-poc-web/wasm/` ;
- Canvas responsive prenant en compte `devicePixelRatio` ;
- rendu de l'`EngineScene` normalisée sans dupliquer le gameplay ;
- entrées clavier flèches, `WASD`, `ZQSD` et quatre boutons pointer/tactiles ;
- état de chargement, score, longueur et retour d'erreur dans le shell ;
- cache Vite et distribution frontend maintenus hors dépôt ;
- audit de distribution étendu au host Web direct ;
- enregistrement sous `history/0.3.0/0-pre.3.md` des validations utilisateur de `0-pre.3`.
## Hors périmètre
- lifecycle complet navigateur ;
- assets runtime du POC ;
- logging structuré Web ;
- `RuntimeProvenance` ;
- Tauri Snake ;
- Android ;
- extraction d'une abstraction Web commune non encore justifiée.
## Validation demandée
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy -p game-snake-poc-wasm --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
cd Web/game-snake-poc
npm install
npm run build
npm run dev
```
Smoke navigateur : ouvrir `http://127.0.0.1:1434/main.html`, puis vérifier le chargement WASM, le Canvas, les quatre familles d'entrées prévues et le comportement responsive Desktop/mobile.
## Suite
`0-pre.5` ferme l'intégration plateforme du POC : lifecycle/resize complet, assets, logging, provenance runtime et non-régression Desktop SDL3.

View File

@@ -0,0 +1,77 @@
<!-- file: deltas/0.3.0/0-pre.5.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.5.fix.1
## Objectif
Corriger uniquement l'exposition des assets runtime du host Snake Web en développement, après validation partielle de `0-pre.5`.
## Défaut confirmé
`cargo`, WASM, `wasm-bindgen` et le build Vite passent. Sous `vite dev`, le frontend échoue néanmoins avant le démarrage du jeu lors de `fetchJson()` : les routes `common/data/runtime.json` et `game/data/game.json` ne sont pas servies comme attendu.
Les targets `vite-plugin-static-copy` utilisent des chemins source absolus. Le plugin conserve la structure source par défaut ; le `dest` seul ne constitue donc pas le contrat de route recherché.
## Correction
Les deux targets Vite utilisent désormais :
```text
rename: { stripBase: true }
```
Le contrat devient explicitement :
```text
assets/common/data/runtime.json -> /common/data/runtime.json
assets/game-snake-poc/data/game.json -> /game/data/game.json
```
Ces routes doivent être identiques sous `vite dev` et dans `dist/`. Aucun asset source n'est dupliqué sous `Web/`.
## Garde-fou
`audit_distribution_layout.py` exige désormais `stripBase: true` sur les deux targets, en plus des namespaces `common/data` et `game/data`.
## Historique
`history/0.3.0/0-pre.5.md` consigne la validation partielle fournie par l'utilisateur et le défaut runtime exact.
## Non modifié
- gameplay Snake ;
- adapter WASM/provenance ;
- lifecycle/resize ;
- logging frontend ;
- SimpleBar/Canvas ;
- runner Desktop SDL3 ;
- ROADMAP et CHANGELOG.
## Validation demandée
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p engine-v1-platform-api --all-targets --all-features
cargo test -p game-snake-poc-wasm --all-targets --all-features
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
cargo tree -p game-snake-poc-wasm --edges normal
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
```
Smoke sur `http://127.0.0.1:1434/main.html` :
1. `Assets` doit passer de `chargement…` à `snake / engine-v1` ;
2. Snake doit démarrer ;
3. la provenance doit quitter `unknown / unknown` dès qu'une classe/device et une entrée sont observées ;
4. revalider lifecycle/resize/SimpleBar/Canvas/contrôles ;
5. terminer par `cargo run -p game-snake-poc-desktop` pour la non-régression SDL3.

118
deltas/0.3.0/0-pre.5.md Normal file
View File

@@ -0,0 +1,118 @@
<!-- file: deltas/0.3.0/0-pre.5.md -->
<!-- version: 1 -->
# Delta 0.3.0-0-pre.5
## Objectif
Fermer l'intégration plateforme du premier POC Snake Web direct après validation de `0-pre.4.fix.3` : lifecycle/resize navigateur, assets runtime, logging frontend structuré, provenance `Web/Wasm/Browser` et non-régression du runner Desktop SDL3.
## État précédent validé
`0-pre.4.fix.3` a été accepté par l'utilisateur le 2026-09-20 : build Vite propre et smoke visuel/fonctionnel conforme, y compris Canvas portrait, contrôles et scroll SimpleBar du shell KSP-derived. Le résultat est consigné sous `history/0.3.0/0-pre.4.fix.3.md`.
## Lifecycle et resize
Le host Web ne laisse plus une boucle `requestAnimationFrame` implicite posséder tout le lifecycle :
- `SnakeWebSession` expose `pause`, `resume`, `renderNow`, `refreshDeviceClass` et `dispose` ;
- `visibilitychange` et `pagehide` suspendent les ticks ;
- la reprise réinitialise l'origine temporelle et l'accumulateur afin d'éviter un rattrapage massif après un onglet caché ;
- `pageshow` reprend et redessine ;
- `ResizeObserver` redessine le Canvas et rafraîchit la classe d'appareil sans faire avancer la simulation ;
- la destruction de page libère l'instance WASM.
Le fixed-step gameplay reste dans Rust/WASM et aucun événement lifecycle n'est ajouté à `game-snake-poc`.
## Assets Web
Le package Vite déclare `vite-plugin-static-copy` et package explicitement les deux assets-sondes depuis les sources canoniques :
```text
assets/common/data/runtime.json -> common/data/runtime.json
assets/game-snake-poc/data/game.json -> game/data/game.json
```
`frontend/ts/assets.ts` charge et valide les deux JSON avant le démarrage WASM. Le shell affiche ensuite un statut d'assets afin que le smoke confirme le chargement réel. Aucune copie source durable n'est créée sous `Web/`.
## Logging navigateur
`frontend/ts/logging.ts` centralise les diagnostics Web sous forme d'événements `level / target / action / fields` vers la console du navigateur.
Ce logging est spécifique au frontend navigateur direct :
- il ne dépend pas de Tauri ;
- il ne remplace pas `tracing` dans le code Rust ;
- les runners natifs continuent d'utiliser `game-logging-lib`.
## Provenance runtime
`game-snake-poc-wasm` dépend désormais de `engine-v1-platform-api` et conserve une `RuntimeProvenance` réelle.
Dimensions fixes :
```text
PlatformFamily = Web
ExecutionModel = Wasm
RuntimeHost = Browser
```
Le host renseigne ensuite la classe `Desktop/Phone/Tablet/Unknown` et le profil d'entrée `Unknown/KeyboardMouse/Touch/Mixed` selon les capacités et inputs observés. Ces dimensions sont exposées par le bridge WASM et affichées dans le shell.
La crate de gameplay `game-snake-poc` reste sans dépendance plateforme.
## Règles et documentation
- `GAME-ASSET-007` fixe le packaging Web `common/` + `game/` sans copie source ;
- `GAME-TRACE-005` distingue logging frontend navigateur et tracing Rust ;
- `GAME-PLATFORM-024` fixe pause/reprise sans catch-up massif ;
- `GAME-PLATFORM-025` fixe la provenance navigateur via `engine-v1-platform-api` ;
- l'architecture assets/provenance, la documentation tracing/frontend et les gates sont réconciliées ;
- `audit_distribution_layout.py` vérifie désormais les fichiers et branchements statiques de cette intégration.
## Non modifié
- règles de gameplay Snake ;
- runner Snake Desktop SDL3 ;
- moteur SDL3 ;
- Android/Tauri ;
- ROADMAP et CHANGELOG, réservés à la consolidation prévue par le plan.
## Validation demandée
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p engine-v1-platform-api --all-targets --all-features
cargo test -p game-snake-poc-wasm --all-targets --all-features
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm --target web --out-dir ../builds/sasedev-games/game-snake-poc-web/wasm --out-name game_snake_poc_wasm
cargo tree -p game-snake-poc-wasm --edges normal
(cd Web/game-snake-poc && npm install && npm run build && npm run dev)
```
Smoke navigateur sur `http://127.0.0.1:1434/main.html` :
1. vérifier que `Assets` passe à `snake / engine-v1` avant le statut `Prêt` ;
2. vérifier que la provenance affiche `web / browser / wasm / ... / ...` ;
3. utiliser clavier/souris puis tactile si disponible et vérifier l'évolution du profil d'entrée (`keyboard-mouse`, `touch` ou `mixed`) ;
4. masquer l'onglet quelques secondes puis revenir : Snake ne doit pas accélérer pour rattraper le temps caché ;
5. redimensionner la fenêtre : Canvas et provenance device doivent rester cohérents sans saut de gameplay ;
6. revalider SimpleBar, clavier et contrôles pointer/tactiles.
Puis vérifier la non-régression Desktop :
```bash
cargo run -p game-snake-poc-desktop
```
## Suite
Si cette gate ne révèle ni duplication structurante ni défaut de frontière, la tranche conditionnelle `0-pre.6` est omise et la version peut entrer directement en `2-beta.1`. Sinon, `0-pre.6` ne traite que la correction/extraction prouvée par cette validation.

64
deltas/0.3.0/2-beta.1.md Normal file
View File

@@ -0,0 +1,64 @@
<!-- file: deltas/0.3.0/2-beta.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-2-beta.1
## Objectif
Entrer en beta avec un scope `0.3.0` feature-complete et exécuter une validation large du POC Snake Web direct et de son témoin Desktop SDL3, sans introduire de nouveau comportement.
## Baseline acceptée
`0.3.0-0-pre.5.fix.1` est validée : audits, check/Clippy, tests plateforme/WASM, build WASM, build Vite, assets/provenance/inputs dans le navigateur et smoke Desktop SDL3 passent. Aucun défaut structurel ne justifie `0-pre.6`, qui est donc omise.
## Changements
- passage de la version workspace à `0.3.0-2-beta.1` ;
- passage du package frontend Snake Web au jalon `0.3.0-2-beta.1` ;
- ajout de l'historique validé `0-pre.5.fix.1` ;
- réconciliation du plan `0.3.0` avec l'omission de `0-pre.6` ;
- documentation de la gate beta release Web/Desktop.
Aucun gameplay, bridge WASM, runtime SDL3, shell HTML/TypeScript ou contrat d'asset n'est modifié par cette tranche.
## Validation attendue
Depuis la racine :
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
Build et smoke Desktop Snake release :
```bash
cargo build -p game-snake-poc-desktop --release
../builds/sasedev-games/target/release/game-snake-poc-desktop
```
Build WASM release et frontend production :
```bash
cargo build -p game-snake-poc-wasm --release --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/release/game_snake_poc_wasm.wasm \
--target web \
--out-dir ../builds/sasedev-games/game-snake-poc-web/wasm \
--out-name game_snake_poc_wasm
(cd Web/game-snake-poc && npm install && npm run build && npm run preview)
```
Ouvrir `http://127.0.0.1:4174/main.html` puis vérifier : jeu visible, clavier/pointer, ratio Canvas, SimpleBar, assets `snake / engine-v1`, provenance cohérente, resize et pause/reprise sans rattrapage massif après masquage d'onglet.
## Après validation
Si cette gate beta est propre, la tranche suivante est `0.3.0-2-beta.2` pour la consolidation documentaire et la transmission : plan, documentation durable, `CHANGELOG.md`, `ROADMAP.md`, history et prompt de la version/session suivante.

View File

@@ -0,0 +1,67 @@
<!-- file: deltas/0.3.0/2-beta.2.fix.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-2-beta.2.fix.1
## Base requise
`0.3.0-2-beta.2` validée mécaniquement par l'utilisateur le 2026-09-20.
## Objectif
Corriger la transmission documentaire de `2-beta.2` : rendre le prompt `0.3.1` suffisamment autonome pour limiter les rappels conversationnels et les fixes évitables, sans reproduire l'intégralité des règles KSP ni modifier le code/runtime.
## Changements
- enrichissement de `prompts/003-V0_3_1_START_PROMPT.md` avec ordre de lecture, handoff `0.3.0`, cadrage `pre.1`, invariants Tauri/Android/WASM, documentation, commandes, validations, forecast et condition de fin de session ;
- ajout de `docs/rules/PROMPT_STRUCTURE.md`, inspiré du principe KSP de prompt autonome mais volontairement plus compact ;
- ajout de règles explicites sur le rôle de `deltas/`/`history/`, le timing `ROADMAP`/`CHANGELOG`/prompt suivant et la séparation des validations utilisateur ;
- ajout d'une politique `DOC-CRATE-*` pour décider utilement de `README.md`/`USAGE.md` sans créer de fichiers cérémoniels ;
- clarification du cycle du prompt suivant : rédaction pendant la consolidation, vérification/complément à la première RC gelée ;
- clarification du versionnement des fixes strictement documentaires : le delta porte `.fix.1` mais `workspace.package.version` et les packages runtime restent `0.3.0-2-beta.2` ;
- ajout de `history/0.3.0/2-beta.2.md` avec les résultats réellement validés et le motif humain du fix ;
- réconciliation du plan `0.3.0` avec ce correctif de transmission.
`CHANGELOG.md` et `ROADMAP.md` ne sont pas modifiés : le fix ne change ni la publication candidate ni le scope produit.
## Fichiers ajoutés
```text
docs/rules/PROMPT_STRUCTURE.md
history/0.3.0/2-beta.2.md
deltas/0.3.0/2-beta.2.fix.1.md
```
## Fichiers modifiés
```text
RULES.md
docs/000-README.md
docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/RULES_SESSION_PLANNING.md
docs/rules/VERSION_WORKFLOW.md
prompts/003-V0_3_1_START_PROMPT.md
```
## Fichiers supprimés
Aucun.
## Version technique
Ce correctif est strictement documentaire. Conformément à `VER-DOCFIX-001`, il ne modifie ni `workspace.package.version` ni la version du package Web : la version technique reste `0.3.0-2-beta.2`.
## Validation attendue
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
```
Aucun build Cargo/npm ni smoke n'est requis pour ce fix : aucun fichier consommé par le build/runtime n'est modifié.
## Après validation
Si la revue humaine du prompt enrichi est satisfaisante, ouvrir `0.3.0-3-rc.1` et appliquer la matrice RC. La RC vérifie/complète le prompt `0.3.1`, écrit l'entrée RC du `CHANGELOG.md` et ne rouvre pas le scope fonctionnel.

View File

@@ -0,0 +1,66 @@
<!-- file: deltas/0.3.0/2-beta.2.fix.2.md -->
<!-- version: 1 -->
# Delta 0.3.0-2-beta.2.fix.2
## Base requise
`0.3.0-2-beta.2.fix.1`.
## Objectif
Finaliser la gouvernance des commandes et des validations transmise au prompt `0.3.1`, afin d'éviter les gates surdimensionnées, les logs pollués et les workflows Tauri incorrects.
Ce correctif est strictement documentaire. Il ne modifie aucun code, manifeste runtime, dépendance, configuration de build ni version technique.
## Décisions consolidées
- un fix strictement documentaire ne modifie aucune version Cargo/npm/Gradle/Tauri/Android ou autre version consommée par le runtime/build ;
- tout changement de code Rust, manifeste Cargo, feature ou dépendance Rust déclenche `cargo fmt --all`, `cargo fmt --all -- --check`, `cargo check --workspace` puis `cargo clippy --workspace --all-targets --all-features -- -D warnings` ;
- les audits Python sont sélectionnés selon les fichiers réellement touchés ;
- `cargo test -p <crate> --all-targets --all-features` est la stratégie normale ;
- `cargo test --workspace --all-targets --all-features` est réservé à un ou quelques jalons explicitement planifiés ou à une portée transverse incertaine ;
- `cargo tree` n'est exécuté que si les dépendances/features changent ou qu'un diagnostic de frontière le justifie ;
- toute commande nécessitant un changement de répertoire utilise `(cd <dir> && <commande>)` ;
- dans une app Tauri, npm sert à gérer les dépendances frontend ; les smokes passent par `cargo tauri dev` et le packaging final/prefinal par `cargo tauri build`, les hooks Tauri possédant Vite/TypeScript/WASM.
## Fichiers ajoutés
```text
history/0.3.0/2-beta.2.fix.1.md
deltas/0.3.0/2-beta.2.fix.2.md
```
## Fichiers modifiés
```text
docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md
docs/rules/PROMPT_STRUCTURE.md
docs/rules/RULES_COMMANDS.md
docs/rules/RULES_SESSION_PLANNING.md
docs/rules/RULES_VALIDATION_MATRIX.md
docs/rules/VERSION_WORKFLOW.md
prompts/003-V0_3_1_START_PROMPT.md
```
## Fichiers supprimés
Aucun.
## Version technique
Aucune version technique n'est modifiée. La workspace et le package Web restent `0.3.0-2-beta.2`.
## Validation attendue
Les seuls fichiers touchés sont Markdown. L'audit applicable est donc :
```bash
python3 scripts/audit_markdown_tables.py docs prompts deltas history
```
Aucun `cargo fmt`, `cargo check`, Clippy, test Cargo, `cargo tree`, audit de distribution, npm, Tauri ou smoke n'est requis pour ce correctif documentaire.
## Après validation
Si la revue humaine confirme ces règles, ouvrir `0.3.0-3-rc.1`. La RC appliquera la matrice finale, écrira l'entrée RC du `CHANGELOG.md` et vérifiera une dernière fois le prompt `0.3.1` sans rouvrir le scope fonctionnel.

45
deltas/0.3.0/2-beta.2.md Normal file
View File

@@ -0,0 +1,45 @@
<!-- file: deltas/0.3.0/2-beta.2.md -->
<!-- version: 1 -->
# Delta 0.3.0-2-beta.2
## Objectif
Consolider la version feature-complete après validation de `2-beta.1`, préparer le gel RC et transmettre un contexte autonome à `0.3.1`, sans modifier le comportement runtime.
## Baseline acceptée
`0.3.0-2-beta.1` est validée : audits, format/check/Clippy, 34 tests workspace, Desktop Snake release et Web/WASM release passent.
## Changements
- passage de la version workspace et du package Web à `0.3.0-2-beta.2` ;
- ajout de `history/0.3.0/2-beta.1.md` ;
- classement des lignes `0.3.0` réellement livrées dans `ROADMAP.md` ;
- ajout d'une baseline architecturale consolidée du POC Snake Web direct ;
- ajout d'une matrice RC spécifique à `0.3.0` ;
- réconciliation de l'étude `pre.1` avec le contrat autoritaire des archives de tag ;
- mise à jour du plan actif avec le résultat beta ;
- ajout du prompt `003-V0_3_1_START_PROMPT.md` pour le second host Snake Tauri Android.
`CHANGELOG.md` est revu mais ne reçoit pas d'entrée beta : `DOC-CHG-002` et `DOC-CHG-003` réservent la synthèse publiée aux RC et stables. La matière de la future entrée RC est consolidée dans les documents durables de cette tranche.
## Validation attendue
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
(cd Web/game-snake-poc && npm install && npm run build)
```
Aucun test/smoke complet n'est requis par ce delta documentaire + métadonnées : `2-beta.1` vient de valider les artefacts release, et aucun code/runtime n'est modifié.
## Après validation
Si cette consolidation est acceptée, produire `0.3.0-3-rc.1` : gel fonctionnel, entrée RC du `CHANGELOG.md`, historique `2-beta.2`, réexécution de la matrice RC et vérification finale du prompt `0.3.1`.

88
deltas/0.3.0/3-rc.1.md Normal file
View File

@@ -0,0 +1,88 @@
<!-- file: deltas/0.3.0/3-rc.1.md -->
<!-- version: 1 -->
# Delta 0.3.0-3-rc.1
## Base requise
`0.3.0-2-beta.2.fix.2`.
Cette base a été validée par revue humaine et audit Markdown proportionnel au correctif documentaire.
## Objectif
Créer la candidate de publication `0.3.0` sans rouvrir le scope fonctionnel.
## Gel RC
Le scope `0.3.0` est gelé. Cette tranche n'ajoute :
- aucune fonctionnalité ;
- aucune capability ;
- aucun refactor architectural ;
- aucune dépendance ;
- aucun changement de gameplay ou de runtime.
Seuls les correctifs autorisés par `VER-RC-*` pourront suivre sous `3-rc.1.fix.N`.
## Changements
- passage de `workspace.package.version` à `0.3.0-3-rc.1` ;
- passage du package Web Snake à `0.3.0-3-rc.1` ;
- affichage de la candidate courante dans `README.md` ;
- création de l'entrée RC `0.3.0-3-rc.1` dans `CHANGELOG.md` ;
- enregistrement de la validation `2-beta.2.fix.2` sous `history/0.3.0/` ;
- réconciliation du plan `0.3.0` avec l'ouverture de la RC ;
- revue finale de `prompts/003-V0_3_1_START_PROMPT.md` : aucune modification supplémentaire requise.
Les métadonnées Android historiques restent inchangées : `0.3.0` ne livre pas un nouveau host Android. Le package Tauri Reflex historique reste également hors scope.
## Validation RC
Appliquer `docs/testing/004-V0_3_0_RC_VALIDATION_MATRIX.md`.
### Gate workspace
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
```
Le test workspace complet est volontairement exécuté à ce jalon RC final. Aucun `cargo tree` n'est requis puisque les dépendances/features ne changent pas.
### Desktop Snake release
```bash
cargo build -p game-snake-poc-desktop --release
../builds/sasedev-games/target/release/game-snake-poc-desktop
```
Critères : démarrage, rendu, contrôles et arrêt propre.
### Web/WASM release
```bash
cargo build -p game-snake-poc-wasm --release --target wasm32-unknown-unknown
wasm-bindgen \
../builds/sasedev-games/target/wasm32-unknown-unknown/release/game_snake_poc_wasm.wasm \
--target web \
--out-dir ../builds/sasedev-games/game-snake-poc-web/wasm \
--out-name game_snake_poc_wasm
(cd Web/game-snake-poc && npm install && npm run build && npm run preview)
```
Ouvrir `http://127.0.0.1:4174/main.html` et confirmer Canvas, inputs, SimpleBar, assets, provenance, resize, lifecycle et absence d'erreur runtime bloquante.
## Après validation
Si la gate RC est propre et qu'aucun correctif n'est nécessaire, la promotion vers `0.3.0` est mécanique : versions stables, entrée stable du `CHANGELOG.md`, historique RC et delta de release, sans nouveau scope.

54
deltas/0.3.0/rel.001.md Normal file
View File

@@ -0,0 +1,54 @@
<!-- file: deltas/0.3.0/rel.001.md -->
<!-- version: 1 -->
# Delta 0.3.0 — release stable
## Base
Base validée : `0.3.0-3-rc.1`.
## Objet
Promouvoir mécaniquement la candidate validée vers `0.3.0` sans introduire de nouveau comportement.
## Changements
La release stable :
- passe `workspace.package.version` de `0.3.0-3-rc.1` à `0.3.0` ;
- passe le package frontend Web Snake de `0.3.0-3-rc.1` à `0.3.0` ;
- positionne `README.md` sur la stable `0.3.0` et indique `0.3.1` comme prochaine version planifiée mais non démarrée ;
- ajoute l'entrée stable `0.3.0` dans `CHANGELOG.md` ;
- enregistre la validation effective de la RC dans `history/0.3.0/3-rc.1.md` ;
- clôt le plan `0.3.0` et réconcilie la baseline architecturale avec l'état effectivement publié.
Les métadonnées Android et le package frontend Reflex/Tauri ne sont pas modifiés : ils n'appartiennent pas au chemin de distribution ajouté par `0.3.0`.
Le prompt `prompts/003-V0_3_1_START_PROMPT.md` est conservé tel que validé en RC.
## Frontière de release
Aucun changement de gameplay, de Rust source, de dépendance, de build frontend, d'architecture ou de capability n'est introduit après la RC.
## Validation
La gate RC complète a déjà été validée sur un état fonctionnellement identique. La promotion stable ne requiert donc que les gates proportionnelles au changement de version et à la documentation :
```bash
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md CHANGELOG.md docs deltas history
cargo check --workspace
(cd Web/game-snake-poc && npm install && npm run build)
```
`cargo fmt`, Clippy, la suite workspace complète, `cargo tree`, l'audit de distribution et les smokes ne sont pas répétés : cette promotion ne touche ni code Rust, ni dépendance, ni layout de distribution, et ces gates ont déjà été validées sur `0.3.0-3-rc.1` fonctionnellement identique.
## Publication
Après validation de ce delta :
- commit de release ;
- tag stable `v0.3.0` ;
- prochaine session ouverte uniquement à partir de la stable taggée `v0.3.0` et de `prompts/003-V0_3_1_START_PROMPT.md`.

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

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

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

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

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

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

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

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

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

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

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md -->
<!-- version: 21 -->
<!-- version: 29 -->
# Documentation games.sasedev
@@ -12,6 +12,12 @@
- [`ideas/000-README.md`](ideas/000-README.md) — rôle des idées non engagées et règles de maturation.
- [`studies/000-README.md`](studies/000-README.md) — rôle des études comparatives non normatives.
## Plans
- [`plans/000-README.md`](plans/000-README.md) — plans vivants des versions ; le plan actif porte notamment le découpage prévisionnel souple des prereleases.
- [`plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md`](plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md) — plan clôturé de `0.3.0`, premier POC Snake Web direct.
- [`plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md`](plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md) — plan actif de `0.3.1`, second host Snake Tauri Android.
## Architecture
- [`architecture/001-WORKSPACE_ARCHITECTURE.md`](architecture/001-WORKSPACE_ARCHITECTURE.md) — workspace, générations de moteur, jeux, runners, assets et Android.
@@ -51,7 +57,7 @@
## Règles
Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](rules/RULES_DOCUMENTATION.md) pour la gouvernance documentaire, [`rules/RULES_COMMANDS.md`](rules/RULES_COMMANDS.md) pour la politique d'exécution, [`rules/RULES_VALIDATION_MATRIX.md`](rules/RULES_VALIDATION_MATRIX.md) pour la matrice des gates, [`rules/RULES_SESSION_PLANNING.md`](rules/RULES_SESSION_PLANNING.md) pour le cadrage des sessions et [`rules/RULES_SERVER_HOSTING.md`](rules/RULES_SERVER_HOSTING.md) pour la portabilité d'auto-hébergement.
Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](rules/RULES_DOCUMENTATION.md) pour la gouvernance documentaire, [`rules/RULES_COMMANDS.md`](rules/RULES_COMMANDS.md) pour la politique d'exécution, [`rules/RULES_VALIDATION_MATRIX.md`](rules/RULES_VALIDATION_MATRIX.md) pour la matrice des gates, [`rules/RULES_SESSION_PLANNING.md`](rules/RULES_SESSION_PLANNING.md) pour le cadrage des sessions, [`rules/PROMPT_STRUCTURE.md`](rules/PROMPT_STRUCTURE.md) pour le contrat des prompts de reprise et [`rules/RULES_SERVER_HOSTING.md`](rules/RULES_SERVER_HOSTING.md) pour la portabilité d'auto-hébergement.
## Validation
@@ -63,11 +69,16 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](ru
- [`testing/001-TEST_ARCHITECTURE.md`](testing/001-TEST_ARCHITECTURE.md) — séparation `unit_tests/` / `tests/`, tests ciblés et tracing de test.
- [`testing/002-BETA_VALIDATION_MATRIX.md`](testing/002-BETA_VALIDATION_MATRIX.md) — matrice beta Desktop SDL3, Tauri/WASM et Android multi-appareils.
- [`testing/003-RC_VALIDATION_MATRIX.md`](testing/003-RC_VALIDATION_MATRIX.md) — matrice RC de reproductibilité, packaging final et promotion vers `0.1.0`.
- [`testing/004-V0_3_0_RC_VALIDATION_MATRIX.md`](testing/004-V0_3_0_RC_VALIDATION_MATRIX.md) — gate RC spécifique au POC Snake Web direct et à son témoin Desktop SDL3.
- [`testing/005-V0_3_1_RC_VALIDATION_MATRIX.md`](testing/005-V0_3_1_RC_VALIDATION_MATRIX.md) — gate RC du POC Snake Tauri Android de référence, incluant workspace complet et APK universal x86_64 + ARM64.
- [`development/003-TRACING_AND_DIAGNOSTICS.md`](development/003-TRACING_AND_DIAGNOSTICS.md) — socle `tracing`, subscriber et appender communs.
- [`development/004-SDL3_DESKTOP_PREREQUISITES.md`](development/004-SDL3_DESKTOP_PREREQUISITES.md) — prérequis SDL3 Desktop, stratégie de liaison système et vérification `pkg-config`.
- [`development/005-DESKTOP_WINDOW_POLICY.md`](development/005-DESKTOP_WINDOW_POLICY.md) — taille initiale, redimensionnement et séparation future entre fenêtre physique et résolution virtuelle.
- [`development/006-ANDROID_RUST_NATIVE_BUILD.md`](development/006-ANDROID_RUST_NATIVE_BUILD.md) — build `cdylib` Rust Android, cargo-ndk, packaging `jniLibs` et symbole `SDL_main`.
- [`development/007-ANDROID_JNI_BRIDGE.md`](development/007-ANDROID_JNI_BRIDGE.md) — frontière Java/JNI minimale, version de contrat et séparation avec l'input SDL3.
- [`development/008-WASM_TAURI_POC.md`](development/008-WASM_TAURI_POC.md) — baseline historique Reflex Tauri/WebAssembly et génération WASM associée.
- [`development/009-SNAKE_WEB_WASM_BUILD.md`](development/009-SNAKE_WEB_WASM_BUILD.md) — build Cargo + `wasm-bindgen` natif de l'adapter Snake pour le navigateur direct.
- [`development/010-SNAKE_WEB_FRONTEND.md`](development/010-SNAKE_WEB_FRONTEND.md) — host Web direct Snake sous Vite/TypeScript, shell Bootstrap 5, Canvas, lifecycle, assets, provenance et contrôles clavier/tactiles.
## Historique validé

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/001-WORKSPACE_ARCHITECTURE.md -->
<!-- version: 5 -->
<!-- version: 7 -->
# Architecture du workspace
@@ -22,7 +22,8 @@ games.sasedev/
│ ├── game-reflex-poc-desktop/
│ ├── game-snake-poc-desktop/
│ ├── game-reflex-poc-tauri/
── game-reflex-poc-wasm/
── game-reflex-poc-wasm/
│ └── game-snake-poc-wasm/
├── assets/
│ ├── common/
│ ├── game-reflex-poc/
@@ -31,6 +32,8 @@ games.sasedev/
│ ├── common/
│ ├── game-reflex-poc/
│ └── game-snake-poc/
├── Web/
│ └── game-snake-poc/
├── docs/
├── deltas/
├── prompts/
@@ -69,15 +72,12 @@ Le runtime devra conserver une distinction logique entre ressources communes et
Chaque crate de jeu reste une bibliothèque. Une crate binaire Desktop séparée sous `crates/apps/` la consomme pour permettre les itérations locales rapides. Cette séparation évite dintroduire `main`, des choix de plateforme ou du code de lancement dans le gameplay réutilisable.
## Variante Tauri / WebAssembly
## Adapters WebAssembly et variante Tauri
La variante Tauri est distincte du runner SDL3 natif.
Les adapters WASM sont distincts des runners SDL3 natifs et ne déplacent aucune règle de gameplay hors des crates de jeu.
`game-reflex-poc-tauri` porte deux faces du même launcher :
`game-reflex-poc-wasm` adapte le POC Reflex à `wasm-bindgen` pour le host Tauri/WebView existant. `game-snake-poc-wasm` adapte séparément Snake pour le premier host navigateur direct de `0.3.0`. Dans les deux cas, l'adapter expose l'état et la scène portable ; il ne possède pas les règles du jeu.
- une bibliothèque `wasm32-unknown-unknown` qui adapte `game-reflex-poc` à `wasm-bindgen` ;
- un binaire natif Tauri qui héberge la WebView locale.
`game-reflex-poc-tauri` reste le binaire natif Tauri qui héberge la WebView Reflex locale. Son frontend Vite/TypeScript pilote un Canvas et appelle l'adapter Reflex WASM. La crate Tauri suit le modèle des apps Desk KSP : façade `lib.rs`, pont `tauri.rs`, modules propriétaires séparés et frontend local à la crate.
Le frontend statique sous `Tauri/game-reflex-poc/frontend/` pilote un Canvas et appelle l'adaptateur WASM. Les règles Reflex restent exclusivement dans `game-reflex-poc`.
La crate Tauri suit le modèle des apps Desk KSP : façade `lib.rs`, pont `tauri.rs`, modules propriétaires séparés et frontend Vite/TypeScript local à la crate.
Le host Web Snake direct réside sous `Web/game-snake-poc/`. Ce package Vite/TypeScript consomme `game-snake-poc-wasm`, reprend la structure HTML/Sass/TypeScript et le thème Bootstrap/Bootswatch des applications Desk KSP sans dépendre de Tauri, traduit clavier/tactile vers les quatre directions logiques et dessine l'`EngineScene` dans un Canvas. Il reste extérieur au workspace Cargo et ne contient aucune règle de gameplay.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-INPUT_AND_CONTROLS.md -->
<!-- version: 7 -->
<!-- version: 9 -->
# Abstraction des entrées et contrôles
@@ -97,6 +97,22 @@ Pour souris et tactile :
Reflex conserve son impulsion `Primary` sur `Down` et reste donc indépendant de la reconnaissance du swipe.
## Baseline Snake portable `0.3.0`
La crate `game-snake-poc` consomme uniquement le contrat générique `engine-v1-common` pour son gameplay. Elle ne dépend pas de `engine-v1-platform-api`, de SDL3, du DOM ou d'un type de périphérique concret.
Le contrat d'entrée Snake retenu pour le premier POC Web est volontairement minimal :
- `Left`, `Right`, `Up` et `Down` pilotent la direction ;
- le rejet d'un demi-tour opposé reste une règle du gameplay Snake ;
- `Primary`, `Secondary`, `Pause` et le pointeur normalisé ne sont pas requis par le gameplay Snake actuel ;
- le host traduit clavier, swipe ou boutons virtuels vers les mêmes actions directionnelles ;
- le rendu reste exposé par `EngineGame::scene` sous forme d'`EngineScene`, sans dépendance au Canvas ou à SDL3.
L'adapter WASM de `0-pre.3` adapte ce contrat existant plutôt que créer un second modèle d'entrée ou de rendu propre au Web. Il mémorise au plus une direction logique en attente ; `tick()` la consomme pour le prochain update puis remet l'entrée en attente à zéro. Un événement clavier ou tactile ne fait donc pas avancer la simulation à lui seul.
Le host Web reste responsable de transformer ses événements physiques ou widgets en appels `left`, `right`, `up` ou `down`, puis de piloter la cadence des `tick()`. Les besoins de provenance, monétisation ou autres services plateforme appartiennent au host/launcher et ne justifient pas une dépendance plateforme inutilisée dans la crate de gameplay.
## Requête de sortie
La plateforme ne décide plus directement de terminer un jeu. Elle traduit l'événement physique en `QuitRequest` :

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/005-ASSET_ARCHITECTURE.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Architecture des assets
@@ -92,3 +92,16 @@ assets/game/...
```
Les répertoires `build/generated/` restent des artefacts jetables.
## Web navigateur direct
Le POC Snake Web valide le même espace logique sans copier les sources dans `Web/`. Vite package explicitement les assets nécessaires depuis `assets/` vers :
```text
dist/common/data/runtime.json
dist/game/data/game.json
```
Le serveur Vite de développement expose les mêmes URL runtime. Le frontend charge donc `common://data/runtime.json` et `game://data/game.json` sous leurs chemins de distribution `./common/data/runtime.json` et `./game/data/game.json`, puis valide leur schéma avant de démarrer la session.
Cette première intégration ne transforme pas encore `game-assets-lib` en loader navigateur : la librairie Rust reste responsable de la validation/résolution de chemins physiques pour les hosts qui disposent d'un filesystem, tandis que Vite possède le packaging Web. Une abstraction commune ne sera extraite que si plusieurs hosts en ont réellement besoin.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/010-RUNTIME_PROVENANCE.md -->
<!-- version: 1 -->
<!-- version: 3 -->
# Provenance d'environnement d'exécution
@@ -45,7 +45,7 @@ RuntimeHost = Native
InputProfile = KeyboardMouse
```
Le futur POC Tauri/WASM :
Un host Tauri Desktop/WASM :
```text
PlatformFamily = Desktop
@@ -55,7 +55,17 @@ RuntimeHost = TauriWebView
InputProfile = KeyboardMouse
```
Une version Web dans un navigateur de téléphone pourra au contraire être `PlatformFamily = Web`, `DeviceClass = Phone`, `ExecutionModel = Wasm`, `RuntimeHost = Browser`, `InputProfile = Touch`.
Le POC Tauri Android de `0.3.1` doit au contraire utiliser :
```text
PlatformFamily = Android
DeviceClass = Phone ou Tablet
ExecutionModel = Wasm
RuntimeHost = TauriWebView
InputProfile = Touch ou Mixed selon les entrées réellement observées
```
Une version Web dans un navigateur de téléphone pourra être `PlatformFamily = Web`, `DeviceClass = Phone`, `ExecutionModel = Wasm`, `RuntimeHost = Browser`, `InputProfile = Touch`.
## Classements et équité
@@ -72,3 +82,17 @@ L'identité joueur reste orthogonale. Un score pourra appartenir à un utilisate
La provenance V1 décrit des catégories techniques générales. Elle ne requiert ni modèle précis de téléphone, ni identifiant matériel, ni adresse réseau.
Un besoin futur de diagnostic plus fin devra être ajouté explicitement plutôt que d'élargir silencieusement cette structure.
## POC Snake Web direct
À partir de `0.3.0-0-pre.5`, `game-snake-poc-wasm` consomme réellement `engine-v1-platform-api` et conserve une `RuntimeProvenance` pour la session navigateur. Les dimensions fixes du host sont :
```text
PlatformFamily = Web
ExecutionModel = Wasm
RuntimeHost = Browser
```
Le frontend observe la classe d'appareil à partir des capacités de pointeur et de la taille logique de l'écran sans collecter de modèle matériel. Le profil d'entrée commence à `Unknown`, devient `KeyboardMouse` ou `Touch` lors de l'utilisation réelle et passe à `Mixed` si les deux familles sont observées pendant la même session.
Cette provenance est affichée dans le shell et émise dans les diagnostics frontend afin de rendre le smoke vérifiable. Elle reste descriptive et ne modifie ni le gameplay ni le score.

View File

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

View File

@@ -0,0 +1,91 @@
<!-- file: docs/architecture/018-V0_3_0_WEB_SNAKE_BASELINE.md -->
<!-- version: 2 -->
# Baseline consolidée 0.3.0 — Snake Web direct
## Statut
Ce document consolide la baseline stable `0.3.0` du premier POC plateforme de la série `0.3.x`. La RC `0.3.0-3-rc.1` a été validée sans correctif supplémentaire ; cet état sert désormais de base au second host Snake prévu en `0.3.1`.
## Chaîne validée
Le chemin Web direct retenu est :
```text
game-snake-poc
game-snake-poc-wasm
↓ wasm-bindgen --target web
Web/game-snake-poc
Vite / TypeScript / Bootstrap 5 / Canvas
```
`game-snake-poc` reste indépendant de SDL3, DOM, Canvas, Tauri et des APIs plateforme. `game-snake-poc-wasm` adapte la scène et les entrées au host Web et porte la provenance runtime ; le frontend gère DOM, lifecycle navigateur, rendu Canvas et shell UI.
## Input et cadence
Les entrées navigateur sont converties vers les quatre actions Snake `Left`, `Right`, `Up`, `Down`. Une entrée ne fait pas avancer directement la simulation : la direction est mise en attente puis consommée au prochain `tick()` WASM.
Le host valide clavier, contrôles pointer/tactiles, resize et pause/reprise sans rattrapage massif après masquage d'onglet.
## Shell Web
Le frontend reprend les conventions utiles des applications Desk KSP sans dépendre de Tauri :
- Vite + TypeScript ;
- Bootstrap 5 / Bootswatch Pulse ;
- Font Awesome ;
- Sass ;
- SimpleBar et `resize-observer-polyfill` ;
- header/footer fixes et zone centrale scrollable ;
- Canvas conservant le ratio logique Snake `12 × 20`.
Le frontend reste un host : il ne contient aucune règle de gameplay.
## Assets
Les assets sources restent hors des crates sous `assets/`. Le build Web conserve les namespaces logiques :
```text
assets/common/data/runtime.json → /common/data/runtime.json
assets/game-snake-poc/data/game.json → /game/data/game.json
```
Les mêmes routes sont valides en développement Vite et dans le bundle production.
## Provenance runtime
La session Web conserve `RuntimeProvenance` dans l'adapter WASM. Les dimensions fixes sont `Web / Browser / Wasm`; la classe d'appareil et le profil d'entrée sont renseignés à partir de l'environnement et des entrées réellement observées.
Le smoke validé expose notamment :
```text
web / browser / wasm / desktop / keyboard-mouse
```
## Build
Le chemin Web direct ne dépend d'aucun orchestrateur Python :
```text
Cargo
→ wasm-bindgen
→ Vite/npm
```
Les artefacts générés restent hors dépôt sous `../builds/sasedev-games/`.
## Résultat de l'extraction
`0.3.0` ne justifie pas encore une abstraction WASM commune entre Reflex et Snake. Les deux adapters spécifiques restent préférables à une factorisation spéculative.
Le prochain host Snake doit réutiliser cette baseline et fournir un second cas réel avant extraction d'une couche commune WebView/WASM.
## Témoin Desktop
Le runner `game-snake-poc-desktop` reste le témoin SDL3 natif. La beta puis la RC `0.3.0-3-rc.1` ont validé son build release et son smoke sans modification de gameplay ni de runtime SDL3.
## Suite
`0.3.1` doit éprouver Tauri Android + Snake. `0.3.2` peut ensuite tester Tauri Desktop et décider, sur deux consommateurs réels, si une factorisation WebView/frontend est justifiée.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/development/003-TRACING_AND_DIAGNOSTICS.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Tracing et diagnostics
@@ -21,3 +21,9 @@ Les runners Desktop initialisent ce socle. Les futures intégrations Android pou
## Évolution
Les filtres par target/niveau, fichiers rotatifs, configuration utilisateur, traces Android et éventuelle télémétrie distante seront ajoutés uniquement lorsquun besoin concret les justifie.
## Host navigateur direct
Le navigateur direct ne possède ni backend Rust Tauri ni plugin de relayage. Son frontend utilise donc un module TypeScript unique de diagnostics structurés qui écrit dans la console du navigateur avec `target`, `action` et champs associés. Les modules applicatifs n'émettent pas leurs propres formats parallèles.
Cette sortie console est un mécanisme frontend. Elle ne remplace pas `tracing` dans le code Rust : les runners natifs continuent d'initialiser `game-logging-lib`, et un futur besoin de tracing Rust directement dans WebAssembly devra être introduit explicitement plutôt que d'être simulé par le frontend.

View File

@@ -0,0 +1,53 @@
<!-- file: docs/development/009-SNAKE_WEB_WASM_BUILD.md -->
<!-- version: 2 -->
# Build WASM du POC Snake Web direct
## Objectif
`game-snake-poc-wasm` fournit le bridge Rust/WASM du premier host navigateur direct. Cette tranche valide uniquement la production du module WebAssembly et de ses bindings JavaScript ; le frontend Vite/TypeScript et le shell Bootstrap 5 appartiennent à `0-pre.4`.
## Prérequis
À installer une fois si nécessaire :
```bash
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --locked
```
Le CLI `wasm-bindgen` utilisé doit rester compatible avec la version de la crate `wasm-bindgen` résolue par Cargo.
## Build Rust
Depuis la racine du dépôt :
```bash
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
```
La configuration Cargo racine place l'artefact sous :
```text
../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm
```
## Génération des bindings navigateur
Créer les bindings avec l'outil natif, sans orchestrateur Python :
```bash
wasm-bindgen \
../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm \
--target web \
--out-dir ../builds/sasedev-games/game-snake-poc-web/wasm \
--out-name game_snake_poc_wasm
```
Les fichiers générés restent intégralement hors du dépôt sous `../builds/sasedev-games/`. Ils ne sont ni commités ni copiés dans `crates/` par cette tranche.
## Frontière avec `0-pre.4`
`0-pre.3` s'arrête au module WASM et à ses bindings générés. Le frontend introduit en `0-pre.4` sous `Web/game-snake-poc/` les consomme ensuite via Vite/TypeScript ; il porte le document HTML Bootstrap 5, le Canvas, le clavier et les contrôles tactiles.
Le frontend reste responsable de la cadence d'appel à `tick()` et de la traduction des événements navigateur vers `left()`, `right()`, `up()` et `down()`.

View File

@@ -0,0 +1,165 @@
<!-- file: docs/development/010-SNAKE_WEB_FRONTEND.md -->
<!-- version: 5 -->
# Frontend du POC Snake Web direct
## Rôle
`Web/game-snake-poc/` est le premier host navigateur direct du projet. Il reste un package frontend autonome, extérieur au workspace Cargo, et consomme les bindings générés de `game-snake-poc-wasm`.
La séparation est volontaire :
```text
game-snake-poc
game-snake-poc-wasm
↓ wasm-bindgen
Web/game-snake-poc
HTML + Sass/Bootstrap 5 + TypeScript + Canvas
```
Le frontend ne réimplémente aucune règle de Snake. Il traduit uniquement les entrées navigateur, cadence les appels à `tick()` et dessine la scène portable exposée par le bridge WASM.
## Baseline frontend KSP
Le shell reprend volontairement la structure frontend utilisée par les applications Desk KSP :
```text
Web/game-snake-poc/
├── frontend/
│ ├── main.html
│ ├── sass/
│ │ ├── _app.scss
│ │ ├── _bootswatch.scss
│ │ ├── _fontawesome.scss
│ │ ├── _simplebar.scss
│ │ ├── _variables.scss
│ │ └── main.scss
│ └── ts/
│ ├── game.ts
│ ├── input.ts
│ └── main.ts
├── package.json
├── tsconfig.json
└── vite.config.ts
```
Les dépendances npm utiles sont alignées sur cette baseline : Bootstrap 5, Font Awesome, SimpleBar, `resize-observer-polyfill`, `sass-embedded`, TypeScript, Vite et les types nécessaires. Les dépendances réellement propres à Tauri et au tracing frontend KSP ne sont pas importées dans ce host navigateur direct. SimpleBar et le polyfill restent au contraire pertinents pour le shell Web lui-même.
Le thème reprend la baseline Bootstrap/Bootswatch Pulse des apps Desk KSP, avec un header, un footer et une carte de contenu adaptés au jeu. Cette réutilisation reste une convention de présentation du POC ; elle ne crée aucune dépendance entre les dépôts KSP et games.sasedev.
## Shell HTML et présentation
Le document `frontend/main.html` fournit :
- un header de statut inspiré du template Desk KSP ;
- un Canvas carré responsive pour le rendu du jeu ;
- les indicateurs de score, longueur et état de chargement ;
- quatre boutons directionnels tactiles ;
- une zone d'erreur de démarrage ;
- un footer léger commun au shell ;
- une zone centrale `app-content.app-scrollable.h-100[data-simplebar]` bornée par `app-main` entre le header et le footer fixes, suivant le même contrat de shell que les apps Desk KSP.
Bootstrap 5 et Font Awesome sont installés comme dépendances npm et intégrés au build Vite/Sass. Aucun CDN n'est nécessaire au POC local.
Le Sass spécifique reste limité au shell, au Canvas et au pavé directionnel. Le gameplay ne dépend ni de Bootstrap, ni de Font Awesome, ni du DOM.
## Entrées
Le frontend accepte :
- les flèches du clavier ;
- `WASD` ;
- `ZQSD` ;
- les quatre boutons directionnels tactiles/pointer.
Une entrée appelle uniquement `left()`, `right()`, `up()` ou `down()` sur `SnakeWasmGame`. La simulation continue d'avancer exclusivement via `tick()`.
## Rendu Canvas
Le frontend lit la couleur de fond et les rectangles normalisés exposés par `game-snake-poc-wasm`. Les coordonnées sont multipliées par la taille physique courante du Canvas.
La grille Snake fait `12 × 20` cellules. Le shell conserve donc un ratio CSS `3 / 5` pour le plateau : une cellule normalisée garde la même largeur et la même hauteur visuelles au lieu d'être écrasée verticalement dans un Canvas carré. Le Canvas suit ensuite cette taille CSS et le `devicePixelRatio` du navigateur.
Le header et le footer restent fixes. `app-main` borne la hauteur utile à `100vh - header - footer` et reste lui-même non scrollable. Comme dans les apps Desk KSP, un enfant `h-100` contient `app-content app-scrollable h-100` portant `data-simplebar`; cette zone possède explicitement `height: 100%` et `max-height: 100%`. Si le plateau portrait et les contrôles dépassent la hauteur disponible, SimpleBar porte donc le scroll de cette zone centrale sans déplacer le header ni le footer. Cette tranche couvre le resize visuel nécessaire au shell ; la gestion complète du lifecycle navigateur reste prévue par `0-pre.5`.
## Build
Depuis la racine du dépôt, construire d'abord l'adapter WASM et ses bindings :
```bash
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm \
--target web \
--out-dir ../builds/sasedev-games/game-snake-poc-web/wasm \
--out-name game_snake_poc_wasm
```
Puis installer et construire le frontend :
```bash
cd Web/game-snake-poc
npm install
npm run build
```
Vite place son cache et le `dist/` sous :
```text
../builds/sasedev-games/game-snake-poc-web/
```
Le dépôt ne reçoit donc ni bindings WASM générés, ni `dist/`, ni cache Vite.
## Smoke navigateur
Après génération du WASM :
```bash
cd Web/game-snake-poc
npm run dev
```
Ouvrir explicitement `http://127.0.0.1:1434/main.html` — le host utilise volontairement `main.html` et ne fournit pas d'`index.html` — puis vérifier :
1. le statut passe à `Prêt` sans erreur ;
2. Snake est visible dans le Canvas ;
3. les flèches clavier déplacent Snake ;
4. `WASD` et `ZQSD` produisent les mêmes directions ;
5. les quatre boutons tactiles/pointer fonctionnent ;
6. le plateau conserve son ratio portrait `3 / 5` et les cellules ne sont pas écrasées ;
7. lorsque le contenu dépasse la hauteur utile, seule la zone centrale défile via SimpleBar tandis que header et footer restent fixes ;
8. le shell KSP-derived et le Canvas restent utilisables sur une largeur mobile et Desktop ;
9. score et longueur restent visibles et évoluent avec l'état WASM.
## Lifecycle navigateur
`0-pre.5` ferme le lifecycle du POC. La boucle `requestAnimationFrame` est suspendue lorsque le document devient caché ou reçoit `pagehide`. La reprise sur `visibilitychange`/`pageshow` réinitialise l'origine temporelle et l'accumulateur afin de ne pas rattraper artificiellement les ticks perdus en arrière-plan.
Un `ResizeObserver` attaché à la zone de jeu redessine le Canvas et rafraîchit la classe d'appareil observée sans faire avancer la simulation. La destruction de page libère l'instance WASM.
## Assets runtime
Le package Vite utilise `vite-plugin-static-copy` pour exposer les deux assets de sonde depuis les sources canoniques :
```text
assets/common/data/runtime.json -> dist/common/data/runtime.json
assets/game-snake-poc/data/game.json -> dist/game/data/game.json
```
Les deux targets utilisent explicitement `rename: { stripBase: true }`. Ce point est nécessaire puisque les sources sont résolues en chemins absolus : seule la basename (`runtime.json` / `game.json`) doit être placée sous le `dest` canonique, sans conserver l'arborescence du chemin source. Le serveur Vite de développement expose ainsi les mêmes routes `/common/data/runtime.json` et `/game/data/game.json` que le build de production.
Le frontend charge et valide les deux JSON avant le démarrage WASM. Le statut visible `Assets` permet de confirmer le chargement pendant le smoke. Aucune copie source n'est ajoutée sous `Web/`.
## Logging frontend
`frontend/ts/logging.ts` centralise les diagnostics navigateur. Les événements sont structurés par niveau, target, action et champs, puis écrits dans la console. Ce module est volontairement distinct du bridge tracing Tauri : le host Web direct ne possède pas de backend Tauri à qui relayer ses événements.
Le Rust natif continue d'utiliser `tracing`/`game-logging-lib`; le frontend console ne remplace pas cette façade Rust.
## Provenance runtime
`game-snake-poc-wasm` dépend de `engine-v1-platform-api` uniquement au niveau adapter et conserve une `RuntimeProvenance`. Les dimensions `Web / Wasm / Browser` sont fixes ; le host renseigne la classe `Desktop`/`Phone`/`Tablet`/`Unknown` et le profil `Unknown`/`KeyboardMouse`/`Touch`/`Mixed` à partir des capacités et entrées réellement observées.
Le shell affiche ces cinq dimensions afin que le smoke puisse confirmer que la provenance évolue lorsqu'un clavier/souris ou un contrôle tactile est utilisé. Le gameplay `game-snake-poc` reste sans dépendance plateforme.

15
docs/plans/000-README.md Normal file
View File

@@ -0,0 +1,15 @@
<!-- file: docs/plans/000-README.md -->
<!-- version: 2 -->
# 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.
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.

View File

@@ -0,0 +1,146 @@
<!-- file: docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md -->
<!-- version: 17 -->
# Plan v0.3.0 — baseline Snake et premier POC Web direct
## But de la version
`0.3.0` ouvre la série de POC `0.3.x`. La version doit rendre Snake suffisamment portable pour servir de jeu-sonde et valider un premier host de bout en bout : le navigateur Web direct.
La version doit rester assez petite pour être fermée dans la session qui l'ouvre. Si une tranche devient trop lourde, elle est scindée ; si le scope global ne tient plus, la partie non indispensable est reportée à une version suivante.
## Base et décisions acquises
Base stable : `0.2.0`.
Décisions issues du cadrage `0-pre.1` :
- Snake reste le jeu-sonde principal ;
- le premier host de `0.3.0` est Web navigateur direct ;
- Tauri Android est reporté à `0.3.1` ;
- le gameplay reste dans `game-snake-poc` ;
- le chemin Web utilise Cargo, `wasm-bindgen` et Vite/TypeScript, sans orchestrateur Python ;
- le host navigateur possède un shell HTML piloté par Vite/TypeScript, avec Bootstrap 5 pour le layout/les contrôles et un Canvas dédié au rendu du jeu ; Bootstrap ne porte aucune règle de gameplay ;
- aucune abstraction n'est extraite avant qu'un besoin partagé réel soit observé.
## Scope
La version couvre :
- nettoyage de la baseline Snake et de ses dépendances de plateforme ;
- adaptation WASM dédiée à Snake ;
- frontend Web avec shell HTML responsive, Bootstrap 5, Canvas, clavier et contrôles directionnels tactiles ;
- resize/lifecycle adaptés au navigateur ;
- branchement des assets, du logging et de la provenance runtime nécessaires au POC ;
- maintien du runner Desktop SDL3 comme témoin de non-régression ;
- documentation des duplications observées et des extractions réellement justifiées.
Hors périmètre : Tauri Android/Desktop Snake, Android natif multi-ABI, réseau realtime, WebTransport/QUIC, Uroburas, ads, billing, auth, leaderboard et nouveau moteur.
## Prévision souple
Les numéros ci-dessous sont des repères de progression, pas un contrat rigide. Une découverte peut insérer un fix, scinder une tranche ou décaler les numéros suivants sans forcer la fermeture.
### `0-pre.1` — cadrage
Audit de la baseline, requirements, choix du host Web, sizing et validations prévues. Le présent plan manquant à la livraison initiale est ajouté par `0-pre.1.fix.1`.
### `0-pre.1.fix.1` — plan et règles de suivi
Créer `docs/plans/`, formaliser le caractère vivant du plan, corriger le contrat des archives taggées et rendre explicite qu'une session est planifiée pour fermer au minimum la version concrète ouverte.
### `0-pre.2` — baseline Snake portable
Nettoyer les dépendances inutiles/spécifiques au launcher, stabiliser la frontière gameplay/input et confirmer les tests Snake ainsi que le runner Desktop SDL3.
Livraison candidate : `engine-v1-platform-api` est retiré des crates de gameplay Snake et Reflex où il n'est pas consommé ; les launchers qui utilisent réellement les capacités plateforme conservent leur dépendance. Le contrat Snake est fixé sur `EngineGame`, `InputState`, les quatre actions directionnelles et `EngineScene`, sans SDL3, DOM, Canvas ni API plateforme dans la crate de gameplay. Aucun adapter WASM n'est introduit avant `0-pre.3`.
Gate : audits statiques, `cargo check --workspace`, tests ciblés Snake et vérification du graphe de dépendances des crates de gameplay. Un smoke Desktop n'est requis que si le runtime SDL ou son mapping est modifié.
### `0-pre.3` — adaptation Snake WASM
Introduire `game-snake-poc-wasm`, adapter WASM minimal dédié à Snake. La crate possède `SnakeState`, le `FixedStepRunner` et une direction logique en attente consommée au prochain `tick()`. Elle expose à `wasm-bindgen` les quatre directions, le score, la longueur et la scène normalisée nécessaire au futur Canvas, sans SDL3, Tauri, DOM, frontend ni `engine-v1-platform-api`.
Livraison candidate : la crate est membre du workspace et la distribution statique vérifie sa présence. Le bridge reste volontairement spécifique à Snake ; aucune abstraction commune avec `game-reflex-poc-wasm` n'est extraite avant observation d'une duplication réellement problématique.
Gate : audits statiques, `cargo check --workspace`, Clippy ciblé, tests de l'adapter, build `wasm32-unknown-unknown`, génération `wasm-bindgen --target web` dans `../builds/sasedev-games/` et graphe de dépendances. Aucun build Vite/Bootstrap ni smoke navigateur n'est requis avant `0-pre.4`.
### `0-pre.4` — frontend Web/Vite, shell Bootstrap 5 et contrôles
Créer `Web/game-snake-poc/`, package Vite/TypeScript autonome consommant les bindings `game-snake-poc-wasm` générés hors dépôt. Le frontend reprend le template des applications Desk KSP pour sa structure HTML/Sass/TypeScript et ses dépendances npm pertinentes : Bootstrap 5, thème Bootswatch Pulse, Font Awesome, SimpleBar, `resize-observer-polyfill` et `sass-embedded`, sans dépendances Tauri. Le rendu reste dans un Canvas et les règles de gameplay restent dans Rust/WASM. Brancher flèches clavier, `WASD`/`ZQSD`, quatre boutons directionnels tactiles/pointer, chargement/erreur, score et longueur.
Livraison candidate : le shell utilise `frontend/main.html`, `frontend/sass/` et `frontend/ts/` sur le modèle KSP ; SimpleBar porte le scroll de la zone centrale `app-content.app-scrollable.h-100[data-simplebar]`, bornée par `app-main` entre header et footer fixes ; le Canvas conserve le ratio logique Snake `12 / 20`, suit sa taille CSS et le `devicePixelRatio`, le frontend cadence `tick()` sans avancer la simulation au moment d'une entrée et Vite écrit `dist/`/cache uniquement sous `../builds/sasedev-games/game-snake-poc-web/`. Aucun CDN, SDL3, Tauri ni duplication de gameplay n'est introduit. Le lifecycle complet, les assets, le logging et la provenance restent réservés à `0-pre.5`.
Gate : audits statiques, build WASM + `wasm-bindgen`, `npm run build` du package Web direct et smoke navigateur couvrant Canvas, clavier, contrôles tactiles/pointer et comportement responsive du shell.
Suivi `0-pre.4.fix.1` : la validation utilisateur de `0-pre.4` a confirmé le fonctionnement clavier/pointer sous `vite dev`, mais a révélé trois défauts de candidate : `baseUrl` supprimé par TypeScript 7, omission de SimpleBar/`resize-observer-polyfill` du template repris et Canvas carré incompatible avec la grille logique `12 × 20`. Le fix retire `baseUrl`, rétablit le scroll shell KSP-derived et passe le plateau au ratio `3 / 5` avant réexécution de la gate frontend.
Suivi `0-pre.4.fix.2` : la validation de `fix.1` a confirmé les gates Rust/WASM et les audits, mais `npm run build` a encore révélé deux erreurs TypeScript strictes : l'alias `@snake-wasm` résolvait le JavaScript généré sans sélectionner sa déclaration `.d.ts`, et le narrowing du contexte Canvas 2D n'était pas conservé dans le callback `requestAnimationFrame`. Le fix sépare explicitement la résolution de typage (`.d.ts`) de la résolution runtime Vite (`.js`) et capture le contexte 2D validé dans une constante non nullable.
Suivi `0-pre.4.fix.3` : la validation de `fix.2` confirme la gate Rust/WASM, le build Vite production et le ratio Canvas corrigé. Le smoke révèle toutefois que le scroll SimpleBar ne reproduit pas encore le comportement du shell KSP. La comparaison avec `ksp-app-*-desk` montre que `data-simplebar` doit porter sur la zone centrale `app-content app-scrollable h-100`, enfant d'un `app-main` borné, et non sur `app-main` lui-même. Le fix aligne la structure HTML/Sass sur ce contrat sans initialisation manuelle spécifique.
Validation `0-pre.4.fix.3` : le build Vite reste propre et le smoke utilisateur confirme le comportement attendu du shell, y compris le scroll SimpleBar, le Canvas portrait et les contrôles. `0-pre.4` est donc fermé et `0-pre.5` peut porter l'intégration plateforme complète.
### `0-pre.5` — intégration plateforme complète
Fermer resize/lifecycle, assets, logging et provenance runtime du POC ; vérifier que Desktop SDL reste intact.
Livraison candidate : le host suspend la simulation sur `visibilitychange`/`pagehide` et reprend sans rattrapage massif ; un `ResizeObserver` redessine sans tick artificiel. Vite package `assets/common/data/runtime.json` vers `common/data/runtime.json` et `assets/game-snake-poc/data/game.json` vers `game/data/game.json`, puis le frontend charge/valide ces deux sondes avant le démarrage. Les diagnostics navigateur passent par un module TypeScript structuré local. `game-snake-poc-wasm` consomme `engine-v1-platform-api` pour une provenance `Web/Wasm/Browser`, complétée par la classe d'appareil et le profil d'entrée réellement observés. Aucun de ces contrats ne fuit dans `game-snake-poc`.
Gate : audits statiques, `cargo check --workspace`, Clippy workspace strict, tests `engine-v1-platform-api` et `game-snake-poc-wasm`, build WASM + `wasm-bindgen`, build Vite, smoke navigateur couvrant assets/provenance/lifecycle/resize/inputs/SimpleBar, puis smoke `game-snake-poc-desktop` pour la non-régression SDL3.
Suivi `0-pre.5.fix.1` : la validation de `0-pre.5` confirme les gates Rust/WASM et le build Vite production, mais le smoke `vite dev` reste bloqué sur le chargement des assets. Les targets `vite-plugin-static-copy` utilisent des sources absolues sans `rename.stripBase`, ce qui ne garantit pas les routes runtime canoniques. Le fix impose `stripBase: true` aux deux targets afin que dev et build exposent exactement `/common/data/runtime.json` et `/game/data/game.json`.
Validation `0-pre.5.fix.1` : les audits, `cargo check`, Clippy workspace strict, les tests plateforme/WASM, le build WASM, `wasm-bindgen`, le build Vite et le smoke navigateur passent. Les assets affichent `snake / engine-v1`, la provenance observée est `web / browser / wasm / desktop / keyboard-mouse`, les contrôles clavier/souris fonctionnent et le runner Desktop SDL3 démarre puis s'arrête proprement après 411 frames. Aucun défaut structurel supplémentaire n'est identifié.
### `0-pre.6` — correction/extraction seulement si prouvée
Tranche conditionnelle. Elle n'existe que si les POC précédents révèlent une duplication réelle, une frontière mal placée ou un défaut nécessitant une correction structurante avant stabilisation. Sinon elle est omise.
Décision après `0-pre.5.fix.1` : aucun besoin d'extraction/correction structurante n'est observé ; `0-pre.6` est omise et la version entre directement en beta.
### `2-beta.1` — validation large
Scope feature-complete. La tranche ne crée aucun nouveau comportement : elle bascule la version workspace et le package Web au jalon `0.3.0-2-beta.1`, enregistre la validation de `0-pre.5.fix.1` et exécute la gate large de frontière de phase. Celle-ci comprend audits, formatage/check/Clippy, tests workspace complets, build + smoke Desktop Snake en release, build WASM Snake en release, `wasm-bindgen`, build Vite production et smoke du `dist` via `npm run preview`. Aucun nouveau scope.
Validation : la gate est acceptée par l'utilisateur. Les audits sont propres, les tests workspace totalisent 34 tests réussis sans échec, le binaire Desktop Snake release démarre et s'arrête proprement après 144 frames, et le bundle Web/WASM release est construit puis servi par `vite preview` sans défaut remonté.
### `2-beta.2` — consolidation documentaire et transmission
Réconcilier l'état réellement validé avec la documentation durable et le plan actif, mettre à jour `ROADMAP.md`, enregistrer `2-beta.1` dans l'historique et préparer le prompt de `0.3.1` à partir de cet état. La synthèse destinée au `CHANGELOG.md` est consolidée ici, mais l'entrée n'est écrite qu'en RC conformément à `DOC-CHG-002` et `DOC-CHG-003`.
Livraison candidate : synthèse durable de la baseline Web Snake `0.3.0`, matrice RC spécifique, roadmap `0.3.0` classée réalisée, audit `pre.1` réconcilié avec le contrat des archives taggées et prompt `003-V0_3_1_START_PROMPT.md`. Aucun code ni comportement runtime n'est modifié.
Cette tranche est une responsabilité obligatoire du cycle même si son numéro doit être décalé par des fixes ou une beta supplémentaire. La documentation spécifique à une fonctionnalité reste mise à jour dans la tranche qui introduit cette fonctionnalité ; `2-beta.2` réalise la consolidation transversale, elle ne sert pas à reporter toute la documentation en fin de version.
Gate : cohérence docs/plan/deltas/`CHANGELOG.md`/`ROADMAP.md`/history/prompt, audits documentaires, `cargo check --workspace` pour le bump de version et `npm run build` pour confirmer la version frontend.
Suivi `2-beta.2.fix.1` : la gate mécanique de `2-beta.2` passe, mais la revue humaine juge le prompt `0.3.1` trop synthétique pour éviter des rappels de workflow au cours de la prochaine session. Le fix enrichit ce prompt, formalise une structure durable de prompts, précise le timing prompt/RC/CHANGELOG/ROADMAP/history et introduit une politique non cérémonielle pour `README.md`/`USAGE.md`. Le fix est strictement documentaire et conserve donc la version technique `0.3.0-2-beta.2`.
Suivi `2-beta.2.fix.2` : la revue du prompt enrichi valide son niveau de contexte, mais précise la gouvernance des commandes afin d'éviter des validations trop larges ou répétitives. Le fix rend obligatoires `cargo fmt`, `cargo check --workspace` et Clippy workspace strict dès que Rust ou ses dépendances changent, garde les tests ciblés comme norme, réserve le test workspace complet à quelques jalons planifiés, limite `cargo tree` aux changements/diagnostics de dépendances, sélectionne les audits Python selon les fichiers touchés, impose les sous-shells pour les changements de répertoire et rappelle que Tauri possède Vite via `cargo tauri dev/build`. Ce correctif reste strictement documentaire et ne modifie aucune version technique.
Validation `2-beta.2.fix.2` : l'audit Markdown ciblé passe (`5` tables, `189` fichiers) et la revue utilisateur autorise l'ouverture de la RC. Aucune autre gate n'est attribuée à ce correctif strictement documentaire.
### `3-rc.1` — candidate gelée
Reproductibilité, validation finale de la candidate et derniers défauts strictement nécessaires à la publication. Le prompt suivant préparé pendant la consolidation est vérifié/complété si l'état RC apporte une information nouvelle. Aucun nouveau POC ni nouvelle capability.
Livraison candidate : passage des versions techniques au jalon `0.3.0-3-rc.1`, entrée RC du `CHANGELOG.md`, enregistrement de `2-beta.2.fix.2` dans `history/`, revue finale du prompt `0.3.1` et gel explicite du scope. Le prompt est jugé suffisamment complet et ne reçoit aucun changement sémantique supplémentaire dans cette tranche.
Gate RC : appliquer intégralement `docs/testing/004-V0_3_0_RC_VALIDATION_MATRIX.md`. Le test workspace complet est volontairement exécuté à ce jalon final ; aucun `cargo tree` n'est requis en l'absence de changement de dépendances.
Validation `3-rc.1` : les audits statiques, `cargo check --workspace`, Clippy workspace strict et la suite workspace complète passent avec 34 tests réussis. Le binaire Desktop Snake release démarre puis s'arrête proprement après 926 frames. Le build WASM release, `wasm-bindgen`, le build Vite production et `vite preview` passent également. Aucun défaut nécessitant un correctif RC n'est remonté.
### `0.3.0` — release stable
Promotion mécanique de l'état RC validé. La release ne change ni gameplay, ni architecture, ni dépendance ; elle fixe les versions stables, ajoute l'entrée stable du `CHANGELOG.md`, enregistre la validation RC sous `history/0.3.0/3-rc.1.md` et clôt le plan `0.3.0`. Le prompt `0.3.1` reste inchangé après sa revue RC.
## Suivi et ajustements
Le plan est révisé lorsqu'un résultat réel modifie la trajectoire. Un changement de numérotation n'est pas un problème ; une responsabilité importante ne doit en revanche pas être comprimée artificiellement pour respecter le forecast initial.
La session ne doit pas être planifiée pour s'arrêter à `pre.2`, `pre.4` ou beta : ces identifiants sont des tranches de la même version. Si `0.3.0` devient trop grande, le scope non indispensable est déplacé vers `0.3.1+` afin de préserver une version complète par session.
## Version suivante envisagée
`0.3.1` reste le candidat pour le second host Snake, Tauri Android, en réutilisant la baseline Web/WASM réellement validée par `0.3.0`.

View File

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

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/FILE_CONTRACTS.md -->
<!-- version: 6 -->
<!-- version: 8 -->
# Contrats des fichiers principaux
@@ -11,6 +11,7 @@
- `docs/rules/` contient les règles durables.
- `docs/ideas/000-README.md` est le point d'entrée des idées et variantes non engagées.
- `docs/studies/000-README.md` est le point d'entrée des analyses comparatives non normatives préparant une décision.
- `docs/plans/000-README.md` est le point d'entrée des plans de versions ; chaque plan actif porte le découpage prévisionnel souple et les gates de sa version.
- `docs/architecture/` contient les décisions et descriptions d'architecture retenues.
- `docs/objectives/` décrit les objectifs et la stratégie produit/technique.
- `docs/games/` classe les familles de jeux, leurs contrôles et leurs évolutions.
@@ -25,7 +26,8 @@
- `scripts/` contient des audits en lecture seule et des outils du dépôt.
- `assets/` contient les ressources runtime communes et spécifiques aux jeux ; aucune ressource runtime n'est placée dans une crate Rust.
- `Android/` contient le projet Gradle multi-module et son code Java commun/spécifique.
- `crates/apps/` contient les exécutables de développement et launchers Rust, notamment les runners Desktop par jeu.
- `Web/` contient les hosts navigateur directs non-Tauri ; chaque `Web/<game>/` est un package frontend autonome consommant un adapter WASM versionné sous `crates/apps/`.
- `crates/apps/` contient les exécutables de développement et launchers Rust, notamment les runners Desktop par jeu et les adapters WASM.
## Tauri

View File

@@ -0,0 +1,70 @@
<!-- file: docs/rules/PROMPT_STRUCTURE.md -->
<!-- version: 3 -->
# Structure des prompts de reprise
## Objet
Un prompt de démarrage est un contrat opératoire autonome pour la version suivante. Il doit permettre de reprendre le travail sans dépendre de la mémoire conversationnelle, tout en évitant de recopier intégralement les règles du dépôt.
Le prompt rappelle les invariants qui évitent les erreurs de workflow et renvoie aux documents normatifs pour leur détail exact.
## Contenu minimal
- **PROMPT-STR-001** — Le prompt identifie la baseline exacte, la version cible et l'autorité de la base fournie. Une archive annoncée comme téléchargement d'un tag est traitée selon `CMD-GIT-003`/`CMD-GIT-004` sans exiger `.git`.
- **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-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.
- **PROMPT-STR-009** — Le prompt rappelle la cadence de validation sans recopier toute la matrice : audits Python sélectionnés selon les fichiers touchés ; `cargo fmt --all`, `cargo fmt --all -- --check`, `cargo check --workspace` et Clippy workspace strict obligatoires dès que du Rust ou une dépendance Cargo change ; tests `cargo test -p ...` ciblés par défaut ; test workspace complet réservé aux rares jalons planifiés ou aux changements transverses incertains ; `cargo tree` seulement lors d'un changement/diagnostic de dépendances.
## Commandes avec répertoire et Tauri
- **PROMPT-STR-016** — Le prompt rappelle que toute commande nécessitant un `cd` est englobée dans un sous-shell, par exemple `(cd <dir> && <commande>)`, afin de ne pas modifier le répertoire courant pour les commandes suivantes.
- **PROMPT-STR-017** — Lorsqu'une version touche Tauri, le prompt rappelle que `npm` sert à gérer les dépendances frontend mais que les builds/smokes passent par Tauri. Il utilise les commandes de la cible concernée : `(cd <tauri-app> && cargo tauri dev)` / `cargo tauri build` pour Desktop, ou `(cd <tauri-app> && cargo tauri android dev)` / `cargo tauri android build` pour Android. Les hooks Tauri possèdent Vite/TypeScript/WASM ; `npm run dev`/`npm run build` ne deviennent pas des gates manuelles Tauri.
## Documentation et traçabilité à rappeler
- **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-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.
## README et USAGE dans une version
Le prompt ne doit pas imposer mécaniquement des fichiers vides. Il doit en revanche forcer la question au cadrage puis à la consolidation :
```text
nouvelle crate/package durable ?
-> README utile pour responsabilité/frontières/points d'entrée ?
API ou workflow de consommation non trivial ?
-> USAGE utile pour préconditions/commandes/exemples ?
simple adapter/POC déjà documenté durablement ailleurs ?
-> document supplémentaire non obligatoire s'il n'apporte rien
```
## Timing du prompt suivant
- **PROMPT-STR-020** — Le prompt de la version suivante est rédigé pendant la consolidation finale lorsque la cible suivante est suffisamment connue ; il n'est pas reporté à une future session.
- **PROMPT-STR-021** — La première RC gelée vérifie et complète ce prompt à partir de l'état réellement candidat à publication ; elle ne lui attribue pas de validations futures.
- **PROMPT-STR-022** — La stable ne doit normalement effectuer qu'une mise à jour mécanique de la base de départ ou des références devenues certaines depuis la RC.
## Versionnement, deltas et fixes
- **PROMPT-STR-030** — Le prompt rappelle le format SemVer applicable et le rôle des `.fix.N` lorsqu'une erreur est découverte dans une tranche déjà livrée.
- **PROMPT-STR-031** — Le prompt rappelle que chaque delta indique sa base requise, son scope, les fichiers touchés, les validations attendues et l'état connu du jalon précédent.
- **PROMPT-STR-032** — Une validation propre permet de poursuivre automatiquement vers la tranche planifiée suivante ; un échec reste dans un `.fix.N` de la tranche courante sauf décision explicite contraire.
- **PROMPT-STR-033** — Le prompt rappelle qu'une session est dimensionnée pour fermer au minimum une version concrète jusqu'à sa stable, pas pour s'arrêter volontairement sur une prerelease.
## Niveau de détail attendu
Le prompt doit être assez complet pour éviter les rappels conversationnels récurrents, mais il ne devient pas une duplication exhaustive de `docs/rules/`.
Une taille de quelques centaines de lignes est acceptable lorsqu'elle porte du contexte opérationnel réel. Les listes de toutes les règles Rust ou de toutes les commandes du dépôt restent dans leurs documents normatifs ; le prompt cite les règles et reproduit seulement les garde-fous susceptibles d'être oubliés dans la version ciblée.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_COMMANDS.md -->
<!-- version: 9 -->
<!-- version: 14 -->
# Règles d'exécution des commandes
@@ -16,18 +16,20 @@
- **CMD-GEN-009** — Lorsque l'utilisateur fournit une gate complète propre, le delta est considéré validé et le travail peut passer automatiquement au delta planifié suivant sauf instruction contraire.
- **CMD-GEN-010** — Si une gate échoue, la progression vers le delta suivant est suspendue ; le correctif est livré sous le suffixe `.fix.N` du delta courant, sauf décision explicite contraire.
- **CMD-GEN-011** — Les commandes d'audit Markdown couvrent également `history/` dès que cette arborescence existe.
- **CMD-GEN-012** — Les audits Python sont sélectionnés selon les fichiers réellement touchés : audit Rust/workspace pour le périmètre Rust/Cargo/workspace concerné, audit Markdown pour les fichiers Markdown concernés, audit de distribution pour les changements de layout/build/packaging. Ils ne sont pas tous exécutés par habitude sur chaque delta.
- **CMD-GEN-013** — Toute commande nécessitant un changement temporaire de répertoire est exécutée dans un sous-shell, par exemple `(cd Web/game-snake-poc && npm run build)`, afin de revenir automatiquement à la racine du workspace après la commande.
## Rust et Cargo
- **CMD-RUST-001** — Lorsquun delta a modifié au moins un fichier Rust, lutilisateur exécute `cargo fmt --all` au début de la validation afin dappliquer le formatage canonique; les changements purement produits par rustfmt constituent lunique exception à lincrément obligatoire de len-tête de version du fichier.
- **CMD-RUST-002** — `cargo fmt --all -- --check` suit immédiatement le formatage et constitue la gate canonique de conformité rustfmt.
- **CMD-RUST-003** — `cargo check --workspace` est la première gate de compilation globale après les audits statiques.
- **CMD-RUST-004** — `cargo clippy --workspace --all-targets --all-features -- -D warnings` est exécuté après un `cargo check --workspace` propre pour la gate complète.
- **CMD-RUST-005** — Les tests ciblés `cargo test -p <crate> --all-targets --all-features` sont la stratégie normale dun delta et doivent couvrir toutes les crates directement ou transitivement affectées lorsque cela est raisonnablement déterminable.
- **CMD-RUST-006** — `cargo test --workspace --all-targets --all-features` est une gate lourde réservée au démarrage dune nouvelle version `X.Y.Z`, à la fin dune phase/version, aux changements transverses importants ou lorsquil existe un doute raisonnable sur la portée des tests ciblés.
- **CMD-RUST-001** — Dès qu'un delta modifie du code Rust, un manifeste Cargo, une feature ou une dépendance Rust, l'utilisateur exécute `cargo fmt --all`; les changements purement produits par rustfmt constituent l'unique exception à l'incrément obligatoire de l'en-tête de version du fichier.
- **CMD-RUST-002** — `cargo fmt --all -- --check` suit le formatage et constitue la gate canonique de conformité rustfmt.
- **CMD-RUST-003** — Tout delta qui touche du code Rust ou ses dépendances exécute `cargo check --workspace` après les audits statiques applicables. Un `cargo check -p <crate>` peut servir de diagnostic rapide, mais ne remplace pas cette gate workspace.
- **CMD-RUST-004** — Tout delta qui touche du code Rust ou ses dépendances exécute ensuite `cargo clippy --workspace --all-targets --all-features -- -D warnings`. Cette gate n'est pas réservée aux seules phases finales.
- **CMD-RUST-005** — Les tests ciblés `cargo test -p <crate> --all-targets --all-features` sont la stratégie normale d'un delta et couvrent les crates directement affectées ainsi que les consommateurs dont le contrat est réellement impacté.
- **CMD-RUST-006** — `cargo test --workspace --all-targets --all-features` est une gate lourde exécutée seulement un petit nombre de fois explicitement prévues dans le plan de version/session, typiquement à une validation initiale si elle apporte une valeur réelle, à une validation préfinale/finale, lors d'un changement transverse important ou lorsqu'il existe un doute raisonnable sur la portée des tests ciblés. Elle n'est pas répétée à chaque delta.
- **CMD-RUST-007** — `cargo run -p <desktop-runner>` sert aux smokes manuels Desktop et n'est pas substitué aux tests automatisés.
- **CMD-RUST-008** — `cargo build` est utilisé lorsqu'un artefact exécutable ou une bibliothèque est réellement nécessaire ; il n'est pas lancé systématiquement en plus de `cargo check`.
- **CMD-RUST-009** — `cargo tree` et ses variantes sont des commandes de diagnostic de dépendances, pas des gates obligatoires de chaque delta.
- **CMD-RUST-009** — `cargo tree` et ses variantes ne sont exécutés que lorsqu'un delta modifie les dépendances/features, lorsqu'une frontière de dépendances doit être vérifiée ou lorsqu'un diagnostic explicite le justifie. Ils ne font pas partie de la validation automatique d'un delta sans changement de dépendances.
- **CMD-RUST-010** — `cargo update` n'est jamais exécuté opportunistement. Toute mise à jour de dépendance doit appartenir à une tranche explicitement consacrée aux dépendances ou être nécessaire à la fonctionnalité en cours.
- **CMD-RUST-011** — `cargo clean` est l'outil canonique de remise à zéro complète du cache de build Cargo et peut être utilisé périodiquement pour maîtriser la taille de `../builds/sasedev-games/target`.
- **CMD-RUST-012** — Un nettoyage complet n'est pas exécuté à chaque delta. Il est planifié à un jalon de cycle approprié, normalement au démarrage de la première prerelease de développement lorsque l'ancien cache doit être évacué, ou au plus tard avant la validation finale RC/stable si l'accumulation disque le justifie.
@@ -56,29 +58,38 @@
- **CMD-WEB-001** — Aucun gestionnaire de paquets JavaScript ni build Web n'est exécuté tant qu'un frontend Web réel n'a pas été introduit dans le dépôt.
- **CMD-WEB-002** — Lorsqu'une cible Web existe, ses commandes de build et test sont documentées avant d'être ajoutées aux gates.
- **CMD-WEB-003** — Le POC Tauri/WASM utilise `scripts/build_reflex_tauri_wasm.py` pour WASM et Vite/TypeScript pour le frontend local ; aucun site distant n'est requis.
- **CMD-WEB-003** — Le POC Tauri/WASM historique `0.1.0` utilise `scripts/build_reflex_tauri_wasm.py` ; ce script reste une référence de baseline mais ne doit pas être réutilisé comme orchestrateur par un POC `0.3.x`, conformément à `CMD-BUILD-004`.
- **CMD-WEB-004** — Les fichiers produits par `wasm-bindgen` sont générés localement et ne sont pas commités.
- **CMD-WEB-005** — Dans une app Tauri, `npm` sert uniquement à gérer les dépendances frontend lorsque nécessaire, par exemple `npm i <package>`, `npm i -D <package>` ou leur opération inverse. Les scripts `npm run dev`, `npm run build` ou équivalents ne sont pas des gates manuelles : Vite/TypeScript/WASM sont déclenchés par les hooks Tauri configurés.
- **CMD-WEB-006** — Le smoke normal d'une app Tauri Desktop est lancé via `(cd <tauri-app> && cargo tauri dev)`. Cette commande possède le serveur Vite et les hooks frontend nécessaires.
- **CMD-WEB-007** — Le packaging Tauri Desktop est validé dans les phases finales prévues par le plan via `(cd <tauri-app> && cargo tauri build)`, typiquement en beta préfinale, RC ou avant release selon le scope. Il n'est pas exécuté après chaque petit delta Tauri.
- **CMD-WEB-008** — Pour un host Web navigateur direct sous `Web/<game>/`, les commandes npm/Vite peuvent être exécutées directement, toujours dans un sous-shell lorsqu'un changement de répertoire est nécessaire, par exemple `(cd Web/<game> && npm install && npm run build)`.
- **CMD-WEB-009** — Le build d'un host Web direct qui consomme un adapter `wasm-bindgen` exécute d'abord le build Rust/WASM et la génération des bindings hors dépôt, puis seulement le build TypeScript/Vite.
- **CMD-WEB-010** — Un smoke navigateur manuel d'un host Web direct vérifie au minimum le chargement WASM, le Canvas, les entrées prévues par la tranche et le comportement responsive concerné.
- **CMD-WEB-011** — Une cible Tauri Android existante est initialisée depuis sa crate avec `(cd <tauri-app> && cargo tauri android init)` lorsqu'elle ne possède pas encore le projet mobile généré requis ; cette initialisation n'est pas rejouée comme gate ordinaire.
- **CMD-WEB-012** — Le smoke interactif d'une app Tauri Android utilise `(cd <tauri-app> && cargo tauri android dev)` ; il remplace `cargo tauri dev` pour cette cible mobile.
- **CMD-WEB-013** — Le packaging Tauri Android utilise `(cd <tauri-app> && cargo tauri android build)` dans les phases finales prévues par le plan ; `--apk` ou `--aab` est ajouté uniquement lorsque le livrable visé le requiert.
## Git et fichiers générés
- **CMD-GIT-001** — Les commandes Git destructives (`reset --hard`, nettoyage forcé, réécriture non demandée) ne sont jamais utilisées pour remettre artificiellement le workspace en état.
- **CMD-GIT-002** — Les fichiers générés ne sont pas commités sauf contrat explicite du dépôt ou exigence de distribution.
- **CMD-WEB-005** — `npm run dev` et `npm run build` du frontend Tauri sont pilotés par les hooks Tauri ; ils ne constituent pas des gates manuelles indépendantes.
- **CMD-GIT-003** — Lorsqu'une archive fournie par l'utilisateur est déclarée comme téléchargement d'un tag du dépôt, cette archive est la baseline autoritaire de ce tag. L'absence de `.git` dans l'archive est normale et ne constitue ni une anomalie ni une validation manquante.
- **CMD-GIT-004** — Les contrôles nécessitant le répertoire `.git` s'appliquent uniquement à un checkout Git local lorsqu'il est effectivement fourni ; sur une archive taggée, on contrôle la cohérence interne des versions et fichiers sans inventer un état Git inaccessible.
## Beta et packaging
- **CMD-BETA-001** — À l'entrée en beta, `scripts/audit_distribution_layout.py` vérifie les frontières statiques nécessaires aux runners et packagings supportés.
- **CMD-BETA-002** — La transition alpha vers beta exécute une suite Cargo workspace complète en plus des tests ciblés.
- **CMD-BETA-003** — Si la version touche le périmètre Tauri, au moins un build de packaging beta est lancé via `cargo tauri build`; ses hooks possèdent le build WASM et Vite/TypeScript.
- **CMD-BETA-003** — Si la version touche le périmètre Tauri, le plan réserve au moins une validation de packaging dans une phase finale appropriée avec la commande Tauri correspondant à la cible : `cargo tauri build` pour Desktop ou `cargo tauri android build` pour Android. Les smokes utilisent de même `cargo tauri dev` ou `cargo tauri android dev` selon la cible.
- **CMD-BETA-004** — Les APK Debug servent à la validation multi-appareils beta ; la signature de publication appartient à la phase RC/stable.
## RC et release
- **CMD-RC-001** — L'entrée en RC gèle le périmètre fonctionnel de `0.1.0` ; seuls les correctifs, la reproductibilité des builds, le packaging, la documentation de livraison et les défauts de release sont admis.
- **CMD-RC-001** — L'entrée en RC gèle le périmètre fonctionnel de la version courante ; seuls les correctifs, la reproductibilité des builds, le packaging, la documentation de livraison et les défauts de release sont admis.
- **CMD-RC-002** — La validation d'une RC exécute `cargo test --workspace --all-targets --all-features` en plus des audits, du check et de Clippy strict.
- **CMD-RC-003** — Les deux runners Desktop natifs sont construits en `--release` et font l'objet d'un smoke sur les binaires de release.
- **CMD-RC-004** — Le POC Tauri est construit uniquement via `cargo tauri build`; ses hooks possèdent toujours le build WASM et Vite/TypeScript.
- **CMD-RC-004** — Une RC Tauri est construite avec la commande de packaging correspondant à sa cible : `(cd <tauri-app> && cargo tauri build)` pour Desktop ou `(cd <tauri-app> && cargo tauri android build)` pour Android. Ses hooks possèdent toujours le build WASM et Vite/TypeScript ; les smokes utilisent la variante `dev` correspondante.
- **CMD-RC-005** — Android RC revalide au minimum x86_64 sur AVD et ARM64 sur appareil réel avec les APK issus de l'état RC.
- **CMD-RC-006** — Les secrets de signature, keystores et credentials de publication ne sont jamais commités. Leur présence est une condition externe de publication, pas une donnée du dépôt.
- **CMD-RC-007** — Une RC n'est promue en stable que si aucun correctif `.fix.N` n'est nécessaire après la gate RC complète.
@@ -92,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** — Un script Python ne pilote pas le build d'un produit, d'une plateforme ou d'un package distribué.
- **CMD-BUILD-002** — À partir de `0.3.x`, aucun chemin de build nouveau ou modifié nest piloté par Python. Les orchestrateurs historiques `scripts/build_reflex_tauri_wasm.py` et `scripts/build_android_rust.py` restent tolérés uniquement comme mécanismes gelés de la baseline `0.1.0` jusquà la tranche qui réactive leur chemin ; ils ne sont ni copiés, ni généralisés, ni utilisés pour un nouveau POC.
- **CMD-BUILD-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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_DOCUMENTATION.md -->
<!-- version: 6 -->
<!-- version: 8 -->
# Règles de documentation
@@ -28,6 +28,7 @@
- **DOC-CAT-006** — Les documents spécialisés existants (`games/`, `engine/`, `monetization/`, `services/`, `development/`, `testing/`, `validation/`) conservent leur rôle fonctionnel et ne servent pas de dépôt générique d'idées.
- **DOC-CAT-007** — Une information peut mûrir de `idea` vers `study`, puis vers une décision d'architecture ou une réservation de capability ; ce passage est explicite et n'est jamais déduit de la seule présence d'un texte.
- **DOC-CAT-008** — Une étude peut conclure à `retained`, `deferred`, `rejected` ou `needs-poc` sans créer automatiquement une capability, une crate ou une entrée de roadmap.
- **DOC-CAT-009** — `docs/plans/` contient les plans vivants des versions concrètes. Un plan détaille le scope, les décisions, les validations et surtout le découpage prévisionnel souple des prereleases ; il ne remplace ni `ROADMAP.md` ni les deltas.
## Nomenclature documentaire
@@ -40,6 +41,14 @@
- **DOC-NAME-007** — Dans un répertoire documentaire destiné à contenir plusieurs fichiers Markdown, le point d'entrée porte le nom `000-README.md` afin d'être trié en premier.
- **DOC-NAME-008** — Un nouveau répertoire documentaire multi-fichiers ne crée pas de `README.md` concurrent à `000-README.md`.
## Documentation des crates, applications et packages
- **DOC-CRATE-001** — Toute nouvelle crate, application ou package frontend évalue explicitement pendant son cadrage puis sa consolidation finale si un `README.md` ou un `USAGE.md` apporte une information durable utile ; ces fichiers ne sont jamais créés uniquement pour satisfaire une cérémonie.
- **DOC-CRATE-002** — Un `README.md` local décrit la responsabilité, les frontières, les dépendances structurantes et les principaux points d'entrée lorsqu'un composant devient durable ou réutilisable et que ces informations ne sont pas suffisamment couvertes par une documentation centrale.
- **DOC-CRATE-003** — Un `USAGE.md` est ajouté lorsqu'une API, un binaire, une application ou un package possède un workflow de consommation/opérateur, des préconditions, des commandes, de la configuration ou des exemples suffisamment non triviaux pour mériter un guide stable.
- **DOC-CRATE-004** — Un adapter ou POC très petit peut rester documenté uniquement par les documents d'architecture/développement existants lorsque cela couvre réellement son contrat ; l'absence de `README.md`/`USAGE.md` doit alors être un choix de valeur documentaire, pas un oubli.
- **DOC-CRATE-005** — `README.md` et `USAGE.md` restent durables et ne contiennent pas de journal de release ; les changements de version appartiennent à `CHANGELOG.md`, `deltas/` et `history/`.
## Listes de tâches et d'état
- **DOC-TASK-001** — Toute liste Markdown qui représente durablement des tâches, objectifs ou éléments suivis utilise les marqueurs `( )`, `(x)`, `(d)` et `(c)` plutôt que les task lists Markdown `[ ]` / `[x]`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_PROJECT.md -->
<!-- version: 11 -->
<!-- version: 17 -->
# Règles spécifiques games.sasedev
@@ -13,6 +13,7 @@
- **GAME-WS-006** — Les dépendances tierces communes sont centralisées sous `[workspace.dependencies]` et consommées avec `workspace = true` lorsqu'elles sont partagées.
- **GAME-WS-007** — Un jeu est prioritairement une crate `lib`; lorsquun lancement Desktop est nécessaire, une crate `bin` séparée sous `crates/apps/` dépend de cette lib et ne duplique pas son gameplay.
- **GAME-WS-008** — Les runners Desktop sont nommés `<game>-desktop` et restent indépendants des frontends Android.
- **GAME-WS-009** — Les hosts Web navigateur directs résident sous `Web/<game>/`, hors du workspace Cargo ; ils consomment un adapter WASM dédié et ne contiennent ni crate Rust ni règle de gameplay.
## Générations du moteur
@@ -30,6 +31,7 @@
- **GAME-ASSET-004** — Chaque jeu peut posséder `assets/<game>/` pour ses ressources spécifiques.
- **GAME-ASSET-005** — Le packaging de chaque plateforme assemble les assets communs et spécifiques sans créer de copie source durable dans une crate.
- **GAME-ASSET-006** — Les chemins logiques d'assets doivent éviter les collisions entre espace commun et espace jeu.
- **GAME-ASSET-007** — Un host Web direct package les assets runtime communs et spécifiques depuis `assets/` vers les namespaces de distribution `common/` et `game/` sans créer de copie source durable sous `Web/`; le smoke doit charger au moins un asset de chaque namespace lorsqu'une tranche déclare l'intégration assets complète.
## Android
@@ -63,13 +65,14 @@
- **GAME-TRACE-002** — `tracing-subscriber` compose les subscribers applicatifs et de test ; une librairie métier ne configure pas silencieusement le subscriber global.
- **GAME-TRACE-003** — `tracing-appender` est utilisé lorsque lécriture non bloquante ou les fichiers de logs deviennent nécessaires ; le guard associé reste vivant pendant toute la durée utile.
- **GAME-TRACE-004** — La configuration de logging commune réside dans une crate transverse et ne doit pas être dupliquée par jeu.
- **GAME-TRACE-005** — Un host navigateur direct peut émettre ses diagnostics frontend dans la console du navigateur via un module TypeScript structuré dédié ; ce mécanisme ne remplace pas `tracing` dans le code Rust et ne doit pas importer le bridge tracing Tauri lorsqu'aucun host Tauri n'est présent.
- **GAME-PLATFORM-009** — Une adaptation WebAssembly réutilisable est une crate dédiée distincte de la crate Tauri.
- **GAME-PLATFORM-010** — Dans une app Tauri, `lib.rs` reste une façade/reexport ; `tauri.rs` assemble Tauri et expose les commandes qui délèguent aux modules propriétaires.
- **GAME-PLATFORM-011** — Les traces frontend Tauri passent par `@fltsci/tauri-plugin-tracing` vers `tauri-plugin-tracing`/`tracing`; les `console.*` applicatifs directs sont interdits hors fallback interne.
- **GAME-PLATFORM-012** — Le plugin tracing Tauri ne remplace pas l'initialisation du subscriber Rust ; une app Tauri initialise le runtime de logging partagé avant son premier événement `tracing`.
- **GAME-PLATFORM-013** — Les demandes de sortie Tauri sont évaluées par la politique `EngineGame::quit_requested` avant toute fermeture native.
- **GAME-PLATFORM-013** — Les demandes de sortie applicatives Tauri sont évaluées par la politique `EngineGame::quit_requested` avant toute fermeture native contrôlée par l'application. Cette règle n'impose pas de consommer le Back système Android, qui reste régi par `GAME-PLATFORM-014`.
- **GAME-BUILD-001** — Les artefacts générés Cargo, Tauri, Vite et caches frontend sont placés hors de la racine du dépôt, sous `../builds/sasedev-games/`; un répertoire racine `builds/` dans le dépôt est interdit.
@@ -78,10 +81,16 @@
## Réservation de plateformes
- **GAME-PLATFORM-016** — Les familles de plateformes réservées sont Desktop, Mobile et Web. Desktop inclut potentiellement Linux, Windows et macOS ; Mobile inclut potentiellement Android et iOS.
- **GAME-PLATFORM-016** — Les familles conceptuelles réservées sont Desktop, Mobile et Web. Desktop inclut potentiellement Linux, Windows et macOS ; Mobile inclut potentiellement Android et iOS. Le contrat runtime V1 reste volontairement plus concret : `PlatformFamily` expose actuellement `Android`, `Desktop` et `Web` ; un host Tauri Android utilise donc `PlatformFamily::Android` tant qu'aucune généralisation Mobile/iOS n'est justifiée.
- **GAME-PLATFORM-017** — Téléphone, tablette et futures classes de device sont des dimensions distinctes de l'OS et du backend technique.
- **GAME-PLATFORM-018** — SDL3 reste le backend natif de référence du POC, mais l'architecture de jeu ne doit pas assimiler une plateforme à SDL ni empêcher un adapter différent lorsque la plateforme l'exige.
- **GAME-PLATFORM-019** — Une plateforme réservée n'est ni implémentée ni planifiée tant qu'une ligne ROADMAP ou un delta ne l'engage explicitement.
- **GAME-PLATFORM-020** — Un host Web navigateur possède un shell HTML/CSS/TypeScript explicite autour du Canvas/WASM. Le framework de présentation éventuel reste une dépendance frontend et ne fuit ni dans la crate de gameplay ni dans le bridge WASM ; pour le premier POC Snake `0.3.0`, Bootstrap 5 est la baseline de présentation retenue.
- **GAME-PLATFORM-021** — Les dépendances de présentation nécessaires au runtime Web versionné sont déclarées dans le package frontend et intégrées au build Vite ; un CDN externe n'est pas requis pour exécuter le POC local et ne devient une dépendance de distribution qu'après décision explicite.
- **GAME-PLATFORM-022** — SimpleBar et `resize-observer-polyfill` sont des dépendances de shell Web, pas des dépendances Tauri. Lorsqu'un host conserve header et footer fixes, la zone centrale bornée à la hauteur disponible peut les utiliser pour fournir un scroll interne personnalisé sans déplacer les éléments fixes.
- **GAME-PLATFORM-023** — Lorsqu'un host Web reprend le shell KSP avec SimpleBar, `app-main` borne la hauteur disponible et reste non scrollable ; `data-simplebar` appartient à l'enfant central `app-content app-scrollable h-100`, dont `height` et `max-height` valent `100%`. Le polyfill `ResizeObserver` est installé avant l'initialisation applicative et le package `simplebar` est importé par le point d'entrée TypeScript.
- **GAME-PLATFORM-024** — Un host navigateur à simulation fixed-step suspend ses ticks lorsque le document devient caché ou passe en `pagehide`, puis reprend avec une nouvelle origine temporelle sans rattrapage massif au retour ; le resize peut redessiner la scène sans avancer la simulation.
- **GAME-PLATFORM-025** — La provenance d'un host navigateur est construite via `engine-v1-platform-api` avec `PlatformFamily::Web`, `ExecutionModel::Wasm` et `RuntimeHost::Browser`; la classe d'appareil et le profil d'entrée restent des dimensions observées par le host, sans identification matérielle fine.
## Version d'en-tête des fichiers

Some files were not shown because too many files have changed in this diff Show More