Skip to content

Deviations

Владислав edited this page Sep 5, 2026 · 1 revision

Где порт отличается от оригинала

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

Восемь сознательных отступлений

# Что Почему
1 keys.cfg вместо keys.dat / keys2.dat коды VK не совпадают с кодами SDL
2 обход чекпоинтов ограничен оригинал читал за границу таблицы
3 явный запас в сетке тайлов оригинал полагался на соседний .bss
4 возврат при неопознанном уровне оригинал брал неинициализированный указатель
5 ассеты - файлы, а не ресурсы PE вне Windows нет LoadBitmapW
6 сенсорная панель у КПК были аппаратные кнопки, у телефона нет
7 ключи командной строки нужны для проверок, см. Build-and-CI
8 кадр по vsync, а не по WM_TIMER в SDL2 нет SetTimer

1. Формат раскладки

Оригинал держал привязки в keys.dat и keys2.dat, кодируя каждую как «база 1000 + смещение 100» над кодами VK_*. Это Windows-коды, в SDL их нет: SDLK_LEFT - это 1073741904, а VK_LEFT - 0x25. Переносить кодировку значило бы тащить чужую таблицу соответствий, поэтому порт пишет свой keys.cfg - шесть десятичных кодов SDL, по одному в строке (см. Menu-and-controls).

2. Чекпоинты

