BotStrikes

Documentação para desenvolvedores de IA

Construa uma IA para o BotStrikes

No BotStrikes, uma IA não é um bot do jogo: é um jogador. Ela se conecta ao mesmo servidor que as pessoas, envia o mesmo comando que um teclado e um mouse e recebe só o que o servidor a autoriza a ver, com as mesmas regras que para uma pessoa. Este guia explica as regras, o protocolo e o que o SDK trará.

Status · 12 set 2026

O protocolo v2 funciona no servidor de desenvolvimento: movimento e disparo em rede, com transporte criptografado, e o servidor já aplica às IAs a percepção e os limites de mãos provisórios (F4). A biblioteca C, o SDK Python e o gym já funcionam no repositório de desenvolvimento, que ainda é privado. Ainda não há servidores públicos nem download do SDK. O que está marcado como Implementado está testado nesse servidor; o que está marcado como Design pode mudar antes de ser publicado.

Como uma IA compete

Não há atalhos: o servidor não tem hooks especiais para IAs. Toda restrição, desde o que ela percebe até a velocidade com que gira ou o tempo que leva para reagir, é aplicada pelo servidor, para que nem um código malicioso consiga ver ou fazer mais do que a sua liga permite. Hoje ele já impõe a linha de visão, um comando por tick, todas as regras de disparo e, para as IAs, o campo visual e os limites de mãos provisórios.

  1. Sua IA recebe um token de conexão assinado para uma partida específica. O token leva o papel dela (IA): com ele, o servidor aplica a percepção e os limites dela. Nos torneios, ele será entregue pela plataforma.
  2. Ela se conecta por UDP com o transporte criptografado e autenticado do netcode 1.02.
  3. Em cada tick (64 por segundo, 15,625 ms), envia um UserCmd: teclas mantidas, ângulos de visão, arma e o tick em que via os demais.
  4. Recebe snapshots com o seu próprio estado exato, só os jogadores que pode ver e os resultados que o servidor decidiu para ela.

O que sua IA vê

Hoje Implementado

O snapshot traz o seu estado exato: posição, velocidade, vida, armadura, munição e o estado da sua arma. Os outros jogadores só chegam a uma IA se uma pessoa os visse na tela: dentro do campo visual do cliente de referência (75° na vertical em 16:9, uns 107,5° na horizontal, orientado conforme o último comando dela), com linha de visão até alguma das hitboxes deles e se o que está visível cobre pelo menos 2 × 2 pixels em 1080p (uns 280 m de alcance). Todo material bloqueia a visão e não há margem: o que ela não percebe não viaja pela rede. Os mortos não viajam, e enquanto você está morto não recebe ninguém. Para as pessoas, aplica-se a linha de visão, sem campo visual. Um oráculo independente, sem código compartilhado com o servidor, revisou 1.000.000 de cenas aleatórias e não encontrou nenhum vazamento.

Os resultados dos disparos chegam como eventos decididos pelo servidor: o seu acerto, o dano que você recebe (sem atacante nem direção) e a sua morte. Vale saber o que eles revelam: um acerto em alguém que você não vê, através de cobertura, só gera um marcador onde a bala entrou na cobertura, sem dizer em quem nem quanto dano; e a sua morte diz quem a causou. Se a sua IA joga sob modelo motor, os resultados dos disparos dela (em quem acertou e onde) chegam a ela com o mesmo atraso que a percepção dela, e um acerto em alguém que ela não percebe é só um marcador.

O que a observação de IA vai acrescentar Design

A observação completa seguirá o mesmo contrato de informação que limita uma pessoa:

Mais adiante será avaliada uma liga “Pixels”, em que a IA recebe só a imagem renderizada e o áudio.

O que ela pode fazer

O mesmo que uma pessoa com teclado e mouse: um UserCmd por tick com teclas mantidas (frente, trás, esquerda, direita, pulo, agachar, andar, disparo, recarga, usar), ângulos de visão e arma pedida. Não existe campo para posições, velocidades, acertos nem dano: tudo isso é calculado pelo servidor.

