BotStrikes

Guia do SDK vigente

Construa uma IA para o BotStrikes

Status ·

BotStrikes 0.2.0 · protocol 15 · Agent API 14 · bs_observation: 61 · bs_action: 11.

O SDK e o ambiente de treinamento funcionam no repositório privado. Este guia documenta a interface atual para quem tem acesso ao repositório ou a um kit de testes. Não há download público do SDK nem servidor público de jogo verificados.

Como começar

Uma IA usa o transporte criptografado e as entradas públicas do jogo. Precisa de um convite de IA emitido pelo servidor, uma biblioteca da mesma versão e o mesmo mapa e conteúdo. Os exemplos partem da raiz do repositório, com build/check já compilado por tools/check.sh.

A primeira conexão

Inicie o servidor local em um terminal. As caixas dos locais habilitam o objetivo de bomba. Em outro terminal, execute o exemplo com o token emitido. Não reutilize um token após desconectar: solicite um reserva.

build/check/bs_server --listen 127.0.0.1:27149 --players 0 --ai-players 1 \
  --tokens build/tutorial-tokens --seconds 60 --map build/check/content/cinta.bsm \
  --mode teams --rounds 8 \
  --bomb-site-a -36.9,0,12.6,-26.9,3,22.6 --bomb-site-b 23.6,0,11.5,33.6,3,21.5
PYTHONPATH=sdk/python BOTSTRIKES_AGENT_LIB=build/check/libbotstrikes_agent.so \
  python3 sdk/python/examples/first_bot.py --token build/tutorial-tokens/ai-1.token \
  --map build/check/content/cinta.bsm --seconds 10

O resultado esperado é Joined observations maior que zero e saída correta. O exemplo anda e gira; não demonstra navegação nem jogo do objetivo. Agent administra a conexão; o objeto de política devolve Action; run recebe pares de agente e política.

"""A small, executable SDK tutorial. Run from the repository with PYTHONPATH=sdk/python."""
import sys
from pathlib import Path

from botstrikes import Action, Agent, run
from botstrikes.cli import SDKError, diagnostic, message, parser_for, valid_duration


class FirstBot:
    def __init__(self):
        self.observations = 0

    def act(self, observation):
        action = Action()
        if observation.joined:
            self.observations += 1
        if observation.joined and observation.alive:
            action.forward = 1.0
            action.yaw = observation.yaw + 1.0
        return action


def main(argv=None):
    parser, language, argv = parser_for(argv, 'first_bot')
    parser.add_argument('--token', required=True, type=Path, help=message(language, 'token'))
    parser.add_argument('--map', required=True, type=Path, help=message(language, 'map'))
    parser.add_argument('--seconds', type=float, default=10.0, help=message(language, 'seconds'))
    args = parser.parse_args(argv)
    try:
        valid_duration(args.seconds)
        token = args.token.read_bytes()
        bot = FirstBot()
        with Agent(map_path=args.map) as agent:
            agent.connect(token)
            run([(agent, bot)], seconds=args.seconds)
        print(message(language, 'joined', count=bot.observations))
        if not bot.observations:
            raise SDKError('no_join')
        return 0
    except (OSError, ValueError, RuntimeError) as error:
        print(diagnostic(language, error), file=sys.stderr)
        return 1


if __name__ == '__main__':
    raise SystemExit(main())

Observação

O servidor autoriza as informações e o SDK apresenta a observação. Posição, velocidade e visão próprias incluem previsão; inimigos e resultados vêm do estado autorizado. Os contadores delimitam os arrays válidos. Campos de compatibilidade do rig são infraestrutura e não fazem parte desta lista de percepção.

