← Модуль 4/Роллбэк-нетокод
EN
Модуль 4 · Онлайн-миры (1997–2005)

Роллбэк-нетокод (GGPO)

Файтингу нужен отклик в единицы кадров — авторитетный сервер столько не даёт. Роллбэк решает иначе: предскажи ввод оппонента, играй сразу, а ошибся — откатись и пересчитай. Свой удар всегда мгновенный; цена — детерминизм, сейв состояния каждый кадр и короткий визуальный «снап».
~18 мин🏠 лаба в уроке-карте
Суть за 30 секунд
В файтинге бюджет задержки — единицы кадров (~6 кадров = 100 мс при 60 fps), и input lag убивает ридинг. Авторитетный клиент-сервер так не умеет (добавляет весь RTT). Роллбэк (модель «ввод на проводе» + детерминизм): оба клиента крутят одинаковую симуляцию; каждый кадр ты применяешь свой ввод сразу и предсказываешь ввод оппонента («жмёт то же, что в прошлом кадре»), сохраняя состояние каждого кадра. Когда настоящий ввод оппонента приходит (с сетевой задержкой) и совпал с прогнозом — ничего не делаем. Не совпал — откат к последнему согласованному кадру, повтор с верным вводом и пересимуляция нескольких кадров за один — игрок видит короткий рывок. Своё управление — никогда не лагает. Эталон — GGPO (Tony Cannon, 2006; open-source MIT с 2019). Цена: строгий детерминизм (fixed-point) и дешёвый сейв-стейт — поэтому роллбэк живёт в файтингах (state ~10–50 КБ), а не в RTS.

Механизм: предсказать, играть, откатиться

Роллбэк — это спекулятивное исполнение детерминированной симуляции. Оба пира знают: одинаковый ввод на одинаковом кадре → побитово одинаковое состояние. На проводе — только биты ввода (см. таксономию). Проблема одна: ввод оппонента физически приходит с задержкой one-way. Делэй-нетокод решает её в лоб — ждёт ввод оппонента перед исполнением кадра (input delay всем, всегда). Роллбэк не ждёт.

Цикл кадра

Каждый кадр F клиент делает:

  1. читает свой ввод и применяет его сразу;
  2. предсказывает ввод оппонента — обычно «повтори последний известный» (в файтинге игрок чаще держит или ничего не жмёт, чем меняет ввод каждый кадр — прогноз верен в большинстве кадров);
  3. продвигает симуляцию на кадр вперёд;
  4. сохраняет состояние кадра F в кольцевой буфер.

Когда по сети приходит настоящий ввод оппонента за прошлый кадр F′:

Окно отката и его цена

Насколько глубоко придётся откатываться — это число кадров «в полёте», пока ввод оппонента летит к тебе. При one-way задержке towd и кадре Δt:

R= ⌈towdΔt⌉ = ⌈50/16.67⌉ =3 кадра

(RTT 100 мс → one-way ≈ 50 мс ≈ 3 кадра при 60 fps.) Значит каждый кадр клиент должен уметь сохранить состояние и при мисспредикте пересчитать до R кадров в пределах одного бюджета кадра:

Cresim ≈ R·Cstep ≤ Δt

При R=3 это «прогнать симуляцию ~4× за кадр» (откат на R кадров + текущий = R+1 шагов) — нормально, если один шаг дёшев. Отсюда два жёстких требования: (1) сейв-стейт каждого кадра должен быть дешёвым (сериализация всего игрового состояния), (2) шаг симуляции — дёшев и детерминирован. В файтинге состояние крошечное (~10–50 КБ: два бойца, снаряды, таймеры) → сохранять и откатывать копейки. В RTS состояние — мегабайты тысяч юнитов → сейв каждого кадра и пересимуляция убьют и память, и CPU, поэтому RTS берут делэй (локстеп), а не роллбэк.

Кадр 103: пришёл настоящий ввод оппонента за кадр 100 кадры: 99 100 101 102 103 ✓ согласован ← предсказано откат к кадру 100 пересчёт: 100 101 102 103 верный ввод · 4 кадра за один Свой ввод весь этот раз шёл без задержки. «Снап» = разница между жёлтым и зелёным.

Делэй + роллбэк: одна ручка

Чистый роллбэк предсказывает всё окно R. Можно добавить немного input delay d кадров (свой ввод применяется не сразу, а через d) — тогда горизонт предсказания сокращается:

