Матеріал спільноти · Claude Code × Godot

Tekken 3 з нуля менш ніж за тиждень: вайб-реверсинг PlayStation-гри і ремейк на Godot

Автор: n0madic · 1 жовтня 2026

Авторська ілюстрація до теми: Tekken 3 з нуля менш ніж за тиждень: вайб-реверсинг PlayStation-гри і ремейк на Godot
Ілюстрація, створена за допомогою ШІ · Вайб-Кодинг
Опубліковано з дозволу автора. Авторський текст і порядок розділів збережено; змінено лише вебоформлення. Першоджерело на Telegraph.

Як ми з Claude Code розібрали диск 1998 року, знайшли в оригіналі понад 60 багів і зібрали повноцінний ремейк, який збігається з оригіналом кадр у кадр.

З чого все почалося

26 вересня я дав Claude Code одне велике завдання. Звучало воно приблизно так: «Створи з нуля на сучасному рушії ремейк Tekken 3 для PSX. Для початку розбери оригінал на дисках, задокументуй усі формати даних, 3D-моделі, анімацію, звуки, ефекти і всю ігрову логіку».

Код я майже не писав. Моя роль зводилася до трьох речей: ставити цілі, ухвалювати рішення на розвилках і дивитися на результат очима гравця. Усе інше робив агент. Нижче розповідаю, як це виглядало зсередини, де ШІ виявився на висоті, а де помилявся.

Для масштабу. У репозиторії зараз приблизно 45 тисяч рядків GDScript у 215 файлах, 35 тисяч рядків Python-інструментів реверсингу, ще 5 тисяч у конвертері дисків, 35 документів Markdown про формати й логіку гри та понад 170 тестів. Головна сесія Claude Code, у якій відбулися дослідження і перші п'ять віх, налічує понад 46 тисяч повідомлень агента і близько 23 тисяч викликів інструментів.

Етап 1. Вайб-реверсинг: як розібрати гру без вихідного коду

Реверсинг гри для PlayStation складається з двох задач. Перша: зрозуміти формати файлів (моделі, анімації, текстури, звук, відео). Друга, значно важча: зрозуміти логіку, тобто що саме відбувається за один кадр бою, як рахується удар, як «думає» CPU-суперник.

Інструменти. Агент сам розгорнув конвеєр:

  • Ghidra у headless-режимі з сигнатурами бібліотеки PsyQ (вона розпізнала 929 бібліотечних функцій) і скрипти, що імпортують бінарний файл та всі десять оверлеїв гри;
  • Unicorn (емулятор MIPS-процесора) плюс власна програмна модель співпроцесора GTE, який у PlayStation рахує 3D-геометрію;
  • Python-скрипти, що розпаковують диски у робоче дерево, і декомпільований C.

Головна ідея методу. Нам не потрібен повний емулятор PlayStation. Потрібна можливість запускати окремі функції оригінального машинного коду й порівнювати їхній результат з нашою реалізацією. Тому для кожної підсистеми агент ішов за одним циклом: прочитати декомпіляцію, написати Python-порт, прогнати оригінальну функцію в Unicorn на тисячах випадкових вхідних даних, порівняти пам'ять побайтово. Поки є хоч один розбіжний байт, формат або правило в документації мають статус inferred, а не confirmed.

Так, наприклад, анімаційний конвеєр (49 каналів, сплайни) збігається з грою біт у біт на 314290 кадрах усіх 22 банків рухів. Камера звірена на 86058 кадрах. Весь ШІ суперника портований і перевірений у 19 сценаріях без жодної розбіжності.

Проблеми реверсингу

  • Декомпілятор часто бреше. Оверлеї гри (код режимів і екранів вантажиться в одні й ті самі адреси) Ghidra розбирає погано: зливає сусідні функції, «згортає» таблиці констант. Рішення: для складних місць агент переходив на дизасемблер capstone й портував з асемблера, а не з декомпіляції.
  • Агент мав власну пам'ять. Контекстне вікно закінчувалося в головній сесії близько 67 разів. Усе, що було б втрачено, агент записував у файли пам'яті та в CLAUDE.md: конвеєр, відомі пастки, ухвалені рішення. Без цього робота розсипалася б після першого ж «забуття».
  • Емулятор теж має свої «характери». Unicorn при діленні на нуль повертає не те, що справжній R3000 (там −1 або +1). Довелося вчити верифікатори до цього. А спроба переписати код оригіналу «на льоту» через хуки просто валила емулятор у segfault, і з'явилося правило: ніколи не лізти в код через хуки.
  • Оригінальні пастки. У деяких місцях гра читає з адреси 0 (дзеркало RAM на консолі) або використовує значення регістра, що залишилося «з BIOS». Це не відтворюється без реальної консолі, тому такі місця окремо позначені й описані.

