Balões de fala e modelos locais
Faça qualquer unidade do jogo exibir um balão de fala sobre a cabeça, com qualquer identidade; conecte um LLM local e cada frase que entra vira uma resposta sobre a unidade.
Os balões são uma camada visual: não afetam o resultado da partida e servem para lives, narração e depuração.
- Qualquer unidade fala, com qualquer identidade; várias unidades podem falar ao mesmo tempo;
- tamanho da fonte, cor, largura, rabicho, transparência e velocidade de digitação podem ser personalizados em cada balão;
- conecta direto a um LLM local (LM Studio), com saída em streaming: o balão é atualizado enquanto o texto é gerado.
Usando no Bot
O jeito mais simples é o say que vem no SDK:
g.say(hero, "Sigam-me, ataquem!", seconds=4)
Inicialização e interface
O jeito mais fácil: página inicial do Farsight, “Central de controle” — primeiro clique em “LLM local → Iniciar e carregar modelo” (o servidor local do LM Studio + carregar na VRAM o modelo configurado) e depois em “Balões de fala → Iniciar”. No cartão dá para ver os logs, parar e reiniciar.
A interface fica na página “Balões de fala”, na barra lateral esquerda do Farsight: fazer uma unidade falar (escolher a unidade, escrever o texto, ajustar o estilo, conversar com o modelo), roda de conversa dos camponeses, diálogo na câmera, gatilhos da partida e configurações do modelo; tudo atua na instância escolhida na barra superior.
Também dá para usar a linha de comando:
python speech/speak_launch.py # inicia o servidor do modelo local + carrega e aquece o modelo + inicia a API de balões
python speech/speak_launch.py --restart # reinicia a API depois de alterar o código
python speech/speak_launch.py --stop # para a API e descarrega o modelo da VRAM
Cada etapa segue a regra “se já existe, pula”, então rodar de novo não tem efeito colateral.
API HTTP
Padrão: http://127.0.0.1:8872/ (a porta fica em ports.speech, no openwar3.json); qualquer programa pode chamá-la.
Fazer uma unidade falar POST /api/say
{
"inst": 16,
"bubbles": [
{ "unit": "0x14A12614", "name": "Rei da Montanha", "text": "Sigam-me, ataquem!" },
{ "unit": "0x14A12924", "name": "Arquimago", "text": "Deixa que eu lanço a Nevasca.",
"style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
{ "screen": [960, 110], "key": 1, "name": "Narrador", "text": "A primeira leva de orcs chega em 30 segundos.",
"style": { "tail": false, "type_ms": 0 } },
{ "world": [-4684, 2644], "key": 2, "text": "Ponto de encontro", "style": { "font_px": 16 } }
]
}
| Campo | Descrição |
|---|---|
unit / world / screen | Escolha um: segue a unidade (se ela tiver barra de vida, fica logo acima dela) / coordenada do mapa / pixel da tela (para narração) |
name | Quem fala, exibido na primeira linha; pode ser qualquer texto, não precisa ser esta unidade |
text | O texto, com quebra de linha automática |
duration_ms | Por quanto tempo fica visível; 0 = automático, 3 ~ 5 segundos |
key | Identificador de balões de mundo / tela; uma mensagem nova com a mesma key substitui a antiga |
update | Se o mesmo balão já existir, troca só o texto, sem reiniciar o tempo (para streaming) |
style | font_px, max_width_px, text_color, bg_color, border_color, tail, side_px, opacity, type_ms, font… |
No máximo 32 balões ao mesmo tempo; o custo médio é de cerca de 0.1 ~ 0.2 ms por frame.
Conversar com o modelo local POST /api/chat
{
"inst": 16, "unit": "0x14A12614", "name": "Rei da Montanha",
"persona": "Você interpreta Muradin, o Rei da Montanha de Warcraft, expansivo e bom de copo. Uma ou duas frases coloquiais, no máximo 40 palavras.",
"message": "Tem um bando de ogros ali na frente. Vamos atacar?",
"stream": true
}
Retorna {"reply": "...", "first_token_ms": 283, "total_ms": 342}, e a resposta já aparece sobre aquela unidade ao mesmo tempo. Cada unidade lembra as últimas 6 rodadas de conversa.
Outros
| API | Descrição |
|---|---|
GET /api/instances | Jogos em execução |
GET /api/units?inst=16&mine=true&heroes=true | Lista de unidades (com nome em chinês, coordenadas, vida) |
POST /api/clear | Remove um balão ou todos |
GET /api/llm, POST /api/llm | Ver / alterar a configuração do modelo (base_url, model, max_tokens, temperature) |
POST /api/banter | Roda de conversa dos camponeses: os trabalhadores da base se revezam reclamando conforme a persona de cada um, com abertura narrada (todos os dados da partida são reais) |
POST /api/camtalk | Diálogo na câmera: os heróis e seguidores em cena conversam conforme seus papéis |
POST /api/events | Gatilhos da partida: início de combate, fim de combate, herói morto, subida de tier, construção destruída… só fala quando algo acontece |
Como escolher o modelo local
Testado numa RTX 5090 (5 falas de jogo):
| Modelo | VRAM | Velocidade | Uma resposta | Conclusão |
|---|---|---|---|---|
| Qwen3.6-35B-A3B (MoE, só 3B ativos por vez), Q4, raciocínio desligado | 20.6 GB | cerca de 142 token/s | cerca de 0.3 s (primeiro token em cerca de 0.27 s) | Recomendado: rápido, roleplay natural em chinês |
| gpt-oss-20b (MXFP4), raciocínio low | 11.3 GB | cerca de 280 token/s | 0.3 ~ 0.8 s | Para quando a VRAM é curta; chinês um pouco sem graça |
| Qwen3.6-27B (denso), Q4 | 17.2 GB | cerca de 39 token/s | Ainda pensando aos 5.5 s | Não serve para diálogo em tempo real |
- A velocidade depende dos “parâmetros ativos por vez”, não do total: o MoE de 35B ativa só 3B e é 3 ~ 4 vezes mais rápido que o denso de 27B.
- É preciso desligar o “raciocínio” (thinking): senão, todos os tokens vão para o raciocínio e nenhuma palavra sai na resposta.
- O balão digita cerca de 22 caracteres por segundo, então a velocidade de geração já não é o gargalo; o que realmente afeta a experiência é a latência do primeiro token.
Falas que soam reais
Passe ao modelo sempre dados reais da partida (número de partidas, vitórias e derrotas, tropas, estoque) e diga explicitamente “use apenas estes fatos”. Nos testes, sem essa restrição, o modelo inventava batalhas que nunca aconteceram.