0.3.1-0-pre.2

This commit is contained in:
2026-09-20 21:30:12 +02:00
parent ba718ce4ec
commit 51371c1b24
29 changed files with 981 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml
# version: 70
# version: 71
[workspace]
resolver = "3"
@@ -17,10 +17,11 @@ members = [
"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.3.1-0-pre.1"
version = "0.3.1-0-pre.2"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 41 -->
<!-- version: 42 -->
# games.sasedev
@@ -26,7 +26,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
Version stable de référence : `0.3.0`.
Version de développement courante : `0.3.1-0-pre.1`.
Version de développement courante : `0.3.1-0-pre.2`.
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: Web/game-snake-poc/frontend/ts/provenance.ts
// version: 1
// version: 2
import type { SnakeWasmGame } from "@snake-wasm";
import { webInfo } from "./logging";
@@ -83,7 +83,7 @@ export class BrowserRuntimeProvenance {
private synchronize(): void {
const profile = inputProfile(this.keyboardMouseObserved, this.touchObserved);
if (!this.game.configure_runtime_provenance(this.deviceClass, profile)) {
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);

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,25 @@
<!-- file: crates/apps/game-snake-poc-tauri/README.md -->
<!-- version: 1 -->
# game-snake-poc-tauri
Host Tauri Android 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`.
Le frontend minimal de `0.3.1-0-pre.2` valide uniquement le démarrage Tauri, le bridge Rust, le tracing et le chargement du même adapter WASM que le Web direct. Canvas, contrôles, assets et lifecycle complet sont prévus dans les tranches suivantes.
## 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.
Voir [`USAGE.md`](USAGE.md) pour l'initialisation locale et les smokes Android.

View File

@@ -0,0 +1,42 @@
<!-- file: crates/apps/game-snake-poc-tauri/USAGE.md -->
<!-- version: 1 -->
# 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 imposé globalement par le projet. Si une commande Tauri locale le réclame, définir ponctuellement `NDK_HOME` vers une version NDK exacte installée sous `$ANDROID_HOME/ndk/`; ne pas sélectionner implicitement une version lorsque plusieurs NDK coexistent.
`JAVA_HOME` n'est pas imposé tant que la CLI Tauri résout correctement `java` depuis l'environnement. En cas de diagnostic explicite de la CLI, fixer alors `JAVA_HOME` vers le JDK réellement choisi.
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 ne sont pas requises pour `0-pre.2`.
## 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.
## 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 une provenance `android / tauri-webview / wasm / unknown / unknown` et 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.

View File

@@ -0,0 +1,7 @@
// file: crates/apps/game-snake-poc-tauri/build.rs
// version: 1
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,58 @@
/* file: crates/apps/game-snake-poc-tauri/frontend/css/main.css */
/* version: 1 */
:root {
font-family: system-ui, sans-serif;
color-scheme: dark;
background: #101418;
color: #f4f7f8;
}
* {
box-sizing: border-box;
}
html,
body {
min-height: 100%;
margin: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
padding: max(1.5rem, env(safe-area-inset-top)) max(1.5rem, env(safe-area-inset-right)) max(1.5rem, env(safe-area-inset-bottom))
max(1.5rem, env(safe-area-inset-left));
}
.bootstrap-panel {
width: min(100%, 34rem);
padding: 1.5rem;
border: 1px solid currentColor;
border-radius: 0.75rem;
}
.eyebrow {
margin: 0 0 0.5rem;
opacity: 0.7;
}
h1 {
margin: 0 0 1rem;
font-size: clamp(1.6rem, 7vw, 2.4rem);
}
#runtime-status {
margin: 0 0 0.75rem;
}
#runtime-provenance {
display: block;
overflow-wrap: anywhere;
font-family: ui-monospace, monospace;
}
body[data-runtime-state="error"] #runtime-status {
font-weight: 700;
}

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Snake POC Tauri</title>
</head>
<body>
<main class="bootstrap-panel" aria-labelledby="runtime-title">
<p class="eyebrow">games.sasedev</p>
<h1 id="runtime-title">Snake — Tauri Android</h1>
<p id="runtime-status">Initialisation du runtime…</p>
<output id="runtime-provenance" aria-live="polite">provenance en attente</output>
</main>
<script type="module" src="/ts/main.ts"></script>
</body>
</html>

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,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,56 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/main.ts
// version: 1
import init, { SnakeWasmGame } from "@snake-wasm";
import "../css/main.css";
import { getRuntimeDescriptor, reportFrontendReady } from "./bridge";
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();
await init();
const game = new SnakeWasmGame();
const configured = game.configure_runtime_provenance(
descriptor.platformFamily,
descriptor.runtimeHost,
descriptor.deviceClass,
descriptor.inputProfile,
);
if (!configured) {
game.free();
throw new Error("La provenance Tauri Android n'a pas été acceptée par l'adapter Snake WASM.");
}
const status = requiredElement<HTMLParagraphElement>("runtime-status");
const provenance = requiredElement<HTMLOutputElement>("runtime-provenance");
status.textContent = "Runtime minimal initialisé. Le Canvas et les contrôles arrivent dans 0-pre.3.";
provenance.value = [
game.provenance_platform_family(),
game.provenance_runtime_host(),
game.provenance_execution_model(),
game.provenance_device_class(),
game.provenance_input_profile(),
].join(" / ");
window.addEventListener("pagehide", () => game.free(), { once: true });
await reportFrontendReady();
frontendInfo(`Snake Tauri frontend ready: ${provenance.value}`);
}
void bootstrap().catch(reason => {
const message = reason instanceof Error ? reason.message : String(reason);
document.body.dataset.runtimeState = "error";
const status = document.getElementById("runtime-status");
if (status !== null) {
status.textContent = `Échec d'initialisation : ${message}`;
}
frontendError(`Snake Tauri frontend bootstrap failed: ${message}`);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

View File

@@ -0,0 +1,21 @@
{
"name": "game-snake-poc-tauri",
"private": true,
"version": "0.1.0",
"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"
}
}

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,65 @@
// file: crates/apps/game-snake-poc-tauri/src/tauri.rs
// version: 1
//! 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]);
}
/// Runs the Snake Tauri application.
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() -> 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())
},
};
}

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,72 @@
// file: crates/apps/game-snake-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 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 devHost = process.env.TAURI_DEV_HOST;
export default defineConfig({
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 || "localhost",
port: 1437,
},
watch: {
ignored: ["**/src/**"],
},
},
});

