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
+25
View File
@@ -46,6 +46,31 @@ void kbd_raw_close(void);
* на проводе) прибавить KBD_EXT к базовому коду. */
uint8_t kbd_raw_down(uint16_t code);
/* Считать ГОЛЫЕ (без префикса 0xE0) коды нумпада ТЕМИ ЖЕ клавишами, что и
* расширенные: 0x69..0x7D, кроме ESC 0x76 и F11 0x78. По умолчанию
* выключено — нумпад остаётся отдельным набором клавиш.
*
* В PS/2 это и есть одни и те же физические клавиши: навигационный блок
* шлёт код с префиксом 0xE0, нумпад — тот же код голым (KP4 = Left,
* KP8 = Up, KP7 = Home, KP9 = PgUp и т.д.). Флаг восстанавливает это
* равенство: kbd_raw_sync() переносит биты голых кодов в EXT-половину
* карты и гасит их в PLAIN.
*
* ЗАЧЕМ ЭТО НУЖНО (а не только «нумпад тоже работает»). При переполнении
* 3-байтового FIFO SIO теряется префикс, и make стрелки садится в PLAIN
* как код нумпада — а break придёт уже с префиксом и снимет бит в ДРУГОЙ
* половине. Клавиша остаётся зажатой НАВСЕГДА: kbd_raw_any_down()
* отвечает «да», и любое ожидание «пока ничего не нажато» виснет насмерть.
* После перекладки make и break работают с одним и тем же битом.
*
* Чего флаг НЕ лечит: потерю префикса у BREAK (`E0 F0 6B` -> `F0 6B`) —
* тогда бит стоит уже в EXT-половине и выглядит как реально зажатая
* клавиша; снимается перенажатием или typematic-повтором.
*
* Перекладку делает kbd_raw_sync(), то есть приложение обязано звать его
* раз в кадр (оно и так обязано — см. ниже). */
void kbd_raw_keypad_as_ext(uint8_t on);
/* Есть ли хотя бы одна зажатая клавиша (0/1). Полезно для экранов с
* семантикой «продолжить любой клавишей»; один вызов читает 64-байтную
* bitmap, а не перебирает все 512 scan-кодов через kbd_raw_down(). */