allanlotta
~/blog/rewriting-empire-earth-with-agents

Reescrevendo um RTS de 2001 com agentes que testam o próprio trabalho

Empire Earth saiu em 2001. Eu queria o jogo no macOS, com dezenas de milhares de unidades e uma IA que revida. Ninguém ia lançar isso, então comecei uma reescrita em Rust.

A maior parte do código foi escrita por agentes. Meu trabalho foi projetar o sistema em que eles operam: o que podem ver, o que podem tocar, como provam que estão certos e como melhoram nisso. Este post é sobre esse sistema, porque o formato dele não tem nada a ver com jogos.

O loop

Tudo que os agentes fazem é um único loop. A ordem muda dependendo da pergunta, mas cada peça está sempre lá.

 ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
 │ coletar  │──▶│ analisar │──▶│construir │──▶│  testar  │
 │ original │   │evidência │   │  remake  │   │  no lab  │
 └──────────┘   └──────────┘   └──────────┘   └────┬─────┘
       ▲                                           │
       │                                           ▼
 ┌─────┴────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
 │ melhorar │◀──│registrar │◀──│ corrigir │◀──│ comparar │
 │ ferram.  │   │ memória  │   │          │   │ original │
 └──────────┘   └──────────┘   └──────────┘   └──────────┘

Coletar o que o original faz. Analisar até haver evidência. Construir o equivalente no remake. Testar em um lab onde o resultado é um número. Rodar o mesmo cenário no original e comparar. Corrigir o que difere. Registrar o que foi aprendido, incluindo o que se mostrou errado. Depois melhorar a ferramenta ou a regra que teria pegado isso antes, e recomeçar.

O agente não é uma etapa desse loop. O agente roda o loop.

As camadas

              ┌──────────────────────────────┐
              │eu: metas, revisão, prioridade│
              └──────────────┬───────────────┘
                             │
              ┌──────────────▼───────────────┐
              │ agentes de código e pesquisa │
              └───────┬──────────────┬───────┘
                      │              │
        ┌─────────────▼────┐   ┌─────▼──────────────┐
        │     memória      │   │    ferramentas     │
        │                  │   │                    │
        │arquivo/descoberta│   │ 96 pipelines python│
        │ busca por tópico │   │ disassembly, RTTI  │
        │ páginas canônicas│   │ parsers de assets  │
        │ flags de superado│   │ CLI do jogo (win)  │
        └─────────────┬────┘   └─────┬──────────────┘
                      │              │
              ┌───────▼──────────────▼───────┐
              │          avaliação           │
              │                              │
              │  lab do jogo original        │
              │  lab de cenários do remake   │
              │  lab de paridade de UI       │
              │  comparador de fidelidade    │
              └──────────────────────────────┘

Eu fico no topo e decido o que importa. Os agentes são donos do meio. Memória e ferramentas são o que faz a próxima sessão começar mais adiante do que a anterior. A avaliação é o que impede que "parece certo" seja uma resposta aceitável.

Coletar

Antes de escrever qualquer coisa nova, os agentes desmontaram o original. Seis pacotes proprietários, 4.940 arquivos. Todos os 730 modelos 3D parseados, 727 deles idênticos byte a byte no round trip, exportados para glTF para o novo renderer. Todos os 58 scripts de IA achatados em 3.115 transições de estado concretas. Cada tabela do banco de dados, cada cenário, 3.398 strings localizadas.

Nada disso foi feito à mão. Cada formato ganhou um parser, cada parser virou um pipeline com CLI e validador, e cada pipeline ficou no repositório para o próximo agente.

Analisar

Estático primeiro, runtime depois. O disassembly explica o porquê; o jogo rodando diz o quê. Os agentes trabalham no executável Windows de 32 bits a partir do macOS com LIEF e Capstone: 1.132 classes C++ catalogadas via RTTI, vtables nomeadas, endereços de função mapeados para classe e slot, e toda a camada de mensagens do lockstep recuperada: 103 tipos de comando com seus layouts de wire.

O melhor exemplo do método é um erro. Uma passada inicial concluiu que a tabela de tuning da IA era metadado do editor, porque uma busca por referências à tabela encontrou seis. Uma passada posterior notou que o compilador tinha dobrado cada acesso em um endereço absoluto, varreu o intervalo inteiro, encontrou mais de 200 e provou em runtime que a tabela é o painel de controle da IA. A descoberta errada não foi apagada. Foi marcada como superada, e a regra "varra o intervalo, não a base" entrou no processo.

Construir

O remake é Rust sobre Bevy ECS, três crates: dados, simulação, IA. Os agentes escrevem com base nas páginas canônicas, não na memória que têm do jogo. Quando falta um fato, a construção para e o loop volta para coletar.

Testar no lab

O remake tem um lab de cenários headless. Um agente escreve um cenário em JSON: unidades, seed, telemetria a registrar, quando parar, o que verificar. Ele roda a simulação real pelo caminho normal de comandos e recebe linhas JSON de volta.

 scenario.json ──▶ ee_sim (headless) ──▶ events.jsonl
   seed: 42                                 damage
   units: 30 v 30                           moved
   stop_when: t > 120s                      trained
   assert: vermelho vence                   tick_ms

