Files
Sprinter-SDCC/applications/PoP/docs/status_line_text.md
T
snark13 5756101d29 Статус-строка: LEVEL N и лейбл QS/QL до дисковой операции
* pop_status_level() — порт show_level (seg008:25A8): «LEVEL N» на старте
  уровня.  Демо-уровень 0 и номера от 14 молчат, тринадцатый показывается
  двенадцатым, бесшовный переход 12->13 пропускается и гасит флаг за собой.
  Первое сообщение с ПАРАМЕТРОМ: строка собирается вручную (dec2, без
  printf и без деления), номер хранится снимком st_arg — иначе вторая
  страница дабл-буфера нарисовала бы другое число.
* pop_status_show_now() — печать немедленно, в ВИДИМУЮ страницу.
  QuickSave/QuickLoad заявляют лейбл ПЕРВЫМ действием, до mem_alloc/ESTEX:
  запись снимка занимает доли секунды, и раньше игрок видел сначала
  необъяснённый фриз, а надпись — уже после него.  Отказ переписывает
  строку на NO QUICKSAVE/NO QUICKLOAD обычной заявкой.
  Расхождение с оригиналом (он печатает по результату) — impl_diff.md.

Проверено в MAME: watchpoint ловит заявку внутри pop_status_show_now,
step_out возвращает в pop_qsave_process — и QUICKSAVE уже на экране, диск
ещё не тронут.  Бюджет прежний: _CODE 23981, куча 266 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:13:48 +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. Что из этого касается нашего порта

Реализовано (pop_status.c, таймер 24 тика как у оригинала):

  • GAME PAUSED — без таймера, рисует само меню (pop_menu.c);
  • QUICKSAVE / NO QUICKSAVE, QUICKLOAD / NO QUICKLOAD — заявка стоит в pop_qsave_process, то есть в единственном месте, где известно, что именно делали. Лейбл печатается ДО дисковой операции — осознанное расхождение, см. impl_diff.md;
  • SOUND ON / SOUND OFF — Ctrl+S;
  • LEVEL %d — порт show_level() целиком: демо-уровень 0 и номера от 14 молчат, тринадцатый показывается двенадцатым, бесшовный переход 12→13 пропускается и гасит флаг за собой.

Пока НЕ печатается:

  • вся группа времени — таймер есть (pop_timer.c), текста нет;
  • 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).