В 1993 году в Багдаде студент инженерного факультета собрал игру Babylonian Twins на Amiga 500: 512 КБ памяти, ни одного жёсткого диска, подключение к обычному телевизору. Весь код — чистый ассемблер 68000, каждый спрайт и каждая строка развёртки прописаны вручную. Мурта Салман рисовал графику, Махир Аль-Салман писал музыку. Страна жила под санкциями: не было ни интернета, ни материалов по геймдеву — только один экземпляр Amiga Hardware Reference Manual, по которому программировалось железо напрямую, и электричество на несколько часов в день. Постоянная смена дискет из-за нехватки памяти и летняя жара под 50°C трижды выводили из строя дисковод.

Слева: 1993 год, Amiga. Справа: 2026 год, тот же дверной проём.

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

	move.l 	#$dff000,a0			;Base for hardware registers
	lea 	save(pc),a1			;Get the system
	move.w 	#$4000,intena(A0)		;from the AMIGA

«Get the system from the AMIGA» — комментарий 1993 года. С этого момента изображение формируется собственным copper-списком игры (программируемым видео-сопроцессором Amiga), который перезаписывается на лету для спрайтов и цвета неба. Тайлы двигаются напрямую через регистры blitter'а, с ожиданием флага завершения операции. Джойстик читается прямо из аппаратного порта, а кнопка огня — это один пин на чипе CIA. ОС возвращается только между уровнями, чтобы загрузить файлы следующего уровня с диска, после чего снова отключается.

Это была первая коммерческая игра, сделанная в Ираке, и долгое время её мало кто мог увидеть. Commodore рухнула, санкции отпугнули издателей, и готовая игра осталась лежать на полке. В 2008 году форум любителей Amiga обнаружил её по видео на YouTube от брата разработчика и разыскал автора ради дискет — тема жива до сих пор.

Игру уже переносили однажды — вручную, в 2010 году. Та же команда пересобрала её для iPhone на движке, написанном с нуля, — около 34 000 строк C++, месяцы вечеров и выходных. Apple и Google выделили игру в подборках, она набрала более двух миллионов загрузок. Эта история описана здесь.

Новый порт делал не разработчик лично — он поставил задачу, каждый вечер играл в получившийся результат, отмечал, что кажется неправильным, и принимал те немногие решения, для которых требовался человек, помнящий 1993 год. Разбор форматов файлов и чтение ассемблера — работа ИИ, как и решения о том, как перенести тридцатилетний код в новую среду; всё шло быстрее, чем можно было уследить. Этот материал — то, что разработчик обнаружил, когда спустя недели сел и разобрал, что именно сделали с его игрой. Часть решений оказалась ошибочной, и это не замечали неделями.

Зачем понадобилась вторая попытка

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

Затем вышла Claude Fable 5, и ей передали те же файлы.

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

Приближались длинные выходные 4 июля, поэтому был составлен план из трёх шагов, каждый следующий — при условии успеха предыдущего.

Шаг первый, безопасный: перенос собственного движка 2010 года, 34 000 строк C++, в Godot 4. Это была контрольная точка.

Шаг второй, заведомо несправедливый: перенос оригинальных 72 758 строк ассемблера 68000 — для давно снятой с производства машины, практически без комментариев и без ничего общего с C++-версией. Пересобрать это в Godot, причём на родной частоте Amiga — 50 Гц.

Шаг третий, максимально жадный: встроить второй результат внутрь первого, чтобы покупка современной игры включала в себя оригинал 1993 года как отдельный запускаемый режим.

Все три шага сработали. Формат уровней, на разбор которого год назад ушло несколько итераций с подсказками, на этот раз был воссоздан за один проход — без единой подсказки.

Как это происходило технически

Работа велась в Claude Code, с доступом к терминалу и файловой системе: модель могла редактировать файлы, запускать ассемблер, собирать игру, запускать её и анализировать результат. Когда речь идёт о том, что модель «пересобрала бинарники 1993 года и сверила их», это буквально запуск vasm и сравнение вывода.

На раннем этапе в игру добавили набор флагов командной строки, чтобы модель могла играть самостоятельно:

--level=<name>       load a level directly
--pose=<spec>        put the twins at exact positions
--drive=<spec>       press buttons on a script, frame by frame
--probe              dump switch / gate / door / key state
--screenshot=<path>  render a frame and quit

