Files
livrets/docs/superpowers/plans/2026-08-31-generation-ponderee-questions.md
T

147 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Génération pondérée des questions — Plan d'implémentation
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Remplacer le tirage uniforme des facteurs par une pondération progressive (P(k) ∝ k) pour éviter les questions trop faciles (`1 × 2`, `3 × ? = 3`).
**Architecture:** Un seul fichier de production modifié : `QuestionGenerator.kt`. Chaque facteur est tiré indépendamment avec un poids linéaire sur 1..maxTable (tirage par cumul de poids, aucune boucle de rejet sur la difficulté). Formes Direct/MissingFactor et anti-doublon consécutif inchangés.
**Tech Stack:** Kotlin Multiplatform (commonMain/commonTest), `kotlin.random.Random`, `kotlin.test`, Gradle.
**Spec:** `docs/superpowers/specs/2026-08-31-generation-ponderee-questions-design.md`
## Global Constraints
- Commande de test : `./gradlew :sharedUI:jvmTest` (logique de jeu dans `commonTest`, kotlin.test)
- Fichier de production modifié : uniquement `sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt`
- Comportements conservés : facteurs dans 1..maxTable ; formes `Direct`/`MissingFactor` ~50/50 ; pas de doublon consécutif ; terminaison garantie même pour maxTable = 1 (poids > 0 pour tout facteur)
- Pas de dépendance nouvelle ; pas de commentaire dans le code
- Interface et docs du projet en français
---
### Task 1: Générateur pondéré + tests
**Files:**
- Modify: `sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt:10-18`
- Test: `sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt`
- Modify: `docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md:87` (amendement de la règle de génération)
**Interfaces:**
- Consumes: `Question.Direct(a, b)`, `Question.MissingFactor(a, b)` (existants, inchangés)
- Produces: `QuestionGenerator(random: Random) : QuestionSource` — signature inchangée, seule la distribution change
- [ ] **Step 1: Write the failing tests**
Ajouter à la fin de la classe `QuestionGeneratorTest` (après le test `affichage des deux formes`) :
```kotlin
private fun factors(q: Question): Pair<Int, Int> = when (q) {
is Question.Direct -> q.a to q.b
is Question.MissingFactor -> q.a to q.b
}
@Test
fun `biais progressif - les facteurs eleves reviennent plus souvent`() {
val generator = QuestionGenerator(Random(42))
var with1 = 0
var with10 = 0
repeat(5000) {
val (a, b) = factors(generator.next(previous = null, maxTable = 10))
if (a == 1 || b == 1) with1++
if (a == 10 || b == 10) with10++
}
assertTrue(with1 < with10 / 5, "with1=$with1, with10=$with10")
}
@Test
fun `les petits facteurs restent possibles`() {
val generator = QuestionGenerator(Random(42))
var with1 = 0
repeat(5000) {
val (a, b) = factors(generator.next(previous = null, maxTable = 10))
if (a == 1 || b == 1) with1++
}
assertTrue(with1 > 0, "with1=$with1")
}
@Test
fun `maxTable egale a 1 - seule question possible sans boucle infinie`() {
val generator = QuestionGenerator(Random(42))
repeat(200) {
val (a, b) = factors(generator.next(previous = null, maxTable = 1))
assertEquals(1, a)
assertEquals(1, b)
}
}
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `./gradlew :sharedUI:jvmTest`
Expected: FAIL — `biais progressif - les facteurs eleves reviennent plus souvent` échoue (tirage uniforme : with1 ≈ with10 ≈ 19 % chacun, l'assertion `with1 < with10 / 5` est fausse). Les autres tests (existants, `les petits facteurs restent possibles`, `maxTable egale a 1`) passent.
- [ ] **Step 3: Write the implementation**
Remplacer tout le contenu de `sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt` par :
```kotlin
package com.livrets.game
import kotlin.random.Random
fun interface QuestionSource {
fun next(previous: Question?, maxTable: Int): Question
}
class QuestionGenerator(private val random: Random = Random.Default) : QuestionSource {
override fun next(previous: Question?, maxTable: Int): Question {
while (true) {
val a = nextFactor(maxTable)
val b = nextFactor(maxTable)
val question =
if (random.nextBoolean()) Question.Direct(a, b) else Question.MissingFactor(a, b)
if (question != previous) return question
}
}
private fun nextFactor(maxTable: Int): Int {
val totalWeight = maxTable * (maxTable + 1) / 2
var roll = random.nextDouble() * totalWeight
for (k in 1..maxTable) {
roll -= k
if (roll < 0) return k
}
return maxTable
}
}
```
La boucle for consomme les poids cumulés (1, 2, …, maxTable) : chaque facteur k est retourné avec une probabilité k / (maxTable·(maxTable+1)/2). Le `return maxTable` final est un garde-fou contre l'arrondi flottant (roll ≥ 0 après soustraction de tous les poids). Aucun rejet sur la difficulté : terminaison garantie, y compris maxTable = 1.
- [ ] **Step 4: Run the full test suite**
Run: `./gradlew :sharedUI:jvmTest`
Expected: PASS — les tests existants (bornes, formes produites, pas de doublon consécutif, engine, persistance) et les deux nouveaux tests passent.
- [ ] **Step 5: Amend the original spec's generation rule**
Dans `docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md`, ligne 87, remplacer :
```markdown
- Génération : `a`, `b` tirés uniformément dans 1..`config.maxTable` (le joueur limite la difficulté en réduisant la table max) ; forme `Direct` ou `MissingFactor` au hasard (~50/50) ; interdiction de répéter exactement la même question deux fois de suite.
```
par :
```markdown
- Génération : `a`, `b` tirés dans 1..`config.maxTable` avec poids ∝ facteur (amendé le 2026-08-31 : cf. spec « Génération pondérée des questions ») ; forme `Direct` ou `MissingFactor` au hasard (~50/50) ; interdiction de répéter exactement la même question deux fois de suite.
```
- [ ] **Step 6: Commit**
```bash
git add sharedUI/src/commonMain/kotlin/com/livrets/game/QuestionGenerator.kt sharedUI/src/commonTest/kotlin/com/livrets/game/QuestionGeneratorTest.kt docs/superpowers/specs/2026-08-28-livrets-multiplication-game-design.md docs/superpowers/specs/2026-08-31-generation-ponderee-questions-design.md
git commit -m "feat: génération pondérée des questions (les grands facteurs sortent plus souvent)"
```