Производительность и статтеры в Wuthering Waves: Архитектура движка, заблуждения о железе и реальная цена лайв-сервиса
Небрежный перевод статьи PQMlmaoxd посвященной текущим проблемам игры Wuthering Waves
Навигация
- Фундаментальные ограничения движка
- Почему ваш 6- или 8-ядерный процессор здесь не поможет
- ООП, промахи кэша и почему движок борется сам с собой
- Скриптовый слой и его цена
- О чём говорит опыт других игр
- Что Kuro Games делают прямо сейчас
- Ответ на реддит-анализ профилирования производительности
- Горькая правда: Реальное положение дел Kuro Games
- TL;DR Почему WuWa фризит — короткая версия
Предисловие
В продолжение анализа разрастания занимаемого места , сегодня автор статьи хочет углубиться в более спорную тему: почему WuWa работает именно так, и действительно ли жалобы сообщества обоснованы.
Немного контекста перед тем, как мы начнем: автор оригинальной статьи потратил на это довольно много времени, потому что искренне хотел понять, оправдано ли недовольство игроков — или же люди винят разработчиков в проблемах, которые кроются гораздо глубже, чем просто качество исполнения.
Этот пост — не защита Kuro Games. И не список оправданий. Он надеется, что это честная попытка объяснить, почему ситуация с производительностью гораздо сложнее, чем «разработчики не оптимизировали» или «не хватило навыков» (skill issue).
⚠ Предупреждение: Этот пост значительно более технический и академичный, чем предыдущая статья об утечках памяти. В нем рассматриваются архитектура процессора, модели многопоточности движка, механизмы сборки мусора и дата-ориентированное программирование — если это покажется вам слишком перегруженным, можете смело переходить сразу к краткому содержанию (TL;DR) в самом конце.
Проблема, в которой никто не может сойтись во мнении
Автор постоянно видит одни и те же жалобы в Reddit, Discord и на YouTube:
- «Академия Старторч жутко фризит на моем ПК средне-высокого сегмента — что вообще происходит?»
- «Разрабам просто плевать на оптимизацию, они только и делают, что пихают яркие эффекты…»
- «Посмотрите на AKE, Genshin, RDR2 — все они работают плавно. WuWa не может добиться этого после 2 лет разработки?»
Чего никто в этих темах не различает, так это того, что фраза «игра работает плохо» на самом деле описывает как минимум два совершенно разных явления — с разными первопричинами, в разных локациях на карте и с разными сроками исправления.
Вариант А — Статтеры внутри города. Микрофризы, нестабильный фреймпейсинг (время кадра), FPS, который никак не хочет фиксироваться. Это происходит в Академии Старторч, в Септимонте, в любой густонаселенной зоне — независимо от того, сражаетесь вы или просто гуляете. Нагрузка на видеокарту (GPU) в этом сценарии зачастую ничем не примечательна. Основным ограничителем является координация на стороне процессора (CPU), а не чистая пропускная способность видеокарты.
Вариант Б — Жесткое зависание при выходе из города. Такое трудно забыть: вы пересекаете границу, и игра проседает до 0 FPS на время от 500 мс до нескольких секунд, после чего полностью восстанавливается. Игроки, которые с WuWa с самого релиза, помнят ранние версии, где это проявлялось особенно сильно. Ситуация значительно улучшилась благодаря патчам — оптимизация конвейера стриминга в версии 3.1 принесла видимую разницу, — но полностью проблема не исчезла.
Эти два варианта кажутся связанными, потому что оба возникают рядом с оживленными зонами. Но это не одно и то же:
Вариант А — Статтеры в городе
• Когда происходит: Внутри оживленной зоны
• Что происходит: Перегрузка потока GameThread из-за количества тиков активной логики — ИИ неписей (NPC), скрипты, физика, обработчики взаимодействия
• Статус: Потолок на уровне движка, не решено
Вариант Б — Зависание на выходе
• Когда происходит: При выходе из оживленной зоны
• Что происходит: Сброс стриминга ассетов + нагрузка от сборщика мусора (GC) V8 + прогрев шейдеров/материалов или компиляция по требованию
• Статус: Значительно улучшено, остаточные явления сохраняются
Третье явление существует отдельно от первых двух:
Падение FPS в бою (на мобильных)
• Когда: Напряженный бой со множеством визуальных эффектов (VFX)
• Первопричина: Насыщение потока GameThread частицами → каскад неэффективности рендерера видеокарты (GPU)
В этом посте мы разберем все эти случаи. Что еще важнее, мы попытаемся объяснить, почему это разные проблемы — ведь именно этот контекст полностью упускается в большинстве анализов от сообщества, а он меняет само понимание того, что вообще здесь означает слово «оптимизация».
1. Фундаментальные ограничения движка
1.1 Как на самом деле создается кадр в UE4
В Unreal Engine 4 каждый кадр последовательно проходит через три стадии:
[GameThread (Игровой поток)] ──► [RenderThread (Поток рендеринга)] ──► [GPU (Видеокарта)]
GameThread — это место, где происходит вообще все, что делает игру игрой: обновление акторов (actors), симуляция физики, решения ИИ, стейт-машины анимаций, тики партиклей (частиц), просчет коллизий, логика скриптов — все это. Каждый кадр. Последовательно. Игровой поток — это центральная точка координации, через которую должна пройти вся игровая логика, прежде чем работа будет отправлена дальше по цепочке.
Важное уточнение: в UE4 есть система TaskGraph, которая позволяет распределять некоторую асинхронную работу по группам рабочих потоков. Логи клиента WuWa (Wuthering Waves) это подтверждают: там видны активные группы потоков задач NP (нормальный приоритет), HP (высокий приоритет) и BP (фоновый приоритет). Это значит, что WuWa не работает строго в один поток. Однако параллелизм TaskGraph не устраняет проблему «бутылочного горлышка» в лице GameThread — он лишь забирает на себя часть задач. GameThread по-прежнему остается обязательной точкой координации и синхронизации. Игровые API, состояния ИИ, логика скриптов и координация тиков акторов, которые по своей природе потоконебезопасны (not thread-safe), все равно обязаны проходить через него. Когда этот оверхед (затраты на координацию) забивает поток, остальные потоки ниже по цепочке просто ждут, независимо от того, сколько ядер у вас простаивает.
Как только GameThread заканчивает работу, он передает список команд отрисовки в RenderThread. Тот готовит эти команды для видеокарты. И вот тут кроется критически важный момент: оба потока синхронизируются в конце каждого кадра. Ни один из них не может начать следующий кадр, пока оба не закончат текущий.
Из официальной документации Epic Games:
«В Unreal Engine 4 (UE4) весь рендерер работает в отдельном потоке, который отстает от игрового потока на один или два кадра». «Игровой поток вставляет команду в очередь команд рендеринга… Функции RHI (Render Hardware Interface) могут вызываться только из потока рендеринга».
Источник: https://dev.epicgames.com/documentation/unreal-engine/threaded-rendering-in-unreal-engine
Говоря простым языком: если GameThread тормозит, RenderThread ждет. Видеокарта простаивает. Вы получаете фриз (frame spike). И неважно, насколько мощная у вас видеокарта — ей приходится ждать, пока GameThread закончит снабжать её работой.
Исторический контекст: почему сейчас это заметно сильнее, чем раньше
Эта модель координации в UE4 существовала на протяжении нескольких поколений движка. Ограничение не ново. Изменилась среда, в которой движок теперь работает.
- В начале 2010-х (когда вышел UE4): движок создавался под совершенно другие железки и требования к контенту. Процессоры обычно имели от 2 до 4 ядер, жесткие диски (HDD) жестко ограничивали стриминг текстур и мешей, целевой фреймрейт составлял 30–60 FPS с большим запасом времени на кадр, а игровые миры были в разы проще.
- В наши дни: и железо, и игровой дизайн ушли далеко вперед, что дико увеличило нагрузку на каждый кадр, но движок не стал параллелить эту работу лучше. Сейчас у процессоров огромное количество ядер, но однопоточная производительность не выросла пропорционально их числу. SSD убрали ограничения на чтение данных, что позволило делать агрессивный стриминг и огромные миры. Современные тайтлы требуют фотореализма, высокой плотности окружения и сложных систем симуляции.
Итог: те архитектурные ограничения, которые раньше оставались в пределах нормы, теперь все чаще пробивают потолок. Проблема не в том, что движок просто «старый», а в том, что современные рабочие нагрузки постоянно выталкивают модель координации движка на грань ее возможностей.— @argon1ut
Проблема таймлайнов: почему это до сих пор не починили
В обсуждениях производительности часто упускают, что индустрия живет в трех разных временных измерениях, которые движутся с разной скоростью:
- Эволюция движков (быстро) — Unreal Engine постоянно внедряет новые системы, улучшенные модели многопоточности и экспериментальные дата-ориентированные фреймворки (Data-Oriented Technology Stack).
- Циклы разработки игр (средне) — создание крупной игры занимает 5–6 лет. Архитектурные решения намертво фиксируются задолго до релиза.
- Адаптация экосистемы (медленно) — инструментарий, рабочие процессы и опыт разработчиков меняются постепенно, часто на протяжении нескольких проектов.
Эти таймлайны не совпадают. Игра, выходящая в 2025 году, скорее всего, проектировалась с учетом технологий 2019–2020 годов — задолго до того, как многие современные улучшения движка вообще появились или стали стабильными. Но даже если новые системы уже доступны, их нельзя просто так взять и внедрить задним числом в активный проект без огромного риска все сломать.
Возникает структурное отставание: к моменту появления решений на уровне движка, игры, которым они необходимы, находятся на такой стадии разработки, что внедрять их уже поздно. Модель GameThread, система AActor и объектно-ориентированный дизайн остаются на месте не потому, что они идеальны, а потому, что на них держится огромная экосистема, требующая стабильности и обратной совместимости.
Мы находимся в переходном периоде: движки улучшаются, железо становится мощнее, но выходящие игры все еще строятся на старых базах.
Современные игры сталкиваются с проблемами вчерашней архитектуры движка, используя сегодняшний масштаб контента, в то время как завтрашние решения еще просто не готовы к производству.
Аналогия с кухней
Представь себе кухню ресторана, где каждый заказ — даже самый простой — должен быть лично проверен и подписан шеф-поваром перед отдачей. На кухне могут стоять 16 готовых к работе су-шефов. Но каждый чек все равно ждет подписи шефа. Больше су-шефов не помогут. Если шеф начнет работать быстрее — это немного спасет ситуацию. Но «бутылочное горлышко» здесь сам процесс, а не люди.
В UE4 этот шеф-повар — GameThread. Каждому элементу игровой логики нужна его подпись на каждом кадре, строго по очереди, прежде чем процесс двинется дальше.
Диагностическая команда stat unit в UE4 разбивает время кадра на Игру (Game), Отрисовку (Draw) и Видеокарту (GPU). Когда время Frame примерно равно времени Game — упор идет именно в процессор и поток GameThread. Именно в это состояние WuWa стабильно скатывается в локациях с высокой плотностью объектов.
1.2 Почему ваш 6- или 8-ядерный процессор здесь не поможет
Это самый контринтуитивный момент во всей статье, поэтому я хочу прояснить его максимально четко: большое количество ядер процессора никак не спасает от упора в игровой поток (GameThread bottleneck).
Один из участников сообщества поделился скриншотом из программы Process Lasso во время игры в густонаселенном городе. Картина там более чем узнаваемая:
Снимок экрана Process Lasso из сообщества (i7-11700F, WuWa в городе): Процесс Client-Win64-Shipping.exe создал аж 319 потоков. При этом общая загрузка процессора составляет всего 31%, но сама игра сообщает о 100% «нагрузке на отзывчивость» (responsiveness pressure). На графике процессора в углу видно крайне неравномерное распределение: одна группа ядер пашет на полную, тогда как большинство остальных практически простаивают. (Источник: Reddit /r/WutheringWaves)
У компьютера полно вычислительной мощности. Но работа, которая и определяет, будет у вас фриз или нет — деревья поведения NPC, обработчики взаимодействий, координация тиков ИИ, скриптовая логика — не может быть свободно распределена по всем ядрам. Огромная ее часть обязана пройти через точку координации GameThread, прежде чем ее можно будет скинуть на «рабочие» ядра. И оверхед (затраты) на синхронизацию в этой точке как раз и перегружает главный поток. Потоков-то создано 319, но архитектурные ограничения движка не дают запараллелить критически важные задачи.
Мои собственные бенчмарки в CapFrameX полностью подтверждают то, что показывает Process Lasso. За две сессии в городе на связке i7-12700KF + RTX 5070 максимальная загрузка одного потока CPU доходила до 97–100%, в то время как общая средняя загрузка процессора держалась в районе 40–46%. Общая цифра по процессору выглядит красиво, но если посмотреть попоточно — история совершенно другая.
В статье сайта vkguide, посвященной многопоточности, эта проблема UE4 описана идеально:
«Вы часто будете замечать, что игры на Unreal Engine с трудом масштабируются дальше 4 ядер… Игра с тяжелым использованием блюпринтов и расчетов ИИ в UE4 будет грузить Game Thread на полную в пределах 1 ядра, а все остальные ядра в системе будут простаивать».
Источник: https://vkguide.dev/docs/extra-chapter/multithreading/
А вот выдержка из документации Epic Games для разработчиков:
«По своему дизайну многие API движка и геймплейные операции не являются потокобезопасными, поэтому они должны выполняться в GameThread… Game Thread — центральный поток исполнения: координирует всю геймплейную логику каждый кадр». «Но многопоточность — не всегда панацея. Некоторые задачи по своей природе последовательны, и их разделение может лишь усложнить код, не дав никакого прироста производительности».
Анализ тестов в двух городах (i7-12700KF + RTX 5070)
Я записал две сессии на одном железе с идентичными настройками (1080p, максимальные настройки, трассировка лучей, DLAA, генерация кадров отключена). Два разных города, но один и тот же вопрос: упираемся ли мы в CPU?
- Хуанлун (город из версии 1.0):Средний FPS: 40.2
Макс. поток CPU: 97%
Средняя загрузка GPU: 54%
Заметка по графику кадра: Видны регулярные пики фризов. Отдельный жесткий скачок до 245 мс на ~85-й секунде был вызван намеренно — вылетом за границу города на максимальной скорости (Паттерн B).
- Академия Старторч (город из версии 3.0):
Средний FPS: 38.5 Макс. поток CPU: 100%
Средняя загрузка GPU: 58%
Заметка по графику кадра: Органический (не вызванный искусственно) пик фриза до 175 мс происходит без какой-либо корреляции с нагрузкой на видеокарту. Это полностью совпадает с поведением при остановке потоков сборщиком мусора V8 (V8 GC stop-the-world pause).
Вывод по двум городам: Ситуация один в один — максимальный поток CPU забит под завязку, видеокарта недогружена. Это не баг одной конкретной локации или патча. То, что RTX 5070 загружена всего на 54–58%, пока поток процессора долбится в 97–100% — самый четкий маркер. Видеокарта просто ждет. И это обычное нахождение в городе, без боя или стресс-тестов.
Статистика потоков на мобильных устройствах
Участник сообщества @xLOCKnLOADx предоставил данные по поточной загрузки WuWa на смартфоне. Распределение нагрузки говорит само за себя:
- GameThread (Игровой поток): Средняя: 74.9% | Макс: 93.9%
- RenderThread (Поток рендеринга): Средняя: 65.1% | Макс: 83.8%
- RHIThread (Поток интерфейса рендеринга): Средняя: 52.1% | Макс: 69.5%
- TaskGraphHP 3 (Высокий приоритет): Средняя: 13.3% | Макс: 17.4%
- TaskGraphHP 4 (Высокий приоритет): Средняя: 8.5% | Макс: 12.5%
- TaskGraphHP 5 (Высокий приоритет): Средняя: 6.6% | Макс: 10.3%
- V8 DefaultWorker (×2 потока движка JS): Средняя: 3.3% / 3.2% | Макс: 16.1% / 23.1%
- Все остальные потоки: Менее 5% каждый
Вся основная работа лежит на GameThread и RenderThread. Потоки TaskGraph активны, но прохлаждаются на уровне 13% и ниже. Наличие потоков V8 DefaultWorker напрямую доказывает, что среда исполнения V8/Puerts работает параллельно с основными потоками игры.
Сравнение архитектур: WuWa против Arknights: Endfield (на том же устройстве)
У Arknights: Endfield (AKE) на движке Unity распределение потоков выглядит принципиально иначе:
- UnityMain: 70.0%
- Job.Worker 1: 20.3%
- Job.Worker 2: 20.0%
- Job.Worker 3: 14.8%
- Job.Worker 0: 11.0%
При схожей нагрузке на главный поток (~70%), рабочие потоки (Job Workers) в AKE реально пашут — выдают по 20%+ каждый. Это пример того, как встроенная система Unity Job System эффективно раскидывает симуляцию по ядрам. Потоки TaskGraph в WuWa на этом фоне выдают скромные 7% и менее.
Важное различие в поведении: Рабочие потоки WuWa работают волнообразно (пилообразный паттерн), а не под постоянной нагрузкой. Они включаются на короткие промежутки, быстро щелкают задачи и замирают в ожидании, пока главный поток подкинет им новой работы или пока разрешатся точки синхронизации. Проблема не в том, что игра «не умеет в многопоток». Проблема в координации: система может задействовать больше ядер, но задачи им выдаются так, что непрерывной параллельной работы не получается. Будет точнее сказать так: дополнительные ядра не могут ускорить ту работу, которая остается строго последовательной в главной точке координации.
Как подытожил @xLOCKnLOADx:
«Всё висит на главном потоке. Потоки, о которых упоминали разработчики, делают пшик по сравнению с игрой уровня AKE. Если мощное прайм-ядро топового 8-ядерника так сильно задыхается, представьте, как тяжело приходится бюджетному железу».
Тесты под контролем: спектр разрешений и настроек
(Тестовый стенд: Ryzen 9 7900X + RTX 5070 Ti, аналитик @argon1ut)
Чтобы изолировать поведение «бутылочного горлышка», были протестированы три конфигурации.
Результаты тестов:
- Конфиг А — 4K, DLAA, макс. лучи (RT), генерация кадров (FG) ОТКЛ, ночь
FPS: 35
1%Low: 31
Загрузка GPU: 99%
Загрузка CPU: 12%
Итог: Упор в видеокарту (GPU). Заметен пик задержки в 150.2 мс. - Конфиг B — те же настройки, но день
FPS: 40
1% Low: 15
Загрузка GPU: 99%
Загрузка CPU: 17%
Итог: Упор в видеокарту (GPU) + фриз Паттерна B. Заметен пик задержки в 127 мс. - Конфиг C — Окно 1024×768, всё на минимум (но плотность толпы и дальность прорисовки на МАКС для упора в CPU), DLSS Ultra Performance, FG 4x, лучи ОТКЛ
FPS: 412
1% Low: 200
Загрузка GPU: 37%
Загрузка CPU: 17%
Итог: Ни то, ни другое (зона входа в Академию Старторч, до начала основного стресс-теста). Задержка 26.7 мс.
Что показывает этот контролируемый набор данных:
- Конфиг А против Конфига B: Одинаковые настройки, ночь против дня. Ночью FPS ниже (35 против 40), но показатель 1% low значительно лучше (31 против 15). Разница в 1% low ощутима, но без подробного графика времени кадра точную причину назвать сложно — это могут быть как комплексные фризы Паттерна B, так и разница времени кадра на стороне GPU из-за сложности сцены (или всё вместе). GPU загружен на 99% в обоих случаях, что подтверждает: в 4K с лучами главным ограничителем является видеокарта.
- Конфиг C: Замер сделан в Академии Старторч, но в зоне с низкой плотностью объектов, до перехода к толпам NPC. При искусственно разгруженной видеокарте на разрешении 768p и у GPU, и у GameThread появляется огромный запас — отсюда 412 FPS и чистые 200 FPS по 1% low. Это не отражает пиковую нагрузку в Старторче.
- Конфиги А/B в сравнении с Конфигом C: Тот же процессор, но совершенно разный бюджет времени на кадр. Похожая общая загрузка процессора (~17%) намекает, что CPU не является ограничивающим фактором в режимах А/B — там время кадра сжирается мощностями GPU и трассировкой лучей. CPU всё еще участвует в координации, но уже не выступает явным тормозом в 4K с лучами.
Сравнение данных автора (7800X3D) и данных @argon1ut (7900X)
Контекст: L3-кэш, стабильность фреймтайма и аномальное поведение.
Запись сессии CapFrameX в Академии Старторч на тех же базовых настройках, что и для i7-12700KF (1080p, макс. настройки, лучи, DLAA, без генерации кадров). Сравнение идет с процессором Ryzen 9 7900X (тесты @argon1ut, без фильтров NVIDIA).
- Средний FPS: 12700KF (База): 38.5
7800X3D + RTX 5070: 67.3
7900X + 5070 Ti: 67.9
Интерпретация: И X3D, и 7900X показывают колоссальный прирост относительно старой базы 12700KF. - Средний 1% Low: 12700KF (База): 14.8
7800X3D + RTX 5070: 19.2
7900X + 5070 Ti: 35.7
Интерпретация: В данном конкретном тесте хвост графика (минимальный FPS) у 7900X выглядит намного здоровее. - Средний 0.1% Low: 12700KF (База): 9.9
7800X3D + RTX 5070: 5.3
7900X + 5070 Ti: 23.4
Интерпретация: Тест 7800X3D сильно пострадал от аномальных катастрофических единичных фризов. - Время микрофризов (Stuttering):12700KF (База): —
7800X3D + RTX 5070: 2.66 сек (2%)
7900X + 5070 Ti: 0.28 сек (0.2%)
Интерпретация: Сборка с 7900X смогла избежать жесткого статтера в районе 300–470 мс. - Худший заметный фриз:12700KF (База): 175 мс
7800X3D + RTX 5070: ~450 мс
7900X + 5070 Ti: Фризы на уровне ~70 мс
Интерпретация: Поведение редких событий зависит не только от одного лишь размера кэша.
Ключевые выводы из сравнения:
- Чистый бенефит X3D: График 7800X3D показывает очень ровную и чистую линию фреймтайма до и после аномального всплеска фризов. Именно здесь огромный L3-кэш (96 МБ) отрабатывает на полную: он сглаживает перемещения по городу, перебор объектов в GameThread, «погоню за указателями» (pointer chasing), координацию систем и нагрузку от стриминга ассетов. В этих задачах большой кэш снижает задержки хаотичного доступа к объектам UE4 и делает Паттерн А гораздо более плавным.
- Поведение хвоста у 7900X: Сборка @argon1ut показала отличные минимальные кадры и практически полное отсутствие серьезных статтеров. Автор теста отметил, что игра ощущалась плавной на всем пути. Это важное замечание: современные процессоры без X3D-кэша, но с высокой частотой, отличной производительностью на такт (IPC) и приличным базовым кэшем всё еще способны выдать отличную плавность в WuWa. Экстремальный X3D-кэш — не единственный путь к комфортной игре на ПК.
- Почему не стоит делать поспешных выводов из-за фриза в 450 мс: Жуткий фриз на 7800X3D — это не чистая вина процессора. Этот замер вызывает подозрения, поскольку на нем были включены Фильтры NVIDIA (Freestyle), а на системе с 7900X их не было. Эти фильтры встраиваются глубоко в цепочку рендеринга и драйвера и могут вызывать редкие спонтанные спотыкания системы, не роняя при этом средний FPS. Сюда же могли наложиться стриминг, пересечение скрытых границ локаций, прогрев шейдеров, работа сборщика мусора V8 и оверлеи. Пока 7800X3D не будет протестирован начисто без фильтров, этот дикий фриз стоит считать внешней переменной, а не нормой для X3D.
- Паттерн B никуда не делся даже на хорошем железе: На графике 7900X всё равно видны фризы уровня >70 мс при базовом времени кадра в ~15–20 мс. Визуально они могли быть незаметны, но они доказывают, что колебания Паттерна B остаются даже на топовых чипах без X3D. Разница лишь в степени тяжести: тут фризы остались в рамках терпимого, а на системе с фильтрами превратились в катастрофу.
Главный итог: На данный момент технология X3D — это лучшее аппаратное сглаживание углов для WuWa в стандартных городских сценариях (Паттерн А), поскольку игра критична к объему и скорости кэша в местах вроде Академии Старторч. Однако в худших единичных фризах замешаны другие подсистемы, которые кэшем не исправить. X3D не дает иммунитета от комплексных затыков движка, он просто делает саму пробежку по городу намного приятнее.
Вопрос: Так что важнее для WuWa — больше ядер или выше частота?
Благодарность за исправление: Участник сообщества @eggsee (создатель мобильных конфигов для WuWa) предоставил логи клиента, подтвердившие работу групп потоков TaskGraph. Это позволило точнее описать многопоточность в разделе 1.1. Изначальное утверждение про «один-единственный поток GameThread» было сильным упрощением.
Дополнительный фактор статтеров в городах — конкуренция мип-маппинга текстур: Тот же @eggsee указывает на еще одну причину фризов конкретно в Академии Старторч: стриминг мип-текстур. Даже та геометрия, которая скрыта от глаз игрока стенами или находится за спиной, всё равно подгружается. Она начинает конкурировать за пропускную способность стриминга с тем, что вы реально видите на экране. Это забивает пайплайн, и процессору приходится тратить драгоценный бюджет GameThread на обработку запросов для текстур, которые в данном кадре вообще не будут показаны.
На мобилках это накладывается на общую перегрузку GameThread, попутно увеличивая и время кадра на GPU — из-за чего графики загрузки процессора и видеокарты на телефонах порой показывают очень странные и нелогичные результаты.
Важный практический вывод: Фризы в новой локации часто утихают, если просто постоять там какое-то время. Текстуры догружаются, шейдеры прогреваются, и система стабилизируется. Это классический эффект «прогрева стриминга», а не просто статичный потолок производительности процессора. Потолок существует, но его влияние сильно зависит от стадии подгрузки данных.
Ответ на вопрос «частота или ядра» куда тоньше. Чистая частота и архитектурная производительность на такт (IPC) важны, но апгрейд процессора с упором на большой кэш L3 даст для WuWa гораздо больше. И причина как раз в архитектурной проблеме, описанной в разделе 1.3.
Если коротко: Работа GameThread заставляет процессор постоянно прыгать по данным, которые хаотично разбросаны по всей оперативной памяти. Огромный L3-кэш позволяет держать эти данные под рукой, прямо в процессоре, минимизируя время простоя ядер в ожидании ответа от ОЗУ.
Именно поэтому чипы AMD X3D (где кэш распаян прямо поверх кристалла) обеспечивают в WuWa гораздо более плавную картинку, чем процессоры с более высокой частотой, но меньшим кэшем — даже если на бумаге их средний FPS ниже. Условный Ryzen 5 5700X3D может уступить i7-12700KF по среднему кадру, но его 1% low и общая плавность в городах будут ощутимо лучше.
Но помните: X3D — это лучшее обезболивающее, но не лекарство. Кэш делает фризы мягче, но не убирает их полностью, ведь корень проблемы — в самой архитектуре игры. Ограничение GameThread, паузы сборщика мусора V8, прыжки по указателям в ООП — всё это остается на месте. Большой кэш просто позволяет вам страдать от этого гораздо меньше.
Практический совет: Если WuWa у вас безбожно фризит, а видеокарта при этом загружена менее чем на 80% — вам нужен процессор с большим кэшем L3, а не новая видеокарта. И не ждите от железа чуда там, где не доработал движок.
1.3 ООП, промахи кэша и почему движок борется сам с собой
Под «бутылочным горлышком» игрового потока (GameThread) скрывается куда более глубокая проблема. Дело не просто в том, что всё работает на одном ядре — дело в том, как именно этот поток обращается к данным во время работы.
Unreal Engine 4 построен вокруг модели Объектно-Ориентированного Программирования (ООП). И это не просто стороннее наблюдение, а официальная философия дизайна движка. Документация Epic Games описывает иерархию классов UE4 так: класс UObject является базовым для всех объектов движка, а AActor — базовым классом для всего, что можно разместить в игровом мире. Компоненты же прикрепляются к акторам, чтобы определять их поведение.
Источники: UE4 Programming Basics · OOP Principles in Unreal Engine
На практике это означает, что множество геймплейных сущностей в мире — NPC, интерактивные объекты, заскриптованные пропсы и эффекты — представлены через эту цепочку наследования. Каждый AActor имеет указатель на таблицу виртуальных методов (vtable) и владеет списком объектов UComponent. А сами эти компоненты выделяются в куче (heap) по отдельности и располагаются там, куда их закинул менеджер памяти в момент создания.
Статическая геометрия, растительность или инстансы HISM/HLOD работают иначе — речь идет именно о критически важном для геймплея графе объектов, а не вообще о каждом видимом треугольнике в мире.
Говоря простым языком: множество игровых объектов разбросаны по разным углам оперативной памяти (RAM). И чтобы обновить их, процессору приходится каждый раз отправляться на поиски.
Представь, что GameThread должен обработать логику (тики) для огромного количества активных акторов за один кадр: NPC с их деревьями поведения, здания с обработчиками интеракций, физические объекты, системы частиц. В перегруженных локациях вроде Академии Старторч или Новой Суаньи это происходит постоянно. Да, в UE4 есть технологии HISM и HLOD для снижения нагрузки на рендеринг, но они никак не разгружают GameThread по части логики. Деревья поведения всё равно должны выполняться, триггеры — просчитываться, а скрипты — тикать, независимо от того, отрисовывается ли актор в полном качестве или нет.
Работа процессора в GameThread выглядит следующим образом:
- Обновить Актора А → перейти по указателю → найти список компонентов → перейти по указателю → найти данные.[Процессор запрашивает данные из RAM: промах кэша (cache miss)]
- Обновить Актора B → перейти по указателю → найти список компонентов → перейти по указателю → найти данные.[Процессор запрашивает данные из RAM: уже другой адрес, снова промах кэша]
- Обновить Актора C → [опять другой адрес памяти]
- Обновить Актора D → [и снова другой адрес]
- ...и так 10 000 раз.
Каждый отдельный тик актора превращается в «погоню за указателями» (pointer-chasing) по всей памяти. Когда процессор ищет данные, которых еще нет в его сверхбыстром кэше L1 или L2, он замирает (stalls) в ожидании ответа от медленной оперативной памяти.
На современном процессоре попадание в кэш L1 (L1 cache hit) занимает около 4 циклов. Чтение же из основной оперативной памяти в худшем случае может стоить до 200 циклов. Конечно, на практике многие запросы перехватываются кэшем L2 или L3, а процессоры используют предвыборку данных (prefetching), чтобы сгладить углы. Итоговая задержка зависит от паттернов доступа, локальности данных и частоты попадания в кэш. Но в сцене с тысячами обновляемых акторов суммарный эффект от такого недружелюбного к кэшу поведения становится огромным. Процессор тратит уйму времени на простое ожидание данных вместо вычислений.
Важное примечание о Binned Allocator в UE4:
Стандартный аллокатор памяти в UE4 (Binned/Binned2) устроен весьма продвинуто. Он использует многоуровневую систему, которая группирует объекты по классам их размера, что помогает выжимать максимум из кэша L1/L2 для конкретных выделений памяти. Это ощутимо снижает фрагментацию кучи. Однако он не связывает строки кэша между разными акторами. Описанная выше «погоня за указателями» никуда не девается, потому что акторы, которые должны обрабатываться вместе, вовсе не обязательно лежат рядом в памяти. Аллокатор сглаживает проблему на микроуровне, но на макроуровне проблема ООП-обхода остается.
(Спасибо за разъяснение: @aizen76, специалист по UE из Indie-us Games)
Важное примечание об Actor Clustering (кластеризации акторов):
В UE4 есть функция кластеризации акторов, но ее задача специфична. Она помогает избегать случайных поисков по указателям во время прохода сборщика мусора (Garbage Collector), снижая задержки из-за промахов кэша при очистке памяти. Но эта система не является кэшем для логики тиков и никак не улучшает ежекадровый доступ при просчете геймплея. Ее бенефит относится скорее к микрофризам из-за сборщика мусора (зона Паттерна B), нежели к ежекадровому затыку логики (Паттерн А).
(Спасибо за разъяснение: @aizen76)
Наглядная аналогия с библиотекой
Представь библиотекаря, которому нужно проверить 10 000 книг.
- В хорошо организованной библиотеке (подход DOD / ECS — ориентированный на данные) книги по одной теме стоят строго на одной полке в ряд. Она берет одну секцию и быстро просматривает их одну за другой.
- В модели UE4 (ООП) каждая книга ставилась туда, где в тот момент было свободное место. Теперь ради каждой следующей книги ей приходится пешком идти в другой конец здания. Сам процесс чтения занимает столько же времени. Но вот эта бесконечная ходьба и убивает всю производительность.
Кэш L3 — это маленькая комната прямо рядом с ее рабочим столом, куда она складывает недавно использованные книги. Чем больше эта комната (больше L3-кэша), тем меньше раз ей придется бегать через все здание. Вот почему процессоры X3D со своим огромным 3D-кэшем так сильно тащат в WuWa, хотя в других играх разница может быть не так заметна.
Почему кэш L3 для WuWa важнее, чем частота процессора
Большой L3-кэш работает как буфер между ядрами процессора и оперативной памятью. Чем он больше, тем больше данных об акторах игры может сидеть прямо «под рукой» у чипа, не заставляя его лишний раз гонять запросы в RAM.
Процессор с 96 МБ кэша L3 (как у линейки AMD X3D) может удерживать внутри себя огромный массив разрозненных данных WuWa, тогда как чипы с 20–30 МБ кэша будут постоянно сбрасывать их в оперативку. Даже если процессор с меньшим кэшем работает на более высокой тактовой частоте, на практике это выльется в меньший средний FPS на бенчмарках, но заметно более высокий 1% Low и отличную плавность в городах — как раз там, где игра страдает сильнее всего.
Это не косяк, который Kuro Games привнесли от себя. Это структурное следствие того, как в принципе работает объектная модель UE4. Движок изначально спроектирован так, и внутри самого UE4 нет легального способа изменить это без полного переписывания системы сущностей с нуля. Разработчик на форуме сообщества Unreal Engine подметил это ограничение еще в 2018 году:
«Для симуляции огромного количества сущностей UE4 вряд ли будет хорошим выбором. Впрочем, насколько я вижу, ни один из крупных игровых движков не приспособлен для подобных масштабных задач из коробки».
Последняя фраза — «ни один из крупных движков» — на тот момент была чистой правдой. Но с тех пор две студии изменили правила игры. И сделали они это не за счет выбора «лучшего» движка, а благодаря колоссальным вложениям в переработку того, что у них уже было.
Почему Genshin Impact и Arknights: Endfield ощущаются иначе
Сравнение с Genshin всплывает в комьюнити постоянно, и этот момент стоит разобрать честно, а не просто отмахиваться.
Genshin Impact работает на Unity. Но причина, по которой Genshin куда изящнее справляется с высокой плотностью объектов, кроется не в том, что «Unity лучше, чем UE4». Это результат того, что HoYoverse вложили около 10 лет исследований и разработки (R&D) в глубочайшую кастомизацию движка. На выходе у них получился продукт, на котором по-прежнему висит ярлык Unity, но который внутри работает кардинально иначе, чем базовая (стоковая) версия движка, доступная любой другой студии.
Предыдущий крупный проект Hoyo, Honkai Impact 3rd, тоже был создан на Unity и оперировал годами. Этот опыт дал им две вещи, которых у Kuro на старте разработки WuWa попросту не было: команду с глубочайшей экспертизой в Unity и годы времени на то, чтобы переписать архитектуру Unity под структуры, ориентированные на данные (DOD), еще до того, как они упрутся в жесткие лимиты. К моменту релиза Genshin они выпускали не Unity — они выпускали «Unity от HoYoverse», кастомный движок с многолетними инвестициями в свои системы, тулзы и DOD-архитектуру.
Точно так же и Arknights: Endfield не является «стоковой» игрой на Unity. Hypergryph вложили огромные ресурсы в переработку своего пайплайна рендеринга и логики. Они смогли извлечь выгоду из официальной инфраструктуры Unity DOTS/ECS (которую сама Unity допилила до релизного состояния только к 2022 году), наложив её на собственный опыт разработки.
Главная мысль: Это заслуга инженеров Hoyo и Hypergryph, а не самого Unity как коробочного продукта. Если какая-то студия сегодня выпустит игру на «голом» Unity, она автоматически не получит такую же классную работу с объектами, как в Genshin. Стоковый Unity в своем классическом виде точно так же завязан на ООП. Хрошая производительность этих тайтлов — это то, что студии построили поверх движка, а не заслуга самого движка.
Конечно, компонентный дизайн Unity архитектурно ближе к DOD, чем тяжелая система наследования AActor в UE4 — это изначально более удобная стартовая точка. Но «удобная стартовая точка» и «лучший движок» — разные вещи. Расстояние между чистым Unity и тем, на чем реально крутится Genshin — колоссальное. И это расстояние заполнено годами инженерной работы, которую нельзя просто купить, выбрав другой движок.
Разница подходов к адресации памяти на мобильном экране
Подход ООП (Условный пример UE4 AActor)
Позиция Актора А ──► Адрес 0x1A3F00 (где-то в куче)
Позиция Актора B ──► Адрес 0x7C2104 (совсем в другом месте) Позиция Актора C ──► Адрес 0x4E8830 (снова другое место) Результат: Кэш процессора сбрасывается и заполняется заново на каждом тике актора.
Подход DOD/ECS (Условный пример Unity DOTS / кастомный движок)
Массив позиций: [ Поз.А | Поз.B | Поз.C | Поз.D | Поз.E | ... ]
▲ (Строго единый сплошной блок памяти)
Результат: Процессор загружает одну строку кэша, махом обрабатывает несколько сущностей и идет дальше.
Кэш остается «горячим» на протяжении всей пачки.
(Примечание: адреса памяти 0x1A3F00 и т.д. взяты исключительно для наглядности примера).
Источник: https://unity.com/ecs
Система Unity C# Job System делает вопросы потокобезопасности при такой структуре данных абсолютно прозрачными. Из официальной документации Unity:
«Чтобы облегчить написание многопоточного кода, система Unity C# Job System отслеживает все потенциальные состояния гонки (race conditions) и защищает вас от багов, которые они могут вызвать». «Система решает эту проблему, отправляя каждому воркеру копию данных, необходимых для работы, вместо того чтобы передавать ссылку на данные в главном потоке».
Источник: https://docs.unity3d.com/2020.1/Documentation/Manual/JobSystemSafetySystem.html
В этом и кроется архитектурная причина, почему Job System в Unity может безопасно раскидывать задачи по рабочим потокам. У графа AActor в UE4 нет никакого аналога — акторы постоянно делятся ссылками друг с другом, из-за чего безопасная многопоточность для произвольной геймплейной логики без тотальной хирургии кода практически невозможна. Но чтобы получить эти возможности в Genshin, Hoyo пришлось создавать большую часть инфраструктуры самостоятельно еще до того, как появилась официальная поддержка.
Честное и важное уточнение: У Genshin Impact визуальный потолок графики заметно ниже, чем у WuWa. Этот размен работает в обе стороны. Архитектура, дружелюбная к DOD, накладывает жесткие ограничения на создание контента и взаимодействие систем между собой. И у Hoyo были годы форы и готовых наработок, которых у Kuro Games попросту не было, когда они начинали делать WuWa с новой командой на новом для себя движке.
В UE4 нет встроенного фреймворка ECS или DOD. В Unreal Engine 5 ситуация с многопоточностью стала получше — там появилась система Mass Entity, которая предлагает ограниченную поддержку симуляции больших масс объектов. Но WuWa создавалась задолго до релиза UE5 и на нем не базируется, а фундаментальное наследие ООП остается глубоко зашитым в самые основы старых версий движка.
2. Скриптовый слой и его цена
2.1 Что такое Puerts и почему он здесь используется
Между C++ фундаментом Unreal Engine 4 и геймплейной логикой, с которой вы взаимодействуете, в WuWa проложен промежуточный скриптовый слой. И это больше не догадки — данный факт полностью подтверждается логами реального времени и структурой исполняемых файлов игры.
Прямое доказательство №1: Логи рантайма
При каждом запуске игра пишет диагностические данные в файл Client\Saved\Logs\Client.log. Следующие строки стабильно появляются во всех бэкапах логов (Client-backup-*.log), что исключает вероятность случайного единичного сбоя:
Игра прямым текстом сообщает, что инициализирует вспомогательный поток для движка V8, загружает модуль Puerts и выводит используемую версию V8 при старте. Это максимально прямое подтверждение работы скриптового движка JavaScript внутри игры.
Прямое доказательство №2: Строки в бинарном файле
Снятие текстовых строк из исполняемого файла Client\Binaries\Win64\Client-Win64-Shipping.exe показывает наличие встроенных идентификаторов:
- Puerts / PuertsJsEnv
- typescript / TypeScriptGeneratedClass
- KuroPuerts (упоминание «Kuro» намекает на то, что студия использует собственную модифицированную ветку фреймворка, а не стоковую интеграцию)
- puerts/*.js
- /Game/Aki/TypeScript/... (множество внутренних путей к файлам)
Данные датамайнинга: Таймлайн миграции кода
Структура игровых архивов (согласно репозиторию Arikatsu/WutheringWaves_Data) наглядно показывает, как Kuro Games прямо сейчас пытаются переписать игру «на лету» и уйти от старой архитектуры.
Версии с 1.0 по 2.7 Старый TypeScript
В главном манифесте базы данных (aki_base.csv) нет ни одного упоминания C#. Вся логика уровней и объектов завязана на TypeScript.
- Пути в архивах: BinData/level_entity/, BinData/prefab/
Версия 2.8 Первый сигнал перехода
Впервые появляются новые файлы конфигураций под C#. Стартует процесс миграции логики энтити (сущностей) уровней.
- Добавлены: LevelEntityForCSharpConfig, файл БД db_level_entity_csharp.db
Версия 3.0 Расширение миграции
Инфраструктура C# расширяется на систему префабов. Игра начинает массово переезжать на более производительные рельсы.
- Добавлены: PrefabForCSharpConfig, файл БД db_prefab_csharp.db
Версии 3.1 – 3.2 Стабилизация
Новые записи остаются на месте, C# фреймворк работает стабильно, продолжается планомерный перенос оставшегося старого кода.
Доступ к содержимому .pak-файлов:
Хотя Kuro меняют ключи шифрования AES с каждым крупным релизом, участники сообщества(https://github.com/ClostroOffi/wuwa-aes-archive) ведут архив актуальных ключей в репозитории wuwa-aes-archive. Это позволяет распаковывать ассеты через утилиты вроде FModel.
Если заглянуть внутрь папки Client/Content/Aki/, там обнаружится директория ScriptAssemblies. В ней лежат полноценные библиотеки среды выполнения C#: CSharpScript.dll, Microsoft.CSharp.dll, mscorlib.dll и куча системных библиотек System.*. Это окончательно подтверждает: в игре сейчас одновременно развернуты и работают две разные среды — TypeScript/Puerts и C#.
Папки существуют, записи в базе данных на месте, а логи подтверждают активность процессов при запуске. Заявление о наличии Puerts/V8 — это больше не теория, а задокументированный факт.
Вопрос: Зачем Kuro вообще выбрали TypeScript, если он бьет по производительности?
Потому что на этапе зарождения WuWa главным приоритетом была скорость разработки, а виртуальная машина скриптов дает именно её.
Предыдущий хит Kuro Games, Punishing: Gray Raven, создавался на Unity. Переход на Unreal Engine для разработки WuWa означал, что команде пришлось с ходу осваивать совершенно незнакомый и сложный движок. Скриптовый слой вроде Puerts решил критически важную задачу: геймдизайнеры и рядовые программисты геймплея получили возможность писать и мгновенно тестировать логику на TypeScript, вообще не трогая «тяжелый» C++ и не перезапуская движок.
В нативной разработке на C++ любое мелкое изменение или дебаг требуют полной перекомпиляции исходного кода проекта, что на масштабах огромной игры отнимает прорву времени. С Puerts же работает горячая перезагрузка (Hot Reload), а циклы итераций сокращаются в разы.
Расплатой за это становится производительность в реальном времени на релизе, когда проект разрастается до колоссальных масштабов. Однако на ранних этапах разработки, когда игра еще маленькая, эта проблема незаметна.
Перед нами классический компромисс при создании долгосрочных онлайн-игр (live-service): ты покупаешь скорость разработки на старте, но берешь производительность в кредит, который придется отдавать позже. Инженеры Kuro наверняка понимали этот риск — то, что виртуальная машина скриптов под нагрузкой начнет задыхаться от сборщика мусора (GC pressure), не является каким-то секретом. Но когда сроки поджимают, а альтернатива — мучительная и медленная работа на незнакомом движке, такой компромисс становится вынужденной необходимостью, а не глупой ошибкой. Долг был взят осознанно. Текущая миграция на C# — это его выплата.
2.2 Проблема сборщика мусора V8 — и «Паттерн Б» (Pattern B)
В движке V8 одновременно работают два сборщика мусора. Minor GC (Scavenger) обрабатывает короткоживущие объекты в «молодом» поколении памяти — это дешевая, частая и практически незаметная операция. Главная же проблема заключается в Major GC (Mark-Compact). Он запускается тогда, когда долгоживущие объекты накапливаются и полностью забивают кучу (heap) «старого» поколения.
Вот как описывается базовый подход к этой задаче в официальном инженерном блоге V8:
«Самый прямолинейный подход — приостановить выполнение JavaScript и последовательно выполнить каждую из этих задач в основном потоке. Это может привести к заиканиям (jank), задержкам в основном потоке, а также к снижению общей пропускной способности программы».
Источник: v8.dev/blog/trash-talk
Там же четко описан сценарий, который делает работу Major GC невероятно дорогой по ресурсам — и это в точности соответствует тому, что происходит в WuWa при переходе из перенасыщенной локации в открытый мир:
«Потенциальная слабость сборщика мусора, копирующего выжившие объекты, заключается в том, что когда мы выделяем память под огромное количество долгоживущих объектов, мы платим высокую цену за их копирование».
Источник: v8.dev/blog/trash-talk
Внутри Академии Старторч (Startorch Academy) или Септимонта (Septimont) объекты JavaScript создаются непрерывно: это деревья поведения NPC, триггеры квестов, обработчики взаимодействия со зданиями и колбэки обновления интерфейса. Многие из них живут достаточно долго, чтобы попасть в «старое» поколение памяти. Когда игрок покидает локацию, эти объекты становятся ненужными, но V8 не узнает об этом до тех пор, пока не запустится Major GC.
Представьте себе сборщик мусора V8 как команду уборщиков, которая приходит только по вызову — но когда они заходят в здание, они запирают все двери изнутри, пока не закончат. Пока вы находитесь в Академии Старторч, хлам копится повсюду (новые объекты на каждый тик NPC, каждое обновление квеста). Стоит вам выйти наружу, как уборщики решают: «Пора прибраться». Всё замирает до окончания уборки, после чего игра возвращается в норму.
Обновление стриминга в патче v3.1 ускорило очистку самого «здания» (памяти) при вашем уходе. Но оно не изменило принципы работы самих «уборщиков». Именно миграция с TS на C# призвана заменить эту команду на новую, которая работает инкрементально — по чуть-чуть и без блокировки всей игры.
Анатомия комплексного фриза
Этот механизм и лежит в основе «Паттерна Б» — жесткого фриза в момент выхода из города или при одновременной выгрузке больших объемов геометрии. Этот триггер не привязан строго к границам городов; любое событие, где пересекаются массовая выгрузка .pak-файлов и пиковое давление на сборщик мусора V8, вызывает этот сбой. Выход из мегаполиса — просто самый предсказуемый и воспроизводимый триггер.
Это комплексное событие, которое выглядит следующим образом:
PlaintextИгрок пересекает границу города │ ├─► Стриминг ассетов UE4: выгрузка данных .pak города │ Освобождение памяти, очистка ввода-вывода (I/O) │ → Стриминговый пик (Streaming spike) │ └─► Major GC в V8: куча заполнялась в течение всего времени нахождения в городе → Пауза типа Stop-the-world («Замри-все-живое») прямо на переходе
Когда оба процесса бьют одновременно, происходит сильный фриз. На релизе или на старых версиях игры это ощущалось как падение до 0 FPS длительностью от 500 мс до 2 секунд.
Дополнительным усугубляющим фактором на стыках зон является компиляция шейдеров по требованию. В играх с открытым миром шейдеры часто компилируются при первом контакте с новыми материалами или эффектами, а границы зон как раз вводят новые типы ассетов. Если компиляция шейдеров накладывается на очистку стриминга и давление со стороны GC, фриз становится еще глубже. Этот эффект сильно зависит от железа: системы с высокой однопоточной производительностью процессора и большим кэшем (например, 3D V-Cache) щелкают шейдеры быстрее, сглаживая просадку.
Современный V8 (сборщик Orinoco) улучшил ситуацию за счет конкурентной сборки, но разработчики признают ограничения:
«Преимущество здесь в том, что основной поток полностью свободен для выполнения JavaScript — хотя и присутствуют небольшие накладные расходы из-за синхронизации со вспомогательными потоками».
Источник: v8.dev/blog/trash-talk
Даже с Orinoco цикл Major GC невозможно полностью увести в фон. В игровом движке, где основной поток (GameThread) и без того перегружен геометрией города, такое неудачное совпадение таймингов стриминга и фазы сборки мусора как раз и приводило к тем жутким фризам до 0 FPS, которые хорошо помнят ветераны игры.
Почему «Паттерн Б» стал лучше, но не исчез совсем:
Апдейт стриминга в v3.1 («оптимизация пайплайна загрузки и ускорение стриминга данных») починил только ассетную, движковую часть этой формулы. Но компонент сборщика мусора остался на месте — ведь чтобы его убрать, нужно полностью перевести скриптовый слой с V8 на C#. Частичное улучшение — это именно то, что происходит, когда ты лечишь одну из двух одновременно протекающих болезней.
Это тот самый пик на графике фреймтайма, который возникает при неизменной нагрузке на видеокарту, стабильных температурах и полном отсутствии видимых причин на экране.
Анализ бенчмарков: Сравнение фризов
Две сессии тестирования наглядно демонстрируют floor (минимум) и ceiling (максимум) проявления «Паттерна Б» на одном и том же железе и настройках:
Академия Старторч
(Органичный выход на мотоцикле)
Наихудшее значение для одного кадра 175 мс 77 мс~ 58% Стандартный выход из плотной зоны. Видеокарта недогружена, просадка вызвана циклом Stop-the-world сборщика V8 при переходе из локаци.
Хуанлун
(Намеренное пересечение границы на планере)
Наихудшее значение для одного кадра: 245 мс 77 мс ~58% Искусственный триггер «Паттерна Б». Чем быстрее и резче пересекается граница, тем жестче фриз: стриминг и GC бьют одновременно, не успевая распределить нагрузку по времени.
Эти два числа вместе полезны. 175 мс при органическом перемещении (Startorch) против 245 мс при преднамеренном быстром выходе (Huanglong) показывают как нижний, так и верхний предел серьезности паттерна B. Обычное перемещение дает результат в 175 мс. Агрессивное пересечение границы дает результат в 245 мс.
В старых версиях игры (до оптимизации стриминга в v3.1) эти цифры гарантированно были значительно выше, так как вес «движковой» составляющей фриза был больше.
Важная оговорка: Без прямого доступа к профайлеру виртуальной машины скриптов невозможно со 100% уверенностью утверждать, что эти пики вызваны исключительно V8 GC. Однако профиль задержки, ее масштаб и полное отсутствие корреляции с нагрузкой на GPU полностью соответствуют поведению Major GC в V8. На данном этапе мы классифицируем это как обоснованное предположение (inferred), а не подтвержденный факт.
2.3 Миграция и что она исправляет на самом деле
Переход с TS на C# — это не просто «патч для производительности». Это фундаментальная смена архитектурного фундамента игры.
Современные управляемые среды выполнения (managed runtimes) умеют использовать инкрементальные и конкурентные режимы сборки мусора, что критически снижает глубину задержек по сравнению с Major GC в V8 при аналогичной нагрузке. В зависимости от конфигурации рантайма, худшие сценарии фризов могут упасть с сотен миллисекунд до единичных значений. Конечно, в C# при определенных настройках тоже бывают фазы полной остановки потоков (Stop-the-world), но сам вектор ухода от жесткого марк-компактного поведения V8 в сторону гибко настраиваемой управляемой среды — это гигантский шаг вперед для решения проблемы «Паттерна Б», независимо от конкретных нюансов реализации, которые выберут инженеры Kuro.
Что миграция НЕ исправит:
Потолок однопоточной производительности основного потока (GameThread). Архитектура распределения потоков в UE4 всё равно будет стягивать большую часть критически важных геймплейных процессов в один узел на GameThread. Даже после того, как миграция полностью завершится, WuWa всё равно продолжит упираться в ту же самую стену масштабируемости UE4 в сценах с высокой плотностью объектов (например, в центрах городов).
Что миграция ИСПРАВИТ:
Она уберет негативный вклад скриптовой виртуальной машины в худшие пики фреймтайма. Те самые аномальные просадки в 175 мс и выше могут стать значительно мягче, а в ряде сценариев — исчезнуть вовсе, если именно нагрузка со стороны скриптов была их главным катализатором.
Сравнение скриптовых сред: TypeScript (V8) vs C#
1. Модель работы сборщика мусора (GC model)
- TypeScript (V8): Полная остановка выполнения при основных циклах (Stop-the-world major cycles)
- C# (managed runtime): Инкрементальная, параллельная (Incremental, concurrent)
2. Худший случай паузы GC (Worst-case GC pause)
- TypeScript (V8): 100–500 ms
- C# (managed runtime): Часто значительно ниже; иногда единицы миллисекунд в зависимости от конфигурации среды выполнения и нагрузки
3. Рост "долга" с каждым патчем (Per-patch debt growth)
- TypeScript (V8): Накапливается (каждая новая система увеличивает нагрузку на кучу)
- C# (managed runtime): Существенно снижен
4. Взаимодействие с UE4 (Interop with UE4)
- TypeScript (V8): Через мост V8 (Through V8 bridge)
- C# (managed runtime): Более прямое (More direct)
Эффект от миграции имеет накопительный характер. Каждая новая геймплейная система, созданная уже на рельсах C#, создает куда меньше паразитной нагрузки на GC, чем если бы она была написана под V8. Окупаемость этих инвестиций (ROI) растет с каждым новым обновлением. Вот почему Kuro идут на эту тектоническую перестройку прямо по ходу активной поддержки игры, пускай обычные игроки даже не увидят строчки об этом в описании патчноутов.
Масштабы проблемы: Технический долг в цифрах
Данные датамайнинга наглядно показывают, под каким лавинообразным давлением контента оказалась команда разработчиков:
Рост контентной базы данных WuWa (с релиза до v3.2):
- Версия 1.0: Всего файлов — 1 264 | Папок в BinData — 228
- Версия 2.0: Всего файлов — 1 442 | Папок в BinData — 266
- Версия 2.7: Всего файлов — 1 903 | Папок в BinData — 337
- Версия 3.2: Всего файлов — 2 292 | Папок в BinData — 402
Итог: Объем базы данных контента вырос на 81% с момента запуска еще до того, как инфраструктура для миграции на C# была хотя бы частично развернута. Каждая сущность (entity), квестовый триггер и прераб, добавленные за этот период, шли прямиком в перегруженную копилку V8. Текущая миграция — это попытка нагнать и закрыть почти трехлетний накопившийся скриптовый долг.
Скриптовые слои как усилители, а не первопричина
Unreal Engine исторически поддерживал высокоуровневые скриптовые системы — от UnrealScript в старых версиях до Blueprints в UE4. Все они работают в рамках одной и той же фундаментальной исполнительной модели: геймплейная логика в конечном счете координируется через GameThread.
Наличие Puerts и V8 в коде WuWa указывает на надстройку дополнительного скриптового слоя, который позволяет крутить геймплейную логику на JavaScript параллельно с нативными системами. Это неизбежно порождает оверхед (избыточную нагрузку): увеличивается стоимость выполнения инструкций по сравнению с нативным C++, возникают затраты на взаимодействие между языками (cross-language interaction), усложняется управление памятью (включая GC) и плодятся лишние точки синхронизации.
Однако критически важно разделять первопричину и усилитель. Бутылочное горлышко координации потоков существует в движке само по себе, независимо от скриптов. Любая скриптовая система — будь то Blueprint, UnrealScript или V8 — функционирует в одних и тех же архитектурных рамках. Их реальное влияние сводится к тому, что они увеличивают объем работы, которая должна протиснуться через это узкое горлышко, делая проблему более явной или заставляя систему чаще выходить за рамки допустимого лимита времени на кадр.
Скриптовый слой не создает узкое место — он лишь определяет, насколько сильно оно нагружено. Замена одной скриптовой системы на другую не снимает базовых ограничений; она лишь меняет «цену» работы внутри них.
Именно поэтому миграция с TS на C# способна снизить остроту фризов «Паттерна Б» (при выходе из городов), но бессильна против «Паттерна А» (просадки FPS в толпе). Бутылочное горлышко сидит на уровне самой архитектуры движка. Скриптовая же система влияет лишь на скорость, с корой это горлышко забивается до предела.
— @argon1ut
Почему нельзя просто взять и переписать многопоточность движка?
Теоретически, обойти эти ограничения за счет глубокой переработки модели распределения потоков в UE4 возможно. На практике же масштаб такой задачи сопоставим с написанием нового движка с нуля.
Модель координации в Unreal Engine 4 намертво вшита в его ключевые подсистемы: выполнение геймплея, рендеринг, скрипты и базовые API движка спроектированы строго вокруг нее. Изменение этой структуры потребует колоссальной хирургии самого фундамента, что принесет с собой тонны багов, огромные риски и запредельную стоимость разработки.
В этот момент вопрос из плоскости «можно ли это переделать?» переходит в прагматичное русло: «что целесообразнее — пытаться перекроить старый движок или сесть за создание собственного?». Свой движок дает архитектурную свободу, но требует огромных временных затрат и жесткого стратегического условия — он должен окупаться в долгосрочной перспективе на множестве будущих проектов студии. Большинство разработчиков трезво оценивают свои силы и предпочитают мириться с ограничениями готовых коммерческих движков, планомерно оптимизируя код, нежели ввязываться в полную архитектурную перепись.
— @argon1ut
3. Контекст: О чем говорит опыт других игр
3.1 Hogwarts Legacy: Та же болезнь, другой пациент
Если вы хотите понять, являются ли проблемы с фризами в WuWa исключительно виной Kuro Games или же это фундаментальный баг самого движка Unreal Engine 4, лучшим и самым чистым примером для сравнения служит Hogwarts Legacy.
Игра создана студией Avalanche Software на базе UE 4.27 под крылом Warner Bros. с колоссальным бюджетом. При этом на релизе она продемонстрировала едва ли не самые задокументированные проблемы с производительностью на ПК среди всех крупных релизов последних лет. И паттерн этих проблем один в один повторяет профиль WuWa:
- Жесткий упор в процессор (CPU-bound) именно в плотных локациях (Хогсмид, обитаемые части замка), в то время как на открытых пространствах игра летит.
- Видеокарта хронически недогружена на фоне постоянных микрофризов.
- Решающее значение имеет частота на одно ядро; увеличение количества ядер процессора вообще не спасает ситуацию.
Игрок со связкой Ryzen 9 5900X и RTX 4090 — на тот момент одной из самых топовых игровых конфигураций — наглядно описал эту проблему в ветке Steam:
«У меня 5900x и 4090, играю в 4K. Если я не включаю генерацию кадров, то получаю 90–99% загрузки видеокарты при 30–40% загрузки процессора, но фреймрейт при этом просто мусорный (от 50 до 70 FPS)».
Высокая нагрузка на GPU, средняя общая загрузка CPU и отвратительная кадровая частота — это точный слепок профиля производительности WuWa. А вот цитата из другой ветки, где пользователь самостоятельно докопался до истинной сути:
«Игра упирается в процессор только потому, что движок не умеет масштабироваться более чем на 1 или 2 ядра. Процессор с большим количеством ядер тут не поможет, игре нужна именно высокая однопоточная производительность (IPC). Если бы движок умел масштабироваться на 8 ядер, мы бы вообще не увидели этого лимита».
Эта вторая цитата принадлежит обычному игроку, а не разработчику, но он пришел к абсолютно верному техническому диагнозу на основе простых наблюдений. Это описание архитектуры GameThread в UE4 сформулировано точнее, чем во многих профессиональных разборах.
И это не случайное совпадение. Перед нами тот же движок, та же модель распределения потоков и тот же потолок однопоточной производительности на проекте, который на старте обладал куда более внушительными ресурсами, чем WuWa. Вывод здесь не в том, что Kuro не смогли решить задачу, с которой справились другие. Ни одна сопоставимая игра-сервис с открытым миром на UE4 так и не смогла полностью вырваться из этой ловушки.
Вопрос: Зачем Kuro вообще выбрали UE4 при таких ограничениях?
Потому что они хотели создать гача-игру нового поколения, и в 2019–2020 годах UE4 был единственным жизнеспособным фундаментом для такой амбиции. Unreal Engine 5 тогда еще попросту не существовало. Технология DOTS/ECS от Unity не была стабильна для продакшена такого масштаба. А создание собственного движка с нуля — как сделали CD Projekt с REDengine или Rockstar с RAGE — требует долгих лет глубоких исследований и разработки (R&D), которых у студии не было.
UE4 предлагал зрелый инструментарий, огромный рынок готовых специалистов и достаточную гибкость, чтобы его можно было выжимать на максимум. И надо отдать Kuro должное: версия UE4, на которой работает игра — это далеко не стоковый вариант.
🔍 Инсайды с Unreal Fest Tokyo 2025: Архитектурный тупик
На официальной конференции Unreal Fest Tokyo 2025, организованной Epic Games Japan, руководитель команды рендеринга Kuro Games Ван Синь (Wang Xin) раскрыл уникальные детали внутренней кухни проекта под кодовым корейским/японским названием Narushio (внутреннее имя WuWa).
Кастомный «Lumen» на старом движке
Разработчики Kuro детально изучили работу аппаратного трассировщика лучей (Hardware RT) в Unreal Engine 5 — включая алгоритмы Screen Probe Gather, Radiance Cache и отражения — и вручную воссоздали их аналог внутри старой версии UE4.26.
Ключевые элементы перенести «в лоб» не удалось. В докладе отмечается:
«Версия Lumen для UE5 использует технологию Surface Cache, которая сохраняет светимость поверхностей объектов, но её невозможно напрямую применить в UE4.26, поэтому команда Narushio отказалась от прямого порта».
Вместо этого инженеры Kuro внедрили собственную альтернативу — Clipmap Irradiance Cache, подкрепив её пробами, SH-сжатием (сферическими гармониками) и другими кэширующими аппроксимациями. На выходе получилась кастомная система глобального освещения, которая имитирует визуальные цели UE5, но работает на кодовой базе UE4.
Официальное признание невозможности перехода на UE5
В рамках этой же презентации Kuro Games прямым текстом подтвердили тезис о жесткой архитектурной привязке к старой версии движка:
Цитата из презентации Kuro Games: «Поскольку Narushio — это долгосрочный проект, который уже запущен и находится в оперировании, стабильность для нас превыше всего. С самого старта разработки колоссальный объем игровых ассетных данных создавался строго с прицелом на особенности UE4, что делает полноценную миграцию на Unreal Engine 5 на данном этапе практически невозможной».
Это не просто догадки аналитиков — это официальное заявление инженерной команды Kuro, объясняющее, почему игра останется на текущих рельсах.
Когда в 2020 году стартовала разработка, было крайне сложно предсказать, насколько болезненно эти ограничения движка ударят по WuWa. В то время масштабных проектов с открытым миром на UE4 практически не было: Tower of Fantasy вышла только в конце 2021 года, а Hogwarts Legacy — в 2023. Полная картина того, во что обходится архитектура этого движка в контексте live-service игры с такой графикой, тогда еще ни у кого не сформировалась. До Kuro этой дорогой в данном жанре никто не ходил. Сейчас технический долг очевиден, но на старте он был скрыт в тумане.
Взгляд в будущее: В финале доклада было отмечено, что сейчас исследовательская группа Kuro активно копает в сторону «обработки сцен со сложной геометрией с использованием технологий Mega Geometry в духе Nanite». Речь идет не о портировании Nanite из UE5, а о попытках воссоздать похожие принципы оптимизации геометрии внутри своей кастомной сборки UE4.26.
Это подтверждает сквозную стратегию студии: вместо рискованного переноса всей игры на новый движок, Kuro точечно вычленяют самые крутые фичи UE5, решающие конкретные бутылочные горлышки, и вручную переписывают их под свой старый фундамент.
— @argon1ut
3.2 Gears 5 и Fortnite: Исключения из правил UE4 (и почему они здесь неприменимы)
В дискуссиях об оптимизации Gears 5 от студии The Coalition часто приводят в пример как эталонную реализацию на Unreal Engine 4. Игра выдает железные 60 FPS на консолях, отлично масштабируется на ПК и демонстрирует безупречную плавность фреймтайма. Но причина этого успеха напрямую объясняет проблемы WuWa: Gears 5 — это не игра с открытым миром. Линейный дизайн уровней позволяет применять агрессивный пре-бейкнутый куллинг (скрытие объектов вне поля зрения еще на этапе разработки). Здесь нет сложного стриминга «на лету», нет непредсказуемой плотности игровых объектов (actors) и нет накопительного эффекта игры-сервиса. The Coalition изначально проектировали игру вокруг жестких ограничений UE4 для того жанра, который идеально в них вписывался.
Fortnite также заслуживает упоминания как еще одно популярное исключение — и эта игра действительно уникальна, ведь её создала сама Epic Games, то есть авторы движка Unreal. Прямой доступ к команде разработчиков ядра, возможность переписывать движок на любую глубину в любые сроки и институциональные знания, недоступные ни одной внешней студии, дают результат совершенно иного порядка. Кроме того, Fortnite не является открытым миром в том понимании, в каком им является WuWa — плотность застройки её локаций и сессионная структура матчей архитектурно сильно отличаются от персистентного (постоянного) открытого мира с тысячами одновременно обсчитываемых сущностей. Но даже с учетом этих различий показательно, что самый плавный игровой опыт на UE4 выдают именно создатели этого самого движка.
WuWa не относится ни к одной из этих категорий. Жанр игры не позволяет использовать методы Gears 5, а у Kuro Games нет внутренних ресурсов и привилегий Epic.
3.3 Cyberpunk 2077 и RDR2: Когда путают теплое с мягким (сравнение коммерческих и кастомных движков)
Еще одно популярное в игровом сообществе сравнение звучит так: «Cyberpunk 2077 и Red Dead Redemption 2 выглядят потрясающе и работают очень стабильно. Почему же тогда WuWa постоянно страдает от микрофризов?» На этот вопрос стоит ответить прямо, так как подобное сравнение в корне ошибочно на техническом уровне.
Движки REDengine 4 (Cyberpunk) и RAGE (RDR2) не имеют той самой проблемы с перегрузкой основного потока (GameThread), потому что они создавались с совершенно иной философией проектирования, и уж точно не на базе UE4.
- CD Projekt полностью переписали REDengine специально под стриминг открытого мира. Система распределения задач (Job System) является нативной для этой архитектуры, а не надстройкой поверх старого кода. Отправка вызовов отрисовки (Draw Calls) эффективно распределяется по нескольким потокам, а система сущностей (Entity Component System) создавалась под этот сценарий использования с нуля.
- RAGE развивается и итерируется уже более двадцати лет под конкретную задачу — обсчет сверхплотных открытых пространств. Система стриминга Rockstar предугадывает появление геометрии задолго до того, как камера физически до нее доберется. А структура размещения данных в памяти проектировалась с прицелом на когерентность кэша задолго до того, как термин «data-oriented design» (данно-ориентированное проектирование) вошел в мейнстрим.
Сравнивать фреймтайм WuWa с этими титанами — это нечестный тест производительности. Это сравнение коммерческого движка общего назначения, который бросили на амбразуру сложнейшего жанра, с кастомными проприетарными технологиями, которые вытачивались под этот конкретный жанр десятилетиями. Разные инструменты, разные ограничения, разная история разработки.
4. Что Kuro Games делают прямо сейчас
4.1 Хронология оптимизации в патчах
Каждая строчка в описании обновлений, упоминающая оптимизацию производительности — это очередная выплата по балансу технического долга. Если проанализировать официальные патчноуты с версии v1.2 по v3.2 (и далее), вырисовывается четкая закономерность: меняется не просто интенсивность работы Kuro, а сам характер и глубина вносимых изменений.
Развитие оптимизаций можно разделить на несколько ключевых этапов:
1. Ранний этап (Версии 1.х): Поверхностная стабильность
Первые патчи были точечными и в основном боролись со следствиями, а не с причинами:
- v1.2 — «Оптимизирована производительность игры на некоторых мобильных устройствах». Локальные исправления под конкретные чипсеты, никаких архитектурных изменений.
- v1.4 — «Оптимизирована компиляция шейдеров на ПК». Процесс перенесли на экран загрузки игры, чтобы уменьшить фризы и визуальные баги прямо во время геймплея. Для Android добавили функцию Auto FPS для предотвращения перегрева. Этот патч важен тем, что Kuro впервые попытались вынести одну из главных болячек UE4 — компиляцию шейдеров «на лету» — за рамки основного игрового цикла.
2. Средний этап (Версии 2.х): Расширение масштабов
Начиная с версии 2.0, разработчики начинают копать глубже:
- v2.0 — «Оптимизировано использование процессора для NPC, что позволяет отображать больше персонажей на экране одновременно». Это чистая оптимизация потока GameThread. Kuro открыто признали, что плотность толпы бьет именно по CPU, а не по видеокарте, и начали снижать стоимость просчета логики NPC.
- v2.2 — «Повышена эффективность процесса компиляции шейдеров и использования аппаратных ресурсов на ПК». Второй целенаправленный подход к проблеме шейдеров.
- v2.8 — Самый масштабный патч с точки зрения оптимизации в истории игры. В нем было заявлено:«Снижена нагрузка на GPU при включенной трассировке лучей, повышен общий фреймрейт».«Снижено потребление оперативной и видеопамяти при отображении различных типов сцен».Исправление двух фундаментальных проблем одновременно (нагрузка рендеринга и давление на память) говорит о том, что это была не косметическая правка, а глубокая переработка кода.
Техническая изнанка оптимизации RT в v2.8 (из доклада на Unreal Fest Tokyo 2025): Инженеры Kuro устранили бутылочные горлышки на стороне процессора в потоке отрисовки (RHI thread): операции Gather Instances (занимала 3.2 мс), Build Acceleration Structure (2.8 мс) и Bind SBT (2.4 мс).
Они разделили статичную и динамическую работу: вынесли Gather Instances на отдельный поток, а статичные части построения структур ускорения начали просчитывать заранее в начале кадра. В результате общее время кадра при включенном Ray Tracing упало с 19.8 мс до 10.5 мс. Поскольку это было исправление потока рендеринга (RHI thread), а не игрового (GameThread), это объясняет, почему RT стал работать лучше, но «Паттерн А» (микрофризы в городах из-за процессора) остался без изменений.
3. Современный этап (Версии 3.х): Стриминг и генерация кадров
- v3.1 — «Оптимизирована производительность загрузки ресурсов за счет обновления пайплайна загрузки и ускорения стриминга данных...». Прямой удар по заиканиям (loading stutter), которые возникают при подгрузке ассетов движком.
- v3.3 — Внедрена собственная технология генерации кадров (Frame Generation) для мобильных устройств среднего уровня. Это решение указывает на два факта: Kuro продолжают развивать мобильную инфраструктуру, но при этом косвенно признают, что потолок производительности GameThread на смартфонах не получается пробить архитектурно — генерация кадров используется для компенсации нехватки FPS, а не для устранения её первопричины. Также на ПК были добавлены опции DLAA и анизотропной фильтрации, а на мобильных устройствах появилась функция очистки ресурсов (Resource Cleanup), позволяющая удалять неиспользуемые данные окружения карты для снижения раздувания памяти.
Главные выводы по траектории обновлений
Динамика очевидна: от банальных мобильных фиксов в 1.х команда перешла к оптимизации процессора и NPC в 2.0, затем к тотальной перестройке RT и памяти в 2.8, и, наконец, к оптимизации стриминга в 3.1. Каждый шаг срезает часть нагрузки, но не ломает сам архитектурный потолок движка.
При этом стоит зафиксировать два важнейших момента:
- Во-первых, обновление стриминга в v3.1 действительно существенно снизило частоту проявления «Паттерна Б» (тех самых жестких фризов до 0 FPS при выходе из городов). Игроки, которые сидят в игре с релиза, определенно заметили эту разницу в повседневной игре. Тот остаток фриза, что мы наблюдаем сейчас — это исключительно задержка со стороны сборщика мусора (GC) в V8, которую оптимизация стриминга ассетов движка физически не могла исправить.
- Во-вторых, патч v2.8 стал поворотной точкой в обоих направлениях. Это одновременно и самый мощный патч поверхностной оптимизации, и версия, в которой в файлах манифеста (aki_base.csv) впервые прописался путь LevelEntityForCSharpConfig. Это доказывает, что латание текущих дыр и глобальная миграция архитектуры на C# идут параллельно, а не последовательно.
4.2 Миграция в контексте общих задач
Переход с TypeScript на C# заслуживает отдельного сравнения с другими видами оптимизации, которые теоретически могла бы провести студия.
Взять, к примеру, улучшение куллинга (отсечения невидимых объектов) на стороне процессора, детальную настройку уровней детализации (LOD) для визуальных эффектов (VFX) или оптимизацию точности шейдеров. Каждое из этих решений — реальный способ исправить реальную проблему. И ни одно из них не требует смены движка. Тем не менее ни одно из них так и не было реализовано в полной мере.
И причина вовсе не в том, что инженеры Kuro о них не знают. Дело в том, что «технически возможное исправление» и «реализуемая задача в рамках жесткого графика обновлений» — это принципиально разные вещи.
Вся инженерная команда игры сейчас бежит по движущейся беговой дорожке. Чтобы провести глубокую оптимизацию, нужно остановиться и починить саму дорожку — но если дорожка остановится, ты с нее напрочь свалишься. Миграция с TS на C# — это эквивалент замены основных узлов и шестеренок беговой дорожки прямо на ходу, пока на ней бегут, и притом так, чтобы игроки даже не заметили, что под их ногами пересобирают весь механизм.
Например, переработка LOD для визуальных эффектов умений в WuWa — это задача не для программистов, а для контент-отдела. Это значит, что нужно заново открыть файлы эффектов каждого навыка — для 50+ персонажей, у каждого из которых по 5–6 скиллов. Затем вручную создать для них упрощенные LOD-варианты, протестировать каждый на предмет визуальных багов в уже запущенной живой игре, обучить художников новому пайплайну работы и внедрить этот стандарт для всех будущих релизов. И эта колоссальная работа вступает в прямую конкуренцию за человеко-часы с созданием новых персонажей, ивентов и локаций, которые обязаны выходить каждые шесть недель, чтобы проект оставался финансово жизнеспособным.
Миграция с TS на C# получает наивысший приоритет над остальными исправлениями ровно потому, что это единственное изменение с накопительным (кумулятивным) эффектом для всей инфраструктуры. Любой будущий контент, добавленный уже на рельсах C#, несет в себе куда меньше рисков перегрузить сборщик мусора, чем если бы он работал через V8. Окупаемость этих инвестиций (ROI) здесь экспоненциальная, а не линейная. Тонкая настройка шейдеров дает лишь разовый и фиксированный прирост производительности. Ценность же миграции растет с каждым новым патчем.
Kuro Games умудряются проворачивать эту сложнейшую архитектурную миграцию параллельно с поддержкой стандартного шестинедельного цикла выпуска контента. И когда всё сделано правильно, игроки этого даже не замечают. Но такова уж специфика фундаментальной работы с инфраструктурой.
4.3 MagicDawn: Долгосрочная перспектива
MagicDawn — это внутреннее научно-исследовательское подразделение Tencent Games, специализирующееся на технологиях рендеринга. В число их официально опубликованных научных работ за 2025–2026 годы входят следующие прорывные решения:
- Neural Dynamic GI (CVPR 2026): Нейросетевое сжатие для временных наборов лайтмапов (карт освещения), обеспечивающее динамическое глобальное освещение с кардинальным снижением требований к памяти и вычислительной мощности.
- Gaussian Probe Compression (SIGGRAPH 2025): Сжатие световых пробов в соотношении до 1:50 с возможностью декомпрессии на стороне видеокарты в реальном времени.
- Lightmap Compression (Eurographics 2026): Сокращение занимаемого пространства UV-лайтмапов на 83% с одновременным улучшением качества картинки (метрики PSNR). Источник: magicdawnlab.github.io
Что критически важно: в официальном промо-ролике MagicDawn прямым текстом утверждается: «Технологии MagicDawn уже используются в широком спектре блокбастеров, включая Wuthering Waves». Это полностью подтверждает факт активного практического сотрудничества с Kuro Games, а не просто наличие теоретических изысканий на бумаге.
Подтверждение интеграции через Engine.ini
Наличие этих технологий в релизной версии игры подтверждается структурой конфигурационных файлов. Внутри Engine.ini актуальной сборки WuWa прописан следующий путь к плагину:
Это прямое и неоспоримое доказательство того, что наработки MagicDawn внедрены в продакшен-версию движка WuWa в виде интегрированного плагина. Это не просто строчка из рекламного буклета или научной статьи — это зарегистрированный путь к контенту в конфигурации самой игры.
Данный факт переводит статус MagicDawn из категории «подтвержденное сотрудничество» в статус «подтвержденная интеграция на уровне движка».
Другие кастомные модификации Kuro Games
Тот же файл Engine.ini раскрывает список других примечательных плагинов собственной разработки Kuro, которые навешаны на их сильно модифицированную версию UE4.26:
- KuroDynamicMeshBatch — кастомное динамическое батчинг-объединение мешей (полигональных сеток).
- KuroWorldPartition — собственная модификация системы World Partition (аналог разбиения мира из UE5, перенесенный на рельсы UE4).
- KuroPSOTools — инструменты для кэширования объектов состояния конвейера (PSO) и шейдеров для борьбы с микрофризами.
- KuroPerfCat — внутренний профайлер производительности.
- FastGeoStreaming — экспериментальная система ускоренного стриминга геометрии.
- OpacityMicroMap — интеграция технологии NVIDIA OMM (микрокарты прозрачности, которые также упоминались в докладе на Unreal Fest Tokyo 2025).
Этот список наглядно демонстрирует, что перед нами глубоко кастомизированная сборка Unreal Engine 4.26, в которой точечно и избирательно используются как собственные инженерные решения Kuro, так и передовые графические технологии сторонних компаний.
Официальные патчноуты игры пока не раскрывают, какие конкретно функции MagicDawn активны в продакшене прямо сейчас, каков их точный масштаб и как именно они влияют на итоговое качество картинки или фреймрейт. Сама интеграция подтверждена, но степень её внедрения остается коммерческой тайной.
Тем не менее это железно доказывает две вещи: Tencent активно инвестирует в фундаментальные графические технологии под визуальные амбиции WuWa, а связь между исследовательской лабораторией MagicDawn и студией Kuro Games носит сугубо практический характер.
— @argon1ut
5. Ответ на реддит-анализ профилирования производительности
В сообществе WuWa был опубликован подробный анализ производительности от пользователя @xLOCKnLOADx, основанный на аппаратных трассировках с устройства на базе флагманского чипа Snapdragon 8 Elite. Хочется сразу прояснить: данные в этом посте абсолютно реальны и заслуживают самого серьезного внимания.
Измеренные показатели — 50% отсечения примитивов (primitive rejection) в открытом мире, до 99% в бою, частота промахов в кэш L1 на уровне 70–100% и доминирование шейдеров FP32 — взяты непосредственно из реальных системных логов железа, и от них нельзя просто так отмахнуться.
Важная оговорка по поводу платформ: В данном разделе обсуждаются данные профилирования мобильных устройств. Мобильные и ПК-архитектуры различаются фундаментально: смартфоны используют тайловый рендеринг (tile-based), унифицированную память и агрессивный троттлинг для контроля температур и энергопотребления. ПК же полагается на дискретные видеокарты, память с гораздо более высокой пропускной способностью и совершенно другие конвейеры рендеринга.
Метрики вроде процента отсечения примитивов, поведения кэша и утилизации GPU не переносятся напрямую с одной платформы на другую. Причинно-следственные аргументы здесь применимы именно к мобильному контексту; поведение игры на ПК следует оценивать отдельно с помощью ПК-ориентированного профилирования (проблема «узкого горлышка» координации потоков на ПК подтверждается независимыми данными из Раздела 1.2).
Проблема кроется не в самих данных, а в их интерпретации.
Автор оригинального поста рассматривает каждую проблему на стороне GPU как независимый сбой оптимизации: мол, куллинг (отсечение невидимой геометрии) должен работать лучше, батчинг (пакетирование) текстур нужно доработать, а шейдерам пора снизить точность расчетов. Каждое из этих наблюдений по отдельности верно. И вывод автора о том, что всё это можно починить без перехода на новый движок — тоже технически правильный.
Но пост не отвечает на главный вопрос: почему куллинг ломается именно под боевой нагрузкой?
Фантастические 99% тривиального отсечения в бою — это не признак криво написанного кода куллинга. Это признак того, что у этого кода просто не хватило времени на выполнение. К моменту, когда игра доходит до этапа отправки команд на GPU (submission step), основной поток GameThread уже полностью исчерпывает весь свой временной бюджет кадра на обновление тяжелых систем частиц, тики скриптов и физику объектов (акторов). Времени на расчет отсечения банально не остается.
Попытка оптимизировать сам алгоритм куллинга при намертво забитом потоке GameThread не решает проблему. Она лишь маскирует симптом. На практике этот архитектурный каскад выглядит следующим образом:
Каскад падения производительности (от причины к симптомам)
1.Первопричина: Насыщение потока GameThread:Проблема архитектуры движка.
Из-за перегрузки основного потока логикой и скриптами движок упирается в лимит времени, отведенного на кадр.
2.Симптом 1: Срыв дедлайна куллинга:CPU-side culling.
Процессорный куллинг просто не успевает отработать до того, как наступит жесткий дедлайн отправки кадра на отрисовку в GPU.
3.Симптом 2: Проброс «мусорной» геометрии на видеокарту:Тривиальное отсечение GPU.
Неотсеченная геометрия пачкой улетает на GPU. Видеокарта вынуждена аппаратно выполнять тривиальное отсечение (trivial rejection) гигантских массивов невидимых треугольников: ~50% в открытом мире и до 99% во время замесов в бою.
4.Симптом 3: Разрушение кэша:L1/L2 Cache Thrashing.
Поскольку геометрию не отсеяли, вызовы отрисовки (draw calls) начинают хаотично прыгать между несвязанными текстурами. Это мгновенно забивает и «вытряхивает» текстурный кэш: промахи в L1 улетают под 70–100%, промахи в L2 составляют около 45%.
5.Симптом 4: Простой конвейера (GPU Stall):Ожидание памяти.
Видеокарта встает в ступор (stall), ожидая подгрузки текстурных данных напрямую из медленной основной памяти, и эта же история циклически повторяется на следующем кадре.
Воспринимать симптомы со 2 по 4 как изолированные ошибки — значит упускать из виду их общий корень. Графический чип выполняет колоссальный объем бесполезной работы не потому, что программисты Kuro забыли написать код для отсечения невидимых объектов. Видеокарта страдает из-за того, что коду, который должен был предотвратить этот кошмар, планировщик просто не выделил процессорное время.
Заметка о причинно-следственной связи: Описанный выше каскад является наиболее архитектурно обоснованным объяснением имеющихся данных профилирования, однако его невозможно верифицировать на 100% без полного доступа к исходному коду и внутренним профайлерам игры.
Существуют как минимум три рабочие гипотезы сбоя куллинга:
- У GameThread физически нет временного бюджета на куллинг (архитектурное объяснение);
- Сам алгоритм куллинга написан неэффективно (проблема качества реализации);
- Архитектура конвейера куллинга структурно не подходит под такие рабочие нагрузки (гибридный вариант).
Если быть честными, ограничения движка и качество написания кода — это две отдельные переменные, вклад которых невозможно точно разделить, глядя на игру снаружи. В WuWa присутствуют оба фактора, но их точные пропорции неизвестны. В данном анализе гипотеза (1) выбрана в качестве ведущей, поскольку она лучше всего согласуется с общим поведением игры на разных конфигурациях, но читателю стоит оценивать это с долей здорового скептицизма.
Впрочем, есть еще один важный нюанс, на который стоит обратить внимание: конфигурационные файлы релизной сборки WuWa подтверждают, что параметры r.ParallelFrustumCull=1 и r.ParallelOcclusionCull=1 активны и функционируют. Это указывает на то, что задача отсечения в игре может частично распределяться с GameThread на параллельные потоки. Смещает ли это «узкое горлышко» в тяжелых локациях (вроде густонаселенных городов) — вопрос открытый, требующий глубоких замеров, но само наличие этих флагов доказывает, что конвейер куллинга в игре по крайней мере не завязан намертво на один-единственный поток.
— При участии @HtooMyatLin3
5.1 Специфика планирования потоков на Android (Android Scheduling Dimension)
В оригинальном разборе с Реддита также упущена из виду одна чисто платформенная особенность, которая выступает мощным катализатором проблем с производительностью именно на мобильных устройствах.
В операционной системе Android за распределение потоков по физическим ядрам процессора отвечает планировщик EAS (Energy-Aware Scheduling). Он динамически перебрасывает задачи между «большими» (высокопроизводительными, big/prime) и «МАЛЕНЬКИМИ» (энергоэффективными, LITTLE) кластерами ядер, опираясь на историю нагрузки за последние несколько кадров.
И здесь возникает серьезная проблема: EAS использует историческое взвешенное среднее значение для принятия решений о размещении потоков. Если поток GameThread в течение нескольких кадров оставался относительно разгруженным — например, во время экрана загрузки, катсцены или входа в меню — планировщик EAS может посчитать эту задачу легкой и принудительно урезать ей приоритет, сослав на энергоэффективное ядро. Но вот сцена резко усложняется (игрок выходит в открытый мир или прожимает ротацию навыков), и нагрузка на GameThread мгновенно взлетает до пиковых значений. Планировщик ОС не способен среагировать сиюминутно: системе требуется определенное время, чтобы осознать новый уровень нагрузки и мигрировать поток обратно на мощное prime-ядро.
В этот критический промежуток времени (миграционное окно) поток GameThread пытается выполнять тяжелейшие архитектурные расчеты на слабом ядре, которое физически для этого не предназначено. В результате временной бюджет на куллинг схлопывается окончательно, а описанный ранее каскад симптомов проявляется в разы жестче.
Документация Android AOSP (Android Open Source Project) напрямую подтверждает коварство этого механизма:
«Без изменения логики планировщика, повышающего вероятность перевода фоновых и активных (foreground) приложений в кластер больших ядер CPU, у активных приложений может банально не хватить процессорной мощности для отрисовки кадра до тех пор, пока планировщик наконец не примет решение сбалансировать нагрузку и перекинуть поток на большое ядро».
Источник: source.android.com (Раздел: Identify capacity-related jank)
Этого измерения попросту не существует на ПК — десктопные процессоры архитектуры x86 (за исключением гибридных решений, где логика распределения на уровне Windows Thread Director работает иначе) в контексте игровых потоков ведут себя симметрично. Таким образом, мобильные фризы и микростаттеры получают дополнительный, чисто платформенный слой усиления поверх базовой проблемы перегрузки GameThread. Симптомы на ПК и смартфонах могут выглядеть идентично, но их глубокая системная механика принципиально отличается.
5.2 Проблема FP32: Кроссплатформенные ограничения
Автор оригинального поста по профилированию предлагает перевести шейдеры на половинную точность (FP16) в качестве быстрого и эффективного способа оптимизации под мобильные графические чипы Adreno, где пропускная способность FP16 действительно ровно в два раза быстрее.
Для Adreno это утверждение абсолютно верно. Однако оно полностью игнорирует тот факт, что WuWa должна одновременно и стабильно работать на огромном парке совершенно разного железа: Adreno, Mali, Apple GPU, а также десктопных видеокартах от AMD, Nvidia и встроенной графике Intel. Поведение графического чипа и реальный прирост от перехода на FP16 кардинально различаются в зависимости от семейства GPU. Поддержка и ведение отдельных вариантов шейдеров под каждое семейство процессоров неизбежно привели бы к раздуванию размера .pak-файлов игры (в сообществе и без того регулярно вспыхивают споры по поводу общего веса клиента WuWa), а нагрузка на QA-отдел возрастала бы кратно с добавлением каждого нового класса устройств.
Этот компромисс куда глубже и сложнее, чем кажется на первый взгляд. Участник сообщества @HtooMyatLin3 добавляет важный нюанс на уровне аппаратной архитектуры: производительность вычислений FP16 на видеокартах серии GTX (например, GTX 1060) составляет всего 1:64 от скорости FP32. Это значит, что использование FP16 на этой линейке GPU приведёт к катастрофическому падению производительности. Таким образом, перед внедрением любой значимой оптимизации точности шейдеров разработчикам сначала пришлось бы полностью отрезать поддержку FP16 для серии GTX. Напротив, видеокарта AMD Radeon RX 570 демонстрирует идентичную скорость выполнения как для FP32, так и для FP16, что доказывает: разрыв в эффективности вычислений критически завязан на конкретную архитектуру чипа, а не является простым и элегантным переключением одного тумблера «включить FP16».
— При участии @HtooMyatLin3
5.3 Единственное по-настоящему простое исправление
В оригинальном разборе на основе трассировки Perfetto был зафиксирован параметр r.Streaming.PoolSize = 400. На флагманских мобильных устройствах с 12–16 ГБ оперативной памяти выделение всего 400 МБ под стриминг текстур становится прямой причиной разрушения и перегрузки кэша L1 и L2 — текстуры банально не могут загрузиться в быструю память заранее, так как пул слишком мал, чтобы их вместить.
Это чисто конфигурационное значение. Его изменение не несет в себе никаких архитектурных компромиссов или рисков. Это единственная находка во всем стороннем анализе, которую действительно можно легко исправить без каких-либо сложных последствий, описанных выше.
Ограничения по параметрам r.Streaming.FullyLoadUsedTextures и r.Streaming.HLODStrategy:
Это реальные консольные переменные движка UE4. Параметр FullyLoadUsedTextures принудительно заставляет все активные текстуры загружаться в память незамедлительно, а HLODStrategy 2 полностью отключает специфический стриминг для высокодетализированных объектов (HLOD).
Участник сообщества @HtooMyatLin3 отмечает, что обе эти переменные были доступны для использования в старых патчах игры (что зафиксировано в ранних коммитах конфигураций AlteriaX). Однако сейчас Kuro Games задают их напрямую через исходный код или внутреннюю консоль с более высоким приоритетом выполнения. Данный факт подтверждается анализом логов: любые ручные изменения этих параметров в файле Engine.ini теперь просто перекрываются игрой и больше не имеют силы во время работы приложения.
Перед нами классический пример того, как простое и эффективное исправление было доступно, активно использовалось игровым сообществом, но впоследствии было полностью заблокировано разработчиками в релизной сборке клиента.
6. Горькая правда: Реальное положение дел Kuro Games
Студия Kuro вовсе не сидит сложа руки. Но они также не находятся в положении, когда можно просто взять и «оптимизировать» игру до идеально гладкой производительности на всех существующих конфигурациях железа. На самом деле они угодили в куда более тяжелую ловушку — ловушку локального оптимума (local optimal trap). Это состояние системы, при котором любое доступное действие лишь ухудшает общую ситуацию.
Перед разработчиками стоят три жестких тупика:
- Если они поставят контент на паузу ради глубокой оптимизации: вовлеченность игроков упадет, доходы рухнут, а выживание самого проекта окажется под угрозой — что в итоге полностью лишит студию бюджета и всякого смысла заниматься этой самой оптимизацией.
- Если они продолжат штамповать контент без оглядки на оптимизацию: технический долг продолжит нарастать как снежный ком, производительность игры станет еще хуже, а фрустрация и недовольство сообщества будут только расти.
- Если они решатся на масштабный архитектурный рефакторинг прямо сейчас: они рискуют спровоцировать катастрофические баги и регрессии в живой, постоянно работающей онлайн-системе (live-service), которая не прощает подобных сбоев.
Каждое из этих направлений имеет свою тяжелую цену. Текущее техническое состояние игры — это не следствие лени. Это вынужденное равновесие, которое неизбежно возникает, когда любая другая альтернатива оказывается еще хуже.
Именно поэтому миграция с TypeScript на C# имеет такое колоссальное значение. Это не очередная попытка оптимизации внутри существующей ловушки — это попытка изменить саму форму этой ловушки. «Локальный поиск» и латание дыр (исправление точечных багов куллинга, подгонка точности шейдеров) дают лишь временный, точечный прирост производительности, но не способны разбить базовые архитектурные ограничения. Миграция скриптового движка — это фундаментальное структурное изменение, которое открывает инженерные решения, физически недоступные в текущем положении.
Она не пробьет потолок производительности основного потока GameThread. Модель многопоточности Unreal Engine 4 всё еще будет жестко лимитировать объемы данных, которые можно обрабатывать параллельно. Однако полное исключение длительных пауз «остановки мира» (stop-the-world) сборщика мусора V8 из общего бюджета кадра даст потоку GameThread драгоценный запас по времени и существенно сгладит пики самых жестких фризов. А это — огромный шаг вперед, даже если он и не является абсолютной панацеей от всех проблем.
Визуальные амбиции как катализатор, а не первопричина
Популярное мнение в дискуссиях об оптимизации звучит так: «Kuro Games слишком заигрались с графикой — если бы они урезали визуал, игра летала бы на любом железе».
Аппаратные тесты опровергают эту гипотезу.
Проверить это просто: если бы корень проблемы с фризами (stutter) лежал исключительно в графических амбициях, то их отключение полностью решало бы проблему. Кастомные конфигурации Engine.ini от сообщества (в частности, от AlteriaX) позволяют игрокам полностью отключать трассировку лучей (RT), снижать разрешение теней, урезать дальность прорисовки (ViewDistanceScale), выкашивать траву и сводить плотность толпы практически к нулю. Игроки, использующие эти конфиги, действительно отмечают прирост среднего FPS — однако микрофризы «Паттерна А» в Академии и резкие скачки фреймтайма «Паттерна Б» при пересечении границ локаций никуда не исчезают. Снижается их интенсивность, но сам характер их проявления остается неизменным.
Это критически важное различие: графические настройки влияют на тяжесть проявления симптомов, но никак не на сам факт наличия «узкого горлышка» архитектуры.
Четыре бенчмарка наглядно иллюстрируют, как ведет себя игра на кардинально разном железе и графических пресетах:
- i5-4690 + GTX 750Ti, минимальные настройки, FSR включен (версия v3.0, до патча 3.1 со стриминг-фиксом):
Пешая прогулка по Академии: в районе 38–45 FPS.
Использование транспорта или быстрое перемещение: просадки до 25–35 FPS.
Быстрая езда по дорогам открытого мира: стабильные ~30 FPS.
Бой со сложными спецэффектами (VFX): падение до 25–30 FPS.
Перед нами видеокарта 2014 года с 2 ГБ видеопамяти на «минималках». Игра остается условно играбельной, но сам характер фризов — стабильный фреймрейт при ходьбе и просадки при быстром пересечении зон или езде на байке — один в один повторяет то, что происходит на мощных ПК, просто с более низким общим значением FPS. - i5-1035G1 + встроенная графика, 8 ГБ ОЗУ, 720p, минимальные настройки (ниже минимальных системных требований):
Общая производительность: стабильные ~15 FPS.
Примечательно, что распределение кадров (frame pacing) оставалось относительно ровным по сравнению с мощными системами, работающими на высокой частоте кадров. Паттерн утилизации потоков остался прежним — производительность диктовалась малым числом потоков, а рабочие потоки TaskGraph продолжали демонстрировать пилообразное поведение короткими всплесками.Сама активность рабочих потоков выглядела более равномерной, чем на высокобюджетном железе, с меньшей амплитудой всплесков — скорее всего потому, что сниженная пропускная способность основного потока (main thread) снабжает их работой медленнее и планомернее, а не огромными пачками. Постоянное нахождение под жестким давлением дефицита памяти (8 ГБ были забиты под завязку) усложняло общую производительность, но принципиально не ломало общую логику выполнения инструкций движком.
Важное наблюдение из этого теста: При частоте ~15 FPS временной бюджет кадра составляет огромные ~66 мс (против ~16 мс при 60 FPS). Архитектурный тупик координации потоков никуда не делся (что подтверждает мониторинг потоков), но его влияние на визуальную плавность становится менее заметным для глаза, поскольку игра уже работает глубоко ниже порога, при котором фризы начинают явно выделяться на общем фоне. Это полностью сходится с наблюдениями владельцев GTX 750Ti: «узкое горлышко» присутствует на всех уровнях железа, но его критичность растет пропорционально разрыву между возможностями вашего ПК и потолком самого движка. Видео бенчмарка
- i7-4790K (в разгоне до 4.7 ГГц) + GTX 1660 Super, низко-средние настройки, FSR включен (версия v3.1):
Перемещение пешком до посадки на транспорт: ~55–60 FPS.
Езда на транспорте внутри Академии: падение до 30–45 FPS.
Транспорт на дорогах открытого мира: стабильные 47–50+ FPS.
Боевые сцены: 35–60 FPS в зависимости от тяжести эффектов.
Показатель 1% Low в Академии: провалы до 8–15 FPS.
Показатель 1% Low на уровне 8–15 FPS как раз наглядно ловит те самые каскадные пики «Паттерна Б» при быстрых переходах между зонами, накладывающиеся на общую нестабильность фреймтайма в городе («Паттерн А»), прогрев стриминга, компиляцию шейдеров и тяжелые VFX. Даже разогнанный до упора процессор 4790K всё равно бьется головой об архитектурный барьер координации, потому что скорость выполнения инструкций за такт (IPC), поведение кэша, задержки памяти и общая загруженность GameThread здесь значат гораздо больше, чем «сырая» тактовая частота процессора в ГГц. - i7-9700K + RTX 2080 Super, максимальные настройки, RT выключен, DLSS в режиме «Качество» (версия v3.1):
В разрешении 1080p: ходьба в Академии выдает стабильные ~60 FPS, на транспорте внутри города — просадки до 45–60 FPS, на трассах в открытом мире — стабильные 60 FPS, в бою — 55–60 FPS.
В разрешении 1440p: ходьба в Академии — в среднем ~47 FPS (скачет в диапазоне 47–60), на транспорте внутри города — падения до 40–50 FPS.
В разрешении 4K: поведение в Академии аналогично 1440p, при быстром движении в мире — около 45–49 FPS, в бою — стабильные 45–50+ FPS.Обратите внимание: в этом тесте трассировка лучей полностью отключена. Никакой «наценки» за визуальные амбиции RT железо не платит — и тем не менее, архитектурный затык сохраняется на 1440p и 4K, где с ростом разрешения видеокарта начинает выступать дополнительным сдерживающим со-фактором.
Итоговые выводы по массиву данных:
На всех четырех протестированных конфигурациях — от ветерана GTX 750Ti до мощной RTX 2080 Super, от минимальных до ультра-настроек, от 2 ГБ до 8+ ГБ видеопамяти — циклически воспроизводится один и тот же паттерн поведения: стабильный фреймрейт при размеренном перемещении, просадки при резкой смене зон или быстром беге/езде, и дополнительное удушение системы спецэффектами в бою. Механизм «узкого горлышка» аппаратно присутствует везде.
Безусловно, визуальные амбиции разработчиков вносят свою лепту в общую нагрузку: включенный Ray Tracing забивает поток отрисовки (RenderThread), переусложненные VFX отжирают драгоценные миллисекунды у GameThread, а высокое разрешение увеличивает время кадра на стороне GPU. Когда эти графические затраты накладываются на базовый кризис координации потоков GameThread, общий временной бюджет кадра тает на глазах, и игра моментально упирается в свой потолок производительности. Это именно то, о чем пишет инженер @argon1ut: «Графические амбиции лишь масштабируют и подсвечивают уже существующие внутренние ограничения движка».
Но сами эти ограничения никуда не исчезают, даже если превратить игру в «картошку». Отключение графических наворотов снижает амплитуду фризов, но не лечит саму архитектурную болезнь. Правильный диагноз звучит не как «Kuro Games сделали слишком красивую игру, поэтому она лагает», а как «Визуальные амбиции Kuro Games заставляют скрытую архитектурную проблему проявляться ярче, чаще и на гораздо более широком спектре компьютерного железа».
Данные с древней GTX 750Ti доказывают это максимально наглядно: видеокарта, которая физически не умеет в рейтрейсинг, не способна выдать высокие настройки и работает при минимальной плотности NPC, всё равно демонстрирует ровно ту же самую «подпись» фризов и статтеров. Архитектура движка — это нерушимый пол производительности. А графические настройки лишь определяют, как быстро вы об него ударитесь.
— Подготовлено на основе материалов @argon1ut и @HtooMyatLin3
Что на самом деле значит «плохо оптимизирована» — и почему сравнение сложнее, чем кажется
Когда кто-то говорит, что WuWa плохо оптимизирована, негласным ориентиром обычно выступает одна из двух вещей: «Cyberpunk работает плавно при >=60fps, почему WuWa так не может?» или «Genshin, AKE работают плавно, почему здесь не так?».
Оба сравнения упускают из виду то, что WuWa пытается делать одновременно.
Существует также несоответствие ожиданий в ПК-сообществе. На рынках, где конфигурация вроде i5-12400F + RTX 3060 считается базовым уровнем для гейминга, игроки привыкли видеть, что хорошо оптимизированные AAA-игры работают плавно, и ожидают, что WuWa будет вести себя так же. Это ожидание понятно, но оно также может вводить в заблуждение. На максимальных настройках, с DLAA/RT, плотными городами, высокой плотностью VFX, логикой NPC, стримингом и масштабами live-service контента, WuWa больше не ведет себя как легковесная гача-нагрузка. Её визуальный потолок находится гораздо ближе к современным высокобюджетным играм с открытым миром, чем предполагают многие игроки.
Вот почему утилизация CPU/GPU сама по себе может вводить в заблуждение в этой дискуссии. AAA-игра на кастомном движке может держать GPU полностью загруженным и ощущаться плавной, в то время как WuWa может показывать более низкую загрузку GPU и неравномерный фреймпейсинг, потому что GPU ждет координации GameThread, скриптов, стриминга или синхронизации RenderThread. Такой паттерн является реальным ограничением производительности, но он не служит автоматически доказательством того, что рендерер неэффективен или что разработчики «ничего не делали».
Также важно отделять оптимизацию на уровне игры от конфигурации на уровне пользователя и проблем со средой выполнения. У WuWa есть реальные проблемы на стороне движка/контента: насыщение GameThread, пики при стриминге, прогрев шейдеров/материалов, давление GC, стоимость VFX/куллинга на мобильных устройствах и стабильность фреймпейсинга. Но не каждый плохой отчет пользователя является чистым свидетельством провала оптимизации на уровне игры. Неправильные настройки для оборудования, нелимитированный FPS, недостаточный запас VRAM, сломанный кэш шейдеров, оверлеи, фильтры NVIDIA, твики соответствия ядер в Process Lasso, принудительные приоритеты, агрессивные правки INI, нестабильный разгон RAM/CPU или конфликты драйверов/среды выполнения могут создавать или усиливать статтеры. Отношение ко всему этому как к «плохой оптимизации» ухудшает диагностику.
Учитывайте полный стек ограничений, в которых работает Kuro:
- Игра с открытым миром и большими, непрерывно загружаемыми локациями
- Визуальные амбиции уровня 3A — освещение, сложность геометрии и плотность эффектов, сопоставимые с высокобюджетными ПК-тайтлами
- Плотные населенные города с потенциально сотнями активных NPC и интерактивных сущностей, некоторые из которых имеют деревья поведения ИИ и скриптовую логику
- Ориентированный на боёвку дизайн, требующий жесткой задержки ввода, сложных VFX и одновременной физики — всё это делит один и тот же бюджет GameThread
- Кроссплатформенность — ПК, PS5, iOS, Android, Mac — каждая со своими семействами GPU, ограничениями памяти и поведением планировщика
- Широкий диапазон оборудования от флагманских телефонов до бюджетных Android-устройств, требующий настройки конфигурации, которая подходит всем
- 6-недельный цикл live-service, непрерывно поставляющий новых персонажей, области и системы
- UE4 в качестве основы — движок общего назначения, не разработанный для любого из этих специфических требований в комбинации
Ни одна другая игра в настоящее время не делает все эти вещи вместе на UE4. Ни Hogwarts Legacy (офлайн, без мобильных устройств, более простая боёвка). Ни Tower of Fantasy (более низкая визуальная планка). Ни Genshin (другой движок, другая архитектура, более низкий графический потолок). Честный ответ на вопрос «плохо ли оптимизирована WuWa по сравнению с играми в тех же условиях?» заключается в том, что нет игр в таких же условиях, с которыми можно было бы её сравнить.
Прежде чем прийти к выводу, что производительность WuWa хуже, чем должна быть, вам нужно найти другую игру на UE4, которая одновременно является игрой с открытым миром, визуальной точностью 3A, работает как live-service с 6-недельным циклом, ориентирована на боёвку со сложными VFX, кроссплатформенная от мобильных устройств до ПК И поддерживает широкий спектр оборудования от бюджетных устройств до флагманов. Пока такого сравнения не существует, утверждение о том, что оптимизация Kuro находится ниже некоторого разумного базового уровня, не подтверждено.
Что мы можем сказать, так это то, что WuWa работает близко к потолку того, что позволяет архитектура её движка, учитывая всё, что она пытается сделать. Этот потолок реален, он находится на уровне движка, и ни одна внешняя команда не продемонстрировала, как избежать его в сопоставимом контексте.
Cyberpunk работает на REDEngine — созданном специально для производительности в открытом мире студией, которая потратила десятилетие на его создание. RDR2 работает на RAGE — более двадцати лет итераций для плотных сред открытого мира. Это не сравнения производительности. Это сравнения между движком общего назначения под нагрузкой и кастомной инфраструктурой, разработанной специально для того, чтобы избежать этой нагрузки.
Честное сравнение — это UE4 против UE4: Hogwarts Legacy вышла с ограничением по CPU в плотных зонах при бюджете Warner Bros. PUBG — не игра с открытым миром, но самый долгоживущий масштабный тайтл на UE4 — требовал глубоких модификаций кода движка с 2017 года, согласно собственным словам PlayerUnknown («нам пришлось внести много изменений в базовый код движка, чтобы заставить вещи работать в таком большом масштабе» — Rock Paper Shotgun), и отчеты сообщества о микро-статтерах и нестабильности фреймпейсинга сохраняются на протяжении нескольких лет этих инвестиций. Как отметил @argon1ut, который внимательно следил за историей производительности PUBG: «пока что я нашел только PUBG в качестве наиболее вероятного кандидата [для выхода за пределы ограничений UE4], но ничего конкретного, кроме заявлений о модификации глубокого кода движка». Tower of Fantasy, наиболее близкое совпадение по жанру, сталкивалось с жалобами на производительность на протяжении всей своей жизни. Ни один явно сопоставимый live-service тайтл с открытым миром на UE4 публично не продемонстрировал полного избавления от этого паттерна.
Один дополнительный слой усложняет всё: кроссплатформенная поддержка. WuWa выходит на PC, iOS, Android, PS5 и Mac. Каждое решение по оптимизации, каждое значение конфигурации, каждый компромисс по шейдерам должны работать на разных семействах GPU от Adreno до Mali, Apple, десктопных AMD и Nvidia, на устройствах от телефонов с 6 ГБ ОЗУ до рабочих станций с 64 ГБ. Это ограничение, с которым игры для одной платформы просто не сталкиваются, и оно делает любой вопрос «почему бы просто не исправить X» значительно более сложным для ответа.
Точное утверждение звучит не как «Kuro не смогли оптимизировать то, что другие студии сделали правильно». Точное утверждение звучит так: аппаратное обеспечение не поспевает за техническим долгом и ограничениями движка, которые накапливаются, когда проект с такими визуальными амбициями работает на этой архитектуре в модели развертывания live-service, которая делает глубокие изменения инфраструктуры практически невозможными.
Это другой диагноз, отличный от «разработчики ленивы». И он указывает на совсем другие ожидания.
Один конкретный факт подтверждает это: игра с действительно плохой оптимизацией не может быть играбельной на i5-4690 с оперативной памятью DDR3 и GTX 750Ti 2GB — оборудовании 2014 года, работающем ниже минимальных характеристик. WuWa играбельна. Это не поведение игры, в которой инженеры не пытались. Это поведение игры, в которой инженеры работали в реальных ограничениях и всё же предоставили масштабируемый опыт в диапазоне оборудования, на который покушаются немногие AAA-тайтлы.
Тем не менее, архитектурный контекст, описанный в этом анализе, дает оправдание текущей производительности при текущих ограничениях — а не карт-бланш для будущих патчей. По мере созревания миграции на C#, по мере углубления интеграции MagicDawn, по мере того как полная поддержка Tencent сделает возможными более долгосрочные инвестиции в архитектуру, приемлемый порог сдвигается. Если будущие города, выпущенные в значительно более лучших условиях по ресурсам — v4.x, v5.x и далее — продемонстрируют ту же тяжесть Паттерна А, что и Startorch Academy, аргумент об архитектурных ограничениях ослабнет, и качество реализации станет более честным объяснением. Startorch была первым в своем роде стресс-тестом для движка WuWa. У будущих городов при сопоставимой плотности не будет этого оправдания. Это та планка, которую Kuro теперь нужно преодолеть.
TL;DR Почему WuWa фризит — короткая версия
Производительность игры в плотных городах ограничена потоком GameThread — центральной точкой координации UE4, через которую должна проходить вся игровая логика. WuWa использует систему рабочих потоков TaskGraph в UE4, но накладные расходы на координацию и синхронизацию в GameThread остаются потолком, когда плотность сцены высока. Большее количество ядер процессора это не решает. Мой бенчмарк (i7-12700KF + RTX 5070, перемещение по городу, без генерации кадров): GPU в среднем загружен на 54–58%, в то время как максимальный поток CPU достигает 97–100%. GPU находится в ожидании, он не является «узким горлышком».
Представьте это как шоссе с восемью полосами, но только с одной будкой оплаты пошлины. Весь трафик должен слиться в один поток и пройти через эту единственную точку, прежде чем что-либо сможет двигаться дальше. Добавьте больше полос, и пробка не станет меньше. Эта будка оплаты — GameThread. В Startorch Academy есть сотни NPC, здания с активной логикой взаимодействия и сложные системы окружения, и все они пытаются пройти через нее одновременно. Поток насыщается. Всё ждет.
Почему они не могут просто это исправить?
Потому что эта будка оплаты — часть того, как был спроектирован Unreal Engine 4. Это не баг, который написали Kuro, — это архитектурный фундамент, на котором построена каждая игра на UE4. У Hogwarts Legacy та же проблема. У Tower of Fantasy тоже. Ни один явно сопоставимый live-service тайтл с открытым миром на UE4 публично не продемонстрировал полного избавления от этого паттерна.
Что Kuro делают на самом деле?
Постоянно выпускают патчи — улучшения производительности появляются почти в каждом крупном обновлении. И тихо мигрируют скриптовый слой с TypeScript (который использует сборщик мусора V8) на C# (среду выполнения с инкрементным сборщиком мусора с низкими паузами). Эта миграция подтверждается логами среды выполнения, бинарным анализом и данными датамайна. Это дорого и невидимо для игроков — и они всё равно делают это, сохраняя 6-недельный график выпуска контента.
Итог
Фраза «разработчики ленивы» не объясняет, почему у Hogwarts Legacy был тот же паттерн фризов при бюджете Warner Bros. Объяснением служат «потолок движка + технический долг live-service». Утверждение о том, что оптимизация Kuro находится ниже уровня, требует сопоставимой игры — UE4, открытый мир, live-service, кроссплатформенность, упор на боёвку — для сравнения. Ни одного публичного примера, четко соответствующего этому набору сравнения, пока продемонстрировано не было.
Ограничения данного анализа
Этот анализ опирается на датамайненные конфигурационные файлы из Arikatsu/WutheringWaves_Data, личные записи бенчмарков (CapFrameX, i7-12700KF + RTX 5070 — две сессии: Huanglong и Startorch Academy; Ryzen 7 7800X3D + RTX 5070 — Startorch Academy), предоставленные сообществом данные CapFrameX от @argon1ut (Ryzen 9 7900X + RTX 5070 Ti), сообщения сообщества об аппаратных трассировках, официальную документацию Unreal Engine и данные профилирования сообщества.
Относительно утверждения о Puerts/V8: теперь это подкреплено прямыми доказательствами. Лог-файлы среды выполнения (Client.log и несколько Client-backup-*.log) явно фиксируют инициализацию V8, загрузку модулей Puerts и строки версии V8 при каждом запуске. Извлечение строк из Client-Win64-Shipping.exe выдает идентификаторы, включая Puerts, PuertsJsEnv, KuroPuerts, TypeScriptGeneratedClass и множество путей вида /Game/Aki/TypeScript/. Инспекция содержимого паков через FModel (с использованием архива AES-ключей сообщества по адресу https://github.com/ClostroOffi/wuwa-aes-archive) выявляет директорию ScriptAssemblies, содержащую сборки среды выполнения C#, развернутые параллельно со слоем Puerts. Это утверждение подтверждено, а не выведено аналитически.
Относительно миграции на C#: инфраструктура подтверждена в датамайне (aki_base.csv версии v2.8+, директории _csharp). Содержимое пака ScriptAssemblies подтверждает, что среда выполнения C# развернута. Миграция контента продолжается — конфигурационные файлы остаются частично заполненными.
Интерпретации скачков кадра в 175 мс и 245 мс как событий паузы сборщика мусора (GC) согласуются с задокументированным поведением V8, но не могут быть подтверждены без доступа к профайлеру виртуальной машины скриптов. Оба скачка реальны и измерены на основе аппаратных данных. Их причина является наиболее технически последовательным объяснением, доступным при отсутствии корреляции с GPU и температурными показателями.
Причинно-следственная связь между насыщением GameThread и сбоем куллинга (Раздел 5) представляет собой наиболее архитектурно последовательное объяснение для этих данных, но не может быть полностью отделена от вклада качества реализации без доступа к профайлеру на уровне кода.
Относительно кастомизированной сборки UE4: анализ конфигурации движка сообществом выявляет r.Streaming.UsingKuroStreamingPriority — консольную переменную, отсутствующую в стандартной системе стриминга UE4, что дополнительно подтверждает, что движок был существенно модифицирован за рамками стандартной интеграции. Её задокументированное поведение (раздельное управление приоритетом удержания и загрузки с привязкой к конкретным компромиссам игры для различных типов ассетов) согласуется с кастомным пайплайном стриминга, построенным поверх базового фреймворка UE4.
Кто вносил вклад в написание статьи и изменения статьи вы можете посмотреть в Источнике. Я не буду сюда это добавлять
Разрабы уже обещают, что в скором времени результат работы с оптимизацией игры будет виден. Официально по-слухам говорят в 3.7 уже что-то будет