Это превращает вопрос «ощущается ли прыжок правильно» в нечто, что можно прочитать машине:

drive[btw_jump:2.2] pos=(25.44, 24.04) vel=(0.00, -14.51) ground=false apex_y=22.48

Также было две автоматические проверки без участия человека: компиляция всех скриптов и сборка всех уровней с отчётом об ошибках. На стороне Amiga модель управляла реальным набором инструментов — vasm для сборки и FS-UAE для загрузки результата. Что не было автоматизировано: сравнение изображений в современном порте (скриншоты делались, но смотрел на них человек), и ничто не проверяло, ощущается ли игра правильно на уровне геймплея.

Шаг первый: 34 000 строк C++ за один вечер

Ночь со среды на четверг, безопасная задача. Временные метки без правок:

22:23  Godot 4 project scaffold, asset sync, TMX level pipeline
22:44  both twins playable — collision, physics, camera, switching
23:19  all 38 entity types ported — full object roster live
00:35  full screen flow — menus, map, story, save, game flows
02:15  exporting to macOS, iOS and Android

Двадцать одна минута от пустого проекта до играбельного персонажа. Каждая строка, перенесённая за эту ночь, была написана человеком, месяцами, в 2010 году.

Довести ощущения от игры до нужного состояния заняло около трёх дней: дуги прыжков, тайминг батута, попадания, вознаграждающие частое нажатие кнопок, — всё это правилось партиями 2, 3 и 4 июля.

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

Те же единицы, тот же тик

Всё игровое состояние хранится в единицах тайлов (1.0 = один тайл 48px), обновление идёт с фиксированной частотой 60 Гц — потому что именно на 60 Гц работала сборка для iOS 2010 года. Это важно, потому что оригинал применяет торможение мультипликативно, каждый кадр:

static const float GROUND_DRAG_FACTOR = 0.85f;
this->velocity.x *= GROUND_DRAG_FACTOR;   // every tick!

Умножение на 0.85 шестьдесят раз в секунду даёт одно значение трения; пятьдесят раз в секунду — другое. Перенос на иную частоту тика меняет все кривые ускорения в игре. Ничего не ломается, просто ощущения становятся неправильными навсегда, и в diff'е это не найти. На 60 Гц константа переносится дословно. Именно поэтому пересборка 1993 года работает на 50 Гц, а современная — на 60: два набора вручную подобранных чисел, каждый верен только на своей частоте. Оба такта были сохранены раздельно — было бы соблазнительно свести их в один, но это оставили как есть.

Обошлось без CharacterBody2D

Godot поставляется с CharacterBody2D и move_and_slide(), и любой туториал советует их использовать. Порт не задействовал ни то, ни другое для игрока. У оригинала собственный, написанный вручную код движения, и пересадка его на чужую физику давала бы едва уловимо неправильные ощущения, которые потом мучительно отлавливать. Игрок — это обычный Node2D, и 150-строчная процедура коллизий перекочевала построчно, включая подобранные на глаз пятнадцать лет назад магические числа и комментарии самому себе:

# Add 0.5 because we want the character's feet to be in the middle of the tile.
var bottom := pos.y + dim.y / 2 + 0.5 + i + fraction
if int(bottom) == int(pos.y + dim.y / 2 + 0.49):
    continue
var right := pos.x
var left := pos.x - dim.x / 4      # asymmetric probes!

Странное число 0.49 никто не «причесал». Ни тестов, ни документации — эти комментарии и есть спецификация.

Шаг второй: ассемблер 68000

В воскресенье днём, 5 июля, была передана задача, ради которой всё затевалось: 72 758 строк в 26 файлах, написанные для машины с 512 КБ памяти, автором для самого себя, с привычками комментирования человека, не рассчитывавшего, что это будет читать кто-то другой. Никакой документации. Перенос на современный носитель в 2008 году сократил все длинные имена файлов, из-за чего каждый include указывал на несуществующие имена. Один из пяти файлов исходников уровня обрывается прямо посреди таблицы данных. Другой копии не существует.

Перед переносом чего бы то ни было модель заставила исходники 1993 года снова собираться — на vasm, на Apple Silicon Mac — и добивалась побайтового совпадения с бинарниками, ушедшими в релиз.

