Статус-строка: 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>
This commit is contained in:
@@ -577,3 +577,29 @@ seg008.c:1600). XOR идёт по 24-битному RGB того, что УЖЕ
|
|||||||
кайма станет резать глаз — вариантов два: запечь второй набор под светлый
|
кайма станет резать глаз — вариантов два: запечь второй набор под светлый
|
||||||
фон (ещё 32 страницы EMM) или считать эту кайму прозрачной (силуэт станет
|
фон (ещё 32 страницы EMM) или считать эту кайму прозрачной (силуэт станет
|
||||||
на пиксель уже). Оба хуже нынешнего; трогать только по факту жалобы.
|
на пиксель уже). Оба хуже нынешнего; трогать только по факту жалобы.
|
||||||
|
|
||||||
|
|
||||||
|
## QuickSave/QuickLoad: лейбл печатается ДО дисковой операции, а не после
|
||||||
|
|
||||||
|
**Как в оригинале.** SDLPoP печатает `QUICKSAVE` / `NO QUICKSAVE` (и пару
|
||||||
|
для загрузки) уже ПО РЕЗУЛЬТАТУ операции — `process_quicksave` (seg000:497)
|
||||||
|
сначала делает save/load, потом зовёт `display_text_bottom` и ставит
|
||||||
|
`text_time_total = 24`. На PC это незаметно: файл пишется мгновенно.
|
||||||
|
|
||||||
|
**У нас.** `pop_qsave_process` заявляет строку ПЕРВЫМ действием, ещё до
|
||||||
|
`mem_alloc_pages`/ESTEX, через `pop_status_show_now()` — та печатает её
|
||||||
|
немедленно в ВИДИМУЮ страницу, не дожидаясь конца кадра. Отказ уже потом
|
||||||
|
переписывает строку на `NO QUICKSAVE`/`NO QUICKLOAD` обычной заявкой.
|
||||||
|
|
||||||
|
**Зачем.** Запись снимка на диск занимает доли секунды, и всё это время
|
||||||
|
игра стоит. При порядке оригинала игрок видел сначала необъяснённый фриз,
|
||||||
|
и только по его окончании — надпись, объясняющую то, что уже прошло.
|
||||||
|
Решение пользователя, 2026-08-25.
|
||||||
|
|
||||||
|
**Чем платим.** Строка успевает мигнуть даже там, где операция потом не
|
||||||
|
удалась: сначала `QUICKSAVE`, следом `NO QUICKSAVE`. На практике отказ —
|
||||||
|
редкость (нет места/диска), и «заявка → отказ» читается не хуже.
|
||||||
|
|
||||||
|
**Что проверять при регрессе.** Что после неудачной операции на экране
|
||||||
|
остаётся именно `NO QUICKSAVE`/`NO QUICKLOAD`, а не первая строка: отказ
|
||||||
|
идёт обычной заявкой и печатается кадровым проходом, то есть на кадр позже.
|
||||||
|
|||||||
@@ -139,18 +139,23 @@ display_text_bottom: draw_rect(чёрным) + show_text(halign_center, valign_b
|
|||||||
|
|
||||||
## 4. Что из этого касается нашего порта
|
## 4. Что из этого касается нашего порта
|
||||||
|
|
||||||
Реализовано:
|
Реализовано (`pop_status.c`, таймер 24 тика как у оригинала):
|
||||||
|
|
||||||
* `GAME PAUSED` — в строке HP, без таймера (`pop_menu.c`).
|
* `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
|
||||||
|
пропускается и гасит флаг за собой.
|
||||||
|
|
||||||
Есть у нас, но сообщение пока НЕ печатается (показывается иначе или не
|
Пока НЕ печатается:
|
||||||
показывается вовсе):
|
|
||||||
|
|
||||||
* `LEVEL %d` и вся группа времени — таймер уже есть (`pop_timer.c`), текста нет;
|
* вся группа времени — таймер есть (`pop_timer.c`), текста нет;
|
||||||
* `QUICKSAVE`/`QUICKLOAD` — у нас вместо строки экран-уведомление (`ui_notice`);
|
|
||||||
* `SOUND ON/OFF` — у нас индикатор в отладочной полосе, а не текст;
|
|
||||||
* `Press Button to Continue` — у нас авто-респавн по таймеру/стрелке вверх;
|
* `Press Button to Continue` — у нас авто-респавн по таймеру/стрелке вверх;
|
||||||
* номера комнат (`C`/Shift+`C`) — у нас палочки в отладочной полосе.
|
* номера комнат (`C`/Shift+`C`) — у нас отдельная отладочная строка.
|
||||||
|
|
||||||
Нам не нужно: copy protection (уровень 15 исключён из порта — см.
|
Нам не нужно: copy protection (уровень 15 исключён из порта — см.
|
||||||
`full_game_plan.md`), joystick-режимы, replay, скриншоты.
|
`full_game_plan.md`), joystick-режимы, replay, скриншоты.
|
||||||
|
|||||||
@@ -225,6 +225,11 @@ int8_t pop_qsave_process(void) __banked
|
|||||||
int8_t rc=-1;
|
int8_t rc=-1;
|
||||||
pop_qsave_request = 0;
|
pop_qsave_request = 0;
|
||||||
if (!request) return 0;
|
if (!request) return 0;
|
||||||
|
/* Лейбл — ДО дисковой работы: она длится доли секунды, игра на это
|
||||||
|
* время замирает, и строка обязана объяснить фриз, а не подводить под
|
||||||
|
* ним итог. Не удалось — ниже перепишем на NO QUICKSAVE/QUICKLOAD. */
|
||||||
|
pop_status_show_now((uint8_t)(request == QS_SAVE ? POP_MSG_QUICKSAVE
|
||||||
|
: POP_MSG_QUICKLOAD));
|
||||||
blk=mem_alloc_pages(1);
|
blk=mem_alloc_pages(1);
|
||||||
if (!blk) return -1;
|
if (!blk) return -1;
|
||||||
page=mem_get_page(blk,0);
|
page=mem_get_page(blk,0);
|
||||||
@@ -238,14 +243,11 @@ int8_t pop_qsave_process(void) __banked
|
|||||||
}
|
}
|
||||||
pop_sfx_start();
|
pop_sfx_start();
|
||||||
mem_free_block(blk);
|
mem_free_block(blk);
|
||||||
/* Сообщение заявляем ЗДЕСЬ, а не у вызывающего: только тут известно,
|
/* Успех ничего не меняет: нужная строка висит с начала операции.
|
||||||
* что именно делали (F6/F9 и оба пункта меню приходят одним запросом).
|
* Отказ переписывает её на NO QUICKSAVE/NO QUICKLOAD — обычной
|
||||||
* Порт seg000:497/514 — те же четыре строки, что и в оригинале. */
|
* заявкой, кадр её подхватит (порт seg000:497/514). */
|
||||||
if (request == QS_SAVE)
|
if (rc <= 0)
|
||||||
pop_status_msg = (uint8_t)(rc > 0 ? POP_MSG_QUICKSAVE
|
pop_status_msg = (uint8_t)(request == QS_SAVE ? POP_MSG_NO_QUICKSAVE
|
||||||
: POP_MSG_NO_QUICKSAVE);
|
|
||||||
else
|
|
||||||
pop_status_msg = (uint8_t)(rc > 0 ? POP_MSG_QUICKLOAD
|
|
||||||
: POP_MSG_NO_QUICKLOAD);
|
: POP_MSG_NO_QUICKLOAD);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
#define MSG_TOP (MSG_BASE - POP_FONT_BIG_ASCENT - 2)
|
#define MSG_TOP (MSG_BASE - POP_FONT_BIG_ASCENT - 2)
|
||||||
#define MSG_BOTTOM (MSG_BASE)
|
#define MSG_BOTTOM (MSG_BASE)
|
||||||
|
|
||||||
|
/* POP_MSG_LEVEL здесь пустой: его строка собирается с номером уровня. */
|
||||||
static const char * const status_text[POP_MSG_N] = {
|
static const char * const status_text[POP_MSG_N] = {
|
||||||
"",
|
"",
|
||||||
"QUICKSAVE",
|
"QUICKSAVE",
|
||||||
@@ -65,13 +66,15 @@ static const char * const status_text[POP_MSG_N] = {
|
|||||||
"QUICKLOAD",
|
"QUICKLOAD",
|
||||||
"NO QUICKLOAD",
|
"NO QUICKLOAD",
|
||||||
"SOUND ON",
|
"SOUND ON",
|
||||||
"SOUND OFF"
|
"SOUND OFF",
|
||||||
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t pop_status_msg; /* заявка (пишут откуда угодно) */
|
uint8_t pop_status_msg; /* заявка (пишут откуда угодно) */
|
||||||
uint8_t pop_status_ticks; /* сколько кадров ещё висит; читает HP */
|
uint8_t pop_status_ticks; /* сколько кадров ещё висит; читает HP */
|
||||||
static uint8_t st_shown; /* что реально нарисовано */
|
static uint8_t st_shown; /* что реально нарисовано */
|
||||||
static uint8_t st_dirty; /* бит на страницу дабл-буфера */
|
static uint8_t st_dirty; /* бит на страницу дабл-буфера */
|
||||||
|
static uint8_t st_arg; /* номер уровня для POP_MSG_LEVEL */
|
||||||
|
|
||||||
/* ---- верхняя полоса: отладочная строка --------------------------------- *
|
/* ---- верхняя полоса: отладочная строка --------------------------------- *
|
||||||
*
|
*
|
||||||
@@ -151,17 +154,77 @@ static void dec2(uint8_t v, char *buf)
|
|||||||
|
|
||||||
/* ---- нижняя полоса ----------------------------------------------------- */
|
/* ---- нижняя полоса ----------------------------------------------------- */
|
||||||
|
|
||||||
|
/* «LEVEL N» собирается вручную: printf сюда не тянем, а номер — единственное
|
||||||
|
* переменное место во всём наборе сообщений. */
|
||||||
|
static void level_text(char *buf)
|
||||||
|
{
|
||||||
|
buf[0] = 'L'; buf[1] = 'E'; buf[2] = 'V'; buf[3] = 'E'; buf[4] = 'L';
|
||||||
|
buf[5] = ' ';
|
||||||
|
dec2(st_arg, buf + 6);
|
||||||
|
}
|
||||||
|
|
||||||
static void status_draw(void)
|
static void status_draw(void)
|
||||||
{
|
{
|
||||||
|
char buf[9];
|
||||||
|
const char *text;
|
||||||
|
|
||||||
wipe(POP_STATUS_L, MSG_TOP, POP_STATUS_R, MSG_BOTTOM);
|
wipe(POP_STATUS_L, MSG_TOP, POP_STATUS_R, MSG_BOTTOM);
|
||||||
if (!st_shown) return;
|
if (!st_shown) return;
|
||||||
|
if (st_shown == POP_MSG_LEVEL) { level_text(buf); text = buf; }
|
||||||
|
else { text = status_text[st_shown]; }
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
pop_text_map();
|
pop_text_map();
|
||||||
pop_text_center_mapped(POP_TEXT_BIG, (POP_STATUS_L + POP_STATUS_R) / 2,
|
pop_text_center_mapped(POP_TEXT_BIG, (POP_STATUS_L + POP_STATUS_R) / 2,
|
||||||
MSG_BASE, status_text[st_shown]);
|
MSG_BASE, text);
|
||||||
pop_text_unmap();
|
pop_text_unmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Порт show_level (seg008:25A8). Демо-уровень 0 и номера от 14 не
|
||||||
|
* показываются вовсе, тринадцатый представляется двенадцатым, а бесшовный
|
||||||
|
* переход 12->13 молчит — и, как в оригинале, гасит флаг за собой. */
|
||||||
|
void pop_status_level(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t n = pop_current_level;
|
||||||
|
uint8_t quiet = pop_seamless;
|
||||||
|
|
||||||
|
pop_seamless = 0;
|
||||||
|
if (quiet || n == 0 || n >= POP_LEVEL_LAST) return;
|
||||||
|
if (n == 13) n = 12;
|
||||||
|
st_arg = n;
|
||||||
|
pop_status_msg = POP_MSG_LEVEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Показать сообщение НЕМЕДЛЕННО, не дожидаясь конца кадра.
|
||||||
|
*
|
||||||
|
* Обычный путь (заявка в pop_status_msg + pop_status_frame) печатает строку
|
||||||
|
* там же, где рисуется кадр, — то есть уже ПОСЛЕ длинной операции. На
|
||||||
|
* QuickSave это выглядело так: игра замирает на полсекунды, и только потом
|
||||||
|
* появляется «QUICKSAVE», когда объяснять фриз уже поздно. Здесь строка
|
||||||
|
* выкладывается сразу, до дисковой работы.
|
||||||
|
*
|
||||||
|
* Рисуем в ВИДИМУЮ страницу: игрок смотрит именно на неё, а до следующего
|
||||||
|
* флипа кадра ещё вся операция. Скрытой странице сообщение остаётся
|
||||||
|
* должным — её закроет обычный кадровый проход. */
|
||||||
|
void pop_status_show_now(uint8_t msg) __banked
|
||||||
|
{
|
||||||
|
uint8_t visible, saved_page, saved_bank;
|
||||||
|
|
||||||
|
pop_status_msg = msg;
|
||||||
|
if (!pop_ui_ready()) return; /* шрифта нет — останется заявкой */
|
||||||
|
st_shown = msg;
|
||||||
|
pop_status_ticks = (uint8_t)(msg ? MSG_TICKS : 0);
|
||||||
|
st_dirty = 3;
|
||||||
|
|
||||||
|
visible = (uint8_t)(gfx_get_visible_page() & 1);
|
||||||
|
saved_page = gfx_get_draw_page();
|
||||||
|
saved_bank = gfx_get_bank();
|
||||||
|
gfx_set_draw_page(visible);
|
||||||
|
status_draw();
|
||||||
|
st_dirty = (uint8_t)(st_dirty & ~(1 << visible));
|
||||||
|
gfx_set_draw_page(saved_page);
|
||||||
|
gfx_set_bank(saved_bank);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- верхняя полоса ---------------------------------------------------- */
|
/* ---- верхняя полоса ---------------------------------------------------- */
|
||||||
|
|
||||||
/* right — граница зоны (первый столбец СЛЕДУЮЩЕЙ подписи), w — её ширина. */
|
/* right — граница зоны (первый столбец СЛЕДУЮЩЕЙ подписи), w — её ширина. */
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ enum {
|
|||||||
POP_MSG_NO_QUICKLOAD, /* F9 нечего грузить / битый слот */
|
POP_MSG_NO_QUICKLOAD, /* F9 нечего грузить / битый слот */
|
||||||
POP_MSG_SOUND_ON, /* Ctrl+S */
|
POP_MSG_SOUND_ON, /* Ctrl+S */
|
||||||
POP_MSG_SOUND_OFF,
|
POP_MSG_SOUND_OFF,
|
||||||
|
POP_MSG_LEVEL, /* «LEVEL N» — строка собирается с номером */
|
||||||
POP_MSG_N
|
POP_MSG_N
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +35,17 @@ enum {
|
|||||||
* только если строка уже погасла (как у оригинала: он тоже сравнивает). */
|
* только если строка уже погасла (как у оригинала: он тоже сравнивает). */
|
||||||
extern uint8_t pop_status_msg;
|
extern uint8_t pop_status_msg;
|
||||||
|
|
||||||
|
/* Показать сообщение НЕМЕДЛЕННО, до конца кадра: строка выкладывается в
|
||||||
|
* видимую страницу прямо сейчас. Нужна там, где сразу за вызовом идёт
|
||||||
|
* длинная операция (QuickSave/QuickLoad): обычная заявка напечаталась бы
|
||||||
|
* уже ПОСЛЕ фриза, когда объяснять его поздно. */
|
||||||
|
void pop_status_show_now(uint8_t msg) __banked;
|
||||||
|
|
||||||
|
/* Порт show_level (seg008:25A8): заявить «LEVEL N» на старте уровня.
|
||||||
|
* Сама решает, показывать ли (демо-уровень и финальные номера молчат) —
|
||||||
|
* вызывающему условий знать не нужно. */
|
||||||
|
void pop_status_level(void) __banked;
|
||||||
|
|
||||||
/* Сколько логических кадров сообщение ещё висит; 0 — не висит. Читает
|
/* Сколько логических кадров сообщение ещё висит; 0 — не висит. Читает
|
||||||
* pop_hp_draw (банк 4), чтобы не затирать чужой текст своим фоном. */
|
* pop_hp_draw (банк 4), чтобы не затирать чужой текст своим фоном. */
|
||||||
extern uint8_t pop_status_ticks;
|
extern uint8_t pop_status_ticks;
|
||||||
|
|||||||
@@ -1052,6 +1052,9 @@ int pop_level_switch(void) __banked
|
|||||||
gfx_set_draw_page(old_page);
|
gfx_set_draw_page(old_page);
|
||||||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||||
|
/* Уровень готов и показан — заявить «LEVEL N» (порт
|
||||||
|
* show_level из draw_level_first, seg003:222). */
|
||||||
|
pop_status_level();
|
||||||
}
|
}
|
||||||
/* CBL намеренно НЕ открываем здесь. После загрузки caller ещё
|
/* CBL намеренно НЕ открываем здесь. После загрузки caller ещё
|
||||||
* может делать BIOS fade/palette либо восстанавливать QuickSave;
|
* может делать BIOS fade/palette либо восстанавливать QuickSave;
|
||||||
|
|||||||
Reference in New Issue
Block a user