В мире ретро-игр и классических платформ очень часто встречаются загадочные технические детали, которые остаются непонятными даже для обычных игроков и поклонников. Одним из таких тайн стала работа генератора случайных чисел (ГСЧ) в культовой игре Bubble Bobble для компьютера Commodore 64. Много лет спустя после выхода игры я решил раз и навсегда разобраться, каким образом происходит выбор игровых бонусов и почему на определённых уровнях появляются конкретные предметы чаще других. Для этого я создал, пожалуй, самый простейший, но при этом эффективный эмулятор 6502 процессора, который многим может показаться абсолютно неудачным, однако он помог мне раскрыть интересные аспекты внутренней логики игры. Мои поиски начались с просьбы моего друга и художника Давиде Боттино, который занимается ремастеринговыми проектами классики Commodore 64.
Он уже создал потрясающие новые графические наборы для таких игр, как Toki, и хотел заняться Bubble Bobble, чтобы сделать её максимально похожей на оригинальную аркадную версию. Для этого требовалось не только обновить спрайты, но и глубже понять внутреннюю логику игры, чтобы устойчиво интегрировать новые элементы, изменение которых казалось на первый взгляд тривиальным, но оказалось куда сложнее. Процесс оказался намного тоньше. Например, с некоторыми довольно крупными спрайтами, вроде бонусного арбуза на уровне, возникли проблемы: их данные в памяти не были расположены таким образом, как ожидалось. Выяснить естественные причины и понять механизм обработки графики помогала глубокая работа с дизассемблированным кодом через инструменты отладки, ведь часть памяти, которую игра изначально использовала для спрайтов, иногда перекрывалась собственным кодом или данными.
Это усложняло любые попытки патча и замены. Однако главным моим фокусом стал генератор случайных чисел. Несмотря на то что на первый взгляд он задуман для обеспечения некоторой вариативности выпадения бонусов и усилителей, при более глубоком погружении выяснилось, что он далеко не идеален. Функция, которая формирует случайное число для выбора бонуса, содержит последовательность инструкций процессора 6502, реализующую линейный регистр сдвига с обратной связью (LFSR). Этот тип генератора считается быстрым и даёт равномерное распределение чисел, но его качество для важных игровых случайностей — сомнительное.
К тому же, стартовые значения для ГСЧ зависят от того, в какой конкретный момент игрок нажмёт клавишу в меню выбора режима игры. В результате из-за положения в миллисекундах формируется начальное состояние генератора, ограниченное всего десятью возможными вариантами, что сильно уменьшает энтропию, так важную для ощущения настоящей случайности. Понять принципы выбора бонусов и усилителей было затруднительно из-за запутанного кода, состоящего из многочисленных битовых операций и сложных ветвлений. Прямое портирование кода из ассемблера в TypeScript или другой язык без понимания назначения каждой строки оказалось безрезультатным. Вместо этого я решил пойти по другому пути — создать минималистичный эмулятор 6502 непосредственно на TypeScript, но не полный, а лишь с набором самых необходимых команд и флагов, которые нужен для запуска только этой части логики.
В итоге получилась по сути гибридная система, сочетающая черты эмулятора и исходного порта игры. Такой подход позволил отказаться от реализации сложного механизма счётчика команд и адресации, заменив ветвления обычными условными инструкциями и циклами. Это сделало код компактным и понятным, а главное — позволило запускать нужный кусок игры многократно, перебирая все возможные входные состояния генератора и вычислять вероятности выпадения каждого из бонусов. Результаты анализа оказались весьма интересными и частично неожиданными. Оказалось, что для каждого уровня в игре существует доминирующий усиливающий предмет, который выпадает примерно с 50-процентной вероятностью.
Этот предмет постоянно меняется с уровня на уровень и выбирается по довольно простой формуле, основанной на номере уровня. Более того, доминирующими являются только предметы с чётными индексами в массиве возможных бонусов — словно половина из них вообще не учитывалась для основного выпадения, что скорее всего было не намеренным решением, а случайным побочным эффектом того, как предопределялись номера уровней и генератор. Остальная же половина предметов выпадала в оставшихся 50 процентах случаев с равной вероятностью, но есть и более узкие детали: некоторые усилители, такие как башмачок, белый посох или красный посох, не появлялись в рандоме вообще. По видимым данным они либо не задействованы в стандартном механизме, либо активируются другими способами. Такой разбор поведения внутреннего генератора позволил лучше понять, почему на разных этапах игры игрок сталкивается с определенными усилителями часто, а другие — практически не появляются.
Кроме того, подобное исследование помогло выявить ограниченность и несовершенство алгоритма выпадения предметов, заложенных разработчиками, и понять, каким образом можно вмешиваться в процесс для изменения механики, будь то патчинг игры или создание собственных модификаций. Создание этой упрощённой эмуляции дало весомое преимущество: возможность с высокой точностью и без глубокого понимания кода буквально перебрать все варианты работы генератора и наглядно увидеть итоговое распределение вероятностей. Это позволило ускорить процесс исследования и вывести закономерности, которые были скрыты под множеством битовых и арифметических операций в оригинальном ассемблерном коде. Моё творение условно называют «худшим» эмулятором лишь из-за его минималистичности и ограниченной сферы применения. Тем не менее, именно эта простота стала его главным достоинством.
Я взял только те команды и флаги процессора, которые нужны для решения конкретной задачи — анализа выпадения бонусов. Благодаря этому удалось создать понятный, наглядный и расширяемый код, который можно использовать в дальнейших исследованиях или даже при создании фанатских адаптаций игры. Работа с ретро-игрой Bubble Bobble на Commodore 64 по большей части является упражнением в археологии кода. Из-за ограниченности ресурсов тех времён разработчики применяли множество нестандартных и хитроумных решений, которые сейчас требуют глубокого понимания низкоуровневых процессов. Погружение в такую архитектуру не только даёт удовольствие от решения интересных технических задач, но и помогает осознать, насколько сложной и инновационной была работа с аппаратным обеспечением в эпоху 8-битных компьютеров.