Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры

Эта сессия (меню + текст в служебных полосах):

* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
  снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
  (UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
  равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
  ABOUT.  About показывает тот же текст, что стартовый Sprinter screen,
  минус строка про клавиши — общий about_text(), чтобы экраны не
  разъехались.  CONTROLS собирается таблицей и центрируется по
  фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
  (SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
  а не пункт меню.  POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
  модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах.  Нижняя:
  порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
  24 тика).  Верхняя отладочная переведена с палочек на текст
  «Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
  своим форматированием чисел (без printf и без деления).
  Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
  не растёт, весь рендер в банке.  Бюджет после правок не изменился
  (_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
  всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
  не блитятся вовсе, а вход в комнату заливает только игровое поле
  (pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
  ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
  статус-строке: геометрия, семантика text_time_total как идентификатора
  сообщения, мигание, рестарт по истечении 36/288.

Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 13:58:36 +03:00
parent 7fd7f28ffc
commit 47a4b084c4
205 changed files with 8446 additions and 418 deletions
+50 -6
View File
@@ -595,6 +595,15 @@ static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol)
{
uint8_t type = wall_type(tiletype);
int coll_left, cx;
/* ОТКРЫТЫЕ ворота стеной НЕ являются (seg004:0A18: `if (tiletype ==
* tiles_4_gate && !can_bump_into_gate()) return -1`). Без этой ветки
* get_edge_distance считал расстояние до створки как до стены и отдавал
* маленькое d с типом EDGE_WALL: осторожный шаг играл короткую
* последовательность (safe_step берёт seq по самому d), и к краю
* приходилось шагать ДВАЖДЫ. Возврат −1 уводит разбор на тайл впереди,
* где открытые ворота опознаются как обычный пол (d = 11, полный шаг).
* Поймано пользователем 2026-08-24 (подход слева направо). */
if (tiletype == TILE_GATE && gate_passable(tcol, Char.curr_row)) return -1;
if (type == 0) return 127;
coll_left = pop_x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
cx = char_front_coll();
@@ -1367,14 +1376,11 @@ void pop_proc_get_object(void) __banked
* лечебных): красный кадр даёт общий механизм «Кид ранен»
* (flash_if_hurt, seg003:0AFC) — у нас pop_kid_hurt. Раньше мы
* ставили вспышку руками, то есть красили экран дважды.
* Спецсобытие уровня зелий: там синие склянки забирают ПОЛОВИНУ
* запаса, а не одну единицу. */
* Level 15/copy protection отсутствует, поэтому синие склянки
* всегда забирают ровно одну единицу здоровья. */
pop_sfx_stop(); /* seg006:1893 — stop_sounds */
pop_sfx_play(13);
if (pop_current_level == POP_POTIONS_LEVEL)
hitp_delta = (int8_t)(-((hitp_max + 1) >> 1));
else
hitp_delta = -1;
hitp_delta = -1;
break;
default: /* 6 «открыть все ворота» — TODO
* вместе с его уровнем (13) */
@@ -3159,6 +3165,43 @@ static void chomped(uint8_t tilepos, int8_t col)
if (Char.charid == CHARID_0_KID) pop_kid_dead = 1;
}
/* check_gate_push (seg004:0833): ОПУСКАЮЩАЯСЯ решётка выталкивает персонажа
* из своего тайла. Без неё Кид, зашедший в тайл ворот при поднятой решётке,
* так и остаётся в нём после опускания — «в суперпозиции»: створка рисуется
* то перед ним, то за ним (слой выбирается по направлению взгляда), и уйти
* он может в обе стороны. Поймано пользователем 2026-08-24; то же и для
* попытки проползти под опускающейся решёткой в присяде.
*
* Условия оригинала — только позы, где персонаж стоит на месте и его можно
* сдвинуть, не ломая анимацию: РАЗВОРОТ (action 7), СТОЙКА (кадр 15) и
* кадры приседа 108..110. Решётка ищется в своём тайле И в тайле СЛЕВА:
* створка нарисована у ПРАВОЙ грани тайла, поэтому левый сосед давит так
* же. Перекрытие обязано быть обеими гранями и в этом кадре, и в прошлом
* (у оригинала `curr_row_coll_flags & prev_coll_flags == 0xFF`) — иначе
* толчок срабатывал бы на пролёте мимо. Сам толчок — 5 пикселей: ВЛЕВО,
* если решётка в тайле персонажа, ВПРАВО — если в левом соседе. */
static void check_gate_push(void)
{
int8_t orig_col = Char.curr_col, col, row = Char.curr_row;
uint8_t frame = Char.frame;
if (Char.action != ACT_TURN && frame != 15 &&
!(frame >= 108 && frame < 111)) return;
col = orig_col;
if (get_tile(col, row) != TILE_GATE) {
col--;
if (get_tile(col, row) != TILE_GATE) return;
}
/* Слот флагов годен только внутри ОКОН обоих кадров: вне их лежат
* значения прошлых кадров (та же оговорка, что в check_chomped_flags). */
if (col < coll_lo || col > coll_hi ||
col < coll_prev_lo || col > coll_prev_hi) return;
if ((coll_curr[COLL_IDX(col)] & coll_prev[COLL_IDX(col)]) != 3) return;
if (gate_passable(col, row)) return; /* can_bump_into_gate */
pop_sfx_play(8); /* bumped_sound (seg004:05F1) */
Char.x = (uint8_t)((int)Char.x + (orig_col <= col ? -5 : 5));
}
/* check_chomped_kid (seg004:076B): по колонкам ряда — там, где персонаж
* перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF,
* у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (modif & 0x7F == 2),
@@ -3221,6 +3264,7 @@ static void kid_phys(void)
pop_dbg_p6(); /* ЗАМЕР: check_collisions целиком */
pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
check_gate_push(); /* seg004: опустившаяся решётка выталкивает из тайла */
check_action();
pop_dbg_p7(); /* ЗАМЕР */
pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */