67d62e3e8b
Уровень 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>
463 lines
25 KiB
Markdown
463 lines
25 KiB
Markdown
# Что осталось на уровнях 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, 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`.
|