Undertale In Extremis
TeX

Undertale In Extremis

Projekt z Organizacji Komputerów. Symulator walki RPG w stylu Undertale w asemblerze Risc-V.

Projekt uniwersytecki (ORG 2026.1). Symulator walki RPG i silnik Monte Carlo napisany w całości w asemblerze RISC-V.

Konfiguracja

Zbudowałem silnik walki RPG od zera w czystym asemblerze RISC-V. Celem było napisanie trzech różnych maszyn stanów AI, wrzucenie ich do emulatora bez interfejsu graficznego i przeprowadzenie 10 000 automatycznych pojedynków, aby sprawdzić, która strategia okaże się najlepsza.

Walka opiera się na ścisłej ekonomii akcji. MP regeneruje się powoli, ale można je przeładować za pomocą określonych umiejętności.

Umiejętność Koszt Działanie
Atak darmowy Rzut 1k20. < 10 – pudło, 10+ – trafienie, 20 – krytyk za podwójne obrażenia.
Obrona darmowa Blokuje przychodzące obrażenia. Wysokie rzuty wywołują kontratak.
Absolutna Determinacja 20 MP Pomija rzut kostką, gwarantując krytyczne trafienie.
Kradzież Duszy darmowa Zadaje sobie 1–12 obrażeń odrzutu, wysysa tę samą ilość MP wroga i zyskuje 4× tej wartości dla siebie. Jedyny sposób na przekroczenie limitu 100 MP.
Ostateczna Egzekucja 150 MP Atak o 800% obrażeń. Nie udaje się, jeśli cel ma powyżej 50% HP.
Lustrzana Tarcza 30 MP Odbija następny przychodzący atak z powrotem na atakującego.

Uczestnicy

Napisałem trzy boty, każdy z zupełnie innym mózgiem:

Flowey (Chaos): Czysty RNG. Nic nie ocenia, po prostu losuje liczbę, aby wybrać następny ruch.

decision_random:
  li a0, 6
  call randomizer  # wybiera liczbę od 1 do 6, to cała strategia
  j decision_end

Chara (Perfekcjonistka): Optymalizuje pod jedną kombinację: spamuje Kradzież Duszy, aż osiągnie 150 MP, obniża HP wroga poniżej 50% i wykonuje Ostateczną Egzekucję. W kodzie strategia nazywa się też inteligentną, ale nie do końca taka jest.

decision_smart:
  # napisałem tę strategię
  # polega na używaniu kradzieży duszy, aż będzie można użyć egzekucji
  # ale do tego trzeba też przetrwać i obniżyć HP wroga do 50%
  # nie umierając
  ...
  li t6, 150        # cena egzekucji
  blt t4, t6, decision_smart_my_mana_is_low   # mp < 150? idź farmić
  bge a2, t6, decision_smart_enemy_hp_high    # hp wroga > 50? idź dręczyć
  j decision_smart_i_can_kill                 # w przeciwnym razie – egzekucja

decision_smart_my_mana_is_low:
  li a0, 4  # kradzież duszy
decision_smart_enemy_hp_high:
  li a0, 2  # absolutna determinacja
decision_smart_i_can_kill:
  li a0, 5  # ostateczna egzekucja

Toby (Twardy Kontr): Zbudowany specjalnie do farmienia Chary. Obserwuje zarówno własne HP, jak i pasek MP wroga. Jeśli ma poniżej 50 HP, a wróg ma 150 MP, podnosi Lustrzaną Tarczę i pozwala, by atak wrócił. Poza tym oknem miesza ataki i Kradzież Duszy, a także sam może odpalić Ostateczną Egzekucję, gdy spełnione są warunki.

decision_troll_checks:
  li t6, 50
  ble t5, t6, decision_troll_check_enemy_mp  # czy jestem wystarczająco nisko, by się martwić?
  j decision_troll_not_execute
decision_troll_check_enemy_mp:
  li t6, 150
  bge a3, t6, decision_troll_prepare_against_execute  # czy wróg jest blisko 150 mp?
  j decision_troll_not_execute
decision_troll_prepare_against_execute:
  li a0, 6  # lustrzana tarcza
  ret

