Własne boty do Magica: liga, zapadka i skąd wiemy, że grają lepiej
Wpis pisany we dwóch. Sekcje „Po co mi to” i „Jak do tego doszedłem” napisał Michał. Resztę napisał Claude na podstawie dokumentacji projektu, dzienników kolejki zadań i historii gita z 26–27 października 2026. Michał zlecał i decydował, Claude koordynował, a większość kodu napisali agenci GLM.
Ten wpis opisuje boty, które grają w Magic: the Gathering przeciwko sobie, a na końcu przeciwko człowiekowi: jak są zbudowane, jak mierzymy, czy zmiana je poprawia, i co wyszło z ostatnich eksperymentów. O grze w przeglądarce, w której się z nimi gra, i o agentach GLM, którzy tę pracę wykonali, jest osobny wpis.
Po co mi to
Boty do Magica robię głównie dla zabawy. Nie ma tu żadnego zysku finansowego, który mógłbym z tego wyciągnąć.
Racjonalizuję sobie, że uczę się przydatnych rzeczy, że poznam wzorce, których użyję w innych projektach. Zwłaszcza że Software 2.0 i 3.0 Karpathy’ego (programowanie uczeniem maszynowym i dużymi modelami językowymi) naprawdę pobudza moją wyobraźnię: wykład Karpathy’ego.
Racjonalizuję sobie też, że budując te boty i te sieci neuronowe, będę potem w stanie uogólnić je na ogólny osąd i podejmowanie decyzji. Ale przyszła mi myśl, że to ogromna racjonalizacja. Firmy takie jak Anthropic i OpenAI nie potrafią tego zrobić. Jedyna reguła, która wydaje się działać, to to, że większa skala daje lepszą inteligencję, czyli lepszy osąd i lepsze decyzje w różnych procesach biznesowych.
Może jest bardzo mała szansa, ale może znajdę jakiś proces biznesowy (może bardzo mały albo dobrze wyspecyfikowany) i uda mi się zbudować rozwiązanie, które go zautomatyzuje. Mały LLM albo model uczenia maszynowego, albo Frankenstein złożony z kilku wzorców (LLM + sieci neuronowe + zwykły deterministyczny kod). Taki model mógłby zastąpić duży model bazowy jak Opus, bo nawet gdyby Opus się sprawdzał, takie rozwiązanie mogłoby być szybsze i bardziej deterministyczne. Gdyby udało mi się zrobić model, który popełnia mniej błędów, prawie zero, i jest tańszy, to przede wszystkim tańszy, nie musiałbym używać Opusa.
Bałbym się też posadzić Opusa całkiem za kierownicą i go nie kontrolować. Jeśli zbuduję własne rozwiązanie z własnymi zabezpieczeniami, wyobrażam sobie, że powoli będę mu ufał coraz bardziej i w końcu w pełni oddam mu jakiś proces biznesowy. Oczywiście są różne wzorce, które pozwalają człowiekowi zostać w pętli, ale wtedy przynajmniej zabiera mu to mniej uwagi i energii.
Boty i skąd wiemy, że grają lepiej
Co tu właściwie gra
Format to Sealed: każdy gracz dostaje sześć losowych boosterów, buduje z nich talię 40 kart i gra jedną partię (Bo1). Wszystkie karty pochodzą z jednego dodatku, Fractured Reality (FRA), czyli z puli około 295 kart. Nie korzystamy z cudzego silnika. Projekt ma własny silnik reguł, ograniczony do kart z tej puli, ale z pełnymi zasadami: tury, priorytet, stos, płacenie many, walka, zdolności wyzwalane, efekty zastępujące. Forge, otwarty silnik Magica, służy tylko do porównań, jako wyrocznia w testach różnicowych.
Każdy bot składa się z dwóch algorytmów. Budowniczy robi talię z puli kart, a gracz podejmuje decyzje w partii. Botów nie projektuje się jednym sposobem. Powstały rodziny, z których każda ma osobną linię rozwoju:
- heur: ręcznie pisane heurystyki plus przeszukiwanie z wyprzedzeniem (lookahead). Bot losuje kilka możliwych „światów”, czyli to, co przeciwnik może mieć w ręce, rozgrywa w nich kilka ruchów do przodu i wybiera opcję, która średnio wypada najlepiej.
- heur-rules i pure-rules: same reguły bez przeszukiwania.
- neural50: mała sieć neuronowa, która ocenia pozycję.
- evo: programy w małym języku, które ewoluują.
- cmaes: wagi heurystyk strojone algorytmem CMA-ES.
Heurystyki nie są twardymi regułami typu „jeśli X, to zawsze Y”. Każda reguła daje opcji punkty (waga × cecha sytuacji), a wagi się stroi. Nawet reguła w rodzaju „z jedną ziemią w ręce bierz mulligana” to tylko duża kara, a nie zakaz, bo zdarzają się ręce, które z jedną ziemią warto zostawić.
Liga: jedynym sędzią jest turniej
Wszystkie rodziny grają w jednej lidze na domowym serwerze. Liga działa bez przerwy. W pomiarze z 24 października rozegrała 888 tys. partii w 34 godziny, czyli około 627 tys. na dobę. Format jest „duplikatowy”: bot A dostaje pulę X, bot B pulę Y, a potem zamieniają się pulami przy tym samym tasowaniu. Dzięki temu szczęście do kart w dużej mierze się znosi.
Dwie zasady Michała są tu nienegocjowalne. Po pierwsze, liczy się tylko wynik bezpośrednich pojedynków, a nie to, czy pomysł brzmi dobrze. Po drugie, słabszych rodzin się nie wyrzuca: każda dalej ewoluuje we własnej linii. Część z nich jest okresowo zamrażana, ale nie usuwana.
Zapadka: małe kroki, każdy zmierzony
Do zmian wprowadzanych przez agentów służy narzędzie nazwane zapadką. Agent robi jedną małą zmianę (jedną do pięciu reguł albo kilka wag) i mierzy ją przeciwko obecnej wersji. Obie wersje grają te same rozdania przeciwko stałemu panelowi przeciwników. Silnik jest deterministyczny, więc w rozdaniach, w których zmiana nic nie zmienia, różnica wynosi dokładnie zero. Szum pochodzi tylko z rozdań, w których zmiana zadziałała.
Werdykt:
- ACCEPT, gdy prawdopodobieństwo, że nowa wersja jest lepsza, wynosi co najmniej 0,8 po minimum 512 rozdaniach;
- REJECT przy 0,3 lub mniej;
- NEUTRAL pomiędzy. Takiej zmiany nie wyrzuca się, tylko odkłada, żeby później sprawdzić ją w połączeniu z innymi.
Kilka przyjętych kroków po 0,8 to dużo okazji, żeby przepuścić szum. Dlatego co jakiś czas jest punkt kontrolny: aktualna wersja gra przeciwko ostatniemu punktowi kontrolnemu na świeżych rozdaniach i musi wygrać z pewnością 0,95. Jeśli nie wygra, łańcuch cofa się do tego punktu.
Ostatnie wyniki, także te nieudane
Gra wokół odpowiedzi przeciwnika (przyjęte). Domyślnie w symulacjach bota przeciwnik jest bierny: oddaje priorytet i niczego nie zagrywa, więc jego ręka nie ma znaczenia. Nowy kod dodaje „wrogie światy”, w których przeciwnik trzyma konkretną odpowiedź: usunięcie stwora, trik bojowy albo kontrę, w swoich kolorach i za manę, którą ma otwartą. Bot dostaje miękką karę za ruchy, które w takich światach wypadają dużo gorzej. Kara zależy od roli: kiedy bot jest stroną atakującą albo przegrywa, gra wokół odpowiedzi mniej albo wcale.
Pierwsza runda pomiarów była neutralna albo gorsza. Ten sam pomysł bez uwzględniania roli dał −0,49 punktu. Druga runda, już z rolą, przeszła potwierdzenie na 2000 świeżych rozdań: +0,51 punktu (95% przedział [+0,06; +0,94], P = 0,986). Sprawdziliśmy też, czy zysk nie bierze się z pasywności, czyli czy bot nie wygrywa częściej tylko dlatego, że wstrzymuje się z zagrywaniem. Długość partii, otwarte ziemie i odsetek tur, w których bot wstrzymał zagrywkę, nie ruszyły się o więcej niż 0,2 punktu procentowego. Koszt obliczeniowy wzrósł o 5–10% na decyzję. Te parametry wchodzą teraz do ligi.
Uczenie sieci od heurystycznego bota (zamknięte). Sieć neural50 uczyliśmy naśladować oceny przeszukiwania bota heur. Offline wszystko wyglądało dobrze: im więcej sygnału od nauczyciela, tym lepsze dopasowanie. W lidze nic z tego nie wynikło. Obie wersje wyszły neutralnie (−0,31 przy P = 0,35 i +0,31 przy P = 0,69), a obie przegrywały z najsłabszym członkiem panelu. To była już druga runda z tym samym wynikiem, więc ten kierunek jest zamknięty.
Wstrzymywanie trików bojowych (odrzucone). Michał zauważył w grze, że bot zagrał stwora z flash (Teyo) przed walką, czyli zdradził swój trik, zanim zaatakował. Agent odtworzył dokładnie tę partię i dodał regułę „trzymaj trik na po blokach”. W tej partii bot zagrał już jak należy, ale w lidze obie wersje przegrały: −0,59 (P = 0,04) i −0,42 (P = 0,11). Prawdopodobnie wynika to z tego, że nasze boty nie wyceniają ukrytej informacji. Trik zagrany jawnie przed walką zmienia ich decyzje o blokach na naszą korzyść. Przeciwko człowiekowi mogłoby wyjść inaczej, ale liga mierzy grę przeciwko botom.
Budowniczy talii: eksperyment kontrastowy. Zamiast pytać, co zmienić w budowniczym, zmierzyliśmy, co się dzieje, kiedy wymusimy na nim jedną konkretną zmianę: inną parę kolorów, inną krzywą many, jedno usunięcie stwora mniej, i tak dalej. Najpierw był test kontrolny (bot przeciwko samemu sobie): 100 par, 40 tys. partii. Przedziały ufności pokryły prawdziwą wartość w 95 przypadkach na 100, czyli tyle, ile powinny. Właściwy pomiar to 200 par i 48,6 tys. partii. W sześciu z ośmiu typów zmian budowniczego nie dało się średnio pobić. Wymuszenie innej pary kolorów kosztowało średnio −2,9 punktu, ale z dużym rozrzutem między pulami: na niektórych pulach budowniczy wybiera parę źle, na większości dobrze. Całość zajęła około 61 godzin procesora.
GLM przeciwko Opusowi. Z ciekawości te same modele językowe zagrały jako gracze, decyzja po decyzji: trzy mecze po 10 partii, łącznie 6 : 24 dla Opusa. Około 6% odpowiedzi GLM było niepoprawnych (zły numer opcji). Według cennika Opus kosztowałby 15,05 USD za mecz, a GLM 1,33 USD. Oba modele są w abonamentach, więc tych kwot nikt nie zapłacił. Z tej okazji przyjęliśmy zasadę: każdy eksperyment z modelem zapisuje talie, ziarna losowania i pełny zapis partii, żeby dało się go obejrzeć. Pierwszych 20 partii nie dało się odtworzyć, bo nikt nie zapisał talii zbudowanych przez model.
Jak do tego doszedłem
Spróbuję spisać kamienie milowe, które pamiętam, po kolei.
W pewnym momencie przepisaliśmy silnik symulujący reguły Magica z Javy na Rusta i to przyspieszyło go kilkukrotnie.
Próbowałem przenieść go na CUDA, ale dowiedziałem się, że GPU nie uruchomi dowolnego kodu tak jak CPU. Nadal nie do końca rozumiem dlaczego, ale ma to coś wspólnego z tym, że stan naszej symulacji nie ma stałego rozmiaru i jest za duży. W skrócie: GPU nadaje się tylko do mnożenia macierzy, czyli innymi słowy do tej samej operacji wykonywanej tysiące razy na różnych zmiennych, a nie do wykonywania różnych operacji.
Dopisek od Claude’a. Prototyp z gałęzi CUDA rzeczywiście musiał najpierw zmienić silnik: stan partii o stałym rozmiarze (744 bajty), efekty kart jako dane zamiast kodu, i tylko uproszczony podzbiór 42 kart. Na takim podzbiorze był około 9 razy szybszy na rdzeń niż silnik w Ruście. Pełne FRA z dowolnymi efektami kart w ten format się nie mieści, co zgadza się z tym, co opisuje Michał.
Duża sieć neuronowa, która uczy się, grając sama ze sobą
Najnowsza rzecz: zobaczyłem, jak ludzie robią nadludzkie boty do StarCrafta, i zainspirował mnie ten, który właśnie wyszedł dla Brood War, bo nie zrobiła go żadna wielka firma (ten do StarCrafta 2 powstał kilka lat temu). Uczy się sam: grał sam ze sobą w mnóstwo partii i nie jest taki duży, ma podobno tylko 300 milionów parametrów.
Przyjrzałem się temu i, nie rozumiejąc tego, kazałem AI napisać kod w tym stylu, tylko z milionem parametrów, i liczę, że to wystarczy. Liczę na to, że Magic jest prostszy niż StarCraft.
Widzę też, że AI tego typu są dla gier czasu rzeczywistego: StarCraft, StarCraft 2 i Dota, ale nie dla gier turowych, poza szachami i go. Te nie pobudzają zbytnio wyobraźni, ale chyba powinienem się im przyjrzeć, bo może są tam ciekawe techniki.
AI nie bardzo chciało to robić, bo powiedziało mi: „Oj, tu jest za dużo ukrytej informacji”. Człowieku, w StarCrafcie też masz od cholery ukrytej informacji. Nie wiesz, co przeciwnik w tej chwili robi. A AI broni się, nawet kiedy go o to nie pytam: „Nie, nie, ale w StarCrafcie możesz wysłać zwiadowcę”. Powinieneś sprawdzić, jak to robią boty do pokera.
Myślę, że dzięki temu zobaczyłem jeden z moich błędów: dużo moich botów do Magica próbowało jakoś obchodzić ukrytą informację przeciwnika. Może lepiej po prostu wbudować obsługę ukrytej informacji w sieć neuronową. Jeśli sieć będzie wystarczająco duża i wytrenowana na wystarczającej ilości danych, wykształci naturalny mechanizm radzenia sobie z tym, na przykład z tym, jakie karty ma przeciwnik. Mam nadzieję, że ponieważ zawężamy przestrzeń badań do jednego dodatku naraz, milion parametrów wystarczy, bo nie mam mocy obliczeniowej na nic większego.
Dopisek od Claude’a. Bot do Brood War, o którym pisze Michał, to Pluto: jedna sieć trenowana przez grę samej ze sobą, grająca całą partię bez skryptów, z wnioskowaniem na CPU. Ten do StarCrafta 2 to AlphaStar od DeepMind. Plan rodziny
rlbierze z AlphaStara układ treningu: najpierw naśladowanie, potem uczenie przez wzmacnianie, potem liga. Bierze też sposób na ukrytą informację: sieć, która gra, widzi tylko to, co widzi gracz (pilnuje tego test), ale ocena pozycji używana wyłącznie w treningu może widzieć rękę przeciwnika. Boty do pokera (Libratus, Pluribus, ReBeL) idą dalej i jawnie liczą rozkład tego, co przeciwnik może mieć. To dobry kierunek do przeczytania, jeśli sama sieć sobie nie poradzi.
Jak ulepszyć dużą sieć
Teraz chcę sprawdzić, jak podzielić tego dużego bota na mniejsze. Myślałem, że może dałoby się mieć osobne małe sieci neuronowe do różnych części gry:
- jedna sieć do mulligana,
- jedna do ataków,
- jedna do bloków,
- jedna do ogólnej strategii.
Byłoby świetnie, ale tak może być trudniej trenować, bo informacja musi płynąć z dalszych etapów do wcześniejszych. To może być trudne.
Potem pomyślałem: hej, duże LLM-y mają mixture of experts i jakoś zostały wytrenowane. Może sprawdzę, jak to zrobili, i zrobię tak samo. Potem pomyślałem: hej, w LLM-ach, których będę używał, było ostatnio dużo innowacji. Może jakieś, jak TurboQuant, pomogą mi oszczędzić moc obliczeniową przy treningu, więc spróbuję się temu przyjrzeć.
Dopisek od Claude’a. W mixture of experts eksperci nie dostają z góry ról. Router i eksperci trenują się razem, jako jedna sieć, a specjalizacja wychodzi sama. To rozwiązuje problem przepływu informacji, o którym pisze Michał: jest jeden gradient przez całość. Bliższy jego pomysłowi jest wariant ze wspólnym tułowiem i osobnymi głowami dla mulligana, ataków i bloków: każda głowa ma swoje wyjście, ale wszystkie uczą się z jednej wspólnej reprezentacji stanu. Nie jest to tańsze obliczeniowo niż jedna sieć, ale rzadkie decyzje, takie jak mulligan, uczą się z danych z całej partii, a nie tylko z jednej decyzji na grę. W porównaniu z jedną siecią bez podziału wspólny tułów nie oszczędza ani partii, ani obliczeń. Daje modułowość: każdy typ decyzji da się zmierzyć osobno, dotrenować tanio samą głowę (np. tylko mulligana) i dać mu własny format odpowiedzi. Mixture of experts daje coś innego: więcej wag przy tym samym koszcie jednej decyzji, bo każda decyzja przechodzi tylko przez część ekspertów. To pasuje do naszego ograniczenia (liga na CPU, małe GPU), ale korzyści MoE pokazano przy dużej skali. Przy milionie wag eksperci byliby malutcy, więc to ma sens dopiero przy większej sieci, po zmierzeniu zwykłej. TurboQuant służy głównie do kompresji pamięci podręcznej przy wnioskowaniu, a nie do treningu. Przy milionie wag kwantyzacja przyda się raczej w lidze, żeby bot szybko grał na CPU, tak jak Pluto z wagami int8.
Co z tego zostaje
Najbardziej przydatne okazały się nie same boty, tylko pomiary. Liga i zapadka mówią, czy bot gra lepiej, zanim ktokolwiek zacznie się cieszyć. Dwa z ostatnich pomysłów na boty nie przeszły i oba zostały zapisane razem z liczbami.
TODO: do zbadania
- RuneScape Bench: MMOs for Agents (Max Bittker, Recurse Center).
- Jak boty do pokera (Libratus, Pluribus, ReBeL) radzą sobie z ukrytą informacją.
- Techniki z botów do gier turowych: szachy i go.
- Mixture of experts i wspólny tułów z osobnymi głowami jako sposób na podział dużej sieci.
- TurboQuant i inne nowości z LLM-ów, które mogłyby oszczędzić moc obliczeniową.