0.3.1-0-pre.4

This commit is contained in:
2026-09-21 01:21:33 +02:00
parent 4f677dbc77
commit a36b666d22
14 changed files with 270 additions and 29 deletions

View File

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

View File

@@ -1,5 +1,5 @@
<!-- file: README.md -->
<!-- version: 46 -->
<!-- version: 47 -->
# games.sasedev
@@ -27,7 +27,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
Version stable de référence : `0.3.0`.
Version de développement courante : `0.3.1-0-pre.3`.
Version de développement courante : `0.3.1-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.

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md -->
<!-- version: 22 -->
<!-- version: 23 -->
# Roadmap
@@ -47,7 +47,7 @@ Le détail historique des prereleases `0.1.0-*` reste dans `deltas/0.1.0/` et `h
## Série 0.3.x — trajectoire actuelle après 0.3.0
- ( ) `0.3.1` — terminer Snake Tauri Android comme POC de référence réutilisant la baseline Web/WASM ; la production Android reste orientée SDL3 natif.
- ( ) `0.3.2` évaluer Tauri Desktop + Snake seulement si la WebView apporte un avantage explicite, notamment une monétisation publicitaire viable ; factoriser uniquement entre consommateurs réellement retenus.
- (d) `0.3.2` — Tauri Desktop + Snake est différé tant quune solution de monétisation desktop/WebView, notamment vidéo récompensée, nest pas démontrée techniquement et contractuellement ; ne pas créer cette distribution seulement pour réutiliser le host Web.
- ( ) `0.3.3` — Android SDL natif multi-ABI avec Cargo/Gradle natifs, sans script Python de build.
- ( ) `0.3.4` — API de transport realtime + WebSocket/tokio-tungstenite baseline, sans serveur Uroburas Mode 3.
- ( ) `0.3.5` — POC WebTransport/QUIC sur le même protocole, avec comparaison mesurée et fallback WebSocket.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/apps/game-snake-poc-tauri/README.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# game-snake-poc-tauri
@@ -31,3 +31,13 @@ Les hooks `beforeDevCommand` et `beforeBuildCommand` de Tauri possèdent le buil
Le host ne modifie pas le projet Android généré par Tauri. Pour `0.3.1`, la chaîne Tauri 2.11.x est conservée telle que générée : Gradle `8.14.3` et Android Gradle Plugin `8.11.0`. Le JDK Android de référence du projet est JDK 17, qui correspond au minimum et à la valeur par défaut documentés par AGP 8.11. Le Java 25 global et le JBR 25 de l'Android Studio actuellement installé ne doivent pas exécuter ce wrapper.
Voir [`USAGE.md`](USAGE.md) pour l'initialisation locale et les smokes Android.
## Lifecycle et Back Android
Le host suspend explicitement sa boucle `requestAnimationFrame` lorsque la WebView devient cachée et la reprend en réinitialisant son horloge locale. Une reprise après background ne doit donc pas simuler le temps passé hors écran. Resize et rotation passent par un `ResizeObserver` sur la zone de jeu et réévaluent aussi la provenance device.
Le POC reste mono-page et nenregistre volontairement aucun handler Tauri `onBackButtonPress`. La politique `EngineGame::quit_requested` de Snake est actuellement la politique moteur par défaut `Exit`, cohérente avec le Back Android/Tauri naturel. Un futur jeu retournant `Continue` devra introduire une intégration Back explicite au lieu de modifier ce POC silencieusement.
## Limite ergonomique connue
Le D-pad tactile reste sous le Canvas dans ce POC et peut nécessiter un scroll sur petit écran. Des contrôles superposés ou des gestes swipe seraient techniquement possibles, mais ne sont pas introduits dans `0.3.1` : ils ne changent pas la conclusion darchitecture et appartiendraient à une UX produit réelle.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/apps/game-snake-poc-tauri/USAGE.md -->
<!-- version: 5 -->
<!-- version: 6 -->
# Utilisation — Snake Tauri Android
@@ -64,3 +64,16 @@ Ne pas utiliser `npm run dev` ou `npm run build` comme gate manuelle : ces scrip
## Positionnement
`game-snake-poc-tauri` est un POC de référence. Il ne doit pas être utilisé comme base automatique des futurs jeux Android ; la cible Android productive reste SDL3 natif tant quun besoin concret ne justifie pas la WebView Tauri.
## Smoke lifecycle `0-pre.4`
Pendant `cargo tauri android dev` :
1. lancer une partie et effectuer au moins une entrée tactile ;
2. mettre lapplication en arrière-plan puis la ramener au premier plan ;
3. vérifier que le serpent reprend sans accélération ni rattrapage de la durée passée en background ;
4. faire pivoter/redimensionner lémulateur si disponible et vérifier que le Canvas reste rendu ;
5. utiliser Back Android : pour Snake, lapplication doit suivre le comportement système de sortie, sans navigation Web interne ni interception spécifique ;
6. vérifier dans logcat les traces `runtime paused`, `runtime resumed` et les événements `visibility hidden/visible`.
Le D-pad placé sous le Canvas est une limite ergonomique connue du POC de référence et nest pas un critère bloquant de cette gate.