Rpred = max(0,R−d)

Меньше горизонт → реже и короче откаты → меньше визуальных «снапов», но добавляется постоянный input lag d кадров всем. Это спектр между «делэй» (d большой, нет откатов, ровный лаг — как в локстепе) и «чистый роллбэк» (d=0, нулевой свой лаг, максимум откатов). Хорошие реализации дают 1–3 кадра делэя как буфер на джиттер. Сравни: SFV ославился «восемью кадрами делэя» — это чистый делэй, без роллбэка, и потому ощущался вязким.

🕹 В какие игры поиграть — и что заметить

Роллбэк лучше всего «слышен» на контрасте с делэй-нетокодом: одна сигнатура — короткий рывок (снап), другая — ровный input lag и фризы. Сыграй по нарастающей и поймай разницу.

Killer Instinct (2013) первый AAA-роллбэк

Первый крупный консольный файтинг с роллбэком (Double Helix → Iron Galaxy, GGPO-стиль, при участии автора GGPO). Доказал, что роллбэк работает на консоли в проде — и задал моду на жанр.

🎮 Сыграй: KI на Xbox/PC онлайн на среднем пинге — заметь, что свои комбо идут без запинки даже когда у оппонента скачет связь; «лагает» не твоё управление, а короткие подёргивания картинки оппонента. Это и есть откат вместо input lag.

Skullgirls GGPO · инди-эталон

Инди-файтинг прямо на GGPO; годами — витрина того, как должен ощущаться роллбэк. Маленькое состояние, чистый детерминизм, отличный онлайн на «плохой» сети.

🎮 Сыграй: Skullgirls онлайн и оффлайн подряд — разницы в отклике почти нет (в этом вся суть). На скачке пинга лови микро-«телепорт» оппонента на 1–3 кадра: это пересимуляция после мисспредикта.

Guilty Gear Strive / Street Fighter 6 / Mortal Kombat 1 современный стандарт

Все трое — роллбэк из коробки (2021–2023). К началу 2020-х комьюнити отказывалось покупать файтинги без роллбэка; давление + open-source GGPO сделали его индустриальным стандартом.

🎮 Сыграй: в GGST/SF6 включи индикатор соединения (полоски/число кадров делэя) и сыграй на разном пинге. Заметь: при росте пинга растёт частота мелких снапов, но свой отклик не вязнет. Сравни с офлайном — почти неотличимо.

Smash Ultimate · ранний SFV контраст: делэй-нетокод

