Концепт архитектуры сетевой части виртуального мира
Когда в мире одновременно находятся тысячи людей и ИИ-агентов, сеть быстро становится самым капризным местом системы. Чтобы не утонуть в этом, я разложил её на пять слоёв. Каждый слой решает одну задачу и почти ничего не знает про остальные.

Правило простое: зависимости идут сверху вниз. Верхний слой знает только контракт соседа под собой, нижний о верхних не знает ничего. При приёме данные движутся против направления зависимостей.
| Уровень | Сущность | Задача |
|---|---|---|
| 5 | Model | Что клиент вправе знать и что ему разрешено менять |
| 4 | Managers | Каждый отвечает за свой тип данных и кодирует свои сообщения |
| 3 | Dispatcher | Что уйдёт первым, что подождёт, что устарело |
| 2 | Delivery | Доставка, подтверждения, права и допуск |
| 1 | Transport | Единый контракт «передай надёжно» или «передай самое свежее» и технология, которая реально отправляет байты |
Level 5. World Model
Это модель мира. Здесь решается, что именно должен знать каждый клиент, а что ему знать не обязательно или нельзя. Любая команда игрока приходит сюда как просьба, а не как приказ: World Model держит власть над куском мира и принимает окончательное решение по каждой команде — права, роль, частота, допустимые значения. Последнее слово всегда за сервером, и именно поэтому мир трудно обмануть.
Зона интереса клиента считается в два этапа: сначала грубый отбор по пространственному индексу, потом точная проверка прав и ранжирование в пределах бюджета. Полного перебора «каждый клиент × каждая сущность» на каждом тике нет. Симуляция, физика и ECS живут за World Model и о сетевых деталях не знают.
Level 4. Managers
Менеджеры отвечают каждый за свой тип данных. Они не знают друг о друге и не обращаются к уровням 1 и 2. Данные о мире они читают через контракт, доступный только на чтение, — его реализует World Model. Каждый владеет схемой своих сообщений и кодирует данные по общим Wire Schemas, единым для всей системы.
- Event Manager передаёт события в обе стороны: сработал триггер, кто-то что-то сделал. Надёжные события не теряются: при потере они отправляются повторно с тем же идентификатором, а на приёме отсеиваются дубли.
- Input Manager занимается управлением. Клиент присылает не координаты, а то, что игрок нажал, а позицию считает сервер. В пакет идут последние K неподтверждённых команд — избыточность вместо повтора устаревшего. Так отклик остаётся быстрым, а подделать своё положение нельзя.
- Dynamic Object Manager отвечает за динамические объекты: подвижные сущности вокруг игрока и их «призраков» — копии на его экране. Он отправляет только то, что изменилось, и в первую очередь то, что важнее.
- Static Object Manager работает с неподвижным миром: зданиями, ландшафтом. Такие данные меняются редко, клиент подгружает их кусками по мере движения, и у каждого куска есть номер ревизии.
- Observation Manager обслуживает тех, кто не играет, а наблюдает: ИИ-агентов и инструменты. Им нужны не отдельные обновления, а компактные сводки и пачки данных.
- Durable Operation Manager отвечает за важные операции вроде покупки или передачи предмета. Они выполняются ровно один раз, даже если связь оборвалась посреди запроса. Результат всегда можно запросить по номеру операции.
- Reference Data Manager занимается справочниками: описаниями предметов, параметрами и прочим. Данных много, они версионируются и меняются нечасто. Поэтому их загрузка идёт отдельно от игрового канала, как обычная загрузка файлов.
Level 3. Data Flow
Диспетчер потока данных. Менеджеры отдают ему сообщения в служебной оболочке, а он решает, что уйдёт первым, что подождёт, а что вообще устарело. В оболочке только служебные поля: класс доставки, приоритет, срок годности, ключ замены, метка «о чём сообщение», маршрут. Содержимое диспетчер не знает и не разбирает.
- Очереди, приоритеты и срок годности — по классам данных. Данные разных менеджеров собираются в общие пакеты, поэтому клиента не заваливает мелочью.
- Для данных, у которых важна только новейшая версия, новое сообщение вытесняет неотправленное старое.
- Все очереди ограничены, и поведение при переполнении определено заранее: что-то заменяется, что-то останавливает источник, что-то отбрасывается первым.
- Кредитная обратная связь. Диспетчер заранее выдаёт менеджерам лимит в байтах на ближайший тик и актуальный максимальный размер сообщения. Менеджер не кодирует то, что всё равно не уйдёт, а важное не отбрасывается вслепую. Остановка источника — крайняя мера, когда лимит уже исчерпан.
- Сообщение всегда влезает в один пакет. Есть гарантированный минимум, текущий предел пути для конкретного соединения и предел для менеджеров за вычетом накладных расходов. Фрагментации на уровне IP архитектура не допускает: потеря одного фрагмента убивает всё сообщение целиком.
Level 2. Delivery
Здесь два компонента.
- Connection Manager нумерует пакеты, следит, дошли ли они, и сообщает вверх, что произошло с отправленным. Подтверждение пакета означает только, что он дошёл до слоя доставки получателя, но не что команда применена. Заодно здесь проверяется допуск: синтаксис, размеры, квоты, частота — но окончательные права проверяет мир.
- Session Manager помнит, кто подключён и какие у него права. Сессия живёт дольше соединения: если связь оборвалась, клиент может вернуться, в том числе на другой узел, и продолжить, а не начинать всё заново. При возобновлении поколение сессии увеличивается, и команды старого поколения отвергаются.
Level 1. Transport
Этот уровень состоит из двух частей: единого контракта и подключаемых технологий, которые его реализуют. Верхние слои видят только контракт.
Packet Transmitter
Единая точка для отправки и приёма данных. Верхние слои просят у неё «передай надёжно» или «передай самое свежее» и не думают о сокетах, адресах и пакетах. Контракт выражен в терминах сообщений и каналов: ненадёжное сообщение, надёжный канал с приоритетом, события соединения, состояние канала — полоса, задержка, уровень перегрузки, предельный размер сообщения. О мире и игре этот компонент ничего не знает. Обратных вызовов наверх нет: входящие события копятся в очереди, а вышестоящий слой вычитывает их пачками в своём ритме.
Transport Backends
Конкретная технология, которая реально отправляет байты по сети. Какую именно использовать, определяет конфигурация, а не код верхних слоёв. Это единственное место, где зависимость осознанно перевёрнута: бэкенды сами реализуют контракт Packet Transmitter, и именно поэтому транспорт заменяем.
- QUIC (сейчас). Современный протокол поверх UDP. Он умеет шифрование, независимые потоки и ненадёжные сообщения, поэтому потерянный пакет не тормозит всё остальное.
- DPDK (в планах). Приём и отправка пакетов прямо с сетевой карты, минуя ядро операционной системы. Это путь к максимальной скорости, но шифрование и защиту придётся делать самим.
- Mock (для тестов). Имитация сети: потери, задержки, перестановки пакетов. На ней можно проверить, как система ведёт себя при плохой связи, не выходя из тестового окружения.
У бэкенда два набора настроек: для клиентов и для служебных соединений между узлами. Различаются они только параметрами, не кодом: наружу нужна проверка сервера и смена адреса клиента без переподключения, внутри — взаимная проверка сертификатов при каждом соединении. Свои возможности бэкенд сообщает наверх, поэтому запасной транспорт без надёжной доставки подключается без правок в остальной системе.
Где это живёт: Gateway и Simulator
Уровни 1–3 работают на Gateway — узле внешних соединений, который состоянием мира не владеет. Уровни 4–5 работают на Simulator — процессе авторитетной симуляции. Между ними протянут внутренний канал: тот же QUIC, тот же контракт, только профиль внутренний. Для менеджеров это выглядит так, будто диспетчер стоит рядом, поэтому кредиты, отчёты о доставке и ранжирование данных продолжают работать.

