Статус: Ready — РЕШЕНИЕ ЗАФИКСИРОВАНО 2026-06-19


Решение по сетевой архитектуре кооператива и его обоснование. Это главная развилка проекта по неткоду — здесь зафиксирован выбор, что из текущего кода keeper, что запарковано, и какая работа предстоит в Фазе MP.

Связано с 02. Детерминированная симуляция и тик, 6. Мультиплеер, 02. Архитектура реализации — Фаза 1 (Боевое ядро), 07. Техдолг, решения и changelog.


РЕШЕНИЕ

Сетевая модель = host-authoritative + репликация состояния (Вариант A). ⛔ Детерминированный lockstep (Вариант B) — отклонён. Принято 2026-06-19 (Макс + анализ кода). Решающий фактор: игра — кооператив против ИИ (PvE), а текущая симуляция — на float.


1. Простыми словами

Когда двое играют в один бой по сети, есть два способа держать их «в согласии»:

  • Host-authoritative (выбрано). Бой считает только хост. Клиент — «тонкий экран»: получает позиции/HP юнитов по сети и рисует их, а свои действия (пауза, приказ) шлёт хосту. Один источник правды → рассинхрон невозможен в принципе.
  • Lockstep (отклонено). Каждый игрок считает идентичный бой у себя; по сети летят только команды. Чтобы это работало, симуляции обязаны совпадать бит-в-бит на всех машинах.

2. Почему host-authoritative, а не lockstep

Решающий факт: это кооператив (PvE)

В соревновательном PvP нужна защита от читов и сокрытие задержки (каждый кадр критичен) — там lockstep оправдан (так делают RTS: StarCraft, AoE). У нас — кооп против ИИ: читы не угроза (вредишь только себе и друзьям), задержка в 100–200 мс на команду незаметна (юниты ходят сами, игрок изредка ставит юнита/жмёт паузу), игроков мало (до 4), юнитов — десятки.

Убийственный аргумент против lockstep: float

Lockstep требует, чтобы математика боя давала идентичный результат на всех машинах. Но вся симуляция — на float/Mathf/Vector2 (sqrt, normalize и т.п. разнятся между x86/ARM, Mono/IL2CPP, настройками FPU). Чтобы сделать lockstep честно, пришлось бы переписать всю боевую математику на fixed-point — самый большой возможный взрыв скоупа, ради бандвитча, который при 4 игроках нам не нужен.

Host-authoritative идёт по шерсти стека

NGO (Netcode for GameObjects) сделан для host-authoritative: NetworkVariable, NetworkTransform, RPC, дельта-сжатие. Lockstep на NGO — это плыть против течения.

Бандвитч — не проблема

~40 юнитов × (позиция ~4–8 б + HP ~2 б + флаги) ≈ 0.5 КБ/снапшот; при 15–20 Гц ≈ 8–13 КБ/с на клиента; на хост с 3 клиентами ≈ 25–40 КБ/с. Для брода — пыль. NGO ещё и дельта-сжимает простаивающих юнитов.

Сравнение

КритерийHost-authoritative (✅)Lockstep (⛔)
Детерминизм между машинамине нуженобязателен бит-в-бит
Текущий float-симподходит как естьнужен рерайт на fixed-point
Сочетание с NGOродноепротив течения
Бандвитчреплицируем состояние (для 4 игроков — пыль)только команды (минимум)
Анти-чит / PvPне нужен (кооп)сильная сторона
Рассинхронневозможен by designосновной кошмар отладки
Реплеичерез запись состояниябесплатно (команды+сид)

3. Что это значит для существующего кода

Детерминизм не выбрасывается — он остаётся ценным для одиночных реплеев, отладки и «тот же сид → тот же бой». Просто перестаёт быть жёстким межмашинным требованием. XorShiftRng, тиковая модель, ComputeChecksum — всё остаётся.

Кусок кодаСудьбаПочему
CombatSimulation, все системы, эффекты, RNG, тикKeeperhost считает этим же кодом
ICombatCommand + очередь командKeeperклиент шлёт интенты командами, хост применяет
ComputeChecksumKeeper (для SP)тесты детерминизма, отладка реплеев
NetworkCommandRelay (концепт intent→host)Keeper, переработать обвязкупуть «клиент→хост» нужен; «все применяют на одном тике» — уберётся
SimSyncProbe (сравнение checksum пиров)ЗапаркованNet/_Parked/lockstep-инструмент, при host-auth второй симуляции нет
FacepunchTransportBootstrapKeeperтранспорт модель-независим

Парковка SimSyncProbe: файл перемещён в Assets/_Project/Scripts/Net/_Parked/ и помечен баннером в коде. Не удалён намеренно — пригодится для SP-реплеев и как референс, если модель когда-нибудь пересмотрят. История в git.


4. Главная будущая таска: сетевой кооп (Фаза MP)

🎯 ГЛАВНАЯ ТАСКА НЕТКОДА. Реализуется в Фазе MP, не сейчас. В одиночке сетевой слой спит (NGO без NetworkManager неактивен), поэтому он ничего не ломает и не требует немедленной работы.

Состав работы host-authoritative реализации:

  1. Репликация состояния юнитов хост→клиент через NGO (позиция, HP, щит, ключевые флаги; квантование + дельта-сжатие; ~15–20 Гц + интерполяция на клиенте).
  2. Клиент рендерит из реплик-состояния, а не из локального сима. Сейчас CombatPresenter читает живой CombatSimulation; нужен шов, где на клиенте источник — реплицированное состояние. Это главный архитектурный кусок.
  3. Гейт тика: только хост (authority) гоняет CombatLoopService.Tick. Сейчас гейта IsServer нет — добавить при включении сети.
  4. Релей команд для полного набора ввода (не только Pause/Resume): постановка юнита, способности, предметы. Переработать NetworkCommandRelay: оставить client → ServerRpc(intent) → host enqueue, убрать lockstep-broadcast «все на одном тике».
  5. Синхронизация паузы в кооп-планировании (любой ставит — у всех пауза; приказы во время паузы копятся и применяются на резюме).
  6. Late-join / реконнект: полный снапшот состояния присоединившемуся (NGO частично делает).
  7. Сид боя от хоста (сейчас GenerateBattleSeed локальный — для одиночки ок, для сети сид должен прийти в команде старта).

Связь с дизайн-доком 6. Мультиплеер и общим планом 7. Флоу забега и сессия (реконнект, репликация RunState).


5. Когда бы lockstep был правильным (честный контр-аргумент)

Чтобы решение было осознанным, а не «на веру»: lockstep выиграл бы, если бы это был соревновательный автобатлер с сотнями/тысячами юнитов и реплеями/анти-читом как фичами ядра. Тогда стоимость fixed-point-рерайта окупилась бы минимальным трафиком и бесплатными реплеями. Это не наш случай — поэтому host-authoritative.


Итог документа

  • Решение: host-authoritative (Вариант A). Lockstep отклонён.
  • Причины: кооп-PvE (мягкие требования) + float-сим (несовместим с lockstep без рерайта) + NGO заточен под host-auth + бандвитч не проблема.
  • Детерминизм остаётся для SP (реплеи/тесты/дебаг), перестаёт быть межмашинным требованием.
  • Keeper: сим, команды, checksum-для-SP, транспорт. Запарковано: SimSyncProbe. Переработать обвязку: NetworkCommandRelay.
  • Главная будущая таска — реализация host-authoritative репликации в Фазе MP (см. §4).