From 030af7463147a3f19bffbd34fe5418db0645abfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=9F=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= Date: Wed, 5 Aug 2026 12:33:15 +0300 Subject: [PATCH] =?UTF-8?q?BUG-KBD-5:=20=D0=B7=D0=B0=D0=B6=D0=B0=D1=82?= =?UTF-8?q?=D1=8B=D0=B9=20Shift=20=D1=81=D0=BD=D0=B8=D0=BC=D0=B0=D0=BB?= =?UTF-8?q?=D1=81=D1=8F=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BF=D0=BE=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D0=BC=20=D1=81=D1=82=D1=80=D0=B5=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Симптом: Shift работал в одиночку и НЕ работал вместе со стрелками — прыжок с зацепом не выходил (BUG-GRAB-1), а осторожный шаг срывался в бег. Декодеры делали из «fake shift» два вывода, и второй был неверен: обёртка E0 F0 12 / E0 12 есть -> Shift зажат -> взвести бит — верно; расширенный make БЕЗ обёртки -> Shift отпущен -> снять бит — НЕТ. Замер потока байт (MAME, breakpoint на выходе из in a,($18)): клавиатура pc_kbd ms_naturl обёртку не шлёт вовсе — при зажатом Shift поток на ↑ ровно `E0 75 E0 75 …`, ни одного F0/12. А typematic-повторы идут непрерывно, пока стрелка зажата, значит каждый повтор снимал реально зажатый Shift. Короткий тап это маскировал: после отпускания стрелки Shift снова становился последней клавишей, и его собственный автоповтор `12` взводил бит обратно за ~30 мс. Фикс: обратный вывод убран, расширенная клавиша о Shift не судит. Состояние Shift ведут его собственные make/break 12 / F0 12 — они приходят всегда. Прямой вывод оставлен (дёшев и верен там, где обёртка есть). Ушла ставшая ненужной _kbdraw_fakesh; трамплин короче на 36 Б (0x150→0x12C), что важно — его клавиатурный блок упирается в диапазон jr. Плата: потерянный при overrun break Shift снять нечем, модификатор может залипнуть до перенажатия (BUG-KBD-3). Размен решён как и раньше в kbd_raw_sync: лучше залипание, чем отвал — сорванный посреди игры Shift в PoP стоит жизни. Проверено в MAME чтением _kbdraw_down: Shift+↑+→ зажаты 5 с (автоповтор идёт) -> LSh остаётся 04; отпускание Shift -> 00. Зацеп в игре подтверждён пользователем. Co-Authored-By: Claude Opus 5 --- libc/include/kbd_raw.h | 6 +++--- libc/irq/_irq_tramp.c | 39 ++++++++++++--------------------------- libc/kbd/_kbdraw.h | 33 ++++++++++++++++----------------- libc/kbd/_kbdraw_state.c | 3 --- libc/kbd/kbd_raw_poll.c | 39 ++++++++++++--------------------------- libc/kbd/kbd_raw_sync.c | 27 ++++++++++++--------------- 6 files changed, 55 insertions(+), 92 deletions(-) diff --git a/libc/include/kbd_raw.h b/libc/include/kbd_raw.h index e6bdf07..27aef53 100644 --- a/libc/include/kbd_raw.h +++ b/libc/include/kbd_raw.h @@ -63,9 +63,9 @@ void kbd_raw_sync(void); * * Зачем: на каждый принятый байт запрос прерывания живёт единицы * микросекунд, и пропущенный импульс не «догоняется» — байт лежит в - * 3-байтовом FIFO до следующего прерывания. Пачки в 5 байт (тап стрелки; - * при зажатом Shift — ещё и «fake shift» вокруг расширенного кода) при - * этом переполняют FIFO: теряется make («нажатие не сработало») или break + * 3-байтовом FIFO до следующего прерывания. Пачки (тап стрелки — 5 байт; + * одновременное отпускание нескольких клавиш — больше) при этом + * переполняют FIFO: теряется make («нажатие не сработало») или break * (залипание). * * КАК ЕЁ ЗВАТЬ (замеры 2026-08-01, PoP roomtest — см. diff --git a/libc/irq/_irq_tramp.c b/libc/irq/_irq_tramp.c index 45db979..e8f04f7 100644 --- a/libc/irq/_irq_tramp.c +++ b/libc/irq/_irq_tramp.c @@ -82,10 +82,10 @@ void _irq_tramp(void) __naked jr tr_kbd_hub tr_kbd_nE0: - ;; --- «fake shift»: расширенные коды 12/59 — не клавиши, а обёртка, - ;; которой клавиатура сопровождает КАЖДЫЙ расширенный код, пока - ;; реально зажат Shift (см. _kbdraw.h). Отсюда состояние Shift - ;; определяется в обе стороны и не зависит от целости потока. --- + ;; --- «fake shift»: расширенные коды 12/59 — не клавиши, а служебная + ;; обёртка, которой НЕКОТОРЫЕ клавиатуры сопровождают расширенный код + ;; при зажатом Shift (см. _kbdraw.h). Используем её ТОЛЬКО как + ;; подтверждение «Shift зажат»; обратный вывод запрещён. --- ld e, a ; E = код ld a, (__kbdraw_pending) bit 1, a ; EXT? @@ -95,22 +95,9 @@ void _irq_tramp(void) __naked jr Z, tr_kbd_fake cp #0x59 jr Z, tr_kbd_fake - ;; расширенная КЛАВИША: make без обёртки => Shift не зажат - ld a, (__kbdraw_pending) - bit 0, a ; break — состояние Shift не судим - jr NZ, tr_kbd_fkclr - ld a, (__kbdraw_fakesh) - or a, a - jr NZ, tr_kbd_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) - tr_kbd_fkclr: - xor a, a - ld (__kbdraw_fakesh), a - ld a, e - jr tr_kbd_code ; дальше как обычная клавиша + ;; расширенная КЛАВИША — обычный путь. О состоянии Shift по ней НЕ + ;; судим (почему — см. tr_kbd_fake). A всё ещё = код. + jr tr_kbd_code tr_kbd_hub: ;; Ретрансляторы: клавиатурный блок длиннее 127 байт, а jp внутри @@ -124,13 +111,11 @@ void _irq_tramp(void) __naked jr tr_kbd_dss tr_kbd_fake: - ;; E0 F0 12 / E0 12 — реальный Shift ЗАЖАТ. Бит взводит ОБЩИЙ - ;; писатель ниже: достаточно обнулить префиксы — тогда он положит - ;; код в PLAIN-половину карты (без +32) и именно взведёт бит (не - ;; break). В расширенную половину не пишем: это не клавиша. - ld a, (__kbdraw_pending) - and #1 ; 1 = break-форма (идёт ПЕРЕД обёрнутым make) - ld (__kbdraw_fakesh), a + ;; E0 F0 12 / E0 12 — реальный Shift ЗАЖАТ (обе формы обёртки шлются + ;; только пока он зажат). Бит взводит ОБЩИЙ писатель ниже: достаточно + ;; обнулить префиксы — тогда он положит код в PLAIN-половину карты + ;; (без +32) и именно ВЗВЕДЁТ бит, а не снимет, даже у break-формы. + ;; В расширенную половину не пишем: это не клавиша. xor a, a ld (__kbdraw_pending), a ld a, e diff --git a/libc/kbd/_kbdraw.h b/libc/kbd/_kbdraw.h index a3db341..e02fa85 100644 --- a/libc/kbd/_kbdraw.h +++ b/libc/kbd/_kbdraw.h @@ -23,25 +23,24 @@ 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). +/* «Fake shift». Часть PS/2-клавиатур, пока реально зажат Shift, обёртывает + * расширенный код парой служебных посылок: перед make идёт E0 F0 12 (снять + * fake shift), после break — E0 12 (вернуть). Это не клавиши, а прямое + * свидетельство того, что Shift зажат, поэтому декодеры (трамплин и + * kbd_raw_poll) взводят по обёртке plain-бит Shift. * - * Декодеры (трамплин и kbd_raw_poll) используют его в обе стороны: - * - увидели fake shift -> реальный Shift ЗАЖАТ -> взвести plain-бит; - * - расширенный make БЕЗ предшествующей обёртки -> Shift ОТПУЩЕН -> - * снять plain-биты обоих Shift. - * Отсюда: залипший Shift снимается первым же нажатием стрелки, а - * kbd_raw_sync больше не обязан сбрасывать модификаторы «на всякий случай» - * (из-за чего Shift отваливался посреди игры — BUG-KBD-4). + * ОБРАТНЫЙ ВЫВОД ЗАПРЕЩЁН (замер 2026-08-05, BUG-GRAB-1). Раньше отсюда + * делался и второй вывод: «расширенный make БЕЗ обёртки => Shift отпущен, + * снять plain-биты». Он неверен: обёртку шлют не все клавиатуры (в MAME + * Sprinter — pc_kbd ms_naturl — при зажатом Shift поток на стрелку вверх + * ровно `E0 75 E0 75 …`, ни одного F0/12), и она заведомо не сопровождает + * typematic-повторы. Результат: КАЖДЫЙ повтор зажатой стрелки снимал + * реально зажатый Shift, и Shift был неработоспособен вместе со стрелками. * - * Хранит промежуточное состояние: 1 = разобрана break-форма (E0 F0 12), - * ждём расширенный код, который она обёртывает. */ -extern uint8_t _kbdraw_fakesh; + * Состояние Shift ведётся его собственными make/break (12 / F0 12) — они + * приходят всегда; обёртка лишь подтверждает. Ценой этого потерянный при + * overrun'е break Shift снять нечем — лечится перенажатием Shift + * (см. kbd_raw_sync и BUG-KBD-3/4). */ /* Позиции бит модификаторов в карте (используют и декодеры на асме, и * kbd_raw_sync): L Shift 0x12 -> байт 2 бит 2, R Shift 0x59 -> байт 11 бит 1. */ diff --git a/libc/kbd/_kbdraw_state.c b/libc/kbd/_kbdraw_state.c index 6a458e4..c15a240 100644 --- a/libc/kbd/_kbdraw_state.c +++ b/libc/kbd/_kbdraw_state.c @@ -9,7 +9,4 @@ 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) */ diff --git a/libc/kbd/kbd_raw_poll.c b/libc/kbd/kbd_raw_poll.c index 7894824..a01c64a 100644 --- a/libc/kbd/kbd_raw_poll.c +++ b/libc/kbd/kbd_raw_poll.c @@ -12,12 +12,11 @@ * акселератора держат DI на весь блит — сотни микросекунд. Импульс, * попавший в такое окно, теряется НАСОВСЕМ: байт остаётся в FIFO до * следующего прерывания (следующий байт либо кадровое, 50 Гц); - * - трафик от клавиатуры пачками: тап стрелки — 5 байт, а при зажатом - * Shift ещё и «fake shift» (E0 F0 12 / E0 12 вокруг расширенного кода) - * — по 5 байт на нажатие И на отпускание. Три ячейки FIFO такую пачку - * не держат: потерянный make = «нажатие не сработало», потерянный - * break = залипшая клавиша (её потом лечит kbd_raw_sync, ценой сброса - * всех немодификаторных клавиш). + * - трафик от клавиатуры пачками: тап стрелки — 5 байт (E0 75 + E0 F0 75), + * одновременное отпускание нескольких клавиш — больше. Три ячейки FIFO + * такую пачку не держат: потерянный make = «нажатие не сработало», + * потерянный break = залипшая клавиша (её потом лечит kbd_raw_sync, + * ценой сброса всех немодификаторных клавиш). * Опрос из главного цикла снимает зависимость от пойманного импульса: чем * чаще позвали, тем меньше шанс, что в FIFO успеет накопиться четвёртый * байт. Звать между фазами кадра (после тяжёлых блитов), НЕ раз в кадр. @@ -66,8 +65,9 @@ uint8_t kbd_raw_poll(void) __naked jp Z, kp_setext ; (в отличие от трамплина, здесь jp разрешён) ;; --- «fake shift» — тот же разбор, что в трамплине (см. _kbdraw.h): - ;; расширенные 12/59 не клавиши, а обёртка вокруг расширенного кода, - ;; которую клавиатура шлёт, пока реально зажат Shift. --- + ;; расширенные 12/59 не клавиши, а служебная обёртка, которую + ;; НЕКОТОРЫЕ клавиатуры шлют вокруг расширенного кода, пока реально + ;; зажат Shift. Только подтверждение «зажат», обратного вывода нет. --- ld e, a ; E = код ld a, (__kbdraw_pending) bit 1, a ; EXT? @@ -77,29 +77,14 @@ uint8_t kbd_raw_poll(void) __naked 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 + ;; расширенная КЛАВИША — обычный путь. О состоянии Shift по ней НЕ + ;; судим (почему — см. kp_fake). A всё ещё = код. jr kp_code kp_fake: ;; Бит взводит ОБЩИЙ писатель ниже: обнуляем префиксы — и он кладёт - ;; код в PLAIN-половину карты (без +32) и именно взведёт бит. - ld a, (__kbdraw_pending) - and #1 ; 1 = break-форма (идёт ПЕРЕД обёрнутым make) - ld (__kbdraw_fakesh), a + ;; код в PLAIN-половину карты (без +32) и именно ВЗВЕДЁТ бит, а не + ;; снимет, даже у break-формы обёртки. xor a, a ld (__kbdraw_pending), a ld a, e diff --git a/libc/kbd/kbd_raw_sync.c b/libc/kbd/kbd_raw_sync.c index 4d5dcf4..caf33bf 100644 --- a/libc/kbd/kbd_raw_sync.c +++ b/libc/kbd/kbd_raw_sync.c @@ -25,18 +25,18 @@ * жмёт 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. + * Выбран вариант (1) — модификаторы НЕ сбрасываем. Была и третья редакция, + * обещавшая снять сам размен: «fake shift» (E0 F0 12 / E0 12) читался в обе + * стороны, и расширенный make без обёртки снимал Shift, то есть залипание + * лечилось первым же тапом стрелки. Обратный вывод оказался неверным + * (замер 2026-08-05, BUG-GRAB-1: обёртку шлют не все клавиатуры и никогда — + * на typematic-повторах), и он ломал Shift вместе со стрелками; убран, см. + * _kbdraw.h. Значит размен вернулся, и здесь он решается в пользу + * «лучше залипание, чем отвал»: залипший модификатор снимается его же + * перенажатием, а сорванный посреди игры Shift в PoP стоит жизни. * - * Ctrl/Alt fake-shift'ом не покрыты (клавиатура их не обёртывает). Их - * оставляем по тому же принципу «лучше залипание, чем отвал»: залипший - * Ctrl/Alt в PoP не управляет ничем критичным и снимается перенажатием. + * Overrun теперь редок сам по себе: FIFO вычерпывается опросом из главного + * цикла (kbd_raw_poll, KBD-1), а не только по пойманному прерыванию. */ #include #include "_kbdraw.h" @@ -67,9 +67,6 @@ void kbd_raw_sync(void) if (save[i]) _kbdraw_down[c >> 3] |= (uint8_t)(1 << (c & 7)); } /* FSM префиксов трамплин уже сбросил; дублируем на случай overrun'а, - * пойманного вне его (kbd_raw_poll между кадрами). Незакрытую обёртку - * fake shift тоже считаем недействительной: её вторая половина могла - * пропасть вместе с потерянными байтами. */ + * пойманного вне его (kbd_raw_poll между кадрами). */ _kbdraw_pending = 0; - _kbdraw_fakesh = 0; }