BotStrikes

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.

CamposSignificado
tickTick de servidor más reciente recibido.
joined, alive, grounded, crouched, reloadingIndicadores de conexión a la partida, vida, suelo, postura agachada y recarga.
seatAsiento propio en la partida.
position, velocityPosición y velocidad propias predichas; metros y metros por segundo.
yaw, pitchVista propia en grados, con recorte del modelo motor y predicción de comandos en vuelo; pitch positivo mira hacia abajo.
health, armorVida y armadura propias.
weapon, ammo, reserveArma elegida, munición del cargador y reserva. weapon: 1 rifle, 2 pistola, 3 cuchillo.
kills, deaths, respawn_ticksBajas, muertes y ticks hasta reaparición. No se reaparece durante una ronda en juego.
moneyDinero propio; contabilidad implementada, compras pendientes. No informa el dinero de otro asiento.
visible_count, visibleCantidad y lista de jugadores que el servidor autoriza a percibir. No se incluye un enemigo oculto. Python: players().
event_count, eventsCantidad y lista de resultados desde la observación anterior; se consumen una vez. Python: results().
tracer_count, tracersCantidad y lista de trazadoras percibidas desde la observación anterior. Python: streaks().
team, appearance, friendly_fire, animatedBando, apariencia, fuego amigo y animación. La apariencia no identifica si el jugador es humano o IA.
supply_position, supply_radiusCentro y radio de la zona propia de reaprovisionamiento; cero en todos contra todos.
round_phase0 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_leftNúmero de ronda, rondas necesarias para ganar y ticks restantes de la fase actual.
score_own, score_other, alive_own, alive_other, attackingMarcadores 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_pitchAsiento 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, teammatesCompañeros vivos fuera de vista: asiento y posición, nunca enemigos. Vacío mientras se está muerto. Python: mates().
bomb_phase0 sin objetivo, 1 llevada, 2 tirada, 3 plantada, 4 desactivada, 5 explotada.
bomb_site, bomb_carrier, bomb_progress, bomb_fuse_ticks_left, bomb_positionSitio, 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_sitesCantidad y cajas públicas de los sitios de plantado. Python: sites(); cada caja tiene contains() y centre().
roster_count, rosterPlantel público, en orden de asiento; active identifica los asientos ocupados. Python: scoreboard().
gunshot_count, gunshotsDisparos oídos desde la observación anterior; no identifican al tirador. Python: shots_heard().
chat_count, chatMensajes 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.

CamposSignificado
forward, rightEjes de movimiento: negativo, cero o positivo.
yaw, pitchVista deseada en grados; pitch positivo mira hacia abajo. El servidor aplica los límites.
jump, crouch, walk, fire, reloadTeclas mantenidas de salto, agacharse, caminar, disparar y recargar.
useMantener para plantar o desactivar cuando las reglas lo permiten.
weapon0 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

DesplazamientoCampoFormatoBytesSignificado
0typeB1Siempre 1.
1countB11…4 comandos, consecutivos, del más viejo al más nuevo.
2snapshot_ackI4Último tick de snapshot recibido.

UserCmd · Entrada mantenida · 14 B

DesplazamientoCampoFormatoBytesSignificado
0sequenceI4Secuencia del comando, módulo 2^32.
4buttonsH2Bits 0…9: forward, back, left, right, jump, crouch, walk, fire, reload, use; los demás cero.
6yawH265536 unidades por vuelta; positivo gira a la derecha.
8pitchh21/256 de grado; positivo mira abajo; rango −22784…22784 (±89°).
10weaponB10 conserva el arma; 1 rifle, 2 pistola, 3 cuchillo.
11view_fractionB1Fracción tras view_tick, en 1/256 de tick. No es un byte reservado.
12view_tickH216 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

DesplazamientoCampoFormatoBytesSignificado
0typeB1Siempre 2.
1self_and_matesB1Bits 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