14:34  import the Amiga sources, assets, references
14:49  vasm toolchain reproduces the shipped binaries byte-identically
15:20  disk images rebuilt
15:42  the rebuilt demo boots and plays in FS-UAE

Пятнадцать минут от папки с файлами до первой пересборки, совпавшей с релизными байтами. Код был написан в ASM-One, чей диалект отличается от vasm ровно настолько, чтобы менять байты: ASM-One кодирует cmp #4,d0 как CMPI, vasm выбирает другое, столь же корректное кодирование, поэтому одного запрета на оптимизацию недостаточно. Вместо правки исходников модель написала препроцессор, устраняющий пять таких расхождений, и восстановила поломанное отображение имён файлов — файл за файлом.

Самой затратной оказалась директива org. Без линковщика и релокации исходники уровня вручную размечают память Amiga, адрес за адресом:

org $6a000				; this section lives at address $6a000
Mapadd:
	incbin"btwins:binary/L1/Map1.b"	;Game Map
	org mapadd+73*1024		; skip to 73 KB past the map's start
GLBtable:
	dc.w $3333,50,20,100		; one object record begins
	dc.w SahamR-grb,26		;Routine,Length
	...
org glbtable+2*1024			; the object table gets exactly 2 KB

Карта первого уровня использует 74 400 из 74 752 доступных байт — запас всего 352 байта, и в 1993 году это никто, кроме автора, не проверял. (SahamR-grb прикрепляет поведение объекта как именованное смещение; «saham» по-арабски — «стрела».) Директива org в ASM-One умеет двигать счётчик адреса назад, а vasm — нет. Первая попытка обхода ошиблась в одном случае: ds.b 800 внутри отмотанного назад блока, которую ASM-One трактует как «пропустить 800 байт», была записана как 800 байт нулей. Всё, что шло дальше в файле, включая copper-список, оказалось смещено на 944 байта относительно релизного бинарника. Игра собиралась и запускалась — и рисовала неправильную картинку.

Даже после этого некоторые куски всё ещё не совпадали — около 108 байт, разбросанных по области переменных. Эти байты объяснили происхождение релизных файлов. ASM-One собирает код прямо в память, а на диск игра попадала путём сохранения этой памяти — уже после того, как игра успела поработать. То есть релизные файлы — снимок памяти уже запущенной игры, а не чистый вывод ассемблера. Свежая сборка даёт нули в этих переменных, потому что их ещё никто не установил; на релизном диске лежит то, что там было на момент сохранения. Код записывает эти переменные раньше, чем их читает, так что нули безвредны.

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

Форматы

Для каждого формата модель шла от кода, читающего байты, и восстанавливала логику в обратном направлении. Загрузчик уровня — 1652 строки ассемблера 68000 без единого комментария, из-за чего раньше приходилось лезть в hex-редактор.

Уровни

Уровень — это сетка тайлов: длинный список чисел, где каждое число означает «поставить сюда картинку 47» в собственной, приватной раскладке 1993 года. Именно этот формат год назад разбирался с прежней моделью в несколько заходов.

Полный набор тайлов первого уровня — 256 штук, по 16×16 пикселей каждый:

Полный набор тайлов 16x16 для первого уровня игры для Amiga 1993 года: каменные блоки, лестницы, вода, пальмовые листья, декоративная кладка

Фрагмент первого уровня, собранный из этих тайлов:

Горизонтальный срез первого уровня, отрисованный из извлечённых данных карты

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

Затем каждый уровень был заново отрисован из собственных извлечённых данных и сверен, пиксель к пикселю, с полными скриншотами уровней, сделанными в 2020 году. Там, где они не совпадали, модель нашла причину — два copper-эффекта: градиент неба и циклическую смену цвета воды. С учётом этих двух эффектов: пять полных изображений уровней, ноль различающихся пикселей. Один только первый уровень — 600 тайлов в ширину, 9600 пикселей.

Свойства ячеек карты

Отрисовка уровня — лишь половина того, что делает ячейка карты. Каждая ячейка — это 16-битное слово, и картинка занимает в нём меньшую часть:

one map cell, 16 bits:

  bits 15..10   the property: what this square DOES       (6 bits)
  bit 8         which of the two tile banks to use        (1 bit)
  bits 7..0     which of the 256 tile pictures to draw    (8 bits)

