Compare commits

..

1 Commits

Author SHA1 Message Date
snark13 6dabe9b4b1 libc: пока raw-клавиатура открыта, не звать обработчик DSS — он крал наши скан-коды
Корень двух багов SprPoP (KBD-STUCK-WAIT, частично GRAB-KBD-TIMING) нашёлся
в исходниках DSS (docs/sources/Estex-DSS): обработчик прерывания DSS живёт в
IM1 по 0x0038 и ПЕРВЫМ ДЕЛОМ делает `CALL KEYSCAN`, а тот вычерпывает FIFO
SIO досуха.  Наш трамплин проверял «есть ли клавиатурный байт» один раз, на
входе в прерывание, а хвост кадрового пути уходил в DSS — значит скан-код,
прилетевший позже, доставался DSS и уезжал в его буфер.  Rx-overrun при этом
НЕ взводится (байт не потерян железом, а прочитан не тем владельцем) — отсюда
и загадка исходного диагноза: бит залип при `_kbdraw_overrun == 0`.

Измерено в MAME (брейки + totalcycles): окно 738 тактов (~34 мкс) на каждом
кадровом прерывании, из них 481 такт — пролог самого DSS.  Поэтому проверка
FIFO перед chain'ом снимает лишь треть и не годится (пробовали, кражи
продолжались); кадровый путь при открытом raw теперь заканчивается приватным
RETI, окно = 0.  Цена: на это время у DSS замирает опрос мыши и мигание
текстового курсора — зафиксировано в <kbd_raw.h>.

Пойманный случай (старая сборка): DSS прочитал 0x74 (make стрелки «вправо»)
при _kbdraw_pending = EXT, то есть посылку E0 74 разорвало пополам между
двумя владельцами канала.

Проверка: брейк на входе KEYSCAN с условием «страница точно DSS + raw открыт»
до правки срабатывал мгновенно (50/с), после — молчит; положительный контроль
на нашем RETI срабатывает сразу.

Трамплин 300 -> 310 Б (буфер W2-копии поднят 336 -> 384, запас 74 Б);
размерный эталон обновлён: +10 Б у программ, линкующих IRQ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:40:39 +03:00
5 changed files with 132 additions and 21 deletions
+70 -4
View File
@@ -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)` о
<a id="kbd-stuck-wait"></a>
## 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 не
нужно ни то, ни другое; зафиксировано в `<kbd_raw.h>`).
**Проверка (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: Кид влетает в комнату сверху
и, пролетая мимо края пола, МОЖЕТ за него зацепиться — но получается заметно
реже, чем в оригинале. «Похоже, это проблемы нашего клавиатурного модуля».
+11 -12
View File
@@ -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
1 # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
2 accfill
3 accop
4 argv
5 assrtest
9 bankedbg
10 banklocl
11 banktest
12 bgi_img
13 bgitest
14 bios_text
15 blitperf
16 blitw
17 cat
18 cblstream
19 cbltest
20 cblwav
21 conio
22 conio2
23 convbench
28 filetest
29 fpsdiv
30 gets
31 gfx_dbuf
32 gfx_demo
33 gfxbanks
34 hello
35 hello2
36 irqtest
37 kbdpoll
38 kbdraw
39 ls
44 pageflip
45 palfile
46 ptime
47 rt_test
48 scroll
49 seek
50 simple
59 text_palette
60 timedir
61 w0page
62 w3bankgfx w3bgfx
w3bgfx
63 w3big
64 w3huge
65 w3probe
+10
View File
@@ -23,6 +23,16 @@
* cbl_open()/cbl_close() (<cbl.h>) — тот же приватный 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 клавиатуры
+5 -4
View File
@@ -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];
+36 -1
View File
@@ -13,7 +13,9 @@
* клавиши) в битовую карту `_kbdraw_down[]` (бит на код, 64 Б),
* и к DSS НЕ идём
* (private RETI — тот же паттерн, что и у CBL ниже). Пока raw
* открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>).
* открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>) — и на
* кадровом пути тоже: пока 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 (см. <kbd_raw.h>).
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