Étape 0d.1 : domaine « partie » en TypeScript, scénario exécuté ; ADR 0009 accepté #11

Merged
pierre merged 11 commits from plateau into main 2026-11-02 20:14:20 +00:00
Owner

Pourquoi

Étape 0d.1 : avant le plateau jouable (0d.2), il faut la logique d'une partie contre l'ordinateur, sans interface. Elle doit être testable et organisée selon les règles d'architecture : domaine pur, journal d'événements, moteur derrière une interface (ADR 0007). La documentation utilisateur doit aussi être exécutée comme test (ADR 0008). Les mesures sur le Fairphone 4 ont confirmé le socle PWA en TypeScript (ADR 0009), qui est accepté dans cette MR avec ces mesures.

Quoi

  • ADR 0009 accepté, avec les mesures du Fairphone 4 (Chrome 156, 3 essais) : meilleur coup à 2 plis en 348 ms au pire pour un seuil de 3 s, videau en 148 ms, 7,3 Mo à télécharger. Socle PWA, TypeScript, Biome, @cucumber/cucumber, dependency-cruiser. L'étape 0c (Android natif) est abandonnée. ADR 0004 amendé avec ces outils, validés par Pierre.
  • Domaine app/src/domain/game/ (environ 640 lignes) :
    • Game.start(engine, { dice, ply, setup }) ;
    • actions du joueur : roll(), playHalfMove({from, to}), cancelHalfMoves(), double(), take(), drop() ;
    • tour de l'ordinateur : playComputerTurn(). Il double si le moteur le recommande, sinon il joue le meilleur coup à 2 plis (STANDARD_PLY) ;
    • retour en arrière : undo() et goBackTo(id). Une action faite après un retour ouvre une branche.
  • Journal d'événements (events.ts) : GameStarted, DiceRolled, MovePlayed, CubeOffered, CubeTaken, CubeDropped, GameEnded. Les événements sont gelés et forment un arbre ; chacun a un id, un parent et une version. L'état se recalcule sans le moteur (state.ts), et les dés sont injectés.
  • Interface Engine définie par le domaine, et son adaptateur WebAssembly app/src/engine/wasm-engine.ts.
  • Moteur complété, avec tests natifs :
    • validation demi-coup par demi-coup : un dé à la fois, avec GenerateMoves de gnubg, et le coup complet doit être un coup légal de la liste ;
    • décodage d'une position en plateau ;
    • résultat de fin de partie : simple, gammon ou backgammon, videau, Jacoby ;
    • encodage du Match ID ;
    • droit de doubler.
  • Tests :
    • 11 tests unitaires sur un faux moteur ;
    • deux parties complètes jusqu'à GameEnded sur le vrai moteur WebAssembly, à 0 puis 2 plis. Une partie à 2 plis prend 1,6 s sous Node ;
    • le scénario docs/scenarios/jouer-une-partie.feature est exécuté par Cucumber sur le domaine et le vrai moteur : 7 scénarios, 25 étapes ;
    • garde-fous dependency-cruiser : le domaine n'importe que le domaine, ui/ n'importe pas engine/, pas de cycle. tests/test_web.sh vérifie qu'une violation volontaire de chaque règle est bien détectée.
  • Glossaire relié au code :
    • deux fiches ajoutées, demi-coup.md (HalfMove) et plateau.md (Board), à relire par Pierre ;
    • code: renseigné sur 8 fiches ;
    • tests/test_glossary.sh vérifie le lien dans les deux sens.
  • Outillage : image ustagammon/web:1 (docker/web/) ; make web ; make lint lance Biome et tsc --noEmit ; make build compile le moteur WebAssembly et l'application.

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.

