Netcode

Combate validado por el servidor, movimiento predicho por el cliente

Dos problemas que la mayoría de servidores tratan como uno solo. La validación del daño le corresponde al servidor. El movimiento no.

Compromisos

Tres cosas que este servidor no va a negociar

Validación de impactos en el servidor

El daño lo valida MatchServer contra un búfer de snapshots de rebobinado, así que un impacto se juzga contra donde tu objetivo estaba de verdad en el momento en que disparaste, no contra lo que afirme un peer. La aplicación directa de daño entre clientes se está eliminando.

EstadoEn migración

El K-Style no se toca

Butterfly, half-step, slash shot, reload shot, wall canceling, dash canceling. El movimiento y las cancelaciones de animación siguen siendo predichos por el cliente, con los frame delays originales intactos. Cero latencia de input añadida y ningún «arreglo» a los tiempos de los que depende la técnica.

EstadoCerrado

Un bloque de stats por clase

Todas las armas de una clase llevan el mismo daño, cadencia, cargador y recarga. Dentro de una clase solo cambia el modelo, así que un cosmético nunca puede ser una ventaja. Sin stats por donación, sin armas custom, sin reskins con números encima, sin ítems de stats detrás de un pago. Nada de lo que puedas comprar cambia una pelea.

EstadoCerrado

El modelo original

En la versión original, el match server lleva el lobby, la lista de salas, el stage y el marcador. No decide quién muere. En cuanto empieza una partida, los clientes hablan entre ellos directamente por UDP, y entre los mensajes que intercambian están los que aplican daño.

Ese diseño era razonable en 2006 y produce dos fallos que cualquier jugador de GunZ reconoce:

  • La conexión decide la pelea. Dos clientes no se ponen de acuerdo sobre dónde está el otro. Quien tenga la visión del mundo más retrasada pierde impactos que vio conectar y recibe impactos desde ángulos que nunca ocurrieron en su pantalla.
  • El daño es una afirmación, no un hecho. Si un peer dice que te dio, la ruta original se lo cree casi siempre. Editar esa afirmación es la trampa más común de GunZ y nunca ha tenido una respuesta real sobre un modelo de daño peer-to-peer.

Adónde se mueve la autoridad

La aplicación directa de daño entre clientes se está eliminando. En vez de que un peer te diga que has recibido daño, un cliente reporta un disparo: el arma, el origen, la dirección y su propia marca de tiempo. El servidor decide si ese disparo le dio a algo.

La validación se hace contra el registro del mundo que tiene el propio servidor, no contra lo que el tirador afirme sobre el objetivo. Un disparo rechazado no hace nada. Un disparo aceptado produce daño que aplica el servidor y del que se informa a los dos clientes.

La distinción que importa: el cliente sigue decidiendo cuándo disparas. Ya no decide si aciertas.

Compensación de lag

Autoridad del servidor sin compensación de lag es peor que no tener autoridad, porque cada jugador tendría que adelantar sus disparos según su propio ping. Por eso el servidor guarda un historial corto de dónde ha estado cada jugador: un búfer de snapshots de rebobinado.

Cuando llega un disparo, el servidor reconstruye el mundo tal como lo vio el tirador. Rebobina según el tiempo de ida y vuelta medido del propio tirador — el mundo que el servidor ya le había entregado es todo lo que salió de aquí hace al menos un viaje de ida y vuelta — y comprueba el disparo contra las posiciones que había en su pantalla en ese instante. Las posiciones entre dos snapshots guardados se interpolan en vez de saltar, así que la reconstrucción es suave y no escalonada.

Ese rebobinado es medido, nunca declarado. El disparo lleva la marca de tiempo del propio cliente y el servidor no la usa para resolver. Un cliente que pueda elegir el momento contra el que se juzga su disparo va a elegir uno que le convenga, así que la marca de tiempo se guarda solo para registrar la diferencia entre lo que el cliente afirmó y lo que el servidor midió. La única palanca que le queda a un cliente modificado es inflar su propio ping retrasando los pongs, y la ventana de rebobinado está acotada: un rebobinado más atrás de lo que guarda el búfer se recorta en vez de extrapolarse.