Свойство — это невидимая физика уровня. 1 — твёрдая земля. 2 и 3 — можно карабкаться. С 10 по 13 означают «это причиняет вред», четыре кода нужны, потому что отбрасывание требует направления. 14 убивает мгновенно. 63 — дверь. Нигде это не записано. Логика была восстановлена, потому что две процедуры читают одно и то же слово, и каждая раскрывает свою половину: цикл отрисовки маскирует младший байт, а проверка коллизий делает обратное:

	move.w	(a1),d6			; the same cell
	and.w	#$fc00,d6		; keep the top 6 bits
	lsr.w	#2,d6
	lsr.w	#8,d6			; d6 = the property, 0..63
	bsr	cbCheck			; 2 or 3?  you can climb this
	bsr	Checkrmh		; 10..13?  this hurts, and from which side

Checkrmh передаёт болезненные случаи в метку rmhEnjury — авторскую орфографию слова «injury» образца 1993 года.

Эти биты расставлялись в редакторе. Прежде чем собрать саму игру, пришлось собрать инструмент, который строит карты: MEDITOR.S, 1254 строки ассемблера, датированные собственным заголовком файла:

; ***********************************************************************
; *		This Program was written in four days			*
; * 			1993-2-8/7/6/5					*
; *      I made it to help me to make a map to my first serious		*
; *				Game 					*
; ***********************************************************************

Четыре дня в феврале 1993 года. Тайлы рисуются мышью, номер свойства выбирается на счётчике CURRENT FLAG, и штампуется на ячейки кнопкой PUT FLAG, а режим просмотра флагов отмечает все ячейки с выбранным номером. Работая над этим материалом, модель по просьбе запустила редактор карт и сделала скриншот. Она собрала исходник 1993 года современным ассемблером, разместила релизные данные Level 2 в памяти именно там, где их ожидает редактор, и загрузила результат в эмуляторе.

Мой редактор карт 1993 года, работающий в 2026 году, редактирующий реальный уровень 2 с выбранным флагом 1 (твёрдое): сцена с водопадом, твёрдая земля отмечена в режиме просмотра флагов, счётчик CURRENT FLAG показывает 0001

Собственный инструмент тридцатитрёхлетней давности редактирует реальный Level 2 с включённым режимом просмотра флагов. CURRENT FLAG показывает 0001 — твёрдое, и земля, на которой можно стоять, помечена, а декорации, сквозь которые проходишь, — нет. На панели указан 1994 год: художественное оформление панели — отдельный файл изображения, который загружает редактор, и сохранившаяся копия оказалась более поздней, чем февральский код 1993 года.

Второе имя на панели, Удай, принадлежит партнёру по студии Mesopotamia Software — так называла себя команда. Он в то время делал собственную игру. Редактор писался для двоих, но его архитектура прорабатывалась совместно, чтобы один инструмент подходил для обеих игр. Его игра так и не была закончена.

Таблицы объектов

Враги не хранятся в карте. Мир хранится по экранам, 25 на 20 тайлов, и у каждого экрана — своя небольшая таблица объектов. Комментарии 1993 года объясняют маркеры:

;	$1111=this is a Screen but it contain nothing or(End of Screen)
;	$2222=this is an object but do not draw it (dead)go to next
;	other=this is an object,draw it and go to the next

Scr0:	dc.w $3333,50,23,17		; a live object: frame, then x, y
	dc.w hiddenwallR-lrb,20		; its behaviour: a routine, as an offset
	dc.w 0
	dc.w 0
	dc.w 7
	dc.w 10				; parameters only that routine understands
	dc.w $3333,50,12,14
	dc.w GreatTR-LRb,16,GkeyT-GTT,1
	dc.w $1111			; end of this screen

Враг — это строка слов: маркер, кадр, позиция внутри экрана, затем поведение. hiddenwallR-lrb — процедура рассыпающейся стены, прикреплённая как смещение от базовой метки — тот же трюк, что и с метателем стрел выше. Следующие за ней слова — параметры, значение которых определяет сама процедура. Нигде в файле не указано, какое слово за что отвечает, поэтому модель нашла процедуру, обходящую эти таблицы каждый кадр, и по ней подписала поля, после чего перевела все объекты всех пяти уровней в мировые координаты и сверила их с отрисованными картами.