Sem pixels, sem mouse, sem me pedir para jogar. Combate, pathfinding, economia, formações, comportamento da IA e performance são todas perguntas respondidas em segundos. Qualquer coisa que um dia achou um bug fica como regressão. Um fuzzer gera streams de comandos aleatórios e checa invariantes como conservação de recursos e "nenhuma unidade fica presa em um estado que não faz progresso".

A interface tem o mesmo tratamento. O HUD do original é dado, não imagem, então um lint compara a árvore de UI do remake com as tabelas do original, clica em controles reais e faz diff de screenshots por controle.

Comparar com o original

Há uma máquina Windows na rede com o jogo instalado. Do Mac, os agentes conseguem abrir o jogo, tirar screenshot, ler a memória dele e, como a camada de mensagens foi decodificada, controlá-lo por código.

 mac (agente)                      windows (jogo original)
 ─────────────                     ───────────────────────
 ee-inject select 0x01000001  ──▶  mesma mensagem que a UI envia
 ee-inject move 388 196       ──▶  fila lockstep  ──▶ engine
 ee-scan selected             ◀──  lê a memória: posição, estado
 ee-call FindUnitById         ◀──  pergunta direto à engine

Vinte comandos nativos estão provados: selecionar, mover, coletar, atacar, construir, treinar, pesquisar, patrulhar, formações, velocidade do jogo. O mesmo cenário JSON roda nas duas engines e um comparador normaliza tick rate, origem e escala do mapa antes de comparar movimento, combate e economia.

Foi assim que o remake conseguiu seus números. Madeira rende 15 unidades por viagem a 0,209 por segundo. Um cidadão acerta um hipopótamo por 3,75 a cada 1,955 segundos. Prédios sobem do chão durante a construção, não escalam. Um zero na matriz de dano significa imune, não um de dano. Muralhas são um corte no caminho até a capital inimiga, não uma cerca.

Corrigir

Uma correção só é correção se o número se move. Em uma caçada a bugs, onze correções saíram com medições de antes e depois. Uma correção proposta foi medida, piorou as coisas e foi rejeitada no mesmo documento. Esse documento é a evidência para o próximo agente que se perguntar por que a correção óbvia não está lá.

Registrar

Cada descoberta é um arquivo com tópicos, evidência, status e links. Uma ferramenta de consulta carrega só o tópico em questão e sinaliza entradas que depois foram refutadas, superadas ou revertidas. As páginas canônicas guardam o que é verdade hoje; o log guarda como chegamos lá.

 pergunta ──▶ findings_query --topic combat
                 │
                 ├── F-0212 atual        ← citar
                 ├── F-0198 superada     ← não citar, veja por quê
                 └── canon/combat.md     ← a verdade hoje

Isso substituiu um único relatório que tinha crescido até 375k tokens, que ninguém, humano ou agente, conseguia ler. As contradições vinham se acumulando porque a regra dizia "leia tudo" e nada conseguia.

Melhorar

Duas regras fazem o loop compor. Qualquer tarefa feita duas vezes vira uma ferramenta. Qualquer erro que custou tempo vira uma linha nas regras de operação, ao lado da checagem que o pega.

O arquivo do projeto tem uma lista dessas: uma tela modal congela o jogo enquanto o contador de ticks continua subindo, então exija que a flag de modal seja falsa antes de concluir que um comando falhou. A máquina Windows compartilhada devolve screenshots contaminados em silêncio, então verifique a imagem, não o nome dela. Um teste que passa sob um tuning que o jogo real nunca usa não prova nada, então sempre declare qual tuning está em uso.

Cada uma custou um dia uma vez. Nenhuma custou duas.

Por que isso não é sobre jogos

Troque os substantivos e o loop é um processo de negócio.

 jogo                        qualquer empresa
 ─────────────────────       ─────────────────────────────
 executável original         o sistema legado, ou a forma
                             como o trabalho é feito hoje
 descobertas + cânone        o que é de fato verdade sobre ele
 parsers e pipelines         ferramentas que seus agentes criam
 lab de cenários             um teste que devolve um número
 comparação com o original   o mesmo teste contra a realidade
 descobertas superadas       erros mantidos, não escondidos
 regras no CLAUDE.md         o processo melhorando a si mesmo

A parte difícil nunca foi o modelo. Foi dar aos agentes uma memória que não mente, ferramentas que eles podem estender e um jeito de checar o próprio trabalho contra a realidade antes de eu olhar. São as mesmas três coisas para um agente de QA, um agente de contratação ou um pipeline de conteúdo.

O que eu fiz

Eu não escrevi o pathfinder nem o código de posicionamento da IA. Eu projetei o loop, construí a primeira versão de cada ferramenta, escrevi as regras, revisei o que saiu e medi. Depois garanti que cada lição terminasse em uma ferramenta ou em uma regra, não na minha cabeça.

Onde está agora: 96 pipelines reutilizáveis em 26 suítes, 275+ descobertas, 20 comandos nativos no original, 27+ cenários de regressão preservados e um remake em que a IA constrói uma base, cerca com muralhas e avança uma época sozinha.