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

Уровни

Уровень - это картинка. Никакого своего формата, никакого текстового описания: Level_LoadTileMap 0x0001261c читает .bmp пиксель за пикселем и получает сетку проходимости.

Три битмапа

Уровень Ресурс Файл Тема
0 JB_RES_LEVEL_MAP_1 309 BITMAP/309.bmp ICE (0)
1 JB_RES_LEVEL_MAP_2 333 BITMAP/333.bmp DESERT (1)
2 JB_RES_LEVEL_MAP_3 334 BITMAP/334.bmp GRASS (4)

Выбор - три сравнения подряд, причём третье level > 1, так что любой номер больше единицы даёт третий уровень (0x00012628, 0x00012648, 0x00012654). Если бы ни одно не сработало, оригинал ушёл бы читать неинициализированный указатель (ldrne r8,[sp,#0x0] на 0x00012644); порт в этом случае просто выходит.

Пиксель -> тайл

for (row = 0; row < JB_GRID_ROWS; row++) {          /* 1020 строк */
    const uint16_t *p = src;
    for (col = 0; col < JB_MAP_COLS; col++, p++) {  /* 16 столбцов */
        dst[col] = (*p != Color_Pack16If16bpp(screen, 0x00ffffffu));
        if (*p == Color_Pack16If16bpp(screen, 0x000000ffu)) { /* чекпоинт */ }
    }
    dst += JB_MAP_COLS;
    src += map1->w;
}

Правила ровно два:

  • белый пиксель -> 0, всё остальное -> 1. Единица значит «по этому тайлу можно катиться», ноль - провал (вода, трава, пропасть). Никаких промежуточных типов тайлов в игре нет.
  • чисто красный пиксель -> чекпоинт: COLORREF 0x000000ff, то есть R = 0xff, G = B = 0. Он одновременно и тайл (не белый, значит проходимый), и метка.

Важная тонкость: шаг строки источника берётся у первого битмапа для всех трёх. 0x000126c0 (ldr r0,[r9,#0x54]; ldr r3,[r0,#0x4]) читает ширину g_levelMap1 независимо от выбранного уровня, поэтому 333 и 334 обязаны иметь ту же ширину, что 309 - иначе карта «поедет по диагонали». Порт повторяет это буквально.

Сетка

16 x 1024 объявлено (g_mapCols 0x0002628c, g_mapRows 0x00026274), но заполняется только 0x3fc = 1020 строк. Остальное - обнулённый хвост, и он нужен: рисовальщик читает 17 строк вперёд от cam_row, а cam_row доходит до 0x3fe. Поэтому массив в порте выделен с запасом:

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

Оригинал полагался на то, что за g_tileGrid 0x00031178 лежит ещё .bss; порт делает это явным - см. Deviations.

Чекпоинты

Две таблицы по 50 элементов (g_checkpointX 0x000612b0, g_checkpointY 0x000611e8, ровно 0xc8 байт друг от друга), индексируемые с 1, а не с 0. Счётчик красных пикселей в оригинале ничем не ограничен (addeq r10,r10,#0x1 на 0x000126a4), то есть 51-й чекпоинт в картинке писал бы за границу таблицы. Порт ограничивает обход - единственное отступление в этом файле, и оно намеренное.

Как чекпоинт применяется при падении - в Physics (PlaceAtCheckpoint: ball_x = (8 - x) - 0.5, ball_row = y - 0.2, только при cam_row > 0x46).

Тема, фон, цвет HUD

Stage_Begin (оригинал Level_Begin 0x0001376c) выводит из номера уровня всё оформление:

theme = level;
if (theme == 2) theme = 4;   /* GRASS */
if (theme == 3) theme = 1;   /* DESERT */
Тема Фон Ресурс
ICE 0 лёд 321
DESERT 1 пустыня 316
FOREST 2 лес 322
SKY 3 небо 324
GRASS 4 лёд (321) -

Цвет HUD - следствие темы: на льду (0,0,0), во всех остальных 250,250,250 (g_hudR/G/B 0x00026280..0x00026288). Этим же цветом красится маска мяча, так что на первом уровне мяч тёмный, а дальше светлый - одна переменная на два разных визуальных решения.

Таймер и финиш

st->time_sec = -1;   /* Level_Begin 0x0001376c */
st->time_min = 0;

Отрицательная секунда - это пауза перед стартом: Stage_Frozen() возвращает time_sec < 0, и jb_main.c на это время не вызывает физику (WndProc 0x00020934 уходит сразу в Game_DrawFrame). Stage_Step прибавляет секунду каждые 1000 мс и переносит в минуты на 0x3c.

Финиш - cam_row > 0x3fe (WndProc 0x00020920): пауза 1000 мс, max_unlocked = level + 1, возврат в меню выбора уровня. Никакого «экрана результатов» в игре нет.

Как нарисовать свой уровень

Открыть BITMAP/309.bmp (или 333/334), сохранив ширину, и работать тремя цветами: чёрный (любой не белый) - трасса, белый - провал, чисто красный #FF0000 - чекпоинт. Строка 0 картинки - старт. Практические детали и ограничения - в README.md репозитория.

Clone this wiki locally