Étape 0a.4 : comparaison du moteur aux références de gnubg #8

Manually merged
pierre merged 8 commits from comparaison into main 2026-11-02 18:04:53 +00:00
Owner

Pourquoi

Le moteur embarqué (MR !7) doit retrouver les résultats de GNU Backgammon de bureau (cadrage, ADR 0005), et c'est aux tests de le garantir, puisque le code n'est pas relu par un humain. C'est l'étape 0a.4 : make test compare automatiquement engine-cli aux 32 positions de référence produites par gnubg de bureau (MR !6), aux niveaux 0 et 2 plis, avec une tolérance mesurée et justifiée.

La comparaison a fait apparaître deux vrais défauts du moteur embarqué, que les tests de la MR !7 ne voyaient pas. Ils sont corrigés ici.

Cette MR consigne aussi la validation par Pierre des outils de compilation et de test de l'étape 0 (amendement de l'ADR 0004).

Quoi

  • Défauts corrigés dans engine/engine.c :

    • en match, l'équité des coups était convertie deux fois par mwc2eq ;
    • le classement des coups n'était pas exactement celui de la commande hint de gnubg. Il suit maintenant la même méthode : FindnSaveBestMoves avec fAnalyse, seuil arSkillLevel[SKILL_DOUBTFUL], puis RefreshMoveList.
  • engine-cli --ply N reference POSITION_ID MATCH_ID [DÉS] : produit un objet de même structure que plies["N"] d'un fichier de référence (le contrat de tests/reference/README.md). Les nombres sont écrits avec 9 chiffres significatifs, ce qui redonne exactement le float32.

  • engine.h : engine_cube gagne optimal et decision_text ; decision prend les noms de gnubg (DOUBLE_TAKE, NODOUBLE_TAKE…).

  • tests/reference/compare.py et tests/test_engine_reference.sh (lancé par make test). Les critères :

    • même nombre de coups légaux ;
    • même meilleur coup et même pli réel ;
    • équités et probabilités à la tolérance près ;
    • même décision de videau.

    En cas d'écart, le rapport donne la position, le niveau, le champ, la valeur attendue, la valeur obtenue et l'écart.

  • Image ustagammon/build-c:2 : ajoute Python 3 et jq. tests/test_reference.sh s'y exécute, et jq n'est plus utilisé sur le PC (ADR 0004).

  • ADR 0004 amendé : table des outils validés par Pierre (ShellCheck, Node et @cucumber/gherkin, Debian et gnubg de référence, gcc, make, GLib, Python 3, jq, Emscripten), tous dans Docker et jamais embarqués.

Comment

Travail délégué à un sous-agent selon un plan écrit avant la délégation, supprimé à la clôture ; son résultat est repris ici.

Tolérance retenue : 0,002, mesurée sur 2 598 nombres :

Moteur comparé à gnubg de bureau 99 % des écarts sous Écart maximal
C générique avec -ffast-math (build retenu) 1,6e-4 9,5e-4
C générique sans -ffast-math 1,6e-4 9,5e-4
Compilé en SSE2, pour l'essai 0 0 (identique au bit près)

L'écart vient du gnubg de bureau, pas de notre build. Compilé en SSE2 avec -ffast-math, gnubg calcule l'inverse dans sa sigmoïde avec _mm_rcp_ps, une instruction approchée (environ 12 bits), alors que le C générique fait une vraie division. La preuve : compilé en SSE2, notre moteur redonne les références au bit près.

Le seul écart au-delà de 1e-4 concerne opening-64-match à 2 plis (coup 8/2 6/2, 9,5e-4). L'hypothèse la plus probable, non vérifiée : une petite différence fait basculer un choix discret de la recherche à 2 plis.

La tolérance de 0,002 vaut environ deux fois l'écart maximal mesuré, et 15 fois moins que le seuil où gnubg qualifie un coup de « douteux » (0,03).

Vérification par la session principale, après fusion de main (MR !7) dans la branche :

  • make setup && make engine && make lint && make test : 7/7. La ligne de comparaison affiche « 32 positions, 64 niveaux, 2598 nombres comparés ; écart maximal 9.53e-04, tolérance 2e-03 » ;
  • références altérées dans une copie (équité décalée de 0,01, décision de videau changée) : le comparateur échoue et liste exactement les 2 écarts.

