SprPoP: финал больше не убивает программу — прямой вызов в чужой банк

Пройденная игра доходила до таблицы рекордов и умирала: программа
исчезала, машина следом вставала намертво (di;halt на 0x0000) либо уходила
в reset.  Одинаково из Flex Navigator и из голого DSS.

КОРЕНЬ.  pop_ui.h объявлял группу pop_text_*_mapped БЕЗ __banked.  Пока
pop_hof.c лежал в банке 9 рядом с pop_ui.c, прямой call был верен; после
переноса pop_hof/pop_config/pop_pal в банк 10 тот же call стал уходить в
пустой хвост чужого банка.  Процессор полз по 0xFF до 0x0000, где ловушка
DSS ставит B=0x27 и сворачивает процесс — подмена страниц W1/W2/W3,
которую было видно на трупе, оказалась уборкой, а не причиной.

Точную инструкцию (call $E503 = _pop_text_map банка 9) дала трассировка
MAME на узком участке: trace включалась брейкпоинтом на входе в
pop_hof_show и выключалась на процедуре завершения процесса DSS (0x1E56).

ЧТО СДЕЛАНО

* pop_ui.h/.c — группа text_*_mapped помечена __banked.
* toolchain/check_bank_calls.py — две проверки банкового кода:
  1) прямой call в чужой банк (доказательна, ВАЛИТ сборку — проверено
     намеренной поломкой);
  2) указатель на данные своего банка, отданный в чужой (эвристика по
     форме кода, только предупреждает).
  Встроена в app.mk, запускается сразу после линковки.
* pop_hof.c — курсор ввода строится на стеке: литерал "_" лежал в _BANK10
  и после пометки __banked уезжал из-под ног чужому банку, заливая экран
  знаками вопроса.
* libc: kbd_raw_keypad_as_ext() — kbd_raw_sync переносит голые коды
  нумпада в EXT-половину карты.  Лечит залипание стрелок (потерянный
  префикс E0 сажал make в PLAIN как код нумпада, и снять его было нечем),
  заодно нумпад стал управлением: 7/8/9, 4/6, 2 и 5 = вниз.
* pop_pace.c — цикл ожидания луча зовёт тот же idle-хук, что и
  gfx_wait_vsync: без этого F10 в геймплее не работал вовсе.
* pop_hof.c — Esc в таблице рекордов отменяет запись (расхождение с
  оригиналом записано в docs/impl_diff.md).
* Экран версии показывается только через Menu/Settings/About: стартовый
  показ и Ctrl+V убраны, мёртвый код снят.
* sprpop_cold.c — pop_start_level зовёт pop_hp_invalidate: после Ctrl+A с
  выросшим за уровень максимумом полоса HP моргала между страницами.

Разбор всех четырёх багов — в applications/PoP/roomtest/BUGS_CLOSED.md
(FINAL-BANKCALL, FINAL-HOF-GARBAGE, KBD-ARROW-PHANTOM, F10-GAMEPLAY),
правило про банки — в applications/SprPoP/CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 22:11:01 +03:00
parent 25b2db8b0b
commit 4e43890fce
22 changed files with 645 additions and 89 deletions
+8 -9
View File
@@ -99,7 +99,7 @@ static void find_start_level_door(uint8_t room)
int8_t pop_frame_ui(uint8_t *restart_level, uint8_t *dead_reset) __banked
{
static uint8_t esc_prev, qs_save_prev, qs_load_prev;
static uint8_t restart_prev, version_prev;
static uint8_t restart_prev;
static uint8_t intro_prev, showtime_prev, timer_prev;
uint8_t e = (uint8_t)(kbd_raw_down(KBD_MENU) || kbd_raw_down(KBD_MENU_ALT));
uint8_t ctrl = (uint8_t)(kbd_raw_down(KBD_LCTRL) || kbd_raw_down(KBD_RCTRL));
@@ -142,14 +142,6 @@ int8_t pop_frame_ui(uint8_t *restart_level, uint8_t *dead_reset) __banked
if (r && !restart_prev) *restart_level = 1;
restart_prev = r;
}
/* Ctrl+V — версия сборки. Функция уже есть, до сих пор её показывал
* только старт программы. */
{
uint8_t v = (uint8_t)(ctrl && kbd_raw_down(KBD_VERSION));
if (v && !version_prev) pop_build_info_show();
version_prev = v;
}
/* Ctrl+R — вернуться в заставку. Тот же переход, что «Restart Game»
* пунктом меню: автомат сам разложит title/intro с первого экрана. */
{
@@ -431,6 +423,13 @@ void pop_start_level(void) __banked
* гасит заставка «LEVEL N», которую этот же переход и пропускает. */
if (pop_seamless) { pop_seamless = 0; pop_kid_hp_carry(); }
else pop_kid_hp_reset();
/* ПОЛОСУ HP — НА ОБЕИХ СТРАНИЦАХ. Старт уровня перерисовал фон, то есть
* стёр её, а сама она обновляется только при ИЗМЕНЕНИИ чисел. После
* Ctrl+A с выросшим за уровень максимумом (большой кувшин, чит Shift+T)
* это давало моргание: одна страница показывала старые деления, вторая
* новые (поймано пользователем 2026-08-27). invalidate ровно для этого
* и заведён — см. его шапку в pop_cdraw.c. */
pop_hp_invalidate();
}
/* ---- ОТЛАДКА: обход комнат по номеру -------------------------------- *