diff --git a/Cargo.toml b/Cargo.toml index 794f5fb..b7804cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 62 +# version: 63 [workspace] resolver = "3" @@ -20,7 +20,7 @@ members = [ ] [workspace.package] -version = "0.3.0-0-pre.3" +version = "0.3.0-0-pre.4" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/games" diff --git a/README.md b/README.md index 8d6e6e3..286eb5d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - + # games.sasedev @@ -14,6 +14,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale - assets hors des crates sous `assets/` ; - `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 ; +- hosts navigateur directs sous `Web/`, avec frontend Vite/TypeScript séparé des adapters Rust/WASM ; - 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 ; - 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 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. diff --git a/Web/game-snake-poc/frontend/main.html b/Web/game-snake-poc/frontend/main.html new file mode 100644 index 0000000..7b3737d --- /dev/null +++ b/Web/game-snake-poc/frontend/main.html @@ -0,0 +1,106 @@ + + + + + + + + + + + Snake POC — Web direct + + + +
+ +
+ +
+
+
+
+
+
+
+

Snake

+

Gameplay Rust portable, adapter WebAssembly et rendu Canvas piloté par TypeScript.

+
+
+ Score : 0 + Longueur : 0 +
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+ Contrôles + clavier / tactile +
+
+

Utilisez les flèches, WASD, ZQSD ou les boutons directionnels.

