Доски приведены в соответствие с кодом; bug_list/bug_closed → BUGS_OPEN/BUGS_CLOSED

Доска отставала от кода на три задачи — планировать по ней было нельзя.
Сверка проведена грепом по исходникам, а не по записям:

- L4-MIRROR ЗАКРЫТА: шаги 1-5 сделаны и проверены пользователем в MAME
  (зеркало в атласе, постановка тайла, отражение, прыжок сквозь зеркало с
  рождением тени, левый клип тени).  Протокол с разбором решений — в архиве;
- L3-CHOMP и L3-SKEL закрыты ещё 2026-08-08/07 (коммиты dc0bd47, 4d4323f,
  db4106a, 1461ed5), на доске значились как предстоящие;
- тайлсет palace (шаг 2 levels_plan) в коде есть целиком — pop_bg_load(type),
  pal_*.atl, дворцовый wall_pattern, решётки 25-29 и в tile_table, и в
  коллизии (tile_is_floor совпадает с seg006:0628);
- в GUARD-PHYS остаток пересобран по факту: check_chomped_guard сделан,
  скелет в check_guard_fallout сделан, ветки ТЕНИ нет — она уехала в L5-SHADOW.

Приёмки: по решению пользователя уровни 1-4 приняты SMOKE-тестами, полные
обходы всех комнат делаются по готовности ВСЕХ уровней — L3-PASS/L4-PASS как
отдельные задачи отменены, вместо них политика приёмок в архиве.

Новая цель — уровень 5.  Инвентарь res2005.bin: НИ ОДНОГО нового тайла, всё
портировано на уровнях 1-4.  Единственная новая механика — спецсобытие «тень
крадёт зелье» (комната 24): заведена задача L5-SHADOW с портом по SDLPoP
(check_shadow / do_init_shad / do_auto_moves + shad_drink_move /
autocontrol_shadow_level5 + ветка тени в check_guard_fallout), включая
готовые константы и то, что у нас уже есть под это.

Заведён MIRROR-FG-STALE (низкий): place_mirror пишет тайл в данные уровня, но
не в снимок room_fg, по которому работает коллизия — если зеркало поставлено,
пока игрок В комнате 4, оно невидимо для коллизии (тень не родится).  В
обычном прохождении недостижимо: дверь выхода в другой комнате.  Записан
точный сценарий воспроизведения читом ROOMNAV и фикс на несколько строк.

Правило «в _OPEN только незакрытое» теперь выполняется буквально:
- bug_list.md → BUGS_OPEN.md, bug_closed.md → BUGS_CLOSED.md (ссылки
  обновлены во всех документах и в комментарии pop_trob.c);
- из TASKS_OPEN убраны блоки закрытых задач (L3-CHOMP, L3-SKEL, L3-PASS,
  L4-MIRROR, DRAW-CHAR), справка по связности комнат уехала в архив;
- из BUGS_OPEN убраны 8 строк таблицы закрытых багов, закрытый T-2 (уехал в
  BUGS_CLOSED) и раздел «уровень 3 — неначатые задачи» (обе записи закрыты);
  сводная таблица пересобрана по реально открытым записям.