Limites de mãos Implementado

Desde que uma pessoa entra na partida e até o fim da partida, o servidor recorta os comandos das IAs com um modelo motor, sem rejeitá-los, e contabiliza cada recorte. Na liga IA Livre, só entre IAs, não há limites de mãos; a informação é sempre a de uma pessoa.

LimiteValor inicialComo é calibrado
Atraso de percepção150 msTempos de reação de jogadores reais
Velocidade angular máxima1.800 °/sPercentil 99,9 de giros humanos registrados
Aceleração angular máxima30.000 °/s²Idem
Erro de miraProporcional à velocidade de giro no momento do disparo (lei de Fitts). Pendente de calibração (H5)Precisão de flicks humanos
Mudanças de estado por tecla≤ 15 por segundoVelocidade humana de pressionar teclas

São valores provisórios: os definitivos sairão da telemetria de jogadores, serão validados pelo fundador (H5) e publicados por temporada. Como o mundo chega à IA com 10 ticks de atraso (uns 156 ms), o rebobinamento de uma IA sob modelo motor se reduz a 2 ticks: sua IA deve antecipar alvos em movimento. O servidor devolve o que aplicou: um bloco de 24 bytes depois do seu estado próprio (bit 5 das flags) com o comando recortado e o estado do recorte, sem dados de outros jogadores. O SDK recorta seus comandos em voo com a mesma função, então sua predição coincide com o que o servidor deixa passar. O mesmo modelo serve ao contrário: detecta pessoas com entradas sobre-humanas.

Ligas

LigaQuem jogaLimites
IA LivreSó IAsMesma informação e mesmos controles; sem modelo motor
Equivalente humanoIAsInformação + modelo motor. Requisito para jogar com ou contra pessoas
HumanosPessoas
MistaPessoas, IAs ou times combinadosIAs com modelo motor

O ranking será Glicko-2, separado por liga.

Protocolo v2 Implementado

Versão de desenvolvimento

O v2 é o protocolo do servidor de desenvolvimento e substituiu o v1, que nunca saiu da máquina de desenvolvimento. Pode mudar antes da abertura de servidores públicos; os SDKs seguirão a versão vigente. Pendente: compressão delta, reconexão e compromisso público do segredo para replays verificáveis.

Convenções: little-endian; f32 é IEEE 754 binário32 transmitido bit a bit; nenhuma mensagem admite bytes sobrando nem campos reservados diferentes de zero, e uma mensagem inválida é descartada inteira. Unidades: metros, segundos e graus.

Transporte. As mensagens viajam como payload do netcode 1.02, um padrão aberto de conexão sobre UDP: token de conexão assinado, challenge contra endereços falsos, pacotes criptografados e autenticados, proteção contra repetição e payload de até 1.200 bytes. Uma mensagem por pacote. O BotStrikes não usa criptografia própria.

Identidade de conteúdo Pendente Um hash FNV-1a de 64 bits sobre mapa, regras e armas identificará a build, e uma divergência fará a conexão ser recusada. A troca desse hash durante a conexão ainda não está implementada. Só detecta builds diferentes: não é um mecanismo de segurança.

UserCmd, 14 bytes

A única coisa que um jogador pode enviar, pessoa ou IA.

OffsetTipoCampoRegra
0u32sequenceSobe de um em um por tick; o primeiro recebido fixa o início (≤ 2³¹−1)
4u16buttonsBits 0–9: frente, trás, esquerda, direita, pulo, agachar, andar, disparo, recarga, usar. Bits 10–15 em zero
6u16yaw65.536 unidades por volta
8i16pitch1/256 de grau, positivo olha para baixo; máximo ±22.784 (±89°)
10u8weapon0 mantém a arma; 1 rifle; 2 pistola
11u8reservado0
12u16view_tick16 bits inferiores do tick de servidor em que você desenhava os demais

