Files
Sprinter-SDCC/applications/PoP/docs/status_line_text.md
T
snark13 47a4b084c4 Меню, статус-строка и оболочка игры: 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>
2026-08-25 13:58:36 +03:00

13 KiB
Raw Blame History

Текст в нижней статус-строке (строке HP) — полная инвентаризация SDLPoP

Разбор SDLPoP/src/ на 2026-08-25. Цель — знать ВЕСЬ набор сообщений, которые оригинал печатает в ту же полосу, где нарисованы деления HP, и правила их появления/исчезновения. Это входные данные для порта: у нас пока туда пишется только GAME PAUSED.


1. Геометрия: одна полоса на HP и на текст

rect_bottom_text = { top 193, left 70, bottom 202, right 250 }   // data.h:217
display_text_bottom: draw_rect(чёрным) + show_text(halign_center, valign_bottom)
  • Деления HP Кида — от x = 0 вправо, шаг 7, максимум 10 → занимают x 0..69.
  • Деления HP стража — от x = 314 влево, шаг 7, максимум 10 → занимают x 245..320.
  • Текст живёт РОВНО в промежутке x 70..250 и по X с делениями не пересекается.
  • По Y деления на y = 194..200, текст (valign_bottom к 202) — на y = 195..201, то есть на строку ниже. Именно поэтому в оригинале текст выглядит «сидящим» чуть ниже стрелок HP.

У нас: POP_HP_Y = 194 (pop_cdraw.h), экран сдвинут на POP_YOFF = 28, базовая линия крупного шрифта POP_YOFF + POP_HP_Y + 8 = 230 — силуэт ложится на 223..229, то есть та же картинка.

2. Два примитива и два таймера

Имя Что делает
display_text_bottom(text) (seg008:2644) стереть прямоугольник цветом 0 и напечатать текст по центру
erase_bottom_text(arg) (seg008:266D) стереть прямоугольник; при arg != 0 ещё и обнулить оба таймера
text_time_remaining сколько игровых тиков сообщение ещё висит; 0 — ничего не висит
text_time_total идентификатор сообщения, а не только его длительность

Обработка тика — в draw_game_frame/idle (seg000:956). Комментарий в оригинале прямой: «Note: texts are identified by their total time!» Значения text_time_total, у которых есть особое поведение:

total Смысл Что происходит по истечении
12 «1 SECOND LEFT» обычное стирание
24 обычное короткое сообщение обычное стирание
36 смерть на демо-уровне (0) или на уровне зелий (15) — текста нет start_game() — рестарт игры
288 «Press Button to Continue» start_game() — рестарт игры
1188 защита от копирования (уровень 15) не убывает и не исчезает

Мигание: при total == 288 и remaining < 72 сообщение мигает с периодом 12 тиков — 4 тика видно (blink_frame <= 3), 8 нет; в кадре blink_frame == 3 заново печатается текст и играет звук 38 (sound_38_blink).

Сброс: init_game() (seg003:32) обнуляет оба таймера и is_show_time — то есть любое сообщение умирает на старте уровня.


3. Полный список сообщений

3.1. Состояние программы

Текст Где Таймер
GAME PAUSED seg000:1769, пока is_paused без таймера: печатается на входе в паузу, erase_bottom_text(1) на выходе (seg000:1784)

3.2. Уровень и оставшееся время (show_level / show_time, seg008)

Текст Условие total
LEVEL %d show_level() при старте уровня; только 1..12 (hide_level_number_from_level = 14), не при seamless; уровень 13 показывается как 12 (level_13_level_number) 24, дальше сразу is_show_time = 1
%d MINUTES LEFT каждая минута, кратная 5, и каждая из последних 5 24
%d SECONDS LEFT последняя минута, раз в 12 тиков 24
1 SECOND LEFT остался 1 с 12
TIME HAS EXPIRED! rem_min == 0 24
%d MINUTES PASSED / 1 MINUTE PASSED только SDLPoP (ALLOW_INFINITE_TIME), при отрицательном таймере 24

Что взводит is_show_time (все → следующий кадр печатает время):

  • Space — seg000:612, штатная клавиша оригинала «сколько осталось»;
  • читы -/+ numpad (изменение времени) — seg000:762 / 777, при этом таймеры сообщения обнуляются, чтобы новое напечаталось немедленно;
  • смерть Джафараon_guard_killed() seg006:1936, уровень 13 (jaffar_victory_level): вспышка + показать время;
  • истечение очередной минуты — seg008:1796;
  • сразу после show_level().

Обнуляет is_show_time: play_kid() при смерти (seg006:1365) и show_copyprot(1) (seg000:2385).

3.3. Смерть Кида

Текст Где total
Press Button to Continue play_kid() seg006:1383 — умер на обычном уровне 288 (мигает, затем рестарт игры)
(без текста) тот же код, но уровень 0 (демо) или 15 (зелья) 36 (тихая пауза, затем рестарт игры)

