‹ Все записи

Пять человек в комнате и дверь наружу

Как бронь на 24 места подвешивала тестовый сервер, почему лидер в Городе Инея сам уходил на мороз, пробежка за сундуком в Гуй Му на 300 метров и две петли, которые гоняют 28 пати день и ночь.

Если вы хотите найти все тёмные углы, в которых старая сетевая игра способна преподнести сюрприз, не отправляйте в подземелье одну группу. Отправьте двадцать восемь групп сразу в семь разных подземелий в три часа ночи и оставьте их одних на пять часов.

Именно это мы сегодня делали на тестовом сервере. В открытом мире сейчас идёт закрытая бета, но за кулисами мы добиваемся того, чтобы вся таблица подземелий стабильно проходилась группами жителей: от ранних заходов по знакам до Города Инея, трёх глав Сумеречного храма, Гуй Му, Долины Луны и двух подземелий 99 уровня — Чистилища и Земного ада.

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

Вот что нам сегодня принесли логи.

Двенадцать комнат с пятью пальто на стульях

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

У нас есть ротируемые фарм-сквады: они меняют персонажей между сменами, чтобы состав не застаивался. В карточке каждого такого сквада было прописано по 24 аккаунта-кандидата. Но в игре в пати есть жёсткое ограничение движка: максимум шесть персонажей. Только шестеро одновременно заходят на сервер и шагают в портал.

Однако наш диспетчер процессов об этом не догадывался. Когда запускалась задача фарма, он бронировал на рабочем процессе места под все 24 зарегистрированных аккаунта. Воркер исправно выделял сетевые буферы, таблицы отслеживания и память под 24 клиента, хотя 18 из них даже не логинились. Это было похоже на человека, который заходит в битком набитый трактир, раскладывает 24 пальто по свободным стульям и заявляет всем с порога, что мест нет.

В итоге воркеры задыхались от нехватки оперативной памяти, обслуживая в среднем всего по пять активных ботов на процесс. По всей тестовой системе эти призрачные стулья съедали 5784 МБ памяти вхолостую.

Мы добавили параметр --places 6 в диспетчер сценариев. Теперь процесс резервирует ровно те шесть мест, которые реально идут в данж. Это сразу позволило плотно упаковать по 4–6 полноценных групп на один воркер и подняло ёмкость теста до 28 одновременных задач: 168 персонажей на пяти процессах внутри виртуальной машины на 10 ГБ без единого зависания по памяти.

И как только 28 групп смогли одновременно попасть в данжи, началось самое интересное.

Дверь на мороз в Городе Инея

Город Инея — огромное ледяное подземелье с 21 боссом от первых Отродий до самого Хулина. Полная зачистка — это долгий и строгий поход.

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

В Городе Инея всё устроено иначе. Движок игры ещё с 2008 года хранит странность: точка воскрешения «в городе» для Инея находится не на улице в метели, а внутри входного святилища подземелья, в координатах (286, 247, -211).

На одном из первых тестовых боссов лидер группы упал. Он воскрес в святилище, проверил алгоритм и прочитал: «Воскрес в городе. Действие: вернуться в данж». И послушно проложил маршрут к координатам входного портала (305.7, -211.5).

Беда в том, что если идти в эти координаты изнутри святилища, ты упираешься ровно в портал выхода.

Лидер бодро добежал до сияющей арки, шагнул в неё и оказался снаружи, среди сугробов на перевале. Он встал в снегу и застыл, искренне не понимая, куда делось подземелье. Тем временем оставшиеся пять сопартийцев внутри собрались и пошли на следующего босса без танка, сражаясь до закономерного вайпа.

Исправление заняло одну строчку проверки: смотреть на номер текущей локации. Если ты уже внутри Инея, портал трогать нельзя. Пати собирается в зале ожидания (286, 247, -211) и бежит вперёд по коридору.

Пробежка на триста метров за сундуком в Гуй Му

Гуй Му — это не обычная зачистка комнат, а защита от волн монстров 150 уровня, нападающих на дух Вдовы. Монстров 150 уровня не убивают обычным оружием: игроки копают бобы, покупают за них ауры у оракулов по пятьсот бобов за штуку, и Око бога бьёт по наступающим волнам массовыми ударами на 15 метров.

Тут у нас случились сразу две прекрасные истории.

Первая — про порученца. Когда босс первой волны погибает, прямо на оборонительном рубеже у Вдовы появляется наградной сундук — «Дар бога». Время жизни у сундука строго ограничено: ровно 150 секунд. Если за две с половиной минуты его никто не открыл, он исчезает.

В коде была функция chest_actor(), которая выбирала, кому из свободных бойцов пойти открыть коробку. Но фильтр просто искал свободного вспомогательного бойца и не смотрел, где тот находится. В итоге он назначил ответственным друида.

А друид в этот момент находился в трёхстах метрах от рубежа — глубоко в нижней долине, где копал бобы и нарезал круги по Саду.

Друид честно бросил все свои дела и побежал в долгий подъём наверх. Тем временем пять вооружённых до зубов сопартийцев — оборотень, воин, жрец, маг и лучник — стояли буквально в двух метрах от сияющего ларца с оружием наготове. Ровно 150 секунд они плечом к плечу охраняли сундук, наблюдая, как тикают секунды, пока ларец не растворился в воздухе. Ещё через пять секунд на пустую поляну запыхавшись прибежал друид.

Вторая проблема крылась в экономике. Когда группы начали стабильно приносить бобы, они добрались до третьего тира (Магистра). Но в очереди покупки аур мана и чи стояли раньше, чем защита и здоровье.

