ADR 0009 proposé (PWA en TypeScript) ; plan de l'étape 0d.1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-02 20:04:49 +01:00
co-authored by Claude Opus 5.5
parent 14d1186e84
commit 03b26ccba7
5 changed files with 157 additions and 2 deletions
@@ -46,3 +46,9 @@ construire ou tester ; rien n'est embarqué dans l'application.
| Python 3 | PSF | `ustagammon/build-c` | Comparateur des références |
| jq | MIT | `ustagammon/build-c` | Lecture du JSON dans les tests |
| Emscripten | MIT / NCSA | `emscripten/emsdk:3.1.64` | Compilation du moteur en WebAssembly (étape 0b) |
### 2026-11-02 — Outils de l'application web (étape 0d)
Validés par Pierre avec l'ADR 0009, dans Docker, jamais embarqués :
TypeScript (`tsc`, Apache-2.0), Biome (MIT ou Apache-2.0),
`@cucumber/cucumber` (MIT), `dependency-cruiser` (MIT).
+53
View File
@@ -0,0 +1,53 @@
# 0009. Socle : PWA en TypeScript, moteur en WebAssembly
- **Statut** : proposé
- **Date** : 2026-11-02
- **Décideurs** : Pierre (langage, outils), sur proposition de Claude Code ;
le socle sera accepté après les mesures sur le Fairphone 4
## Contexte
L'étape 0 doit trancher entre une PWA (moteur en WebAssembly) et une
application Android native (`docs/etape-0.md`). L'étape 0b a produit le moteur
en WebAssembly (MR !9) : même suite de contrat qu'en natif (écart maximal
9,5e-4 pour une tolérance de 0,002), 7,3 Mo compressés à télécharger (seuil
40 Mo). Sur le PC de développement (i5-2450M, 2011), le meilleur coup à 2 plis
prend au plus 0,3 s sous Chromium et 2,3 s sous Firefox (seuil sur le
téléphone : 3 s). Les mesures sur le Fairphone 4 restent à faire.
L'ADR 0007 renvoyait au choix du socle le langage de l'interface et l'outil de
vérification des dépendances.
## Décision
Sous réserve des mesures sur le Fairphone 4 :
- **Socle : PWA**, installable et utilisable hors ligne, moteur en WebAssembly
dans un Web Worker. L'étape 0c (Android natif) n'est pas faite.
- **Langage : TypeScript**, compilé par `tsc` en modules JavaScript standard,
sans framework d'interface ni empaqueteur.
- **Lint et formatage : Biome.**
- **Scénarios exécutés** (ADR 0008) par `@cucumber/cucumber`, sous Node, sur le
domaine branché au vrai moteur WebAssembly.
- **Règles d'architecture vérifiées** (ADR 0007) par `dependency-cruiser` :
le domaine n'importe ni l'interface, ni le stockage, ni le moteur ; aucun
écran n'importe le moteur.
- Tous ces outils tournent dans Docker et ne sont jamais embarqués dans
l'application.
## Alternatives écartées
- **Android natif (Kotlin, NDK)** : un seul système, une chaîne de build lourde
(Gradle, NDK) sur une machine de 2011, et aucun gain mesuré à ce stade sur
les temps de calcul. Reste le plan B si les mesures du téléphone échouent.
- **JavaScript annoté (JSDoc) vérifié par TypeScript** : pas d'étape de build,
mais des types moins lisibles ; Pierre a choisi TypeScript.
- **Un framework d'interface** (React, Vue…) : inutile pour un plateau et
quelques écrans, et contraire à la règle de simplicité (ADR 0007).
## Conséquences
- La distribution sur F-Droid et le Play Store demandera plus tard une
enveloppe Android minimale autour de la PWA (à décider à l'étape 1).
- Si les mesures du Fairphone 4 dépassent les seuils, cet ADR passe au statut
« obsolète » et l'étape 0c est lancée.
+1
View File
@@ -15,3 +15,4 @@ l'ancien passe au statut « remplacé par NNNN ». Voir l'ADR 0001.
| [0006](0006-merge-requests-systematiques.md) | Merge requests systématiques, fusionnées par Claude Code | accepté |
| [0007](0007-architecture-et-conventions-de-code.md) | Architecture et conventions de code | accepté |
| [0008](0008-glossaire-et-scenarios-source-unique.md) | Glossaire et scénarios : une source unique pour le code, l'aide et les tests | accepté |
| [0009](0009-socle-pwa-typescript.md) | Socle : PWA en TypeScript, moteur en WebAssembly | proposé |
+3 -2
View File
@@ -18,9 +18,10 @@ page de test, page de mesure et guide de test (0b).
| Sous-étape | Contenu | État | Plan |
| --- | --- | --- | --- |
| Mesures | Pierre relève les mesures sur le Fairphone 4, en suivant `docs/guide-test-etape-0.md` | 🔄 | |
| Décision | Socle PWA ou Android natif, consigné dans un ADR — validation de Pierre | ⏳ | |
| Décision | Socle PWA ou Android natif : ADR 0009 proposé (PWA, TypeScript), à accepter après les mesures | 🔄 | |
| 0c | Android natif (NDK, Gradle en ligne de commande, `make deploy` via adb) | ⏸ si 0b échoue | |
| 0d | Plateau minimal jouable sur le socle retenu ; vérifié avec Pierre en suivant `docs/scenarios/jouer-une-partie.feature` | ⏳ | |
| 0d.1 | Domaine « partie » en TypeScript (journal d'événements), scénario exécuté sur le vrai moteur, garde-fous d'architecture | 🔄 | `plans/2026-11-02-domaine-partie.md` |
| 0d.2 | Plateau minimal jouable (PWA hors ligne) ; vérifié avec Pierre en suivant `docs/scenarios/jouer-une-partie.feature` | ⏳ | |
**Critère de fin** : une partie complète jouée sur le Fairphone 4, moins de 2 s
par coup de l'IA en niveau standard, tous les tests au vert.
+94
View File
@@ -0,0 +1,94 @@
# Domaine « partie » en TypeScript, scénarios exécutés, garde-fous
- **Date** : 2026-11-02
- **Étape** : 0d.1
- **Statut** : en cours
- **Exécutant** : sous-agent general-purpose, dans un worktree isolé
## Objectif
Le domaine d'une partie contre l'ordinateur existe en TypeScript, sous forme de
journal d'événements (ADR 0007), branché au moteur WebAssembly par une
interface `Engine`. Le scénario `docs/scenarios/jouer-une-partie.feature`
s'exécute sous Node, sur le vrai moteur, dans `make test`. Les règles
d'architecture et le lien glossaire ↔ code sont vérifiés par les tests.
L'interface graphique et la PWA sont l'étape suivante (0d.2), pas celle-ci.
## Consignes
1. **Outillage** (ADR 0009, ADR 0004) : une image `docker/web/` (Node
`node:22.11.0-alpine3.20` déjà présent, versions npm figées et vérifiées) avec
TypeScript, Biome, `@cucumber/cucumber` et `dependency-cruiser`. Cibles
`make web` (compile `app/` vers `build/web/` avec `tsc`), et intégration à
`make lint` (Biome et `tsc --noEmit`) et `make test`. Étiquette d'image dans
le `Makefile`, `make setup` la construit. `CREDITS.md` à jour.
2. **Arborescence** (à ajuster si plus simple) :
- `app/src/domain/` : le domaine pur, sous-dossier `game/` ;
- `app/src/engine/` : l'adaptateur qui implémente `Engine` avec le moteur
WebAssembly (réutiliser `web/engine-wasm.mjs` et `build/wasm/`) ;
- `app/src/ui/` : vide pour l'instant (étape 0d.2) ;
- `app/tests/` : tests unitaires (runner intégré de Node, `node:test`) et
définitions des étapes Cucumber.
Les pages de prototype existantes (`web/index.html`, `web/bench.html`)
doivent continuer de fonctionner.
3. **Interface `Engine`** définie par le domaine (coups légaux avec position
résultante, meilleur coup pour un niveau, décision de videau, décodage d'une
position en plateau pour l'affichage). Compléter `engine.h` et `engine-cli`
si une information manque (par exemple décoder un Position ID en nombre de
pions par case, savoir si la partie est finie et combien elle rapporte), en
restant simple, avec tests natifs.
4. **Journal d'événements** (ADR 0007, point 3) : événements immuables avec
identifiant, parent et version de format (`GameStarted`, `DiceRolled`,
`MovePlayed`, `CubeOffered`, `CubeTaken`, `CubeDropped`, `GameEnded`). L'état
se calcule en rejouant le journal. Une branche est un nouvel événement
accroché à un ancien ; annuler revient à repartir du parent. Le hasard des
dés est injecté (source de dés passée au domaine).
5. **Règles de la partie de 0d** : partie libre contre l'ordinateur (Jacoby
selon les réglages par défaut de gnubg) ; l'ordinateur joue le meilleur coup
au niveau « standard » (choisir un niveau qui tienne les 2 s sur téléphone :
2 plis d'après les mesures 0b, à confirmer) ; il décide du videau avec le
moteur ; le joueur peut prendre ou passer, et peut lui-même doubler (le
moteur décide alors si l'ordinateur prend ou passe). Le joueur saisit son
coup demi-coup par demi-coup (case de départ, case d'arrivée) ; le domaine
n'accepte que ce qui mène à un coup légal complet. Aucune règle du jeu n'est
réimplémentée : tout vient du moteur.
6. **Glossaire ↔ code** (ADR 0008) : chaque type du domaine qui correspond à
un terme porte `Glossaire : docs/glossaire/<fiche>.md` en commentaire, et la
fiche reçoit `code: <Type>`. Ajouter les fiches manquantes. Étendre
`tests/test_glossary.sh` pour vérifier le lien dans les deux sens.
7. **Scénarios exécutés** : définitions des étapes en français pour
`docs/scenarios/jouer-une-partie.feature`, branchées sur le domaine et le
vrai moteur WebAssembly (sous Node), avec des dés imposés pour être
reproductibles. Si une étape du scénario est impossible à exécuter telle
quelle, ajuster le scénario en gardant son sens pour le joueur, et le dire.
8. **Garde-fous d'architecture** (dependency-cruiser) : le domaine n'importe
ni `engine/`, ni `ui/`, ni rien de spécifique au navigateur ; `ui/`
n'importe pas `engine/` ; pas de dépendance circulaire. Vérifier qu'une
violation volontaire fait échouer `make test`.
Contraintes :
- `CLAUDE.md`, ADR 0004, 0007, 0008, 0009 : simplicité avant tout, pas de
patron de conception sans besoin, identifiants en anglais, commentaires et
docs en français, tout outil dans Docker, `-j2` ;
- ne pas modifier `engine/gnubg/` ni les références ;
- `make lint` et `make test` doivent passer ; commits petits, en français, avec
la ligne `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>` ;
- ne pas pousser ni créer de merge request ; ne rien simuler.
Rapport attendu : ce qui a été fait, l'API du domaine en quelques lignes, les
fiches de glossaire ajoutées, le résultat des scénarios, les vérifications des
garde-fous, les difficultés, les écarts au plan.
## Critères de réussite
- `make test` exécute le scénario « Jouer une partie contre l'ordinateur » sur
le vrai moteur et passe.
- Une partie complète (jusqu'à `GameEnded`) se joue dans un test, l'ordinateur
contre une stratégie simple du joueur, avec des dés imposés.
- Les garde-fous d'architecture et le lien glossaire ↔ code échouent sur une
violation volontaire.
- `make lint` et `make test` passent.
## Résultat
À remplir à la clôture.