--- title: Personnages — un objet, quatre facettes area: visitapp backend horizon: v2 tags: 3 repos, Studio IA flag: good | V2 · lots 8 et 8bis codés le 15/09, à tester src: v2/studio-plan.md §3.8 — lots 7 et 8 ---
✅ Lot 8 et 8bis codés le 15/09, à tester (plan : test-plan-studio-narration.md). Narrateurs assignables (étape, point, article ; défaut parcours, carte, visite ; guide par visite) avec la chaîne de § 3.8 ; génération audio Gemini TTS → MP3, 1 crédit par minute, état « à régénérer » sur changement de texte ou de voix ; onglet Narrateurs du parcours, blocs sur l'article et le point, carte Narration et casting dans l'écran Configuration ; portrait du narrateur à côté du lecteur et écran « les personnes que vous allez rencontrer » sur les trois apps visiteur. Reste : les extraits d'écoute des voix (clé Gemini). Le lecteur audio d'un point de carte côté visiteur a été ajouté au lot (8e).
🛠️ Lot 7 en cours (15/09). Entité Persona, écran Personnages, TtsVoice en base, migration des colonnes Instance.Guide* faits. Figures des 12 archétypes générées et embarquées. Reste : l'écoute des voix (échantillons audio à produire, pas de clé Gemini en local), puis le lot 8 (narration attribuée).
Trois plans décrivaient le même objet sans se croiser (relevé le 01-02/09) : le canon visuel (studio-plan.md), les 3 frames de lipsync (talking-head-plan.md) et PersonaConfig { WakewordId, GuideName, PersonaPrompt, VoiceName } (tts-pregenerated-plan.md). Une entité Persona les remplace : identité, visage (canon en jeu de vues), voix + intonation, parole.
⛔ Le talking head est abandonné, sa dépendance était déjà cassée. talking-head-plan.md:11 anime ses frames « selon les timestamps retournés par Google Cloud TTS » (enable_time_pointing) — or le code livré tourne sur Gemini TTS (GeminiTtsEngine, gemini-2.5-flash-preview-tts) : aucune source de timestamps. Remplacé par le portrait canon statique à côté du lecteur audio, puis une boucle vidéo 6-8 s si du mouvement est voulu. En-tête d'abandon posé sur le fichier.
⚠️ « Le visiteur choisit entre Viva et Marco » était un choix de voix déguisé en choix de guide. Le besoin réel est celui du Bastogne War Museum : 3-4 personnages qui narrent chacun leurs stations, avec leur voix et leur intonation. Le conservateur assigne (NarratorPersonaId sur GuidedStep/GeoPoint/SectionArticle), le visiteur ne choisit rien. Bonne nouvelle : ça divise le stockage TTS par deux au lieu de le multiplier — chaque contenu n'existe plus qu'en une voix.
Le wakeword devient une exigence du canal, pas une propriété du personnage. Il corrigeait un bug latent : GuideName est du texte libre sans lien avec le modèle OpenWakeWord, donc le visiteur dirait « Marco » à quelqu'un qui se présente comme « Léon ». Nouvelle règle : nom libre par défaut, contraint à un modèle disponible seulement si un canal mains-libres est actif (lunettes, casque VR). Mobile/web/kiosk : push-to-talk.
Deux champs à ajouter : Persona.VoicePrompt (l'intonation — aujourd'hui la constante de build kGeminiTtsPrompt, visitapp constants.dart:27) et une table TtsVoice à la place des deux constantes : deux voix ne suffisent pas à quatre narrateurs. Migration : les 4 colonnes Instance.Guide* deviennent une ligne Persona.
🔄 Révisé le 02/09 : le Kind Guide/Narrateur est abandonné. Un guide n'est rien d'autre qu'un personnage dont la facette parole est remplie. Donc un seul objet, et ce qu'il sait faire découle des facettes renseignées : VoiceId → il narre, SystemPrompt → il peut être guide, WakewordId → on l'appelle à la voix. Quatre combinaisons valides, y compris visage seul — une silhouette de décor qui revient huit fois a besoin d'un canon, pas d'une voix.
Un guide par visite est possible : Configuration.GuidePersonaId ?? Instance.GuidePersonaId. Donc le gouverneur peut narrer les étapes 1, 3, 5 et répondre aux questions sur ce parcours, en personnage. Assignation : le défaut se pose sur tout conteneur, la surcharge sur toute feuille — narrateur sur Instance → Configuration → GuidedPath/SectionMap, surchargeable sur GuidedStep, GeoPoint, SectionArticle.
✅ Le mains-libres n'est pas la limite que je croyais (vérifié dans le code le 02/09) : NativeWakeWordEngine fait tourner N modèles en parallèle et l'événement dit lequel a déclenché (detected:hey_marco). Quatre tournent déjà, en dur (voice_controller.dart:105), sans lien avec le CMS. Cinq modèles sont embarqués : hey_viva, hey_marco, hey_alba, hey_vasco, hey_visit. La vraie contrainte est que le catalogue de noms est fini et embarqué au build — un autre prénom demande un entraînement et une republication de l'app.
🐛 Bug relevé au passage — le chemin mains-libres Android est cassé. Le moteur passe le nom du modèle dans onDetectedWithCommand, que VoiceOrchestrator._onWakeWordWithCommand (voice_orchestrator.dart:132) interprète comme la question du visiteur et envoie à _dispatch(). Le visiteur dit « Hey Marco », l'assistant répond à la question « hey_marco » et n'ouvre jamais le cycle d'écoute. À corriger indépendamment du Studio.
⚠️ VisitorQuestion a ConfigurationId, AppType, IsVoice, ThemeId — mais pas de PersonaId. Sans lui, impossible de ventiler les stats par personnage, donc de voir qu'un personnage répond mal.