# План: от одного уровня к нескольким (загрузка, переходы, тайлсеты)

Статус: план, 2026-08-01.  Продолжает `PORT_PLAN.md` §7 (Фаза 1: «переходы
между экранами» → теперь между УРОВНЯМИ).  Текущая точка: `roomtest` играет
уровень 1 целиком в одной комнате-за-комнатой модели, но уровень нельзя
ни выбрать, ни закончить.

Источник истины — `../SDLPoP/src/` (правило `../CLAUDE.md`).  Ключевые
места: `seg000.c: load_lev_spr/play_level_2/init_game`, `seg005.c:
up_pressed/go_up_leveldoor`, `seg006.c: play_seq → SEQ_END_LEVEL`,
`seg002.c` (спецсобытия уровней), `data.h:835..850` (потабличные различия
уровней).

---

## 0. Что уже готово (не проектировать заново)

- **Формат и загрузчик уровня.**  `pop_level.c/.h` читает сырой
  `res200N.bin` (2305 Б) в отдельную EMM-страницу; путь — параметр
  `pop_level_load(const char *)`.  Мультиуровневость здесь стоит одной
  функции формирования имени.
- **Стартовая позиция уровня** уже разобрана: `pop_level_start_room()`,
  `pop_level_start_pos()`, `pop_level_start_dir()` — реализованы и пока
  НЕ вызываются (см. `../roomtest/TASKS.md` L1-START).
- **Страж по данным уровня**: `pop_level_guard()` (порт `enter_guard`),
  сохранение состояния между комнатами (`pop_guard_state_save`).
- **Палитра разложена по слотам ровно как в оригинале** (`pop_pack_kid.py`
  `build_palette`): env 0x50, wall 0x60, pot 0x40, kid 0x70, меч 0x80,
  страж 0x90.  Это тот же раскрой, что `set_pal_arr(0x50/0x60)` в
  `seg000.c:1140..1148`, — значит смена тайлсета не требует переиндексации
  спрайтов Кида (см. §3).
- **Все 16 файлов уровней распакованы**: `../SDLPoP/data/LEVELS/res2000..
  res2015.bin` (0 — демо-уровень).

---

## 1. Что реально различается между уровнями (замер по данным, не по памяти)

Таблицы из `../SDLPoP/src/data.h:840..847` + инвентарь тайлов, снятый прямо
с `res200N.bin` (маска `fg & 0x1F`):

| Ур. | Тайлсет | Страж | Новое против предыдущих |
|-----|---------|-------|--------------------------|
| 1 | dungeon | обычный | — (текущая база) |
| **2** | **dungeon** | **обычный** | **ничего нового: тот же набор объектов минус меч** |
| 3 | dungeon | СКЕЛЕТ | чомперы |
| 4 | palace | обычный | **тайлсет palace**, зеркало (спецсобытие `mirror_level`) |
| 5 | palace | обычный | — |
| 6 | palace | ТОЛСТЫЙ | падение на входе (спецсобытие) |
| 7 | dungeon | обычный | — |
| 8, 9 | dungeon | обычный | — |
| 10, 11 | palace | обычный | — |
| 12 | dungeon | ТЕНЬ | seamless-выход (комната 23), исчезающий меч |
| 13 | dungeon | ВИЗИРЬ | мышь, особый выход |
| 14 | palace | нет | — |
| 15 | dungeon | нет | финал |

Прямое следствие для порядка работ: **уровень 2 не требует ни одного нового
ассета и ни одной новой механики** — он проверяет ровно машинерию перехода.
Это и есть первый шаг.

Прочие потабличные различия, которые придётся завести массивами по 16:
`tbl_level_type` (тайлсет), `tbl_guard_type` (−1 = стражей нет),
`tbl_guard_hp`, `tbl_level_color` (вариантные палитры, 1.3), `tbl_entry_pose`.

---

## 2. Шаг 1 — машинерия перехода (цель: уровни 1 → 2 → 3) — **СДЕЛАН 2026-08-04**

> **Итог.**  Всё в этом разделе портировано и проверено в MAME: уровень 1 →
> Shift+L → уровень 2 (комната 5, дверь захлопывается за спиной, большие
> колонны рисуются) → уровень 3.  Разбор что именно сделано и что по
> уровню 2 осталось — `../roomtest/TASKS.md`, запись **L2**.
>
> Сверх плана пришлось доделать две вещи, без которых уровень 2 не играется:
> **`find_start_level_door`** (стартовый тайл уровня 2 — это правая половина
> двери уровня) и **большую склянку** `add_life` (тип зелья 2, комната 20).