CamposSignificado
tickTick mais recente recebido do servidor.
joined, alive, grounded, crouched, reloadingIndicadores de entrada na partida, vida, contato com o chão, agachamento e recarga.
seatAssento do próprio agente na partida.
position, velocityPosição e velocidade próprias previstas; metros e metros por segundo.
yaw, pitchVisão própria em graus, com limitação do modelo motor e previsão dos comandos em trânsito; pitch positivo olha para baixo.
health, armorVida e armadura próprias.
weapon, ammo, reserveArma selecionada, munição no carregador e reserva. weapon: 1 rifle, 2 pistola, 3 faca.
kills, deaths, respawn_ticksEliminações, mortes e ticks até reaparecer. Não há reaparecimento durante uma rodada em andamento.
moneyDinheiro próprio; contabilização implementada, compras pendentes. Não informa o dinheiro de outro assento.
visible_count, visibleQuantidade e lista de jogadores que o servidor autoriza o agente a perceber. Inimigos ocultos não são incluídos. Python: players().
event_count, eventsQuantidade e lista de resultados desde a observação anterior; consumidos uma vez. Python: results().
tracer_count, tracersQuantidade e lista de traçantes percebidos desde a observação anterior. Python: streaks().
team, appearance, friendly_fire, animatedEquipe, aparência, fogo amigo e animação. A aparência não identifica se o jogador é humano ou IA.
supply_position, supply_radiusCentro e raio da zona de reabastecimento da própria equipe; zero no modo todos contra todos.
round_phase0 sem rodadas, 1 aquecimento, 2 congelamento, 3 em jogo, 4 fim da rodada, 5 fim da partida.
round_number, rounds_to_win, round_ticks_leftNúmero da rodada, rodadas necessárias para vencer e ticks restantes na fase atual.
score_own, score_other, alive_own, alive_other, attackingPlacares e jogadores vivos das duas equipes, primeiro a própria; attacking indica que a própria equipe ataca nesta metade.
spectating, spectate_position, spectate_yaw, spectate_pitchAssento do companheiro observado ou −1, posição e visão dessa câmera. Quando morto, fire solicita outro companheiro vivo; nunca um inimigo.
teammate_count, teammatesCompanheiros vivos fora de vista: assento e posição, nunca inimigos. Vazio enquanto morto. Python: mates().
bomb_phase0 sem objetivo, 1 carregada, 2 caída, 3 plantada, 4 desarmada, 5 explodida.
bomb_site, bomb_carrier, bomb_progress, bomb_fuse_ticks_left, bomb_positionLocal, portador, progresso próprio, pavio e posição autorizada. Só a equipe do portador recebe sua identidade; a posição nunca é enviada enquanto carregada. Uma bomba plantada é pública.
bomb_site_count, bomb_sitesQuantidade e caixas públicas dos locais de plantio. Python: sites(); cada caixa tem contains() e centre().
roster_count, rosterLista pública em ordem de assento; active identifica assentos ocupados. Python: scoreboard().
gunshot_count, gunshotsDisparos ouvidos desde a observação anterior; não identificam quem atirou. Python: shots_heard().
chat_count, chatMensagens de chat desde a observação anterior; remetente, alcance e texto autorizados pelo servidor. Python: messages().

Ações

Uma ação mantém teclas e solicita uma visão e uma arma. Não permite escolher posição, vida, dano ou inimigos. O servidor decide movimento, combate e percepção. O chat é enviado com agent.say(text), fora de Action.

CamposSignificado
forward, rightEixos de movimento: negativo, zero ou positivo.
yaw, pitchVisão desejada em graus; pitch positivo olha para baixo. O servidor aplica os limites.
jump, crouch, walk, fire, reloadTeclas mantidas de salto, agachamento, caminhada, disparo e recarga.
useManter pressionado para plantar ou desarmar quando as regras permitirem.
weapon0 mantém a arma; 1 rifle, 2 pistola, 3 faca.

Equidade e comunicação

Quando o modelo motor humano se aplica, o servidor impõe atraso mínimo de percepção de 150 ms, velocidade angular máxima de 1800 graus por segundo, aceleração máxima de 30000 graus por segundo ao quadrado e até 15 mudanças por tecla por segundo. Os valores são provisórios e H5 está pendente. O erro de mira de Fitts não está implementado; isolamento e prazo de comandos continuam pendentes. Esses limites não demonstram equivalência humana.

O chat permite uma sequência de 3 mensagens e depois 1 por segundo, com até 120 bytes UTF-8 por mensagem. Durante uma rodada em andamento, chega apenas à mesma equipe e a jogadores com o mesmo estado de vivo ou morto. O servidor define remetente e audiência. As IAs leem e escrevem; falta a entrada de texto do jogador. O jogo ainda não tem voz; canais externos não chegam automaticamente à IA.

Comando de rede

UserCmd ocupa 14 bytes e mantém seu formato no protocolo vigente. Este exemplo mostra a codificação; não implementa sozinho o transporte nem um decodificador de snapshots. Para jogar, use o SDK atual. As tabelas históricas de snapshot v2 foram retiradas deste guia porque não descreviam o servidor atual.

import math
import struct

def f32(value):
    return struct.unpack("<f", struct.pack("<f", value))[0]

def lround(value):
    magnitude = abs(value)
    integer = math.floor(magnitude)
    if magnitude - integer >= 0.5:
        integer += 1
    return int(math.copysign(integer, value))

def user_cmd(sequence, buttons, yaw, pitch, weapon, view_tick, view_fraction=0):
    if buttons & ~0x03ff or not 0 <= weapon <= 3 or not 0 <= view_fraction <= 255:
        raise ValueError("invalid command fields")
    yaw, pitch = f32(yaw), f32(pitch)
    turn = math.remainder(yaw, 360.0) / 360.0 if math.isfinite(yaw) else 0.0
    if turn < 0:
        turn += 1
    yaw_bits = lround(turn * 65536.0) & 0xffff
    pitch_bits = lround(max(-89.0, min(89.0, pitch)) * 256.0) if math.isfinite(pitch) else 0
    return struct.pack("<IHHhBBH", sequence, buttons, yaw_bits,
                       pitch_bits, weapon, view_fraction, view_tick & 0xffff)

cmd = user_cmd(1, (1 << 0) | (1 << 7), 90.0, 0.0, 1, 4091)
assert cmd.hex(" ") == "01 00 00 00 81 00 00 40 00 00 01 00 fb 0f"

Versões e limites

A versão do protocolo faz parte da identidade do transporte: atualize cliente, servidor e SDK juntos e emita novos convites quando ela mudar. A API do agente tem sua própria versão. O cliente gráfico diagnostica convites incompatíveis ou vencidos; o SDK pode informar timeout. A validade do token é configurável. Reconexão, compras, voz, isolamento e validação humana não se tornam recursos disponíveis apenas por aparecerem em um plano.

Referência completa do protocolo 15

Revisão: .

Transporte e convenções

Referência completa do payload atual, consolidada do código; não exige reconstruir v1…v14. O repositório e o SDK continuam privados: documentar o protocolo não comprova download nem servidor públicos. Documentos anteriores preservam o histórico das mudanças.

Simulação e snapshots a 64 Hz. Transporte netcode criptografado: transport_protocol_id = 0x4253545200000000 | 15; token de conexão de 2048 bytes; payload máximo de 1200 bytes. O envelope de transporte e o handshake pertencem ao netcode, não às tabelas abaixo. Uma versão incompatível não conclui a conexão: recompilar cliente, servidor e SDK e emitir tokens novos.

Todos os inteiros são little-endian; inteiros com sinal usam complemento de dois; f é IEEE 754 binary32. Notação das tabelas: B=u8, b=i8, H=u16, h=i16, I=u32, Q=u64, f=f32; um prefixo numérico repete o tipo. Não há preenchimento nem cópias de structs de memória. Os deslocamentos começam em zero em cada bloco. Os decodificadores rejeitam truncamento e bytes finais excedentes.

Comandos e confirmação

O pacote é CommandHeader seguido de count registros UserCmd: 6 + 14×count bytes. O cliente repete seus três comandos mais recentes (48 bytes); o formato permite até quatro. As sequências devem ser consecutivas, inclusive ao passar de 0xffffffff para 0. O servidor aplica no máximo um comando por tick; uma duplicata não cria novos pressionamentos. O adiantamento inicial é de 2 ticks.

A consecutividade módulo 2^32 acima descreve o decodificador do pacote, não permissão para reiniciar a sequência do servidor. CommandQueue rejeita sequence=0 e um primeiro comando acima de 0x7fffffff; seu fluxo não dá a volta. Mantém uma janela de 8 comandos futuros, não 8 comandos por tick. Descarta duplicatas e entradas adiantadas demais; após esgotar uma janela sem entrada aplicável, pode ressincronizar sem reproduzir comandos omitidos.

A quantização recebe ângulos f32: yaw é reduzido módulo 360 e arredondado para 65536 unidades por volta; pitch é limitado a ±89° e multiplicado por 256. Empates são arredondados para longe de zero; uma visão não finita vira zero. O servidor reconstrói view_tick perto do tick atual e limita a compensação de latência; o campo não permite escolher posições, dano nem resultados.

CommandHeader · Cabeçalho de comandos · 6 B

DeslocamentoCampoFormatoBytesSignificado
0typeB1Sempre 1.
1countB11…4 comandos consecutivos, do mais antigo ao mais recente.
2snapshot_ackI4Último tick de snapshot recebido.

UserCmd · Entrada mantida · 14 B

DeslocamentoCampoFormatoBytesSignificado
0sequenceI4Sequência do comando, módulo 2^32.
4buttonsH2Bits 0…9: forward, back, left, right, jump, crouch, walk, fire, reload, use; os demais são zero.
6yawH265536 unidades por volta; positivo gira à direita.
8pitchh21/256 de grau; positivo olha para baixo; intervalo −22784…22784 (±89°).
10weaponB10 mantém a arma; 1 rifle, 2 pistola, 3 faca.
11view_fractionB1Fração após view_tick, em 1/256 de tick. Não é um byte reservado.
12view_tickH216 bits baixos do tick desenhado para os outros jogadores.

Ordem completa do snapshot

Ordem obrigatória: SnapshotHeader → PlayerState → MotorState se flags bit 5 → Rig se identity bit 3 → Supply se equipe diferente de 0 → Spectate se identity bit 5 → count u8 + RemotePlayer[count] → count u8 + eventos[count] → count u8 + Tracer[count] → count u8 + Gunshot[count] → Round → Bomb e suas caixas se sides bit 4 → RosterEntry[16] → count u8 + Teammate[count] se self_and_mates bit 7. Os quatro contadores vazios e o elenco nunca são omitidos.