Choix et compromis

  • Le moteur reste en C générique, et non en SSE2, sur PC. Une concordance au bit près serait possible, mais l'application tournera en C générique (WebAssembly) ou en NEON (Android) : c'est ce chemin qu'il faut tester.
  • Une tolérance unique pour les probabilités et les équités : plus simple, et suffisante au regard des écarts mesurés.
  • Le format des nombres d'engine-cli passe à 9 chiffres significatifs pour toutes les commandes : un peu moins lisible, mais exact.

Risques et déploiement

  • make setup reconstruit l'image ustagammon/build-c (étiquette 2).
  • Changement d'interface dans engine.h (engine_cube) : aucun autre code ne l'utilise encore.
  • Sur téléphone, en NEON, gnubg utilise une autre approximation de la division. L'écart devra y être mesuré de la même façon si le socle Android est retenu.

Retour arrière : annuler le commit de fusion.

Comment vérifier

git switch main && git pull
make setup && make engine
make lint && make test
build/engine-cli --ply 2 reference 4HPwATDgc/ABMA cIkFAAAAAAAA 31
BUILD_C_IMAGE=ustagammon/build-c:2 bash tests/test_engine_reference.sh --details

Hors périmètre

  • La même suite de contrat sur le moteur WebAssembly : étape 0b, en cours.
  • Les MR de ce dépôt restent affichées « open » après fusion, à cause des hooks git du serveur Gitea (diagnostic à part) ; Pierre les marque fusionnées dans l'interface.

Generated with Claude Code https://claude.com/claude-code

