Меню, статус-строка и оболочка игры: 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
+73
View File
@@ -0,0 +1,73 @@
/*
* pop_demo.c — воспроизведение attract-demo SDLPoP, банк 9 (FG7).
*
* Это не отдельная физика и не сценарные телепорты. Таблица custom->demo_moves
* подаёт ровно те же control_* в pop_ctrl_auto_move(), через которые идут
* игрок и автоконтроль персонажей. Поэтому демо проверяет настоящий маршрут
* level 0, а не его нарисованную имитацию.
*/
#include <stdint.h>
#include <kbd_raw.h>
#include "pop_ctrl.h"
#include "pop_guard.h"
#include "pop_map.h" /* hitp_curr — смерть demo имеет приоритет над AI */
#include "pop_demo.h"
typedef struct {
uint8_t time;
int8_t move;
} demo_move_t;
/* data.h SDLPoP: демо пробегает уровень 0 до комнаты 24. Последний -1
* означает «больше не менять уже поданный control_*», а не ввод. */
static const demo_move_t demo_moves[] = {
{0x00, 0}, {0x01, 1}, {0x0D, 0}, {0x1E, 1}, {0x25, 5},
{0x2F, 0}, {0x30, 1}, {0x41, 0}, {0x49, 2}, {0x4B, 0},
{0x63, 2}, {0x64, 0}, {0x73, 5}, {0x80, 6}, {0x88, 3},
{0x9D, 7}, {0x9E, 0}, {0x9F, 1}, {0xAB, 4}, {0xB1, 0},
{0xB2, 1}, {0xBC, 0}, {0xC1, 1}, {0xCD, 0}, {0xE9, -1},
/* do_auto_moves читает moves[demo_index] ещё после сдвига индекса.
* Это недостижимая метка из data.h, не команда: она держит индекс в
* границах, пока demo_time доходит до 0xFE. */
{0xFF, -2}
};
static uint8_t demo_time;
static uint8_t demo_index;
void pop_demo_begin(void) __banked
{
demo_time = 0;
demo_index = 0;
}
uint8_t pop_demo_tick(void) __banked
{
uint8_t index;
int8_t move;
/* Урон мечом сводится ПОСЛЕ этого вызова в предыдущем игровом кадре.
* На следующем тике HP уже ноль, но pop_kid_dead ещё не поднят: обычный
* pop_ctrl_tick делает это в ctrl_kid_death(), а demo обходит его своим
* AI. Без этого гарда AI снова управлял мёртвым Кидом, возвращал его в
* стойку и страж убивал его по кругу. Attract-demo при смерти кончается;
* текущий главный цикл ещё дорисует один последний кадр и уйдёт в title. */
if (hitp_curr == 0) return 1;
/* do_demo(): в бою запись времени/кнопок стоит, Кидом управляет тот же
* combat AI, что стражами, но с отдельным skill 10. */
if (pop_demo_kid_ai()) return 0;
if (demo_time >= 0xFE) return 1;
++demo_time;
index = demo_index;
if (demo_moves[index].time <= demo_time) ++demo_index;
else index = (uint8_t)(demo_index - 1);
move = demo_moves[index].move;
pop_ctrl_auto_move(move);
return (uint8_t)(demo_time >= 0xFE);
}
uint8_t pop_demo_user_input(void) __banked
{
return kbd_raw_any_down();
}