Mínimo: 2 + 74 + 4 + 10 + 48 = 138 bytes. Máximos do formato: 16 assentos, 15 remotos visíveis, 8 eventos, 8 traçadoras, 12 disparos ouvidos, 2 locais e 15 companheiros ocultos. O servidor do jogo atende até 10 assentos. Visíveis e companheiros ocultos são conjuntos distintos, sem duplicatas nem o assento próprio.

SnapshotHeader · Cabeçalho de snapshot · 2 B

DeslocamentoCampoFormatoBytesSignificado
0typeB1Sempre 2.
1self_and_matesB1Bits 0…3: assento próprio; bit 7: bloco de companheiros ocultos presente; bits 4…6: zero.

Estado próprio e extensões

Posições próprias, de suprimento e de espectador devem ser finitas e estar em ±16384 m por eixo; velocidade em ±1000 m/s. Vida e armadura: 0…1000 finitos; alive equivale a health > 0. Um jogador vivo tem respawn=0. O recuo é finito e está em ±90°. Equipes válidas: 0, 1 e 2.

Com arma 3, weapon_timer é melee_remaining, não recarga: deve ser ≤ knife_cooldown, e esse cooldown ≤128. Outras armas não têm impacto de faca pendente. Motor mantém as restrições de botões e pitch e os limites finitos de giro da tabela. Rig só aparece se animated e sua assinatura não pode ser zero.

Spectate só pode aparecer para um jogador morto, com assento diferente do próprio. A visão e a percepção são as do companheiro autorizado; os eventos privados continuam pertencendo ao dono do snapshot. Sem o bloco, o estado de espectador é vazio.

PlayerState · Estado próprio fixo · 74 B

DeslocamentoCampoFormatoBytesSignificado
0tickI4Tick do servidor.
4last_commandI4Última sequência aplicada pelo servidor.
8position3f12x, y, z em metros.
20velocity3f12x, y, z em metros por segundo.
32flagsB1Bits 0…5: grounded, crouched, jump_held, alive, trigger_held, motor. Bits 6…7: equipe (0 nenhuma, 1 alpha, 2 bravo).
33identityB1Bits 0…1: arma 1…3; bit 2: aparência (0 humana, 1 máquina); bit 3: animated; bit 4: friendly_fire; bit 5: spectating; bits 6…7: zero.
34healthf4Vida própria, 0…1000.
38armorf4Armadura própria, 0…1000.
42rifle_ammoH2Munição no carregador do rifle.
44rifle_reserveH2Reserva do rifle.
46rifle_cooldownB1Ticks até poder disparar o rifle.
47pistol_ammoH2Munição no carregador da pistola.
49pistol_reserveH2Reserva da pistola.
51pistol_cooldownB1Ticks até poder disparar a pistola.
52knife_cooldownB1Ticks até outro ataque de faca; a faca não envia munição.
53idleB1Contador de repouso da arma, saturado em 255.
54patternB1Índice do padrão da arma ativa, saturado em 255.
55weapon_timerH2Ticks de recarga; com faca, ticks até o impacto pendente.
57recoil_pitchf4Recuo vertical em graus, ±90.
61recoil_yawf4Recuo horizontal em graus, ±90.
65respawnH2Ticks até reaparecer; zero enquanto vivo.
67killsH2Abates próprios.
69deathsH2Mortes próprias.
71moneyH2Dinheiro próprio; compras não estão implementadas.
73leadB1Comandos deste jogador na fila à frente do tick; saturado.

MotorState · Extensão motora · 24 B

DeslocamentoCampoFormatoBytesSignificado
0applied_buttonsH2Bits 0…9: forward, back, left, right, jump, crouch, walk, fire, reload, use; os demais são zero.
2applied_yawH265536 unidades por volta; positivo gira à direita.
4applied_pitchh21/256 de grau; positivo olha para baixo; intervalo −22784…22784 (±89°).
6turn_yawf4Giro horizontal anterior, graus por tick, ±180.
10turn_pitchf4Giro vertical anterior, graus por tick, ±178.
14keys10B10Crédito de mudança para cada botão, em 1/64 de mudança.

Rig · Assinatura do rig · 8 B

DeslocamentoCampoFormatoBytesSignificado
0rig_hashQ8Assinatura não nula; palavra baixa primeiro. Não autentica conteúdo.

Supply · Zona de suprimento · 16 B

DeslocamentoCampoFormatoBytesSignificado
0position3f12x, y, z em metros.
12radiusf4Raio em metros, 0…16384.

Spectate · Visão do companheiro observado · 17 B

DeslocamentoCampoFormatoBytesSignificado
0seatB1Assento 0–15; não é um identificador persistente.
1eye3f12x, y, z em metros.
13yawH265536 unidades por volta; positivo gira à direita.
15pitchh21/256 de grau; positivo olha para baixo; intervalo −22784…22784 (±89°).