Порядок именно такой; каждый пункт проверяем в MAME отдельно.

**2.1 Выход с уровня.**  Портировать `up_pressed()` ветку двери
(`seg005.c:410..423`) + `go_up_leveldoor()` (`seg005.c:497`): дверь рядом
(при/за/перед персонажем) И `drawn_room != level.start_room` И створка
открыта полностью (`curr_room_modif >= 42` — вариант `fix_exit_door`) →
`Char.x = x_bump[...] + 10`, направление влево, `seq_70_go_up_on_level_door`.
Затем оживить опкод `0xF1 END_LEVEL` в `play_seq` (`../roomtest/pop_kid.c:418`
— сейчас пустой `break`): `++pop_next_level`, как `seg006.c:662`.

**2.2 Цикл уровня.**  В `main()` после тика: `if (pop_next_level !=
pop_current_level) → load_level(pop_next_level)`.  Порядок сноса/подъёма
состояния (порт `load_lev_spr` + `play_level_2`):
`pop_level_free` → `pop_level_load("LEVELS\\res200%d.bin")` →
`pop_trob_reset` → `pop_guard_reset` → сброс tile-override'ов
(`ovr_*` в `roomtest.c`) → `enter_room(pop_level_start_room())` →
`kid_init(поза/позиция/направление из данных уровня)`.
**HP через уровень переносится** (в оригинале `hitp_beg_lev`), не сбрасывать
в максимум — сверить с `seg000.c` `init_game`/`play_level_2`.

**2.3 Стражи по уровню.**  Завести `tbl_guard_type[16]`/`tbl_guard_hp[16]`;
`-1` = стражей на уровне нет (уровни 14, 15) — `pop_guard_enter` обязан это
понимать, иначе на 14-м полезут стражи из мусора.  Для шага 1 (уровни 2, 3)
достаточно обычного стража, но проверку `-1` заложить сразу.

**2.4 Чит «следующий уровень» (Shift+L).**  Реализуется ровно тем же
`pop_next_level` — и без него отладка уровней превращается в прохождение
игры руками.  Делать в этом же шаге, не позже (см. §4).

**Приёмка шага 1:** дверь уровня 1 → уровень 2 играется целиком → его дверь
→ уровень 3 стартует (чомперы могут быть ещё не портированы — тогда
фиксируем как известное ограничение, а не «баг»).

---

## 3. Шаг 2 — второй тайлсет (palace, уровни 4+)

**Ассеты.**  `toolchain/pop_pack_bg.py` уже читает PNG каскадом
VDUNGEON→VPALACE (та же логика, что в игре), но печёт ОДИН набор атласов
(`pop_env0..4.atl`, `pop_wall.atl`, `pop_fore.atl` ≈ 75 КБ).  Нужен второй
набор из VPALACE (`pal_env*.atl` / `pal_wall.atl`), плюс `torch_debris` —
тайл, который встречается только на palace-уровнях.  По EMM это ещё ~6
страниц при бюджете ~3.3 МБ — не проблема.

**Палитра — главный технический вопрос, и он уже решён раскроем.**
Тайлсет живёт в слотах `0x50..0x5F` (env) и `0x60..0x6F` (wall); Кид, меч,
страж, склянки — в других слотах.  Значит смена тайлсета = перезапись 32
записей палитры (`gfx_pal_set` на обе страницы, как `flash_bg` в
`roomtest.c`), а НЕ перезагрузка `kid.pal` и не переиндексация спрайтов.
Сделать `pal_dungeon.bin` / `pal_palace.bin` (по 32 записи) и грузить при
смене типа уровня.  Проверить артефактом: скриншот palace-комнаты против
рендера `render_room.py` для того же уровня.

**Вариантные цвета уровней** (`tbl_level_color`, `level_var_palettes` — это
уже 1.3, в 1.0 их нет): по той же механике, тот же диапазон слотов.  Решение
на будущее — сначала базовые два тайлсета, потом при желании цвета.

