# Moteur embarqué : extraction du cœur de gnubg, `moteur.h` et `engine-cli` - **Date** : 2026-11-02 - **Étape** : 0a.3 - **Statut** : terminé - **Exécutant** : sous-agent general-purpose, dans un worktree isolé > **Renommage** (conventions de code décidées par Pierre pendant la tâche : > identifiants en anglais) : `moteur.h` devient `engine/include/engine.h`, > l'API C est préfixée `engine_` et les clés JSON sont en anglais. Le texte > ci-dessous garde la rédaction d'origine. ## Objectif Le cœur de GNU Backgammon (commit 14b67984737ded0f6de43d63dcb789f729d9b06b, ADR 0005) compile comme bibliothèque C autonome, sans GLib ni interface graphique, derrière l'interface `engine/include/moteur.h`, et un programme `engine-cli` l'interroge en ligne de commande avec une sortie JSON. ## Consignes 1. **Sources** : copie figée des seuls fichiers nécessaires de gnubg dans `engine/gnubg/` (liste de départ et plan d'isolement : ADR 0005), avec un `engine/gnubg/PROVENANCE.md` (commit exact, liste des fichiers, toute modification apportée à un fichier d'origine, signalée aussi en commentaire dans le fichier). Garder les en-têtes de copyright. Modifier les fichiers d'origine le moins possible : préférer un `config.h`, un shim `glib.h` et un en-tête réduit propres à Ustagammon dans `engine/compat/`. 2. **Données** : `gnubg.weights` et les tables d'équité de match nécessaires (`Kazaross-XG2` par défaut) copiées dans `engine/data/`. Les fichiers générés (`gnubg.wd`, bases `gnubg_os0.bd`, `gnubg_ts0.bd`) sont produits par le build avec les outils officiels (`makeweights`, `makebearoff`) compilés pour l'hôte, dans `build/`, jamais commités. Paramètres de génération identiques à ceux du build officiel de gnubg (`Makefile.am`). 3. **Interface `engine/include/moteur.h`**, en C, documentée en français, positions au format Position ID + Match ID, sans exposer de type gnubg : - initialisation (chemin des données) et libération ; - coups légaux pour une position et des dés ; - classement des coups avec équités et probabilités, pour un niveau donné (au minimum 0-ply et 2-ply, réglages prédéfinis de gnubg, sans bruit) ; - décision de videau (équités sans double, double/prend, double/passe, et décision), en match et en partie libre (Crawford, Jacoby) ; - évaluation d'une position (5 probabilités et équité) ; - erreurs signalées par code de retour, jamais par `exit`. 4. **`engine-cli/`** : `engine-cli bestmove ` (ex. `31`), `engine-cli moves …`, `engine-cli cube `, `engine-cli eval `, option de niveau (`--ply 0|2`), sortie JSON sur la sortie standard, erreurs en JSON avec un code de sortie non nul. 5. **Build** : compilation dans Docker (image `docker/build-c/Dockerfile` sur `debian:trixie`, déjà présente), jamais avec les outils de l'hôte. `make engine` produit `build/engine-cli` et les données ; `make build` l'appelle. Dépendances make correctes : rien n'est recompilé si rien n'a changé (le vérifier : un second `make engine` ne recompile rien). `-j2` maximum. `make clean` supprime `build/`. Les nouvelles cibles apparaissent dans `make help`. 6. **Tests** (`tests/test_engine.sh`, lancé par `make test`, qui suppose `make engine` déjà fait ou le lance) : position de départ (Position ID `4HPwATDgc/ABMA`) avec plusieurs jets d'ouverture dont le meilleur coup est connu sans ambiguïté (ex. 31 : 8/5 6/5), nombre de coups légaux d'une position connue, symétrie et bornes des probabilités, gestion d'un identifiant invalide, décision de videau d'une position manifestement « pas de double ». La comparaison systématique au gnubg de bureau est l'étape suivante (0a.4), pas celle-ci. 7. **Mesure** : temps de `bestmove` à 0-ply et 2-ply sur la position de départ, rapporté (pas de seuil à tenir sur le PC). Contraintes : - respecter `CLAUDE.md` (français, `-j2`, aucune installation hors Docker) ; - pas de threads (USE_MULTITHREAD désactivé) ; C générique sur x86 pour commencer (pas de SSE/AVX obligatoire), en gardant le chemin NEON possible ; - ne pas copier de code de CBG ; s'en inspirer est permis (clone en lecture seule indiqué dans la consigne de l'agent), en le citant ; - `make lint` et `make test` doivent passer ; commits petits, en français ; - ne pas pousser ni créer de merge request ; - ne rien simuler : un échec est rapporté avec sa cause. Rapport attendu : ce qui a été fait, fichiers repris et modifiés, commandes de vérification, temps de compilation, tailles des données générées, temps de calcul mesurés, difficultés, écarts au plan. ## Critères de réussite - `make engine && ./build/engine-cli bestmove 4HPwATDgc/ABMA cAkAAAAAAAAA 31` affiche le meilleur coup en JSON (8/5 6/5). - Un second `make engine` ne recompile rien. - `make lint` et `make test` passent. ## Résultat Terminé (2026-11-02), branche `moteur-embarque-travail`. Les trois critères sont remplis : `make engine && ./build/engine-cli bestmove 4HPwATDgc/ABMA cAkAAAAAAAAA 31` répond `8/5 6/5` en JSON ; un second `make engine` affiche « Nothing to be done » ; `make lint` et `make test` passent. - **Sources** : 38 fichiers de gnubg (plus `COPYING`) copiés **sans aucune modification** (`engine/gnubg/PROVENANCE.md`) ; en plus de la liste de l'ADR 0005 : `drawboard.c/h` (notation des coups, `FormatMove`), `mtsupport.c` (données du fil unique), `dice.h`. Tout le reste est remplacé dans `engine/compat/` (config.h, shim GLib, en-têtes réduits, fonctions de support). - **Tables d'équité** : pas de conversion en tableau C ni de repli sur Zadeh ; le shim fournit un petit analyseur XML compatible GMarkup, et `matchequity.c` lit `Kazaross-XG2.xml` à l'exécution, comme gnubg de bureau. `engine_init` échoue si la table chargée n'est pas celle-ci. Contre-vérification : une variante d'engine-cli liée à la vraie GLib donne une sortie identique au caractère près (partie libre, match en 5 points, Crawford, 0 et 2 plis). - **Données** : générées au build par les outils officiels compilés avec la vraie GLib, mêmes paramètres que `Makefile.am` : `gnubg.wd` 408 016 octets, `gnubg_os0.bd` 1 465 788 octets, `gnubg_ts0.bd` 6 830 248 octets. - **Build** : image `ustagammon/build-c:1` (`docker/build-c/Dockerfile`), `engine/engine.mk` lancé par `make engine` avec `-j2` ; build complet depuis zéro : 43 s, génération des données comprise. Options de gnubg (`-O3 -ffast-math -DG_DISABLE_ASSERT`) ; notre C en `-std=c11 -Wall -Wextra -Werror`. - **Temps de `bestmove`** (position de départ, processus complet, données en cache disque) : 0-ply 0,02 à 0,03 s ; 2-ply 0,03 s pour 31, 0,12 à 0,20 s pour 53, 64, 21 ; environ 35 Mo de mémoire. Premier lancement à froid : 0,8 s. - **Match ID** : `cAkAAAAAAAAA` met le joueur **1** au trait (bit fMove à 1) ; le Match ID équivalent avec le joueur 0 au trait est `MAEAAAAAAAAA`. Les deux donnent le même résultat sur la position de départ (testé). - **Écarts au plan** : identifiants en anglais (voir l'encadré) ; sous-commande `legal` ajoutée (coups légaux sans évaluation) ; `make test` ne vide plus `build/` (test_makefile.sh essaie `make clean` sur un dossier jetable), sinon chaque test recompilait le moteur ; mesure de l'écart avec et sans `-ffast-math` laissée à l'étape 0a.4.