Files
Sprinter-SDCC/applications/PoP/docs/levels_12_15_plan.md
T
snark13 67d62e3e8b Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв
Уровень 12 (тень) — порт seg002/seg006:
- check_shadow: подъём тени в комнате 15 по условию «меч подобран»,
  init_shad_12, вход падением (seq 7);
- autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ;
- общий урон (ранил тень — ранил себя) и check_killed_shadow
  (убил тень — убил себя);
- таймер вспышки слияния 42 -> -1, sword_disappears при уходе из
  комнаты 18, появление плит в комнатах 2/13 после слияния.

Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом
попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт
сбросить HP.  Чит навигации по комнатам этот триггер придерживает —
иначе комнату 23 двенадцатого уровня не посмотреть в принципе.

Уровень 13 (Джафар): guard_notice_timer (фора после встречи),
on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три
исключения loose-механики.  Плюс спрайты визиря VIZIER.DAT — без них он
рисовался обычным стражем; заодно цвет из данных комнаты применяется
только к обычному стражу, как в оригинале.

Падающие плиты — семь дефектов, найденных прогонами:
- MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние
  терялись без щебня;
- одиночные сигналы посадки/провала больше не затираются в кадре;
- честный loose_fall: сбитая плита рождается в том же кадре, от места
  удара, с половинной скоростью;
- при снятии плиты метится и сосед справа (висел передний торец);
- heal и отрисовка кусков разнесены на два прохода;
- куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y
  (compare_curr_objs для пары 0x80 сортирует наоборот);
- wall_pattern убран из ceil_over_kid_tile: у оригинала узор из
  draw_tile_bottom идёт в фон, а не в передний слой.

Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44).
TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам.

Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами
тени при слиянии, чит навигации не запускает бесшовный переход, чит K
убивает через штатный путь смерти.

Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER,
GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:21:52 +03:00