Стирается: fell_out() (seg006:1342, упал из комнаты 0) и чит R (воскрешение, seg000:783) — оба зовут erase_bottom_text(1).

3.4. Сохранение и загрузка

Текст Клавиша total
GAME SAVED / UNABLE TO SAVE GAME Ctrl+G (save_game, seg000:2211) total не ставится, remaining = 24
QUICKSAVE / NO QUICKSAVE F6 (расширение SDLPoP, seg000:497) 24
QUICKLOAD / NO QUICKLOAD F9 (расширение SDLPoP, seg000:514) 24

3.5. Ответы на клавиши (answer_textneed_show_text, все total = 24)

Текст Клавиша
SOUND ON / SOUND OFF Ctrl+S
KEYBOARD MODE Ctrl+K
JOYSTICK MODE / JOYSTICK NOT FOUND / JOYSTICK UNAVAILABLE Ctrl+J
PRINCE OF PERSIA V1.0 (в SDLPoP заменено на SDLPoP v%s) Ctrl+V
SDL COMP v… LINK v… Ctrl+C — только SDLPoP

3.6. Отладочные читы (cheats_enabled, total = 24)

Текст Клавиша Смысл
S%d L%d R%d A%d B%d C номер отрисованной комнаты и её соседей L/R/A/B
AL%d AR%d BL%d BR%d Shift+C диагональные соседи

3.7. Защита от копирования (только уровень 15)

Текст Где total
WORD %d LINE %d PAGE %d show_copyprot(1) seg000:2389 1188 — висит, пока не сменится уровень

3.8. Только SDLPoP, в оригинале 1989 отсутствует

Текст Где
RECORDING, REPLAY SAVED, REPLAY CANCELED replay.c:599/626/628
имя файла скриншота screenshot.c:62

4. Что из этого касается нашего порта

Реализовано:

  • GAME PAUSED — в строке HP, без таймера (pop_menu.c).

Есть у нас, но сообщение пока НЕ печатается (показывается иначе или не показывается вовсе):

  • LEVEL %d и вся группа времени — таймер уже есть (pop_timer.c), текста нет;
  • QUICKSAVE/QUICKLOAD — у нас вместо строки экран-уведомление (ui_notice);
  • SOUND ON/OFF — у нас индикатор в отладочной полосе, а не текст;
  • Press Button to Continue — у нас авто-респавн по таймеру/стрелке вверх;
  • номера комнат (C/Shift+C) — у нас палочки в отладочной полосе.

Нам не нужно: copy protection (уровень 15 исключён из порта — см. full_game_plan.md), joystick-режимы, replay, скриншоты.

Механика, которую придётся портировать целиком, если брать группу времени: пара таймеров text_time_total/text_time_remaining с семантикой «идентификатор сообщения» — иначе не воспроизвести ни мигание, ни рестарт по истечении 36/288.


5. Цена вывода и что делать, если упрёмся

Блит одного глифа стоит ~4,6 тыс. тактов почти независимо от размера — это цена вызова, а не пикселей (memory blit_cost_model). Полсотни символов = полкадра. Что уже сделано в pop_status.c / pop_ui.c:

  • change-driven: пока показанное не изменилось, не рисуем вовсе;
  • по полям: смена комнаты — две цифры (~9 тыс. тактов, 2% кадра), а не вся строка; подписи рисуются только при полной инвалидации;
  • пробелы не блитятся: их глиф целиком прозрачен, а стоит как буква — на полной отладочной строке это девять сэкономленных блитов;
  • заливка только поля при входе в комнату (pop_screen_fill_field): борта от комнаты к комнате не меняются, это и четверть заливки, и то, что обе полосы вход переживают.

Запас, если бюджета всё же не хватит (идеи пользователя, 2026-08-25):

  1. Растянуть вывод на несколько кадров, не показывая полуготовую строку. Печатать по нескольку букв за кадр, держа цвет шрифта чёрным (отдельный индекс палитры), а по готовности подменить этот индекс на белый — строка появится целиком и мгновенно. Стоит ноль байт памяти и укладывается в нашу же технику «два разных чёрных» (POP_COL_OUTSIDE).
  2. Собирать строку в один спрайт в свободном хвосте страницы шрифта и блитить одним вызовом. Дороже по подготовке (~35 тыс. тактов на копирование), но выгодно там, где строка ЦЕЛИКОМ меняется каждый раз. Для меню этот путь уже рассматривался и был отвергнут; для статус-строк он имеет смысл только вместе с п.1.

Про QuickSave/QuickLoad оптимизация не нужна вовсе: там игра и так стоит на время дисковой операции.

6. Ловушка: свисающие глифы

Зона стирания текста обязана захватывать строку НИЖЕ базовой линии. В малом шрифте 'p' имеет высоту 7 при ascent 5, ',' — 6: они свисают под базовую линию. Стирание ровно до неё оставляло от хвоста «p» в «Speed:» одинокую точку (поймано в MAME 2026-08-25).