diff --git a/applications/PoP/poc/res/bg/pal_env4.atl b/applications/PoP/poc/res/bg/pal_env4.atl index 7c1ec1d..b813f03 100644 Binary files a/applications/PoP/poc/res/bg/pal_env4.atl and b/applications/PoP/poc/res/bg/pal_env4.atl differ diff --git a/applications/PoP/poc/res/bg/pal_fore.atl b/applications/PoP/poc/res/bg/pal_fore.atl index 1d039fe..6fd76e5 100644 Binary files a/applications/PoP/poc/res/bg/pal_fore.atl and b/applications/PoP/poc/res/bg/pal_fore.atl differ diff --git a/applications/PoP/poc/res/bg/pop_env4.atl b/applications/PoP/poc/res/bg/pop_env4.atl index af610f3..683fcdc 100644 Binary files a/applications/PoP/poc/res/bg/pop_env4.atl and b/applications/PoP/poc/res/bg/pop_env4.atl differ diff --git a/applications/PoP/poc/res/bg/pop_fore.atl b/applications/PoP/poc/res/bg/pop_fore.atl index 712de61..87f32bb 100644 Binary files a/applications/PoP/poc/res/bg/pop_fore.atl and b/applications/PoP/poc/res/bg/pop_fore.atl differ diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index 6409b65..81eca73 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -55,6 +55,7 @@ | 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 | | — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — | | 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели | +| 4 | [L4-MIRROR](#l4-mirror) | **АКТИВНА с 2026-08-10 по решению пользователя**: зеркало уровня 4 + тень | прохождение ур. 4 | | — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3** (игра быстрее на треть). Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях | | — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент | | — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности | @@ -720,6 +721,59 @@ loose-плиту (7, кол 4, ряд 0). --- +### L4-MIRROR. Зеркало уровня 4 и тень — **АКТИВНА с 2026-08-10** + +Приоритет переставлен пользователем: palace-уровни были отложены решением +2026-08-04, но уровень 4 уже гоняется в MAME, и зеркало — единственное, что +мешает пройти его сюжетно. + +**Как это работает в оригинале** (SDLPoP, всё сверено по коду): + +| что | где | суть | +|---|---|---| +| постановка тайла | `animate_leveldoor`, seg007:0457 | дверь выхода ДОРИСОВАЛА открытие (`modif >= 43`, `leveldoor_open` 0/2 -> 1) -> в комнату 4, колонку 4, ряд 0 пишется `tiles_13_mirror` | +| отражение | `check_mirror`, seg003:0798 | пока Кид на тайле зеркала — каждый кадр рисуется зеркальная копия: `Char.x = (xpos<<1) - Char.x`, `direction` инвертируется, копия идёт в objtable типом 4 с клипом `left = (curr_col<<5)+9`, `top = y_clip[row+1]` | +| прыжок сквозь | seg004:0240 | засчитывается ТОЛЬКО `run-jump` СПРАВА НАЛЕВО (`frame 39..43`, `direction < 0`) -> `modif = 0x56` (разбитое), `jumped_through_mirror = -1` | +| рождение тени | `jump_through_mirror`, seg003:080A | Кид копируется в слот Guard как `charid_1_shadow`; `guardhp = hitp_max`, у Кида `hitp_curr = 1` | +| поведение тени | `autocontrol_shadow_level4`, seg002:1131 | в зеркальной комнате при `x < 80` исчезает (`clear_char`), иначе бежит вперёд. Боя нет | +| клип тени | seg008:1699 | `obj_clip_left = 137 + (mirror_column-4)*32` — тень видна только СПРАВА от зеркала | +| коллизия | `wall_type`, seg006:1632 | зеркало = 2 «стена слева» — **уже портировано** (`pop_map.c`), как и спецкейс `can_climb_up` | + +**Шаги.** + +1. **АТЛАС — СДЕЛАНО 2026-08-10.** `tile_table[0x0D]` = база 75, фронт 77 + (наша таблица совпадает с SDLPoP такт в такт). Тайла 13 **нет ни в одном + уровне статически** — проверено перебором всех 15 `res200N.bin`, ноль + попаданий; санити-проверка разбора: ур.1 без чомпера, ур.3 с 18, ур.4 с + паласными 25..29. Значит `render_room` эти id не увидит и на месте + зеркала был бы чёрный провал (memory `pop_atlas_dynamic_ids`). Добавлены + `MIRROR_ENV_IDS = {75, 77}` в `pop_pack_bg.py`, 77 — ещё и в + `FORE_ENV_IDS`. Оба набора переупакованы: fore 17 -> 18 спрайтов, все + страницы EMM в пределах 16 КБ. +2. **ПОСТАНОВКА ТАЙЛА — СДЕЛАНО 2026-08-10, НЕ ПРОВЕРЕНО В MAME.** + `place_mirror()` в `pop_trob.c`, вызывается из `animate_leveldoor` по + переходу `pop_leveldoor_open` 0/2 -> 1 (условие оригинала — иначе тайл + ставился бы заново каждый кадр открытой двери). Если комната зеркала уже + на экране, ставится `POP_RD_FLOOR` на обе страницы. + **Что проверить:** нажать плиту выхода на ур. 4, дойти до комнаты 4 — + зеркало должно быть нарисовано. Отдельно посмотреть, не устарел ли + `g_fg` коллизии, если игрок окажется в комнате 4 в момент постановки. +3. **ОТРАЖЕНИЕ.** Самый дорогой шаг: это ЧЕТВЁРТЫЙ рисуемый Char помимо + Кида и соперника, со своим клипом. Отрисовка у нас уже общая + (`pop_cdraw.c`, слоты — memory `pop_char_draw_unified`), вопрос в числе + слотов и в том, что отражение меняется вместе с Кидом каждый кадр, то + есть `pop_char_skip_mask` на нём не сработает. ЧИСТО КОСМЕТИКА — + делается последним. +4. **ПРЫЖОК СКВОЗЬ + РОЖДЕНИЕ ТЕНИ.** Слот Guard уже умеет держать + не-стража (скелет ур. 3), `charid_1_shadow` встаёт рядом. + `autocontrol_shadow_level4` — десяток строк. +5. **КЛИП ТЕНИ** (seg008:1699). + +Порядок: 1 -> 2 -> 4 -> 5 -> 3. После 4 и 5 уровень уже проходится, потому +что сюжетно достаточно прыгнуть сквозь зеркало. + +--- + ## P1 — берётся в любой момент ### L1-SPEED. Игра идёт быстрее оригинала (найдено 2026-08-01) diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index 6e73c35..b1e153b 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -409,7 +409,37 @@ static void animate_button(uint8_t idx, int8_t *type) * type = -1. */ static const uint8_t leveldoor_close_speeds[5] = {0, 5, 17, 99, 0}; -static void animate_leveldoor(uint8_t *m, int8_t *type) +/* ---- Спецсобытие уровня 4: ПОЯВЛЕНИЕ ЗЕРКАЛА ------------------------ * + * + * seg007:0457, хвост animate_leveldoor: когда дверь выхода ДОРИСОВАЛА + * открытие и leveldoor_open переходит 0/2 -> 1, оригинал ставит тайл + * зеркала прямо в данные комнаты: + * + * get_tile(mirror_room, mirror_column, mirror_row); + * curr_room_tiles[curr_tilepos] = tiles_13_mirror; + * + * Ставится он в ЧУЖУЮ комнату (4), а не в ту, где дверь. В статике уровня + * зеркала НЕТ НИ В ОДНОМ файле (проверено перебором всех 15 res200N.bin), + * поэтому его спрайты 75/77 пришлось добавить в атлас явным списком — + * MIRROR_ENV_IDS в pop_pack_bg.py (грабли memory pop_atlas_dynamic_ids). */ +#define MIRROR_LEVEL 4 +#define MIRROR_ROOM 4 +#define MIRROR_TILEPOS 4 /* row 0, col 4 */ +#define TILE_MIRROR 0x0D + +static void place_mirror(uint8_t cur_room) +{ + if (pop_current_level != MIRROR_LEVEL) return; + pop_level_set_tile(MIRROR_ROOM, MIRROR_TILEPOS, TILE_MIRROR); + /* Комната зеркала обычно НЕ та, где дверь выхода, — игрок войдёт в неё + * позже и она отрисуется целиком. Но если она уже на экране, тайл надо + * вернуть: зеркало ДОБАВЛЯЕТ графику поверх пустого места, поэтому + * хватает обычной пометки (wipe пола + полный draw_tile). */ + if (cur_room == MIRROR_ROOM) + pop_set_redraw(MIRROR_TILEPOS, POP_RD_FLOOR, 2); +} + +static void animate_leveldoor(uint8_t *m, int8_t *type, uint8_t cur_room) { if (*type < 0) return; if (*type >= 3) { @@ -420,7 +450,17 @@ static void animate_leveldoor(uint8_t *m, int8_t *type) else *m = (uint8_t)(*m - sp); } else { if (*m < 43) (*m)++; - if (*m >= 43) { *m = 43; *type = -1; pop_leveldoor_open = 1; } + if (*m >= 43) { + *m = 43; *type = -1; + /* Условие оригинала — переход ИЗ 0 или 2, то есть ровно один раз + * за открытие; иначе зеркало ставилось бы заново каждый кадр, + * пока дверь стоит открытой. */ + if (pop_leveldoor_open == 0 || pop_leveldoor_open == 2) { + pop_leveldoor_open = 1; + place_mirror(cur_room); + } + pop_leveldoor_open = 1; + } } } @@ -538,7 +578,7 @@ void pop_process_trobs(uint8_t cur_room) __banked animate_door(&mod[tp], &type); break; case TILE_LEVELDOOR: - animate_leveldoor(&mod[tp], &type); + animate_leveldoor(&mod[tp], &type, cur_room); break; case TILE_TORCH: case TILE_TORCH_DEBRIS: diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py index 322bee6..713ce3c 100644 --- a/applications/PoP/toolchain/pop_pack_bg.py +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -43,8 +43,9 @@ OUT_DIR = os.path.join(HERE, "..", "poc", "res", "bg") # Спрайты FORE-слоя (add_foretable в seg008 — «перед персонажем»): фронты # столбов, ворот, дебриса. См. tile_table.fore_id + анализ pop_background_strategy. +# 119..123 — фронт чомпера и его кровь; 77 — фронт ЗЕРКАЛА (MIRROR_ENV_IDS). FORE_ENV_IDS = {9, 12, 49, 88, 91, 95, 100} | {106, 107, 108, 109, 110} \ - | {119, 120, 121, 122, 123} # фронт чомпера + его кровь + | {119, 120, 121, 122, 123} | {77} # Пол-оверлей подъёма (floor_left_overlay, draw_floor_overlay seg008): рисуется # рантаймом поверх нижней части Kid при подтягивании на пол у кромки — НЕ @@ -177,6 +178,16 @@ SRC_DIRS = None # ставит main() по выбранному н # добавляем явно — иначе при переключении набора в этих местах будет дыра. PALACE_ENV_IDS = {78, 80, 81, 82, 83, 84, 145} +# ЗЕРКАЛО (tile_table[0x0D] = база 75, фронт 77; 42/43 — общие грани пола). +# Тайла 13 НЕТ НИ В ОДНОМ уровне статически (проверено перебором всех 15 +# res200N.bin): оригинал СТАВИТ его в рантайме, когда дверь выхода уровня 4 +# дорисовала открытие — animate_leveldoor, seg007:0457: +# curr_room_tiles[curr_tilepos] = tiles_13_mirror; +# Значит в used от render_room эти id не попадают и без явного добавления на +# месте зеркала был бы чёрный провал (те же грабли, что у нажатой кнопки и +# дрожащей плиты — memory pop_atlas_dynamic_ids). +MIRROR_ENV_IDS = {75, 77} + ENV_SHIFT = 4 # env фон: страница = id>>SHIFT, idx = id&MASK. # Было 5: паласный набор добавляет куски верха # двери (78..84), и страница 2 переваливала за @@ -492,7 +503,8 @@ def main(): | CHOMPER_BLOOD_FORE_IDS | OPENER_NOFLOOR_ENV_IDS # + opener без пола слева | LEVELDOOR_ENV_IDS # + дверь уровня (створка/лестница) - | PALACE_ENV_IDS) # + куски, что рисует только палас + | PALACE_ENV_IDS # + куски, что рисует только палас + | MIRROR_ENV_IDS) # + зеркало (ставится в рантайме) wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL) env_bg = [i for i in env_ids if i not in FORE_ENV_IDS]