Undertale In Extremis
TeX

Undertale In Extremis

Проект по организации компьютеров (Organização de Computadores). Симулятор RPG-сражений на ассемблере RISC-V в стиле Undertale.

Университетский проект (ORG 2026.1). Симулятор RPG-боёв и движок Монте-Карло, полностью написанный на ассемблере RISC-V.

Подготовка

Я создал движок RPG-боёв с нуля на чистом ассемблере RISC-V. Целью было написать три различных конечных автомата ИИ, запустить их в эмуляторе без графического интерфейса и провести 10 000 автоматических матчей, чтобы выяснить, какая стратегия окажется лучшей.

Бой строится вокруг строгой экономики действий. Очки магии (MP) восстанавливаются медленно, но их можно перезарядить с помощью определённых навыков.

Навык Стоимость Что делает
Атака бесплатно Бросок 1к20. < 10 — промах, 10+ — попадание, 20 — крит с двойным уроном.
Защита бесплатно Блокирует входящий урон. Высокие броски вызывают контратаку.
Абсолютная стойкость 20 MP Игнорирует кубик, гарантированный критический удар.
Кража души бесплатно Заклинатель получает 1–12 единиц урона отдачи, высасывает столько же MP у врага и получает в 4 раза больше для себя. Единственный способ обойти лимит в 100 MP.
Финальная казнь 150 MP Ядерный удар с уроном 800%. Не срабатывает, если у цели больше 50% HP.
Зеркальный щит 30 MP Отражает следующую входящую атаку обратно на атакующего.

Участники

Я написал три бота, каждый с совершенно разным «мозгом»:

Флауи (Хаос): Чистый RNG. Он ничего не оценивает и просто бросает случайное число, чтобы выбрать следующее действие.

decision_random:
  li a0, 6
  call randomizer  # выбирает число от 1 до 6, вот и вся стратегия
  j decision_end

Chara (Усердный): Она оптимизирует одну комбинацию: спамить Кражу души, пока не наберёт 150 MP, снизить HP врага ниже 50% и применить Финальную казнь. В коде стратегия также названа умной, но это не совсем так.

decision_smart:
  # eu escrevi essa estrategia
  # ela se consiste em usar roubo de alma até poder usar execute
  # só que pra isso também precisamos sobreviver e diminuir a vida do inimigo pra 50%
  # sem morrer
  ...
  li t6, 150        # preço do execute
  blt t4, t6, decision_smart_my_mana_is_low   # mp < 150? go farm
  bge a2, t6, decision_smart_enemy_hp_high    # enemy hp > 50? go bully
  j decision_smart_i_can_kill                 # otherwise, execute

decision_smart_my_mana_is_low:
  li a0, 4  # soul suck
decision_smart_enemy_hp_high:
  li a0, 2  # absolute grit
decision_smart_i_can_kill:
  li a0, 5  # final execution

Тоби (Жёсткий контрпик): Создан специально для фарма Chara. Он следит как за своим HP, так и за полосой MP врага. Если у него ниже 50 HP, а у врага 150 MP, он поднимает Зеркальный щит и позволяет ядерному удару вернуться обратно. В остальное время он чередует атаки и Кража души, а также может сам применить Финальную казнь при соблюдении условий.

decision_troll_checks:
  li t6, 50
  ble t5, t6, decision_troll_check_enemy_mp  # am I low enough to worry?
  j decision_troll_not_execute
decision_troll_check_enemy_mp:
  li t6, 150
  bge a3, t6, decision_troll_prepare_against_execute  # is enemy close to 150 mp?
  j decision_troll_not_execute
decision_troll_prepare_against_execute:
  li a0, 6  # mirror shield
  ret

Результаты тестирования

После проведения 10 000 матчей в Terminal с использованием rars.jar (JAR-файл от симулятора RARS на Java для RISC-V) были получены следующие результаты.

Персонаж Процент побед
Флауи ~52%
Тоби ~29%
Chara ~17%

Самый глупый ИИ одержал абсолютное большинство побед.

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

29% Тоби — результат успешной провокации Chara. Против Флауи ситуация иная: условие для щита требует, чтобы у врага было около 150 MP, а Флауи никогда целенаправленно не стремится к этому. Контрприём никогда не срабатывает, поэтому Тоби просто играет в свою базовую игру — атакует и использует Кража души, что не даёт ему реального преимущества перед хаосом.

