0.3.0-0-pre.4

This commit is contained in:
2026-09-20 13:14:40 +02:00
parent c7ccf57723
commit c4b68b8a60
27 changed files with 1195 additions and 48 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 62 # version: 63
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -20,7 +20,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.3.0-0-pre.3" version = "0.3.0-0-pre.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games" repository = "https://git.sasedev.com/Sasedev/games"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md --> <!-- file: README.md -->
<!-- version: 30 --> <!-- version: 31 -->
# games.sasedev # games.sasedev
@@ -14,6 +14,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
- assets hors des crates sous `assets/` ; - assets hors des crates sous `assets/` ;
- `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ; - `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ;
- frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ; - frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ;
- hosts navigateur directs sous `Web/`, avec frontend Vite/TypeScript séparé des adapters Rust/WASM ;
- monétisation optionnelle et spécifique à chaque plateforme/distribution ; - monétisation optionnelle et spécifique à chaque plateforme/distribution ;
- runner Desktop natif SDL3 par défaut, avec variante Tauri uniquement si un besoin futur la justifie ; - runner Desktop natif SDL3 par défaut, avec variante Tauri uniquement si un besoin futur la justifie ;
- documentation sous `docs/` ; - documentation sous `docs/` ;
@@ -25,7 +26,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
Version stable de référence : `0.2.0`. Version stable de référence : `0.2.0`.
Version de développement : `0.3.0-0-pre.3`. Version de développement : `0.3.0-0-pre.4`.
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. 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
// file: Web/game-snake-poc/frontend/ts/main.ts
// version: 1
import { startGame } from "./game";
import { bindInputs } from "./input";
function requireElement<T extends Element>(selector: string): T {
const element = document.querySelector<T>(selector);
if (element === null) {
throw new Error(`Élément frontend requis absent : ${selector}`);
}
return element;
}
async function main(): Promise<void> {
const canvas = requireElement<HTMLCanvasElement>("#game");
const score = requireElement<HTMLOutputElement>("#score");
const length = requireElement<HTMLOutputElement>("#length");
const status = requireElement<HTMLSpanElement>("#runtime-status");
const error = requireElement<HTMLParagraphElement>("#startup-error");
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>("[data-direction]"));
status.textContent = "Chargement WASM…";
const session = await startGame(canvas, score, length);
bindInputs(session, buttons);
status.className = "badge text-bg-success";
status.textContent = "Prêt";
error.hidden = true;
canvas.focus();
}
void main().catch(caughtError => {
const status = document.querySelector<HTMLSpanElement>("#runtime-status");
const error = document.querySelector<HTMLParagraphElement>("#startup-error");
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
if (status !== null) {
status.className = "badge text-bg-danger";
status.textContent = "Erreur";
}
if (error !== null) {
error.textContent = `Impossible de démarrer Snake : ${message}`;
error.hidden = false;
}
});

View File

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

View File

@@ -0,0 +1,37 @@
{
"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"
],
"baseUrl": ".",
"paths": {
"@snake-wasm": [
"../../../builds/sasedev-games/game-snake-poc-web/wasm/game_snake_poc_wasm.js"
]
}
},
"include": [
"frontend",
"vite.config.ts"
]
}

View File

@@ -0,0 +1,85 @@
// file: Web/game-snake-poc/vite.config.ts
// version: 1
import { NodePackageImporter } from "sass-embedded";
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-web"));
const wasmRoot = normalizePath(resolve(externalBuildRoot, "wasm"));
const wasmModule = normalizePath(resolve(wasmRoot, "game_snake_poc_wasm.js"));
const frontendDist = normalizePath(resolve(externalBuildRoot, "dist"));
const viteCacheDir = normalizePath(resolve(externalBuildRoot, "vite-cache"));
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 (["eot", "otf", "ttf", "woff", "woff2"].includes(extension)) {
return "fonts/[name]-[hash][extname]";
}
if (["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"].includes(extension)) {
return "imgs/[name][extname]";
}
if (extension === "wasm") {
return "wasm/[name]-[hash][extname]";
}
return "otherassets/[name][extname]";
},
},
},
},
css: {
preprocessorOptions: {
scss: {
quietDeps: true,
silenceDeprecations: ["import", "color-functions", "global-builtin"],
verbose: false,
importers: [new NodePackageImporter()],
},
},
},
server: {
host: "127.0.0.1",
port: 1434,
strictPort: true,
fs: {
allow: [appRoot, externalBuildRoot],
},
},
preview: {
host: "127.0.0.1",
port: 4174,
strictPort: true,
},
});

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

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