Corpos visíveis e animação

motion: 0 idle, 1 walk, 2 jog, 3 sprint, 4 crouch_idle, 5 crouch_walk, 6 airborne, 7 shoot, 8 reload, 9 death, 10 jump_start, 11 jump_land; 12…15 inválidos. Posição finita em ±16384 m por eixo. O servidor decide quais corpos podem ser percebidos; ausência não revela um inimigo oculto.

Origem da mistura: códigos 0…6 correspondem a motion 0…6; código 7 significa jump_start (motion 10). Passo 0: sem mistura e origem 0 obrigatória. Passos 1…3: pesos 1/4, 2/4, 3/4. Passos 4…7: pesos 1/8, 3/8, 5/8, 7/8; somente pistola ou faca passando de walk/jog/sprint para idle. Toda mistura exige origem e destino diferentes, ambos agachados (crouch_idle/crouch_walk) ou ambos não agachados.

reload_phase não pode ser diferente de zero com faca nem motion=death. attack_age não pode ser diferente de zero com death, shoot, reload ou reload_phase ativa. São restrições de decodificação; o codificador não substitui a validação de entradas arbitrárias.

O protocolo 15 mantém motion_phase como fase do clipe destino e adiciona stop_phase como fase explícita do clipe fonte durante a frenagem secundária. O servidor congela a última fase pública de marcha; em troca simultânea de arma sem pose anterior compatível, calcula a fonte pela distância percorrida com a arma nova. A mistura e o byte fonte vêm juntos do snapshot mais próximo; não exigem histórico de visibilidade do cliente. O destino idle mantém seu relógio normal. Tokens/clientes de protocolos anteriores são incompatíveis. Revisão de pose 13; Agent API 14 sem mudanças de campos ou ABI.

RemotePlayer · Jogador visível · 22 B

DeslocamentoCampoFormatoBytesSignificado
0idB1Assento 0–15; não é um identificador persistente.
1position3f12x, y, z em metros.
13yawH265536 unidades por volta; positivo gira à direita.
15pitchb1Graus inteiros, −89…89; diferente do pitch de UserCmd.
16flagsB1Bit 0: crouched; bits 1…4: motion; bits 5…6: equipe 0…2; bit 7: aparência 0…1.
17motion_phaseB1Fração do clipe, em passos de 1/256.
18poseB1Bits 0…1: arma 0…2; bits 2…4: origem da mistura; bits 5…7: passo da mistura.
19reload_phaseB10 inativa; 1…255 progresso normalizado.
20attack_ageB10 inativo; caso contrário 1 + idade em ticks.
21stop_phaseB1Fase fonte congelada da frenagem secundária, 0–255 em 1/256 do clipe de origem. Ativa somente com pistola/faca, idle, fonte walk/jog/sprint e fade_step 1–7; fora desse contexto deve ser zero. Zero é uma fase válida dentro do contexto.

Eventos, traçadoras e disparos ouvidos

Cada evento é EventHeader + conteúdo: shot 19 B, hurt 6 B, death 4 B, kill 6 B no total. Tipos 1…3 são privados; kill é público, sem posição. Eventos se repetem do mais antigo ao mais recente até snapshot_ack confirmar um snapshot que os continha; processar cada id uma única vez. Os ids são consecutivos módulo 65536.

zone: 0 cabeça, 1 tronco, 2 membro. O zero de um erro ou de uma morte pelo mundo é um valor vazio canônico, não um acerto na cabeça.

Em shot, other diferente de 255 exige outro assento válido e unseen=0. Com other=255, damage e zone são zero; killed só pode ser 1 se unseen=1. Um acerto oculto conserva uma posição autorizada de entrada na cobertura, não a do inimigo. Em kill, vítima 0…15; atacante diferente da vítima ou 255. Se o mundo mata, zone, through e weapon são zero.

Tracer contém o segmento selecionado pelo filtro do servidor, com extremos finitos em ±16384 m, sem identidade do atirador. Gunshot contém posição autorizada arredondada e arma, sem identidade. As coordenadas são codificadas dividindo por 0,25 m, arredondando empates para longe de zero e limitando a ±32767; −32768 é rejeitado na leitura. Traçadoras e disparos ouvidos são efêmeros, sem a confirmação dos eventos.

As traçadoras são recortadas por amostras; não se garante visibilidade contínua de todo o segmento.

EventHeader · Cabeçalho de evento · 3 B

DeslocamentoCampoFormatoBytesSignificado
0idH2Sequência consecutiva por jogador, módulo 65536.
2kindB11 shot, 2 hurt, 3 death, 4 kill.

Shot · Conteúdo do disparo · 16 B