GAME.S

Большинство файлов данных шифруют свои 16-байтные заголовки ключом, хранящимся внутри самого файла, — трюк 1993 года против дисковых редакторов. У розничного загрузчика, GAME.S, никакого этапа расшифровки нет вовсе. Это было воспринято как подсказка: GAME.S написан до того, как добавили шифрование, а значит, это более старый файл. Именно эта зацепка позже помогла восстановить утраченный двухдисковый розничный комплект — из карты секторов внутри того же файла.

Двери не хранятся в карте

Разработчик был уверен в обратном.

При загрузке карты уровня на месте каждой двери — просто дыра, без тайла двери, открытой или закрытой. 18-байтная запись объекта проставляет их на карту во время выполнения — столбец 1×4 тайла, из таблицы:

closed  $528  $53C  $550  $564      ; solid, blocks the way
open    $129  $13D  $151  $165      ; passable — exactly one sheet-column right

Данные карты говорят, что двери нет. Код уровня говорит, что она есть. Тридцать три года разработчик был бы уверен, что источник истины — карта, а двери — это данные карты, и никогда бы туда не заглянул. Модель удержала оба факта, нашла процедуру, которая их примиряет, и вернулась с ответом: двери рисуются кодом во время выполнения, их никогда не рисовали в редакторе карт. Именно поэтому прямолинейный перенос данных карты даёт башню с дверными проёмами, полными неба.

Copper-небо

На каждом уровне цветовой индекс 31 — это небо, и художественная графика тайлов никогда его не закрашивает. Атлас тайлов рендерит его прозрачным, а за ним copper перекрашивает фоновый цвет на выбранных строках развёртки, создавая вертикальный градиент. Градиент хранится в исходнике уровня простым списком цветов. Вот всё небо второго уровня целиком:

backgndcol:
col1:   dc.w    $09FF,$09FF,$09FF,$09FF,$09EF,$09EF,$0ADF,$0ADF
        dc.w    $0ACF,$0ACF,$0ABF,$0BBF,$0BBF,$0CBF,$0CBF,$0DCF
        dc.w    $0DCF,$0ECF,$0DCF,$0DCF,$0CCF,$0CCF,$0CDF,$0CDF

Читая список сверху вниз, видно, как небо переходит от бледно-голубого к тёплому оттенку у горизонта.

24 цветовых слова таблицы неба второго уровня, отрисованные вертикальными полосами: от бледно-голубого вверху экрана до тёплого лилового у горизонта

Те же 24 слова, отрисованные наглядно. Слева — верх экрана.

Первая пересборка это пропустила, и уровни выглядели вполне нормально — но плоско, непонятно почему. Автоматическое сравнение пикселей упорно не давало зелёный результат, пока градиент не вернули обратно.

Диагностическая разница для уровня 2: белым отмечен каждый несовпадающий пиксель — всё небо и анимированная вода, отсутствовавшие в первой пересборке

Diff, который никак не давал зелёный: белым отмечен каждый пиксель, который первая пересборка воспроизвела неверно — небо и вода из copper-эффектов.

Неоднозначность спрайт-листа

Спрайт-листы Amiga планарные (пять отдельных однобитных битплоскостей в полосах с плоскостным порядком, плюс маска прозрачности), и всё это было восстановлено по процедурам отрисовки и арифметике org. Размеры листа вида frames * width * height * 2 * 5 неоднозначны: этот 2 может означать кадры двойной ширины либо два ряда друг над другом, по одному на каждое направление. Оба толкования согласуются с каждым байтом файла. На деле это два ряда по направлению — таким было решение 1993 года.

Расшифрованный спрайт-лист 1993 года: два ряда друг над другом с одинаковой последовательностью из шести кадров, по одному ряду на каждое направление

Два ряда друг над другом, по одному на направление, кадры отрисованы по очереди, без зеркалирования.

Неоднозначность была отмечена, и модель задала вопрос напрямую.

Цикл бега того же персонажа из шести кадров: пиксели Amiga 1993 года сверху, графика высокого разрешения 2026 года снизу

Один и тот же персонаж, те же шесть кадров: 1993 год сверху, 2026-й снизу.