Счётчик красных пикселей в оригинале не ограничен ничем (Level_LoadTileMap 0x000126a4, addeq r10,r10,#0x1 без сравнения), а таблицы всего по 50 элементов - картинка с 51-м чекпоинтом писала бы за границу. Порт ставит проверку на месте записи:

if (*p == Color_Pack16If16bpp(screen, 0x000000ffu)) {
    found++;
    if (found < JB_CHECKPOINT_N) {
        jb_checkpoint_x[found] = col;
        jb_checkpoint_y[found] = row;
    }
}

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

3. Запас в сетке тайлов

Заполняется 1020 строк, а рисовальщик читает 17 строк вперёд от cam_row, и cam_row доходит до 0x3fe. Оригинал этим не смущался: за g_tileGrid 0x00031178 в .bss лежали другие переменные, и чтение уходило в них. Порт объявляет запас явно:

#define JB_GRID_ALLOC_ROWS (JB_GRID_ROWS + JB_VISIBLE_ROWS + 2)   /* 1020 + 17 + 2 */

Поведение то же - за концом трассы читаются нули, - но теперь это гарантия, а не совпадение раскладки памяти. См. Levels.

4. Неопознанный уровень

Выбор карты - три независимых if, и третий покрывает всё больше единицы:

if (level == 0) src = map1->pixels;
if (level == 1) src = map2->pixels;
if (level > 1)  src = map3->pixels;
if (src == 0)   return;

Последняя строка - добавление порта. В оригинале (Level_LoadTileMap 0x00012644, ldrne r8,[sp,#0x0]) указатель при несовпадении оставался неинициализированным, и функция шла читать мусор. Достижимо это только отрицательным номером уровня, то есть на практике никогда, но повторять чтение по случайному адресу смысла нет.

5. Ассеты файлами

Оригинал грузил картинки через LoadBitmapW(g_hInstance, id) (Game_Init 0x000113bc), звук и музыку - через hss.dll. Порт читает те же номера из BITMAP/<id>.bmp, Sounds/, Musics/; папку можно переназначить переменной JUMPYBALL_ASSETS. Номера ресурсов при этом сохранены как есть - см. Reverse-engineering.

6. Сенсорная панель

В оригинале управление - аппаратные кнопки КПК (g_gxKeyA, g_gxKeyStart и прочие), поэтому экранной панели там нет и быть не может. Порт добавляет её в jb_touch_sdl2.c, но знает она те же шесть действий, что и клавиатура, - новых возможностей у игрока не появляется. Подробности раскладки - в Menu-and-controls.

7. Ключи командной строки

--level=, --screen=, --index=, --unlocked= и путь для дампа кадра - целиком добавление порта, сделанное ради проверок: попасть в нужный экран одной командой вместо прохождения игры. Масштаб окна первым позиционным аргументом - тоже порт: у оригинала окно было размером с экран КПК. См. Build-and-CI.

8. Кадровая частота

Оригинал заводил SetTimer(hwnd, 10, 5, 0) и считал кадр по WM_TIMER каждые 5 мс. Порт крутит обычный цикл с SDL_RENDERER_PRESENTVSYNC, то есть кадры приходят с частотой монитора. Физика от этого не меняется: и там и там шаг считается из реального времени -

/* JumpyBall.exe WndProc 0x00020920 takes g_dtMs 0x00064968 as
   GetTickCount() - DAT_0006496c and 0x00020974 scales it by 0.001. */
dt = (float)(ticks - prev_ticks) * 0.001f;
  • поэтому скорость мяча и таймер уровня одинаковы. Разница видна только там, где результат обрезается в целое каждый кадр, например в накоплении угла вращения spin = (int)(vel_z * dt * 350.0f + (float)spin): при 60 Гц округлений меньше, чем при 200 Гц. Единственная явная пауза в порте - Platform_Delay на JB_LEVEL_CLEAR_PAUSE_MS после прохождения уровня; ограничителя кадров нет.

Портировано, хотя недостижимо

Stage_Begin умеет шесть тем оформления, но номер темы выводится из номера уровня, а уровней три:

theme = level;
if (theme == 2) theme = 4;   /* GRASS */
if (theme == 3) theme = 1;   /* DESERT */

Темы FOREST 2 и SKY 3 не получаются ни при каком level. Код их фонов и цветов HUD перенесён полностью - выбрасывать ветку только потому, что до неё нет пути, значило бы терять факт из дизассемблера. То же с проекцией трассы для этих тем - см. Track-and-camera и Levels.

Похоже на ошибку порта, но это оригинал

Самая частая реакция при чтении кода - «тут опечатка». Ниже - список мест, где опечатки нет: так было в JumpyBall.exe или hss.dll, и порт повторяет это намеренно.

Место Что смущает Почему так
jb_pl.z_axis_damped = 0 флаг никогда не меняется, ветки под ним мертвы в оригинале это .bss-переменная, которую тоже никто не пишет
настройки - экран 4 экрана 3 не существует нумерация Menu_DrawFrame 0x0001a7d0
view_center_x + 0x14 как координата Y смешаны оси буквальный перенос мастера переназначения
((MOD_BASEFREQ << 8) / 0xac44) делимое равно делителю оба числа - 44100; выражение всегда даёт 0x100
MOD_CLOCK / p до умножения на 65536.0 потеря точности целочисленное деление было в hssSpeaker::createTables 0x00103664
(max < 0) ? max + 1 : max) >> 1 max_unlocked не бывает отрицательным знаковый сдвиг в оригинале
шаг строки от map1->w для всех трёх карт второй и третий уровни читаются шириной первого 0x000126c0 читает ширину g_levelMap1 независимо от уровня

Последняя строка таблицы - не косметика: из неё следует требование к картинкам уровней. 333.bmp и 334.bmp обязаны иметь ту же ширину, что 309.bmp, иначе карта поедет по диагонали. Подробности - в Levels.

Отдельно стоит jb_ctx.layout_mode = JB_LAYOUT_240x320. Оригинал умел две раскладки - 240x320 и 176x220, - и весь код второй перенесён (видно по выражениям вида 0xa0 - (layout_mode >> 1)), но порт всегда ставит первую. Причина простая: окно теперь масштабируемое, подстраиваться под экран КПК не нужно.

Чего в порте нет вообще

  • Сохранения прогресса. max_unlocked живёт только в памяти; оригинал тоже не писал его на диск. На диск попадает единственный файл - keys.cfg.
  • Экрана результатов. Финиш - это пауза 1000 мс и возврат в меню выбора уровня, см. Levels.
  • Второй раскладки клавиш. Оригинал держал две (keys.dat и keys2.dat), порт - одну.

Clone this wiki locally