Terrain 6 — Machine d’état & timer
8 drills · ~10 min · Chapitre lié : 6.3
1. Écrivez (de tête) l’union Phase de Drill avec les données de chaque variante.
✅
type Phase =
| { name: 'playing'; endsAt: number }
| { name: 'answered'; choiceIndex: number | null; correct: boolean }
| { name: 'finished' }(choiceIndex null = timeout.)
2. Pourquoi une union discriminée plutôt que isAnswered + isFinished + endsAt ?
✅
Les combinaisons absurdes (answered ET finished, endsAt hors playing) deviennent irreprésentables, et TS force l’exhaustivité des switch.
3. La garde de l’action answer — le code et ce qu’elle empêche ?
✅
if (get().phase.name !== 'playing') return false — pas de double réponse, pas de réponse
après timeout : les transitions illégales sont refusées à la source.
4. Le joueur passe 40 s dans une autre app pendant une question de 20 s. Que se passe-t-il au retour, et quel code le garantit ?
✅
Timeout immédiat : le listener AppState compare Date.now() >= endsAt au retour à
‘active’ → timeout(). Le tick gelé aurait raté l’échéance — l’horloge fait foi.
5. À quoi sert le never en fin de switch sur phase.name ?
✅
Verrou d’exhaustivité : ajouter une phase à l’union casse la compilation à chaque switch non mis à jour — la spec vit dans les types.
6. L’écran résultat : push ou replace depuis le quiz, et la conséquence sur back ?
✅
replace — la pile devient tabs → results : back ramène aux tabs, jamais dans la partie finie (pas de double comptage possible).
7. Le TimerRing (anneau SVG) et le useCountdown (chiffre) : qui fait foi, et à quelle fréquence chacun tourne-t-il ?
✅
La vérité = endsAt (comparé à la demande). Le chiffre : tick JS 250 ms (4 Hz, déclenche aussi le timeout). L’anneau : UNE animation withTiming sur le thread UI (60-120 Hz). Trois étages, trois fréquences.
8. Où vit answerState(phase, i, correctIndex), et pourquoi là ?
✅
Dans lib/ (pur) : la table de vérité phase → état visuel des boutons est testable sans RN et réutilisable — l’écran reste déclaratif.
👉 Terrain suivant : 7 — Réseau & offline