Fix/duree seance defis - #336
Merged
Merged
Conversation
L'horloge de timeline est gelée pendant tout un défi (choix assumé : le défi ne consomme pas de temps de séance). C'est pourtant elle que l'écran de fin affichait et que `_finish` créditait au temps de jeu cumulé — un défi de dix minutes ne rapportait donc ni point de spécialisation, ni déblocage de coach, ni badge d'endurance, et la séance était rapportée plus courte que ce que la joueuse venait de jouer. `playedSeconds` porte désormais le temps réellement passé en séance, pauses exclues. Le gel de la timeline est inchangé : il continue de piloter le contenu.
Le générateur réserve après chaque trigger de défi une enveloppe `breath + nominalDurationSeconds` où il ne pose aucun contenu, puisque le défi y sera joué. Cette enveloppe était prise sur la durée demandée, et le runtime la retirait *encore* de la durée de séance (`_excisChallengeFromSession`) : la joueuse la payait deux fois. Une séance « Moyenne » (1500 s) avec les 3 défis du retour utilisateur — dont un d'endurance à 530 s — ne rendait plus que ~980 s de contenu. `GenerationContext.challengeReserveSeconds` cumule ces fenêtres : elles décalent d'autant la fin de la boucle main et sont retranchées de `progress`, si bien que le contenu et la courbe de difficulté restent ceux de la durée demandée. La séance dure donc le temps demandé plus le temps des défis.
`challenges` était découpée sur le curseur de file, qui avance aussi quand un défi est écarté faute de place, alors que `challengeTriggerTimes` ne reçoit une entrée que pour les défis insérés. Le contrôleur indexe les deux listes par la même position : dès qu'un défi était écarté, `_updateChallengePhase` sortait des bornes dans le ticker principal, où une exception ne s'auto-annule pas — gel de séance ou plantage. Le générateur ne livre plus que les défis réellement insérés, et la boucle d'armement se borne à la plus courte des deux listes. Cas structurel : les trigger times sont répartis à 20/50/80 % du budget sans tenir compte de la taille du défi, donc un défi de 530 s tiré en dernier n'a jamais la place — d'où le désalignement.
Un défi ne s'arme que si l'horloge traverse sa fenêtre `[trigger, trigger + 13 s)` pendant qu'un tick tourne. La reprise après un fail saute à la prochaine section : quand le fail tombait pile dans cette fenêtre, le saut passait par-dessus et le défi disparaissait sans un mot — N défis demandés au menu, N-1 joués. Le saut de reprise est désormais borné à l'ouverture de la fenêtre du prochain défi encore armable ; quand elle est déjà ouverte, on ne saute pas du tout (le défi s'arme au tick suivant et gèle l'horloge lui-même).
Le défi ne prend rien à la séance, il s'y ajoute : une « Moyenne » à 3 défis annonçait « 25 min » pour ~35 min vécues. La durée affichée sous le sélecteur porte désormais la mention quand les défis sont activés — « 25 min + le temps des défis » — dans les 4 langues. La mention va sur la ligne pleine largeur et non dans les cartes du sélecteur : à 5 cartes sur un écran de 360 dp il reste ~42 dp de texte par carte, où « Herausforderungen » ne rentre pas.
Le contrôle de place était devenu auto-contradictoire : depuis que l'enveloppe d'un défi est reportée sur `genUntil`, insérer un défi ne coûte plus de contenu, donc `remaining = genUntil - time` est invariant sous l'insertion — le test réclamait une place que l'insertion crée elle-même. Un défi de 530 s tiré en dernier n'entrait jamais : une séance sur trois perdait son plus gros défi, sans un mot. Contrepartie assumée et annoncée : la séance dure la durée choisie plus le temps des défis. Mesuré sur 200 tirages, une « Bâclée » (360 s nominal, 456 s générés sans défi) avec le trio du retour utilisateur passe de 516 s à 1059 s — 17,6 min médianes. Le taux d'insertion est de 100 % sur 4800 générations croisant 10 niveaux, 4 paliers et l'ordre de tirage.
Puisque tous les défis entrent désormais, la joueuse doit savoir au moment où elle active le réglage que sa séance dépassera le format choisi. La note complète le texte du réglage dans les 4 langues. La mention portée par la ligne de durée tenait sur deux lignes avec un libellé d'exemple mais pas avec les vrais : « Zeit für Herausforderungen » poussait la carte à trois lignes sur les 5 paliers allemands. Formulation allemande resserrée, et le test porte désormais sur les libellés réels de tous les paliers — dont « Überraschung », qui n'est pas un nombre.
La borne défensive de la boucle d'armement empêche le plantage si `challenges` et `challengeTriggerTimes` divergeaient à nouveau, mais elle abandonnait le défi orphelin en silence complet : personne ne l'aurait su avant qu'une joueuse ne le signale — ce qui vient d'arriver. Une ligne, une seule fois par séance : la boucle tourne à chaque tick.
… les stats Décision produit : l'horloge qui décide de la fin de séance ignore le temps passé en défi — la joueuse joue la durée de contenu qu'elle a choisie — tandis que le chiffre rendu en fin de séance et le temps crédité aux statistiques comptent tout, défis compris. C'est ce que fait `297abe4` au milieu d'un lot de correctifs ; ce test énonce l'invariant pour lui-même. Il casse sur les deux neutralisations (crédit ramené sur l'horloge gelée : 7 s au lieu de 21 ; panneau de fin recâblé sur `elapsedSeconds`). Le panneau de fin est privé à `session_screen.dart` et une séance ne tourne pas sous horloge simulée : à défaut de lire le chiffre rendu, la clause d'affichage verrouille la ligne qui l'alimente.
L'ampleur d'un défi « durée » vaut `comfort × kChallengeOverloadFactor` et n'avait aucun plafond — contrairement aux défis de vitesse (bornés par le BPM maximum du moteur) et de profondeur (bornés par le nombre de crans). Or `comfort` ne fait que monter avec la pratique : sur le profil du retour utilisateur 0.6.1 une Bâclée annoncée 6 minutes en durait 15, et rien ne bornait la suite (73 minutes à `comfort = 3000`). Le plafond est dérivé du palier : la part `kChallengesShareOfFormat` de la durée annoncée, divisée par le nombre d'événements que le palier planifie. Comme un palier ne demande jamais plus de défis qu'il ne planifie d'événements, la somme des défis d'une séance ne peut plus dépasser la moitié de la durée annoncée, quel que soit le format et quel que soit l'état du catalogue de milestones. Le tutoriel (5 s) et les défis exploratoires (5-30 s) passent très en dessous du plus petit plafond (180 s) : le plafond ne mord que sur les axes durée d'un profil déjà entraîné. Tronquer la cible tronque aussi la valeur créditée au profil, qui passe alors sous le `comfort` — la régulation y est neutre, ni le record ni la cible ne bougent. La mesure n'est pas faussée, elle est rendue optionnelle : « JE TIENS ENCORE » reste ouverte pour la reprendre, sur décision de la joueuse. Le paramètre est requis plutôt que défaillant : un appelant qui l'oublie ne doit pas retomber silencieusement sur l'ancien comportement sans borne.
… redit le temps Le sous-titre annonçait « un défi vers 60 % de la séance » alors que le palier en pose jusqu'à quatre, répartis entre 20 % et 80 % de la séance. Faux dans les quatre langues, et faux juste à côté d'une phrase qu'on venait de rendre exacte. Le nombre annoncé est désormais celui des paliers, avec une garde qui casse si un palier planifie plus d'événements que les textes n'en annoncent. La mention courte allemande avait perdu le mot « Zeit » en se resserrant : « 12 Min. + Herausforderungen » se lit comme une énumération — durée et défis compris — c'est-à-dire l'inverse de ce qu'elle veut dire, quand le français, l'anglais et l'espagnol gardent tous les trois le temps. Aucune formulation gardant `Herausforderung` en entier ne tient : mesurées aux 5 paliers × 4 largeurs, « Herausforderungszeit », « Zeit für Herausforderungen » et « zzgl. Herausforderungen » débordent toutes à trois lignes — le mot seul occupe déjà une ligne complète. « Challenge-Zeit » tient partout et garde les deux notions ; « Challenge » se lit sans effort comme le synonyme du toggle affiché juste au-dessus. Le test de mise en page porte maintenant sur les quatre largeurs : le resserrement d'un texte allemand se relit sur toute la gamme visée, pas seulement sur l'écran le plus étroit.
…le source La clause d'affichage se verrouillait sur la ligne qui alimente le panneau de fin, faute de pouvoir lire le chiffre rendu : le panneau est privé à `session_screen.dart` et `Stopwatch` n'est pas simulé par `flutter_test`. Elle se lit maintenant sur le texte que l'écran affiche vraiment. Deux points rendaient le montage possible. `debugFinishSuccess()` clôt une séance par arithmétique en passant par le même chemin de fin que le jeu réel, et un bouton de debug l'appelle déjà depuis l'écran. Et une boucle qui avance les deux horloges ensemble — le temps du mur sous `runAsync` pour le `Stopwatch`, l'horloge simulée par `pump` pour le ticker — fait tourner une séance dans un test de widget. Le défi, lui, se joue en temps réel : c'est le gel de la timeline pendant le défi qui fait diverger les deux horloges, et sans divergence les deux câblages rendraient le même chiffre. Le test coûte 16 s et casse bien sur la neutralisation : « Durée : 7 s » au lieu des 15 s jouées.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.