diff --git a/applications/SprPoP/docs/BUGS_OPEN.md b/applications/SprPoP/docs/BUGS_OPEN.md index 95119d2..e9174ca 100644 --- a/applications/SprPoP/docs/BUGS_OPEN.md +++ b/applications/SprPoP/docs/BUGS_OPEN.md @@ -21,7 +21,7 @@ | [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт | | [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводим: ни сценарием, ни попиксельной подгонкой X не поднимается | | [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводим: на повторе не поднялся; на пререлиз | -| [GRAB-KBD-TIMING](#grab-kbd-timing) | зацеп в падении срабатывает менее стабильно, чем в оригинале | **после всех уровней** | открыт: подозрение на клавиатурный модуль, не на физику | +| [GRAB-KBD-TIMING](#grab-kbd-timing) | зацеп в падении срабатывает менее стабильно, чем в оригинале | **после всех уровней** | подозрение на клавиатуру ПОДТВЕРДИЛОСЬ частично: кража байт DSS устранена 2026-08-28, перепроверить | | [GUARD-ENTRY-DEATH](#guard-entry-death) | вход в комнату вплотную к стражу = мгновенная смерть (не портирован `bump_into_opponent`) | порт | **фикс есть, ждёт проверки в MAME** | | [BUG-SHADOW-SET](#bug-shadow-set) | Тень на 12-м уровне дерётся спрайтами СТРАЖА: SHADOW.DAT у нас нет вовсе | порт | открыт, разбор готов | | [PAL-L1-AFTER-INTRO](#pal-l1-after-intro) | вход в игру на уровень 1 после интро — игровая палитра не установлена (чёрный экран) | палитра/fade | открыт: воспроизведение нестабильно, корень не установлен | @@ -32,7 +32,7 @@ | [MOB-CLIP-RIGHT](#mob-clip-right) | окна и узор кладки рисуются ПОВЕРХ падающих плит | окклюзия | открыт: корень найден 2026-08-13 | | [MID-OVERLAY-LAYER](#mid-overlay-layer) | оверлей кромки всегда поверх плит и персонажей (у оригинала — midtable с сортировкой) | слои | открыт: нужен разбор архитектуры | | [GUARD-FALLOUT-VICTORY](#guard-fallout-victory) | страж, выпавший из комнаты, не засчитывается убитым | порт | открыт: две строки | -| [KBD-STUCK-WAIT](#kbd-stuck-wait) | потерянный break-код клавиши вешает игру НАСМЕРТЬ в межуровневой заставке | клавиатура/клин | открыт: диагноз полный, чинить не сейчас (решение пользователя 2026-08-24) | +| [KBD-STUCK-WAIT](#kbd-stuck-wait) | потерянный break-код клавиши вешает игру НАСМЕРТЬ в межуровневой заставке | клавиатура/клин | **корень найден и устранён** 2026-08-28 (байты крал KEYSCAN DSS); ждёт полевой проверки | | [L10-BUTTON-DEBRIS](#l10-button-debris) | ур.10 к.1: плита упала на кнопку (1,8) — щебень есть, а решётки закрыты | кнопки/ворота | открыт: **не воспроизводится**, проверено многое (см. разбор) | | [LOOSE-SEAM-ANIM](#loose-seam-anim) | плита в шве (col −1), уроненная в прошлой комнате, исчезает без анимации падения | **мелкий** | открыт по решению пользователя: «пока пусть будет так» | | [SND-PACE-DEAD](#snd-pace-dead) | пейсинг сцен по насосу CBL не включается: признак «часы идут» вычисляется двумя чтениями подряд | **тайминг/звук** | открыт: диагноз полный, симптом обойдён подгонкой (2026-08-27) | @@ -98,8 +98,67 @@ POS=18`, MAME): Кид стоит на кнопке -> решётка `(2,7)` о ## KBD-STUCK-WAIT. Потерянный break-код вешает игру насмерть в межуровневой заставке -> **Решение пользователя (2026-08-24): не чинить сейчас.** Записан разбор и -> варианты; вернуться, когда дойдут руки до клавиатурного слоя. +> **КОРЕНЬ НАЙДЕН И УСТРАНЁН 2026-08-28.** Байты у нас крал сам DSS — см. +> раздел «Корень» ниже. Правка в `libc/irq/_irq_tramp.c`. Запись остаётся +> открытой до полевой проверки и решения по страховке-таймауту. + +## Корень (2026-08-28): скан-коды воровал KEYSCAN DSS + +Пришёл из чтения исходников DSS (`docs/sources/Estex-DSS/DSS/`, подсказка +пользователя). Обработчик прерывания DSS живёт в IM1 по `0x0038` +(`DSS-MAIN.ASM`), и его `INTx38_Handler` ПЕРВЫМ ДЕЛОМ делает `CALL KEYSCAN`, +а `KEYSCAN.RESCAN` (`KEYINTER.ASM`) вычерпывает FIFO SIO досуха. + +Наш трамплин проверял «есть ли клавиатурный байт» ОДИН раз, на входе в +прерывание, а хвост кадрового пути уходил в DSS (`jp 0x0038`). Значит +скан-код, прилетевший ПОЗЖЕ этой проверки, доставался DSS и уезжал в его +буфер — при этом **Rx-overrun не взводится**, потому что байт не потерян +железом, он просто прочитан не тем владельцем. Отсюда и загадка +исходного диагноза: бит залип, а `_kbdraw_overrun == 0`. + +**Измерено в MAME (сессия 2026-08-28, брейки + `totalcycles`):** + +| Что | Значение | +|---|---| +| окно от входа трамплина до чтения байта DSS | **738 тактов ≈ 34 мкс**, на КАЖДОМ кадровом прерывании (50/с) | +| из них пролог самого DSS (`jp 0x38` → `.Handler` → `call INTx38_Handler` → 11 `push` → `call KEYSCAN`) | 481 такт ≈ 22 мкс | +| цепочка кадровых хендлеров у SprPoP | пуста (`_irq_chain_n = 0`) — окно уже минимальное | +| расчётная частота на железе при удержании клавиши (typematic 30 Гц = 60 байт/с) | потеря байта раз в ~10 с | + +Последняя строка сходится с полевым наблюдением пользователя: залипание +стрелки ловилось на ур. 14, когда Кид долго бежит влево через несколько +комнат. + +**Пойманный случай (MAME, старая сборка).** Стоп на `IN A,($18)` внутри +`KEYSCAN` при `_kbdraw_active = 1`: DSS прочитал `A = 0x74` — make стрелки +«вправо», при том что `_kbdraw_pending = 2` (EXT), то есть префикс `E0` уже +разобрали МЫ. Посылка `E0 74` разорвана пополам между двумя владельцами +канала. Злейший подвид — кража `E0` у break'а `E0 F0 6B`: тогда мы гасим +бит в PLAIN-половине, а взведённый бит в EXT остаётся навсегда, и это ровно +залипшая стрелка `0x6B`/`0x72` из исходного разбора. + +**Правка.** Пока raw-канал открыт, кадровый путь трамплина НЕ вызывает +обработчик DSS вообще (приватный `RETI` вместо `jp 0x0038`) — окно ровно +ноль. Промежуточный вариант «проверить FIFO перед chain'ом» пробовали и +отвергли замером: он снимает лишь свою треть окна (738 → 481 такт), кражи +продолжались. Цена принятого варианта: пока открыт raw, у DSS замирает +кадровое обслуживание — опрос мыши и мигание текстового курсора (PoP не +нужно ни то, ни другое; зафиксировано в ``). + +**Проверка (MAME 2026-08-28).** До правки брейк на входе `KEYSCAN` с +условием «страница точно DSS + наш raw открыт» срабатывал мгновенно (50/с); +после правки молчит, а положительный контроль — брейк на нашем приватном +`RETI` — срабатывает сразу. ВАЖНО про методику: первый брейк без проверки +сигнатуры байтов дал ЛОЖНОЕ срабатывание — в W0 периодически лежит +драйверная страница DSS, и по адресу `0x0570` там своя команда. + +**Что осталось открытым.** Кража устранена, но потерять break всё ещё +может настоящий Rx-overrun SIO (редкий: FIFO вычерпывает `kbd_raw_poll` из +главного цикла). Значит вариант A из таблицы ниже — таймаут в циклах +«ждём отпускания всех клавиш» — остаётся разумной страховкой от КЛАССА +отказа «вечный клин», и его стоит сделать отдельно. + +## Исходный разбор (2026-08-24) **Наблюдение (пользователь, 2026-08-24).** Нажат Shift+L (чит «следующий уровень») — игра встала намертво: картинка прежнего уровня, реакции нет. @@ -169,6 +228,13 @@ Rx-overrun не срабатывала. Гашение бита отладчи > **Решение пользователя (2026-08-12): отложено до готовности всех уровней.** > Механика работает, это вопрос ощущения, а не проходимости. +> **Обновление 2026-08-28.** Гипотеза «дело в клавиатурном модуле, а не в +> физике» подтвердилась ЧАСТИЧНО: найдена и устранена кража скан-кодов +> обработчиком DSS (разбор — [KBD-STUCK-WAIT](#kbd-stuck-wait)). Украденный +> make Shift'а или стрелки — это ровно «нажал, а не сработало», и на +> железе при удержании клавиши такое случалось раз в ~10 с. Перепроверить +> ощущение зацепа на новой сборке ДО того, как лезть в физику. + **Наблюдение (пользователь).** Вход на уровень 7: Кид влетает в комнату сверху и, пролетая мимо края пола, МОЖЕТ за него зацепиться — но получается заметно реже, чем в оригинале. «Похоже, это проблемы нашего клавиатурного модуля». diff --git a/docs/size_baseline.tsv b/docs/size_baseline.tsv index 510771f..a79bd53 100644 --- a/docs/size_baseline.tsv +++ b/docs/size_baseline.tsv @@ -1,5 +1,5 @@ # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update -accfill 3786 +accfill 3783 accop 7067 argv 3431 assrtest 3847 @@ -9,15 +9,15 @@ banked 1056 bankedbg 1067 banklocl 4683 banktest 3767 -bgi_img 8680 -bgitest 3751 +bgi_img 8677 +bgitest 3748 bios_text 4461 blitperf 5860 blitw 4939 cat 913 -cblstream 5850 -cbltest 5996 -cblwav 6089 +cblstream 5860 +cbltest 6006 +cblwav 6099 conio 4605 conio2 3929 convbench 3508 @@ -28,12 +28,12 @@ fdmax 6023 filetest 10547 fpsdiv 4870 gets 509 -gfx_dbuf 5134 -gfx_demo 4117 +gfx_dbuf 5131 +gfx_demo 4114 gfxbanks 6044 hello 4167 hello2 4244 -irqtest 6680 +irqtest 6690 kbdpoll 1282 kbdraw 4911 ls 4835 @@ -44,7 +44,7 @@ openenv 6124 pageflip 6334 palfile 5406 ptime 5744 -rt_test 5187 +rt_test 5197 scroll 6744 seek 4188 simple 955 @@ -59,8 +59,7 @@ strtest 1340 text_palette 5024 timedir 5454 w0page 8831 -w3bankgfx 5240 -w3bgfx 5246 +w3bgfx 5243 w3big 3673 w3huge 3700 w3probe 3556 diff --git a/libc/include/kbd_raw.h b/libc/include/kbd_raw.h index 2f31593..33f93b6 100644 --- a/libc/include/kbd_raw.h +++ b/libc/include/kbd_raw.h @@ -23,6 +23,16 @@ * cbl_open()/cbl_close() () — тот же приватный IM2-хук, не * чейнящийся к DSS. * + * КАДРОВОЕ ОБСЛУЖИВАНИЕ DSS НА ЭТО ВРЕМЯ ОСТАНОВЛЕНО. Пока raw открыт, + * трамплин НЕ вызывает обработчик прерывания DSS вообще: тот первым делом + * зовёт свой KEYSCAN, который вычерпывает FIFO SIO и уводит наши байты + * (потерянный make = несработавшее нажатие, потерянный break = залипшая + * клавиша; поймано в MAME 2026-08-28). Вместе с KEYSCAN замирают и + * остальные его кадровые дела — опрос мыши (Dss.Mouse.GetPackets) и + * мигание текстового курсора. ЗНАЧИТ: приложению, которому нужна мышь + * через DSS, raw-канал открывать нельзя (мышь встанет); клавиатурные + * события всё равно берутся из kbd_raw_down(). + * * КОДЫ КЛАВИШ: значения ниже — общеизвестный стандарт AT/PS-2 Scan * Code Set 2 (не специфика Sprinter). TODO до использования в PoC: * подтвердить в MAME, что реальный поток байт с SIO-A клавиатуры diff --git a/libc/irq/_irq.h b/libc/irq/_irq.h index 41a8ccb..921e141 100644 --- a/libc/irq/_irq.h +++ b/libc/irq/_irq.h @@ -30,10 +30,11 @@ #define _IRQ_VEC_BUF_SIZE (256 + 257) /* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в - * W1). С запасом над реальным размером трамплина (267 Б после - * упаковки held-карты клавиатуры в биты, 2026-07-30 — проверять - * __irq_tramp_end при правках, запас держать >= 60 Б). */ -#define _IRQ_TRAMP_BUF_SIZE 336 + * W1). С запасом над реальным размером трамплина (310 Б после отказа + * звать обработчик DSS, пока открыт raw-канал, 2026-08-28 — проверять + * `grep 'A _CODE' libc/build/fast/irq/_irq_tramp.rel` при правках, + * запас держать >= 60 Б). */ +#define _IRQ_TRAMP_BUF_SIZE 384 extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE]; diff --git a/libc/irq/_irq_tramp.c b/libc/irq/_irq_tramp.c index e8f04f7..6100449 100644 --- a/libc/irq/_irq_tramp.c +++ b/libc/irq/_irq_tramp.c @@ -13,7 +13,9 @@ * клавиши) в битовую карту `_kbdraw_down[]` (бит на код, 64 Б), * и к DSS НЕ идём * (private RETI — тот же паттерн, что и у CBL ниже). Пока raw - * открыт, DSS клавиатуру не видит вообще (см. ). + * открыт, DSS клавиатуру не видит вообще (см. ) — и на + * кадровом пути тоже: пока raw открыт, обработчик DSS НЕ вызывается + * совсем (иначе его KEYSCAN крадёт наши байты — см. tr_frame_exit). * - CBL (бит 7 порта 0xFE) — только при зарегистрированном хуке * _irq_cbl_hook (иначе бит подтянут к 1); личное прерывание, RETI * без чейна к DSS; @@ -312,6 +314,39 @@ void _irq_tramp(void) __naked ld a, (__irq_saved_w1) ; вернуть прерванную W1 перед chain out (#_IRQ_W1_PORT), a tr_frame_exit: + ;; --- ПОКА RAW ОТКРЫТ, В DSS НЕ ХОДИМ ВООБЩЕ --- + ;; Хвост кадрового пути уходил в DSS (0x0038), а его обработчик + ;; INTx38_Handler ПЕРВЫМ ДЕЛОМ зовёт KEYSCAN, и тот вычерпывает FIFO + ;; SIO досуха (docs/sources/Estex-DSS/DSS/DSS-MAIN.ASM, + ;; KEYINTER.ASM:KEYSCAN.RESCAN). Наша проверка «есть ли байт» стоит в + ;; НАЧАЛЕ трамплина, поэтому скан-код, прилетевший позже, доставался + ;; DSS и уезжал в ЕГО буфер — БЕЗ Rx-overrun. Украденный make = + ;; несработавшее нажатие, украденный break = залипшая навсегда + ;; клавиша при `_kbdraw_overrun == 0` (BUGS_OPEN KBD-STUCK-WAIT, + ;; GRAB-KBD-TIMING). Поймано в MAME 2026-08-28: DSS прочитал 0x74 + ;; (make стрелки «вправо») при `_kbdraw_pending = EXT` — посылка + ;; `E0 74` разорвана пополам между двумя владельцами канала. + ;; + ;; Окно измерено брейками + totalcycles: от входа трамплина до + ;; чтения DSS — 738 тактов (~34 мкс), и открыто оно на КАЖДОМ + ;; кадровом прерывании (50/с). Из них 481 такт — пролог самого DSS + ;; (jp 0x38 → jp .Handler → call INTx38_Handler → 11 push → call + ;; KEYSCAN), то есть закрыть окно проверкой FIFO перед chain'ом + ;; нельзя: она снимает лишь треть (проверено там же). + ;; + ;; Поэтому пока raw открыт, кадровый путь заканчивается ПРИВАТНЫМ + ;; RETI и обработчик DSS не вызывается совсем — окно ровно ноль. + ;; ЦЕНА: на это время замирает кадровое обслуживание DSS — + ;; Dss.Mouse.GetPackets и мигание текстового курсора. Приложению с + ;; raw-клавиатурой (игре) не нужно ни то, ни другое; кому нужна мышь + ;; DSS одновременно с raw — не открывать raw (см. ). + ld a, (__kbdraw_active) + or a, a + jr Z, tr_chain_dss + pop af + ei + reti + tr_chain_dss: pop af .db 0xC3 ; jp 0x0038 (литерал — copy-safe) .dw 0x0038