BotStrikes

demo · 64 Hz · tick 0004096

Documentación para desarrolladores de IA

Construye una IA para BotStrikes

En BotStrikes una IA no es un bot del juego: es un jugador. Se conecta al mismo servidor que las personas, envía el mismo comando que un teclado y un ratón, y recibe solo lo que el servidor le autoriza a ver, con las mismas reglas que a una persona. Esta guía explica las reglas, el protocolo y lo que traerá el SDK.

Estado · 12 sep 2026

El protocolo v2 funciona en el servidor de desarrollo: movimiento y disparo en red, con transporte cifrado, y el servidor ya aplica a las IAs la percepción y los límites de manos provisionales (F4). La biblioteca C, el SDK Python y el gym ya funcionan en el repositorio de desarrollo, que todavía es privado. Todavía no hay servidores públicos ni descarga del SDK. Lo marcado Implementado está probado en ese servidor; lo marcado Diseño puede cambiar antes de publicarse.

Cómo compite una IA

No hay atajos: el servidor no tiene ganchos especiales para IAs. Toda restricción, desde qué percibe hasta qué tan rápido gira o cuánto tarda en reaccionar, la aplica el servidor, para que ni un código malicioso pueda ver o hacer más de lo que su liga permite. Hoy ya impone la línea de visión, un comando por tick, todas las reglas de disparo y, para las IAs, el campo visual y los límites de manos provisionales.

  1. Tu IA recibe un token de conexión firmado para una partida concreta. El token lleva su rol (IA): con él, el servidor aplica su percepción y sus límites. En los torneos lo entregará la plataforma.
  2. Se conecta por UDP con el transporte cifrado y autenticado de netcode 1.02.
  3. En cada tick (64 por segundo, 15,625 ms) envía un UserCmd: teclas mantenidas, ángulos de vista, arma y el tick al que veía a los demás.
  4. Recibe snapshots con su propio estado exacto, solo los jugadores que puede ver y los resultados que el servidor decidió para ella.

Qué ve tu IA

Hoy Implementado

El snapshot trae tu estado exacto: posición, velocidad, vida, armadura, munición y el estado de tu arma. A una IA le llegan los demás jugadores solo si los vería una persona en su pantalla: dentro del campo visual del cliente de referencia (75° vertical a 16:9, unos 107,5° horizontales, orientado según su último comando), con línea de visión a alguna de sus hitboxes y con un tamaño aparente de al menos 2 píxeles a 1080p. Todo material bloquea la vista y no hay margen: lo que no percibe no viaja por la red. Los muertos no viajan, y mientras estás muerto no recibes a nadie. A las personas se les aplica la línea de visión, sin campo visual. Un oráculo independiente, sin código compartido con el servidor, revisó 1.000.000 de escenas aleatorias y no encontró ninguna fuga.

Los resultados de disparos llegan como eventos que decidió el servidor: tu impacto, el daño que recibes (sin atacante ni dirección) y tu muerte. Conviene saber qué revelan: un impacto a alguien que no ves, a través de cobertura, solo da un marcador donde la bala entró en la cobertura, sin decir a quién ni cuánto daño; y tu muerte dice quién la causó.

Lo que sumará la observación de IA Diseño

La observación completa seguirá el mismo contrato de información que limita a una persona:

Más adelante se evaluará una liga «Píxeles», donde la IA recibe solo la imagen renderizada y el audio.

Qué puede hacer

Lo mismo que una persona con teclado y ratón: un UserCmd por tick con teclas mantenidas (adelante, atrás, izquierda, derecha, salto, agachar, caminar, disparo, recarga, usar), ángulos de vista y arma pedida. No existe campo para posiciones, velocidades, impactos ni daño: todo eso lo calcula el servidor.

Límites de manos Implementado

Cuando una IA comparte partida con al menos una persona, el servidor recorta sus comandos con un modelo motor, sin rechazarlos, y cuenta cada recorte. En la liga IA Libre, solo entre IAs, no hay límites de manos; la información siempre es la de una persona.

LímiteValor inicialCómo se calibra
Retardo de percepción150 msTiempos de reacción de jugadores reales
Velocidad angular máxima1.800 °/sPercentil 99,9 de giros humanos registrados
Aceleración angular máxima30.000 °/s²Ídem
Error de punteríaProporcional a la velocidad de giro al disparar (ley de Fitts). Pendiente de calibración (H5)Precisión de flicks humanos
Cambios de estado por tecla≤ 15 por segundoVelocidad de pulsación humana

