Борьба за качество в Factorio с помощью матриц
Я играю в Factorio так, как и подобает рациональному человеку: для проектирования производственных линий я задействую матричные вычисления.
Впрочем, к деталям мы ещё вернемся. Если же вам не терпится изучить результат, можете сразу оценить мой новый онлайн-калькулятор.
Знакомство с Factorio и механикой качества
Игры серии Factorio по праву считаются родоначальниками жанра симуляторов фабрикостроения. Игрок управляет инженером, который собирает сырье и объединяет его в детальнейшие цепочки крафта, создавая все более продвинутое оборудование. На первый взгляд концепция близка к Minecraft и другим выживалкам, однако ключевое отличие кроется в абсолютном упоре на автоматизацию. Очень быстро ручной труд отходит на второй план, уступая место сложнейшей логистике: конвейерным сетям, манипуляторам и автотранспорту. Некоторые разработчики в этом жанре и вовсе отказались от аватара игрока.
По мере исследования «технологий» игра открывает всё более эффективные механизмы. Один из ключевых элементов — слоты для модулей в производственных зданиях. Модули скорости сокращают время обработки ценой повышенного энергопотребления, а модули продуктивности позволяют получать больше готовой продукции из того же объема сырья, пусть и замедчая процесс.
Выпущенное в 2024 году дополнение Space Age привнесло важнейшую механику — качество (Quality). Теперь любые предметы и рецепты имеют пять градаций: ⚀ обычное, ⚁ необычное, ⚂ редкое, ⚃ эпическое и ⚄ легендарное. Каждый последующий уровень улучшает параметры объектов (например, ускоряет станки или повышает эффективность модулей). Создавать элитные товары можно напрямую из ингредиентов соответствующего качества, однако единственный легальный способ поднять уровень материала — это использование модулей качества.

Эти модули определяют вероятность апгрейда (Q). Базовый шанс скачка на следующий уровень за одну операцию составляет 10%. Зависимость итогового качества от исходных материалов можно представить в виде матрицы вероятностей:

Например, в автоматоне с четырьмя разъемами под модули пиковый шанс апгрейда достигает 24,8%:

Некоторые игроки встретили элемент рандома в традиционно детерминированной игре без восторга, однако на больших дистанциях любые случайности превращаются в стабильные математические пропорции.
Баланс устроен так, что для получения премиальных экземпляров даже при многоэтапном производстве неизбежно приходится мириться с лавиной некондиционного «мусора» низких градаций.
Чтобы склады не трещали по швам от брака, в Space Age добавили переработчик (recycler). Этот агрегат уничтожает предмет, возвращая (как правило) 25% исходных компонентов. Это позволило инженерам проектировать замкнутые циклы апсайклинга: продукция гоняется по кругу через модули качества до тех пор, пока не достигнет нужной кондиции ценой колоссального перерасхода сырья:
Источник: Factorio blog
Инструменты для проектирования баз
Игры этого жанра часто требуют вовлечения за пределами самого экрана. Например, в Blue Prince пользователи вынуждены вести бумажные конспекты из-за отсутствия встроенного блокнота. Фабричные симуляторы провоцируют создание гигантских электронных таблиц, но стандартных таблиц быстро становится недостаточно для амбициозных проектов. Энтузиасты тратят сотни часов на разработку продвинутых веб-инструментов, эмулирующих внутриигровую логику для точного расчета производственных цепочек.

Конечно, в Factorio можно играть интуитивно, просто достраивая новые мощности по мере возникновения дефицита.
Но мне нравится просчитывать всё заранее: сколько станков каждого типа потребуется? Какова будет реальная производительность? Где возникнет узкое горлышко? Замкнутые циклы повышения качества делают подобные прикидки «на коленке» практически невозможными.
Матричные вычисления
Смоделируем ситуацию:
-
Определенные ингредиенты (скажем, железные листы) поступают из внешней линии, где уже задействованы модули качества. У каждого компонента есть распределение вероятностей по пяти уровням: от обычного ⚀ до легендарного ⚄.
-
У нас имеется достаточное число сборочных автоматов под каждый рецепт. Станки оснащены модулями качества, обеспечивающими базовые 10% шанса апгрейда.
Рассмотрим возможные варианты для отдельно взятого продукта:
Изделие определенного качества может быть собрано из сырья аналогичного или худшего грейда. Итоговая вероятность такого исхода складывается из независимых путей получения, каждый из которых представляет собой произведение шанса перехода конкретного рецепта и доли исходных компонентов нужного качества:

