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:
@@ -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(). */
|
||||
|
||||
@@ -57,4 +57,28 @@ extern uint8_t _kbdraw_overrun; /* 1 = ISR поймал Rx-overrun SIO (по
|
||||
* читает kbd_raw_down(). */
|
||||
extern uint8_t _kbdraw_down[64];
|
||||
|
||||
/* ГОЛЫЕ КОДЫ НУМПАДА = ТЕ ЖЕ КЛАВИШИ, ЧТО И РАСШИРЕННЫЕ
|
||||
* (взводит kbd_raw_keypad_as_ext).
|
||||
*
|
||||
* Стрелки приходят как E0 6B / E0 72 / E0 74 / E0 75, то есть в EXT-половину
|
||||
* карты. Если префикс E0 потерян (переполнение 3-байтового FIFO SIO в
|
||||
* длинном DI-окне), make садится в PLAIN-половину как код нумпада, а его
|
||||
* break приходит уже с префиксом и снимает бит в EXT-половине — PLAIN-бит
|
||||
* остаётся зажатым НАВСЕГДА (BUG-KBD: 0x72 2026-08-24, 0x6B 2026-08-27).
|
||||
*
|
||||
* Лечится перекладкой: kbd_raw_sync() переносит биты голых кодов нумпада
|
||||
* в EXT-половину и гасит их в PLAIN. Тогда make без префикса ставит ТОТ ЖЕ
|
||||
* бит, который потом снимет обычный break `E0 F0 xx`, — залипание исчезает,
|
||||
* а нумпад заодно работает как навигационные клавиши (в PS/2 это и есть
|
||||
* одни и те же физические клавиши). Позиции бит совпадают: EXT — те же
|
||||
* байты карты плюс 32.
|
||||
*
|
||||
* Диапазон Set 2 — 0x69..0x7D, но ESC (0x76) и F11 (0x78) лежат внутри и
|
||||
* перекладке НЕ подлежат.
|
||||
*
|
||||
* Чего это не лечит: потерю префикса у BREAK (`E0 F0 6B` -> `F0 6B`). Тогда
|
||||
* бит остаётся стоять уже в EXT-половине, то есть выглядит как реально
|
||||
* зажатая клавиша; снимается перенажатием или typematic-повтором. */
|
||||
extern uint8_t _kbdraw_keypad_ext;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,3 +10,6 @@ uint8_t _kbdraw_pending;
|
||||
uint8_t _kbdraw_overrun; /* ISR взвёл при Rx-overrun SIO (потерян
|
||||
* break) — kbd_raw_sync снимет залипшее */
|
||||
uint8_t _kbdraw_down[64]; /* БИТ на скан-код (см. _kbdraw.h) */
|
||||
uint8_t _kbdraw_keypad_ext; /* 1 = голые коды нумпада считать теми же
|
||||
* клавишами, что и расширенные
|
||||
* (kbd_raw_keypad_as_ext; см. _kbdraw.h) */
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* kbd_raw_keypad_as_ext — голые коды нумпада = те же клавиши, что и
|
||||
* расширенные (стрелки и навигация).
|
||||
*
|
||||
* Мотивация и разбор залипания — в шапке _kbdraw.h рядом с
|
||||
* _kbdraw_keypad_ext; публичный контракт — в <kbd_raw.h>. Саму перекладку
|
||||
* делает kbd_raw_sync(): в декодерах ей места нет — клавиатурный блок
|
||||
* трамплина уже длиннее 127 байт, а `jp` внутри него запрещён (копия в W2),
|
||||
* и вставка рвала относительные переходы.
|
||||
*/
|
||||
#include <kbd_raw.h>
|
||||
#include "_kbdraw.h"
|
||||
|
||||
void kbd_raw_keypad_as_ext(uint8_t on)
|
||||
{
|
||||
_kbdraw_keypad_ext = (uint8_t)(on ? 1 : 0);
|
||||
}
|
||||
@@ -47,11 +47,42 @@ static const uint16_t _kbd_mod_codes[6] = {
|
||||
0x12, 0x59, 0x14, 0x11, 0x0114, 0x0111
|
||||
};
|
||||
|
||||
/* Голые коды нумпада (Set 2 0x69..0x7D) — по БИТАМ карты: байт 13 держит
|
||||
* 0x68..0x6F, байт 14 — 0x70..0x77, байт 15 — 0x78..0x7F. Гасим только
|
||||
* клавиши нумпада: ESC (0x76, байт 14 бит 6) и F11 (0x78, байт 15 бит 0)
|
||||
* обязаны проходить, поэтому маски именно такие, а не 0xFF. */
|
||||
#define KP_BYTE0 13
|
||||
#define KP_MASK0 0x1A /* 0x69 KP1, 0x6B KP4, 0x6C KP7 */
|
||||
#define KP_BYTE1 14
|
||||
#define KP_MASK1 0xBF /* 0x70..0x75, 0x77 — всё, кроме ESC 0x76 */
|
||||
#define KP_BYTE2 15
|
||||
#define KP_MASK2 0x3E /* 0x79..0x7D — всё, кроме F11 0x78 */
|
||||
|
||||
void kbd_raw_sync(void)
|
||||
{
|
||||
uint8_t save[6];
|
||||
uint8_t i;
|
||||
|
||||
/* ПЕРЕКЛАДЫВАЕМ ГОЛЫЕ КОДЫ НУМПАДА В EXT-ПОЛОВИНУ, если приложение
|
||||
* попросило. Делается здесь, а не в декодерах: клавиатурный блок
|
||||
* трамплина и так длиннее 127 байт (внутри него `jp` запрещён —
|
||||
* трамплин копируется в W2), и вставка рвала относительные переходы.
|
||||
* Цена — шесть операций раз в кадр вместо проверки на каждый байт.
|
||||
*
|
||||
* Почему это лечит залипание: стрелка приходит как E0 6B, и при потере
|
||||
* префикса make садится в PLAIN-половину как код нумпада, а break
|
||||
* придёт уже с префиксом и снимет бит в EXT — plain-бит остался бы
|
||||
* зажатым навсегда. После перекладки make и break работают с ОДНИМ и
|
||||
* тем же битом. Позиции бит совпадают: EXT — те же байты плюс 32. */
|
||||
if (_kbdraw_keypad_ext) {
|
||||
_kbdraw_down[KP_BYTE0 + 32] |= (uint8_t)(_kbdraw_down[KP_BYTE0] & KP_MASK0);
|
||||
_kbdraw_down[KP_BYTE1 + 32] |= (uint8_t)(_kbdraw_down[KP_BYTE1] & KP_MASK1);
|
||||
_kbdraw_down[KP_BYTE2 + 32] |= (uint8_t)(_kbdraw_down[KP_BYTE2] & KP_MASK2);
|
||||
_kbdraw_down[KP_BYTE0] &= (uint8_t)~KP_MASK0;
|
||||
_kbdraw_down[KP_BYTE1] &= (uint8_t)~KP_MASK1;
|
||||
_kbdraw_down[KP_BYTE2] &= (uint8_t)~KP_MASK2;
|
||||
}
|
||||
|
||||
if (!_kbdraw_overrun) return;
|
||||
_kbdraw_overrun = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user