На этом форматы закончились. Дальше игра 1993 года перешла в Godot тем же путём, что и версия на C++: поведение переписано на GDScript, на исходной частоте 50 Гц.

Шаг третий: старая игра внутри новой

Самая амбициозная задача заняла один вечер, с 21:58 до 23:43. Ретро-игра работает как гостевой режим со своим пространством имён и хост-сценой, а движок переключается на 50 Гц при входе и обратно на 60 при выходе. Это тонкая работа, и она была сделана за один присест — ожидалось, что фича займёт неделю и будет вырезана. Именно поэтому Steam-версия включает в себя игру 1993 года.

Один и тот же дверной проём во дворце в обеих играх: слева версия для Amiga 1993 года в формате 4:3 с крупными тайлами, справа сборка Godot 2026 года в широкоформатном разрешении с той же дверью, перерисованной детально

Один и тот же дверной проём в обеих играх, запущенных внутри одной программы. Слева: 1993 год. Справа: 2026-й.

Загружаемая игра не содержит ни строчки кода Amiga. Данные — упакованные блоки, планарная графика и музыка — были декодированы один раз, на локальной машине, Python-скриптами, в обычные PNG, WAV и JSON. Поведение (как патрулирует охранник, когда открывается дверь) переписано на языке движка. Тем, кто хочет настоящий оригинал — бесплатный образ диска доступен в конце публикации, вместе с эмулятором.

Где были ошибки

Баг с охранником

На втором уровне есть коридор: водопад слева, каменная колонна впереди. На экране ни одного врага, ничего не приближается — и всё равно наносится удар. Источником оказался копейщик, стоящий на тринадцать тайлов выше, на травяном уступе рядом с пальмой, а между ним и игроком — сплошная скала.

Два этажа одного и того же столбца второго уровня. Охранник стоит в красной рамке на тайле (155,90) на травяном уступе рядом с пальмой; жёлтая стрелка идёт прямо вниз сквозь сплошную скалу к коридору на y103-108, где игрок получал урон

Это привратник. Он толкает всякого, кто стоит у его ног. В оригинале эта проверка ограничена с обеих сторон:

        sub.w   d1,d4           ; d4 = vertical distance to the kid
        cmp.w   #4,d4
        bpl     Sg.Far          ; 4 or more rows below? not my problem
        cmp.w   #-2,d4
        bmi     SG.far          ; too far above? also not my problem

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

Привратник по данным спрайтов 1993 года: единственный кадр стоя, 48 на 64 пикселя, копьё в руке

Сам привратник, по листу спрайтов 1993 года.

Мелкие огрехи: у каждого уровня есть второй слой тайлов, который оригинал никогда не рендерит — это скрытая графика, открывающаяся при открытии двери или обрушении фальшивой стены. Отрендерь его «честно» — и все секретные проходы будут открыты с самого начала. Обновляй врагов до игроков вместо после — и прыжок на батуте засчитывается дважды, на высоте двадцати тайлов. Дверь со списком "p1,p2,p3,p4", означающим все четыре пальмы, была прочитана как единственный ключ со странным именем, из-за чего выход из обучающего уровня никогда не открывался. Длина звукового цикла, посчитанная как стерео при моно-эффектах, обрезала каждый звук на середине и запускала его заново.

Дороже всего обошлась фича, о которой разработчик сам просил ещё для сборки 1993 года: возможность менять местами близнецов на любом расстоянии. Проверку дистанции убрали — и статуи начали портиться. Модель вернулась в процедуру и выдала настоящий ответ, оказавшийся неожиданным: эта проверка никогда не была ограничением по расстоянию. Неактивный близнец «впечатывается» прямо в карту как статуя, и две статуи, наложенные друг на друга, съедают тайлы друг друга. Проверку вернули на место, а саму фичу для сборки 1993 года пришлось убрать.

Точно такую же ошибку разработчик когда-то сам совершил в 2010 году — только медленно, растянув её на месяцы.

Затем игру выпустили

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

Скриншоты получаются прямо из самой игры: она запускается в нужном разрешении под каждый магазин, на нужном языке, персонаж проводится в заданную точку, делается кадр, затем поверх дорисовывается полоса с подписью собственными шрифтами игры. ИИ никогда не рендерит текст прямо в изображении для магазина — подписи составляются из настоящих шрифтов и настоящих переведённых строк, иначе изображение не идёт в релиз. Один раз этот процесс всё же сломался: русские и корейские подписи вышли рядами пустых прямоугольников, что было замечено в папке с результатом ещё до загрузки.