Обратный полюс. Smash Ultimate — делэй-нетокод (Сакураи: «побочки слишком велики», от роллбэка отказались; в 2020-м комьюнити взорвалось хэштегом #FixUltimateOnline). SFV запомнился «8 кадрами делэя». Сигнатура — не снапы, а плавающий input lag и фризы, когда ввод оппонента не успел.

🎮 Сыграй: Smash Ultimate онлайн на неидеальной сети — почувствуй, как само управление становится вязким и дёргается весь матч. Затем GGST — отклик мгновенный, артефакт другой (снап). А для контр-примера: Melee через Slippi (комьюнити-роллбэк поверх игры 2001 года) играет лучше «официального» делэя Ultimate.

Хардкор · инженерия: детерминизм, сейв-стейт и API GGPOможно пропустить

Роллбэк держится на побитовом детерминизме: одинаковый ввод на одинаковом кадре обязан давать побитово одинаковое состояние на обеих машинах — иначе пересимуляция разъедется (desync), и «согласованного» кадра не существует.

Что ломает детерминизм

  • Float. Один и тот же код на разных CPU/компиляторах даёт разный результат (порядок операций, FMA, x87 80-бит vs SSE, fast-math). Почти все роллбэк-движки берут fixed-point (целочисленную арифметику) для всей геймплейной симуляции.
  • Недетерминированный рандом. ГПСЧ с общим сидом и синхронным порядком вызовов; никакого «незаметного» rand() в геймплее.
  • Неинициализированная память / хэш-итерация / указатели в состоянии. Сериализуемое состояние должно быть «плоским» и воспроизводимым.

API в трёх колбэках (форма GGPO)

GGPO абстрагирует сеть и просит у игры ровно три вещи:

  • save_game_state() — сериализовать всё геймплейное состояние в буфер (+ контрольную сумму);
  • load_game_state(buf) — восстановить состояние из буфера (для отката);
  • advance_frame(inputs) — продвинуть симуляцию на один кадр с заданным вводом.

Дальше GGPO сам предсказывает ввод, копит сейвы в кольцевом буфере, при приходе настоящего ввода зовёт load + многократный advance. Главный инструмент отладки — sync test: режим, где движок откатывается каждый кадр и сверяет контрольные суммы; любой источник недетерминизма всплывает сразу, а не «через 3 минуты матча».

Сколько хранить

Кольцевой буфер последних R+запас состояний (обычно 7–8 кадров с запасом на джиттер). Сейв-стейт обязан быть быстрым: в файтинге это memcpy компактной структуры. Если состояние нельзя сделать дешёвым и плоским — роллбэк не ваш инструмент.

Хардкор · теория: почему «повтори последний ввод» — хороший предсказательможно пропустить

Качество роллбэка определяется частотой и глубиной откатов, а они — точностью предсказателя ввода оппонента. Наивная стратегия «оппонент на кадре F сделал то же, что на F−1» работает на удивление хорошо — и вот почему.

Статистика ввода файтинга

Геймплейный ввод сильно автокоррелирован: игрок держит направление, держит/отпускает кнопку, и меняет ввод лишь в редкие кадры (старт удара, смена направления). Если доля «кадров-изменений» равна p, то на горизонте R кадров вероятность хотя бы одного мисспредикта ≈ 1−(1−p)R. При p≈0.1 и R=3 это ~27% кадров с откатом — но каждый откат короткий (≤3 кадра) и почти всегда корректирует мелочь, поэтому визуально терпимо.

Граница худшего случая

Глубина отката ограничена сверху окном R (нельзя откатиться дальше неподтверждённого ввода), значит и стоимость пересимуляции ограничена R·Cstep — алгоритм имеет жёсткий потолок работы за кадр. Чем выше пинг, тем больше R: растёт и стоимость пересчёта, и длина «снапов». Поэтому роллбэк не «чинит» сколь угодно большой пинг — он делает малый пинг неотличимым от офлайна, а большой — играбельным, но с заметными рывками.

Почему откаты сходятся

Поскольку симуляция детерминирована, пересчёт с верным вводом даёт ровно то состояние, к которому позже придёт и оппонент. Откаты не накапливают ошибку: каждый «согласованный» кадр — общая истина обоих пиров, от которой отсчитывается следующее предсказание.

Аналогия
Два пианиста играют дуэт по сети с задержкой. Делэй-нетокод — каждый ждёт, пока услышит ноту партнёра, и только потом играет свою: дуэт ровный, но оба постоянно отстают. Роллбэк — каждый угадывает следующую ноту партнёра («наверное, та же, что сейчас») и играет свою сразу; когда настоящая нота долетает и совпала — отлично; не совпала — быстро «переигрывает» последний такт с правильной нотой. Свою партию ты слышишь без задержки всегда; иногда — короткая «перемотка» партнёра.
Почему это важно
Роллбэк — это доведённая до предела идея «не жди round-trip, действуй спекулятивно и исправляйся». Он определил, какими вообще бывают хорошие сетевые файтинги, и стал индустриальным стандартом не сверху, а под давлением игроков. Понимать его — значит видеть общий приём: когда сетевая задержка в петле управления, а откат дёшев и мисспредикт редок, оптимизм бьёт ожидание. И обратное: где состояние огромно или симуляция недетерминирована, тот же приём становится ядом — это и отделяет «работает в файтинге» от «не взлетит в RTS/MMO».
🔁 За пределами игр — куда это переносится
Роллбэк = оптимистичное спекулятивное исполнение с откатом при мисспредикте. Это один из самых переносимых паттернов в системах.

ML / AI: speculative decoding в инференсе LLM — это роллбэк дословно. Маленькая draft-модель предсказывает следующие k токенов; большая модель проверяет их за один параллельный forward-pass; совпавший префикс принимается, на первом расхождении — «откат» к этой позиции и продолжение от неё. Предскажи дёшево → исполни спекулятивно → проверь → отбрось и пересчитай хвост. Выигрыш тот же, что у роллбэка: прячем латентность за предсказанием, когда оно часто верное и проверка/откат дёшевы.

Процессоры: branch prediction + спекулятивное исполнение конвейера; мисспредикт → pipeline flush (откат спекулятивных стадий) и перезапуск с верной ветки. Ровно цикл «предскажи / исполни / проверь / откатись».

Базы данных: optimistic concurrency control (OCC) и MVCC — транзакция исполняется в предположении «конфликта нет», на коммите валидируется; конфликт → abort + retry (= откат). Выгодно ровно когда конфликты редки.

Фронтенд / UX: optimistic UI — рисуем результат действия сразу (лайк, отправка), сверяемся с сервером, при ошибке откатываем. Тот же визуальный «снап», что у роллбэка.

Принцип: если round-trip — это потолок, а предсказание дёшево и обычно верно, а откат дёшев — действуй сейчас и исправляйся. Окупается тем сильнее, чем реже мисспредикт и чем дешевле откатить.

Связи
основа
Таксономия нетокода — карта трёх моделей; роллбэк = «ввод на проводе» + детерминизм. Этот урок — третья модель вглубь.
основа
Игровой цикл и fixed timestep — детерминированный фикс-шаг симуляции — обязательное условие роллбэка (иначе пересчёт разъедется).
контраст
Клиент-предсказание — тоже «предскажи и поправь», но при авторитетном сервере и состоянии на проводе; здесь — P2P, детерминизм и пересимуляция у себя.
Вопросы пытливого ума
Почему «повтори последний ввод» вообще работает — игрок же постоянно жмёт кнопки?
Потому что геймплейный ввод сильно автокоррелирован: подавляющую часть кадров игрок держит направление/кнопку или ничего не жмёт, а меняет ввод лишь в редкие кадры (старт удара, смена стороны). На коротком горизонте 2–4 кадра «то же, что в прошлом кадре» совпадает в большинстве случаев. А когда не совпадает — окно отката мало, пересчёт дёшев, и снап короткий. Предсказатель не обязан быть умным; он обязан быть дешёвым и часто правым на коротком горизонте.
Если роллбэк так хорош, почему RTS не используют его, а делэй?
Из-за состояния, которое надо сохранять каждый кадр для возможного отката. Файтинг — два бойца, ~10–50 КБ: memcpy и пересчёт нескольких кадров копеечны. RTS — тысячи юнитов, мегабайты состояния: сейв каждого кадра + пересимуляция нескольких кадров на каждый мисспредикт убьют память и CPU. Поэтому RTS терпят input delay (детерминированный локстеп — ничего не сохраняем/не откатываем), а файтинги, где state крошечный и важен каждый кадр, идут в роллбэк ради нулевого своего input lag.
Откат «снапает» картинку — почему это считается лучше ровного input lag?
Потому что в файтинге решает ридинг и реакция: ты смотришь на оппонента и отвечаешь за единицы кадров. Постоянный input lag сдвигает твою петлю «вижу → жму → вижу» и ломает тайминги (привыкаешь к онлайну — мажешь в офлайне). Короткий визуальный снап ввода оппонента петлю не трогает: своё управление мгновенно, ошибается лишь предсказание чужого, и ненадолго. Для жанра, где важен каждый кадр собственного отклика, рывок чужой картинки — меньшее зло, чем вязкое своё управление.
Детерминизм в роллбэке «легче», чем в локстепе RTS — почему, если оба его требуют?
Объём симуляции и цена ошибки. В роллбэке два игрока и крошечное состояние — детерминизм проверяется (sync test: откат каждый кадр + сверка контрольных сумм) и держится легче, а мисспредикт всё равно корректируется откатом. В RTS-локстепе тысячи взаимодействующих юнитов, и любое расхождение в одном из них → desync всей игры без шанса на коррекцию (нет авторитета). Оба используют fixed-point и детерминированный ГПСЧ, но поверхность багов и последствия в RTS на порядок страшнее.
Роллбэк «убирает» пинг — значит, на 250 мс будет идеально?
Нет. Роллбэк делает малый пинг неотличимым от офлайна и средний — играбельным, но он не бесплатен на больших задержках. С ростом пинга растёт окно R: чаще и глубже откаты → длиннее визуальные снапы (оппонент «телепортируется» на больше кадров) и выше стоимость пересимуляции за кадр. Есть жёсткий потолок: пересчёт R кадров обязан уложиться в бюджет кадра. Плюс обычно ставят 1–3 кадра input delay как буфер на джиттер. Поэтому даже идеальный роллбэк на 250 мс ощущается заметно «дёрганее», чем на 30 мс — просто всё ещё лучше, чем делэй на том же пинге.
Что почитать