**Выбор набора в коде.**  `pop_bg_load()` сейчас грузит фиксированные имена;
превратить в `pop_bg_load(type)` с двумя таблицами имён + выгрузка старых
атласов при смене типа (`atlas_free`).  Переключение — только на границе
уровня, не в кадре.

---

## 4. Читы SDLPoP: что взять на следующем этапе

Из `../SDLPoP/README.md` (раздел Cheats).  У нас уже есть: **K** — убить
стража, **I** — бессмертие (наш, в оригинале нет), **S** — выдать меч (наш),
**+/−** — обход комнат (`ROOMNAV`, наш).

**Брать сразу вместе с переходами уровней** (без них отладка дороже самой
работы):

| Чит | Что даёт | Цена |
|-----|----------|------|
| **Shift+L — следующий уровень** | единственный вменяемый способ тестировать уровни 2..15 | тривиально: `++pop_next_level` из §2.2 |
| **R — воскресить Кида** | у нас респавн по ↑ + таймаут; порт `resurrect` ближе к оригиналу и не мешает управлению | низкая |
| **Shift+S / Shift+T — +1 HP / +максимум** | отладка боёвки без «ровно трёх попыток»; честная замена нашему читу бессмертия | низкая, HP-машинерия уже есть |
| **[ и ] — сдвинуть Кида на пиксель** (debug-чит SDLPoP) | прямо бьёт в наш класс багов «окклюзия/шов на один пиксель» — воспроизведение позы без ловли момента | тривиально |

**Брать во вторую очередь:**

| Чит | Почему позже |
|-----|--------------|
| **H / J / U / N + Ctrl+B — смотреть соседние комнаты** | требует честной модели `drawn_room ≠ Kid.room` (наш S3-straddle, каркас есть: `update_kid_render_dx`).  Зато потом заменяет самодельный `ROOMNAV` и попутно закрывает straddle-задачу |
| **Shift+W — медленное падение (feather)** | ветка `JMP_IF_FEATHER` (опкод `0xF7`) в `play_seq` уже есть, но не проверена ничем — чит станет её единственным тестом |
| **C / Shift+C — номера комнат** | у нас номер рисуется палочками именно потому, что текст тянет 2 КБ знакогенератора в W2 (`roomtest.c`).  Ждёт своего шрифта |

**Не брать:** `Shift+I` (переворот экрана), `Shift+B` (blind mode) —
развлекательные, к отладке порта отношения не имеют.  `−/+` (время) — нужен
таймер уровня, которого у нас нет (Фаза 6).

**Отдельно, дорого, но очень ценно — `F6`/`F9` (quicksave/quickload точного
состояния).**  Это сериализация `Char` + `room_modif` всех комнат + trob'ов +
состояния стражей.  Даёт то, чего нам сейчас сильно не хватает:
воспроизводимый регресс в MAME («вот кадр, где баг») вместо ручного подхода
к позиции.  Кандидат сразу после того, как заработают уровни.

---

## 5. Риски и что проверить артефактом до кодинга

1. **Размер кода.**  Замер сборки 2026-08-01: `_CODE` 25 119 Б,
   куча ~2.4 КБ, банк 2 (`pop_bg`) 13 792 / 16 384, банк 3 (`pop_map`)
   6 331, банк 1 (`guards`) 1 896, банк 4 (`pop_gdraw`) 2 236.  Чомперы,
   зеркало, скелет и второй тайлсет пойдут в банк 2 — там осталось 2.6 КБ.
   **Прежде чем начинать §3, посчитать, куда лягут новые тайлы**, иначе
   повторится история «банк 2 упёрся в потолок» (коммит 2f3e854).  Свободные
   номера банков есть (5+), гранулярность — файл.
2. **Спецсобытия уровней** (`seg002.c`: `level3_set_chkp`, `sword_disappears`,
   `Jaffar_exit`, зеркало, мышь) — их НЕ надо портировать заранее.  Для
   уровней 2 и 3 нужен только чекпойнт уровня 3.  Остальное — по мере
   подхода к уровню.
3. **Чомперы** (уровень 3 и почти все дальше) — отдельная механика
   (`animate_chomper` + коллизия + смерть); шаблон работы тот же, что у
   пик/ворот, см. `gates_spikes_plan.md`.
4. **`tbl_guard_type = -1`** на уровнях 14/15: без проверки страж
   «появится» из неинициализированных данных.
5. **Уровень 0 (демо)** существует в данных, но в скоуп не входит.
