Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions rhythm_coach/assets/career/phrases.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,54 @@
"Moins profond, là.",
"Reste plus haute."
]
},
"break_entry": [
"On souffle. Deux minutes pour toi.",
"Pause. Tu récupères, mais tu restes à moi.",
"On marque une pause. Respire.",
"Stop. On relâche un instant."
],
"break_orders": [
"Bois une gorgée d'eau.",
"Respire à fond, par le ventre.",
"Regarde-toi dans la glace.",
"Détends ta mâchoire.",
"Essuie-toi, remets-toi en place.",
"Roule les épaules, relâche la nuque.",
"Hydrate-toi.",
"Ferme les yeux, respire calmement."
],
"break_resume": [
"On reprend.",
"Fini de souffler. On y retourne.",
"Pause terminée, reprends.",
"Allez, on reprend le travail."
],
"break_posture": {
"sitting": [
"Assieds-toi, bien droite.",
"Pose-toi, assise.",
"Mets-toi assise, dos droit."
],
"standing": [
"Lève-toi. Debout.",
"Mets-toi debout.",
"Debout, bien droite."
],
"kneeling": [
"À genoux, maintenant.",
"Mets-toi à genoux.",
"À genoux, c'est ta place."
],
"all_fours": [
"À quatre pattes.",
"Mets-toi à quatre pattes.",
"À quatre pattes, comme une chienne."
],
"on_back": [
"Allonge-toi sur le dos.",
"Sur le dos, maintenant.",
"Sur le dos, la tête en arrière."
]
}
}
49 changes: 49 additions & 0 deletions rhythm_coach/assets/career/phrases_de.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,54 @@
"Weniger tief, so.",
"Bleib weiter oben."
]
},
"break_entry": [
"Durchatmen. Zwei Minuten für dich.",
"Pause. Du erholst dich, aber du bleibst mein.",
"Wir machen kurz Pause. Atme.",
"Stopp. Lass einen Moment locker."
],
"break_orders": [
"Trink einen Schluck Wasser.",
"Atme tief, aus dem Bauch.",
"Sieh dich im Spiegel an.",
"Entspann deinen Kiefer.",
"Wisch dich ab, mach dich zurecht.",
"Roll die Schultern, lockere den Nacken.",
"Trink etwas.",
"Schließ die Augen, atme ruhig."
],
"break_resume": [
"Weiter geht's.",
"Genug verschnauft. Zurück an die Arbeit.",
"Pause vorbei, mach weiter.",
"Los, weiter."
],
"break_posture": {
"sitting": [
"Setz dich hin, schön gerade.",
"Setz dich, sitzend.",
"Setz dich, Rücken gerade."
],
"standing": [
"Steh auf. Aufrecht.",
"Stell dich hin.",
"Hoch, steh gerade."
],
"kneeling": [
"Auf die Knie, jetzt.",
"Knie dich hin.",
"Auf die Knie, da gehörst du hin."
],
"all_fours": [
"Auf alle viere.",
"Geh auf alle viere.",
"Auf alle viere, wie eine Hündin."
],
"on_back": [
"Leg dich auf den Rücken.",
"Auf den Rücken, jetzt.",
"Auf den Rücken, Kopf nach hinten."
]
}
}
49 changes: 49 additions & 0 deletions rhythm_coach/assets/career/phrases_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,54 @@
"Less deep, there.",
"Stay higher."
]
},
"break_entry": [
"Breathe. Two minutes for you.",
"Break. You recover, but you stay mine.",
"We pause here. Breathe.",
"Stop. Ease off for a moment."
],
"break_orders": [
"Take a sip of water.",
"Breathe deep, from the belly.",
"Look at yourself in the mirror.",
"Relax your jaw.",
"Wipe yourself, set yourself right.",
"Roll your shoulders, loosen your neck.",
"Hydrate.",
"Close your eyes, breathe slowly."
],
"break_resume": [
"We resume.",
"Done catching your breath. Back to it.",
"Break's over, pick it up.",
"Come on, back to work."
],
"break_posture": {
"sitting": [
"Sit down, nice and straight.",
"Settle down, seated.",
"Sit, back straight."
],
"standing": [
"Get up. On your feet.",
"Stand up.",
"Up, stand straight."
],
"kneeling": [
"On your knees, now.",
"Get on your knees.",
"On your knees, where you belong."
],
"all_fours": [
"On all fours.",
"Get on all fours.",
"On all fours, like a bitch."
],
"on_back": [
"Lie on your back.",
"On your back, now.",
"On your back, head tipped back."
]
}
}
49 changes: 49 additions & 0 deletions rhythm_coach/assets/career/phrases_es.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,54 @@
"Menos profundo, así.",
"Quédate más arriba."
]
},
"break_entry": [
"Respira. Dos minutos para ti.",
"Pausa. Te recuperas, pero sigues siendo mía.",
"Paramos un momento. Respira.",
"Para. Afloja un instante."
],
"break_orders": [
"Bebe un trago de agua.",
"Respira hondo, desde el vientre.",
"Mírate en el espejo.",
"Relaja la mandíbula.",
"Límpiate, recolócate.",
"Gira los hombros, suelta el cuello.",
"Hidrátate.",
"Cierra los ojos, respira tranquila."
],
"break_resume": [
"Seguimos.",
"Ya has respirado. Volvemos.",
"Se acabó la pausa, sigue.",
"Vamos, de vuelta al trabajo."
],
"break_posture": {
"sitting": [
"Siéntate, bien recta.",
"Ponte sentada.",
"Siéntate, espalda recta."
],
"standing": [
"Levántate. De pie.",
"Ponte de pie.",
"Arriba, bien recta."
],
"kneeling": [
"De rodillas, ahora.",
"Ponte de rodillas.",
"De rodillas, tu sitio."
],
"all_fours": [
"A cuatro patas.",
"Ponte a cuatro patas.",
"A cuatro patas, como una perra."
],
"on_back": [
"Túmbate de espaldas.",
"De espaldas, ahora.",
"Boca arriba, la cabeza hacia atrás."
]
}
}
18 changes: 18 additions & 0 deletions rhythm_coach/lib/career/models/coach.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import '../../models/posture.dart';
import '../../models/session.dart';
import '../../models/session_step.dart';
import '../../services/random_comments_loader.dart';
Expand Down Expand Up @@ -1125,4 +1126,21 @@ class _CoachComposedPhraseBank extends PhraseBank {
// déclinés par coach.
return fallback.pickSwallowOrder(rng);
}