## Pourquoi Le moteur embarqué (MR !7) doit retrouver les résultats de GNU Backgammon de bureau (cadrage, ADR 0005), et c'est aux tests de le garantir, puisque le code n'est pas relu par un humain. C'est l'étape 0a.4 : `make test` compare automatiquement `engine-cli` aux 32 positions de référence produites par gnubg de bureau (MR !6), aux niveaux 0 et 2 plis, avec une tolérance mesurée et justifiée. La comparaison a fait apparaître **deux vrais défauts du moteur embarqué**, que les tests de la MR !7 ne voyaient pas. Ils sont corrigés ici. Cette MR consigne aussi la validation par Pierre des outils de compilation et de test de l'étape 0 (amendement de l'ADR 0004). ## Quoi - **Défauts corrigés dans `engine/engine.c`** : - en match, l'équité des coups était convertie deux fois par `mwc2eq` ; - le classement des coups n'était pas exactement celui de la commande `hint` de gnubg. Il suit maintenant la même méthode : `FindnSaveBestMoves` avec `fAnalyse`, seuil `arSkillLevel[SKILL_DOUBTFUL]`, puis `RefreshMoveList`. - **`engine-cli --ply N reference POSITION_ID MATCH_ID [DÉS]`** : produit un objet de même structure que `plies["N"]` d'un fichier de référence (le contrat de `tests/reference/README.md`). Les nombres sont écrits avec 9 chiffres significatifs, ce qui redonne exactement le float32. - **`engine.h`** : `engine_cube` gagne `optimal` et `decision_text` ; `decision` prend les noms de gnubg (`DOUBLE_TAKE`, `NODOUBLE_TAKE`…). - **`tests/reference/compare.py`** et **`tests/test_engine_reference.sh`** (lancé par `make test`). Les critères : - même nombre de coups légaux ; - même meilleur coup et même pli réel ; - équités et probabilités à la tolérance près ; - même décision de videau. En cas d'écart, le rapport donne la position, le niveau, le champ, la valeur attendue, la valeur obtenue et l'écart. - **Image `ustagammon/build-c:2`** : ajoute Python 3 et jq. `tests/test_reference.sh` s'y exécute, et `jq` n'est plus utilisé sur le PC (ADR 0004). - **ADR 0004 amendé** : table des outils validés par Pierre (ShellCheck, Node et `@cucumber/gherkin`, Debian et gnubg de référence, gcc, make, GLib, Python 3, jq, Emscripten), tous dans Docker et jamais embarqués. ## Comment Travail délégué à un sous-agent selon un plan écrit avant la délégation, supprimé à la clôture ; son résultat est repris ici. **Tolérance retenue : 0,002**, mesurée sur 2 598 nombres : | Moteur comparé à gnubg de bureau | 99 % des écarts sous | Écart maximal | | --- | --- | --- | | C générique avec `-ffast-math` (build retenu) | 1,6e-4 | 9,5e-4 | | C générique sans `-ffast-math` | 1,6e-4 | 9,5e-4 | | Compilé en SSE2, pour l'essai | 0 | 0 (identique au bit près) | **L'écart vient du gnubg de bureau, pas de notre build.** Compilé en SSE2 avec `-ffast-math`, gnubg calcule l'inverse dans sa sigmoïde avec `_mm_rcp_ps`, une instruction approchée (environ 12 bits), alors que le C générique fait une vraie division. La preuve : compilé en SSE2, notre moteur redonne les références au bit près. Le seul écart au-delà de 1e-4 concerne `opening-64-match` à 2 plis (coup `8/2 6/2`, 9,5e-4). L'hypothèse la plus probable, non vérifiée : une petite différence fait basculer un choix discret de la recherche à 2 plis. La tolérance de 0,002 vaut environ deux fois l'écart maximal mesuré, et 15 fois moins que le seuil où gnubg qualifie un coup de « douteux » (0,03). Vérification par la session principale, après fusion de `main` (MR !7) dans la branche : - `make setup && make engine && make lint && make test` : 7/7. La ligne de comparaison affiche « 32 positions, 64 niveaux, 2598 nombres comparés ; écart maximal 9.53e-04, tolérance 2e-03 » ; - références altérées dans une copie (équité décalée de 0,01, décision de videau changée) : le comparateur échoue et liste exactement les 2 écarts. ## Choix et compromis - **Le moteur reste en C générique**, et non en SSE2, sur PC. Une concordance au bit près serait possible, mais l'application tournera en C générique (WebAssembly) ou en NEON (Android) : c'est ce chemin qu'il faut tester. - **Une tolérance unique** pour les probabilités et les équités : plus simple, et suffisante au regard des écarts mesurés. - **Le format des nombres** d'`engine-cli` passe à 9 chiffres significatifs pour toutes les commandes : un peu moins lisible, mais exact. ## Risques et déploiement - `make setup` reconstruit l'image `ustagammon/build-c` (étiquette 2). - **Changement d'interface dans `engine.h`** (`engine_cube`) : aucun autre code ne l'utilise encore. - **Sur téléphone, en NEON**, gnubg utilise une autre approximation de la division. L'écart devra y être mesuré de la même façon si le socle Android est retenu. Retour arrière : annuler le commit de fusion. ## Comment vérifier ``` git switch main && git pull make setup && make engine make lint && make test build/engine-cli --ply 2 reference 4HPwATDgc/ABMA cIkFAAAAAAAA 31 BUILD_C_IMAGE=ustagammon/build-c:2 bash tests/test_engine_reference.sh --details ``` ## Hors périmètre - La même suite de contrat sur le moteur WebAssembly : étape 0b, en cours. - Les MR de ce dépôt restent affichées « open » après fusion, à cause des hooks git du serveur Gitea (diagnostic à part) ; Pierre les marque fusionnées dans l'interface. Generated with Claude Code https://claude.com/claude-code
pierre added 8 commits 2026-11-02 18:04:51 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- engine_best_moves suit hint_move de gnubg : fAnalyse vrai, seuil
  « douteux », puis RefreshMoveList (classement par niveau d'évaluation
  et départage des égalités).
- Correction : en match, l'équité des coups était convertie deux fois par
  mwc2eq (ScoreMove la convertit déjà).
- engine_cube : ajout de optimal et decision_text ; decision prend le nom
  de l'énumération cubedecision de gnubg, comme les références.
- engine-cli : commande reference (un niveau au format de
  tests/reference/) et nombres écrits avec 9 chiffres significatifs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jq n'est plus utilisé sur le PC (ADR 0004) : tests/test_reference.sh se
relance dans l'image ustagammon/build-c:2, qui contient aussi Python 3
pour le comparateur du moteur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/test_engine_reference.sh lance tests/reference/compare.py sur les
32 positions aux niveaux 0 et 2 plis. Tolérance de 0,002 mesurée et
justifiée dans tests/reference/README.md : écart maximal de 9,5e-4, avec
ou sans -ffast-math ; la cause est la sigmoïde approchée (_mm_rcp_ps) de
gnubg de bureau en SSE2, qu'un build SSE2 du moteur reproduit au bit près.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pierre manually merged commit 39052215b1 into main 2026-11-02 18:04:53 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: pierre/Ustagammon#8