Le moteur reste la seule autorité pour les règles. Le domaine ne fait que ce que le moteur ne gère pas, faute d'état de partie : alterner les tours, gérer le jet d'ouverture (relance en cas d'égalité), doubler la valeur du videau et le donner à celui qui prend, faire perdre à la valeur d'avant le double celui qui passe.

Deux retouches du scénario, sans changer son sens pour le joueur :

  • l'exemple de coup interdit « 8/4 » avec 3-1 était en fait un coup permis (8/5/4). Il devient « 13/12 », case tenue par l'ordinateur ;
  • un constat portait le même texte qu'une situation de départ d'un autre scénario. Il devient « c'est ensuite au tour de l'ordinateur ».

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

  • make setup && make build, puis make lint (Biome et TypeScript inclus) ;
  • make test : 10/10. Comparaisons native et WebAssembly aux références, 7 scénarios Cucumber, garde-fous sans violation, test du site de production ;
  • relecture du domaine : fichiers courts (313 lignes au plus) et commentés en français. Le journal d'événements est documenté comme contrat durable.

Choix et compromis

  • Cucumber 12.9 plutôt que 13, qui exige Node 22.12 alors que l'image Node imposée est la 22.11.
  • Un dé par demi-coup : 8/4 avec 3-1 se saisit 8/5 puis 5/4. C'est simple et sans ambiguïté ; l'interface pourra proposer plus tard le déplacement direct.
  • Coup enregistré dès qu'il est complet et légal : dans le cas théorique d'un coup encore prolongeable en un autre coup légal, le joueur ne pourrait pas le prolonger.
  • Videau : l'ordinateur ne double pas sur un double « facultatif » ; ni l'abandon ni les castors ne sont gérés à ce stade.
  • @types/node (MIT) ajouté pour compiler les tests ; les autres outils sont ceux validés.

Risques et déploiement

  • make setup construit une image de plus (ustagammon/web:1).
  • Le format du journal d'événements devient un contrat durable (version 1) : le changer demandera une migration et un ADR.
  • L'interface engine.h s'enrichit sans casser la sortie de reference : les suites de contrat native et WebAssembly passent toujours.
  • Rien n'est publié par cette MR : le site en ligne ne change pas tant que make deploy-prod n'est pas relancé.

Retour arrière : annuler le commit de fusion.

Comment vérifier

git switch main && git pull
make setup && make build
make lint && make test
./build/engine-cli halfmoves 4HPwATDgc/ABMA MAEAAAAAAAAA 31 "8/5 6/5"

Hors périmètre

  • Le plateau jouable, la PWA hors ligne et la vérification sur le téléphone avec Pierre : étape 0d.2.
  • La relecture des nouvelles fiches de glossaire par Pierre.

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

## Pourquoi Étape 0d.1 : avant le plateau jouable (0d.2), il faut la logique d'une partie contre l'ordinateur, sans interface. Elle doit être testable et organisée selon les règles d'architecture : domaine pur, journal d'événements, moteur derrière une interface (ADR 0007). La documentation utilisateur doit aussi être exécutée comme test (ADR 0008). Les mesures sur le Fairphone 4 ont confirmé le socle PWA en TypeScript (ADR 0009), qui est accepté dans cette MR avec ces mesures. ## Quoi - **ADR 0009 accepté**, avec les mesures du Fairphone 4 (Chrome 156, 3 essais) : meilleur coup à 2 plis en 348 ms au pire pour un seuil de 3 s, videau en 148 ms, 7,3 Mo à télécharger. Socle PWA, TypeScript, Biome, `@cucumber/cucumber`, `dependency-cruiser`. L'étape 0c (Android natif) est abandonnée. ADR 0004 amendé avec ces outils, validés par Pierre. - **Domaine `app/src/domain/game/`** (environ 640 lignes) : - `Game.start(engine, { dice, ply, setup })` ; - actions du joueur : `roll()`, `playHalfMove({from, to})`, `cancelHalfMoves()`, `double()`, `take()`, `drop()` ; - tour de l'ordinateur : `playComputerTurn()`. Il double si le moteur le recommande, sinon il joue le meilleur coup à 2 plis (`STANDARD_PLY`) ; - retour en arrière : `undo()` et `goBackTo(id)`. Une action faite après un retour ouvre une branche. - **Journal d'événements** (`events.ts`) : `GameStarted`, `DiceRolled`, `MovePlayed`, `CubeOffered`, `CubeTaken`, `CubeDropped`, `GameEnded`. Les événements sont gelés et forment un arbre ; chacun a un `id`, un `parent` et une `version`. L'état se recalcule sans le moteur (`state.ts`), et les dés sont injectés. - **Interface `Engine`** définie par le domaine, et son adaptateur WebAssembly `app/src/engine/wasm-engine.ts`. - **Moteur complété**, avec tests natifs : - validation demi-coup par demi-coup : un dé à la fois, avec `GenerateMoves` de gnubg, et le coup complet doit être un coup légal de la liste ; - décodage d'une position en plateau ; - résultat de fin de partie : simple, gammon ou backgammon, videau, Jacoby ; - encodage du Match ID ; - droit de doubler. - **Tests** : - 11 tests unitaires sur un faux moteur ; - deux parties complètes jusqu'à `GameEnded` sur le vrai moteur WebAssembly, à 0 puis 2 plis. Une partie à 2 plis prend 1,6 s sous Node ; - **le scénario `docs/scenarios/jouer-une-partie.feature` est exécuté par Cucumber** sur le domaine et le vrai moteur : 7 scénarios, 25 étapes ; - garde-fous `dependency-cruiser` : le domaine n'importe que le domaine, `ui/` n'importe pas `engine/`, pas de cycle. `tests/test_web.sh` vérifie qu'une violation volontaire de chaque règle est bien détectée. - **Glossaire relié au code** : - deux fiches ajoutées, `demi-coup.md` (`HalfMove`) et `plateau.md` (`Board`), à relire par Pierre ; - `code:` renseigné sur 8 fiches ; - `tests/test_glossary.sh` vérifie le lien dans les deux sens. - **Outillage** : image `ustagammon/web:1` (`docker/web/`) ; `make web` ; `make lint` lance Biome et `tsc --noEmit` ; `make build` compile le moteur WebAssembly et l'application. ## 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. Le moteur reste la seule autorité pour les règles. Le domaine ne fait que ce que le moteur ne gère pas, faute d'état de partie : alterner les tours, gérer le jet d'ouverture (relance en cas d'égalité), doubler la valeur du videau et le donner à celui qui prend, faire perdre à la valeur d'avant le double celui qui passe. Deux retouches du scénario, sans changer son sens pour le joueur : - l'exemple de coup interdit « 8/4 » avec 3-1 était en fait un coup permis (8/5/4). Il devient « 13/12 », case tenue par l'ordinateur ; - un constat portait le même texte qu'une situation de départ d'un autre scénario. Il devient « c'est ensuite au tour de l'ordinateur ». Vérification par la session principale, après fusion de `main` dans la branche (MR !9 et !10) : - `make setup && make build`, puis `make lint` (Biome et TypeScript inclus) ; - `make test` : 10/10. Comparaisons native et WebAssembly aux références, 7 scénarios Cucumber, garde-fous sans violation, test du site de production ; - relecture du domaine : fichiers courts (313 lignes au plus) et commentés en français. Le journal d'événements est documenté comme contrat durable. ## Choix et compromis - **Cucumber 12.9** plutôt que 13, qui exige Node 22.12 alors que l'image Node imposée est la 22.11. - **Un dé par demi-coup** : 8/4 avec 3-1 se saisit 8/5 puis 5/4. C'est simple et sans ambiguïté ; l'interface pourra proposer plus tard le déplacement direct. - **Coup enregistré dès qu'il est complet et légal** : dans le cas théorique d'un coup encore prolongeable en un autre coup légal, le joueur ne pourrait pas le prolonger. - **Videau** : l'ordinateur ne double pas sur un double « facultatif » ; ni l'abandon ni les castors ne sont gérés à ce stade. - **`@types/node` (MIT)** ajouté pour compiler les tests ; les autres outils sont ceux validés. ## Risques et déploiement - `make setup` construit une image de plus (`ustagammon/web:1`). - **Le format du journal d'événements devient un contrat durable** (version 1) : le changer demandera une migration et un ADR. - **L'interface `engine.h` s'enrichit** sans casser la sortie de `reference` : les suites de contrat native et WebAssembly passent toujours. - Rien n'est publié par cette MR : le site en ligne ne change pas tant que `make deploy-prod` n'est pas relancé. Retour arrière : annuler le commit de fusion. ## Comment vérifier ``` git switch main && git pull make setup && make build make lint && make test ./build/engine-cli halfmoves 4HPwATDgc/ABMA MAEAAAAAAAAA 31 "8/5 6/5" ``` ## Hors périmètre - Le plateau jouable, la PWA hors ligne et la vérification sur le téléphone avec Pierre : étape 0d.2. - La relecture des nouvelles fiches de glossaire par Pierre. Generated with Claude Code https://claude.com/claude-code
pierre added 11 commits 2026-11-02 20:14:19 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ce dont le domaine d'une partie a besoin, sans réimplémenter de règle :
décodage d'un Position ID en plateau et position vue de l'adversaire,
résultat d'une partie finie (gammon, règle Jacoby), codage d'un Match ID,
vérification de demi-coups saisis un par un (la légalité vient de
GenerateMoves de gnubg), demi-coups de chaque coup légal, droit de doubler
et recommandations de double et de prise. Nouvelles commandes d'engine-cli
position, halfmoves et matchid, avec leurs tests natifs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Image docker/web/ (Node 22.11) avec TypeScript 6.0.3, @types/node,
Biome 2.5.15, @cucumber/cucumber 12.9.0 et dependency-cruiser 18.6.0,
versions figées (ADR 0009). make web compile app/ dans build/web/ ;
make lint passe Biome et tsc --noEmit, et vérifie à part que le domaine
compile sans les types de Node ni du navigateur. make setup construit
l'image ; CREDITS.md à jour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
app/src/domain/game/ : une partie libre contre l'ordinateur, enregistrée
comme un arbre d'événements immuables et versionnés (GameStarted,
DiceRolled, MovePlayed, CubeOffered, CubeTaken, CubeDropped, GameEnded) ;
l'état se recalcule en les rejouant, annuler repart du parent, une action
après un retour ouvre une branche. Dés injectés. Le joueur saisit son coup
demi-coup par demi-coup ; l'ordinateur joue et décide du videau à 2 plis.
Toutes les règles viennent du moteur, derrière l'interface Engine que le
domaine définit.

