Уровни 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>
This commit is contained in:
2026-08-13 18:21:52 +03:00
parent 716f6f9273
commit 67d62e3e8b
37 changed files with 3169 additions and 79 deletions
+462
View File
@@ -0,0 +1,462 @@
# Что осталось на уровнях 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`.