L1-START + L1-EXIT: старт по данным уровня и выход через дверь уровня

L1-START.  Старт и оба рестарта (смерть, выпадение из уровня) сведены в
pop_start_level() — порт start_level + do_startpos + set_start_pos (seg003).
Комната/тайл/направление берутся из pop_level_start_*, направление
инвертируется (~start_dir), поза входа — из tbl_entry_pose: у уровня 1 это
падение внутрь (seq_7_fall) плюс нажатие кнопки room5(0,2), то самое, что
захлопывает решётку за спиной.  Жёсткие START_ROOM/COL/ROW убраны.
Проверено в MAME: старт даёт room 1, col 0, падение на row 1 — как по данным.

L1-EXIT.  Ветка двери уровня из up_pressed + go_up_leveldoor (seg005:0482/
0574): тайлы и геометрия — pop_leveldoor_enter() в pop_map, последовательность
seq_70 — в pop_ctrl.  Опкод 0xF1 END_LEVEL в play_seq инкрементит
pop_next_level (порт next_level), главный цикл по нему перезапускает уровень
— ровно та точка, куда levels_plan §2.2 подключит загрузку уровня 2.
Открытость двери проверяется по modifier >= 42 (ветка fix_exit_door), а не по
ванильному leveldoor_open: иначе можно войти в ещё ползущую створку.

Отдельно стоило разбора: go_up_leveldoor сначала писал Char.x/Char.direction,
и оба присваивания молча терялись — окно Char вокруг диспетчера возвращает
назад только curr_seq и sword (pop_savekid_state).  Направление оставалось
«вправо», а все DX в seq_70 отрицательные, поэтому Кид уходил ИЗ проёма
влево (поймано стоп-кадром).  Геометрию персонажа в этом порте меняет
pop_map, пишет в Kid — как pop_down_action и pop_jump_up_seq.

