Conseil du maître : rang cité seulement pour un coup proche ou une imprécision au 2e ou 3e choix ; 1d faite, plan clos

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-10 05:36:11 +01:00
co-authored by Claude Opus 5.5
parent 4fa54ad3f3
commit c7a54a8c81
7 changed files with 49 additions and 153 deletions
+8 -6
View File
@@ -73,7 +73,7 @@ export const PHRASE_KEYS: readonly string[] = [
"coup.douteux",
"coup.mauvais",
"coup.tres-mauvais",
"coup.tres-mauvais.loin",
"coup.loin",
"fait.frappe",
"fait.frappe-plusieurs",
"fait.point",
@@ -145,13 +145,15 @@ function moveWords(phrases: MasterPhrases, advice: MoveAdvice): MasterWords {
base = fill("coup.meilleur");
fact = selfFact(phrases, advice.eventId, advice.played.notation, advice.playedFacts);
} else {
// Le rang n'est cité que pour un bon coup non meilleur ou une
// imprécision, au 2e ou 3e choix : il mesure mal la gravité d'une
// erreur, et le classement est dans les chiffres du moteur.
const near = advice.rank <= 3;
const key = {
good: "coup.proche",
doubtful: "coup.douteux",
good: near ? "coup.proche" : "coup.loin",
doubtful: near ? "coup.douteux" : "coup.loin",
bad: "coup.mauvais",
// Une grosse erreur ne cite son rang que s'il est proche (2e ou
// 3e choix) ; plus loin, le classement est dans les chiffres.
veryBad: advice.rank <= 3 ? "coup.tres-mauvais" : "coup.tres-mauvais.loin",
veryBad: "coup.tres-mauvais",
}[verdict];
base = fill(key);
fact = differenceFact(phrases, advice);
+8 -8
View File
@@ -121,14 +121,14 @@ export function createAdviceView(options: AdviceViewOptions): AdviceView {
verdictLine.append(icon(VERDICT_ICONS[words.verdict]));
element(verdictLine, "span", { text: `${words.word} · ${words.context}` });
title.textContent = words.title;
element(sheet, "p", {
id: "explain-phrase",
className: "explain-phrase",
// Le titre dit déjà le coup du maître : la phrase ne le répète pas.
text: words.phrase.startsWith(`${words.title}. `)
? words.phrase.slice(words.title.length + 2)
: words.phrase,
});
// Le titre dit déjà le coup du maître : la phrase ne le répète pas
// (et disparaît si elle ne disait que lui).
const rest = words.phrase.startsWith(`${words.title}.`)
? words.phrase.slice(words.title.length + 1).trim()
: words.phrase;
if (rest !== "") {
element(sheet, "p", { id: "explain-phrase", className: "explain-phrase", text: rest });
}
const body = element(sheet, "div", { className: "explain-body" });
const boardBox = element(body, "div", { className: "explain-board-box" });
+12 -12
View File
@@ -163,25 +163,25 @@ test("le coup du maître est nommé, et le fait qui distingue les coups vient du
assert.deepEqual(worst.bestFacts.madePoints, [5]);
const said = masterWords(phrases, worst);
assert.equal(said.title, "J'aurais joué 8/5 6/5");
assert.equal(
said.phrase,
"J'aurais joué 8/5 6/5. Votre 24/23 13/10 était le 2e choix. Mon coup fait votre point 5.",
);
assert.equal(said.phrase, "J'aurais joué 8/5 6/5. Mon coup fait votre point 5.");
});
test("grosse erreur : le rang n'est cité que pour un 2e ou 3e choix", async () => {
test("le rang n'est cité que pour un bon coup non meilleur ou une imprécision, au 2e ou 3e choix", async () => {
const engine = await loadNodeEngine();
const worst = (await adviceOn(
engine,
await playOpening(engine, [3, 1], "24/23 13/10"),
)) as MoveAdvice;
for (const rank of [2, 3, 4, 7, 15]) {
const { phrase } = masterWords(phrases, { ...worst, rank });
if (rank <= 3) {
assert.ok(phrase.includes(`était le ${rank}e choix`), phrase);
} else {
assert.ok(!phrase.includes("choix"), phrase);
assert.equal(phrase, "J'aurais joué 8/5 6/5. Mon coup fait votre point 5.");
for (const skill of ["none", "doubtful", "bad", "veryBad"] as const) {
for (const rank of [2, 3, 4, 7, 15]) {
const { phrase } = masterWords(phrases, { ...worst, skill, rank });
const cited = (skill === "none" || skill === "doubtful") && rank <= 3;
if (cited) {
assert.ok(phrase.includes(`le ${rank}e choix`), `${skill} ${rank} : ${phrase}`);
} else {
assert.ok(!phrase.includes("choix"), `${skill} ${rank} : ${phrase}`);
assert.equal(phrase, "J'aurais joué 8/5 6/5. Mon coup fait votre point 5.");
}
}
}
});
+1 -1
View File
@@ -20,7 +20,7 @@ Repris du cadrage (modules, écrans clés, IA générative en V1) et des ADR 001
| 1a. Moteur pour l'analyse (fait) | Niveaux de jeu (plis et bruit de GNU Backgammon : débutant à expert), classement des coups d'un jet avec équités, analyse d'un coup joué et d'une décision de videau, chance d'un jet ; exposés par `engine.h`, `engine-cli` et le Worker, avec positions de référence comparées à gnubg de bureau | — |
| 1b. Parties enregistrées (fait) | Stockage IndexedDB derrière l'interface de stockage existante : plusieurs parties, reprise automatique, branches (« Rejouer d'ici » crée une branche, l'originale reste intacte et rechargeable), export SGF avec variantes | — |
| 1c. Match et réglages (fait) | Partie libre ou match en N points, règle Crawford, règle Jacoby, choix du niveau de l'ordinateur, conseil du maître activable ou non ; écran de nouvelle partie | 1a, 1b |
| 1d. Conseil du maître | Après chaque coup du joueur, jamais avant (ADR 0011) : appréciation du moteur (bon coup, imprécision, erreur, grosse erreur) avec mot, icône et couleur, phrase du maître fondée sur les chiffres du moteur, coup recommandé montré sur demande ; aussi pour les décisions de videau ; calcul en arrière-plan sans retarder le jeu | 1a |
| 1d. Conseil du maître (fait) | Après chaque coup du joueur, jamais avant (ADR 0011) : appréciation du moteur (bon coup, imprécision, erreur, grosse erreur) avec mot, icône et couleur, phrase du maître fondée sur les chiffres du moteur, coup recommandé montré sur demande ; aussi pour les décisions de videau ; calcul en arrière-plan sans retarder le jeu | 1a |
| 1e. Fin de partie et analyse | Bilan (ADR 0011) : résultat, coups par appréciation, taux d'erreur comparé à la chance, « le coup à revoir » ; écran d'analyse : graphe d'équité (toucher un point ouvre la position), liste des erreurs filtrable, visualiseur pas à pas coup joué contre meilleur coup, « Rejouer d'ici » | 1a, 1b, 1d |
| 1f. Prompts à copier | Gabarits versionnés et testés (`docs/prompts/` ou équivalent, source unique), bouton « Demander à une IA » sur le plateau, le conseil du maître, l'analyse et la fin de partie ; le moteur décide, l'IA explique | 1d, 1e |
| 1g. Accueil, tutoriel et leçons | Accueil (reprendre, nouvelle partie, prochaine leçon, progression résumée), tutoriel des règles interactif et illustré (0e.5), cinq premières leçons numérotées (ADR 0011) avec positions et « à vous de jouer », progression locale ; contenus CC BY-SA 4.0 en brouillon, à relire par un joueur fort | 0e.5, 1a |
+19 -10
View File
@@ -33,7 +33,7 @@ mots entre accolades sont remplis avec les données du moteur :
| `{best}` | le coup du maître, en notation de gnubg (« 8/5 6/5 ») |
| `{played}` | votre coup, même notation |
| `{move}` | le coup dont parle le fait (le vôtre ou celui du maître) |
| `{rank}` | le rang de votre coup dans le classement du moteur (2, 3…) |
| `{rank}` | le rang de votre coup dans le classement du moteur (2 ou 3 seulement) |
| `{points}` | « votre point 5 » ou « vos points 5 et 7 » |
| `{roll}` | le jet, « 3-1 » |
| `{hits}` | le nombre de pions frappés, en chiffres |
@@ -76,26 +76,35 @@ Tout ce qui précède la première section `## mot.…` n'est pas lu.
## coup.proche
« Bon coup » sans être le meilleur, votre coup étant le 2e ou le 3e choix du
moteur. Le rang n'est cité qu'ici et dans coup.douteux : il mesure mal la
gravité d'une erreur (un 2e choix peut être loin du premier), et le
classement complet est dans « Voir les chiffres du moteur ».
- Tout près : j'aurais joué {best}, votre coup était le {rank}e choix.
## coup.douteux
Imprécision, votre coup étant le 2e ou le 3e choix.
- J'aurais joué {best}. Votre coup était le {rank}e choix, tout près.
## coup.loin
« Bon coup » non meilleur ou imprécision, votre coup étant au-delà du 3e
choix : le rang n'est pas cité.
- J'aurais joué {best}.
## coup.mauvais
- J'aurais joué {best}. Votre {played} était le {rank}e choix.
Erreur : le rang n'est jamais cité.
- J'aurais joué {best}.
## coup.tres-mauvais
Votre coup est le 2e ou le 3e choix du moteur.
- J'aurais joué {best}. Votre {played} était le {rank}e choix.
## coup.tres-mauvais.loin
Votre coup est plus loin dans le classement : le rang n'est pas dit ici, il
se lit dans « Voir les chiffres du moteur ».
Grosse erreur : le rang n'est jamais cité.
- J'aurais joué {best}.
-115
View File
@@ -1,115 +0,0 @@
# Conseil du maître (1d)
- **Date** : 2026-11-03
- **Étape** : 1d (`docs/etape-1.md`)
- **Statut** : prévu (après 1c)
- **Exécutant** : sous-agent general-purpose, dans un worktree
## Objectif
Le coach bienveillant, « mis en avant mais discret » (Pierre, 2026-11-03) :
après chaque coup et chaque décision de videau du joueur, jamais avant
(ADR 0011), une appréciation et une phrase du maître fondées sur les chiffres
du moteur.
## Références
- ADR 0011 ; maquette « conseil du maître après le coup » du canevas
https://claude.ai/artifact/CQw6vbDpi6XQqVWQa4sANe ; règles de la charte
(`docs/identite/charte/`, dont interaction.md) ; la ligne réservée de 44 px
de la révision UX.
- Moteur : `analyseMove`, `analyseCube`, `luck`, seuils `SKILL_THRESHOLDS`
(gnubg : 0,03 / 0,06 / 0,12).
## Consignes
1. **Analyse en arrière-plan** : après la validation d'un coup (ou une
décision de videau), l'analyse à 2 plis part dans le Worker sans retarder le
tour de l'ordinateur ; son résultat est rattaché au coup dans le journal
(événement d'analyse rétrocompatible, ou cache séparé justifié) pour servir
au bilan (1e) sans recalcul.
2. **Ligne du maître** : icône d'appréciation, mot en couleur (« Bon coup. »,
« Imprécision. », « Erreur. », « Grosse erreur. » (choix de Pierre du 2026-11-09, plutôt que « gaffe ») : correspondance avec les seuils
de gnubg documentée), phrase courte en italique, chevron. Toucher ouvre la
feuille d'explication : coup joué et meilleur coup montrés sur le plateau
(pions animés, ADR 0011 « comparaison visuelle »), écart d'équité seulement
sur demande, lien vers la fiche du glossaire du concept en jeu si le moteur
permet de le déterminer, sinon rien.
3. **Phrases du maître** : gabarits versionnés en français dans une source
unique sous `docs/` (CC BY-SA 4.0). Référence de ton, approuvée par Pierre
le 2026-11-09 : la section « Les phrases du maître » de
`docs/identite/charte/ecrans-etape-1.md` (1re personne et vouvoiement,
faits du moteur uniquement, moins de 140 caractères, ton sobre sans
exclamation, formules qui tournent pour les bons coups, exemples). Un test
vérifie la longueur et l'absence de « ! » sur chaque gabarit rendu. Les
phrases livrées passent par la session « identité » puis par Pierre pour
la relecture finale.
4. **Discret** : rien n'apparaît pendant que le joueur réfléchit ; la ligne
s'efface à l'action suivante ; désactivable (réglage de 1c et menu).
5. **Videau** : même traitement pour doubler, ne pas doubler, prendre, passer.
6. Scénarios Gherkin (bon coup, erreur avec meilleur coup montré, conseil
désactivé, décision de videau commentée) avec positions et dés imposés ;
tests unitaires des gabarits (chaque gabarit rendu sur des analyses réelles
du moteur). `make lint`, `make test`, `make test-browser`, captures pour la
recette design.
## Critères de réussite
Après une erreur volontaire dans une position connue, la ligne du maître dit
« Erreur. », nomme le coup recommandé par le moteur et le montre sur demande ;
le tour de l'ordinateur n'est pas retardé ; les trois commandes passent.
## Résultat
Réalisé le 2026-11-10 sur la branche `conseil-du-maitre` (partie de 1c
fusionnée, corrections de recette C1 à C3 comprises).
- **Analyse sans retard** : un second moteur, dans son propre Web Worker
(`app/src/main.ts`), fait les analyses à 2 plis ; le premier, qui ne
traite qu'une commande à la fois, reste à la partie. Le second démarre à
la première analyse, une fois le premier chargé et le service worker
installé (au plus 20 s), pour ne pas croiser deux téléchargements des
données du moteur. Les décisions analysées : coup, jet sans doubler
(aurait-il fallu doubler ?), double, prise, refus
(`app/src/domain/analysis/analyse.ts`). Le domaine a une méthode de plus
dans `Engine` : `rankMoves` (commande `moves` d'engine-cli, déjà
existante : le moteur n'est pas recompilé).
- **Persistance** : le conseil est rattaché à l'événement de la décision,
dans un champ facultatif `advice` du journal, versionné
(`ADVICE_VERSION`) ; migration testée (journal sans conseil, conseil
d'une autre version ou orphelin). ADR 0007 amendé.
- **Ligne du maître** (portrait) : à la place du texte du dernier coup,
dans la ligne de 44 px ; pictogramme et mot en couleur, phrase en
italique tronquée, chevron ; lien vers la feuille. Rien avant le coup ;
effacée à l'action suivante ; analyse trop tardive : pas de ligne, mais
le conseil reste au journal. Un double manqué avant le jet passe avant le
coup dans la ligne (le jet seul ne montre rien, le joueur réfléchit).
- **Paysage** : la charte ne place pas la ligne ; choix proposé : une
pastille (pictogramme et mot) au début de la bande centrale, avant la
consigne, lien vers la même feuille. À valider en recette design.
- **Feuille d'explication** : `#/explication/<événement>` (ADR 0013 amendé),
rechargeable ; mot et contexte, titre à la première personne, phrase,
« Votre coup » / « Le coup du maître » joués pion par pion (le maître en
`verdict-good`), « Voir les chiffres du moteur » replié, lien vers la
fiche du glossaire pour le videau seulement (le moteur ne nomme pas de
concept pour un coup). « Demander à une IA » attend 1f.
- **Phrases** : `docs/maitre/phrases.md` (CC BY-SA 4.0), lues à
l'exécution ; test de longueur, d'absence de « ! » et de repère non
rempli sur de vraies analyses, et sur tous les gabarits remplis au plus
long. Vérifiées par la session design ; retouches P1 (grosse erreur :
plus de « sur N », rang cité seulement pour un 2e ou 3e choix) et P2
(« Mon coup frappe un pion. ») appliquées et testées. À soumettre à
Pierre.
- **Réglage** : interrupteur « Conseil du maître » dans le menu, en plus de
l'écran « Nouvelle partie » ; coupé, la ligne reste vide et les coups
restent analysés pour le bilan.
- **Tests** : `app/tests/advice.test.ts`, `app/tests/advice-tracker.test.ts`
(dont le tour de l'ordinateur joué pendant qu'une analyse attend),
scénario `docs/scenarios/comprendre-mes-coups.feature`, et
`tests/browser/maitre.mjs` (captures dans `build/browser-shots/maitre/`).
Écarts et points à trancher : en portrait, la ligne du maître remplace le
texte « L'ordinateur a joué … » tant qu'elle est là (les traces et
« Revoir » restent) ; la pastille du paysage ne montre pas la phrase ; le
mot du videau est « Bonne décision » dans tous les cas (la charte cite
aussi « Bon choix »).
+1 -1
View File
@@ -1,6 +1,6 @@
// maitre.mjs -- le conseil du maître (étape 1d) joué par l'interface, dans
// un vrai navigateur sans écran (Playwright), sur le site servi par l'image
// de production (docs/plans/2026-11-03-conseil-du-maitre.md).
// de production (étape 1d, docs/etape-1.md ; charte, ecrans-etape-1.md).
//
// Copyright (C) 2026 les auteurs d'Ustagammon.
// Licence : GPLv3 ou ultérieure (voir LICENSE).