Wyniki testów porównawczych

Po przeprowadzeniu 10 000 pojedynków w Terminalu przy użyciu rars.jar – pliku JAR z symulatora RISC-V RARS – wyniki były następujące.

Postać Wskaźnik wygranych
Flowey ~52%
Toby ~29%
Chara ~17%

Najgłupsze AI wygrało absolutną większość gier.

17% wskaźnik wygranych Chary ujawnia problem ze sztywnymi, zachłannymi kombinacjami. Aby osiągnąć 150 MP, musi przyjmować obrażenia odrzutu z Kradzieży Duszy. Często Toby po prostu podnosi tarczę, a skrypt Chary zmusza ją do dalszego drenowania własnego zdrowia, aż dosłownie się zabije, zanim zdąży rzucić swój ostateczny atak.

29% Toby'ego wynika z udanego wabienia Chary. Przeciwko Flowey'owi jest inaczej: warunek tarczy wymaga, by wróg miał blisko 150 MP, a Flowey nigdy celowo do tego nie dąży. Kontra nie uruchamia się, więc Toby gra swoją domyślną grę – atakuje i kradnie dusze – co nie daje mu realnej przewagi nad chaosem.

Flowey wygrał w 52% przypadków, ponieważ brak strategii jest niemożliwy do konsekwentnego kontrowania. Nigdy nie przyjmuje obrażeń odrzutu, próbując przygotować wielki ruch; po prostu przypadkiem wykonuje wartościowe akcje. Okazuje się, że jeśli cały twój kod opiera się na przewidywaniu zachowania wroga, automatycznie przegrywasz z wrogiem, który robi rzeczy bez powodu.

Czy to właściwie zaskakujące?

Prawdopodobnie nie. Dominacja losowych agentów nad deterministycznymi to dobrze udokumentowany wynik w symulacjach strategicznych.

Symulacja Citadel, na którą trafiłem w grupie studyjnej, dała ten sam wzorzec: niektóre strategie konsekwentnie pokonywały losowe, niektóre były przez nie miażdżone, a inne pokonywały pewnych przeciwników, ale przegrywały z innymi. Dynamika kamień-papier-nożyce była obecna, ale losowość wciąż utrzymywała swoją pozycję wobec większości z nich.

Różnica polega na tym, że w bogatszym środowisku (więcej strategii, więcej zmiennych decyzyjnych, więcej sposobów dla inteligentnego agenta na wykorzystanie losowego) wyniki mają tendencję do większego rozrzutu. Dobrze dostrojona strategia deterministyczna może wypracować sobie wiarygodną przewagę, jeśli przestrzeń akcji daje jej wystarczająco dużo do wykorzystania.

Tutaj prawdopodobnie nie daje. Przy zaledwie sześciu możliwych akcjach i systemie walki, w którym jeden szczęśliwy krytyk może zakończyć pojedynek niezależnie od strategii, różnica między starannym planowaniem Chary a losowym wciskaniem przycisków Floweya po prostu nie jest aż tak duża. Kombinacja Chary wymaga kilku tur przygotowania, a RNG ma mnóstwo okazji, by zabić ją, zanim tam dotrze. Przestrzeń akcji może być po prostu zbyt mała i zbyt zmienna, by zachłanna strategia mogła konsekwentnie przewyższać chaos.

Innymi słowy: zwycięstwo Floweya to mniej odkrycie, a bardziej ograniczenie projektowe. Mądrzejsza Chara potrzebowałaby mądrzejszej gry, by się wykazać.

Dlaczego te liczby nie są w pełni wiarygodne

Zanim wyciągniemy wnioski z testów porównawczych, warto wskazać kilka strukturalnych obciążeń.

Pojedynki nie są izolowane. Obaj gracze otrzymują losowo przypisaną strategię w każdej walce, co oznacza, że wskaźniki wygranych są zagregowane dla wszystkich możliwych par, a nie czystymi statystykami 1v1. 52% Floweya obejmuje walki, w których Flowey walczył z innym Floweyem, a w tych jeden z nich musiał wygrać. Aby faktycznie wiedzieć, czy Chara pokonuje Toby'ego, czy Flowey pokonuje wszystkich jednakowo, należałoby ustalić pary i przeprowadzić każdą kombinację osobno.