(Символ пробела можно интерпретировать как деление на 100.)
Здесь коэффициенты транспонированы относительно диагонали в сравнении с вики-таблицей, поскольку мы расположили уровни предметов по вертикали. Сгруппируем показатели для каждого грейда в вектор-строку:

Теперь нашу систему линейных уравнений можно записать лаконично: вектором умножаем на переходную матрицу:

Нулевой шанс апгрейда означает отсутствие изменений между стадиями — в этом случае матрица переходов превращается в единичную:

Главный плюс подхода в том, что многоэтапные процессы теперь просчитываются простым перемножением матриц. Возьмем, к примеру, добычу руды (шанс качества 7.5%), выплавку слитков (5%) и финальный крафт труб (10%). Без бонусов продуктивности итоговое распределение выглядит так:

Стратегии контроля качества
Разумеется, можно установить максимальное количество модулей качества и пытаться выжать топ-грейды везде и сразу, однако куда практичнее строить автономные специализированные подсистемы.
«Казино»: оппортунистический подход без рециклинга
Самый примитивный путь — использовать обычное сырье, воткнуть модули качества и пустить всё на самотек без переработки. Метод доступен до открытия переработчиков, но годится лишь для штучных задач: скажем, скрафтить сотню сборщиков астероидов в надежде получить пару редких экземпляров для раннего звездолета.
Ситуация меняется, если у базы есть утилизация для обычного барахла. Размещение тысяч обычных солнечных панелей параллельно с производством премиальных аналогов для космоса позволяет полезно утилизировать излишки до того, как буферы переполнятся.
Здесь задействован единственный шаг без циклов, поэтому расчет тривиален — искомое распределение совпадает с первой строкой матрицы Tquality(q).
«Отмывка»: чистый цикл рециклинга
Для большинства позиций переработчик инвертирует рецепт, возвращая 25% компонентов. Однако сырье (например, руда) рецепта крафта не имеет, либо процесс считается необратимым (выплавка, химия). В этом случае переработчик либо ничего не возвращает, либо в 25% случаев выдает исходный предмет обратно. Загнав такой предмет в бесконечный цикл через переработчики с модулями качества, мы заставим его циркулировать до тех пор, пока он не улучшится или не будет безвозвратно уничтожен.

Возьмем поток поступающих единиц freshunit в виде 5-компонентного вектора.
После прогона сумма вероятностей может упасть ниже единицы — разница уходит на уничтожение предметов. Например, потеря 75% массы при рециклинге эквивалентна умножению вероятностного вектора на 1/4.
Фильтрация по качеству также выражается через матричное умножение. Для отбора редкого грейда и выше:

Общий эффект одной итерации кольца выглядит следующим образом:

Рассмотрим сценарии вывода предметов из системы:
-
Сразу обладают нужным качеством: вектор

-
Прошли через рециклинг ровно один раз:

-
Прошли два цикла:

-
и так далее.
Совокупность исходов описывается бесконечным рядом:

Считать такие суммы в лоб неудобно. Хотя ряд сходится, нам нужен более элегантный путь без приближений.
Долгосрочная средняя производительность
Ключевое озарение: нас интересует не судьба отдельной вещицы, а стабильный поток. Случайные флуктуации сглаживаются на дистанции согласно закону больших чисел.
Вместо вероятностей для единичного предмета введем средний вектор потока для элементов, проходящих через узел системы. Для петли отмывки уравнения равновесия выглядят так:
-
Поступающее сырье
fresh; -
Материал после рециклинга
fromRecycling; -
Совокупный поток на разделителе
splitter; -
Извлеченные годные продукты
extracted; -
Остаток на переработку
toRecycle.
Считая fresh и q константами, запишем баланс системы:

