Уже четыре месяца я в одиночку пишу SpaceDigger — космическую онлайн-стратегию, которая целиком живёт во вкладке браузера. Ничего не нужно скачивать: регистрируешься, открываешь вкладку и летишь. Сервер авторитетный, прогресс хранится на нём, производство идёт, пока тебя нет.
Игра сейчас в закрытом бета-тесте. В этой статье — не маркетинг, а то, что интересно разработчикам: как устроен сервер, почему по сети не передаются координаты, как пустые системы не тратят ни такта процессора, и про баг, из-за которого живой мир молча замолчал на трое суток без единой ошибки в логах.
Стек, если сразу: TypeScript-монорепозиторий (shared/server/client), сервер на Node + Colyseus (авторитетный, WebSocket), клиент на PixiJS (2D WebGL), PostgreSQL + Drizzle. Один процесс на целевой масштаб ~300 игроков.
Авторитетный сервер и ноль координат по сети
Первое решение, которое задало всю архитектуру: сервер — единственный источник правды, клиент только рисует состояние и шлёт команды. Банально для MMO, но из него растёт красивое следствие.
Положение всех небесных тел — это чистая функция от времени: bodyPosition(body, ms). Орбиты детерминированы, и сервер с клиентом вычисляют их одинаково по одной и той же формуле. Поэтому координаты планет, лун и станций вообще не синхронизируются по сети — клиент сам знает, где они, в любой момент. По сокету летят только те сущности, которые действительно непредсказуемы: корабли, снаряды, дроны. Это заметно разгружает и канал, и сериализацию состояния.
Тот же приём — у вселенной в целом: она не хранится в базе, а генерируется из сида детерминированно. В БД лежит только изменяемый слой — профили игроков, колонии, станции, склады. Шаблоны (характеристики кораблей, модулей, рецепты, баланс) живут в коде; в базе — только то, чем игрок реально владеет.
«Ленивые» комнаты: пустые системы не тикают
Каждая звёздная система — отдельная комната Colyseus. И существует она только пока внутри есть хоть один игрок: создаётся при входе, уничтожается, когда последний вышел. Пустая система не тратит ни такта процессора.
Возникает очевидный вопрос: а как же шахты, фабрики и колонии работают, пока никого нет? Ответ — никак, и это не нужно. Когда игрок прилетает в систему, сервер смотрит accruedAt — метку времени последнего расчёта — и за один шаг досчитывает всё, что колония успела бы произвести за прошедшие часы (общий детерминированный colonySim.ts проматывает время). Для игрока результат неотличим от честного тика, а сервер не крутит сотни миров вхолостую.
Исключения — ровно те процессы, которые обязаны идти глобально: менеджер червоточин, экономика NPC-фракций, вторжения пиратов. Но это по одному таймеру на процесс, которые проходят курсором по нескольким системам за проход и догоняют каждую по прошедшему времени — а не тик на каждую систему. Правило железное: ничего «всегда-живого на систему».
Баг, из-за которого мир замолчал на трое суток
Теперь обещанная история — она про то, как «всё зелёное», а живой мир стоит.
В галактике пять фракций. У них свои склады, дефициты и излишки, они воюют за системы и возят товар конвоями. Ключевое решение: фракции торгуют через тот же рынок, что и игроки. У фракции кончилась сталь — игрок не видит таблички «квест: привези сталь», он видит, что сталь в этой системе подорожала. Конвой фракции можно сопроводить, а можно перехватить.
Сначала торговые маршруты строились только между системами одной фракции. И однажды мир замолчал: трое суток никто никуда не ехал. Ничего не упало, в логах чисто, формулы покрыты тестами. Просто — тишина.
Причина оказалась обидно простой. Фракция производит свой товар в каждой системе, где она присутствует. Значит, своего дефицита у неё не бывает по определению — и маршрутов «из системы с избытком в систему с нехваткой» не возникало вовсе. Лечилось вторым проходом построения маршрутов: производитель экспортирует товар туда, где его не хватает другой фракции. После этого конвои поехали.
Главный урок проекта вырос именно отсюда: формулы были верные и даже проверены тестами — но тесты проверяли математику, а не то, что механику вообще кто-то вызывает. Четыре из шести системных эффектов когда-то были написаны, покрыты смоук-тестами на формулы и не подключены ни к чему. Тесты гоняли арифметику, а в игре эффекта не было.
Теперь у проекта около 55 автоматических проверок, которые гоняются перед сборкой, и одна из них (audit:effects) делает ровно то, чего не хватило: берёт каждую экспортируемую функцию-эффект и проверяет, что её реально вызывают из серверного кода. Написал правило, но нигде не дёрнул — красный бар. Дёшево, а ловит самый неприятный класс багов — молчаливые, без единой ошибки.
А что это за игра, если коротко
Чтобы было ясно, вокруг чего вся эта техника. Ты пилот, у тебя один корабль и пустой трюм. Дальше — сам: копаешь руду в поясах и газ в туманностях, возишь товар и ловишь разницу цен, строишь колонию и станцию, исследуешь технологии, собираешь корабли и модули, лезешь в войны фракций и охоту на пиратов — или не лезешь. Мирный путь равноценен боевому: можно всю игру торговать и строить, ни разу не нажав на гашетку.
Экономика завязана на риск. У каждой системы класс опасности D1–D7, видимый на карте ещё до прыжка. D1–D2 — безопасные зоны: PvP запрещён, за попытку — розыск и полиция. D3 и глубже — законная охота, и при гибели из трюма выпадает часть груза (в D3–D4 около трети, глубже — половина). Зато чем глубже, тем дороже руда: за час добычи в D7 выходит примерно втрое больше, чем в D1. Вопрос любого рейса один: хватит ли щита довезти полный трюм обратно.
Масштаб на сегодня
-
45 кораблей и 431 рецепт крафта
-
541 ресурс и материал
-
270 технологий в дереве исследований
-
72 вида пиратов
-
100 уровней пилота
Один нюанс сериализации, который может пригодиться тем, кто тоже на Colyseus: colyseus/schema допускает максимум 63 поля на один Schema-класс. У нас Player упёрся в этот предел, поэтому новые данные группируются во вложенные схемы (navigation, combat, cargo), а не вешаются плоскими полями. В предыдущей мажорной версии схемы переполнение ломало кодирование молча — на клиенте сыпались каскадные ошибки, будто пропали данные; в текущей — падает сразу на импорте, что честнее.
Статус и как попробовать
Идёт закрытый бета-тест. Заявки одобряю вручную, партиями, так что доступ появляется не мгновенно. Плюс в том, что галактика пока почти пустая, и лучшие жилы достаются тем, кто пришёл раньше. Это тест, а не релиз: баги встречаются, баланс правится по ходу, в кабинете есть кнопка «Сообщить о баге», и исправления часто выходят в тот же день. Всё бесплатно, без подписки.
Подать заявку и посмотреть — spacedigger.ru.
Дальше планирую писать сюда про отдельные подсистемы: динамическую экономику (цены на планетах двигаются от чужих сделок), вторжения пиратов с логовами и «шрамами» на карте, взлом руин Предтеч как мини-игру. Что из этого расписать подробнее с кодом — скажите в комментариях. Отвечаю сам.
ссылка на оригинал статьи https://habr.com/ru/articles/1100584/