Bulles de dialogue et modèles locaux
Faites parler n’importe quelle unité du jeu, sous n’importe quelle identité, dans une bulle au-dessus de sa tête. Branchez un LLM local : une phrase entre, une réponse s’affiche au-dessus de l’unité.
Les bulles sont une couche destinée au spectacle : elles n’influent pas sur l’issue de la partie, et se prêtent au streaming, au commentaire et au débogage.
- n’importe quelle unité peut parler, sous n’importe quelle identité ; plusieurs unités peuvent parler en même temps ;
- taille de police, couleur, largeur, pointe, transparence et vitesse de frappe se règlent bulle par bulle ;
- branchement direct sur un LLM local (LM Studio), avec sortie en streaming : la bulle se met à jour au fil de la génération.
Depuis un Bot
Le plus simple est le say fourni avec le SDK :
g.say(hero, "Avec moi, chargez !", seconds=4)
Démarrage et interface
Le plus simple : le « Centre de contrôle », page d’accueil de Farsight — cliquez d’abord sur « LLM local → Démarrer et charger le modèle » (serveur local LM Studio + chargement du modèle configuré en mémoire vidéo), puis sur « Bulles de dialogue → Démarrer ». Les cartes permettent aussi de consulter les journaux, d’arrêter et de redémarrer.
L’interface se trouve dans la page « Bulles de dialogue », à gauche dans Farsight : faire parler les unités (choisir une unité, écrire le texte, régler le style, dialoguer avec le modèle), pause-café des paysans, dialogues à la caméra, déclenchement par la partie, réglages du modèle ; tout s’applique à l’instance choisie dans la barre du haut.
Également en ligne de commande :
python speech/speak_launch.py # lance le serveur de modèle local + charge et préchauffe le modèle + lance l'API des bulles
python speech/speak_launch.py --restart # relance l'API après une modification du code
python speech/speak_launch.py --stop # arrête l'API et décharge le modèle de la mémoire vidéo
Chaque étape est ignorée si elle est déjà faite : relancer la commande n’a aucun effet de bord.
API HTTP
Adresse par défaut : http://127.0.0.1:8872/ (le port est défini par ports.speech dans openwar3.json) ; n’importe quel programme peut l’appeler.
Faire parler une unité POST /api/say
{
"inst": 16,
"bubbles": [
{ "unit": "0x14A12614", "name": "Roi de la montagne", "text": "Avec moi, chargez !" },
{ "unit": "0x14A12924", "name": "Archimage", "text": "Je m'occupe du Blizzard.",
"style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
{ "screen": [960, 110], "key": 1, "name": "Narrateur", "text": "Première vague d'orcs dans 30 secondes.",
"style": { "tail": false, "type_ms": 0 } },
{ "world": [-4684, 2644], "key": 2, "text": "Point de ralliement", "style": { "font_px": 16 } }
]
}
| Champ | Description |
|---|---|
unit / world / screen | Un seul des trois : suivre une unité (collée juste au-dessus de sa barre de vie si elle en a une) / coordonnées sur la carte / pixels à l’écran (pour la narration) |
name | Nom de l’orateur affiché en première ligne ; libre, pas forcément celui de l’unité |
text | Texte principal, avec retour à la ligne automatique |
duration_ms | Durée d’affichage ; 0 = automatique, 3 à 5 secondes |
key | Identifiant d’une bulle monde / écran : un nouveau message avec la même key remplace l’ancien |
update | Si la même bulle existe déjà, ne remplace que le texte, sans réinitialiser la minuterie (pour le streaming) |
style | font_px, max_width_px, text_color, bg_color, border_color, tail, side_px, opacity, type_ms, font… |
32 bulles au maximum en même temps ; coût moyen d’environ 0.1 à 0.2 ms par frame.
Dialoguer avec le modèle local POST /api/chat
{
"inst": 16, "unit": "0x14A12614", "name": "Roi de la montagne",
"persona": "Vous incarnez Muradin, le Roi de la montagne de Warcraft : jovial, amateur de bière. Une ou deux phrases parlées, pas plus de 40 caractères.",
"message": "Il y a une bande d'ogres devant, on charge ou pas ?",
"stream": true
}
Renvoie {"reply": "...", "first_token_ms": 283, "total_ms": 342} ; au même moment, la réponse s’affiche déjà au-dessus de l’unité. Chaque unité garde en mémoire ses 6 derniers échanges.
Autres
| Interface | Description |
|---|---|
GET /api/instances | Parties en cours |
GET /api/units?inst=16&mine=true&heroes=true | Liste des unités (avec nom chinois, coordonnées, points de vie) |
POST /api/clear | Efface une bulle ou toutes les bulles |
GET /api/llm, POST /api/llm | Consulter / modifier la configuration du modèle (base_url, model, max_tokens, temperature) |
POST /api/banter | Pause-café des paysans : les ouvriers de la base râlent à tour de rôle selon leur personnage, et annoncent le début de la partie (toutes les infos de jeu sont réelles) |
POST /api/camtalk | Dialogues à la caméra : les héros et leur escorte visibles à l’écran dialoguent selon leur rôle |
POST /api/events | Déclenché par la partie : début de combat, fin de combat, mort d’un héros, montée de tier, bâtiment détruit… on ne parle que lorsqu’il se passe quelque chose |
Choisir un modèle local
Mesuré sur une RTX 5090 (5 répliques de jeu) :
| Modèle | VRAM | Vitesse | Une réponse | Verdict |
|---|---|---|---|---|
| Qwen3.6-35B-A3B (MoE, seulement 3B actifs à chaque pas), Q4, réflexion désactivée | 20.6 GB | Environ 142 token/s | Environ 0.3 seconde (premier token vers 0.27 seconde) | Recommandé : rapide, jeu de rôle naturel en chinois |
| gpt-oss-20b (MXFP4), raisonnement low | 11.3 GB | Environ 280 token/s | 0.3 à 0.8 seconde | Quand la VRAM est juste ; chinois un peu plat |
| Qwen3.6-27B (dense), Q4 | 17.2 GB | Environ 39 token/s | Toujours en réflexion après 5.5 secondes | Inadapté au dialogue en temps réel |
- La vitesse dépend des « paramètres actifs à chaque pas », pas du total : le MoE de 35B n’en active que 3B et va 3 à 4 fois plus vite que le 27B dense.
- Désactivez impérativement la « réflexion » : sinon tous les tokens partent dans la réflexion, et pas un mot de réponse ne sort.
- La bulle s’écrit caractère par caractère, à environ 22 caractères par seconde : la vitesse de génération n’est plus le goulot d’étranglement. Ce qui compte vraiment pour l’expérience, c’est la latence du premier token.
Des répliques qui sonnent « vrai »
Ne donnez au modèle que des données réelles sur la partie (nombre de parties, victoires et défaites, effectifs, stocks), et précisez explicitement « n’utilisez que ces faits ». En test, sans cette contrainte, le modèle invente des combats qui n’ont jamais eu lieu.