From ce2bd46ec3d54d7be0304c1e0ba3a7ca968d2b11 Mon Sep 17 00:00:00 2001 From: Pierre MARTIN Date: Tue, 3 Nov 2026 07:00:38 +0100 Subject: [PATCH] =?UTF-8?q?Pages=20de=20test=20du=20moteur=20retir=C3=A9es?= =?UTF-8?q?=20du=20site=20;=20make=20bench=20garde=20sa=20mesure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- Makefile | 4 +- docs/adr/0009-socle-pwa-typescript.md | 21 ++++ docs/deploiement.md | 7 +- engine-wasm/bench.mjs | 4 +- {web/moteur => engine-wasm}/benchmark.mjs | 17 ++-- tests/test_site.sh | 24 +++-- web/moteur/bench-page.mjs | 98 ------------------- web/moteur/bench.html | 35 ------- web/moteur/engine-client.mjs | 66 ------------- web/moteur/index.html | 48 --------- web/moteur/page.mjs | 71 -------------- web/moteur/style.css | 113 ---------------------- 12 files changed, 50 insertions(+), 458 deletions(-) rename {web/moteur => engine-wasm}/benchmark.mjs (91%) delete mode 100644 web/moteur/bench-page.mjs delete mode 100644 web/moteur/bench.html delete mode 100644 web/moteur/engine-client.mjs delete mode 100644 web/moteur/index.html delete mode 100644 web/moteur/page.mjs delete mode 100644 web/moteur/style.css diff --git a/Makefile b/Makefile index 3883141..b4cd0cc 100644 --- a/Makefile +++ b/Makefile @@ -107,8 +107,6 @@ lint: ## Vérifie le style et les erreurs courantes deploy: site ## Sert l'application sur le réseau local (Ctrl+C pour arrêter) @ip="$$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i < NF; i++) if ($$i == "src") print $$(i + 1) }')"; \ echo "Application : http://$${ip:-ADRESSE-DU-PC}:$(WEB_PORT)/"; \ - echo "Test du moteur : http://$${ip:-ADRESSE-DU-PC}:$(WEB_PORT)/moteur/"; \ - echo "Page de mesure : http://$${ip:-ADRESSE-DU-PC}:$(WEB_PORT)/moteur/bench.html"; \ echo "À ouvrir depuis le téléphone, sur le même Wi-Fi. Ctrl+C pour arrêter." @docker run --rm --init -p $(WEB_PORT):8080 -v "$(CURDIR)/$(BUILD_DIR)/site:/site:ro" \ $(BUILD_C_IMAGE) python3 -m http.server 8080 --directory /site @@ -123,7 +121,7 @@ SITE_ICONS := icone.svg icone-192.png icone-512.png icone-maskable-192.png icone # Le site tel qu'il sera publié, reconstruit entièrement à chaque appel : # - web/ à la racine : la page de l'application, son style, sa police, le -# manifeste, le Web Worker du moteur, et les pages de test sous moteur/ ; +# manifeste et le Web Worker du moteur ; # - sous icons/, le logo et les icônes de la charte, copiés depuis # docs/identite/logo/ (une seule source, ADR 0010) ; # - l'application compilée (build/web/src/) sous app/ ; diff --git a/docs/adr/0009-socle-pwa-typescript.md b/docs/adr/0009-socle-pwa-typescript.md index c9cbed2..72bdfb4 100644 --- a/docs/adr/0009-socle-pwa-typescript.md +++ b/docs/adr/0009-socle-pwa-typescript.md @@ -68,3 +68,24 @@ vérification des dépendances. standard ») est tenu même au niveau fort (2 plis) : c'est le niveau standard de l'ordinateur à l'étape 0d. - Firefox sur le téléphone reste à vérifier, sans bloquer. + +## Amendements + +### 2026-11-03 — Pages de test du moteur retirées du site + +Décidé par Pierre après son premier test sur le téléphone : « supprimer la +partie test du moteur totalement ». + +- Les pages de test et de mesure de l'étape 0b (`web/moteur/` : `index.html`, + `bench.html` et leurs scripts) sont supprimées. Le site publié n'a plus rien + sous `/moteur/`, ni dans le cache du service worker ; `make deploy` + n'annonce plus que l'application, et l'aide ne renvoie plus vers le test du + moteur. +- Les mesures du Fairphone 4 ci-dessus, faites avec `bench.html`, restent la + référence. Pour les refaire, la mesure se lance sous Node sur le PC + (`make bench`) : son code partagé est déplacé de `web/moteur/benchmark.mjs` + vers `engine-wasm/benchmark.mjs`. Une mesure sur le téléphone demanderait de + remettre une page de mesure, à décider le moment venu. +- Le moteur reste vérifié par la suite de contrat sur WebAssembly + (`make test`) et, dans l'application, par la partie jouée dans un navigateur + (`make test-browser`). diff --git a/docs/deploiement.md b/docs/deploiement.md index ea30cc3..13b27b2 100644 --- a/docs/deploiement.md +++ b/docs/deploiement.md @@ -2,8 +2,8 @@ Ustagammon est un site statique (PWA, ADR 0009) : des fichiers HTML, JavaScript, le moteur en WebAssembly et ses données. L'application est à la -racine ; les pages de test du moteur de l'étape 0b restent publiées sous -`/moteur/` (test du moteur) et `/moteur/bench.html` (mesure). Il est publié sur +racine ; les pages de test du moteur de l'étape 0b ont été retirées (ADR +0009, amendement du 2026-11-03). Il est publié sur CapRover, machine `sans-pub` (https://captain.cloud.sans.pub), app `ustagammon`, sur https://ustagammon.sans.pub. @@ -112,8 +112,7 @@ ssh sans.pub 'sudo docker service ps srv-captain--ustagammon --format "{{.Image} ``` Puis ouvrir le site sur le téléphone : le plateau s'affiche et une consigne -remplace « Chargement du moteur… ». La page de test du moteur -(https://ustagammon.sans.pub/moteur/) doit afficher « Moteur prêt ». +remplace « Chargement du moteur… ». ## Retour arrière diff --git a/engine-wasm/bench.mjs b/engine-wasm/bench.mjs index f45b59c..a414894 100644 --- a/engine-wasm/bench.mjs +++ b/engine-wasm/bench.mjs @@ -1,4 +1,4 @@ -// bench.mjs -- la mesure de la page bench.html, sous Node, sur le PC +// bench.mjs -- mesure du moteur WebAssembly sous Node, sur le PC // (`make bench`). Premier ordre de grandeur seulement : le chiffre qui compte // est celui du téléphone. // @@ -10,7 +10,7 @@ import os from "node:os"; import { fileURLToPath } from "node:url"; import { gzipSync } from "node:zlib"; import createEngineModule from "../build/wasm/engine.mjs"; -import { DOWNLOADED_FILES, formatReport, measureTimes } from "../web/moteur/benchmark.mjs"; +import { DOWNLOADED_FILES, formatReport, measureTimes } from "./benchmark.mjs"; import { commands, loadEngine } from "../web/engine-wasm.mjs"; const root = new URL("../", import.meta.url); diff --git a/web/moteur/benchmark.mjs b/engine-wasm/benchmark.mjs similarity index 91% rename from web/moteur/benchmark.mjs rename to engine-wasm/benchmark.mjs index b9c3348..213e877 100644 --- a/web/moteur/benchmark.mjs +++ b/engine-wasm/benchmark.mjs @@ -1,5 +1,7 @@ -// benchmark.mjs -- mesure du moteur WebAssembly, partagée par la page de -// mesure (bench.html, dans le navigateur) et `make bench` (sous Node). +// benchmark.mjs -- mesure du moteur WebAssembly sous Node, pour `make bench` +// (engine-wasm/bench.mjs). Elle servait aussi à la page de mesure du site, +// retirée avec les pages de test du moteur (ADR 0009, amendement du +// 2026-11-03). // // Copyright (C) 2026 les auteurs d'Ustagammon. // Licence : GPLv3 ou ultérieure (voir LICENSE). @@ -20,13 +22,12 @@ export const TIME_THRESHOLDS = [ // Taille totale à télécharger, compressée : moins de 40 Mo. export const DOWNLOAD_LIMIT_BYTES = 40e6; -// Fichiers téléchargés pour faire tourner le moteur dans la page de test, -// chemins relatifs à la racine du site (web/ ; wasm/ est build/wasm/). +// Fichiers téléchargés pour faire tourner le moteur dans l'application +// (hors code de l'application elle-même, quelques dizaines de Ko), chemins +// relatifs à la racine du site (web/ ; wasm/ est build/wasm/). export const DOWNLOADED_FILES = [ - "moteur/index.html", - "moteur/style.css", - "moteur/page.mjs", - "moteur/engine-client.mjs", + "index.html", + "app.css", "engine-worker.mjs", "engine-wasm.mjs", "wasm/engine.mjs", diff --git a/tests/test_site.sh b/tests/test_site.sh index ff3b846..774c058 100755 --- a/tests/test_site.sh +++ b/tests/test_site.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # Vérifie l'image de production (deploy/) avec le site assemblé (make package) : # construit l'image en local, la lance, et contrôle ce que reçoit un -# navigateur (application, pages de test du moteur, aide, manifeste, service -# worker, types MIME, compression, politique de sécurité, version). +# navigateur (application, aide, manifeste, service worker, types MIME, +# compression, politique de sécurité, version), et l'absence des anciennes +# pages de test du moteur. set -euo pipefail cd "$(dirname "$0")/.." @@ -71,10 +72,12 @@ check "wasm/engine.mjs" "text/javascript" check "wasm/engine.wasm" "application/wasm" check "wasm/engine.data" "application/octet-stream" check "version.json" "application/json" -# Pages de test du moteur (étape 0b). -check "moteur/" "text/html" -check "moteur/bench.html" "text/html" -check "moteur/engine-client.mjs" "text/javascript" +# Les pages de test du moteur (étape 0b) sont retirées (ADR 0009, +# amendement du 2026-11-03) : plus rien sous /moteur/. +[ ! -e build/site/moteur ] || report "build/site/moteur/ existe encore" +for gone in moteur/ moteur/bench.html; do + curl -s -o /dev/null -w "%{http_code}" "$base/$gone" | grep -qx 404 || report "/$gone : 404 attendu" +done # Aide : copies du glossaire et des scénarios (ADR 0008). check "docs/scenarios/jouer-une-partie.feature" "text/plain" check "docs/glossaire/README.md" "text/markdown" @@ -94,15 +97,16 @@ done < <(grep -o '"src": "[^"]*"' <<<"$manifest" | sed 's/"src": "\(.*\)"/\1/') # Le service worker met en cache chaque fichier du site, avec une version. sw="$(curl -s "$base/sw.js")" grep -qE '^const VERSION = "[0-9a-f]{16}";' <<<"$sw" || report "sw.js : version absente" -for file in ./ index.html app/main.js wasm/engine.data wasm/engine.wasm moteur/ \ +for file in ./ index.html app/main.js wasm/engine.data wasm/engine.wasm \ docs/glossaire/videau.md version.json fonts/AlegreyaSans-Bold.woff2 icons/favicon.svg; do grep -qF " \"$file\"," <<<"$sw" || report "sw.js : $file absent de la liste à mettre en cache" done listed="$(grep -c '^ "' <<<"$sw")" present="$(find build/site -type f ! -name sw.js | wc -l)" -# Chaque fichier, plus « ./ » et « moteur/ » pour les deux index.html. -[ "$listed" -eq "$((present + 2))" ] \ - || report "sw.js liste $listed adresses pour $present fichiers (plus 2 dossiers)" +# Chaque fichier, plus « ./ » pour index.html. +[ "$listed" -eq "$((present + 1))" ] \ + || report "sw.js liste $listed adresses pour $present fichiers (plus « ./ »)" +grep -q '"moteur/' <<<"$sw" && report "sw.js met encore en cache des pages de test du moteur" grep -o '^ "[^"]*"' <<<"$sw" | tr -d ' "' | while read -r file; do curl -sf -o /dev/null "$base/$file" || echo "sw.js : $file introuvable sur le site" done | grep . && report "sw.js liste des fichiers introuvables" diff --git a/web/moteur/bench-page.mjs b/web/moteur/bench-page.mjs deleted file mode 100644 index f6a47c1..0000000 --- a/web/moteur/bench-page.mjs +++ /dev/null @@ -1,98 +0,0 @@ -// bench-page.mjs -- page de mesure (bench.html) : chronomètre le moteur -// dans un Web Worker et produit le rapport à copier. -// -// Copyright (C) 2026 les auteurs d'Ustagammon. -// Licence : GPLv3 ou ultérieure (voir LICENSE). - -import { DOWNLOADED_FILES, DOWNLOAD_LIMIT_BYTES, formatReport, measureTimes, summarize } - from "./benchmark.mjs"; -import { startEngine } from "./engine-client.mjs"; - -const $ = (id) => document.getElementById(id); - -// Taille d'un fichier du site, brute et compressée gzip (comme le ferait un -// serveur qui compresse ses réponses). -// name est relatif à la racine du site, un dossier au-dessus de cette page. -async function fileSize(name) { - const blob = await (await fetch("../" + name, { cache: "no-store" })).blob(); - const gzipped = blob.stream().pipeThrough(new CompressionStream("gzip")); - const gzipBytes = (await new Response(gzipped).arrayBuffer()).byteLength; - return { name, bytes: blob.size, gzipBytes }; -} - -function addRow(cells) { - const row = $("table").insertRow(); - for (const text of cells) { - row.insertCell().textContent = text; - } -} - -async function measure() { - $("start").disabled = true; - $("results").hidden = true; - $("table").replaceChildren(); - - $("status").textContent = "Chargement du moteur…"; - const start = performance.now(); - const engine = startEngine(); - await engine.ready; - const loadMs = performance.now() - start; - - const { positions } = await (await fetch("../wasm/positions.json")).json(); - let memoryBytes = 0; - const times = await measureTimes({ - bestMove: (...args) => engine.bestMove(...args).then(keepMemory), - cubeDecision: (...args) => engine.cubeDecision(...args).then(keepMemory), - }, positions, (done, total) => { - $("status").textContent = "Calcul " + done + " sur " + total + "…"; - }); - function keepMemory(answer) { - memoryBytes = answer.memoryBytes; - return answer; - } - - $("status").textContent = "Mesure de la taille à télécharger…"; - const files = await Promise.all(DOWNLOADED_FILES.map(fileSize)); - - for (const s of summarize(times)) { - addRow([s.label, Math.round(s.medianMs) + " ms", Math.round(s.maxMs) + " ms", - s.limitMs + " ms", s.ok ? "OK" : "dépassé"]); - } - const gzipTotal = files.reduce((sum, f) => sum + f.gzipBytes, 0); - addRow(["Taille compressée", "", (gzipTotal / 1e6).toFixed(1).replace(".", ",") + " Mo", - DOWNLOAD_LIMIT_BYTES / 1e6 + " Mo", gzipTotal < DOWNLOAD_LIMIT_BYTES ? "OK" : "dépassé"]); - - $("report").value = formatReport({ - environment: navigator.userAgent, files, loadMs, memoryBytes, times, - }); - $("results").hidden = false; - $("status").textContent = "Mesure terminée."; -} - -// Copie dans le presse-papiers. L'API moderne n'existe que sur les pages -// sécurisées (https ou localhost) : sur le réseau local en http, on -// sélectionne le texte et on utilise l'ancienne commande de copie. -async function copyReport() { - try { - if (navigator.clipboard) { - await navigator.clipboard.writeText($("report").value); - } else { - $("report").select(); - if (!document.execCommand("copy")) { - throw new Error(); - } - } - $("copy-status").textContent = "Rapport copié : collez-le dans la conversation."; - } catch { - $("report").select(); - $("copy-status").textContent = "Copie impossible : le texte est sélectionné, " - + "copiez-le à la main (appui long, puis Copier)."; - } -} - -$("start").onclick = () => measure().catch((error) => { - $("status").textContent = "La mesure a échoué : " + error.message; - $("status").classList.add("error"); - $("start").disabled = false; -}); -$("copy").onclick = copyReport; diff --git a/web/moteur/bench.html b/web/moteur/bench.html deleted file mode 100644 index ca4d92a..0000000 --- a/web/moteur/bench.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - -Ustagammon : mesure du moteur - - - - -
-

