Files
Sprinter-SDCC/libc/kbd/kbd_raw_sync.c
T
snark13 4e43890fce 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>
2026-08-27 22:11:01 +03:00

104 lines
6.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}