Побічний продукт: реєстр багів оригіналу

Я попросив фіксувати кожен знайдений дефект у game-bugs.md: де, що робить оригінал, який ефект, як виправити. На сьогодні там 61 запис. Кілька з них:

  • AI-суперник «пам'ятає» удари, але порівнює їх лише з нульовим слотом історії з восьми, тож «навчання» працює не так, як задумано;
  • розблокування персонажів у аркаді пораховано циклом по бітових позиціях замість маски: після 11-го пройденого персонажа прогрес просто «не рухається»;
  • у Tekken Ball суперник мав би віддавати перевагу найменш зустрічаним персонажам, але через помилку сортування вибір рівномірний;
  • у Tekken Force таймери ворогів 16-бітні: після ~9 хвилин на одному рівні всі вони вважаються «прострочені»;
  • цикл очищення результатів десять разів чистить один і той самий запис, бо покажчик не просувається (на щастя, без наслідків).

Політика проста: баги без впливу на гру не відтворюємо, баги, що впливають на геймплей, залишаємо за замовчуванням і виправляємо однією опцією Gameplay fixes. Так «автентичний» режим лишається автентичним.

Етап 2. Вибір рушія і план

Коли документація дозріла, я спитав: який сучасний рушій найкраще підійде для файтингу, щоб він був відкритим, легким і запускався на десктопі, мобільних і в вебі. Агент запропонував варіанти, ми обговорили, і я вибрав Godot 4 із GDScript. Головний аргумент: C#-проєкти Godot 4 не експортуються у веб, а веб був у цілях.

Перед будь-яким кодом ми написали великий план реалізації: архітектура, шари, рішення щодо графіки, віхи M0–M5. Дві ключові ідеї:

  1. Ядро симуляції без Node-ів. Детермінована гра на 60 Гц, цілі числа і фіксована кома (4096 одиниць кута на оберт, 4.12), ніяких float-ів і фізики рушія. Усе, що виходить назовні (звуки, ефекти, вібрація), виходить подіями кадру. Презентація лише читає стан.
  2. Код не дослівний, а ідіоматичний, але підтверджений «золотими трасами». Python-порти, перевірені проти оригіналу, генерують еталонні записи кадрів. Godot-тести програють ті самі входи через ядро і звіряють стан поле за полем щокадру. Один із підсумків: 32 повні матчі збігаються з оригіналом по кожному полю бійців, камері, таймеру, генераторах випадкових чисел, звукових і вібраційних викликах.

Етап 3. Godot-порт: віхи

  • M0, фундамент. Python-конвертер диска в формати Godot, тестова сцена з одним бійцем.
  • M1, заставка. NAMCO PRESENTS, відео, титульний екран, демонстраційний бій на сцені. Збігається з оригінальним кодом, прогнаним в емуляторі, кадр у кадр: 3549 кадрів на кожен із трьох сценаріїв.
  • M2, бій. Два гравці, усі персонажі, усі арени.
  • M3, CPU і гра. Меню, аркада з Огром, результати, рейтинг, Theater, збереження.
  • M4, Tekken Force і Tekken Ball.
  • M5, платформи. Android, iOS, веб, Windows, Linux, macOS, сенсорне керування, DualShock/DualSense, пакети текстур.

Зручна робота з агентом виглядала так: я пишу «реалізуй повністю M3 з плану», а далі переглядаю результат, граю й скидаю недоліки: «боєць не падає за ринг», «Esc не працює в Command List», «аптечка не підбирається».

