Compare commits

..

3 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
snark13 a05970cd36 SprPoP: убрана устаревшая копия релизного дерева dist/SprPoP
dist/SprPoP/** — снимок готового к раздаче каталога (exe + упакованные
ассеты) от старой раскладки.  Ничто его больше не собирает: `make`
кладёт результат в build/, образ — в build/hdd/.  В dist/ остаются
только ИСХОДНИКИ README (README.txt / README.ru.txt), из которых
Makefile печёт README на образе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:00:22 +03:00
snark13 2176c12cc5 PoP: чистка архива roomtest + справка по клавишам оригинала
Замороженная roomtest: убраны рабочие заметки прошлых сессий
(NEXT_SESSION.md, «new bugs») и скриншоты уже закрытых багов;
в Makefile — PROF=0 по умолчанию и путь к HDD-образу.

applications/PoP/docs/keys.txt — раскладка управления оригинального
PoP (справочник для порта).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:00:15 +03:00
72 changed files with 193 additions and 259 deletions
+58
View File
@@ -0,0 +1,58 @@
* Left: turn or run left
* Right: turn or run right
* Up: jump or climb up
* Down: crouch or climb down
* Down+Left/Right: hop
* Shift: pick up things
* Shift+Left/Right: careful step
* Home or Up+Left: jump left
* Page Up or Up+Right: jump right
* Up while running: running jump
* Shift while falling: grab onto ledge
* Left/Right: walk (advance or retreat)
* Shift: strike (attack)
* Up: block (defend)
* Down: put sword away; press Shift to draw your sword again.
===
* Esc: Pause game.
* Space: Show how much time is left.
* Ctrl+A: Restart level.
* Ctrl+R: Return to intro.
* Ctrl+S: Sound on/off.
* Ctrl+M: Music on/off.
* Ctrl+V: Show version of SprPoP.
* Ctrl+Q: Quit game.
* F6: Quicksave: Save the exact state of the game.
* F9: Quickload: Load what the last quicksave saved.
* F12: Save a screenshot to the screenshots folder.
* Backspace: Display the in-game menu. (Esc will also display the menu by default, but you can turn that off.)
* Shift+L: Go to next level.
* -: Decrease remaining time by one minute.
* +: Increase remaining time by one minute.
* R: Resurrect kid.
* K: Kill guard.
* Shift+I: Flip the screen upside down.
* Shift+W: Slow falling.
* Shift+S: Restore a lost hit-point. (Like a small red potion.)
* Shift+T: Give more hit-points. (Like a big red potion.)
===
* H: Look at the room to the left.
* J: Look at the room to the right.
* U: Look at the room above.
* N: Look at the room below.
* Ctrl+B: Go back to the room where the prince is. (Undo H,J,U,N.)
===
* [: Shift kid 1 pixel to the left.
* ]: Shift kid 1 pixel to the right.
* T: Toggle display of timer (remaining minutes:seconds:ticks). Also shows the total elapsed ticks during playback.
+3 -1
View File
@@ -3,6 +3,8 @@
PROJ_ROOT := $(abspath $(CURDIR)/../../..)
EXAMPLE := roomtest
HDD_IMG := $(CURDIR)/build/hdd/roomtest.chd
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи)
@@ -88,7 +90,7 @@ EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) \
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
# MAME = время фазы в растровых строках.
PROF ?= 1
PROF ?= 0
PROF_FLAGS := -DPROF_BORDER=$(PROF)
# Стартовый уровень (отладка): make LEVEL=9 — начать сразу с девятого.
# Дефолт держим на отлаживаемом сейчас уровне; для «настоящей» игры — LEVEL=1.
-187
View File
@@ -1,187 +0,0 @@
# Точка входа для следующей сессии (обновлено 2026-08-12, ночь)
Файл для старта с чистого контекста: где всё стоит, что делать первым, какие
грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам:
`TASKS_OPEN.md` (доска), `BUGS_OPEN.md` (открытые баги), `CLAUDE.md`.
---
## 1. СЛЕДУЮЩАЯ ЗАДАЧА: smoke-прогон уровня 9 и регресс 1-8
Переворот (зелье инверсии) **доведён и принят пользователем 2026-08-12**:
фон, персонажи, клинок, полоса HP, факелы, окклюзия передних граней и
переходы между комнатами работают зеркально; чит `U` и зелёное зелье идут
одним путём. Осталось пройти сам уровень 9 живьём и убедиться, что на
уровнях 1-8 ничего не поехало (весь слой фона теперь спрашивает `pop_upside`).
Что смотреть на прогоне в первую очередь:
- **вис на кромке и спуск в перевёрнутом виде** — это новый срез `clip_char`
(он режет кадр СНИЗУ вместо верха); тело должно уходить за кладку, а не
ложиться поверх неё;
- **бой со стражем перевёрнутым** — у соперника свой зеркальный набор
(`g*_v.atl`), а тень рисуется атласами Кида, отдельных файлов ей не надо;
- **зелье в другой комнате** — переворот перерисовывает не комнату, а чистит
факелы и отражает страницу; проверить, что после смены комнаты и возврата
фон остаётся чистым.
Открытый риск (пока не проявился, но проверить осознанно): имена зеркальных
атласов `kid10_v.atl``kid27_v.atl` — **9 символов до точки, а DSS работает
по 8.3**. Если часть кадров Кида в перевёрнутом виде рисуется обычными
(неперевёрнутыми) — это оно; лечится переименованием в `pop_pack_kid.py`
(например `k10v.atl`) плюс те же имена в `pop_kboot.c`.
Дальше по плану — уровни 10/11 (по анализу нового в них нет) и
[MEM-COLD2](TASKS_OPEN.md#mem-cold2) п.3, если снова понадобится место.
Ощущенческий хвост прежний — [GRAB-KBD-TIMING](BUGS_OPEN.md#grab-kbd-timing),
разбирается после готовности всех уровней (решение пользователя).
## 2. Состояние репозитория
Всё закоммичено, `main` чист. `tests-host`: **6 наборов** проходят
(`[char] 65`, `[geom] 3144`, `[grab] 55`, `[mouse] 17`, `[phys] 1727`,
`[wall] 1`).
Коммиты 2026-08-12 (сверху — свежие):
| хеш | что |
|---|---|
| `7f5c99b` | чистка факелов при перевороте — в СТАРОЙ системе координат |
| `c9aa6d9` | переворот: не перерисовывать комнату, стирать только запечённое пламя |
| `948d8f0` | переворот: перерисовка вместо отражения, переключение на границе кадра, `clip_char` |
| `5b6664f` | зеркальные кадры персонажей — готовыми файлами `*_v.atl` |
| `c4dd2c7` | отсев тайлов по fore-окну — в ЛОГИЧЕСКИХ координатах |
| `7551906` | L9-INVERT II.4: персонажи рисуются перевёрнутыми |
| `055d6d7` | чтение файлов нельзя выносить в банк (код банка сам живёт в W3) |
| `147cb18` | резидент: `pop_kid`/`pop_guard`/`main` расколоты по частоте вызова |
| `5d31ed0` | резидент: `pop_redraw` + холодная половина `pop_level` в банки |
| `f541c0a` | клип поля при перевороте (мусор в бортах) |
Уровни 1-8 проходятся, уровень 9 ждёт прогона.
## 3. Раскладка кода после разгрузки резидента
Куча выросла **902 → 5795 Б** (`_CODE` 25 628 → 20 569). Кандидатов выбирали
не по размеру, а по тому, КТО зовёт: если единственный вызывающий уже в
банке, код едет к нему и трамплин не появляется вовсе.
| банк | модули |
|---|---|
| 1 | `guards.c` |
| 2 | `pop_bg.c` |
| 3 | `pop_map.c` |
| 4 | `pop_cdraw.c` + **`pop_kdraw.c`** (клинок, блит по id — прямые вызовы) |
| 5 | `pop_ctrl.c` |
| 6 | `pop_trob.c` |
| 7 | `pop_room.c` + **`pop_redraw.c`** |
| 8 | `roomtest_cold.c` + **`pop_level_cold.c`** + **`pop_kboot.c`** + **`pop_guard_cold.c`** |
Внутренние контракты: `_pop_level.h`, `_pop_kid.h`, `_pop_kdraw.h`. В них же
записаны два правила, которые линкер НЕ проверяет:
1. **Банковый модуль не маппит W3** — он в этом окне и исполняется (через W0
можно). Поэтому `pop_level_read_file` и `pop_kid_data_load` остались в
резиденте: первый же вызов из банка улетал в halt по мусору.
2. **Прямые вызовы между модулями одного банка** корректны только пока они
действительно в одном банке; меняешь раскладку в Makefile — проверь пары.
## 4. Переворот (L9-INVERT) — как он устроен
- **Флаги:** `pop_upside_want` (пишут зелье, смерть Кида, чит `U`) и
`pop_upside` (читают все слои). Переключение — ОДНО место, начало кадра,
вместе с `pop_flip_screen`. Оригиналу этого не нужно (он рисует в offscreen
неперевёрнутым и зеркалит на выводе) — расхождение записано в
`../docs/impl_diff.md`.
- **Сам переворот:** чистка запечённого пламени (`pop_torch_wipe`, 0..4
тайла) + отражение страницы акселератором + прямая копия во вторую.
**904 247 тактов = 2.1 кадра.** Вариант с полной перерисовкой комнаты
(как `redraw_screen` в оригинале) стоил 6 245 477 = 14.5 кадра — отвергнут.
- **Спрайты персонажей:** column-major, вертикальное зеркало для них не
бесплатно, поэтому второй набор файлов делает упаковщик (`vflip_cols` в
`pop_pack_kid.py`). Загрузка одной страницы с диска — 970 003 такта,
разворот той же страницы в рантайме — 12 645 582; поэтому файлы.
`pop_vflip_load_all()` идемпотентна и зовётся откуда угодно (сейчас — старт
и смена уровня, если это `POP_UPSIDE_LEVEL`; задел под загрузку в интро).
- **Координаты:** у окна fore-клипа ДВЕ пары границ — экранная
(`pop_t_fclip_y0/y1`, по ней режется блит) и логическая
(`pop_t_fclip_ly0/ly1`, по ней идут ВСЕ отсевы тайлов). Смешать их — и
передние грани перестают перекрывать персонажа.
## 5. Состояние окружения
- **MAME запущена с мостом**, на HDD — roomtest с `LEVEL=9`. Стартовый
уровень теперь **параметр сборки**: `make LEVEL=9` (дефолт 9; для «настоящей»
игры — `LEVEL=1`), константа объявлена в `pop_tune.h`.
- **ПРАВИЛО ПОЛЬЗОВАТЕЛЯ: перед сборкой образа — `make clean`, потом `make`,
потом `make hdd`** (memory `mame_build_clean_rule`). После пересборки
образа MAME **обязана** пройти полный рестарт (memory
`mame_hdd_rebuild_restart`): остановка через `exit` в дебаггере, затем
`run_bridge.sh`.
- **Тайминги моста** (правило пользователя, длиннее не ждать; они же в
`docs/mame-autotest.md` §10): старт `run_bridge.sh`**6 с**`go`
**8 с**`keyseq d:{ENTER}` + `keyseq roomtest{ENTER}`**5 с**
программа работает. Весь цикл перезапуска ~20 секунд.
- **MCP-инструменты `mame-z80` работают** (в отличие от прошлой сессии):
`press_key`, `screenshot`, `read_logical_memory`, `debugger_command`.
Профилирование — `bpset <addr>` + `print totalcycles`, затем `out` и снова
`print totalcycles`; кадр = 430 000 тактов.
- Адреса состояния смещаются после КАЖДОЙ пересборки — брать из
`.sprinter-cc-roomtest/roomtest.map` заново. Банковые функции лежат по
`0x0008XXXX`; для брейкпоинта брать младшие 16 бит (`0xC912` и т.п.).
- **SDLPoP собран с отладочной информацией** (`-O0 -g3`).
- **НЕ ПРИБРАНО:** в `SDLPoP/src/seg008.c` диагностический `fprintf` с меткой
`DBGMIRROR` в начале `add_objtable`.
## 6. Остальное открытое
- **[FORE-DUP](BUGS_OPEN.md#fore-dup)** — передний слой тайла рисуется дважды
при перекрытии объектов. Картинку не портит, тратит такты. Сначала
замерить, потом чинить.
- **TORCH-ANIM-RIGHT** — под запечённым пламенем могут застыть пики/меч/зелье
справа от факела.
- **[GATE-FORE-KID](BUGS_OPEN.md#gate-fore-kid)** — Кид в проёме ворот виден
поверх решётки; портирован только шовный случай.
- Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1`, `L3-COLOR`, `L1-SPEED`, `TUNE-1`.
- **Вид тени (OR+XOR) отложен** до готовности всех уровней —
[`../docs/shadow_render.md`](../docs/shadow_render.md).
## 7. Грабли, собранные 2026-08-12 (вечер/ночь)
- **`return 1;` из ветки у SDCC 4.5 может не положить единицу в A** — функция
возвращает мусор. Годами маскировался (в A случайно оставался ненулевой
байт) и вылез ровно на `__banked`. Репро — `docs/bugs/sdcc-z80-ret-const-lost/`,
обход — один выход через переменную. Аудит всех `.asm` roomtest: других
мест нет.
- **Банковый модуль не может маппить W3** (см. §3). Симптом — halt по мусору
в 0xC4xx на первом же вызове.
- **Логические и экранные координаты при перевороте — разные системы.** Три
бага подряд из одного корня: отсев тайлов по fore-окну, `clip_char`, чистка
факелов. Правило: всё, что считается ИЗ РЯДА комнаты (`63*row + …`), —
логическое; всё, что режет блит, — экранное.
- **Пламя факела ЗАПЕКАЕТСЯ в ОЗУ-копию** (heal'а у него нет, следующий кадр
накрывает предыдущий непрозрачно). Любое отражение/копирование страницы
тащит его с собой — перед такими операциями звать `pop_torch_wipe`.
- **`pop_upside` переключать в середине кадра нельзя**: остаток кадра
рисуется зеркально поверх ещё неперевёрнутой картинки, и запечённые
транзиенты остаются в комнате навсегда.
- **Перенос константы между файлами — потерянное значение.** При выносе
`FIRST_LEVEL` в `pop_tune.h` записал дефолт 1, и игра молча стартовала с
первого уровня вместо девятого; заметил пользователь. Теперь это параметр
сборки (`make LEVEL=N`).
- **`make -C tests-host` не пересобирает по изменению заголовков** — при
правке прототипов (особенно `__banked`) обязателен `make -C tests-host
clean`, иначе стаб и вызывающий разъезжаются по ABI и тест врёт.
## Грабли 2026-08-10/11
- **Не оценивать железо по своей же memory-заметке.** Дважды поправлял
пользователь: accel умеет не только копирование (есть AND/OR/XOR) и умеет
работать с обычной RAM, а не только с видео (`docs/part2/accelerator_doc.txt`:
«supports data blocks transfer of RAM and Video-RAM», не поддерживает только
ROM и FastRAM). Проверять по драйверу MAME и докам, а не по памяти.
- **lldb через FIFO — плохая идея.** Работает надёжно: `fprintf(stderr, …)`
прямо в SDLPoP, пересборка (секунды) и чтение stdout.
- **Доска отставала от кода на три задачи.** Перед планированием — сверять
`TASKS_OPEN.md` с кодом грепом, а не верить записи.
- Диапазон `obj_x` = **416..695** (посчитан из `kid_data.bin`).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 KiB

-6
View File
@@ -1,6 +0,0 @@
Уровень 5, комната 2 - с разбега чуть раньше - иногда попадаем в стену (некорректное отображение падения)
Уровень 7, комната 9 - падает плита -1,1 - звук потери HP у Кида (причем раз даже когда Кид просто бъет по плите стоя на 0,0)
/// Уровень 10, комната 1 - когда Кид стоя на кнопке 1,8 роняет плиту 0,8 на кнопку 1,8 - кнопка ломается (превращается в щебень)
/// но двери которые она должна открыть остаются закрытыми
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+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