+
+ + + + +
+
+ Les entrées ne font pas avancer la simulation : elles sont consommées au prochain tick() WASM. +
+
+
+
+
+ + +
+
+
+
+
+ + + + + + diff --git a/Web/game-snake-poc/frontend/sass/_app.scss b/Web/game-snake-poc/frontend/sass/_app.scss new file mode 100644 index 0000000..9a6462e --- /dev/null +++ b/Web/game-snake-poc/frontend/sass/_app.scss @@ -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)); + } +} diff --git a/Web/game-snake-poc/frontend/sass/_bootswatch.scss b/Web/game-snake-poc/frontend/sass/_bootswatch.scss new file mode 100644 index 0000000..9ad7048 --- /dev/null +++ b/Web/game-snake-poc/frontend/sass/_bootswatch.scss @@ -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; + } + } +} diff --git a/Web/game-snake-poc/frontend/sass/_fontawesome.scss b/Web/game-snake-poc/frontend/sass/_fontawesome.scss new file mode 100644 index 0000000..8dc8fed --- /dev/null +++ b/Web/game-snake-poc/frontend/sass/_fontawesome.scss @@ -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; diff --git a/Web/game-snake-poc/frontend/sass/_variables.scss b/Web/game-snake-poc/frontend/sass/_variables.scss new file mode 100644 index 0000000..e9efaaf --- /dev/null +++ b/Web/game-snake-poc/frontend/sass/_variables.scss @@ -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; diff --git a/Web/game-snake-poc/frontend/sass/main.scss b/Web/game-snake-poc/frontend/sass/main.scss new file mode 100644 index 0000000..335f705 --- /dev/null +++ b/Web/game-snake-poc/frontend/sass/main.scss @@ -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"; diff --git a/Web/game-snake-poc/frontend/ts/game.ts b/Web/game-snake-poc/frontend/ts/game.ts new file mode 100644 index 0000000..1e7ff33 --- /dev/null +++ b/Web/game-snake-poc/frontend/ts/game.ts @@ -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 { + 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); + }, + }; +} diff --git a/Web/game-snake-poc/frontend/ts/input.ts b/Web/game-snake-poc/frontend/ts/input.ts new file mode 100644 index 0000000..16a85d4 --- /dev/null +++ b/Web/game-snake-poc/frontend/ts/input.ts @@ -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> = { + 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); + }); + } +} diff --git a/Web/game-snake-poc/frontend/ts/main.ts b/Web/game-snake-poc/frontend/ts/main.ts new file mode 100644 index 0000000..9f41f79 --- /dev/null +++ b/Web/game-snake-poc/frontend/ts/main.ts @@ -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(selector: string): T { + const element = document.querySelector(selector); + if (element === null) { + throw new Error(`Élément frontend requis absent : ${selector}`); + } + return element; +} + +async function main(): Promise { + const canvas = requireElement("#game"); + const score = requireElement("#score"); + const length = requireElement("#length"); + const status = requireElement("#runtime-status"); + const error = requireElement("#startup-error"); + const buttons = Array.from(document.querySelectorAll("[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("#runtime-status"); + const error = document.querySelector("#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; + } +}); diff --git a/Web/game-snake-poc/package.json b/Web/game-snake-poc/package.json new file mode 100644 index 0000000..5f16409 --- /dev/null +++ b/Web/game-snake-poc/package.json @@ -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" + } +} diff --git a/Web/game-snake-poc/tsconfig.json b/Web/game-snake-poc/tsconfig.json new file mode 100644 index 0000000..63fbb6c --- /dev/null +++ b/Web/game-snake-poc/tsconfig.json @@ -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" + ] +} diff --git a/Web/game-snake-poc/vite.config.ts b/Web/game-snake-poc/vite.config.ts new file mode 100644 index 0000000..0837b12 --- /dev/null +++ b/Web/game-snake-poc/vite.config.ts @@ -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, + }, +}); diff --git a/deltas/0.3.0/0-pre.4.md b/deltas/0.3.0/0-pre.4.md new file mode 100644 index 0000000..b638728 --- /dev/null +++ b/deltas/0.3.0/0-pre.4.md @@ -0,0 +1,60 @@ + + + +# 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. diff --git a/docs/000-README.md b/docs/000-README.md index 3efe0ef..1076a25 100644 --- a/docs/000-README.md +++ b/docs/000-README.md @@ -1,5 +1,5 @@ - + # 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/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/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é diff --git a/docs/architecture/001-WORKSPACE_ARCHITECTURE.md b/docs/architecture/001-WORKSPACE_ARCHITECTURE.md index 8259cef..2927e54 100644 --- a/docs/architecture/001-WORKSPACE_ARCHITECTURE.md +++ b/docs/architecture/001-WORKSPACE_ARCHITECTURE.md @@ -1,5 +1,5 @@ - + # Architecture du workspace @@ -32,6 +32,8 @@ games.sasedev/ │ ├── common/ │ ├── game-reflex-poc/ │ └── game-snake-poc/ +├── Web/ +│ └── game-snake-poc/ ├── docs/ ├── deltas/ ├── 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. -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. diff --git a/docs/development/009-SNAKE_WEB_WASM_BUILD.md b/docs/development/009-SNAKE_WEB_WASM_BUILD.md index 3f5b8c0..3f2a61a 100644 --- a/docs/development/009-SNAKE_WEB_WASM_BUILD.md +++ b/docs/development/009-SNAKE_WEB_WASM_BUILD.md @@ -1,5 +1,5 @@ - + # 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` -`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()`. diff --git a/docs/development/010-SNAKE_WEB_FRONTEND.md b/docs/development/010-SNAKE_WEB_FRONTEND.md new file mode 100644 index 0000000..a53c435 --- /dev/null +++ b/docs/development/010-SNAKE_WEB_FRONTEND.md @@ -0,0 +1,130 @@ + + + +# 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`. diff --git a/docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md b/docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md index 365f05f..db89042 100644 --- a/docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md +++ b/docs/plans/001-V0_3_0_WEB_SNAKE_POC_PLAN.md @@ -1,5 +1,5 @@ - + # 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 -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 diff --git a/docs/rules/FILE_CONTRACTS.md b/docs/rules/FILE_CONTRACTS.md index 69ae571..a7560c9 100644 --- a/docs/rules/FILE_CONTRACTS.md +++ b/docs/rules/FILE_CONTRACTS.md @@ -1,5 +1,5 @@ - + # Contrats des fichiers principaux @@ -26,7 +26,8 @@ - `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. - `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//` 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 diff --git a/docs/rules/RULES_COMMANDS.md b/docs/rules/RULES_COMMANDS.md index e6d35c4..f6f8103 100644 --- a/docs/rules/RULES_COMMANDS.md +++ b/docs/rules/RULES_COMMANDS.md @@ -1,5 +1,5 @@ - + # 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-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-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//`, `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 @@ -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-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 - **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. diff --git a/docs/rules/RULES_PROJECT.md b/docs/rules/RULES_PROJECT.md index 8af3512..5719ea3 100644 --- a/docs/rules/RULES_PROJECT.md +++ b/docs/rules/RULES_PROJECT.md @@ -1,5 +1,5 @@ - + # 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-007** — Un jeu est prioritairement une crate `lib`; lorsqu’un 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 `-desktop` et restent indépendants des frontends Android. +- **GAME-WS-009** — Les hosts Web navigateur directs résident sous `Web//`, 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 @@ -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-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-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 diff --git a/docs/rules/RULES_VALIDATION_MATRIX.md b/docs/rules/RULES_VALIDATION_MATRIX.md index 2ad2f43..3916090 100644 --- a/docs/rules/RULES_VALIDATION_MATRIX.md +++ b/docs/rules/RULES_VALIDATION_MATRIX.md @@ -1,5 +1,5 @@ - + # 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 -| ID | Commande / action | Dépend de | Déclencheur principal | Phase minimale typique | -|-----------|------------------------------------------------------------------------------------|-------------------------------------|--------------------------------------------|------------------------| -| `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-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-012` | `python3 scripts/audit_distribution_layout.py` | — | layout/build/distribution | `pre` | -| `CMD-020` | `cargo check -p ` | `CMD-002`, `CMD-010` | crate Rust ciblée | `pre` | -| `CMD-021` | `cargo test -p --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-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-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-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-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-051` | Gradle `assembleDebug` application ciblée | `CMD-050` lorsque Rust natif change | Android/app/manifest/Java touché | pre/beta | -| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta | -| `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 ` ou nettoyage par `--release` / `--profile` / `--target` | — | nettoyage ciblé suffisant | maintenance | +| ID | Commande / action | Dépend de | Déclencheur principal | Phase minimale typique | +|-----------|------------------------------------------------------------------------------------|----------------------------------------|--------------------------------------------|------------------------| +| `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-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-012` | `python3 scripts/audit_distribution_layout.py` | — | layout/build/distribution | `pre` | +| `CMD-020` | `cargo check -p ` | `CMD-002`, `CMD-010` | crate Rust ciblée | `pre` | +| `CMD-021` | `cargo test -p --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-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-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-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-041` | smoke Tauri release | `CMD-040` | runtime Tauri touché | beta | +| `CMD-042` | build Rust `wasm32-unknown-unknown` + `wasm-bindgen --target web` | gates Rust de l'adapter | adapter WASM/Web direct touché | `pre` | +| `CMD-043` | `npm run build` depuis `Web//` | `CMD-042` si le frontend consomme WASM | frontend Web direct touché | `pre` | +| `CMD-044` | smoke navigateur du host Web direct | `CMD-043` | Canvas/input/responsive Web touchés | `pre`/beta | +| `CMD-050` | build Rust Android ABI ciblé | gates Rust applicables | Android/JNI/backend natif touché | pre/beta | +| `CMD-051` | Gradle `assembleDebug` application ciblée | `CMD-050` lorsque Rust natif change | Android/app/manifest/Java touché | pre/beta | +| `CMD-052` | install + smoke AVD | `CMD-051` | Android concerné | beta | +| `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 ` ou nettoyage par `--release` / `--profile` / `--target` | — | nettoyage ciblé suffisant | maintenance | ## Dépendances entre crates diff --git a/docs/validation/001-VALIDATION_GATES.md b/docs/validation/001-VALIDATION_GATES.md index 695ec84..04edc40 100644 --- a/docs/validation/001-VALIDATION_GATES.md +++ b/docs/validation/001-VALIDATION_GATES.md @@ -1,5 +1,5 @@ - + # Gates de validation @@ -24,7 +24,7 @@ Les audits et gates de compilation restent ensuite : ```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 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 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. -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. diff --git a/history/0.3.0/0-pre.3.md b/history/0.3.0/0-pre.3.md new file mode 100644 index 0000000..bef26da --- /dev/null +++ b/history/0.3.0/0-pre.3.md @@ -0,0 +1,43 @@ + + + +# 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. diff --git a/scripts/audit_distribution_layout.py b/scripts/audit_distribution_layout.py index 28a37eb..2b3a6d3 100644 --- a/scripts/audit_distribution_layout.py +++ b/scripts/audit_distribution_layout.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # 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 @@ -30,6 +30,12 @@ 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", + "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-snake-poc/build.gradle", "scripts/build_android_rust.py",