diff --git a/.gitignore b/.gitignore
index 3a6dcd8..bcb6648 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,3 +63,6 @@ __pycache__/
*.jks
*.keystore
local.properties
+# Generated by scripts/build_reflex_tauri_wasm.py.
+Tauri/game-reflex-poc/frontend/game_reflex_poc_tauri.js
+Tauri/game-reflex-poc/frontend/game_reflex_poc_tauri_bg.wasm
diff --git a/Android/game-reflex-poc/build.gradle b/Android/game-reflex-poc/build.gradle
index f844081..8882d93 100644
--- a/Android/game-reflex-poc/build.gradle
+++ b/Android/game-reflex-poc/build.gradle
@@ -1,5 +1,5 @@
// file: Android/game-reflex-poc/build.gradle
-// version: 10
+// version: 11
plugins {
id 'com.android.application'
@@ -18,7 +18,7 @@ android {
minSdk 21
targetSdk 36
versionCode 1
- versionName '0.1.0-1-alpha.1'
+ versionName '0.1.0-1-alpha.2'
}
compileOptions {
diff --git a/Android/game-snake-poc/build.gradle b/Android/game-snake-poc/build.gradle
index a4bb181..5cac17d 100644
--- a/Android/game-snake-poc/build.gradle
+++ b/Android/game-snake-poc/build.gradle
@@ -1,5 +1,5 @@
// file: Android/game-snake-poc/build.gradle
-// version: 10
+// version: 11
plugins {
id 'com.android.application'
@@ -18,7 +18,7 @@ android {
minSdk 21
targetSdk 36
versionCode 1
- versionName '0.1.0-1-alpha.1'
+ versionName '0.1.0-1-alpha.2'
}
compileOptions {
diff --git a/Cargo.toml b/Cargo.toml
index a9d57e6..e47c311 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
# file: Cargo.toml
-# version: 27
+# version: 28
[workspace]
resolver = "3"
@@ -14,10 +14,11 @@ members = [
"crates/common/game-assets-lib",
"crates/common/game-logging-lib",
"crates/apps/game-android-entrypoint",
+ "crates/apps/game-reflex-poc-tauri",
]
[workspace.package]
-version = "0.1.0-1-alpha.1"
+version = "0.1.0-1-alpha.2"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games"
@@ -29,6 +30,9 @@ sdl3 = "^0.20"
tracing = "0.1.44"
tracing-appender = "0.2.5"
tracing-subscriber = "0.3.23"
+tauri = "2"
+tauri-build = "2"
+wasm-bindgen = "0.2"
[workspace.lints.rust]
missing_docs = "warn"
diff --git a/ROADMAP.md b/ROADMAP.md
index ac75621..f1f1369 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,5 +1,5 @@
-
+
# Roadmap
@@ -16,7 +16,7 @@
- [x] `0-pre.9` — assets communs + spécifiques empaquetés sans copie dans les crates.
- [x] `0-pre.10` — POC Reflex jouable Desktop + Android.
- [x] `0-pre.11` — POC Snake jouable et validation de la réutilisation du moteur.
-- [ ] `1-alpha.1` — première API moteur V1 volontairement stabilisée et provenance runtime/input pour les futures sessions et scores.
+- [x] `1-alpha.1` — première API moteur V1 volontairement stabilisée et provenance runtime/input pour les futures sessions et scores.
- [ ] `1-alpha.2` — POC Web/WASM embarqué dans Tauri, sans site distant, et validation de la frontière des services Web/ads.
- [ ] `2-beta.1` — stabilisation, packaging, tests multi-appareils.
- [ ] `3-rc.1` — candidat de release du socle 0.1.0.
diff --git a/Tauri/README.md b/Tauri/README.md
new file mode 100644
index 0000000..75be8dd
--- /dev/null
+++ b/Tauri/README.md
@@ -0,0 +1,10 @@
+
+
+
+# Tauri
+
+Cette arborescence contient les frontends statiques des variantes Desktop Tauri.
+
+Le backend Rust reste une crate sous `crates/apps/`, conformément à la règle du workspace Cargo unique.
+
+Les fichiers générés par `wasm-bindgen` ne sont pas versionnés.
diff --git a/Tauri/game-reflex-poc/frontend/index.html b/Tauri/game-reflex-poc/frontend/index.html
new file mode 100644
index 0000000..513e109
--- /dev/null
+++ b/Tauri/game-reflex-poc/frontend/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Reflex POC Tauri
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tauri/game-reflex-poc/frontend/main.js b/Tauri/game-reflex-poc/frontend/main.js
new file mode 100644
index 0000000..5145b36
--- /dev/null
+++ b/Tauri/game-reflex-poc/frontend/main.js
@@ -0,0 +1,76 @@
+// file: Tauri/game-reflex-poc/frontend/main.js
+// version: 1
+
+import init, { ReflexWasmGame } from "./game_reflex_poc_tauri.js";
+
+const FRAME_MS = 16;
+
+function color(red, green, blue) {
+ return `rgb(${red} ${green} ${blue})`;
+}
+
+function resizeCanvas(canvas) {
+ 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, canvas, context, score) {
+ 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()}`;
+}
+
+async function start() {
+ await init();
+
+ const canvas = document.querySelector("#game");
+ const score = document.querySelector("#score");
+ const context = canvas.getContext("2d");
+ if (!(canvas instanceof HTMLCanvasElement) || !(score instanceof HTMLOutputElement) || context === null) {
+ throw new Error("Reflex Tauri frontend DOM is incomplete");
+ }
+
+ const game = new ReflexWasmGame();
+ canvas.addEventListener("pointerdown", (event) => {
+ const bounds = canvas.getBoundingClientRect();
+ const x = (event.clientX - bounds.left) / bounds.width;
+ const y = (event.clientY - bounds.top) / bounds.height;
+ game.pointer_down(x, y);
+ render(game, canvas, context, score);
+ });
+
+ let previous = performance.now();
+ function frame(now) {
+ while (now - previous >= FRAME_MS) {
+ game.tick();
+ previous += FRAME_MS;
+ }
+ render(game, canvas, context, score);
+ window.requestAnimationFrame(frame);
+ }
+
+ render(game, canvas, context, score);
+ window.requestAnimationFrame(frame);
+}
+
+start().catch((error) => {
+ console.error("Reflex Tauri WASM startup failed", error);
+});
diff --git a/Tauri/game-reflex-poc/frontend/style.css b/Tauri/game-reflex-poc/frontend/style.css
new file mode 100644
index 0000000..d2af18d
--- /dev/null
+++ b/Tauri/game-reflex-poc/frontend/style.css
@@ -0,0 +1,45 @@
+/* file: Tauri/game-reflex-poc/frontend/style.css */
+/* version: 1 */
+
+html,
+body {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background: rgb(18 18 24);
+ font-family: sans-serif;
+}
+
+#app {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+#game {
+ display: block;
+ width: 100%;
+ height: 100%;
+ touch-action: none;
+}
+
+#score,
+#runtime {
+ position: absolute;
+ left: 12px;
+ padding: 6px 8px;
+ border-radius: 4px;
+ background: rgb(0 0 0 / 55%);
+ color: white;
+ pointer-events: none;
+}
+
+#score {
+ top: 12px;
+}
+
+#runtime {
+ bottom: 12px;
+ font-size: 12px;
+}
diff --git a/crates/apps/game-reflex-poc-tauri/Cargo.toml b/crates/apps/game-reflex-poc-tauri/Cargo.toml
new file mode 100644
index 0000000..90f1115
--- /dev/null
+++ b/crates/apps/game-reflex-poc-tauri/Cargo.toml
@@ -0,0 +1,32 @@
+# file: crates/apps/game-reflex-poc-tauri/Cargo.toml
+# version: 1
+
+[package]
+name = "game-reflex-poc-tauri"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+authors.workspace = true
+publish.workspace = true
+build = "build.rs"
+
+[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-reflex-poc = { path = "../../games/game-reflex-poc" }
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+tauri.workspace = true
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen.workspace = true
+
+[build-dependencies]
+tauri-build.workspace = true
+
+[lints]
+workspace = true
diff --git a/crates/apps/game-reflex-poc-tauri/build.rs b/crates/apps/game-reflex-poc-tauri/build.rs
new file mode 100644
index 0000000..e99a476
--- /dev/null
+++ b/crates/apps/game-reflex-poc-tauri/build.rs
@@ -0,0 +1,12 @@
+// file: crates/apps/game-reflex-poc-tauri/build.rs
+// version: 1
+
+fn main() {
+ let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH");
+ match target_arch {
+ std::result::Result::Ok(value) if value == "wasm32" => return,
+ _ => {},
+ }
+ tauri_build::build();
+ return;
+}
diff --git a/crates/apps/game-reflex-poc-tauri/src/lib.rs b/crates/apps/game-reflex-poc-tauri/src/lib.rs
new file mode 100644
index 0000000..6ac0fdd
--- /dev/null
+++ b/crates/apps/game-reflex-poc-tauri/src/lib.rs
@@ -0,0 +1,15 @@
+// file: crates/apps/game-reflex-poc-tauri/src/lib.rs
+// version: 1
+
+#![warn(missing_docs)]
+#![deny(unreachable_pub)]
+#![forbid(unsafe_code)]
+
+//! WebAssembly adapter for the Reflex POC when hosted by the Tauri variant.
+
+#[cfg(target_arch = "wasm32")]
+mod wasm_runtime;
+
+#[cfg(target_arch = "wasm32")]
+/// Re-export of the Reflex WebAssembly runtime adapter.
+pub use self::wasm_runtime::ReflexWasmGame;
diff --git a/crates/apps/game-reflex-poc-tauri/src/main.rs b/crates/apps/game-reflex-poc-tauri/src/main.rs
new file mode 100644
index 0000000..d8b4dd5
--- /dev/null
+++ b/crates/apps/game-reflex-poc-tauri/src/main.rs
@@ -0,0 +1,25 @@
+// file: crates/apps/game-reflex-poc-tauri/src/main.rs
+// version: 1
+
+#![warn(missing_docs)]
+#![deny(unreachable_pub)]
+#![forbid(unsafe_code)]
+
+//! Native Tauri host for the local Reflex WebAssembly POC.
+
+#[cfg(not(target_arch = "wasm32"))]
+fn main() {
+ let result = tauri::Builder::default().run(tauri::generate_context!());
+ match result {
+ std::result::Result::Ok(()) => {},
+ std::result::Result::Err(error) => {
+ eprintln!("Reflex POC Tauri runner failed: {error}");
+ },
+ }
+ return;
+}
+
+#[cfg(target_arch = "wasm32")]
+fn main() {
+ return;
+}
diff --git a/crates/apps/game-reflex-poc-tauri/src/wasm_runtime.rs b/crates/apps/game-reflex-poc-tauri/src/wasm_runtime.rs
new file mode 100644
index 0000000..94ebc5a
--- /dev/null
+++ b/crates/apps/game-reflex-poc-tauri/src/wasm_runtime.rs
@@ -0,0 +1,152 @@
+// file: crates/apps/game-reflex-poc-tauri/src/wasm_runtime.rs
+// version: 1
+
+/// WebAssembly-owned Reflex game state consumed by the local Tauri WebView.
+#[wasm_bindgen::prelude::wasm_bindgen]
+pub struct ReflexWasmGame {
+ runner: engine_v1_common::FixedStepRunner,
+ state: game_reflex_poc::ReflexState,
+}
+
+#[wasm_bindgen::prelude::wasm_bindgen]
+impl ReflexWasmGame {
+ /// Creates a fresh Reflex POC session using the same gameplay crate as native Desktop.
+ #[wasm_bindgen::prelude::wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ return Self {
+ runner: engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(16)),
+ state: game_reflex_poc::ReflexState::new(),
+ };
+ }
+
+ /// Advances one deterministic update without an input impulse.
+ pub fn tick(&mut self) {
+ self.runner.tick(&mut self.state, engine_v1_common::InputState::none());
+ return;
+ }
+
+ /// Delivers one normalized primary-pointer impulse to the shared Reflex gameplay state.
+ pub fn pointer_down(&mut self, x: f32, y: f32) {
+ let pointer = engine_v1_common::PointerState::normalized(true, x, y);
+ let input = engine_v1_common::InputState::none().with_pointer(pointer).with_action(engine_v1_common::GameAction::Primary, true);
+ 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 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 the index is 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 the index is 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 the index is 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 the index is 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 the index is 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 the index is 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 the index is absent.
+ pub fn rectangle_blue(&self, index: u32) -> u8 {
+ return match self.rectangle(index) {
+ Some(rectangle) => rectangle.color().blue(),
+ None => 0,
+ };
+ }
+}
+
+impl Default for ReflexWasmGame {
+ fn default() -> Self {
+ return Self::new();
+ }
+}
+
+impl ReflexWasmGame {
+ fn scene(&self) -> engine_v1_common::EngineScene {
+ return engine_v1_common::EngineGame::scene(&self.state);
+ }
+
+ fn rectangle(&self, requested_index: u32) -> Option {
+ 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;
+ }
+}
diff --git a/crates/apps/game-reflex-poc-tauri/tauri.conf.json b/crates/apps/game-reflex-poc-tauri/tauri.conf.json
new file mode 100644
index 0000000..db99203
--- /dev/null
+++ b/crates/apps/game-reflex-poc-tauri/tauri.conf.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://schema.tauri.app/config/2",
+ "productName": "Reflex POC Tauri",
+ "version": "0.1.0-1-alpha.2",
+ "identifier": "com.sasedev.games.reflex.tauri",
+ "build": {
+ "frontendDist": "../../../Tauri/game-reflex-poc/frontend"
+ },
+ "app": {
+ "windows": [
+ {
+ "title": "Reflex POC Tauri",
+ "width": 405,
+ "height": 720,
+ "resizable": true
+ }
+ ],
+ "security": {
+ "csp": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost"
+ }
+ },
+ "bundle": {
+ "active": false
+ }
+}
diff --git a/deltas/0.1.0/1-alpha.2.md b/deltas/0.1.0/1-alpha.2.md
new file mode 100644
index 0000000..392cf19
--- /dev/null
+++ b/deltas/0.1.0/1-alpha.2.md
@@ -0,0 +1,129 @@
+
+
+
+# Delta 0.1.0-1-alpha.2
+
+## Base
+
+Base validée : `0.1.0-1-alpha.1`.
+
+## Objectif
+
+Valider un POC Reflex WebAssembly embarqué dans une fenêtre Tauri locale, sans site distant, tout en réutilisant `game-reflex-poc`.
+
+## Architecture
+
+La nouvelle crate :
+
+```text
+crates/apps/game-reflex-poc-tauri
+```
+
+porte :
+
+- une face native Tauri ;
+- une face bibliothèque WASM pour `wasm32-unknown-unknown`.
+
+Le frontend statique réside sous :
+
+```text
+Tauri/game-reflex-poc/frontend
+```
+
+Le JavaScript orchestre Canvas et pointeur mais ne contient aucune règle gameplay.
+
+## Build WebAssembly
+
+Préparer une fois :
+
+```bash
+rustup target add wasm32-unknown-unknown
+cargo install wasm-bindgen-cli --locked
+```
+
+Construire :
+
+```bash
+python3 scripts/build_reflex_tauri_wasm.py
+```
+
+## Prérequis Tauri Debian
+
+Si nécessaire :
+
+```bash
+sudo apt update
+sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
+cargo install tauri-cli --version "^2.0.0" --locked
+```
+
+## Validation Rust
+
+```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 Tauri Android deltas history
+
+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-reflex-poc --all-targets --all-features
+```
+
+## Gate WASM
+
+```bash
+python3 scripts/build_reflex_tauri_wasm.py
+```
+
+Vérifier :
+
+```bash
+test -f Tauri/game-reflex-poc/frontend/game_reflex_poc_tauri.js
+test -f Tauri/game-reflex-poc/frontend/game_reflex_poc_tauri_bg.wasm
+```
+
+## Smoke Tauri
+
+```bash
+cd crates/apps/game-reflex-poc-tauri
+cargo tauri dev
+```
+
+Critères :
+
+- fenêtre Tauri locale ;
+- cible Reflex visible ;
+- clic sur cible comptabilisé ;
+- cible déplacée après succès ;
+- score visible ;
+- aucun site distant requis.
+
+## Provenance attendue
+
+La variante correspond conceptuellement à :
+
+```text
+PlatformFamily::Desktop
+DeviceClass::Desktop
+ExecutionModel::Wasm
+RuntimeHost::TauriWebView
+InputProfile::KeyboardMouse
+```
+
+La connexion effective de cette provenance à une session/score fera partie d'une tranche ultérieure.
+
+## Services Web
+
+Aucune régie Ads n'est intégrée dans cette alpha.
+
+Le POC valide uniquement la capacité structurelle à disposer d'une frontière Web locale dans Tauri. Les APIs de publicité, rewarded/revive et analytics resteront derrière les contrats plateforme.
+
+## Transition
+
+Si les gates passent, `0.1.0-1-alpha.2` valide la coexistence Desktop SDL3 natif + Tauri/WASM.
+
+En cas d'échec imputable au projet, produire `0.1.0-1-alpha.2.fix.1`.
diff --git a/docs/000-README.md b/docs/000-README.md
index 1a5b53e..4b4c509 100644
--- a/docs/000-README.md
+++ b/docs/000-README.md
@@ -1,5 +1,5 @@
-
+
# Documentation games.sasedev
diff --git a/docs/architecture/001-WORKSPACE_ARCHITECTURE.md b/docs/architecture/001-WORKSPACE_ARCHITECTURE.md
index d5ae7b9..02b724b 100644
--- a/docs/architecture/001-WORKSPACE_ARCHITECTURE.md
+++ b/docs/architecture/001-WORKSPACE_ARCHITECTURE.md
@@ -1,5 +1,5 @@
-
+
# Architecture du workspace
@@ -20,11 +20,14 @@ games.sasedev/
│ │ └── game-snake-poc/
│ └── apps/
│ ├── game-reflex-poc-desktop/
-│ └── game-snake-poc-desktop/
+│ ├── game-snake-poc-desktop/
+│ └── game-reflex-poc-tauri/
├── assets/
│ ├── common/
│ ├── game-reflex-poc/
│ └── game-snake-poc/
+├── Tauri/
+│ └── game-reflex-poc/
├── Android/
│ ├── common/
│ ├── game-reflex-poc/
@@ -66,3 +69,14 @@ Le runtime devra conserver une distinction logique entre ressources communes et
## Runners Desktop
Chaque crate de jeu reste une bibliothèque. Une crate binaire Desktop séparée sous `crates/apps/` la consomme pour permettre les itérations locales rapides. Cette séparation évite d’introduire `main`, des choix de plateforme ou du code de lancement dans le gameplay réutilisable.
+
+## Variante Tauri / WebAssembly
+
+La variante Tauri est distincte du runner SDL3 natif.
+
+`game-reflex-poc-tauri` porte deux faces du même launcher :
+
+- une bibliothèque `wasm32-unknown-unknown` qui adapte `game-reflex-poc` à `wasm-bindgen` ;
+- un binaire natif Tauri qui héberge la WebView locale.
+
+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`.
diff --git a/docs/development/008-WASM_TAURI_POC.md b/docs/development/008-WASM_TAURI_POC.md
new file mode 100644
index 0000000..b739daf
--- /dev/null
+++ b/docs/development/008-WASM_TAURI_POC.md
@@ -0,0 +1,97 @@
+
+
+
+# POC WebAssembly embarqué dans Tauri
+
+## Objectif
+
+Le POC vérifie qu'une même crate de gameplay Rust peut fonctionner :
+
+- dans le runner Desktop SDL3 natif ;
+- compilée en WebAssembly ;
+- dans une WebView Tauri locale ;
+- sans site distant ni serveur applicatif.
+
+Le POC ne remplace pas le runner SDL3 natif.
+
+## Découpage
+
+```text
+game-reflex-poc
+ |
+ +--> game-reflex-poc-desktop --> engine-v1-sdl --> SDL3 natif
+ |
+ +--> game-reflex-poc-tauri --wasm32--> wasm-bindgen --> Canvas Web
+ |
+ +--natif--> Tauri WebView
+```
+
+Le JavaScript ne contient aucune règle Reflex. Il traduit les événements pointeur vers l'adaptateur WASM et dessine la scène générique exposée par le moteur.
+
+## Frontend local
+
+Tauri agit comme hôte de fichiers Web statiques locaux. `frontendDist` pointe sur :
+
+```text
+Tauri/game-reflex-poc/frontend/
+```
+
+Aucun site, CDN, Vite ou gestionnaire de paquets JavaScript n'est requis par ce POC.
+
+## Sécurité
+
+La CSP reste locale. `wasm-unsafe-eval` est autorisé uniquement parce que la WebView charge du WebAssembly.
+
+Aucun script distant n'est autorisé.
+
+## Build WASM
+
+Préparer une fois :
+
+```bash
+rustup target add wasm32-unknown-unknown
+cargo install wasm-bindgen-cli --locked
+```
+
+Puis :
+
+```bash
+python3 scripts/build_reflex_tauri_wasm.py
+```
+
+Les fichiers générés sont ignorés par Git.
+
+## Prérequis Tauri Linux
+
+Le développement Tauri v2 requiert les bibliothèques système WebKitGTK/GTK correspondantes.
+
+Sur Debian, vérifier notamment la présence de `libwebkit2gtk-4.1-dev` et des autres prérequis documentés par Tauri avant la gate Tauri.
+
+Installer une fois le CLI si nécessaire :
+
+```bash
+cargo install tauri-cli --version "^2.0.0" --locked
+```
+
+## Exécution
+
+Après génération du WASM :
+
+```bash
+cd crates/apps/game-reflex-poc-tauri
+cargo tauri dev
+```
+
+Critères :
+
+- une fenêtre locale 405 × 720 s'ouvre ;
+- le Canvas affiche le même fond et la même cible que Reflex SDL3 ;
+- cliquer sur la cible incrémente le score et déplace la cible ;
+- aucun site distant n'est requis ;
+- le runtime est identifiable comme `Desktop + Wasm + TauriWebView + KeyboardMouse`.
+
+## Frontière services Web
+
+Ce POC ne choisit aucune régie publicitaire.
+
+Il valide seulement qu'une variante Tauri possède une WebView capable d'héberger une frontière Web distincte. Ads, rewarded ads, revive, analytics et autres services resteront derrière `engine-v1-platform-api` et feront l'objet de tranches dédiées.
diff --git a/docs/rules/FILE_CONTRACTS.md b/docs/rules/FILE_CONTRACTS.md
index 9a0bde6..64b17c5 100644
--- a/docs/rules/FILE_CONTRACTS.md
+++ b/docs/rules/FILE_CONTRACTS.md
@@ -1,5 +1,5 @@
-
+
# Contrats des fichiers principaux
@@ -24,3 +24,9 @@
- `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.
+
+## Tauri
+
+- `Tauri//frontend/` contient le frontend Web statique versionné d'une variante Tauri.
+- Les bindings JavaScript et modules `.wasm` générés restent ignorés par Git.
+- Le backend Rust Tauri reste sous `crates/apps/`.
diff --git a/docs/rules/RULES_COMMANDS.md b/docs/rules/RULES_COMMANDS.md
index 1f08885..6519cf3 100644
--- a/docs/rules/RULES_COMMANDS.md
+++ b/docs/rules/RULES_COMMANDS.md
@@ -1,5 +1,5 @@
-
+
# Règles d'exécution des commandes
@@ -53,6 +53,8 @@
- **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`; aucun gestionnaire de paquets JavaScript ni serveur Web externe n'est requis pour cette variante.
+- **CMD-WEB-004** — Les fichiers produits par `wasm-bindgen` sont générés localement et ne sont pas commités.
## Git et fichiers générés
diff --git a/docs/rules/RULES_PROJECT.md b/docs/rules/RULES_PROJECT.md
index 44d1b99..1edd020 100644
--- a/docs/rules/RULES_PROJECT.md
+++ b/docs/rules/RULES_PROJECT.md
@@ -1,5 +1,5 @@
-
+
# Règles spécifiques games.sasedev
@@ -49,6 +49,8 @@
- **GAME-PLATFORM-004** — Ads et Billing sont des capacités optionnelles ; le gameplay reste fonctionnel lorsqu'elles sont disponibles, désactivées ou non supportées.
- **GAME-PLATFORM-005** — Un backend de monétisation est spécifique à sa plateforme et à sa distribution ; une intégration Android n'est pas réutilisée implicitement sur Web ou Desktop.
- **GAME-PLATFORM-006** — Le runner Desktop natif SDL3 est la forme Desktop par défaut. Une variante Tauri peut coexister uniquement lorsqu'un besoin explicite le justifie et consomme la même crate lib de jeu.
+- **GAME-PLATFORM-007** — Une variante Tauri conserve son backend Rust sous `crates/apps/`; son frontend Web statique réside sous `Tauri//` et ne duplique pas le gameplay Rust.
+- **GAME-PLATFORM-008** — Le gameplay compilé en WebAssembly est adapté par une frontière dédiée ; le code JavaScript orchestre la WebView, les événements et le rendu Canvas mais ne réimplémente pas les règles du jeu.
## POC
diff --git a/history/0.1.0/1-alpha.1.md b/history/0.1.0/1-alpha.1.md
new file mode 100644
index 0000000..1c1266e
--- /dev/null
+++ b/history/0.1.0/1-alpha.1.md
@@ -0,0 +1,26 @@
+
+
+
+# Historique 0.1.0-1-alpha.1
+
+## Statut
+
+Validé par l'utilisateur le 2026-09-16.
+
+## Gates
+
+- audits Rust/workspace propres ;
+- audit Markdown : `1 table(s), 80 file(s)` ;
+- `cargo check --workspace` propre ;
+- Clippy workspace strict propre ;
+- `engine-v1-platform-api` : 3/3 tests ;
+- `engine-v1-common` : 8/8 tests ;
+- `engine-v1-sdl` : 5/5 tests ;
+- `game-reflex-poc` : 4/4 tests ;
+- `game-snake-poc` : 5/5 tests ;
+- smoke Desktop Snake propre ;
+- smoke Desktop Reflex propre.
+
+## Portée validée
+
+La provenance runtime/input est disponible comme donnée descriptive indépendante de la politique de classement.
diff --git a/scripts/build_reflex_tauri_wasm.py b/scripts/build_reflex_tauri_wasm.py
new file mode 100644
index 0000000..5a6f374
--- /dev/null
+++ b/scripts/build_reflex_tauri_wasm.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# file: scripts/build_reflex_tauri_wasm.py
+# version: 1
+
+"""Build the Reflex WASM adapter and generate browser bindings for the local Tauri frontend."""
+
+from __future__ import annotations
+
+import pathlib
+import subprocess
+import sys
+
+
+def main() -> int:
+ """Build the WASM library and run wasm-bindgen into the committed Tauri frontend directory."""
+
+ root = pathlib.Path(__file__).resolve().parent.parent
+ target_dir = root.parent / "builds" / "sasedev-games" / "target"
+ wasm_path = target_dir / "wasm32-unknown-unknown" / "debug" / "game_reflex_poc_tauri.wasm"
+ output_dir = root / "Tauri" / "game-reflex-poc" / "frontend"
+
+ build = subprocess.run(
+ ["cargo", "build", "-p", "game-reflex-poc-tauri", "--lib", "--target", "wasm32-unknown-unknown"],
+ cwd=root,
+ check=False,
+ )
+ if build.returncode != 0:
+ return build.returncode
+ if not wasm_path.is_file():
+ print(f"WASM artifact not found: {wasm_path}", file=sys.stderr)
+ return 2
+
+ bindings = subprocess.run(
+ [
+ "wasm-bindgen",
+ str(wasm_path),
+ "--target",
+ "web",
+ "--out-dir",
+ str(output_dir),
+ "--out-name",
+ "game_reflex_poc_tauri",
+ "--no-typescript",
+ ],
+ cwd=root,
+ check=False,
+ )
+ if bindings.returncode != 0:
+ return bindings.returncode
+
+ print(f"Reflex Tauri WASM frontend generated in {output_dir}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())