SprPoP: раскладка управления — фазы A и B плана keys_plan.md

Приводим клавиши к keys.txt (раскладка SDLPoP).  Работа делится надвое, и
здесь только то, что не трогает игровую логику: переназначения (A) и три
мелкие функции поверх готовых механизмов (B).  Читы, требующие правки
логики, и осмотр соседних комнат — фазы C и D, отложены.

ПЕРЕНАЗНАЧЕНИЯ (A)

  U            -> Shift+I   переворот экрана; U отдан «комнате сверху»
  Esc          -> Backspace меню; Esc остаётся дублёром, как у SDLPoP
  F7 / F8      -> - / +     ±минута, основной ряд и цифровой блок
  - / +        -> Ctrl+- / Ctrl++  обход комнат (наш отладочный телепорт)
  I            -> Ctrl+I    бессмертие
  P            -> Ctrl+P    режим скорости
  1 и 2        -> Ctrl+F    стоп-кадр, теперь одной клавишей
  F10 в игре   -> Ctrl+Q    вторая клавиша выхода; F10 ловится глобально

Плюс новое на готовых путях: Ctrl+A — рестарт уровня, Ctrl+V — версия
сборки (функция была, её показывал только старт), Ctrl+D — отладочная
строка (тот же тумблер, что в Settings; POP.CFG не пишем), Home / Page Up —
дублёры диагональных прыжков (у SDLPoP это не отдельное действие, а те же
Up+Left / Up+Right, поэтому просто добавляются к стрелкам).

Все наши сверхштатные клавиши ушли под Ctrl, чтобы не занимать голые буквы
из раскладки, и вписаны в keys.txt отдельным разделом.  Ctrl+B намеренно
не занята: keys.txt держит её под «вернуться в комнату Кида» (фаза D).

ФУНКЦИИ (B)

  Space  «сколько осталось» (seg000:612).  Не печатает сама: поднимает тот
         же pop_show_time, которым пользуется автоматическое объявление
         минут, и строку собирает time_msg() — «59 MINUTES LEFT» и «11
         SECONDS LEFT» остаются в одном месте.
  T      постоянный показ таймера.  Переиспользует поле DBG_F_TIME
         отладочной строки, своего рендера нет.  У верхней полосы теперь
         три состояния, и отслеживается РЕЖИМ (0 нет / 1 таймер / 2 всё),
         а не флаг: переход «таймер -> полоса» тоже перерисовывает всё.
  Ctrl+R возврат в заставку — тот же переход, что «Restart Game» в меню.

ДВЕ ЛОВУШКИ, найденные по дороге

  Модификатор обязан входить в САМО значение, а не в условие блока: с
  `if (ctrl) { nav = ...; nav_prev = nav; }` при отпускании Ctrl кромка
  застревала ненулевой и следующее нажатие глохло.

  Один скан-код на два чита: Shift+I и Ctrl+I — это 0x43 в обоих случаях.
  По той же причине ±минута требует ОТПУЩЕННОГО Ctrl (иначе сработает и
  время, и обход комнат), а T — отпущенных Shift и Ctrl (Shift+T отдан
  «добавить HP» в фазе C).