DesplazamientoCampoFormatoBytesSignificado
0tickI4Tick del servidor.
4last_commandI4Última secuencia aplicada por el servidor.
8position3f12x, y, z en metros.
20velocity3f12x, y, z en metros por segundo.
32flagsB1Bits 0…5: grounded, crouched, jump_held, alive, trigger_held, motor. Bits 6…7: equipo (0 ninguno, 1 alpha, 2 bravo).
33identityB1Bits 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.
34healthf4Vida propia, 0…1000.
38armorf4Armadura propia, 0…1000.
42rifle_ammoH2Balas en el cargador del rifle.
44rifle_reserveH2Reserva del rifle.
46rifle_cooldownB1Ticks hasta poder disparar el rifle.
47pistol_ammoH2Balas en el cargador de la pistola.
49pistol_reserveH2Reserva de la pistola.
51pistol_cooldownB1Ticks hasta poder disparar la pistola.
52knife_cooldownB1Ticks hasta otro ataque de cuchillo; no viaja munición del cuchillo.
53idleB1Contador de reposo del arma, saturado a 255.
54patternB1Índice del patrón del arma activa, saturado a 255.
55weapon_timerH2Ticks de recarga; con cuchillo, ticks hasta el impacto pendiente.
57recoil_pitchf4Retroceso vertical en grados, ±90.
61recoil_yawf4Retroceso horizontal en grados, ±90.
65respawnH2Ticks hasta reaparecer; cero si está vivo.
67killsH2Bajas propias.
69deathsH2Muertes propias.
71moneyH2Dinero propio; las compras no están implementadas.
73leadB1Comandos de este jugador en espera por delante del tick; saturado.

MotorState · Extensión motor · 24 B

DesplazamientoCampoFormatoBytesSignificado
0applied_buttonsH2Bits 0…9: forward, back, left, right, jump, crouch, walk, fire, reload, use; los demás cero.
2applied_yawH265536 unidades por vuelta; positivo gira a la derecha.
4applied_pitchh21/256 de grado; positivo mira abajo; rango −22784…22784 (±89°).
6turn_yawf4Giro horizontal anterior, grados por tick, ±180.
10turn_pitchf4Giro vertical anterior, grados por tick, ±178.
14keys10B10Crédito de cambio para cada botón, en 1/64 de cambio.

Rig · Firma del rig · 8 B

DesplazamientoCampoFormatoBytesSignificado
0rig_hashQ8Firma no nula; palabra baja primero. No autentica contenido.

Supply · Zona de suministro · 16 B

DesplazamientoCampoFormatoBytesSignificado
0position3f12x, y, z en metros.
12radiusf4Radio en metros, 0…16384.

Spectate · Vista del compañero espectado · 17 B

DesplazamientoCampoFormatoBytesSignificado
0seatB1Asiento 0–15; no es un identificador persistente.
1eye3f12x, y, z en metros.
13yawH265536 unidades por vuelta; positivo gira a la derecha.
15pitchh21/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

DesplazamientoCampoFormatoBytesSignificado
0idB1Asiento 0–15; no es un identificador persistente.
1position3f12x, y, z en metros.
13yawH265536 unidades por vuelta; positivo gira a la derecha.
15pitchb1Grados enteros, −89…89; distinto del pitch de UserCmd.
16flagsB1Bit 0: crouched; bits 1…4: motion; bits 5…6: equipo 0…2; bit 7: apariencia 0…1.
17motion_phaseB1Fracción del clip, en pasos de 1/256.
18poseB1Bits 0…1: arma 0…2; bits 2…4: origen de mezcla; bits 5…7: paso de mezcla.
19reload_phaseB10 inactiva; 1…255 progreso normalizado.
20attack_ageB10 inactivo; de lo contrario 1 + edad en ticks.
21stop_phaseB1Fase 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

DesplazamientoCampoFormatoBytesSignificado
0idH2Secuencia consecutiva por jugador, módulo 65536.
2kindB11 shot, 2 hurt, 3 death, 4 kill.

