Positions de référence évaluées par gnubg (0 et 2 plis)

32 positions (ouvertures, milieu, prime, blitz, course, sortie, videau,
match, Crawford, Jacoby), générées par make reference ; format documenté
dans tests/reference/README.md, vérifié par tests/test_reference.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-02 13:57:14 +01:00
co-authored by Claude Opus 5.5
parent 8677193bee
commit b199698b11
37 changed files with 8031 additions and 2 deletions
+229
View File
@@ -0,0 +1,229 @@
# Positions de référence
Ce dossier contient des positions de backgammon évaluées par le GNU Backgammon
de bureau, compilé dans Docker au commit figé par l'ADR 0005. Le moteur
embarqué d'Ustagammon (et son `engine-cli`) doit retrouver ces résultats, à
une tolérance près. **Ce document est le contrat du format** : `engine-cli`
produit pour une position un objet de même structure que la partie `plies`
d'un fichier de résultat.
| Fichier | Rôle |
| --- | --- |
| `positions.json` | Positions d'entrée, versionnées, écrites à la main |
| `results/<id>.json` | Résultats de gnubg, un fichier par position, générés |
| `generate.py` | Script exécuté dans gnubg qui produit `results/` |
Commandes :
```
make reference-image # construit l'image Docker du gnubg de référence
make reference # régénère results/ (une vingtaine de secondes)
make test # vérifie la cohérence (tests/test_reference.sh)
```
`make reference` est déterministe : deux exécutions donnent des fichiers
identiques (`git diff --exit-code tests/reference`).
## Référence utilisée
- **gnubg** : GNU Backgammon 1.08.003, commit
14b67984737ded0f6de43d63dcb789f729d9b06b, compilé par
`docker/gnubg-reference/Dockerfile` (Debian trixie, sans interface
graphique, SIMD SSE2 forcé, drapeaux par défaut de gnubg `-O3 -ffast-math`).
- **Données** : `gnubg.weights` du dépôt, et `gnubg.wd`, `gnubg_os0.bd`,
`gnubg_ts0.bd` produits dans l'image par les outils officiels (`makeweights`,
`makebearoff -o 6 -s 7999999`, `makebearoff -t 6x6`). Leurs sommes SHA-256
sont dans l'image, `/usr/local/share/gnubg/donnees.sha256` ; le moteur
embarqué doit utiliser exactement ces fichiers.
- **Table d'équité de match** : Kazaross-XG2 (`met/Kazaross-XG2.xml`), la
table par défaut de gnubg.
- **Autres réglages** : ceux par défaut de gnubg. En particulier les castors
(beavers) sont autorisés en partie libre (ils ne figurent pas dans le Match
ID) et ignorés en match, et le filtre de coups est le filtre « Normal ».
## Positions d'entrée : `positions.json`
```json
{
"positions": [
{
"id": "opening-31",
"type": "move",
"categories": ["opening"],
"description": "Position de départ, premier coup 3-1, partie libre.",
"position_id": "4HPwATDgc/ABMA",
"match_id": "cIkFAAAAAAAA",
"dice": [3, 1]
}
]
}
```
- `id` : identifiant unique, sert de nom de fichier dans `results/`.
- `type` : `"move"` (dés jetés : quel coup jouer) ou `"cube"` (dés pas encore
jetés : faut-il doubler).
- `categories` : étiquettes de couverture. Valeurs utilisées : `opening`,
`middlegame`, `prime`, `blitz`, `race`, `bearoff`, `edge_case`,
`cube_no_double`, `cube_double_take`, `cube_double_pass`, `cube_redouble`,
`cube_too_good`, `match`, `crawford`, `post_crawford`, `jacoby`.
- `position_id`, `match_id` : la position au format GNU Backgammon. Le Match ID
porte tout le contexte : dés, joueur au trait, videau et propriétaire,
longueur du match, score, Crawford, règle Jacoby.
- `dice` : les dés, redondants avec le Match ID (le générateur vérifie qu'ils
concordent) ; `null` pour une décision de videau.
Pour ajouter une position : obtenir ses identifiants dans gnubg (ils
s'affichent sous le plateau, ou `show gnubgid`), l'ajouter à
`positions.json`, puis `make reference`.
## Résultats : `results/<id>.json`
```json
{
"id": "opening-31",
"type": "move",
"categories": ["opening"],
"description": "…",
"position_id": "4HPwATDgc/ABMA",
"match_id": "cIkFAAAAAAAA",
"gnubg_commit": "14b67984737ded0f6de43d63dcb789f729d9b06b",
"met": "Kazaross-XG2",
"context": {
"dice": [3, 1],
"match_length": 0,
"score_player": 0,
"score_opponent": 0,
"crawford": false,
"jacoby": true,
"beavers": true,
"cube_value": 1,
"cube_owner": "centered"
},
"plies": {
"0": { "evaluation": { … }, "moves": { … } },
"2": { "evaluation": { … }, "moves": { … } }
}
}
```
`context` est le contexte tel que gnubg l'a relu dans le Match ID :
`match_length` vaut 0 en partie libre ; `score_player` est le score du joueur
au trait ; `cube_owner` vaut `centered`, `player` (le joueur au trait) ou
`opponent` ; `beavers` dit si les castors comptent (partie libre seulement).
Voir `results/opening-31.json` pour un fichier complet.
### Niveaux : `plies`
Les clés `"0"` et `"2"` sont des niveaux comptés en **plis à la manière de
gnubg** : 0 pli = évaluation statique du réseau de neurones, 2 plis = deux
demi-coups de recherche. Ce sont les réglages prédéfinis « 0ply » et « 2ply »
de gnubg (`aecSettings` dans `eval.c`), sans bruit : cubeful, déterministe,
élagage (`prune`) désactivé à 0 pli et activé à 2 plis.
### Conventions communes
- **Point de vue** : tout est donné du point de vue du **joueur au trait**
(celui qui a les dés ou qui doit décider de doubler). Une équité positive
lui est favorable.
- **Probabilités** (`probabilities`), entre 0 et 1, issues des sorties du
réseau de gnubg :
- `win` : probabilité de gagner la partie ;
- `win_gammon` : de gagner au moins un gammon (backgammons compris) ;
- `win_backgammon` : de gagner un backgammon ;
- `lose_gammon` : de perdre au moins un gammon (backgammons compris) ;
- `lose_backgammon` : de perdre un backgammon.
La probabilité de perdre est `1 - win`.
- **Équités** : en points pour un videau à 1. En match, gnubg convertit sa
probabilité de gagner le match en « équité monétaire normalisée »
(`mwc2eq`) ; c'est cette valeur qui est donnée.
- **Nombres** : gnubg calcule en float32 ; chaque nombre est écrit sous la
forme décimale la plus courte qui redonne exactement ce float32.
### `evaluation` : la position avant le jet des dés
```json
"evaluation": {
"probabilities": { "win": 0.52483594, "win_gammon": 0.14950693,
"win_backgammon": 0.006851064, "lose_gammon": 0.124798514,
"lose_backgammon": 0.0052878163 },
"equity": 0.07594353
}
```
Sortie de `gnubg.evaluate` : les cinq probabilités et l'équité **sans videau**
(cubeless) correspondante, au score du match, pour la position *avant* que le
joueur au trait jette les dés (même pour une position de type `move`).
### `moves` : classement des coups (type `move`)
```json
"moves": {
"legal_move_count": 16,
"ranking": [
{ "rank": 1, "move": "8/5 6/5", "ply": 1, "equity": 0.20373425,
"equity_diff": 0.0, "probabilities": { … } },
…
]
}
```
- `legal_move_count` : nombre de coups légaux au sens de gnubg, c'est-à-dire de
**positions d'arrivée distinctes** (deux suites de déplacements qui mènent à
la même position comptent une fois). 0 si aucun coup n'est possible.
- `ranking` : les 10 meilleurs coups (tous s'il y en a moins), dans l'ordre de
la commande `hint` de gnubg :
- `move` : notation de gnubg (`FormatMove`), points comptés du point de vue
du joueur au trait, `bar` et `off`, `*` pour une frappe, `(2)` pour un
déplacement répété ;
- `ply` : niveau auquel ce coup a réellement été évalué. À 2 plis, gnubg
évalue d'abord tous les coups à 0 pli puis ne garde pour les plis
suivants que les meilleurs (filtre « Normal » : à 0 pli les 12 meilleurs
au plus dans un écart de 0,24 ; à 1 pli les 6 meilleurs au plus dans un
écart de 0,12). Les coups éliminés gardent une évaluation à 0 ou 1 pli.
Si un seul coup survit à un filtre, gnubg s'arrête là : c'est le cas de
l'exemple ci-dessus (3-1 d'ouverture à 2 plis), où `8/5 6/5` reste seul
après le filtre de 1 pli et garde donc `"ply": 1`, comme dans l'affichage
« Cubeful 1-ply » de gnubg. Les coups forcés restent à 0 pli ;
- `equity` : équité **avec videau** (cubeful) après ce coup ;
- `equity_diff` : `equity` moins celle du premier coup (0 ou négatif) ;
- `probabilities` : les cinq probabilités après ce coup, toujours du point de
vue du joueur qui vient de jouer.
Le classement reproduit exactement `hint` : `FindnSaveBestMoves` avec
`fAnalyse` vrai, puis `RefreshMoveList` qui départage les équités égales.
### `cube` : décision de videau (type `cube`)
```json
"cube": {
"no_double": 0.83056116,
"double_take": 0.9218164,
"double_pass": 1.0,
"optimal": 0.9218164,
"decision": "DOUBLE_TAKE",
"decision_text": "Double, take"
}
```
Sortie de `gnubg.cfevaluate` (`GeneralCubeDecisionE` puis
`FindCubeDecision`), en équités avec videau ramenées à un videau de valeur 1
(même quand le videau est déjà à 2, voir `cube-redouble`) ; exemple tiré de `cube-double-take` :
- `no_double` : si le joueur au trait ne double pas ;
- `double_take` : s'il double et que l'adversaire prend ;
- `double_pass` : s'il double et que l'adversaire passe ; vaut 1 par
construction (en match, la probabilité de gagner le match après le passe,
tirée de la table d'équité, est normalisée à 1 par `mwc2eq`) ;
- `optimal` : l'équité retenue après les meilleures décisions des deux côtés ;
- `decision` : nom de la décision dans l'énumération `cubedecision` de
`eval.h` de gnubg (`NODOUBLE_TAKE`, `DOUBLE_TAKE`, `DOUBLE_PASS`,
`TOOGOOD_PASS`, `REDOUBLE_TAKE`, `NODOUBLE_DEADCUBE`…) ;
- `decision_text` : le libellé anglais correspondant de gnubg.
## Comparaison avec le moteur embarqué
Une concordance au bit près n'est pas attendue (ADR 0005) : `-ffast-math` et
le jeu d'instructions (SSE2 ici, NEON sur téléphone, C générique en
WebAssembly) changent les derniers chiffres. Les tests de concordance devront
donc comparer les nombres avec une tolérance, et accepter qu'entre deux coups
d'équités presque égales l'ordre s'inverse.