# roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-07) Что берём в работу сейчас и в каком порядке. Каждая запись: что сделать, почему именно сейчас, чем подтверждать результат. - закрытые задачи с протоколами и замерами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md); - открытые баги — [`bug_list.md`](bug_list.md), закрытые с разбором корней — [`bug_closed.md`](bug_closed.md); - планы фаз — `../docs/PORT_PLAN.md`, `../docs/layout_plan_v2.md`, `../docs/levels_plan.md`. **Состояние на 2026-08-07:** пользователь прогнал ВСЕ комнаты уровней 1 и 2 — крупных багов нет. Приёмки [L1-PASS](TASKS_CLOSED.md#l1-pass) и [L2-PASS](TASKS_CLOSED.md#l2-pass) закрыты; с прогона открыты три записи по уровню 2 (цвет стража, брызги по стражу, чит `+`/`−` в бою) — все в [`bug_list.md`](bug_list.md). Уровень 3 играется, но приёмки не было: сначала чомперы и скелет. Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга; диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не гипотезой (memory `defer_unexplained_quirks`). --- ## ТЕКУЩАЯ ЦЕЛЬ: уровни 1–3 (подземелье) отлажены целиком Решение 2026-08-04: **palace (уровни 4+) откладываем**, доводим до играбельности три dungeon-уровня. Основание — они не требуют ни одного нового ассета фона: инвентарь тайлов, снятый с `res200N.bin`, показывает, что новое появляется только так — ``` ур. 1 empty, floor, spike, pillar, gate, closer, potion, loose, debris, opener, level_door L/R, torch, wall, skeleton, sword ← всё есть ур. 2 bigpillar_bottom(8), bigpillar_top(9), doortop(12) ← есть (2026-08-04) ур. 3 chomper(18) ← НЕТ механики ур. 4 lattice_pillar(25)…lattice_right(29) + тайлсет palace ← отложено ``` | # | Задача | Что | Блокирует | |---|--------|-----|-----------| | 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME | | 2 | [L3-CHOMP](#l3-chomp) | чомперы (5 шт) | прохождение ур. 3 | | 3 | [L3-SKEL](#l3-skel) | скелет (единственный противник ур. 3) | прохождение ур. 3 | | 4 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели | | — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент | | — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности | Сделанное по этой цели — в [`TASKS_CLOSED.md`](TASKS_CLOSED.md): L2 (машинерия уровней), L2-PASS, L1-PASS, [L3-CHKP](TASKS_CLOSED.md#l3-chkp) (чекпойнт уровня 3), MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS. --- ## Ждёт ФИНАЛЬНОЙ приёмки уровней 1–3 Сюда попадает то, что уже работает в проверочном прогоне, но должно быть подтверждено на сквозных прогонах уровней — потому что задевает механику шире, чем собственный сценарий. - **Зацеп ПРЯМО В ПРЫЖКЕ** (`POP_ENABLE_JUMP_GRAB`, `pop_tune.h`, сделан 2026-08-06, предварительно проверен пользователем). Почему нужен именно финальный прогон: точки вызова стоят не только в `check_action`, но и в ОБЕИХ ветках `check_bumped` — то есть код вклинивается перед обычным ударом о стену. Регрессия проявится не в самом зацепе, а рядом: удар о стену с зажатым Shift, осторожный шаг у стены, отскок в прыжке. На уровнях 1–3 это надо специально потрогать в паре мест каждого уровня. Напоминание: в ВАНИЛИ этого зацепа нет (у SDLPoP — `enable_jump_grab`), так что сверять его с оригиналом «как есть» нельзя — только с SDLPoP при включённых enhancements. Прогон 2026-08-07 (уровни 1 и 2) регрессий рядом не показал, но специально на удар о стену с Shift не проверялся. ## P0 — делаем сейчас ### GUARD-PHYS. Страж живёт по тем же правилам, что Кид — **ЯДРО СДЕЛАНО 2026-08-07** > **Что уже работает** (решение пользователя: переносим физику на `Char`, > без предварительных замеров — иначе третий-четвёртый экземпляр той же > логики неизбежен). > > - **физика переведена на `Char`**: `pop_map.c` целиком работает с активным > персонажем, а кто в `Char` — решают окна `loadkid`/`savekid` и > `loadshad`/`saveshad`, как в оригинале (seg006:809). Два входа: > `pop_phys_tick` (порт хвоста play_kid_frame) и `pop_guard_phys_tick` > (порт play_guard_frame) — списки вызовов отличаются ровно тем, чем в > оригинале; > - **`take_hp` стал общим** (`pop_take_hp` в резиденте `pop_guard.c`): урон > идёт тому, кто в `Char`, по `charid`. Раньше у боёвки и у физики были > свои копии, причём у физики неверная — правила `hitp_curr` мимо дельты и > про не-Кидов не знала; > - **порт веток по charid в `land()`** (seg005:173): страж гибнет с двух > рядов, тень падает как с одного, у не-Кида приземление даёт боевую > стойку; `check_guard_bumped` (seg004:0522), `droppedout` + > `guard_follows_kid_down` (seg002:09F8), `check_guard_fallout` > (seg002:0241); > - **`pop_savekid_state` снова полное `Kid = Char`**, а > `pop_load_fram_det_col` пересчитывает колонку ЛЮБОМУ персонажу — обе > заплатки существовали только потому, что физика знала один `Kid`. > > **Проверено:** `tests-host` — трассы Кида не изменились (`[phys] ok: 1723`, > тот же эталон), новый набор `t_char` (32 проверки) покрывает ветки по > персонажам; в MAME проверено, что игра жива (респавн, бег, падение, > приземление). Цена: `_CODE` +80 Б, банк 3 (`pop_map`) 7973 → 8485 > (51.8 %), банк 1 (`guards`) 2042 → 2156. > > **Проверено пользователем 2026-08-07:** страж СПРЫГИВАЕТ ЗА КИДОМ на ряд > ниже — связка «ИИ + физика» работает вживую, не только в тестах. > > **`follow_guard` портирован и проверен в MAME (2026-08-07):** уровень 1, > бой в комнате 3, Кид отступает влево — страж приходит следом (`Guard.room` > 3 → 2, X перенесён через шов), ровно как в SDLPoP. Условия отбора > покрыты тестами `t_char` (7 сценариев: пороги 91/165, «не бой», мёртвый, > вверх/вниз, занятая соседняя комната). **Сцена вскрыла отдельный баг — > [BUG-SWORD-GHOST-1](bug_list.md#bug-sword-ghost-1): при переходе в бою Кид > прячет меч и дальше дерётся пустой рукой.** > > **Осталось (потому и запись открыта):** > 1. `check_chomped_guard` — вместе с [L3-CHOMP](#l3-chomp); > 2. ветки `check_guard_fallout` для тени и скелета (скелет возрождается в > комнате 3) — вместе с [L3-SKEL](#l3-skel); > 3. страж, нажимающий напольную кнопку, вживую не проверялся (код — > общий `check_press`). **Почему это была ОДНА физика, а не «сделаем стражу свою».** В оригинале слот `Guard` — не «стражи», а все НЕ-Киды: тем же `play_guard_frame` ходят **страж** (charid 2), **скелет** (4, ур. 3), **тень** (1, ур. 4/5/6/12), **визирь-Джаффар** (ур. 13), **толстяк** (FAT, ур. 12) и **мышь** (0x18, ур. 8) — `tbl_guard_type` = {0,0,0,2,0,0,1,0,0,0,0,0,4,3,−1,−1}, а `autocontrol_opponent` (seg002:628) разводит их ТОЛЬКО по ИИ. То есть второй экземпляр логики пришлось бы делать не один раз, а пять. **Гард по X: страж не уходит САМ — но его МОГУТ ПЕРЕНЕСТИ.** Поправка к формулировке, которая была здесь раньше («страж не покидает комнату ни в каком виде») — она неверна, контрпример дал пользователь: в SDLPoP страж из комнаты 3 оказывается в комнате 2 вслед за отступающим Кидом. Разделять надо два разных механизма: - **своим ходом — не может.** Физика персонажа слота Guard обёрнута `Char.room == drawn_room` и `Char.x >= 44 && Char.x < 211` (seg000:1252), и никакого `check_leave` в его списке вызовов нет. Провалившегося ниже комнаты убирает `check_guard_fallout` (seg002:0241) — вниз он не уходит. - **следом за Кидом — переносит движок.** `exit_room` (seg002:03C7) вызывается ПОСЛЕ того, как комнату сменил Кид, и решает судьбу стража: ```c if (Guard.alive < 0 && Guard.sword == sword_2_drawn) { // жив и В БОЮ if (guards_tile[kid_room−1] >= 30 || // в новой комнате guards_seq_hi[kid_room−1] != 0) { // своего живого нет if (ушёл ВЛЕВО) { if (Guard.x >= 91) leave = 1; } // страж далеко — остаётся else if (ВПРАВО) { if (Guard.x < 165) leave = 1; } else if (ВВЕРХ) { if (Guard.curr_row >= 0) leave = 1; } // всегда → не идёт else { if (Guard.curr_row < 3) leave = 1; } // вниз → не идёт } else leave = 1; } else leave = 1; leave ? leave_guard() : follow_guard(); ``` `follow_guard` (seg002:039E) стирает `guards_tile` в ОБЕИХ комнатах (0xFF — «стража здесь нет», чтобы он не раздвоился) и гонит стража через `goto_other_room` в окне `loadshad`/`saveshad`. **Что это значит для нас.** У нас в `enter_room` (`roomtest.c:265`) стоит безусловный `pop_guard_leave()` — то есть всегда ветка `leave_guard`, и страж ВСЕГДА остаётся. Портировать надо сам `exit_room`-выбор: условия «жив + меч вынут + в целевой комнате нет своего стража + он у нужного края» и `follow_guard`. Пороги 91/165 — это «страж у того края, в который ушёл Кид»; вверх и вниз оригинал не пускает никогда. **Чем подтверждать:** уровень 1, комната 3 — начать бой, отступить влево в комнату 2: страж обязан прийти следом и продолжить бой (как в SDLPoP на скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ живой страж — переход не происходит. ### L3-CHOMP. Чомперы — механика уровня 3 **Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти на каждом уровне, так что это вложение не только в ур. 3. **Что портировать:** `animate_chomper` (seg007) — состояние в `room_modif`, как у пик/ворот, значит шаблон уже отработан (`gates_spikes_plan.md`); коллизия и смерть Кида в сомкнутых челюстях (`seg004`/`seg006`); отрисовка кадра по модификатору (`draw_tile_anim`, ветка `tiles_18_chomper`). **Риск:** банк 2 (`pop_bg`) занят на **90.8 %, свободно 1503 Б** (замер 2026-08-07, было 2213 Б) — считать место ДО кодинга (`levels_plan.md` §5.1), иначе повторится «банк 2 упёрся в потолок» (коммит 2f3e854). Свободные номера банков есть (5+). **Ассеты УЖЕ упакованы (2026-08-07):** `pop_pack_bg.py` кладёт в атлас весь набор кадров чомпера явно (`CHOMPER_BOT_IDS` 101-105, `CHOMPER_TOP_IDS` 111-113, `CHOMPER_FORE_IDS` 106-110 + кровь 114-123 mono-силуэтом цветом 12). Раньше в атласе не было НИ ОДНОГО его кадра: `render_room.py` пропускает анимированные тайлы, а `tile_table[0x12].base_id = 0` — отсюда и «чомпера не видно вовсе». Рост: `pop_env3.atl` 5200 -> 12937 Б, `pop_fore.atl` 7763 -> 12359 Б, число EMM-страниц НЕ изменилось (7). **Чем подтверждать:** комната 22 уровня 3 — чомпер (2,6) анимируется и рисуется (сейчас его не видно вовсе, см. раздел «Уровень 3» в [`bug_list.md`](bug_list.md)); смерть Кида в сомкнутых челюстях. ### L3-SKEL. Скелет — единственный противник уровня 3 **Важно:** в данных уровня 3 **стражей нет вообще** (`guards_tile` пуст во всех 24 комнатах). Единственный враг — скелет, и он не «страж из данных», а **спецсобытие** `check_skel` (seg002:1044): > в комнате 1, когда `Kid.curr_col` == 2 или 3 и дверь уровня открыта, > тайл `tiles_21_skeleton` (комната 1, тайлпос 15) стирается в пол, а на > его месте поднимается персонаж: `charid_4_skeleton`, меч сразу вынут, > `seq_88_skel_wake_up`, skill 2, HP 3. Ещё два тайла скелета (комнаты 17 и 19) — декорация, они не оживают. > **Сделано 2026-08-07 (ждёт живой проверки на уровне 3):** > - **атлас скелета** — `pop_pack_guard.py SKEL` собирает `poc/res/skel/g0..g3.atl` > (28 кадров, 30 КБ, 4 EMM-страницы); упаковщик получил параметр набора > (`GUARD`/`SKEL`). Палитра у скелета СВОЯ (`SKEL/res750.pal`), а не из > `res10.bin`: на уровне 3 `tbl_guard_type != 0`, значит > `curr_guard_color = 0` и оригинал палитру не подменяет вовсе; > - `pop_guard_load` выбирает набор по типу уровня и заливает палитру > скелета; Makefile кладёт атласы в `SKEL\` на диск; > - **`check_skel`** (seg002:1042) — порт в `guards.c`: уровень 3, комната 1, > дверь уровня открыта, `Kid.curr_col` 2 или 3, тайл 21 на (5,1) → тайл > стирается в пол (обе страницы), персонаж встаёт с `seq_88_skel_wake_up`, > мечом наголо, skill 2, HP 3. Зовётся из главного цикла ДО логики > персонажей, как в `play_frame`; > - **`leveldoor_open`** — флаг появился (`pop_state.c`), взводит анимация > двери при `modif >= 43` (seg007:456); > - **`enter_guard`** — ветка `charid_4_skeleton`: встаёт сразу активным > (меч вынут), а не в стойке покоя; > - **возрождение** — `check_guard_fallout`: упавший скелет, под комнатой > которого лежит комната 3, появляется там снова (x 133, ряд 1); > - **ИИ** — `autocontrol_skeleton` (seg002:685): меч у скелета вынут всегда. > > Регресс: `tests-host` зелёные, `t_char` вырос до 65 проверок (добавлены два > сценария возрождения). Цена: `_CODE` +80 Б, банк 1 (`guards`) 2342 → 2519, > банк 4 (`gdraw`) 3875 → 4269. > > **Не проверено вживую:** сцена требует пройти уровень 3 до кнопки, которая > открывает выход — без открытой двери скелет по условию не встаёт. **Что нужно:** - `charid_4_skeleton` в `enter_guard`/`pop_guard_enter` (seg002:196): при `tbl_guard_type[level] == 2` персонаж поднимается **с вынутым мечом** и последовательностью `seq_63_guard_active_after_fall`, а не `seq_77_guard_stand_inactive`; - скелет **бессмертен** — чит `K` его уже не берёт (`pop_guard_kill` проверяет `CHARID_4_SKELETON`), но и боёвка должна возвращать его к жизни (seg002:252); - **новый атлас**: `../SDLPoP/data/SKEL/` (29 файлов) — `pop_pack_guard.py` сейчас прибит к `GUARD/`, нужен параметр набора (`tbl_guard_dat` = GUARD/FAT/SKEL/VIZIER/SHADOW). Здесь же удобно закрыть [BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1): палитра стража подменяется по `guards_color`, и оба изменения живут в одном упаковщике. ### L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный) **Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В оригинальной VGA-версии кладка уровня 3 ЗЕЛЁНАЯ, а уровней 1-2 — серо-синяя. В SDLPoP все подземелья одинаковые, поэтому по нему разницу не увидеть. **Почему в SDLPoP её нет — проверено, не гипотеза.** Механизм там ЕСТЬ (seg000:1140, «Level colors (1.3)»): ```c int level_color = custom->tbl_level_color[current_level]; if (level_color != 0) { byte* env_pal = level_var_palettes + 0x30*(level_color-1); byte* wall_pal = env_pal + 0x30 * custom->tbl_level_type[current_level]; set_pal_arr(0x50, 0x10, (rgb_type*)env_pal); /* chtab_6 environment */ set_pal_arr(0x60, 0x10, (rgb_type*)wall_pal); /* chtab_7 wall */ } ``` `tbl_level_color` (data.h:842) = `{0,0,0,1,0,0,0,1,2,2,0,0,3,3,4,0}` — у уровня 3 цвет **1**, у 7 тоже 1, у 8/9 — 2, у 12/13 — 3, у 14 — 4. Но `level_var_palettes` — это ресурс **20** из `PRINCE.DAT` (только версии 1.3/1.4), а в `SDLPoP/data/PRINCE/` его НЕТ: там лежит лишь `res10.bin` (палитры стражей). Значит `level_var_palettes == NULL` и вся ветка молча пропускается — отсюда одинаковые подземелья. **Данные у нас есть.** В `../MSDOS/PRINCE.DAT` ресурс 20 присутствует: offset 22790, **240 байт** = 5 палитр × 16 цветов × 3 байта (6-битные каналы, как res10). **Что делать (когда дойдём до вида уровня 3).** 1. Достать ресурс 20 из `MSDOS/PRINCE.DAT` (упаковщику придётся читать сам `.DAT` — сейчас все скрипты берут распакованные PNG из SDLPoP); 2. сгенерировать таблицу палитр рядом с `pop_guard_pal.h`; 3. при загрузке уровня заливать слоты **0x50..0x5F** (env) и **0x60..0x6F** (wall) — у нас ровно эти базы (`pop_pack_bg.load_indexed`: `pal_base = 0x60` для WALL, `0x50` для env), то есть совпадение со `set_pal_arr` один в один; 4. `wall_pal = env_pal + 0x30 * tbl_level_type[level]` — для подземелья (`level_type == 0`) обе палитры одинаковые. **Грабли, уже пойманные на цвете стражей:** `gfx_pal_load` отдаёт указатель в BIOS (`$A4` через `rst #0x08`), а BIOS читает только `#4000-#BFFF` — таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)). ### DRAW-CHAR. Отрисовка — ОДНА на всех Char, как физика после GUARD-PHYS **Почему заведено (пользователь, 2026-08-07).** После GUARD-PHYS физика стала общей над `Char`, а ОТРИСОВКА так и осталась продублированной: `kid_draw` и `pop_guard_draw` считают `load_frame_to_obj`, флип и heal-прямоугольники каждая по-своему, окклюзия — двумя проходами (`pop_fore_over_kid` / `pop_fore_over_char`). Общее вынесено лишь частично (`char_footprint`, `other_overlay_tile`, `pop_sword_draw`), а СПИСКИ ВЫЗОВОВ разные. **В оригинале она общая — проверено.** `seg008` держит два входа с ИДЕНТИЧНЫМ телом: ``` add_kid_to_objtable (seg008:22F0) add_guard_to_objtable (seg008:2324) loadkid() loadshad() load_fram_det_col() load_fram_det_col() load_frame_to_obj() load_frame_to_obj() stuck_lower() stuck_lower() set_char_collision() set_char_collision() set_objtile_at_char() set_objtile_at_char() redraw_at_char() redraw_at_char() redraw_at_char2() redraw_at_char2() clip_char() clip_char() add_objtable(0) add_objtable(1 тень / 2 страж) ``` Различаются ТОЛЬКО окно (`Kid`/`Guard`), тип объекта и спецкейс тени у зеркала (уровень 4). То есть та же схема, что `play_kid_frame` / `play_guard_frame` у физики. **Цена дублирования уже видна.** Скелет, падающий в пропасть (ур. 3), рисовался ПОВЕРХ верхней грани пола соседней колонки: проход `other_overlay_tile` (порядок midtable — «тайлы позже персонажа ложатся поверх него») вызывался только у Кида. Симптом закрыт 2026-08-07 переносом прохода в `pop_fore_over_char`, но корень — именно дублирование. Раньше это не всплывало потому, что ОБЫЧНЫЙ страж в пропасть не падает: его физика гейтится `x ∈ [44,211)`, а провалившегося убирает `check_guard_fallout`. Тем же путём пойдут тень (ур. 4/5/6/12), визирь (13), толстяк (12) — расхождение будет повторяться на каждом. **Что сделать:** свести к одному набору функций над `Char`, оставив параметрами ровно то, что различается в оригинале: - окно (`loadkid`/`loadshad` — уже есть); - набор атласов и таблица кадров (`kid*` / `GUARD` / `SKEL` — уже выбирается в `pop_guard_load`); - слот heal-прямоугольников (у каждого персонажа свой, по страницам дабл-буфера); - тип объекта для порядка (Kid / тень / страж). **Не портированы вовсе и относятся сюда же:** `stuck_lower`, `redraw_at_char2`, `clip_char` (см. memory `pop_clip_char_todo`). **Чем подтверждать:** падение скелета в пропасть с разных X (сегодняшний сценарий), бой стража на ур. 1-2 без регрессий, `tests-host` зелёные. ### L3-PASS. Приёмка уровня 3 — обход всех комнат Как L1-PASS/L2-PASS: сквозной проход руками плюс обход комнат читом ROOMNAV. Осмысленна ТОЛЬКО после L3-CHOMP и L3-SKEL — без них уровень заведомо неполон, и половина наблюдений будет «механики нет». Что уже снято и пригодится (справка ниже): комнаты **23 и 24 недостижимы** и полностью пусты — баги в них не в приоритете; **чекпойнт** уровня 3 сделан ([L3-CHKP](TASKS_CLOSED.md#l3-chkp)), его тоже надо потрогать вживую: уйти влево из комнаты 7, умереть, проверить респавн в комнате 2 и снятую loose-плиту (7, кол 4, ряд 0). ### Справка: связность комнат уровней 1–3 (снято 2026-08-05) Обход графа `roomlinks` (@1952, по 4 байта на комнату: L, R, U, D; 0 = нет соседа) от стартовой комнаты — тем же методом, которым на уровне 1 нашлись 13/18/24 (см. «НЕ БАГИ» в [`bug_closed.md`](bug_closed.md)). Скрипт разовый, в репозиторий не клался: чтение трёх массивов, BFS и проверка симметрии. | уровень | старт | недостижимы | признак | |---------|-------|-------------|---------| | 1 | к.1 (0,0) | **13, 18, 24** | ссылки наружу есть, обратных нет | | 2 | к.5 (1,3) | **нет** | граф полностью симметричен, все 24 достижимы | | 3 | к.9 (2,4) | **23, 24** | то же, что на 1: односторонние ссылки, обе комнаты **полностью пустые** | ``` ур.3: 23 L→4, у 4 R=22 | 24 L→2, у 2 R=7 23 R→22, у 22 L=4 | 24 R→7, у 7 L=2 | 24 U→16, у 16 D=0 на 23 ссылается только 24, на 24 — только 23; тайлы обеих = все empty ``` То есть на уровне 3 это даже более чистый случай, чем на уровне 1: там в брошенных комнатах была геометрия, здесь — пустота. Практический вывод тот же: **в 23/24 возможен «мусор в шве»** (наш рендер кромки читает крайнюю колонку соседа ПО ССЫЛКЕ, а сосед соседом себя не считает), приоритет багов там низкий, в игре их не видно. **Уровень 2 — недостижимых нет, но есть три КОЛОДЦА без выхода** (единственная связь — вверх, откуда Кид падает): ``` к.10 U→4 пики(2,2) + пол — падение из комнаты 4 к.14 U→21 шахта 2 тайла шириной, дно = обломки к.17 U→15 то же ``` Это не баги данных: в 14/17 попадают только падением насмерть, а из 10 (если выжил) выхода нет вовсе — так в оригинале. При приёмке не считать «застрял» багом. --- ## P1 — берётся в любой момент ### L1-SPEED. Игра идёт быстрее оригинала (найдено 2026-08-01) Сверка таймингов: оригинал — `BASE_FPS = 60` при `base_speed = 5` тиков на логический кадр (`SDLPoP/src/types.h:1373`, `data.h:869`) = **83.3 мс**, в бою `fight_speed = 6` = **100 мс**. У нас `roomtest.c` ждёт **три** `gfx_wait_vsync()` = 60 мс, и отдельной скорости боя нет — то есть примерно **+39 % к скорости эталона**. Соответствие: 4 ожидания (80 мс) обычно, 5 (100 мс) в бою. Условие «делать ПОСЛЕ CLIP-1» снято — CLIP-1 закрыт. Проверка — секундомером по одинаковому отрезку рядом с живым SDLPoP, не «на глаз». ### TUNE-1. Параметры движка — в конфиг, а не в код **Что уже есть.** `pop_tune.h` — все настраиваемые числа собраны в одном заголовке: чекпойнт уровня 3 (`POP_CHKP_*`), отладочное окно решётки (`POP_DBG_GATE_HOLD`), включатель зацепа в прыжке (`POP_ENABLE_JUMP_GRAB`). Правило уже действует: новое «особое событие» или тайминг заводится ТАМ, а не константой по месту. **Что нужно сделать.** Читать их из ФАЙЛА рядом с exe, чтобы менять без пересборки — под отладку («растянуть решётку, чтобы успеть пройти») и под моды. Формат: простой ini/`ключ=значение`, парсер на ~50 строк (числа, комментарии `;`, неизвестные ключи игнорировать), файл необязателен — нет файла, значит зашитые дефолты. Секции по смыслу: `[level]`, `[debug]`, `[enhancements]`. **Ориентир — SDLPoP.** У него это `custom_options_type` (types.h) + `SDLPoP.ini` + меню Settings/Mods; наши имена намеренно совпадают с его (`custom->имя`), чтобы сверка оставалась механической. Осмотр его меню и опций — часть задачи: у него уже разложены по группам стартовые HP/минуты, номера «особых» комнат и уровней (шадоу, скелет, зеркало, чекпойнт), тайминги ворот и пик, скорости, а отдельной группой — `fixes`/`enhancements` (включая `enable_jump_grab`, который мы уже портировали). Брать всё подряд не надо: переносим по мере того, как константа реально понадобилась в игре. **Оговорка по памяти.** Парсер и таблица параметров — холодный код, исполняется один раз при старте: кандидат в банк, а не в резидент W1. ### MEM. Следующий шаг разгрузки W1/W2 `pop_ctrl.c` уехал в банк 5 ([MEM-BANK5](TASKS_CLOSED.md#mem-bank5), куча 180 Б → 2298 Б; после снижения `--max-allocs` — 2751 Б). Следующий кандидат по тому же критерию (**не размер, а частота вызова и отсутствие горячих банк→банк переходов**) — **расщепление `pop_kid.c`**: холодная половина (загрузка страниц спрайтов, `pop_kid_load`) в банк, движок кадров (`load_frame`/`play_seq`, 2×/кадр) оставить в резиденте. Брать по факту нехватки места, не заранее. Таблица резидентного кода по модулям и разбор, почему `pop_level.c` в банк НЕЛЬЗЯ, — в [`TASKS_CLOSED.md`](TASKS_CLOSED.md#mem-bank5). --- ## Отложено осознанно (не брать, пока не появится причина) - **KBD-1, остаток** — «иногда при зажатом Shift стрелка всё-таки пропускается» (ощущение пользователя, не замер: счётчики на 35 нажатиях подряд потерь не показывают). Решение пользователя 2026-08-01: отложено до финальной полировки. Где именно осталась дыра и что делать, если вернёмся, — в [`TASKS_CLOSED.md`](TASKS_CLOSED.md#kbd-1) (там же весь протокол замеров и список того, что делать НЕЛЬЗЯ). - **Quickload (Shift+F9) и остальные читы SDLPoP** — оценка сделана ([DBG-CHEATS](TASKS_CLOSED.md#dbg-cheats)), код не написан. Самое ценное и самое дорогое: сериализация `Char` + `room_modif` всех комнат + trob'ов + стражей (`levels_plan.md` §4), зато даёт воспроизводимый регресс «вот кадр, где баг» вместо ручной подгонки позы. - **Звук** (CBL-эффекты, Фаза 5 `PORT_PLAN.md`) — геймплей не блокирует. - **Таймер уровня / HUD времени / меню / сохранения** — Фаза 6. - **[T-1](bug_list.md#t-1)** (пики: перерисовка по причине) — отдаётся почти бесплатно после [T-2](bug_list.md#t-2), отдельно не окупается. - **Отключение мыши на время игры** и **замена PRNG** — `../docs/ideas_backlog.md` (оба дают доли процента кадра). - **OPT-1** (хирургический редрой шва) — решено НЕ делать, стоимость транзиентная; разбор в [`bug_closed.md`](bug_closed.md).