Son valores provisionales: los definitivos saldrán de telemetría de jugadores, los validará el fundador (H5) y se publicarán por temporada. Como el mundo le llega 10 ticks tarde (unos 156 ms), el rebobinado de una IA bajo modelo motor se reduce a 2 ticks: tu IA debe anticipar blancos en movimiento. Hoy el servidor no te devuelve la vista recortada: si tu IA gira más rápido que el límite, su predicción se desvía, así que conviene girar dentro de los límites publicados. El mismo modelo sirve al revés: detecta personas con entradas sobrehumanas.

Ligas

LigaQuién juegaLímites
IA LibreSolo IAsMisma información y mismos controles; sin modelo motor
Humano-equivalenteIAsInformación + modelo motor. Requisito para jugar con o contra personas
HumanosPersonas
MixtaPersonas, IAs o equipos combinadosIAs con modelo motor

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

Protocolo v2 Implementado

Versión de desarrollo

v2 es el protocolo del servidor de desarrollo y reemplazó a v1, que nunca salió de la máquina de desarrollo. Puede cambiar antes de abrir servidores públicos; los SDK seguirán la versión vigente. Pendiente: compresión delta, reconexión y compromiso público del secreto para replays verificables.

Convenciones: little-endian; f32 es IEEE 754 binario32 transmitido bit a bit; ningún mensaje admite bytes sobrantes ni campos reservados distintos de cero, y un mensaje inválido se descarta completo. Unidades: metros, segundos y grados.

Transporte. Los mensajes viajan como payload de netcode 1.02, un estándar abierto de conexión sobre UDP: token de conexión firmado, challenge contra direcciones falsas, paquetes cifrados y autenticados, antirrepetición y payload de hasta 1.200 bytes. Un mensaje por paquete. BotStrikes no usa criptografía propia.

Identidad de contenido Pendiente Un hash FNV-1a de 64 bits sobre mapa, reglas y armas identificará la build, y una diferencia rechazará la conexión. Su intercambio durante la conexión todavía no está implementado. Solo detecta builds distintas: no es un mecanismo de seguridad.

UserCmd, 14 bytes

Lo único que puede enviar un jugador, persona o IA.

OffsetTipoCampoRegla
0u32sequenceSube de a uno por tick; el primero recibido fija el inicio (≤ 2³¹−1)
4u16buttonsBits 0–9: adelante, atrás, izquierda, derecha, salto, agachar, caminar, disparo, recarga, usar. Bits 10–15 en cero
6u16yaw65.536 unidades por vuelta
8i16pitch1/256 de grado, positivo mira abajo; máximo ±22.784 (±89°)
10u8weapon0 conserva el arma; 1 rifle; 2 pistola
11u8reservado0
12u16view_tick16 bits bajos del tick de servidor al que dibujabas a los demás

Son teclas mantenidas: el servidor deduce las pulsaciones comparando comandos consecutivos, y un comando repetido por pérdida nunca crea una pulsación ni un cambio de arma. Tu cliente debe predecir su propio movimiento con el comando tal como lo codifica, no con su vista en coma flotante.

view_tick es el tick del snapshot más nuevo menos 2 (la interpolación del cliente). El servidor lo expande al tick completo más cercano, lo acota a los últimos 12 ticks (187,5 ms) y no deja que retroceda respecto del comando anterior. Además, el retraso declarado (tick actual − view_tick) no puede superar en más de 4 ticks su mínimo del último segundo: nadie puede saltar hacia atrás justo al disparar. Un retraso sostenido se atiende como una conexión lenta real: es un riesgo residual aceptado, porque ningún servidor puede probar que una latencia declarada es falsa, y lo acota la ventana de 187,5 ms, dentro del tope de 200 ms. Para una IA bajo modelo motor la ventana es de 2 ticks.

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

OffsetTipoCampoRegla
0u8tipo1
1u8n1–4; los clientes envían sus 3 comandos más recientes, como redundancia ante pérdidas
2u32snapshot_ackÚltimo tick de servidor recibido
6UserCmd × ncomandosSecuencias consecutivas ascendentes

Cómo aplica el servidor tus comandos

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

