libc/kbd: kbd_raw_poll + замер потери нажатий при зажатом Shift (KBD-1)
Симптом: при удерживаемом Shift часть нажатий стрелок не отрабатывает
(~15 % по наблюдению пользователя), без Shift потерь нет.
Переведено в числа: нажимается Home — тоже расширенная клавиша (тот же
E0-префикс и тот же «fake shift»), но игрой игнорируется, поэтому рельеф
комнаты на результат не влияет. Счётчики — брейкпоинты MAME с действием
{ b@ADDR = b@ADDR+1 ; g } на входе клавиатурной ветки трамплина, на чтении
порта 0x18 и на установке make-бита.
Что измерено (10 нажатий Shift+Home, дошло make):
игра идёт, опрос ВКЛ 9/10 игра идёт, опрос ВЫКЛ 9/10
игра ЗАМОРОЖЕНА (блитов нет вообще, длинных DI нет) 8/10
Обе исходные гипотезы отпали:
- длина наших DI-окон ни при чём (в замороженном кадре потерь больше);
- снятие di в accel-ядрах libbgi УРОНИЛО машину — режим «акселератор при
EI» из docs/new/06-accel.md §6.6 в этой прошивке недоступен.
Байт теряется НИЖЕ нашего кода: на 49 прочитанных байт пришлось только 28
входов в клавиатурную ветку, то есть ~44 % импульсов запроса прерывания не
обслуживается и трёхбайтовый FIFO SIO переполняется.
Потолок приёма измерен отдельной программой tests/kbdpoll (ничего, кроме
kbd_raw_poll в цикле): 25 нажатий -> 25 make, 250 байт из 250, ноль потерь.
Значит опрос лечит полностью, вопрос только в плотности: нужно раз в
~0.5 мс, а шесть вызовов за 60-мс кадр давали раз в 10 мс.
Поэтому вызовы из roomtest.c УБРАНЫ (они стояли там, где прерывания и так
разрешены, и дублировали трамплин — 9/10 с ними и без). Сама функция
kbd_raw_poll оставлена в libc: она корректна и нужна как основа плотного
опроса. В заголовке и в libc-reference — честная оговорка, чтобы её не
ставили в игровой цикл «на всякий случай» без замера.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+26
-9
@@ -279,15 +279,32 @@ Quick wins:
|
||||
- [ ] **kbd_raw: частые Rx-overrun в MAME при тапах** (roomtest,
|
||||
2026-07-22) — замеры watchpoint-счётчиками: при стабильном
|
||||
удержании клавиши overrun'ов ноль, но каждый быстрый тап (5 байт:
|
||||
E0 74 + E0 F0 74 при FIFO 3) даёт overrun. Подозрение: dev-MAME
|
||||
не эмулирует прерывание SIO на каждый принятый байт (байты
|
||||
вычерпываются только кадровым IRQ 50 Гц) и/или подаёт пачку без
|
||||
реальных ~1 мс/байт — на железе per-byte INT должен делать
|
||||
overrun'ы редкостью. Проверить mame/sources/MAME/src/mame/
|
||||
sinclair/sprinter.cpp (путь байта клавиатуры → INT) и при
|
||||
желании поправить dev-MAME. Recovery-политика уже терпима к
|
||||
частым overrun'ам (селективный wipe, docs/kbd-games.md), но
|
||||
аккорд «держу →, тапнул ↑» остаётся уязвим.
|
||||
E0 74 + E0 F0 74 при FIFO 3) даёт overrun.
|
||||
**Уточнение 2026-08-01 (по коду драйвера, прежняя гипотеза
|
||||
НЕВЕРНА):** dev-MAME per-byte INT ДАЁТ —
|
||||
`mame/sources/MAME/src/mame/sinclair/sprinter.cpp`
|
||||
`on_kbd_data()` ставит `m_irqs->in_set<1>()` на каждый принятый
|
||||
байт. Но тут же заводит `m_irq_off_timer` на **32 такта CPU**, и
|
||||
`irq_off()` снимает линию — то есть импульс, пришедшийся на наше
|
||||
DI-окно, теряется НАСОВСЕМ (байт остаётся в FIFO до следующего
|
||||
IRQ или кадрового 50 Гц). А DI-окна у нас длинные: ядра
|
||||
акселератора держат `di` на весь блит (`libbgi/bgi256/
|
||||
_bgi_blit_cols_raw.c`), это сотни микросекунд против 32 тактов.
|
||||
**Замерено 2026-08-01 (PoP roomtest, счётчики в MAME): обе
|
||||
«наши» гипотезы отпали.** (а) `kbd_raw_poll()` из главного цикла
|
||||
не меняет ничего (9/10 с ним и без); (б) снятие `di` в accel-ядрах
|
||||
УРОНИЛО машину — режим «акселератор при EI» тут недоступен; и сама
|
||||
длина DI ни при чём (в замороженном кадре без блитов потерь
|
||||
БОЛЬШЕ). Байт теряется ДО чтения порта: на 49 прочитанных байт
|
||||
только 28 входов в клавиатурную ветку, т.е. ~44 % импульсов
|
||||
запроса не обслужены и 3-байтовый FIFO переполняется. Главный
|
||||
подозреваемый — `m_irq_off_timer` в sprinter.cpp: он ОДИН на два
|
||||
источника (экран + клавиатура), `irq_off()` гасит обе линии, так
|
||||
что кадровое прерывание способно обрезать клавиатурный импульс.
|
||||
Полный протокол — applications/PoP/roomtest/TASKS.md, KBD-1.
|
||||
Recovery-политика уже терпима к частым overrun'ам (селективный
|
||||
wipe, docs/kbd-games.md), но аккорды «держу →, тапнул ↑» и
|
||||
«держу Shift, тапаю ←» остаются уязвимы.
|
||||
|
||||
## Known quirks (зафиксированы, обходы в libc)
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ Set 2: `0xF0` — префикс отпускания, `0xE0` — префикс
|
||||
| `void kbd_raw_close(void)` | выключить, вернуть клавиатуру DSS; идемпотентно; висит на atexit |
|
||||
| `uint8_t kbd_raw_down(uint16_t code)` | зажата ли code ПРЯМО СЕЙЧАС (0/1); code вне 0..511 — 0 |
|
||||
| `void kbd_raw_sync(void)` | звать РАЗ В КАДР до опроса: recovery после Rx-overrun SIO — сбрасывает held-состояние всех клавиш КРОМЕ модификаторов (те не перечитываются typematic'ом; docs/kbd-games.md) |
|
||||
| `uint8_t kbd_raw_poll(void)` | вычерпать FIFO ОПРОСОМ, не дожидаясь прерывания: 0 — было пусто (~40 тактов), 1 — что-то декодировано. Звать МЕЖДУ фазами кадра, после тяжёлых блитов — не раз в кадр (см. ниже, «почему одного прерывания мало») |
|
||||
| `KBD_EXT` | ИЛИ-флаг кода: клавиша была расширенной (0xE0-префикс на проводе) |
|
||||
| `KBD_UP/DOWN/LEFT/RIGHT/SPACE/ENTER/ESC/LSHIFT/RSHIFT` | позиционные коды PS/2 Set 2 — LEFT/ESC/UP подтверждены (см. ниже); остальные — по стандарту, не перепроверены поштучно |
|
||||
| `KBD_LCTRL/LALT/RCTRL/RALT` | коды модификаторов (R* — расширенные, с KBD_EXT); добавлены 2026-07-22 |
|
||||
@@ -335,6 +336,32 @@ git как урок: при повторных «нет эффекта» на с
|
||||
брейкпоинтом/watchpoint'ом на конкретный адрес кода, не полагаться
|
||||
только на визуальный снимок с произвольным таймингом.
|
||||
|
||||
**ПОЧЕМУ ОДНОГО ПРЕРЫВАНИЯ МАЛО (`kbd_raw_poll`, 2026-08-01).** Приёмный
|
||||
FIFO SIO — 3 байта, и рассчитывать на «каждый байт разбудит нас» нельзя:
|
||||
запрос прерывания клавиатуры держится единицы микросекунд (в dev-MAME —
|
||||
32 такта CPU, `sprinter.cpp` `on_kbd_data` → `irq_off_timer`), а ядра
|
||||
акселератора держат `DI` на весь блит — сотни микросекунд. Импульс,
|
||||
попавший в такое окно, теряется насовсем; байт лежит в FIFO до следующего
|
||||
прерывания (следующий байт либо кадровое, 50 Гц). Трафик же идёт пачками:
|
||||
тап стрелки = 5 байт, а при УДЕРЖИВАЕМОМ Shift PS/2 обрамляет расширенный
|
||||
код «фиктивным шифтом» (`E0 F0 12` … `E0 12`) — по 5 байт и на нажатие, и
|
||||
на отпускание. Три ячейки такую пачку не держат: потерянный make = «нажатие
|
||||
не сработало», потерянный break = залипание. Отсюда `kbd_raw_poll()` —
|
||||
вычерпывание тем же декодером, но из главного цикла. Тело идёт под `DI`
|
||||
(чтение порта 0x18 деструктивно, а read-modify-write карты гонится с
|
||||
трамплином) и БЕЗУСЛОВНО делает `EI` на выходе — из ISR звать нельзя.
|
||||
|
||||
**Замер эффективности (2026-08-01, PoP roomtest — читать до применения!).**
|
||||
Расстановка «несколько вызовов за кадр, после тяжёлых фаз» **не даёт
|
||||
ничего**: 9 дошедших make-байт из 10 нажатий и с ней, и без неё. Причина —
|
||||
такие вызовы попадают ровно в участки с разрешёнными прерываниями и лишь
|
||||
дублируют трамплин. Там же измерено, что длина DI-окон графики на потери
|
||||
НЕ влияет (в кадре вообще без блитов потерь больше), а теряется байт ДО
|
||||
чтения порта: примерно 44 % импульсов запроса прерывания не обслуживаются,
|
||||
и трёхбайтовый FIFO переполняется. Полный протокол и куда копать —
|
||||
`applications/PoP/roomtest/TASKS.md`, задача KBD-1. Смысл может быть
|
||||
только у ПЛОТНОГО опроса (в цикле ожидания кадра), это ещё не проверено.
|
||||
|
||||
**ГЛАВНОЕ СЛЕДСТВИЕ:** пока `kbd_raw_open()` активен, `kbhit/getch/getkey/
|
||||
kbd_mod_state` НЕ получают новых событий (в т.ч. CTRLKEY подряд отдаёт
|
||||
то же самое, что было на момент открытия — резидентный обработчик DSS,
|
||||
|
||||
Reference in New Issue
Block a user