Guía del SDK vigente
Construye una IA para BotStrikes
Estado ·
BotStrikes 0.2.0 · protocol 15 · Agent API 14 · bs_observation: 61 · bs_action: 11.
El SDK y el gimnasio funcionan en el repositorio privado. Esta guía documenta la interfaz actual para quienes tienen acceso al repositorio o a un kit. No hay descarga pública del SDK ni servidor público de juego acreditados.
Cómo empezar
Una IA usa el transporte cifrado y las entradas públicas del juego. Necesita una invitación de IA emitida por el servidor, una biblioteca de la misma versión y el mismo mapa y contenido. Las rutas de estos ejemplos parten de la raíz del repositorio, con build/check ya compilado mediante tools/check.sh.
La primera conexión
En una terminal, iniciar el servidor local. Las cajas de sitio habilitan el objetivo de bomba. En otra terminal, ejecutar el ejemplo con el token emitido. No reutilizar un token después de desconectarse: solicitar un repuesto.
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
El resultado esperado es Joined observations mayor que cero y salida correcta. El ejemplo camina y gira; no demuestra navegación ni juego del objetivo. Agent administra la conexión; el objeto de política devuelve Action; run recibe parejas de agente y 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())
Observación
El servidor autoriza la información y el SDK presenta la observación. La posición, velocidad y vista propias incluyen predicción; los enemigos y resultados proceden del estado autorizado. Los contadores delimitan los arreglos válidos. Los campos de compatibilidad del rig son infraestructura y no forman parte de esta lista de percepción.
| Campos | Significado |
|---|---|
tick | Tick de servidor más reciente recibido. |
joined, alive, grounded, crouched, reloading | Indicadores de conexión a la partida, vida, suelo, postura agachada y recarga. |
seat | Asiento propio en la partida. |
position, velocity | Posición y velocidad propias predichas; metros y metros por segundo. |
yaw, pitch | Vista propia en grados, con recorte del modelo motor y predicción de comandos en vuelo; pitch positivo mira hacia abajo. |
health, armor | Vida y armadura propias. |
weapon, ammo, reserve | Arma elegida, munición del cargador y reserva. weapon: 1 rifle, 2 pistola, 3 cuchillo. |
kills, deaths, respawn_ticks | Bajas, muertes y ticks hasta reaparición. No se reaparece durante una ronda en juego. |
money | Dinero propio; contabilidad implementada, compras pendientes. No informa el dinero de otro asiento. |
visible_count, visible | Cantidad y lista de jugadores que el servidor autoriza a percibir. No se incluye un enemigo oculto. Python: players(). |
event_count, events | Cantidad y lista de resultados desde la observación anterior; se consumen una vez. Python: results(). |
tracer_count, tracers | Cantidad y lista de trazadoras percibidas desde la observación anterior. Python: streaks(). |
team, appearance, friendly_fire, animated | Bando, apariencia, fuego amigo y animación. La apariencia no identifica si el jugador es humano o IA. |
supply_position, supply_radius | Centro y radio de la zona propia de reaprovisionamiento; cero en todos contra todos. |
round_phase | 0 sin rondas, 1 calentamiento, 2 congelación, 3 en juego, 4 fin de ronda, 5 fin de partida. |
round_number, rounds_to_win, round_ticks_left | Número de ronda, rondas necesarias para ganar y ticks restantes de la fase actual. |
score_own, score_other, alive_own, alive_other, attacking | Marcadores y jugadores vivos de ambos bandos, primero el propio; attacking indica que el bando propio ataca en esta mitad. |
spectating, spectate_position, spectate_yaw, spectate_pitch | Asiento del compañero espectado o −1, posición y vista de esa cámara. Muerto, fire pide otro compañero vivo; nunca un enemigo. |
teammate_count, teammates | Compañeros vivos fuera de vista: asiento y posición, nunca enemigos. Vacío mientras se está muerto. Python: mates(). |
bomb_phase | 0 sin objetivo, 1 llevada, 2 tirada, 3 plantada, 4 desactivada, 5 explotada. |
bomb_site, bomb_carrier, bomb_progress, bomb_fuse_ticks_left, bomb_position | Sitio, portador, progreso propio, mecha y posición autorizada. El portador sólo llega a su bando; la posición nunca se envía mientras alguien la lleva. Una bomba plantada es pública. |
bomb_site_count, bomb_sites | Cantidad y cajas públicas de los sitios de plantado. Python: sites(); cada caja tiene contains() y centre(). |
roster_count, roster | Plantel público, en orden de asiento; active identifica los asientos ocupados. Python: scoreboard(). |
gunshot_count, gunshots | Disparos oídos desde la observación anterior; no identifican al tirador. Python: shots_heard(). |
chat_count, chat | Mensajes de chat desde la observación anterior; remitente, alcance y texto autorizado por el servidor. Python: messages(). |
Acciones
Una acción mantiene teclas y solicita una vista y un arma. No permite elegir posición, vida, daño ni enemigos. El servidor decide el movimiento, el combate y la percepción. El chat se envía con agent.say(text), fuera de Action.
| Campos | Significado |
|---|---|
forward, right | Ejes de movimiento: negativo, cero o positivo. |
yaw, pitch | Vista deseada en grados; pitch positivo mira hacia abajo. El servidor aplica los límites. |
jump, crouch, walk, fire, reload | Teclas mantenidas de salto, agacharse, caminar, disparar y recargar. |
use | Mantener para plantar o desactivar cuando las reglas lo permiten. |
weapon | 0 mantiene el arma; 1 rifle, 2 pistola, 3 cuchillo. |
Justicia y comunicación
Cuando corresponde el modelo humano, el servidor aplica retardo mínimo de percepción de 150 ms, velocidad angular máxima de 1800 grados por segundo, aceleración máxima de 30000 grados por segundo cuadrado y hasta 15 cambios por tecla y segundo. Los valores son provisionales y H5 sigue pendiente. El error de puntería de Fitts no está implementado; sandbox y deadline siguen pendientes. Estos límites no demuestran equivalencia humana.
El chat permite una ráfaga de 3 mensajes y luego 1 por segundo, con hasta 120 bytes UTF-8 por mensaje. Durante una ronda en juego, sólo llega al mismo bando y entre jugadores con el mismo estado de vivo o muerto. El servidor fija remitente y audiencia. Las IAs leen y escriben; falta que el jugador pueda escribir. El juego no tiene voz todavía; los canales externos no llegan automáticamente a la IA.
Comando de red
UserCmd ocupa 14 bytes y conserva su formato en el protocolo vigente. Este ejemplo enseña su codificación; no implementa por sí solo el transporte ni un decodificador de snapshots. Para jugar, usar el SDK actual. Las tablas históricas de snapshot v2 fueron retiradas de esta guía porque no describían el servidor actual.
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"
Versiones y límites
La versión de protocolo forma parte de la identidad del transporte: actualizar cliente, servidor y SDK juntos y emitir nuevas invitaciones cuando cambie. La API del agente tiene su propia versión. El cliente gráfico diagnostica invitaciones incompatibles o vencidas; el SDK puede informar un timeout. La vida del token es configurable. Reconexión, compras, voz, sandbox y validación humana no se vuelven funciones disponibles por estar descritas en un plan.
Referencia completa del protocolo 15
Revisión: .
Transporte y convenciones
Referencia vigente completa del payload, consolidada del código; no requiere reconstruir v1…v14. El repositorio y el SDK siguen siendo privados: describir el protocolo no acredita descarga ni servidor públicos. Los documentos anteriores conservan el historial de cada cambio.
Simulación y snapshots a 64 Hz. Transporte netcode cifrado: transport_protocol_id = 0x4253545200000000 | 15; token de conexión de 2048 bytes; payload máximo de 1200 bytes. La envoltura de transporte y el handshake pertenecen a netcode, no a las tablas siguientes. Una versión incompatible no completa la conexión: recompilar cliente, servidor y SDK y emitir tokens nuevos.
Todos los enteros son little-endian; los enteros con signo usan complemento a dos; f es IEEE 754 binary32. Notación de las tablas: B=u8, b=i8, H=u16, h=i16, I=u32, Q=u64, f=f32; un prefijo numérico repite el tipo. No hay relleno ni copias de structs de memoria. Los desplazamientos empiezan en cero dentro de cada bloque. Los decodificadores rechazan truncamiento y bytes finales sobrantes.
Comandos y acuse
El paquete es CommandHeader seguido de count UserCmd: 6 + 14×count bytes. El cliente repite sus tres comandos más nuevos (48 bytes); el formato admite hasta cuatro. Las secuencias deben ser consecutivas, incluso al pasar de 0xffffffff a 0. El servidor aplica como máximo un comando por tick; un duplicado no crea pulsaciones nuevas. El adelanto inicial es de 2 ticks.
La consecutividad módulo 2^32 anterior describe el decodificador del paquete, no permiso para reiniciar la secuencia del servidor. CommandQueue rechaza sequence=0 y un primer comando superior a 0x7fffffff; su flujo no hace wrap. Mantiene una ventana de 8 comandos futuros, no 8 comandos por tick. Descarta duplicados y entradas demasiado adelantadas; tras agotar una ventana sin entrada aplicable puede resincronizarse sin reproducir comandos omitidos.
La cuantización recibe ángulos f32: yaw se reduce módulo 360 y se redondea a 65536 unidades por vuelta; pitch se limita a ±89° y se multiplica por 256. Los empates se redondean alejándose de cero; una vista no finita se convierte en cero. El servidor reconstruye view_tick cerca de su tick actual y limita la compensación de latencia; el campo no permite elegir posiciones, daño ni resultados.
CommandHeader · Cabecera de comandos · 6 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | type | B | 1 | Siempre 1. |
| 1 | count | B | 1 | 1…4 comandos, consecutivos, del más viejo al más nuevo. |
| 2 | snapshot_ack | I | 4 | Último tick de snapshot recibido. |
UserCmd · Entrada mantenida · 14 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | sequence | I | 4 | Secuencia del comando, módulo 2^32. |
| 4 | buttons | H | 2 | Bits 0…9: forward, back, left, right, jump, crouch, walk, fire, reload, use; los demás cero. |
| 6 | yaw | H | 2 | 65536 unidades por vuelta; positivo gira a la derecha. |
| 8 | pitch | h | 2 | 1/256 de grado; positivo mira abajo; rango −22784…22784 (±89°). |
| 10 | weapon | B | 1 | 0 conserva el arma; 1 rifle, 2 pistola, 3 cuchillo. |
| 11 | view_fraction | B | 1 | Fracción tras view_tick, en 1/256 de tick. No es un byte reservado. |
| 12 | view_tick | H | 2 | 16 bits bajos del tick dibujado para los otros jugadores. |
Orden completo del snapshot
Orden obligatorio: SnapshotHeader → PlayerState → MotorState si flags bit 5 → Rig si identity bit 3 → Supply si equipo distinto de 0 → Spectate si identity bit 5 → count u8 + RemotePlayer[count] → count u8 + eventos[count] → count u8 + Tracer[count] → count u8 + Gunshot[count] → Round → Bomb y sus cajas si sides bit 4 → RosterEntry[16] → count u8 + Teammate[count] si self_and_mates bit 7. No se omiten los cuatro contadores vacíos ni el plantel.
Mínimo: 2 + 74 + 4 + 10 + 48 = 138 bytes. Máximos del formato: 16 asientos, 15 remotos visibles, 8 eventos, 8 trazadoras, 12 disparos oídos, 2 sitios y 15 compañeros ocultos. El servidor de juego sirve hasta 10 asientos. Visibles y compañeros ocultos son conjuntos distintos sin duplicados ni el asiento propio.
SnapshotHeader · Cabecera de snapshot · 2 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | type | B | 1 | Siempre 2. |
| 1 | self_and_mates | B | 1 | Bits 0…3: asiento propio; bit 7: bloque de compañeros ocultos presente; bits 4…6: cero. |
Estado propio y extensiones
Las posiciones propias, de suministro y de espectador deben ser finitas y estar dentro de ±16384 m por eje; la velocidad dentro de ±1000 m/s. Vida y armadura: 0…1000 finitos; alive equivale a health > 0. Un vivo lleva respawn=0. El retroceso es finito y está dentro de ±90°. Los equipos válidos son 0, 1 y 2.
Con arma 3, weapon_timer es melee_remaining y no una recarga: debe ser ≤ knife_cooldown, y ese cooldown ≤128. Con otras armas no hay impacto de cuchillo pendiente. Motor conserva las restricciones de botones y pitch y límites finitos de giro indicados en la tabla. Rig sólo aparece si animated y su firma no puede ser cero.
Spectate sólo puede aparecer para un jugador muerto, con un asiento distinto del propio. La vista y la percepción son las del compañero autorizado; los eventos privados siguen perteneciendo al dueño del snapshot. Sin el bloque, el estado de espectador es vacío.
PlayerState · Estado propio fijo · 74 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | tick | I | 4 | Tick del servidor. |
| 4 | last_command | I | 4 | Última secuencia aplicada por el servidor. |
| 8 | position | 3f | 12 | x, y, z en metros. |
| 20 | velocity | 3f | 12 | x, y, z en metros por segundo. |
| 32 | flags | B | 1 | Bits 0…5: grounded, crouched, jump_held, alive, trigger_held, motor. Bits 6…7: equipo (0 ninguno, 1 alpha, 2 bravo). |
| 33 | identity | B | 1 | Bits 0…1: arma 1…3; bit 2: apariencia (0 humana, 1 máquina); bit 3: animated; bit 4: friendly_fire; bit 5: spectating; bits 6…7: cero. |
| 34 | health | f | 4 | Vida propia, 0…1000. |
| 38 | armor | f | 4 | Armadura propia, 0…1000. |
| 42 | rifle_ammo | H | 2 | Balas en el cargador del rifle. |
| 44 | rifle_reserve | H | 2 | Reserva del rifle. |
| 46 | rifle_cooldown | B | 1 | Ticks hasta poder disparar el rifle. |
| 47 | pistol_ammo | H | 2 | Balas en el cargador de la pistola. |
| 49 | pistol_reserve | H | 2 | Reserva de la pistola. |
| 51 | pistol_cooldown | B | 1 | Ticks hasta poder disparar la pistola. |
| 52 | knife_cooldown | B | 1 | Ticks hasta otro ataque de cuchillo; no viaja munición del cuchillo. |
| 53 | idle | B | 1 | Contador de reposo del arma, saturado a 255. |
| 54 | pattern | B | 1 | Índice del patrón del arma activa, saturado a 255. |
| 55 | weapon_timer | H | 2 | Ticks de recarga; con cuchillo, ticks hasta el impacto pendiente. |
| 57 | recoil_pitch | f | 4 | Retroceso vertical en grados, ±90. |
| 61 | recoil_yaw | f | 4 | Retroceso horizontal en grados, ±90. |
| 65 | respawn | H | 2 | Ticks hasta reaparecer; cero si está vivo. |
| 67 | kills | H | 2 | Bajas propias. |
| 69 | deaths | H | 2 | Muertes propias. |
| 71 | money | H | 2 | Dinero propio; las compras no están implementadas. |
| 73 | lead | B | 1 | Comandos de este jugador en espera por delante del tick; saturado. |
MotorState · Extensión motor · 24 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | applied_buttons | H | 2 | Bits 0…9: forward, back, left, right, jump, crouch, walk, fire, reload, use; los demás cero. |
| 2 | applied_yaw | H | 2 | 65536 unidades por vuelta; positivo gira a la derecha. |
| 4 | applied_pitch | h | 2 | 1/256 de grado; positivo mira abajo; rango −22784…22784 (±89°). |
| 6 | turn_yaw | f | 4 | Giro horizontal anterior, grados por tick, ±180. |
| 10 | turn_pitch | f | 4 | Giro vertical anterior, grados por tick, ±178. |
| 14 | keys | 10B | 10 | Crédito de cambio para cada botón, en 1/64 de cambio. |
Rig · Firma del rig · 8 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | rig_hash | Q | 8 | Firma no nula; palabra baja primero. No autentica contenido. |
Supply · Zona de suministro · 16 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | position | 3f | 12 | x, y, z en metros. |
| 12 | radius | f | 4 | Radio en metros, 0…16384. |
Spectate · Vista del compañero espectado · 17 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | seat | B | 1 | Asiento 0–15; no es un identificador persistente. |
| 1 | eye | 3f | 12 | x, y, z en metros. |
| 13 | yaw | H | 2 | 65536 unidades por vuelta; positivo gira a la derecha. |
| 15 | pitch | h | 2 | 1/256 de grado; positivo mira abajo; rango −22784…22784 (±89°). |
Cuerpos visibles y animación
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. Posición finita dentro de ±16384 m por eje. El servidor decide qué cuerpos se pueden percibir; ausencia no revela un enemigo oculto.
Origen de mezcla: códigos 0…6 corresponden a motion 0…6; código 7 significa jump_start (motion 10). Paso 0: sin mezcla y origen 0 obligatorio. Pasos 1…3: peso 1/4, 2/4, 3/4. Pasos 4…7: peso 1/8, 3/8, 5/8, 7/8; sólo para pistola o cuchillo pasando de walk/jog/sprint a idle. Toda mezcla exige origen y destino distintos y ambos agachados (crouch_idle/crouch_walk) o ambos no agachados.
reload_phase no puede ser distinto de cero con cuchillo ni motion=death. attack_age no puede ser distinto de cero con death, shoot, reload ni una reload_phase activa. Son restricciones de decodificación; el codificador no sustituye la validación de entradas arbitrarias.
Protocolo 15 conserva motion_phase como fase del clip destino y añade stop_phase como fase explícita del clip fuente durante la frenada secundaria. El servidor congela la última fase pública de marcha; ante cambio simultáneo de arma sin pose previa compatible, calcula la fuente con la distancia recorrida bajo el arma nueva. La mezcla y el byte fuente del snapshot más cercano viajan juntos; no requieren historia de visibilidad del cliente. El destino idle sigue su reloj normal. Se invalidan tokens/clientes de protocolos anteriores. Revisión de pose 13; Agent API 14 sin cambios de campos ni ABI.
RemotePlayer · Jugador visible · 22 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | id | B | 1 | Asiento 0–15; no es un identificador persistente. |
| 1 | position | 3f | 12 | x, y, z en metros. |
| 13 | yaw | H | 2 | 65536 unidades por vuelta; positivo gira a la derecha. |
| 15 | pitch | b | 1 | Grados enteros, −89…89; distinto del pitch de UserCmd. |
| 16 | flags | B | 1 | Bit 0: crouched; bits 1…4: motion; bits 5…6: equipo 0…2; bit 7: apariencia 0…1. |
| 17 | motion_phase | B | 1 | Fracción del clip, en pasos de 1/256. |
| 18 | pose | B | 1 | Bits 0…1: arma 0…2; bits 2…4: origen de mezcla; bits 5…7: paso de mezcla. |
| 19 | reload_phase | B | 1 | 0 inactiva; 1…255 progreso normalizado. |
| 20 | attack_age | B | 1 | 0 inactivo; de lo contrario 1 + edad en ticks. |
| 21 | stop_phase | B | 1 | Fase fuente congelada de frenada secundaria, 0–255 en 1/256 del clip de origen. Sólo activa con pistola/cuchillo, idle, fuente walk/jog/sprint y fade_step 1–7; fuera de ese contexto debe ser cero. Cero es una fase válida dentro del contexto. |
Eventos, trazadoras y disparos oídos
Cada evento es EventHeader + su carga: shot 19 B, hurt 6 B, death 4 B, kill 6 B en total. Los tipos 1…3 son privados; kill es público, sin posición. Los eventos se repiten del más antiguo al más nuevo hasta recibir snapshot_ack de un snapshot que los contenía; procesar cada id una sola vez. Sus id son consecutivos módulo 65536.
zone: 0 cabeza, 1 torso, 2 extremidad. El cero de un fallo o de una muerte por el mundo es un valor vacío canónico, no un impacto en la cabeza.
En shot, other distinto de 255 exige otro asiento válido y unseen=0. Con other=255, damage y zone son cero; killed sólo puede ser 1 si unseen=1. Un impacto oculto conserva una posición autorizada de entrada en cobertura, no la del enemigo. En kill, víctima 0…15; atacante distinto de víctima o 255. Si mata el mundo, zone, through y weapon son cero.
Tracer contiene el segmento seleccionado por el filtro del servidor, con extremos finitos dentro de ±16384 m, sin identidad del tirador. Gunshot contiene posición autorizada redondeada y arma, sin identidad. Para codificar sus coordenadas se divide por 0,25 m, se redondea alejándose de cero en los empates y se limita a ±32767; −32768 se rechaza al leer. Trazadoras y disparos oídos son efímeros, sin el acuse de eventos.
Las trazadoras se recortan por muestras; no se garantiza visibilidad continua de todo el segmento.
EventHeader · Cabecera de evento · 3 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | id | H | 2 | Secuencia consecutiva por jugador, módulo 65536. |
| 2 | kind | B | 1 | 1 shot, 2 hurt, 3 death, 4 kill. |
Shot · Carga de disparo · 16 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | flags | B | 1 | Bit 0: through; bit 1: killed; bits 2…3: zone 0…2; bit 4: unseen; bit 5: cero; bits 6…7: arma 0…2. |
| 1 | other | B | 1 | Asiento impactado o 255; nunca el propio. |
| 2 | damage | H | 2 | Daño en centésimas de punto. |
| 4 | position | 3f | 12 | Impacto autorizado; con impacto oculto, entrada en la cobertura, no posición del enemigo. |
Hurt · Carga de daño propio · 3 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | zone | B | 1 | 0…2; no contiene atacante ni posición. |
| 1 | damage | H | 2 | Daño en centésimas de punto. |
Death · Carga de muerte propia · 1 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | other | B | 1 | Asiento del atacante o 255 para el mundo; nunca el propio. |
Kill · Carga de baja pública · 3 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | killer | B | 1 | Asiento del atacante o 255 para el mundo. |
| 1 | victim | B | 1 | Asiento 0–15; no es un identificador persistente. |
| 2 | flags | B | 1 | Bits 0…1: zone 0…2; bit 2: through; bits 3…7: arma 0…2. |
Tracer · Segmento visible · 24 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | from | 3f | 12 | x, y, z en metros. |
| 12 | to | 3f | 12 | x, y, z en metros. |
Gunshot · Disparo oído · 7 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | position | 3h | 6 | x, y, z en unidades de 0,25 m; −32768 inválido. |
| 6 | weapon | B | 1 | 0 rifle, 1 pistola, 2 cuchillo; 3 inválido. |
Ronda, bomba, plantel y compañeros
Con phase=none, Round debe estar enteramente en cero. En las demás fases rounds_to_win es positivo, los marcadores no lo superan y attackers es 1 o 2. number es cero exactamente en warmup. winner es distinto de cero exactamente en over o ended.
Bomb sólo viaja si sides bit 4 está activo: 19 bytes más 13 por sitio. site es distinto de cero exactamente en planted, defused o exploded; fuse_left sólo puede ser distinto de cero en planted. carrier sólo puede nombrar un asiento en carried; de lo contrario vale 255. En carried la posición es cero; en otros estados debe ser finita. Las cajas se ordenan A, B; min ≤ max por eje y coordenadas decodificadas dentro de ±3276 m. Al codificar cajas se multiplica cada f32 por 10 y se redondea alejándose de cero en los empates.
La audiencia de cada campo de bomba la decide el servidor: plantado, sitio, mecha, posición plantada y cajas son públicos; portador y posición caída se revelan sólo al equipo atacante; progress es propio. No se envía la posición de un jugador oculto por medio de la bomba.
El plantel lleva siempre 16 filas, 48 bytes, en orden de asiento. Un asiento inactivo tiene toda su fila en cero. El rol procede del token autenticado; equipo y apariencia no permiten cambiarlo. El bloque opcional de compañeros lleva count=1…15 y filas Teammate; nunca un bloque vacío anunciado. Sólo lo recibe un jugador vivo con equipo. Cada fila debe corresponder en el plantel a un compañero activo y vivo del mismo equipo, con posición finita en ±16384 m, sin duplicados ni jugadores ya visibles. No lleva vida, arma ni puntería.
Round · Estado de ronda · 10 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | phase | B | 1 | 0 none, 1 warmup, 2 freeze, 3 live, 4 over, 5 ended. |
| 1 | number | B | 1 | 0 en calentamiento; desde 1 en rondas. |
| 2 | rounds_to_win | B | 1 | Rondas necesarias para ganar. |
| 3 | score_alpha | B | 1 | Rondas ganadas por alpha. |
| 4 | score_bravo | B | 1 | Rondas ganadas por bravo. |
| 5 | alive_alpha | B | 1 | Jugadores vivos de alpha. |
| 6 | alive_bravo | B | 1 | Jugadores vivos de bravo. |
| 7 | ticks_left | H | 2 | Ticks restantes de la fase. |
| 9 | sides | B | 1 | Bits 0…1: atacantes; bits 2…3: ganador (equipos 0…2); bit 4: bomba presente; bits 5…7: cero. |
Bomb · Estado fijo de bomba · 19 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | phase | B | 1 | 1 carried, 2 dropped, 3 planted, 4 defused, 5 exploded; 0 implica bloque ausente. |
| 1 | site | B | 1 | 0 sin plantar, 1 A, 2 B. |
| 2 | carrier | B | 1 | Asiento del portador autorizado o 255. |
| 3 | progress | B | 1 | Progreso propio, 0…100. |
| 4 | fuse_left | H | 2 | Ticks de mecha; cero salvo mientras está plantada. |
| 6 | position | 3f | 12 | x, y, z en metros. |
| 18 | site_count | B | 1 | 0…2 cajas de sitio, a continuación. |
BombSite · Caja de plantado · 13 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | letter | B | 1 | 1 para la primera caja (A), 2 para la segunda (B). |
| 1 | min | 3h | 6 | Mínimos x, y, z en decímetros. |
| 7 | max | 3h | 6 | Máximos x, y, z en decímetros. |
RosterEntry · Fila del plantel · 3 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | flags | B | 1 | Bit 0: active; bit 1: alive; bits 2…3: equipo 0…2; bit 4: apariencia 0…1; bit 5: rol 0 persona, 1 IA; bits 6…7: cero. |
| 1 | kills | B | 1 | Bajas saturadas a 255. |
| 2 | deaths | B | 1 | Muertes saturadas a 255. |
Teammate · Compañero vivo no visible · 13 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | id | B | 1 | Asiento 0–15; no es un identificador persistente. |
| 1 | position | 3f | 12 | x, y, z en metros. |
Chat separado
ChatSayHeader + texto viaja del cliente al servidor. ChatHeardHeader + texto viaja del servidor al cliente: el byte from está inmediatamente después del tipo, antes de scope. El cliente no elige remitente. El texto mide 1…120 bytes, no caracteres, sin terminador cero. Se rechazan bytes ASCII de control 0x00…0x1f y 0x7f, un mensaje formado sólo por espacios ASCII y UTF-8 inválido (incluidas codificaciones demasiado largas, sustitutos y valores superiores a U+10FFFF). No se afirma filtrado de todos los controles Unicode.
Tasa por asiento: ráfaga de 3 mensajes y después 1 por segundo. En una ronda live, sólo el mismo equipo y el mismo estado vivo/muerto, aunque se solicite scope=1. Fuera de live, scope=0 va al equipo y scope=1 a todos. Sin rondas se mantiene ese filtro de alcance: scope=0 incluye a los compañeros; en modo libre, sin equipo, sólo al remitente. scope=1 incluye todos los asientos activos. El remitente siempre está incluido en la audiencia; el eco no garantiza entrega. El servidor descarta y cuenta excesos; estos mensajes no tienen el mecanismo de reenvío/acuse de eventos.
Agent API 14 expone chat_count y chat[4] con from, scope y texto terminado en cero de 121 bytes; bs_observation mide 2964 bytes y bs_action 44 en la ABI comprobada. Hay 61 campos de observación y 11 de acción; la ABI en memoria no es este formato de red. C: bs_agent_say y bs_gym_say. Python: observation.messages() y agent.say(text). Las IAs leen y escriben; falta entrada de texto humana y voz del juego. Voz externa no llega automáticamente a la IA; la decisión de paridad sigue abierta.
ChatSayHeader · Chat cliente → servidor · 3 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | type | B | 1 | Siempre 3. |
| 1 | scope | B | 1 | 0 equipo, 1 todos; el servidor restringe la audiencia. |
| 2 | length | B | 1 | 1…120 bytes de texto a continuación, sin terminador. |
ChatHeardHeader · Chat servidor → cliente · 4 B
| Desplazamiento | Campo | Formato | Bytes | Significado |
|---|---|---|---|---|
| 0 | type | B | 1 | Siempre 3. |
| 1 | from | B | 1 | Asiento 0–15; no es un identificador persistente. |
| 2 | scope | B | 1 | 0 equipo, 1 todos. |
| 3 | length | B | 1 | 1…120 bytes de texto a continuación, sin terminador. |
Presupuestos y límites de alcance
Peor caso analítico servido 5v5: snapshot de 857 bytes con objetivo y dos sitios, IA viva con motor, equipo con suministro y rig. Con 73 bytes de cabeceras y 64 Hz: (857+73)×64=59520 B/s; margen 4480 frente al tope de 64000. Sin objetivo: 812 bytes, 56640 B/s. Subida habitual con 3 comandos: (48+73)×64=7744 B/s frente a 8000. Estas cuentas no son una nueva medición de tráfico.
Chat máximo: 4+120+73=197 bytes por mensaje recibido; cinco compañeros al límite sostenido generan 985 B/s adicionales, con presupuesto separado. Las ráfagas y el tráfico recibido fuera de un equipo 5v5 no se describen con esa tasa sostenida. El límite de diez asientos servidos sigue vigente; los dieciséis del formato no acreditan soporte de partidas mayores.
Pendientes: reconexión, compresión delta y hash de contenido en el handshake; sandbox y deadline de bots; validación humana de justicia H5. El rol autenticado en el token identifica para quién se emitió el token, no prueba que juegue una persona. El servidor limita percepción y entradas, pero no está demostrada equivalencia humana. Los ejemplos siguientes verifican bytes de payload; no constituyen una implementación del transporte ni una prueba humana.
Ejemplos de bytes
command · 14 B
Secuencia 0x01020304, forward+fire, yaw 90°, pitch −1,5°, rifle, medio tick después de 4091.
04 03 02 01 81 00 00 40 80 fe 01 80 fb 0f
commands · 20 B
Paquete con un comando y acuse del 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 sin extensiones ni listas, con estado propio de valores distinguibles y plantel inactivo. Sirve para comprobar desplazamientos, no representa una 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 muerto con equipo y vista del compañero 1; sin 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
Vector sintético con motor, rig, suministro, un remoto, los cuatro tipos de evento, trazadora, disparo oído, bomba con dos cajas, plantel y un compañero oculto. Prueba el orden de los bloques, no el máximo de ancho de banda ni una sesión jugada.
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
Solicitud al equipo: texto UTF-8 “ñ”, dos bytes.
03 00 02 c3 b1
chat_heard · 6 B
La misma línea recibida desde el asiento 3.
03 03 00 02 c3 b1
snapshot_secondary_stop · 333 B
Mismo vector extendido con frenada de pistola: destino idle, fuente walk, primer peso 1/8 y fase fuente 66/256. El cero del campo en otros contextos no sustituye este ejemplo activo.
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