Обработчики положены в pop_frame_ui (банк 8), а не в резидент: там куча
всего 308 байт.  Проверено в MAME: Ctrl+D поднимает строку
«Level 1, Room 1, Speed: NORMAL...», T — один таймер 59:30 без подписей,
Space — watchpoint на pop_show_time ловит запись значения 2 (именно
обработчик клавиши, автообъявление пишет 1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 15:22:21 +03:00
parent c71981fdf9
commit 76f02e76db
10 changed files with 376 additions and 39 deletions
+164
View File
@@ -0,0 +1,164 @@
# План: перевод управления на раскладку `keys.txt`
Целевая раскладка — [`keys.txt`](keys.txt), она же раскладка SDLPoP. Здесь
разобрано, что у нас уже есть, что стоит не на той клавише и каких функций
нет вовсе, плюс порядок работ. Кода этот документ не меняет.
## 1. Принципы
**Раскладка — не самоцель.** Часть строк `keys.txt` — это функции SDLPoP,
которых у нас нет вообще (воскрешение, замедленное падение, осмотр соседних
комнат). Поэтому работа делится на две разные по цене части: *переназначить
клавишу* (минуты) и *реализовать функцию* (от часа до дня). Смешивать их в
одной фазе нельзя — иначе простые переименования застрянут за сложным.
**Механику сверять с SDLPoP.** Для каждой новой функции — сначала найти её в
`assets/orig/SDLPoP/src/`, потом писать (правило проекта, memory
`pop_check_sdlpop_first`). Особенно для читов: `Shift+S`/`Shift+T` — это не
«прибавить HP», а конкретные `add_life`/`set_health_life`.
**Кромка против удержания.** Игровые клавиши (стрелки, Shift) читаются как
удержание — это held-state, ради которого и заводился `<kbd_raw.h>`. Все
служебные и читерские — как НОВОЕ нажатие (edge), иначе одно касание
сработает столько раз, сколько кадров клавиша была внизу. В коде уже есть
оба образца: `read_user_control()` для первого, `intro_skip_requested()` и
`hof_*` для второго.
**Где живёт обработчик.** Служебный слой — `pop_frame_ui()` в
`sprpop_cold.c` (банк 8), читы — `pop_cheat.h` + вызовы из главного цикла.
После переноса `pop_config`/`pop_hof` в банк 10 в банке 9 свободно 4436
байт, в банке 8 — 1786; новые обработчики лучше класть в 8-й или 10-й, а не
в 9-й.
**Ctrl-комбинации уже поддержаны:** `Ctrl+S`/`Ctrl+M` проверяют
`KBD_LCTRL || KBD_RCTRL` вместе с буквой (`sprpop_cold.c:518`). Тот же
приём годится для `Ctrl+A`, `Ctrl+R`, `Ctrl+V`, `Ctrl+Q`, `Ctrl+B`.
## 2. Что есть сейчас
### Движение и бой — совпадает, кроме диагоналей
| `keys.txt` | у нас | статус |
|---|---|---|
| Left / Right / Up / Down | стрелки, `read_input()` | **есть** |
| Down+Left/Right — прыжок на месте | комбинация стрелок | **есть** |
| Shift — поднять предмет | `KBD_LSHIFT`/`KBD_RSHIFT` | **есть** |
| Shift+Left/Right — осторожный шаг | комбинация | **есть** |
| Up+Left / Up+Right — прыжок в сторону | комбинация | **есть** |
| **Home / Page Up** — то же самое | — | **НЕТ**: читаются только стрелки |
| Up на бегу — прыжок с разбега | | **есть** |
| Shift в падении — зацеп | | **есть** |
| Бой: Left/Right/Shift/Up | | **есть** |
| Бой: Down — убрать меч | | **проверить отдельно** |
### Служебные
| `keys.txt` | у нас | статус |
|---|---|---|
| `Esc` — пауза/меню | `KBD_MENU = 0x76` | **есть** |
| `Backspace` — меню | — | **НЕТ** (в оригинале это основная клавиша меню, Esc — дублёр) |
| `Space` — показать остаток времени | — | **НЕТ** (`Space` занят только в меню и вводе имени) |
| `Ctrl+A` — рестарт уровня | только пунктом меню | **НЕТ клавиши** |
| `Ctrl+R` — вернуться в заставку | — | **НЕТ** |
| `Ctrl+S` — звук вкл/выкл | `Ctrl+S` | **есть, совпадает** |
| `Ctrl+M` — музыка вкл/выкл | `Ctrl+M` | **есть, совпадает** |
| `Ctrl+V` — версия | `pop_build_info_show()` есть, зовётся на старте | **НЕТ клавиши** |
| `Ctrl+Q` / `F10` — выход | `F10` в двух местах | **половина** (см. §3) |
| `F6` / `F9` — quicksave/load | совпадает | **есть** |
| `F12` — скриншот | — | помечено опциональным, **не делаем** |
### Читы
| `keys.txt` | у нас | статус |
|---|---|---|
| `Shift+L` — следующий уровень | совпадает | **есть** |
| `K` — убить стража | совпадает | **есть** |
| `[` / `]` — сдвинуть Кида на пиксель | совпадает | **есть** |
| `+` / `` — минута времени | у нас это **F7/F8**, а `+`/`` заняты обходом комнат | **конфликт, см. §3** |
| `Shift+I` — перевернуть экран | у нас это **`U`** | **переназначить** |
| `R` — воскресить Кида | — | **НЕТ функции** |
| `Shift+W` — замедленное падение | — | **НЕТ функции** |
| `Shift+S` — вернуть одну единицу HP | — | **НЕТ функции** |
| `Shift+T` — добавить HP | — | **НЕТ функции** |
| `T` — показывать таймер | — | **НЕТ функции** |
### Осмотр комнат — нет целиком
`H` / `J` / `U` / `N` (соседняя комната слева/справа/сверху/снизу) и
`Ctrl+B` (вернуться к Кида) не реализованы. У нас есть отладочный **обход
комнат по номеру** (`+`/``, `ROOMNAV` в `sprpop.c`) — это другое: он
ТЕЛЕПОРТИРУЕТ Кида, а `H/J/U/N` только смотрят, не двигая его.
### Наше сверх `keys.txt`
`I` — бессмертие, `P` — режим скорости, `1`/`2` — стоп-кадр, `+`/``
обход комнат. Всё это отладочное и в оригинале отсутствует; выбросить или
оставить — решение пользователя (§6).
## 3. Конфликты, которые надо решить до кодинга
**К1. `+`/`−`: время или комнаты.** `keys.txt` отдаёт их под ±минуту, у нас
на них обход комнат, а время висит на F7/F8. В `pop_cheat.h` это записано
как осознанный выбор («Numpad +/- уже принадлежат ROOMNAV»). Варианты:
вернуть `+`/`` времени и увести ROOMNAV на другие клавиши; оставить как
есть и разойтись с `keys.txt`; либо развести по контексту (ROOMNAV — только
в отладочной сборке). Развилка требует решения.
**К2. `U` занят.** Сейчас `U` — переворот экрана, а `keys.txt` хочет `U` под
«посмотреть комнату сверху», переворот — на `Shift+I`. Разрешается само,
если делать К5 и К7 одной фазой.
**К3. `S` без Ctrl.** Звук у нас требует Ctrl, и это правильно, но
`keys.txt` хочет ещё и `Shift+S` (вернуть HP). Проверка обязана различать
`Ctrl+S`, `Shift+S` и голое `S`, иначе они будут срабатывать вместе. То же
для `T`: `T` (таймер) против `Shift+T` (добавить HP).
**К4. Два выхода.** `F10` живёт в `pop_frame_ui()` (только в игре) и, с
коммита 808c2a5, в `kbd_idle()` (везде). Дублирование безвредное, но лишнее:
проверку в `pop_frame_ui` стоит убрать, оставив одну точку. Заодно повесить
туда же `Ctrl+Q`.
## 4. Порядок работ
**Фаза A — переназначения, функции уже есть.** Дёшево и не трогает логику.
- `Shift+I` вместо `U` для переворота экрана (К2).
- `Backspace` как основная клавиша меню, `Esc` оставить дублёром.
- `Ctrl+V` на существующий `pop_build_info_show()`.
- `Ctrl+Q` рядом с `F10`, убрать дубль `F10` из `pop_frame_ui` (К4).
- `Ctrl+A` на существующий путь `POP_MENU_RESTART_LEVEL`.
- Решение по К1 и правка `+`/``/F7/F8.
**Фаза B — мелкие новые функции.**
- `Space` — показать остаток времени. Механика есть (`pop_timer`, статус-
строка); нужен показ на несколько секунд, как `text_time_total` оригинала.
- `T` — постоянный показ таймера (минуты:секунды) в статус-строке.
- `Ctrl+R` — возврат в заставку. Автомат это умеет (`POP_APP_EV_RESTART_INTRO`),
нужен только вход с клавиши.
**Фаза C — читы, требующие правки игровой логики.** Каждый — сверка с
SDLPoP до кодинга.
- `Shift+S` / `Shift+T` — HP: аналоги малого и большого красного зелья.
- `R` — воскрешение Кида: сложнее прочих, поднимает мёртвого персонажа и
должен согласоваться с уже существующим гейтом «мёртв» (memory
`pop_level_restart_scope`).
- `Shift+W` — замедленное падение.
**Фаза D — осмотр соседних комнат (`H`/`J`/`U`/`N` + `Ctrl+B`).** Самое
крупное: нужен показ комнаты, в которой Кида нет, с возвратом. У нас уже
разобрана модель `kid_room ≠ drawn_room` (memory `pop_seam_room_model`,
[`room_model_plan.md`](room_model_plan.md)) — это её прямое применение, и
именно поэтому фаза идёт последней.
**Фаза E — `F12` скриншот.** Помечен опциональным; предлагаю не делать
вовсе: файл на 80 КБ и запись на диск посреди кадра.
## 5. Открытые вопросы
1. **К1** — кому отдать `+`/``: времени (как `keys.txt`) или обходу комнат?
2. Что делать с нашими сверхштатными клавишами (`I` бессмертие, `P` скорость,
`1`/`2` стоп-кадр): оставить как есть, увести под отладочную сборку или
выбросить?
3. Нужны ли `Home`/`Page Up` как дублёры диагональных прыжков — на IBM-клавиатуре
Sprinter они есть, но комбинация `Up+Left` уже работает.
4. Фазы C и D — делать ли их вообще: это читы и отладочный осмотр, к
прохождению игры отношения не имеющие.