From 3d264fe1550892322e85070c6846d303d614535e Mon Sep 17 00:00:00 2001 From: BB Studio <282851981+bbstudioapp@users.noreply.github.com> Date: Sat, 30 May 2026 15:42:28 +0200 Subject: [PATCH] =?UTF-8?q?feat(career):=20calibration=20breaks=20?= =?UTF-8?q?=E2=80=94=20cadence=20d'ordres=20irr=C3=A9guli=C3=A8re=20+=20ge?= =?UTF-8?q?l=20animation=20en=20pause=20(issue=20#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/controllers/session_controller.dart | 17 +++++++++++++---- .../controllers/session_controller_break.dart | 14 ++++++++++---- rhythm_coach/lib/screens/session_screen.dart | 5 ++++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/rhythm_coach/lib/controllers/session_controller.dart b/rhythm_coach/lib/controllers/session_controller.dart index 83795d7..eb0462a 100644 --- a/rhythm_coach/lib/controllers/session_controller.dart +++ b/rhythm_coach/lib/controllers/session_controller.dart @@ -179,9 +179,13 @@ class SessionController extends ChangeNotifier { /// post-fail est inutile. static const double _breathSkipStaminaThreshold = 60.0; - /// Intervalle minimal (s) entre deux ordres énoncés pendant un break - /// scénarisé (issue #77). ~1 ordre toutes les 25 s sur une pause de 60-120 s. - static const int _breakOrderIntervalSeconds = 25; + /// Intervalle aléatoire borné (s) entre deux ordres énoncés pendant un break + /// scénarisé (issue #77). Tiré dans [min, max] après chaque ordre (et à + /// l'entrée) plutôt que fixe — une cadence régulière sonne mécanique, un peu + /// d'irrégularité est plus naturel (cf. variété des cycles de séance). ~1 + /// ordre toutes les ~25 s en moyenne sur une pause de 60-120 s. + static const int _breakOrderMinIntervalSeconds = 18; + static const int _breakOrderMaxIntervalSeconds = 32; final Stopwatch _stopwatch = Stopwatch(); @@ -404,9 +408,14 @@ class SessionController extends ChangeNotifier { int _nextBreakIndex = 0; /// `elapsedSeconds` du dernier ordre de break énoncé. Sert à espacer les - /// ordres (`_breakOrderIntervalSeconds`). + /// ordres. int _breakOrderLastAtSec = 0; + /// Intervalle courant (s) avant le prochain ordre de break, re-tiré dans + /// [`_breakOrderMinIntervalSeconds`, `_breakOrderMaxIntervalSeconds`] à + /// l'entrée du break et après chaque ordre (cadence irrégulière). + int _breakOrderInterval = _breakOrderMinIntervalSeconds; + /// Posture courante imposée (issue #77). Initialisée à /// `session.initialPose` au `start()`, mise à jour à la reprise de chaque /// break qui change de pose. Exposée pour l'indicateur de posture (PR5). diff --git a/rhythm_coach/lib/controllers/session_controller_break.dart b/rhythm_coach/lib/controllers/session_controller_break.dart index 5f5382a..44bf596 100644 --- a/rhythm_coach/lib/controllers/session_controller_break.dart +++ b/rhythm_coach/lib/controllers/session_controller_break.dart @@ -61,6 +61,7 @@ extension BreakSequencer on SessionController { _activeBreak = b; _nextBreakIndex++; _breakOrderLastAtSec = elapsedSeconds; + _breakOrderInterval = _pickBreakOrderInterval(); _disarmHoldVerifier(); unawaited(_beep.pause()); final entry = _phraseBank?.pickBreakEntry(_random); @@ -75,17 +76,22 @@ extension BreakSequencer on SessionController { final bank = _phraseBank; if (bank == null) return; final now = elapsedSeconds; - if (now - _breakOrderLastAtSec < - SessionController._breakOrderIntervalSeconds) { - return; - } + if (now - _breakOrderLastAtSec < _breakOrderInterval) return; if (_tts.isSpeaking) return; final order = bank.pickBreakOrder(_random); if (order == null) return; _breakOrderLastAtSec = now; + _breakOrderInterval = _pickBreakOrderInterval(); _speakScripted(order); } + /// Tire un intervalle d'ordre dans [min, max] (cadence irrégulière). + int _pickBreakOrderInterval() => + SessionController._breakOrderMinIntervalSeconds + + _random.nextInt(SessionController._breakOrderMaxIntervalSeconds - + SessionController._breakOrderMinIntervalSeconds + + 1); + /// Sortie d'un break : applique la nouvelle posture, énonce la phrase de /// changement de pose (ou de reprise neutre en récup pure), et relâche /// `_breakActive`. Le beep n'est PAS restauré ici : `_checkSteps` (appelé diff --git a/rhythm_coach/lib/screens/session_screen.dart b/rhythm_coach/lib/screens/session_screen.dart index ab999a3..512afa6 100644 --- a/rhythm_coach/lib/screens/session_screen.dart +++ b/rhythm_coach/lib/screens/session_screen.dart @@ -1092,7 +1092,10 @@ class _SessionScreenContentState extends State<_SessionScreenContent> { if (_showTimer) TimerDisplay( elapsed: ctrl.elapsed, total: ctrl.session.duration) - else if (ctrl.hasConfig) + // Pendant un break (issue #77), on fige l'animation : l'orbe + // en mouvement = effort, ce qui contredirait la bannière + // PAUSE. Slot de même hauteur → pas de saut de layout. + else if (ctrl.hasConfig && !ctrl.breakActive) MovementAnimation( mode: ctrl.currentMode, from: ctrl.currentFrom,