DeslocamentoCampoFormatoBytesSignificado
0flagsB1Bit 0: through; bit 1: killed; bits 2…3: zone 0…2; bit 4: unseen; bit 5: zero; bits 6…7: arma 0…2.
1otherB1Assento atingido ou 255; nunca o próprio.
2damageH2Dano em centésimos de ponto.
4position3f12Impacto autorizado; em acerto oculto, entrada na cobertura, não a posição do inimigo.

Hurt · Conteúdo de dano próprio · 3 B

DeslocamentoCampoFormatoBytesSignificado
0zoneB10…2; não contém atacante nem posição.
1damageH2Dano em centésimos de ponto.

Death · Conteúdo de morte própria · 1 B

DeslocamentoCampoFormatoBytesSignificado
0otherB1Assento do atacante ou 255 para o mundo; nunca o próprio.

Kill · Conteúdo de abate público · 3 B

DeslocamentoCampoFormatoBytesSignificado
0killerB1Assento do atacante ou 255 para o mundo.
1victimB1Assento 0–15; não é um identificador persistente.
2flagsB1Bits 0…1: zone 0…2; bit 2: through; bits 3…7: arma 0…2.

Tracer · Segmento visível · 24 B

DeslocamentoCampoFormatoBytesSignificado
0from3f12x, y, z em metros.
12to3f12x, y, z em metros.

Gunshot · Disparo ouvido · 7 B

DeslocamentoCampoFormatoBytesSignificado
0position3h6x, y, z em unidades de 0,25 m; −32768 inválido.
6weaponB10 rifle, 1 pistola, 2 faca; 3 inválido.

Rodada, bomba, elenco e companheiros

Com phase=none, Round deve estar inteiramente em zero. Nas outras fases rounds_to_win é positivo, os placares não o superam e attackers é 1 ou 2. number é zero exatamente em warmup. winner é diferente de zero exatamente em over ou ended.

Bomb só é enviado se sides bit 4 estiver ativo: 19 bytes mais 13 por local. site é diferente de zero exatamente em planted, defused ou exploded; fuse_left só pode ser diferente de zero em planted. carrier só pode indicar um assento em carried; caso contrário é 255. Em carried a posição é zero; nos outros estados deve ser finita. Caixas ordenadas A, B; min ≤ max por eixo e coordenadas decodificadas em ±3276 m. A codificação multiplica cada f32 por 10 e arredonda empates para longe de zero.

O servidor decide a audiência de cada campo da bomba: estado plantado, local, temporizador, posição plantada e caixas são públicos; portador e posição caída são revelados somente à equipe atacante; progress é próprio. A posição de um jogador oculto não é enviada pela bomba.

O elenco sempre contém 16 linhas, 48 bytes, na ordem dos assentos. Um assento inativo tem a linha inteira em zero. O papel vem do token autenticado; equipe e aparência não o alteram. O bloco opcional de companheiros tem count=1…15 e linhas Teammate; um bloco vazio anunciado é inválido. Só um jogador vivo com equipe o recebe. Cada linha deve corresponder no elenco a um companheiro ativo e vivo da mesma equipe, com posição finita em ±16384 m, sem duplicatas nem jogadores já visíveis. Não contém vida, arma nem mira.

Round · Estado da rodada · 10 B

DeslocamentoCampoFormatoBytesSignificado
0phaseB10 none, 1 warmup, 2 freeze, 3 live, 4 over, 5 ended.
1numberB10 no aquecimento; rodadas começam em 1.
2rounds_to_winB1Rodadas necessárias para vencer.
3score_alphaB1Rodadas vencidas por alpha.
4score_bravoB1Rodadas vencidas por bravo.
5alive_alphaB1Jogadores vivos de alpha.
6alive_bravoB1Jogadores vivos de bravo.
7ticks_leftH2Ticks restantes da fase.
9sidesB1Bits 0…1: atacantes; bits 2…3: vencedor (equipes 0…2); bit 4: bomba presente; bits 5…7: zero.

Bomb · Estado fixo da bomba · 19 B

DeslocamentoCampoFormatoBytesSignificado
0phaseB11 carried, 2 dropped, 3 planted, 4 defused, 5 exploded; 0 implica bloco ausente.
1siteB10 não plantada, 1 A, 2 B.
2carrierB1Assento autorizado do portador ou 255.
3progressB1Progresso próprio, 0…100.
4fuse_leftH2Ticks do temporizador; zero exceto enquanto plantada.
6position3f12x, y, z em metros.
18site_countB10…2 caixas de local a seguir.

BombSite · Caixa de plantio · 13 B

DeslocamentoCampoFormatoBytesSignificado
0letterB11 para a primeira caixa (A), 2 para a segunda (B).
1min3h6Mínimos x, y, z em decímetros.
7max3h6Máximos x, y, z em decímetros.

RosterEntry · Linha do elenco · 3 B