Все внутренние ссылки проверены скриптом: битых якорей 0.  make size-check
OK (65 программ), tests-host 5/5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 16:10:10 +03:00
parent c0075c7754
commit 5348feb5f4
20 changed files with 488 additions and 654 deletions
+112 -292
View File
@@ -1,30 +1,33 @@
# roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-08)
# roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-11)
Что берём в работу сейчас и в каком порядке. Каждая запись: что сделать,
почему именно сейчас, чем подтверждать результат.
- закрытые задачи с протоколами и замерами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md);
- открытые баги — [`bug_list.md`](bug_list.md), закрытые с разбором корней —
[`bug_closed.md`](bug_closed.md);
- открытые баги — [`BUGS_OPEN.md`](BUGS_OPEN.md), закрытые с разбором корней —
[`BUGS_CLOSED.md`](BUGS_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_closed.md`](bug_closed.md)); открытым остался чит `+`/`` в бою.
Уровень 3: **скелет сделан и проверен в MAME 2026-08-07** (бой, падение в
пропасть, окклюзия), чомперов ещё нет.
**Состояние на 2026-08-11 (сверено с кодом, не только с доской):**
**Разгрузка банка 2 сделана 2026-08-08** ([MEM-BANK2](TASKS_CLOSED.md#mem-bank2)): 90.4 % →
**35.8 %, свободно 10 512 Б** — холодная половина уехала в банк 7
(`pop_room.c`), чомперам места с запасом.
**DRAW-CHAR сделана и ПРОВЕРЕНА 2026-08-08** (протокол и замеры —
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char)): отрисовка теперь одна на
всех `Char`. Банку 2 она дала всего +265 Б — место под чомперов дала уже
[MEM-BANK2](TASKS_CLOSED.md#mem-bank2).
- **уровни 1-4 приняты smoke-тестами** (пользователь). Полные обходы всех
комнат делаются по готовности ВСЕХ уровней — политика приёмок в
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#pass-policy); отдельных задач
`L3-PASS`/`L4-PASS` больше нет. Уже сделанные полные обходы уровней 1 и 2
остаются регресс-базой;
- **[L4-MIRROR](TASKS_CLOSED.md#l4-mirror) закрыта**: зеркало, отражение,
прыжок сквозь него и рождение тени проверены в MAME. Хвосты — вид тени
(отложен, [`../docs/shadow_render.md`](../docs/shadow_render.md)) и
[MIRROR-FG-STALE](BUGS_OPEN.md#mirror-fg-stale);
- **[L3-CHOMP](TASKS_CLOSED.md#l3-chomp) и [L3-SKEL](TASKS_CLOSED.md#l3-skel)
закрыты** (2026-08-08 / 2026-08-07);
- **тайлсет palace сделан** — `pop_bg_load(type)`, `pal_*.atl`, дворцовая
кладка `wall_pattern`, решётчатые тайлы 25-29 и в `tile_table`, и в
коллизии (`tile_is_floor` совпадает с seg006:0628). То есть шаг 2
`levels_plan.md` закрыт;
- **libbgi:** блочные AND/OR/XOR/NOT акселератора (2026-08-11, `tests/accop`)
— задел под вид тени и под любые эффекты «поверх того, что уже нарисовано».
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
@@ -32,41 +35,40 @@
---
## ТЕКУЩАЯ ЦЕЛЬ: уровни 1–3 (подземелье) отлажены целиком
## ТЕКУЩАЯ ЦЕЛЬ: уровень 5
Решение 2026-08-04: **palace (уровни 4+) откладываем**, доводим до
играбельности три dungeon-уровня. Основание — они не требуют ни одного
нового ассета фона: инвентарь тайлов, снятый с `res200N.bin`, показывает,
что новое появляется только так —
Уровни 1-4 играются (smoke). Дальше идём по порядку уровней; уровень 5 —
следующий.
**Хорошая новость по ассетам: уровень 5 не приносит НИ ОДНОГО нового тайла.**
Инвентарь, снятый перебором `res2005.bin` (fg & 0x1F):
```
ур. 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 ← отложено
ур. 5: empty, floor, spike, pillar, gate, closer, doortop_with_floor(7),
bigpillar_bottom(8), bigpillar_top(9), potion, loose, doortop(12),
debris, opener, level_door L/R, chomper(18), torch, wall,
lattice_pillar(25)…lattice_right(29)
```
— всё это уже встречалось на уровнях 1-4 и портировано. Единственное новое
на уровне 5 — **спецсобытие «тень крадёт зелье»**.
| # | Задача | Что | Блокирует |
|---|--------|-----|-----------|
| 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME |
| | [DRAW-CHAR](TASKS_CLOSED.md#draw-char) | отрисовка одна на всех `Char`**закрыта и проверена 2026-08-08** | — |
| — | [MEM-BANK2](TASKS_CLOSED.md#mem-bank2) | разгрузка банка 2 — **закрыта 2026-08-08**: 90.4 % → 35.8 %, свободно 10 512 Б | — |
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
| 4 | [L4-MIRROR](#l4-mirror) | **АКТИВНА с 2026-08-10 по решению пользователя**: зеркало уровня 4 + тень | прохождение ур. 4 |
| — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3** (игра быстрее на треть). Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях |
| 1 | [L5-SHADOW](#l5-shadow) | **СЛЕДУЮЩАЯ**: тень уровня 5 (крадёт зелье в комнате 24) + движок автодвижений | прохождение ур. 5 |
| 2 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида — ядро сделано, остаток: ветка ТЕНИ в `check_guard_fallout` и живая проверка кнопки под стражем | ур. 5+ (тень) |
| — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3**. Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях |
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
| — | [MEM](#mem-next) | следующий шаг разгрузки W1/W2 | берётся по факту нехватки места |
Сделанное по этой цели — в [`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.
Сделанное — в [`TASKS_CLOSED.md`](TASKS_CLOSED.md): L4-MIRROR, L3-CHOMP,
L3-SKEL, L3-CHKP, L2 (машинерия уровней), L2-PASS, L1-PASS, DRAW-CHAR,
MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
---
## Ждёт ФИНАЛЬНОЙ приёмки уровней 13
## Ждёт ФИНАЛЬНОЙ приёмки (полные обходы по готовности всех уровней)
Сюда попадает то, что уже работает в проверочном прогоне, но должно быть
подтверждено на сквозных прогонах уровней — потому что задевает механику
@@ -87,6 +89,63 @@
## P0 — делаем сейчас
### <a id="l5-shadow"></a>L5-SHADOW. Тень уровня 5: крадёт зелье — **СЛЕДУЮЩАЯ**
Единственная новая механика уровня 5 (тайлов новых нет вовсе, см. цель выше).
Тень появляется в комнате 24, дожидается, пока откроется дверь, идёт к зелью,
**выпивает его** и уходит за левый край. Боя нет.
**Как это в оригинале** (всё сверено по коду, `custom->*` — это дефолты 1.0):
| что | где | суть |
|---|---|---|
| появление | `check_shadow`, seg002:0064 | при СМЕНЕ КОМНАТЫ: если `current_level == 5` и `drawn_room == 24`, и тайл (кол 3, ряд 0) всё ещё `tiles_10_potion` — породить тень |
| порождение | `do_init_shad`, seg002:0000 | `memcpy(&Char, init_shad_5, 7)` + `seqtbl_offset_char(2 /*stand*/)`, `charid = charid_1_shadow`, `demo_time = 0`, `guard_skill = 3`, `guardhp_* = 4`, `saveshad()` |
| данные | `init_shad_5` | `{0x0F, 0x37, 0x37, 0, 0xFF, 0, 0}` = frame 15, x 55, y 55, direction 0, curr_col 1, curr_row 0, action 0 |
| поведение | `autocontrol_shadow_level5`, seg002:1157 | в комнате 24: пока `demo_time == 0` — ждать, пока дверь (кол 1, ряд 0) не откроется (`modif >= 80`), затем `demo_index = 0`; дальше каждый кадр `do_auto_moves(shad_drink_move)`; при `Char.x < 15``clear_char()` |
| движения | `do_auto_moves`, seg002:1089 | крошечный интерпретатор: `demo_time++`, по таблице `{time, move}` выбирается запись, `move` = 0 nothing / 1 forward / 2 backward / 3 up / 4 down / 5 up+forward / 6 shift / 7 move_7; 1 = ничего, −2 = конец |
| таблица | `shad_drink_move` (data.h:866) | `{0x00,0} {0x01,1} {0x0E,0} {0x12,6} {0x1D,7} {0x2D,2} {0x31,1} {0xFF,2}` — 8 записей по 2 байта |
**Что из этого у нас уже есть:**
- **механизм «спецсобытие порождает персонажа в слоте соперника»** —
`pop_check_skel` (`guards.c`, зовётся из `roomtest.c` в тике); тень
уровня 5 садится на тот же шов, только условие другое;
- **тень как `charid_1_shadow`** — заведена под уровень 4
([L4-MIRROR](TASKS_CLOSED.md#l4-mirror)): своя ветка ИИ
(`autocontrol_shadow` + `autocontrol_shadow_level4`), выбор таблицы кадров
Кида (`pop_frame_tbl_is_guard`), отрисовка спрайтами Кида;
- **питьё зелья** — `SEQ_78_DRINK` и `get_item` в `pop_ctrl.c` (Кид уже
умеет); тень «нажимает» те же кнопки через автодвижения;
- **зелья как trob** — фаза пузырька, тип в старших битах (`pop_trob.c`).
**Что писать:**
1. `do_auto_moves` + таблица `shad_drink_move` + `demo_time`/`demo_index`
интерпретатор ~30 строк, кладётся рядом с `autocontrol_shadow` в
`guards.c` (банк 1). «Движения» — это те же переменные управления, что
заполняет `read_user_control` (`pop_ctrl.c`), так что `move_*` сводятся к
присваиваниям.
2. `do_init_shad(init_shad_5, seq stand)` — общий порождатель тени; пригодится
и на уровнях 6 и 12 (`init_shad_6`, `init_shad_12` — те же 7 байт).
3. Ветка `check_shadow` для уровня 5 — по образцу `pop_check_skel`, вызов из
того же места тика.
4. `autocontrol_shadow_level5`.
5. **Ветка ТЕНИ в `check_guard_fallout`** (seg002:0241): тень падает, только
если она в свободном полёте (`action == 4`), и тогда
`loadshad(); clear_char(); saveshad()`. Сейчас в `pop_guard_fallout`
(`pop_guard.c`) есть ветки стража и скелета, а тени нет — комментарий там
обещает её «вместе с L3-SKEL», но она относится именно к тени.
**Чем подтверждать:** smoke уровня 5 — дойти до комнаты 24, увидеть, как
тень выходит после открытия двери, выпивает зелье (тайл зелья исчезает) и
уходит влево. Сверять последовательность движений с живым SDLPoP на том же
месте — таблица `shad_drink_move` короткая, расхождение будет видно сразу.
**Оговорка по виду:** тень пока рисуется обычной копией спрайтов Кида, то
есть выглядит вторым Кидом — это [отложенный](../docs/shadow_render.md)
вопрос, к механике уровня 5 отношения не имеет.
### <a id="guard-phys"></a>GUARD-PHYS. Страж живёт по тем же правилам, что Кид — **ЯДРО СДЕЛАНО 2026-08-07**
> **Что уже работает** (решение пользователя: переносим физику на `Char`,
@@ -126,13 +185,17 @@
> 3 → 2, X перенесён через шов), ровно как в SDLPoP. Условия отбора
> покрыты тестами `t_char` (7 сценариев: пороги 91/165, «не бой», мёртвый,
> вверх/вниз, занятая соседняя комната). **Сцена вскрыла отдельный баг —
> [BUG-SWORD-GHOST-1](bug_list.md#bug-sword-ghost-1): при переходе в бою Кид
> [BUG-SWORD-GHOST-1](BUGS_CLOSED.md#bug-sword-ghost-1): при переходе в бою Кид
> прячет меч и дальше дерётся пустой рукой.**
>
> **Осталось (потому и запись открыта):**
> 1. `check_chomped_guard` — вместе с [L3-CHOMP](#l3-chomp);
> 2. ветки `check_guard_fallout` для тени и скелета (скелет возрождается в
> комнате 3) — вместе с [L3-SKEL](#l3-skel);
> **Осталось (потому и запись открыта) — ревизия 2026-08-11 по коду:**
> 1. ~~`check_chomped_guard`~~**сделан** вместе с
> [L3-CHOMP](TASKS_CLOSED.md#l3-chomp) (`pop_map.c`);
> 2. ветки `check_guard_fallout`: **скелет сделан** (возрождается в комнате 3,
> `pop_guard_fallout` в `pop_guard.c`), **ветки ТЕНИ нет** — падает только
> в свободном полёте, `loadshad/clear_char/saveshad`; идёт в
> [L5-SHADOW](#l5-shadow) п. 5 (комментарий в коде обещает её «вместе с
> L3-SKEL» — устарел, это про тень);
> 3. страж, нажимающий напольную кнопку, вживую не проверялся (код —
> общий `check_press`).
@@ -187,90 +250,6 @@
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
живой страж — переход не происходит.
### <a id="l3-chomp"></a>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 расчищено** ([MEM-BANK2](TASKS_CLOSED.md#mem-bank2), 2026-08-08):
35.8 %, **свободно 10 512 Б** (было 1074 Б) — холодная половина слоя фона
уехала в банк 7 (`pop_room.c`). Считать место ДО кодинга всё равно
обязательно (`levels_plan.md` §5.1); чомпер — это анимация тайла (холодная
перерисовка), поэтому его отрисовка ложится в `pop_room.c`, а состояние —
в `room_modif`, как у пик и ворот.
**Ассеты УЖЕ упакованы (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)); смерть Кида в сомкнутых челюстях.
### <a id="l3-skel"></a>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`, и оба изменения живут в одном упаковщике.
### <a id="l3-color"></a>L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный)
**Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В
@@ -315,7 +294,7 @@ offset 22790, **240 байт** = 5 палитр × 16 цветов × 3 байт
**Грабли, уже пойманные на цвете стражей:** `gfx_pal_load` отдаёт указатель
в BIOS (`$A4` через `rst #0x08`), а BIOS читает только `#4000-#BFFF` —
таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в
W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)).
W1/W2 (см. [BUG-GUARD-COLOR-1](BUGS_CLOSED.md#bug-guard-color-1)).
### <a id="draw-cost"></a>DRAW-COST. Кадр НЕ УКЛАДЫВАЕТСЯ в бюджет — нужна оптимизация
@@ -660,165 +639,6 @@ tp/10 у факелов таблицей, пустой слот соперник
(клинок/брызги), а не по всему окну; кэш «в этом тайле fore-слоя нет
вовсе»; считать окно клипа в тайловых координатах один раз.
### <a id="draw-char"></a>DRAW-CHAR — **ЗАКРЫТА И ПРОВЕРЕНА 2026-08-08**
Отрисовка сведена к одному набору функций над `Char` (`pop_cdraw.c`), проход
окклюзии — один на всех (`pop_fore_over_char`). Разбор, таблица «было →
стало», пять починенных расхождений и замеры — в
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char). Оттуда же список того, что
надо потрогать вживую при приёмке уровней.
### <a id="l3-pass"></a>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).
### <a id="rooms-graph"></a>Справка: связность комнат уровней 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
(если выжил) выхода нет вовсе — так в оригинале. При приёмке не считать
«застрял» багом.
---
### <a id="l4-mirror"></a>L4-MIRROR. Зеркало уровня 4 и тень — **АКТИВНА с 2026-08-10**
Приоритет переставлен пользователем: palace-уровни были отложены решением
2026-08-04, но уровень 4 уже гоняется в MAME, и зеркало — единственное, что
мешает пройти его сюжетно.
**Как это работает в оригинале** (SDLPoP, всё сверено по коду):
| что | где | суть |
|---|---|---|
| постановка тайла | `animate_leveldoor`, seg007:0457 | дверь выхода ДОРИСОВАЛА открытие (`modif >= 43`, `leveldoor_open` 0/2 -> 1) -> в комнату 4, колонку 4, ряд 0 пишется `tiles_13_mirror` |
| отражение | `check_mirror`, seg003:0798 | пока Кид на тайле зеркала — каждый кадр рисуется зеркальная копия: `Char.x = (xpos<<1) - Char.x`, `direction` инвертируется, копия идёт в objtable типом 4 с клипом `left = (curr_col<<5)+9`, `top = y_clip[row+1]` |
| прыжок сквозь | seg004:0240 | засчитывается ТОЛЬКО `run-jump` СПРАВА НАЛЕВО (`frame 39..43`, `direction < 0`) -> `modif = 0x56` (разбитое), `jumped_through_mirror = -1` |
| рождение тени | `jump_through_mirror`, seg003:080A | Кид копируется в слот Guard как `charid_1_shadow`; `guardhp = hitp_max`, у Кида `hitp_curr = 1` |
| поведение тени | `autocontrol_shadow_level4`, seg002:1131 | в зеркальной комнате при `x < 80` исчезает (`clear_char`), иначе бежит вперёд. Боя нет |
| клип тени | seg008:1699 | `obj_clip_left = 137 + (mirror_column-4)*32` — тень видна только СПРАВА от зеркала |
| коллизия | `wall_type`, seg006:1632 | зеркало = 2 «стена слева» — **уже портировано** (`pop_map.c`), как и спецкейс `can_climb_up` |
**Шаги.**
1. **АТЛАС — СДЕЛАНО 2026-08-10.** `tile_table[0x0D]` = база 75, фронт 77
(наша таблица совпадает с SDLPoP такт в такт). Тайла 13 **нет ни в одном
уровне статически** — проверено перебором всех 15 `res200N.bin`, ноль
попаданий; санити-проверка разбора: ур.1 без чомпера, ур.3 с 18, ур.4 с
паласными 25..29. Значит `render_room` эти id не увидит и на месте
зеркала был бы чёрный провал (memory `pop_atlas_dynamic_ids`). Добавлены
`MIRROR_ENV_IDS = {75, 77}` в `pop_pack_bg.py`, 77 — ещё и в
`FORE_ENV_IDS`. Оба набора переупакованы: fore 17 -> 18 спрайтов, все
страницы EMM в пределах 16 КБ.
2. **ПОСТАНОВКА ТАЙЛА — СДЕЛАНО И ПРОВЕРЕНО ПОЛЬЗОВАТЕЛЕМ 2026-08-10.**
`place_mirror()` в `pop_trob.c`, вызывается из `animate_leveldoor` по
переходу `pop_leveldoor_open` 0/2 -> 1 (условие оригинала — иначе тайл
ставился бы заново каждый кадр открытой двери). Если комната зеркала уже
на экране, ставится `POP_RD_FLOOR` на обе страницы.
Зеркало появляется, спрайты из атласа читаются. Остался незакрытый
краевой вопрос: не устареет ли `g_fg` коллизии, если игрок окажется в
комнате 4 ровно в момент постановки (в обычном прохождении дверь в другой
комнате).
3. **ОТРАЖЕНИЕ.** Самый дорогой шаг: это ЧЕТВЁРТЫЙ рисуемый Char помимо
Кида и соперника, со своим клипом. Отрисовка у нас уже общая
(`pop_cdraw.c`, слоты — memory `pop_char_draw_unified`), вопрос в числе
слотов и в том, что отражение меняется вместе с Кидом каждый кадр, то
есть `pop_char_skip_mask` на нём не сработает. ЧИСТО КОСМЕТИКА —
делается последним.
4. **ПРЫЖОК СКВОЗЬ + РОЖДЕНИЕ ТЕНИ — СДЕЛАНО 2026-08-10, НЕ ПРОВЕРЕНО В MAME.**
- `is_obstacle` (`pop_map.c`): ветка зеркала (seg004:0239) — Кид, кадры
бегового прыжка 39..43, направление влево -> `modif = 0x56`,
`pop_jumped_mirror = -1`, препятствия нет.
- `mirror_image` / `jump_through_mirror` / `pop_check_mirror`
(`pop_map.c`, порт seg003:0798..08A9). Отражённый Char уходит в слот
Guard как `CHARID_1_SHADOW`; `guardhp = hitp_max`, у Кида `hitp_curr = 1`.
`savekid` НЕ делается — как в оригинале.
- `pop_check_mirror()` зовётся из главного цикла ПЕРЕД отрисовкой
персонажей (в оригинале — первая строка `draw_people`).
- `autocontrol_shadow` + `autocontrol_shadow_level4` + `clear_char`
(`guards.c`, seg002:081D/1131/seg006:1945). Тень идёт СВОЕЙ веткой
целиком: к стражьему ИИ она не сводится, на ур. 4 не дерётся вовсе.
- **АТЛАС ТЕНИ.** Вскрылось при чтении seg006:0532: тень вне боевых
кадров 150..189 ходит по таблице КИДА, и `image` оттуда индексирует
спрайты Кида, а не стража. Выбор атласа в `pop_cdraw` шёл по СЛОТУ —
тень рисовалась бы спрайтами стража. Условие вынесено в
`pop_frame_tbl_is_guard()` (`pop_kid.c`), его теперь читают и
`load_frame`, и отрисовка — разъехаться не могут.
- Звука (`sound_45_jump_through_mirror`) в порте нет, пропущен.
Цена: `_CODE` +18 Б, банк 1 2311 -> 2367, банк 3 10328 -> 10551,
банк 4 8243 -> 8267.
**Что проверить в MAME:** на ур. 4 открыть дверь выхода, дойти до
комнаты 4, разбежаться СПРАВА НАЛЕВО и прыгнуть в зеркало — должна
появиться тень (спрайтами Кида, зеркально) и убежать влево, растворившись
при `x < 80`. У Кида после этого 1 HP, у тени полная полоса.
5. **КЛИП ТЕНИ** (seg008:1699): `obj_clip_left = 137 + (mirror_column-4)*32`
— тень видна только СПРАВА от зеркала. **Не сделано и не однострочник:**
в `pop_cdraw` есть клип сверху, снизу и СПРАВА (`vis_w`), а левого нет
вовсе — `gfx_blit_cols_part_w` умеет ограничить ширину, но не пропустить
колонки слева. Нужен либо новый примитив, либо сдвиг `bx` со срезом
исходных колонок. Пока без него тень будет вылезать левее зеркала.
6. **ВИД ТЕНИ (два блиттера OR + XOR) — ОТЛОЖЕН 2026-08-11 по решению
пользователя.** Пока тень рисуется как обычный персонаж, простой копией
из атласов Кида — сюжетно уровень это не задерживает. Все изыскания
собраны в [`../docs/shadow_render.md`](../docs/shadow_render.md):
почему XOR несовместим с нашей прозрачностью `#FF` (нужен источник с
прозрачным `0x00`), почему два прохода оригинала не воспроизводятся в лоб
(операция читает ОЗУ-копию, а персонажи рисуются банком 0x5C) и во что
это выливается — однопроходный композит `s | bg ^ s(сдвиг)`. Там же
ЗАМЕР стоимости подготовки такого источника в рантайме (145.3 такта/байт:
0.11 с на страницу, 1.3 с на реальные 186 КБ атласа) и четыре варианта.
Блочные операции акселератора для этого уже есть в libbgi
(`gfx_blit_cols_op` и семейство, `tests/accop` 10/10 PASS).
Вернуться, когда будут сделаны все уровни и станет известно, какими
кадрами тень реально пользуется (ожидание: бег, длинный прыжок из
зеркала, питьё зелья, боёвка — тогда готовить надо единицы страниц).
Порядок: 1 -> 2 -> 4 -> 5 -> 3. После 4 и 5 уровень уже проходится, потому
что сюжетно достаточно прыгнуть сквозь зеркало.
---
## P1 — берётся в любой момент
### <a id="l1-speed"></a>L1-SPEED. Игра идёт быстрее оригинала (найдено 2026-08-01)
@@ -889,9 +709,9 @@ HP/минуты, номера «особых» комнат и уровней (
где баг» вместо ручной подгонки позы.
- **Звук** (CBL-эффекты, Фаза 5 `PORT_PLAN.md`) — геймплей не блокирует.
- **Таймер уровня / HUD времени / меню / сохранения** — Фаза 6.
- **[T-1](bug_list.md#t-1)** (пики: перерисовка по причине) — отдаётся почти
бесплатно после [T-2](bug_list.md#t-2), отдельно не окупается.
- **[T-1](BUGS_OPEN.md#t-1)** (пики: перерисовка по причине) — отдаётся почти
бесплатно после [T-2](BUGS_OPEN.md#t-2), отдельно не окупается.
- **Отключение мыши на время игры** и **замена PRNG**
`../docs/ideas_backlog.md` (оба дают доли процента кадра).
- **OPT-1** (хирургический редрой шва) — решено НЕ делать, стоимость
транзиентная; разбор в [`bug_closed.md`](bug_closed.md).
транзиентная; разбор в [`BUGS_CLOSED.md`](BUGS_CLOSED.md).