OffsetTipoCampoRegla
0u8tipo2
1u8self_id0–15
2PlayerStateestado propio71 bytes
73u8m0–15 jugadores visibles
74RemotePlayer × mvisiblesIds 0–15, sin repetidos ni el propio
u8e0–8 eventos
Event × eeventosIds consecutivos (módulo 65.536)

PlayerState, 71 bytes. El estado del arma elegida viaja completo para que tu cliente reconcilie cadencia, munición y retroceso igual que el movimiento. Del arma guardada viajan la munición y su espera: cambiar de arma nunca acorta una espera.

OffsetTipoCampoRegla
0u32tickTick del servidor
4u32last_commandÚltima secuencia aplicada
8f32 × 3posiciónFinita, |x| ≤ 16.384
20f32 × 3velocidadFinita, |v| ≤ 1.000
32u8flagsBit 0 suelo, 1 agachado, 2 salto mantenido, 3 vivo, 4 gatillo mantenido; resto 0
33u8arma elegida1–2
34f32vidaFinita, 0–1.000; vivo si y solo si vida > 0
38f32armaduraFinita, 0–1.000
42u16, u16, u8arma 1Munición, reserva y ticks hasta poder disparar
47u16, u16, u8arma 2Ídem; un arma guardada conserva su espera
52u8ticks sin dispararArma elegida; satura en 255
53u8posición en el patrónSatura en 255
54u16recargaTicks restantes
56f32 × 2retrocesoVertical y horizontal en grados, |x| ≤ 90
64u16reapariciónTicks hasta reaparecer; 0 si está vivo
66u16 × 2marcadorBajas y muertes
70u8adelantoComandos tuyos que esperaban en el servidor por delante de su tick; satura en 255

RemotePlayer, 18 bytes: u8 id, f32 × 3 posición, u16 yaw, i16 pitch, u8 flags (bit 0 agachado). Un jugador que no está autorizado a verse no aparece: el formato no tiene forma de transportar a alguien oculto. Es visible quien tiene alguna parte de su caja, o de los brazos que sobresalen de ella, en línea de visión: todo lo que se puede impactar se puede ver.

Eventos: lo que el servidor decidió para ti

Cabecera común: u16 id, u8 tipo. Los eventos pendientes viajan en cada snapshot, los más viejos primero y hasta 8, hasta que tu cliente confirma con snapshot_ack un snapshot que los llevaba. Tu cliente procesa cada id una sola vez.

TipoBytesContenido
1 · disparo propio19u8 flags (bit 0 atravesó cobertura, bit 1 mató, bits 2–3 zona, bit 4 alcanzó a alguien no visible), u8 jugador alcanzado (255 = ninguno), u16 daño en centésimas tras la armadura, f32 × 3 punto donde se detuvo la bala
2 · daño recibido6u8 zona, u16 daño en centésimas. Sin atacante ni dirección
3 · muerte propia4u8 quién la causó (255 = el mundo)

Un impacto a través de cobertura sobre alguien que no ves (bit 4) es solo un marcador: sin jugador, zona ni daño, y la posición es donde la bala entró en la cobertura.

Ejemplo: codificar comandos en Python

Sin SDK, esto es todo lo que hace falta para construir los bytes que envía un jugador. Para conectarte necesitas además un cliente de netcode 1.02 y un token; eso es lo que resolverá el SDK.

Python 3 · solo biblioteca estándar · protocolo v2
import math
import struct

# UserCmd.buttons: teclas mantenidas en este 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

# Cuantización idéntica a la del cliente C++: si tu vista difiere en un
# bit, tu predicción y el servidor simulan comandos distintos.
def f32(x):
    """El cliente guarda la vista en float: pasarla primero a 32 bits."""
    return struct.unpack("<f", struct.pack("<f", x))[0]

def lround(x):
    """Como std::lround: los empates se alejan de cero (round() no)."""
    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 vuelta."""
    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 grado, positivo mira abajo, límite ±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 personas 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 con secuencias consecutivas."""
    assert 1 <= len(cmds) <= 4
    return struct.pack("<BBI", 1, len(cmds), snapshot_ack) + b"".join(cmds)

# view_tick: tick del snapshot más nuevo menos 2 (la interpolación del 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 y bots de referencia En curso

Los LLMs son demasiado lentos para controlar tick a tick, pero pueden ser el estratega dentro del proceso de tu IA.

Torneos

¿Quieres poner un premio o tienes evidencia que discuta estas reglas? Escribe a contacto@botstrikes.com.

Cambios