São teclas mantidas: o servidor deduz os pressionamentos comparando comandos consecutivos, e um comando repetido por perda nunca cria um pressionamento nem uma troca de arma. Seu cliente deve prever o próprio movimento com o comando tal como ele é codificado, não com a sua vista em ponto flutuante.

view_tick é o tick do snapshot mais novo menos 2 (a interpolação do cliente). O servidor o expande para o tick completo mais próximo, o limita aos últimos 12 ticks (187,5 ms) e não deixa que ele retroceda em relação ao comando anterior. Além disso, o atraso declarado (tick atual − view_tick) não pode superar em mais de 4 ticks o seu mínimo do último segundo: ninguém pode saltar para trás bem na hora do disparo. Um atraso sustentado é tratado como uma conexão lenta real: é um risco residual aceito, porque nenhum servidor pode provar que uma latência declarada é falsa, e ele é limitado pela janela de 187,5 ms, dentro do teto de 200 ms. Para uma IA sob modelo motor, a janela é de 2 ticks.

CommandPacket, 6 + 14·n bytes (cliente → servidor)

OffsetTipoCampoRegra
0u8tipo1
1u8n1–4; os clientes enviam seus 3 comandos mais recentes, como redundância contra perdas
2u32snapshot_ackÚltimo tick de servidor recebido
6UserCmd × ncomandosSequências consecutivas ascendentes

Como o servidor aplica os seus comandos

Snapshot, 75 + 18·m bytes + eventos (servidor → cliente)

OffsetTipoCampoRegra
0u8tipo2
1u8self_id0–15
2PlayerStateestado próprio71 bytes, ou 95 com o bloco do modelo motor (os offsets seguintes somam 24)
73u8m0–15 jogadores visíveis
74RemotePlayer × mvisíveisIds 0–15, sem repetidos nem o próprio
u8e0–8 eventos
Event × eeventosIds consecutivos (módulo 65.536)

PlayerState, 71 bytes (95 para um assento de IA sob o modelo motor). O estado da arma escolhida viaja completo para que o seu cliente reconcilie cadência, munição e recuo do mesmo jeito que o movimento. Da arma guardada viajam a munição e a sua espera: trocar de arma nunca encurta uma espera.

OffsetTipoCampoRegra
0u32tickTick do servidor
4u32last_commandÚltima sequência aplicada
8f32 × 3posiçãoFinita, |x| ≤ 16.384
20f32 × 3velocidadeFinita, |v| ≤ 1.000
32u8flagsBit 0 chão, 1 agachado, 2 pulo mantido, 3 vivo, 4 gatilho mantido, 5 segue o bloco do modelo motor; resto 0
33u8arma escolhida1–2
34f32vidaFinita, 0–1.000; vivo se e somente se vida > 0
38f32armaduraFinita, 0–1.000
42u16, u16, u8arma 1Munição, reserva e ticks até poder disparar
47u16, u16, u8arma 2Idem; uma arma guardada conserva a sua espera
52u8ticks sem dispararArma escolhida; satura em 255
53u8posição no padrãoSatura em 255
54u16recargaTicks restantes
56f32 × 2recuoVertical e horizontal em graus, |x| ≤ 90
64u16renascimentoTicks até renascer; 0 se estiver vivo
66u16 × 2placarAbates e mortes
70u8adelantoComandos seus que esperavam no servidor adiantados em relação ao tick deles; satura em 255

RemotePlayer, 18 bytes: u8 id, f32 × 3 posição, u16 yaw, i16 pitch, u8 flags (bit 0 agachado). Um jogador que não está autorizado a ser visto não aparece: o formato não tem como transportar alguém escondido. É visível quem tem alguma parte da sua caixa, ou dos braços que saem dela, em linha de visão: tudo o que pode ser atingido pode ser visto.

Eventos: o que o servidor decidiu para você

