Создаю с 0 олдскульную ММОРПГ в духе Lineage 2
7 постов
Здравствуйте, друзья.
Недавно я проводил стресс-тест сервера мойе браузерной MMORPG: запустил 5000 синтетических ботов, порадовался результату. Казалось бы, победа, можно открывать шампанское.
Но затем пришло время суровой реальности. Код прошел через глубокий аудит серверной безопасности. В отчёте всплыло аж 45 уязвимостей.
Причём не абстрактных «теоретических брешей», а вполне конкретных, наглых дыр: от выдачи любого легендарного меча в один клик через F12 до падения всего онлайна из-за одного битого запроса на иконку зелья.
Собрал самые некоторые баги, которые пришлось фиксить ИИ.
В игре есть стандартная механика: открыл сумку, кликнул по броне - персонаж её надел. Элементарно? Вроде да.
Пока мы не полезли в серверный код. Выяснилось прекрасное: сервер вообще не проверял, есть ли этот предмет у тебя в инвентаре.
Клиент просто отправлял пакет вида equip(itemID: 100500), а сервер добродушно отвечал: «О, классный клинок, надевай!». В итоге любой пытливый игрок мог открыть консоль браузера, написать две строчки с ID светящегося меча судного дня — и сервер послушно натягивал его на голого персонажа 1-го уровня. Никаких проверок на наличие, уровень или класс. Инвентарь Шрёдингера, где любая вещь уже твоя, стоит только попросить.
Для обычного бега у нас стояла строгая античит-проверка: сервер непрерывно считал скорость и не давал разгоняться быстрее положенных 12 м/с. Любая попытка спидхака тут же каралась откатом персонажа назад. Сквозь горы и стены пробежать было невозможно.
Но читерская смекалка всегда найдет обходной путь. Лазейка нашлась в пакете остановки.
Когда игрок отпускает клавишу движения, клиент рапортует серверу: «Всё, я побегал и остановился вот в этих координатах: X, Y, Z». И сервер вместо сверки времени и пройденной дистанции просто брал эти координаты и перезаписывал позицию игрока в базе!
В итоге спидхак работал так: нажимаем шаг вперед, тут же жмем «Стоп» и отправляем координаты противоположного конца карты. Сервер пожимает плечами: «Ну ладно, стоишь так стоишь». Персонаж мгновенно материализовался сквозь любые скалы, стены и непроходимые реки.
Чтобы быстро отдавать ассеты (текстуры, иконки, звуки), у нас крутился встроенный статик-сервер на Node.js.
Если в строке запроса к картинке подсунуть скрытый нулевой байт (%00, излюбленный инструмент пентестеров), низкоуровневая системная функция чтения файлов падает с фатальной ошибкой ядра.
А так как именно этот кусок не был завернут в аварийный перехват (try/catch), один кривой запрос ронял весь Node.js-процесс. Буквально: один школьник с модифицированным браузером мог кликнуть на сломанную ссылку - и весь сервер с сотнями игроков моментально падал наглухо.
Один из самых смешных багов. В формуле расчета шанса блока щитом разработчик банально забыл объявить одну переменную (кап шанса блока в 70%).
Почему этого не заметили на стресс-тестах? Потому что 5000 синтетических ботов самозабвенно месили друг друга голыми кулаками без экипировки. Нагрузочный тест прошел идеально, графики зеленые, разработчики довольны.
Но стоило живому тестеру надеть щит и принять первый удар от моба или другого игрока - движок спотыкался о необъявленную переменную, падал в необработанный эксепшн и… моментально обрывал сокет с атакующим!
Получилась идеальная тактическая защита: враг бьет тебя мечом по щиту - и его тут же выбрасывает с сервера в главное меню. Лучшая защита - это не броня, а бан атакующего.
Для удобства локальной разработки клиент умел брать IP сервера прямо из URL-параметров (например, game.com/?server=127.0.0.1).
Беда была в том, что клиент не просто подключался к этому серверу без валидации домена, но и намертво сохранял его в localStorage браузера.
Сценарий атаки был элементарным: злоумышленник кидает в чат ссылку на игру со своим сервером-прокладкой. Жертва кликает, клиент сохраняет фишинговый адрес, и с этого момента даже при обычном входе на официальный сайт игра молча отправляла все сессионные токены, пароли и данные аккаунта прямо на сервер хакера.
Когда мы взялись закрывать эти дыры, вскрылся главный босс - монолитный серверный файл на 10 000 строк кода.
В нем перемешалось всё: боевая система, торговля, координаты, чат, база данных и инвентарь. Пытаешься починить надевание штанов - внезапно перестает работать покупка банок маны у торговца, потому что они делили один и тот же глобальный массив без нормальной изоляции.
Что было сделано:
Распил монолита на модули: Логику растащили по изолированным файлам. Трейд отдельно, банк/склад отдельно, боевой движок отдельно, инвентарь отдельно.
Тотальная паранойя в инвентаре (Zero Trust): Клиент больше вообще не оперирует ID предметов. Он может лишь попросить: «Сервер, надень то, что лежит у меня в слоте №3». Сервер сам лезет в БД, проверяет, есть ли там предмет, подходит ли уровень, совпадает ли класс персонажа. Сгенерировать вещь из воздуха больше нельзя технически.
Честный серверный стоп: Из пакета остановки координаты вырезали под корень. Теперь при получении команды «Стоп» сервер сам считает, где должен находиться игрок, учитывая направление вектора, пройденное время, скорость и геодату высот карты. Телепорты закрыты.
Вшитый белый список адресов: Чтение серверов из ссылок выпилили. Список игровых миров жестко зашит в клиенте, а localStorage принудительно санируется от любых левых хвостов.
Сейчас фундамент безопасности и сетевой логики приведен в порядок (но это не точно). Архитектура стала модульной, дыры заколочены досками, а мы наконец-то можем вернуться к приятным вещам: настройке мобов, балансировке лута и расширению бесшовного мира.
Следить за разработкой, подкидывать идеи для мобов и участвовать в обсуждениях можно в нашем уютном Telegram-канале: 👉 t.me/ostrov_stali
Там же когда-нибудь )) будем раздавать ключи на пре-альфа тест (для тех, кто хочет лично прийти и попробовать сломать всё то, что мы только что починили). Первые смельчаки, которые выдержат бунт серверных кофемашин, получат памятный внутриигровой титул Первопроходцев.
Когда я начал настраивать экономику первой фазы игры (до 20 уровня), я понял: оставлять мир на одних вояках и ученых - верный путь к тому, чтобы игра превратилась в унылый коридорный гринд. Кто-то должен потрошить автоматоны на шестерни, крафтить уникальные шмотки и продавать соски на площади.
Так в игре появился третий базовый класс - Мастеровой (Craftsman).
В моем техногенном средневековье нет сказочных рас. Поэтому вместо низкорослых бородачей класс Мастерового - это люди труда: суровые заводские слесари, механики и кузнецы.
Вот как выглядит стартовая 3D-модель женского персонажа Мастерового:
Рендер стартовой модели Мастерового в рабочей спецовке (Т-поза)
Никаких сверкающих шелков и вычурных мантий: защитный рабочий комбинезон (worker_overalls), прочные ремни под инструмент, карманы под детали и готовность к тяжелой смене у раскаленного котла.
Вместо изящных рапир или магических посохов в руках у Мастерового двухпудовая кувалда:
Боевая стойка с тяжелым молотом
Если автоматон не разбирается ключом по резьбе - в ход идет рабочий молот. В бою этот персонаж держит удар не хуже оператора.
В мире игры назревал социальный тупик. Общество раскололось на два упертых лагеря:
Гарнизон Операторов относился к технике как к расходнику: круши автоматоны ломами, выжимай из машин ресурс и выбрасывай на помойку.
Академия Инженеров заперлась в кабинетах над перфокартами, запрещая простым работягам даже прикасаться к священным чертежам Создателей.
Два практика в городе не выдержали этой дури:
Оружейник Векс ушел из гарнизона, когда офицеры приказали пустить в утиль сотню ремонтопригодных тяжелых молотов только из-за забитых паровых клапанов.
Бронница Дора хлопнула дверью Академии, когда ученый совет запретил ей вставать к наковальне и испытывать армор на прочность в полевых условиях.
Векс и Дора выкупили заброшенный литейный цех в южном квартале Деревни Круна и открыли Вольную Гильдию Ремесленников («Свободный Цех»).
Портрет мастера Вольной Гильдии
Гном в классической Lineage 2 всегда был невероятно живучим танком. Чтобы спокойно ковыряться разводным ключом во внутренностях взбесившейся паровой турели, пока она бьет тебя током, нужно адское здоровье.
Я перенес каноничные характеристики корейского гнома:
CON (Выносливость) - 45: Абсолютный максимум в игре. Огромный запас HP, самый быстрый реген стоя/сидя и врожденный резист к оглушению (стану).
STR (Сила) - 39: Тяжелые сокрушительные удары молотами, топорами и ключами (чуть уступает чистому Оператору с его 40 STR).
DEX (Ловкость) - 29: Неторопливый, размеренный шаг. Мастеровой ни за кем не гонится - враги подходят сами.
Грузоподъемность +80% к весу: 100 ячеек инвентаря вместо стандартных 80 и колоссальный запас переносимой тяжести.
Обычный инж или стрелок забивает карманы лутом за 15 минут фарма и вынужден бежать в город из-за перевеса. Мастеровой может часами бродить по дальним свалкам, таская на себе сотни слитков чугуна, связки медных трубок и ящики патронов.
Главное, ради чего качают ремесленников в олдскульных играх - это эксклюзивный доступ к ресурсам.
В моей игре обычный моб после смерти оставляет горсть монет и изредка - дешевый предмет. Но если в группе есть Мастеровой, включается механика потрошения:
«Оценка узлов» (аналог Spoil): перед ударом Мастеровой активирует сканер уязвимостей. Из сочленений автоматона вырывается синий пар - цель помечена.
«Демонтаж» (аналог Sweep): когда машина повержена, Мастеровой подходит к автоматону со специнструментом.
Пока обычные бойцы видят перед собой бесполезную груду металлолома, Мастеровой срезает с неё каленые пружины, поршневые кольца, легированные пластины и чертежи деталей. Без этих компонентов в игре невозможно собрать ни одно оружие серьезнее ржавой трубы.
На 20 уровне Мастеровой проходит сертификацию Вольной Гильдии и выбирает одну из двух дорог:
Утилизатор (Scrapper / Спойлер):Полевой добытчик. Получает усиленные скиллы поиска скрытых деталей, умеет вскрывать защитные кожухи элитных мобов, орудует топорами и кинжалами, накладывая маслотечение и сбивая механизмам гусеницы. Это незаменимый компаньон для любой группы, идущей в дальние локации.
Котловой Кузнец (Boiler Blacksmith / Крафтер):Инженер тяжелого машиностроения. Только он имеет право работать с книгой рецептов Вольной Гильдии, создавать оружие победы, отливать заготовки и крафтить соски высшего качества. А в бою он призывает парового механического голема-помощника, который принимает удары на себя.
Я решил подойти к этому вопросу радикально:
Тотальная зачистка NPC-торговцев. Я удалил из продажи у Оружейника Векса и Бронницы Доры все вещи D-ранга. Никаких «Мечей Революции», «Тяжелых Молотов Рока» и «Чешуйчатых сетов» на полках больше нет. В магазине продается только стартовый No-Grade хлам.
D-ранг — это эндгейм первой фазы. Хочешь топ-броню? Добывай чертежи, нанимай Утилизатора для спойла ресурсов, неси всё Кузнецу и запускай крафт.
Хардкорный шанс крафта 60% (High-Stakes). Ровно как в старой Линейке: крафт не гарантирован на 100%. Ты можешь неделю собирать ресурсы на лучший меч на сервере, нажать кнопку крафта… и с вероятностью 40% получить горсть бесполезного пепла. Это жестоко, но именно это делает каждый скрафченный синий меч легендарным событием для всего сервера.
Общественный Паровой Горн и оффлайн-мануфактура. На площади Деревни Круна стоит гигантская печь. Кузнец может выставить книгу рецептов, назначить цену за работу, активировать режим мануфактуры, закрыть браузер и лечь спать. Другие игроки подходят к его персонажу, платят за аренду навыка и куют себе патроны и броню.
Ракурсы и варианты внешнего вида персонажа
С вводом Мастерового игра наконец-то перестала быть просто боевой ареной и превратилась в живую экосистему. Как громко это сказано, когда самой игры еще нет ))) :
Воинам нужны соски, чтобы пробивать толстых мобов.
Кузнецам нужны деньги воинов, чтобы скупать ресурсы.
Спойлеры нужны всем, потому что без редких пружин и клапанов экономика встает намертво.
Теперь на площади у Горна будет шумно, в чате будут искать спойлеров на ночной фарм карьеров, а за удачный крафт Меча Революции на сервере искренне радуются всем кланом. Но это не точно )))
У меня появился ТГ канал, милости прошу. Туда я буду выкладывать всякое разное по проекту. А главное - именно среди подписчиков канала я раздам первые доступы на закрытый мегаальфапреальфасупербета «Арена-тест», где можно будет лично опробовать боевку и разбить свою первую кофемашину.
Когда я начинал делать игру, хотелось уйти от двух крайностей современного геймдева. С одной стороны от - бесконечного шаблонного фэнтези с эльфами, орками и светящимися посохами. С другой - от стерильного постапокалипсиса.
Представьте мир, где когда-то победило технологическое будущее.
Здесь жили гениальные инженеры и конструкторы — цивилизация Создателей. Они не знали нужды, обуздали энергию пара и электричества, оплели остров сложнейшими сетями и автоматизировали буквально всё. Заводы работали без участия людей, сервисные дроны обслуживали дома, по проводам гудело высокое напряжение, а умные приборы делали жизнь абсолютно комфортной.
А потом Создатели исчезли, и случилась Катастрофа.
Но мирное время закончилось, когда на востоке острова рванул топливный химзавод. Техногенная авария (местные зовут её «Зелёной порчей») вызвала цепной сбой систем управления: охранные дроны, сервисные машины и бытовые приборы потеряли связь с протоколами, одичали и обрели искажённое самосознание — «Искру». Превратившись в агрессивных механических хищников.
Оставшиеся люди в один миг были отброшены назад. Цивилизация провалилась в настоящее глухое Средневековье. Но не в сказочное, с деревянными телегами и магами, а в жестокое техногенное Средневековье.
Единственный оплот цивилизации на острове — Деревня поющей стали.
В её центре стоит исполинский памятник ушедшей эпохе. Никто уже не помнит, кто именно его собрал, но этот гигантский агрегат, подключенный к глубинным термальным жилам острова, не останавливается ни на секунду. Он дышит раскаленным паром, крутит уцелевшие генераторы и дает ток в мастерские.
Когда котёл и турбины выходят на полную мощность, пар под давлением устремляется по километрам труб, уложенных под мостовыми. Весь металл в округе - каркасы мостов, чугунные балки башенных часов, наковальни - начинает вибрировать, издавая глубокий, гулкий низкочастотный звук. Кажется, будто сам металл поет хором. Так остров получил свое имя.
В городе нет королей, лордов или враждующих каст. Здесь каждый занят тем, что умеет делать руками, а жизнь общества держат две школы.
Операторы - это рыцари нового средневековья. Изначально это были простые монтажники, путейцы и обычные работяги. Когда автоматика взбесилась, именно они взяли в руки тяжелый инструмент и встали живым щитом у городских ворот.
Оператор идет по силовой ветке. Это бойцы гарнизона, люди физической закалки и тяжелого вооружения.
У них самые высокие базовые показатели силы и выносливости (STR 40, CON 43). Они идут в бой в тяжелой броне - от стартовых медных кольчуг до цельнокованых чешуйчатых кирас Регулятора.
Их арсенал жестко привязан к индустриальному происхождению:
Тяжелые рычажные мечи: от стартового Меча Ученика до грозного Меча Революции.
Паровые молоты: массивные двуручные орудия с поршневыми камерами - от Рычажного Молота до Тяжелого Молота Рока, наносящего сокрушительный физический урон.
Чешуйчатые котловые щиты: тяжелые металлические щиты, способные заблокировать прямой таран автоматона.
Пневматические луки и стилеты. Дальний бой операторов - это пружинные и пневматические луки (Рессорный лук, Усиленный Пневмолук с силой удара 71 ед.), стреляющие тяжелыми калеными стрелами, и пробивные Стилеты Сбоя для работы по сочленениям брони.
На 20-м уровне оператор сдает экзамен и выбирает боевую специализацию:
Механик: классический несокрушимый танк. Закрывается ростовым щитом, удерживает агро монстров на себе и прикрывает отряд.
Разрушитель: свирепый боец ближнего боя. Вооружен двуручным паровым молотом, жертвует защитой ради колоссального урона.
Стрелок: аналог канонического Rogue. Дистанционный снайпер с пневматическим луком и мастер скрытных ударов стилетом.
Инженеры - здешние книжники и алхимики. Их школа расположилась на западном склоне. Они сидят над пожелтевшими чертежами, полуистлевшими перфокартами и манометрами, пытаясь расшифровать формулы ушедших гениев.
Если Операторы - это мускулы Деревни, то Инженеры - её мозг. Их школа расположена на западном склоне города.
Инженеры изучают сохранившиеся чертежи Создателей, принципы термодинамики, гидравлику и высоковольтные цепи. Их характеристики - это интеллект и ментальная стойкость (INT 41, MEN 39). Они не лезут под гусеницы в лобовую атаку.
Их оружие - технологические передатчики:
Калибраторы Цеха: длинные медные посохи-излучатели, фокусирующие высоковольтные разряды.
Импульсные булавы: компактные проводники для генерации высокотемпературных паровых вспышек и дуговых замыканий на дистанции.
На 20-м уровне Инженер разделяется на две ключевые роли:
Конструктор: боевой техногений. Бьет по цепям автоматонов цепными электрическими разрядами, перегрузками напряжения и контурными взрывами пара. Мощный дамагер.
Наладчик: полевой инженер поддержки. Восстанавливает гидроприводы и броню союзников, распределяет давление пара в группе и разгоняет сервоприводы бойцов, повышая их скорость атаки и бега. незаменимый класс поддержки.
В гарнизоне Операторов оружие считали расходным материалом. Оружейный мастер Векс годами срывал голос, доказывая командирам: нельзя сваливать в овраг повреждённые щиты и погнутые молоты - новых поставок со складов Создателей уже не будет. Когда после тяжелой вылазки интенданты приказали списать в лом сотню ремонтопригодных приводов, а новобранцев отправили в караул с сырыми железными болванками, Векс сорвался. Он швырнул рабочий фартук в лицо офицерам и ушёл из казармы: воевать сырым железом - это не доблесть, а дебилизм.
В тот же день скандалом закончилось заседание в Школе Инженеров. Дора, талантливейший металлург и практик академии, вдребезги разругалась с учёным советом. Теоретики годами вычерчивали на пергаментах идеальные профили кирас, но панически боялись подойти к настоящему горну и запрещали полевые испытания. Дора знала: прочность брони проверяется не циркулем в тишине кабинета, а ударом тяжелого пресса на наковальне, и реальными полевыми испытаниями. Оставив профессоров спорить над пожелтевшими синьками, она ушла, хлопнув железной дверью.
Той же ночью на рыночной площади у остывшего горна встретились злой ветеран и упрямый конструктор.
И всё встало на свои места.
Векс знал машины как свои пять пальцев: куда во время схватки загнать клин и какой жгут перерезать, чтобы повалить автоматона и аккуратно выдернуть из него уцелевший калёный поршень, реле или редчайший клапан (так родилась механика спойла). А Дора знала, как переплавить этот трофейный лом в легированную сталь, способную держать удары паровых молотов.
Они вдвоём разожгли печь и основали Вольную Гильдию Ремесленников.
Без генеральских приказов и академических споров они дали острову класс Мастеровых - людей, которые соединили живучесть бойца и смекалку инженера. Они научили бойцов не просто крушить роботов, а потрошить их на бесценные компоненты прямо в бою. И именно в их мастерских родилось оружие, которое не раскалывалось от первого удара, и броня, спасавшая жизни на передовой.
Случившуюся катастрофу старики зовут просто - «Искра».
Прошивка машин острова слетела. Платы замкнуло, датчики переклинило, и приборы, созданные ради комфорта человека, обрели собственную искаженную, хищную автономию.
За стенами деревни игрок встречает не сказочных тварей, а одичавшее наследие Создателей:
Кофемашина-Бешеная: рожковая кофеварка на колесах в предгорьях Астарда. В её заросшем накипью бойлере кипит вода. Завидев движение, она с воем заклинившего насоса несётся наперерез, плюет струей кипятка под давлением и добивает упавшего человека тяжелым латунным холдером.
Бешеная Газонокосилка: триммер с заклинившим карбюратором. Ножи вращаются со звуком циркулярной пилы. Программа требует «срезать всё, что выше четырех дюймов». Под этот норматив попадают ноги любого зазевавшегося путника.
Стиралка-Убийца: чугунный барабан весом за сотню килограммов на усиленных рессорах. В режиме отжима на 1400 оборотов, с куском бордюрного камня внутри, она скачет по карьерам, ломая кости ударами открывающегося люка.
Шуруповерты-Жужжала: стаи летающих аккумуляторных дрелей на старой пасеке. Они нападают роем, вкручивая каленые саморезы прямо в стыки брони на скорости две тысячи оборотов в минуту.
Мечта о Большой земле и тайна южного Порта
С 1 по 20 уровень игра ведет вас понятной, классической дорогой взросления.
Вы начинаете безымянным подмастерьем. Вы учитесь выживать среди одичавших станков, осваиваете ремесло, исследуете свалки и наконец сдаете тяжелейший квалификационный экзамен на настоящую профессию.
Для каждого жителя острова 20-й уровень — это не просто рубеж мастерства. Это надежда.
Все новобранцы живут одной заветной мечтой. На юге острова шумит морской Порт. Там стоят краны, пахнет солью и смолой, и качаются на волнах большие океанские корабли. И в голове одна лишь мысль - выбраться с этого обезумевшего острова и навсегда уплыть на Большую землю.
С этим чувством игрок и отправляется на юг...
Но то, что он увидит за портовыми воротами, переворачивает игру с ног на голову.
Но об этом - уже в самой игре.
Если вам близок дух олдскульных MMO и такой приземленный, необычный сеттинг — заглядывайте в наш свежий Telegram-канал:
Туда я буду выкладывать всякое разное по проекту. А главное - именно среди подписчиков канала я раздам первые доступы на закрытый мегаальфапреальфасупербета «Арена-тест», где можно будет лично опробовать боевку и разбить свою первую кофемашину.
Немного живых скринов )