View File

@@ -1,5 +1,5 @@
<!-- file: docs/000-README.md --> <!-- file: docs/000-README.md -->
<!-- version: 23 --> <!-- version: 24 -->
# Documentation games.sasedev # Documentation games.sasedev
@@ -75,6 +75,7 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_DOCUMENTATION.md`](ru
- [`development/007-ANDROID_JNI_BRIDGE.md`](development/007-ANDROID_JNI_BRIDGE.md) — frontière Java/JNI minimale, version de contrat et séparation avec l'input SDL3. - [`development/007-ANDROID_JNI_BRIDGE.md`](development/007-ANDROID_JNI_BRIDGE.md) — frontière Java/JNI minimale, version de contrat et séparation avec l'input SDL3.
- [`development/008-WASM_TAURI_POC.md`](development/008-WASM_TAURI_POC.md) — baseline historique Reflex Tauri/WebAssembly et génération WASM associée. - [`development/008-WASM_TAURI_POC.md`](development/008-WASM_TAURI_POC.md) — baseline historique Reflex Tauri/WebAssembly et génération WASM associée.
- [`development/009-SNAKE_WEB_WASM_BUILD.md`](development/009-SNAKE_WEB_WASM_BUILD.md) — build Cargo + `wasm-bindgen` natif de l'adapter Snake pour le navigateur direct. - [`development/009-SNAKE_WEB_WASM_BUILD.md`](development/009-SNAKE_WEB_WASM_BUILD.md) — build Cargo + `wasm-bindgen` natif de l'adapter Snake pour le navigateur direct.
- [`development/010-SNAKE_WEB_FRONTEND.md`](development/010-SNAKE_WEB_FRONTEND.md) — host Web direct Snake sous Vite/TypeScript, shell Bootstrap 5, Canvas et contrôles clavier/tactiles.
## Historique validé ## Historique validé

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/001-WORKSPACE_ARCHITECTURE.md --> <!-- file: docs/architecture/001-WORKSPACE_ARCHITECTURE.md -->
<!-- version: 6 --> <!-- version: 7 -->
# Architecture du workspace # Architecture du workspace
@@ -32,6 +32,8 @@ games.sasedev/
│ ├── common/ │ ├── common/
│ ├── game-reflex-poc/ │ ├── game-reflex-poc/
│ └── game-snake-poc/ │ └── game-snake-poc/
├── Web/
│ └── game-snake-poc/
├── docs/ ├── docs/
├── deltas/ ├── deltas/
├── prompts/ ├── prompts/
@@ -78,4 +80,4 @@ Les adapters WASM sont distincts des runners SDL3 natifs et ne déplacent aucune
`game-reflex-poc-tauri` reste le binaire natif Tauri qui héberge la WebView Reflex locale. Son frontend Vite/TypeScript pilote un Canvas et appelle l'adapter Reflex WASM. La crate Tauri suit le modèle des apps Desk KSP : façade `lib.rs`, pont `tauri.rs`, modules propriétaires séparés et frontend local à la crate. `game-reflex-poc-tauri` reste le binaire natif Tauri qui héberge la WebView Reflex locale. Son frontend Vite/TypeScript pilote un Canvas et appelle l'adapter Reflex WASM. La crate Tauri suit le modèle des apps Desk KSP : façade `lib.rs`, pont `tauri.rs`, modules propriétaires séparés et frontend local à la crate.
Le futur host Web Snake direct consommera `game-snake-poc-wasm` depuis son propre frontend Vite/TypeScript ; ce frontend n'est pas introduit par la tranche WASM elle-même. Le host Web Snake direct réside sous `Web/game-snake-poc/`. Ce package Vite/TypeScript consomme `game-snake-poc-wasm`, reprend la structure HTML/Sass/TypeScript et le thème Bootstrap/Bootswatch des applications Desk KSP sans dépendre de Tauri, traduit clavier/tactile vers les quatre directions logiques et dessine l'`EngineScene` dans un Canvas. Il reste extérieur au workspace Cargo et ne contient aucune règle de gameplay.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/development/009-SNAKE_WEB_WASM_BUILD.md --> <!-- file: docs/development/009-SNAKE_WEB_WASM_BUILD.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Build WASM du POC Snake Web direct # Build WASM du POC Snake Web direct
@@ -48,6 +48,6 @@ Les fichiers générés restent intégralement hors du dépôt sous `../builds/s
## Frontière avec `0-pre.4` ## Frontière avec `0-pre.4`
`0-pre.3` s'arrête au module WASM et à ses bindings générés. `0-pre.4` ajoutera le frontend Vite/TypeScript qui les consomme, le document HTML Bootstrap 5, le Canvas, le clavier et les contrôles tactiles. `0-pre.3` s'arrête au module WASM et à ses bindings générés. Le frontend introduit en `0-pre.4` sous `Web/game-snake-poc/` les consomme ensuite via Vite/TypeScript ; il porte le document HTML Bootstrap 5, le Canvas, le clavier et les contrôles tactiles.
Le frontend reste responsable de la cadence d'appel à `tick()` et de la traduction des événements navigateur vers `left()`, `right()`, `up()` et `down()`. Le frontend reste responsable de la cadence d'appel à `tick()` et de la traduction des événements navigateur vers `left()`, `right()`, `up()` et `down()`.