Флауи выиграл 52% времени, потому что отсутствие стратегии невозможно последовательно контр-читать. Он никогда не получает урон отдачи, пытаясь подготовить масштабный ход; он просто случайным образом выбрасывает ценные действия. Оказывается, если весь ваш код основан на предсказании поведения врага, вы автоматически проигрываете врагу, который делает что-то без причины.

Это На Самом Деле Удивительно?

Вероятно, нет. Доминирование случайных агентов над детерминированными — хорошо задокументированный результат в стратегических симуляциях.

Симуляция Citadel, с которой я столкнулся в учебной группе, показала ту же закономерность: некоторые стратегии последовательно побеждали случайные, некоторые проигрывали им, а некоторые побеждали одних противников, но проигрывали другим. Динамика «камень-ножницы-бумага» присутствовала, но случайность всё равно удерживала свои позиции против большинства из них.

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

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

Другими словами: победа Флауи — это не столько открытие, сколько ограничение дизайна. Более умной Chara потребовалась бы более умная игра, чтобы доказать это.

Почему Эти Числа Не Полностью Надёжны

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

Матчи не изолированы. Оба игрока получают случайно назначенную стратегию в каждом матче, что означает, что проценты побед являются агрегированными по всем возможным парам, а не чистыми статистиками 1 на 1. 52% Флауи включают матчи, где Флауи сражался с другим Флауи, и в них один из них должен был победить. Чтобы действительно узнать, побеждает ли Chara Тоби или Флауи побеждает всех одинаково, нужно зафиксировать пару и провести каждую комбинацию отдельно.

Порядок ходов никогда не меняется. Игрок 1 всегда ходит первым. В системе, где один крит может закончить матч, право первого хода — реальное преимущество. Результаты этого вообще не учитывают.

RNG — это кастомный xorshift, инициализированный системным временем. Он достаточно хорош для университетского проекта, но не является статистически проверенным генератором. Если распределение результатов неравномерно по 6 возможным действиям, результаты Флауи напрямую зависят от этого, поскольку вся его стратегия заключается в вызове этой функции.

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

Chara, вероятно, была недостаточно хорошо спроектирована. В её стратегии нет защитного запаса. Если условия комбинации не выполнены и она получает большой урон, она всё равно продолжает фармить MP. Более надёжная версия переключалась бы в режим выживания при падении HP ниже определённого порога, что, вероятно, значительно улучшило бы её показатели.

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

Будущие Исследования

Очевидный следующий шаг — полностью отказаться от RARS. Эта версия работает на эмуляторе RISC-V на Java, что накладывает жёсткий потолок на скорость симуляции. Вторая версия, ориентированная на реальный скомпилированный RISC-V с системными вызовами Linux вместо ecalls RARS, должна быть значительно быстрее, что очень важно, когда количество матчей начинает исчисляться миллионами.

Помимо производительности, более интересные направления лежат в области дизайна:

Больше игроков в матче. Сейчас это всегда 1 на 1. Добавление третьего или четвёртого участника полностью меняет стратегический ландшафт. Внезапно контр-стратегия должна учитывать атаки с двух сторон одновременно, и чистая случайность становится менее устойчивой.

Машинное обучение внутри ассемблера. Идея заключается в реализации минимального алгоритма ML непосредственно на RISC-V, используя матричные операции, чтобы бот мог обновлять свои собственные веса на основе результатов матчей. Никаких внешних библиотек, никакой высокоуровневой среды выполнения, только целочисленная матричная математика в регистрах. Будет ли это практично или просто интересной болью для реализации — часть привлекательности.

Больше стратегий и более глубокий движок. Текущее пространство действий слишком мало, чтобы какая-либо стратегия могла значительно оторваться от случайности. Более прямое вдохновение из World of Warcraft (перезарядки, компромиссы ресурсов, эффекты позиционирования, более разнообразные взаимодействия навыков) дало бы хорошо продуманным стратегиям реальное пространство, чтобы доказать своё превосходство над хаосом.

Инфраструктура для тестирования уже есть. Узким местом является глубина игры, чтобы результаты имели значение.

Запуск

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

make simulate