Цікаві проблеми порту

  • Моделі не вдалося покласти в glTF. У ігрових моделях кути швів є сумішшю 50/50 двох частин тіла, які в одній позі ніколи не збігаються. Тому кожен кут зберігає дві локальні позиції, а шейдер змішує їх сам. Анімації запечені по кадрах, сплайни Godot не обчислює.
  • «Пластикові» волосся Law і Lei. Причина виявилася в Godot: навіть слабкий specular на чорних полігонах підсилюється членом Френеля до ~70 %. В оригіналі освітлення суто дифузне (Gouraud через GTE), тож спекуляр прибрали.
  • Музика стартувала двічі, HUD з'являвся поверх сцени з Огром. Ці дрібниці агент теж виловлював через порівняння з поведінкою оригінального коду.
  • Один диск замість двох. Агент побудував систему «відповідностей адрес» (layouts/<реліз>.json): будь-який з трьох релізів (Japan Rev.1, оригінальний Japan, USA) переводиться в адресний простір Rev.1, і гра збирається з будь-якого одного. У репозиторії лежать лише числа і хеші, жодних даних Namco.

Рефакторинг: «а чи не дослівний це переклад?»

Тривалий час я працював із кодом, не заглядаючи всередину. Потім зазирнув і побачив, що значна частина ремейку виглядає як дослівний переклад декомпільованого коду: константи на кшталт 0x800B9554, умови типу slot == 0xD41. Я запитав, чи є сенс переписати це ідіоматично.

Агент відповів неочікувано чесно: переписувати ядро «по-годотівськи» (Node, сигнали, Tween, float) не слід, бо це зламає детермінізм. Натомість варто замінити магічні числа на enum-и і імена та розбити монолітні функції. Тести-траси тут страхують: після кожного кроку проганяється повний набір, і всі поля мають збігатися з оригіналом на кожному кадрі. Так і зробили: понад два десятки комітів «refactor: …» ввели Character, GameMode, RoundState, BankType, імена бітів станів, кнопок, умов гілок рухів. Поведінка не змінилася, а читати код стало набагато легше.

Яка роль ШІ і де закінчується вайб

Що працювало добре:

  • Ітерації з верифікацією. Кожен твердий факт підтверджувався запуском оригінального коду. Це найкращий «запобіжник від галюцинацій»: якщо агент вигадав правило, порівняння пам'яті це покаже.
  • Пам'ять і документація як продовження мислення. CLAUDE.md, файли пам'яті та Markdown-документи дозволили пережити десятки обнулень контексту.
  • Розбиття на віхи й «виправ усі зауваження». Короткі команди типу «реалізуй M4», «виправити все», /code-review дали на диво багато.

Де потрібна людина:

  • Рішення. Рушій, політика щодо багів, «один інтерфейс замість двох», заборона на дублі ресурсів тощо. ШІ радить, але «куди» вирішує автор.
  • Очі гравця. Агент надійно порівнює числа, але не бачить, що бій «не відчувається». Усі скарги на кшталт «гори як шпалери», «дригання у ролику» і «бачу пластик» прийшли від мене.
  • Контроль якості роботи. Агент помилявся і сам: перший план виправлень включав складну емуляцію кешу завантажень лише заради зупинки музики, і я його відхилив; код-рев'ю знаходило падіння екрана, якого тести «не помічали». Після цього з'явилося правило в пам'яті: «ніколи не відновлювати копію поверх змін, зроблених не мною».

Висновки

  1. Вайб-реверсинг працює, якщо є «оракул». Оригінальний машинний код, який можна запускати й порівнювати, перетворює галюцинації на помилки тестів. Без такого оракула ми б мали гарні, але неперевірені документи.
  2. Сильні межі й тести важливіші за красиві промпти. Шари «ядро / презентація», детермінізм і золоті траси зробили можливими і рефакторинг, і масові виправлення.
  3. Записуйте контекст у файли. Для агента довгий проєкт це серія знайомств із нуля. CLAUDE.md і пам'ять роблять із нього колегу.
  4. Автор лишається директором. ШІ написав майже все, але сенс проєкту (точний, а не «схожий») задала людина.

Проєкт не містить і не поширює ігрових даних: потрібен власний образ диска, а конвертер сам перетворює його на ресурси ремейка. Усі дані Namco лишаються у вас.

Інші матеріали спільноти · Обговорити в Telegram

Telegram-чат ↗