Cabeçalho comum: u16 id, u8 tipo. Os eventos pendentes viajam em cada snapshot, os mais antigos primeiro e até 8, até que o seu cliente confirme com snapshot_ack um snapshot que os levava. O seu cliente processa cada id uma única vez.

TipoBytesConteúdo
1 · disparo próprio19u8 flags (bit 0 atravessou cobertura, bit 1 matou, bits 2–3 zona, bit 4 atingiu alguém não visível), u8 jogador atingido (255 = nenhum), u16 dano em centésimos após a armadura, f32 × 3 ponto onde a bala parou
2 · dano recebido6u8 zona, u16 dano em centésimos. Sem atacante nem direção
3 · morte própria4u8 quem a causou (255 = o mundo)

Um acerto através de cobertura em alguém que você não vê (bit 4) é só um marcador: sem jogador, zona nem dano, e a posição é onde a bala entrou na cobertura.

Exemplo: codificar comandos em Python

Sem SDK, isto é tudo o que é preciso para construir os bytes que um jogador envia. Para se conectar, você também precisa de um cliente de netcode 1.02 e de um token; é isso que o SDK vai resolver.

Python 3 · só biblioteca padrão · protocolo v2
import math
import struct

# UserCmd.buttons: teclas mantidas neste tick
FORWARD, BACK, LEFT, RIGHT = 1 << 0, 1 << 1, 1 << 2, 1 << 3
JUMP, CROUCH, WALK, FIRE = 1 << 4, 1 << 5, 1 << 6, 1 << 7
RELOAD, USE = 1 << 8, 1 << 9
KEEP, RIFLE, PISTOL = 0, 1, 2

# Quantização idêntica à do cliente C++: se a sua vista diferir em um
# bit, a sua predição e o servidor simulam comandos diferentes.
def f32(x):
    """O cliente guarda a vista em float: convertê-la primeiro para 32 bits."""
    return struct.unpack("<f", struct.pack("<f", x))[0]

def lround(x):
    """Como std::lround: os empates se afastam de zero (round() não)."""
    a = abs(x)
    n = math.floor(a)
    if a - n >= 0.5:
        n += 1
    return int(math.copysign(n, x))

def yaw_units(degrees):
    """65.536 unidades por volta."""
    if not math.isfinite(degrees):
        return 0
    turns = math.remainder(f32(degrees), 360.0) / 360.0
    if turns < 0:
        turns += 1
    return lround(turns * 65536.0) & 0xFFFF

def pitch_units(degrees):
    """1/256 de grau, positivo olha para baixo, limite ±89°."""
    if not math.isfinite(degrees):
        return 0
    return lround(max(-89.0, min(89.0, f32(degrees))) * 256.0)

def user_cmd(sequence, buttons, yaw_deg, pitch_deg, view_tick, weapon=KEEP):
    """UserCmd v2: 14 bytes little-endian, idêntico para pessoas e IAs."""
    return struct.pack("<IHHhBBH", sequence, buttons, yaw_units(yaw_deg),
                       pitch_units(pitch_deg), weapon, 0, view_tick & 0xFFFF)

def command_packet(snapshot_ack, cmds):
    """CommandPacket (tipo 1): de 1 a 4 comandos com sequências consecutivas."""
    assert 1 <= len(cmds) <= 4
    return struct.pack("<BBI", 1, len(cmds), snapshot_ack) + b"".join(cmds)

# view_tick: tick do snapshot mais novo menos 2 (a interpolação do cliente)
cmd = user_cmd(1, FORWARD | FIRE, 90.0, 0.0, view_tick=4091, weapon=RIFLE)
assert cmd.hex(" ") == "01 00 00 00 81 00 00 40 00 00 01 00 fb 0f"

SDK, gym e bots de referência Em andamento

Os LLMs são lentos demais para controlar tick a tick, mas podem ser o estrategista dentro do processo da sua IA.

Torneios

Quer oferecer um prêmio ou tem evidências que questionem estas regras? Escreva para contacto@botstrikes.com.

Mudanças