Известный остаток — BUG-DOOR-CLIP в bug_list.md: нет обрезки силуэта правым
косяком проёма (недопортирован obj_clip_right в clip_char); нужен вариант
колоночного блита с ограничением ширины.  ROOMNAV пока оставлен включённым —
он нужен, чтобы попадать в комнату 9 для этой работы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-01 22:47:47 +03:00
parent 1146c57544
commit e86f254b87
8 changed files with 251 additions and 46 deletions
+37 -15
View File
@@ -6,11 +6,11 @@
а то, **что берём в работу сейчас и в каком порядке**. Каждая запись: что
сделать, почему именно сейчас, чем подтверждать результат.
**Открытых багов нет** (ревизия 2026-08-01): в [`bug_list.md`](bug_list.md)
остались только незакрытые оптимизации [T-1](bug_list.md#t-1),
**Открытые баги** ([`bug_list.md`](bug_list.md)):
[BUG-DOOR-CLIP](bug_list.md#bug-door-clip) (Кид не обрезается правым косяком
двери уровня) + незакрытые оптимизации [T-1](bug_list.md#t-1),
[T-2](bug_list.md#t-2) и незаконченная
[таблица обхода 24 комнат](bug_list.md#обход-всех-24-комнат-уровня-1).
Следующая порция багов придёт оттуда и из L1-PASS.
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
@@ -364,19 +364,41 @@
## P1 — сразу после P0 (закрываем уровень 1 как ИГРУ, а не стенд)
### L1-START. Старт по данным уровня
`roomtest.c` жёстко стартует `START_ROOM 1 / COL 3 / ROW 0`, хотя
`pop_level_start_room()` / `pop_level_start_pos()` / `pop_level_start_dir()`
в `pop_level.h` уже реализованы и НИКЕМ не вызываются. Перевести старт и
респавн на них; `#define ROOMNAV` оставить, но выключенным по умолчанию.
### L1-START. Старт по данным уровня — **СДЕЛАНО 2026-08-01**
Старт и оба рестарта (смерть, выпадение из уровня) сведены в один
`pop_start_level()` — порт `start_level` + `do_startpos` + `set_start_pos`
(seg003): комната/тайл/направление берутся из `pop_level_start_*`, направление
инвертируется (`~start_dir`), поза входа — из `tbl_entry_pose`. У уровня 1
это «падение внутрь» плюс нажатие кнопки room5(0,2) — то самое, что
захлопывает решётку за спиной. Проверено: старт даёт room 1, col 0, падение
на row 1 — как по данным уровня.
### L1-EXIT. Выход с уровня (дверь уровня)
Сейчас: дверь открывается (`animate_leveldoor`), но войти в неё нельзя —
`up_pressed()` (`pop_ctrl.c:188`) не проверяет `tiles_16_level_door_left`, а
опкод `0xF1 END_LEVEL` в `play_seq` (`pop_kid.c:418`) пустой. Портировать
`up_pressed`-ветку + `go_up_leveldoor()` (`seg005.c:410..500`) и завести
`pop_next_level`, который взводит `END_LEVEL` (`seg006.c:662`). **Это же
первый шаг плана следующих уровней** — см. `../docs/levels_plan.md`.
**Остаток:** `#define ROOMNAV` пока ВКЛЮЧЁН — он нужен, чтобы попадать в
комнату 9 для работы над [BUG-DOOR-CLIP](bug_list.md#bug-door-clip).
Выключить перед L1-PASS.
### L1-EXIT. Выход с уровня (дверь уровня) — **СДЕЛАНО 2026-08-01**
Портированы: ветка двери уровня из `up_pressed` + `go_up_leveldoor`
(seg005:0482/0574) — в `pop_leveldoor_enter()` (`pop_map.c`, тайлы и
геометрия) и `up_pressed()` (`pop_ctrl.c`, только последовательность);
опкод `0xF1 END_LEVEL` в `play_seq` теперь инкрементит `pop_next_level`
(порт `next_level`), а главный цикл по нему перезапускает уровень — ровно
та точка, куда `levels_plan.md` §2.2 подключит загрузку уровня 2.
Открытость двери проверяем по `modifier >= 42` (ветка `fix_exit_door`), а не
по ванильному `leveldoor_open`: с ванильным условием можно войти в ещё
ползущую створку.
**Грабли, которые стоили отдельного разбора:** `go_up_leveldoor` сначала
писал `Char.x`/`Char.direction`, и оба присваивания молча терялись — окно
`Char` вокруг диспетчера возвращает назад ТОЛЬКО `curr_seq` и `sword`
(`pop_savekid_state`). Направление оставалось «вправо», а все `DX`
последовательности seq_70 отрицательные, поэтому Кид уходил ИЗ проёма влево.
Вывод на будущее: геометрию персонажа в этом порте меняет `pop_map` (пишет в
`Kid`), а не диспетчер.
⏳ Косметика: [BUG-DOOR-CLIP](bug_list.md#bug-door-clip) — нет обрезки силуэта
правым косяком проёма.
### L1-TRIAGE. Ревизия багов — **ЧАСТЬ 1 СДЕЛАНА 2026-08-01**
+49 -5
View File
@@ -14,17 +14,61 @@ odd-pixel арифметика `char_x`, подстановка тайла на
починена кодом ещё в июле, список просто отстал — чем именно закрыта каждая,
расписано в [`bug_closed.md`](bug_closed.md)).
**Открытых багов сейчас нет** — только две незакрытые оптимизации и
незаконченная ревизия комнат:
| ID | что | тип |
|----|-----|-----|
| [BUG-DOOR-CLIP](#bug-door-clip) | Кид не обрезается правым косяком двери уровня | Medium |
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация |
| [T-2](#t-2) | Кид перерисовывается в покое | оптимизация |
| [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия |
Это НЕ значит «багов нет» — значит, что известные найдены и закрыты, а
следующая порция придёт из обхода 24 комнат и сквозного прохождения (L1-PASS).
---
<a id="bug-door-clip"></a>
## BUG-DOOR-CLIP. Подъём по лестнице двери уровня: нет обрезки по правому косяку — **Medium**
**Симптом (2026-08-01, найден пользователем сразу после L1-EXIT):** при
подъёме по лестнице за дверью уровня (seq_70, кадры 224..228) силуэт Кида
вылезает ПРАВЕЕ правого косяка проёма — должен уходить за него. По ВЫСОТЕ
обрезка корректна.
**Причина — недопортированная половина `clip_char`** (seg006:1231). Для
кадров двери уровня оригинал ставит ДВА клипа, а мы портировали только
первый:
```c
if (frame >= frame_224_exit_stairs_8 && frame < 229) {
obj_clip_top = leveldoor_ybottom + 1; // ЕСТЬ (pop_clip_char_top)
obj_clip_right = leveldoor_right; // НЕТ
}
```
`leveldoor_right = (draw_xh << 3) + 48` считается в `draw_leveldoor`
(seg008:1D29) от колонки ЛЕВОЙ половины двери; для palace-уровней (+8,
`tbl_level_type`) — на уровне 1 не применяется.
Обратить внимание: комментарий в SDLPoP говорит «frames 217..228», а КОД
проверяет `>= frame_224_exit_stairs_8`, то есть **224..228**. Портировать по
коду.
**Чего не хватает в движке:** обрезки СПРАВА у колоночного блита.
`gfx_blit_cols_part` (libbgi) умеет только обрезку сверху (`skip`/`rows`) плюс
клип по краю ЭКРАНА (`w = _bgi_maxx + 1 - x`). Для column-major спрайта
обрезка справа — это ровно уменьшение `w`, то есть внутри ядра она уже есть,
не хватает способа задать её снаружи.
**План (не начат):**
1. libbgi: тело `gfx_blit_cols_part` вынести в вариант с ограничением
ширины (напр. `gfx_blit_cols_part_w(..., uint8_t maxw)`), а нынешнюю
функцию оставить тонкой обёрткой — тем же приёмом, каким `gfx_blit_cols`
уже обёрнут вокруг `gfx_blit_cols_part`. Флип тут не мешает: в кадрах
двери направление всегда dir_FF_left, то есть flip=0.
2. PoP: `pop_clip_char_top` дополнить правым клипом (или завести
`pop_clip_char_right`), считать `leveldoor_right` от колонки двери.
3. `make size-check` (трогаем libbgi) + проверка стоп-кадром в MAME:
поймать кадр 224..228 клавишей `1` и сверить кромку.
**Пока не починено, дефект косметический** — вход на следующий уровень
работает.
---
+10 -2
View File
@@ -60,6 +60,7 @@
#define SEQ_14_JUMP_INTO_CEILING 14 /* прыжок вверх: сверху стена/пол */
#define SEQ_28_JUMP_NOTHING_ABOVE 28 /* прыжок вверх: сверху пусто */
#define SEQ_25_HANG_AGAINST_WALL 25 /* вис у стены (Shift в висе) */
#define SEQ_GO_UP_LEVELDOOR 70 /* seq_70_go_up_on_level_door */
#define FRAME_7_RUN 7
#define FRAME_11_RUN 11
@@ -187,8 +188,15 @@ static void jump_up(void)
static void up_pressed(void)
{
/* верь уровня/телепорт — нет в тесте.) ↑+forward = прыжок с места;
* иначе вертикальный прыжок вверх. */
/* Дверь уровня рядом и открыта → войти в неё: постановку в проём
* (go_up_leveldoor) делает pop_leveldoor_enter, здесь остаётся только
* последовательность подъёма по лестнице. Телепортов у нас нет это
* модификация SDLPoP, а не оригинал.
* Иначе: +forward = прыжок с места, иначе вертикальный прыжок вверх. */
if (pop_leveldoor_enter()) {
seqtbl_offset_char(SEQ_GO_UP_LEVELDOOR);
return;
}
if (control_x == CONTROL_HELD_FORWARD)
standing_jump();
else
+13 -1
View File
@@ -23,6 +23,13 @@ pop_char_t Kid;
* в play_seq, потребляется pop_check_knock (pop_map) трясёт loose-полы. */
int8_t knock;
/* Порт глобала next_level: seqtbl-команда END_LEVEL инкрементит его в конце
* seq_70 (подъём по лестнице за дверью уровня), а главный цикл сравнивает с
* текущим уровнем и грузит следующий. Счётчик, а не флаг ровно как в
* оригинале, чтобы читы «следующий/предыдущий уровень» (levels_plan.md §4)
* легли сюда же без переделки. */
uint8_t pop_next_level;
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */
#define KID_MAXPAGES 28
@@ -418,7 +425,12 @@ void play_seq(void)
case 0xF2: /* SOUND — потребить 1 байт */
Char.curr_seq++;
break;
case 0xF1: /* END_LEVEL */
case 0xF1: /* END_LEVEL: seq_70 доиграл — уровень пройден.
* В оригинале это `++next_level`, а главный цикл
* (play_level_2, seg003:0386) выходит, когда next_level
* разошёлся с current_level. У нас так же: счётчик, а
* решение снаружи (roomtest.c). */
pop_next_level++;
break;
case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета
* (порт play_seq -> proc_get_object, seg006) */
+5
View File
@@ -21,6 +21,11 @@ extern pop_char_t Kid;
* (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */
extern int8_t knock;
/* Порт next_level (seg000): END_LEVEL в play_seq инкрементит — «уровень
* пройден». Читает и обнуляет главный цикл. Пока уровень один, поэтому
* там рестарт того же уровня; загрузка следующего levels_plan.md. */
extern uint8_t pop_next_level;
/* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph.
* 0 OK, -1 ошибка. */
int pop_kid_load(uint8_t npages);
+54
View File
@@ -30,6 +30,7 @@
#include "pop_bg.h"
#include "pop_state.h"
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
#include "pop_level.h" /* pop_level_start_room — вход в дверь уровня */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
#define TILE_EMPTY 0
@@ -46,6 +47,7 @@
#define TILE_POTION 10 /* зелье */
#define TILE_SWORD 22 /* меч на полу */
#define TILE_OPENER 15 /* кнопка-открывалка (RAISE, 0x0F) */
#define TILE_LEVELDOOR_LEFT 16 /* ЛЕВАЯ половина двери уровня (на ней modif) */
#define ACT_STAND 0
#define ACT_RUNJUMP 1
#define ACT_HANGCLIMB 2
@@ -777,6 +779,58 @@ uint8_t pop_hang_fall_seq(void) __banked
* либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 =
* приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки
* пола + под соседним тайлом есть за что зацепиться спуск на этаж вниз. */
/* Ветка «войти в дверь уровня» из up_pressed (seg005:0482) вместе с
* go_up_leveldoor (seg005:0574). Дверь рядом и открыта поставить Кида в
* проём, вернуть 1 (последовательность seq_70 ставит вызывающий это его
* слой); иначе 0 и Кид не тронут.
*
* Геометрию персонажа меняем в Kid, а НЕ в Char: окно Char вокруг
* диспетчера возвращает назад ТОЛЬКО curr_seq и sword
* (pop_savekid_state), поэтому во всём порте позицию правит pop_map как
* в pop_down_action и pop_jump_up_seq. Запись в Char.x/Char.direction
* отсюда молча теряется (проверено: Кид уходил из проёма влево).
*
* Порядок проб как в оригинале и с той же семантикой `else if`: берём
* ПЕРВУЮ клетку, в которой стоит дверь (под персонажем за спиной перед
* лицом), и уже её проверяем на открытость а не ищем «первую открытую».
*
* Открытость: `modifier >= 42`, то есть створка доехала до верха. Это
* ветка fix_exit_door, а не ванильный глобал leveldoor_open: с ванильным
* условием в дверь можно войти, пока она ещё ползёт вверх, и персонаж
* проходит сквозь опущенную створку.
*
* Стартовая комната исключена (`level.start_room != drawn_room`): в первой
* комнате уровня дверь та, через которую вошли, и обратно в неё нельзя. */
uint8_t pop_leveldoor_enter(void) __banked
{
int8_t cols[3];
uint8_t i;
if (pop_level_start_room() == g_room) return 0;
cols[0] = Kid.curr_col;
cols[1] = (int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col);
cols[2] = (int8_t)(dir_front[Kid.direction + 1] + Kid.curr_col);
for (i = 0; i < 3; i++) {
if (get_tile(cols[i], Kid.curr_row) != TILE_LEVELDOOR_LEFT)
continue;
/* дверь найдена — дальше решает только её modif (см. выше) */
if (cols[i] < 0 || cols[i] > 9) return 0; /* дверь в шве: не наша */
if (pop_trob_modif(g_room)[Kid.curr_row * 10 + cols[i]] < 42)
return 0;
/* go_up_leveldoor (seg005:0574): встать РОВНО в проём и развернуться
* ВЛЕВО. Направление тут не косметика: все DX последовательности
* seq_70 ОТРИЦАТЕЛЬНЫЕ (5,4,4,2,3,1,3,1,2), а
* char_dx_forward при взгляде влево меняет им знак то есть именно
* dir_FF_left и гонит персонажа ВПРАВО-ВВЕРХ по лестнице. При
* взгляде вправо тот же seq уводит его влево, ИЗ проёма. */
Kid.x = (uint8_t)(pop_x_bump[cols[i] + FIRST_ONSCREEN_COLUMN] + 10);
Kid.direction = -1;
return 1;
}
return 0;
}
uint8_t pop_down_action(void) __banked
{
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) {
+6
View File
@@ -151,4 +151,10 @@ int8_t pop_hang_wall(void) __banked; /* Shift в висе: 1=у сте
uint8_t pop_hang_fall_seq(void) __banked; /* seq_23 / seq_11 (сорваться), +Kid.x */
uint8_t pop_down_action(void) __banked; /* 1=обработано(спуск/отступ), 0=присед */
/* Дверь уровня рядом и ОТКРЫТА ПОЛНОСТЬЮ? → поставить Кида в проём (x +
* разворот влево, go_up_leveldoor) и вернуть 1; иначе 0, Кид не тронут.
* Ветка двери уровня из up_pressed (seg005:0482). Последовательность
* seq_70 ставит вызывающий (pop_ctrl) тайлы наши, управление его. */
uint8_t pop_leveldoor_enter(void) __banked;
#endif
+77 -23
View File
@@ -89,12 +89,11 @@ static void kbd_idle(void)
#define ROOM_1 1
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
/* Тест-старт для отладки пик (Фаза S): комната 6 = тестовая (пики в col3/col4
* row2, шахта col3). Kid на полу row0 col1, идёт вправо падает в шахту
* пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */
#define START_ROOM 1 /* обход комнат: старт с 1-й, дальше +/- (ROOMNAV) */
#define START_COL 3
#define START_ROW 0
/* Поза входа на уровне (tbl_entry_pose, data.h:848): у уровня 1 — падение
* внутрь плюс нажатие кнопки. Уровень пока один, поэтому константа; когда
* появятся уровни 2+ (levels_plan.md), это станет таблицей в pop_level. */
#define LEVEL_ENTRY_POSE ENTRY_POSE_FALL
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
@@ -262,6 +261,60 @@ static void enter_room(uint8_t room)
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
}
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
* (seg003:028A) поставить Кида в стартовую позицию УРОВНЯ. Один вход и
* для запуска, и для рестарта после смерти / выпадения из уровня: в
* оригинале это буквально один и тот же путь (play_level_2 зовёт
* start_level заново), поэтому и у нас не три копии четырёх строк.
*
* Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define:
* start_pos тайл 0..29 (row*10+col), start_dir направление, в котором
* персонаж должен ОКАЗАТЬСЯ.
*
* Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж
* ставится спиной и доворачивается кадрами позы входа. Сама поза из
* tbl_entry_pose (data.h:848 = {0,1,0,,2,}):
* 1 (уровень 1) падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2);
* это оно захлопывает решётку за спиной в первой комнате;
* 2 (уровень 13) вбегание (seq_84_run);
* иначе разворот на месте (seq_5_turn).
* Таблица индексируется НОМЕРОМ УРОВНЯ, поэтому появится в pop_level, когда
* дойдём до уровней 2+ (levels_plan.md); пока уровень один. */
#define SEQ_TURN 5
#define SEQ_FALL 7
#define SEQ_RUN 84
#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
static void pop_start_level(uint8_t entry_pose)
{
uint8_t pos = pop_level_start_pos();
int8_t dir = (int8_t)~pop_level_start_dir();
uint8_t seq = SEQ_TURN;
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
/* Кнопку жмём ДО отрисовки комнаты — как в оригинале (do_startpos
* отрабатывает раньше первого draw_room), иначе шов кадр показывал бы
* ещё не тронутое состояние решётки. */
if (entry_pose == ENTRY_POSE_FALL) {
/* get_tile(5,2,0) + trigger_button(0,0,-1) — «нажать» кнопку room5,
* col2, row0 её же типом и модификатором (в оригинале и то и другое
* берётся из curr_* после get_tile). */
uint8_t tp = 2; /* row0*10 + col2 */
pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp]);
seq = SEQ_FALL;
} else if (entry_pose == ENTRY_POSE_RUN) {
seq = SEQ_RUN;
}
enter_room(pop_level_start_room());
kid_init(seq, (int8_t)(pos % 10), (int8_t)(pos / 10), dir);
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
pop_have_sword = 0; /* start_level: have_sword с уровня 2 */
pop_kid_hp_reset();
}
int main(void)
{
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
@@ -308,15 +361,11 @@ int main(void)
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
pop_trob_reset();
enter_room(START_ROOM);
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
* pop_start_level. Управление: / бег, присед, + прыжок с места,
* Shift шаг, ESC выход. Карта коллизии задана внутри enter_room. */
pop_start_level(LEVEL_ENTRY_POSE);
gfx_set_draw_page(0);
/* K2b: Kid стоит на уступе (row1), управляется с клавиатуры.
* / бег, присед, + прыжок с места, Shift шаг (K3). ESC выход.
* Карта коллизии (pop_map_set) уже задана в enter_room. */
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
pop_kid_hp_reset();
kid_draw();
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
kid_fp_width(), kid_fp_height(),
@@ -428,12 +477,9 @@ int main(void)
if (down) { /* L2: переход в комнату снизу — продолжает падать */
enter_room(down); /* координаты Kid уже репроецированы pop_map */
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
} else { /* выпал из уровня (нет комнаты снизу) — респавн */
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
enter_room(START_ROOM);
} else { /* выпал из уровня (нет комнаты снизу) — рестарт */
pop_start_level(LEVEL_ENTRY_POSE);
gfx_set_draw_page(back);
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
pop_kid_hp_reset();
}
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
uint8_t nb = pop_room_link(cur_room, (uint8_t)(pop_leave_dir - 1));
@@ -468,16 +514,24 @@ int main(void)
}
}
}
if (pop_next_level) { /* END_LEVEL: доиграл seq_70 в двери уровня */
/* Порт play_level_2 (seg003:0386): цикл уровня кончается, когда
* next_level разошёлся с current_level, и start_game грузит
* следующий. Уровень у нас пока один, поэтому «следующий» = тот
* же самый зато вся цепочка (дверь seq_70 END_LEVEL
* конец уровня) работает целиком и проверяема. Загрузка уровня
* 2 подключается ровно сюда, см. ../docs/levels_plan.md §2.2. */
pop_next_level = 0;
pop_start_level(LEVEL_ENTRY_POSE);
gfx_set_draw_page(back);
}
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
dead_frames++;
if (dead_frames > RESPAWN_SETTLE &&
(kbd_raw_down(KBD_UP) || dead_frames > RESPAWN_DELAY)) {
dead_frames = 0;
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
enter_room(START_ROOM);
pop_start_level(LEVEL_ENTRY_POSE);
gfx_set_draw_page(back);
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
pop_kid_hp_reset();
}
}
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */