Executando verificação de segurança...
4

Pitch: 🎮 Coloquei um emulador de Gameboy colaborativo no Telegram (código aberto)

Prefácio

O ano é 2026, na bolha dev só se fala em agentes de IA e LLMs, e as taxas de FOMO batem recordes dia após dia. Nessas horas, o que fazer? Existe mais de uma única resposta certa, mas certamente uma das possibilidades é um projeto pessoal despretensioso para exercitar essas ferramentas, entender suas limitações, e com sorte aprender algumas coisas.

Este não será um post sobre IA, mas sim sobre alguns dos aprendizados específicos que obtive durante o projeto.

Como assim um emulador colaborativo?

A ideia não é nova: alguns de vocês se lembrarão do fenômeno de Twitch Plays Pokémon, criado há mais de uma década (nessas horas, os cabelos grisalhos começam a aparecer). Em resumo, a ideia era que todos os espectadores de uma live controlassem o jogo simultaneamente enviando comandos através do chat, que eram executados um a um e o resultado era reproduzido em tela.

Aqui, o mesmo acontece, porém no contexto de uma conversa do Telegram.

demo

E como grupos de Telegram são razoavelmente comuns no meio de desenvolvimento e de criação de conteúdo (grupos para apoiadores, etc.), decidi que este seria um projeto de código aberto para que quem quiser possa autohospedar o bot e acrescentar um passatempo a mais em seus grupos.

Chega de ler, onde posso ver como ficou?

O repositório com instruções para subir o bot você encontra aqui (Link do Github).

Caso só queira testar o bot ao vivo, você pode fazê-lo aqui (Link do Telegram). *

*enquanto eu decidir manter este grupo no ar. 😉

Desafios

1. O emulador em si

Antes de mais nada, a principal questão para viabilizar o projeto era: onde a emulação irá acontecer?

Felizmente, o projeto PyBoy existe, e rapidamente se tornou o principal alicerce do bot, por ser um emulador implementado em Python com todas as capacidades que este bot precisa: rodar em modo headless, receber comandos, salvar/carregar o estado do jogo, capturar o conteúdo da tela do jogo, entre outros. Essas capacidades, inclusive, tornaram o PyBoy uma base popular para projetos que utilizam algum tipo de IA para jogar os jogos.

Na data em que escrevo este post, por mais que a maioria dos jogos rode perfeitamente bem nele, é de se esperar que exista uma ou outra pequena incompatibilidade com certas ROMs. No entanto, sua comunidade é bastante ativa no Discord, novos releases ainda têm saído, e inclusive o mantenedor já respondeu a uma issue que eu abri lá recentemente, então sem dúvidas o projeto só se fortalecerá com o tempo!

2. Limitações do Telegram

2.1. Latência das interações

Em seguida, precisamos considerar que o Telegram, enquanto canal de comunicação, tem algumas diferenças importantes em relação à Twitch: não estamos trabalhando com um feed de vídeo contínuo, e sim com mensagens de texto que incluem mídias pré-construídas (as animações do jogo respondendo aos seus inputs). Uma vantagem disso é que podemos exibir botões de ação logo abaixo da mensagem para que os comandos do jogo não poluam o chat e permitam que outras conversas ocorram em paralelo, ao invés de enviá-los pelo chat, como era necessário no caso da Twitch. Uma desvantagem é que não podemos transmitir a emulação em tempo real, precisamos processar os inputs, gravar um pouco da tela adiante (para podermos ver os efeitos do comando), “fechar” esta animação e só então enviar o resultado para os usuários.

Em um emulador normal, o comportamento acima seria um problemão para a experiência do usuário. É fácil de imaginar que, se você apertar um botão e esse comando for enviado a um servidor que irá gravar a tela por 3 segundos antes de mandá-la de volta para o seu chat, a sua experiência com o bot ficará super arrastada pois você estará lidando com uma latência da ordem de segundos em um mundo em que o feedback imediato é soberano.

A solução é mais simples do que parece, graças à capacidade do PyBoy de processar os quadros (ou ticks) do emulador arbitrariamente sem qualquer limite de velocidade. Se soubermos que o jogo normalmente roda a uma taxa de 60 quadros por segundo, e queremos gravar 3 segundos de vídeo, basta pedirmos ao PyBoy que processe os próximos 3 × 60  = 180 quadros, salvarmos os quadros em imagens, e depois reconstruir a animação que será enviada ao Telegram. É mais ou menos como se você gravasse a tela do emulador em modo fast-forward, depois editasse o vídeo para retorná-lo à velocidade normal. Nessa etapa o FFMpeg brilha, fazendo uso de seus presets para codificar o vídeo o mais rápido possível, em detrimento da qualidade. Mas é preciso lembrar que estamos falando de um jogo de Gameboy aqui, e não de gráficos ultrarrealistas, então essa perda de qualidade muitas vezes nem é perceptível. 🙂

2.2. Proteção contra sobrecarga da API