View File

@@ -0,0 +1,130 @@
<!-- file: docs/development/010-SNAKE_WEB_FRONTEND.md -->
<!-- version: 1 -->
# Frontend du POC Snake Web direct
## Rôle
`Web/game-snake-poc/` est le premier host navigateur direct du projet. Il reste un package frontend autonome, extérieur au workspace Cargo, et consomme les bindings générés de `game-snake-poc-wasm`.
La séparation est volontaire :
```text
game-snake-poc
game-snake-poc-wasm
↓ wasm-bindgen
Web/game-snake-poc
HTML + Sass/Bootstrap 5 + TypeScript + Canvas
```
Le frontend ne réimplémente aucune règle de Snake. Il traduit uniquement les entrées navigateur, cadence les appels à `tick()` et dessine la scène portable exposée par le bridge WASM.
## Baseline frontend KSP
Le shell reprend volontairement la structure frontend utilisée par les applications Desk KSP :
```text
Web/game-snake-poc/
├── frontend/
│ ├── main.html
│ ├── sass/
│ │ ├── _app.scss
│ │ ├── _bootswatch.scss
│ │ ├── _fontawesome.scss
│ │ ├── _variables.scss
│ │ └── main.scss
│ └── ts/
│ ├── game.ts
│ ├── input.ts
│ └── main.ts
├── package.json
├── tsconfig.json
└── vite.config.ts
```
Les dépendances npm utiles sont alignées sur cette baseline : Bootstrap 5, Font Awesome, `sass-embedded`, TypeScript, Vite et les types nécessaires. Les dépendances propres à Tauri, SimpleBar ou au tracing frontend KSP ne sont pas importées car elles n'ont aucun rôle dans ce host navigateur direct.
Le thème reprend la baseline Bootstrap/Bootswatch Pulse des apps Desk KSP, avec un header, un footer et une carte de contenu adaptés au jeu. Cette réutilisation reste une convention de présentation du POC ; elle ne crée aucune dépendance entre les dépôts KSP et games.sasedev.
## Shell HTML et présentation
Le document `frontend/main.html` fournit :
- un header de statut inspiré du template Desk KSP ;
- un Canvas carré responsive pour le rendu du jeu ;
- les indicateurs de score, longueur et état de chargement ;
- quatre boutons directionnels tactiles ;
- une zone d'erreur de démarrage ;
- un footer léger commun au shell.
Bootstrap 5 et Font Awesome sont installés comme dépendances npm et intégrés au build Vite/Sass. Aucun CDN n'est nécessaire au POC local.
Le Sass spécifique reste limité au shell, au Canvas et au pavé directionnel. Le gameplay ne dépend ni de Bootstrap, ni de Font Awesome, ni du DOM.
## Entrées
Le frontend accepte :
- les flèches du clavier ;
- `WASD` ;
- `ZQSD` ;
- les quatre boutons directionnels tactiles/pointer.
Une entrée appelle uniquement `left()`, `right()`, `up()` ou `down()` sur `SnakeWasmGame`. La simulation continue d'avancer exclusivement via `tick()`.
## Rendu Canvas
Le frontend lit la couleur de fond et les rectangles normalisés exposés par `game-snake-poc-wasm`. Les coordonnées sont multipliées par la taille physique courante du Canvas.
Le Canvas suit sa taille CSS et le `devicePixelRatio` du navigateur. Cette tranche couvre le resize visuel nécessaire au shell ; la gestion complète du lifecycle navigateur reste prévue par `0-pre.5`.
## Build
Depuis la racine du dépôt, construire d'abord l'adapter WASM et ses bindings :
```bash
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown
wasm-bindgen ../builds/sasedev-games/target/wasm32-unknown-unknown/debug/game_snake_poc_wasm.wasm \
--target web \
--out-dir ../builds/sasedev-games/game-snake-poc-web/wasm \
--out-name game_snake_poc_wasm
```
Puis installer et construire le frontend :
```bash
cd Web/game-snake-poc
npm install
npm run build
```
Vite place son cache et le `dist/` sous :
```text
../builds/sasedev-games/game-snake-poc-web/
```
Le dépôt ne reçoit donc ni bindings WASM générés, ni `dist/`, ni cache Vite.
## Smoke navigateur
Après génération du WASM :
```bash
cd Web/game-snake-poc
npm run dev
```
Ouvrir `http://127.0.0.1:1434/main.html` et vérifier :
1. le statut passe à `Prêt` sans erreur ;
2. Snake est visible dans le Canvas ;
3. les flèches clavier déplacent Snake ;
4. `WASD` et `ZQSD` produisent les mêmes directions ;
5. les quatre boutons tactiles/pointer fonctionnent ;
6. le shell KSP-derived et le Canvas restent utilisables sur une largeur mobile et Desktop ;
7. score et longueur restent visibles et évoluent avec l'état WASM.
Le lifecycle complet, les assets, le logging structuré et la provenance runtime appartiennent à `0-pre.5`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md --> <!-- file: docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md -->
<!-- version: 5 --> <!-- version: 6 -->
# Plan v0.3.0 — baseline Snake et premier POC Web direct # Plan v0.3.0 — baseline Snake et premier POC Web direct
@@ -67,9 +67,11 @@ Gate : audits statiques, `cargo check --workspace`, Clippy ciblé, tests de l'ad
### `0-pre.4` — frontend Web/Vite, shell Bootstrap 5 et contrôles ### `0-pre.4` — frontend Web/Vite, shell Bootstrap 5 et contrôles
Créer le frontend Vite/TypeScript avec un document HTML responsive servant de shell au jeu. Utiliser Bootstrap 5 côté frontend pour la mise en page, les états UI et les contrôles tactiles ; conserver le rendu du jeu dans un Canvas et les règles de gameplay dans Rust/WASM. Brancher clavier, boutons directionnels tactiles, chargement/erreur et affichage d'état nécessaires au POC. Créer `Web/game-snake-poc/`, package Vite/TypeScript autonome consommant les bindings `game-snake-poc-wasm` générés hors dépôt. Le frontend reprend le template des applications Desk KSP pour sa structure HTML/Sass/TypeScript et ses dépendances npm pertinentes : Bootstrap 5, thème Bootswatch Pulse, Font Awesome et `sass-embedded`, sans dépendances Tauri. Le rendu reste dans un Canvas et les règles de gameplay restent dans Rust/WASM. Brancher flèches clavier, `WASD`/`ZQSD`, quatre boutons directionnels tactiles/pointer, chargement/erreur, score et longueur.
Gate : build Web par le chemin natif prévu et smoke navigateur couvrant Canvas, clavier, contrôles tactiles et comportement responsive du shell. Livraison candidate : le shell utilise `frontend/main.html`, `frontend/sass/` et `frontend/ts/` sur le modèle KSP ; le Canvas suit sa taille CSS et le `devicePixelRatio`, le frontend cadence `tick()` sans avancer la simulation au moment d'une entrée et Vite écrit `dist/`/cache uniquement sous `../builds/sasedev-games/game-snake-poc-web/`. Aucun CDN, SDL3, Tauri ni duplication de gameplay n'est introduit. Le lifecycle complet, les assets, le logging et la provenance restent réservés à `0-pre.5`.
Gate : audits statiques, build WASM + `wasm-bindgen`, `npm run build` du package Web direct et smoke navigateur couvrant Canvas, clavier, contrôles tactiles/pointer et comportement responsive du shell.
### `0-pre.5` — intégration plateforme complète ### `0-pre.5` — intégration plateforme complète

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/FILE_CONTRACTS.md --> <!-- file: docs/rules/FILE_CONTRACTS.md -->
<!-- version: 7 --> <!-- version: 8 -->
# Contrats des fichiers principaux # Contrats des fichiers principaux
@@ -26,7 +26,8 @@
- `scripts/` contient des audits en lecture seule et des outils du dépôt. - `scripts/` contient des audits en lecture seule et des outils du dépôt.
- `assets/` contient les ressources runtime communes et spécifiques aux jeux ; aucune ressource runtime n'est placée dans une crate Rust. - `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. - `Android/` contient le projet Gradle multi-module et son code Java commun/spécifique.
- `crates/apps/` contient les exécutables de développement et launchers Rust, notamment les runners Desktop par jeu. - `Web/` contient les hosts navigateur directs non-Tauri ; chaque `Web/<game>/` est un package frontend autonome consommant un adapter WASM versionné sous `crates/apps/`.
- `crates/apps/` contient les exécutables de développement et launchers Rust, notamment les runners Desktop par jeu et les adapters WASM.
## Tauri ## Tauri

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_COMMANDS.md --> <!-- file: docs/rules/RULES_COMMANDS.md -->
<!-- version: 11 --> <!-- version: 12 -->
# Règles d'exécution des commandes # Règles d'exécution des commandes
@@ -58,6 +58,10 @@
- **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-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 historique `0.1.0` utilise `scripts/build_reflex_tauri_wasm.py` ; ce script reste une référence de baseline mais ne doit pas être réutilisé comme orchestrateur par un POC `0.3.x`, conformément à `CMD-BUILD-004`. - **CMD-WEB-003** — Le POC Tauri/WASM historique `0.1.0` utilise `scripts/build_reflex_tauri_wasm.py` ; ce script reste une référence de baseline mais ne doit pas être réutilisé comme orchestrateur par un POC `0.3.x`, conformément à `CMD-BUILD-004`.
- **CMD-WEB-004** — Les fichiers produits par `wasm-bindgen` sont générés localement et ne sont pas commités. - **CMD-WEB-004** — Les fichiers produits par `wasm-bindgen` sont générés localement et ne sont pas commités.
- **CMD-WEB-005** — `npm run dev` et `npm run build` du frontend Tauri sont pilotés par les hooks Tauri ; ils ne constituent pas des gates manuelles indépendantes.
- **CMD-WEB-006** — Pour un host Web navigateur direct sous `Web/<game>/`, `npm install`/`npm run build` et le serveur Vite peuvent être exécutés directement comme gates frontend ; ils ne sont pas couplés à Tauri.
- **CMD-WEB-007** — Le build d'un host Web direct qui consomme un adapter `wasm-bindgen` exécute d'abord le build Rust/WASM et la génération des bindings hors dépôt, puis seulement le build TypeScript/Vite.
- **CMD-WEB-008** — Un smoke navigateur manuel d'un host Web direct vérifie au minimum le chargement WASM, le Canvas, les entrées prévues par la tranche et le comportement responsive concerné.
## Git et fichiers générés ## Git et fichiers générés
@@ -66,8 +70,6 @@
- **CMD-GIT-003** — Lorsqu'une archive fournie par l'utilisateur est déclarée comme téléchargement d'un tag du dépôt, cette archive est la baseline autoritaire de ce tag. L'absence de `.git` dans l'archive est normale et ne constitue ni une anomalie ni une validation manquante. - **CMD-GIT-003** — Lorsqu'une archive fournie par l'utilisateur est déclarée comme téléchargement d'un tag du dépôt, cette archive est la baseline autoritaire de ce tag. L'absence de `.git` dans l'archive est normale et ne constitue ni une anomalie ni une validation manquante.
- **CMD-GIT-004** — Les contrôles nécessitant le répertoire `.git` s'appliquent uniquement à un checkout Git local lorsqu'il est effectivement fourni ; sur une archive taggée, on contrôle la cohérence interne des versions et fichiers sans inventer un état Git inaccessible. - **CMD-GIT-004** — Les contrôles nécessitant le répertoire `.git` s'appliquent uniquement à un checkout Git local lorsqu'il est effectivement fourni ; sur une archive taggée, on contrôle la cohérence interne des versions et fichiers sans inventer un état Git inaccessible.
- **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.
## Beta et packaging ## Beta et packaging
- **CMD-BETA-001** — À l'entrée en beta, `scripts/audit_distribution_layout.py` vérifie les frontières statiques nécessaires aux runners et packagings supportés. - **CMD-BETA-001** — À l'entrée en beta, `scripts/audit_distribution_layout.py` vérifie les frontières statiques nécessaires aux runners et packagings supportés.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_PROJECT.md --> <!-- file: docs/rules/RULES_PROJECT.md -->
<!-- version: 12 --> <!-- version: 13 -->
# Règles spécifiques games.sasedev # Règles spécifiques games.sasedev
@@ -13,6 +13,7 @@
- **GAME-WS-006** — Les dépendances tierces communes sont centralisées sous `[workspace.dependencies]` et consommées avec `workspace = true` lorsqu'elles sont partagées. - **GAME-WS-006** — Les dépendances tierces communes sont centralisées sous `[workspace.dependencies]` et consommées avec `workspace = true` lorsqu'elles sont partagées.
- **GAME-WS-007** — Un jeu est prioritairement une crate `lib`; lorsquun lancement Desktop est nécessaire, une crate `bin` séparée sous `crates/apps/` dépend de cette lib et ne duplique pas son gameplay. - **GAME-WS-007** — Un jeu est prioritairement une crate `lib`; lorsquun lancement Desktop est nécessaire, une crate `bin` séparée sous `crates/apps/` dépend de cette lib et ne duplique pas son gameplay.
- **GAME-WS-008** — Les runners Desktop sont nommés `<game>-desktop` et restent indépendants des frontends Android. - **GAME-WS-008** — Les runners Desktop sont nommés `<game>-desktop` et restent indépendants des frontends Android.
- **GAME-WS-009** — Les hosts Web navigateur directs résident sous `Web/<game>/`, hors du workspace Cargo ; ils consomment un adapter WASM dédié et ne contiennent ni crate Rust ni règle de gameplay.
## Générations du moteur ## Générations du moteur
@@ -83,6 +84,7 @@
- **GAME-PLATFORM-018** — SDL3 reste le backend natif de référence du POC, mais l'architecture de jeu ne doit pas assimiler une plateforme à SDL ni empêcher un adapter différent lorsque la plateforme l'exige. - **GAME-PLATFORM-018** — SDL3 reste le backend natif de référence du POC, mais l'architecture de jeu ne doit pas assimiler une plateforme à SDL ni empêcher un adapter différent lorsque la plateforme l'exige.
- **GAME-PLATFORM-019** — Une plateforme réservée n'est ni implémentée ni planifiée tant qu'une ligne ROADMAP ou un delta ne l'engage explicitement. - **GAME-PLATFORM-019** — Une plateforme réservée n'est ni implémentée ni planifiée tant qu'une ligne ROADMAP ou un delta ne l'engage explicitement.
- **GAME-PLATFORM-020** — Un host Web navigateur possède un shell HTML/CSS/TypeScript explicite autour du Canvas/WASM. Le framework de présentation éventuel reste une dépendance frontend et ne fuit ni dans la crate de gameplay ni dans le bridge WASM ; pour le premier POC Snake `0.3.0`, Bootstrap 5 est la baseline de présentation retenue. - **GAME-PLATFORM-020** — Un host Web navigateur possède un shell HTML/CSS/TypeScript explicite autour du Canvas/WASM. Le framework de présentation éventuel reste une dépendance frontend et ne fuit ni dans la crate de gameplay ni dans le bridge WASM ; pour le premier POC Snake `0.3.0`, Bootstrap 5 est la baseline de présentation retenue.
- **GAME-PLATFORM-021** — Les dépendances de présentation nécessaires au runtime Web versionné sont déclarées dans le package frontend et intégrées au build Vite ; un CDN externe n'est pas requis pour exécuter le POC local et ne devient une dépendance de distribution qu'après décision explicite.
## Version d'en-tête des fichiers ## Version d'en-tête des fichiers