app/src/engine/wasm-engine.ts : l'adaptateur Engine sur les commandes
d'engine-cli du moteur WebAssembly.

Tests : unitaires avec un faux moteur, et deux parties complètes jusqu'à
GameEnded sur le vrai moteur, avec des dés imposés.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Les étapes de docs/scenarios/jouer-une-partie.feature pilotent le domaine
sur le vrai moteur WebAssembly, avec des dés imposés (Cucumber, sous Node).
Deux retouches du scénario : 8/4 avec 3-1 est un coup permis (8/5/4),
remplacé par 13/12 (case tenue par l'ordinateur) ; « c'est ensuite au tour
de l'ordinateur » distingue le constat de la situation de départ du même nom.

dependency-cruiser vérifie que le domaine n'importe que le domaine, que
l'interface n'appelle pas le moteur et qu'il n'y a pas de cycle ;
tests/test_web.sh prouve aussi que des violations volontaires sont
détectées.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fiches Demi-coup (HalfMove) et Plateau (Board) ajoutées ; champ code des
fiches Coup, Jet, Pli, Moteur, Identifiant de position, Videau, Événement de
partie et Partie. tests/test_glossary.sh vérifie que chaque référence
« Glossaire : » du code mène à une fiche qui nomme le type, et que chaque
champ code désigne un type qui renvoie à sa fiche.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fusion du travail du sous-agent (branche plateau-domaine-travail) ; plan
supprimé à la clôture, son résultat est repris dans la merge request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pierre merged commit b4421dcea5 into main 2026-11-02 20:14:20 +00:00
Author
Owner

Correction de la section « Comment vérifier » : la commande halfmoves ne prend pas de Match ID. La bonne syntaxe est :

./build/engine-cli halfmoves 4HPwATDgc/ABMA 31 "8/5 6/5"

Elle répond "valid": true, "complete": true et la position obtenue 4HPwATCwZ/ABMA.

Correction de la section « Comment vérifier » : la commande `halfmoves` ne prend pas de Match ID. La bonne syntaxe est : ``` ./build/engine-cli halfmoves 4HPwATDgc/ABMA 31 "8/5 6/5" ``` Elle répond `"valid": true, "complete": true` et la position obtenue `4HPwATCwZ/ABMA`.
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#11