DeslocamentoCampoFormatoBytesSignificado
0flagsB1Bit 0: active; bit 1: alive; bits 2…3: equipe 0…2; bit 4: aparência 0…1; bit 5: papel 0 pessoa, 1 IA; bits 6…7: zero.
1killsB1Abates saturados em 255.
2deathsB1Mortes saturadas em 255.

Teammate · Companheiro vivo não visível · 13 B

DeslocamentoCampoFormatoBytesSignificado
0idB1Assento 0–15; não é um identificador persistente.
1position3f12x, y, z em metros.

Chat separado

ChatSayHeader + texto vai do cliente ao servidor. ChatHeardHeader + texto vai do servidor ao cliente: o byte from fica imediatamente após type, antes de scope. O cliente não escolhe o remetente. O texto tem 1…120 bytes, não caracteres, sem terminador zero. São rejeitados controles ASCII 0x00…0x1f e 0x7f, mensagens formadas só por espaços ASCII e UTF-8 inválido (incluindo codificações excessivamente longas, substitutos e valores acima de U+10FFFF). Isso não afirma filtragem de todos os controles Unicode.

Taxa por assento: sequência de 3 mensagens e depois 1 por segundo. Em uma rodada live, somente a mesma equipe e o mesmo estado vivo/morto, mesmo pedindo scope=1. Fora de live, scope=0 vai à equipe e scope=1 a todos. Sem rodadas, o filtro de alcance continua valendo: scope=0 inclui companheiros; no modo livre, sem equipe, somente o remetente. scope=1 inclui todos os assentos ativos. O remetente sempre está incluído na audiência; o eco não garante entrega. O servidor descarta e conta excessos; essas mensagens não têm o mecanismo de reenvio/confirmação dos eventos.

Agent API 14 expõe chat_count e chat[4] com from, scope e texto de 121 bytes terminado em zero; bs_observation mede 2964 bytes e bs_action 44 na ABI verificada. Há 61 campos de observação e 11 de ação; a ABI em memória não é este formato de rede. C: bs_agent_say e bs_gym_say. Python: observation.messages() e agent.say(text). IAs leem e escrevem; faltam entrada de texto humana e voz do jogo. Voz externa não chega automaticamente à IA; a decisão de paridade continua aberta.

ChatSayHeader · Chat cliente → servidor · 3 B

DeslocamentoCampoFormatoBytesSignificado
0typeB1Sempre 3.
1scopeB10 equipe, 1 todos; o servidor restringe a audiência.
2lengthB11…120 bytes de texto a seguir, sem terminador.

ChatHeardHeader · Chat servidor → cliente · 4 B

DeslocamentoCampoFormatoBytesSignificado
0typeB1Sempre 3.
1fromB1Assento 0–15; não é um identificador persistente.
2scopeB10 equipe, 1 todos.
3lengthB11…120 bytes de texto a seguir, sem terminador.

Orçamentos e limites de alcance

Pior caso analítico servido 5v5: snapshot de 857 bytes com objetivo e dois locais, IA viva com motor, equipe com suprimento e rig. Com 73 bytes de cabeçalhos e 64 Hz: (857+73)×64=59520 B/s; margem 4480 para o limite de 64000. Sem objetivo: 812 bytes, 56640 B/s. Upload usual com 3 comandos: (48+73)×64=7744 B/s para 8000. Essas contas não são uma nova medição de tráfego.

Chat máximo: 4+120+73=197 bytes por mensagem recebida; cinco companheiros no limite sustentado acrescentam 985 B/s, com orçamento separado. Rajadas e tráfego recebido fora de uma equipe 5v5 não são descritos por essa taxa sustentada. O limite de dez assentos atendidos permanece; os dezesseis do formato não comprovam suporte a partidas maiores.

Pendentes: reconexão, compressão delta e hash de conteúdo no handshake; isolamento e prazo de bots; validação humana de justiça H5. O papel autenticado no token identifica para quem o token foi emitido, não prova que uma pessoa esteja jogando. O servidor limita percepção e entradas, mas equivalência humana não foi demonstrada. Os exemplos abaixo verificam bytes de payload; não constituem uma implementação do transporte nem um teste humano.

Exemplos de bytes

command · 14 B

Sequência 0x01020304, forward+fire, yaw 90°, pitch −1,5°, rifle, meio tick após 4091.

04 03 02 01 81 00 00 40 80 fe 01 80 fb 0f

commands · 20 B

Pacote com um comando e confirmação do tick 4090.

01 01 fa 0f 00 00 04 03 02 01 81 00 00 40 80 fe 01 80 fb 0f

snapshot_minimal · 138 B

Snapshot sem extensões nem listas, com valores distintos no estado próprio e elenco inativo. Verifica deslocamentos; não representa uma partida completa.

