0.1.0-1-alpha.2-fix.2

This commit is contained in:
2026-09-17 00:25:37 +02:00
parent f8b93050d8
commit 37a00b3c2b
35 changed files with 620 additions and 181 deletions

6
.gitignore vendored
View File

@@ -64,5 +64,7 @@ __pycache__/
*.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
crates/apps/game-reflex-poc-tauri/frontend/wasm/game_reflex_poc_wasm.js
crates/apps/game-reflex-poc-tauri/frontend/wasm/game_reflex_poc_wasm_bg.wasm
crates/apps/game-reflex-poc-tauri/frontend/wasm/game_reflex_poc_wasm.d.ts
crates/apps/game-reflex-poc-tauri/frontend/wasm/game_reflex_poc_wasm_bg.wasm.d.ts

View File

@@ -1,5 +1,5 @@
// file: Android/game-reflex-poc/build.gradle
// version: 12
// version: 13
plugins {
id 'com.android.application'
@@ -18,7 +18,7 @@ android {
minSdk 21
targetSdk 36
versionCode 1
versionName '0.1.0-1-alpha.2.fix.1'
versionName '0.1.0-1-alpha.2.fix.2'
}
compileOptions {

View File

@@ -1,5 +1,5 @@
// file: Android/game-snake-poc/build.gradle
// version: 12
// version: 13
plugins {
id 'com.android.application'
@@ -18,7 +18,7 @@ android {
minSdk 21
targetSdk 36
versionCode 1
versionName '0.1.0-1-alpha.2.fix.1'
versionName '0.1.0-1-alpha.2.fix.2'
}
compileOptions {

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 29
# version: 30
[workspace]
resolver = "3"
@@ -15,10 +15,11 @@ members = [
"crates/common/game-logging-lib",
"crates/apps/game-android-entrypoint",
"crates/apps/game-reflex-poc-tauri",
"crates/apps/game-reflex-poc-wasm",
]
[workspace.package]
version = "0.1.0-1-alpha.2.fix.1"
version = "0.1.0-1-alpha.2.fix.2"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games"
@@ -32,6 +33,7 @@ tracing-appender = "0.2.5"
tracing-subscriber = "0.3.23"
tauri = "2"
tauri-build = "2"
tauri-plugin-tracing = "^0.3"
wasm-bindgen = "0.2"
[workspace.lints.rust]

View File

@@ -1,10 +0,0 @@
<!-- file: Tauri/README.md -->
<!-- version: 1 -->
# 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.

View File

@@ -1,76 +0,0 @@
// 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);
});

View File

@@ -1,5 +1,5 @@
# file: crates/apps/game-reflex-poc-tauri/Cargo.toml
# version: 1
# version: 3
[package]
name = "game-reflex-poc-tauri"
@@ -9,24 +9,24 @@ license.workspace = true
repository.workspace = true
authors.workspace = true
publish.workspace = true
build = "build.rs"
[lib]
crate-type = ["cdylib", "rlib"]
name = "game_reflex_poc_tauri_lib"
path = "src/lib.rs"
crate-type = ["staticlib", "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
[[bin]]
name = "game-reflex-poc-tauri"
path = "src/main.rs"
[build-dependencies]
tauri-build.workspace = true
[dependencies]
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
tauri.workspace = true
tauri-plugin-tracing.workspace = true
tracing.workspace = true
[lints]
workspace = true

View File

@@ -1,14 +1,9 @@
// file: crates/apps/game-reflex-poc-tauri/build.rs
// version: 2
// version: 3
//! Tauri build script skipped for the WebAssembly library target.
//! Native Tauri build script for the Reflex Desktop WebView runner.
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;
}

View File

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

View File

@@ -4,14 +4,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Reflex POC Tauri</title>
<link rel="stylesheet" href="./style.css">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<main id="app">
<canvas id="game" aria-label="Reflex POC"></canvas>
<output id="score">Score : 0</output>
<output id="runtime">Tauri · WebView · WASM</output>
<output id="runtime">Initialisation…</output>
</main>
<script type="module" src="./main.js"></script>
<script type="module" src="/ts/main.ts"></script>
</body>
</html>

View File

@@ -1,4 +1,4 @@
/* file: Tauri/game-reflex-poc/frontend/style.css */
/* file: crates/apps/game-reflex-poc-tauri/frontend/style.css */
/* version: 1 */
html,

View File

@@ -0,0 +1,12 @@
// file: crates/apps/game-reflex-poc-tauri/frontend/ts/bridge.ts
// version: 1
import { invoke } from "@tauri-apps/api/core";
export async function getRuntimeLabel(): Promise<string> {
return await invoke<string>("get_runtime_label");
}
export async function notifyFrontendReady(): Promise<void> {
await invoke("frontend_ready");
}

View File

@@ -0,0 +1,67 @@
// file: crates/apps/game-reflex-poc-tauri/frontend/ts/game.ts
// version: 1
import init, { ReflexWasmGame } from "../wasm/game_reflex_poc_wasm.js";
import { tracingDebug, tracingTrace } from "./logging";
const FRAME_MS = 16;
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: ReflexWasmGame, canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, score: 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()}`;
}
export async function startGame(canvas: HTMLCanvasElement, score: HTMLOutputElement): Promise<void> {
await init();
await tracingDebug("Reflex WASM module initialized");
const context = canvas.getContext("2d");
if (context === null) {
throw new Error("2D canvas context is unavailable");
}
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);
void tracingTrace(`pointerdown x=${x.toFixed(4)} y=${y.toFixed(4)} score=${game.score()}`);
});
let previous = performance.now();
function frame(now: number): void {
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);
}

View File

@@ -0,0 +1,38 @@
// file: crates/apps/game-reflex-poc-tauri/frontend/ts/logging.ts
// version: 1
import { attachConsole, debug, error, info, trace, warn } from "@fltsci/tauri-plugin-tracing";
let detachRustConsole: (() => void) | null = null;
export async function initializeTracing(): Promise<void> {
detachRustConsole = await attachConsole();
await info("Reflex Tauri frontend tracing initialized", "games::tauri::reflex::frontend");
}
export async function tracingTrace(message: string): Promise<void> {
await trace(message, "games::tauri::reflex::frontend");
}
export async function tracingDebug(message: string): Promise<void> {
await debug(message, "games::tauri::reflex::frontend");
}
export async function tracingInfo(message: string): Promise<void> {
await info(message, "games::tauri::reflex::frontend");
}
export async function tracingWarn(message: string): Promise<void> {
await warn(message, "games::tauri::reflex::frontend");
}
export async function tracingError(message: string): Promise<void> {
await error(message, "games::tauri::reflex::frontend");
}
export function detachTracingConsole(): void {
if (detachRustConsole !== null) {
detachRustConsole();
detachRustConsole = null;
}
}

View File

@@ -0,0 +1,25 @@
// file: crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts
// version: 1
import { getRuntimeLabel, notifyFrontendReady } from "./bridge";
import { startGame } from "./game";
import { initializeTracing, tracingError, tracingInfo } from "./logging";
async function main(): Promise<void> {
await initializeTracing();
const canvas = document.querySelector<HTMLCanvasElement>("#game");
const score = document.querySelector<HTMLOutputElement>("#score");
const runtime = document.querySelector<HTMLOutputElement>("#runtime");
if (canvas === null || score === null || runtime === null) {
throw new Error("Reflex Tauri frontend DOM is incomplete");
}
runtime.value = await getRuntimeLabel();
await startGame(canvas, score);
await notifyFrontendReady();
await tracingInfo("Reflex Tauri frontend started");
}
void main().catch(async caughtError => {
const message = caughtError instanceof Error ? caughtError.stack ?? caughtError.message : String(caughtError);
await tracingError(`Reflex Tauri frontend startup failed: ${message}`);
});

View File

@@ -0,0 +1,20 @@
{
"name": "game-reflex-poc-tauri",
"private": true,
"version": "0.1.0-1-alpha.2.fix.2",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
},
"dependencies": {
"@fltsci/tauri-plugin-tracing": "^0.3",
"@tauri-apps/api": "^2.11"
},
"devDependencies": {
"@tauri-apps/cli": "^2.11",
"@types/node": "^26.1",
"typescript": "^7.0",
"vite": "^8.2"
}
}

View File

@@ -1,15 +1,19 @@
// file: crates/apps/game-reflex-poc-tauri/src/lib.rs
// version: 1
// version: 2
//! Tauri desktop application facade for the Reflex WebAssembly POC.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
//! WebAssembly adapter for the Reflex POC when hosted by the Tauri variant.
mod runtime;
mod tauri;
#[cfg(target_arch = "wasm32")]
mod wasm_runtime;
/// Runs the Reflex Tauri desktop application.
pub use self::tauri::run;
#[cfg(target_arch = "wasm32")]
/// Re-export of the Reflex WebAssembly runtime adapter.
pub use self::wasm_runtime::ReflexWasmGame;
/// Records that the Vite/TypeScript frontend reached its ready state.
pub(crate) use self::runtime::record_frontend_ready;
/// Returns the runtime label exposed by the Tauri bridge.
pub(crate) use self::runtime::runtime_label;

View File

@@ -1,25 +1,20 @@
// file: crates/apps/game-reflex-poc-tauri/src/main.rs
// version: 1
// version: 2
//! Binary entry point for the Reflex Tauri desktop application.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//! 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(()) => {},
fn main() -> std::process::ExitCode {
let result = game_reflex_poc_tauri_lib::run();
return match result {
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
std::result::Result::Err(error) => {
eprintln!("Reflex POC Tauri runner failed: {error}");
eprintln!("Reflex Tauri application error: {error}");
std::process::ExitCode::FAILURE
},
}
return;
}
#[cfg(target_arch = "wasm32")]
fn main() {
return;
};
}

View File

@@ -0,0 +1,18 @@
// file: crates/apps/game-reflex-poc-tauri/src/runtime.rs
// version: 1
/// Stable label for the current Tauri/WASM runtime combination.
pub(crate) fn runtime_label() -> &'static str {
return "Desktop / TauriWebView / Wasm / KeyboardMouse";
}
/// Records that the frontend completed its startup sequence.
pub(crate) fn record_frontend_ready() {
tracing::info!(
target: "games::tauri::reflex",
action = "frontend_ready",
runtime = crate::runtime_label(),
"Reflex Tauri frontend ready"
);
return;
}

View File

@@ -0,0 +1,39 @@
// file: crates/apps/game-reflex-poc-tauri/src/tauri.rs
// version: 1
#[tauri::command]
fn get_runtime_label() -> String {
return crate::runtime_label().to_string();
}
#[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_label, frontend_ready]);
}
/// Runs the Reflex Tauri desktop application.
pub fn run() -> std::result::Result<(), String> {
tracing::info!(
target: "games::tauri::reflex",
action = "runtime_start",
"Starting Reflex Tauri 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(()) => std::result::Result::Ok(()),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}

View File

@@ -1,14 +1,24 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Reflex POC Tauri",
"version": "0.1.0-1-alpha.2.fix.1",
"version": "0.1.0-1-alpha.2.fix.2",
"identifier": "com.sasedev.games.reflex.tauri",
"build": {
"frontendDist": "../../../Tauri/game-reflex-poc/frontend"
"beforeDevCommand": {
"script": "python3 ../../../scripts/build_reflex_tauri_wasm.py && npm run dev",
"cwd": "."
},
"devUrl": "http://localhost:1432",
"beforeBuildCommand": {
"script": "python3 ../../../scripts/build_reflex_tauri_wasm.py && npm run build",
"cwd": "."
},
"frontendDist": "../../../builds/sasedev-games/game-reflex-poc-tauri/dist"
},
"app": {
"windows": [
{
"label": "main",
"title": "Reflex POC Tauri",
"width": 405,
"height": 720,
@@ -16,10 +26,11 @@
}
],
"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"
"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 http://localhost:1432 ws://localhost:1433"
}
},
"bundle": {
"active": false
"active": false,
"icon": ["icons/icon.png"]
}
}

View File

@@ -0,0 +1,19 @@
{
"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
},
"include": ["frontend/**/*.ts", "vite.config.ts"]
}

View File

@@ -0,0 +1,36 @@
// file: crates/apps/game-reflex-poc-tauri/vite.config.ts
// version: 1
import { fileURLToPath } from "node:url";
import { resolve } from "node:path";
import { defineConfig, normalizePath } from "vite";
const appRoot = fileURLToPath(new URL(".", import.meta.url));
const frontendRoot = normalizePath(resolve(appRoot, "frontend"));
const frontendDist = normalizePath(resolve(appRoot, "../../../builds/sasedev-games/game-reflex-poc-tauri/dist"));
const devHost = process.env.TAURI_DEV_HOST;
export default defineConfig({
clearScreen: false,
root: frontendRoot,
publicDir: false,
build: {
outDir: frontendDist,
emptyOutDir: true,
minify: true,
sourcemap: false,
},
server: {
port: 1432,
strictPort: true,
host: devHost || false,
ws: {
protocol: "ws",
host: devHost || "localhost",
port: 1433,
},
watch: {
ignored: ["**/src/**"],
},
},
});

View File

@@ -0,0 +1,22 @@
# file: crates/apps/game-reflex-poc-wasm/Cargo.toml
# version: 1
[package]
name = "game-reflex-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" }
game-reflex-poc = { path = "../../games/game-reflex-poc" }
wasm-bindgen.workspace = true
[lints]
workspace = true

View File

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

View File

@@ -1,7 +1,7 @@
// file: crates/apps/game-reflex-poc-tauri/src/wasm_runtime.rs
// file: crates/apps/game-reflex-poc-wasm/src/runtime.rs
// version: 1
/// WebAssembly-owned Reflex game state consumed by the local Tauri WebView.
/// WebAssembly-owned Reflex game state consumed by the Tauri WebView.
#[wasm_bindgen::prelude::wasm_bindgen]
pub struct ReflexWasmGame {
runner: engine_v1_common::FixedStepRunner,
@@ -10,7 +10,7 @@ pub struct ReflexWasmGame {
#[wasm_bindgen::prelude::wasm_bindgen]
impl ReflexWasmGame {
/// Creates a fresh Reflex POC session using the same gameplay crate as native Desktop.
/// Creates a fresh Reflex POC session using the shared gameplay crate.
#[wasm_bindgen::prelude::wasm_bindgen(constructor)]
pub fn new() -> Self {
return Self {
@@ -65,7 +65,7 @@ impl ReflexWasmGame {
return count;
}
/// Returns one rectangle's normalized left coordinate or `-1.0` if the index is absent.
/// 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(),
@@ -73,7 +73,7 @@ impl ReflexWasmGame {
};
}
/// Returns one rectangle's normalized top coordinate or `-1.0` if the index is absent.
/// 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(),
@@ -81,7 +81,7 @@ impl ReflexWasmGame {
};
}
/// Returns one rectangle's normalized width or `0.0` if the index is absent.
/// 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(),
@@ -89,7 +89,7 @@ impl ReflexWasmGame {
};
}
/// Returns one rectangle's normalized height or `0.0` if the index is absent.
/// 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(),
@@ -97,7 +97,7 @@ impl ReflexWasmGame {
};
}
/// Returns one rectangle's red channel or zero if the index is absent.
/// 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(),
@@ -105,7 +105,7 @@ impl ReflexWasmGame {
};
}
/// Returns one rectangle's green channel or zero if the index is absent.
/// 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(),
@@ -113,7 +113,7 @@ impl ReflexWasmGame {
};
}
/// Returns one rectangle's blue channel or zero if the index is absent.
/// 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(),

View File

@@ -0,0 +1,3 @@
Tauri/game-reflex-poc
Tauri/README.md
crates/apps/game-reflex-poc-tauri/src/wasm_runtime.rs

View File

@@ -0,0 +1,154 @@
<!-- file: deltas/0.1.0/1-alpha.2.fix.2.md -->
<!-- version: 1 -->
# Delta 0.1.0-1-alpha.2.fix.2
## Base
Base déclarée : `0.1.0-1-alpha.2.fix.1`, techniquement compilable mais architecture Tauri/WASM rejetée avant validation fonctionnelle.
## Réorganisation
Le POC est réaligné sur le modèle des apps Desk KSP.
### Tauri
```text
crates/apps/game-reflex-poc-tauri/
├── src/lib.rs
├── src/tauri.rs
├── src/runtime.rs
├── src/main.rs
├── frontend/
├── capabilities/
├── package.json
├── tsconfig.json
├── vite.config.ts
└── tauri.conf.json
```
`lib.rs` reste une façade. `tauri.rs` assemble Tauri et expose les commandes qui délèguent aux modules propriétaires.
### WASM
```text
crates/apps/game-reflex-poc-wasm/
├── src/lib.rs
└── src/runtime.rs
```
La crate WASM ne dépend pas de Tauri. La crate Tauri ne compile plus elle-même en `wasm32`.
### Frontend
Le frontend utilise Vite + TypeScript.
Le hook Tauri `beforeDevCommand` exécute :
```text
python3 ../../../scripts/build_reflex_tauri_wasm.py && npm run dev
```
Le hook `beforeBuildCommand` exécute le build WASM puis `npm run build`.
`npm run build` n'est donc pas une gate manuelle indépendante.
### Logging
Backend :
```text
tracing
tauri-plugin-tracing
```
Frontend :
```text
@fltsci/tauri-plugin-tracing
```
La capability inclut :
```text
tracing:default
```
Le frontend trace startup, chargement WASM, pointeur, score et erreurs via le plugin.
## Suppressions nécessaires
Un ZIP delta ne supprime pas les anciens fichiers lors d'un simple overlay.
Après extraction du delta, appliquer la liste :
```bash
while IFS= read -r path; do
rm -rf -- "$path"
done < deltas/0.1.0/1-alpha.2.fix.2.delete.txt
```
Cette suppression retire uniquement l'ancien prototype statique et l'ancien module WASM interne à la crate Tauri.
## Installation frontend
Depuis :
```bash
cd crates/apps/game-reflex-poc-tauri
npm install
cd ../../..
```
## 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 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
cargo test -p game-reflex-poc-wasm --all-targets --all-features
```
## Gate WASM
```bash
python3 scripts/build_reflex_tauri_wasm.py
test -f crates/apps/game-reflex-poc-tauri/frontend/wasm/game_reflex_poc_wasm.js
test -f crates/apps/game-reflex-poc-tauri/frontend/wasm/game_reflex_poc_wasm_bg.wasm
```
## Smoke Tauri
Depuis la crate :
```bash
cd crates/apps/game-reflex-poc-tauri
cargo tauri dev
```
Tauri lance lui-même Vite et le build WASM.
Critères :
- fenêtre locale 405 × 720 ;
- frontend Vite/TypeScript ;
- cible Reflex visible ;
- clic comptabilisé et cible déplacée ;
- score visible ;
- traces frontend visibles via le plugin tracing ;
- aucun site distant.
## Transition
Si les gates et le smoke passent, `0.1.0-1-alpha.2.fix.2` valide le POC restructuré.
En cas d'échec imputable au projet, produire `0.1.0-1-alpha.2.fix.3`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md -->
<!-- version: 14 -->
<!-- version: 15 -->
# Documentation games.sasedev

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/001-WORKSPACE_ARCHITECTURE.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# Architecture du workspace
@@ -21,13 +21,12 @@ games.sasedev/
│ └── apps/
│ ├── game-reflex-poc-desktop/
│ ├── game-snake-poc-desktop/
── game-reflex-poc-tauri/
── game-reflex-poc-tauri/
│ └── game-reflex-poc-wasm/
├── assets/
│ ├── common/
│ ├── game-reflex-poc/
│ └── game-snake-poc/
├── Tauri/
│ └── game-reflex-poc/
├── Android/
│ ├── common/
│ ├── game-reflex-poc/
@@ -80,3 +79,5 @@ La variante Tauri est distincte du runner SDL3 natif.
- 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`.
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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/development/008-WASM_TAURI_POC.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# POC WebAssembly embarqué dans Tauri
@@ -111,3 +111,37 @@ crates/apps/game-reflex-poc-tauri/icons/icon.png
```
Il s'agit d'une icône minimale de développement ; l'identité graphique définitive viendra plus tard.
## Réorganisation fix.2
La crate Tauri et la crate WASM sont désormais séparées.
```text
crates/apps/game-reflex-poc-tauri/
├── src/lib.rs
├── src/tauri.rs
├── src/runtime.rs
├── src/main.rs
├── frontend/
├── package.json
├── tsconfig.json
├── vite.config.ts
└── tauri.conf.json
crates/apps/game-reflex-poc-wasm/
├── src/lib.rs
└── src/runtime.rs
```
`lib.rs` joue le rôle de façade. `tauri.rs` contient le pont Web/Rust et l'assemblage Tauri. Les fonctions métier/runtime sont portées par leurs modules puis appelées par les commandes Tauri.
Le frontend utilise Vite + TypeScript. Le build WASM reste séparé et produit les bindings `wasm-bindgen` dans `frontend/wasm/`.
Le logging est unifié avec `tracing` :
- `tracing` côté Rust ;
- `tauri-plugin-tracing` côté Tauri ;
- `@fltsci/tauri-plugin-tracing` côté TypeScript ;
- capability `tracing:default`.
Les événements frontend utiles sont donc remontés dans le même système de traces que le backend Rust.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/FILE_CONTRACTS.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# Contrats des fichiers principaux
@@ -27,6 +27,8 @@
## Tauri
- `Tauri/<game>/frontend/` contient le frontend Web statique versionné d'une variante Tauri.
- `crates/apps/<game>-tauri/frontend/` contient le frontend Vite/TypeScript versionné.
- `crates/apps/<game>-tauri/src/lib.rs` est la façade Rust de l'app Tauri.
- `crates/apps/<game>-tauri/src/tauri.rs` assemble Tauri et porte le pont Web/Rust.
- `crates/apps/<game>-wasm/` contient l'adaptation WebAssembly distincte.
- Les bindings JavaScript et modules `.wasm` générés restent ignorés par Git.
- Le backend Rust Tauri reste sous `crates/apps/`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_COMMANDS.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Règles d'exécution des commandes
@@ -53,10 +53,12 @@
- **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-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-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
- **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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_PROJECT.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Règles spécifiques games.sasedev
@@ -49,7 +49,7 @@
- **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/<game>/` et ne duplique pas le gameplay Rust.
- **GAME-PLATFORM-007** — Une variante Tauri conserve son backend Rust sous `crates/apps/`; son frontend Vite/TypeScript réside dans la même crate sous `frontend/` 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
@@ -63,3 +63,7 @@
- **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-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.

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python3
# file: scripts/build_reflex_tauri_wasm.py
# version: 1
# version: 2
"""Build the Reflex WASM adapter and generate browser bindings for the local Tauri frontend."""
"""Build the dedicated Reflex WASM adapter for the Vite/Tauri frontend."""
from __future__ import annotations
@@ -12,15 +12,15 @@ import sys
def main() -> int:
"""Build the WASM library and run wasm-bindgen into the committed Tauri frontend directory."""
"""Build the WASM crate and generate browser bindings inside the Tauri frontend."""
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"
wasm_path = target_dir / "wasm32-unknown-unknown" / "debug" / "game_reflex_poc_wasm.wasm"
output_dir = root / "crates" / "apps" / "game-reflex-poc-tauri" / "frontend" / "wasm"
build = subprocess.run(
["cargo", "build", "-p", "game-reflex-poc-tauri", "--lib", "--target", "wasm32-unknown-unknown"],
["cargo", "build", "-p", "game-reflex-poc-wasm", "--target", "wasm32-unknown-unknown"],
cwd=root,
check=False,
)
@@ -30,6 +30,7 @@ def main() -> int:
print(f"WASM artifact not found: {wasm_path}", file=sys.stderr)
return 2
output_dir.mkdir(parents=True, exist_ok=True)
bindings = subprocess.run(
[
"wasm-bindgen",
@@ -39,8 +40,7 @@ def main() -> int:
"--out-dir",
str(output_dir),
"--out-name",
"game_reflex_poc_tauri",
"--no-typescript",
"game_reflex_poc_wasm",
],
cwd=root,
check=False,
@@ -48,7 +48,7 @@ def main() -> int:
if bindings.returncode != 0:
return bindings.returncode
print(f"Reflex Tauri WASM frontend generated in {output_dir}")
print(f"Reflex WASM bindings generated in {output_dir}")
return 0