463 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Что осталось на уровнях 12, 13, 14, 15 и 0
Статус: разбор по `../SDLPoP/src/`, 2026-08-13. Продолжает
[`levels_plan.md`](levels_plan.md) (машинерия уровней и тайлсеты — уже
сделаны). Здесь только СПЕЦСОБЫТИЯ, которых у нас ещё нет.
Правило проекта: источник истины — SDLPoP; все ссылки ниже даны на функцию и
строку, чтобы порт начинался с чтения, а не с гипотезы.
---
## 0. Что из этой области УЖЕ есть
Проверено грепом по `roomtest/`:
| механика | где у нас | статус |
|---|---|---|
| слот соперника, общий на всех Char | `pop_guard.c`, `pop_cdraw.c` | готово |
| `check_shadow` (спецвход тени) | `guards.c:310` | готово для уровней 4/5/6 |
| `do_init_shad` + таблицы `init_shad_5/6` | `guards.c:284` | готово |
| ИИ тени 4/5/6 | `guards.c:599/658/680` | готово |
| диспетчер `autocontrol_shadow` | `guards.c:711` | ветки 12 НЕТ |
| боёвка (удар/блок/парирование/HP соперника) | `guards.c`, `pop_ctrl.c` | готово |
| `flash_color` / `flash_time` | `pop_map.c`, `roomtest.c` | готово |
| `add_life` | `pop_map.c` | готово |
| таблицы уровней (`guard_type`, `guard_hp`, `entry_pose`, `level_type`) | `pop_level_cold.c:41..52` | готово, включая 12=SHADOW, 13=VIZIER |
| loose-полы, `make_loose_fall`, mob | `pop_map.c`, `pop_room.c` | готово (без спецкейсов ур. 13) |
То есть каркас есть весь; ниже — недостающие спецсобытия.
---
## 1. Уровень 12 — тень: встреча, бой, слияние
### 1.1 Подъём тени в комнате 15 (`check_shadow`, seg002:0070)
```c
if (current_level == 12) {
if (!united_with_shadow && drawn_room == 15) {
Char.room = drawn_room;
if (get_tile(15, 1, 0) == tiles_22_sword) return; // меч ещё лежит
shadow_initialized = 0;
do_init_shad(init_shad_12, 7 /* fall */);
return;
}
}
```
Отличия от наших веток 5/6: тень поднимается **в падении** (seq 7), условием
служит содержимое тайла (меч уже подобран) и флаг `united_with_shadow`.
Таблица `init_shad_12 = {0x0F, 0x51, 0xE8, 0, 0, 0, 0, 0}` — то есть
x=81, y=232, вправо, колонка 0, ряд 0.
**Что добавить:** ветку в `pop_check_shadow` + константу `init_shad_12` +
глобалы `united_with_shadow`, `shadow_initialized`.
### 1.2 ИИ тени (`autocontrol_shadow_level12`, seg002:1184)
Самая содержательная функция уровня. Три режима:
1. **Первый кадр в комнате 15**: пока Кид не подошёл (`Opp.x < 150`) —
`shadow_initialized = 1`; иначе тень ещё раз падает (`do_init_shad`).
2. **Кид с мечом** (`Char.sword >= sword_2_drawn`) → тень дерётся обычным
`autocontrol_guard_active` (у нас есть, `guards.c:535`). Особый случай:
если тень уже ранена (`offguard != 0 && guard_refrac != 0`) — она убирает
меч (`move_4_down`).
3. **Кид убрал меч** → тень тоже убирает и идёт навстречу; на дистанции
`< 10`**СЛИЯНИЕ**:
```c
flash_color = color_15_brightwhite; flash_time = 18;
add_life(); // +1 к максимуму HP
united_with_shadow = 42; // время вспышки Кид-тень
Char.charid = charid_0_kid; savekid(); // Кид ПЕРЕЕЗЖАЕТ на место тени
clear_char(); // тень со сцены
```
Плюс «если Кид бежит к тени — тень бежит к Киду» (кадры бега 3..14 и
шага 127..132).
**Что добавить:** `autocontrol_shadow_level12` в `guards.c` + ветку в
диспетчере `autocontrol_shadow` (там уже три ветки, будет четвёртая).
### 1.3 Общий урон (`do_delta_hp`, seg000:1518)
```c
if (Opp.charid == charid_1_shadow && current_level == 12 && guardhp_delta != 0)
hitp_delta = guardhp_delta; // ранил тень — ранил себя
```
Три строки, но без них бой с тенью теряет смысл. У нас `do_delta_hp`
портирован — добавить условие.
### 1.4 Таймер вспышки (`do_timers`, seg003:503)
```c
if (united_with_shadow > 0) {
--united_with_shadow;
if (united_with_shadow == 0) { --united_with_shadow; /* → -1 */ ... }
}
```
`united_with_shadow` живёт как счётчик, потом как «уже слились» (−1).
На него смотрят `check_shadow` и `check_can_guard_see_kid`.
### 1.5 Луч видимости (`check_can_guard_see_kid`, seg003:702)
```c
if ((Guard.charid != charid_1_shadow || current_level == 12) && ...
```
У нас (`guards.c:103`) условие про тень **нужно сверить**: на уровне 12 тень
ОБЯЗАНА быть видимой (иначе Кид не достанет меч и бой не начнётся).
### 1.6 Меч исчезает (`sword_disappears`, seg002:0536)
```c
if (current_level == 12 && Char.room == 18) {
get_tile(15, 1, 0);
curr_room_tiles[curr_tilepos] = tiles_1_floor;
curr_room_modif[curr_tilepos] = 0;
}
```
Срабатывает при уходе Кида ВПРАВО из комнаты 18 (`leave_room`, ветка 1).
У нас есть `pop_level_set_tile` — порт на пять строк.
### 1.7 Переход 12 → 13: НЕТ двери уровня, есть «бесшовный выход»
Проверено по данным (`res2012.bin` / `res2013.bin`) — портала действительно
нет, уровень кончается **фактом попадания в комнату**:
```c
// play_level_2, seg000:900
} else if (custom->tbl_seamless_exit[current_level] >= 0) {
if (Kid.room == /*23*/ custom->tbl_seamless_exit[current_level]) {
++next_level;
stop_sounds();
seamless = 1;
}
}
```
`tbl_seamless_exit[12] = 23` (пара «уровень, комната» читается из оригинального
`PRINCE.EXE`, options.c:724). Дальше геометрия складывается так:
| | комната | ряд 1 | что происходит |
|---|---|---|---|
| ур. 12 | 13 | `floor bigpil empty empty wall wall …` | Кид бежит ВЛЕВО с колонки 0 |
| ур. 12 | 23 | `empty ×6, floor floor floor floor` | попал сюда → **уровень сменился** |
| ур. 13 | 23 | `floor bigpil floor ×6 bigpil floor` | старт: ряд 1, кол 9, `seq_84_run` |
Связи: `комната 13.left = 23`, и в комнату 23 больше ниоткуда не войти.
Номер комнаты у обоих уровней **один и тот же (23)**, стартовая позиция
уровня 13 — `BP_START = 23, поз 19 (ряд 1, кол 9), dir 0`, а
`tbl_entry_pose[13] = 2` даёт «вбегающий» вход (`seq_84_run`, seg003:172).
То есть Кид вбегает в комнату слева-направо… нет, `Char.direction =
~level.start_dir` — влево, тем же ходом, каким выбежал из уровня 12. Швов не
видно.
Что делает флаг `seamless` (всего два места, оба косметические):
* `start_level`, seg003:158 — **НЕ сбрасывает HP**: Кид уносит на уровень 13
то здоровье, с которым добежал;
* `show_level`, seg008:1861/1870 — не показывает заставку «LEVEL 13» и тут же
гасит флаг.
Двери уровня при этом на карте есть, но не при делах: у уровня 12 она одна
(комната 3, tilepos 23) — это ВХОД (стартовая комната 12-го — 3), а у уровня
13 их две (комната 3 tilepos 13 и комната 5 tilepos 24) — это выходы,
открываемые кнопкой из `Jaffar_exit`.
**Что это значит для нас.** Наш переход уровня сейчас идёт только через
`SEQ_END_LEVEL` в двери. Для 12-го нужен второй триггер — проверка в главном
цикле «`pop_current_level == 12 && cur_room == 23``pop_next_level++`» плюс
флаг `seamless`, который пропустит сброс HP в `pop_start_level`. Обе правки
маленькие и локальные.
---
## 2. Уровень 13 — Джафар
### 2.1 Кто такой Джафар
`tbl_guard_type[13] = 3``VIZIER.DAT` (у нас в `pop_level_cold.c` уже 3).
`tbl_guard_hp[13] = 6`. Отдельного ИИ у него нет:
```c
void autocontrol_Jaffar() { autocontrol_guard(); } // seg002:0697
```
То есть **бой с Джафаром — обычный бой стражи**, отличаются только спрайты,
HP и три спецсобытия ниже. Это хорошая новость: боёвка у нас есть.
### 2.2 Встреча (`meet_Jaffar`, seg002:0544)
```c
if (current_level == 13 && leveldoor_open == 0 && Char.room == 3) {
play_sound(sound_29_meet_Jaffar);
guard_notice_timer = 28; // Джафар ждёт 28/12 ≈ 2.33 с
}
```
Срабатывает при уходе Кида ВПРАВО (`leave_room`, ветка 1).
Пара к нему — в `autocontrol_guard_inactive` (seg002:0734), она у нас уже
портирована (`guards.c:401`), но **без условия по уровню**:
```c
if (can_guard_see_kid) {
if (current_level != 13 || guard_notice_timer == 0) move_down_forw();
}
```
**Что добавить:** глобал `guard_notice_timer` + его тик в `do_timers`
(seg003:509) + оба условия.
### 2.3 Победа (`on_guard_killed`, seg006:1929)
```c
} else if (current_level == 13) {
flash_color = color_15_brightwhite; flash_time = 18;
is_show_time = 1;
leveldoor_open = 2; // ← ключ к выходу
play_sound(sound_43_victory_Jaffar);
}
```
и парная `Jaffar_exit` (seg002:0517), срабатывающая при уходе Кида ВЛЕВО:
```c
if (leveldoor_open == 2) { get_tile(24, 0, 0); trigger_button(0, 0, -1); }
```
То есть смерть Джафара не открывает дверь сама — она ставит флаг, а дверь
открывается кнопкой, «нажатой» при уходе влево. `trigger_button` у нас есть.
### 2.4 Падающие плиты (`check_fall_flo`, seg000:1319)
```c
if (current_level == 13 && (drawn_room == 23 || drawn_room == 16)) {
curr_room = room_A; // комната СВЕРХУ
for (curr_tilepos = 22; curr_tilepos <= 27; ++curr_tilepos)
make_loose_fall(-(prandom(0xFF) & 0x0F)); // ОТРИЦАТЕЛЬНЫЙ модификатор
}
```
Вот это и есть «плиты появляются»: при входе в комнаты 23/16 шесть плит ряда 2
комнаты СВЕРХУ получают отрицательную фазу — то есть отложенный старт, и
сыплются на Кида вразнобой. Зовётся из `check_the_end` при смене комнаты.
**Три спецкейса уровня 13 в loose-механике**, без них это не работает:
| место | что | зачем |
|---|---|---|
| `animate_loose`, seg007:823 | при `modif & 0x80` НЕ останавливать тряску | иначе отрицательная фаза не досчитает до нуля и плита не упадёт |
| `loose_make_shake`, seg007:949 | на уровне 13 сотрясение НЕ трясёт плиты | иначе отложенные плиты сбрасываются в 0x80 |
| `fell_on_your_head`, seg007:1218 | плита бьёт и в БЕГЕ (кадры 5..14) | на прочих уровнях бегущего не задевает |
У нас первый пункт критичен: `pop_loose_tick` трактует бит 7 как «тряска» и
на `>= 0x84` гасит фазу — отрицательный старт умрёт, не начавшись. Заодно
это ровно та же ветка, что мы правили сегодня в
[`LOOSE-ROOM-CHANGE`](../roomtest/BUGS_OPEN.md#loose-room-change), так что
код на виду.
### 2.5 Поза входа
`tbl_entry_pose[13] = 2` — у нас в таблице уже есть; проверить, что режим 2
(`seg003:172`) реализован.
---
## 3. Уровень 14 — принцесса и конец игры
Боя нет вовсе (`tbl_guard_type[14] = -1`). Всё сводится к одному событию:
```c
// check_the_end, seg000:1299
if (current_level == 14 && drawn_room == 5) end_sequence();
```
`end_sequence` (seg001:573) → `end_sequence_anim` (seg001:332): катсцена
«Кид добежал до принцессы» — обнимаются, появляется мышь, затухание, Hall of
Fame. Персонажи там играются ТЕМ ЖЕ интерпретатором `seqtbl`
(`seq_108_princess_turn_and_hug`, `seq_101_mouse_stands_up`), то есть движок
у нас уже подходит — нужны спрайты принцессы (`PRINCESS.DAT`) и раскадровка.
**Оценка:** это не игровая механика, а ролик. Логично делать вместе с интро
и межуровневыми вставками — отдельным банком, как и договаривались
(см. memory `pop_banking_architecture`). На проходимость игры не влияет:
достаточно довести Кида до комнаты 5 и показать заглушку.
---
## 4. Уровень 15 — «уровень зелий» (защита от копирования)
Не часть сюжета. Это экран проверки подлинности из оригинала: после
уровня `copyprot_level` игра подменяет номер на 15, показывает комнату с
**14 зельями, на которых нарисованы буквы**, и требует выпить нужное.
Механика (всё под `USE_COPYPROT` в SDLPoP):
| место | что делает |
|---|---|
| `play_level`, seg003:53 | `level_number == copyprot_level` → грузим 15 |
| `redraw_screen`, seg003:273 | поверх зелий рисуются БУКВЫ (`copyprot_letter`) |
| `load_alter_mod`, seg008:1204 | одно зелье в комнате делается «открытым» (тип 6) |
| `animate_potion`, seg007:259 | на уровне 15 своя ветка перерисовки |
| `up_pressed`/`do_pickup`, seg005:657 | выпитое зелье убирает букву из таблицы |
| `seq` эффект зелья, seg006:1896 | синие зелья на уровне 15 отнимают ПОЛОВИНУ HP |
| выход, seg000:700 | из 15 возвращаемся в `copyprot_level` |
**Рекомендация: не портировать.** Это анти-пиратский экран 1989 года,
требующий книжки-манускрипта; SDLPoP держит его выключенным по умолчанию
(`enable_copyprot`). Единственное, что стоит взять — **половинный урон
синего зелья**, если вдруг захочется полной совместимости; остальное только
съест банк. Если решим делать — это отдельная фича «уровень 15», а не часть
основного прохождения.
---
## 5. Уровень 0 — демо-уровень (аттракт)
`res2000.bin` у нас распакован. Это тот самый ролик, который крутится на
титульном экране: Кид сам бежит, дерётся со стражем и убегает.
Как устроено:
| место | что |
|---|---|
| `do_demo`, seg006:1409 | на уровне 0 вместо чтения клавиатуры зовётся `do_demo()` + `control()` |
| `autocontrol_kid`, seg002:702 | Кид управляется тем же `autocontrol_guard` |
| `do_auto_moves`, seg002:1089 | проигрыватель ЗАПИСИ ходов: таблица `(time, move)` |
| `on_guard_killed`, seg006:1928 | на уровне 0 после убийства стража Кид убегает (`checkpoint = 1`, сброс демо) |
| `demo_index` / `demo_time` | позиция в записи; у нас уже объявлены в `guards.c:267` |
**Хорошая новость:** движок автодвижений (`do_auto_moves`) у нас уже есть —
он нужен был тени на уровнях 4/5/6, и `demo_time`/`demo_index` объявлены там
же. То есть демо-уровень — это в основном таблица ходов + ветка «Кидом
управляет ИИ» в `pop_ctrl`.
**Когда делать:** вместе с интро/титульным экраном, не раньше. На
прохождение не влияет.
---
## 6. Порядок работ
Порядок задан пользователем 2026-08-13: **строго по номерам уровней**, а не
по дешевизне кода — уровень 13 не имеет смысла раньше, чем на него можно
попасть.
1.**Уровень 12** — тень: `init_shad_12`, `autocontrol_shadow_level12`,
`united_with_shadow`, общий урон, «убил тень — убил себя»,
`sword_disappears`, появление плит. Сделано 2026-08-13, хост-набор
`roomtest/tests-host/t_shadow.c` (45 проверок); живой проверки в MAME
ещё не было — доска [`L12-SHADOW`](../roomtest/TASKS_OPEN.md#l12-shadow).
2.**Переход 12 → 13** (§1.7) — room-триггер вместо двери и флаг
`pop_seamless` (не сбрасывать HP). Сделано там же.
3.**Уровень 13** — Джафар: `guard_notice_timer`,
`on_guard_killed`/`Jaffar_exit`, три спецкейса loose (§2.4) и
`check_fall_flo`. Плюс СПРАЙТЫ визиря (VIZIER.DAT) — без них он
рисовался обычным стражем. Сделано 2026-08-13, хост-набор
`roomtest/tests-host/t_jaffar.c` (44 проверки); живой проверки в MAME ещё
не было — доска [`L13-JAFFAR`](../roomtest/TASKS_OPEN.md#l13-jaffar).
4. **Уровень 14** — довести до комнаты 5 и поставить заглушку вместо ролика;
сам ролик — в общую задачу «катсцены».
5. **Уровень 0 и 15** — отложить: аттракт и защита от копирования на
прохождение не влияют.
---
## 7. Сверка констант с ОРИГИНАЛЬНЫМИ данными (сделана 2026-08-13)
Всё ниже снято скриптом прямо с `../SDLPoP/data/LEVELS/res2013.bin` и
`res2012.bin` — не из головы и не из констант SDLPoP.
### 7.1 Падающие плиты уровня 13 — константы сходятся ТОЧНО
Комнаты, у которых в ряду 2 вообще есть loose:
| комната | loose в колонках | комната снизу |
|---|---|---|
| 17 | 2, 3, 4, 5, 6, 7 | **23** |
| 1 | 2, 3, &nbsp;&nbsp; 5, 6, 7 | **16** |
| 14 | 2 | 24 |
Связи: `комната 23: up = 17`, `комната 16: up = 1`. То есть
`loose_tiles_room_1 = 23` и `room_2 = 16` — это ровно те две комнаты, над
которыми лежит ПОЛНАЯ гряда плит, а `first_tile = 22, last_tile = 27` — ровно
ряд 2, колонки 2..7:
```
комната 23 → сверху 17: 22:LOOSE 23:LOOSE 24:LOOSE 25:LOOSE 26:LOOSE 27:LOOSE [6/6]
комната 16 → сверху 1: 22:LOOSE 23:LOOSE 24:empty 25:LOOSE 26:LOOSE 27:LOOSE [5/6]
```
Два вывода:
* диапазон 22..27 — **надмножество**: в комнате 1 тайл 24 пустой, и
`make_loose_fall` его молча пропустит (проверяет тип тайла). Копировать
константы можно как есть;
* пара «комната 14 → 24» НЕ входит в спецсобытие намеренно — одна плита, это
обычный loose.
**Важное следствие, которого не было в плане:** стартовая комната уровня 13 —
**23** (`BP_START = 23`, поз 19, dir 0), а `check_fall_flo` зовётся из
`draw_level_first``check_the_end` (seg003:217). Значит плиты начинают
сыпаться СРАЗУ при входе на уровень, это его первый кадр, а не событие
где-то в середине.
### 7.2 Джафар — один страж со skill 9
В `res2013.bin` заполнен ровно один слот стража:
| комната | тайл | ряд, кол | направление | skill |
|---|---|---|---|---|
| 1 | 7 | 0, 7 | 255 (влево) | **9** |
И это сходится с `meet_Jaffar`: событие срабатывает, когда Кид уходит ВПРАВО
из комнаты 3, а `комната 3: right = 1` — то есть ровно туда, где стоит
Джафар. Skill 9 у нас поддержан: `NUM_GUARD_SKILLS = 12`, все шесть таблиц
вероятностей (`guards.c:411..415`) имеют индекс 9. HP = `tbl_guard_hp[13] = 6`
`pop_level_cold.c` уже стоит).
`Jaffar_exit` тоже проверен: **комната 24, tilepos 0 = `opener`** (кнопка),
модификатор 0 — то есть `trigger_button(0,0,-1)` жмёт реальную кнопку, а не
пустой тайл.
### 7.3 Уровень 12 — меч на месте
`комната 15, tilepos 1 = SWORD` — условие подъёма тени
(`get_tile(15,1,0) == tiles_22_sword`) на наших данных выполняется.
`комната 18: right = 19``sword_disappears` срабатывает при уходе вправо.
Комната 15 целиком (по ней видно всю сцену встречи с тенью):
```
ряд 0: floor SWORD floor torch torch LOOSE LOOSE floor empty empty
ряд 1: wall wall wall empty empty empty empty wall floor floor
ряд 2: wall opener pillar empty empty empty empty wall pillar empty
```
### 7.4 `pop_loose_tick` действительно убьёт отрицательную фазу
Подтверждено чтением кода: `m = ++pop_loose_modif[pos]; if (m & 0x80) { if (m
>= 0x84) → сброс в 0 }`. Для стартового `0xF0..0xFF` первый же тик даёт
`m >= 0x84` → фаза обнуляется, плита не падает. **Это и есть та правка №1 из
таблицы §2.4**, и она обязательна.
### 7.5 `init_shad_12` и байтовое переполнение `y`
`init_shad_12 = {0x0F, 0x51, 0xE8, 0, 0, 0, 0, 0}` → frame 15, x 81,
**y 232**, вправо, колонка 0, ряд 0, action 0 + `seq 7 (fall)`. Поле `y` в
`char_type``byte` (беззнаковое), то есть 232 лежит НИЖЕ поля (192), и
падение уводит его дальше с переполнением байта: тень «падает» из-под экрана и
появляется сверху. У нас `pop_char_t.y` тоже `uint8_t` — поведение
переносится без правок; при портировании просто не «чинить» это как ошибку.
Отдельно про ряд: `curr_row` берётся ИЗ ТАБЛИЦЫ и равен **0**, хотя
`pop_y_to_row(232)` дал бы 1. `do_init_shad` копирует семь полей как есть и
ряд не пересчитывает, так что «согласовать» их — значит разойтись с
оригиналом. Закреплено тестом `shadow12_rises_when_sword_gone`.