@override
String? pickBreakEntry(Random rng) {
// Délégation au pool global : phrases de break (issue #77) non encore
// déclinées par coach.
return fallback.pickBreakEntry(rng);
}

@override
String? pickBreakOrder(Random rng) => fallback.pickBreakOrder(rng);

@override
String? pickBreakResume(Random rng) => fallback.pickBreakResume(rng);

@override
String? pickPostureChange(Posture pose, Random rng) =>
fallback.pickPostureChange(pose, rng);
}
53 changes: 52 additions & 1 deletion rhythm_coach/lib/career/models/phrase_bank.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:math';

import '../../models/posture.dart';
import '../../models/session.dart';
import '../../models/session_step.dart';
import 'phrase_entry.dart';
Expand Down Expand Up @@ -99,6 +100,25 @@ class PhraseBank {
/// varier le ton (impératif sec) sans polluer les autres pools beg.
final List<PhraseEntry> _swallowOrders;

/// Phrase d'entrée d'un break scénarisé (issue #77) — « on souffle deux
/// minutes ». Jouée à l'entrée de la fenêtre de break.
final List<PhraseEntry> _breakEntry;

/// Ordres intercalés pendant un break (hors changement de posture) — « bois
/// une gorgée », « respire à fond ». Tirés espacés par le runtime. Ne
/// doivent jamais contenir « tiens/hold/halten » (réservés au pool hold).
final List<PhraseEntry> _breakOrders;

/// Phrase de reprise d'un break qui ne change PAS de posture (récup pure) —
/// « on reprend ». Quand le break change de posture, le runtime préfère
/// `_breakPostureChange[newPose]`.
final List<PhraseEntry> _breakResume;

/// Ordre de changement de posture à la reprise d'un break, par posture
/// imposée (« mets-toi à quatre pattes », « à genoux maintenant »). Jamais
/// de clé pour [Posture.free] (aucune imposition).
final Map<Posture, List<PhraseEntry>> _breakPostureChange;

const PhraseBank({
required Map<SessionMode, Map<String, List<PhraseEntry>>> byMode,
required List<PhraseEntry> congrats,
Expand All @@ -113,6 +133,10 @@ class PhraseBank {
List<PhraseEntry> postFinalBeg = const [],
List<PhraseEntry> postFinalLick = const [],
List<PhraseEntry> swallowOrders = const [],
List<PhraseEntry> breakEntry = const [],
List<PhraseEntry> breakOrders = const [],
List<PhraseEntry> breakResume = const [],
Map<Posture, List<PhraseEntry>> breakPostureChange = const {},
}) : _byMode = byMode,
_congrats = congrats,
_intros = intros,
Expand All @@ -125,7 +149,11 @@ class PhraseBank {
_postFinal = postFinal,
_postFinalBeg = postFinalBeg,
_postFinalLick = postFinalLick,
_swallowOrders = swallowOrders;
_swallowOrders = swallowOrders,
_breakEntry = breakEntry,
_breakOrders = breakOrders,
_breakResume = breakResume,
_breakPostureChange = breakPostureChange;

/// Tire une phrase pour [mode] dans le tier demandé. Si le tier est absent,
/// fallback sur 'medium' puis 'any' puis première liste non vide.
Expand Down Expand Up @@ -279,4 +307,27 @@ class PhraseBank {
/// Retourne `null` si le pool est vide — l'appelant peut alors
/// retomber sur le tier `hard` du mode beg comme fallback.
String? pickSwallowOrder(Random rng) => pickPhraseEntry(_swallowOrders, rng);

/// Tire la phrase d'entrée d'un break scénarisé (issue #77). Retourne `null`
/// si la banque n'a pas de pool `break_entry` — le runtime reste alors
/// silencieux à l'entrée.
String? pickBreakEntry(Random rng) => pickPhraseEntry(_breakEntry, rng);

/// Tire un ordre intercalé pendant un break (hors posture). Retourne `null`
/// si le pool `break_orders` est vide.
String? pickBreakOrder(Random rng) => pickPhraseEntry(_breakOrders, rng);

/// Tire la phrase de reprise d'un break sans changement de posture. Retourne
/// `null` si le pool `break_resume` est vide.
String? pickBreakResume(Random rng) => pickPhraseEntry(_breakResume, rng);

/// Tire l'ordre de changement de posture à la reprise d'un break, pour la
/// [pose] imposée. Retourne `null` pour [Posture.free] ou si aucun pool
/// n'existe pour cette posture — le runtime retombe alors sur
/// [pickBreakResume].
String? pickPostureChange(Posture pose, Random rng) {
final list = _breakPostureChange[pose];
if (list == null) return null;
return pickPhraseEntry(list, rng);
}
}
11 changes: 11 additions & 0 deletions rhythm_coach/lib/career/screens/career_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import '../services/career_difficulty_resolver.dart';
import '../services/career_encore_gate.dart';
import '../services/career_progress_service.dart';
import '../services/challenge_service.dart';
import '../services/debug_settings_service.dart';
import '../services/generation/career_session_generator.dart';
import '../services/phrase_bank_loader.dart';
import '../services/specialization_service.dart';
Expand Down Expand Up @@ -102,6 +103,7 @@ class _CareerScreenState extends State<CareerScreen> {
_challengeService.tutorialSeen(),
_progress.getLastLengthChoice(),
_stats.getTotalSeconds(),
DebugSettingsService().getScriptedBreaks(),
]);
final capabilityProfile = results[8] as CapabilityProfile;
final totalSeconds = results[12] as int;
Expand Down Expand Up @@ -141,6 +143,7 @@ class _CareerScreenState extends State<CareerScreen> {
lastLengthChoice: results[11] as SessionLengthChoice,
totalSeconds: totalSeconds,
synthLevel: CareerDifficultyResolver.synthLevelFor(completedSessions),
scriptedBreaks: results[13] as bool,
);
}

Expand Down Expand Up @@ -461,6 +464,7 @@ class _CareerScreenState extends State<CareerScreen> {
// figé de plafond.
capability: CapabilityInputs(profile: bundle.capabilityProfile),
challenge: ChallengeInputs(challenges: challenges),
scriptedBreaks: bundle.scriptedBreaks,
);

final introText = coachBank.pickIntro(Random());
Expand Down Expand Up @@ -1059,6 +1063,7 @@ class _CareerScreenState extends State<CareerScreen> {
profile: bundle.capabilityProfile,
sessionCeilings: previousSessionCeilings,
),
scriptedBreaks: bundle.scriptedBreaks,
);

final camService = CameraMotionService();
Expand Down Expand Up @@ -2047,6 +2052,11 @@ class _CareerBundle {
/// Sert au déblocage des coachs par investissement (Phase 19.10).
final int totalSeconds;

/// Flag debug `debug.scripted_breaks` (issue #77). Quand `true`, les
/// postures imposées + breaks scénarisés sont activés : passé à
/// `generate(scriptedBreaks:)` à tous les call sites.
final bool scriptedBreaks;

const _CareerBundle({
required this.bank,
required this.punishments,
Expand All @@ -2062,5 +2072,6 @@ class _CareerBundle {
required this.lastLengthChoice,
required this.totalSeconds,
required this.synthLevel,
required this.scriptedBreaks,
});
}
Loading
Loading