Всем привет! С момента прошлого поста прошло прилично времени. Я не забросил проект, руки не опустил и всё ещё продолжаю пилить-пиить-пилить :).
Сразу дисклеймер: 5000 онлайна — это синтетические боты. Не живые игроки. Но это честные боты, которые подключаются по реальным WebSocket-сокетам, перемещаются по геодате 25 зон острова, собирают лут, применяют скиллы, пишут в чат и нагружают Spatial Grid сервера точно так же, как живые люди.
Городская площадь Деревни поющей стали: толпа ботов у фонтана и NPC, живой чат и стабильные 50 FPS при 5001 онлайне
Cамый главный вопрос - сколько людей сервер потянет до того, как сгорит процессор). На 100–300 игроках всё летает. На 1000 - начинаются первые проблемы. Но хотелось проверить планку в 3000-5000 одновременных подключений (CCU) на скромном сервере (2 vCPU, 2 ГБ RAM на базе Яндекс Облака).
Я попросил ИИ написать генератор нагрузки на Node.js и запустил его со своего домашнего компьютера прямо в боевой сервер.
План был простой: заспавнить 3000 сокетов батчами по 20 штук и посмотреть на тик ядра.
Где-то на отметке в 2200–2400 ботов дома внезапно наглухо лёг интернет. Браузер перестал открывать страницы, а в консоли посыпались тайм-ауты сокетов.
Сначала я подумал, что лёг VPS. Захожу с мобильного интернета - сервер живой, тик 0.5 мс, всё спокойно. А домашний роутер просто завис.
Причина оказалась банальной: Обычные домашние роутеры и Wi-Fi-коробочки от провайдеров не рассчитаны на то, что с одной машины в квартире откроется 3000 одновременных TCP/WebSocket соединений. На роутере кончилась таблица NAT-трансляций (SNAT порты), память забилась, и он просто ушел в глубокий нокдаун.
Стало ясно: стресс-тестировать тысячи сокетов с домашнего ПК больше нельзя.
Чтобы не вешать домашнюю сеть и не арендовать отдельную пачку серверов только ради тестов, пришла идея: использовать раннеры GitHub Actions Matrix.
Восточный полигон: боты распределены по полям, на счетчике 5001 онлайн, рендер 7.6 мс, сеть 0.8 мс
Но тут возникла вторая засада. Если запустить один раннер Ubuntu в GitHub Actions на 5000 сокетов, он упирается в лимит Azure NAT Gateway - для одной виртуальной машины выделяется всего около 2048 эфемерных портов на внешний IP. Всё, что выше, начинает отваливаться с ETIMEDOUT и ECONNRESET.
Решение - матричный запуск:
Раннер #1 (Runner 1): 1700 ботов (смещение ID от 0 до 1699, часть ботов в городе, часть в полях);
Раннер #2 (Runner 2): 1700 ботов (смещение ID от 1700 до 3399);
Раннер #3 (Runner 3): 1600 ботов (смещение ID от 3400 до 4999);
Четвертый агрегатор: собирает метрики, перцентили RTT и формирует итоговый отчет.
Каждый раннер запускается на изолированной виртуальной машине со своим независимым публичным IP-адресом. Проблема с исчерпанием портов исчезла.
Казалось бы, всё готово. Жму кнопку запуска в GitHub Actions, захожу в игру и вижу: Онлайн: 49.
Запрашивали 5000, а зашло 49. В консолях раннеров - сотни ошибок: Connect error: Unexpected server response: 429
Полез в логи разбираться:
Раннер 1 подключил ровно 16 ботов.
Раннер 2 подключил ровно 16 ботов.
Раннер 3 подключил ровно 16 ботов.
$16 \times 3 = 48$ ботов + я в клиенте = ровно 49.
Оказалось, сработала защита от DoS-атак! В сетевом транспорте стоял лимит: не более 16 активных сокетов с одного внешнего IP-адреса. Для реальных игроков это имело смысл (хотя даже для CGNAT мобильных операторов 16 маловато), но для нагрузочных раннеров это стало железобетонной стеной. Сервер честно отвечал: HTTP 429 Too Many Requests.
Мы с ИИ добавили авторизованный заголовок x-stress-key, подняли базовый порог для игроков до 64, передеплоили ядро на VPS и перезапустили тест.
После фикса все три раннера залетели на сервер на полной скорости.
Холмы Астарда: боты фармят автоматонов и бешеных кофемашин
Вот реальные цифры из консоли и игрового оверлея:
Запрошено ботов: 5000
Успешно подключено: 5000 из 5000 (100%)
Ошибок рукопожатия: 0
Онлайн в игре: 5001 CCU (5000 ботов + 1 GM персонаж)
FPS клиента в браузере: 50–59 FPS (WebGL Three.js)
Время тика игрового сервера: 1.0 мс (EMA: 0.3 мс при норме тика в 100 мс / 10 Hz)
Event Loop Lag Node.js: 11.88 мс (полное отсутствие блокировок главного потока)
Обработано входящих пакетов: свыше 4.7 млн пакетов за тестовую сессию
Трафик: благодаря дельта-сжатию AOI сервер отфильтровал 61.5% лишних обновлений координат
uWebSockets.js (C++ Zero-GC транспорт): забыли про стандартный ws из npm для 5000 сокетов на 2GB RAM. Нативный C++ транспорт держит соединения без давления на сборщик мусора V8.
C++ SIMD (AVX2) для расчёта дистанций и коллизий: модуль project_steam_native.node считает AOI и радиусы агро мобов векторными инструкциями процессора за микросекунды.
Worker Threads для сохранения в базу: вся работа с диском и состоянием игроков вынесена на отдельный поток vCPU, поэтому запись профилей никак не задерживает тик игры.
Проект жив, пилится дальше. Впереди ещё огромная гора работы.
Спасибо всем, кто следит и поддерживает! На бумаге все выглядит шикарно, как будет в реальной жизни? Время покажет))
Документация
Все знают, что делать MMORPG в одиночку — верный способ сойти с ума, и прослыть дибилом. Это честный девлог о том, как мой сервер чуть не захлебнулся от JSON, почему толпа на спавне сломала сетку мира и как ужать расчеты в 17 байт на игрока.
Проблема №1: Клиент — это враг
В обычной игре клиенту можно доверить базовые вещи: например, расчет анимации или координаты. В браузере доверия нет вообще. Ноль. Любой игрок может открыть DevTools (F12), написать в консоли player.speed = 99999 или отправить поддельный пакет «я убил босса». Поэтому сервер спроектирован по принципу Zero-Trust (нулевое доверие): Клиент не знает своих координат. Он отправляет только намерение: «Я зажал клавишу W, хочу бежать вперед». Сервер сам считает скорость, проверяет геодату рельефа, смотрит, нет ли перед носом стены, делает шаг и подтверждает точку. Урон, заточка, выпадение дропа, радиус каста клиент вообще не имеет к этому доступа. И всё это сервер должен рассчитывать 10 раз в секунду для каждого моба и игрока.
Проблема №2: Смерть от мусора и 42 мегабайта в секунду
Первый сетевой протокол ИИ написал на старом добром JSON. Сервер слал клиенту строчки с ID, координатами X/Y и жизнями. Красиво и удобно. Ровно до первого стресс-теста на 2 000 игроков и 1 500 мобов.Когда каждый видит вокруг хотя бы 30–40 соседей, сервер генерирует под 50 000 обновлений в секунду.И тут начался кошмар:1. Трафик: 42.5 МБ в секунду! Нужен гигабитный канал ради сотни тестеров.2. Мусор в памяти: Создание сотен тысяч временных JSON-строк мгновенно забивало оперативку. Сборщик мусора Node.js врубал экстренную очистку (Stop-the-World).3. Весь мир замирал на 200–300 мс каждые пару секунд. Мобы телепортировались, удары не проходили.Спасение: диета до 17 байтМы выбросили JSON из симуляции навсегда. Вместо текста упаковали всю информацию об игроке или мобе в 17 сырых бинарных байт:— 2 байта на ID;— по 2 байта на координаты X, Y, Z (квантование до сантиметра);— 1 байт на угол поворота;— 2 байта на процент жизней;— 2 байта на маску состояний (бежит, атакует, кастует, мертв).Итог: сетевой трафик рухнул с 42.5 МБ/с до 2.2 МБ/с при "5 000" онлайна. Падение в 19 раз! Сервер вздохнул свободно, а мир перестал заикаться.
Проблема №3: Проклятие Городской Площади
Чтобы сервер не проверял расстояние между каждым игроком и каждым мобом, мир нарезается на сетку — Spatial Grid с клетками по 108 метров. Ты опрашиваешь только свою клетку и 8 соседних. В поле это работает идеально.А потом зашли живые игроки. Они толпой пришли на центральную площадь: торговать, орать в чат и собирать группы.В одну несчастную клетку набилось 400 человек. И вся оптимизация мгновенно выродилась обратно в жуткие тормоза: сервер снова начал считать расстояние каждого ко всем.Как решили:Написали адаптивное дробление. Как только в клетке собирается больше 100 сущностей, она прямо на лету делится на 16 мелких зон по 27 метров. Игроки в толпе получают обновления только о тех, кто рядом, а дальние персонажи обновляются реже. Сервер даже не заметил нагрузки.
Проблема №4: «Проклятие одной монетки»
Еще одна ошибка новичка — писать данные в базу при каждом чихе игрока. Моб умер -> упала шестеренка -> игрок подобрал -> пишем в базу. Когда тысячи игроков месят мобов, диск захлебывается за секунды. Игра виснет.Решение: игра живет только в оперативной памяти.База данных вообще не знает, что вы бегаете по острову. Сервер просто ставит флаг «персонаж изменен». Раз в 15 секунд фоновый процесс пачкой сбрасывает всех изменившихся игроков в базу за один быстрый запрос. Никаких лагов и никаких откатов инвентаря при рестарте.
Миллиард токенов и 320 часов марафона: сколько стоит разработка с ИИ
Пишу этот проект один вместе с Gemini 3.8 Flash )) Когда люди слышат «делает игру с нейросетью», они представляют кнопку «сделай шедевр». В реальности это был 29-дневный марафон по 11–12 часов в день. Сложнейшие сессии отладки длились по 40 шагов, где на каждом шаге в модель скармливался контекст по 150 000 – 180 000 токенов: монолит сервера на 5 300 строк, таблицы боевой математики, геодата и сырые дампы сетевых пакетов.
442 миллиона токенов только на серверную часть
суммарно весь проект (Three.js клиент и сервер) сожрал 1.265 МИЛЛИАРДА токенов!
Сколько это стоило в деньгах?
Продолжаю делиться разработкой своей браузерной RPG. Проект обрастает контентом, и сейчас я перешел к созданию визуала и к наполнению мира.
Как собирается мир
Террейн острова: Сначала нейросеть сгенерировала общие очертания карты. Затем я загрузил её в Blender и вручную вылепил весь террейн с помощью скульптинга, формируя холмы, равнины и береговую зону под игровой процесс.