Зачем так:
- Всплеск подключений или атака бьют по Gateway, а не по миру;
- Сеть и симуляция масштабируются независимо;
- Клиент держит одно соединение, даже если видит несколько частей мира;
- Когда кусок мира переходит к другому серверу, клиент не переподключается — узел просто меняет маршрут.
Кто владеет каким куском мира, решает Directory — отдельный отказоустойчивый сервис. Он выдаёт право владения на время и следит за сменой владельца, а в обработке тиков не участвует: команда клиента его ответа не ждёт.
Что это даёт
- Заменяемость. Транспорт можно поменять, не переписывая менеджеров и логику мира.
- Тестируемость. Каждую границу между слоями можно подменить: время в тестах продвигается вручную, а сеть имитируется с потерями, задержками и перестановками. Плохая связь воспроизводится по сценарию.
- Один клиент для всех. Человек и ИИ-агент подключаются одинаково, по одному коду. Различаются только права и лимиты, и они собраны в одном месте — профиле сессии.
- Честный учёт канала. Вместо «отправь и посмотрим, что получится» — заранее известный лимит, поэтому работа не пропадает впустую и важное не тонет вместе с неважным.
- Сервер всегда прав. Клиент просит, сервер решает.
- Физика не ждёт сеть. Симуляция идёт в своём ритме, а сеть читает готовое состояние в своём. На всём пути состояния мира от физики до экрана нет чисел с плавающей запятой — только фиксированная точность, поэтому одинаковый ввод даёт одинаковый результат на любой платформе.