Burbujas y modelos locales
Haz que cualquier unidad del juego muestre burbujas de diálogo con cualquier identidad; conecta un LLM local y cada frase que entra recibe una respuesta sobre la cabeza de la unidad.
Las burbujas son una capa para el espectador: no afectan al resultado y sirven para directos, comentarios y depuración.
- Cualquier unidad puede hablar con cualquier identidad; varias unidades pueden hablar a la vez;
- El tamaño de letra, el color, el ancho, la cola, la opacidad y la velocidad de escritura de cada burbuja se pueden personalizar por separado;
- Se conecta directamente a un LLM local (LM Studio) con salida en streaming: la burbuja se actualiza mientras se genera el texto.
Desde un Bot
Lo más sencillo es el say que incluye el SDK:
g.say(hero, "¡Conmigo, a la carga!", seconds=4)
Arranque e interfaz
Lo más cómodo: el “Centro de control” de la página de inicio de Farsight. Primero pulsa “LLM local → Iniciar y cargar el modelo” (el servicio local de LM Studio + cargar en la VRAM el modelo configurado) y después “Burbujas de diálogo → Iniciar”. En las tarjetas puedes ver el log, detener y reiniciar.
La interfaz está en la página “Burbujas de diálogo” de la barra lateral izquierda de Farsight: hacer hablar a unidades (elegir la unidad, escribir el texto, ajustar el estilo, conversar con el modelo), tertulia de campesinos, diálogos de cámara, reacciones a la partida y ajustes del modelo; todo actúa sobre la instancia elegida en la barra superior.
También desde la línea de comandos:
python speech/speak_launch.py # arranca el servicio del modelo local + carga y precalienta el modelo + arranca la API de burbujas
python speech/speak_launch.py --restart # reinicia la API tras cambiar el código
python speech/speak_launch.py --stop # detiene la API y descarga el modelo de la VRAM
Cada paso es “si ya está, se omite”, así que ejecutarlo varias veces no tiene efectos secundarios.
API HTTP
Por defecto, http://127.0.0.1:8872/ (el puerto está en ports.speech de openwar3.json); cualquier programa puede llamarla.
Hacer hablar a una unidad POST /api/say
{
"inst": 16,
"bubbles": [
{ "unit": "0x14A12614", "name": "Rey de la Montaña", "text": "¡Conmigo, a la carga!" },
{ "unit": "0x14A12924", "name": "Archimago", "text": "Yo lanzo la Ventisca.",
"style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
{ "screen": [960, 110], "key": 1, "name": "Narrador", "text": "La primera oleada de orcos llega en 30 segundos.",
"style": { "tail": false, "type_ms": 0 } },
{ "world": [-4684, 2644], "key": 2, "text": "Punto de reunión", "style": { "font_px": 16 } }
]
}
| Campo | Descripción |
|---|---|
unit / world / screen | Uno de los tres: sigue a una unidad (si tiene barra de vida, justo encima de ella) / coordenadas del mapa / píxeles de pantalla (para el narrador) |
name | Quién habla, en la primera línea; texto libre, no tiene por qué ser esa unidad |
text | Texto, con salto de línea automático |
duration_ms | Cuánto tiempo se muestra; 0 = automático, 3 ~ 5 segundos |
key | Número de una burbuja de mundo / pantalla; un mensaje nuevo con la misma key sustituye al anterior |
update | Si ya existe esa burbuja, solo cambia el texto sin reiniciar el temporizador (para streaming) |
style | font_px, max_width_px, text_color, bg_color, border_color, tail, side_px, opacity, type_ms, font… |
Como máximo 32 burbujas a la vez; el coste medio por fotograma es de unos 0.1 ~ 0.2 ms.
Conversar con un modelo local POST /api/chat
{
"inst": 16, "unit": "0x14A12614", "name": "Rey de la Montaña",
"persona": "Eres Muradin Barbabronce, el Rey de la Montaña de Warcraft: campechano y amante de la cerveza. Responde con una o dos frases coloquiales, de no más de 40 caracteres.",
"message": "Hay un grupo de ogros delante, ¿cargamos o no?",
"stream": true
}
Devuelve {"reply": "...", "first_token_ms": 283, "total_ms": 342}, y para entonces la respuesta ya está sobre la cabeza de esa unidad. Cada unidad recuerda las últimas 6 rondas de conversación.
Otros
| Endpoint | Descripción |
|---|---|
GET /api/instances | Partidas en curso |
GET /api/units?inst=16&mine=true&heroes=true | Lista de unidades (con nombre en chino, coordenadas y vida) |
POST /api/clear | Borra una burbuja o todas |
GET /api/llm, POST /api/llm | Ver / cambiar la configuración del modelo (base_url, model, max_tokens, temperature) |
POST /api/banter | Tertulia de campesinos: los trabajadores de la base se quejan por turnos según su personaje, con una presentación al inicio (todos los datos de la partida son reales) |
POST /api/camtalk | Diálogos de cámara: los héroes y sus acompañantes que salen en cámara conversan según su identidad |
POST /api/events | Reacciones a la partida: empieza un combate, termina, cae un héroe, se sube de tier, destruyen un edificio… solo hablan cuando pasa algo |
Cómo elegir un modelo local
Medido en una RTX 5090 (5 frases de diálogo del juego):
| Modelo | VRAM | Velocidad | Una respuesta | Conclusión |
|---|---|---|---|---|
| Qwen3.6-35B-A3B (MoE, solo 3B activos por paso), Q4, sin razonamiento | 20.6 GB | Aprox. 142 token/s | Aprox. 0.3 s (primer token en aprox. 0.27 s) | Recomendado: rápido y con un rol natural en chino |
| gpt-oss-20b (MXFP4), razonamiento low | 11.3 GB | Aprox. 280 token/s | 0.3 ~ 0.8 s | Si vas justo de VRAM; en chino, algo plano |
| Qwen3.6-27B (denso), Q4 | 17.2 GB | Aprox. 39 token/s | A los 5.5 s seguía pensando | No sirve para diálogo en tiempo real |
- La velocidad depende de los “parámetros activos por paso”, no del total: el MoE de 35B solo activa 3B y es 3 ~ 4 veces más rápido que el denso de 27B.
- Hay que desactivar el “razonamiento”: si no, todos los tokens se van en pensar y no responde nada.
- Las burbujas escriben unos 22 caracteres por segundo, así que la velocidad de generación ya no es el cuello de botella; lo que de verdad marca la experiencia es la latencia del primer token.
Que los diálogos parezcan reales
Pasa siempre al modelo datos reales de la partida (partidas jugadas, victorias y derrotas, tropas, recursos) y dile claramente “usa solo estos hechos”. En las pruebas, sin esa restricción, el modelo se inventaba combates que nunca ocurrieron.