Выполним преобразования:

Транспонируем векторы-строки в столбцы, приводя систему к классическому виду:

Введем обозначения:

Получаем матричное уравнение A·x=b, решаемое методом Гаусса. На выходе мы сразу получаем объемы переработки и искомый выход продукции.
При масштабировании системы до 100 единиц руды в секунду параметры будут такими:

Решение методом Гаусса дает следующий результат:

Переведя значения в минуты, получаем около 90 редких, 9 эпических и 1 легендарный предмет.
Другой пример:
-
Подаем на вход исключительно обычное сырье:

-
Перерабатываем с топовыми модулями: q = 24.8%.
-
На выходе отбираем только легендарные изделия.
Решение уравнения:

Для самоперерабатываемых ресурсов брутфорс-цикл требует в среднем

входных единиц на каждую легендарную единицу на выходе, что совпадает с расчетами других исследователей.
«Апсайклинг»: производство вперемешку с утилизацией
Если рецепт возвращает компоненты при переработке, мы можем связать сборочные линии с рециклерами в единый контур:

Сложные рецепты требуют разнородных ингредиентов, но мы примем за базовую единицу «комплект компонентов для одного цикла крафта». Скажем, автомат роботов потребляет 4 платы и 2 каркаса, но мы будем оперировать абстрактными «2 единицами сырья в секунду».
Мы измеряем долгосрочные потоки для каждого уровня качества отдельно по ингредиентам и продуктам, сохраняя удобные 5-компонентные векторы.
Векторы для сырья:
-
freshI— поступающие извне ресурсы; -
fromRecycling; -
totalI; -
extractedI; -
toCraft— отправляемые на сборку.
Для готовых изделий:
-
freshP; -
fromCrafting; -
totalP; -
extractedP; -
toRecycle— уходящие под нож.
Матрицы фильтрации качества формируются как дополнительные части единичной матрицы:

С учетом бонуса продуктивности (в нашем примере 0%) и различий в шансах апгрейда для сборки и рециклинга выстраиваем балансовые уравнения:

После серии подстановок и сокращений приходим к стандартному виду A·x=b:

Решив систему, находим totalP и вычисляем остальные показатели.
При масштабировании под базовые единицы параметры будут такими:

А результат Гаусса — таким:

Ограничивающим фактором здесь выступает скорость крафта обычных деталей (360 в минуту). Нормализуем значения:

Итог: линия выдает почти 2 легендарных робота в минуту, поглощая порядка 309 комплектов сырья.
В Space Age максимальный бонус продуктивности ограничен +300% за счет бесконечных исследований. При таком пороге переработка с возвратом компонентов в точности компенсирует затраты исходного сырья. Задействуем модули продуктивности в станках, полностью отказавшись там от модулей качества, и подадим в систему готовые изделия.

Модель выдает следующий прогноз:

Любопытно, что коэффициенты для промежуточных градаций качества выравниваются.
Максимальная продуктивность позволяет конвертировать обычный предмет в легендарный абсолютно без потерь сырья, пусть для этого и требуются массивы оборудования.
«Космическое казино»: рециклинг астероидов
Космические платформы в Space Age функционируют как автономные мини-базы. Вместо добычи руды они улавливают осколки астероидов, перерабатывая их в ресурсы для топлива, боеприпасов или отправки на планету.
Три разновидности обломков (металлические, углеродные и оксидные) дают разные материалы в зависимости от региона системы. Их можно дробить с шансом трансмутации в другой тип.

При наличии достаточного числа дробилок процесс описывается умножением вектора на матрицу 3×3:

С учетом модулей качества дробилки можно закольцевать. На выходе получаются легендарные базовые ресурсы для топового крафта.
Учитывая 3 типа обломков и 5 градаций качества, получаем 15 состояний. Математика аналогична предыдущим кейсам, но размерность матриц увеличивается до 15×15 с блочной структурой:

Система уравнений сохраняет прежний вид A·x=b:

Дробилки имеют по 2 слота под модули (максимум q = 12.4%), а сборщики астероидов лишены слотов, поэтому собранные осколки всегда обычного качества. Для орбиты Наувиса входной вектор выглядит так:

