From 5756101d295df4e4e66e00e127ebc76b0826c085 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Tue, 25 Aug 2026 14:13:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D0=B0=D1=82=D1=83=D1=81-=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BA=D0=B0:=20LEVEL=20N=20=D0=B8=20=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9=D0=B1=D0=BB=20QS/QL=20=D0=B4=D0=BE=20=D0=B4=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B2=D0=BE=D0=B9=20=D0=BE=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- applications/PoP/docs/impl_diff.md | 26 +++++++++ applications/PoP/docs/status_line_text.md | 21 ++++--- applications/PoP/roomtest/pop_qsave.c | 20 ++++--- applications/PoP/roomtest/pop_status.c | 67 ++++++++++++++++++++++- applications/PoP/roomtest/pop_status.h | 12 ++++ applications/PoP/roomtest/roomtest_cold.c | 3 + 6 files changed, 130 insertions(+), 19 deletions(-) diff --git a/applications/PoP/docs/impl_diff.md b/applications/PoP/docs/impl_diff.md index 55dac36..40976b8 100644 --- a/applications/PoP/docs/impl_diff.md +++ b/applications/PoP/docs/impl_diff.md @@ -577,3 +577,29 @@ seg008.c:1600). XOR идёт по 24-битному RGB того, что УЖЕ кайма станет резать глаз — вариантов два: запечь второй набор под светлый фон (ещё 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`, а не первая строка: отказ +идёт обычной заявкой и печатается кадровым проходом, то есть на кадр позже. diff --git a/applications/PoP/docs/status_line_text.md b/applications/PoP/docs/status_line_text.md index 1cba2ca..4b28649 100644 --- a/applications/PoP/docs/status_line_text.md +++ b/applications/PoP/docs/status_line_text.md @@ -139,18 +139,23 @@ display_text_bottom: draw_rect(чёрным) + show_text(halign_center, valign_b ## 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`), текста нет; -* `QUICKSAVE`/`QUICKLOAD` — у нас вместо строки экран-уведомление (`ui_notice`); -* `SOUND ON/OFF` — у нас индикатор в отладочной полосе, а не текст; +* вся группа времени — таймер есть (`pop_timer.c`), текста нет; * `Press Button to Continue` — у нас авто-респавн по таймеру/стрелке вверх; -* номера комнат (`C`/Shift+`C`) — у нас палочки в отладочной полосе. +* номера комнат (`C`/Shift+`C`) — у нас отдельная отладочная строка. Нам не нужно: copy protection (уровень 15 исключён из порта — см. `full_game_plan.md`), joystick-режимы, replay, скриншоты. diff --git a/applications/PoP/roomtest/pop_qsave.c b/applications/PoP/roomtest/pop_qsave.c index 637c269..5a48df4 100644 --- a/applications/PoP/roomtest/pop_qsave.c +++ b/applications/PoP/roomtest/pop_qsave.c @@ -225,6 +225,11 @@ int8_t pop_qsave_process(void) __banked int8_t rc=-1; pop_qsave_request = 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); if (!blk) return -1; page=mem_get_page(blk,0); @@ -238,14 +243,11 @@ int8_t pop_qsave_process(void) __banked } pop_sfx_start(); mem_free_block(blk); - /* Сообщение заявляем ЗДЕСЬ, а не у вызывающего: только тут известно, - * что именно делали (F6/F9 и оба пункта меню приходят одним запросом). - * Порт seg000:497/514 — те же четыре строки, что и в оригинале. */ - if (request == QS_SAVE) - pop_status_msg = (uint8_t)(rc > 0 ? POP_MSG_QUICKSAVE - : POP_MSG_NO_QUICKSAVE); - else - pop_status_msg = (uint8_t)(rc > 0 ? POP_MSG_QUICKLOAD - : POP_MSG_NO_QUICKLOAD); + /* Успех ничего не меняет: нужная строка висит с начала операции. + * Отказ переписывает её на NO QUICKSAVE/NO QUICKLOAD — обычной + * заявкой, кадр её подхватит (порт seg000:497/514). */ + if (rc <= 0) + pop_status_msg = (uint8_t)(request == QS_SAVE ? POP_MSG_NO_QUICKSAVE + : POP_MSG_NO_QUICKLOAD); return rc; } diff --git a/applications/PoP/roomtest/pop_status.c b/applications/PoP/roomtest/pop_status.c index c4cf2f8..2ceaaa5 100644 --- a/applications/PoP/roomtest/pop_status.c +++ b/applications/PoP/roomtest/pop_status.c @@ -58,6 +58,7 @@ #define MSG_TOP (MSG_BASE - POP_FONT_BIG_ASCENT - 2) #define MSG_BOTTOM (MSG_BASE) +/* POP_MSG_LEVEL здесь пустой: его строка собирается с номером уровня. */ static const char * const status_text[POP_MSG_N] = { "", "QUICKSAVE", @@ -65,13 +66,15 @@ static const char * const status_text[POP_MSG_N] = { "QUICKLOAD", "NO QUICKLOAD", "SOUND ON", - "SOUND OFF" + "SOUND OFF", + "" }; uint8_t pop_status_msg; /* заявка (пишут откуда угодно) */ uint8_t pop_status_ticks; /* сколько кадров ещё висит; читает HP */ static uint8_t st_shown; /* что реально нарисовано */ 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) { + char buf[9]; + const char *text; + wipe(POP_STATUS_L, MSG_TOP, POP_STATUS_R, MSG_BOTTOM); 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); pop_text_map(); 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(); } +/* Порт 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 — её ширина. */ diff --git a/applications/PoP/roomtest/pop_status.h b/applications/PoP/roomtest/pop_status.h index cf1eca2..dbfd659 100644 --- a/applications/PoP/roomtest/pop_status.h +++ b/applications/PoP/roomtest/pop_status.h @@ -26,6 +26,7 @@ enum { POP_MSG_NO_QUICKLOAD, /* F9 нечего грузить / битый слот */ POP_MSG_SOUND_ON, /* Ctrl+S */ POP_MSG_SOUND_OFF, + POP_MSG_LEVEL, /* «LEVEL N» — строка собирается с номером */ POP_MSG_N }; @@ -34,6 +35,17 @@ enum { * только если строка уже погасла (как у оригинала: он тоже сравнивает). */ 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 — не висит. Читает * pop_hp_draw (банк 4), чтобы не затирать чужой текст своим фоном. */ extern uint8_t pop_status_ticks; diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index e940a52..106cf75 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -1052,6 +1052,9 @@ int pop_level_switch(void) __banked gfx_set_draw_page(old_page); full.x = 0; full.y = 0; full.w = 320; full.h = 256; gfx_copy_page(&full, GFX_COPY_DIRECT); + /* Уровень готов и показан — заявить «LEVEL N» (порт + * show_level из draw_level_first, seg003:222). */ + pop_status_level(); } /* CBL намеренно НЕ открываем здесь. После загрузки caller ещё * может делать BIOS fade/palette либо восстанавливать QuickSave;