Shot · Carga de disparo · 16 B

DesplazamientoCampoFormatoBytesSignificado
0flagsB1Bit 0: through; bit 1: killed; bits 2…3: zone 0…2; bit 4: unseen; bit 5: cero; bits 6…7: arma 0…2.
1otherB1Asiento impactado o 255; nunca el propio.
2damageH2Daño en centésimas de punto.
4position3f12Impacto autorizado; con impacto oculto, entrada en la cobertura, no posición del enemigo.

Hurt · Carga de daño propio · 3 B

DesplazamientoCampoFormatoBytesSignificado
0zoneB10…2; no contiene atacante ni posición.
1damageH2Daño en centésimas de punto.

Death · Carga de muerte propia · 1 B

DesplazamientoCampoFormatoBytesSignificado
0otherB1Asiento del atacante o 255 para el mundo; nunca el propio.

Kill · Carga de baja pública · 3 B

DesplazamientoCampoFormatoBytesSignificado
0killerB1Asiento del atacante o 255 para el mundo.
1victimB1Asiento 0–15; no es un identificador persistente.
2flagsB1Bits 0…1: zone 0…2; bit 2: through; bits 3…7: arma 0…2.

Tracer · Segmento visible · 24 B

DesplazamientoCampoFormatoBytesSignificado
0from3f12x, y, z en metros.
12to3f12x, y, z en metros.

Gunshot · Disparo oído · 7 B

DesplazamientoCampoFormatoBytesSignificado
0position3h6x, y, z en unidades de 0,25 m; −32768 inválido.
6weaponB10 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

DesplazamientoCampoFormatoBytesSignificado
0phaseB10 none, 1 warmup, 2 freeze, 3 live, 4 over, 5 ended.
1numberB10 en calentamiento; desde 1 en rondas.
2rounds_to_winB1Rondas necesarias para ganar.
3score_alphaB1Rondas ganadas por alpha.
4score_bravoB1Rondas ganadas por bravo.
5alive_alphaB1Jugadores vivos de alpha.
6alive_bravoB1Jugadores vivos de bravo.
7ticks_leftH2Ticks restantes de la fase.
9sidesB1Bits 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

DesplazamientoCampoFormatoBytesSignificado
0phaseB11 carried, 2 dropped, 3 planted, 4 defused, 5 exploded; 0 implica bloque ausente.
1siteB10 sin plantar, 1 A, 2 B.
2carrierB1Asiento del portador autorizado o 255.
3progressB1Progreso propio, 0…100.
4fuse_leftH2Ticks de mecha; cero salvo mientras está plantada.
6position3f12x, y, z en metros.
18site_countB10…2 cajas de sitio, a continuación.

BombSite · Caja de plantado · 13 B

DesplazamientoCampoFormatoBytesSignificado
0letterB11 para la primera caja (A), 2 para la segunda (B).
1min3h6Mínimos x, y, z en decímetros.
7max3h6Máximos x, y, z en decímetros.

RosterEntry · Fila del plantel · 3 B

DesplazamientoCampoFormatoBytesSignificado
0flagsB1Bit 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.
1killsB1Bajas saturadas a 255.
2deathsB1Muertes saturadas a 255.

Teammate · Compañero vivo no visible · 13 B

DesplazamientoCampoFormatoBytesSignificado
0idB1Asiento 0–15; no es un identificador persistente.
1position3f12x, 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

DesplazamientoCampoFormatoBytesSignificado
0typeB1Siempre 3.
1scopeB10 equipo, 1 todos; el servidor restringe la audiencia.
2lengthB11…120 bytes de texto a continuación, sin terminador.

ChatHeardHeader · Chat servidor → cliente · 4 B

DesplazamientoCampoFormatoBytesSignificado
0typeB1Siempre 3.
1fromB1Asiento 0–15; no es un identificador persistente.
2scopeB10 equipo, 1 todos.
3lengthB11…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