Kolejność tur nigdy się nie zmienia. Gracz 1 zawsze wykonuje ruch pierwszy. W systemie, w którym jeden krytyk może zakończyć walkę, bycie pierwszym to realna przewaga. Wyniki w ogóle tego nie uwzględniają.

RNG to niestandardowy xorshift inicjowany czasem systemowym. Działa wystarczająco dobrze dla projektu uniwersyteckiego, ale nie jest statystycznie zweryfikowanym generatorem. Jeśli rozkład wyników dla 6 możliwych akcji jest nierównomierny, wyniki Floweya są bezpośrednio dotknięte, ponieważ cała jego strategia polega na wywoływaniu tej funkcji.

Przestrzeń akcji jest bardzo mała. Sześć możliwych akcji oznacza, że każda strategia ma ograniczone możliwości odróżnienia się od chaosu. W głębszym systemie kombinacja Chary mogłaby być trudniejsza do przerwania lub mogłyby istnieć opcje regeneracji zmniejszające koszt jej przygotowania. Tutaj gra jest na tyle karząca, że obrażenia odrzutu często kończą rozgrywkę, zanim nastąpi wypłata.

Chara prawdopodobnie nie została wystarczająco dobrze zaprojektowana. Jej strategia nie ma defensywnego zabezpieczenia. Jeśli warunki kombinacji nie są spełnione, a ona przyjmuje duże obrażenia, po prostu dalej farmi MP. Bardziej solidna wersja przełączyłaby się w tryb przetrwania poniżej pewnego progu HP, co prawdopodobnie znacząco poprawiłoby jej wyniki.

Wyniki są kierunkowo interesujące, ale należy je traktować jako migawkę tej konkretnej konfiguracji, a nie ogólne stwierdzenie o strategii kontra losowość.

Przyszłe badania

Oczywistym następnym krokiem jest całkowite odejście od RARS. Ta wersja działa na emulatorze RISC-V opartym na Javie, co nakłada twardy limit na szybkość symulacji. Druga wersja, przeznaczona dla prawdziwego skompilowanego RISC-V z wywołaniami systemowymi Linux zamiast ecall RARS, powinna być dramatycznie szybsza, co ma ogromne znaczenie, gdy liczba walk zaczyna sięgać milionów.

Poza wydajnością, bardziej interesujące kierunki leżą po stronie projektowej:

Więcej graczy na walkę. Obecnie zawsze jest to 1v1. Dodanie trzeciego lub czwartego uczestnika całkowicie zmienia krajobraz strategiczny. Nagle kontrstrategia musi uwzględniać ataki z dwóch kierunków jednocześnie, a czysta losowość staje się trudniejsza do utrzymania.

Uczenie maszynowe w asemblerze. Pomysł polega na zaimplementowaniu minimalnego algorytmu ML bezpośrednio w RISC-V, przy użyciu operacji na macierzach, aby bot mógł aktualizować własne wagi na podstawie wyników walk. Bez zewnętrznych bibliotek, bez środowiska uruchomieniowego wysokiego poziomu – tylko matematyka na macierzach całkowitych w rejestrach. To, czy jest to praktyczne, czy tylko interesująco bolesne do zaimplementowania, stanowi część atrakcyjności.

Więcej strategii i głębszy silnik. Obecna przestrzeń akcji jest zbyt mała, by jakakolwiek strategia mogła znacząco wyprzedzić losowość. Czerpanie większej bezpośredniej inspiracji z World of Warcraft (czas odnowienia, kompromisy zasobów, efekty pozycjonowania, bardziej zróżnicowane interakcje umiejętności) dałoby dobrze zaprojektowanym strategiom rzeczywistą przestrzeń do wykazania się ponad chaosem.

Infrastruktura testów porównawczych już istnieje. Wąskim gardłem jest to, by gra była wystarczająco głęboka, aby wyniki miały znaczenie.

Uruchamianie

Aby skompilować asembler i uruchomić silnik lokalnie:

make simulate