Mesure du moteur

-

Cette page chronomètre le moteur sur les positions de référence : meilleur coup - aux niveaux rapide et fort, décision de videau au niveau fort. Elle mesure aussi - la taille à télécharger et la mémoire utilisée. Comptez une à quelques minutes ; - gardez l'écran allumé et la page au premier plan.

- -

- - - -

Page de test

-
- - diff --git a/web/moteur/engine-client.mjs b/web/moteur/engine-client.mjs deleted file mode 100644 index f8ddfc5..0000000 --- a/web/moteur/engine-client.mjs +++ /dev/null @@ -1,66 +0,0 @@ -// engine-client.mjs -- le moteur vu depuis les pages de test : démarre le -// Web Worker (../engine-worker.mjs, partagé avec l'application) et lui envoie -// les commandes. -// -// Copyright (C) 2026 les auteurs d'Ustagammon. -// Licence : GPLv3 ou ultérieure (voir LICENSE). -// -// Toutes les fonctions renvoient une promesse de { result, ms, memoryBytes } : -// result est l'objet JSON d'engine-cli, ms le temps de calcul mesuré dans le -// Worker. La promesse est rejetée si le moteur signale une erreur. - -import { commands } from "../engine-wasm.mjs"; - -export function startEngine() { - const worker = new Worker(new URL("../engine-worker.mjs", import.meta.url), { type: "module" }); - const pending = new Map(); - let nextId = 1; - let resolveReady, rejectReady; - const ready = new Promise((resolve, reject) => { - resolveReady = resolve; - rejectReady = reject; - }); - - worker.onmessage = (event) => { - const message = event.data; - if (message.type === "ready") { - resolveReady(message); - } else if (message.type === "failed") { - rejectReady(new Error(message.message)); - } else { - const { resolve, reject } = pending.get(message.id); - pending.delete(message.id); - if (message.error) { - reject(new Error(message.error)); - } else { - resolve(message); - } - } - }; - // Erreur au chargement du Worker lui-même (fichier absent, navigateur - // trop ancien pour les Workers en module ES…). - worker.onerror = (event) => { - rejectReady(new Error(event.message || "le Web Worker du moteur n'a pas démarré")); - }; - - function run(args) { - const id = nextId++; - return new Promise((resolve, reject) => { - pending.set(id, { resolve, reject }); - worker.postMessage({ id, args: args.map(String) }); - }); - } - - return { - // Promesse de { loadMs, memoryBytes } une fois le moteur chargé. - ready, - bestMove: (positionId, matchId, dice, ply) => - run(commands.bestMove(positionId, matchId, dice, ply)), - cubeDecision: (positionId, matchId, ply) => - run(commands.cubeDecision(positionId, matchId, ply)), - evaluate: (positionId, matchId, ply) => - run(commands.evaluate(positionId, matchId, ply)), - reference: (positionId, matchId, dice, ply) => - run(commands.reference(positionId, matchId, dice, ply)), - }; -} diff --git a/web/moteur/index.html b/web/moteur/index.html deleted file mode 100644 index 614357b..0000000 --- a/web/moteur/index.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - -Ustagammon : test du moteur - - - - -
-

