8.9 KiB
Moteur en WebAssembly : page de test, contrat et mesures
- Date : 2026-11-02
- Étape : 0b
- Statut : en cours
- Exécutant : sous-agent general-purpose, dans un worktree isolé
Objectif
Le même moteur (engine/, engine.h) compilé en WebAssembly tourne dans un
Web Worker d'une page web minimale servie sur le réseau local. Il donne les
mêmes résultats que le moteur natif sur les 32 positions de référence, et une
page de mesure produit les chiffres qui permettront de trancher entre PWA et
Android natif (docs/etape-0.md, section 0b).
Consignes
- Compilation :
make engine-wasmcompile le moteur avec Emscripten, dans l'imageemscripten/emsdk:3.1.64(déjà téléchargée ; version figée), en C générique (pas de SIMD), sans threads,-j2. Sorties dansbuild/wasm/. Les données (gnubg.weightsougnubg.wd, bases.bd, table d'équité) viennent debuild/data/produit parmake engine, et sont chargées une fois (système de fichiers virtuel d'Emscripten ou équivalent simple). Recompilation minimale si rien n'a changé. Cible visible dansmake help. - Interface JavaScript minimale au-dessus d'
engine.h(par exemplebestMoves,cubeDecision,evaluate,reference), qui renvoie les mêmes objets JSON queengine-cli. Réutiliser le code de mise en JSON d'engine-cliplutôt que de le réécrire, si c'est simple. - Même suite de contrat (ADR 0007, point 7) : rejouer les 32 positions de
référence aux niveaux 0 et 2 plis avec le build WebAssembly, sous Node (dans
l'image Emscripten, qui contient Node, ou une image Node figée), avec le
même comparateur
tests/reference/compare.pyet la même tolérance. Le plus simple : un petit programme Node qui imite l'interface en ligne de commande d'engine-cli, et une option du comparateur pour choisir le programme interrogé. Test lancé parmake test(tests/test_wasm_reference.sh). - Page de test
web/(HTML, CSS et JavaScript sans framework ni étape de build) : moteur chargé dans un Web Worker, jamais sur le fil d'affichage ; saisie d'un Position ID, d'un Match ID et des dés ; bouton « Meilleur coup » ; affichage du résultat et du temps de calcul. Textes en français, identifiants en anglais. - Page de mesure (dans
web/) : sur une série de positions de référence, temps du meilleur coup à 0 et 2 plis et de la décision de videau à 2 plis (médiane et maximum), temps de chargement du moteur, taille totale téléchargée (non compressée et compressée gzip), mémoire WebAssembly utilisée. Un bouton « Copier les résultats » met un rapport texte dans le presse-papiers, que Pierre collera dans la conversation. La page compare elle-même aux seuils dedocs/etape-0.md. make deploy-websertweb/etbuild/wasm/sur le réseau local (serveur dans Docker, rien sur le PC ; types MIME corrects dontapplication/wasm), et affiche l'adresse exacte à ouvrir depuis le téléphone (IP du PC sur le réseau local). Le serveur s'arrête par Ctrl+C. Mettre à jour la cibledeploysi pertinent.make bench: la même mesure que la page, sous Node, sur le PC, pour un premier ordre de grandeur (le chiffre qui compte reste celui du téléphone).- Guide de test pour Pierre :
docs/guide-test-etape-0.md(document temporaire de l'étape 0), pas à pas : préparer le PC, lancermake deploy-web, ouvrir l'adresse sur le Fairphone 4 (même Wi-Fi), faire un essai de meilleur coup, lancer la page de mesure, copier le rapport et le coller dans la conversation. Phrases courtes, aucune connaissance technique supposée, que faire si la page ne s'ouvre pas (pare-feu, réseau).
Contraintes :
- respecter
CLAUDE.mdet les ADR 0004, 0005, 0007 : simplicité, pas de framework, identifiants en anglais, commentaires et textes en français, tout outil dans Docker ; - ne pas modifier
engine/gnubg/ni les références ; toute adaptation du moteur passe parengine/compat/ou le code d'Ustagammon ; - signaler chaque nouvelle dépendance et sa licence dans
CREDITS.md(Emscripten : MIT/NCSA) ; make lintetmake testdoivent passer ; commits petits, en français, avec la ligneCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>;- ne pas pousser ni créer de merge request ;
- ne rien simuler : les mesures viennent d'exécutions réelles.
Rapport attendu : ce qui a été fait, commandes de vérification, taille des
fichiers téléchargés, mesures make bench sur le PC, résultat de la suite de
contrat en WebAssembly (écart maximal), difficultés, écarts au plan.
Critères de réussite
make engine-wasmpuismake test: les 32 positions concordent en WebAssembly avec la même tolérance qu'en natif.make deploy-webaffiche une adresse ; la page donne le meilleur coup d'une position de référence avec son temps de calcul (vérifié dans un navigateur sans interface si possible, sinon par une requête HTTP sur les fichiers).make benchproduit un rapport.make lintetmake testpassent.
Résultat
Statut : terminé (2026-11-02, branche wasm-travail). Tous les critères
de réussite sont tenus ; reste la mesure sur le Fairphone 4 par Pierre
(docs/guide-test-etape-0.md).
Ce qui a été fait :
make engine-wasm(engine-wasm/wasm.mk) compile dansemscripten/emsdk:3.1.64les mêmes sources que le natif (liste communeengine/sources.mk), plusengine-cli/main.c, en C générique, sans threads,-O3 -ffast-math. Les données debuild/data/sont embarquées dansengine.data(--preload-file, système de fichiers virtuel,/data). Recompilation nulle si rien n'a changé.- Interface JavaScript (
web/engine-wasm.mjs) : le module appelleengine_initune fois, puismaind'engine-cli pour chaque commande (callMain) ; la sortie est exactement le JSON d'engine-cli, sans code de mise en JSON dupliqué. Seule modification du C :mainn'appelle pasengine_shutdownsous Emscripten. FonctionsbestMove,cubeDecision,evaluate,reference. - Suite de contrat :
tests/test_wasm_reference.sh(lancé parmake test) rejoue les 32 positions à 0 et 2 plis sous Node avecengine-wasm/engine-cli.mjs, qui imite engine-cli ;compare.py --cliaccepte désormais une commande avec arguments. Résultat : 2 598 nombres comparés, écart maximal 9,53e-4 (le même casopening-64-matchqu'en natif), tolérance 2e-3. Comparé directement au natif : mêmes classements, mêmes décisions, écart maximal 2,3e-6. web/: page de test (index.html), page de mesure (bench.html), Worker en module ES (engine-worker.mjs), client (engine-client.mjs), mesure partagée avec Node (benchmark.mjs). Aucun framework, aucune étape de build.make deploy-web: serveur HTTP de Python dans l'imagebuild-c(aucune nouvelle image), adresse LAN affichée (route par défaut), Ctrl+C l'arrête (vérifié).make deployrenvoie versmake deploy-web.make bench: même mesure sous Node.
Vérification de la page : Chromium 130 et Firefox 131 sans interface (image
mcr.microsoft.com/playwright:v1.48.2-noble, utilisée une fois, hors du
dépôt), ouverts sur l'adresse LAN http://192.168.1.21:8080/ (contexte non
sécurisé, comme le téléphone) : meilleur coup de middlegame-1 à 2 plis
(24/14, 305 ms Chromium, 2 433 ms Firefox), mesure complète, bouton de copie
fonctionnel. Types MIME vérifiés par curl : application/wasm,
text/javascript pour les .mjs.
Mesures sur le PC (i5-2450M, 2011) :
| Mesure (max sur les positions) | Node 18 | Chromium 130 | Firefox 131 | Seuil |
|---|---|---|---|---|
| Meilleur coup 0 pli | 2 ms | 5 ms | 14 ms | 300 ms |
| Meilleur coup 2 plis (médiane / max) | 107 / 454 ms | 87 / 316 ms | 693 / 2 341 ms | 3 000 ms |
| Videau 2 plis (médiane / max) | 27 / 109 ms | 21 / 103 ms | 158 / 902 ms | 3 000 ms |
| Chargement du moteur | 82 ms | 178 ms | 204 ms | |
| Mémoire WebAssembly | 51,9 Mo | 51,9 Mo | 51,9 Mo |
Taille téléchargée : 9,0 Mo non compressée, 7,3 Mo compressée gzip (seuil
40 Mo), dont engine.data 8,7 Mo (7,2 Mo gzip), engine.wasm 180 Ko
(78 Ko gzip), engine.mjs 73 Ko (20 Ko gzip).
Points d'attention : Firefox sans interface est environ 7 fois plus lent que Chromium sur le même PC, à vérifier sur le téléphone (le guide demande les deux navigateurs). La mémoire de 52 Mo vient surtout des bases de fin de partie copiées en mémoire et du cache d'évaluation de gnubg ; les données existent en plus une fois côté JavaScript (système de fichiers virtuel).
Écarts au plan : aucun sur le fond. Fichiers en .mjs (modules ES partagés
entre navigateur et Node sans package.json) ; seuils jugés sur le maximum,
la médiane étant donnée en plus ; la taille compressée est calculée par la page
(CompressionStream) car le serveur de test ne compresse pas.