Главная претензия к Steam — количество полей в формах, значительно больше, чем в App Store и Google Play Console. К тому же там нет API для удобного обновления метаданных. Для iOS и Android есть нормальные API, и модель ими воспользовалась. У Steam есть только веб-панель, поэтому модель управляла браузером напрямую: поля страницы магазина, достижения, чек-лист демо-версии, загрузка артов, переходы по Steamworks. Вход в аккаунт и любые действия, которые публикуют, назначают цену или выпускают релиз, оставались за человеком — модель лишь заполняла формы.

Также модель разобрала отзывы. Официальный API Google Play отдаёт только последние семь дней, что бесполезно для игры с пятнадцатилетней историей отзывов, поэтому остальное было собрано публичным скрапером, язык за языком. Затем все отзывы были прочитаны и составлен список тех, что описывают реальные дефекты. Список был одобрен.

Один из отзывов — однозвёздочный, в Google Play, с ошибками, из тех, что обычно пролистывают не глядя:

«cant get through door on level one. opens but level dowsnt end»

Прочитанный буквально, это был баг-репорт — и он оказался верным. В игре открытие выхода и проход через него — два разных действия, и подсказка об этом присутствует на всех одиннадцати языках, но только на двух из восемнадцати уровней. Один из уровней без этой подсказки оказался как раз последним бесплатным. Так что игрок, решающий, стоит ли покупать игру, оказывался перед открытой дверью без единого намёка на то, что делать дальше — и делал вывод, что игра сломана.

Этого не находили пятнадцать лет — ни разработчик, ни тестировщики, ни две пересборки. Понадобился однозвёздочный отзыв незнакомого человека. Исправление вышло как обновление 2.0.3 в обоих магазинах. Тот отзыв решено сохранить.

Баг с батутом

«Батут подбрасывает слишком высоко» — так выглядел весь баг-репорт, составленный лично, во время ночной проверки сборки. Константы сходились: двадцатистрочная симуляция интегратора из оригинала предсказывала 19,1 тайла, сборка показывала 19,5. С физикой всё было в порядке.

Проблема оказалась в семантике ввода. Сборка 2010 года была событийно-ориентированной, и из-за обходного пути вокруг особенности tvOS, добавленного много лет назад, удержанная кнопка прыжка считывалась как отпущенная, пока её не нажимали заново физически. Godot опрашивает состояние ввода и честно продолжал сообщать об удержании. Воспроизведение этой случайности и есть причина, по которой высокий прыжок требует свежего, точно рассчитанного по времени нажатия — именно так игра ощущалась на телефоне, и именно этого ожидали руки при тестировании.

В исходнике 2010 года это нигде не зафиксировано, потому что с точки зрения самого кода ничего необычного не происходит. Понять это можно было, только если сам держал телефон в руках и когда-то обходил баг в прошивке телевизора.

Оригинал, наконец опубликован

Спустя тридцать три года полный оригинал вышел бесплатно на itch.io. Его можно загрузить в FS-UAE, WinUAE или на реальном железе. Definitive Edition уже доступна на iOS и Android, имеет бесплатную демо-версию в Steam, а полный релиз в Steam (Windows, Mac, Linux) выйдет этой осенью — с игрой 1993 года внутри как отдельным вариантом запуска.

Порт делала Claude Fable 5, работавшая в Claude Code; человек ставил задачи, играл и принимал решения. Этот материал написан тем же способом: модели передали заметки о ходе порта, воспоминания о ключевых деталях старой игры (кодировка карт, таблицы объектов) и репозитории обеих версий — Amiga и порта, — после чего она подготовила черновик. Неделя ушла на построчную правку. Код, временные метки и скриншоты подлинные. Единственное, в чём есть наименьшая уверенность, — история со 108 байтами: модель объяснила, что релизные файлы — это снимок памяти, сохранённый после запуска, и что код записывает эти переменные раньше, чем читает их. Это объяснение было принято на веру и никогда не проверялось лично.

Пять горизонтальных срезов, по одному на уровень: подземелье, сад, святые сады, башня и синие стены с фризом львов

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