View File

@@ -1,5 +1,5 @@
<!-- file: docs/rules/RULES_VALIDATION_MATRIX.md --> <!-- file: docs/rules/RULES_VALIDATION_MATRIX.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Matrice normative des commandes et validations # Matrice normative des commandes et validations
@@ -13,30 +13,33 @@ Les commandes ciblées restent la norme pendant l'implémentation ; les gates wo
## Matrice ## Matrice
| ID | Commande / action | Dépend de | Déclencheur principal | Phase minimale typique | | ID | Commande / action | Dépend de | Déclencheur principal | Phase minimale typique |
|-----------|------------------------------------------------------------------------------------|-------------------------------------|--------------------------------------------|------------------------| |-----------|------------------------------------------------------------------------------------|----------------------------------------|--------------------------------------------|------------------------|
| `CMD-001` | `cargo fmt --all` | — | source Rust modifié | `pre` | | `CMD-001` | `cargo fmt --all` | — | source Rust modifié | `pre` |
| `CMD-002` | `cargo fmt --all -- --check` | `CMD-001` si formatage requis | source Rust modifié | `pre` | | `CMD-002` | `cargo fmt --all -- --check` | `CMD-001` si formatage requis | source Rust modifié | `pre` |
| `CMD-010` | `python3 scripts/audit_rust_workspace_rules.py` | — | Rust/workspace/règles Rust | `pre` | | `CMD-010` | `python3 scripts/audit_rust_workspace_rules.py` | — | Rust/workspace/règles Rust | `pre` |
| `CMD-011` | `python3 scripts/audit_markdown_tables.py ...` | — | Markdown/règles/docs | `pre` | | `CMD-011` | `python3 scripts/audit_markdown_tables.py ...` | — | Markdown/règles/docs | `pre` |
| `CMD-012` | `python3 scripts/audit_distribution_layout.py` | — | layout/build/distribution | `pre` | | `CMD-012` | `python3 scripts/audit_distribution_layout.py` | — | layout/build/distribution | `pre` |
| `CMD-020` | `cargo check -p <crate>` | `CMD-002`, `CMD-010` | crate Rust ciblée | `pre` | | `CMD-020` | `cargo check -p <crate>` | `CMD-002`, `CMD-010` | crate Rust ciblée | `pre` |
| `CMD-021` | `cargo test -p <crate> --all-targets --all-features` | `CMD-020` | comportement/API crate | `pre` | | `CMD-021` | `cargo test -p <crate> --all-targets --all-features` | `CMD-020` | comportement/API crate | `pre` |
| `CMD-022` | tests des crates consommatrices impactées | `CMD-021` | API publique/contrat partagé modifié | `pre` | | `CMD-022` | tests des crates consommatrices impactées | `CMD-021` | API publique/contrat partagé modifié | `pre` |
| `CMD-023` | `cargo check --workspace` | audits applicables | changement transverse / gate globale | selon portée | | `CMD-023` | `cargo check --workspace` | audits applicables | changement transverse / gate globale | selon portée |
| `CMD-024` | `cargo clippy --workspace --all-targets --all-features -- -D warnings` | `CMD-023` | gate complète | alpha/beta/RC | | `CMD-024` | `cargo clippy --workspace --all-targets --all-features -- -D warnings` | `CMD-023` | gate complète | alpha/beta/RC |
| `CMD-025` | `cargo test --workspace --all-targets --all-features` | `CMD-024` | changement transverse / frontière de phase | beta/RC | | `CMD-025` | `cargo test --workspace --all-targets --all-features` | `CMD-024` | changement transverse / frontière de phase | beta/RC |
| `CMD-030` | build Desktop `--release` ciblé | gates Rust applicables | runner/distribution Desktop touché | beta | | `CMD-030` | build Desktop `--release` ciblé | gates Rust applicables | runner/distribution Desktop touché | beta |
| `CMD-031` | smoke Desktop release | `CMD-030` | runtime Desktop touché | beta | | `CMD-031` | smoke Desktop release | `CMD-030` | runtime Desktop touché | beta |
| `CMD-040` | `cargo tauri build` | gates Rust/frontend applicables | périmètre Tauri touché | beta | | `CMD-040` | `cargo tauri build` | gates Rust/frontend applicables | périmètre Tauri touché | beta |
| `CMD-041` | smoke Tauri release | `CMD-040` | runtime Tauri touché | beta | | `CMD-041` | smoke Tauri release | `CMD-040` | runtime Tauri touché | beta |
| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta | | `CMD-042` | build Rust `wasm32-unknown-unknown` + `wasm-bindgen --target web` | gates Rust de l'adapter | adapter WASM/Web direct touché | `pre` |
| `CMD-051` | Gradle `assembleDebug` application ciblée | `CMD-050` lorsque Rust natif change | Android/app/manifest/Java touché | pre/beta | | `CMD-043` | `npm run build` depuis `Web/<game>/` | `CMD-042` si le frontend consomme WASM | frontend Web direct touché | `pre` |
| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta | | `CMD-044` | smoke navigateur du host Web direct | `CMD-043` | Canvas/input/responsive Web touchés | `pre`/beta |
| `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC | | `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta |
| `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance | | `CMD-051` | Gradle `assembleDebug` application ciblée | `CMD-050` lorsque Rust natif change | Android/app/manifest/Java touché | pre/beta |
| `CMD-061` | `cargo clean` | décision explicite de nettoyage | accumulation disque / jalon de cycle | maintenance | | `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta |
| `CMD-062` | `cargo clean -p <package>` ou nettoyage par `--release` / `--profile` / `--target` | — | nettoyage ciblé suffisant | maintenance | | `CMD-053` | install + smoke appareil réel | `CMD-051` | Android concerné | beta/RC |
| `CMD-060` | `cargo clean --dry-run --verbose` | — | contrôle disque / préparation nettoyage | maintenance |
| `CMD-061` | `cargo clean` | décision explicite de nettoyage | accumulation disque / jalon de cycle | maintenance |
| `CMD-062` | `cargo clean -p <package>` ou nettoyage par `--release` / `--profile` / `--target` | — | nettoyage ciblé suffisant | maintenance |
## Dépendances entre crates ## Dépendances entre crates

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/001-VALIDATION_GATES.md --> <!-- file: docs/validation/001-VALIDATION_GATES.md -->
<!-- version: 5 --> <!-- version: 6 -->
# Gates de validation # Gates de validation
@@ -24,7 +24,7 @@ Les audits et gates de compilation restent ensuite :
```bash ```bash
python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas history python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
cargo check --workspace cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings cargo clippy --workspace --all-targets --all-features -- -D warnings
``` ```
@@ -58,4 +58,17 @@ Si une commande échoue, ne pas avancer de prerelease. Produire un correctif du
Le build Android n'est pas encore une gate : le projet Gradle exécutable et l'intégration SDL3/NDK restent planifiés pour une prerelease dédiée. Dès leur introduction, les tâches ciblées par module sont documentées avant d'être rendues obligatoires. Le build Android n'est pas encore une gate : le projet Gradle exécutable et l'intégration SDL3/NDK restent planifiés pour une prerelease dédiée. Dès leur introduction, les tâches ciblées par module sont documentées avant d'être rendues obligatoires.
La cible Web suit la même règle : aucune gate Web n'est inventée avant l'existence de son build réel. Le host navigateur direct Snake possède désormais une gate réelle. Lorsqu'il est touché, construire d'abord l'adapter WASM et ses bindings hors dépôt, puis le 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
```
Le smoke direct utilise ensuite `npm run dev` et `http://127.0.0.1:1434/main.html`. Les frontends Tauri restent pilotés par leurs hooks Tauri et ne reprennent pas cette gate manuelle.

