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 + + + +
+ + Score : 0 + Tauri · WebView · WASM +
+ + + 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())