Смесь 3D и 2D: Окружение в игре трехмерное, а вот монстры и NPC — плоские 2D-спрайты. Это экономит огромное количество полигонов и снимает избыточную нагрузку на рендер в браузере, создавая ретро-стилистику.



Генерация контента: Весь контент делаю в нейросетях, tripo, gemine, nano banana и т.д
Что уже работает:
Разметка карты: Остров поделен на сектора с мобами с 1 по 20 уровень.
Инвентарь: Оружие можно брать в руки, менять и убирать обратно. Слоты под броню готовы, параллельно чинятся мелкие интерфейсные баги.
3. Скиллы: Внедрил первый паровой удар, сейчас довожу до ума применение умений по противникам.



Базы данных: Полностью готовы таблицы по характеристикам экипировки, параметрам мобов, луту и вероятностям дропа.
Боссы: Поставил первого крупного босса зоны и заложил механику скрытых энкаунтеров с редкими плюшками.
Пишите в комментариях, как вам идея мобов и визуальное решение с 2D-спрайтами в трехмерном мире.
Сразу признаюсь честно: я не программист. От слова «совсем».
Но у меня есть дикое желание сделать игру своей мечты, и сегодня для этого больше не нужно 5 лет учиться в институте на кодера.
Вся эта разработка — это тандем: я + нейросеть.
Я выступаю в роли геймдизайнера, архитектора и самого въедливого заказчика:
Я "придумываю" механики, баланс, сеттинг и говорю ИИ: «Хочу, чтобы мир был не фэнтези, а тяжелый стимпанк».
Нейросеть генерирует код на Three.js и WebGL.
Я запускаю это в браузере, вижу, лентяйский слоп, ругаюсь, заставляю ИИ переделывать, оптимизировать и чинить ошибки.
Повторяем этот цикл сотни раз.
И самое удивительное — это работает!
Я с детства обожал старые хардкорные игры вроде Lineage 2. Чтобы поиграть в современные игры надо скачать 80–100 гигабайт клиента, поставить три лаунчера и ждать загрузки.
Я захотел сделать игру, которая запускается в один клик прямо во вкладке браузера. Кликнул ссылку — и через 3 секунды ты уже в 3D-мире, но это не точно ))).
Что мы уже сделали:
Сеттинг «Остров поющей стали»: мир людей, которые живут в мире вышедшей из под контроля техники, злобных утюгов и пчёломиксеров.
Классы: Силовая линия (Операторы, Механики-танки с котловыми щитами, Стрелки с пневматикой) и Контурная линия (Инженеры класс поддержки и суровые ДД).
Оптимизация: Стремимся к плюс минус стабильным фпс)
Я не строю иллюзий и не обещаю, что сделаю «убийцу Вова», если вообще хоть что-то сделаю )). Это чистый эксперимент: насколько далеко сегодня обычный человек без знания кода может зайти в создании ММО, используя только нейросети?
Такой промо сайт ): 👉 fatmax123.github.io/ostrov-stali
А на YouTube можно посмотреть, как постепенно собирается мир: 📺 Канал: @AindieGus
Интересно ваше честное мнение: Реально ли так дойти до полноценного релиза?