43
history/0.3.0/0-pre.3.md Normal file
View File

@@ -0,0 +1,43 @@
<!-- file: history/0.3.0/0-pre.3.md -->
<!-- version: 1 -->
# Historique 0.3.0-0-pre.3
## Statut
Adapter Snake WASM validé par l'utilisateur le 2026-09-20 avant ouverture de `0-pre.4`.
## Validation utilisateur
Les gates suivantes ont passé sur `0.3.0-0-pre.3` :
```text
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy -p game-snake-poc-wasm --all-targets --all-features -- -D warnings: clean
cargo test -p game-snake-poc-wasm: 2 passed, 0 failed
cargo build -p game-snake-poc-wasm --target wasm32-unknown-unknown: clean
wasm-bindgen --target web: clean
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 178 file(s))
Distribution layout audit: clean (20 required path(s), 1 forbidden path(s) absent)
```
Le graphe de dépendances validé de `game-snake-poc-wasm` contient uniquement les dépendances fonctionnelles attendues :
```text
game-snake-poc-wasm
├── engine-v1-common
├── game-snake-poc
│ └── engine-v1-common
└── wasm-bindgen
```
Aucune dépendance SDL3, Tauri ou `engine-v1-platform-api` n'est présente dans l'adapter WASM Snake.
## Suite
`0-pre.4` introduit le host navigateur direct Vite/TypeScript avec shell HTML Bootstrap 5, Canvas et contrôles clavier/tactiles.

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# file: scripts/audit_distribution_layout.py # file: scripts/audit_distribution_layout.py
# version: 2 # version: 3
"""Audit the static distribution layout expected by the 0.1.0 beta.""" """Audit the static distribution layout expected by supported POCs."""
from __future__ import annotations from __future__ import annotations
@@ -30,6 +30,12 @@ REQUIRED_PATHS = (
"crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts", "crates/apps/game-reflex-poc-tauri/frontend/ts/main.ts",
"crates/apps/game-reflex-poc-wasm/Cargo.toml", "crates/apps/game-reflex-poc-wasm/Cargo.toml",
"crates/apps/game-snake-poc-wasm/Cargo.toml", "crates/apps/game-snake-poc-wasm/Cargo.toml",
"Web/game-snake-poc/package.json",
"Web/game-snake-poc/tsconfig.json",
"Web/game-snake-poc/vite.config.ts",
"Web/game-snake-poc/frontend/main.html",
"Web/game-snake-poc/frontend/sass/main.scss",
"Web/game-snake-poc/frontend/ts/main.ts",
"Android/game-reflex-poc/build.gradle", "Android/game-reflex-poc/build.gradle",
"Android/game-snake-poc/build.gradle", "Android/game-snake-poc/build.gradle",
"scripts/build_android_rust.py", "scripts/build_android_rust.py",