Mais adiante, outra questão iria aparecer: pela natureza do bot, é esperado que ele receba vários comandos em um curto intervalo de tempo, e então precise rapidamente responder editando a mensagem do chat com uma nova animação. Todas essas edições precisam necessariamente passar pela API do Telegram, que impõe alguns limites de frequência (rate limits) para evitar abusos. Nas primeiras versões, eu estava atingindo esses limites mesmo nos meus testes enviando comandos sozinho, então por um momento pensei que o projeto estaria comprometido por essa limitação da infraestrutura da qual ele dependia. Ou será que não?

Aqui, uma estrutura de fila de comandos foi a estratégia para mitigar o problema. Já que estamos gravando alguns segundos de gameplay, por que deveríamos nos apressar para processar os próximos inputs enquanto os usuários do chat estarão assistindo o vídeo gerado? A fila, então, funciona da seguinte forma: o bot sempre aceitará novos comandos, e os colocará na fila. Se ela estiver vazia, processamos o comando imediatamente e enviamos o vídeo resultante para o chat pela API do Telegram. Com base na duração do vídeo gerado (geralmente alguns segundos), colocamos a fila em espera: durante esse tempo todos os inputs que chegarem serão enfileirados, e depois desse tempo a fila fica disponível para processar o próximo comando. Para otimizar, caso um mesmo jogador envie vários comandos de uma vez nesse período, eles serão encadeados e processados numa tacada só.

2.3. Contextualização e recapitulação da partida

Através da Twitch, outra coisa que ganhamos “de brinde” é a possibilidade de reassistir um momento recente da partida através da funcionalidade de VODs (Video on Demand). Como neste caso temos a premissa de não poluir o chat, e para isso estamos trabalhando com edições sobre uma mesma mensagem, rapidamente perderemos todo o histórico da partida, e quem não estiver acompanhando de perto poderá ter dificuldades para entender em que momento do jogo o chat está.

Pensando nisso, uma das últimas funcionalidades que trouxe para o bot é a construção incremental de um timelapse desde o início da partida, que pode ser resgatado através de um comando (/recap YYYYMMDD passando o dia em questão para reassistir).

Conceitualmente, essa funcionalidade traz algumas preocupações. Talvez a principal delas seja o armazenamento: como manter todo esse histórico visual do jogo no nosso servidor da maneira mais eficiente possível?

Ao invés de armazenar os quadros para reconstruir o timelapse a cada vez que o comando for recebido, optei mais uma vez por acessar os poderes do FFMpeg. A estratégia é a seguinte: cada chat terá uma pasta e, a cada vez que for recebido um comando e processado um input, iremos assincronamente (para não comprometer a latência do bot) codificar a animação deste último input outra vez, dessa vez com um preset que nos dê a melhor compressão, e colocar em um arquivo MP4 temporário. Se este for o primeiro MP4 deste chat neste dia, ele será promovido a MP4 principal. Caso contrário, chamaremos o concat demuxer do FFMpeg para concatenar o MP4 principal com o MP4 temporário, gerando um terceiro arquivo que terá a animação completa. Por fim, substituímos atomicamente o MP4 principal por este terceiro arquivo, e limpamos os arquivos temporários gerados. Nesta configuração, por exemplo, um teste com um timelapse de 47 segundos (numa taxa de 1 quadro a cada 20 quadros de gameplay, correspondendo a aproximadamente 15 minutos de gameplay) ocupa apenas 1.6 MB.

Outro ganho interessante está em lembrar em cache o ID do arquivo retornado pelo Telegram após enviar o arquivo, em resposta a um comando /recap. Desse modo, não precisamos refazer o upload a cada vez que alguém chamar o comando deste chat para este mesmo dia. É só não esquecer de invalidar este cache caso cheguem novos inputs e o dia em questão seja hoje, pois o vídeo do timelapse terá sido atualizado.

No geral, considero que esta é uma área que ainda tem espaço para otimização. Seria possível, por exemplo, consolidar todos os timelapses de um período (semana / mês) em um só vídeo e enviar para o YouTube, liberando o espaço dos arquivos no armazenamento local.

Conclusão

O que começou como um pequeno projeto acabou prendendo minha atenção por vários fins de semana, cada vez com uma nova funcionalidade ou melhoria, mesmo sem intenção de monetizá-lo. Com tantas ferramentas para acelerar o processo de codificação, acredito que estar próximo das decisões do seu projeto se tornou mais importante do que nunca, assim como refletir e aprender com os problemas enfrentados. E testes, não se esqueça dos testes.

Ficarei feliz em responder qualquer comentário, dúvida ou sugestão que tenham a respeito do projeto. Ou ainda, se quiserem contribuir com algo relevante, fiquem à vontade para abrir um Pull Request lá.

Se você leu até aqui, muito obrigado pela atenção! Se você usou uma LLM para resumir este texto, tudo bem também, sem ressentimentos.

Carregando publicação patrocinada...