Test du moteur

-

Le moteur GNU Backgammon, compilé en WebAssembly, calcule dans le navigateur.

-

Chargement du moteur…

- -
- - - -
- - - -
- -
- - - -

Page de mesure · L'application

-
- - diff --git a/web/moteur/page.mjs b/web/moteur/page.mjs deleted file mode 100644 index a51c175..0000000 --- a/web/moteur/page.mjs +++ /dev/null @@ -1,71 +0,0 @@ -// page.mjs -- page de test du moteur (index.html) : meilleur coup d'une -// position, avec son temps de calcul. -// -// Copyright (C) 2026 les auteurs d'Ustagammon. -// Licence : GPLv3 ou ultérieure (voir LICENSE). - -import { startEngine } from "./engine-client.mjs"; - -const $ = (id) => document.getElementById(id); -const engine = startEngine(); - -engine.ready.then( - ({ loadMs }) => { - $("status").textContent = "Moteur prêt, chargé en " + Math.round(loadMs) + " ms."; - $("best-move").disabled = false; - }, - (error) => { - $("status").textContent = "Le moteur n'a pas pu se charger : " + error.message; - $("status").classList.add("error"); - }, -); - -// Exemples : les positions de référence avec des dés. -fetch("../wasm/positions.json") - .then((response) => response.json()) - .then(({ positions }) => { - for (const p of positions.filter((p) => p.type === "move")) { - const option = new Option(p.id + " : " + p.description, p.id); - option.position = p; - $("example").add(option); - } - }) - .catch(() => {}); // la page reste utilisable sans les exemples - -$("example").onchange = () => { - const p = $("example").selectedOptions[0].position; - if (p) { - $("position-id").value = p.position_id; - $("match-id").value = p.match_id; - [$("die1").value, $("die2").value] = p.dice; - } -}; - -function showResult(move, details, time) { - $("move").textContent = move; - $("details").textContent = details; - $("time").textContent = time; - $("result").hidden = false; -} - -$("form").onsubmit = async (event) => { - event.preventDefault(); - $("best-move").disabled = true; - $("result").hidden = true; - try { - const { result, ms } = await engine.bestMove($("position-id").value.trim(), - $("match-id").value.trim(), [$("die1").value, $("die2").value], $("ply").value); - const best = result.best_move; - if (best) { - showResult(best.move, "Équité " + best.equity.toFixed(3).replace(".", ",") - + ", chances de gain " + (100 * best.probabilities.win).toFixed(1).replace(".", ",") - + " %, évalué à " + best.ply + " pli(s).", Math.round(ms) + " ms"); - } else { - showResult("aucun coup possible", "", Math.round(ms) + " ms"); - } - } catch (error) { - showResult("erreur", error.message, ""); - } finally { - $("best-move").disabled = false; - } -}; diff --git a/web/moteur/style.css b/web/moteur/style.css deleted file mode 100644 index 9bdc915..0000000 --- a/web/moteur/style.css +++ /dev/null @@ -1,113 +0,0 @@ -/* Pages de test du moteur : mise en forme minimale, lisible sur téléphone. */ - -:root { - --text: #1d1d1b; - --background: #fbfaf7; - --muted: #5f5e5a; - --accent: #1f5c4a; - --error: #a3261b; - --border: #cfccc4; -} - -@media (prefers-color-scheme: dark) { - :root { - --text: #ecebe6; - --background: #1b1c1a; - --muted: #a9a8a2; - --accent: #7cc4a8; - --error: #f08a7e; - --border: #4a4a46; - } -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; - font: 17px/1.5 system-ui, sans-serif; - color: var(--text); - background: var(--background); -} - -main { - max-width: 40rem; - margin: 0 auto; - padding: 1rem; -} - -h1 { - font-size: 1.5rem; -} - -label { - display: block; - margin-bottom: 0.75rem; -} - -input, -select, -button, -textarea { - display: block; - width: 100%; - margin-top: 0.25rem; - padding: 0.5rem; - font: inherit; - color: inherit; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; -} - -.row { - display: flex; - gap: 0.75rem; -} - -.row label { - flex: 1; -} - -button { - margin: 0.5rem 0 1rem; - font-weight: 600; - color: var(--background); - background: var(--accent); - border-color: var(--accent); -} - -button:disabled { - opacity: 0.5; -} - -textarea { - height: 16rem; - font: 13px/1.4 monospace; -} - -table { - width: 100%; - border-collapse: collapse; -} - -th, -td { - padding: 0.25rem; - text-align: left; - border-bottom: 1px solid var(--border); -} - -.status { - color: var(--muted); -} - -.error { - color: var(--error); -} - -.result { - padding: 0.5rem 1rem; - border-left: 4px solid var(--accent); -}