Если выводить из системы только легендарные оксидные обломки, а остальное пускать в переработку:

|
Для переработки |
|||||
|---|---|---|---|---|---|
|
|
⚀ |
⚁ |
⚂ |
⚃ |
⚄ |
|
Металлические |
1.31616 |
0.33791 |
0.13314 |
0.05286 |
0.0175 |
|
Углеродные |
1.11409 |
0.33244 |
0.13245 |
0.05277 |
0.01748 |
|
Оксидные |
0.91202 |
0.32697 |
0.13175 |
0.05268 |
0 |
|
Выводимые из системы |
|||||
|
|
⚀ |
⚁ |
⚂ |
⚃ |
⚄ |
|
Металлические |
0 |
0 |
0 |
0 |
0 |
|
Углеродные |
0 |
0 |
0 |
0 |
0 |
|
Оксидные |
0 |
0 |
0 |
0 |
0.01398 |
Выводы:
-
С ростом качества пропорции типов обломков стремятся к идеальной трети для каждого. Состав поступающего сырья слабо влияет на распределение легендарных осколков.
-
Выход легендарных предметов составляет около 1.4% от входящего объема. Это значительно эффективнее обычной отмывки, поскольку при дроблении астероидов сырье теряется лишь в 20% случаев (против 75% у стандартных предметов).
Альтернативные стратегии
Существуют и другие инженерные схемы (например, знаменитый The LDS Shuffle). Однако при любых сложностях с циклами можно применять описанный метод:
-
Представление потоков продукции в виде векторов.
-
Замена технологических процессов матричным умножением.
-
Сведение баланса к матричному уравнению.
-
Решение системы методом Гаусса.
Разработка интерактивного калькулятора
Проводить матричные расчеты вручную утомительно, поэтому делегируем задачу машине.
По привычке я выбрал Rust и библиотеку nalgebra. В ней отличные солверы линейных уравнений, но они завязаны на типы f32 или f64, тогда как стандартные задачи требуют максимальной точности.
Хотя числа с плавающей запятой дают хорошую аппроксимацию, накапливаемая погрешность смущала меня. Раз уж операции сводятся к базовой арифметике над рациональными числами, почему бы не получать абсолютно точный результат? num_rational закрывает эту потребность, а встроенного i128 с контролем переполнения хватает для матриц 15×15.
Написав движок на Rust, я задумался об удобном интерфейсе. Пользоваться консолью неудобно — нужен инструмент вроде Factoriolab, доступный прямо в браузере.
Компиляция Rust в WebAssembly — рабочий вариант, но писать интерфейс на Rust+wasm показалось избыточным, а связка JS-интерфейса с wasm-ядром через массивный API выглядела громоздко.
В итоге я переписал проект на TypeScript. Встроенный BigInt закрывает вопросы точности, в экосистеме Factoriolab уже есть проверенная библиотека rational, а написать собственную легкую матричную обертку оказалось несложно.
Для создания веб-интерфейса я выбрал легкий стек:
Теперь проект доступен по адресу factoqual.grebedoc.dev, а его исходный код выложен на codeberg.org/SimonSapin/factoqual.
Благодарности
Главным источником вдохновения для меня стали <труды>Дэниела Монтейро по математике качества в Factorio. Свой вклад внесли также Konage и редакторы вики Factorio. Тем не менее, мой подход на базе уравнений динамического равновесия предлагает принципиально иную альтернативу итеративному суммированию бесконечных рядов.
Физика пламени и векторы познания
Искусственный интеллект научился создавать ДНК
Блокнот: программа, которую Microsoft не решалась менять сорок лет
Время на исходе: что означает загадочная фраза о фотонах?
Интервью с Роджером Пенроузом: о Вселенной, сознании и пространстве-времени
Первая часть 16-й проблемы Гильберта: перебор схем 8-й степени с ограничениями
У астероида неожиданно нашли кольца: ученые в недоумении
Кого ждет рынок труда России в 2032 и 2040 годах: демографический анализ Росстата