From e86f254b878446359a9601203e5ba690596a8d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=9F=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= Date: Sat, 1 Aug 2026 22:47:47 +0300 Subject: [PATCH] =?UTF-8?q?L1-START=20+=20L1-EXIT:=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D1=82=20=D0=BF=D0=BE=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=BC=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D1=8F=20=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=8B=D1=85=D0=BE=D0=B4=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20?= =?UTF-8?q?=D0=B4=D0=B2=D0=B5=D1=80=D1=8C=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- applications/PoP/roomtest/TASKS.md | 52 ++++++++++---- applications/PoP/roomtest/bug_list.md | 54 ++++++++++++-- applications/PoP/roomtest/pop_ctrl.c | 12 +++- applications/PoP/roomtest/pop_kid.c | 14 +++- applications/PoP/roomtest/pop_kid.h | 5 ++ applications/PoP/roomtest/pop_map.c | 54 ++++++++++++++ applications/PoP/roomtest/pop_map.h | 6 ++ applications/PoP/roomtest/roomtest.c | 100 ++++++++++++++++++++------ 8 files changed, 251 insertions(+), 46 deletions(-) diff --git a/applications/PoP/roomtest/TASKS.md b/applications/PoP/roomtest/TASKS.md index 6c44ca8..ed1c79f 100644 --- a/applications/PoP/roomtest/TASKS.md +++ b/applications/PoP/roomtest/TASKS.md @@ -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** diff --git a/applications/PoP/roomtest/bug_list.md b/applications/PoP/roomtest/bug_list.md index 6f6a860..b8fba1e 100644 --- a/applications/PoP/roomtest/bug_list.md +++ b/applications/PoP/roomtest/bug_list.md @@ -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). +--- + + +## 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` и сверить кромку. + +**Пока не починено, дефект косметический** — вход на следующий уровень +работает. --- diff --git a/applications/PoP/roomtest/pop_ctrl.c b/applications/PoP/roomtest/pop_ctrl.c index 6773809..56d3844 100644 --- a/applications/PoP/roomtest/pop_ctrl.c +++ b/applications/PoP/roomtest/pop_ctrl.c @@ -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 diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index cac64fb..5121ca9 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -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) */ diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index 9c99f90..75ceaa6 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -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); diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index e93b9af..75ca0e6 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -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) { diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 73b2fe8..3d2eeba 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -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 diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 7969d4d..87bebee 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -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 в комнате снизу */