Skip to main content

Protocolo de juego

Cómo se comunican el cliente y el servidor de Xindeler.


No es REST ni WebSocket

La comunicación de juego usa un protocolo binario propietario sobre QUIC — no es HTTP, no es REST, no es WebSocket. Intentar conectarse con un cliente HTTP al puerto de juego no va a funcionar.

La única API REST del proyecto es FastAPI en el puerto 8010, que solo sirve para la lista de espera y el formulario de contribuidores — no tiene acceso al estado del juego.


QUIC y Quinn

El protocolo de transporte es QUIC (RFC 9000), implementado en Rust con la crate Quinn.

QUIC corre sobre UDP y ofrece:

  • Multiplexing de streams — múltiples canales lógicos sobre una conexión sin head-of-line blocking
  • Reconexión rápida — 0-RTT handshake para reconexiones
  • TLS 1.3 integrado — cifrado obligatorio en el transporte
  • Mejor comportamiento en redes con pérdida — a diferencia de TCP, la pérdida de un paquete en un stream no bloquea a los demás

El servidor escucha en el puerto 14004 por defecto.


Estructura de mensajes

Los mensajes se serializan con bincode sobre los streams QUIC. Hay dos tipos principales de stream:

Mensajes confiables (streams QUIC)

Para eventos importantes que deben llegar en orden: inventario, chat, comandos, login, cambios de estado del juego. Usan streams QUIC garantizados.

// Mensajes del cliente al servidor (simplificado)
enum ClientMsg {
Register { username, password },
ChatMsg { msg },
ControllerInputs { inputs },
SwapEquipment { slots },
DropItem { slot },
// ...
}

// Mensajes del servidor al cliente
enum ServerMsg {
StateAnswer { answer },
Notification { notification },
ChatMsg { msg },
CreateEntity { entity, components },
DeleteEntity { entity },
UpdateComponent { entity, component },
// ...
}

Updates de posición (datagrams QUIC)

Para updates frecuentes de posición y orientación de entidades, que pueden perderse sin problema si llega uno más nuevo. Usan datagrams QUIC (no confiables) para menor latencia.


Sincronización de entidades

El servidor no envía el estado de todas las entidades del mundo a cada cliente. Solo envía las entidades dentro del rango de presencia del jugador (radio de chunks alrededor de su posición).

Protocolo de sincronización:

  1. Una entidad entra al rango → servidor envía CreateEntity con todos sus componentes
  2. Un componente cambia → servidor envía UpdateComponent con el diff
  3. La entidad sale del rango → servidor envía DeleteEntity

Los componentes sincronizados son solo los que el cliente necesita para renderizar y simular localmente: Pos, Vel, Ori, Body, Health, CharacterState, etc. Componentes internos del servidor (IA, inventario completo de NPCs) no se sincronizan.


Login y autenticación

En un servidor local (desarrollo), cualquier combinación usuario/contraseña funciona — el servidor crea la cuenta si no existe.

En producción, el servidor puede configurarse para usar autenticación externa. El flujo es:

Cliente → Register { username, password }
Servidor → StateAnswer::Accepted / Rejected
Cliente → conectado, recibe el estado inicial del mundo

Implementación

Si necesitás conectarte al servidor desde fuera del cliente oficial (herramientas de debug, bots de test), la crate client del workspace expone la API de conexión:

use client::Client;

let client = Client::new(
ConnectionArgs::Tcp { hostname: "localhost", port: 14004 },
None, // runtime tokio compartido
).await?;

La crate common_net define todos los tipos de mensajes compartidos entre cliente y servidor.