Клиент-предсказание: как спрятать пинг
Проблема: авторитет стоит пинга
Наивная схема «сервер решает всё» безопасна, но медленна. Нажал W → пакет летит на сервер → сервер двигает → состояние летит обратно → только теперь персонаж шагнул. Задержка движения = полный round-trip:
При RTT 120 мс каждый шаг, прыжок и поворот отстаёт на 120 мс — управление «по почте». Нельзя просто отдать власть клиенту («я на X, у меня 999 HP») — это открывает дверь читам. Нужно и авторитет сервера, и мгновенный отклик. Разрешение конфликта — предсказание.
Предсказание + сверка (reconciliation)
Клиент делает две вещи разом на каждый ввод: (1) применяет его локально немедленно — персонаж двигается в этом же кадре; (2) отправляет ввод на сервер с монотонным порядковым номером. Сервер обрабатывает ввод, двигает авторитетно и в ответном снапшоте сообщает «последний обработанный ввод = N» (ack) и итоговое состояние.
Клиент хранит историю своих вводов. Получив ack на N, он: выкидывает из истории всё ≤ N; берёт серверное состояние как истину; и заново переигрывает поверх него ещё не подтверждённые вводы N+1, N+2, … теми же формулами. Если прогноз совпал с сервером (так в 99% кадров при честной сети) — позиция не дёрнется. Если разошёлся (клиент думал, что бежит, а сервер знал про стену) — позиция мягко съезжает к правде.
Числовой пример. RTT 120 мс, клиент на вводе #50. Без предсказания персонаж замер бы до t=120 мс. С предсказанием он шагнул на t=0; в t=120 мс приходит ack на #47 с серверной позицией. Клиент ставит позицию = серверная (на момент #47) и мгновенно переигрывает #48, #49, #50 → возвращается ровно туда, где уже рисует. Игрок ничего не заметил. Если же между #47 и #50 сервер увидел стену, переигровка упрётся в неё → лёгкий «доводчик» к правде вместо телепорта.
Жёсткое требование — детерминизм: клиент и сервер должны гонять идентичный код движения. Разойдись формулы на копейку — прогноз будет постоянно мазать, и персонажа будет «колбасить» поправками каждый кадр. Поэтому физику игрока пишут один раз и компилируют в обе стороны.
Чужие игроки: интерполяция в прошлом
Свой ввод предсказать можно — он у тебя в руках. Чужой нельзя: ты не знаешь, куда враг нажмёт. Поэтому остальных не предсказывают, а интерполируют между двумя последними полученными снапшотами — и намеренно рисуют чуть в прошлом (буфер интерполяции Δ, обычно 50–100 мс), чтобы всегда иметь две точки для плавного lerp:
где t = t_now − Δ, а S(t0), S(t1) — снапшоты до и после. Альтернатива — экстраполяция (dead reckoning): продолжить движение врага по последней скорости. Дёшево, но если он свернул — будет рывок-«резинка» при коррекции. Большинство быстрых шутеров выбирают интерполяцию (платим фиксированной задержкой за плавность), а экстраполяцию держат для редких пропусков пакетов.
Lag compensation: сервер отматывает время
Теперь конфликт: ты стреляешь по врагу, которого видишь в прошлом (интерполяция + пинг). На сервере «сейчас» враг уже сдвинулся. Если сервер проверит попадание по своему «сейчас» — ты будешь мазать по тому, во что целился. Решение Яна Бернье (Valve, Counter-Strike; статья «Latency Compensating Methods», 2001): сервер откатывает мир назад к тому моменту, который видел стрелок, и там проверяет луч:
Сервер хранит кольцевой буфер недавних позиций всех игроков; для выстрела клиента он восстанавливает хитбоксы на t_rewind (его латентность + его буфер интерполяции) и трассирует там. Цена компромисса — знаменитое «я уже зашёл за угол, но всё равно умер»: с точки зрения стрелка ты ещё был на виду, и сервер встал на сторону стрелка (favor-the-shooter). Честно сделать обоим хорошо при ненулевом пинге математически нельзя — выбирают, кому отдать правду.
Хардкор · инженерия: тикрейт, снапшоты, UDP и потеря пакетовможно пропустить
- UDP, не TCP. TCP гарантирует порядок и доставку → при потере пакета всё встаёт в очереди (head-of-line blocking), а в шутере устаревший пакет уже не нужен — нужен свежий. Поэтому шлют UDP и сами решают, что требует надёжности (события: «дверь открыта»), а что можно терять (позиции — придёт следующий снапшот).
- Тикрейт. Сервер симулирует фиксированными тиками (Quake — десятки/с; CS:GO — 64/128; Overwatch поднимал до ~63). Выше тик = точнее симуляция и lag-comp, но дороже CPU и трафик. Клиент рендерит на своём fps, между тиками — интерполяция/предсказание.
- Снапшоты и дельта-сжатие. Сервер шлёт состояние мира N раз/с; чтобы влезть в канал — дельты от последнего подтверждённого клиентом снапшота (только то, что изменилось) + приоритет ближних/видимых сущностей (PVS из урока Quake режет и сетевой объём, не только отрисовку).
- Избыточность ввода. Над UDP клиент в каждом пакете досылает последние несколько вводов, а не только новый — потеря одного пакета не создаёт дыру в истории, сервер возьмёт дубль из следующего.
- Джиттер-буфер. Пакеты приходят неравномерно; небольшой буфер сглаживает разброс времён прибытия ценой ещё чуть-чуть задержки. Тот же буфер интерполяции
Δ.
Хардкор · хостинг: авторитет, детерминизм и где это считатьможно пропустить
- Модель авторитета. Dedicated-сервер как единственный источник истины — стандарт для соревновательных игр (античит, нет «host advantage»). P2P/listen-server дешевле (нет инфраструктуры), но хост-игрок имеет нулевой пинг и его сложнее защитить от читов.
- Детерминизм через платформы. Если прогноз/линкстеп опирается на полное совпадение симуляции, всплывает беда float: один и тот же код на разных CPU/компиляторах может дать чуть разный результат (порядок операций, FMA, x87 vs SSE). RTS с лок-степом фиксируют математику (fixed-point или строго оговорённый float) — иначе клиенты разъезжаются (desync).
- Консистентность vs задержка. Это та же дилемма, что в распределённых системах: сильная консистентность (ждать авторитет) безопасна и медленна; оптимистичное локальное действие быстро, но требует сверки и отката. Геймдев выбрал оптимизм + reconciliation задолго до того, как это стало мейнстримом в вебе.
- Региональные серверы. Физику пинга не обмануть:
RTT ≥ 2·расстояние/c. Поэтому матчмейкинг привязывает к ближайшему дата-центру — снизить базовый RTT, на котором работает всё предсказание.
Системы / фронтенд: оптимистичные апдейты UI (React/Redux: показать результат до ответа сервера, откатить при ошибке) — буквально prediction+reconciliation. Optimistic concurrency control в БД (версии/CAS вместо локов). Eventual consistency и CRDT: локальная правка сразу, схождение позже.
ML / AI: speculative decoding — точная копия приёма: дешёвая draft-модель «предсказывает» несколько токенов вперёд, большая модель их верифицирует одним проходом и откатывает с первого расхождения, переигрывая дальше — это reconciliation по номеру ввода, слово в слово. Off-policy RL: распределённые акторы (IMPALA) действуют устаревшей политикой, а корректирующий importance-sampling (V-trace) правит рассинхрон обучения — тот же «оптимистично действуй, потом поправь по авторитету».
Железо: branch prediction + speculative execution в CPU: процессор угадывает ветку и считает наперёд, при промахе сбрасывает конвейер (rollback) — предсказание с откатом на кремнии.
Принцип: не плати латентность ожидания истины — действуй по детерминированной догадке, держи историю, сверяйся с авторитетом и переигрывай только разошедшееся.
net_graph 3 — пинг, потери, тикрейт, интерполяция. Покрути cl_interp / cl_interp_ratio (буфер интерполяции чужих) и cl_predict 0/1 — с выключенным предсказанием почувствуешь движение «по пингу». Включи sv_showhitboxes 1 и (на своём сервере) визуализацию lag-comp: увидишь, как сервер ставит хитбоксы туда, где цель была в прошлом. В QuakeWorld-сорс-порте — cl_predict и net-графики аналогично.net_fakelag, net_fakeloss в Source) и лови «резину» (rubber-banding) при упоре в стену, варп чужих игроков при потерях, «смерть из-за угла» (lag-comp favor-the-shooter), рассинхрон при дёрганой сети. Чеклист сетевых артефактов.🕹 В какие игры поиграть — и что заметить
От первой реализации в QuakeWorld к индустриальному стандарту и контрастам (лок-степ, роллбэк). По каждому: что внутри и во что сыграть / что ввести в консоль.
Первая широкая реализация client-side prediction (Кармак, .plan от 16 авг 1996: «позволяю клиенту угадывать результат движения, пока не придёт авторитетный ответ сервера»). Сделала Quake играбельным на дайалапе и породила соревновательный онлайн-шутер.
🎮 Сыграй: в сорс-порте (ezQuake/FTE QuakeWorld) поставь высокий пинг и потыкай cl_predict 0 vs 1 — с нулём движение запаздывает на пинг, с единицей мгновенно. Ты щёлкаешь ровно тот тумблер, что Кармак добавил в 96-м.
Бернье в Valve добавил поверх предсказания lag compensation: сервер отматывает хитбоксы в прошлое стрелка. Отсюда фирменное «зашёл за угол — и всё равно убит».
🎮 Сыграй: в CS введи net_graph 3, посмотри пинг/тик/интерп. Поймай момент, когда умер уже за укрытием — это не баг, это favor-the-shooter: на экране врага ты ещё был на виду, и сервер отмотал к его кадру.
Подробно разобран на GDC 2017 (Тим Форд / Tim Ford): высокий тикрейт, предсказание, интерполяция и осознанный выбор «верим стрелку». Хороший современный референс той же архитектуры.
🎮 Посмотри: в настройках сети включи оверлей (ping, tickrate). Сравни ощущение хитскана у героя с мгновенным выстрелом (lag-comp решает всё) и снаряда (летит — частично предсказывается на лету). Та же netcode-модель, разные виды оружия.
Другая ветвь предсказания: rollback netcode (GGPO; Killer Instinct, Guilty Gear Strive). Предсказывают ввод оппонента (обычно «жмёт то же, что в прошлом кадре»), симулируют дальше, а при приходе реального ввода — откат и пересимуляция кадров. Никакого авторитетного сервера — детерминированный P2P-лок-степ с откатом.
🎮 Сыграй: в GGST/любом rollback-файтинге онлайн поймай микро-«тряску» персонажа при скачке пинга — это видимый откат+пересимуляция. Глубокий разбор rollback и лок-степа — в Модуле 4.
Совсем другой выбор: при сотнях юнитов слать их позиции нереально. Шлют только команды, а симуляцию гоняют детерминированно и синхронно на всех машинах (lockstep). Латентность прячут не предсказанием, а небольшой задержкой команды (turn delay).
🎮 Сыграй: в старом StarCraft/AoE на плохой сети заметь, что лагует весь мир разом (ждём всех игроков), а не отдельный юнит резиной — это лок-степ, противоположность предсказанию. Почему так — детерминизм и объём (см. хардкор).
Если сервер всё равно авторитет, зачем вообще предсказывать на клиенте?
Почему UDP, а не надёжный TCP — ведь пакеты теряются?
Почему чужих игроков рисуют в прошлом, а не экстраполируют вперёд?
Δ платит фиксированной задержкой (50–100 мс), но движение всегда гладкое и соответствует тому, что реально прислал сервер. Для хитскана эту задержку компенсирует lag-comp на сервере.Что ломается, если клиентская и серверная физика не идентичны?
«Я умер, уже зайдя за угол» — это лаг, баг или дизайн?
Не открывает ли предсказание дверь читерам — клиент же сам себя двигает?
- John Carmack,
.planот 16 августа 1996 — первое описание клиентского предсказания в QuakeWorld. - Yahn Bernier, «Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization» (Valve, GDC 2001) — lag compensation.
- Gabriel Gambetta, «Fast-Paced Multiplayer» — наглядная серия про prediction/reconciliation/interpolation.
- Glenn Fiedler (Gaffer on Games) — «What every programmer needs to know about game networking», UDP-надёжность, снапшоты.
- Valve Developer Wiki, «Source Multiplayer Networking» — тикрейт, интерполяция, lag compensation на практике.
- Модуль 3 (
03-3d-revolution-1993-1999.md), раздел «Client-Side Prediction»; глубже — Модуль 4.