Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dabe9b4b1 | |||
| a05970cd36 | |||
| 2176c12cc5 |
@@ -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,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.
|
||||
|
||||
@@ -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`).
|
||||
|
Before Width: | Height: | Size: 482 KiB |
|
Before Width: | Height: | Size: 426 KiB |
|
Before Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 413 KiB |
|
Before Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 825 KiB |
|
Before Width: | Height: | Size: 825 KiB |
|
Before Width: | Height: | Size: 825 KiB |
@@ -1,6 +0,0 @@
|
||||
Уровень 5, комната 2 - с разбега чуть раньше - иногда попадаем в стену (некорректное отображение падения)
|
||||
|
||||
Уровень 7, комната 9 - падает плита -1,1 - звук потери HP у Кида (причем раз даже когда Кид просто бъет по плите стоя на 0,0)
|
||||
|
||||
/// Уровень 10, комната 1 - когда Кид стоя на кнопке 1,8 роняет плиту 0,8 на кнопку 1,8 - кнопка ломается (превращается в щебень)
|
||||
/// но двери которые она должна открыть остаются закрытыми
|
||||
@@ -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: Кид влетает в комнату сверху
|
||||
и, пролетая мимо края пола, МОЖЕТ за него зацепиться — но получается заметно
|
||||
реже, чем в оригинале. «Похоже, это проблемы нашего клавиатурного модуля».
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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 клавиатуры
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||