02 00 04 03 02 01 11 00 00 00 00 00 80 3f 00 00 00 40 00 00 40 40 00 00
80 40 00 00 a0 40 00 00 c0 40 08 01 00 00 c8 42 00 00 c8 41 1e 00 5a 00
07 0c 00 18 00 09 0b 0d 0f 13 00 00 00 c0 bf 00 00 20 40 00 00 15 00 17
00 20 03 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

snapshot_spectator · 171 B

Estado morto com equipe e visão do companheiro 1; sem listas.

02 00 04 03 02 01 11 00 00 00 00 00 80 3f 00 00 00 40 00 00 40 40 00 00
80 40 00 00 a0 40 00 00 c0 40 40 21 00 00 00 00 00 00 c8 41 1e 00 5a 00
07 0c 00 18 00 09 0b 0d 0f 13 00 00 00 c0 bf 00 00 20 40 07 00 15 00 17
00 20 03 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 40
41 00 00 40 40 00 00 80 40 00 80 00 02 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00

snapshot_extended · 333 B

Vetor sintético com motor, rig, suprimento, um remoto, os quatro tipos de evento, traçadora, disparo ouvido, bomba com duas caixas, elenco e um companheiro oculto. Testa a ordem dos blocos, não a largura de banda máxima nem uma sessão jogada.

02 80 04 03 02 01 11 00 00 00 00 00 80 3f 00 00 00 40 00 00 40 40 00 00
80 40 00 00 a0 40 00 00 c0 40 79 1d 00 00 c8 42 00 00 c8 41 1e 00 5a 00
07 0c 00 18 00 09 0b 0d 0f 13 00 00 00 c0 bf 00 00 20 40 00 00 15 00 17
00 20 03 02 81 00 00 40 80 fd 00 00 a0 3f 00 00 20 c0 0a 14 1e 28 32 3c
46 50 5a 64 08 07 06 05 04 03 02 01 00 00 80 3f 00 00 00 40 00 00 40 40
00 00 80 40 01 01 00 00 e0 40 00 00 00 41 00 00 10 41 00 80 e2 4b 80 31
21 00 00 04 01 00 01 49 01 d2 04 00 00 80 3f 00 00 00 40 00 00 40 40 02
00 02 01 ae 08 03 00 03 01 04 00 04 01 00 15 01 00 00 80 3f 00 00 00 40
00 00 40 40 00 00 80 40 00 00 a0 40 00 00 c0 40 01 05 00 f6 ff 0f 00 02
03 02 0d 01 00 05 04 d2 04 11 03 02 ff 25 ae 08 00 00 20 41 00 00 00 00
00 00 a0 41 02 01 0a 00 00 00 14 00 1e 00 28 00 32 00 02 3c 00 00 00 46
00 50 00 28 00 5a 00 37 15 17 0b 02 03 07 04 05 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 01 02 00 00 20 41 00 00 30 41 00 00 40 41

chat_say · 5 B

Pedido à equipe: texto UTF-8 “ñ”, dois bytes.

03 00 02 c3 b1

chat_heard · 6 B

A mesma linha recebida do assento 3.

03 03 00 02 c3 b1

snapshot_secondary_stop · 333 B

O mesmo vetor estendido com frenagem de pistola: destino idle, fonte walk, primeiro peso 1/8 e fase fonte 66/256. Zero em outros contextos não substitui este exemplo ativo.

02 80 04 03 02 01 11 00 00 00 00 00 80 3f 00 00 00 40 00 00 40 40 00 00
80 40 00 00 a0 40 00 00 c0 40 79 1d 00 00 c8 42 00 00 c8 41 1e 00 5a 00
07 0c 00 18 00 09 0b 0d 0f 13 00 00 00 c0 bf 00 00 20 40 00 00 15 00 17
00 20 03 02 81 00 00 40 80 fd 00 00 a0 3f 00 00 20 c0 0a 14 1e 28 32 3c
46 50 5a 64 08 07 06 05 04 03 02 01 00 00 80 3f 00 00 00 40 00 00 40 40
00 00 80 40 01 01 00 00 e0 40 00 00 00 41 00 00 10 41 00 80 e2 40 50 85
00 00 42 04 01 00 01 49 01 d2 04 00 00 80 3f 00 00 00 40 00 00 40 40 02
00 02 01 ae 08 03 00 03 01 04 00 04 01 00 15 01 00 00 80 3f 00 00 00 40
00 00 40 40 00 00 80 40 00 00 a0 40 00 00 c0 40 01 05 00 f6 ff 0f 00 02
03 02 0d 01 00 05 04 d2 04 11 03 02 ff 25 ae 08 00 00 20 41 00 00 00 00
00 00 a0 41 02 01 0a 00 00 00 14 00 1e 00 28 00 32 00 02 3c 00 00 00 46
00 50 00 28 00 5a 00 37 15 17 0b 02 03 07 04 05 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 01 02 00 00 20 41 00 00 30 41 00 00 40 41