Rebobinado en el servidor

Tres vistas de un mismo instante: la pantalla del tirador, el presente del servidor, y el servidor rebobinado un viaje de ida y vuelta.

Lo que viste al disparar

Acierta

El presente del servidor, un viaje después

el objetivo se ha movido

Falla

El servidor, rebobinado según tu tiempo medido

rebobinado

Acierta

El disparo se comprueba contra la tercera fila, no contra la segunda. Sin el rebobinado cada jugador tendría que adelantar sus disparos según su propio ping; con él, lo que había en tu pantalla es lo que comprueba el servidor. El rebobinado sale del viaje de ida y vuelta que midió el servidor, nunca de un momento que pidiera el cliente. La flecha de la segunda fila es el objetivo moviéndose durante ese viaje; en la tercera es el servidor devolviéndolo a su sitio.

Lo que no se toca

El movimiento, las cancelaciones de animación y el cambio de arma siguen siendo predichos por el cliente y con autoridad local. No se ha metido nada entre tu input y la animación, y no se va a meter.

Esto no es un descuido que se vaya a ordenar más adelante. El K-Style existe por retardos concretos de cancelación de animación y por sobrescrituras de estado en el código original. Tratarlos como bugs y arreglarlos es exactamente cómo un servidor de GunZ se convierte en otro juego:

  • Butterfly — slash cancelado en dash, una y otra vez.
  • Half-step — el dash acortado que sale de cancelar la animación de dash antes de tiempo.
  • Slash shot y reload shot — las ventanas de tiempo del cambio de arma.
  • Wall canceling y dash canceling — sobrescrituras de estado al contacto y al input.

Ninguna de estas cosas la valida el servidor, ni se retrasa a la espera de confirmación, ni se reescribe. Los frame delays de los que dependen se tratan como parte de la especificación.

Lo que eso cuesta

Siendo honestos con el intercambio: como el movimiento tiene autoridad en el cliente, un cliente modificado todavía puede moverse de formas que no debería. Eso es un problema de detección, no de simulación, y se responde con comprobaciones de plausibilidad y observación, no quitándole el movimiento al cliente. Entre un servidor que atrapa todos los exploits de movimiento posibles y un servidor donde el butterfly se siente bien, este elige lo segundo.

Trampas

Mover el daño al servidor elimina de raíz una clase entera de trampa. Un cliente que afirma daño ya no lo consigue, porque en el servidor ya no hay nada que lea esa afirmación.

Lo que queda es la asistencia de puntería y la manipulación del movimiento. Eso se maneja con comprobaciones de plausibilidad en el servidor sobre el flujo de disparos y el de movimiento, sumado al hecho de que una población competitiva pequeña se da cuenta. No hay anticheat a nivel de kernel y no lo va a haber.

Estado

Este trabajo está en marcha y esta página describe hacia dónde va. El estado actual, sin adornos:

Validación de disparosImplementada en el servidor para armas cuerpo a cuerpo y a distancia, funcionando en la build en vivo.
Búfer de rebobinadoImplementado. Historial de snapshots con reconstrucción interpolada; el tamaño de la ventana se está ajustando contra latencias reales.
Ruta de daño entre peersTodavía presente en el cliente por compatibilidad durante la migración. En proceso de eliminación.
Registro de rechazosImplementado, opcional. Cada comprobación de impacto puede escribir su resultado y su código de motivo — pared, fuera de alcance, fuera del cono, bloqueado, cadencia — junto al rebobinado con el que se resolvió. Desactivado por defecto porque un disparo de escopeta son doce líneas; se activa por partida para resolver una ronda en disputa.
Autoridad del movimientoCliente. Sin cambios, y no está previsto cambiarla.

Notas de parche