4e43890fce
Пройденная игра доходила до таблицы рекордов и умирала: программа
исчезала, машина следом вставала намертво (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>
104 lines
6.9 KiB
C
104 lines
6.9 KiB
C
/*
|
||
* kbd_raw_sync.c — восстановление held-состояния после Rx-overrun SIO.
|
||
*
|
||
* Аппаратный приёмный FIFO SIO — 3 байта. Если прерывания запрещены
|
||
* достаточно долго (DI-окна графики/кадрового IRQ), а в этот момент
|
||
* приходит пачка скан-кодов — FIFO переполняется, байт(ы) теряются
|
||
* аппаратно. Потерянный break → клавиша «залипает» в _kbdraw_down.
|
||
*
|
||
* Трамплин (и kbd_raw_poll) ловят overrun (RR1 бит5): делают Error Reset,
|
||
* чистят FSM префиксов _kbdraw_pending и взводят _kbdraw_overrun.
|
||
* Приложение зовёт kbd_raw_sync() раз в кадр — по флагу сбрасывается
|
||
* held-состояние карты. Реально зажатые клавиши перечитаются make-повтором
|
||
* (typematic) за ~0.1 с.
|
||
*
|
||
* МОДИФИКАТОРЫ ИЗ СБРОСА ИСКЛЮЧЕНЫ (2026-08-03, третья редакция). История
|
||
* вопроса — две неудачные крайности:
|
||
*
|
||
* 1) исключать модификаторы, не имея иного способа их проверить:
|
||
* потерянный break Shift снять нечем, Shift залипает навсегда
|
||
* (BUG-KBD-3);
|
||
* 2) сбрасывать ВСЁ, как DSS (KBD_Receiver_Overrun в KEYINTER.ASM чистит
|
||
* и KEYCTRL, и KEY_FLG): залипания нет, но удерживаемый Shift сносится
|
||
* каждым overrun'ом, а при зажатом Shift КАЖДЫЙ тап стрелки — это 10
|
||
* байт в 3-байтовый FIFO, то есть overrun почти гарантирован. Игрок
|
||
* жмёт Shift+→ ожидая осторожный шаг, а Кид уходит в бег — в PoP это
|
||
* прямо стоит жизни (яма/пики). Хуже залипания (BUG-KBD-4).
|
||
*
|
||
* Выбран вариант (1) — модификаторы НЕ сбрасываем. Была и третья редакция,
|
||
* обещавшая снять сам размен: «fake shift» (E0 F0 12 / E0 12) читался в обе
|
||
* стороны, и расширенный make без обёртки снимал Shift, то есть залипание
|
||
* лечилось первым же тапом стрелки. Обратный вывод оказался неверным
|
||
* (замер 2026-08-05, BUG-GRAB-1: обёртку шлют не все клавиатуры и никогда —
|
||
* на typematic-повторах), и он ломал Shift вместе со стрелками; убран, см.
|
||
* _kbdraw.h. Значит размен вернулся, и здесь он решается в пользу
|
||
* «лучше залипание, чем отвал»: залипший модификатор снимается его же
|
||
* перенажатием, а сорванный посреди игры Shift в PoP стоит жизни.
|
||
*
|
||
* Overrun теперь редок сам по себе: FIFO вычерпывается опросом из главного
|
||
* цикла (kbd_raw_poll, KBD-1), а не только по пойманному прерыванию.
|
||
*/
|
||
#include <string.h>
|
||
#include "_kbdraw.h"
|
||
|
||
/* Скан-коды модификаторов (PS/2 Set 2): L/R Shift, LCtrl, LAlt,
|
||
* RCtrl (E0 14 → +256), RAlt (E0 11 → +256). */
|
||
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;
|
||
|
||
/* Карта упакована по битам (см. _kbdraw.h): сохраняем/возвращаем
|
||
* именно БИТ модификатора, а не байт. */
|
||
for (i = 0; i < 6; i++) {
|
||
uint16_t c = _kbd_mod_codes[i];
|
||
save[i] = (uint8_t)((_kbdraw_down[c >> 3] >> (c & 7)) & 1);
|
||
}
|
||
memset(_kbdraw_down, 0, sizeof(_kbdraw_down));
|
||
for (i = 0; i < 6; i++) {
|
||
uint16_t c = _kbd_mod_codes[i];
|
||
if (save[i]) _kbdraw_down[c >> 3] |= (uint8_t)(1 << (c & 7));
|
||
}
|
||
/* FSM префиксов трамплин уже сбросил; дублируем на случай overrun'а,
|
||
* пойманного вне его (kbd_raw_poll между кадрами). */
|
||
_kbdraw_pending = 0;
|
||
}
|