KBD: состояние Shift по «fake shift» — ни залипания, ни отвала
Клавиатура PS/2 обёртывает КАЖДЫЙ расширенный код парой E0 F0 12 / E0 12,
пока реально зажат Shift (замер на железе/MAME: правый шифт обёртывается
своим кодом 0x59). Это прямое и непрерывное свидетельство состояния
Shift — единственное доступное, потому что опросить PS/2 нельзя, а
typematic повторяет последнюю нажатую клавишу, то есть стрелку.
Оба декодера (_irq_tramp.c, kbd_raw_poll.c) читают обёртку в обе стороны:
обёртка есть -> Shift взвести; расширенный make без обёртки -> Shift
снять. Бит взводит общий писатель — достаточно обнулить префиксы, и код
уходит в plain-половину карты как make.
Это снимает размен, между крайностями которого мы метались:
- исключать модификаторы из сброса по overrun -> Shift залипал навсегда
(BUG-KBD-3);
- сбрасывать всю карту, как DSS -> Shift сносился каждым overrun'ом, а
при зажатом Shift тап стрелки это 10 байт в 3-байтовый FIFO, то есть
overrun почти гарантирован (BUG-KBD-4).
Теперь kbd_raw_sync снова не трогает модификаторы, и это безопасно:
залипание снимается первым же нажатием стрелки.
Раскладка трамплина: клавиатурный блок перевалил за 127 байт, а jp внутри
запрещён (копия в W2). Префиксные обработчики переехали вплотную к своим
cp, посередине тела стоят ретрансляторы tr_kbd_hub/tr_hub_notkbd/
tr_hub_dss. В kbd_raw_poll такого ограничения нет — там три jp.
Проверено в MAME: Shift переживает пять тапов подряд; штатное отпускание
снимает; искусственно залипший бит снимается первым тапом.
make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,33 @@ extern uint8_t _kbdraw_pending; /* FSM декодера make/break, см. в
|
||||
extern uint8_t _kbdraw_overrun; /* 1 = ISR поймал Rx-overrun SIO (потерян
|
||||
* байт → возможно потерян break); снимает
|
||||
* kbd_raw_sync (чистит _kbdraw_down) */
|
||||
/* «Fake shift» (замер на железе/MAME 2026-08-03). Пока реально зажат
|
||||
* Shift, клавиатура PS/2 обёртывает КАЖДЫЙ расширенный код парой служебных
|
||||
* посылок: перед make идёт E0 F0 12 (снять fake shift), после break — E0 12
|
||||
* (вернуть). Это не клавиши, а прямое и непрерывное свидетельство того,
|
||||
* зажат ли Shift на самом деле, — и единственный способ узнать это, не
|
||||
* полагаясь на целость потока (PS/2 состояние опросить нельзя, а typematic
|
||||
* повторяет только ПОСЛЕДНЮЮ нажатую клавишу, то есть стрелку, не Shift).
|
||||
*
|
||||
* Декодеры (трамплин и kbd_raw_poll) используют его в обе стороны:
|
||||
* - увидели fake shift -> реальный Shift ЗАЖАТ -> взвести plain-бит;
|
||||
* - расширенный make БЕЗ предшествующей обёртки -> Shift ОТПУЩЕН ->
|
||||
* снять plain-биты обоих Shift.
|
||||
* Отсюда: залипший Shift снимается первым же нажатием стрелки, а
|
||||
* kbd_raw_sync больше не обязан сбрасывать модификаторы «на всякий случай»
|
||||
* (из-за чего Shift отваливался посреди игры — BUG-KBD-4).
|
||||
*
|
||||
* Хранит промежуточное состояние: 1 = разобрана break-форма (E0 F0 12),
|
||||
* ждём расширенный код, который она обёртывает. */
|
||||
extern uint8_t _kbdraw_fakesh;
|
||||
|
||||
/* Позиции бит модификаторов в карте (используют и декодеры на асме, и
|
||||
* kbd_raw_sync): L Shift 0x12 -> байт 2 бит 2, R Shift 0x59 -> байт 11 бит 1. */
|
||||
#define _KBDRAW_LSH_BYTE 2
|
||||
#define _KBDRAW_LSH_BIT 2
|
||||
#define _KBDRAW_RSH_BYTE 11
|
||||
#define _KBDRAW_RSH_BIT 1
|
||||
|
||||
/* Held-состояние: БИТ на скан-код, код>>3 = байт, код&7 = номер бита;
|
||||
* коды 0..255 обычные, 256..511 — расширенные (префикс 0xE0), т.е.
|
||||
* смещение +32 байта. Упаковка вместо байта на код: 64 Б вместо 512 —
|
||||
|
||||
@@ -9,4 +9,7 @@ uint8_t _kbdraw_active;
|
||||
uint8_t _kbdraw_pending;
|
||||
uint8_t _kbdraw_overrun; /* ISR взвёл при Rx-overrun SIO (потерян
|
||||
* break) — kbd_raw_sync снимет залипшее */
|
||||
uint8_t _kbdraw_fakesh; /* декодирована break-форма fake shift —
|
||||
* ждём расширенный код, который она
|
||||
* обёртывает (см. _kbdraw.h) */
|
||||
uint8_t _kbdraw_down[64]; /* БИТ на скан-код (см. _kbdraw.h) */
|
||||
|
||||
+49
-4
@@ -61,13 +61,54 @@ uint8_t kbd_raw_poll(void) __naked
|
||||
kp_drain:
|
||||
in a, (#_IRQ_SIO_DATA) ; чтение деструктивно = дренаж байта
|
||||
cp #0xF0
|
||||
jr Z, kp_setbrk
|
||||
jp Z, kp_setbrk ; jp, а не jr: тело цикла длиннее 127 Б (fake shift)
|
||||
cp #0xE0
|
||||
jr Z, kp_setext
|
||||
jp Z, kp_setext ; (в отличие от трамплина, здесь jp разрешён)
|
||||
|
||||
;; --- «fake shift» — тот же разбор, что в трамплине (см. _kbdraw.h):
|
||||
;; расширенные 12/59 не клавиши, а обёртка вокруг расширенного кода,
|
||||
;; которую клавиатура шлёт, пока реально зажат Shift. ---
|
||||
ld e, a ; E = код
|
||||
ld a, (__kbdraw_pending)
|
||||
bit 1, a ; EXT?
|
||||
ld a, e ; A = код обратно (ld флаги не трогает)
|
||||
jr Z, kp_code ; обычный код — прежний путь
|
||||
cp #0x12
|
||||
jr Z, kp_fake
|
||||
cp #0x59
|
||||
jr Z, kp_fake
|
||||
;; расширенная КЛАВИША: make без обёртки => Shift не зажат
|
||||
ld a, (__kbdraw_pending)
|
||||
bit 0, a ; break — состояние Shift не судим
|
||||
jr NZ, kp_fkclr
|
||||
ld a, (__kbdraw_fakesh)
|
||||
or a, a
|
||||
jr NZ, kp_fkclr ; обёртка была => Shift зажат
|
||||
ld hl, #(__kbdraw_down + _KBDRAW_LSH_BYTE)
|
||||
res _KBDRAW_LSH_BIT, (hl)
|
||||
ld hl, #(__kbdraw_down + _KBDRAW_RSH_BYTE)
|
||||
res _KBDRAW_RSH_BIT, (hl)
|
||||
kp_fkclr:
|
||||
xor a, a
|
||||
ld (__kbdraw_fakesh), a
|
||||
ld a, e
|
||||
jr kp_code
|
||||
|
||||
kp_fake:
|
||||
;; Бит взводит ОБЩИЙ писатель ниже: обнуляем префиксы — и он кладёт
|
||||
;; код в PLAIN-половину карты (без +32) и именно взведёт бит.
|
||||
ld a, (__kbdraw_pending)
|
||||
and #1 ; 1 = break-форма (идёт ПЕРЕД обёрнутым make)
|
||||
ld (__kbdraw_fakesh), a
|
||||
xor a, a
|
||||
ld (__kbdraw_pending), a
|
||||
ld a, e
|
||||
;; fall through в kp_code
|
||||
|
||||
;; --- обычный байт кода: БИТ code&7 в _kbdraw_down[code>>3].
|
||||
;; Маска сдвигом, а не таблицей — ровно как в трамплине, чтобы
|
||||
;; расхождения декодеров были невозможны by construction. ---
|
||||
kp_code:
|
||||
ld e, a ; E = код
|
||||
and #7
|
||||
ld b, a
|
||||
@@ -120,7 +161,7 @@ uint8_t kbd_raw_poll(void) __naked
|
||||
kp_next:
|
||||
in a, (#_IRQ_SIO_A)
|
||||
rrca ; ещё байт в FIFO?
|
||||
jr C, kp_drain
|
||||
jp C, kp_drain
|
||||
|
||||
;; --- FIFO пуст: тот же разбор Rx-overrun, что и в трамплине.
|
||||
;; RR1 бит5 = Rx Overrun; без Error Reset (WR0 = 0x30) бит залипнет
|
||||
@@ -132,7 +173,11 @@ uint8_t kbd_raw_poll(void) __naked
|
||||
jr Z, kp_noovr
|
||||
ld a, #0x30
|
||||
out (#_IRQ_SIO_A), a ; WR0 Error Reset
|
||||
ld a, #1
|
||||
;; Сброс FSM префиксов — как KBD_Receiver_Overrun у DSS (чистит
|
||||
;; KEY_FLG): повисший 0xF0/0xE0 инвертировал бы следующий код.
|
||||
xor a, a
|
||||
ld (__kbdraw_pending), a
|
||||
inc a
|
||||
ld (__kbdraw_overrun), a ; снимет kbd_raw_sync()
|
||||
kp_noovr:
|
||||
pop bc
|
||||
|
||||
+39
-14
@@ -3,21 +3,40 @@
|
||||
*
|
||||
* Аппаратный приёмный FIFO SIO — 3 байта. Если прерывания запрещены
|
||||
* достаточно долго (DI-окна графики/кадрового IRQ), а в этот момент
|
||||
* приходит пачка скан-кодов (напр. быстрый тап стрелки = E0 74 + E0 F0 74,
|
||||
* 5 байт) — FIFO переполняется, байт(ы) теряются аппаратно. Потерянный
|
||||
* break → клавиша «залипает» в _kbdraw_down.
|
||||
* приходит пачка скан-кодов — FIFO переполняется, байт(ы) теряются
|
||||
* аппаратно. Потерянный break → клавиша «залипает» в _kbdraw_down.
|
||||
*
|
||||
* Трамплин ловит overrun (RR1 бит5) и взводит _kbdraw_overrun. Приложение
|
||||
* зовёт kbd_raw_sync() раз в кадр: при взведённом флаге сбрасывается
|
||||
* held-состояние ВСЕХ клавиш, КРОМЕ модификаторов (Shift/Ctrl/Alt —
|
||||
* таблица ниже). Обычные реально зажатые клавиши «перечитаются» их
|
||||
* make-повтором (typematic) за ~0.1 с. Модификаторы НЕ сбрасываем
|
||||
* принципиально: PS/2 автоповторяет только ПОСЛЕДНЮЮ нажатую клавишу,
|
||||
* удерживаемый Shift при повторе стрелки не шлёт НИЧЕГО — сброшенный
|
||||
* модификатор невозможно перечитать, он «отваливался» до перенажатия
|
||||
* (баг PoP: Shift+→ = осторожный шаг превращался в бег, 2026-07-22).
|
||||
* Цена: если overrun потерял именно break модификатора, тот залипнет до
|
||||
* следующего нажатия — редкий случай против регулярного отвала.
|
||||
* Трамплин (и 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).
|
||||
*
|
||||
* Третий вариант убирает сам размен. Те самые лишние байты — это «fake
|
||||
* shift» (E0 F0 12 перед расширенным make, E0 12 после break), и шлются они
|
||||
* ТОЛЬКО пока Shift реально зажат. Декодеры (_irq_tramp.c, kbd_raw_poll.c)
|
||||
* теперь читают их в обе стороны: обёртка есть → Shift взвести, расширенный
|
||||
* make без обёртки → Shift снять. Значит:
|
||||
* - сбрасывать модификаторы здесь НЕ НАДО (состояние Shift подтверждается
|
||||
* независимо от того, что съел overrun);
|
||||
* - залипание невозможно: первый же тап стрелки снимет ложный Shift.
|
||||
*
|
||||
* Ctrl/Alt fake-shift'ом не покрыты (клавиатура их не обёртывает). Их
|
||||
* оставляем по тому же принципу «лучше залипание, чем отвал»: залипший
|
||||
* Ctrl/Alt в PoP не управляет ничем критичным и снимается перенажатием.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "_kbdraw.h"
|
||||
@@ -47,4 +66,10 @@ void kbd_raw_sync(void)
|
||||
uint16_t c = _kbd_mod_codes[i];
|
||||
if (save[i]) _kbdraw_down[c >> 3] |= (uint8_t)(1 << (c & 7));
|
||||
}
|
||||
/* FSM префиксов трамплин уже сбросил; дублируем на случай overrun'а,
|
||||
* пойманного вне его (kbd_raw_poll между кадрами). Незакрытую обёртку
|
||||
* fake shift тоже считаем недействительной: её вторая половина могла
|
||||
* пропасть вместе с потерянными байтами. */
|
||||
_kbdraw_pending = 0;
|
||||
_kbdraw_fakesh = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user