View File

@@ -1,5 +1,5 @@
// file: crates/apps/game-snake-poc-wasm/src/runtime.rs
// version: 2
// version: 3
const FIXED_STEP_MILLIS: u64 = 16;
@@ -31,8 +31,16 @@ impl SnakeWasmGame {
};
}
/// Updates browser-observed device and input dimensions while preserving Web/WASM/Browser provenance.
pub fn configure_runtime_provenance(&mut self, device_class: &str, input_profile: &str) -> bool {
/// 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,
@@ -45,8 +53,8 @@ impl SnakeWasmGame {
device_class,
engine_v1_platform_api::ExecutionModel::Wasm,
input_profile,
engine_v1_platform_api::PlatformFamily::Web,
engine_v1_platform_api::RuntimeHost::Browser,
platform_family,
runtime_host,
);
return true;
}
@@ -236,6 +244,24 @@ impl SnakeWasmGame {
}
}
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),

View File

@@ -1,5 +1,5 @@
// file: crates/apps/game-snake-poc-wasm/unit_tests/runtime.rs
// version: 2
// version: 3
#[test]
fn direction_is_queued_without_advancing_simulation() {
@@ -34,7 +34,7 @@ fn browser_runtime_provenance_preserves_static_and_observed_dimensions() {
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("phone", "touch"));
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");
@@ -45,8 +45,19 @@ fn browser_runtime_provenance_preserves_static_and_observed_dimensions() {
#[test]
fn invalid_runtime_provenance_labels_are_rejected_without_mutation() {
let mut game = crate::SnakeWasmGame::new();
assert!(game.configure_runtime_provenance("desktop", "keyboard-mouse"));
assert!(!game.configure_runtime_provenance("console", "telepathy"));
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,
}

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é.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Plan v0.3.1 — second host Snake Tauri Android
@@ -55,7 +55,7 @@ Audit complet de l'archive stable, règles, baseline Web/WASM, Tauri Reflex, ré
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éé.
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
@@ -73,7 +73,9 @@ Créer `game-snake-poc-tauri` avec :
La tranche vise un premier démarrage Tauri Android minimal, pas encore toute la finition input/lifecycle.
Gate utilisateur : audits, Cargo workspace strict, build WASM nécessaire, `cargo tauri android dev` sur un AVD ou appareil disponible. `cargo tauri android build` reste reporté aux phases finales sauf besoin diagnostique.
É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.
### `0-pre.3` — shell Snake, Canvas, input et assets
@@ -158,6 +160,8 @@ 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` et `NDK_HOME` sont vides dans cet environnement mais ne sont pas traités comme prérequis globaux du dépôt : Java est résolu depuis le `PATH`, et les NDK side-by-side restent sous `$ANDROID_HOME/ndk/<version>`. Si la CLI Tauri exige explicitement `NDK_HOME` pour une opération donnée, la validation utilisera une variable ponctuelle visant une version NDK exacte plutôt qu'un réglage global ambigu.
## Critères de fermeture de 0.3.1
La version peut entrer en RC lorsque :

45
history/0.3.1/0-pre.1.md Normal file
View File

@@ -0,0 +1,45 @@
<!-- file: history/0.3.1/0-pre.1.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.1
## Statut
Cadrage `0.3.1-0-pre.1` validé par l'utilisateur le 2026-09-20. Les audits, le formatage, le check workspace et Clippy strict passent ; aucun `.fix.N` n'est requis avant l'ouverture de `0-pre.2`.
## Gate utilisateur
La gate fournie dans le delta a été exécutée avec succès :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 210 file(s))
Distribution layout audit: clean (31 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
## Environnement confirmé
L'inventaire de transmission vers `0-pre.2` confirme :
- Rust/Cargo `1.94.1` ;
- Tauri CLI `2.11.3` ;
- cibles Rust `aarch64-linux-android`, `x86_64-linux-android`, `wasm32-unknown-unknown` et `x86_64-unknown-linux-gnu` ;
- Node `24.21.0`, npm `11.19.0`, wasm-bindgen `0.2.128` ;
- OpenJDK Temurin `25.0.4.1` disponible via `java` ;
- `ANDROID_HOME=/home/sinus/DEV/AndroidSDK` ;
- `JAVA_HOME` et `NDK_HOME` non définis globalement ;
- Android Debug Bridge `37.0.1` ;
- un Samsung SM-G965F réel connecté ;
- AVD `Medium_Phone`, `Medium_Phone_API_36.0` et `sasedev_games_api36` disponibles.
Le projet ne transforme pas l'absence de `NDK_HOME` en défaut : l'installation locale conserve plusieurs NDK sous `$ANDROID_HOME/ndk/<version>`. Si une commande Tauri demande cette variable, elle sera définie ponctuellement vers une version exacte. De même, `JAVA_HOME` ne devient nécessaire que si la CLI ne résout pas le JDK disponible depuis l'environnement.
## Transmission vers 0-pre.2
L'archive complète `khadhroony-solana-project-v0.3.15.zip` est désormais disponible directement pour la tranche suivante. L'audit de ses six applications `ksp-app-*-desk` confirme le modèle retenu : façade `lib.rs`, assemblage `tauri.rs`, frontend Vite dans la crate, `tauri.conf.json`, capability dédiée et dépendances tracing Rust/TypeScript.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_distribution_layout.py
# version: 7
# version: 8
"""Audit the static distribution layout expected by supported POCs."""
@@ -30,6 +30,17 @@ REQUIRED_PATHS = (
"crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts",
"crates/apps/game-reflex-poc-wasm/Cargo.toml",
"crates/apps/game-snake-poc-wasm/Cargo.toml",
"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/package.json",
"crates/apps/game-snake-poc-tauri/tsconfig.json",
"crates/apps/game-snake-poc-tauri/vite.config.ts",
"crates/apps/game-snake-poc-tauri/tauri.conf.json",
"crates/apps/game-snake-poc-tauri/capabilities/default.json",
"crates/apps/game-snake-poc-tauri/icons/icon.png",
"crates/apps/game-snake-poc-tauri/frontend/main.html",
"crates/apps/game-snake-poc-tauri/frontend/ts/main.ts",
"Web/game-snake-poc/package.json",
"Web/game-snake-poc/tsconfig.json",
"Web/game-snake-poc/vite.config.ts",
@@ -136,6 +147,56 @@ def audit_snake_web_integration(root: pathlib.Path) -> list[str]:
violations.append(message)
return violations
def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
"""Validate the minimal Snake Tauri Android host boundary."""
violations: list[str] = []
cargo_path = root / "crates/apps/game-snake-poc-tauri/Cargo.toml"
package_path = root / "crates/apps/game-snake-poc-tauri/package.json"
tauri_path = root / "crates/apps/game-snake-poc-tauri/tauri.conf.json"
tauri_rust_path = root / "crates/apps/game-snake-poc-tauri/src/tauri.rs"
frontend_path = root / "crates/apps/game-snake-poc-tauri/frontend/ts/main.ts"
logging_path = root / "crates/apps/game-snake-poc-tauri/frontend/ts/logging.ts"
wasm_runtime_path = root / "crates/apps/game-snake-poc-wasm/src/runtime.rs"
paths = (cargo_path, package_path, tauri_path, tauri_rust_path, frontend_path, logging_path, wasm_runtime_path)
if not all(path.exists() for path in paths):
return violations
cargo = cargo_path.read_text(encoding="utf-8")
package = package_path.read_text(encoding="utf-8")
tauri = tauri_path.read_text(encoding="utf-8")
tauri_rust = tauri_rust_path.read_text(encoding="utf-8")
frontend = frontend_path.read_text(encoding="utf-8")
logging = logging_path.read_text(encoding="utf-8")
wasm_runtime = wasm_runtime_path.read_text(encoding="utf-8")
required_fragments = (
(cargo, 'game-logging-lib = { path = "../../common/game-logging-lib" }', "DIST-LAYOUT-021: Snake Tauri host must use shared Rust logging"),
(cargo, 'tauri-plugin-tracing.workspace = true', "DIST-LAYOUT-022: Snake Tauri host must enable the tracing plugin"),
(package, '"wasm:dev": "cargo build -p game-snake-poc-wasm', "DIST-LAYOUT-023: Snake Tauri dev hook must build the shared Snake WASM adapter"),
(package, 'wasm-bindgen ../../../../builds/sasedev-games/', "DIST-LAYOUT-024: Snake Tauri bindings must be generated outside the repository"),
(tauri, '"script": "npm run dev"', "DIST-LAYOUT-025: Tauri must own the frontend development hook"),
(tauri, '"script": "npm run build"', "DIST-LAYOUT-026: Tauri must own the frontend production hook"),
(tauri_rust, '#[cfg_attr(mobile, tauri::mobile_entry_point)]', "DIST-LAYOUT-027: Snake Tauri run entry point must support Tauri mobile"),
(tauri_rust, 'tauri_plugin_tracing::Builder::new()', "DIST-LAYOUT-028: Snake Tauri runtime must install the tracing plugin"),
(frontend, 'configure_runtime_provenance(', "DIST-LAYOUT-029: Snake Tauri frontend must configure provenance through the shared adapter"),
(logging, '@fltsci/tauri-plugin-tracing', "DIST-LAYOUT-030: Snake Tauri frontend logs must use the tracing bridge"),
(wasm_runtime, '"tauri-webview" => Some(engine_v1_platform_api::RuntimeHost::TauriWebView)', "DIST-LAYOUT-031: Snake WASM adapter must accept the Tauri WebView host"),
(wasm_runtime, '"android" => Some(engine_v1_platform_api::PlatformFamily::Android)', "DIST-LAYOUT-032: Snake WASM adapter must accept Android provenance"),
)
for content, fragment, message in required_fragments:
if fragment not in content:
violations.append(message)
forbidden_fragments = (
(package, "python", "DIST-LAYOUT-033: Snake Tauri build hooks must not use Python"),
(tauri, "python", "DIST-LAYOUT-034: Snake Tauri configuration must not use Python build orchestration"),
(frontend, "console.", "DIST-LAYOUT-035: Snake Tauri application frontend must not log directly through console.*"),
)
for content, fragment, message in forbidden_fragments:
if fragment in content.lower():
violations.append(message)
return violations
def main() -> int:
"""Validate that every static distribution boundary exists."""
@@ -154,6 +215,7 @@ def main() -> int:
forbidden_present.append(relative)
contract_violations.extend(audit_snake_web_shell(root))
contract_violations.extend(audit_snake_web_integration(root))
contract_violations.extend(audit_snake_tauri_android_integration(root))
if missing:
for relative in missing:
print(f"DIST-LAYOUT-001: missing required path: {relative}", file=sys.stderr)