Группа стояла на рубеже с полным запасом маны, сияя тремя каплями ци и излучая непобедимость. Ровно до тех пор, пока не пришла первая волна с физическим уроном и не снесла танка с пристом ровно за один удар. Бесконечная мана не очень помогает, когда у тебя нулевой бонус к физической защите.

Приоритет покупки теперь зафиксирован намертво: сначала защита, затем здоровье, потом атака, и только в самом конце — чи и мана. В Магистре сначала надо выжить, а красивые вспышки подождут.

Телохранители Войдвейвера и пещера на километр

В райском подземелье 99 уровня, Чистилище, финальный босс — Войдвейвер. Он стоит на высоком постаменте в окружении шестерых элитных охранников 99 уровня.

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

Вместо того чтобы сделать логичный вывод «надо тянуть аккуратно», танк рассудил радикально: «Раз при пуле любого моба всё равно сагрится босс, чистить охрану нет смысла. Бьём сразу босса!»

Оборотень с разбега перепрыгивал охранников и влетал прямо в Войдвейвера и всех шестерых элиток одновременно. В логах это выглядело жутко: от 70 до 91 смерти за заход. Шесть мобов 99 уровня и финальный босс превращали группу в пыль быстрее, чем жрец успевал докастовать «Бескрайнее море».

На самом же деле призыв о помощи работает только в радиусе 15 метров от босса. Если вытянуть охранника на расстояние от 16 метров, его можно спокойно разобрать в стороне, и Войдвейвер даже не пошевелится. Мы научили функцию lair_pullable измерять реальную дистанцию, а не паниковать от совпадения масок, и побоище прекратилось.

Тем временем в Земном аду, соседнем адовом данже 99 уровня, проблема оказалась не в бою, а в расстояниях.

Финальный босс, Перст горы, ждёт в пещере в координатах (125.5, 163.1, -245.4). Путь от точки сбора у входа (-138.6, 165.0, 309.1) петляет по подземным залам на целых 1108 метров.

Когда навигатор попытался построить прямой пеший маршрут через всю эту километровую толщу, алгоритм поиска пути захлебнулся. Он перебрал 64 159 узлов, упёрся в аварийный лимит вычислений и сдался за 275 метров до зала босса. Группа проходила три четверти данжа и намертво вставала посреди тёмного тоннеля, глядя в пустоту.

Мы разбили 1108-метровый маршрут на восемь промежуточных коридорных точек (A24_MOUNTAIN_HOPS), не длиннее 160 метров каждая. Теперь алгоритм считает каждый короткий отрезок за миллисекунды, и группа спокойно доходит до Перста горы без единой паузы.

Сброс агра на 50 % и тридцатиметровый балкон

В Долине Луны, в долине Юмора, охрана преподносит неприятный сюрприз.

По правилу Policy 1468 у каждого рядового моба в долине стоит триггер на 50 % здоровья. В тот момент, когда его HP падает ровно до половины, он полностью очищает свой список агрессии.

В обычном бою оборотень держит монстра на себе, бойцы наносят урон, а жрец непрерывно лечит группу. Как только у моба остаётся половина здоровья, накопленный танком агр сбрасывается в ноль. Кто в эту долю секунды оказывается на первой строчке таблицы ненависти? Жрец, чьи тикающие сферы и лечение летят на всю группу.

Все недобитые мобы дружно разворачивались, плевали на танка и со всех ног бежали вбивать лекаря в землю. Жрец падал за три секунды, а следом ложилась вся группа.

Мы научили танка отслеживать эту границу в боях по Policy 1468. Как только здоровье моба пробивает отметку 50 %, оборотень мгновенно прожимает Укус, перехватывая монстра на себя до того, как тот успеет сделать хотя бы шаг в сторону саппортов.

А в Сумеречном храме преграда оказалась вертикальной.

В первой и второй главах боссы Диментора и Стремительный дух стоят на каменном полу Золотого зала на высоте 94,4 метра. Коридор, по которому идёт группа, проходит по верхнему балкону на высоте 128,2 метра. Перепад между перилами балкона и полом зала — ровно 33,6 метра.

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

Мы прописали явные вертикальные переходы со спрыгиванием с балкона. Теперь группа спокойно шагает вниз на площадку к боссу, побеждает его и выбирается обратно наверх через Транспортный сундук 9.

Две петли в мастерской

Иногда кажется, что автоматизация подземелий — это когда толпа инженеров сидит перед экранами или скармливает запросы языковой модели. На практике такой подход развалился бы в первый же день. Гонять 168 персонажей в реальном времени через языковые модели разорило бы проект на счетах за токены за один уикенд, а задержки измерялись бы секундами. А если бы живые люди читали по 6000 строк сырых боевых логов после каждого неудачного захода, разработка встала бы намертво.

Наша мастерская работает на двух сцепленных между собой петлях.

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

Вторая петля — наша пятичасовая инженерная смена. Когда заход падает, супервизор не вываливает гигабайты мусора. Он сжимает неудачу в компактный пакет отчёта — packet.md и packet.json — объёмом меньше 4 килобайт. В этом брифинге отсеяно всё лишнее: указано, на какой волне случился сбой, что произошло с таблицей агра, где застрял маршрут и где лежат тела.

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

Именно так заваленная смертями таблица превращается в уверенные зачистки. Мы продолжим полировать маршруты и тактики, пока каждое подземелье в списке не выйдет на стандарт проекта: три смерти или меньше за весь заход.