Fix/postures debloquees - #337
Merged
Merged
Conversation
Les notes de version 0.6.0 annoncent les postures imposées et les pauses scénarisées, mais leur tirage était conditionné à un toggle de la section debug de l'écran SONS, off par défaut : la fonctionnalité était invisible pour tout le monde depuis deux versions. Le réglage devient une préférence utilisateur assumée (`pref.scripted_breaks`, on par défaut, section « Déroulé de séance » du Profil) au lieu d'un flag debug. L'ancienne clé `debug.scripted_breaks` est abandonnée plutôt que migrée : la reprendre rendrait le off historique collant. Les presets de debug `reset` et `mi_carriere_profondeur`, qui recopiaient les défauts d'usine, sont réalignés sur le nouveau défaut.
…vancé Le coach Marc a coûté un correctif en 0.6.1 : son palier de déblocage était franchissable une seule fois, et qui l'avait dépassé avant sa sortie restait verrouillé définitivement. Les 5 milestones `intro_posture_*` (levels 4 à 12) sont exposées au même risque pour un profil déjà au-delà. Ces tests tournent sur le vrai catalogue `assets/career/milestones.json` et établissent que `level` est un plancher et non une fenêtre, que les postures remontent en tête de file (règle overdue), et qu'il faut 3 séances moyenne/longue — 5 courtes — pour les débloquer toutes. Ils caractérisent aussi l'exigence de capacité de `intro_posture_on_back` (`hold.full.streak ≥ 5`), seul palier qui attend une preuve physique.
… une pause La sortie d'une pause scénarisée qui impose une nouvelle posture arme un gate de mise en place : `_checkSteps` ne consomme plus de step et `_onTick` gèle l'horloge jusqu'à la validation. Trois chemins traversaient cette fenêtre sans jamais passer par `_exitBreak`, donc sans lever le gate — il survivait au saut de timeline et le gel délibéré devenait définitif (ticker actif, plus rien qui avance, jusqu'au timeout de 90 s) : - « je peux pas » pendant la pause ou pendant l'attente de mise en place — le bouton n'est masqué par aucun des deux états ; - « Utilise-moi » et le retry milestone, via `requestUpgrade` (qui remettait déjà l'état de pause à zéro, mais pas le gate) ; - la régénération post-défi, par symétrie. Le bouton reste actif dans les deux états : c'est la seule sortie visible en séance, et une pause de 60 à 120 s suivie d'une attente n'est pas un moment où la priver serait défendable. Le flow d'échec annule la pause au lieu de la subir — sans appliquer sa posture, dont l'annonce ne sera pas prononcée. Corrige aussi le retour d'arrière-plan pendant une pause : `resume()` relançait l'audio d'effort coupé par l'entrée dans la pause. Chemin atteignable par tout le monde, une notification suffit. Les 4 tests runtime ajoutés sont rouges sans ces correctifs.
Le gel de mise en place était un drapeau armé à la sortie d'une pause et levé par un seul chemin nominal. Quatre chemins distincts l'ont laissé orphelin derrière eux, et chaque correctif ciblé n'a jamais rien dit du suivant : on cesse de fermer des chemins. `PostureGate` porte désormais ce qui *justifie* le gel — identité de la timeline, tête de lecture, décalage d'horloge, numéro de flow d'échec — et `awaitingPostureReady` rejoue cette justification à chaque lecture. Les trois premières composantes sont les seules variables qui portent la progression d'une séance : rebattre la timeline sans en toucher aucune est impossible. Un chemin qui la rebat n'a donc rien à lever, aujourd'hui comme demain — les trois levées explicites de la passe 2 sont retirées. Une garde centrale enterre à chaque battement une ancre dont la justification est tombée (timeout de sécurité compris), pour qu'un gel enterré ne ressuscite pas si sa justification redevenait vraie. Ferme au passage le quatrième chemin, jamais corrigé : un défi armé au même tick qu'un ordre de posture n'empile plus son bandeau sur lui, et sa clôture ne laisse plus la séance gelée.
Les horaires de pause et de défi étaient calculés par deux fonctions indépendantes qui visaient toutes deux le milieu de la séance. Le trou d'effort de la pause avalait le trigger du défi médian, que la boucle de génération réémettait à `break.endTime` : l'ordre de se mettre en position et l'annonce du défi tombaient au même instant. Le planificateur de pauses reçoit les créneaux de défi et cède le passage — les horaires de défi ne bougent pas. Sans défi, les horaires nominaux sont inchangés. Balayage sur le générateur réel (8 durées × 4 comptes de défis × 60 graines, 1920 générations) : 175 collisions avant, 0 après.
`_excisChallengeFromSession` reconstruit la séance sans passer `breaks:` ni `initialPose:` — le défaut par défaut de `Session.breaks` est `const []`, donc toute la mise en scène restante disparaissait dès qu'un défi avait eu lieu, sur la fonctionnalité qu'on est en train d'allumer. La liste garde sa longueur (`_nextBreakIndex` pointe dedans) et ses horaires suivent le même décalage que les steps.
…éance `stop()` et `_finish()` remettaient la timeline à zéro mais laissaient courir le `Timer` de sécurité du gel de posture : ~90 s plus tard, il réveillait un contrôleur devenu inactif ou l'écran de fin. Il n'y a pas de gel à lever — il est tombé de lui-même — juste une ressource à libérer, aux trois bornes de vie d'une séance (`start` le faisait déjà). Entre elles, c'est la garde du battement qui s'en charge.
…le levait La garde ne cherchait que la chaîne `confirmPostureReady`. Tous les fichiers du contrôleur sont des `part of` : écrire `_postureGate = null` depuis n'importe lequel d'entre eux est valide et passait au travers sans un mot — exactement le motif que ces passes ont servi à éliminer. Elle scanne désormais les écritures du champ dans toute sa library privée et exige que l'ensemble des membres qui y touchent soit exactement les six sites légitimes : naissance de l'ancre, validation joueuse, ramasse-miettes du battement, et les trois bornes de vie d'une séance.
La clause d'identité protège contre le remplacement de la séance, pas contre une mutation en place de ses listes. Aucun code ne le fait aujourd'hui, rien ne l'empêche demain et rien ne le détecterait : le noter là où la décision vit plutôt que de laisser « par définition » porter plus qu'il ne prouve.
Intègre le lot « durée de séance faussée par les défis » (13 commits) avant que celui-ci ne parte vers develop, pour que la demande de fusion des postures soit propre une fois la première intégrée. Deux conflits, résolus en gardant de chaque côté ce dont il a raison. session_controller_challenge.dart — bloc de reconstruction après un défi : `initialPose` et `breaks` viennent des postures (sans ce report, `Session.breaks` retombe à `const []` et la mise en scène restante disparaît dès qu'un défi se termine). Le commentaire vient des défis : celui des postures affirmait que l'excision retire « 2 steps (breath + défi) », faux depuis la refonte des défis en streaming où un seul step tombe dans la timeline. career_session_generator.dart — appel à `_computeBreakTriggerTimes` : les deux paramètres survivent, `genUntil: ctx.genUntil` (accesseur dérivé) **et** `challengeWindows`. À ce point du code la réserve de défis vaut encore 0, donc `ctx.genUntil` égale la valeur qu'utilisait la branche postures — pas de changement de sens. `_estimatedChallengeDuration` gardant sa signature, les fenêtres de défi héritent d'office de la durée désormais plafonnée par format. Vérifié sur l'arbre fusionné : 978 tests verts (818 base + 29 postures + 131 défis, égalité exacte : aucun test perdu), `flutter analyze` propre, format inchangé, l10n générés conformes à `gen-l10n`. Collisions pauses/défis : 0 sur 4800 générations à défis courts et 0 sur les formats du catalogue, hors le seul couple `longue` × 4 défis au plafond (4 cas sur 500) — identiques avant fusion. Durées avec défis : 10,8 min en express, 39,7 min en moyenne.
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.