View File

@@ -1,5 +1,5 @@
<!-- file: crates/apps/game-snake-poc-tauri/frontend/main.html -->
<!-- version: 1 -->
<!-- version: 2 -->
<!doctype html>
<html lang="fr">
<head>
@@ -23,7 +23,7 @@
<output id="score">Score : 0</output>
<output id="length">Longueur : 0</output>
</div>
<div class="game-stage">
<div id="game-stage" class="game-stage">
<canvas id="game" width="360" height="600" tabindex="0" aria-label="Zone de jeu Snake"></canvas>
</div>
</section>

View File

@@ -1,5 +1,5 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/game.ts
// version: 1
// version: 2
import init, { SnakeWasmGame } from "@snake-wasm";
import type { RuntimeDescriptor } from "./bridge";
@@ -13,6 +13,8 @@ export type SnakeDirection = "left" | "right" | "up" | "down";
export interface SnakeTauriSession {
queueDirection(direction: SnakeDirection, source: SnakeInputSource): void;
pause(): void;
resume(): void;
renderNow(): void;
refreshDeviceClass(): void;
dispose(): void;
@@ -92,6 +94,7 @@ export async function startGame(
let previous = performance.now();
let accumulator = 0;
let animationFrame: number | null = null;
let running = false;
let disposed = false;
function renderNow(): void {
@@ -101,8 +104,16 @@ export async function startGame(
render(game, canvas, renderingContext, score, length);
}
function schedule(): void {
if (!running || disposed || animationFrame !== null) {
return;
}
animationFrame = window.requestAnimationFrame(frame);
}
function frame(now: number): void {
if (disposed) {
animationFrame = null;
if (!running || disposed) {
return;
}
const elapsed = Math.min(Math.max(0, now - previous), MAX_FRAME_ELAPSED_MILLIS);
@@ -113,11 +124,35 @@ export async function startGame(
accumulator -= FRAME_MILLIS;
}
renderNow();
animationFrame = window.requestAnimationFrame(frame);
schedule();
}
function pause(): void {
if (!running || disposed) {
return;
}
running = false;
accumulator = 0;
if (animationFrame !== null) {
window.cancelAnimationFrame(animationFrame);
animationFrame = null;
}
frontendInfo("Snake Tauri runtime paused");
}
function resume(): void {
if (running || disposed) {
return;
}
previous = performance.now();
accumulator = 0;
running = true;
schedule();
frontendInfo("Snake Tauri runtime resumed");
}
renderNow();
animationFrame = window.requestAnimationFrame(frame);
resume();
return {
queueDirection(direction: SnakeDirection, source: SnakeInputSource): void {
if (disposed) {
@@ -126,6 +161,8 @@ export async function startGame(
provenance.recordInput(source);
queueDirection(game, direction);
},
pause,
resume,
renderNow,
refreshDeviceClass(): void {
if (!disposed) {
@@ -136,12 +173,10 @@ export async function startGame(
if (disposed) {
return;
}
pause();
disposed = true;
if (animationFrame !== null) {
window.cancelAnimationFrame(animationFrame);
animationFrame = null;
}
game.free();
frontendInfo("Snake Tauri runtime disposed");
},
};
}

View File

@@ -0,0 +1,60 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/lifecycle.ts
// version: 1
import type { SnakeTauriSession } from "./game";
import { frontendInfo } from "./logging";
export interface SnakeTauriLifecycleBinding {
dispose(): void;
}
export function bindTauriLifecycle(session: SnakeTauriSession, stage: HTMLElement): SnakeTauriLifecycleBinding {
const resizeObserver = new ResizeObserver(() => {
session.refreshDeviceClass();
session.renderNow();
});
function synchronizeVisibility(): void {
if (document.visibilityState === "hidden") {
session.pause();
frontendInfo("Snake Tauri lifecycle: visibility hidden");
return;
}
session.resume();
session.refreshDeviceClass();
session.renderNow();
frontendInfo("Snake Tauri lifecycle: visibility visible");
}
function pageHide(event: PageTransitionEvent): void {
if (event.persisted) {
session.pause();
} else {
session.dispose();
}
frontendInfo(`Snake Tauri lifecycle: page hide persisted=${event.persisted}`);
}
function pageShow(event: PageTransitionEvent): void {
session.resume();
session.refreshDeviceClass();
session.renderNow();
frontendInfo(`Snake Tauri lifecycle: page show persisted=${event.persisted}`);
}
resizeObserver.observe(stage);
document.addEventListener("visibilitychange", synchronizeVisibility);
window.addEventListener("pagehide", pageHide);
window.addEventListener("pageshow", pageShow);
synchronizeVisibility();
return {
dispose(): void {
resizeObserver.disconnect();
document.removeEventListener("visibilitychange", synchronizeVisibility);
window.removeEventListener("pagehide", pageHide);
window.removeEventListener("pageshow", pageShow);
session.dispose();
},
};
}

View File

@@ -1,11 +1,12 @@
// file: crates/apps/game-snake-poc-tauri/frontend/ts/main.ts
// version: 2
// version: 3
import "../css/main.css";
import { loadSnakeTauriAssets } from "./assets";
import { getRuntimeDescriptor, reportFrontendReady } from "./bridge";
import { startGame } from "./game";
import { bindInputs } from "./input";
import { bindTauriLifecycle } from "./lifecycle";
import { frontendError, frontendInfo, initializeFrontendTracing } from "./logging";
function requiredElement<T extends HTMLElement>(identifier: string): T {
@@ -21,6 +22,7 @@ async function bootstrap(): Promise<void> {
frontendInfo("Snake Tauri frontend bootstrap started");
const descriptor = await getRuntimeDescriptor();
const canvas = requiredElement<HTMLCanvasElement>("game");
const stage = requiredElement<HTMLElement>("game-stage");
const score = requiredElement<HTMLOutputElement>("score");
const length = requiredElement<HTMLOutputElement>("length");
const provenance = requiredElement<HTMLOutputElement>("runtime-provenance");
@@ -37,11 +39,7 @@ async function bootstrap(): Promise<void> {
status.textContent = "Chargement WASM…";
const session = await startGame(canvas, score, length, provenance, descriptor);
bindInputs(session, buttons);
window.addEventListener("resize", () => {
session.refreshDeviceClass();
session.renderNow();
});
window.addEventListener("pagehide", () => session.dispose(), { once: true });
bindTauriLifecycle(session, stage);
document.body.dataset.runtimeState = "ready";
status.textContent = "Prêt";

54
deltas/0.3.1/0-pre.4.md Normal file
View File

@@ -0,0 +1,54 @@
<!-- file: deltas/0.3.1/0-pre.4.md -->
<!-- version: 1 -->
# Delta 0.3.1-0-pre.4
## Objet
Fermer le comportement runtime du POC Snake Tauri Android après validation du Canvas/input/assets de `0-pre.3`, sans élargir la cible vers une distribution Android de production.
## Changements
- version workspace : `0.3.1-0-pre.4` ;
- historique de validation `0-pre.3` ajouté ;
- nouvelle liaison lifecycle Tauri dédiée : `visibilitychange`, `pagehide`, `pageshow`, `ResizeObserver` ;
- session Canvas dotée de `pause()` / `resume()` avec annulation du frame pending et remise à zéro de l'accumulateur ;
- reprise sans simulation du temps passé en background ;
- refresh de la provenance device lors des reprises et resize/rotation ;
- tracing frontend des transitions lifecycle ;
- Back Android volontairement non intercepté pour le Snake mono-page dont la politique moteur est `Exit` ;
- limite ergonomique du D-pad sous le Canvas documentée sans ajouter swipe/overlay hors scope ;
- Tauri Desktop marqué différé tant qu'une voie de monétisation WebView/desktop n'est pas démontrée.
## Invariants
- aucune logique gameplay n'entre dans TypeScript ;
- `game-snake-poc-wasm` reste l'unique adapter WASM Snake ;
- aucun fichier `gen/android` n'est versionné ou patché ;
- aucun handler `onBackButtonPress` n'est installé par ce POC ;
- aucun SDK publicitaire n'est déclaré comme compatible sans POC technique et vérification contractuelle ;
- SDL3 natif reste la direction Android de production.
## Validation demandée
```bash
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-snake-poc-wasm --all-targets --all-features
(cd crates/apps/game-snake-poc-tauri && cargo tauri android dev)
```
Smoke manuel : entrée tactile, background/foreground sans rattrapage, rotation/resize, provenance `phone / touch`, traces lifecycle, puis Back Android naturel.
## Suite prévue
Si cette gate est propre, `0-pre.5` est omise sauf duplication durable nouvellement démontrée. La version passe à `0.3.1-2-beta.1` pour validation large et packaging du POC de référence.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/015-PLATFORM_POC_ARCHITECTURE.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Architecture des POC plateforme et réseau
@@ -15,7 +15,7 @@ Première vague, réordonnée à partir du résultat réel de `0.3.0` :
- Web navigateur direct + Snake — validé par `0.3.0` ;
- Tauri Android + Snake — POC de référence terminé par `0.3.1`, conservé pour comparaison mais non retenu comme voie de production Android par défaut ;
- Tauri Desktop + Snake — évaluation conditionnelle seulement si la WebView apporte un bénéfice produit explicite, notamment une voie de monétisation publicitaire viable ;
- Tauri Desktop + Snake — différé tant quun bénéfice produit explicite nest pas démontré, en particulier une voie de monétisation publicitaire/reward vidéo compatible avec une WebView desktop distribuée ;
- build Android multi-ABI avec outils natifs — tranche dédiée après les POC WebView.
Plateformes supplémentaires lorsque l'environnement existe : Windows SDL natif, macOS SDL natif et iOS SDL natif.
@@ -26,7 +26,7 @@ Le host Tauri Android reste pertinent comme preuve de portabilité du gameplay R
La baseline Android productive reste SDL3 natif avec wrapper Java/JNI : elle fournit déjà le rendu et les contrôles nécessaires avec moins de couches doutillage. Tauri Android ne doit être rouvert pour un produit que lorsquun besoin concret apporte une valeur supérieure à ce coût, par exemple si les tests futurs démontrent un avantage net de la WebView pour une saisie texte ou une capacité UI difficile à obtenir proprement via SDL.
Cette décision ninterdit pas Tauri Desktop. Le Desktop reste une distribution distincte et optionnelle ; sa valeur doit être démontrée séparément, notamment au regard des possibilités de monétisation dans une WebView Desktop.
Cette décision ninterdit pas Tauri Desktop. Le Desktop reste une distribution distincte et optionnelle ; sa valeur doit être démontrée séparément. À la clôture du POC Android, aucune solution de publicité ou reward vidéo dans une WebView Tauri Desktop nest considérée comme validée. Une approche exigeant une URL Web publique, un chargement distant du jeu ou un SDK tiers spécifique doit faire lobjet dun POC et dune vérification contractuelle avant de rouvrir cette cible.
## POC réseau

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-V0_3_1_TAURI_ANDROID_SNAKE_PLAN.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Plan v0.3.1 — second host Snake Tauri Android
@@ -123,6 +123,8 @@ Stabiliser :
Cette tranche doit rendre le second host fonctionnel de bout en bout et suffisamment stable pour être conservé comme POC de référence.
État candidat `0-pre.4` : la boucle Tauri dispose maintenant de `pause/resume`, remet à zéro son accumulateur à la reprise, suit `visibilitychange`, `pagehide/pageshow` et `ResizeObserver`, et journalise les transitions via le tracing frontend. Le Back Android nest pas intercepté : Snake conserve la politique moteur par défaut `Exit`, identique au comportement système Tauri du host mono-page. Le D-pad sous le Canvas est conservé comme limite ergonomique documentée plutôt que délargir cette version à un système swipe/overlay.
### `0-pre.5` — extraction conditionnelle
Créer uniquement si l'état `0-pre.4` montre une duplication réellement durable entre `Web/game-snake-poc` et le frontend Tauri **et** si l'extraction garde une valeur au-delà du seul POC Android de référence.

55
history/0.3.1/0-pre.3.md Normal file
View File

@@ -0,0 +1,55 @@
<!-- file: history/0.3.1/0-pre.3.md -->
<!-- version: 1 -->
# Historique 0.3.1-0-pre.3
## Statut
Tranche validée par l'utilisateur le 2026-09-21 sur l'AVD Android x86_64. Le POC Tauri Android affiche et exécute réellement Snake via le Canvas et l'adapter WASM partagé.
## Gate workspace
Les validations suivantes ont passé :
```text
OpenJDK Temurin 17.0.20.1 sélectionné par JAVA_HOME
npm install : 0 vulnérabilité
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 219 file(s))
Distribution layout audit: clean (47 required path(s), 1 forbidden path(s) absent)
cargo fmt --all: clean
cargo fmt --all -- --check: clean
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
cargo test -p game-snake-poc-wasm --all-targets --all-features: 5 passed, 0 failed
```
## Smoke Tauri Android
`cargo tauri android dev` a construit le WASM, collecté les deux assets canoniques, assemblé et installé l'APK puis lancé `com.sasedev.games.snake.tauri/.MainActivity`.
Les traces ont confirmé :
```text
Snake Tauri assets ready: game-snake-poc / snake / engine-v1
Snake Tauri WASM initialized
Snake Tauri provenance: android / tauri-webview / wasm / phone / unknown
Snake Tauri frontend ready
Snake Tauri provenance: android / tauri-webview / wasm / phone / touch
```
Le Canvas est visible, le serpent se déplace et les boutons tactiles modifient effectivement sa direction.
## Limite ergonomique observée
Sur le petit écran de l'AVD, les boutons directionnels sont placés sous le Canvas et nécessitent un scroll pour être atteints. Le contrôle fonctionne mais n'est pas adapté à un jeu final. Une UX produit pourrait utiliser des boutons superposés/transparents ou des gestes swipe ; cette optimisation n'est pas ajoutée à `0.3.1`, dont l'objectif reste le POC de référence.
## Décision plateforme confirmée
Le POC confirme que Tauri Android est techniquement viable mais apporte une chaîne supplémentaire JDK/Gradle/Tauri/WebView/WASM sans avantage actuellement démontré sur le host Android SDL3 natif. Les futurs jeux Android restent donc orientés SDL3/Java/JNI.
La variante Tauri Desktop est elle aussi différée tant qu'une solution de monétisation desktop/WebView, notamment reward vidéo, n'est pas démontrée. Les pistes nécessitant une URL Web publique ou un SDK tiers restent des hypothèses à valider, pas des capacités acquises.
## Suite
`0.3.1-0-pre.4` ferme le POC fonctionnel par la gestion explicite background/foreground, resize/rotation, tracing lifecycle et politique Back Android.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# file: scripts/audit_distribution_layout.py
# version: 12
# version: 13
"""Audit the static distribution layout expected by supported POCs."""
@@ -44,6 +44,7 @@ REQUIRED_PATHS = (
"crates/apps/game-snake-poc-tauri/frontend/ts/assets.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/game.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/input.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/lifecycle.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/main.ts",
"crates/apps/game-snake-poc-tauri/frontend/ts/provenance.ts",
"Web/game-snake-poc/package.json",
@@ -168,6 +169,7 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
game_path = app_root / "frontend/ts/game.ts"
input_path = app_root / "frontend/ts/input.ts"
provenance_path = app_root / "frontend/ts/provenance.ts"
lifecycle_path = app_root / "frontend/ts/lifecycle.ts"
logging_path = app_root / "frontend/ts/logging.ts"
vite_path = app_root / "vite.config.ts"
wasm_runtime_path = root / "crates/apps/game-snake-poc-wasm/src/runtime.rs"
@@ -183,6 +185,7 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
game_path,
input_path,
provenance_path,
lifecycle_path,
logging_path,
vite_path,
wasm_runtime_path,
@@ -201,6 +204,7 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
game = game_path.read_text(encoding="utf-8")
input_module = input_path.read_text(encoding="utf-8")
provenance = provenance_path.read_text(encoding="utf-8")
lifecycle = lifecycle_path.read_text(encoding="utf-8")
logging = logging_path.read_text(encoding="utf-8")
vite = vite_path.read_text(encoding="utf-8")
wasm_runtime = wasm_runtime_path.read_text(encoding="utf-8")
@@ -222,6 +226,7 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
(main, 'loadSnakeTauriAssets()', "DIST-LAYOUT-046: Snake Tauri startup must load canonical runtime assets"),
(main, 'startGame(canvas, score, length, provenance, descriptor)', "DIST-LAYOUT-047: Snake Tauri startup must run the shared WASM game through Canvas"),
(main, 'bindInputs(session, buttons)', "DIST-LAYOUT-048: Snake Tauri startup must bind host inputs"),
(main, 'bindTauriLifecycle(session, stage)', "DIST-LAYOUT-059: Snake Tauri startup must bind WebView lifecycle"),
(assets, './common/data/runtime.json', "DIST-LAYOUT-049: Snake Tauri asset probe must load common runtime metadata"),
(assets, './game/data/game.json', "DIST-LAYOUT-050: Snake Tauri asset probe must load Snake metadata"),
(game, 'window.requestAnimationFrame(frame)', "DIST-LAYOUT-051: Snake Tauri Canvas host must schedule its fixed-step session"),
@@ -230,6 +235,13 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
(input_module, '"keydown"', "DIST-LAYOUT-054: Snake Tauri input must retain keyboard controls when available"),
(provenance, '"touch"', "DIST-LAYOUT-055: Snake Tauri provenance must observe touch input"),
(provenance, '"keyboard-mouse"', "DIST-LAYOUT-056: Snake Tauri provenance must observe keyboard/mouse input"),
(lifecycle, '"visibilitychange"', "DIST-LAYOUT-060: Snake Tauri lifecycle must observe visibility changes"),
(lifecycle, '"pagehide"', "DIST-LAYOUT-061: Snake Tauri lifecycle must observe pagehide"),
(lifecycle, '"pageshow"', "DIST-LAYOUT-062: Snake Tauri lifecycle must observe pageshow"),
(lifecycle, "ResizeObserver", "DIST-LAYOUT-063: Snake Tauri lifecycle must observe stage resize/orientation"),
(game, "pause(): void;", "DIST-LAYOUT-064: Snake Tauri session must expose pause semantics"),
(game, "resume(): void;", "DIST-LAYOUT-065: Snake Tauri session must expose resume semantics"),
(game, "accumulator = 0;", "DIST-LAYOUT-066: Snake Tauri resume/pause must reset accumulated elapsed time"),
(vite, 'dest: "common/data", rename: { stripBase: true }', "DIST-LAYOUT-057: Snake Tauri common assets must retain the common/data namespace"),
(vite, 'dest: "game/data", rename: { stripBase: true }', "DIST-LAYOUT-058: Snake Tauri game assets must retain the game/data namespace"),
(logging, '@fltsci/tauri-plugin-tracing', "DIST-LAYOUT-030: Snake Tauri frontend logs must use the tracing bridge"),
@@ -245,6 +257,8 @@ def audit_snake_tauri_android_integration(root: pathlib.Path) -> list[str]:
(build_script, "gen/android", "DIST-LAYOUT-040: Snake Tauri build script must not mutate generated Android Gradle files"),
(tauri, "python", "DIST-LAYOUT-034: Snake Tauri configuration must not use Python build orchestration"),
(main, "console.", "DIST-LAYOUT-035: Snake Tauri application frontend must not log directly through console.*"),
(main, "onbackbuttonpress", "DIST-LAYOUT-067: Snake Tauri reference POC must leave Android Back to the system while Snake quit policy is Exit"),
(lifecycle, "onbackbuttonpress", "DIST-LAYOUT-067: Snake Tauri reference POC must leave Android Back to the system while Snake quit policy is Exit"),
)
for content, fragment, message in forbidden_fragments:
if fragment in content.lower():