From 47a4b084c4abdb4aaac1790c53b44d6c564abafc Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Tue, 25 Aug 2026 13:58:36 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B5=D0=BD=D1=8E,=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D1=83=D1=81-=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D0=BE=D0=BB=D0=BE=D1=87=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=B3=D1=80=D1=8B:=20title/intro/cutscene/HoF,=20POP.CFG?= =?UTF-8?q?,=20=D0=BF=D0=B0=D0=BB=D0=B8=D1=82=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Эта сессия (меню + текст в служебных полосах): * Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках (UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space — равноправные клавиши действия (ui_action_down). * Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и ABOUT. About показывает тот же текст, что стартовый Sprinter screen, минус строка про клавиши — общий about_text(), чтобы экраны не разъехались. CONTROLS собирается таблицей и центрируется по фактическому числу строк. * GAME PAUSED переехала в нижнюю статус-строку, как в оригинале (SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы, а не пункт меню. POP_HP_Y вынесен в pop_cdraw.h — полосу делят два модуля. * pop_status.c (банк 9) — текст в обеих служебных полосах. Нижняя: порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF, 24 тика). Верхняя отладочная переведена с палочек на текст «Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с своим форматированием чисел (без printf и без деления). Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2 не растёт, весь рендер в банке. Бюджет после правок не изменился (_CODE 23981, куча 267 Б). * Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы не блитятся вовсе, а вход в комнату заливает только игровое поле (pop_screen_fill_field) — борта от комнаты к комнате не меняются. * Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие ворот (4) и открытие двери покоев (51), из которой входит Джафар. * docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в статус-строке: геометрия, семантика text_time_total как идентификатора сообщения, мигание, рестарт по истечении 36/288. Вместе с этим выкладывается накопленная работа по оболочке полной игры: автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo, Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и fade, звуковой набор, host-тесты на новые швы. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/README.md | 2 + applications/PoP/docs/full_game_plan.md | 76 +- applications/PoP/docs/impl_diff.md | 47 + applications/PoP/docs/menu_settings_plan.md | 185 ++- applications/PoP/docs/palette_plan.md | 297 ++++ applications/PoP/docs/sound_plan.md | 45 +- applications/PoP/docs/status_line_text.md | 201 +++ applications/PoP/poc/res/bg/pal_env4.atl | Bin 6976 -> 6976 bytes applications/PoP/poc/res/bg/pop_env4.atl | Bin 6992 -> 6992 bytes applications/PoP/poc/res/bg/pop_pot.atl | Bin 5374 -> 5374 bytes applications/PoP/poc/res/font/font.atl | Bin 0 -> 7667 bytes applications/PoP/poc/res/kid/kid_data.bin | Bin 3720 -> 4150 bytes applications/PoP/poc/res/pv/a0.atl | Bin 0 -> 15848 bytes applications/PoP/poc/res/pv/a1.atl | Bin 0 -> 16013 bytes applications/PoP/poc/res/pv/a2.atl | Bin 0 -> 11142 bytes applications/PoP/poc/res/pv/b0_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/b0_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/b0_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/b0_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/b0_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/h0.atl | Bin 0 -> 4776 bytes applications/PoP/poc/res/pv/j0.atl | Bin 0 -> 14453 bytes applications/PoP/poc/res/pv/j1.atl | Bin 0 -> 14718 bytes applications/PoP/poc/res/pv/j2.atl | Bin 0 -> 14081 bytes applications/PoP/poc/res/pv/p0_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p0_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p0_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p0_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p0_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p1_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p1_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p1_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p1_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p1_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p2_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p2_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p2_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p2_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p2_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p3_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p3_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p3_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p3_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p3_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p4_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p4_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p4_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p4_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p4_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p5_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p5_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p5_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p5_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p5_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p6_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p6_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p6_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p6_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p6_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/p7_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p7_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p7_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p7_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/p7_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/pv.pal | Bin 0 -> 1024 bytes applications/PoP/poc/res/pv/s0_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s0_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s0_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s0_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s0_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/s1_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s1_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s1_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s1_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s1_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/s2_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s2_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s2_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s2_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s2_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/s3_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s3_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s3_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s3_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s3_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/s4_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s4_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s4_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s4_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/pv/s4_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/pv/story.pal | Bin 0 -> 1024 bytes applications/PoP/poc/res/pv/t0.atl | Bin 0 -> 2804 bytes applications/PoP/poc/res/sound/s0.bin | 2 +- applications/PoP/poc/res/sound/s1.bin | 3 +- applications/PoP/poc/res/sound/s2.bin | Bin 16384 -> 16384 bytes applications/PoP/poc/res/sound/s3.bin | Bin 16384 -> 16384 bytes applications/PoP/poc/res/sound/s4.bin | 6 +- applications/PoP/poc/res/sound/s5.bin | 2 +- applications/PoP/poc/res/sound/s6.bin | 2 +- applications/PoP/poc/res/sound/s7.bin | 2 +- applications/PoP/poc/res/sound/s8.bin | 1 + applications/PoP/poc/res/sound/s9.bin | 1 + applications/PoP/poc/res/title/t0_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t0_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t0_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t0_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t0_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/title/t1_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t1_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t1_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t1_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t1_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/title/t2_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t2_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t2_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t2_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t2_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/title/t3_0.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t3_1.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t3_2.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t3_3.atl | Bin 0 -> 15796 bytes applications/PoP/poc/res/title/t3_4.atl | Bin 0 -> 1396 bytes applications/PoP/poc/res/title/title.pal | Bin 0 -> 64 bytes applications/PoP/roomtest/BUGS_CLOSED.md | 166 ++ applications/PoP/roomtest/BUGS_OPEN.md | 229 +++ applications/PoP/roomtest/CLAUDE.md | 3 +- applications/PoP/roomtest/Makefile | 141 +- applications/PoP/roomtest/_pop_config.h | 39 + applications/PoP/roomtest/guards.c | 29 +- applications/PoP/roomtest/kid_data.h | 5 + applications/PoP/roomtest/new bugs | 8 +- applications/PoP/roomtest/pop_app.c | 133 ++ applications/PoP/roomtest/pop_app.h | 63 + applications/PoP/roomtest/pop_cdraw.c | 80 +- applications/PoP/roomtest/pop_cdraw.h | 6 + applications/PoP/roomtest/pop_cfg.h | 19 + applications/PoP/roomtest/pop_cheat.h | 14 +- applications/PoP/roomtest/pop_config.c | 192 +++ applications/PoP/roomtest/pop_ctrl.c | 77 +- applications/PoP/roomtest/pop_ctrl.h | 6 + applications/PoP/roomtest/pop_cutscene.c | 55 + applications/PoP/roomtest/pop_cutscene.h | 49 + applications/PoP/roomtest/pop_demo.c | 73 + applications/PoP/roomtest/pop_demo.h | 17 + applications/PoP/roomtest/pop_flow.c | 48 + applications/PoP/roomtest/pop_flow.h | 40 + applications/PoP/roomtest/pop_font.h | 23 + applications/PoP/roomtest/pop_guard.h | 16 + applications/PoP/roomtest/pop_guard_cold.c | 2 +- applications/PoP/roomtest/pop_hof.c | 370 +++++ applications/PoP/roomtest/pop_hof.h | 15 + applications/PoP/roomtest/pop_intro.c | 1383 +++++++++++++++++ applications/PoP/roomtest/pop_intro.h | 23 + applications/PoP/roomtest/pop_kid.c | 27 +- applications/PoP/roomtest/pop_level.h | 14 +- applications/PoP/roomtest/pop_level_cold.c | 34 +- applications/PoP/roomtest/pop_map.c | 56 +- applications/PoP/roomtest/pop_menu.c | 757 +++++++++ applications/PoP/roomtest/pop_menu.h | 45 + applications/PoP/roomtest/pop_pal.c | 114 ++ applications/PoP/roomtest/pop_pal.h | 43 + applications/PoP/roomtest/pop_qsave.c | 48 +- applications/PoP/roomtest/pop_qsave.h | 8 + applications/PoP/roomtest/pop_qsave_io.c | 1 + applications/PoP/roomtest/pop_settings.h | 39 + applications/PoP/roomtest/pop_sfx.c | 8 +- applications/PoP/roomtest/pop_sound_tbl.h | 68 +- applications/PoP/roomtest/pop_status.c | 303 ++++ applications/PoP/roomtest/pop_status.h | 56 + applications/PoP/roomtest/pop_timer.c | 63 + applications/PoP/roomtest/pop_timer.h | 44 + applications/PoP/roomtest/pop_title.c | 130 ++ applications/PoP/roomtest/pop_title.h | 17 + applications/PoP/roomtest/pop_tune.h | 5 - applications/PoP/roomtest/pop_ui.c | 299 ++++ applications/PoP/roomtest/pop_ui.h | 78 + applications/PoP/roomtest/roomtest.c | 381 ++++- applications/PoP/roomtest/roomtest_cold.c | 370 +++-- applications/PoP/roomtest/roomtest_cold.h | 16 +- applications/PoP/roomtest/tests-host/Makefile | 40 +- applications/PoP/roomtest/tests-host/stubs.c | 7 + applications/PoP/roomtest/tests-host/t_app.c | 135 ++ applications/PoP/roomtest/tests-host/t_cfg.c | 197 +++ .../PoP/roomtest/tests-host/t_cutscene.c | 46 + applications/PoP/roomtest/tests-host/t_demo.c | 89 ++ applications/PoP/roomtest/tests-host/t_flow.c | 52 + applications/PoP/roomtest/tests-host/t_gate.c | 202 +++ applications/PoP/roomtest/tests-host/t_phys.c | 15 + .../PoP/roomtest/tests-host/t_timer.c | 116 ++ .../PoP/toolchain/pop_extract_kid_data.py | 18 +- applications/PoP/toolchain/pop_pack_bg.py | 11 +- applications/PoP/toolchain/pop_pack_sound.py | 13 +- docs/libc-reference.md | 6 +- libbgi/_bgi.h | 1 + libbgi/bgi256/_bgi_mode_set.c | 3 +- libbgi/common/_bgi_state.c | 1 + libbgi/common/atlas_load.c | 12 +- libbgi/common/initgraph.c | 3 + libbgi/common/initgraph_black.c | 27 + libbgi/include/graphics.h | 4 + libc/include/kbd_raw.h | 5 + libc/kbd/kbd_raw_any_down.c | 17 + toolchain/pop_extract_font.py | 185 +++ toolchain/pop_pack_intro.py | 313 ++++ toolchain/pop_pack_title.py | 126 ++ 205 files changed, 8446 insertions(+), 418 deletions(-) create mode 100644 applications/PoP/docs/palette_plan.md create mode 100644 applications/PoP/docs/status_line_text.md create mode 100644 applications/PoP/poc/res/font/font.atl create mode 100644 applications/PoP/poc/res/pv/a0.atl create mode 100644 applications/PoP/poc/res/pv/a1.atl create mode 100644 applications/PoP/poc/res/pv/a2.atl create mode 100644 applications/PoP/poc/res/pv/b0_0.atl create mode 100644 applications/PoP/poc/res/pv/b0_1.atl create mode 100644 applications/PoP/poc/res/pv/b0_2.atl create mode 100644 applications/PoP/poc/res/pv/b0_3.atl create mode 100644 applications/PoP/poc/res/pv/b0_4.atl create mode 100644 applications/PoP/poc/res/pv/h0.atl create mode 100644 applications/PoP/poc/res/pv/j0.atl create mode 100644 applications/PoP/poc/res/pv/j1.atl create mode 100644 applications/PoP/poc/res/pv/j2.atl create mode 100644 applications/PoP/poc/res/pv/p0_0.atl create mode 100644 applications/PoP/poc/res/pv/p0_1.atl create mode 100644 applications/PoP/poc/res/pv/p0_2.atl create mode 100644 applications/PoP/poc/res/pv/p0_3.atl create mode 100644 applications/PoP/poc/res/pv/p0_4.atl create mode 100644 applications/PoP/poc/res/pv/p1_0.atl create mode 100644 applications/PoP/poc/res/pv/p1_1.atl create mode 100644 applications/PoP/poc/res/pv/p1_2.atl create mode 100644 applications/PoP/poc/res/pv/p1_3.atl create mode 100644 applications/PoP/poc/res/pv/p1_4.atl create mode 100644 applications/PoP/poc/res/pv/p2_0.atl create mode 100644 applications/PoP/poc/res/pv/p2_1.atl create mode 100644 applications/PoP/poc/res/pv/p2_2.atl create mode 100644 applications/PoP/poc/res/pv/p2_3.atl create mode 100644 applications/PoP/poc/res/pv/p2_4.atl create mode 100644 applications/PoP/poc/res/pv/p3_0.atl create mode 100644 applications/PoP/poc/res/pv/p3_1.atl create mode 100644 applications/PoP/poc/res/pv/p3_2.atl create mode 100644 applications/PoP/poc/res/pv/p3_3.atl create mode 100644 applications/PoP/poc/res/pv/p3_4.atl create mode 100644 applications/PoP/poc/res/pv/p4_0.atl create mode 100644 applications/PoP/poc/res/pv/p4_1.atl create mode 100644 applications/PoP/poc/res/pv/p4_2.atl create mode 100644 applications/PoP/poc/res/pv/p4_3.atl create mode 100644 applications/PoP/poc/res/pv/p4_4.atl create mode 100644 applications/PoP/poc/res/pv/p5_0.atl create mode 100644 applications/PoP/poc/res/pv/p5_1.atl create mode 100644 applications/PoP/poc/res/pv/p5_2.atl create mode 100644 applications/PoP/poc/res/pv/p5_3.atl create mode 100644 applications/PoP/poc/res/pv/p5_4.atl create mode 100644 applications/PoP/poc/res/pv/p6_0.atl create mode 100644 applications/PoP/poc/res/pv/p6_1.atl create mode 100644 applications/PoP/poc/res/pv/p6_2.atl create mode 100644 applications/PoP/poc/res/pv/p6_3.atl create mode 100644 applications/PoP/poc/res/pv/p6_4.atl create mode 100644 applications/PoP/poc/res/pv/p7_0.atl create mode 100644 applications/PoP/poc/res/pv/p7_1.atl create mode 100644 applications/PoP/poc/res/pv/p7_2.atl create mode 100644 applications/PoP/poc/res/pv/p7_3.atl create mode 100644 applications/PoP/poc/res/pv/p7_4.atl create mode 100644 applications/PoP/poc/res/pv/pv.pal create mode 100644 applications/PoP/poc/res/pv/s0_0.atl create mode 100644 applications/PoP/poc/res/pv/s0_1.atl create mode 100644 applications/PoP/poc/res/pv/s0_2.atl create mode 100644 applications/PoP/poc/res/pv/s0_3.atl create mode 100644 applications/PoP/poc/res/pv/s0_4.atl create mode 100644 applications/PoP/poc/res/pv/s1_0.atl create mode 100644 applications/PoP/poc/res/pv/s1_1.atl create mode 100644 applications/PoP/poc/res/pv/s1_2.atl create mode 100644 applications/PoP/poc/res/pv/s1_3.atl create mode 100644 applications/PoP/poc/res/pv/s1_4.atl create mode 100644 applications/PoP/poc/res/pv/s2_0.atl create mode 100644 applications/PoP/poc/res/pv/s2_1.atl create mode 100644 applications/PoP/poc/res/pv/s2_2.atl create mode 100644 applications/PoP/poc/res/pv/s2_3.atl create mode 100644 applications/PoP/poc/res/pv/s2_4.atl create mode 100644 applications/PoP/poc/res/pv/s3_0.atl create mode 100644 applications/PoP/poc/res/pv/s3_1.atl create mode 100644 applications/PoP/poc/res/pv/s3_2.atl create mode 100644 applications/PoP/poc/res/pv/s3_3.atl create mode 100644 applications/PoP/poc/res/pv/s3_4.atl create mode 100644 applications/PoP/poc/res/pv/s4_0.atl create mode 100644 applications/PoP/poc/res/pv/s4_1.atl create mode 100644 applications/PoP/poc/res/pv/s4_2.atl create mode 100644 applications/PoP/poc/res/pv/s4_3.atl create mode 100644 applications/PoP/poc/res/pv/s4_4.atl create mode 100644 applications/PoP/poc/res/pv/story.pal create mode 100644 applications/PoP/poc/res/pv/t0.atl create mode 100644 applications/PoP/poc/res/sound/s8.bin create mode 100644 applications/PoP/poc/res/sound/s9.bin create mode 100644 applications/PoP/poc/res/title/t0_0.atl create mode 100644 applications/PoP/poc/res/title/t0_1.atl create mode 100644 applications/PoP/poc/res/title/t0_2.atl create mode 100644 applications/PoP/poc/res/title/t0_3.atl create mode 100644 applications/PoP/poc/res/title/t0_4.atl create mode 100644 applications/PoP/poc/res/title/t1_0.atl create mode 100644 applications/PoP/poc/res/title/t1_1.atl create mode 100644 applications/PoP/poc/res/title/t1_2.atl create mode 100644 applications/PoP/poc/res/title/t1_3.atl create mode 100644 applications/PoP/poc/res/title/t1_4.atl create mode 100644 applications/PoP/poc/res/title/t2_0.atl create mode 100644 applications/PoP/poc/res/title/t2_1.atl create mode 100644 applications/PoP/poc/res/title/t2_2.atl create mode 100644 applications/PoP/poc/res/title/t2_3.atl create mode 100644 applications/PoP/poc/res/title/t2_4.atl create mode 100644 applications/PoP/poc/res/title/t3_0.atl create mode 100644 applications/PoP/poc/res/title/t3_1.atl create mode 100644 applications/PoP/poc/res/title/t3_2.atl create mode 100644 applications/PoP/poc/res/title/t3_3.atl create mode 100644 applications/PoP/poc/res/title/t3_4.atl create mode 100644 applications/PoP/poc/res/title/title.pal create mode 100644 applications/PoP/roomtest/_pop_config.h create mode 100644 applications/PoP/roomtest/pop_app.c create mode 100644 applications/PoP/roomtest/pop_app.h create mode 100644 applications/PoP/roomtest/pop_cfg.h create mode 100644 applications/PoP/roomtest/pop_config.c create mode 100644 applications/PoP/roomtest/pop_cutscene.c create mode 100644 applications/PoP/roomtest/pop_cutscene.h create mode 100644 applications/PoP/roomtest/pop_demo.c create mode 100644 applications/PoP/roomtest/pop_demo.h create mode 100644 applications/PoP/roomtest/pop_flow.c create mode 100644 applications/PoP/roomtest/pop_flow.h create mode 100644 applications/PoP/roomtest/pop_font.h create mode 100644 applications/PoP/roomtest/pop_hof.c create mode 100644 applications/PoP/roomtest/pop_hof.h create mode 100644 applications/PoP/roomtest/pop_intro.c create mode 100644 applications/PoP/roomtest/pop_intro.h create mode 100644 applications/PoP/roomtest/pop_menu.c create mode 100644 applications/PoP/roomtest/pop_menu.h create mode 100644 applications/PoP/roomtest/pop_pal.c create mode 100644 applications/PoP/roomtest/pop_pal.h create mode 100644 applications/PoP/roomtest/pop_settings.h create mode 100644 applications/PoP/roomtest/pop_status.c create mode 100644 applications/PoP/roomtest/pop_status.h create mode 100644 applications/PoP/roomtest/pop_timer.c create mode 100644 applications/PoP/roomtest/pop_timer.h create mode 100644 applications/PoP/roomtest/pop_title.c create mode 100644 applications/PoP/roomtest/pop_title.h create mode 100644 applications/PoP/roomtest/pop_ui.c create mode 100644 applications/PoP/roomtest/pop_ui.h create mode 100644 applications/PoP/roomtest/tests-host/t_app.c create mode 100644 applications/PoP/roomtest/tests-host/t_cfg.c create mode 100644 applications/PoP/roomtest/tests-host/t_cutscene.c create mode 100644 applications/PoP/roomtest/tests-host/t_demo.c create mode 100644 applications/PoP/roomtest/tests-host/t_flow.c create mode 100644 applications/PoP/roomtest/tests-host/t_gate.c create mode 100644 applications/PoP/roomtest/tests-host/t_timer.c create mode 100644 libbgi/common/initgraph_black.c create mode 100644 libc/kbd/kbd_raw_any_down.c create mode 100644 toolchain/pop_extract_font.py create mode 100644 toolchain/pop_pack_intro.py create mode 100644 toolchain/pop_pack_title.py diff --git a/applications/PoP/docs/README.md b/applications/PoP/docs/README.md index bc01fe9..2d9d46d 100644 --- a/applications/PoP/docs/README.md +++ b/applications/PoP/docs/README.md @@ -16,6 +16,8 @@ | [`quicksave_plan.md`](quicksave_plan.md) | **QuickSave/QuickLoad** (✅ реализовано, F6/F9, POP.SAV+BAK): разбор (это enhancement SDLPoP, в оригинале 1989 его НЕТ), инвентаризация нашего состояния, формат снимка 'POPQ' v3, шаги QS1..QS6. Справочник. 2026-08-22 | | [`full_game_plan.md`](full_game_plan.md) | **Полноценная игра**: app state machine, title/intro, demo level 0, таймер, cutscenes, уровни 1..14, ending и Hall of Fame. Уровень 15 исключён. 2026-08-21 | | [`menu_settings_plan.md`](menu_settings_plan.md) | **Pause menu и Settings**: QuickSave/QuickLoad в основном menu, `POP.CFG`, один `POP.SAV` + `POP.BAK`, текущий VANILLA и задел под ENHANCED; §10 — выбор UI-рендера (текстовые строки + свой растровый рендерер, референс SDLPoP: два шрифта), restart без подтверждения. 2026-08-22 | +| [`palette_plan.md`](palette_plan.md) | **Палитры и fade**: карта всех 256 слотов (kid.pal/title/story, тайлсеты dungeon/palace, стражи), механика fade (4 ступени vs ~64 у SDLPoP), план модуля `pop_pal.c` (API load/apply/black) + переход уровня через fade. 2026-08-23 | +| [`status_line_text.md`](status_line_text.md) | **Строка HP как статус-строка**: полная инвентаризация ВСЕХ текстов SDLPoP в `rect_bottom_text` (геометрия, семантика `text_time_total`, мигание, рестарт по истечении) + что из этого уже есть у нас. 2026-08-25 | | [`levels_plan.md`](levels_plan.md) | Следующий этап: уровни 2+, второй тайлсет, читы SDLPoP | | [`levels_12_15_plan.md`](levels_12_15_plan.md) | **Уровни 12/13** (тень, Джафар, падающие плиты) + что такое 14/15 и 0. 2026-08-13 | | [`midtable_analysis.md`](midtable_analysis.md) | **Слои отрисовки**: как устроены back/mid/fore и objtable в оригинале, чего стоит порт, развилки. 2026-08-13 | diff --git a/applications/PoP/docs/full_game_plan.md b/applications/PoP/docs/full_game_plan.md index 33c2b39..6c0a643 100644 --- a/applications/PoP/docs/full_game_plan.md +++ b/applications/PoP/docs/full_game_plan.md @@ -1,6 +1,9 @@ # От roomtest к полноценной игре — сценарий и оболочка -Статус: **согласованный план, код не начат** (2026-08-21). +Статус: **частично реализовано; аудит обновлён 2026-08-24**. Ранее пометка +«завершены FG0–FG12» была неверной: для многих этапов уже есть код и +host-тесты, но их критерии приёмки на Sprinter ещё не выполнены. Фактический +статус каждого FG приведён в [§14](#14-этапы-реализации). Этот документ описывает превращение текущего игрового цикла `roomtest` в законченную игру: заставка, интро, демонстрационный уровень, @@ -72,17 +75,24 @@ attract loop после демо или финала. Джаффар, специальные события, checkpoint, переходы уровней, бесшовный выход 12-го уровня, перенос максимального HP и звуковые эффекты. -Отсутствуют: +Поверх игрового цикла уже добавлены автомат оболочки, title/story, demo +уровень 0, global timer, сценарный интерпретатор, level-flow, ending и Hall +of Fame. Полный маршрут также собирается в HDD-образ. -- верхнеуровневая оболочка приложения; -- title/story sequence и текстовые экраны; -- уровень 0 и записанное управление демо; -- общий cutscene engine и PV-ресурсы; -- сцены между уровнями; -- глобальный таймер и time-expired sequence; -- корректный переход из уровня 14 в ending; -- финальная сцена и Hall of Fame; -- возврат к title без перезапуска программы. +Однако это **не означает готовность оболочки**. На момент аудита остаются +существенные незакрытые места: + +- lifecycle палитр: gameplay-переходы используют чёрный барьер без fade; + cold start и полный набор dungeon/palace переходов ещё не прошли приёмку; +- PV intro Princess/Jaffar уже покадровый (актёры, факелы, звёзды, часы, + молния и foreground-колонна); сцены перед 2/4/6 и длинной веткой 12 + анимируют факелы, звёзды и песок, а сцены 8/9 и короткая ветка 12 пока + используют статические позы с исходной длительностью; +- demo отображается с игровой палитрой, проходит второй разворот/зацеп и + доходит до боя; после смерти Кида корректно завершает цикл; +- time-expired, ending и Hall of Fame имеют маршрут и реализацию UI, но не + прошли сквозную MAME-проверку вместе с ресурсами и возвратом к title; +- нет полного регресса EMM/FD для каждого перехода состояния. ## 4. Архитектура: автомат состояний приложения @@ -218,6 +228,13 @@ title -> demo -> title повторяется неограниченно. Критерий: одинаковый игровой отрезок в NORMAL даёт то же уменьшение времени, что SDLPoP; сохранение/загрузка не добавляет и не отнимает тики. +Реализация FG4 живёт одним модулем `roomtest/pop_timer.c` в bank 9: +`60:719`, 720 тиков на минуту, счёт только в живом игровом кадре. Settings +хранит `TIME LIMIT: 60 MIN / UNLIMITED` в `POP.CFG`; старый семибайтный v1 +payload по-прежнему читается как `60 MIN`. Читы таймера повторяют SDLPoP, +но из-за занятого `+/-` используют F7 (−1 минута, не ниже одной) и F8 +(+1 минута). Состояние входит в QuickSave v4. + ## 10. Cutscene engine Сцены SDLPoP состоят из небольшого набора повторяемых команд. Вместо набора @@ -305,24 +322,26 @@ Hall of Fame хранится на HDD в отдельном версионир ## 14. Этапы реализации -| этап | результат | критерий приёмки | -|---|---|---| -| **FG0** | удалить уровень 15, формализовать 1..14 | HDD не содержит res2015; переход выше 14 невозможен | -| **FG1** | автомат состояний, текущая игра = PLAYING | старт/рестарт/выход проходят без рекурсии и утечки EMM | -| **FG2** | QuickSave/QuickLoad | критерии `quicksave_plan.md`, включая POP.BAK | -| **FG3** | минимальный pause menu | Resume/Save/Load/Restart/Settings/Quit работают | -| **FG4** | глобальный таймер | совпадение с SDLPoP и корректный save/load | -| **FG5** | text/full-screen/fade primitives | тестовые экраны и переходы на Sprinter | -| **FG6** | build info + оригинальный title | точный порядок, корректный пропуск | -| **FG7** | demo level 0 | бесконечный attract loop, ввод начинает игру | -| **FG8** | cutscene interpreter + intro | интро проходит и пропускается без утечек | -| **FG9** | сцены 2/4/6/8/9/12 | правильный вызов один раз перед уровнем | -| **FG10** | time expired | отдельная сцена и возврат на title | -| **FG11** | level 14 -> ending | полный маршрут после Джаффара | -| **FG12** | Hall of Fame | запись HDD, ввод имени, возврат к attract loop | +Легенда аудита: **✓** — критерий этапа закрыт; **~** — код существует, но +критерий приёмки ещё не закрыт; **○** — не начат. Статус отражает состояние +исходников и последней MAME-проверки на 2026-08-24, а не только наличие +модуля в bank 9. -QuickSave допускается реализовать до остальных частей оболочки: FG2 — одна -из ближайших самостоятельных задач. +| этап | статус | результат и фактическое состояние | критерий приёмки | +|---|---|---|---| +| **FG0** | ✓ | `POP_LEVEL_LAST=14`, HDD содержит `res2000..res2014`; `t_flow` отвергает 15 | HDD не содержит res2015; переход выше 14 невозможен | +| **FG1** | ~ | автомат `pop_app` и `t_app` реализованы; сквозной ресурсный lifecycle и контроль EMM/FD ещё не измерены | старт/рестарт/выход проходят без рекурсии и утечки EMM | +| **FG2** | ✓ | QuickSave/QuickLoad с `POP.SAV` и `POP.BAK`; отдельно проверен в MAME 2026-08-22 | критерии `quicksave_plan.md`, включая POP.BAK | +| **FG3** | ✓ | pause menu, Settings, подтверждения и двойной буфер реализованы; меню проверялось в MAME; добавлены SDLPoP-звуки навигации и защита CBL вокруг полного redraw/файловых операций | Resume/Save/Load/Restart/Settings/Quit работают | +| **FG4** | ~ | `pop_timer`, настройка unlimited, F7/F8 и состояние QuickSave реализованы; есть host-тест, но нет буквального сравнения темпа со SDLPoP на всех переходах | совпадение с SDLPoP и корректный save/load | +| **FG5** | ~ | text/full-screen/fade примитивы есть; для входа в первый уровень и границ уровней выбран мгновенный чёрный барьер без fade: CBL и яркая новая палитра включаются только после подготовки обеих страниц; Level 1 проверен в MAME | тестовые экраны и переходы на Sprinter | +| **FG6** | ~ | title-ресурсы и порядок кадров реализованы; Enter на title и Esc на первом story в MAME переводят прямо в `FIRST_LEVEL`, минуя demo; полная cold-boot приёмка fade остаётся в FG5 | основной титул/Presents/название/Mechner идут в точном порядке `show_title()`; Enter/Space/Esc/стрелки прерывают ожидание; story/intro продолжит FG8 | +| **FG7** | ✓ | level 0, исходная таблица `demo_moves`, demo HP=4 и блокировка игрового UI реализованы; исправлены зеркалирование auto-control, боевой AI Кида и завершение после смерти; в MAME demo проходит разворот/зацеп, доходит до боя и возвращается в attract-цикл без повторного убийства | `res2000.bin`, исходная `demo_moves`, demo HP=4; бесконечный attract loop, любой ввод начинает чистую новую игру; Pause/QuickSave/читы/таймер отключены | +| **FG8** | ~ | data-driven interpreter и покадровый PV intro работают; в MAME проверены актёры, факелы, звёзды 1x1, часы/песок, palette-0 lightning и foreground-колонна; Enter/Esc переводят прямо в `FIRST_LEVEL`; временный темп 12,5 FPS и TODO точного pacing записаны в `impl_diff.md` | story/PV intro проходит, любой raw-ввод пропускает его без удержания EMM-страниц | +| **FG9** | ~ | `pop_flow` корректно маршрутизирует 2/4/6/8/9/12 и ветку <=5 минут (`t_flow`); 2/4/6 и длинная 12 уже обновляют часы, песок, факелы и звёзды каждые 5 кадров Sprinter; длительности всех веток сверены с SDLPoP: 2/4/6/12 — 2,6 с, 8 — 6,0 с, 9 — 7,2 с; входная клавиша gameplay/Shift+L поглощается до сцены, а новое нажатие делает skip; анимации мыши/Princess в 8/9 и разворот Princess в короткой 12 ещё статичны | таблица flow переводит в CUTSCENE ровно перед 2/4/6/8/9/12; scene 12 выбирает короткий вариант при <=5 минутах | +| **FG10** | ~ | переход TIME_EXPIRED и экран существуют, но это ещё статическая PV-стадия; сквозной MAME-маршрут не принят | PV-сцена истечения с пропуском, затем возврат на title/attract; новая игра сбрасывает таймер | +| **FG11** | ~ | room 5 уровня 14 переводит в ENDING (`t_flow`); объятие/мышь заменены статической стадией, полный маршрут не принят | room 5 уровня 14 переводит в ENDING; PV-финал и Hail-экран возвращают управление оболочке | +| **FG12** | ~ | версионированный `POP.HOF`, ввод имени и восстановление после повреждённого файла реализованы; нужна сквозная MAME-проверка ending → HOF → title | версионированный `POP.HOF`, ввод имени raw-клавиатурой, повреждённый файл = пустая таблица, затем title/attract | ## 15. Проверки @@ -346,4 +365,3 @@ QuickSave допускается реализовать до остальных - replay/recording; - точная эмуляция SDL video/controller options; - профиль ENHANCED и индивидуальные switches fixes. - diff --git a/applications/PoP/docs/impl_diff.md b/applications/PoP/docs/impl_diff.md index 4a86f4f..55dac36 100644 --- a/applications/PoP/docs/impl_diff.md +++ b/applications/PoP/docs/impl_diff.md @@ -505,6 +505,53 @@ BUG-CHEAT-FIGHT-1 (выход из боя), и лечится там же. «страж давит сильнее». Режим NORMAL (дефолт) даёт 102,4 мс — см. `frame_pacing_plan.md`. +## PV intro: единые 12,5 FPS вместо переменных 10/7,5/8,57 FPS + +**Оригинал.** `proc_cutscene_frame()` двигает последовательности через +`cutscene_frame_time`: 6 тиков 60 Гц в начале, 8 после первой речи и 7 во +время заклинания. Это соответственно 10, 7,5 и примерно 8,57 FPS. + +**У нас (осознанное временное отличие).** Один логический кадр PV держится +четыре физических кадра Sprinter: номинально 50/4 = 12,5 FPS. Молния живёт +на отдельной физической шкале и не растягивается этим делителем. Если полная +отрисовка пересечёт дополнительный фронт, реальная частота может упасть до +10 FPS — это допустимо на текущем этапе, но должно быть измерено. + +**TODO.** Перевести PV-сцену на тот же anchor-based механизм точного темпа, +который gameplay использует через `pop_beam_sample/pop_pace_end`: измерять +число реально прошедших фронтов во время сборки кадра, держать период ровно +четыре фронта при укладывании в бюджет и явно учитывать overrun. После замера +можно вернуть точные переменные интервалы SDLPoP без накопления фазы. + +## Межуровневые PV-сцены: сохранён реальный период 100 мс + +Это правило не относится к временному темпу основного Princess/Jaffar intro +выше. `reset_cutscene()` SDLPoP задаёт для сцен перед уровнями период +6 кадров при 60 Гц, то есть 100 мс. На Sprinter тот же период получается +ровно как 5 кадров при 50 Гц. + +Суммы вызовов `proc_cutscene_frame()` перенесены без изменения реального +времени: сцены 2/4/6 и обе ветки 12 содержат 26 логических кадров (130 +физических, 2,6 с), сцена 8 — 60 (300, 6,0 с), сцена 9 — 72 (360, 7,2 с). +Fade in/out в эти числа не входят, как и в оригинале. + +## Gameplay: загрузка уровней через чёрный cut, без fade + +**Оригинал.** На границах игровых уровней использует fade out/in. + +**У нас (решение пользователя 2026-08-24).** Вход в первый уровень и +переход между уровнями выполняются как `старый кадр -> чёрная палитра -> +подготовка -> новый кадр с новой палитрой`. Fade на этих двух маршрутах +отсутствует. Сюжетные title/story/PV переходы сохраняют собственные fade и +left-to-right эффекты. + +Чёрная палитра устанавливается до любого HDD I/O. Загрузчики guard и +tileset сами физически правят отдельные цветовые слоты, поэтому после них +чёрный экран подтверждается повторно. Зеркальные атласы уровня 9 готовятся +до финального источника палитры. CBL открывается последним: старый порядок +`level_switch -> CBL open -> BIOS fade` давал скрежет повторяющейся половины +аппаратного буфера на входе в Level 1; после перестановки баг исчез в MAME. + ## Тень: кайма силуэта не подкрашивается фоном **Оригинал.** Спрайт Тени не хранится — он кладётся ДВАЖДЫ: обычным diff --git a/applications/PoP/docs/menu_settings_plan.md b/applications/PoP/docs/menu_settings_plan.md index 875a684..bb1a52b 100644 --- a/applications/PoP/docs/menu_settings_plan.md +++ b/applications/PoP/docs/menu_settings_plan.md @@ -1,8 +1,8 @@ # Pause menu и Settings для Sprinter PoP -Статус: **согласованный план, код не начат** (2026-08-21; обновлён -2026-08-22 — решение о рендеринге UI см. §10, Restart Level/Game без -подтверждения). +Статус: **MS0, MS2 и MS4–MS8 выполнены** (2026-08-23). Pause menu, CFG, +Settings, диалоги, Controls и build screen находятся в bank 9. Решение о +рендеринге и затемнении — §10. Связанные документы: @@ -41,12 +41,12 @@ acceleration, scaling, aspect ratio, rumble. UI берёт структуру SD ```text RESUME -QUICKSAVE -QUICKLOAD +QUICKSAVE (F6) +QUICKLOAD (F9) RESTART LEVEL SETTINGS RESTART GAME -QUIT +QUIT GAME ``` Поведение: @@ -56,9 +56,13 @@ QUIT - QuickSave/QuickLoad только взводят запрос, фактическая операция идёт на безопасной границе кадра; - QuickLoad disabled/показывает `NO QUICKLOAD`, если нет валидных SAV/BAK; +- перед QuickLoad из меню лёгкий probe проверяет заголовок и checksum обоих + файлов: валидный `POP.BAK` при отсутствующем/битом `POP.SAV` требует + отдельного `LOAD BACKUP?`, а не загружается молча; - Restart Level и Restart Game выполняются сразу, БЕЗ подтверждения - (2026-08-22): обе операции дёшево обратимы — Restart Level перечитывает - уровень, после Restart Game можно тут же сделать QuickLoad из POP.SAV; + (2026-08-22): Restart Level перечитывает уровень, Restart Game завершает + gameplay и возвращает к первому экрану title/intro; новая игра создаётся + общим LEVEL_LOAD только после skip/attract; - Quit требует подтверждения и закрывает файлы/каналы штатным путём; - меню недоступно в demo, cutscene, time-expired и ending; - отдельная debug-комбинация немедленного выхода может остаться только в @@ -86,6 +90,10 @@ BACK `Gameplay profile: VANILLA` показывается read-only: место в модели уже есть, но пользователь не может выбрать ещё не реализованный ENHANCED. +Изменения применяются немедленно к скорости, читам и звуку, но `POP.CFG` +записывается один раз при Back/Esc. На экране есть итог `SETTINGS SAVED` или +`SAVE ERROR`; во втором случае runtime-значения остаются рабочими. + Отладочные параметры `ROOMNAV`, border profiling, stop-frame и переключение double buffering не являются пользовательскими Settings. Они остаются compile-time/debug функциями и скрываются из release UI. @@ -215,9 +223,10 @@ Restart Level: Restart Game: - выполняется сразу, без подтверждения; -- завершить текущий gameplay session; -- освободить level/atlas/temporary EMM; -- создать новую игру с уровня 1 и новым глобальным таймером; +- завершить текущий gameplay session и вернуть автомат в TITLE; +- начать title/intro с самого первого экрана; +- создать новую игру с `FIRST_LEVEL` и новым глобальным таймером только + после пользовательского skip либо ввода в attract-demo; - настройки оставить; - QuickSave не удалять. @@ -234,7 +243,9 @@ Restart Game: - F6/F9 QuickSave/QuickLoad; - Ctrl+S sound; - P speed; -- доступные cheats, только если они включены. +- доступные cheats, только если они включены: K/Kill Guard, I/Immortal, + Shift+L/Next Level, U/Flip Screen и F7/F8/Time −/+ на отдельных понятных + строках. Нижней подсказки `Esc or Enter: Back` нет. ## 10. UI renderer и ввод @@ -321,38 +332,123 @@ On/Off и т.д.). ### 10.3. Наша реализация -- Новый банк (свободный номер, автонумерация sprinter-cc): код рендерера, +- Банк 9: код рендерера, шрифт, таблицы строк, автомат меню. Резидентно — только request-flag и вызов процесса на границе кадра (паттерн pop_qsave_io). -- Рендерер портирует контракт seg009, упрощённо: `font_type` + массив - глифов переменной ширины, блит монохромного глифа в теневую страницу - через W0-окно (как весь остальной код рисования), `draw_string(x,y,color)` - + центрирование по сумме ширин. Пропорциональность — сразу, API не меняется - от моноширинного. -- Шрифт — АССЕТ, генерируемый паковщиком toolchain из TTF (красивый, сразу - с кириллицей), а НЕ системный ZG. Быстрый прототип для MS4 — системный - CP866 знакогенератор через существующий `bios_get_zg` в буфер банка - (паттерн `gfx_load_default_font`, но буфер в банке, не `_gfx_font_buf`); - потом файл шрифта заменяется без смены API. +- Рендерер повторяет минимальный контракт seg009: пропорциональные глифы, + baseline, `draw_string` и центрирование по сумме advance. Блит идёт через + W0-атлас, в `GFX_BANK_SPRITE`: `0xFF` в атласе пропускается, а UI временный + и не портит теневую копию игрового фона. Перед каждым кадром UI `gfx_copy_page` переносит чистый + shadow видимой страницы в скрытую, затем готовый кадр показывается только + на следующем фронте. При выходе чистый фон тем же способом возвращается на + обе страницы и восстанавливается исходная visible-страница. Поэтому + перемещение выделения не показывает поэтапную перерисовку и не оставляет + следов на back buffer. +- Шрифт — АССЕТ из **оригинальных** `hc_small_font_data[]` и + `hc_font_data[]` SDLPoP, не системный ZG и не TTF. Паковщик + `toolchain/pop_extract_font.py` делает `FONT\\font.atl`: 95 ASCII-глифов + малого и 95 крупного шрифта (7667 Б). Номер ленты вычисляется из ASCII, + поэтому это один текстовый движок, а не атлас готовых надписей. - Двуязычность (eng/rus): строки храним в CP866 — латиница и кириллица одним байтовым порядком, одна кодировка на оба алфавита. Локаль = пара (указатель на таблицу строк, файл шрифта); переключатель — одна настройка. Русские строки длиннее английских ~10–15% — раскладку экранов и ширину колонок закладывать по русской. Второй язык можно добавить позже без переделки: сначала eng. -- Подсветка выделенного пункта: инверсия прямоугольника или контур + цвет, - как в SDLPoP. Цвета текста брать из стабильного диапазона палитры - 0x3A..0x3F (его никто не перезаписывает, проверено на борде-индикаторе). -- Фон открытого меню: затемнение замороженного кадра (запечь тёмный прямоуг. - в теневую копию страницы поверх сохранённого фона) — двойную перерисовку - игры под меню делать не нужно, игра стоит. -- Навигация: вверх/вниз по пунктам, left/right для значения, Enter/Esc; - edge-triggered поверх существующего `kbd_raw`. Звук навигации — menu tick - из имеющихся сэмплов. +- Визуальная композиция MS4 следует SDLPoP: замороженная сцена остаётся + открытой, поверх неё компактный центрированный список без чёрной карточки, + выбранная строка обведена тонким светло-серым контуром, а крупное + `GAME PAUSED` лежит в нижнем борту. Цвета текста и контура берутся из + стабильного диапазона палитры 0x37..0x3F. +- Фон открытого меню: снимок текущей палитры, затемнение всех слотов кроме + UI 0x37..0x3F и точное восстановление при выходе. Снимок хранится в + свободном хвосте EMM-страницы шрифта, не в W2. +- Навигация MS4: вверх/вниз, Enter/Esc, edge-triggered поверх `kbd_raw`. + Left/right и menu tick добавляются вместе с настройками на MS5. Первый UI может быть визуально простым. Критично отсутствие потери клавиш, предсказуемая пауза и отсутствие повреждения игрового back buffer. +### 10.4. Затенение экрана под меню — решение MS4 + +Режим меню виден сразу: bank 9 делает динамический снимок palette 0, +затемняет RGB-каналы вдвое и пишет одинаковый результат в обе экранные +палитры. Девять стабильных UI-слотов 0x37..0x3F не гасятся. При Resume/Enter +палитра восстанавливается из EMM-снимка. Это выбранный вариант Б ниже; +ступенчатый fade для роликов пока не нужен и остаётся отдельной будущей +задачей, а не причиной раздувать MS4. + +**Как сделано в SDLPoP** (`seg009.c`): + +- Меню: `draw_rect_with_alpha(&screen_rect, color_0_black, pause_menu_alpha)` + (menu.c:1364) — альфа-заливка чёрным поверх замороженного кадра средствами + SDL; нижняя полоса рисуется с alpha=0, чтобы сквозь неё просвечивало + «GAME PAUSED». Прямого аналога на Sprinter НЕТ (альфа-блендинг в железе + отсутствует) — это SDL-специфика, переносить нечего. +- Ролики/переходы: `fade_in_2/fade_out_2(rows)` (seg009.c:3947+, вызовы из + seg000.c) — ПОШАГОВОЕ затухание ПАЛИТРЫ к чёрному и обратно: палитра + копируется, каждая строка по 16 цветов гасится за несколько кадров + (`which_rows` маской выбирает, какие строки участвуют: 0x800/0x1000/...). + Вот этот механизм на Sprinter воспроизводим один в один. + +Отсюда рабочая гипотеза: наш примитив = «снимок текущей палитры → ступенчатое +приближение к затемнённой копии (кроме резервного блока для UI)», статично для +меню и анимированно для роликов/переходов. Варианты: + +**Вариант А — единая основная палитра (глобальный рефакторинг палитры).** + +1. Собрать ВСЕ палитры игры (уровневые наборы `pal_env*`, kid.pal, палитра + Тени и пр.) в одну общую 256-цветную; использовать её целиком всегда. + Сейчас переиспользования цветов НЕТ — каждая загрузка ассетов перезаписывает + слоты (см. pop_boot: kid.pal затирает тайловые цвета, приходится + восстанавливать `pop_bg_pal_apply`/`pop_shadow_pal_apply`). +2. Для затенения — затемнённая копия основной палитры, КРОМЕ зарезервированного + блока из 16 цветов для самого меню (кандидат — стандартные 16 цветов VGA). +3. Выход из меню — возврат к полной основной палитре. + +Плюс: решает попутно существующую боль с перезаписью палитр при загрузках. +Минус: большой разовый рефакторинг упаковщиков и всех загрузчиков атласов; +нужен аудит, что все цвета всех уровней влезают в 256. **Против говорит +план перевода камней подземелья на цвета VGA-версии PoP: там ряд уровней +несёт ДРУГУЮ палитру, отличную от SDLPoP (VDUNGEON/VPALACE каскад, +levels_plan.md), — единая палитра этому прямо противоречит.** + +**Вариант Б — динамический снимок текущей палитры (сейчас выглядит +предпочтительным).** + +1. При открытии меню прочитать всю текущую палитру, сохранить. +2. Записать затемнённую копию (кроме зарезервированного блока для меню). +3. При выходе — восстановить сохранённую. + +Плюс: локальная правка внутри меню, ничего в пайплайне ассетов не меняется; +работает при любой текущей палитре автоматически — включая будущие +уровне-специфичные палитры VGA-камней; тот же примитив ступенями даёт +fade-out/fade-in для роликов и переходов между уровнями (как fade_*_2 в +SDLPoP). Минус: чтение/запись 256 записей палитры при входе/выходе (раз на +открытие — дёшево); затемнение «на глаз» может по-разному выглядеть на разных +уровнях. + +Резервный блок 16 цветов нужен в ОБОИХ вариантах; текущий диапазон 0x37..0x3F +(стабильный, проверен) даёт 9 цветов — этого может не хватить на +текст+подсветку+рамку, тогда резервировать отдельный блок. + +**Следствие для архитектуры:** работа с цветом/палитрой должна собраться в +ОДИН модуль (сейчас она разбросана: gfx_pal_* вызовы в boot, pop_bg_pal_apply, +pop_shadow_pal_apply, вспышки урона в roomtest.c и т.д.). Модуль палитры — +единственный владелец записи в палитру и предоставляет примитивы, которые +понадобятся и меню, и роликам: + +```text +pal_snapshot()/pal_restore() — снимок/восстановление всей палитры +pal_dim(step) / pal_undim(step) — ступени затемнения (кроме резервного блока) +pal_fade_out(rows)/pal_fade_in(rows) — анимированное затухание по строкам + (порт fade_out_2/fade_in_2, seg009) +``` + +Меню уже использует snapshot+dim локально в bank 9. Когда появятся ролики, +выделить из него общий palette/fade-модуль; вспышка урона сможет переехать +туда же после отдельного аудита. + ## 11. Диалоги Общий диалог подтверждения: @@ -367,6 +463,10 @@ YES / NO Диалог не выполняет действие напрямую: он возвращает решение автомату меню, который формирует команду приложению. Так UI не зависит от gameplay-модулей. +По умолчанию выбран `NO`; Up/Down/Left/Right меняют ответ, Enter подтверждает, +Esc отменяет. Реализованы все три вопроса: Quit, Restore defaults и backup +QuickLoad. В Quit-dialog вопрос и `YES / [NO]` заключены в общую рамку; +отдельная строка `Enter: Select Esc: Cancel` не выводится. ## 12. Будущий ENHANCED @@ -387,15 +487,15 @@ YES / NO | этап | результат | критерий приёмки | |---|---|---| -| **MS0** | определить команды app/menu и структуру settings | UI не вызывает gameplay internals напрямую | +| **MS0** ✓ | определить команды app/menu и структуру settings | UI возвращает команду главному циклу; прямых gameplay-вызовов нет | | **MS1** | проверить запись/rename/copy на HDD DSS | crash/power-loss сценарий не теряет обе копии save | -| **MS2** | `POP.CFG`: defaults, load, validate, save | повреждённый CFG безопасно даёт defaults | +| **MS2** ✓ | `POP.CFG`: defaults, load, validate, save | v1 codec, будущий хвост, checksum; повреждённый CFG даёт defaults | | **MS3** | QuickSave hotkeys + POP.SAV/BAK | полный критерий `quicksave_plan.md` | -| **MS4** | текстовый рендерер + два шрифта (малый для пунктов, крупный для сообщений) + минимальный pause menu | рендерер строк (глифы, локали, центрирование); все семь пунктов доступны и корректно паузят игру | -| **MS5** | General/Gameplay Settings | значения применяются и переживают рестарт | -| **MS6** | dialogs + backup recovery | подтверждения и fallback на POP.BAK | -| **MS7** | Controls help | полная актуальная раскладка на экране | -| **MS8** | интеграция с title/build info | CFG применяется до первого экрана | +| **MS4** ✓ | текстовый рендерер + два шрифта (малый для пунктов, крупный для сообщений) + минимальный pause menu | SDLPoP fonts в одном W0-atlas, центрирование, dim/restore palette и tear-free page flip; все семь пунктов видимы, навигация и Resume/QuickSave работают в MAME | +| **MS5** ✓ | General/Gameplay Settings | значения применяются сразу и после Back/Esc записываются в POP.CFG | +| **MS6** ✓ | dialogs + backup recovery | подтверждения default-NO; QuickLoad спрашивает перед валидным POP.BAK | +| **MS7** ✓ | Controls help | показаны движение, action, menu, save/load, звук, speed и conditional cheats; MAME проверил отдельные K/I и Shift+L/U и возврат Esc ровно на один уровень | +| **MS8** ✓ | build info | CFG читается до первого показа; включаемый build screen получает ID и дату из Make/git | QuickSave (`MS1/MS3`) можно реализовать раньше визуального menu: сначала F6/F9 и сообщения, затем подключить те же команды к пунктам UI. @@ -409,8 +509,12 @@ F6/F9 и сообщения, затем подключить те же кома - Host: SAV invalid -> BAK valid; оба invalid -> NO QUICKLOAD. - MAME: F6, изменение сцены, F9; затем рестарт программы и повторный F9. - MAME: прервать запись/испортить SAV — BAK остаётся загружаемым. -- MAME: pause на бое/падении, Resume не меняет состояние и таймер. +- MAME: pause на бое/падении, Resume не меняет состояние и таймер; смена + выбранного пункта не показывает промежуточный кадр и после закрытия не + оставляет меню на второй странице. - MAME: Settings сохраняются после полного выхода и запуска с HDD. +- MAME: включить Show Sprinter screen, перезапустить `roomtest`, увидеть + build ID/date до первого игрового кадра и пропустить экран Esc/Enter/Space. - Проверка лимита 8 DSS handles на каждом error path. - `make size-check`; menu/text строки не должны съесть резидентный бюджет. @@ -423,4 +527,3 @@ F6/F9 и сообщения, затем подключить те же кома - key rebinding; - SDL visual/controller options; - фактическая реализация ENHANCED и individual fix switches. - diff --git a/applications/PoP/docs/palette_plan.md b/applications/PoP/docs/palette_plan.md new file mode 100644 index 0000000..8aeed34 --- /dev/null +++ b/applications/PoP/docs/palette_plan.md @@ -0,0 +1,297 @@ +# План: консолидация работы с палитрами + переход уровня через fade + +Статус: **этапы A и B реализованы; визуальная приёмка полного маршрута ещё +идёт** (2026-08-24). Палитры выделены в bank 10, а renderer cutscene/intro — +в bank 11, чтобы не переполнять bank 9 оболочки. +Обсуждение велось вокруг `roomtest/` (банк 9 — оболочка, fade из `pop_ui.c`). + +--- + +## 1. Текущее состояние: карта палитры + +Палитра Sprinter — 256 записей по 4 байта (B, G, R, 0) = 1 КБ на страницу. +У страниц дабл-буфера ДВЕ раздельные палитры (`gfx_pal_load(0,…)` и +`gfx_pal_load(1,…)` — почти всегда парой). BIOS читает буферы только из +#4000–#BFFF: банковую rodata напрямую отдавать нельзя (копия в стек/W2), +см. грабли `pop_guard_set_palette` и `bg_load_tile_pal`. + +### 1.1 Игровая палитра `KID\kid.pal` — раскладка слотов + +Собирается `toolchain/pop_pack_kid.py build_palette()`, грузится одним +`gfx_pal_fload` (перезаписывает все 256 записей). Атласы запекались под эти +индексы — менять раскладку нельзя без перепаковки ассетов. + +| Слоты | Назначение | Источник | Динамика | +|---|---|---|---| +| 0x00 | Цвет фона + **вспышка молнии** (подмена записи 0, `flash_bg` ← do_flash/set_bg_attr SDLPoP) | — | меняется в игре | +| 0x01–0x2F | Не закреплены (нули) | — | свободно | +| 0x30–0x3F | VGA16 — базовые 16 цветов для mono-блитов: пламя факелов, пузырьки зелья (+12 красный «лечение», +10 зелёный, +9 синий), кровь чомпера (12), дворцовая кладка mono (+6) | `VGA16[]` | статично | +| ↳ 0x37–0x3F | Поддиапазон **UI**: текст/рамка меню; единственное, что `keep_ui` не затемняет (`MENU_BORDER`=0x37) | — | — | +| 0x40–0x4F | chtab_1 пламя/зелья (`POT_PAL_BASE`) | VDUNGEON res150.pal | статично | +| 0x50–0x5F | **ENV фон тайлсета** (`POP_PAL_ENV`) | res200.pal набора | **меняется при смене тайлсета** | +| 0x60–0x6F | **WALL тайлсета** (`POP_PAL_WALL`) | res360.pal набора | **меняется при смене тайлсета** | +| 0x70–0x7F | Kid (`PAL_BASE`) | KID res400.pal | статично | +| 0x80–0x8F | Меч chtab_0 (`SWORD_PAL_BASE`) | POT res700.pal | статично | +| 0x90–0x9F | Страж chtab_5 (`GUARD_PAL_BASE`) | res10.bin guard_palettes | **меняется по КОМНАТАМ** | +| 0xA0–0xAF | Тень (`POP_SHADOW_PAL_BASE`) | RGB-сетка pop_pack_shadow.py | статично | +| 0xB0–0xFF | Свободны (5 слотов) | — | — | + +Итого динамических зон три: запись 0 (молния), env+wall (тип здания), +стражи (per-room). Всё остальное одинаково всю игру. + +### 1.2 Полноэкранные палитры заставок + +Каждая перезаписывает ВСЕ 256 записей: + +| Файл | Где используется | +|---|---| +| `KID\kid.pal` (+ fallback `a:\kid.pal`) | BOOT и возврат в игру после заставок | +| `TITLE\title.pal` | экран TITLE | +| `PV\story.pal` | INTRO и HALL_OF_FAME (одна палитра на обе фазы) | + +### 1.3 Тайлсеты: подземелье ↔ дворец + +Оба набора используют ОДНИ И ТЕ ЖЕ слоты 0x50–0x5F/0x60–0x6F, заполняя их +разными цветами (атласы обоих наборов запекались под эти индексы). +Переключение = загрузка 64 байт (32 записи env+wall) в обе страницы +(`bg_load_tile_pal`); остальные 224 записи не трогаются. + +Какие уровни дворец — `tbl_level_type` (`pop_level_cold.c:44`): +**4, 5, 6, 10, 11, 14**; остальные подземелье. + +Палитра дворца `pal_tile.pal` (расшифровка, формат записи B,G,R): + +ENV 0x50–0x5F (пол, ковры, факелы, ворота, пики, арки): + +| Слот | RGB | | Слот | RGB | +|---|---|---|---|---| +| 50 | 0,0,0 чёрный | | 58 | 202,190,178 серо-бежевый | +| 51 | 121,89,60 коричневый | | 59 | 153,133,129 серо-лиловый | +| 52 | 161,121,76 светло-коричневый | | 5A | 76,64,56 тёмный серо-бурый | +| 53 | 194,149,89 песочный | | 5B | 153,97,89 кирпично-красный | +| 54 | 230,178,113 яркий песок | | 5C | 137,80,72 тёмный кирпич | +| 55 | 246,202,125 кремовый | | 5D | 48,125,125 бирюзовый | +| 56 | 255,234,170 бледно-кремовый | | 5E | 12,56,89 тёмно-синий | +| 57 | 255,255,255 белый | | 5F | 202,56,28 красно-оранжевый | + +WALL 0x60–0x6F (вся палитра песочная): 61=(218,170,89), 62=(226,165,93), +63=(226,170,97), 64=(218,161,85), 65=белый, 66=(226,165,93), 67=(218,165,89), +68=(226,170,89), 69=(218,170,97), 6A=(255,210,137), 6B=(255,218,149), +6C=(255,210,137), 6D=(255,218,145), 6E=(194,153,80 тёмный песок), +6F=(238,186,117). + +Чем рисуется во дворце: +- **Тело стены — НЕ спрайты**, а сплошные заливки; цвет разыгрывается на + комнату prandom'ом (`gen_palace_wall_colors`, `pop_bg.c:140`, порт + seg000:1942): подряды 1 и 3 берут случайный из 0x61–0x64, подряды 0 и 2 — + из 0x66–0x69; соседи по горизонтали не повторяются. +- Декор стен id 3–17 — mono-силуэт цветом VGA16+6 (0x36). +- Верх дверных проёмов дворца — спец-id 78–84 + полоса 145 («полоса под + окнами», pop_room.c:478). +- Остальное (пол, ковры, порталы-факелы, ворота, пики) — env-куски + pal_env*.atl с ENV-таблицей выше. + +### 1.4 Стражи (0x90–0x9F) + +Цвет задаётся на КОМНАТУ (`level.guards_color[room-1]`), при входе в +комнату зовётся `pop_guard_set_palette(color)` ДО отрисовки (слоты общие +на экран — смена посреди кадра дала бы стража в новой палитре с полосой HP +в старой). Только для обычных стражей (`tbl_guard_type == 0`): скелет и +Джафар имеют собственную палитру, зашитую в kid.pal; им зовётся с color=0 +(не трогать — иначе Джафар на ур.13 покрасился бы в цвет стража своей +комнаты). Внутри одного уровня слоты могут перезаписываться многократно. + +## 2. Текущее состояние: механика fade + +### 2.1 Наша реализация (`pop_ui.c`, банк 9) + +- `pop_ui_palette_snapshot()` — снимок всех 256 записей через + `gfx_pal_get` по 4 чанкам × 64; хранится в хвосте страницы шрифта + FONT.ATL ([0x3C00,0x4000)), map/unmap W0. Требует `font_ready`. +- `pop_ui_palette_dim(step, keep_ui)` — готовит ОБЕ экранные палитры из + снимка. Шкала без умножений (только сдвиги): + +| Шаг | Формула на канал | Яркость | +|---|---|---| +| 0 | x | оригинал | +| 1 | `(x>>1)+(x>>2)` | ≈3/4 | +| 2 | `x>>1` | 1/2 | +| 3 | `x>>2` | 1/4 | +| 4 | 0 | чёрный | + + `keep_ui` пропускает 0x37–0x3F (меню остаётся ярким). +- `pop_ui_fade_out/in(steps)` — проигрывание ступеней за `steps` кадров + vsync (`step = i*4/steps`, целочисленно): steps=4 — канонический (по кадру + на ступень), steps<4 — перескакивает ступени, steps>4 — повторяет (плавнее), + steps=0 у fade_in — мгновенный restore. +- Контракт map/unmap: обращения к EMM/W0 и BIOS-палитре строго после unmap. + +Стоимость одного dim ≈ 15–25 тыс. тактов (~4–7 мс при 3.5 МГц) — +укладывается в кадр vsync, на практике лагов нет. + +### 2.2 Как сделано в SDLPoP (seg009.c, USE_FADE/gmMcgaVga) + +- fade_out: каждый кадр КАЖДЫЙ ненулевой канал каждой записи −1; до нуля. +- fade_in: `fade_pos` от 0x40 вниз; канал +1, пока меньше оригинала. +- Уровней затемнения до 63–64 (VGA-канал 6 бит), полный фейд ~63 кадра × + wait_time=2 тика — медленно и кинематографично. +- `which_rows` — битовая маска групп по 16 записей: можно фейдить часть + палитры (в оригинале используется). +- По завершении принудительно восстанавливается оригинал; после out экран + заливается чёрным. + +Это осознанное расхождение (скорость/такты vs плавность) — ЗАПИСАТЬ в +`docs/impl_diff.md` (сейчас записи нет). + +## 3. Зафиксированные решения + +1. **Ступени затемнения: остаются 4.** Вариант 8 ступеней той же сдвиговой + техникой — рассмотреть отдельно, сейчас не внедрять. +2. **Предрасчёт fade-вариантов палитры отклонён.** Аргументы: чтение файла + с диска на порядок дороже вычисления; 3–7 КБ постоянной RAM при + MEMORY=small непозволительны; предрасчёт привязан к конкретным палитрам, + а снимок работает с любой текущей автоматически; keep_ui удвоил бы набор. +3. **Считать на лету**, хранить один снимок (уже есть, бесплатно в хвосте + страницы шрифта). +4. **Буферы на стеке**, не статика (W1/W2 мало) и не 1 КБ: обнулить 64/256 + байт дешевле, чем держать килобайт резидентно. +5. **Контракт `gfx_pal_load(pal, start, count, data)`**: count — число + СЛОТОВ, буфер обязан быть `count*4` байт; count=0 означает «все 256». +6. **Leaf-applеры остаются на месте** (`pop_bg_pal_apply` — банк 7 со своими + таблицами, `pop_shadow_pal_apply`, `pop_guard_set_palette`): банковая + rodata чужого банка не видна, перенос сломал бы доступ к данным. +7. **Молния (`flash_bg` в roomtest.c) не переносится** — игровой эффект + записи 0; после вспышки восстановление записи 0 из снимка ложится на API. +8. Модель состояния: разделены «какая палитра логически загружена» (load_*) + и «с какой яркостью показана» (apply/fade). Любой load_* обновляет снимок; + apply/fade показывает его с нужной глубиной. Это позволяет грузить новую + палитру «в темноте» (экран остаётся чёрным, пока не позвали apply/fade_in). + +## 4. Целевой API `pop_pal.c/.h` (банк 9) + +```c +/* сброс */ +void pop_pal_black(void) __banked; +/* все 256 записей ОБЕИХ страниц = 0. Стековый buf[256], обнуление циклом, + * 8 вызовов gfx_pal_load (4 чанка × 2 страницы, паттерн как в dim). + * Зовётся СРАЗУ ПОСЛЕ initgraph в pop_boot (раньше нельзя — нет гарантий + * состояния графического режима): закрывает кейс «мусор/палитра предыдущей + * программы при включении графики». СНИМОК НЕ ТРОГАЕТ (контракт: + * чёрный экран без изменения логической палитры). */ + +/* загрузка (пишет полную палитру в обе страницы + refresh снимка; + * видимую яркость НЕ трогают — экран меняется только по apply/fade) */ +void pop_pal_file_load(const char *name) __banked; +/* gfx_pal_fload + fallback "a:\" + gfx_pal_sync (fallback сегодня + * скопирован в каждом из ~6 мест вызова) */ + +void pop_pal_game_load(void) __banked; +/* file_load("KID\kid.pal") + pop_bg_pal_apply + pop_shadow_pal_apply. + * Сегодня тройка скопирована 3 раза (roomtest_cold ~958, pop_title ~88, + * pop_intro ~183). Единое место инварианта «kid.pal затирает слоты + * тайлсета 0x50..0x6F и тени 0xA0..0xAF». */ + +void pop_pal_level_load(uint8_t full) __banked; +/* палитра уровня: kid.pal/shadow + tileset 0x50..0x6F если набор сменился + * (сравнение через pop_level_type()). full=1 — ПРИНУДИТЕЛЬНО перечитать + * kid.pal/shadow (один экспорт с флагом, не две функции — меньше банковых + * точек входа). СТРАЖЕЙ (0x90..0x9F) НЕ включает: это компетенция входа + * в комнату (pop_guard_set_palette до первого draw). */ + +void pop_pal_story_load(void) __banked; /* PV\story.pal (INTRO и HOF — файл один, функция одна) */ +void pop_pal_title_load(void) __banked; /* TITLE\title.pal */ + +/* отображение */ +void pop_pal_snapshot(void) __banked; /* переезд из pop_ui, тело то же */ +void pop_pal_apply(uint8_t fade) __banked; /* = dim(fade, 0), 0..4 */ +void pop_pal_fade_in(uint8_t steps) __banked; /* переезд из pop_ui */ +void pop_pal_fade_out(uint8_t steps) __banked; + +/* меню продолжает звать низкоуровневый dim(step, keep_ui=1) — отдельный + * тонкий экспорт, чтобы не тащить флаг в горячий apply. Старые имена + * pop_ui_palette_* / pop_ui_fade_* УДАЛЯЮТСЯ (без алиасов — меньше + * экспорта банка). */ +``` + +Соответствие старое→новое: snapshot→snapshot, restore→apply(0), +fade_out/in→fade_out/in, тройка kid.pal×3→game_load, fload+fallback+sync×6→file_load. + +## 5. Этап A: рефакторинг — выполнен (2026-08-24) + +1. Создан `roomtest/pop_pal.c/.h` в **bank 10**, добавлен в Makefile. + Он владеет политикой `load logical palette → snapshot → apply brightness`. + Низкоуровневые snapshot/dim/fade остаются физически в `pop_ui.c`: там + владелец страницы FONT.ATL, где лежит снимок; наружу они доступны только + через `pop_pal`. +2. Заменены call-sites: + - `roomtest_cold.c` ~958: black → game_load вместо тройки; + - `pop_title.c` title_restore_game_palette → game_load; загрузка title.pal → title_load; + - `pop_intro.c` intro_load/intro_restore → story_load/game_load; + - `pop_hof.c` (2 × story.pal) → story_load; + - `pop_menu.c`: fade/dim → новые имена (dim с keep_ui — низкоуровневый экспорт); + - `roomtest.c` demo-start (snapshot+dim(4,0)+fade_in(4)) → новый API. +3. Старые вызовы не остаются в коде приложения; внутренние функции `pop_ui` + сохранены как реализации одного владельца памяти снимка. +4. Сборка и host-тесты пройдены. `make size-check` неприменим: меняется + приложение, а не libc/libbgi. +5. MAME smoke-тест полного цикла смен палитр: boot → title (title.pal + + fade) → intro (story/kid) → demo fade-in → игра → HOF (story.pal). + Проверить: отсутствие мусора при включении графики (эффект black), + меню с keep_ui остаётся ярким при затемнении, молния (запись 0) + восстанавливается. + +## 6. Этап B: переход уровня через fade — реализован, ждёт визуальной приёмки + +Сценарий (обсуждён, детали уточнить по SDLPoP перед реализацией — как +оригинал делает смену уровня, есть ли там fade в DOS-версии): + +``` +fade_out // последний кадр уровня N темнеет +рисуем комнату 1 уровня N+1 // во ВТОРУЮ страницу, в темноте +pop_pal_level_load(full=0) // новая палитра: железо+снимок обновлены, + // экран всё ещё чёрный +флип + копия второй страницы обратно в первую +fade_in // = анимированный apply 3→2→1→0 +``` + +Экономия: реально переезжают только 32 записи (env/wall) при смене набора +dungeon↔palace; guards_color обновит вход в комнату. Kid/shadow не меняются +— потому full=0. + +Реализация находится в `roomtest.c` / `roomtest_cold.c`: последний кадр +уровня N темнеет, `pop_level_switch()` подготавливает первый кадр N+1 и +обновляет логический источник через `pop_pal_level_load(1)`, затем главный +цикл показывает кадр только через fade-in. Восемь ступеней и отдельная +анимация смерти не входят в этот этап. + +**Этап B закрывает два открытых бага** (разборы — `roomtest/BUGS_OPEN.md`): +- [PAL-L1-AFTER-INTRO] — вход в игру на уровень 1 после интро с чёрным + экраном (маршрут demo_new_game; корень не установлен, воспроизведение + нестабильно); +- [PAL-DUNGEON-STALE] — переход 3→4 оставляет подземную палитру (корень + ясен: fade_in восстанавливает из снимка, снятого ДО загрузки тайлсета + дворца; быстрый фикс `fade_in_pending` 2026-08-23 сам же и проявляет этот + дефект модели). + +Быстрый фикс 2026-08-23 (маршрут CUTSCENE → LEVEL_LOAD → PLAYING, +`fade_in_pending` + `pop_ui_fade_in(4)` после `pop_level_switch`) закрыл +чёрный экран на переходах с pre-cutscene внутри подземелья (1→2), но модель +«кто и когда меняет яркость» остаётся разношёрстной — её и приводит в +порядок этап B. + +## 7. Этап C: документирование + +- Запись в `docs/impl_diff.md`: наши 4 ступени vs SDLPoP ~64 (что делает + оригинал, что делаем мы — сдвиговая шкала ради тактов, чем платим — + грубее градации, что проверять при регрессе). +- После этапа B — дополнить запись про сам переход. + +## 8. Не трогаем + +- Молнию (`flash_bg`, roomtest.c) — включая обход SDCC-бага + `gfx_pal_set(0,0,0,0,0)` → ручные `gfx_pal_set(0/1, 0, r,g,b)`; +- leaf-applеры: `pop_bg_pal_apply` (банк 7), `pop_shadow_pal_apply`, + `pop_guard_set_palette` (данные своих модулей); +- хранилище снимка в хвосте страницы шрифта FONT.ATL (бесплатное место, + guard `font_ready`); +- раскладку слотов 0x00–0xAF (зафиксирована атласами). diff --git a/applications/PoP/docs/sound_plan.md b/applications/PoP/docs/sound_plan.md index bd38d18..11059f7 100644 --- a/applications/PoP/docs/sound_plan.md +++ b/applications/PoP/docs/sound_plan.md @@ -1,6 +1,6 @@ # Звук в порте PoP — разбор и план -Дата: 2026-08-20. Статус: **разбор, кода нет.** +Дата: 2026-08-20. Статус: **PCM-эффекты реализованы; музыка/AY не реализованы.** Задача пользователя: добавить звук. Приоритет — эффекты; музыку, если найдётся способ. Эффекты — **обязательно WAV, а не PC-спикер** @@ -351,6 +351,7 @@ IBM PC 1989 года: один квадратный голос. Он у нас | 4/5/6/7 | ворота: закрываются / открываются / рухнули / стоп | `pop_trob.c` | seg007 | | 8 | удар о стену | `pop_map.c` bumped_fall/bumped_floor + seqtbl | seg004/seg006 | | 9 | зацеп за карниз | `pop_map.c` check_grab | seg006 | +| 10 | клинок о клинок | `roomtest.c` после `check_sword_hurt`, если один из бойцов в кадре 167 | seg000:1353 | | 11 | свист клинка мимо | `guards.c` check_hurting | seg002:0DAE | | 12/13 | ранен соперник / Кид | `guards.c` hurt_by_sword | seg002:0C1F | | 13 | Кид ранен зельем | `pop_map.c` ветка «злого» зелья | seg006:1894 | @@ -368,7 +369,8 @@ IBM PC 1989 года: один квадратный голос. Он у нас | 48 | напоролся на пики | `pop_map.c` | seg005 | | 49 | пики пошли | `pop_map.c` start_anim_spike | seg007:08F6 | -Что осталось не разведено — только МУЗЫКА (25 презентация, 26 объятия, +Что осталось не разведено — только МУЗЫКА (24/28 смерть, 25 презентация, +26 объятия, 27/35/40 заставки, 29 встреча Джафара, 30/33 зелья, 32/41 конец уровня, 36 время вышло, 37 победа, 43 смерть Джафара, 50/52/53 сюжетные вставки) и 51 (дверь принцессы, тоже из заставки). Их черёд — фаза «музыка». @@ -641,3 +643,42 @@ play_next_sound() seg000:1304 — раз в кадр решает, зап нужно сходить Кидом на левую кнопку и вернуться. Это поведение оригинала (trob челюстей создаётся событием), а не наш дефект — учитывать при постановке автотестов. + +## 13. Повторный аудит игрового звука (2026-08-24) + +Проверены три независимых слоя: содержимое атласов, места вызова и живой +тракт `play -> tick -> CBL`. + +- В восьми `SND*.ATL` есть все 31 оцифрованных ресурса: `0..23`, `44..49` + и `51`; ненулевая страница/длина есть у каждой записи таблицы. +- Для всех 30 PCM-эффектов, которые могут возникать непосредственно в игре, + есть место вызова. Последним пропуском был звук 10 при столкновении + клинков; условие перенесено буквально из `check_sword_vs_sword` SDLPoP. + Звук 51 относится к сцене с принцессой, а не к игровому циклу. +- Звук 19 «Кид достал меч» проверен в MAME брейкпоинтами. В момент вызова + предыдущий PCM уже закончился (`sfx_left=0`), номинация дошла до + `pop_sfx_tick`, после чего курсор получил id 19, страницу 5, смещение + `0x1500` и длину 2816 байт. В этом прогоне его не подавляли решётка, + плиты, шаги или приоритеты. Если он всё ещё субъективно не слышен, искать + надо после выбора эффекта — в непрерывности CBL/громкости самого сэмпла. +- После продолжительного прогона title/intro/demo/menu CBL обслужил 3303 + блока и сообщил 0 программных недоливов (`cbl_requests=0x0CE7`, + `cbl_underruns=0`). Это исключает возврат `fill=0`, но само по себе не + измеряет запоздание прерывания внутри слишком длинной секции `DI`. +- Регрессия full-game на входе в Level 1 оказалась именно запозданием CBL: + `pop_level_switch` открывал его ДО блокирующего BIOS fade-in. Demo был + чистым, потому что включал палитру без fade. Теперь загрузчик оставляет + CBL закрытым, а caller открывает его после окончательной палитры/QuickLoad; + скрежет на Level 1 исчез в MAME. Однократный щелчок самого первого + `cbl_open` за всю MAME-сессию остаётся отдельной низкоприоритетной задачей. +- Открытие pause menu у SDLPoP беззвучно; движение играет 21, вход/выход + из подменю — 22, изменение настройки — 10. Эти вызовы перенесены. На + время полного копирования страницы и файловых операций CBL закрывается, + после flip открывается снова: аппаратная половина не должна повторять + старые данные и давать «скрежет». + +Отдельно остаётся игровая музыка и сигнальные мелодии без PCM: смерть +(`24/28`), начало/появление Shadow (`25`), встреча Jaffar (`29`), большое +и малое зелья (`30/33`), Shadow (`32`), победа/меч (`37`), перо (`39`), +конец уровня (`41`) и победа над Jaffar (`43`). Вызовы и AY-проигрыватель +для них ещё не реализованы; наличие всех PCM-эффектов эту задачу не закрывает. diff --git a/applications/PoP/docs/status_line_text.md b/applications/PoP/docs/status_line_text.md new file mode 100644 index 0000000..1cba2ca --- /dev/null +++ b/applications/PoP/docs/status_line_text.md @@ -0,0 +1,201 @@ +# Текст в нижней статус-строке (строке HP) — полная инвентаризация SDLPoP + +Разбор `SDLPoP/src/` на 2026-08-25. Цель — знать ВЕСЬ набор сообщений, +которые оригинал печатает в ту же полосу, где нарисованы деления HP, +и правила их появления/исчезновения. Это входные данные для порта: +у нас пока туда пишется только `GAME PAUSED`. + +--- + +## 1. Геометрия: одна полоса на HP и на текст + +``` +rect_bottom_text = { top 193, left 70, bottom 202, right 250 } // data.h:217 +display_text_bottom: draw_rect(чёрным) + show_text(halign_center, valign_bottom) +``` + +* Деления HP **Кида** — от `x = 0` вправо, шаг 7, максимум 10 → занимают `x 0..69`. +* Деления HP **стража** — от `x = 314` влево, шаг 7, максимум 10 → занимают `x 245..320`. +* Текст живёт РОВНО в промежутке `x 70..250` и по X с делениями не пересекается. +* По Y деления на `y = 194..200`, текст (`valign_bottom` к 202) — на `y = 195..201`, + то есть на строку ниже. Именно поэтому в оригинале текст выглядит «сидящим» + чуть ниже стрелок HP. + +**У нас**: `POP_HP_Y = 194` (`pop_cdraw.h`), экран сдвинут на `POP_YOFF = 28`, +базовая линия крупного шрифта `POP_YOFF + POP_HP_Y + 8 = 230` — силуэт +ложится на `223..229`, то есть та же картинка. + +## 2. Два примитива и два таймера + +| Имя | Что делает | +|-----|------------| +| `display_text_bottom(text)` (seg008:2644) | стереть прямоугольник цветом 0 и напечатать текст по центру | +| `erase_bottom_text(arg)` (seg008:266D) | стереть прямоугольник; при `arg != 0` ещё и обнулить оба таймера | +| `text_time_remaining` | сколько игровых тиков сообщение ещё висит; 0 — ничего не висит | +| `text_time_total` | **идентификатор сообщения**, а не только его длительность | + +Обработка тика — в `draw_game_frame`/`idle` (seg000:956). Комментарий в +оригинале прямой: *«Note: texts are identified by their total time!»* Значения +`text_time_total`, у которых есть особое поведение: + +| `total` | Смысл | Что происходит по истечении | +|---------|-------|------------------------------| +| 12 | «1 SECOND LEFT» | обычное стирание | +| 24 | обычное короткое сообщение | обычное стирание | +| 36 | смерть на демо-уровне (0) или на уровне зелий (15) — **текста нет** | `start_game()` — рестарт игры | +| 288 | «Press Button to Continue» | `start_game()` — рестарт игры | +| 1188 | защита от копирования (уровень 15) | **не убывает и не исчезает** | + +Мигание: при `total == 288` и `remaining < 72` сообщение мигает с периодом 12 +тиков — 4 тика видно (`blink_frame <= 3`), 8 нет; в кадре `blink_frame == 3` +заново печатается текст и играет звук 38 (`sound_38_blink`). + +Сброс: `init_game()` (seg003:32) обнуляет оба таймера и `is_show_time` — то есть +любое сообщение умирает на старте уровня. + +--- + +## 3. Полный список сообщений + +### 3.1. Состояние программы + +| Текст | Где | Таймер | +|-------|-----|--------| +| `GAME PAUSED` | seg000:1769, пока `is_paused` | **без таймера**: печатается на входе в паузу, `erase_bottom_text(1)` на выходе (seg000:1784) | + +### 3.2. Уровень и оставшееся время (`show_level` / `show_time`, seg008) + +| Текст | Условие | `total` | +|-------|---------|---------| +| `LEVEL %d` | `show_level()` при старте уровня; только `1..12` (`hide_level_number_from_level = 14`), не при `seamless`; уровень 13 показывается как **12** (`level_13_level_number`) | 24, дальше сразу `is_show_time = 1` | +| `%d MINUTES LEFT` | каждая минута, кратная 5, и каждая из последних 5 | 24 | +| `%d SECONDS LEFT` | последняя минута, раз в 12 тиков | 24 | +| `1 SECOND LEFT` | остался 1 с | **12** | +| `TIME HAS EXPIRED!` | `rem_min == 0` | 24 | +| `%d MINUTES PASSED` / `1 MINUTE PASSED` | только SDLPoP (`ALLOW_INFINITE_TIME`), при отрицательном таймере | 24 | + +Что взводит `is_show_time` (все → следующий кадр печатает время): + +* **Space** — seg000:612, штатная клавиша оригинала «сколько осталось»; +* читы **`-`/`+` numpad** (изменение времени) — seg000:762 / 777, при этом + таймеры сообщения обнуляются, чтобы новое напечаталось немедленно; +* **смерть Джафара** — `on_guard_killed()` seg006:1936, уровень 13 + (`jaffar_victory_level`): вспышка + показать время; +* истечение очередной минуты — seg008:1796; +* сразу после `show_level()`. + +Обнуляет `is_show_time`: `play_kid()` при смерти (seg006:1365) и +`show_copyprot(1)` (seg000:2385). + +### 3.3. Смерть Кида + +| Текст | Где | `total` | +|-------|-----|---------| +| `Press Button to Continue` | `play_kid()` seg006:1383 — умер на обычном уровне | **288** (мигает, затем рестарт игры) | +| *(без текста)* | тот же код, но уровень 0 (демо) или 15 (зелья) | **36** (тихая пауза, затем рестарт игры) | + +Стирается: `fell_out()` (seg006:1342, упал из комнаты 0) и чит **R** +(воскрешение, seg000:783) — оба зовут `erase_bottom_text(1)`. + +### 3.4. Сохранение и загрузка + +| Текст | Клавиша | `total` | +|-------|---------|---------| +| `GAME SAVED` / `UNABLE TO SAVE GAME` | Ctrl+G (`save_game`, seg000:2211) | `total` не ставится, `remaining = 24` | +| `QUICKSAVE` / `NO QUICKSAVE` | F6 (расширение SDLPoP, seg000:497) | 24 | +| `QUICKLOAD` / `NO QUICKLOAD` | F9 (расширение SDLPoP, seg000:514) | 24 | + +### 3.5. Ответы на клавиши (`answer_text` → `need_show_text`, все `total = 24`) + +| Текст | Клавиша | +|-------|---------| +| `SOUND ON` / `SOUND OFF` | Ctrl+S | +| `KEYBOARD MODE` | Ctrl+K | +| `JOYSTICK MODE` / `JOYSTICK NOT FOUND` / `JOYSTICK UNAVAILABLE` | Ctrl+J | +| `PRINCE OF PERSIA V1.0` (в SDLPoP заменено на `SDLPoP v%s`) | Ctrl+V | +| `SDL COMP v… LINK v…` | Ctrl+C — только SDLPoP | + +### 3.6. Отладочные читы (`cheats_enabled`, `total = 24`) + +| Текст | Клавиша | Смысл | +|-------|---------|-------| +| `S%d L%d R%d A%d B%d` | `C` | номер отрисованной комнаты и её соседей L/R/A/B | +| `AL%d AR%d BL%d BR%d` | Shift+`C` | диагональные соседи | + +### 3.7. Защита от копирования (только уровень 15) + +| Текст | Где | `total` | +|-------|-----|---------| +| `WORD %d LINE %d PAGE %d` | `show_copyprot(1)` seg000:2389 | **1188** — висит, пока не сменится уровень | + +### 3.8. Только SDLPoP, в оригинале 1989 отсутствует + +| Текст | Где | +|-------|-----| +| `RECORDING`, `REPLAY SAVED`, `REPLAY CANCELED` | replay.c:599/626/628 | +| имя файла скриншота | screenshot.c:62 | + +--- + +## 4. Что из этого касается нашего порта + +Реализовано: + +* `GAME PAUSED` — в строке HP, без таймера (`pop_menu.c`). + +Есть у нас, но сообщение пока НЕ печатается (показывается иначе или не +показывается вовсе): + +* `LEVEL %d` и вся группа времени — таймер уже есть (`pop_timer.c`), текста нет; +* `QUICKSAVE`/`QUICKLOAD` — у нас вместо строки экран-уведомление (`ui_notice`); +* `SOUND ON/OFF` — у нас индикатор в отладочной полосе, а не текст; +* `Press Button to Continue` — у нас авто-респавн по таймеру/стрелке вверх; +* номера комнат (`C`/Shift+`C`) — у нас палочки в отладочной полосе. + +Нам не нужно: copy protection (уровень 15 исключён из порта — см. +`full_game_plan.md`), joystick-режимы, replay, скриншоты. + +Механика, которую придётся портировать целиком, если брать группу времени: +пара таймеров `text_time_total`/`text_time_remaining` с семантикой +«идентификатор сообщения» — иначе не воспроизвести ни мигание, ни рестарт по +истечении 36/288. + +--- + +## 5. Цена вывода и что делать, если упрёмся + +Блит одного глифа стоит ~4,6 тыс. тактов почти независимо от размера — это +цена вызова, а не пикселей (memory `blit_cost_model`). Полсотни символов = +полкадра. Что уже сделано в `pop_status.c` / `pop_ui.c`: + +* **change-driven**: пока показанное не изменилось, не рисуем вовсе; +* **по полям**: смена комнаты — две цифры (~9 тыс. тактов, 2% кадра), а не + вся строка; подписи рисуются только при полной инвалидации; +* **пробелы не блитятся**: их глиф целиком прозрачен, а стоит как буква — + на полной отладочной строке это девять сэкономленных блитов; +* **заливка только поля** при входе в комнату (`pop_screen_fill_field`): + борта от комнаты к комнате не меняются, это и четверть заливки, и то, что + обе полосы вход переживают. + +Запас, если бюджета всё же не хватит (идеи пользователя, 2026-08-25): + +1. **Растянуть вывод на несколько кадров, не показывая полуготовую строку.** + Печатать по нескольку букв за кадр, держа цвет шрифта чёрным (отдельный + индекс палитры), а по готовности подменить этот индекс на белый — строка + появится целиком и мгновенно. Стоит ноль байт памяти и укладывается в + нашу же технику «два разных чёрных» (`POP_COL_OUTSIDE`). +2. **Собирать строку в один спрайт** в свободном хвосте страницы шрифта и + блитить одним вызовом. Дороже по подготовке (~35 тыс. тактов на + копирование), но выгодно там, где строка ЦЕЛИКОМ меняется каждый раз. + Для меню этот путь уже рассматривался и был отвергнут; для статус-строк + он имеет смысл только вместе с п.1. + +Про QuickSave/QuickLoad оптимизация не нужна вовсе: там игра и так стоит на +время дисковой операции. + +## 6. Ловушка: свисающие глифы + +Зона стирания текста обязана захватывать строку НИЖЕ базовой линии. В малом +шрифте `'p'` имеет высоту 7 при ascent 5, `','` — 6: они свисают под базовую +линию. Стирание ровно до неё оставляло от хвоста «p» в «Speed:» одинокую +точку (поймано в MAME 2026-08-25). diff --git a/applications/PoP/poc/res/bg/pal_env4.atl b/applications/PoP/poc/res/bg/pal_env4.atl index b813f035b0fd5c49f8651b9002e1ae5aea83dc77..bb17cdf0b75ee4b31b2738d911746e43a01eb40f 100644 GIT binary patch delta 193 zcmX?LcED`IEoLSLhRKZFBJ~VF@E-$!L@~u7qUho8 Lfx+fEEK9@y2~BHs diff --git a/applications/PoP/poc/res/bg/pop_env4.atl b/applications/PoP/poc/res/bg/pop_env4.atl index 683fcdc1bed73e36aa28e99d0e3da912b1f73865..54e5ab801e8dce2cd9fdce257bb42e39153c31a7 100644 GIT binary patch delta 193 zcmca$cEN1JEoLSLhRK2)BJ~VF@E-$!L@~u7qUho8 Lfx+fEEL+3?Dz0m> diff --git a/applications/PoP/poc/res/bg/pop_pot.atl b/applications/PoP/poc/res/bg/pop_pot.atl index e2e3695492cd1d1f819a3031de04c12e2e23779a..09fe972c8534d08fdf28a84951ef5d34bfcc0808 100644 GIT binary patch literal 5374 zcmdT^J#Q015WR>5Vh6A>>4~TiLLf>a8vE|-18gf2B|ia5L{HBTprEHk)S;)Lr;G$O zJ=HN^yE`*`X9ppWSQO^#y*IPtwV$3$@82u_{yEzziXHg-zB8Vn-M_&0&Mw=NJ+?0| zvVD7r?dd+-vjN-P1Gcv=9}Q9G#TB+6t_}|{zBw9>(4LMszB}gl^LTH7`JZBMU*q_h z*f(N7iTxq=#)NTU{`1-s;k_37QS3Ldhu2xR7W+zUpZN$E4|wK2=$TE-ve@H`!ZJ*ZC8dCB@D~Z_HwOH^(opk4?gS1b~F^nyZI*rFLWxc0nkCM>VB5ODF&`bm(KJ=Nou^ zdR9u1>Pp|ozFpVO6diY@)S;4opVW>Fg91x}t`-7gr3AP(ri7{kSi{|&C^U8aDx?J0 zeVpW>xxHgooajL8ct=X^+&eOy&4IoTAu*xuhzrD%RJO!h%CwWsa@&7z)HJjcAIq@k zen)iWB*Itdk9j9)BNV$(ugvRZd`u|5d$mwO1r2a9eOFc8zq1YBwWDHKwEydw@!*ep z&J$b{oJV~}Dt~(Yf4Rdd3>%%05L;1!|92%0D>|{1?-1*8qxlY*A6M#kxTSv_J32hc zusFbXSXc9fj24}?4yowXfNL56mU7ys3RWU8Rx$vvC*YvRqU9b+cTpX&uUIIY|`qfhxOLt~YdSnkDy%d~jG^sjH6VO4iXf z;=+tP8jjmQH*S}_7VpAWZ9p5my<(7lpB{3lZF7ezm^K8=25hi|OleCHP#>1WLI(yp zsA?EH2SQB92nwrIz>S0iRM;dFSs5W#I>0E3gx@-*!*Q4^cc_ACL%?jn22044wgdt7 zVM#1>V332VhOu)X#Dt8Xuu28oNJv11O)`;{5n`nSjG~6{AhgLHs$kj>FdMMJ5;CPN pK|p<25(^y|>LO&AtNZPQUNy-5>R22Ok`z*Sm^+x=r4c0Z&m;R literal 5374 zcmdT^J8u&~5Wa{6Vh6AZ>4~TiLOe<$3hcYH53sFBl>7uJ5j{OWfP$VDQHP#}o-z{D z^i;>Zc6Mg>&JIE#u~9Ow@B3zUvi8&C$-O(p-#@2YMX?3H-?zs5Xm`)Cy|vBuc!%xF z^K9Q~pd2#C{Qr z|JiY|Up%BUWut$mJ~7Q=MY02M&u}r8i~_2`uM*J5qna2@OiXASb3*Z`4kyM*2xTXS z@g$yP7(9Kl_PDsBMGz(CndaYh<3h5*GAdn6%_h=K{AKPJ8rZu!-B}j*y(_aLBZ#wM z(>P` z=%o!(eR@_#km^Q1jnj5rJ2Q0Lkx_?A{C+Y!atsPQ3A%a+j1?2$`j`@?4qyvU?nI%h z<3}MTxSqyQ9-8|*cEynn#Ey4l1GKkhkCa7}O?^&P4F>Gl8R4y!P%Iw2vpq5}U-N*q>nw3P1<>$=f=hs@WNraRow zU&mI5Cm9xd_zvr8HkZ-VY3q z7;#6H@90=pbLzu<;E`rMbLz1V4N;Y|vT4ahhepG}=z3L6%~3}@yfe;&Jc<@23Y`YTMkQ3Z@+avjH0{A!FJT1k{Hm zvCx4*3Dh*y&Vir_8NpzU3b;y$K!c4k%E|~@=>VfB67K%aaUG7rT)9IPOgjQ*12$Mf z#_@gO~?oaYgE8hLIfIYlu=ej&`JjwMSlVKG+ZPA diff --git a/applications/PoP/poc/res/font/font.atl b/applications/PoP/poc/res/font/font.atl new file mode 100644 index 0000000000000000000000000000000000000000..a90e7dd998ab84656fb42d3cf44adea3635e0fbe GIT binary patch literal 7667 zcmcIoO^76C8Sa_xuI}0S-Er5aSE|>91E)>1V5PMf_(~CI8jnH>;IZ;{Oi6 znmKQOHk}YZKO0NmGh0>u=VxOy8jZd*bDuBICaU*%^!I10@_!otrRdk9Z$?k&&O3^} z5Pfg-#kuQ$ZSKC0%{|xCbIPKIQzBl?}^rO+=jec>>eXhs< zOY~dO^Hbi>bEjPY?&$lYFQ4)pPsV>f`r0Yi{eAp5qW_b6`|GZEKKe83=6`YB`*|t& zH==*AKGt=6A-rF$ugd>T;(w0*SK_N1u741H*M{qVYGX3v^}HDT(GBnWw>MUWKO6k# z!LM!j{QngE&FE^=b>0{K=}p&vaMOLS#Q#?OA4dOT)Aep7{zmdATh2R-{_vLPx;y^; z(U-TzBhG(i%kzG7YpVP6ds{Qz55L=*={(-qnoD2Uw!X6M`}nbK&;6t5m$#kw&+SV2 zXLiPO*8kAXM8AVi?zqnvc3kI~ol14DN52_;c-r`9PrLu)@n4MoYxLT#c^`?sxa<4< zkzMzFdUvY&zuxtJyc+-Q@Xx)+`S(RX5q&lK_2~68u5(xPgJ+!go#ln7&Yf3c}!YG;u3B_|(zk;00Rcpk;wH9*x@((a`hO zlXh^_>b{L|mF9pxC|AK3tgd8n04(@~U|hUoZ& z%a>Mv>g2Wjo)4z$Xd74|o5cXU>^nL50N|1Rt!sHXM+6k5@ONfwEK@PcrbBEEpJl9+2Km1YiPyl39Lm#u-2)C2DdD zm3FT*m;g-AG(d(q}lOIlfP%rdctZ z4zya_)szJ-OaKj&$D?^(VTgFCA;hG-7PWYOy@iMhb?prz#Crde!jya#U@2DyEY?|+ zcEyIN9xbQzb@!56*TdS#D!BuTy>89!=0ORxD0g9*MZ+M(uN7Dd(&K_+3!-KTQh*4T z6$w~_tyzdRWQs&fMq3+^a8*u%tb)oOo-E@yYAk9Sv}&xuB!UpY7Yk)&P}=ZoA3~{9 zZZO43$12>Zep_@G4tt#}&qk+`%@`jh`Nb%^ z(#$eE>C(BvGuzUSMc66P*DR;r3p=7tJtT=F>xWCfkq4P?{LWL6Fx&eqeyQDeYd;7D zsTi67>sEshJtQyD$M?i+W+X8MnZ0bk#Y2o8inQU{4D@5<7$F}+Mc!6z7edCn)J_L< z(p1Z8tFZ?_y|yrnwzhJ?L;PfEHaV_wk`y6x--?|=P)pt)O3)gVy1!gDvkvES3Mn`j zX33HPYse-mEkM-BJLd7#25cL!Fc!X$?3g_V>RV_k}el$A&*%&j>{q2 zq~ko}!<}bEP2_M-7Mxu<$uQH@6Z$B_c_-caU1sS}`uO%_Pc01M3E{~mN&`!;3yl&f z2!-2XLWi4F$WG~`E4({fJdNRJFh}YybuT1q4OV9XVpH8_!@LjylLR4v#_$p_uJAz0 vWiAYdj-OGDWrA3(Am0B6VUzCEIv~2+oe}z!ma*$f2RCOEtPWT_Xy!$=X>V zvgE9gvqH`aktGC0phjQ}CSdB)49xLgSWZhmVr5mWIoNp&nxiK*UO!? zgqF|}T0%=dE&Xfhqs8Bwf*F|emPN&5OY&D>joZe*R@*DgSnqJ!b31450FEx5xN-&; GuDk$Yqe$5R delta 7 Ocmdm{&>_2_gAV`<3j)Fb diff --git a/applications/PoP/poc/res/pv/a0.atl b/applications/PoP/poc/res/pv/a0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e196cd983d17a678adce810d61876804be6b4cb5 GIT binary patch literal 15848 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 CP(|4Q literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/a1.atl b/applications/PoP/poc/res/pv/a1.atl new file mode 100644 index 0000000000000000000000000000000000000000..dc21da7a0c6f54e4e88fbf583ee13936606f4435 GIT binary patch literal 16013 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/a2.atl b/applications/PoP/poc/res/pv/a2.atl new file mode 100644 index 0000000000000000000000000000000000000000..bc1cfb0949bd72460433a312130bf15cebd53708 GIT binary patch literal 11142 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/b0_2.atl b/applications/PoP/poc/res/pv/b0_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..d60500278ec4f5f8c11af1440dbddf7bed3e732d GIT binary patch literal 15796 zcmeHOQF7Zj43!+&PCIMgB{ThwyEoXGy~y7Grwf1t2#|yXQBJ$rDJKa@5D$2Kgk(8! z{{HLFw|V;b_RsX=ZH7eO=Qo|~R^@(qd6{q8VVai9<$675+kC1U23>0gzUDZz7K%Q1 zSf^|u_!_Z*eiLTr7$*=|wPktP($By-%=3Cp&Wj5ADb6qi+SxY(_rjvRqhF^0rTnJF zw)1%@+B$ibOMyEs2h5S^r;}y90PUPI0$*eITfbw68_^HSosVmz{xp2;D96?!(O2p( z0$(E*(GP0;DlyEJ0N24Hw?=WP1_oZO9iN7xpHQaKkao@-g0HZQ=!Z97387h#e4F|i zxWswZAQW8};2E08H-+FUTqF9y8S&!_Mp7?j>V?_QQNex3p_O2C)Jd9p$qT|=Y6JSY zv7CjY6rHfyd@%Y!`0nQyVm(V+-&4)l_&zB3oiaEiP`6$KJ;PCF!+?;)9I`W2e814if)Gx{R$@J@y z{w4ZN^oAT>&NiQ2@YIe{bWT2-k@~|c0Ut;&u?zSp#XfPiMx#5Vqi{}F z_XGOjFEbXp{avq-EBGkInzEe-qkE)-aGO)je(M)}`B9qb;edW{_oIt}9_Mu|XMFUC zov_&)jqZ?+!aWir`a!H2MTF*s)%K$lb*y&0BYKbY4tSdzEJOOi&15;lFJ-*D-7L}A^J_Y6_)SkJ-?W&jSQbjzv&A!-od~Te`V}; zucPa%4f1p8_bTN#P=8;ixPV4KmGwWZUpmW+{1v7l3sx+V6{ScD222X$T{DNC8c3y|A!%o=FN6hn$o^?anNXqp->6du$ zU89RDq*dHhzFmLe-{!|X=N8uwDdulQMQps5d(w}_a)lLq<=e!d{iu9v{#<|LgIPVC zCS81_{#;z~EwNd>9na7DjP$Ko+c9jO)0IT?te@k@{|%Rn`XDCvD&jX4~*# z`pMh3Tp`<3iU-t3nQV-V0Od_%i~3~x4c8y;JT9L-o&Qh$sq;4#qU_HZhNJ(w`h&NP z_7A;%DxMw<+;beVhokpNhv5#nkGlV_}usvKrebQmLL+}QFO5+B~ zp?J`&bCaV_#e&MLftRHxjKb0Tq{DEBD(RPmBjsxC5Y~J~xKG8&(84{(A$vp4i_#|@ zhC5+zTyjm-2>_0${QnGD`&0yA2QA#LADhLacS*kEbm-yf-ge!!91GS6U3S`{i#dRTlYV20SP hg4;!&-S0ed`@V~fz7eI6mMnH}VD6_8fARoz{{@v*bT0q^ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/b0_3.atl b/applications/PoP/poc/res/pv/b0_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..985f941f8ce14a2993cc446ed8e9ebeb8b43f764 GIT binary patch literal 15796 zcmd6tYj)$f5rySsGV?>z-U~;f)FwqK*+I_9MrQwil6$KPr~(PTB-0yb{#WyM`1CMmfBDC+zy0>>FAs+E zSso9yDC76Cd0A;W8Kzp}g)=rq?=v>miHr7Li{?^nlXjC@Y60NyNBJQ06)eWbGG7MtbK0>3bJIf~?FW*#i(_#GLD4qenj(=;6cx|EF05@oCR z_aF6+qS@SF(MD921(h5|TNutoFBoaB^8~cM#!uQH`tbuQ>H+u^pi7DTu&^JGqUC&7 z!E9l`C#*t~QPM?^Dr(tfQ4@ao!<4>qu1pBNrh^B%l)#T0BbKlS_T{s(6WhfT3(xZQG(Gh^ral=vsQvi&&+LpBVO z(AbiR9-l7dA*)vDrN;LBsUi3M#fdzdIY)KTYFbC*-B>EF$y5n`DS4D>*_81ZZUepO zhTk4P$g&K9+`5i&a8nP)TuaLjMxB3?fFWg@cjrV)97 z3uz3uzV8^o0KYDUrwNy?u%n@{_Gcn^$w4TdhEvmgQq;8eX{-Qr(b=%(Te&hMX6$TS z0W*#iCjb%+GfWx8u>VO3ZNyQ9qD2PsY6OWH;nG~2rv20~oD5)|Fzj2`EAErPfTcxI0jieMxJ3CpFhNPZlhf)k`HG=NvdCmTSUo$<8balH%o}^!~^}#4?Ka zXWJwRHF+SADnZ#Uk>uxaz2w7#T?jGZ#hm+J&Kb>|54LEm#VN)Dt~ub?1DE&-l_}PG z$@{zb-|2*jg|maynuedX5QMrRvtxopC0s5pMK?(x3k2-~Dd!4V_E-4PfU`HbpZOaZ zWfY!RtX1^hOwn%w=%TAwcDNw@?{qqmhaJ6_i_<%kAhVaD00^K4@aj_N1MoeZ7rnaR zARb8v6m)%u6jmtk<+}^|$EGz5=7gCXx&qLJT2}MK9fm|+@&k6jdP=23{-@3+@HASk4gN=biX45w9sLq`4qqMi=KIOn*w>x?-ITd%nl3xPoqF4B>pChDq zzw-vsJ~}e8H7xsHN$+rQc9J47mqL6ki#U;UjWB8Lcl$2Pi^qS zw&j%~eUnuJG=Fl@Yy4KPGNdN}hxTyj1}CVypDtXK4nv2pT1$Xn-nta#iXiOd49P5U zh0(8H$wrKY86(dnjwsn!8yce{IYJfzy6AO&D+CoEJ#aW2h6IqiAkSSnHT)RIv?XX(GEc!X3u&_!?Ix44KCKM(}J z0I-Slah>QBkwL|>Nj^vH_&spc<>=GbB6iaV;h_4s8eLB{%MQ;VmXzbsmGBx>n7 z6}O?}RVgnG4t-CxchSXNWW#hz6Aq5wr#?gJ>A9=Ci{O?1`O(qOa~(LpSEn!UB%H(e zy?8?&+~cd{h880e8~nM^+1IljI6uCU$xH1>+Lmk@eZyZl#YxAJ3>~D5>sRyAf$3j0 zt{ObqgNuH$gDgha`Fh%w68#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/h0.atl b/applications/PoP/poc/res/pv/h0.atl new file mode 100644 index 0000000000000000000000000000000000000000..4dae700e5fcc814a62b93126dfd045e7356e024b GIT binary patch literal 4776 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/j2.atl b/applications/PoP/poc/res/pv/j2.atl new file mode 100644 index 0000000000000000000000000000000000000000..9426213c50510e83e9060bf82fa44e0a1c6230bc GIT binary patch literal 14081 zcmeI1%dXr+5QZmuYh<7p27T8 z)t9R7>h>i$Q9$v+?#usmx7}6ccKer~e|+^|clXENyJxTN-P`T{y8oQ7zkYfBhVoy0 zb^VsEe>{Eoj;=p_eZ}SXzj^SQ@_&B&_$6IW`=@;U=SHr-e|N?C;rxWT-!6~j`h)17 z?OyGs4|N>ZHgsG`9C57bV=b`Xuj~N8ssZiy`?dNWaJdw%q5ZCY9H;3x-QGrw^!r1b z8OQN59gk6efRy!g@u&-caX20a?m*=GQPgsdJ%=mlN9YMS;b4&gihdXvkHhVW^~9XU z5>P`s^N_~zmX+zGhZNFbMCVDmC;%o)ijZ>;Aj(3}pC=iU+;JL`Nn{B6^TcDK$u^=Q z6Bq%|g&0R6Dzd}?_&(gcy%gGzy}7xe5zQSMzPa%`sM^R-Rn;pXtXJ>U5%7F#^jjN; z;6m%-1OQBoGweD7ZCJ zAp9AC%8ITy5Fo-%w+!rSpg=e_A;8{pSV1_hdN93&*aY(%;38DjAmYGcxI{!2Uu*(0 zuyX`PMC5en$HaZclZt*yE}T4?s0>#QW)dzdfUxj9GbPcFA5$w@S_S|LFwq8mepklFaz^UTI>yO1CT$Ze^p8m_}8jILWVfZ{qs3S_?H; zQw+0t%SJf%7s_PUatfQ%`B30M)ON8rd z>RP7i;+YbnvgCUAHbxv2ZVEcYZHhYp;Uzir&mtlqFU1GiF~=0- zqurN*N4F_pQX-*2WVKkQ3t0`e*gPyK_^6xG-A3n&YIXI%j0Q03#TfHBmg_6uU|2Xv z`GJp2sgwq(S6QM+gk~x}F2&?zqF+!1gp>*l*1;JjkTijda(m4kh7{BRr1*|t-99YK z4;DSb%8o;qLyH=XGs2*3IkfxKY*HNu*?LF?jYRBKRS(mG9-(Jcaf_1#E*5K{R$`c&!zEa_a*mi}-s0O!ZaxRi82xly=(hl6CvMU>YygbG2(5A@d#t2z z*8Qf!G=L%=kpTV|buLSl#Pbq~_8R=7$?gXVF64MVh| z$ufS9;wT=~fP=C0{7j<30v6dauUCO8w0@O8Rkke~wmAW2A)CLsQ8&Z;RN;~HHm;<3 zE?s~N8)jfFpv1{~ipk}yrMO6;(N9WMnU5_q8s{#2Kg7D8jlw+=#c^O93(Lr@=oFin zj2A<~!!IZ|OW!K-9=S7RJ}$t8WY3Y35GlDfK{ffd5?dr3o)r&vuQ$9&*6w`bdpw&( zF|t-x5jLu}0F>udZ2V0TOI!#~n&(p%nd!S%PkqsKB4WGHq6tGtCUnTNDV>bzo75-3 z5o8FSwq_sFlUR&zn1hI6nn9#+>X6BP*RWbTheu(W8Bm$XE1Owbg$Q&bjeg6bac zUT(18vei!@zsjZXOeN4R8#ty-Ip7?_`^%SUh#otuXVaq}gyKOnJLKmx+a2WCjV1$g z^kR3?anSScLCii?HT?&5u+-oRX>WN>AA&&AvyI6R>N9x)-$42IQ7p3p=)al_sOxFU zu0xDj=x6*rfex1Z801cz=~SJYWRO06KOb9V+2|6M?ToQqv{p}R@pfxo&02trz-z~p zrMQUF1$sLU|5_4~vocdSVppzN@8Qbk@|1e1%H}*#DywWR)%fq_(&nPoVQ%5c)e6$FxkNy& zZIwIj*_=$SF`{{!BPb&)ZD_5HzW5*5+>_5c&3UVhEJwaQlv^~Dykg+rbJ4E#cS{rF z3XX)vBQ(nvY#PXWM&T_%o1w|jE0$^!AK0X}373uh=G|i2@!h)9T(G?DD`S?BV9xet z!q2RAJI$d9+L^SQ(vc}wrCy?LSA zXtP>-XA5=-*j{vcdXmvPT`im-E-(f)abWU$KfmLtiM;Qs^rw?rCz#>&*WPh#xQ5}? z`>w$n(#0_JGR6Jku=}n_Es4Y|)H6j8>G=U++pK8Q&C*((2efeCmG2rI$iC~6KEiXd T7k4aDXoXd-A}d9|;57XU@Aetf literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p0_0.atl b/applications/PoP/poc/res/pv/p0_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p0_2.atl b/applications/PoP/poc/res/pv/p0_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..822e3c36387b6434646efc8ad002306eab887383 GIT binary patch literal 15796 zcmeHO!EPfr44tvZc6L$wv^I(y>Td|J=eYfY0{xNx|D{V(5=Ba)hMuvT4X}cfktp)% z>50;eW9Og0|9TsT=a+wn|K3Iz=+E&@4t6ZY-fp+!Q92C6wBH{NyJ#CXWkJwhQQ+_&@^3N0ABj(W$a{Ry;YA3)xnD}U9m!hJ=i@xoYq&%`o1)>1TJqj@Fz%J93NUx@X{r9ZdOyOv^q3^zJ+R?qwHaD&C7`~xbRa3mY3 z%Dk4M0;?Ks^fqaCcr|b^EYlCw+wJ!IbQd$ggLy5*E;ziqREM+eOiNyoc843l3-t@~ zeKP%8q(4Nzf!>hA?QHhh1y^k?MPuco*^k~N?GJC$SdxFZ6Yzm_@7us@DYl6xs~6oM z?F~=EmDdIO;U6<5I{pp^&kkNov81fqPIQa3GhB};TEFyj-hPB;vbsP&IQ?id(Br(s zGU{uW*no{@FSOhp!UFx%t|*=Sc#y7Xgv zagRb5qq0M)z(WI`+68G9o?P#^y3aqz37Qq=hS&;VWP9qzE6gO>iK6A#v?Hd0TiR20 z2Rn|+I zL*>G}@j|cuHLOnL5%g=u6{OR+K|jYhif2~PtsjPjsln49Kfl1h!k+qJ>M)b!4-{Wt zgMcJWvK!Z=8_vn$AM^`6(y7sm%cKQvNZ+PE^RIc}oMZ9phZN(Fpu)D)7q6rr)rsP3 zP*7kL{tAcYn&AI?8BF8gNeq~0CR&vK3AQ7r8U8{4##s7?A^EFBp#KU`vbL4h~E;V*!jIM-lL z{b;0}6#Q|9m-87C@b#PJFYPE@kS~r$Z5Xz74FbgRrc~<-@b(JK;+lRjYYTmllCSA=<2hFyQ@KNL@2uI45 z+95#o9pN?=Ypm`0JPovepCDgN&Z5yKUCqr3H|)oha#hxG04!1d{~5Bjsc?Xz z7v1rYR!z==ZIbqf8?d-ri|tx^)?+lOlwqkI!tn%5mPcM|au#}%v_IT}owslB-%Zp5 zV3Udk6vCgSUro;9(Io8;w_xY(^%|8_Znq9f&cA0QU`@`VQj#V=f4BuZZ?DHFsNBBy z1Qhx`LBN`vHKBrZaPx;-usu5+j?)&t=m+0>rAGLbsT_}|)9v;h1Qgw>8pCn_UazVr zH%_@~)GQAscd|SfP?AnY4hKUY;Uv3znsFaQKpAO;@artQ5t{EeESU%)1 l;qa)R?v1U}scDwRwhN;Zxfzs9nt}D)z}y~&{mBE={Q-|)gm?e| literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p0_3.atl b/applications/PoP/poc/res/pv/p0_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..f9b4968ab591eb3b0aff0a68e6b0ce22b02a725e GIT binary patch literal 15796 zcmd6t+ma)<4Td#|V_#@@?z~mwz3?-%rIvPDYAJbxRQW~n{x`}010Vsm*hkH7%<-~$ z0738*K(ZzO`R{)5g1o^NF3xqts@jNhJ#=%AvO znx^Rp&`rtctWb`6ef?~TjNjL{Qh>Fw$x6v3Qkbo7x2STXCxmoT0+uqETsUwa_|8W;Sb z$~Gi&>pJFrw2r{)q-eX1< z;6fVx^>XQ$uy*`&Q+S%t`IMUqvz!nbK?P?ybR7D_hhO9Ch*K2DHOp-2ojc?!Y281bP7(8vQYpI_{j{alH$|GTV`|cb075d%11s!&xL{uZ1W*Hbc2np8 z@I8W37wp9&>3@Rm(xHS1ReS&lWD=@<-&-k@gR1FXcJsvT`$S&qJ$Ar+35;7{>49!^ zmEYp)G)9x&!Er=-?_iq%7W^*#9~FKQ*Telz^6n=NL+=s)Q&$vtnkAR4_XhtzN+hPUr#N0T4BY13=jWHG_1KNp zzFHpTYyH+nddd$`W4@9KXQX3BC^a^zfBEpZo%Z zbRQ-aB{bG6{Z3CX0%35#5W!q;x=6dxBl{nkjnBA%_9(uV-Uob)UNYm7EEoUNi_8F^ z0CQ6^KLrRc6bE!4#KCOt=lGOSLC@~bLPH4!M0BuFMZd}UgF9fp!~}q3Vl>?789ehR zF`e&p>tTlCQ-HGXk)iK@WXrhqBwL`HLVnx_kn+189Z;Rno{Ziug>PDOU@+})mENQy zJVzXms0^SRJ;QJQ93j2i4d+(d-a9g~7?ypnq_^8SJ1LQvn?igoo4Ay=Rcs)cwn5ng zyRK~gi+1n{^6DnK`Sj^FH6hV%qr-|lu@?*w)C)4)Y*-*@<`wFC&} zt((GHk%XO`A(n;!|`CTx`O+n=$F_5_r>e~m{F}Jcv)eJ}CBq0x;KC;pKH*1fmN?c;sXGyWB#|g)CM5AV z^!TC<*NpG?=&#`p^8nC|Ud)d{tcOoI1osy#kheg8WP;#?)v}Mw=zY>a5=Tp0d1o#~v*kt(zAZZ`}&ndi`xzVv>vYdVh7z&-SI{sHYuvWStmu!9X zN+vWElEwX{qKVQ-HVFD8lCQYN{D_qJ9qna89(XZxez9Y^Ebnvq4KT~f@+uhsrk{AV zmoUo~X?Ig9O4u|QhN^6X*SxkLQV2nZrMuCgeWplae1jf{$?|F$Zi7pbn=&HJAU$}W zSAGb$JV~)^H@aw^t%%o^Qh9=50k`F0Q?2f%)O!CyZE@LdbZlK#2E!;gzQSby-IQ@n ze~F&BXg4~xE-e=TLFuXFraYzQ2Mi8)-00A_qF4}2;|7kInrEKRnlgSfotap>t1Fou z`Lxj$R@*Gua+NQaw{hO2WYh4c`8|Buajo_7j z`|QxOO@Z@!cKPy7!Zi%vixpB$+yi;U0W46KfaR5OYOk4CDk;0`c+gs zaYZIm2UAuIRZ&r3_OBi+12$@Kqcuep!T@> literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p0_4.atl b/applications/PoP/poc/res/pv/p0_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..b2cb293a76b88e36211492097510bda847c9f23d GIT binary patch literal 1396 zcmcJK%ME}a5JXvB;b1~9cAy=Y*oggK{7Xof1GCXRk^Q`REb}yo`@dGVsT@J0sO!xY z<9RAgtDO(Qs|9p3ESK304=JU2MkZi-K8d9YLHo#>#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p1_0.atl b/applications/PoP/poc/res/pv/p1_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p1_2.atl b/applications/PoP/poc/res/pv/p1_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..56be514a62374bba22921d86cac722c7b554af42 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p1_4.atl b/applications/PoP/poc/res/pv/p1_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..b2cb293a76b88e36211492097510bda847c9f23d GIT binary patch literal 1396 zcmcJK%ME}a5JXvB;b1~9cAy=Y*oggK{7Xof1GCXRk^Q`REb}yo`@dGVsT@J0sO!xY z<9RAgtDO(Qs|9p3ESK304=JU2MkZi-K8d9YLHo#>#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p2_0.atl b/applications/PoP/poc/res/pv/p2_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p2_2.atl b/applications/PoP/poc/res/pv/p2_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..897aef58a02c457fd79fc8f4ca81f4142202f298 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p3_0.atl b/applications/PoP/poc/res/pv/p3_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p3_2.atl b/applications/PoP/poc/res/pv/p3_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..a0ef0f26c9fc712fcc9bc220f1541c9370a514dd GIT binary patch literal 15796 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 I7jd5d2Sdt{O8@`> literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p3_4.atl b/applications/PoP/poc/res/pv/p3_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..b2cb293a76b88e36211492097510bda847c9f23d GIT binary patch literal 1396 zcmcJK%ME}a5JXvB;b1~9cAy=Y*oggK{7Xof1GCXRk^Q`REb}yo`@dGVsT@J0sO!xY z<9RAgtDO(Qs|9p3ESK304=JU2MkZi-K8d9YLHo#>#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p4_0.atl b/applications/PoP/poc/res/pv/p4_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p4_2.atl b/applications/PoP/poc/res/pv/p4_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..0eaee8e78fd79747992dc42fd335e6b27d59420a GIT binary patch literal 15796 zcmeHO+j1K>3|+}9$;niDiZmTR)ZgHlJlpLbbf!Pj|9@!!kN^P^q_DejQ+L8iyd;Qo zaQ2XUQLOy+=N~V->HXW^(?2geDD?a8MHf3)wI3cHc4wV1P4n@1IvsLsx33$Lj+%jw z5?9TI*msWVR4oJ_2@Ci)VRh!%gTboJ^TR#=44i4c-%*R}v4Vd}Gju_A&W*r(v>12% zb2gxCziGDZaF~m?ojUWez&j}$!V&iU-m0EKb}ku#kJSD2Z`22YdV^9Og=Z>tL2!qoh4zX?x;?fduMUrg1;hL7am^a&cD z!HOmR%-H)!&9199$dBdUqinww`|tAz)LTH^a&0=|Kd3PF-JO+6i*V}yf3L4 zJX(k6jR*QOKBm>lI+A}bt`ZH@#LMD%96J^V!~P*8of^FT$@>dvP;s7zQ-?F*d|qN6 ztK-vCGLS6GarO;QPsfGN#*=?Z2j4YZToJ8!)An`qJM|!}TiidCnExs%663jiB>$){ zS6IQ9+^6KA;>I~TpDI=`mWTHr&12ObPKz!%vj4icl3TcogJJ*l8tGfHjw5Xz)0ITy z?4O@^yCnw&C*Jw_RUQ=J-A(yewX^d@+u4uAwl&ZQsW{RioA zVrg;VgKzp5z<=>@4U7S*YJ>4b8+e1cA9y$af}cO|?uQq! zRT{U?uFn5w|0zF)OZ;@Z{fdtemA|YzWY<(bZvVl@M*E9iUy2s1`wd)A-fmZVhtaV` z{UN(%@-tqCMmyyBd8>aTb|u2Rzo>W^t}wc*{Y&2rZ@)m^tdYNznpXE4 zc$s|0Mz4S+wy-x~w@kk1Fx-;Ki2O5OsfYKZ<_q}M@2Gq!!dZ0&ODz6XkOjj5yJhi3 zhvAk?MrZg{8aHqb&4VV>)M;w?QY@&98hDwS^8Q=@_^{7k#oTm#(IasUg%cqEl5yl* z?Hxjz?+E)+9Civda8A5lCp7SO+hAWSj@j#@FS;wPhIhti-g3>Z696nx`TsK%^`!{F zc3L>kUvs;0V8yY|UX!_^H?kVLWc(JcCEPfc1E3f6T7gT+wm3 zrdrnD<9|0XFMzI;9%&2LnOprEv+GK(=r~+cE$g>yR7-h#8!)zTos9l%*??nqU8xpL zb8)z)TGnrCR8ro)jtp$!x;*)}!N4)Qu2hLmQ*pSaiuUQ|I6uG_{os4AG)O;+l=Jy= zd3*Z`2C8Y(b>MnF`KKdp|Kdi^XJNneG6bkab5P(A7&Bb-bjUwIK-z6E*^8=d`=*8dF3|)SjD!?{@C{_e*NvA84dD?{*MN xUKJ^@^>Fi{ff-^C72HnZDErq=*vRE#$7y8Q-IM|=q9ta}4Z?mI$)^l(_Zv{dgf##F literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p4_3.atl b/applications/PoP/poc/res/pv/p4_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..1bf5ff6b106c074544fd50bd6c67b419237bb2a3 GIT binary patch literal 15796 zcmd6t+m_ol5=GS$Px8=qzu9Yb{E%;8BvNislqP?WwelnL|1XnsstTwA@1)$Gpxq!Y z1>j(-aG~0N{rN}rba?;p-^2f^Ck*tTr)p`ij}l%Gn0T_FrMKJdet-JcPappM^Dn>r z{Nc`UI?3a%7G?ZWHZLnbe*DcszkHAvDTC5d+=Pv_a?#$+P@GNLO~z6)06#v<58-L5 z7lYre&rj(g?%ATJ7wd0Wqa}&oRDCWVSJUf#cC^4R_u6Z(n9~SogUbLL55F>yiVm=nDhP4e6p`HYz3*vZHCElAkw=ZLSi$?)h2Wj~?h! zoZs!%l3-{sJ-!EOmzp$l(P%yc$2*TWyc8+t7m9pDqjY{^8APnUA1&mkhc z)YzWiG~~WV6UqDFfTQ*63-fHI0J>;3T?dd`f_H5x@XKYQ(58$>ckP4(Xtel2mSqU! zcDXQ){CFCZUxFtc8UE|l5Ps8EAx>Cq?aBgvZ-9M%D;F`%h6EEnnTV{sVMG?-LK@w* zZ7&R9fM1uw(}d2a+|-!qgfJ0oauABg?pRfb6r9CYCKVf^sQ@|jL=l)s;YA+_`T-hO^NPk0P~D_ z4;;G82)}2s9Gd2yasnNOJ|$!HIKOid#8tpEd!$eVBOypwE`>$%Y-g+7*V+&j#<5)>nE4kWF&Q%uy4- z=u)HzJEjI*wFKC<=6?e4eEw$q#QmBKu;lj&Zn;Vrrkp-gj#ZWh!8-FhMRysz`MBt8 zhJ-NO>Vb(~A0O%AICY3J3FRn+%qd+;BG)E{;)hC5lHdn;2<7UPj+-X)0|ulUS-4gQ zMhs+yT0AW$)t;{F?wB0V0gmi}E;{mKdStQpO)L-EOeKD4a}w|4B-3?{X@)$NCVL|h zx|B#&7~=RgZty@!OFzP|vDK?g7z@AV*I(ZllvTo`PsCiLTFVwWm+Y`v|Jm=ksaHhHnEj_haE6q0^=4~0(8-%Otx%;q<3^2k=`5F zCV&OMw)=gApX`wEJf9Eh^}# z$ac{IOMu~IUF5fZ5evP_a6nyNxo^j}w-X~>!}s8TOeTOXg&vtaFghT&K>39Owu(I77QR5 z9kZ83y_$uXJ77M813)q{8ZLSP&+=uE&ez;J$WVL=Q1(4S^!<-)xo$nk7U)vQkNW_E zyru|0&{HS0Cs*%9-`sr4fx*1PjnsNCIW^&cL}dV7^a8)-bAe) z(mNcSouo+2r4V1uB2J{P6`Oqbpe`t9$mLR3jxT^?8zJBxwxu@sAr|no11dlly~J5|VfvJM2)nX1w2{yM{Z=0HBLr&5w)NPVb@(?k|`i zZ-M^E1i=ZjWgVH(`=o#*7}(CG6kUW4dEBB04f?023-GX$yzR?-8074_}%pxN>BT)@-Bi``u(G$pM4!TzelGp?M#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p5_0.atl b/applications/PoP/poc/res/pv/p5_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 zcmd5?QMT(Q476iAAMc}kc{^x#&~v(x?*FHqkq|~83{Lz`>)c$20Ad&7qw(PHU+4jwqtKzojFV^L?z0mgcS~vE={k5a}X(=~V z(^at^sVz%~<+Z;S0XFD=B8z82`#^}9ULi+i!$FITmNwyx#$L<9Y3~bgf&pKyi@BaC zwin(j`NJEZ6Mtr@?&~dQOTCf(N@D5m^>6L3I@2C<`oOZRAY(WZEwMRnaJvPsO@8lS zwJN(}Q7R3MNS^z?=1(`v#OW&goQ3}Y~B&n zb-Ac6>xqIZ0LRlGe5U_7OGV$nay?RAcJzPHzqXgkUF0u>WXwTe2?0byLzG!$J-dcq;h5SSC(?$(DKB{wjLk#kL_O|rwh!E1lOU~wA}dK9_we3w>?ATst#C%?If!OUxpvl^+XZH zFPeHw_~ODcCRDZeWu-qxX355n=lcitr;!gW0Xg(er2A^k)2b_7#b*ED76Q6TMM?U~ za$SF2R{E3L3KVKP-nG4L?;&sJII=_s!hRX)KCBzUqpu4YqR@=w1{()|VR-Hzcn^Bs ztXJK~EDJz^LcKlSgUG1^nVIJ`Pu7V<0VYcQOzu9g1T(~tE&uBBv&coRFpdGcQg4@K zq6nVf_S=?JKvAb)JZ2R5aQ`x-j2o3)caiJ?mImLDL~wY2yyuV$XPEpa>WnCugW-hw!-NF> za7`&nH08)b-hioowNi+qBxa(qTGN?6ggl*@4$lAJaR`r72j){aq6nTpIM-Q4*(L9{ z-W4#Jys$mFtdZyP1IPe?6Dg7H=7?al{&V66AC=^bg z+KKW9>W`88k?!g~Yv1F~o&O=CJ7vbL=fP1lF8Iz&g@#P;eI5EGa#NsbW~KV#n#}6J zP83`!M)-27uh<*Y6}BijRNZa%uV(Tc^4c=wcnVS7{!I*w)RnGcvwzg*e<^s;8m}~x z_q~vrcXWjJ6oCC6S%Nkp9cuI-X3B!k28 z<@xO+K`s&>@{#=EU8tjcUPqOKGn3Q#irI(k%zar{F$OUer&t?2H<6#( z*DNVjN*f|dFD;vSfCkP}#N>v)o z_&ZsYCMs)s>J1u*D1!3?7+3w(>b}VN4h;MCJ2RLGqzm@W2X+6TaPTJ|7sz!+5~AeJ zhLOAHk6*C~)TngQQ#ocdrKK!0Bqj5e=LZPhwk6jMe??+@UU|O9vBCQucxQ_`0AO9} z(3{Bh3{?Hj2FUl8KUN8Yn08-HNfcn$(8B||qCCGz7gAsM%=G2Cg1F~i*=m{?J&-#w zVVLe@8M*l`a+wL)%beju@IPK45Tu^qQib>}OT=lJT_!Cy7 zBJeBZ;(Xcn3d|+I)BlXXAIT9DgA&B8nj?R~Q+TTw8~TppO@&06gzLe(tVl(m;DG=^ zE>aZ-hnhu>XWQg___x#^&c^?gM&ZyHgr0By4onn?2!^=6k!<+<*0l7&3%y7G1F7gt z$@AQu508A9Q3fr9fd`{7i<>T;Mvk)%wSvEKba6)hmnqByYDi*ayj0y0MfCp%bxXsb zv*P^Vf5#isK^EMfyl=uD%vS`~I&c;_yiewj9C(4VVB7x9bMSuQR zd!M8z_#dCKL7Y5}KXs@7CHtO0)q&jCZXnOzH^6z}f1bcf2a-RemNNh0=#MF|P>i0w zl%ME-7^RWZE6bT2(r-$5YUGu)vzK!GW*Z#*KHV;JR16^1~ ztfPbkE5NGz{{s@36{Dvw=O^Yr!W!kkm7zUo2hz-rv+q6dCUVH`l0L&}ckE|_pQ$Vd=80nO#7sXRyo&2{Y(F2y(17XPaQZwPPNBL@zHmf zqsRVN5_qaRje>x`tPo57n4?^NvYZm#NgXCzU&3hAp3{u%3xOnpM+qMwztnw}nBIH>}*p&Z2+oddFE_bq`@(?x?=qzF5|E z+3q~~h-G{5c#HLMi%mSebW6NoU7hxxed+#-K)#JUwD>y5Q{<6iMG-(+z>n~^E-MQ8 zlkH0W)|;GH`h-95N&>eVI3odI?!jZL*Xwc6 z?|!F?V4I>JT~`=ds`yvNK6PEPHzZHdL<)sL27yESMj!+@k32}oQQW{U3(^nK7T>8@Z%UVwA?0~3AILrJQ6CuI`)k} z#-3y3ao!6BzQv-8k*8ZSi2lPzO;hhE@&^=<9u!FasAq^!f3Z*g7#$-B^2dr8>&o~< z?ZsSn<&wW5MwR-gV&5zD zeE@YN&|+!Ve#zeeITNoq@}a_quJ)rU$rJ`q>Ek713Qd1>V5+a@zmQTf`>H}TmXz_S zj(r}!J>s?|5eO~QUeA8W4m?KAxUTZE{8YTdA&*c*g9U#=Vdq+259hQQ7^;J2{P}}o zO5raABhbzNUUpukvLvu?AJC%!5di5gJ>)!N{1vfzKnY`T(x2?~HCXZo1foCcT_z5G z$kYKPgkttZ-#JEA@#$3TGbLsM83TR$y#4IJW8|y3?<2?N0ev6z;hI0Xai@jpPXb`* zt{((G+i^-^_Ci}ipqKxhR3B|80=xF*W-@=w?x{a-?KlY|t(_LIb;bs2VdkTT&{21Ypzaxa>^EL^~_~i8+vmZw%@y8tO z5IGPx?Ykfm#{9B#W9sh_`NPwpMqx*PMxVK_5GVqv;*$>VY5HKDSxnEqi90)RT)FgC z-V$=~$7f#3uS=Ybs?Q#4+o>e!PcuM{B2s1yClts(>{EYh5&mNDaSAE*cOr2%Afa|3@7pS2%s`vlf*ob?C(ZgKqu%%%C4j*B0LMmjMdr(P+LeSUU6 z`YDAfK+k>xU^6h0OXLQ?J@S}6OTa-sIT8IB-iM`dW9lyjKpjQS9=doy`N{JepW=)^ zQ-}cCn)N@xJ0JbngGE5ces;eU00Ef#bCIX)8S#(wO}OtNfys?Ue-;2^79a~(oI3LQ zK}UZOV4a@{zO%D$)}L?R(cc7e4!1t{=JtcVq5gO>&c>zw@D52wkzPP=Wfl|lr}E3w z2XB%NT(AF;zia4Y7VFzDZaN3RW8~^CbMAYrKcN?HT^;IC_|N&D zjPC`rZ*pMFz&raHfIPwx(*bfy0n-QG4@&qe20*3meIDvh86c@Gnx&XN z;xkA&KVIm*sy_&PXTQ9C4kwE7*FlcwFZ`FwRK{4=0nC;AD0b<8->MxVNf34 zv^VvLD*q$CEA=P*!KM_u|AIi$2W=f7Cl9dTv~Ow4$80lGM1Po*G79LiDa`+bKN;=Z z2QWGMxHA5E_9b#Ndb40ar;wNYY0L5F40Uo%j4zo#rzH?Z{*e6TlJ#frf6agMH7E9h zE<{)HPyDY!D6cV9e^tF6B;+N3&ULR>wfd8n0PRP?U)|Hv>W@*f_wZOZJwAdR*M8M9 z^VfowV?!R~`w<)F$BT=`zvRCdFt;TA)!pyo)1?5Hz5Tj>PH4bO{WUSCIAr3V^w+jO zdyH2rQsBT?D;*S|z#QFlhx?1|x#9fA=yz3t#hv}uHZTc)W8{3CQG9Ltv&Z}jgOo!< zOmQ6kti5dCz+Yp(h3TPcuL;47tj)sw{>tyxa9p&YrR_Rf+THWlHlYn(c}=0I3W{Sk zVyZ5y4@x7Bfs<89e;eKzyP*%2SY?(sqOJVUw~efBFC`0K zPWzKpNIg;+^0zqTq3_ffI~#)GNjOb&J_F~%A@<7GGUP8A=(YE3&enZ{KbOD+4m&@@ zAD%q_5B&K8{4el_$6n8OSx5g(5Pw#GzHcUQKc_!T7yl9dxN_hXSqVJk4}splW;T}W zudCu8?C-BY4f*R8Ab+p^LJELDH}HZLQN~5D0@O+JYWm1cY2O79usAiNzaIU*(S7Zw zmfeY*EeU@dNqv!!_n5z64Q8tZUzyI(R{Ws&ydyIb>V8_5r_2*|a^ZD7o z+`e?GTMR0geQW>L86b-|4R3|~5*T2QcHp4PvLld7yev2^JsS+$VvS+M zpEbuTus4)s9f`1Cgugy={nja#5KffObZca=B@l_56c9+?)hJl(@D=btf4A5i-&%h| zN!IZ_alb_V3c%?Wa4YzaBNnLR^^)mR2{r^8e@~r27yyMx*~kmF5q-h#@cCB$o_zr+ zf%KLdIa~_->F7S6uhSO}_`|Ce7U@F8CyuO26c9uHB7yIr4+E~?`t(TxW4JSNlE+5* z-d<*~(i^PqJQxeT{0Bl5j3q9}6PVz_d(H9ek($2@an$h8It6GxOAQKqhI$EGhs{TQ z^IR!E*MadGczarMie8;E?xScvzVM~96V@HXsM6jhnvY@a_}dGRv)tIo@a)OvL)#ef l9|m{KALgj%)7EBpV|j3URBzGz@apegFl)Vod$xHE>mR4eqD}w+ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p5_2.atl b/applications/PoP/poc/res/pv/p5_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..ed06ed1216df440887c7bf6f69dad7856f90dcb2 GIT binary patch literal 15796 zcmeHO+j1K>4BeGhl9P$_FP`zU(`GW7*bjAQ`Xl}SmlgmC5WIj3aqXn;nB#~`EDmsZ z2vRFs`RmW$@8{|5+uzea_cJ8=ZNAsZ&PDG1em|eJ!!#|&6gJd%hc924Ad*0hGXL?6iE?QHeg1y^k=MPuco*&n@0+7EBj7^y$p3HU&I^ljj+6x+nv>WyxY z_QE+`*$?Q4zsy+Z_&c3EJ9sO_l5#$FMz=^i;dxFq`q0n$_M^n+M4iU`dLtLR9#l4bfYqZ-Cdi!7`*D945;Feko&} zexL46(C+R+#;p{ayv3NJ=UK&pFWRINoc(U=-$cI&r^536yyurJYbC>b(r@|%jb|{h z#Ge_v-s|W(YlVDY`rS$S4bg3(^^!U2o{!=O5Gr%?fKnZ3Qrjo$q50GhsVXwEUWOjA_Iz?Off# zj#(uym|!J%tVt_7>nDEf-|4lyx9CT80deU8kUqh|=U*I$pRA{u1bd)p_| z{q*~=I*~`vuZb&2b6zILV_%;XbnAzZFg1Akljj$lYO?cvm^#da?YzW1Ug=pkl#Qh9 z_d&nFgHMenE|FGoL-{uSg@2tN=Ny|~KcpD{DJo*)d$A||=vsDI!WX_(44RM1*YoH4 zBOlCa;WX*wBlTzEl5dL5>fP}Ctk*~%#oCTxb=o9d6xu(XsoQT!?UxPNI;BgLj)iNi4oK(BS&+FuF?Zc$cpsr}TU zqxi=CTk&t;RooyQz54aTnc)vK_~09U1GtNG4R*edM%qcipJs?Sb>G6F9i}Dkw*O@AJOjyeyD)7Tl5o9sy{K67pPxsvPIa??`bCBg1w_<(N)$8+9sXB zE6lp#&GeIxZ?QtwsU#1mk2F}h$^=Ls8f(-$({H%`aOQsb^40nO)Sp`atU{FibuQh} ze_j2-$Hx2*y=^L%9u?ej9J0Hkw@JI<2D!I7|5Q46g(!^w(jC36pBrv4()xvOhSy&p zuhytd#mF#KaLaMX?vCCj?S>oVNWbhW_3)fjeF4At8&#W%ks&|fEvI3-yMWrH-Ef28 z8UB>U4U|Lipyke0j*80n&~g}$DmdSGmEIk_B2DStaDy!A7lb3_O6?Frq}eENF!Uo_ z^lg`xfPgmB3gOpT4l@iNzhX;NaD@6{4PLL;7sB96knia!hs*8!0=TcVwnss(A0v?# zKNmUiP8aL?eF+Wp1CG>|c?R3#BPy7ve0_L$2-1OiKYqNtMBoeS2LdX1yjygaOJS&o YZI_(fN|Z!e!sl}Xb32XrlLyH7FOaE>S^xk5 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p5_3.atl b/applications/PoP/poc/res/pv/p5_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..510f32a27d25a796a45ad51e1acd706477c48e8e GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p5_4.atl b/applications/PoP/poc/res/pv/p5_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..b2cb293a76b88e36211492097510bda847c9f23d GIT binary patch literal 1396 zcmcJK%ME}a5JXvB;b1~9cAy=Y*oggK{7Xof1GCXRk^Q`REb}yo`@dGVsT@J0sO!xY z<9RAgtDO(Qs|9p3ESK304=JU2MkZi-K8d9YLHo#>#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p6_0.atl b/applications/PoP/poc/res/pv/p6_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p6_2.atl b/applications/PoP/poc/res/pv/p6_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..4b043b6743e267fa1fcadd6ef408d5df47364f3d GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p6_4.atl b/applications/PoP/poc/res/pv/p6_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..b2cb293a76b88e36211492097510bda847c9f23d GIT binary patch literal 1396 zcmcJK%ME}a5JXvB;b1~9cAy=Y*oggK{7Xof1GCXRk^Q`REb}yo`@dGVsT@J0sO!xY z<9RAgtDO(Qs|9p3ESK304=JU2MkZi-K8d9YLHo#>#sF!WL3|UfzIi|et>fMc!PDw4 ejX}~hq*@OxrZl924qz`);Iz6)W1uumx%_q>=Qt?< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p7_0.atl b/applications/PoP/poc/res/pv/p7_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..e39209073af2d77333eb30d80cff4c0e97dfdc56 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/p7_2.atl b/applications/PoP/poc/res/pv/p7_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..1b1c3613db6a34b9dcd61df8fcfeb463f6a8e800 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/pv.pal b/applications/PoP/poc/res/pv/pv.pal new file mode 100644 index 0000000000000000000000000000000000000000..ec99c2b3576d76a4da8fb78b842526ccbcbfb97d GIT binary patch literal 1024 zcmeH@5tqY26oo(NPgp4I4+s>3fk2^92owT=LNHJmSO^RR3c!eQHZT)o_J^D6H|g4c>M9BIu7KU*s7Bz>u}u;}FA z^`dRbX4g~H6XV-RqZ_}MR2|A(xvmD!NyS^cc=DKeCy!Xdw4xq9h#zJYv79aJE3ubG z1harNP^d*kyy{rzNAmi1=l{~b7#AVYPqhETAUU+}mcrbK%7G+&r0;vL<$7-BcE0nw qyZHGn{*$!EoGdQCTn+51Go6{xjnlpOxyuiXpShjM`Tnmzoxm5clc+HO literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s0_0.atl b/applications/PoP/poc/res/pv/s0_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..1035efa48d422517480662972582851f76e4fc74 GIT binary patch literal 15796 zcmeI2ZF1uv42IJmj_ZkMdY0Z`_Xaz&7uoy&+12|>!ooHt$uvvTLZ=vn0P$FSn91*7 zKi^+Yj}L!Nf8SrEqaQEtr`H(H<2=nvnC78veiCaYL@6{kX&%p!Se^zyT6=-Pk`#VT z^Ybv!4zcF5n6$@F%p*wh^Vf|Re-5wz|L5o7EWzI)-)L)-Cig!_MsA)_ehN@(n%}lT< z82SglfGo>^-(?ubk^dJ~Zkm@FEX!Y06J!g>`mgburU0A!N3;BM_@AtR>W{Clwtp;> zo&2Uo`6pTbw)_Mq&#j+f|BVNju%)##akGDH_|Y~!`1kYQ<_NsUkB1nEIDdifvmExH z#x?dZ-Y{h>c;o&hvSE z{N>rR_b{F_WH=L~Kz`Gy|CI@x#&g1j1lH$ix-h2ym8OKTcn{aR$h~{#9Xe{|#G)Q0x0|ecHOl&i;$jU_bvk zpxSeD{)>m{zW-M7fnP+%v_~Jd{=G>kn}X6+IRv$}XI&@iUsFHo-<=kIPK!@$8!43h z57vcn+&RICc2SFE>v9ni_%Zfko0HcsYkWva+JBwdz~7kg-cORazuNa3u`h*v4U;Mu z(Jg675&Bq_yR?j55EX1gPgegxqh!x*a`8V!##=)CyeWi||6#Zx92qHUmEoAi%50VH zmsx*D_?r1&HkohtKN;{F-o1MhlJFCWh@3+=nPW#8(hz&B{zFBRsTOq*8EzyOKh#`$ zVaQs$)M2spU}c)~8cR!VTlKkF{|K;t|3_Oaik(12qm}_81+h=6C1pB3*{EZllc&)` zl7#k6mipS$OFF4vk)$>hEG=~V+^TDyT>m1n>@w?i|1%Uc5QEu46O5+@%@m>QYTX!% zhGe5e(i}qvvwd4H_m1;IBsq&7JR$@GfT2P18MxskJV>p|OM&7_AH zqxJPEEnC0TOP10u^qX1b)>WSA{O4Du*=5%4{s-$92W)FDtD8lM!WqSpV+1|6{inWew)dfA!qf|K#|bF7t2q zzi)?<`)@!d_^!JD>;6Yq+1Y<_8m!;{MSfp>k{*q3bN-8mY0Te3<;bt3^9|(mzle-! zkNWyobpi1|r^RQueL4L7LH9qRP5ZBN`|$g}>KiO95KmH1{O_Une@}wp-j}%Xzicw! zz5Zc#mrP1C|4Ou>Emmc_Y+s_#MxI;szSP9`iPoYFk$!_PB_;A)mxo?f?Sfy1>>mNP Br+NSY literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s0_1.atl b/applications/PoP/poc/res/pv/s0_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..7002d5bd7565eaf1e4caf124acfb29e660d660ac GIT binary patch literal 15796 zcmeHNTaFt?43s~#f?)xB7T+MbL4aH&_y2KNMHb1P?vZ9!vXeD-7U*X4&1%kb_4i+Y ze*bd%`Nuz}AK$-Npg+ERKV84#_{Q=2aygy8uK!-!F>1WR!lWZtE0~dfrQVKrX@_R| zGUKqoTr22}tHHw{Exn-3dr?WJP3doa+WK_5ybO?CxjF;CEAd+xnQH))2iE!L@R%$) zC1Y{|L-SH^VDrn%>A-iIAz8HW;y`%pu8cNv< zYWy{@i_`>3_a*wR@HJ2>x5}lc)9Y5=WXh;!_Fsq0P`jQlm&+Lh@Y^ZOyyj8vkft4e z5;)~xs&A4<8?K~}g>l8xHne_lsar}jB2tT>!rp8-lA?Zs0v%(sD$EnLk(ajn*^cbL z1{q*8014k?{pUq6ol1gbF?l9}rJpk3*}_Ng0)zlG(+(Y3n`ad)>#{`#ise$1WgPd6 z^+1hsuH<7eNB94f01CEU=bYoc=!<-rs=XalB+E# z59%t{l$O&A5I31A33We{2HmhyJCKwt>%#%l_+-k=AhI`R|!Y{0d*a7oQo=`cG(a#>Fs? z8}A?g&o0}`>L+zyUjGuY>&yAYjthqK`8VpnXZBq8W&LM3={Wm)hv58e`fsl&VakyW1w)=RZTu-#>6de_sFY zojHs5kM=V6*8Ynp_Ip8H?oang`+LZ=-*CC>@0M)ksopRq=_bZu&+NYj8QSj1_m_17 zhr?RB(j`2$m_6=GLU(C3`yt1-VQaC73hh-kOTT&l)hE;8W7b>yFFqS=_Hr3Gbv#vp z*>(3NUF#sd1v2SyinJuEy%N{>BJ!8%N z$J6)N_dlM37RycoAJu3V<(y)bm6tL1L35>#@m4HQkqk$bb;Web) zn=F(UwRw@4wJJ);60&i4Vjs18v}(mKC*9{(Pbll1Z-Tb?!Wc~jqiVfUnV;m1`tXqQmOPr)joPs zF>dSxs~72!T(e8$Gcz%U(LQSCikbsF&M9xqOO~v$*$n)QwnSon{=*B%f%lI{Xh_@B zGl?Ao2$`V&ceN!Snl>e*k?<@rjvcKGUy0XNMmAt!;hcoD>B?WTacSfs`Eq9e0Wtk# zgS3WQ_uoiJCqYXnB}ky90zR*Deo8kBmAA5V9!IxmWi?qs)HJ((&~Bl;a#fj9Z{W9h z3ka?MgcfI9_VT!M{d0E=W<%|G^b%p}sdSwVnOT}hMJY*WZzK^z8XGJ6=rNpbS?U@m zuwjw?Ol@$K&28XZn)hE}<{z`}fB&;U4CSee|GU7+f>JpF*7&n^B}buKhbAG-asptg zr6gA64*=jGCn*x{HDWhTSTHYHQY&;an~0+9*R%EC=l8!gG)K5o{|zVet?S>R-8quN z$JUvye+2OU@BcpGBLdU?x4!?~{{7#_?waRiKR^E+`~BZ%|5g9#lPO*~a2)yl-}83x zV|!x%HOKK`7eq+lKL^7OxcL`){LZ$am>dJt(A{$oC0X2UDBea=f9)xA4daJs5_K7 z43LTv9LBk0EzSLT;Ugo?RtxP_2Gje;(f42Tj}SSM@;)Jt1%u-E5ewaDDoi~EuBS)} zgKV2$_^CpmWT11z014*aty+T%I&z5>*3<|a((VPIa#`_a%KC4BjL&}>4t)QMgd&s$ zw2%?Fc$qX4(&^ZXOkE`%pafhI#rvJp%qwEmGYn{=y z!(^tB4=K1xPWsG@jWhM%a5CR||Bz$M3~B^J_)%`Mzl6Y(P&u`mD0E721&&h55*(u0R{o+OQxK==68!AI bK}Y|s&deYU$c;obX>Cj9FWwG_)BX2fhozUa literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s0_2.atl b/applications/PoP/poc/res/pv/s0_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..b1a30cebf3bcf0accc7a7b1b24e2ebeeb48f3fd8 GIT binary patch literal 15796 zcmeHNTax284D@_>T&{7I&oVbiZjh>6B=`SGfJTD^M2e!cFj(F0A-~3 zt*bhx2J4iG-k_eao^2iSwcAVfIhq-M0Wv}DcD`ILXB`adub4dq#X%qr`6`h<07%qd zfJ|W%00G}z{T)i_Y2YJ%1+v=*0MzDVqW%QD{ZNR;5zr6$dSvaXZN)D_roa_2C-y(z z?sxq(c+l+9kzfz`dMtq4|0-k#->&^{FAPs5-`W56p1+S>;a}T&=lq?0{`<8P`&rSU z{sLrpe7=4t7`yB3ggDosz8h$(joojkYn{o5Bb%F?!DBp1nyzb^Jz6yD z(64+-EUXG2Uol;Ll#xfKLCJ@ z0SN_kkir;LJDm+Cj!Qsl3z`-<5-9KrL&CLo!FN5kS`sT3%#&)1thF^p##OXhj%pBW z9ac$>wElkN{;x6UEAv11om3wYQColU$2_RNM+EbgHrq=1O6%2DXaB2*iQ-p9ztQL? zRbTh~)u+Lk|Ns3)a6YgIn&-dxF#T=o?}32jvcdirA;Z|4Z@mBIG#@vU`g63<3>Th!+korrIIgaRG^|!nJ(vNw&{<_cP$?3Ml<{GO#HIwWnE#)rw zN=Pyt?WN9^e23rce*kde{!da;bHfm}B|1s9K?1lD9N;bC^2o-px88P9n)5eZW)17F zykV|7+?JSwZ)+I{?E)yL4yteo`D!NXaW>C?JMaH|l{>CW0*l1fgXwbUfz~>HR(=Df zNA0fVa{Jxq&QN*&)>k$?H)ihtBrb#|P2HPFGsl<+bU{K%n2QA1i<=G7QsAIT4M`6j z00?4*Qh7R4vbW|3f~1)gWVE3d9sWQaqW%J8ICp3M{*O-0xwHu1(qv*?2_$Hs0uq7` zStd`e!(+CioQxMT29|sGXe=g=EO5CTgj^#hnC^4BFVBAtS-Q*`?SC~bamMs>TCIeq z$-ZiNbwx{_5~M=%JsB)s@opNiHGZVfD(niJhre2XhwlFvfOH{vn=zPH!YanH)9Adg%`+Ofq9lKn2Nt-s0r&!I5qXf3enhVvjXQGad7 zrXQgprFgRXQEKaJOfhduduM#A6 iU-dUre+jbm$84|qo2owm$Sc$N`@b#IzXyN${P!Or|9$rW literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s0_3.atl b/applications/PoP/poc/res/pv/s0_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..c6254c83fbb9249fd05469bcb2d9bcae6be9085a GIT binary patch literal 15796 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 ORtH9S!O1+o(eHm;*Nrv+ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s0_4.atl b/applications/PoP/poc/res/pv/s0_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..76b82fede907332d0bf79e16cb4b913aa92081e2 GIT binary patch literal 1396 zcmcIiL2kq_2qcp2^x!)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s1_0.atl b/applications/PoP/poc/res/pv/s1_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..245ed564e5b8442b2d0b732b22857551b70aff97 GIT binary patch literal 15796 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 RWW11d61On_i@)Ld`wxx|kD&kn literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s1_2.atl b/applications/PoP/poc/res/pv/s1_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..ae9ab0f43a441de770c8e9d50fd28a3dc4b8ff49 GIT binary patch literal 15796 zcmeHNZEhns40V1aMz98QmfWDdL4jVR_y5!Ky+=~ij7Bp~?I7O70VMI8dg4ek+Wq^l zKi{8D_y7HK`uF|G0{!vye!5&}|JGa6|$H4bP-4m)K=Hmyz=x-X2=Bl&4HkU+ubDNoDxbbjd|yMTH=8-O+f zeoKP~pr;eQZT=_XS0FRgF3;!l`5A-l`uozbmRh`fp2Dv|2H1=ud=FZGp9-Jjmylh4 z5kNG*9y*TMYKUhd+Ps zerPnf2j}mh_rFg}u_gNzN36dB8DF1Ye}VJ*py%&r!p!<3SPmJ-(LCt;-=_k2ehIYL z^Zbow`~2PeteH%qei98nj%J=`vSCCRY-}}G`x^Sm^EV)SetwCc4L84k^%XgEIh;t< zER%}q`6T(~7QB(4nQWxreT&x|Y<#t^g}vh0{?|bkII`cazgc3#iIm{$#QmL3wPNB; z4>MVb(CpW-Qg*ekv@DTU_18VjcdNfX6`!O}XfoyNYi0t{EKZ-_jI#RcAUk^gW@0vI z7eeBaWkK1m_aEwW4WYmX2|Wn+goK^SwFaG4Phr&}qttEC*3%OqNUNayAqKx`MP%28 zEaPT2^kCHL{OvBY_Uq5Qw=n6aEoMX=>7{>Kky^d~9lZaujC0By=M)-cBTRSsSGd&effGd?No8L& zDIm|CNvt!oWfpq}3<305GLl zCxY4e*0C1ZXS$+7=1XTJInc5}7e{nyZ79lp7Fv9^)@Tmi^1)qX!H}cq-f7pH3ysE8F&RAa;L7uc3(2rV+->S6v zS}l&OVn#s~*xJIP{yuR3XFeS!^>COY$OHuYB0&n2vVlc-aS3&U$39YpDsA0^_}~gs z_Ay1WgD;;Ex%%@{m)fC4!u`mn+OX^Ucz*MdRW1J9|Mf6!Q-l4l9VV0Sk67t%-Ve@S zyiV`_`|mf0t<~e|{f}QGKlgtJ3xxGoA>%lj$@$w>;Cqj({sJxbI6w0Dv;XZCN1nfd zS$CQBQ-8aUvDIG(S>VY2r~a<4zwTlFslOxj*Fkpl{%_au-pc(R^40l!^!`uN$H&ed z5L2D0{nWB7;8DFnrqlQX{SsBu)%)MU`#<{%g#(Mfa!JPp)=iz6J-yB>>*S8=RU3^z zZYlf0_eZ(Rn(coA&f>3=GERkcOJ@vxs#ksAXw3R6kn!9-@c;ijW$Q+XbQ9Xp z8QCp(M=g))4VQWm^E#b;{|m^v%dGACvy7RvHGZj-7gMZ0NtIgF$tx?R3ALp@c=m&> zb$+eH5hFEyRz6glo^FeqZxbdw(Bw~R477lHZop>H!T(;t~6}QV#^J2St|Kr!l{{0_Z_n<{ERDesFc3pQH zQ<@1dxv(Ht9JZZh*deYjSHw43utkr!9H+!N#R8nrvXN<~-{5C_$_v=CMa%lDkZ~N% z_WA41ke{VqS`0lMCdd?<4pQ>TBD^TJu;L#mc`Q=N$dGscryqLo_=^cPKkhmGQlIfk z1kak;N0tz0mdz?MMQ`;NXt96n`i_ZjG%7Q=lL7Bb(dMQ`cp-Gu{L+VQC^1M zZ9uDDto}O40!Q}1uPT4_@f1EhlsBKvs{Xo%`Jw91z?s&U!FLY;m-`<9+&_PBzLNjH Zw(s$H?q#()f3Lg$+jIZ>%a`we{{i@Re|i7_ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s1_3.atl b/applications/PoP/poc/res/pv/s1_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..b9d1390f4a063413f53aba5069a3d765b94fd1d2 GIT binary patch literal 15796 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 L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s2_0.atl b/applications/PoP/poc/res/pv/s2_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..c075c6f7eca04f2cadaf2a85e5eb9ccfaa7fc0a8 GIT binary patch literal 15796 zcmeHNZE~D24Bh+y!Yng6OK#BKpfkNl@BgROd$MfV1`K3VHW}zF{*tAqhy2<6{`K?y z<@Wi{pWEN}7cunX<^A^RpMIFeDLv!Vom(CS&4^cgPKPuNy=N@XgFm9Z1F&R;zf;Ro z*Aa)Hc@{`>{0Te~N&f6}c~Y z!{7)!ACgcK;?F3X<4?xS)H$#xl|6qaL5Lxg>{t9LlYDFZbwfAFFUH?#@j_1AA;+>&-4#A^Mh_#-ym_+R_KY70EaA2%`b;`jx9&$8M7 zBIgg!Ux+>=ryn+D`{fQmPXWpH&yg}5k>Vp;m+NUT5XoXD6Yk}-bUh6QGR6isT&JlY zKL7IUt6LcQj56#A;zRz%+x2gmf!okWx{$&AJdSt18Gp-Nua;2;kHnv+OgGAU+oI$5 zqG^o*@J9jzW!R?R#EY%M6@SgCi}B|v!(_ueK0> zi84K^k-qyCf9(Ll{KwO6pzMmjT_diJKMr5HaR!ms{MRIt>#tifgtcq^Z4XHPpFJZmCEhyjNqG>Q) zmRfz0uFz3PjyIKE!Pv01<^|)=Q^vf#+VdYx?{Q`g4BAYWr3>;qTyAJ`HWzGFAH>T^ z{25zJv24U=d;G~5fa@41Foz+*8ef}aatSS=AG(J)iTc=DxnQKAL(-OOl9FPv-jG#P zp--}@c1>&OY5i8xxc(Yv9skCJ_kNPa{ne^6!iQM`fb;&Rwa|cCmQjJA<1HcnyvaSA;*X$=ART}ajJhcj!O29g zkm+Sbz_pRySZ3b>y46v&w0UjptrpRX7$B!o1#xB<8We3|X`Rd}{<6WmCH{grI%HrZ zWm(@=n>%T>mO2su+1y=l3%#6_kzy36b#=W!Vw!_pvxA<#*E(&fwVMA(;O6_kXp#;Y z82#E}#A-Df0_0e0u2@PJlo-l{-l2ws8F1RQ*~}3c5SXP%a4! zT7R?4tY-WR)Re8F=c51L3^_C7ml|1}FNKsG%n2VM} zKUC@*4Rk03j4TC9kl6pP+W$S`{6Q$Qj_(I}%&q-)gw`YUkw152!|D+)vzqZY4^v%! zjN^~dGLJK>Jw%^e;D5sXpF%kZsBr4gpvrV?iYgt_`}-Mx@yay2%sLoAJt_aX#@u3)=H%gt6xkd z{){cA?)K%Qe}ACV6;34}5$Y_(v2{KYiawMs>Ug;0;W62q&3S~Cj@cdwQ(S+Ix1INY zE9zTN*4x)xXAfHy<S&dUFk3-kA#dueV literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s2_1.atl b/applications/PoP/poc/res/pv/s2_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..d4441f91d4b8b69b454cb8febd679b161b04b947 GIT binary patch literal 15796 zcmeI2TW;(&42CBUlfl6mAZy7E+8q?=M!Nr(mjBO-sEfz)*vat}#{=6kMM~snTC$b= z{nwwLZ$NT&3a{2M>@8d%(KV-uZt)MKjc4w_>P)GYq zT3(kuF&Uc_x%I`8$V@X!*}r;%dg`dAY0)>ATZ_5Jartt&y;sQYyt;zlo%}SMCypA1 zIXZAZg^}H5I8~q~sRBq^kVh|&hki)=`ea|Uq$SG3vx%s|FC)9->UtA^QreY&#px*V z9(#y>W^Hkq;bDe4xq^ZK|%62(j%2HnO2Mjwmt{d8Q>qc$jfQo4n!?B@__d zpe}k}jh^|JkO7+jB)*;b*9L9Z>h!8(ZcPBw+HUn-XUo5W?EaGgarn7A|At|21F}u~ zyRemhkbfB&z{erX^JqbTy# zrUjoiknl@4a=B*7qLB~gU%dJq@&5Ov_yp3heFBL(#q$7CXf{}eZ4Y(Y_dn1(=KEvV z-MnLuHSvP6s1d+a=Yi&KADMp{8Owgy`~J7vy=6r4)+T)jAWSrZO&`?&mhulzv9y-- z%t~eHVb_)ydv0Qq{Bv47bN=7L zDrrECy^Q%ckX_$pyWw!Xo;LqT#j!D5gOtmF0SG1&8wcz)ItUOj?DIax)k`J+gp+Sq z{q6{ud)8m3?qBcFjD%P`c=^;58$}f|ZAGD0Sh8NWf;{h&-go&|JYKLX_X-`@2X3u4NRO}dH< zFbS}+k8jX zx;&!30Fc%YwuY~`h$DJYC;})X2`a#rJzCiibN~K_50G5Cm$m7fB(Q6JKye?1HwDK)edx*tdo^nm$OuI zE0U`8u(o(p*)03NQZpF;%4D+k{Yz&8&w@h&u~w2**RC%)Ermwgs%8CbgZ`s- zm)d(EAI!gafu8*TzbR)@AXEB^D_M=)f7lXjvj)p6EslDbkah2Wo4!BXNjx(A9Os8T zKE(5Y^`eKl*|iT93s6y_WjjKjvRX z#)R8dlJ^5Z$T`)bybjx-6S*@0ZoON7jGa?&fRB zKc~gBuVeANHvXxz9z;0@$P|8Y9B4G?V!?2dc5Y!5YNhXXY*&1e4$uSpU@5el8wQbe!Q)!uTa4OZLGeoAlF6DnaBy(mPHP-;iLWxFAE g;~xQj>-rx8WpKk_SUap~p-x`|ByRNqt#0|smc1DuHo`|gtBaH+apv#8 z{``7>|&E-tdd*1bK7e1!<{%u897k8dLMGY8jNOWT}J7sN5y z{u5j5dH+?jc>nP53Bg~w9I}H0K*II(EC+hP@jl1JBIAUNz@iv=EeP}i88@s8f$~xB zzJZ|NaLkE_%h%Nb3>(xzm-k;$_V)I!j}80xU(!Q5%r%PzIY~jJi^Q^-^eT?tpd;c6 za+x!JUx;Z@nrp&45y_stDIF8zvjP&O_2E;=w~$nrHQc8#Gv+*3L+4vf=*Q9cC@|Uvs--#FDS@zt+AZ`ieX^`VG&2 zk3Rpy%3ksP?(~trC!YWLrLmVlv%ZGx|H0Pz`#Jx+k@~#<>d3S^%zDJ{-;F@`?$~GB zVgLP{|J}T(>Ccl4c=YKQzRT=IR_8Iji+#pO^^`k!}rpBje z*P^;%K24@a@4bO2J*GCX{~Be7J^!(wuEYQ^lQodis&ATZG(=yucguLgC^6OupVCzP z7JBYKymQ1qne(5|SI++yuIoXeVL>yHGg*r)t@@_vMyt^~tHfc_CBe)2b~S6-;r(Ol z{dXeBI8cMA<%Nzi#$j%Z+>X)0Hpdae z?XPTY|8cAnoOT$^|F(XU zm$~aa@4w=$JIuQ0`H#f|7U2h(Gw=onHI~u0ptzGnd6Z?OlDSbA+8SBeXfJQ{`a@02 z@ojYu`>(s0@7#Y5i3LP?Hr7s4k3tsOt~}pSg*rwh@fo?%;npG(UT)bIN4jOd%&>Dl z{Qi-^>G_}2v9q;8{IbJjfS(RZ5I+tyq zVdreP{~q@Iw-dm{r4FZ^pU?Op8s7(A1|_=&~G+#{@RfN z@4Gt8y666T9Z|olb>4qPS$CLq&;9qC?UP3>5cc2C`QOcpJ?=ldfPc>a_V3oo?_WF2 zy6^W7k63sr!&m(!VCE3^-mlmr@UUlQGWwgs;|vz5S;;fxSlY$@Ym^=N{72Yc=%n0$ z5~T@PD^-$dKaD5f!6RrI=!>_L!Tus`&4c?7?;J;;|5=Jl)B)y_2+h^6lrp7i)hCFX z?6zr~m7I;VHX7bP4txJK5DpG43jlm8(K_robZVc|Zp9 zMq6`n`Gh8xGI7Nm{u^K0e;s9qoxi9^hmjx+e!Qm=eAAlT33ad_gJf$N`XVAqMB_qu zJa{8cN1z|4MqBb1tH_rad@(kNb3A$f6>r^P)}7CPC^E{Al2{y}xh}OStMVX;z798A z$m*Y#1yaUJEeUm^LYBFRCh2x8bQkK2^x1!)eAM}0fGh9_pM|EbYJDuS3OS-Q$&2!W zUXNJpTON7(4}}O~oXDdXNq5b^hrW1z(f0FyKGGF^#Qt+YY*`LQ_9x)h=Y@+gZ3;n6 zQt9i;Rc!4mj}dvCENgKsNT&QvQt{-t%eX@3@cT#Z?mzz{1-KZz02>%A&d`Mxx?Yxa zC6ywyFQ#~9JohyX@=)rTLJqQ=z?MI)FbF%0gms0qhGn?_9{K#&(%dm(v4g)a%f<2r Ozr#Iv{bJ9>^WT3k2!QAS literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s2_3.atl b/applications/PoP/poc/res/pv/s2_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..773c0894b785432dcef560294f4a2dcb361f652b GIT binary patch literal 15796 zcmeI2ZF1W>5=EIRQj#lnc`a`TGdq~7*~skw&&)Zu0g#laM0t+O&Xnnx1cAoA-4~4y zDgO7r{`uFp>HV+&O#l7s8$|#3_Sf|M{QUH^oR_C3N~iNQJ$>xY>+1pk>+1)5HGG<; z^Jym;FVD;Qd|oW5-!T0MmtPO%`v_0&ah86oWXi@{H@On6tuJIpxKiGetxH!IefVsc_#<5%H_LAPV>(t(cn!;*FlHS^% zcf;t?^yiIGLD;sw%b_Rvw~>s~AJ_NXf3W}lG`u6BD#UF_YtX&+`ng){ORpvS!EkiD zAZ`5Y$i4lyJd=N0$x`mO`}x`4BwynQYV;tDc&xhW}{@W#6mQ%kd{_yk1 z)`{l!fpa*#2CHi=(D6(#<1Gk#tA@P`OVq0Oo|nR2#Q=$V`M4Tl&*jyJk;f0oweul8 z$iJOrUB^6slz%|s*6K*0hoil|#3|cGUIA~%|0W}veX|tn>ZfXUY_W)7%{Hx+_s@Fs zo)Xx%=+|sNc)!WN-DJL>e<78;IZ|##&TZX|z_`+-O>c2}Ahrwvy470KWJxfUW!h}n zET^t)nlK%SCpFdH>qIZCE&8vNZ`QvE_`v=v^OzjXjnt*gZ3xrtnp;C8>M^Sj(sL`| z*6L>bgN}9Y-DDU8AyXl4OItri>s#gX{IZqIudg0B|MigGNdLe{zdu8d)+eQ?@vcKSpR-y|NYa$f2;p3|5nL%hncVS z@23+x`L{VT=Pda1@BiL9ZvQX+lYiUKA1U|SSN=Vce_gFSbN=hM==c7A|L{jM`1x&f z@uvstbvpO&0>7U36j;?tX>WGb`1N|Az+SP-_Bx$gX1=_2rZz?gc8fm*danJ|%0 z910yrBd-<)9dga6xxs`3S}y-AjGM*BcsqLhe+zH)YdY+44)jAih`W9-2!?oQv7Ej0 z&)GAbk__a?G>r*mT(kPB2|?%5IvKm-sBFVAMCIeBCo4<~*;r!^C6=V0lnb}Z@`8xr zwQsKCt;E{$R-?Q!4*v`J9`C>`-bA7|(V=j<|CrwDr+g(~<)1K8pHUl)eOJpuhjArg`wPXk7iDyh zcmsItf9vG(a>Lt3EuIhpysLiK{>MoDD3EP+_fcm~>q!UgWOiz`*gKPXHI6=U&6(dl%VctlCgFMaKcYA4q)ax;@Q{$xhr#@un@$|YNoIJsye39)L6 zr#;cawk(9u3 z9bTG#84_`j#_=VGcmp~sKCMz`nsHXERSam?a=oWVNmN5yI4$DBQ3KOXcL8;=WXO{X z4t!34a+<-ThIgc75oRJ*_Dm*ol(~b`8ky&=G+L7k`fnt4d4}eQ1bEX= zp?AFW+3>23NFthlhyKf}e)5*JGwPOCyJQ~f9qDki{$1(E62V@J_e@@~*cY72b&gTK zod4oaNzBQx;$v-L6UNcR5geQr#~D*NanL|W?ehgFQpet|oRuV56=UX*x&LxCUbPZ3 zi|2_oHo6wC%GCVDe|qeA#lU*-+P?B;_uuhm{;oJfhr9ZDhH?EPQ2HXh=&apnXP!gX z)}fg+)Egl^_S$8Rf{5p_t@P8qBb@5W;36PP;6$HHJ3oWimEUADsX?^J+tkQonGS4PFis3NCKRP_Ul!Q;tT@%e*tGRM5OlFcW} zC$-XwgK>@A%8TX3|LB+V;EosPiWj$>BgMb3i!lHvugG%TX z32~?-ga)sIdNg!z=;GepOYVN)x9fGQ#e^Jq{QdW6wu*AyutfhH-(V@RPyOk6W#O^2yr-46jZM zUd`|G#;J9Wl9%2kA0Mw$ZgCMzmLmnc_8*~d&o9xg=Ue)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s3_0.atl b/applications/PoP/poc/res/pv/s3_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..4d5e9b2f27150aac93e779f15f8b7aafaefa7ec9 GIT binary patch literal 15796 zcmeI2TXNel3`Ly}Nzqs{UQ2e+?w~W>NcaEK;$8qGXgwV}r9GiCmiPcc@DO~qzkmJw zcsqUl^XK&U<4r31@%C|gk54~LgvWvv1Y_8J=IN`hCUK2!{A42&oEem!mnz4 z>N?sX);xz>XB`;a|a+oaL`&ykUvN0gre6kKL(tvK)v zLjYLcBtc^S=g5}$$(WhCU~Hv$6}5VHo&3vvAe8j9{7ms+u56KvDl5zi|w(xqeiOzlQ(G459_-{d zR*JvK{5R((IJs|qhV?gYV8W7C&&1jKG2=(ubmQOlf3q#{5(ZVb0tprag>WyYrS|L)D2NSVxK2|)eEsFw ztJg5}1v2aj(nEgZsr{`JI1PQmg#hN~aXj-)|64EGaz+_E;(rmDZj|-5MaOT$v_$~? z2+)vWn}QQBwhGPvcBd}-Uqpt;~KZ&jIay zXZydnnQrUvEAi>R+iG5uQ8QVvS5*m14fYdUOo+W$GVVlY3z|CSnb z1ZkEjG_Zj=FMt(95?gI}p(S@|DW761(z#p;iT@SIipAu9|66L%5#xePYLw#%d8rwm z7l9Vxs3nvtb(OKAd?*vs>3;-hSEj{f)=~c}he1)5n53qQjQR}%l4jn5RE;CoV)*({ zqF-4YAIr-r-bE;Ze7;q?>wvJOgnVjJn+!>ilN2pev?ij{@NEBg>-mFq8k)I!8i8E@ zv3WGk2Ah;u6Zu6nG}{1qbIoiinNkwOGKG>wo|dI&C(tmL}Sq~=1#G@yjb z<@}uDaw*RMP{O)O2Q}<*ZT_W=tPAoi18e1vVTR0>c^Llp`TM^`|3jiz#4zI_Rn79Z z8w@E3DJsqNMp!C!P{tuJ#qfD~)naeULku8kS*Sx2GA3lRb_8O1Z_WIh_J7I$=!@I; ze^9^u%qyCwuqmtkn&sUObIQX*sk?92yfFXm-v4d0J7~;7eDII2{yS*#k{t`{FLs05 z_kZB0%ADkq>dmyIVN>d>!976#Z2uQG(<|qX+<52r07G6AV!Z literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s3_1.atl b/applications/PoP/poc/res/pv/s3_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..80db75f59dd7a2b420569177042a9505933ecaeb GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s3_3.atl b/applications/PoP/poc/res/pv/s3_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..2faab3cd37d0a34f2732c8a6cedb9dcd1a62513f GIT binary patch literal 15796 zcmeI2TXG}25kz|;*d!Gr#jNG-z}|r)ybp?PJUf0|0 zwpviXcRGJd`_lB=BvUrtBHwrZeV9C5zaiP{&j{%4=RE&@{rYu=rRsO(BG6vTOFiYu zd+P9`J=40gX&zeLdQ@qLlP)0lXSWMx#q${HIr8YYmrUV?nDg~-BGxB}{;a@F00WO) z33C%}8MT(mrUzMGm4NoB`hio(%GEGO?;c@)KKK1G|03VJ>tDImoy_xNqUSDr&~gfQ z$Yt8dP4FB0R8<=6$dmj4D{jB}tu>O6t|3VW2BWR%IP8@YYSX-^R z_g3wjYw2fWBp5gujH9DzD}Q>A=kUko)NE#5cB& z$Nl$(i{pz2nEC$OWH7KEIM0$o*;3nE`_nwZX4oaP3cdAgHD@>N(-0{pz}!=-c$*iN z$|6|9qhmcLowqFPG;AY(tB=QLe@^ewZzmb2Kdzs-KiPjBuJ}|ft$x=+&ErqK6Q?ppB`-2_13=&{C+-DU{fol zquHtP`*otgQL+2}y53r1zP-IsdVA$jMP97QRc+&Sw2b;{N?$SfIbVSG%2Ci}_cB1L zzMG$s{`c=YA7t3;AdFZ!XuFK`Va$m3D8SM^s7yXQ3SGUL6Ra@)|_Gb}<<*b#J z!|Vhze|^S#MxOWHY9}~T{a#-qWY=%GEXy^k{C!#Y(;@>{E*JlUWWDfd@epz4Uq&DORMcSZ{x?%y1dOSd7^qd-lL-^~#G%k}H1cX;&>`0aHFubB zK+ENyg>kd^Io^&Q|Njba^lLgCaVGkq9mHL~Hv~gGv{-N6`RD9eu1N-RWLd_9GOmmI zstG~o(mENt;izoGF+}C#rza~+E7@3M4kebPpOh=N`}&56;k9qB;;qEm^H!t0G7kR> z`5EuPEZ#(-H_@SRy8oCy>Zg1qVCA1MQr}e3(6aa$3~|(73hH@8)JtZUE*LjGiImpp z7aaN+O>n5SjcE=8)^-z4$M8Vfzckwijh|XS@MC``QHv*p z0Pm*X+5Z@+9|f|l?mp_R&0kqA3+aTD2Dwu2Xk5?{fzSshl4$Ch=y7myS&W8Q2pMmK zL+!w64d!r!yCINHqaRqVd6mOgRuA7LShd;nCONcpEKa<@#{Xo`jMot%`5kY0)$how zez*ld)8XjSyAAlJ|-KgSQ+Gi2}OoxtMIc%Gr1)I)L+_U&N~$`Yjg$ zOEt2D+3OavFr7&{4l_nFE?Q960jzm(yIw9@n2#3}oZ#Ru)0Q^JXmL|_sZvwMYh3D^ z6%zC8zjoWM#*0roUdAE7bN`E%wvE@JKJ&_FsuR)_=RR5gD)&?{`axkzs2GbkiZ0jt z!Xrv@e(7spRJ*!{k(-&c@h39^H!d9~uH3T~iIa;qk`Sx5c-j*!Y|BCjJ>D#mp`Y*+ zC8j;9T^Y4UUh2Y&&kz1*=?$+d!-QA(!Hd(_1gH7mb(riQb$Ds|Wk|$98poF$;tlAm z__Ru$X~tQtRxzMm%k`cfB~cA+;k1YgM-5Cn-38Rek|9qnIPf_E%E1Z=W$9M~^T11| z@S;cYqS2mr=Ra)97T#J}ol1~r+<-T zhIgc75oRJ*_ADlIlzD>F8ky&=G+L7k`fnt4d4}eQ1bEX=p%1+D+3~84NFthlQ~%{v zKY7dA8TH7kT`~{#W;)E)zmt9}5$v^i&*T-0eZi?*=NRS7`7i#I#GDK(KGqgCVH`~y z!NF;9oH2zH2MvVOzTJT$b?oiRSxJ&rF=h^#`!84HRVyL0cwSj!qigZ1OwC{Xr^kU; z46FyQ?Hg}){{wI4?}jsUc&eXg7}q}nr7zNp&f1N3<~ek29lDT)dLyLAUb|ePAmVv! zEB$nDhErV`Tm)nZT!#N{ns+{)e~?&zhw#}2lCO)+(;5+UBq?bB@DBQ4z#RyQcoOx z7YF+wGajz4jEa#_MNXNh>IWEu$CI(*^9SW*j(Ke*n@^TcYNZti;~Ke*7t5Xh(J$x0 z124`MFK#(UikESFhVx|o!?Bis9@{Q%;wa$?iw-^wDxp^-#G#T98oUaUZ^lv!j7yti zEsl{59G1J7v!-I|N9u@@8Oh`qOk8V?$B&Hnn!n+5?b`9;#DSMy8}GsYEXwrVcq1rT z;X=CObpT;dzotWi?>bD+FF3vBU(UVGVY20NA!9twxLJ`-CWl%XW(i34tEvK3nm8qq z5qAF_alptJdp@cQscO|Sj5{oapA3$E+-m)kPu?bAcy(g%YW|!zPHlUXy!0;l_;{6a zi;G~g94X-0e}sNKzeKy9e@zx1o+)wy;*$krj7BKKrH}>$Lnfi(nK6bB>FT-032J}O z2puQtJ82855D3Pjd3`MbZiXpJ`Q;-pK@O!95r4;P4`mInUd?!^CL;nhJRiLD-!4t$!XP_^t1+V7kziPIlE%&#IYLvfwClGL(wM1c-R1k!D7RqgA-w zk2p%>6zUteuv|@XTa~Bq0Z+j&#I4tm0MdDBu}$3Ymb_K zvkux$^y~7KegF}YFgaeD#?jApN8+4v!|EJYyb7Sj5e1#O%q>DX;4lbfZxmj6;%Gc| zV;}hR1KhafRiE%0I>qAcAojDGYvU!1;eX?mt$x#U;}xLC)p%uA2S#|{WS*b&`wz4* Bf@lB$ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s3_4.atl b/applications/PoP/poc/res/pv/s3_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..76b82fede907332d0bf79e16cb4b913aa92081e2 GIT binary patch literal 1396 zcmcIiL2kq_2qcp2^x!)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s4_0.atl b/applications/PoP/poc/res/pv/s4_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..fd979b438c84afec2b2ad39414d6ca3c2ba74ec6 GIT binary patch literal 15796 zcmeI2TW*6e5Jky{aV)%ld3co1kN4YU zdi?mBzHb+L(A(uUUFDd43~?PHy0Pm()&xG(G2Eo+XGyG`Ge20nu)#DXenZ#MIoLtg zJc~(N{K!0j1i$jS`NhxT_5b}kx|v3Khj^o-O$tSS1sS-NMdFtL60U6Qs5RsBUH}|# z5+TX@E6BF^kLBK}-7`A>5{ZRx=dc0n9{#czI{KBC1i^RX4 zAK^sZ`V9G37BC{EjWe;!A00o~mI{BX|GE-*iyw*@I>3J!-?J3^FS7rT{IdG^G<>s3 z<(Jq1J%vf7ey+%Pi&O`dE;bYb6&5DV%`@$mY$ya8VqUmwjI)1y^X%21;O7Pz*93Kd zUzqIgK7q;46&Fu9K8LX22>spvs$B`>JfuH~EEUSB+mig(FzpdwegK$}ahbw9UM>|( ze`@H0{vZRVT)RIup32xOLjRb*0&8~K;3LF@lX zNPBbWPaw1X$9et9{RigdVDV3d+3kN(0OkYp+Y_%{tNFKJv;N;XQIP(u|91|Vyt~t1 P34J(Q{8Pi%@A&rvHxPU_ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s4_1.atl b/applications/PoP/poc/res/pv/s4_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..7023f03beada192a22822a1f70513873c439f7ba GIT binary patch literal 15796 zcmeI2T~5O=5QNhQ*Q)Bv^agMPBrd}JUto8g^-t}18YCW&CP=mugDQQD_h*{VkN3xA z`}^x_`+i*HgWfKWE#36G+nBC#+iv_>5ZJbO?T{tA3(7AUzr`OD?albL$dUpKQ2@${ z`a=@Q{2F9JErl3k(1r4&{+0yv{ITiiowYlrBB1?AzsKcH5L&rDAzZRK- zqyC`o9PFq+!^imNfY3aNf6TuKTHT-IUoHJu|5ZfmmFcfRrc3r8=i*L%ts{gmw zFTa-dDx_Y1V)E-)r$t@%bwow)TPj2RwbEKc{80hP`sJ|3C^X z`PZ3cGmHKo0_XdpKl*y=L7REOe~^5r)9JI xST-~Mjf(4^l={!P(f{iKp*ztZ{XYchcz@7ecbFOf&c{CkNdFIl%bFbW?+-JUFOL8K literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s4_2.atl b/applications/PoP/poc/res/pv/s4_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..a3f89420663fbb1fb3280cfc2b1edf6de67a2f48 GIT binary patch literal 15796 zcmeI2!EM7Z3`L!TEf~=<*`VE^Ko{x$mqw&bk*sLbKJ@leK%%7p0Uo~iDZ1ZZpRdRC z|DT`f_w{H$^znF2wXW?m(@^VD+K;dJJ0UQY#UZQoG?xajMZ^#I&InA*&me2m>Rd{h zDS-Y``E2|Y8DVQrj1T(D@$LLNvif9zG(4fdv!lE15kHHJz;&3&e;L3%|0%K!AM#%Y zPWFTR2mci~`5gE!>c1SDeuMRAka79EK51oz|6cQ7hP5N(SO(O8Il!_AEuPtbWq|+c zL5-vCzk+O@7x}ah_um+suD1N=kO@bgp+5x`M}z*{VTS(h=RXDr|4qSddSv}sWUiS7 z{iPU(`ValTIMcZrfq~S!5i`0RK&mrRh&- z@r?dUip*-V`>$|w%`E6Ih1vAykO@bgk^ijLqIl4sJIv7E75zD6c>k6nvpO977yUnr zCy$5v5B0c8>E&md#pm1Y+FH^sN}>;JNb{$Ga7?-M`t|89A% z1=#C9IkL!q7Ec}r`A^LR`7Z+)_%D^y=>KKlWS{kCkm3GuD;Ey;4|kcv{Wpc#^yiRq z{U-zRpB&Spx%;mmbImO1?~48$GU3QG^mn8?Ss(Q04m0$3MSl(%{5M4=+M{{!U-bVh Ko;?0r|Gfbh2`_U1 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s4_3.atl b/applications/PoP/poc/res/pv/s4_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..f85076687e6d83318c363d33ddcb69a844da082e GIT binary patch literal 15796 zcmeI2TaF{U5kzZ%dPtb=#mo-u9T@ONy#F8joJg`>DqW3neLoBoyIaL7k`bA~WRgsd{Ud2VR+s@f1toHEb~D!ZoZ~vStcv$ z_XmS-X`fBMO)_QUF7n;+_c3_7eye0(KO>+oKkxYayuw_+d&v}T74y!2p9Fl3zsPsz zzfZ#T_S!!V`S0F;yEvb(7yIv%!tuoe%)S49-atIR>?BM5`O_!Qd;k3`pX)bbHOW%% zFL(TX7I2xnTDfNb?JxKK`zZsi`)`+Qn&w`p-tRv?3b@P%$-0iY-tqTQzIJ|DWjie9 zJO2Lr{1*Z5{r97A6`9F@d&v%mnLGYI%9!=5fbGc4LcZUBe9C%T`^Ec@`}4m|p3k+5 z_rLe&FP~-WJ=XJI|H$<4Vb-1hek=dopZ|T7smI&-`Wk<1Aou@2&N98fzsP_0{`+38 zbIZm4yF1L>`S0VxemVc`57X**R{Xx-E0FlxOO|?nx#RC+_z-_xtz6d+60AhOzyA9V z|JMxp{=udiUp?5$y!6ik@9R4SHnmbab~`oRw-*W=6?^Qv87l@q=K^SdxK`}5yIDc%dGCHk`rqI0Y`6kn=DlLa`l{bB&cpbac%5eO z^mYfk^Ztrq_iT@|du+8U*snzpma|qqT)UlMu5aJ*o{_IdZ%;39F7^BRijZBuVH}1z zS^hB$e6>gihH>;CB-6;dm52WXn8p+;-17j|fi4@l_d6WF_q7%bGuDR=GtbN80qBa! z1IkF3g+70ZcQ4xS@>aB5+28cT_$~Hy%e$r@!=`B<@DOq0H?xm^DylON|IJhdhv{k& z1GQ>k8~_P0T%mlxhPYq10u;NA2)+n-MAM}=ssS1)zf)~_t%KsaHfPEI^` zHb!(rAoRhA1e&_Wdkh>*i_wrCLdM$Q@N~s#4aRVUTM$U2(GM(hUd8ZC_V8YU$v%7D z1cyDHixV%fu|Lss#p{d^{LZ($>UZQ-Kg

2S3FN{ zI+@7~G4yMN2ly-_t?VMMH|>)C3dcaD@6g2(Mgg->S=100a(bwc`Wg7Y$@|9smA4EI zi2}OonM^ib#(gfUToU&G7ka1w!e63+jyPoGp~Gx zIxbz}JTCHI#hwaAKPU_d6(f10=rBJ9E>V*6OV52#Z8nDyn_0B6Co2Lo#*PzH9@&b- zNuiA(q*qIxjzkOFk_e&4nn5!36P~KXv`4j>S$pN>S$MJe#r~w;@R}Jec!ghiF`7QX zY4&#=F7}T)yfpnXCE_5B^GghI2Xrz%tx{*1aaOA=2DG_c>lslJ)zlVF^SE%-z_8O? zKuwlRd1Aqd&k0ZtG9;9xUro#lFN4C19>t4Bd)}S>uqj)3E3>+kAkWO5d7~TxXz@{p z@2mJb=*QWwoyO`+rq$r+jB~Ha>&G!u7|c=T4NhxhottUwO)zM`o>cJ+ z%@GOkrk_F|cp0=|v}Z zqn&XMUCTowVW>4idhB(`7zGi}V_WH`c~>|!l*vUvQsBUtEIZ$WIF!FcGogXE$lLQ@ zB39;WUK(obHLn8|aL=0u*Sy$}_4}&B#r|vjB?fE%m=t5;Fz5bjo%QMoIPc%G1QG*z z>t<{u2$B~u9e;7dsA2#u(}U0xN881re~_6Evn#WrXFMXN3{>?4jLGB5T=D*cdLqZT z_92^3(kD-)6^G6>a~m%`clJlWoCgoQ7+1WQVjc(#7VTMHq zuR0ai%M<)iNeFdb1&KF(sRUzcbFRhFvlWMQm+s_Ly84znqGUy~_yrT!r~2bt#z(E+ z@R_@IycludWz@!dus=zeu^Vp$B`Qp$J6dh~lNjg%%v;7GDdVYy^J^q?3Ts%|c1jIWF$e4{# zhD#xJ2!<>|#WP}bAJWNljSJNAo)J1u)OXPqR3YGuNAvnv0^Cefl=8zzV1gVEv$)*yF5&eJ}Lu@|Au7;gT>pUK+;H&%7gXPPt)qjVoR` z(ENyku3Xj@E*)@~gzB#smRvD3mb$U8_>2QwzvWe*@aj6%;_V=g^D$-P#f{be#w%O> XW#q;yK=-Th%B&8|@QRale$wwh>i%R< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/s4_4.atl b/applications/PoP/poc/res/pv/s4_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..76b82fede907332d0bf79e16cb4b913aa92081e2 GIT binary patch literal 1396 zcmcIiL2kq_2qcp2^x!)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/story.pal b/applications/PoP/poc/res/pv/story.pal new file mode 100644 index 0000000000000000000000000000000000000000..557bed2daeccfa3b85a0332d48a97b5f0f609b24 GIT binary patch literal 1024 zcmZQzU|?`yP+({f2w+H1C}0pU0OEiN3<3oQ7#J2jU?@27fFXfFfT6*Ffnh>|08n)U b5N}|(FhPOg!-o$*_9z$)fzc2csv!UXs!|X^ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/pv/t0.atl b/applications/PoP/poc/res/pv/t0.atl new file mode 100644 index 0000000000000000000000000000000000000000..d541c08cedbc5577ad633dd7357888bc5a8f2fcb GIT binary patch literal 2804 zcmcIk%T>cb477Rta?8t^Cg=bG=PXT z)bx}+dW#8-ij`*wM2ip@PN|Zuizu8Bfsd`l1Ej~N2CVeP(6XiI}?SX zB{hv9&xPpNR|fi+Dq4bhRPyRsgO~0Vc|?Nf4TolrjVqmWDL{U$UR@SGAP?bb5)4fJ zd0-23Ly%WY^>nYuBPN(723fa~161Fy6$yHt^cCxIJa1Wv0@KU-pEXsnH-ipUWE)qUUACX;dRCy&T=d4+#`~^*rQUj4tO4CB0w|3 zK&({Rz#D1zWL<9i|AxA8PZXb;z4#va$SD#dEaUJb9dRs>EmOUw9s#PCTWc98FhQ`t MtCZJ!w(_q10mQdta{vGU literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/sound/s0.bin b/applications/PoP/poc/res/sound/s0.bin index 276ca8f..9dc621d 100644 --- a/applications/PoP/poc/res/sound/s0.bin +++ b/applications/PoP/poc/res/sound/s0.bin @@ -1 +1 @@ -{{yzyz}}iG0!#=]t_PLI_q}}R]g`E*JG^Ƽc9%.GME5( !A[{ǻ|zkdkgnmZ\K:/4<>Snw|}mUVRH8-4NoX[x˹u|o{y`MXvqmQKabl|yȰξbcszif_x}ib_aNK[aoz~i[P7JowfD4OfjZOQS\lx}wpxflearvsq|~|lenkJBVw|wxpprWVg|tfPWtxZc~||^o}}sS\ns`X]nt~jhnmulhXY[`u~pjnqlqzraOKKUWYiosxtgbturk^YR`s~~{up{xtxtpzmhovyw~yvxxivtoy|ypuglwxwnlz{}{}yqvxyyr}rpy}wzxpsu||~|zuqkopopnsuz{|{vnmpu{swstuv{ljsxxuhvw||t{~}xu{}|xwzyrqv||zz~zxuqty|vv|}zz{uswuuwtonsx|~|urtyyux~~}|}{}xvtyyqinnptx}{zz{|zvy}z{|||~~tx|}||||{zxwyxz{tqx~}~|ustx{{yxz}|||}}}z{|~~{z~}}}|~~|zw|~}y{{}}{~~}zxzz|{{z~~}}~}}z~~xuy}~~}||}}{{|yx||z{}yxyyxy{}|~|{{~~}~}||zz{}}~}}}~~|z{{~||}yxxy~~~~||yzz}|}||~~z{~}{|{yy{~~|||}~~}}~~~~~}}~~~}}~~}}}z|}~~}||~yy{}~~}|~~|{}}|z}~}|}~~~~|zyyy{|}}zyzyxy{||zxz|}~}~~{{~yy{|||~~~|}~~}|{|}~~~~~}||{{|}~~}}~~~~~~}~}||~}y{~|{}yxy}wx~vttzyy}|z}{|~wwzztv|zsqyzz}~}}|trxtkjvwty|~|wt{tjnzurvy~vt|xx~wpt|ulnyuopxww{~~tnuxibq{tv||{|y~olt~{srx}vrw{z}{y{|poxunqzxqysu}rr{upt|{x{}||{qs~rlr~|stzxw|voxpks~}zwwy~z}uuroyxv|~~~xv|urwymjswnq|uywnr}ljuphn{zvv{~kjykZi|qsx}{~qozzigpk`cxyjk{zs|vfh{yrswvpuzptvnuxtw~{wxtqx}mjsnah}wgfwvt}rrzscl}tbbtwqm|wysjsmY`z|urnw}tvvjqj[bxxqpoy{svvouoch{wrqv|uz|twtjm{urryxt|tpu~mhn|sf]omgupqlXU}lcqqix{rwi[bzvjdrnizvnvdYe~}tniw|oo~kkxx]Xlzujk{rzslvlW_{yrhotn|mit~`R^|tm^ppi~jh{w_Qjxshd~|jrzloo[]uuphprkxwhnfWa{{spkw~mlrgngYd|{tll{nv}jhzz`Znzqgoum|oit`S_~ri^qidag{kVFpkRLpX^~xw^_zfS`lT_|~uyk^g|xmfu}icxljwjW]u~{qaqpg}sgokXaw|tfrvpkguaR]{um[kof{^kgQIl|ufR}z_dyjtiTYx|xhktgrxlpm]`ruldoohzwhljU]vvk]pfd}pgoeVfyje}{holjuz_[j|rebpai]dqmPPhvh[dgScheumOZzzk\oeVlwhmmVcz{upopbcw{TNljLMlnjhkrYaVPmiLVrhiqzsnwmfro`iz`\p\Wj`Oa[IUymZdtoeezoPZubL[uwciuxnncawsaj~d^l]Re_TcXPbjU^lfW^|TOknO]ukdft|lelgZft]f|pasvjqa\mSKcaQcxjUbwgITygJGmw^Yexzfbwr]f]WijZbxxdcrgUa[Rgkbmztioy\Q\{PF^gP]kd[dzUX{bTb~hV]{p[_ucHY[Kbl`mvrrwa\lkIZxu[YjYThaDPhFGrzcZWfiYmi]o[E\{xgUUvvUIk^RbxgnxxLFwǮ[9LsZlt^ixďSFTH=Xwl\TXxpRU|aQf]F^{oXWk}[QbiLVa\o]RfWKd}_assdkzcHKuY8BvaS^d{iVU{—RNYȵ_=TiyUS[]IM|qG=hӯ]EQul]]v}c[o_G[zEIgkR[upJSo{HDUĀ>5RƨmGHWreLPl„RNe̲kHNfrNA`UCZ[FW{JG^|WN]hZ\zUOdd@OpaKdq^D]r_I6_`D1fĚlGW`hSVxϯfOQˇNIY[GLrnG>b}B>dΦ\9iةK6IqŃMCOxFOgeW[{hY_oND_̽k5CsS7KmĮb3@meB\pyQfr~tdYkhRPexSAHzB8GzvVK[mSV{Ȱ{\JhgUaX?T˾l45`ƜMERq_RZgiOIdʠJ8WcMIrͶhzکX1Wvf\aTcIRڕV':a9&dzLgf``Url?1oN*JȨ{^Ub^ujT^ʟV2F~;6`•iVZgmaZtyE?gܧP1Mĥ~]Xicyi^cԜX3Eڄ>1bȖkXjphm^rI4]_2;vdUebfx^Y|²^;8w8(Y—ujWcPvfOZmJ7fL,PԤkc[o^iy_PƮb@FzӁ:5gəcHNZgjJHhӥW4BӁ::_ȝiGL^{iLS|υA(R^,BvʻSNama\fμyB2iܯa5F|ƳQOhuv][o͟S/9؎9*Qţd?TkqfQZv݂=#GV4&s̈́YG`jtkQ[k;9nܠN3TɣqVVajcRc®j;Bz͎I8Zr[_fpfWfY@Ny5@nĠVG}B?W̤YG?gcJPiϲfI4dͭ^GFqмnOEj¡aM[vƳrPB\ìoJGcſW9IU?QŎT=UƯi@LmÇTAD|K@Ktՠ`F9k̙WGQtԷ}X:Q½WDRPKcҒ\Q(bǮmS9^؞dM=}DGcΦiQIZwQOZĽlPENͬ[DRhΆ_=>~ʟd@@p̱qJDlTMg˦eOMfΨ]@JgāY<@\HQƋV@SȷoADb˛bI5]ūhJ8^_A,q݌G9O۶sT@R}SKW{L;Da4@\\; yX;auL0W̪_DWwxH>Sѿo<@Y֢S>$fбY>9kת\<@ˆGAaΨkJFbëpKLa|Lvӯk?=oUEUu˫aB6]ѫY>@a}]*6ڐY5;{b8>̂BD\ר\E8cӮUzÏ^MOyҠc9:wѝW5=txK3OzDCHV12~L5bɩ^>MlvE9SqC=Y֣d>3lӳoA5^ƇM9TijqDLhǏR?DӐM8CwڽyO'IʈR5GӕS5Iv@C`Ѧ]E0[νeB/NiK%Nk>:Zݞ\>:rΣW?IgӺbD4YұX=F^}T+2ޏO-=ղe>:lƛ\ETrƳqC?_ϴi>FeԞ\?.mбiB7aѸnCDa֮sK0RLJI3EΕF8Kѻb2KpǕOCBs˟YBEg~O6KľQDV͕P9IŁ?;T˱oJ5I]CK~˒U8L~GCdɦiJLcxTQYtK6Jh@EYܟl;*m׵pA*XϔO9FRO]~ҟF<8E4KwӀ[-0٦`92g|A8\ʭb;NmƒMBEϗN>GrϿmH5^˶nF>\×_CJufQWlnJF`ƯkFLg˒Y>C~ϗV=Fqƾ\@Aq¦pMB_˹jFBsכR6Z~վyOEA~ǞbI<_ʽpI5\ͪaBNjM?EϒG9GtĺqHG>]MIs֤Z=EԎP=WʳyPELskKF]¶lDCaȞYEUoQ>OďTQo|vhw]G\D(NZd~sb;lmKK{oCNqqko|e~oqaTbgn|mowk[ateSc|mvʺ|Mfmeg{sloWabtnwo;b}v\oxY{Ȟh[kMRrkc`Pynd_5ifRof^wws}jJMlznZ\oz|}wozwzxu}j<@f{}nWc_T]daYC,8XgF-Odtrp|ýǚjj<=qt}}NWR'Cm|ZN~lXsv{pstRSvs|KLffF8RVM>)M\KIdzkhn~}hsxsxnwhp}xtn{kJYlP@VzeYܧϹeHv`ocoUjgNTYph{}vvovUN:0^p[H9}cTT`acz}tplg۱m}w~y{~QEd|}xdeZwihnObtgsplnogn{|or}wqaJ=X|T(QwjWtaVtkJ–x_fl|w|s{uysvmpƣiGc`qs=~ngU?tbC?BtNq^fSmJr_Ty~|opvjrkafPzzs[r~aWgwkv|qck_}֫tYsiu`UifgHQ~KEe7\\FIzkqrqc@xbV6_}NdqNycOfxvztgTPlo`t|i]=II~BS}julgwq[Lny[i}gyocef[VkŤdU]PAAO|~dxzi_rRi_sewwpUZW}tkeaVK>8y|_̐|ub`gxv~ix}kP\Rgzjs}qs^orpaayzSWxMS}unl_|eVCaj^jsjlzl\RcdB>NNnodo{xe{xxitnLD\tuguxwkеʥ|l]o[aSV^T5[sM[F[Y}czrzQFSx^Swsprò̸wvy\cjJ:CPmxOK^_n_LTKLiv~|fgyam{t0AfS:=EfJ!7OnRB9AzfsuNJu|~o\TFDIOKdj|o{oMnr|qcbwJNo~ia[VNaqiMavdgis{viqyv\Xn]Xk}e\UuaknO_Slq-8_OwtXiZYgH|qIefXvySktpl`egp\kYTxh`\||yoxsaTkryyvriXdlt`bm~iTdleztrypn~bri~tjmp{axzy}|SrbWdut:BA^LTtPWj}hQzfew~}{mpaEUtlmhmjRAepqlitdf|yw~^Wgvk_`ctsvj]umM_qx~hZxwxm~o]|ztxsmOa{rez~Zm}yfe|{|kn~ok{fme_k[p||pjonlz^juawŶsl]UYcg~~ns}uy|wUGa{}bptFCojOYZnzjmurnfsdlcpu~ylhX}fqzc]{1JU`C[~zigqrmbaxha{gdxti{uJJ}sqrzjhrekk[n{jxfhvaklgpr~nVQvzm٦yjVKsgl6XRHkScsR^Oniftkr`̗¤{vpypvvkmp`FQ^qghmnzr[lvn~qq]RiX@JyM9WRxpbdKRVhsci|zt`iG[\ttyyyXw}idt{amot}bXt~jI^v`a{ngyz||wZLaUKValagf|}{|{tcloplxn{fU\p} \ No newline at end of file +{{yzyz}}iG0!#=]t_PLI_q}}R]g`E*JG^Ƽc9%.GME5( !A[{ǻ|zkdkgnmZ\K:/4<>Snw|}mUVRH8-4NoX[x˹u|o{y`MXvqmQKabl|yȰξbcszif_x}ib_aNK[aoz~i[P7JowfD4OfjZOQS\lx}wpxflearvsq|~|lenkJBVw|wxpprWVg|tfPWtxZc~||^o}}sS\ns`X]nt~jhnmulhXY[`u~pjnqlqzraOKKUWYiosxtgbturk^YR`s~~{up{xtxtpzmhovyw~yvxxivtoy|ypuglwxwnlz{}{}yqvxyyr}rpy}wzxpsu||~|zuqkopopnsuz{|{vnmpu{swstuv{ljsxxuhvw||t{~}xu{}|xwzyrqv||zz~zxuqty|vv|}zz{uswuuwtonsx|~|urtyyux~~}|}{}xvtyyqinnptx}{zz{|zvy}z{|||~~tx|}||||{zxwyxz{tqx~}~|ustx{{yxz}|||}}}z{|~~{z~}}}|~~|zw|~}y{{}}{~~}zxzz|{{z~~}}~}}z~~xuy}~~}||}}{{|yx||z{}yxyyxy{}|~|{{~~}~}||zz{}}~}}}~~|z{{~||}yxxy~~~~||yzz}|}||~~z{~}{|{yy{~~|||}~~}}~~~~~}}~~~}}~~}}}z|}~~}||~yy{}~~}|~~|{}}|z}~}|}~~~~|zyyy{|}}zyzyxy{||zxz|}~}~~{{~yy{|||~~~|}~~}|{|}~~~~~}||{{|}~~}}~~~~~~}~}||~}y{~|{}yxy}wx~vttzyy}|z}{|~wwzztv|zsqyzz}~}}|trxtkjvwty|~|wt{tjnzurvy~vt|xx~wpt|ulnyuopxww{~~tnuxibq{tv||{|y~olt~{srx}vrw{z}{y{|poxunqzxqysu}rr{upt|{x{}||{qs~rlr~|stzxw|voxpks~}zwwy~z}uuroyxv|~~~xv|urwymjswnq|uywnr}ljuphn{zvv{~kjykZi|qsx}{~qozzigpk`cxyjk{zs|vfh{yrswvpuzptvnuxtw~{wxtqx}mjsnah}wgfwvt}rrzscl}tbbtwqm|wysjsmY`z|urnw}tvvjqj[bxxqpoy{svvouoch{wrqv|uz|twtjm{urryxt|tpu~mhn|sf]omgupqlXU}lcqqix{rwi[bzvjdrnizvnvdYe~}tniw|oo~kkxx]Xlzujk{rzslvlW_{yrhotn|mit~`R^|tm^ppi~jh{w_Qjxshd~|jrzloo[]uuphprkxwhnfWa{{spkw~mlrgngYd|{tll{nv}jhzz`Znzqgoum|oit`S_~ri^qidag{kVFpkRLpX^~xw^_zfS`lT_|~uyk^g|xmfu}icxljwjW]u~{qaqpg}sgokXaw|tfrvpkguaR]{um[kof{^kgQIl|ufR}z_dyjtiTYx|xhktgrxlpm]`ruldoohzwhljU]vvk]pfd}pgoeVfyje}{holjuz_[j|rebpai]dqmPPhvh[dgScheumOZzzk\oeVlwhmmVcz{upopbcw{TNljLMlnjhkrYaVPmiLVrhiqzsnwmfro`iz`\p\Wj`Oa[IUymZdtoeezoPZubL[uwciuxnncawsaj~d^l]Re_TcXPbjU^lfW^|TOknO]ukdft|lelgZft]f|pasvjqa\mSKcaQcxjUbwgITygJGmw^Yexzfbwr]f]WijZbxxdcrgUa[Rgkbmztioy\Q\{PF^gP]kd[dzUX{bTb~hV]{p[_ucHY[Kbl`mvrrwa\lkIZxu[YjYThaDPhFGrzcZWfiYmi]o[E\{xgUUvvUIk^RbxgnxxLFwǮ[9LsZlt^ixďSFTH=Xwl\TXxpRU|aQf]F^{oXWk}[QbiLVa\o]RfWKd}_assdkzcHKuY8BvaS^d{iVU{—RNYȵ_=TiyUS[]IM|qG=hӯ]EQul]]v}c[o_G[zEIgkR[upJSo{HDUĀ>5RƨmGHWreLPl„RNe̲kHNfrNA`UCZ[FW{JG^|WN]hZ\zUOdd@OpaKdq^D]r_I6_`D1fĚlGW`hSVxϯfOQˇNIY[GLrnG>b}B>dΦ\9iةK6IqŃMCOxFOgeW[{hY_oND_̽k5CsS7KmĮb3@meB\pyQfr~tdYkhRPexSAHzB8GzvVK[mSV{Ȱ{\JhgUaX?T˾l45`ƜMERq_RZgiOIdʠJ8WcMIrͶhzکX1Wvf\aTcIRڕV':a9&dzLgf``Url?1oN*JȨ{^Ub^ujT^ʟV2F~;6`•iVZgmaZtyE?gܧP1Mĥ~]Xicyi^cԜX3Eڄ>1bȖkXjphm^rI4]_2;vdUebfx^Y|²^;8w8(Y—ujWcPvfOZmJ7fL,PԤkc[o^iy_PƮb@FzӁ:5gəcHNZgjJHhӥW4BӁ::_ȝiGL^{iLS|υA(R^,BvʻSNama\fμyB2iܯa5F|ƳQOhuv][o͟S/9؎9*Qţd?TkqfQZv݂=#GV4&s̈́YG`jtkQ[k;9nܠN3TɣqVVajcRc®j;Bz͎I8Zr[_fpfWfY@Ny5@nĠVG}B?W̤YG?gcJPiϲfI4dͭ^GFqмnOEj¡aM[vƳrPB\ìoJGcſW9IU?QŎT=UƯi@LmÇTAD|K@Ktՠ`F9k̙WGQtԷ}X:Q½WDRPKcҒ\Q(bǮmS9^؞dM=}DGcΦiQIZwQOZĽlPENͬ[DRhΆ_=>~ʟd@@p̱qJDlTMg˦eOMfΨ]@JgāY<@\HQƋV@SȷoADb˛bI5]ūhJ8^_A,q݌G9O۶sT@R}SKW{L;Da4@\\; yX;auL0W̪_DWwxH>Sѿo<@Y֢S>$fбY>9kת\<@ˆGAaΨkJFbëpKLa|Lvӯk?=oUEUu˫aB6]ѫY>@a}]*6ڐY5;{b8>̂BD\ר\E8cӮUzÏ^MOyҠc9:wѝW5=txK3OzDCHV12~L5bɩ^>MlvE9SqC=Y֣d>3lӳoA5^ƇM9TijqDLhǏR?DӐM8CwڽyO'IʈR5GӕS5Iv@C`Ѧ]E0[νeB/NiK%Nk>:Zݞ\>:rΣW?IgӺbD4YұX=F^}T+2ޏO-=ղe>:lƛ\ETrƳqC?_ϴi>FeԞ\?.mбiB7aѸnCDa֮sK0RLJI3EΕF8Kѻb2KpǕOCBs˟YBEg~O6KľQDV͕P9IŁ?;T˱oJ5I]CK~˒U8L~GCdɦiJLcxTQYtK6Jh@EYܟl;*m׵pA*XϔO9FRO]~ҟF<8E4KwӀ[-0٦`92g|A8\ʭb;NmƒMBEϗN>GrϿmH5^˶nF>\×_CJufQWlnJF`ƯkFLg˒Y>C~ϗV=Fqƾ\@Aq¦pMB_˹jFBsכR6Z~վyOEA~ǞbI<_ʽpI5\ͪaBNjM?EϒG9GtĺqHG>]MIs֤Z=EԎP=WʳyPELskKF]¶lDCaȞYEUoQ>OďTTmjEUbӵb/P~qw›j]}ݸe" "^{XcuƓE *LŖdTmˈxYmqLAHE~Ćfz~eu4JfcQpr_^apmP`mi|jrpxfvTPɍdvcSObğ|zrdto_O=^}{~m|hJQr~jWlƼwmJ>SjwncqvxW7#@yljzn\HKd~u[Yuepmizyqs|vberyfohah^N_x|x`o}qgq{~zig{iUUtarrfdvzx}rzwsvys|qxyzrjcg\[q|vwrrx{jczsxwm^Xs{|~}~zlrnjbli^fu~tpx{n_l}yps|wjm}|pv~zwyy~xnmvyyv|usvusvx{pl}z{{{~~urxtnx|~wtvyvry{wsuz~~upw~{sqw{vtw}pgt~usv}{}|xz{zwux~|{~~|}|y|}z{ywwvz}~}~~yvxyvuzz{|z~~{y{{wx|}wyrpty}~{try{|zuw{}}~}~~{wuu{~~}zz~{zz}}ztv|||}|~xv|}}}{zz|zx|}}wtv{|}{xxy}}~|~|{}z{}~|~|zz~~}|~}}~{~}}~~|{~}|}~~z|}ypaRpfgcNryNOm{kMI^n|mctv|tsQexMNxYFkaAMzhL\ePer}omfZmmsn[\h_djYxFQsq]cY`irze|hXl|WebZh|}lfmjVA8?JVbrxrorsusosobSP[ky̾˺`DDR\[\ev}w|}rb\fnXZf`KFWmvuvxvojmmeepxfiwsc^c`RE=756647BXuÿsfbdhms|wommmoxy|{m^\eomb\cllb^epy}{qf_``\WUX^bel{ú÷xmfb_`flppnrx|~wePA=<703M^grxm\^xzzȻԵмɺs`_soI9IhmPFZsyun`QO]lpk`TR^lka\_``egb[[`eksreZ`p|ysxwtihp|sa^jrrqmaU[ozreZW[ixtbY`efnvsgbjrvz|uot}smw~pfdgnsqhgknorxwojq|yvv}{qr|wonqsohb^[_imh][cillh``dlkhe`]courlmt}y|ywvqlnrollnkhkjqmhijpwxpijpuw{yqou}xokox{riimnnrskfint{xpu}|~z{{stvyywrjimqssqlefnsnihilsyxqooooqxyngmv{|yrmt}{|}~|yrlmpqomligjorpkhiovwpgflrwyxtruz{wz|xvvvy||xx~zux}}zxtpsz{vqqttv{{vtttwz|xrv|}yy|~~|yvvwwursuuuvwutsrqrvxyz}}}}zz}|{}}~~}}{ywy||{wtrtxxsopqrrrqnnqtv{{y{~~||{zxxzzzywuwy{yvuwz|~~{y{~~}{zz|~}|}}||}~~|z|~|}~~~x}~}}vns}}phbyok~cX{giswUXw|yumhqpU`}|hspioqsk^j[JX|[kh,)Wsn_RUwnCV}znzcubtvleiowG[hov`9Hn`O5Qͺw߭G4.KbetqvYVA"']hvɳm?5EgptikY29~ȺdyԻwzfFT޿wFHuylhpF#&fldȷTHUHgY\ybAXsttuam\*-]\`hax:Grv}B@/yOcǒ}ടʹȜUg|voN\",]VHGwFTsd`s|{`B7IozjB9XhjHVXAas^l|LU˽wTPJAXtѫu]Kcƶ|nscFbu> 3fB1Wgal|Y&<~RR[v=30Fhlf9elN!PϓUlmᣆȬ~o}|S)2`ytgfuoM$;Zlr]Vl]3HXd|pfsv`gqzvsy{~siwɹԺſrjgrV@Kh_|vY`yxJ..;Xcwc\KWdsoz]FaL1FW55X_dsr^WbAHx|I[JCě}}~^`؜U)Nm'*hs/?sh`fxc23cΜǯÎ`RgwhgRBRdnsvu`D$(`}wbND?>9QZu`509;AsVeͻˬ~ʹМqx]iy`VicG_qi[M[kpf[Z`hpqfpsWayZ@7>OXNOrzgbpmnxz{w}WKmiNIblsf\PB<.1GMhcKCSjodes{XENYYbif^gr~~nƼyrzxyws{[JOSaxwTE=Gf|uSH[rw^EAJT_pnt~tzwt}xsvsį{jh[Y\ff]]lyw{xiW:Ikwrmztqldi_R[go|tkgkmpvzvyxy~yaU[joquya]hpwstteZ[dhj|prpgmxustYJYojkov{y{xwx}|xe[Xatw{z{uk[ZfnpgikcU[caMHEBT[NJUsyrx}~xyzutuu~zyypt~}|ocejov~urphdqvzwfuxkuwabskbgywityykwoowtnne\UV^hmehr}yfV\nzfbaVC9=Oi}vmdPBN`p{vgaaeuzsmlpuy|{{xi[]ppp}tglzxyplnqmg]Xar}ps~q~~ydVSYYcrxt{u_Z^]nxsh[OKSUWTTguyyy}zxtmifnt|{yz|~yuv}|l_o}zwus}|zv{~xmhp~qbVQW^`XWURTU`lilgr|wcamgc]_ev~sxycXewxwzo\TLDBGIJU`jnpzwh`jyyqikxsqysotqcev{nfoytou~vqk`ixu~|ou{zfmurloff^_kme^QXfqonvwhjidiyqib]]dq{~vg\W]luutmyups}~~zqrx|wjmv{tkiir|}vzttw~vji~~yov|rklks~}}}~zyuy}yrlmoqi^baag^OP[dbK22DSY\]`hnmnojgpyzxukfba`fv~~|nor{yy}}s}wztt}wx||trvz{sllllihhmvvnjegmuyxullw|wvz|zyxztkhf^^eefe`cr~yxzz{xpks}{|{olr~zowsw~xnknvth`dqoc]gonquxodahnlmjllnmjedeny|yuqnkhoxtkjlpw}~yonmmmozzuztlmnwsqt}wxz|~{z{~ws{}wz}~ssv|yyzw}}|{~|v}}z~{w{zqru{{trtyxniov{}yv|{}zw{ytx{zz{xla]gw~|zmotuwxz~xsvmn|{x~njhlpkZ[fkjljfijebglz}xvx|{~|ystw~~yyvtw{wprtzvrv~|{spy||~rhmty~xssvw{wx{yywumpqswz||qmtxwnot~}}yy|x||wodgprry|tpswz}wtz{yz}{sov~zvuxxvuojlloz{}~z}ypls~~unlnpmlorru~wv{~zupmpvy~z{|~}xz}{xxzttx}{vuz{wtw~}|}zxxqtz~}~~z{{|}znljhmpvyxtmjjknrruxz}~yxwx|~zzropx|wzzyxvy|~zz}}wrw|yqnjgjpqsy~~~xy{}zqptwxvy~|xy{tlnuy{|truyts||www{~|{~xtruxxxrkflx}vosy|wrrtz~rmlqw|~ytvwxtzxpjjjlnjlkgebbeknllsyvswz||}}||wtyywywrsv|~{|{y|}}truvxvsstvsqvzzxqlmsx|}{xvy~~}}~{}|z{}{~}zz{}tnntyy|~{y~z{~}}{upu}{yxy}zvzzpjpxyrnopou|~|{}}~yy~}urty}|wuz{|||yspuzzz|unpwzuomrvvsusrx~xw~}~}zy||zxtonpu}~xy~|yusw|~~|yz|~~|}{|}xy|}xy~~~}~|{~}}yyy{~z{|~~x|}}}}~zz}~|}~|}}{y{|{{|}xv{}{zyz{}~~}}zyyyvv}~~~z{{z{|~}xx|{|~{|~{{}}{~~}}}{wtwz|~}~yvtw|zqptutwyzz{||{|~~}|zxxyzyvtv|}zz{{{}}||{ywuuwyyy|{x{~~|~~{z|~~~|zz{{}~}}}|}zyz|}|}~~~~}~{{}}{{|}||}~~~~nNZvl]fhfl|{SPrmGe[egYrs]nhHZ_Rbvi[vnfs|xi[ilZq}kvzk`bjqu~lekt||syki|mZ[k}mfxhQYk~|mc^U_wfvmwwporkkwvsghk|}u|xcP\ykkhkv]qfIDYsygSUo{hl|xfktmmcoxqtx~yz}}hbu|pz{jegs{or|mjv|qr~u`QZssbevv{g_fy~zsyhm}rx{vxjagi_UR\v|y{rrw|}vmmyxnhjxypmt~sowwv~zulkr}xnv~ww}vrphfe[U\]dpbbfh{|unhq||~ur~{wzxqt{ztkfefjlkigipuoikpz~vuvvnhgks|~}}}{yy~~{}zvtwvrrx{wy}{smlsz~~~~{y{|}}{}|usvxxvu{{z{||{{{}|xy}~wrpruuttx}z~voh`Z[]^_cfjou{~vof\OB855;GT`hov~}vgS=( -?KQYft˿xgTC91' -*6CQ[clvƽw_J8% "2@Qf{ƺseXK?4* (5@MYcoĺwi]SKD=60+'# (3?JU`js}}tld^WQLHD@<950,-3;BKU]ciq{ºyrlhd`\XTRPLHDA=:89>DLQUY_goxýyupligeb_][YUPJE?:89=AFKOSY`it}yuspnljfb\XSNID<62//38=AFKQX`hr{xuromlifda]WPIB?<8423679:753112359=AFKOU]hv{vsqpruwwurmigeedb_[WTSSROMLLOT[aglpu}ý|yvrnjfb_\YVSPNKJIKMPSTVX\agoy|yvtsrokgeca`_\YVUWY]bgknrv|~yuqmhda\XTOKHDA?>>>>@BFIMQU[bjt}}zxtpkhedb`^\[[ZY\_cgjmpsw|~zvsqnljfb_\YXWVVVVVVWZ^cgkorvz|yvtpmkigdb`^\ZYXXZ[[[[Z[]afknprtw|{ywtqnkhfdbaa`_^]\[[[\^_aaabcfjouy|~|{zyyyyyyyxwvutsrrqpnmkihffffghhijkkllmnoqsuwz|~}{zxwusrpnmlljhgggghhhijjjkkklmoqstuwy{~~|zxxxwwvvuuuvuuuuvvuutssssstttuwxz{}~~~}}||{zzxutroke\K:-+^zP\tDGlɈE')5F[ealfUW]\TXoww}zpf^\]^`ejouy|}}|{}{xjf|sg_abdinu~oh^TPPU^kv}zvrnjmruxyuojebdjs}tkhgebbhpx~vnllmptwy|}|}~~|xtqtx|~|wuxypkjnw}zropsvxwuuy~|zyyxwttwz~zwsrsqnklnrx~}|{{~zvtssuxxwz}}zwttuw|{ywvvx}|xvvvvxzzyz{}~|z{~~{xwwx{~}{xvuuvxy}}yvuttwx|{xxxyz{}||zyxz{|~~||||||{yz{{~~~||}~~~vu}yvz|}~xw{zwz~~~|}}}~~rhnmm{wzyno~vq{ztwutz|z~|ut|vuvajyqasygivwhp|{qv~nvzrt \ No newline at end of file +βgyk>Qo|vhw]G\D(NZd~sb;lmKK{oCNqqko|e~oqaTbgn|mowk[ateSc|mvʺ|Mfmeg{sloWabtnwo;b}v\oxY{Ȟh[kMRrkc`Pynd_5ifRof^wws}jJMlznZ\oz|}wozwzxu}j<@f{}nWc_T]daYC,8XgF-Odtrp|ýǚjj<=qt}}NWR'Cm|ZN~lXsv{pstRSvs|KLffF8RVM>)M\KIdzkhn~}hsxsxnwhp}xtn{kJYlP@VzeYܧϹeHv`ocoUjgNTYph{}vvovUN:0^p[H9}cTT`acz}tplg۱m}w~y{~QEd|}xdeZwihnObtgsplnogn{|or}wqaJ=X|T(QwjWtaVtkJ–x_fl|w|s{uysvmpƣiGc`qs=~ngU?tbC?BtNq^fSmJr_Ty~|opvjrkafPzzs[r~aWgwkv|qck_}֫tYsiu`UifgHQ~KEe7\\FIzkqrqc@xbV6_}NdqNycOfxvztgTPlo`t|i]=II~BS}julgwq[Lny[i}gyocef[VkŤdU]PAAO|~dxzi_rRi_sewwpUZW}tkeaVK>8y|_̐|ub`gxv~ix}kP\Rgzjs}qs^orpaayzSWxMS}unl_|eVCaj^jsjlzl\RcdB>NNnodo{xe{xxitnLD\tuguxwkеʥ|l]o[aSV^T5[sM[F[Y}czrzQFSx^Swsprò̸wvy\cjJ:CPmxOK^_n_LTKLiv~|fgyam{t0AfS:=EfJ!7OnRB9AzfsuNJu|~o\TFDIOKdj|o{oMnr|qcbwJNo~ia[VNaqiMavdgis{viqyv\Xn]Xk}e\UuaknO_Slq-8_OwtXiZYgH|qIefXvySktpl`egp\kYTxh`\||yoxsaTkryyvriXdlt`bm~iTdleztrypn~bri~tjmp{axzy}|SrbWdut:BA^LTtPWj}hQzfew~}{mpaEUtlmhmjRAepqlitdf|yw~^Wgvk_`ctsvj]umM_qx~hZxwxm~o]|ztxsmOa{rez~Zm}yfe|{|kn~ok{fme_k[p||pjonlz^juawŶsl]UYcg~~ns}uy|wUGa{}bptFCojOYZnzjmurnfsdlcpu~ylhX}fqzc]{1JU`C[~zigqrmbaxha{gdxti{uJJ}sqrzjhrekk[n{jxfhvaklgpr~nVQvzm٦yjVKsgl6XRHkScsR^Oniftkr`̗¤{vpypvvkmp`FQ^qghmnzr[lvn~qq]RiX@JyM9WRxpbdKRVhsci|zt`iG[\ttyyyXw}idt{amot}bXt~jI^v`a{ngyz||wZLaUKValagf|}{|{tcloplxn{fU\p}rX_twit~p{]de_jdcjadyjr}usnahzxyw[E{^kzxWlqv{tgt~Vhl{p~lxtuqwmRGRbldG\jtp]ons}vwyhax[T~_`|yegnjpwqp]elgkv}mmN_oo_hsxqv}f}u|}fvxsuUTmobab}~f[lyWly~on|qswzt{woke{vtyywufj^XY_ibq{{yw}~iQGJQb|xont~|oyes}]NSUfwn{wyobPaIDAVk]9Bdyy_cnYnKTz~cdkQSqsvnjqu{q|zm^hnYkwqn^]qYgslr{wl}~~srlrvX`au|nht~ix~n]byhxkxp}~oa^jzokid[yw|uty}kpu|va_sgPJVeinnvzeZhu~z|r}}|YZjojojltr{~jjzv{r}l||lY}y|~x|Vjmbb[sq{mqrguhMVe\n}|xq{ubyv{s{~sxnrqUQdvu{wohv}uhh{wcsq~g]k}wldewuqpkqoo|w~z{wwjslhnm||oux~xrux}~Nc|{i]OhdVQM\aWY]brv|orwztolz|x{{{irrcrztj]i}wts{|oXm~nit{zm|u}borye[ozs`_jibnr}tXnSIyuy|}nmc\swn}xkr~utxyuvdarymbio}qfcfw{rlj{}tllus~|gkx}|mj~fsrnutysvwt|}wxu|wp]K\nmz[r|vfsx|rox\WI5Nfn`S`ywsuvnkrvysvu}}v|~~|vxyyrzxrqtzz{vxkWiujMGcvo_fxz{wu|tzx_d{sttyju|ojis~rv}y}xc_hrz~|w~ri~okqyvq{{|{z~nhhc^bprimpbdjz{pq}yhbnnseYk{t~~wxbtnqvvfp_fmhmc}anpvq{vzostqmzz~~t|ru}~kjsuririjuqkowtim|xrr~yz}{~y|zts~xlrcheit{z{~vy\j~nvwwsmthgxnwkx}ysm~ztvzzmmwxr{wvz}}~{zoendbv}pbi~uq~|ry{xdl}tvzz||{ux{vspprkmtotw{v}}yxvvtkqgf^cYgkdmonu{{|}qkx}~v{}ztz~}zzswy{}|rpysssz|zx}~xvyjbmkejlqxux}z~~mt~txx}{}}{{rt|zvmhzqp|hstypq{}}z}srp{xovjoegelowmpmirzwuzy~|xfwohpw}em}ertd|vz~~yiWYikpxss{~vlw~|n]lwnnkl}x}qxsv~oz~||t|~|}p}}~~uuwjhppkqant|wo~{wznlmw{i`jpfdd]gmafs|tbmxxv|~{{es|wtsrlyyt||{~yxx|{|sjzsqymw}gmpvthwn|ivoyvyyzwnxuvkwpqrgqq|zyzqxy{|ttvqssjghekyvsvt}u}wuqs~}y|uw{{z{~}}tspgfpnw~uvxz}}}sx|yy{}vyyz{kvylwvlr~zz~|{zw|wvy}yvtxsmrtz|xxtx~ywytr{z{us|{||}zxz|}zpl}}~}y~xzyyzwz}ytw{sn}|x{}}{yt{||y}}}z{y{z~wswxw~{pw{y|yw{}|zz|}{sux{wv|}|{x{zw{}|z{|}~|wu}x~yu{yvwx}yxy}~z~}zy{z}|}z||{||}{{}{}z|}}~|wxzzywwyyyxx{|~}xvy}zy{}ztuxz~}~|{}y|{}}~}~}}}}z|{|yxxvvx|y{|z~}}{|}||||z~~}~~~}~~{{~z}~~}~zy{{|}}~}~{{||{{zyz}{}}y~~|~}~}|~z|~|}||~~~~|~~|{}~}~}}~~}~}|~}}}|{~~~}}~~~}~~|}~smrr`bOGg^Drpqg,)IIJO3>TmjEUbӵb/P~qw›j]}ݸe" "^{XcuƓE *LŖdTmˈxYmqLAHE~Ćfz~eu4JfcQpr_^apmP`mi|jrpxfvTPɍdvcSObğ|zrdto_O=^}{~m|hJQr~jWlƼwmJ>SjwncqvxW7#@yljzn\HKd~u[Yuepmizyqs|vberyfohah^N_x|x`o}qgq{~zig{iUUtarrfdvzx}rzwsvys|qxyzrjcg\[q|vwrrx{jczsxwm^Xs{|~}~zlrnjbli^fu~tpx{n_l}yps|wjm}|pv~zwyy~xnmvyyv|usvusvx{pl}z{{{~~urxtnx|~wtvyvry{wsuz~~upw~{sqw{vtw}pgt~usv}{}|xz{zwux~|{~~|}|y|}z{ywwvz}~}~~yvxyvuzz{|z~~{y{{wx|}wyrpty}~{try{|zuw{}}~}~~{wuu{~~}zz~{zz}}ztv|||}|~xv|}}}{zz|zx|}}wtv{|}{xxy}}~|~|{}z{}~|~|zz~~}|~}}~{~}}~~|{~}|}~~z|}ypaRpfgcNryNOm{kMI^n|mctv|tsQexMNxYFkaAMzhL\ePer}omfZmmsn[\h_djYxFQsq]cY`irze|hXl|WebZh|}lfmjVA8?JVbrxrorsusosobSP[ky̾˺`DDR\[\ev}w|}rb\fnXZf`KFWmvuvxvojmmeepxfiwsc^c`RE=756647BXuÿsfbdhms|wommmoxy|{m^\eomb\cllb^epy}{qf_``\WUX^bel{ú÷xmfb_`flppnrx|~wePA=<703M^grxm\^xzzȻԵмɺs`_soI9IhmPFZsyun`QO]lpk`TR^lka\_``egb[[`eksreZ`p|ysxwtihp|sa^jrrqmaU[ozreZW[ixtbY`efnvsgbjrvz|uot}smw~pfdgnsqhgknorxwojq|yvv}{qr|wonqsohb^[_imh][cillh``dlkhe`]courlmt}y|ywvqlnrollnkhkjqmhijpwxpijpuw{yqou}xokox{riimnnrskfint{xpu}|~z{{stvyywrjimqssqlefnsnihilsyxqooooqxyngmv{|yrmt}{|}~|yrlmpqomligjorpkhiovwpgflrwyxtruz{wz|xvvvy||xx~zux}}zxtpsz{vqqttv{{vtttwz|xrv|}yy|~~|yvvwwursuuuvwutsrqrvxyz}}}}zz}|{}}~~}}{ywy||{wtrtxxsopqrrrqnnqtv{{y{~~||{zxxzzzywuwy{yvuwz|~~{y{~~}{zz|~}|}}||}~~|z|~|}~~~x}~}}vns}}phbyok~cX{giswUXw|yumhqpU`}|hspioqsk^j[JX|[kh,)Wsn_RUwnCV}znzcubtvleiowG[hov`9Hn`O5Qͺw߭G4.KbetqvYVA"']hvɳm?5EgptikY29~ȺdyԻwzfFT޿wFHuylhpF#&fldȷTHUHgY\ybAXsttuam\*-]\`hax:Grv}B@/yOcǒ}ടʹȜUg|voN\",]VHGwFTsd`s|{`B7IozjB9XhjHVXAas^l|LU˽wTPJAXtѫu]Kcƶ|nscFbu> 3fB1Wgal|Y&<~RR[v=30Fhlf9elN!PϓUlmᣆȬ~o}|S)2`ytgfuoM$;Zlr]Vl]3HXd|pfsv`gqzvsy{~siwɹԺſrjgrV@Kh_|vY`yxJ..;Xcwc\KWdsoz]FaL1FW55X_dsr^WbAHx|I[JCě}}~^`؜U)Nm'*hs/?sh`fxc23cΜǯÎ`RgwhgRBRdnsvu`D$(`}wbND?>9QZu`509;AsVeͻˬ~ʹМqx]iy`VicG_qi[M[kpf[Z`hpqfpsWayZ@7>OXNOrzgbpmnxz{w}WKmiNIblsf\PB<.1GMhcKCSjodes{XENYYbif^gr~~nƼyrzxyws{[JOSaxwTE=Gf|uSH[rw^EAJT_pnt~tzwt}xsvsį{jh[Y\ff]]lyw{xiW:Ikwrmztqldi_R[go|tkgkmpvzvyxy~yaU[joquya]hpwstteZ[dhj|prpgmxustYJYojkov{y{xwx}|xe[Xatw{z{uk[ZfnpgikcU[caMHEBT[NJUsyrx}~xyzutuu~zyypt~}|ocejov~urphdqvzwfuxkuwabskbgywityykwoowtnne\UV^hmehr}yfV\nzfbaVC9=Oi}vmdPBN`p{vgaaeuzsmlpuy|{{xi[]ppp}tglzxyplnqmg]Xar}ps~q~~ydVSYYcrxt{u_Z^]nxsh[OKSUWTTguyyy}zxtmifnt|{yz|~yuv}|l_o}zwus}|zv{~xmhp~qbVQW^`XWURTU`lilgr|wcamgc]_ev~sxycXewxwzo\TLDBGIJU`jnpzwh`jyyqikxsqysotqcev{nfoytou~vqk`ixu~|ou{zfmurloff^_kme^QXfqonvwhjidiyqib]]dq{~vg\W]luutmyups}~~zqrx|wjmv{tkiir|}vzttw~vji~~yov|rklks~}}}~zyuy}yrlmoqi^baag^OP[dbK22DSY\]`hnmnojgpyzxukfba`fv~~|nor{yy}}s}wztt}wx||trvz{sllllihhmvvnjegmuyxullw|wvz|zyxztkhf^^eefe`cr~yxzz{xpks}{|{olr~zowsw~xnknvth`dqoc]gonquxodahnlmjllnmjedeny|yuqnkhoxtkjlpw}~yonmmmozzuztlmnwsqt}wxz|~{z{~ws{}wz}~ssv|yyzw}}|{~|v}}z~{w{zqru{{trtyxniov{}yv|{}zw{ytx{zz{xla]gw~|zmotuwxz~xsvmn|{x~njhlpkZ[fkjljfijebglz}xvx|{~|ystw~~yyvtw{wprtzvrv~|{spy||~rhmty~xssvw{wx{yywumpqswz||qmtxwnot~}}yy|x||wodgprry|tpswz}wtz{yz}{sov~zvuxxvuojlloz{}~z}ypls~~unlnpmlorru~wv{~zupmpvy~z{|~}xz}{xxzttx}{vuz{wtw~}|}zxxqtz~}~~z{{|}znljhmpvyxtmjjknrruxz}~yxwx|~zzropx|wzzyxvy|~zz}}wrw|yqnjgjpqsy~~~xy{}zqptwxvy~|xy{tlnuy{|truyts||www{~|{~xtruxxxrkflx}vosy|wrrtz~rmlqw|~ytvwxtzxpjjjlnjlkgebbeknllsyvswz||}}||wtyywywrsv|~{|{y|}}truvxvsstvsqvzzxqlmsx|}{xvy~~}}~{}|z{}{~}zz{}tnntyy|~{y~z{~}}{upu}{yxy}zvzzpjpxyrnopou|~|{}}~yy~}urty}|wuz{|||yspuzzz|unpwzuomrvvsusrx~xw~}~}zy||zxtonpu}~xy~|yusw|~~|yz|~~|}{|}xy|}xy~~~}~|{~}}yyy{~z{|~~x|}}}}~zz}~|}~|}}{y{|{{|}xv{}{zyz{}~~}}zyyyvv}~~~z{{z{|~}xx|{|~{|~{{}}{~~}}}{wtwz|~}~yvtw|zqptutwyzz{||{|~~}|zxxyzyvtv|}zz{{{}}||{ywuuwyyy|{x{~~|~~{z|~~~|zz{{}~}}}|}zyz|}|}~~~~}~{{}}{{|}||}~~~~ \ No newline at end of file diff --git a/applications/PoP/poc/res/sound/s2.bin b/applications/PoP/poc/res/sound/s2.bin index 9e277791096e77aa761ecb87b6cf1f087f7564be..f7044f3228d7b6e63d396ef4d3f7ec90ea33f6c0 100644 GIT binary patch delta 3381 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 I;Q#6SAAAFDtN;K2 delta 6780 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 bz%2>flE5tq+>*d83EYyvEeZU8mB9Z3&~w8P literal 16384 zcmYkD2XNizb>*F8H}N{MTw+`6#Ij^r%@QS6i4;3UfFMCMq7%LM-h1!8_YM#p1VMsC zVF7#ZA}PtTEnBu^?d&>UkDcu7WM{I!^F7e^H$wt=czEys|GsrWV(=4^PjXTv|G{aC-U7%7qK( zPA@OaADf;S9q#Yx>KU3@x^n-MFMj{W-+%ep&px<${?zfgW0PYOQ`5&zE}gq{qy8uHC->!J{W1fBHZE@WCzW@N&&^GC7Do9V_Vx=5@bd5q3Xh14 zPfd*v3-EDw@eGQL3JVMf42g)~;i$;4V1G}C-RoX{@x@oxY}$3u&CAna&$=IeENx4!YrSO4ZKU-_@!dj7RH*KXOl@1VmWpYZtTKo4gZcTW$OgVuJQfl*1x@!|gN zj<$BLK?!-~HMKRR`5DQ{$w{%{LH@o0A<=31rPYnCO$~K5RprH5@v%uMDe3tYwY63G z(axKm`@64vrXP^&2*>f8&L3|NU3~YW
4U^5I9H{`!m0fA-+!<#P+i=9bSb&5ieW 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 bU*21v?vkt!i!+}a!Cz4$?UFtk|M>p{0YAj6 diff --git a/applications/PoP/poc/res/sound/s4.bin b/applications/PoP/poc/res/sound/s4.bin index 0b765e9..50c3438 100644 --- a/applications/PoP/poc/res/sound/s4.bin +++ b/applications/PoP/poc/res/sound/s4.bin @@ -1,3 +1,5 @@ -ĿĿ|vohb[TNGC?;8889:;=?ABCDEEEDDA>:741-*'%"!)19AFJOSRRQQRRSTY_djnsw{xvtqmiebccdfnv{rkfb]]_behmquz~~|zxvuttsrqqponnmnopqqppolheb`^\ZYWUSPLIFDB?>?@ABEHKOSW\^_`a^YTOID>96778:@CEGKPTX[]`b_\ZWTQNKNPSV[`eilortvxz|~}|yvroljhfdcbaa``_____]ZWTRPOMORTWYYZ[XTQMLKKJNSY^bfjmnnnnnoopqrtuxz}}xtrpmkpv{yxwww~}|ywurolifb_[YWVTTSSRQPONNNMMNOPQPONMHB=71,'" !"$(,15:@EKLMMNKHEBAA@@CGJNSY^djpv{~~}{zz}}yusrpnnnmmmllkhea^XQKEDBA@HPYajs|}}ulfc`]\_bdghijjhgecba``cehjmorsstttssrrsstvz}~xrlf_YSMJHECHMRW^elsvx{~|zywsolhea^[[ZZZ^adhov}~}|{zxvtrojfa\WRNJIHGFFEEEDCBAAAA@?=<:864479;?DINQSUWWVUTUWXZ^chmqsuwwutrqqpooonnprtwxz|~{xurpnlilpsw|{vqrrssx}}wqkihhghjkmnpqstvwyyyyyzz{|~~||{{{zyxwtqnkgc_\XUROLJHFFEEEEFFFDB@?>=<;98766666655532001346:?CFJMQSTUVVSPNJFB=:741////0134679:??@ABBCDDCCBCEFHLRW]cioux{~¿}{ywvusqoljgc_[WRNIFDA?=<;99887788999::::9986420-+(&$" !"$%),047:>AEINRVZ]abccdddddgknry}zwtrpmkjjiiiihhgfedb`][WRMIEB>;9753210..//024689:;<:752/,)&$#! &,26:?CFHKNOPPQSUVX\`dhknqtx{~yupljhgefhikkkjjgc_[UPJE?93-)&# !"#$&'&&%$$$##$&')*+,-+)(&"#(-39>??@@><:9;<>?BDFHIJJKLMNOPPQQPONMNOPRVY]`bdggfdb`^\YXWVUVWXY]bglrw|Ŀ~|{wtronpruy~ytplgghijmptwwwvutrpnnnnnljhfa\VQLHD??>>=AFJOTY^cbba`[WRMKHFDFGIJLOQSUWXZXWUTRPNLMOPQTX[^adgiiiiihgffhjlnsw|}|zxvsqnlifedba`_^]]^^^`bdfghijiihhggffghhiihhgd`\XTPLIGFDCDDEDB?=94/*%#   +vwwxxz}{vspmjhfdb`_^]\ZYWUTSRSUWXYYXXUQLHEB@>@DHMQVZ_aabcbba`aabcdfhjiihgeb^[ZYXW[_chmsy~xrlea]XTVWY[^adgfedc`]ZWY[]_djosuvxywusrqponopprtwy|~~~|zwvvwwy|Ŀ|zwtvwy{}}{xvtvxz|~|yvroliklnpsvy|}}~}zwtpmifdfilpv||xusrrqrtvxyzz{{{zz{~}zwtrqonlkjhhhhhhggfffgghjlnoonnkfa\XUQNLKJIJJKLOSWZ]_adfgikmpruuuuusrpnnnnnoqrtx{~xqib]\[ZYZZ[ZWTQNMLKLNPRTUVWXXYYYXWVVVWWZ^bgkoswz}~{xvutsrsstuvxz{}~}{xuqmifdb_]\[ZYXWVUVVWWWXXXWWVVVWWWVUTSPMKGC?;730,++,,/37;>@BDEDDCBBA@@@@@AABCDEFGIKMOQSUXY[\^_`bcegjlptx|~{yxwusqnlhd`\ZXWUVWYZZYYXWUTRRRQQPONMKJHFFEEDDEEEEEEEDDCCCCCCEGIKORVY\_behjlnoqrstuvxz|~~}|{yxwwwwy|~~|ywusqqqppruxz~ytpnkihiklnqsvwwvvuqnkgc`\YXWVVWXYZZYYXVTSRRRRSUWY[]_`a``_^]\ZYYXWXZ[]_aceffeec`^[\\]^`bdfedcb`^\ZZZ[[\^_abcefghjklnopqrstuuvvtsqpnlkijlmoty~~}{zxwvttuvwwwwwvtrpnnmllmmnnnnnmmlkkjjiijjknqtx{~}|{z|}|vqmljhhknpsvx{|}}~}{xvspmiijkloty~}|{z|~}}}}}}}}}{yxvuttstvwxyzz{{|||}}~~~}}}|{{zxvtrpnljjiiijjkjigfedcbbdeffeca_\YVTUVWX[^acdfghijkkllmmmmmmnnoqtx{}~}{{zy{}{xvsqnkfa\WPJC=:742468::::97420//./247;?DIMOQTUTSRQPONORUW[afkortwwvtsqpnmllmmmllkkklllkkjihfedccbccdegjloty~~|ywvtsqoljheb_\ZWUSQOMKJIHJMPTX^cilmnpnligfedcdfhjmoqsuwy{}}~~~}}|{{zzyyzz{yxvtrqomoprtvxz|||}}|zyxvtrpomlklmnoprstsrrqpnmljhfeeeeeedddba`^\ZXWUSQPONMMOQSVZ_cghjkllkklmoqtw{{tnnmmmrw|{yvsrux{~|yvronoqsuz~zupkfb][[\\\]]^]\[ZWUROLIFBAABBDGKNOONNMJHFDCBAAABBCEGIMQVZ^bfjmpsvvvutplgc`]ZWXXYZ\^`bdfhjmqtxz|~{xuplgca_][^acgmsy~~}|{zyyxxxvtsqpnmlkihgfeedfgijkmnonnmmlkkjhfdca`__adfimrvyxwwuqnjhgfedefghhhhgeca_]ZXWWVVWXZ[]_`bccccb`_]]^`aeinrttuutrpnnopqsvz}}{yw{}zwusqoonnnprtvx{~|zwurpmkiihhimptx~}|zyyyyyyxxwtromkihhjlnprtvvtsqolifc_\YWUTRSUVXY[]_``abcefhknrvz~{vusrpu{}{yxxyyyzz{{zzyywvutrqpooppqtvy{||}}|{zz}~{wtrqonopqrrsssqomlkjihgfedba`____`bdfhikmnnnnnmmllnoprtvxyz{|}|{zyxwvvwxyyzz{zxusplheb`^\[\\]]^_`_^][YWTRPONLMNOPQQRSRPOMLJIGILNQUY]accdec`^[[[ZZ\^acddefffeeefffgijklmnoonnnljhfdba__```bcefhiklmmnnmllklmnprtvxyyzzz{{|}|ywutruwz||ywxyy{~~}}~~~|yvsstuwy|~{wsokheaaabbdgillllligda]YUQNKHEC@><841-)&"!#%'+047889851-*(&$#&*-18>DJNSW[]_abdegikmoqtvy{{||{ywtsrrqrtwy{~{xvtrqqppqrstvx{}{wrnkosx}}|}|wrnifedcbccdddeeeeffghjkmnpqpnkid^XSNJFBBDEGKOTY]adhjklmlkihhiijkmnpqqrstuwx{}}zwurqonmnopqqpppmkhfc`][YXVUVVWWYZ\]_`bcegijlmoppoooqrsuwz||z{{|yqmifbgmsy|wurpnoqrssrrrmhc^[XUTY_eku~|y{}wphc`^[\`cgmt{~{wvuuttttttssrrrssuwy{|vrmieddccegikmpruwxz|{{zyyxxwz|~{xurpnljihgfedcba`_^]\[[ZYXXWVUTRPNJFA=83.*)*+,/26:::9851,(&$" "$&(+/36889:853000//37;?BDGJKLMNPRTVWXZ[\^_`cfhklnoppqqqux|~}{z{{||zwusmhc^XRLGDA><==>>>>>><:8789:;=?ACCBBBAA@@BCDEEFFFEEDDDDDDFGHJLORSRQPNKGDBBAACFILNOPQPOMLJIGFFGIJNRVZ]_acb`][YXWVX\_chmsx|~yurnmoqsuwz|~}|{ywusrssttuvwxwvusrqppoonoqrtwz}~~~}{zywwvvuuttsuxz}ysokgcfhjmt{}ytqppoptw{{vspmlnprv{|wsnlkjikoswz}}{yy{|~ztojefhkmrx}ztokgb^\[YXWUTSPMJGC?;77888<;:9741-*('%$&(*,.024578:;<=>>==<<<==>@ACEGIKMPRTX[_begjllkkkklllptx{}yuqnljhgggggghhggfeca_][ZXVUTSROKHD@;612234:AGNT[agggggc`\YWVTSSRRRSTUVY\_adfilnqtwz}{wsnid``__`dhlpuz|yvroljhfdba_^\ZXVTQOMLLKLORUWYZ[[XTQNJFB?><;:987765433333445543210/./02369<@CGKORTVXXWUTRQPNOQSUY]bfkpuz~~{yvuuuuvy|~~~}ztoje`[VSRQOOPPQQPPONMLKJJIHHHIIHGFEC@>;97523467<:752112258<:7543212468;>ADFIKMNOOPONLKJIIHILNPSVX[]_acccccdefhkosw|~{ywxxyy|~|zyxwvvuuutsrqomkheb_\ZWTSSSSSTTUUUUUVWXY[]`bdefgfedccbbacegikmoqqqrrqpnmmmnnqtw{|}~|zxwwvuvxy{~¿}vpid`\XWWWWXYZ\\\[[]_acfjnrux|~|yvsqomkmnpruy||xtpkfb]YVROMJHFCA>;72.*&" "&*.13467889:<>@BEGJMORTW[_cfjmqtvxz|~|xwutsrrqqpoonnooooppprtvxz|~~|zxvspnlkjijklmopqqomkiea][YXWWXZ\]^_`_\ZWURPMLMMNORUXZ[\]\YWTQNJGEFFGJNRVZ^aeeca_[VQMJHFDEGJLPTY]^]]\ZWTRRSTUY]bfkotx}ÿ|xupkgb^ZUQNKHEC@><9753211013579;=?@BCEEFFGHIKLORUY\`cgjmptwz}~yuponmlosvy{~}|ywusqomljihfdb`^\[ZZ\^`begjmnprssttsqomkgda__``bfkptw{~~~|zxvw{~~xrmhb]\\[[]`cfilortvx{~ſ¿yvrolkigec`^ZTNID@=98:ADGKNRUYXWUTNG@840+'+/48?ELSX]afimpsw{~{vsrqpqrsttsrqponlnruy|~|ywtqnlllmnt|}{ywurpmknqsvy{~}zxuqmifdba_]\ZYYXXXYZZ[\\]]_`abbccccccb^[WTPMIGIJKMQVZ^djpw}ypgb`][]dkrx}}ywusqqrsttsrqqppopqrtuvwyxwvupjd]WPJCBBAAFMSY^bfkhda]XQKEDDCBFJNRTUWYVSQNLJIGGGGGKOSW]djprtuwwxxy}ĿĿ|vohb[TNGC?;8889:;=?ABCDEEEDDA>:741-*'%"!)19AFJOSRRQQRRSTY_djnsw{xvtqmiebccdfnv{rkfb]]_behmquz~~|zxvuttsrqqponnmnopqqppolheb`^\ZYWUSPLIFDB?>?@ABEHKOSW\^_`a^YTOID>96778:@CEGKPTX[]`b_\ZWTQNKNPSV[`eilortvxz|~}|yvroljhfdcbaa``_____]ZWTRPOMORTWYYZ[XTQMLKKJNSY^bfjmnnnnnoopqrtuxz}}xtrpmkpv{yxwww~}|ywurolifb_[YWVTTSSRQPONNNMMNOPQPONMHB=71,'" !"$(,15:@EKLMMNKHEBAA@@CGJNSY^djpv{~~}{zz}}yusrpnnnmmmllkhea^XQKEDBA@HPYajs|}}ulfc`]\_bdghijjhgecba``cehjmorsstttssrrsstvz}~xrlf_YSMJHECHMRW^elsvx{~|zywsolhea^[[ZZZ^adhov}~}|{zxvtrojfa\WRNJIHGFFEEEDCBAAAA@?=<:864479;?DINQSUWWVUTUWXZ^chmqsuwwutrqqpooonnprtwxz|~{xurpnlilpsw|{vqrrssx}}wqkihhghjkmnpqstvwyyyyyzz{|~~||{{{zyxwtqnkgc_\XUROLJHFFEEEEFFFDB@?>=<;98766666655532001346:?CFJMQSTUVVSPNJFB=:741////0134679:??@ABBCDDCCBCEFHLRW]cioux{~¿}{ywvusqoljgc_[WRNIFDA?=<;99887788999::::9986420-+(&$" !"$%),047:>AEINRVZ]abccdddddgknry}zwtrpmkjjiiiihhgfedb`][WRMIEB>;9753210..//024689:;<:752/,)&$#! &,26:?CFHKNOPPQSUVX\`dhknqtx{~yupljhgefhikkkjjgc_[UPJE?93-)&# !"#$&'&&%$$$##$&')*+,-+)(&"#(-39>??@@><:9;<>?BDFHIJJKLMNOPPQQPONMNOPRVY]`bdggfdb`^\YXWVUVWXY]bglrw|Ŀ~|{wtronpruy~ytplgghijmptwwwvutrpnnnnnljhfa\VQLHD??>>=AFJOTY^cbba`[WRMKHFDFGIJLOQSUWXZXWUTRPNLMOPQTX[^adgiiiiihgffhjlnsw|}|zxvsqnlifedba`_^]]^^^`bdfghijiihhggffghhiihhgd`\XTPLIGFDCDDEDB?=94/*%#   !&+06=EMSY_ehjlnpqrsuy}}|zywuspmjgeb_\ZWTPLHC>94/*%   !(/47:>?@ABBCDEGILOSX]afkpty~zvqnlkiilorux|{vrnjfbabcdfimpponmhb[TOLHDEHKMPSVXWURPKE@:51,('(()/7>ELSZadegigeb``__^adhkorvy|~~~~~}{zxvttwz~~ytomjhfghiiklmnpqrsttuuvwxyyzzzyxwxyz{þ~~}}|xndZTNHBFIMQ[epyysonmmmpsvy|~}vojgebcfilorvy|}~|zvqmhc_ZVSQOMMNOPSX]behknnljhffedfiloqqrsoje`\XTPOOOORUX[]_ac`]ZWQKD><:86:50+%  -  #(/7>FLPTWXWUTRPNMNQTX]ciotx|}yuqmkjihkpty}}yuromkikmoqsuxzyxwvspmjigfddeefhjlnprtvvwwwwvvvxy{|~~{xussrrsux{}|{zxtokhfdbacdfimrw|xqjfb^Z[]_aeimqqqrromjgggffhjlnmllkgd`]ZXUSUWY\dlu}|void_ZZ\_bfkptvwxyxvtssstux|{vrmmmnou{|qg`ZTNOPQRUX[^__``aabbdfgiklnoooookhea\WQMLKKKNQTX\afjmqtvtrpnid_[XVSQQQQRSUWZ_dintz~}|{}|yvspnligfecbbbccfimpswz}{zxwqke`[WSOQRTUY\_bdfhjkklljhfdb`^]_acehkoqonmkfb^ZYWUUXZ]aejnrtwz{||}}|{zyxxwwwwwvutsqpnmkihfeeeegjmprstusqnlifc`__^^__`aaaaaabbcccbbbaa``aabbbaaaa``abdefhikmpruwy|~~~~~}}}xsrtuwy{~~|zwtqnljighhijkmnpppoolhd`\WSNMLJIKNPSVZ]aabbc`\YVTRQOOPPPRTUWZ]`ceghjjjjjklmnqsuwxyyzzzzzz{{{|}~}zxwvuuwz|}yvsqopruwz|~|zyxwvtsrqonljhfdcbaa`aaba_]ZWPJD>83-*+,-048ACDLSY`dijqzzphd]YRND><697=COXajmtrw}zxrrnkknie`\XVRPKGA?BFQUZffkmpszzsmkihgijjhfida]XUNJGHJMUY_ehmpu{xspnlorrsppmkga\WPNLORVY^_ehos|~{yxvvtvvuqnnjfebYUXS\]fimwx{~ywxtslffddcbdfhijloquuz}{vqlmnomilnponnotuz~~xtqrpprpofebdgkmnprw|~|wspmkigcdfjmptw~}yvqlgda]XVWUTQNKKNSY_dhjjlosy~yuqpmnlnonkhhac]_[XUTRNNNNLMNQRTUTSVYZ\cfs{xwuwwrjfddehgjigfeffheaXRLKKLJGHKOTY\`dhmrx~~zvsqrpnhd`]ZXUSRPMHFFHLPRQQTY\cmt|~}zz|{~wzxyusogd`_YWRQMOS\bjoptt}yuttuwvsqmhea^ZURNJHFGILORVX[`elv}}zvqnopsrsqnia_]`eiotx}vqkiopttrlgeabafdeehlt~~zvrnje_ZWWZ[\\]]_acfgfdeghnpsw||zurptronlmghgfgfhe_^WWRTWW_ajllqr{}~|z{{~|{{{{z|{yxvututuvuuyy{~~|wrmhdbciptutrqppolhda__beinsuwz}}|}~}ywtrmjihhlmmkhgghfda`aaaceghklnrttsuux|~}{yxwutusux~}{xvuusttuusstuvx|~~~~{z|~|tpmnopqomlhhhilosvusqopux||{xusttxz}}|~}~||}|yvspmjgda_^adhlosvxxvusrqonoqsvz~~~|zyz{|~}{xvvwvwvvupnllmnprttuutussrppmknqv{}}}~~~~~|{|}||{}|zxvutvvuttsrnjhffgijklmnonnonooppprsuvwvvwvwvvwwxz~~{z{~~|zvsqpqsuvutuxz}}{{z{}}{zyzz|}||{zzzxxx{{{|}~~}}~}|{|}~~~|xttsrqonmnoqqpnllmoponomkllmopopopnpqtx{|~~||{|{{z{zwvututuvz}~~~~~~}||||~~|}~|{zyz{}~~}zxxy|~|yxyz|~~}{yyz||~~~}|zwrnommnpsw{}|{z|~~{xvvwz{{|{|}}~{yurssvx|||{{{|{zyyyyxy{~~~~~}|zyyz}{xxz}~~~}}|}~||}~~}}}~~}~~~~~~|{zxxyz}}}{ywvwxz{{||{xusqrqpruwy{~~|zyy|~~~}|{z{zz|~~}~~~~|~~}}}}~~~~~~~}}|{{zz{}}}}}~||{{{|{}~~|}||{{~~||}~~~}}{yxwwwxy|~|zyzz|~}|zyxx{~~|{{}~{ywtrqqsvxyzyxwwy||{{z{}~~}}yyxx{~~}}|~|yxx{{xwvvvvvwxzz|~~|{}~}{{zzzz{~}{ywwxy{|{{z{{{{{||~~~~~~}}{yxwwusssvy{}}}{zxyy{}}{zwwvvwxy{{~~}yurrrttvy|}zyxy|~~~~}zxyzz{z{}~~}{yzyyyyz|~~}|yyyz|~}{zyxxx{|~}|||{|}}~~~~~~~}|~}}}}zwvvuuvx{}~~|{||}~~|zyxxy{~~||{|~~|z{|~}{{z{||||{|}}{zyxxyz|~~~~~~~{{yxxy|~~~}||}}|||{|}~~~}}~~}||{}~~}||{zyzzz{|~~}{zyyy{}~~~~}~~}e@(")%(/6DcwbN>3..16;@FLRY\V[he[[]`cfjmortwz|~¼~wrsqnrupmquoe]VZ]ZZ]bgeemnnpopsvusuuuuw|{zyz~}|~~|}zvvwxywwxuuuvy}~~|uokkiffjllmomgb_\^daWZffcfhhgbadbcedeecacikkllmmnqsqv{{yywwurtusw{}}|yuronopruxz}|xuqmkklkkloruy||}~|{{|}|zzyxwwxy{}~~}|}~~|zxvtsrpnlkkkjhgffghjlklllmnoqqponnnopomjhfcaaa``a`bdgjlmopqppomjgea^ZWWWWWXYZ]`bflptx||zxvtrpomlkjhijklnqstuxyyxurponpqsuvy{|||}~}{yvutstuwy{}}}}~}{yyyyyyz|}}zxvuvwxyyyz|~zyz|~|yvplhe`[TOMNRV[_dfhhikkkjihhhiknmllmorux{|}~|{zzxtrpnmlkihhhilpsuuuuuuvwxuqnic_ZWVUUVZ^bglqtwyxvsojfb_\YWWWY[_ejosuw}{upmlotwx{|}}|}|zz{|}}}|||~~~}|zxtqmklnqstw|}|||{yxwxyxwwusppqrrruxz|~~|zwspppppprssuz}{zz{}{xuuvwxwutv{}xvuutsqonnpsvyzz{}~~}{yuqmkmoruy|}|||}~{xsqooqvzzvsplhc_^]\]]ZVUVWZ]bdfjlmnprrsssssssttuvx{|~}ysmkllkkmrw|~{wsponosx|{unigda`aadgjmqx{xutux{~{xsnifdba`_^]\^`cfkpuwxz|}}{ywvwxwvwvtqomlkkkjjkmoruwz|}||}|zwutuuvwxyz|}~|zwspljhfefhjmpuzzuonnlkkkkkkllnopoprtutsrrrrqpoliecbdfhhjkkjklnnnnnmmnqtvvwwxz}~|zxurqsuvvvwxwvvvvusrpmkjhfc_[YXXYZ[\]_aabceghijlmnpsuwz|}~~|{yvspppprrsuw{}|{{{z{}~{wsokhhfefiknosvy||}|{yunhd^[XVTTVVX[`eiloruuwxyz{||{zxwwwxwtrponmlllkkkkklmnmmnnnmljihgecba`aabdgjnqtwyzyxvutrpljhffgjlmprtuvwy{||{zyxvuutttuuwxyyz{||~{yyxwwxxwwx{|}~|||}~|ywusrstuvxz}}zwrollllkklmmmnnprrtuvx{|~{vqmjgdbba_^^^^`abaa`^^^_``bcddegjlpuz~|yvtrrrqqqrsuw{}~}}|{zyxtrpnoprsuwz|}|yyyyz|~}|~}|{ywvvw{|{zzz{z{|}~~||}}}~|zxvvxz|||{{}~|{{z{|}{zyy{}~~~}|}||}|{zyyxwvvuuuvvwxz|~~}{{|{{zyxusttvy{~}|||{yvttuvwyzz{{|~~~~{yxwwwwwwwuuuvy{{|zxwvwxxxvtrpppprsttuuvvwy{|||{ywvvvvurqpoprtuuttuvxyz|~~{xvvusrrrrstuwxyyxxxyz{{{zxvustuuuuuvwy{|~}}{zyxxyz{||z{||~~~}|{{}~}zyyyyyyxwxz}~}{yxwwxyz}}{zzz{|}}{z|~{xwwz|~~{ywvtsqonmnnnnnooooppqrtttsrrqqsuuuuvvvwyz|{yz|~~}yyyxwvvuuvw{}|{{zyyyz|~}||}~}|{ywutuutuuvwxyy{|~}zxusrqqrtvxz|~}{ywusqonnoruwxyz|}}zxwwxxyyyyz|~~|zxvtrpnmnnnnpprsuwz|~~}|{zzyyyz{zzyxxxyzzzzzyyyyyzzz{|||}~~~}~~}{yxxwvvuwyz{|}~~}||}}}|||{{||}}}~~~}|||}}}~~}|}|{zzyyyxxxyz{||~~}}}|||{{{{|||~~}}}|||||||{zzzyyyxxxxxxxyyyyyyyzzz{{|||}}~~~}}|{{zyyxxxwwwwvvvuuuuuuvvwwwwxyyyzz{|}~~~}|{zyxwvutssrqpppooooopqqrstvwyz|}~|zxvtsponmmmlmmmnopqsuvxz|~}{zxwwvvuuuuvvwxyz{}~~}|{zyxwwwwwwxxxyz{}~~}{zzyxxxxxxyyyzz{{|}~~~}}||{{{{{|||}}~~~~~~t}}barkKLfkZWr{xw}vtwwqmmmha^_]YWZ]^_bfijjlnoopruwx{~}||}|||{zywusrqpppqrqqqrsrpppooopqrrtuvwwxxxxyxwyz{{}~~}|||||}}||||{zzyxxxwwwwxxwwwwwwvvvvvvvvwxxxxyyyyyyyyzzzz{{|}}}~}~~~~~~~~~}}}}}}}}|||{zzzzyyyyyxxyyyyyxxxxxxxxxxxyyyyyzzzz{{|||||}}||}}}}}}}~~~~~~~~~~~~~}}}}|||||||{{{{{{{{zzzzzzyzzzzzzzzzzzzzzzzzzzzzzz{{{||}}}}}}~~~~~~~~~~}}|||||{{{{{{{{{{{{{{{{|{|{{{{{{{{zzz{{{|||||||||||||||||||||||||}}~~~~~~~~~}}}}}||{||{{{{zzzzzzzzzzzzzzzzzz{{{{zzz{{{{{{{ \ No newline at end of file +  #(/7>FLPTWXWUTRPNMNQTX]ciotx|}yuqmkjihkpty}}yuromkikmoqsuxzyxwvspmjigfddeefhjlnprtvvwwwwvvvxy{|~~{xussrrsux{}|{zxtokhfdbacdfimrw| \ No newline at end of file diff --git a/applications/PoP/poc/res/sound/s5.bin b/applications/PoP/poc/res/sound/s5.bin index 91f46f9..ad44575 100644 --- a/applications/PoP/poc/res/sound/s5.bin +++ b/applications/PoP/poc/res/sound/s5.bin @@ -1 +1 @@ -{{{{||||}}||}}}}~~~~~~}}}}||||||{{{{{{{{{{zzzzzyyyyyyzzzzzz{{{{{{{|||||||||||}}~~~~~~}}}~~~~~~~}}}|||{{{{{{zzzzzzzzzzyyyyyyyyyzzzzzzyzzzzz{{{||||||}}~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}||{{{{{zzzyyyyyxxxyyyyyyyyyyyyyzzzzz{{{{{{{|||}}}}~~~~~~~}}||||{{{zzzzyyyyyyxxxxxxxxxxxwwxxxxxxxxxxxxxxxxyyyzzzzz{{z{|||}}~~~}|||{{zyyyxxxxwwwvuuvuvuvvvvvvvvvvvuuuuuuuvvvvwwwxxxxxxyyyz{{||}}~~~~~}||{{zzyxxxwwvvuutsssrrqqqqqppppqqqrrrrssssttttuuvwwxxyz{|}}~~}|{{yxxwvutsrqpponnmmlllkkkkjjjkkkkkkkllmnoppqrstuvwxyz{}~~|zywutsqpomljigedcba`__^]]\\]]]^^^__`abdeghjkmortvxz}|yurolifc`]ZWUROMKIHFEEEEEFGHIKLNPSUX[_bfimquy}{tmga[TNHC=840,)&%$$%&(*-048<@EKPV\cjrzſ}rg[QG>60*%!"&+18?HR\gq|Žwmc[SKE?:620.-.037@CGKOSX]chntzĿyqh_WOG?82-($" !$'*.28>DKRZcluľzqibZTNHC?<:87789:<@CGKPV[bhnu|~xrmhc_[WUSQONNNOPRTWZ^bfjnsw|}yurnlifdba`_____abdfiknqsvy|umu_AHTTFEVconl}Ҫ{~wjmg|jqvj\Z]UVNJLPTPT\bnt|}zyy}{qgfeiu~yi\RLOWbqrdVMLOXfu{naXUYcqsg_]akxpcZY^huyj^WW[ftzla[[`jxzl`[[`lzuf]YZbm{vh_[[bm{rf]X[blzrg^[^eq~xlebdjtuia^`gp|tjc`bgp|xngcejr|zqjggjr|{rkhhkrz{slggkpvvpljlqxzsommrx~zsnlmptz|wqppruz}xp{sm}|zwsrqu||wstootwz{tspmswy{urpopux~}wtrqsvz}ywuuy|}yuttvz~{xvvwz|yvvwy}{yvuvx{}zwuttvx{~}zxvuuvx{}{{{|~~~~~~}|{zz{|}}|{zzz|}~}||||}~~||{{|}}~~||{{{{{|}~~}}}|||}}~~~}}~~}|{{{z{{|}~~}}}||}~~~~~~}||{{|||}~~}~~~{~~~~}~~z~|~~~}~}}~~~}|~}{}{z~~~~||~}~~{~}~~~|{}~}}}x|~x|vx~|||}~}~}{{}}~|~~~}~}~}}{|~}}}{z||~|{||{}}|z{|~|~z~{x~|~{yz~{~w~~|{|z}w}yw}|uwz~z{~~}zx}|}|y~{}~zz|~z~~}}|}}}~}{}||~y|~~|~|z~||}~{|{yz{z{t~~|xxy~xu}|{qvy{r{~~~yyx{|{}s{~x}ox}||y~yw|x~{}~}~||}~}y~~}}}}}}y{}}w}}}z}y~xyw{wr|xtxklkhowvoqt|~xkwourzzrixvxqy~~yt~wxwz|yr}tw~usyv}|xzp~ux}}rz~}|t~xu~tzwv|s{x{{x~{vqnmlnt~{{ny^[sdiqjtovop|euzpwku|r|~qwjzuqyszyj{}x{owy{hx}olglasfy}cultzXqzrxk{ylyv~`avrv_v{xywZfryuyw~~pxeucygyh~xyvgi~o{qywv~{zpyxqwtu{ko}hzf{akfpueqvwlrvyltwvalHzeoy~gprt^d{p~bfuga[va~uab`hp{wynzsr{pzlwfiv~ht|zxu{x{|}~}r|i}sxbyl~t|co|tivor}pskkvq_[u`wuomxvizqsp~wT{mw|fqqe_xgUtx|V{no{sxot~tlou|}mw||sidnzvi}}z~pquohxk}{mrvrimjwpvznxw{r]tsuyzzg{wlz~_{yrwbvquqqirzrayxwzmukvbsirjuvykbletmnx}~yikz~{n}kowrltk_~fyozbw|t]ZrKtoV]`xl_lxTqlrevo{nkmy~dzosnm}_~wI{kxthsqszpo~vx{}h_}t^_slgwzvew|uhjfjhzbw~k}{uomxx|xkpl\vgK~xivpvprqr~y{gzpo|~xrzys{qno~xqhui{}jkq|zw|}{yu~y}mtzsk{uwy{nvlrh~ry|qpxutnstvolavtqhett|jv}qwV~eiisw}wlvivrnxr}rthzxgw||~rv{|}qg|bh}}~znk|e{|xxejxuynutrvq]}ztupyotxvwzhpfqu{y{rzudyrv{ptnxvxyx~n{yy{ozyz{|s|yv}|z~}{{z}v{~|z}~}|~|x{~~}{|w~ys||{x~zx}{|y{x|~zz|{~}~{~q{~}~}w|~|~~{{w~~|{{}|y~yzz{~zy}~~~~}~z{||}~~~}~|}z}||}{~|~}{}~}~|}z~}|~~~x}y~|z~{~~~~~}~~~~}|~~~|{~~~}|~|~}~}~}~}}~~~|~}}}~~|}~~~~y~lp{|tqq]ytaeqkUMYbny\ax|ujivojkuvrzp~zloxlf{xoYenqz}uoqltreT`gbv}er]UbkycOmkctw[v~buIi{LTejiWTp}|v~ym^ltlxzty{wfLivprtrk]Xc|yr~z~sqry{n_`qvjdnwt~vltqelmjxtydqndqrvpW~daurrsyvcn`khXUclfzők~XRle\{xb]p{neZcryy}x~|srwwlwqXd{tpqbtd`8Hmxyn`wǭs[TSeoUb{pql\kogn{`U`jxhknmy|gyvvqoox~vhzsy}q^Rl~zkhhgnlmlgovzwxz|}t^lUL~gpzm[^}wynat|aUf}yrw}tt~tot}{t[tqg~{rclniz{~ysz|{sqypitz}}zw|yxwqy||y|whn|}yy~~xt~x{}rjnv{~{xz{vqrywtw|}zy{xyuvxry~ztx~~y~~}zy{xz{}~||{}~~}~~}}yv{~}}xx}~~{w|~}}|~|{~z}z~y{~}}|}}{|~}~}}zwz}~zz~}~~|{~|~~yy~~zyz~~}}~}{wz}y}}~}{~{z~}}{{~}~|~}}~~~{~~~}}|}~~|~~~~~}~~~uvmgfe~{gcgTUKex}uRIf[dfSOhҊI2KpYW]h͓A'A^=\ďW)&WϹxE6\Z;EpБI8`tGAerINmVW|X:Rznjn[Hg~hfvlroneSY}vbnw}~Y^u^bhftmwy|ff}x{}uyo`rvt}qvy~wkyti|shxzz}u{v|}wrhp{l]gpgrkjxzzuqnrxisxvtihk_swkfskeiu}cfh]cofWnrjfne_swmk~oepqdilhwztzvqw~qp{xx{zz}vuu|xowwu|~xs}{{x~tu~yvwzvw}vu{y}~{z{yr{{{~}{}}~}~~}}xu||xy||}}{~~xw|z{~|y|~{}|{zz|{zx{~{uw{|}~}}xy}|z}zy}~{yz|}{~xx}|~~~}~~}~~{|~~~|~~||~}}~~z|~}{}~{~{{~~}}|}~|||}~}~~z{|{}}}|~}{|~|}~~~{z}~}~{|~}}}~~}|{{~~}}~~~|}~~~~|}~smrr`bOGg^Drpqg,)IIJO3>TmjEUbӵb/P~qw›j]}ݸe" "^{XcuƓE *LŖdTmˈxYmqLAHE~Ćfz~eu4JfcQpr_^apmP`mi|jrpxfvTPɍdvcSObğ|zrdto_O=^}{~m|hJQr~jWlƼwmJ>SjwncqvxW7#@yljzn\HKd~u[Yuepmizyqs|vberyfohah^N_x|x`o}qgq{~zig{iUUtarrfdvzx}rzwsvys|qxyzrjcg\[q|vwrrx{jczsxwm^Xs{|~}~zlrnjbli^fu~tpx{n_l}yps|wjm}|pv~zwyy~xnmvyyv|usvusvx{pl}z{{{~~urxtnx|~wtvyvry{wsuz~~upw~{sqw{vtw}pgt~usv}{}|xz{zwux~|{~~|}|y|}z{ywwvz}~}~~yvxyvuzz{|z~~{y{{wx|}wyrpty}~{try{|zuw{}}~}~~{wuu{~~}zz~{zz}}ztv|||}|~xv|}}}{zz|zx|}}wtv{|}{xxy}}~|~|{}z{}~|~|zz~~}|~}}~{~}}~~|{~}|}~~~}~wx{}}}z{}~~zwqlndmam[jpoo^|kfuobv|`oomklmmllnjj||vtxwx|}~{wx~}zy~~~~|}}{xxy{}}zy{|yy~}{zzy{|}~}~~}~~~}z}~{x{~{~}||~}|}}~xxwwz}z|}~~{wvx~~~{xz}~|}|yy}}|{zz{~{vrqqsy~yvy~|z{~}|{yxy|{yz}}zy{~|}~|z|~{yz|}|}}}}}|{}~~~~~|{z|~~~}~}{z~~~}|}~}}{|~~}|}~zxy|~|{|}}||~}|~~~~~}}|}~~~}yxz|~}|}~~}|}}}~~}}~}}~~~~||}}}}}}~}~}}~~~~~~~}}~~~~~}}~~}}~}~~~~}~~~~~~}}~~}~~~~~}|~~~~}}}~~~~}}}}|}jeq}|z|yrryyux}{xqjq}|{uf_`^[f|zyv_NXf[LNQKY~htr[K=,(ǁ:g̝aFeC]^%ja;Z½O)ۙKA]Tsp!C}ƭyUUi~fLF`ʨ\=;OalsiWTamqz[1-L{ǬoI2;füX.$:b|zҠ_1%8Z{umq|~o^V`}uYQXemh\SWktXQ`}|w_C5?arfmiVUkw]RZpkQELa{ykhqzƦo?.9CI^uzw`SkhjyykhvzlkxebpzpkdfwstvjRMo~}vjfs}lfeeiqpd^k{yf]mq=*DoɹjUXx¶nadg_TQUc`;;^}o\SfƗo]cry~zWAKow\J=:ETf}rmq|jYZlzaTS^qq`Z]ht{dC5FiaB8Gayza[ltehu\LYu}jaepskdi~mUQ_qyozxh`gqvwzhQO]ktvqmqwxrlmz{oms||wz~znaY[clrvzshehlmpzsel|xtor|wgdm{xoszzrlp~vuxyxuqrwyshag{pho{}vrszyswzrns~lfimnlhgmtvtpoyytx||{|zy~|vtvwtrrtvvvtsv{}wngfn{zuv|aMXp}~wjjtrhimlsoillp|{quzhXlwkn}z~F$Bijkuã~vt[Tpi3;[ceq|ûqr|vS,0LXbq`tӶod{|WHA2:^vx|fn~xpr];1DZl{}{ֵ~lQ6"$C]_`ltx_A@KS\jtztP?EP]kpowhB,&1NgfbjntĬnJ4?bvvzzjfydF8CUg|}|hO?^}yaX_oxmXMSk|igpvo_OGJ\t~{|}sf^^gr{|rhdhs}zvxzz|~|vmfeipx}z~neiw|k`]ezzpr}|eSOZpsdajm`bp|kaeuxe^fzs_V\po``m~j\Zfx}qkm{vfdkxrijt{tsw~xplms{zx{zohhoz}upqz~xy}tpqqwznpmWYtgOaW7GʼnF6b^?`|R8NZJupQIoRS~_D:icbaȃhm^y73Mcf_Ukz}wV0IINoYgq`XXZom||fTi|smt}|obUb~~sdl~}{qxrjqmelupnwvso\PJKZlzzwol|}i]YTUafdkqqu||xbYVR\ox{woljm{~wssuy~|unh\W^fr~yjb\RQYeoyuqmlgbbackw{zzxvvyuqlklnrv}~|~zyxuttw{}{z{~{wy}}xrnov~}zyyxurswzyurnkkklow}{ysnlqtx~|xtstvz}{wtwx|}~~{www{||{|~~~~}|}|{{|{zyzyyyxwvututssrpooonnlkjkjiiijjjkjjjiiihjklklnopqrstuuuuttuvwyyz{|}~|{{~}ytrrruuoloolkhghihfeeedgihfedfhhiijlmjghkntvutsrqquvwxxz|zvu{ \ No newline at end of file +xqjfb^Z[]_aeimqqqrromjgggffhjlnmllkgd`]ZXUSUWY\dlu}|void_ZZ\_bfkptvwxyxvtssstux|{vrmmmnou{|qg`ZTNOPQRUX[^__``aabbdfgiklnoooookhea\WQMLKKKNQTX\afjmqtvtrpnid_[XVSQQQQRSUWZ_dintz~}|{}|yvspnligfecbbbccfimpswz}{zxwqke`[WSOQRTUY\_bdfhjkklljhfdb`^]_acehkoqonmkfb^ZYWUUXZ]aejnrtwz{||}}|{zyxxwwwwwvutsqpnmkihfeeeegjmprstusqnlifc`__^^__`aaaaaabbcccbbbaa``aabbbaaaa``abdefhikmpruwy|~~~~~}}}xsrtuwy{~~|zwtqnljighhijkmnpppoolhd`\WSNMLJIKNPSVZ]aabbc`\YVTRQOOPPPRTUWZ]`ceghjjjjjklmnqsuwxyyzzzzzz{{{|}~}zxwvuuwz|}yvsqopruwz|~|zyxwvtsrqonljhfdcbaa`aaba_]ZWPJD>83-*+,-048ACDLSY`dijqzzphd]YRND><697=COXajmtrw}zxrrnkknie`\XVRPKGA?BFQUZffkmpszzsmkihgijjhfida]XUNJGHJMUY_ehmpu{xspnlorrsppmkga\WPNLORVY^_ehos|~{yxvvtvvuqnnjfebYUXS\]fimwx{~ywxtslffddcbdfhijloquuz}{vqlmnomilnponnotuz~~xtqrpprpofebdgkmnprw|~|wspmkigcdfjmptw~}yvqlgda]XVWUTQNKKNSY_dhjjlosy~yuqpmnlnonkhhac]_[XUTRNNNNLMNQRTUTSVYZ\cfs{xwuwwrjfddehgjigfeffheaXRLKKLJGHKOTY\`dhmrx~~zvsqrpnhd`]ZXUSRPMHFFHLPRQQTY\cmt|~}zz|{~wzxyusogd`_YWRQMOS\bjoptt}yuttuwvsqmhea^ZURNJHFGILORVX[`elv}}zvqnopsrsqnia_]`eiotx}vqkiopttrlgeabafdeehlt~~zvrnje_ZWWZ[\\]]_acfgfdeghnpsw||zurptronlmghgfgfhe_^WWRTWW_ajllqr{}~|z{{~|{{{{z|{yxvututuvuuyy{~~|wrmhdbciptutrqppolhda__beinsuwz}}|}~}ywtrmjihhlmmkhgghfda`aaaceghklnrttsuux|~}{yxwutusux~}{xvuusttuusstuvx|~~~~{z|~|tpmnopqomlhhhilosvusqopux||{xusttxz}}|~}~||}|yvspmjgda_^adhlosvxxvusrqonoqsvz~~~|zyz{|~}{xvvwvwvvupnllmnprttuutussrppmknqv{}}}~~~~~|{|}||{}|zxvutvvuttsrnjhffgijklmnonnonooppprsuvwvvwvwvvwwxz~~{z{~~|zvsqpqsuvutuxz}}{{z{}}{zyzz|}||{zzzxxx{{{|}~~}}~}|{|}~~~|xttsrqonmnoqqpnllmoponomkllmopopopnpqtx{|~~||{|{{z{zwvututuvz}~~~~~~}||||~~|}~|{zyz{}~~}zxxy|~|yxyz|~~}{yyz||~~~}|zwrnommnpsw{}|{z|~~{xvvwz{{|{|}}~{yurssvx|||{{{|{zyyyyxy{~~~~~}|zyyz}{xxz}~~~}}|}~||}~~}}}~~}~~~~~~|{zxxyz}}}{ywvwxz{{||{xusqrqpruwy{~~|zyy|~~~}|{z{zz|~~}~~~~|~~}}}}~~~~~~~}}|{{zz{}}}}}~||{{{|{}~~|}||{{~~||}~~~}}{yxwwwxy|~|zyzz|~}|zyxx{~~|{{}~{ywtrqqsvxyzyxwwy||{{z{}~~}}yyxx{~~}}|~|yxx{{xwvvvvvwxzz|~~|{}~}{{zzzz{~}{ywwxy{|{{z{{{{{||~~~~~~}}{yxwwusssvy{}}}{zxyy{}}{zwwvvwxy{{~~}yurrrttvy|}zyxy|~~~~}zxyzz{z{}~~}{yzyyyyz|~~}|yyyz|~}{zyxxx{|~}|||{|}}~~~~~~~}|~}}}}zwvvuuvx{}~~|{||}~~|zyxxy{~~||{|~~|z{|~}{{z{||||{|}}{zyxxyz|~~~~~~~{{yxxy|~~~}||}}|||{|}~~~}}~~}||{}~~}||{zyzzz{|~~}{zyyy{}~~~~}~~}e@(")%(/6DcwbN>3..16;@FLRY\V[he[[]`cfjmortwz|~¼~wrsqnrupmquoe]VZ]ZZ]bgeemnnpopsvusuuuuw|{zyz~}|~~|}zvvwxywwxuuuvy}~~|uokkiffjllmomgb_\^daWZffcfhhgbadbcedeecacikkllmmnqsqv{{yywwurtusw{}}|yuronopruxz}|xuqmkklkkloruy||}~|{{|}|zzyxwwxy{}~~}|}~~|zxvtsrpnlkkkjhgffghjlklllmnoqqponnnopomjhfcaaa``a`bdgjlmopqppomjgea^ZWWWWWXYZ]`bflptx||zxvtrpomlkjhijklnqstuxyyxurponpqsuvy{|||}~}{yvutstuwy{}}}}~}{yyyyyyz|}}zxvuvwxyyyz|~zyz|~|yvplhe`[TOMNRV[_dfhhikkkjihhhiknmllmorux{|}~|{zzxtrpnmlkihhhilpsuuuuuuvwxuqnic_ZWVUUVZ^bglqtwyxvsojfb_\YWWWY[_ejosuw}{upmlotwx{|}}|}|zz{|}}}|||~~~}|zxtqmklnqstw|}|||{yxwxyxwwusppqrrruxz|~~|zwspppppprssuz}{zz{}{xuuvwxwutv{}xvuutsqonnpsvyzz{}~~}{yuqmkmoruy|}|||}~{xsqooqvzzvsplhc_^]\]]ZVUVWZ]bdfjlmnprrsssssssttuvx{|~}ysmkllkkmrw|~{wsponosx|{unigda`aadgjmqx{xutux{~{xsnifdba`_^]\^`cfkpuwxz|}}{ywvwxwvwvtqomlkkkjjkmoruwz|}||}|zwutuuvwxyz|}~|zwspljhfefhjmpuzzuonnlkkkkkkllnopoprtutsrrrrqpoliecbdfhhjkkjklnnnnnmmnqtvvwwxz}~|zxurqsuvvvwxwvvvvusrpmkjhfc_[YXXYZ[\]_aabceghijlmnpsuwz|}~~|{yvspppprrsuw{}|{{{z{}~{wsokhhfefiknosvy||}|{yunhd^[XVTTVVX[`eiloruuwxyz{||{zxwwwxwtrponmlllkkkkklmnmmnnnmljihgecba`aabdgjnqtwyzyxvutrpljhffgjlmprtuvwy{||{zyxvuutttuuwxyyz{||~{yyxwwxxwwx{|}~|||}~|ywusrstuvxz}}zwrollllkklmmmnnprrtuvx{|~{vqmjgdbba_^^^^`abaa`^^^_``bcddegjlpuz~|yvtrrrqqqrsuw{}~}}|{zyxtrpnoprsuwz|}|yyyyz|~}|~}|{ywvvw{|{zzz{z{|}~~||}}}~|zxvvxz|||{{}~|{{z{|}{zyy{}~~~}|}||}|{zyyxwvvuuuvvwxz|~~}{{|{{zyxusttvy{~}|||{yvttuvwyzz{{|~~~~{yxwwwwwwwuuuvy{{|zxwvwxxxvtrpppprsttuuvvwy{|||{ywvvvvurqpoprtuuttuvxyz|~~{xvvusrrrrstuwxyyxxxyz{{{zxvustuuuuuvwy{|~}}{zyxxyz{||z{||~~~}|{{}~}zyyyyyyxwxz}~}{yxwwxyz}}{zzz{|}}{z|~{xwwz|~~{ywvtsqonmnnnnnooooppqrtttsrrqqsuuuuvvvwyz|{yz|~~}yyyxwvvuuvw{}|{{zyyyz|~}||}~}|{ywutuutuuvwxyy{|~}zxusrqqrtvxz|~}{ywusqonnoruwxyz|}}zxwwxxyyyyz|~~|zxvtrpnmnnnnpprsuwz|~~}|{zzyyyz{zzyxxxyzzzzzyyyyyzzz{|||}~~~}~~}{yxxwvvuwyz{|}~~}||}}}|||{{||}}}~~~}|||}}}~~}|}|{zzyyyxxxyz{||~~}}}|||{{{{||| \ No newline at end of file diff --git a/applications/PoP/poc/res/sound/s6.bin b/applications/PoP/poc/res/sound/s6.bin index 3a3be2f..20ad5a4 100644 --- a/applications/PoP/poc/res/sound/s6.bin +++ b/applications/PoP/poc/res/sound/s6.bin @@ -1 +1 @@ -~{|zwttrpqolpqqphcgknmjffgdbdgomlkkjfbgknnllmnkfgkrvqmmppppuxvsposuvv{~~}zwttw}xtsqonnpmidceimonkgedeknkcZ[djigeglqmjnsz|yuw||{zvvvzzsrrokeceig_^flnd]^didY\chd]]gsvpmouxwy|||wruy|vrvwwtsuyxqkpw}~zqr|~~wurniclqtxsry|{|~|y}}~vonnlc]][[VRX\WNMWeuxruy{vlp{uhilsmaagf]RRY__]`iqvxvxotmcad\YV_daS>AOWbXYlifa]irunlvz}wuynjkrsnigd\TW`kttty|uokryz~{}{jco}}j\]^^VOZdkh^gpme\^k{}qswz|yĺȷy}mTEIVWH>:4. )30!&::7/3FVYOISX^YXr{lyyyprzylmiheetxx}wtoq}~~syshjnwjWH;0)#1:;/ 2DURQ[ihd]y˼Ȼxs~xYXQNLEQbeaQRZUH>DOVWD1W_bPPzwKVamransVbѶzp}|mtrd_z[XdhoinsZL?BQ]lhWGHRJIWmvwwwpT@AFKSTemjuyh`ON^btr`GDLXlgm~henzii\^eYm|ziUSKQccxdo޽ͿŪƯrc\TYZjpX:'5IO_e}zhXceaZ[}^X]fniqweJ69@BLUm~mUVcgqw·›{|{mVH,% "-9FDN\}t}ʹ~oxtvTH9:PRZRFMIQk|}_fhqna\NH>'/OxĺrtyR7CQ]XMOE7$"D]ffr`Zlqalv{ueZftduK?UhvbSD>;2P{mlUC]zǺƸvtrVrnfaiFBcoXT\\YEYxusun^:A^l{wtZ.GlqkqtmZioIG]sumpmdOX_@QpcSU[P@Xrs{s79UhV+'$(Euʠ_\|s`fňe|nF 5byj;Hp|c-/Pso\ryweqĴ˶tbntZ7+Zɲe-0]P331),Uw}snhֳN-@ex`@BNULBLa}bvpqqXK_{aiyqmYmyVNl\qokUB>2IeinnYMAUxorVZVglmydO^o}|xznkloybr~naj^ae^TpeoyuusviZbdRI7.02NMKwsz]KQBDUagOI0Dchzkntp[^~|g[XL]XYx{yssr~wrskecf`eyx|}~yxg]\RRV^pz}o`epsi`r{jhjasrla^USPQopmbl||zx{}~pjbejp|t|{{piclokcu|qtzuyrn|{{}vilpuql~}s|h^ct{w}|wngjlp}|yklrumt~yyskovz|~~yiqpyyqx{}wifi}{}ytpkorz|{skmxzvy|{z{ttsxz~rkrx}{zz}}njlw}~quruuu|}|ynpuwz{yzsuxzz{}y}~iow~|}z{vlpr}~xotxzyx|yztqu}|{}~}~usx|}|~|}tms||~|}qru|}}}otz~~v{~}|y|wrw{~z}|xty}~{z}}}qrz~~}}wtwz~~~xtx}|{{||vx{}~|vx{}|~}vsw~}~{vx{~}}~~uw{|}~~|}zx{~~~~zy{~}~|wx|~~~zz{~~~|z|~~~~}{|~}z|}}~{z|~~~x{}}}|~}{|}{}~|~~ww|{|}{~{|}~~}{z}|}~|ww~z{}}~}xzy|~~{||z{yz~}z|zwz{{~}{}}w{z~~~~{||~}yz|{x|xxywy{}~~}}|~{}}~}}}}}~y~zx|}}}yxy}~||xx~|x{|{}{y}zz~vvxxswx}~|y~{||x~|xy}}}|}}y~z{}z|z~~{}}}{x|t~|}}}}z~||{}~x|~}}||}ztsvzwr~w{}}t{|wx}|wy~|wmsxyzvz~~|~zzx}~{~bbexzhy{iyvwz|x|~sy~|{w}yz~ru|z{t{vn~}wszx}stv{{wxy|~~xrwupx}{}|~u|~|ls}}r~pqs|{yylu|xtwyrvusbywvvup}jqglxysxyzu}r}xx}{||}xuyxyz~|zsmutyytq{wzvz|hxwwvty}|{xtvym|xz}wq~p{~{}|vy|{~||xz{}{{yryvxtn~w}}x|zyn}pryn}xl{rlvxn}rz}|nvwdurn|zspvrtyxpz}~vmr}{{vtjf`{sfwdxzxww}jhup}waXztxl{q]quq}jpxvRp}amqZH?mxoWkh{urzb+1zoO^vVUdwNȎP]Lgqcը*M;buGfƩ?C|RloplҔ4[j%gWRl[i%gn>-]kUYr^e_KX{pUcy]QO.DxdGZikr|پuMKcu{ursw͹{m`^k}}tlgYKMZfg^VMELduvrph`affW>/-8Q\VSX[Zns{x}|}ɿs_[`cfims{~xpgZL=1/7<@DHMXaa`_VOUYVW^gkpw|xwvx{zrmljec_YVUSTURNKHHIMU]doy}vplc]]ZWWVVVVWWTRQNJGECBBCBDGDEMPOSYXYZ\]Z\bcdlw{}}~yttv|~~xsttxyrrx{ww{{~x{ytpmg_[SR]^WVW[VICDED@<9?LQB>QWMPTONOTOG\gWTZ]fqsstvn\]pxsoXRW^fq}}sswy{{xvtty{~{w{~vojhd]\bghjmoruy{ywz|ytvz{yxwy{{yxzyxvrsuspruwxvuxytrsonuz|~xrwxuwyy|~~xsojfhjlnigjmosxywvusqole^YTOMORUY]^_bdfkrxzxwvwwupkkprsw}}}{xvtqnjhhfcdb`dedjrv}yvvvvwwuw{|}~}~~~~|zyxvsokga\WQKFCCDHIGEGJNTY]diihhgilmkjnommoqux{|~|xtpnnoponmptx{~~}||{{}}yvwwvvvuttuvxyyz||zxurpmigfcbbbbb`aba_]]]\\[ZYXWXZ\_a``dfgknljlnprtx~~|zyxspopnmlkmmkhfffec``chha_bcbcgiiihghmrstw{}~{z{|{|~~||{y{||}~}~~~|yzyy{~}~}zxxxxwwxwy{zyxwvxyyxxyxwvwyzz{zxvsqqqpooonmkklkkkklnqsuwyyzzyxwxxvwyxvuw|~|{zyxyzyyzyxywuttrqpqqrsqponmkigedbaabcdegjnoqtvuvxwwxxy{|}~~}}~~}}~~}|zwuttsrrqpqpooqrponnoooopqrrtvyzzyz|}}}}}~~}{zxwvvvwxwuuuuuuvvvwwvwwwxxwxyzzz|~|yxxwvvtsrponmkjijklklnoonpqqrstttsssrsrstuvxz{|{{{{zyyz{{|}~~~~~~}|zxvuusqqpoooopqrssrqpoooponmnnmmmnmnoprqppqppqpppoonnoooopqrssrqrsstuwyyxwxwxyxy}~~}~}zwtqooruwy{zwutstuvwwwutrqppqrtuuutsrqpqsttsssrrqonmmkjjknrtw{~~|zxyz}~}|{|}~~}}|}~}{zz{|||{|}||~~|zxvutsrssrqponmlkjiijiiklllmnopppppoonmkkklmprtvvwyyz|~~~~}|{{{zyyxxwvvvxxwwvvutsrrqqpooppqrtuvvvwxyyyyyyxyzzzzyzzz{{||||}}~~}~}|~~~~~}{z{|}~~}|{{yxxxwyzzyzxwwwvvxyxwxyyyzzyyzzyxxwwxxxxxxyxwvutsrrrrstwyzz{}}}}|{{zzxvuwxwvvvuttuvutstttuuuvvwxxxwwwxxxyyyz{|}}}||}}}~~~{~{{~{{olftRq``gՓurIkb?=* ,T/ ob;jtG_Ŷkc̰ũP5qPBLw9ZvrP=A8P^boаth8]t`hafd^Y5&^]t}]Hwgr{lHCRB Q{aJju;jvdqw^R#Br'AwCkBsmӺǵynyOPQ)):+bnGRȵw^2[flfejpveW;45=CVAqpRˉjpʦqx7,\ANMY|SybRBjf}wzklJPJW>R(q/mov͠w͖jrgM22;Z0IKPVdYB/g[F@l|szëȬrz|wwvHOhC_|`fks`XZlPf[[qq~y}gZpyimfoxd\T]\fwsyzvnwhTdWLTR[qdowzm}|{mSmw]cJLiwYQhiftdzhhV@^L`mQ^Xy`ph8\_ujx}}zow~zzvaN\`mqdapzvvl[`jfeilwz{grnv|vzyrggW_za^xwjlsygknogkmp~plujRrlHTV1WYUNcWM^_ejvxkiyvyhe~zspOV`e__vvwuohdkVP\i_SWYTXmefzeZ`NJUi_cjdy{r{qblyWqz{v}n`jXcsqrgyuirpfiV>@C5Gd@EfQIeerƽqlkJOf_]krr{v|peuvibYPTTPcnsjYZ[U`qbp|nrx|qidRMPQdld}uur~|w\opYYQSSO\n|{ntx}snaiQMOUZbdq{rle\S?@RUYdlwx_fdYrslu|pqzndS\LJZekzzrdMLBNtkwQByPSttoXRnjhjSiiSmѧz~nlrrj}y\lÜU|]LSqql[bgb|f=xqr=vSAYcktPo.dvm_fc{Ldkbpk`~qA`o7qvfx|ilvOL~eo{]_\WIVB[jZ{eYgSvqIv{{yjr0;uY;$0TVPcy[uTjJMlIvj;^uhuMev|\XРcmdjAdcix]uqTO]^`Y{xv˚r}yyzimVc`r}d[``~RpVrtyvxnw~sG{ʈ>xsVfxUr^IleyNT=R#Yr~{zmRam|nik|ll`|r}tyuyƉ~uyuiayg@[|eKp{uF?zeyq^LtinosldRYkokvn{bbnt|mi{fnqowiWapIcURGUuhtZLb8viOm_|bhPribhm}yjv~Wpog}{ĬÑywnw}byzuxy`xmmw}f}i}}{ywm}xmvz_~mdgw`hZ|WnMSyYdkv^n}U|wz}mvolo^nwqqWkpd{lglgvhgBP|z}d`Zye^q]PyXq~HqriM_j>crL{A{exLdX{PwQh]PzYkuY_qag}Y}ul[UliQoZedY|ot`fu~nrm|kbw|woqa|uvnzlr{pz{su}j}ydym{tpyv{czhwowtw}xfhNrk}oalfLhseJ=FJDMx{A\e^q`ka]dVSUW[fqSOGDz_jmXO3ZuPGUKa7=QO~xt@H,Faz[}}|R5dFTE4pYXmz{ZelfMM[Zmhb[mVoRzTz|}wnm}{~tK{yx|~~ew}jt~Qþh{yrnnw~{rwy}rxzuzzehq~~evrdVqup]baxjdWt`gyykejz|_ofythik~zzvxyzuvvn}uu~ss{zry}|s~~}tcajs|~qw~z}z|pdqzbbvy}|znjvjn|xaQvyx|wtx||yq~t~~tj{j~u~t{}{z}zx \ No newline at end of file +~~}}}|||||||{zzzyyyxxxxxxxyyyyyyyzzz{{|||}}~~~}}|{{zyyxxxwwwwvvvuuuuuuvvwwwwxyyyzz{|}~~~}|{zyxwvutssrqpppooooopqqrstvwyz|}~|zxvtsponmmmlmmmnopqsuvxz|~}{zxwwvvuuuuvvwxyz{}~~}|{zyxwwwwwwxxxyz{}~~}{zzyxxxxxxyyyzz{{|}~~~}}||{{{{{|||}}~~~~~~t}}barkKLfkZWr{xw}vtwwqmmmha^_]YWZ]^_bfijjlnoopruwx{~}||}|||{zywusrqpppqrqqqrsrpppooopqrrtuvwwxxxxyxwyz{{}~~}|||||}}||||{zzyxxxwwwwxxwwwwwwvvvvvvvvwxxxxyyyyyyyyzzzz{{|}}}~}~~~~~~~~~}}}}}}}}|||{zzzzyyyyyxxyyyyyxxxxxxxxxxxyyyyyzzzz{{|||||}}||}}}}}}}~~~~~~~~~~~~~}}}}|||||||{{{{{{{{zzzzzzyzzzzzzzzzzzzzzzzzzzzzzz{{{||}}}}}}~~~~~~~~~~}}|||||{{{{{{{{{{{{{{{{|{|{{{{{{{{zzz{{{|||||||||||||||||||||||||}}~~~~~~~~~}}}}}||{||{{{{zzzzzzzzzzzzzzzzzz{{{{zzz{{{{{{{{{{{||||}}||}}}}~~~~~~}}}}||||||{{{{{{{{{{zzzzzyyyyyyzzzzzz{{{{{{{|||||||||||}}~~~~~~}}}~~~~~~~}}}|||{{{{{{zzzzzzzzzzyyyyyyyyyzzzzzzyzzzzz{{{||||||}}~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}||{{{{{zzzyyyyyxxxyyyyyyyyyyyyyzzzzz{{{{{{{|||}}}}~~~~~~~}}||||{{{zzzzyyyyyyxxxxxxxxxxxwwxxxxxxxxxxxxxxxxyyyzzzzz{{z{|||}}~~~}|||{{zyyyxxxxwwwvuuvuvuvvvvvvvvvvvuuuuuuuvvvvwwwxxxxxxyyyz{{||}}~~~~~}||{{zzyxxxwwvvuutsssrrqqqqqppppqqqrrrrssssttttuuvwwxxyz{|}}~~}|{{yxxwvutsrqpponnmmlllkkkkjjjkkkkkkkllmnoppqrstuvwxyz{}~~|zywutsqpomljigedcba`__^]]\\]]]^^^__`abdeghjkmortvxz}|yurolifc`]ZWUROMKIHFEEEEEFGHIKLNPSUX[_bfimquy}{tmga[TNHC=840,)&%$$%&(*-048<@EKPV\cjrzſ}rg[QG>60*%!"&+18?HR\gq|Žwmc[SKE?:620.-.037@CGKOSX]chntzĿyqh_WOG?82-($" !$'*.28>DKRZcluľzqibZTNHC?<:87789:<@CGKPV[bhnu|~xrmhc_[WUSQONNNOPRTWZ^bfjnsw|}yurnlifdba`_____abdfiknqsvy|umu_AHTTFEVconl}Ҫ{~wjmg|jqvj\Z]UVNJLPTPT\bnt|}zyy}{qgfeiu~yi\RLOWbqrdVMLOXfu{naXUYcqsg_]akxpcZY^huyj^WW[ftzla[[`jxzl`[[`lzuf]YZbm{vh_[[bm{rf]X[blzrg^[^eq~xlebdjtuia^`gp|tjc`bgp|xngcejr|zqjggjr|{rkhhkrz{slggkpvvpljlqxzsommrx~zsnlmptz|wqppruz}xp{sm}|zwsrqu||wstootwz{tspmswy{urpopux~}wtrqsvz}ywuuy|}yuttvz~{xvvwz|yvvwy}{yvuvx{}zwuttvx{~}zxvuuvx{}{{{|~~~~~~}|{zz{|}}|{zzz|}~}||||}~~||{{|}}~~||{{{{{|}~~}}}|||}}~~~}}~~}|{{{z{{|}~~}}}||}~~~~~~}||{{|||}~~}~~~{~~~~}~~z~|~~~}~}}~~~}|~}{}{z~~~~||~}~~{~}~~~|{}~}}}x|~x|vx~|||}~}~}{{}}~|~~~}~}~}}{|~}}}{z||~|{||{}}|z{|~|~z~{x~|~{yz~{~w~~|{|z}w}yw}|uwz~z{~~}zx}|}|y~{}~zz|~z~~}}|}}}~}{}||~y|~~|~|z~||}~{|{yz{z{t~~|xxy~xu}|{qvy{r{~~~yyx{|{}s{~x}ox}||y~yw|x~{}~}~||}~}y~~}}}}}}y{}}w}}}z}y~xyw{wr|xtxklkhowvoqt|~xkwourzzrixvxqy~~yt~wxwz|yr}tw~usyv}|xzp~ux}}rz~}|t~xu~tzwv|s{x{{x~{vqnmlnt~{{ny^[sdiqjtovop|euzpwku|r|~qwjzuqyszyj{}x{owy{hx}olglasfy}cultzXqzrxk{ylyv~`avrv_v{xywZfryuyw~~pxeucygyh~xyvgi~o{qywv~{zpyxqwtu{ko}hzf{akfpueqvwlrvyltwvalHzeoy~gprt^d{p~bfuga[va~uab`hp{wynzsr{pzlwfiv~ht|zxu{x{|}~}r|i}sxbyl~t|co|tivor}pskkvq_[u`wuomxvizqsp~wT{mw|fqqe_xgUtx|V{no{sxot~tlou|}mw||sidnzvi}}z~pquohxk}{mrvrimjwpvznxw{r]tsuyzzg{wlz~_{yrwbvquqqirzrayxwzmukvbsirjuvykbletmnx}~yikz~{n}kowrltk_~fyozbw|t]ZrKtoV]`xl_lxTqlrevo{nkmy~dzosnm}_~wI{kxthsqszpo~vx{}h_}t^_slgwzvew|uhjfjhzbw~k}{uomxx|xkpl\vgK~xivpvprqr~y{gzpo|~xrzys{qno~xqhui{}jkq|zw|}{yu~y}mtzsk{uwy{nvlrh~ry|qpxutnstvolavtqhett|jv}qwV~eiisw}wlvivrnxr}rthzxgw||~rv{|}qg|bh}}~znk|e{|xxejxuynutrvq]}ztupyotxvwzhpfqu{y{rzudyrv{ptnxvxyx~n{yy{ozyz{|s|yv}|z~}{{z}v{~|z}~}|~|x{~~}{|w~ys||{x~zx}{|y{x|~zz|{~}~{~q{~}~}w|~|~~{{w~~|{{}|y~yzz{~zy}~~~~}~z{||}~~~}~|}z}||}{~|~}{}~}~|}z~}|~~~x}y~|z~{~~~~~}~~~~}|~~~|{~~~}|~|~}~}~}~}}~~~|~}}}~~|}~~~~y~lp{|tqq]ytaeqkUMYbny\ax|ujivojkuvrzp~zloxlf{xoYenqz}uoqltreT`gbv}er]UbkycOmkctw[v~buIi{LTejiWTp}|v~ym^ltlxzty{wfLivprtrk]Xc|yr~z~sqry{n_`qvjdnwt~vltqelmjxtydqndqrvpW~daurrsyvcn`khXUclfzők~XRle\{xb]p{neZcryy}x~|srwwlwqXd{tpqbtd`8Hmxyn`wǭs[TSeoUb{pql\kogn{`U`jxhknmy|gyvvqoox~vhzsy}q^Rl~zkhhgnlmlgovzwxz|}t^lUL~gpzm[^}wynat|aUf}yrw}tt~tot}{t[tqg~{rclniz{~ysz|{sqypitz}}zw|yxwqy||y|whn|}yy~~xt~x{}rjnv{~{xz{vqrywtw|}zy{xyuvxry~ztx~~y~~}zy{xz{}~||{}~~}~~}}yv{~}}xx}~~{w|~}}|~|{~z}z~y{~}}|}}{|~}~}}zwz}~zz~}~~|{~|~~yy~~zyz~~}}~}{wz}y}}~}{~{z~}}{{~}~|~}}~~~{~~~}}|}~~|~~~~~}~~~uvmgfe~{gcgTUKex}uRIf[dfSOhҊI2KpYW]h͓A'A^=\ďW)&WϹxE6\Z;EpБI8`tGAerINmVW|X:Rznjn[Hg~hfvlroneSY}vbnw}~Y^u^bhftmwy|ff}x{}uyo`rvt}qvy~wkyti|shxzz}u{v|}wrhp{l]gpgrkjxzzuqnrxisxvtihk_swkfskeiu}cfh]cofWnrjfne_swmk~oepqdilhwztzvqw~qp{xx{zz}vuu|xowwu|~xs}{{x~tu~yvwzvw}vu{y}~{z{yr{{{~}{}}~}~~}}xu||xy||}}{~~xw|z{~|y|~{}|{zz|{zx{~{uw{|}~}}xy}|z}zy}~{yz|}{~xx}|~~~}~~}~~{|~~~|~~||~}}~~z|~}{}~{~{{~~}}|}~|||}~}~~z{|{}}}|~}{|~|}~~~{z}~}~{|~}}}~~}|{{~~}}~~~|}~~~~|}~smrr`bOGg^Drpqg,)IIJO3>TmjEUbӵb/P~qw›j]}ݸe" "^{XcuƓE *LŖdTmˈxYmqLAHE~Ćfz~eu4JfcQpr_^apmP`mi|jrpxfvTPɍdvcSObğ|zrdto_O=^}{~m|hJQr~jWlƼwmJ>SjwncqvxW7#@yljzn\HKd~u[Yuepmizyqs|vberyfohah^N_x|x`o}qgq{~zig{iUUtarrfdvzx}rzwsvys|qxyzrjcg\[q|vwrrx{jczsxwm^Xs{|~}~zlrnjbli^fu~tpx{n_l}yps|wjm}|pv~zwyy~xnmvyyv|usvusvx{pl}z{{{~~urxtnx|~wtvyvry{wsuz~~upw~{sqw{vtw}pgt~usv}{}|xz{zwux~|{~~|}|y|}z{ywwvz}~}~~yvxyvuzz{|z~~{y{{wx|}wyrpty}~{try{|zuw{}}~}~~{wuu{~~}zz~{zz}}ztv|||}|~xv|}}}{zz|zx|}}wtv{|}{xxy}}~|~|{}z{}~|~|zz~~}|~}}~{~}}~~|{~}|}~~~}~wx{}}}z{}~~zwqlndmam[jpoo^|kfuobv|`oomklmmllnjj||vtxwx|}~{wx~}zy~~~~|}}{xxy{}}zy{|yy~}{zzy{|}~}~~}~~~}z}~{x{~{~}||~}|}}~xxwwz}z|}~~{wvx~~~{xz}~|}|yy}}|{zz{~{vrqqsy~yvy~|z{~}|{yxy|{yz}}zy{~|}~|z|~{yz|}|}}}}}|{}~~~~~|{z|~~~}~}{z~~~}|}~}}{|~~}|}~zxy|~|{|}}||~}|~~~~~}}|}~~~}yxz|~}|}~~}|}}}~~}}~}}~~~~||}}}}}}~}~}}~~~~~~~}}~~~~~}}~~}}~}~~~~}~~~~~~}}~~}~~~~~}|~~~~ \ No newline at end of file diff --git a/applications/PoP/poc/res/sound/s7.bin b/applications/PoP/poc/res/sound/s7.bin index 07834ed..f7a69cc 100644 --- a/applications/PoP/poc/res/sound/s7.bin +++ b/applications/PoP/poc/res/sound/s7.bin @@ -1 +1 @@ -~kjqnf[uxp^l}|pfcsvfqzskzop}lTRnto|q|xtwk}tp{ikn~y}mfsut}|wrv{|{sxvnrxoux|nq~wuyw||qx~~t~~}yz||tzs~rrps{}}|vwpr`omiflyz}x}z{no|rs~txuvs|pfks||~y~wu}v}z~|rwuu}y~voj{xz{|{wyyvv|}|~|~zz}zwu|~}|v~~}~~}|w{wx~x~}z~{u{yf|cuza_czv^yxtgerbixw}ok~ytzl`p~hiwrs~z|{w~z{u|mzvw|~xyr|_x{lw|~spzuzxxuxz~xv|w{}|xuvyy|{w|{|{z}}}|xz{{y}~}}{}~|{{}|vyzz}x|{ux}vsxn{zswvx{y~umkfhurlhglvgg}oi^w`o|vyikpzjzxnq}xz|~}}|{z~|x~xz{xv|~|~zzoyz||}}~{}~~{~||x|z~y{xw||}z~~z||}}~|~}~|{}||||~~}||{~}{||}~{||}xy}|xz~}~||{~}~}}{x|~}}}||}~~}~~}}~z~~y~}~~~|~~~~~~~~~}~~~~~}~~~~~~}~~}y{}~~~~~~~~~}~~~~~~}~~}~~~~~zz|}}|~|}}~~~~~~~~}}~~~{|}z|}~~~}|}~~}}~}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}}~~}~}~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~|}}}~~~}~~~~~~~~~~~~|{{{{{|}}|~~||}{|~~|{{}~~}||zvsrsuxz}|xwxz|zyyz|||{zxyzzyxvtssrqpnmlmooosx}yvvtqmd[VW[_dgkljgffeimopsyz{{{}~xuvwvwupnqtrrrpmjjlooqrsstvz~~}}~~zurruy{yursx}~}~~||~|~|z|oux}}}}vtnkghinw}|}~zsvrsy}~|~}}{}|yx|}~yxtsonppssptxz~~}|}{{zuqrwzz|~y|vpptz|}~~}~}yldflpv}zz}~~{snikptxyyz~{xwkig`adl}ywzo`W[hqrty~}zxyn_Zblszvqsy|wssvwcL=>JYqykdfkotzytxsianx}raWY`o|trnv|iZ[pvk\[wi\[cnt{uqoeclkp}wljwq[Waz{ePSgtimx|}ne_]\[j|~toqu|xjlqyvbKFMn|jgr|uwr|~pm}~xtxyurvw{tikgdemmu}|vrmpiehdl~yqot}|tqnpyx{zywtpopxyqnljmrx~xvrjhmmrwtxznkt~wrz}~{{iduyt{rv{upw}saYSJiieqxtxx~pwxhc]]mm_VMOkwT<7Ikzpx~spon{`VVXavzmf_^[[_gw|~~{roowwzrknns~su}|xiekm{wswy}snx~wofaitpqtsv}{nefiwrlf__trhnpd`zlp^N_|y[Sct~z}x`Vi{z^RYjty~uivtsx|b\mwzywv^Ycxm[ey}aTZlg_o|Z=An}nkx|jai|z~wsorvs{wzi_ipttocajkmv~yhZOWeunqyv|rqbZcrhX]kmtkqw}here}rkvstnovuࢢsfbfnqn~GOax~}}tztzuqno}sY]|_TkBJetp{yefYWdcmiebyqz`R^|ycmvXBUsxy|zholyxg{pq~edRtz_J+Npk^_OV|yalqKrv}k{|mj_M5HsqS_acqvrZV_vx}lo{aSZtlein{n_jwhs~~xknag?3<[qO83[eJgrrmbpGhYNw|Htqnۥy~{{sa=44>=Qmv|wU;.<_bnO +Vӭyp{fhywywzri]UYTJSY\|nwgZ}ֶzxi|mj}ovMICUcj[[n{hOtdTko}uww~nkp{{uwlnt|mnw{uviboxydYq|v`ix~{h[]SNE+2=" &1Halnwұ|npmxttjkia_DF7B;38WQc|wYOdixzw}tTUvzuoy{|shcYcry|w{ƽ|uhRB@BHJEK\y}ngjVQ\__mujefloulfe^ZSSfmosqwbgolmjnlnow\Lfuqr~~j\UYfrzq_eiuofopw}}unkg\Z^eq}gabehkjmpy~xs~srvtgljtzx{s~zot~wuyUHOfn|x|momd\^pnsv^qs^LLWeeiou{n~~zhlnkjrz~uj_epqu{wmpxy|||}uxy}sy}rmkcackpicljnokhuytzqqcjjleq~yrxzzzqjldefhkqqx|rlttio{}}ywyrt{zqsskfkpt}zqq{}~wogWOR[ejhosrukpriakpnwx}xtzyonoimukfa`]dirpu|yyyzsy{urysjjifmppqmhdcgnpx|~|~{txxy~~yvtvz~~~z|wu|zwtvzx{p}{}|zyspllmnyxvz~uligeiiimopqv|{vvy}~xnjggjmptyyyyzz{{y|}}}wyy}}~}zwx}x|{xwvtuww|{zwtrpnmpqsusolmkkkiffhjmruwwvvx||xwxwyvsqqnkklosyyvvwy}}|{ww}rjdaainqpsvvuxyxy{~|{yyz~{yzyz~|wtqtz|yzxz~}zvtssqpopokjnrtw|~z{ytxz{zrnklnrux|wstux{|~zvsohdbaeipu}~{z{z|}|yututpmkjkmoopppppqsuxz|zxyy{~~|{yxvxywtqnmoqssuvvvvvvvuvvy|{xvx{~|{{zzxvuuwyxwvvuvx{|{{|}}|}~~~}||zxvtstuvx{~~{xssssuvuuvvvvvuvuuttvw||}}~}}~~{ywvwz|}~|{z{}~zwtrrsux{{z{|}~|{zyyxwwxxy{~}zzz{|~~|yyxy|~}}}}}}||zyyyyxyz|||||{z|||~}|}~}~~~~~}~~}}|{zyyzzyyz{{|||||}~~}}~}}|||||||}~~~~~~~~~~}|z{}~|yxy{~}zwwy||ywx|}zy{}|yy|}{{}~|zxxz}}|zwutux|~~}}|{z{~|zz|~ywtsrsw{~|||zyz}}{zyyz~~~~yutv||vrqonorx}{spsvwz}zvx|}xsswy|zusv{}yz}{ussstuwxxurnmnmmnqvz}~{yyxvvx|}}xtrty~tlhioy{tomqywnjjp{zqjgktwpknvyqlnu~|smmr|vpnr|~ulgiq|~xod_akxzrnqzyrrxzogekvxogdisxokmu~wpnry~umjls{~ulghox~tmlqz{tqsyvmggmv}{qkjpyvnkoxwmhir}xmfgmxsgcgqwjejtqiiq{xmglwxlb`ix|sg`bn{tjeitxvurqyulow}|nhhhhp||g\cott{~oecixskjihlw~|~{vv~{{ztvztpw~~qfjxyqlgis|pkr~~zspruyyvvyywvvwvw}~}wsv}y}z}{{|ysquvts|zppuwqjlwztvzzwy~{{}yppw~|wsvyvtuyysruzxohly{tsvvpmswsywt{~x{oehsy|{upjipxmo{{|xnnyvf^amyri`Z_q{pkr|||}}ukb^br{necflt||qhejs|zsppryxrkkv{|qlmpwtkeck{uf\atyj`cmws\V`p~jdhkmv|z{seey}{o^Xh|bU_w{pmkhgo{zslkvvrqkb^dqqb_huzppv~uebn{z~ycQZ|qimztebk{wmfgoz}rhfp{~}~{x|zuppy{qfcm}xhVQ^uxoqz~ytlegwrmssh]b~seamxoaVXlo[MPlyrbL@OttVGQmvaTS`tjWTao~|ikuqiq~ttqf`ksmlgXQ`suzq_Wfx\Xi|wnprrzzsj^^t|ri\R]{uhXLTokSQhhT[n~e\fnnnz|f_gpqpxtmuxu~srtq|ystrdTVoyxwjUQgsZQf}fVdlZaywcaozzxsmt{yst}~plqx{}rjoutqwyhciot~|nkllrzne]\m|meXKKe~vaOSpl^guyu}xrtl_`ysYKYyy`PWr}hevw|yu~rw}laj}pqz|m\[m{zrr~men{|us}||qtuuocgqtrum^]flotwqsrnp~||ypio{uy{sik~ytozw|txzxxr}yspvwpkqvlnohagy{vundh{zpc[cyyePIUnz[OYm~ladpx}unhaZ]m|scY`tngnpdeoz~|k]]iuz{w}yps{sy{~{nhpwhgr{xrv~lhs~{lfpzwjdo||k_g{zqw~mouksrkpn]MObuvliu}ta]j|wovsktux}zvovyrxqddpy{z|qtywomwrdcovsournxysz|{nku~wrv~ysy~tpu~{v{~rnt||yy}|rlr|zxyvrpty~}}||zz{}}zz}~}xwy|xpmt}~~ssz|yz~{rjkpttuy~{yvtsw~}~~~yrpv|uqsutpowzuuxyxvx~||}{x|}yqljmqsrpppmga][^dinrw}|zz}~}}xttvunhdee`[VYbjmmmoqnf_^fs~wpot~}yx||xtszviabhnmjehq{yohgknnigiotw|}yssx}~ztmhfghec``chovzxsoosxxtpkknv~~~~|xutx}{{xqpt|zspokc\Y^gqvuqqyzqprtsrrx|phhnv}}sh`bn|ypjhhgdbelsyzl`[^hzpeemwtifhlqsu{|vla_ht~}nd_blw|vwtaSORZesymdadm{wlgimomknwzoiiigddhoxzmeceijhhmuui`[Y[ahnt~vsv||pnrzyrooljhhjq{}|unmortqjedkt{zoffmw{wsu|tnnsyxqhekxzngfgfcabjuyz|nglstqtz|{~|uoiiu|ywuuvzuqrstwz|~zsot}rlosqmjhfglt{~}ymc^ZWZdsrg^WST_qtd]XRKGLYgopsz~wokijknrw|}|}}|zy|{tnlotyzvrswyvsx}}}}yyytleaafr~{pd[X\cijlpu|zqhdcfkpw|yz}|sf``aaeoy}yussx|vuy~}~|unlnu||wsswywros|ywvtsxzssz~wpnpqqrw~}urstsqrtwyzyxz}tt}}}zngfjoruywd_fqvrkghp}vomptx{~{okq{}uopv{~~~~||xvtqlgefjpwyrnlnrtvy||yusv{zussuxy}~{{zsmhddhov|{yyxwy{{xsoou}tqw}|ukegoy~~sos|yzxpifinrsrv}sga`ciry~|xxz{xsposy}wja\]agox}{||xwywsnmr~wneaaeoz~yuw|yrmklptvx|~zvssvz{vsruwwxz|}||}}vnkmrvy}~|zxuqkefqtpoliec`bkw}vstx|}|wsru|xttuvwxyxwxy}{usuy|yrljq|~~}{yy{}~|xuuxzxtppt{|vsuz}}|{|{yxxyywtqquz|}|~~}|~|{{zwspqtxz{}~~~}zxxyzzyy{~~zwuuvxyyyy{}~{yy}{ursvz}~{yy|~}|z}~{yyz{{{{|||{zywvwwvspnosy~}~~~{yxvurpnnooooqsvy{||||}~~|zyywuttvxz|~}|{zyyyzzzz{{{||}~~}}|}~|yxwwxxwvuuvxz{{{yvuvwy}~~~~}||{{{{{||{zywvuuvwxyxwvvvwxyz{|~}{z{{}~~|zwwxz{|}}||zzyyxwvvvvwwxxyyz}~~~~}|zzz{||}}||{zzzyyyz{{{|}~~~~}||||||||| \ No newline at end of file +}}}~~~~}}}}|}jeq}|z|yrryyux}{xqjq}|{uf_`^[f|zyv_NXf[LNQKY~htr[K=,(ǁ:g̝aFeC]^%ja;Z½O)ۙKA]Tsp!C}ƭyUUi~fLF`ʨ\=;OalsiWTamqz[1-L{ǬoI2;füX.$:b|zҠ_1%8Z{umq|~o^V`}uYQXemh\SWktXQ`}|w_C5?arfmiVUkw]RZpkQELa{ykhqzƦo?.9CI^uzw`SkhjyykhvzlkxebpzpkdfwstvjRMo~}vjfs}lfeeiqpd^k{yf]mq=*DoɹjUXx¶nadg_TQUc`;;^}o\SfƗo]cry~zWAKow\J=:ETf}rmq|jYZlzaTS^qq`Z]ht{dC5FiaB8Gayza[ltehu\LYu}jaepskdi~mUQ_qyozxh`gqvwzhQO]ktvqmqwxrlmz{oms||wz~znaY[clrvzshehlmpzsel|xtor|wgdm{xoszzrlp~vuxyxuqrwyshag{pho{}vrszyswzrns~lfimnlhgmtvtpoyytx||{|zy~|vtvwtrrtvvvtsv{}wngfn{zuv|aMXp}~wjjtrhimlsoillp|{quzhXlwkn}z~F$Bijkuã~vt[Tpi3;[ceq|ûqr|vS,0LXbq`tӶod{|WHA2:^vx|fn~xpr];1DZl{}{ֵ~lQ6"$C]_`ltx_A@KS\jtztP?EP]kpowhB,&1NgfbjntĬnJ4?bvvzzjfydF8CUg|}|hO?^}yaX_oxmXMSk|igpvo_OGJ\t~{|}sf^^gr{|rhdhs}zvxzz|~|vmfeipx}z~neiw|k`]ezzpr}|eSOZpsdajm`bp|kaeuxe^fzs_V\po``m~j\Zfx}qkm{vfdkxrijt{tsw~xplms{zx{zohhoz}upqz~xy}tpqqwznpmWYtgOaW7GʼnF6b^?`|R8NZJupQIoRS~_D:icbaȃhm^y73Mcf_Ukz}wV0IINoYgq`XXZom||fTi|smt}|obUb~~sdl~}{qxrjqmelupnwvso\PJKZlzzwol|}i]YTUafdkqqu||xbYVR\ox{woljm{~wssuy~|unh\W^fr~yjb\RQYeoyuqmlgbbackw{zzxvvyuqlklnrv}~|~zyxuttw{}{z{~{wy}}xrnov~}zyyxurswzyurnkkklow}{ysnlqtx~|xtstvz}{wtwx|}~~{www{||{|~~~~}|}|{{|{zyzyyyxwvututssrpooonnlkjkjiiijjjkjjjiiihjklklnopqrstuuuuttuvwyyz{|}~|{{~}ytrrruuoloolkhghihfeeedgihfedfhhiijlmjghkntvutsrqquvwxxz|zvu{~{|zwttrpqolpqqphcgknmjffgdbdgomlkkjfbgknnllmnkfgkrvqmmppppuxvsposuvv{~~}zwttw}xtsqonnpmidceimonkgedeknkcZ[djigeglqmjnsz|yuw||{zvvvzzsrrokeceig_^flnd]^didY\chd]]gsvpmouxwy|||wruy|vrvwwtsuyxqkpw}~zqr|~~wurniclqtxsry|{|~|y}}~vonnlc]][[VRX\WNMWeuxruy{vlp{uhilsmaagf]RRY__]`iqvxvxotmcad\YV_daS>AOWbXYlifa]irunlvz}wuynjkrsnigd\TW`kttty|uokryz~{}{jco}}j\]^^VOZdkh^gpme\^k{}qswz|yĺȷy}mTEIVWH>:4. )30!&::7/3FVYOISX^YXr{lyyyprzylmiheetxx}wtoq}~~syshjnwjWH;0)#1:;/ 2DURQ[ihd]y˼Ȼxs~xYXQNLEQbeaQRZUH>DOVWD1W_bPPzwKVamransVbѶzp}|mtrd_z[XdhoinsZL?BQ]lhWGHRJIWmvwwwpT@AFKSTemjuyh`ON^btr`GDLXlgm~henzii\^eYm|ziUSKQccxdo޽ͿŪƯrc\TYZjpX:'5IO_e}zhXceaZ[}^X]fniqweJ69@BLUm~mUVcgqw·›{|{mVH,% "-9FDN\}t}ʹ~oxtvTH9:PRZRFMIQk|}_fhqna\NH>'/OxĺrtyR7CQ]XMOE7$"D]ffr`Zlqalv{ueZftduK?UhvbSD>;2P{mlUC]zǺƸvtrVrnfaiFBcoXT\\YEYxusun^:A^l{wtZ.GlqkqtmZioIG]sumpmdOX_@QpcSU[P@Xrs{s79UhV+'$(Euʠ_\|s`fňe|nF 5byj;Hp|c-/Pso\ryweqĴ˶tbntZ7+Zɲe-0]P331),Uw}snhֳN-@ex`@BNULBLa}bvpqqXK_{aiyqmYmyVNl\qokUB>2IeinnYMAUxorVZVglmydO^o}|xznkloybr~naj^ae^TpeoyuusviZbdRI7.02NMKwsz]KQBDUagOI0Dchzkntp[^~|g[XL]XYx{yssr~wrskecf`eyx|}~yxg]\RRV^pz}o`epsi`r{jhjasrla^USPQopmbl||zx{}~pjbejp|t|{{piclokcu|qtzuyrn|{{}vilpuql~}s|h^ct{w}|wngjlp}|yklrumt~yyskovz|~~yiqpyyqx{}wifi}{}ytpkorz|{skmxzvy|{z{ttsxz~rkrx}{zz}}njlw}~quruuu|}|ynpuwz{yzsuxzz{}y}~iow~|}z{vlpr}~xotxzyx|yztqu}|{}~}~usx|}|~|}tms||~|}qru|}}}otz~~v{~}|y|wrw{~z}|xty}~{z}}}qrz~~}}wtwz~~~xtx}|{{||vx{}~|vx{}|~}vsw~}~{vx{~}}~~uw{|}~~|}zx{~~~~zy{~}~|wx|~~~zz{~~~|z|~~~~}{|~}z|}}~{z|~~~x{}}}|~}{|}{}~|~~ww|{|}{~{|}~~}{z}|}~|ww~z{}}~}xzy|~~{||z{yz~}z|zwz{{~}{}}w{z~~~~{||~}yz|{x|xxywy{}~~}}|~{}}~}}}}}~y~zx|}}}yxy}~||xx~|x{|{}{y}zz~vvxxswx}~|y~{||x~|xy}}}|}}y~z{}z|z~~{}}}{x|t~|}}}}z~||{}~x|~}}||}ztsvzwr~w{}}t{|wx}|wy~|wmsxyzvz~~|~zzx}~{~bbexzhy{iyvwz|x|~sy~|{w}yz~ru|z{t{vn~}wszx}stv{{wxy|~~xrwupx}{}|~u|~|ls}}r~pqs|{yylu|xtwyrvusbywvvup}jqglxysxyzu}r}xx}{||}xuyxyz~|zsmutyytq{wzvz|hxwwvty}|{xtvym|xz}wq~p{~{}|vy|{~||xz{}{{yryvxtn~w}}x|zyn}pryn}xl{rlvxn}rz}|nvwdurn|zspvrtyxpz}~vmr}{{vtjf`{sfwdxzxww}jhup}waXztxl{q]quq}jpxvRp}amqZH?mxoWkh{urzb+1zoO^vVUdwNȎP]Lgqcը*M;buGfƩ?C|RloplҔ4[j%gWRl[i%gn>-]kUYr^e_KX{pUcy]QO.DxdGZikr|پuMKcu{ursw͹{m`^k}}tlgYKMZfg^VMELduvrph`affW>/-8Q\VSX[Zns{x}|}ɿs_[`cfims{~xpgZL=1/7<@DHMXaa`_VOUYVW^gkpw|xwvx{zrmljec_YVUSTURNKHHIMU]doy}vplc]]ZWWVVVVWWTRQNJGECBBCBDGDEMPOSYXYZ\]Z\bcdlw{}}~yttv|~~xsttxyrrx{ww{{~x{ytpmg_[SR]^WVW[VICDED@<9?LQB>QWMPTONOTOG\gWTZ]fqsstvn\]pxsoXRW^fq}}sswy{{xvtty{~{w{~vojhd]\bghjmoruy{ywz|ytvz{yxwy{{yxzyxvrsuspruwxvuxytrsonuz|~xrwxuwyy|~~xsojfhjlnigjmosxywvusqole^YTOMORUY]^_bdfkrxzxwvwwupkkprsw}}}{xvtqnjhhfcdb`dedjrv}yvvvvwwuw{|}~}~~~~|zyxvsokga\WQKFCCDHIGEGJNTY]diihhgilmkjnommoqux{|~|xtpnnoponmptx{~~}||{{}}yvwwvvvuttuvxyyz||zxurpmigfcbbbbb`aba_]]]\\[ZYXWXZ\_a``dfgknljlnprtx~~|zyxspopnmlkmmkhfffec``chha_bcbcgiiihghmrstw{}~{z{|{|~~||{y{||}~}~~~|yzyy{~}~}zxxxxwwxwy{zyxwvxyyxxyxwvwyzz{zxvsqqqpooonmkklkkkklnqsuwyyzzyxwxxvwyxvuw|~|{zyxyzyyzyxywuttrqpqqrsqponmkigedbaabcdegjnoqtvuvxwwxxy{|}~~}}~~}}~~}|zwuttsrrqpqpooqrponnoooopqrrtvyzzyz|}}}}}~~}{zxwvvvwxwuuuuuuvvvwwvwwwxxwxyzzz|~|yxxwvvtsrponmkjijklklnoonpqqrstttsssrsrstuvxz{|{{{{zyyz{{|}~~~~~~}|zxvuusqqpoooopqrssrqpoooponmnnmmmnmnoprqppqppqpppoonnoooopqrssrqrsstuwyyxwxwxyxy}~~}~}zwtqooruwy{zwutstuvwwwutrqppqrtuuutsrqpqsttsssrrqonmmkjjknrtw{~~|zxyz}~}|{|}~~}}|}~}{zz{|||{|}||~~|zxvutsrssrqponmlkjiijiiklllmnopppppoonmkkklmprtvvwyyz|~~~~}|{{{zyyxxwvvvxxwwvvutsrrqqpooppqrtuvvvwxyyyyyyxyzzzzyzzz{{||||}}~~}~}|~~~~~}{z{|}~~}|{{yxxxwyzzyzxwwwvvxyxwxyyyzzyyzzyxxwwxxxxxxyxwvutsrrrrstwyzz{}}}}|{{zzxvuwxwvvvuttuvutstttuuuvvwxxxwwwxxxyyyz{|}}}||}}}~~ \ No newline at end of file diff --git a/applications/PoP/poc/res/sound/s8.bin b/applications/PoP/poc/res/sound/s8.bin new file mode 100644 index 0000000..4a4da05 --- /dev/null +++ b/applications/PoP/poc/res/sound/s8.bin @@ -0,0 +1 @@ +~{~{{~{{olftRq``gՓurIkb?=* ,T/ ob;jtG_Ŷkc̰ũP5qPBLw9ZvrP=A8P^boаth8]t`hafd^Y5&^]t}]Hwgr{lHCRB Q{aJju;jvdqw^R#Br'AwCkBsmӺǵynyOPQ)):+bnGRȵw^2[flfejpveW;45=CVAqpRˉjpʦqx7,\ANMY|SybRBjf}wzklJPJW>R(q/mov͠w͖jrgM22;Z0IKPVdYB/g[F@l|szëȬrz|wwvHOhC_|`fks`XZlPf[[qq~y}gZpyimfoxd\T]\fwsyzvnwhTdWLTR[qdowzm}|{mSmw]cJLiwYQhiftdzhhV@^L`mQ^Xy`ph8\_ujx}}zow~zzvaN\`mqdapzvvl[`jfeilwz{grnv|vzyrggW_za^xwjlsygknogkmp~plujRrlHTV1WYUNcWM^_ejvxkiyvyhe~zspOV`e__vvwuohdkVP\i_SWYTXmefzeZ`NJUi_cjdy{r{qblyWqz{v}n`jXcsqrgyuirpfiV>@C5Gd@EfQIeerƽqlkJOf_]krr{v|peuvibYPTTPcnsjYZ[U`qbp|nrx|qidRMPQdld}uur~|w\opYYQSSO\n|{ntx}snaiQMOUZbdq{rle\S?@RUYdlwx_fdYrslu|pqzndS\LJZekzzrdMLBNtkwQByPSttoXRnjhjSiiSmѧz~nlrrj}y\lÜU|]LSqql[bgb|f=xqr=vSAYcktPo.dvm_fc{Ldkbpk`~qA`o7qvfx|ilvOL~eo{]_\WIVB[jZ{eYgSvqIv{{yjr0;uY;$0TVPcy[uTjJMlIvj;^uhuMev|\XРcmdjAdcix]uqTO]^`Y{xv˚r}yyzimVc`r}d[``~RpVrtyvxnw~sG{ʈ>xsVfxUr^IleyNT=R#Yr~{zmRam|nik|ll`|r}tyuyƉ~uyuiayg@[|eKp{uF?zeyq^LtinosldRYkokvn{bbnt|mi{fnqowiWapIcURGUuhtZLb8viOm_|bhPribhm}yjv~Wpog}{ĬÑywnw}byzuxy`xmmw}f}i}}{ywm}xmvz_~mdgw`hZ|WnMSyYdkv^n}U|wz}mvolo^nwqqWkpd{lglgvhgBP|z}d`Zye^q]PyXq~HqriM_j>crL{A{exLdX{PwQh]PzYkuY_qag}Y}ul[UliQoZedY|ot`fu~nrm|kbw|woqa|uvnzlr{pz{su}j}ydym{tpyv{czhwowtw}xfhNrk}oalfLhseJ=FJDMx{A\e^q`ka]dVSUW[fqSOGDz_jmXO3ZuPGUKa7=QO~xt@H,Faz[}}|R5dFTE4pYXmz{ZelfMM[Zmhb[mVoRzTz|}wnm}{~tK{yx|~~ew}jt~Qþh{yrnnw~{rwy}rxzuzzehq~~evrdVqup]baxjdWt`gyykejz|_ofythik~zzvxyzuvvn}uu~ss{zry}|s~~}tcajs|~qw~z}z|pdqzbbvy}|znjvjn|xaQvyx|wtx||yq~t~~tj{j~u~t{}{z}zx~kjqnf[uxp^l}|pfcsvfqzskzop}lTRnto|q|xtwk}tp{ikn~y}mfsut}|wrv{|{sxvnrxoux|nq~wuyw||qx~~t~~}yz||tzs~rrps{}}|vwpr`omiflyz}x}z{no|rs~txuvs|pfks||~y~wu}v}z~|rwuu}y~voj{xz{|{wyyvv|}|~|~zz}zwu|~}|v~~}~~}|w{wx~x~}z~{u{yf|cuza_czv^yxtgerbixw}ok~ytzl`p~hiwrs~z|{w~z{u|mzvw|~xyr|_x{lw|~spzuzxxuxz~xv|w{}|xuvyy|{w|{|{z}}}|xz{{y}~}}{}~|{{}|vyzz}x|{ux}vsxn{zswvx{y~umkfhurlhglvgg}oi^w`o|vyikpzjzxnq}xz|~}}|{z~|x~xz{xv|~|~zzoyz||}}~{}~~{~||x|z~y{xw||}z~~z||}}~|~}~|{}||||~~}||{~}{||}~{||}xy}|xz~}~||{~}~}}{x|~}}}||}~~}~~}}~z~~y~}~~~|~~~~~~~~~}~~~~~}~~~~~~}~~}y{}~~~~~~~~~}~~~~~~}~~}~~~~~zz|}}|~|}}~~~~~~~~}}~~~{|}z|}~~~}|}~~}}~}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}}~~}~}~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~|}}}~~~}~~~~~~~~~~~~|{{{{{|}}|~~||}{|~~|{{}~~}||zvsrsuxz}|xwxz|zyyz|||{zxyzzyxvtssrqpnmlmooosx}yvvtqmd[VW[_dgkljgffeimopsyz{{{}~xuvwvwupnqtrrrpmjjlooqrsstvz~~}}~~zurruy{yursx}~}~~||~|~|z|oux}}}}vtnkghinw}|}~zsvrsy}~|~}}{}|yx|}~yxtsonppssptxz~~}|}{{zuqrwzz|~y|vpptz|}~~}~}yldflpv}zz}~~{snikptxyyz~{xwkig`adl}ywzo`W[hqrty~}zxyn_Zblszvqsy|wssvwcL=>JYqykdfkotzytxsianx}raWY`o|trnv|iZ[pvk\[wi\[cnt{uqoeclkp}wljwq[Waz{ePSgtimx|}ne_]\[j|~toqu|xjlqyvbKFMn|jgr|uwr|~pm}~xtxyurvw{tikgdemmu}|vrmpiehdl~yqot}|tqnpyx{zywtpopxyqnljmrx~xvrjhmmrwtxznkt~wrz}~{{iduyt{rv{upw}saYSJiieqxtxx~pwxhc]]mm_VMOkwT<7Ikzpx~spon{`VVXavzmf_^[[_gw|~~{roowwzrknns~su}|xiekm{wswy}snx~wofaitpqtsv}{nefiwrlf__trhnpd`zlp^N_|y[Sct~z}x`Vi{z^RYjty~uivtsx|b\mwzywv^Ycxm[ey}aTZlg_o|Z=An}nkx|jai|z~wsorvs{wzi_ipttocajkmv~yhZOWeunqyv|rqbZcrhX]kmtkqw}here}rkvstnovuࢢsfbfnqn~GOax~}}tztzuqno}sY]|_TkBJetp{yefYWdcmiebyqz`R^|ycmvXBUsxy|zholyxg{pq~edRtz_J+Npk^_OV|yalqKrv}k{|mj_M5HsqS_acqvrZV_vx}lo{aSZtlein{n_jwhs~~xknag?3<[qO83[eJgrrmbpGhYNw|Htqnۥy~{{sa=44>=Qmv|wU;.<_bnO +Vӭyp{fhywywzri]UYTJSY\|nwgZ}ֶzxi|mj}ovMICUcj[[n{hOtdTko}uww~nkp{{uwlnt|mnw{uviboxydYq|v`ix~{h[]SNE+2=" &1Halnwұ|npmxttjkia_DF7B;38WQc|wYOdixzw}tTUvzuoy{|shcYcry|w{ƽ|uhRB@BHJEK\y}ngjVQ\__mujefloulfe^ZSSfmosqwbgolmjnlnow\Lfuqr~~j\UYfrzq_eiuofopw}}unkg\Z^eq}gabehkjmpy~xs~srvtgljtzx{s~zot~wuyUHOfn|x|momd\^pnsv^qs^LLWeeiou{n~~zhlnkjrz~uj_epqu{wmpxy|||}uxy}sy}rmkcackpicljnokhuytzqqcjjleq~yrxzzzqjldefhkqqx|rlttio{}}ywyrt{zqsskfkpt}zqq{}~wogWOR[ejhosrukpriakpnwx}xtzyonoimukfa`]dirpu|yyyzsy{urysjjifmppqmhdcgnpx|~|~{txxy~~yvtvz~~~z|wu|zwtvzx{p}{}|zyspllmnyxvz~uligeiiimopqv|{vvy}~xnjggjmptyyyyzz{{y|}}}wyy}}~}zwx}x|{xwvtuww|{zwtrpnmpqsusolmkkkiffhjmruwwvvx||xwxwyvsqqnkklosyyvvwy}}|{ww}rjdaainqpsvvuxyxy{~|{yyz~{yzyz~|wtqtz|yzxz~}zvtssqpopokjnrtw|~z{ytxz{zrnklnrux|wstux{|~zvsohdbaeipu}~{z{z|}|yututpmkjkmoopppppqsuxz|zxyy{~~|{yxvxywtqnmoqssuvvvvvvvuvvy|{xvx{~|{{zzxvuuwyxwvvuvx{|{{|}}|}~~~}||zxvtstuvx{~~{xssssuvuuvvvvvuvuuttvw||}}~}}~~{ywvwz|}~|{z{}~zwtrrsux{{z{|}~|{zyyxwwxxy{~}zzz{|~~|yyxy|~}}}}}}||zyyyyxyz|||||{z|||~}|}~}~~~~~}~~}}|{zyyzzyyz{{|||||}~~}}~}}|||||||}~~~~~~~~~ \ No newline at end of file diff --git a/applications/PoP/poc/res/sound/s9.bin b/applications/PoP/poc/res/sound/s9.bin new file mode 100644 index 0000000..6f7f476 --- /dev/null +++ b/applications/PoP/poc/res/sound/s9.bin @@ -0,0 +1 @@ +~}|z{}~|yxy{~}zwwy||ywx|}zy{}|yy|}{{}~|zxxz}}|zwutux|~~}}|{z{~|zz|~ywtsrsw{~|||zyz}}{zyyz~~~~yutv||vrqonorx}{spsvwz}zvx|}xsswy|zusv{}yz}{ussstuwxxurnmnmmnqvz}~{yyxvvx|}}xtrty~tlhioy{tomqywnjjp{zqjgktwpknvyqlnu~|smmr|vpnr|~ulgiq|~xod_akxzrnqzyrrxzogekvxogdisxokmu~wpnry~umjls{~ulghox~tmlqz{tqsyvmggmv}{qkjpyvnkoxwmhir}xmfgmxsgcgqwjejtqiiq{xmglwxlb`ix|sg`bn{tjeitxvurqyulow}|nhhhhp||g\cott{~oecixskjihlw~|~{vv~{{ztvztpw~~qfjxyqlgis|pkr~~zspruyyvvyywvvwvw}~}wsv}y}z}{{|ysquvts|zppuwqjlwztvzzwy~{{}yppw~|wsvyvtuyysruzxohly{tsvvpmswsywt{~x{oehsy|{upjipxmo{{|xnnyvf^amyri`Z_q{pkr|||}}ukb^br{necflt||qhejs|zsppryxrkkv{|qlmpwtkeck{uf\atyj`cmws\V`p~jdhkmv|z{seey}{o^Xh|bU_w{pmkhgo{zslkvvrqkb^dqqb_huzppv~uebn{z~ycQZ|qimztebk{wmfgoz}rhfp{~}~{x|zuppy{qfcm}xhVQ^uxoqz~ytlegwrmssh]b~seamxoaVXlo[MPlyrbL@OttVGQmvaTS`tjWTao~|ikuqiq~ttqf`ksmlgXQ`suzq_Wfx\Xi|wnprrzzsj^^t|ri\R]{uhXLTokSQhhT[n~e\fnnnz|f_gpqpxtmuxu~srtq|ystrdTVoyxwjUQgsZQf}fVdlZaywcaozzxsmt{yst}~plqx{}rjoutqwyhciot~|nkllrzne]\m|meXKKe~vaOSpl^guyu}xrtl_`ysYKYyy`PWr}hevw|yu~rw}laj}pqz|m\[m{zrr~men{|us}||qtuuocgqtrum^]flotwqsrnp~||ypio{uy{sik~ytozw|txzxxr}yspvwpkqvlnohagy{vundh{zpc[cyyePIUnz[OYm~ladpx}unhaZ]m|scY`tngnpdeoz~|k]]iuz{w}yps{sy{~{nhpwhgr{xrv~lhs~{lfpzwjdo||k_g{zqw~mouksrkpn]MObuvliu}ta]j|wovsktux}zvovyrxqddpy{z|qtywomwrdcovsournxysz|{nku~wrv~ysy~tpu~{v{~rnt||yy}|rlr|zxyvrpty~}}||zz{}}zz}~}xwy|xpmt}~~ssz|yz~{rjkpttuy~{yvtsw~}~~~yrpv|uqsutpowzuuxyxvx~||}{x|}yqljmqsrpppmga][^dinrw}|zz}~}}xttvunhdee`[VYbjmmmoqnf_^fs~wpot~}yx||xtszviabhnmjehq{yohgknnigiotw|}yssx}~ztmhfghec``chovzxsoosxxtpkknv~~~~|xutx}{{xqpt|zspokc\Y^gqvuqqyzqprtsrrx|phhnv}}sh`bn|ypjhhgdbelsyzl`[^hzpeemwtifhlqsu{|vla_ht~}nd_blw|vwtaSORZesymdadm{wlgimomknwzoiiigddhoxzmeceijhhmuui`[Y[ahnt~vsv||pnrzyrooljhhjq{}|unmortqjedkt{zoffmw{wsu|tnnsyxqhekxzngfgfcabjuyz|nglstqtz|{~|uoiiu|ywuuvzuqrstwz|~zsot}rlosqmjhfglt{~}ymc^ZWZdsrg^WST_qtd]XRKGLYgopsz~wokijknrw|}|}}|zy|{tnlotyzvrswyvsx}}}}yyytleaafr~{pd[X\cijlpu|zqhdcfkpw|yz}|sf``aaeoy}yussx|vuy~}~|unlnu||wsswywros|ywvtsxzssz~wpnpqqrw~}urstsqrtwyzyxz}tt}}}zngfjoruywd_fqvrkghp}vomptx{~{okq{}uopv{~~~~||xvtqlgefjpwyrnlnrtvy||yusv{zussuxy}~{{zsmhddhov|{yyxwy{{xsoou}tqw}|ukegoy~~sos|yzxpifinrsrv}sga`ciry~|xxz{xsposy}wja\]agox}{||xwywsnmr~wneaaeoz~yuw|yrmklptvx|~zvssvz{vsruwwxz|}||}}vnkmrvy}~|zxuqkefqtpoliec`bkw}vstx|}|wsru|xttuvwxyxwxy}{usuy|yrljq|~~}{yy{}~|xuuxzxtppt{|vsuz}}|{|{yxxyywtqquz|}|~~}|~|{{zwspqtxz{}~~~}zxxyzzyy{~~zwuuvxyyyy{}~{yy}{ursvz}~{yy|~}|z}~{yyz{{{{|||{zywvwwvspnosy~}~~~{yxvurpnnooooqsvy{||||}~~|zyywuttvxz|~}|{zyyyzzzz{{{||}~~}}|}~|yxwwxxwvuuvxz{{{yvuvwy}~~~~}||{{{{{||{zywvuuvwxyxwvvvwxyz{|~}{z{{}~~|zwwxz{|}}||zzyyxwvvvvwwxxyyz}~~~~}|zzz{||}}||{zzzyyyz{{{|}~~~~}||||||||| \ No newline at end of file diff --git a/applications/PoP/poc/res/title/t0_0.atl b/applications/PoP/poc/res/title/t0_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..f65d6e5e4de1f47156bd6e375a058578f0d5dd8a GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t0_1.atl b/applications/PoP/poc/res/title/t0_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..015078175884c10fd31ea640c1e6c4385919bebe GIT binary patch literal 15796 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 K6@D*UrvC@_7F$XH literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t0_2.atl b/applications/PoP/poc/res/title/t0_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..506f97fdce2cc568b27146df288c5d7b7369e510 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t0_4.atl b/applications/PoP/poc/res/title/t0_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..76b82fede907332d0bf79e16cb4b913aa92081e2 GIT binary patch literal 1396 zcmcIiL2kq_2qcp2^x!)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t1_0.atl b/applications/PoP/poc/res/title/t1_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..f65d6e5e4de1f47156bd6e375a058578f0d5dd8a GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t1_1.atl b/applications/PoP/poc/res/title/t1_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..015078175884c10fd31ea640c1e6c4385919bebe GIT binary patch literal 15796 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 K6@D*UrvC@_7F$XH literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t1_2.atl b/applications/PoP/poc/res/title/t1_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..f5dbcb648e3a3d0e7816c40cb229c3009bf80953 GIT binary patch literal 15796 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 BM*aW* literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t1_3.atl b/applications/PoP/poc/res/title/t1_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..721abaee3582d4287fee6c480c848a0952fdd528 GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t1_4.atl b/applications/PoP/poc/res/title/t1_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..76b82fede907332d0bf79e16cb4b913aa92081e2 GIT binary patch literal 1396 zcmcIiL2kq_2qcp2^x!)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t2_0.atl b/applications/PoP/poc/res/title/t2_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..f65d6e5e4de1f47156bd6e375a058578f0d5dd8a GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t2_1.atl b/applications/PoP/poc/res/title/t2_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..015078175884c10fd31ea640c1e6c4385919bebe GIT binary patch literal 15796 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 K6@D*UrvC@_7F$XH literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t2_2.atl b/applications/PoP/poc/res/title/t2_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..a7e40b973f4f215bacfe8f6627f60d2a30011cd8 GIT binary patch literal 15796 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 CN@X|z literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t2_4.atl b/applications/PoP/poc/res/title/t2_4.atl new file mode 100644 index 0000000000000000000000000000000000000000..76b82fede907332d0bf79e16cb4b913aa92081e2 GIT binary patch literal 1396 zcmcIiL2kq_2qcp2^x!)eI>sOylat2%e&@qG-ZFVP${=~4TY)p1uq_CX=ClKKL6nLfma>adf%HX))`b_`k{2}pWYJDhrx-6cF0NsLf_?pcn^3@g? zxM?s*lO9zfb;i?EkN8jTd68Xsbr-~XXemIJr~^>&Iu@J|;YL3f4`=Kj5u<51G7UL| zJQ>1MFIozYe?Gb3AjLk_OB^a^B;xvbMbt*;6DuS68{yz(#w`8}IJlFQWuxM9nNmTm L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t3_0.atl b/applications/PoP/poc/res/title/t3_0.atl new file mode 100644 index 0000000000000000000000000000000000000000..f65d6e5e4de1f47156bd6e375a058578f0d5dd8a GIT binary patch literal 15796 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t3_1.atl b/applications/PoP/poc/res/title/t3_1.atl new file mode 100644 index 0000000000000000000000000000000000000000..015078175884c10fd31ea640c1e6c4385919bebe GIT binary patch literal 15796 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 K6@D*UrvC@_7F$XH literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t3_2.atl b/applications/PoP/poc/res/title/t3_2.atl new file mode 100644 index 0000000000000000000000000000000000000000..b86535ad049c37481cda1989b30bfee806298f25 GIT binary patch literal 15796 zcmcJVZE_>Iaz#mrR6j}`(`%U>*gJ59H{$*OvG2K=H-RGA(saKzLaIXHGxH!5K%w~G z|Mj1LpHKh);eSs5`}bKL{m1$5)A#Q;g)5iu-`~Ee^Y04(>Oh?E)${3J-zr>BYWPfe zd3k9~Ug+z5JDqOlv;I1qUf$kR$Nz8Fx9h8d>ZtJfdVOOZ7YY$B*O%+n zUC_R2I9y)Um;M|ErL;TK@@W&aH2Ye&U3WG3?d^a}tq9*Y0aWY{^OGtlayq^0pZM(z zL#6og_9jqQLsCougo0N|1J79^NT_)6yVm%)12?aW5Yo-9Iw+7zh93C-6u%7_qkzCy zB7cNm2451F8ORV7Ej0l6t_O5^6~J%MqW`yV#n9MD_P=@6MFxlIU1W4u0KUGc5!w+P z0v$LI>y%Dx<8z_qC@4(W?dz*&HI4mk$aoMyz*MFpfDkO(10Y!f_-ba1-t`;a zXLx=LI4sU>aBbJUUe82<#WcaxQw&KU^#PxH59B|@Z;*ZeFhDLJ#gDP`RHBQ=@gF>I z(@LSCqBW7BFs+?lZGzG_E{sjeU)>Y%9@)XrF8ex@V+{v}DD%_koHZ~GAcWh{tEe8^ zJpUxW9U0&Tru`QYemSgR9ezxAaxke1ORj2F0B>!FdIBH+xa$N#bv$>&(?cyUO%U@t z%Y!{PFj#aOo9t&Rv8`Axv;_TS9jik5?;tCD`~3I*e!G2qeEy#@3_J17{^UYntEq|G z%UK2}JINw428cYcs)=v-t%x$sEtkt6wDqZNwcvHZL*pLIP8UxY$_84}6x{?Z$)ic` zpK1R7t_C0Hzq|F9miqs+#glfp|D0b5tpyiVa9(LA4@05T?FGwp^mHm*wU&}ksI*{v zWgS=Gxu-x4h#MF*SRVrvSk-6Mt3n-L_BUD{H2Pd)HDZ1X*$noZdei-cK4Ig2YNn6;YWRsT#7=63gl{3fbM^UAes_&;!G*E1RhSkU%&&BrlS7ZAqZ0{%ev%>ku50Ns*Qx*A87OBeI zrWFX0+JK04xJYNK0o1}rZhTpO?oKL41uvfcXzvCxx=~zJWX|1#o*SRMcLk-X0%fWK zWhz+Es$6QR(GTHL(J0Ee@Tjm94HWo6V}jS;T%PgMXi!*9x=nW#emoSv^SSmP7X(#e z^K-0ZC#m?A)u4~FY25l*SBeQVSUlmOy3LK+Ri!H$j?3x76iaLFg}T*Pb)hETU*2Ez zcNV`%YX<4I6>VKn_V%1#X!-7U&D!oM%KL9nxBa= zHD+!vNxnZTY72~!c4>7yjmkI1(JEUhakMe2>|X|38*$y9@I#BpHW8_e{dG4vM1}W z9ap1qHyoQR^c-K_l5Gwxz&)ZA1K=pUgioyzUq}TNhy+G)3aj(M>9OdxR{>R>{#MK@ ziqcm(9x^a$6K_sE-(@8%BXZ=5AiMT22H5NGVKZa+R}sVl8|sD82h18j9TS z{~pf&velWr&)%JOhTZ&mCcuT~{IU4$Ho#^gQZc)?#OzDFA|(N|24Z}wMa8Yjswb_O z1)}yeth&ap33GB^wGW&EQ-y9^otZbO+VOT`r>Ml;j#uhEzJF|oS%>*==1%R8BDlAP z-OEZv?PxQx&G0K)+S~@|I7a63x3hMq=*nv3xy-aS!bT+)QnS>^AlTO z63ma?&ft4_*Pcz)P;?q>r%J8diy!4S>(ndZWdY0}v(SlFHI({e4<4*8>M?x6Z=JvF zhgo0PU#z)$44 z=K?bzoSo{lpnFVnpei0)Q4(W}!%w!#O3)$Q{}R7_{#%Dx_TOxEX74jQJ#CN)nVr+I z<(Lsm>dsH+PCB|vP88<6PVZ)8R>e<=C@dQ+fUYH2vc@#vA~BpK;`mcH$j)`DP(3bw z?ZE{w4QR)k=)7E=>ySbA(VOBY>n}6Q{{1)Ovfa~elInR+4bzbkA8b%s0{J!>H|okd zPHljlAON&q_M++2vtS#KZaVf$gC`CQ^`+VECy{yzN|T{(eRm>dE8DSXLWd+MbD}G@ zO{!Dje*Xu)&(Htjn`<|8lpKp(VniIAums~M?y9rTJXfmb!vfgl(9?lz3@Hp;TuC~W zsi^At#I+tfu}e``HK%BeLYl}F><)7NmsaDy+Qhz(H~B172c6jz+x~WBj^}b%f6H0D zc25`f-z-!~RqfMXGmJ^ybr8JQWj@x%8#9hq9$tMxP-fUoMf-umXkCK$SSa`+1zw+e zKem?HD ztB^^I`52Rrmc{Q-0R|#apxQ&No=Pi3Q=%0=UuMt2ObSJR`Ix>od~RbmcO4!O((1;q zTbJy9;g>~bQ_T8N)%tok%rd`hb?W?2OY7rqsD*EP;q-WHVDxRcvXlg!WP8R>GvGKdx+^S0E8X25dDCDMVPrHf zOn>S{o$%#tnd(+lUsSXUIO#iA*YK#Q!!$tW4wIh}$?*Mt|MzhI*ZZRN?ek$bR2^h! zHE3%Pv#Z5MBA8FFmikFHl7j)7oqwaTRA`72eNY98)|r>JD;GA5yv#4I-bLActGYZ` zCc60fcq-QBZ6EHs`o{ho=F_mmfS;UJ@4x=ZB$MI1%-Mf?U(_!DY&EJLhL4;eec}Wq zGd|lfX;l{3&nbXc-=Ru%=%~ix{Kl093_p8 zaU$C`-$``1teobq6c*BzQS?H9`{{kNzo24&XnGe8#?rf{(y`hDJRWR;*+Ctq!10~3 zzt$~I{M`O_WWQm5?jx45iONq97QfB@-_$F|HNr*TE7lCs2sIJ$o0j)Ac<0xuJ$Ylj=Qm4+@U6wf z{>!F_EnRJS@cr>rCfM#nMIsI&=Jzkl{$>yM2|u)VC@p1iocVRTw!iSZorNnIeu3gl zmin0%kU@1*rdv!dcE04@Jb3EQM<*4XMR?Bt%!GaJe=Vnrl;>J;$M|1miXKwBwbB0gq^2Vv`g%| zIgYL?-0%NPcK`if_J8yHzd2ixd?mx#$6_M_iJ$Rf=df{^oizH6%H8rjD*``vqQm?n zKY8%d=paX$$$ds+?@7Y0eNS|XXZfd(NzD0QpxFL)WRB-@SbrbS|4JsYs<*+T+y(HN z{dtw#`DGZ?QV6B#zN4%{a^=M$IsWUQ~ijNJAA8PEPZm@q4!8J?vjX z%XhE8xjf^yo&U8=`B}~9{FWce{5TVNZVAz^HS?`ig=iW8a?9kP##uQ_tFb^F`>(9@h_x1OH>;Ur%eyG6t zM8DeIcYNHxto0XuK9mbj+KKp`R&R06NY*N3UNW|+!wRDC#tR@vJC(uu%Wuo+EwW*r zJudanLA_#KcPocSw$d7C-Rf04Dsinubk=unHt=)?qtTG0zD(LsO`9Aby-uAg8y zq&*eCypf{)xA+N~yMmtI(&WEs+*3gKO`-=n-A`(Yed)gkWRK^6m91Z@m(4!g4b|2# z`V2DMVjf}@elgXek#7~XptjUvmnIiPh^Zv2KXlLWsB4Nz~^^dtZ$&u z?E|UL`917-@wh;FSbu-({15Gi$H6b2@smQEN``>W1evBjZL+`NAk|1=wt^=x9bMPg sOQVLimkuwzZGSqOa&xb*w!T58CfBr=0pIzJ^jno(H*30I?)HEG2OYQLumAu6 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/t3_3.atl b/applications/PoP/poc/res/title/t3_3.atl new file mode 100644 index 0000000000000000000000000000000000000000..f7b5ca053f177a87f8210393a26246f7ecac0d4c GIT binary patch literal 15796 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 L$W8yDUuyOP1;-_9 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/title/title.pal b/applications/PoP/poc/res/title/title.pal new file mode 100644 index 0000000000000000000000000000000000000000..2cc3a604598d647381849f1254f0664cf776111b GIT binary patch literal 64 zcmWN{(Eva(2td(`5JCt;7{U-j2qA +## CUTSCENE-SKIP-HELD-KEY. Катсцены схлопывались в один кадр (и финал — в чёрный экран) — **ЗАКРЫТ 2026-08-25** + +**Наблюдение (пользователь, 2026-08-24/25).** Сначала: после 14-го уровня +вместо финала чёрный экран. Затем, после первого фикса: во ВСЕХ сценах +(8, 9, 12 и финал) показывается один кадр — и сразу следующий уровень. + +**Корень.** Все сцены проверяют «не просят ли пропустить» через +`kbd_raw_any_down()` на КАЖДОЙ итерации, начиная с первой. А клавиша, +которой закончили уровень, в этот момент ещё зажата: в финальную комнату Кид +ВБЕГАЕТ (стрелка удерживается), дверь уровня проходят на ходу, чит Shift+L +тоже удерживается. Удержание засчитывалось как запрос skip. + +Первый фикс (ждать отпускания перед сценой) проблему только сдвинул: ждать +безусловно нельзя — потерянный break-код вешает игру намертво +([KBD-STUCK-WAIT](BUGS_OPEN.md#kbd-stuck-wait)), — а с таймаутом в секунду +сцена всё равно стартовала при зажатой клавише и гасла на первом же кадре. + +**Правка.** Детектор ФРОНТА (`intro_skip_begin`/`intro_skip_requested`): +пока со старта сцены хоть что-то зажато, skip не взводится; как только всё +отпущено — следующее нажатие сцену прерывает. Применён во всех четырёх +местах: `intro_run`, `pre_princess_animated`, `intro_pv_animated`, `cut_run`. +Ожидание отпускания убрано за ненадобностью. Залипший бит в худшем случае +делает сцену непропускаемой — она доиграет сама, игра не встанет. + +**Проверка.** MAME: переход 7 -> 8 читом (клавиша удерживается) — сцена 8 +отыгрывает целиком, видно сидящую принцессу и мышь; финал (уровень 14, +комната 5) — принцесса поворачивается и обнимает Кида, затем мышь, затем +текст HAIL. + +--- + + +## GATE-SAFE-STEP-SHORT. Перед ОТКРЫТОЙ решёткой осторожный шаг вдвое короче — **ЗАКРЫТ 2026-08-24** + +**Наблюдение (пользователь, 2026-08-24).** Идя слева направо к поднятой +решётке, Кид делает лишний мелкий шаг: чтобы подойти к краю, Shift+вперёд +надо нажать дважды. + +**Корень.** Длину осторожного шага выбирает сам `safe_step` (seg005:0604) по +числу из `get_edge_distance`: последовательности 29..42 — это «шаг ровно на +distance пикселей». А `dist_from_wall_forward` у оригинала ПЕРВОЙ строкой +отвечает −1, если тайл — ворота, сквозь которые можно пройти (seg004:0A18: +`if (tiletype == tiles_4_gate && !can_bump_into_gate()) return -1`); минус +единица уводит разбор дальше, где открытые ворота опознаются обычным полом и +дают `distance = 11`. В нашем порту этой строки не было: расстояние честно +считалось до створки (у ворот `wall_type = 1`, «стена справа»), край +классифицировался как `EDGE_WALL`, и шаг выходил коротким. + +**Правка.** Ветка возвращена в `dist_from_wall_forward` (pop_map.c), +проходимость берётся у нашего `gate_passable` (это и есть +`!can_bump_into_gate`). + +**Проверка.** Хост-набор `tests-host/t_gate.c` (новый): на одном и том же X +открытая решётка обязана давать то же, что чистый пол, а закрытая — остаться +`EDGE_WALL`. Со снятым фиксом набор падает ровно на симптоме: `d = 9`, +`EDGE_WALL` вместо `11`, `EDGE_FLOOR`. + +--- + + +## GATE-SUPERPOSITION. Кид остаётся в тайле опустившейся решётки — **ЗАКРЫТ 2026-08-24** + +**Наблюдение (пользователь, 2026-08-24).** Кид встал в тайл решётки, пока та +была поднята; решётка опустилась — Кид остался внутри. Дальше он «в +суперпозиции»: лицом влево рисуется ЗА решёткой, лицом вправо — ПЕРЕД ней, и +уйти может в обе стороны. То же самое — при попытке проползти под +опускающейся решёткой в присяде. + +**Корень.** В оригинале выталкиванием занимается ОТДЕЛЬНАЯ функция кадровой +цепочки — `check_gate_push` (seg004:0833, зовётся из play_kid_frame, +seg000:1241, между `check_bumped` и `check_action`). В нашем `kid_phys` её +не было вовсе: `check_bumped` ловит только движение В препятствие, а +«препятствие появилось вокруг стоящего» — случай именно `check_gate_push`. + +**Правка.** Порт добавлен в pop_map.c и вставлен в `kid_phys` на своё место. +Условия оригинала сохранены: срабатывает только в позах «стоит на месте» — +разворот (action 7), стойка (кадр 15), присед (кадры 108..110); решётка +ищется в своём тайле И в тайле слева (створка нарисована у правой грани); +перекрытие обязано быть обеими гранями и в этом кадре, и в прошлом; толчок — +5 пикселей влево (решётка своя) либо вправо (решётка левого соседа) + +`bumped_sound`. + +--- + + +## GUARD-DROPPEDOUT-CARRY. Страж идёт в челюсти: `droppedout` пережил границу уровня — **ЗАКРЫТ 2026-08-24** + +**Наблюдение (пользователь, 2026-08-24).** Уровень 4, комната 22: страж без +всякого повода идёт навстречу Киду и гибнет в челюстях; если Кид спустился +рядом ниже и челюсти остановились — страж проходит сквозь них и сваливается к +Киду. Воспроизводилось несколько раз, но ТОЛЬКО когда на 4-й уровень +приходили ногами, через дверь 3-го; при отладочном старте прямо на 4-м +(`make LEVEL=4 ROOM=22`) сцена вела себя правильно — страж стоял, боя не было. + +**Корень.** Единственная ветка, которая двигает стража ВПЕРЁД при +`can_guard_see_kid == 0`, — `guard_follows_kid_down()` (seg002:0806), и гейтит +её флаг `droppedout`: «Кида спихнули из боя вниз — идти ли следом». Флаг +взводит `start_fall` (seg006:1123, у нас pop_map.c), когда персонаж срывается +с уступа В БОЕВОЙ ПОЗЕ (кадры 150..179) — то есть обычная концовка боя со +скелетом на 3-м уровне. В оригинале его гасит `set_start_pos()` +(seg003:028A) при старте КАЖДОГО уровня; в нашем порту (pop_start_level, +roomtest_cold.c) этого сброса не было, и флаг переезжал на следующий уровень. + +Дальше всё сходится: `can_guard_see_kid` считался ПРАВИЛЬНО (в живой сцене +прочитано `1` — чомпер в луче даёт «вижу, но не пойду»), но активная ветка +при 0 уходила не в «попятиться» (`move_down_back`), а в «идти за упавшим +Кидом», а она смотрит только на пол впереди и внизу — чомпер ей не помеха. + +**Правка.** В порт `set_start_pos` возвращены сбросы оригинала: +`pop_droppedout = 0`, `offguard = 0`, `knock = 0` (fall_x/fall_y уже обнуляет +`kid_init`). + +**Проверка.** MAME, сборка `make LEVEL=4 ROOM=22 POS=0`: флаг взведён +отладчиком (`pop_droppedout = 1`), затем меню -> RESTART LEVEL -> флаг снова +0, страж стоит. Само старое поведение вживую НЕ переигрывалось: причина +доказана кодом (в оригинале сброс есть, у нас его не было) и тем, что +единственный путь стража вперёд при `can_guard_see_kid == 0` гейтится этим +флагом. + +**Урок.** Меж-уровневые флаги проверять поимённо по `set_start_pos`: +симптом вылезает НЕ там, где флаг взводится, и не воспроизводится отладочным +стартом с нужного уровня — именно потому, что тот стартует с чистым +состоянием. + +--- + + +## PAL-SKEL-GUARD. Скелет (уровень 3) розовый — палитру набора затирал kid.pal — **ЗАКРЫТ 2026-08-24** + +**Наблюдение (пользователь, 2026-08-24).** Уровень 3, комната 1: поднявшийся +скелет нарисован розово-телесным вместо костяного белого. Кид, фон и факелы — +верные. + +**Корень.** Все пиксели спрайтов скелета — ОДИН индекс 3 его набора +(`SKEL/res751..res778.png`, проверено гистограммой), то есть слот `0x93`. +Палитру набора `pop_guard_load()` заливал ОДИН раз, сразу после атласов, а +дальше по маршруту загрузки уровня шёл `pop_pal_level_load(1)` → +`pop_pal_game_load()` → `gfx_pal_fload("KID\\kid.pal")`. `kid.pal` содержит +ВСЕ 256 записей, включая диапазон стража `0x90..0x9F` с запечённым цветом 2 +(`pop_pack_guard.py`, `COLOR = 2`), — и он затирал палитру скелета. Слот +`0x93` цвета 2 = `(FF,79,79)`, ровно тот розовый, что был на экране. Тот же +механизм ждал Джафара на уровне 13 (`tbl_guard_type == 3`, палитра тоже из +файла набора). + +Обычный страж симптома не давал только по случайности: у него цвет +переставляет `pop_guard_set_palette()` при входе в комнату (enter_guard), то +есть ПОСЛЕ file-load. У скелета и визиря `curr_guard_color == 0` (seg002:186) +— пере-установки нет вовсе. + +**Правка.** `0x90..0x9F` — такой же ДИНАМИЧЕСКИЙ диапазон, как тайлсет и Тень, +и обязан доплачиваться после каждого file-load логической палитры. Добавлен +`pop_guard_pal_apply()` (pop_cdraw.c, банк 4) — пара к `pop_bg_pal_apply` / +`pop_shadow_pal_apply`: набор со своим цветом (скелет/Джафар) берёт палитру из +своей таблицы, обычный страж — последний `curr_guard_color` +(`guard_pal_cur`, его пишет `pop_guard_set_palette`). Зовётся из +`pop_pal_game_load()` и `pop_pal_level_load(0)` ДО snapshot, чтобы правильный +цвет попал и в снимок для fade. `pop_guard_load()` больше не грузит палитру +сам, а зовёт ту же функцию. + +**Проверка.** MAME, сборка `make LEVEL=3 ROOM=1 POS=12`, `pop_leveldoor_open` +взведён отладчиком, чтобы скелет поднялся сразу. Скелет белый; цвет пикселей +на скриншоте `(255,255,242)` — ровно запись 3 `pop_skel_pal`. + +--- + ## GRAB-BELOW-ROOM. Зацеп за кромку НИЖНЕГО ряда при пролёте вниз не работал вовсе diff --git a/applications/PoP/roomtest/BUGS_OPEN.md b/applications/PoP/roomtest/BUGS_OPEN.md index 07f0daa..5c95324 100644 --- a/applications/PoP/roomtest/BUGS_OPEN.md +++ b/applications/PoP/roomtest/BUGS_OPEN.md @@ -24,16 +24,158 @@ | [GRAB-KBD-TIMING](#grab-kbd-timing) | зацеп в падении срабатывает менее стабильно, чем в оригинале | **после всех уровней** | открыт: подозрение на клавиатурный модуль, не на физику | | [GUARD-ENTRY-DEATH](#guard-entry-death) | вход в комнату вплотную к стражу = мгновенная смерть (не портирован `bump_into_opponent`) | порт | **фикс есть, ждёт проверки в MAME** | | [BUG-SHADOW-SET](#bug-shadow-set) | Тень на 12-м уровне дерётся спрайтами СТРАЖА: SHADOW.DAT у нас нет вовсе | порт | открыт, разбор готов | +| [PAL-L1-AFTER-INTRO](#pal-l1-after-intro) | вход в игру на уровень 1 после интро — игровая палитра не установлена (чёрный экран) | палитра/fade | открыт: воспроизведение нестабильно, корень не установлен | +| [PAL-DUNGEON-STALE](#pal-dungeon-stale) | переход 3→4 (подземелье→дворец): остаётся палитра подземелья | палитра/fade | открыт: КОРЕНЬ ЯСЕН — fade_in восстанавливает из протухшего снимка | +| [CUTSCENE-LTR](#cutscene-ltr) | `Marry Jaffar` → title → credits должны проявляться полосами слева направо | заставка | открыт: зафиксировано 2026-08-24, взять после текущего цикла анимации PV | | [BUG-CORPSE-AIR](#bug-corpse-air) | труп Кида висит в воздухе, когда убившая его плита улетела вниз | физика | открыт | | [LOOSE-ROOM-CHANGE](#loose-room-change) | расшатанная плита не доваливается, если Кид ушёл в соседнюю комнату | порт | закрыт 2026-08-13, кроме [LOOSE-SEAM-ANIM](#loose-seam-anim) | | [JUMP-FLOOR-LEGS](#jump-floor-legs) | в прыжке с пола ноги Кида рисуются ПОВЕРХ кромки пола, а должны за ней | окклюзия | **корень найден и починен, ждёт проверки в MAME** | | [MOB-CLIP-RIGHT](#mob-clip-right) | окна и узор кладки рисуются ПОВЕРХ падающих плит | окклюзия | открыт: корень найден 2026-08-13 | | [MID-OVERLAY-LAYER](#mid-overlay-layer) | оверлей кромки всегда поверх плит и персонажей (у оригинала — midtable с сортировкой) | слои | открыт: нужен разбор архитектуры | | [GUARD-FALLOUT-VICTORY](#guard-fallout-victory) | страж, выпавший из комнаты, не засчитывается убитым | порт | открыт: две строки | +| [KBD-STUCK-WAIT](#kbd-stuck-wait) | потерянный break-код клавиши вешает игру НАСМЕРТЬ в межуровневой заставке | клавиатура/клин | открыт: диагноз полный, чинить не сейчас (решение пользователя 2026-08-24) | +| [L10-BUTTON-DEBRIS](#l10-button-debris) | ур.10 к.1: плита упала на кнопку (1,8) — щебень есть, а решётки закрыты | кнопки/ворота | открыт: **не воспроизводится**, проверено многое (см. разбор) | | [LOOSE-SEAM-ANIM](#loose-seam-anim) | плита в шве (col −1), уроненная в прошлой комнате, исчезает без анимации падения | **мелкий** | открыт по решению пользователя: «пока пусть будет так» | --- + +## L10-BUTTON-DEBRIS. Плита, упавшая на кнопку (ур.10, к.1) — щебень есть, решётки закрыты + +**Наблюдение (пользователь, 2026-08-24).** Уровень 10, комната 1: Кид стоит +на кнопке `(1,8)` и роняет на неё плиту `(0,8)`. Кнопка превращается в +щебень, но решётки, которые она открывает, остаются закрытыми. Важная деталь +из того же прогона: на 10-й уровень пришли **телепортом Shift+L**, а не +проходом. + +**Ожидаемое поведение (оно же оригинал).** Плита, севшая на opener, жмёт его +ОДИН раз с `button_type = tiles_14_debris`; у `trigger_gate` это отдельная +ветка «открыть насовсем»: `modifier >= 188` -> сразу `0xFF`, иначе возврат 2 и +`animate_door` доводит створку до `0xFF`. Ворота с `0xFF` заморожены +открытыми, обычный opener их больше не трогает. Сама кнопка съедается в +щебень. Разобрано в [BUG-LOOSE-BUTTON-1](BUGS_CLOSED.md#bug-loose-button-1). + +**НЕ ВОСПРОИЗВЕЛОСЬ** (2026-08-24, отладочный старт `make LEVEL=10 ROOM=1 +POS=18`, MAME): Кид стоит на кнопке -> решётка `(2,7)` открылась; удар головой +роняет плиту `(0,8)`; кнопка стала щебнем; **решётка осталась открытой** и +после ухода Кида с тайла. Пользователь тоже не смог повторить в этой сборке. + +**Что проверено и совпадает с оригиналом:** + +* цепочка кнопки — ровно две цели, обе в комнате 1: `(2,9)` и `(2,7)` + (`bg = 20`, терминатор цепочки — бит 0x80 в LINKLOC, как в seg007:0C09); +* `trigger_gate` построчно равен оригиналу, включая обе ветки debris; +* `add_trob` дедуплицирует по (room, tilepos) — как `find_trob` оригинала, + двойных записей на одни ворота не бывает; +* `animate_door` гасит анимацию при `modifier == 0xFF` и доводит тип 2 до + `0xFF`; +* таймеры связей в файле уровня 10 нулевые (не «jammed» 0x1F); +* `pop_dl1/pop_dl2` перечитываются из файла при каждой загрузке уровня, а + `pop_level_reset_tiles` восстанавливает LINKMAP из эталонной страницы. + +**Живые гипотезы (по убыванию правдоподобия):** + +1. **Ветка `return 2` (створка ещё не «полностью» открыта).** Визуально + решётка выглядит открытой уже при `modifier ~ 140` (проходимость считается + как `(modifier >> 2) + 6 >= высота кадра`), а мгновенный `0xFF` требует + `modifier >= 188`. То есть глазами «открыто», а по коду — ветка с + анимацией; её в MAME поймать пока не удалось. +2. **Накопленное состояние после телепорта Shift+L** через несколько уровней + (у нас уже был такой класс: [GUARD-DROPPEDOUT-CARRY](BUGS_CLOSED.md#guard-droppedout-carry)). + Отладочный старт сразу на 10-м даёт чистое состояние — и баг не всплывает. +3. Кнопка была нажата/отпущена ранее в этой же сессии, и падение пришлось на + момент, когда trob кнопки уже умер, а таймер связи ещё не истёк. + +**Как ловить в следующий раз:** как только симптом повторится — сразу +**QuickSave (F6)**, не выходя из сцены. Сохранение везёт с собой LINKMAP +(`pop_level_qsave` пишет `pop_dl1/pop_dl2`) и модификаторы комнат, то есть даёт +ровно то состояние, которое сейчас не удаётся собрать искусственно. + +--- + + +## KBD-STUCK-WAIT. Потерянный break-код вешает игру насмерть в межуровневой заставке + +> **Решение пользователя (2026-08-24): не чинить сейчас.** Записан разбор и +> варианты; вернуться, когда дойдут руки до клавиатурного слоя. + +**Наблюдение (пользователь, 2026-08-24).** Нажат Shift+L (чит «следующий +уровень») — игра встала намертво: картинка прежнего уровня, реакции нет. + +**Диагноз (снят в живой сессии MAME).** Программа НЕ рухнула и не потеряла +кадровые прерывания — IRQ приходили штатно (вход по вектору виден в +`history`). Стояли в `pop_pre_cutscene_show` (pop_intro.c:949), в его первом +цикле: + +```c +while (kbd_raw_any_down()) { kbd_raw_sync(); gfx_wait_vsync(); } +``` + +Цикл намеренный: клавиша, которой закончили уровень (тот самый Shift+L), не +должна тут же сойти за «пропустить заставку». Внутри — `gfx_wait_vsync` +(опрос бита 5 порта #FE), он отрабатывал нормально; не выходил ВНЕШНИЙ цикл. + +Причина — залипший бит в `_kbdraw_down[]`: код `0x72` в PLAIN-половине карты +(то есть **keypad 2**; стрелка Down — это `E0 72` и живёт в EXT-половине, +байт 46). Состояние на момент разбора: `_kbdraw_active = 1`, +`_kbdraw_pending = 0`, `_kbdraw_overrun = 0` — то есть штатная страховка по +Rx-overrun не срабатывала. Гашение бита отладчиком (`_kbdraw_down[14] = 0`) +сразу вернуло игру к жизни: заставка прошла, уровень 2 загрузился. + +**Чем бит может залипнуть** (разбор `libc/irq/_irq_tramp.c`): + +1. **Потерянный break при переполнении FIFO SIO.** Приёмник глубиной 3 + байта; когда прерывания запрещены надолго (загрузка уровня через ESTEX — + ровно момент Shift+L), пачка `F0 code` не помещается. Защита есть + (чтение RR1 → `_kbdraw_overrun` → `kbd_raw_sync()` снимает held), но она + срабатывает, только если SIO успел выставить сам бит overrun. +2. **Разъехавшийся префиксный автомат.** `F0` (break) и `E0` (ext) — отдельные + байты, состояние копится в `_kbdraw_pending`; любой сброс между префиксом + и кодом переворачивает смысл, и break декодируется как MAKE. Ветка + «fake shift» (`E0 12` / `E0 59`) делает это по построению — она гасит + `pending` и пишет код как make даже у break-формы. +3. **Make и break у разных владельцев канала.** Пока `_kbdraw_active == 0`, + байты уходят DSS. Клавиша, нажатая до `kbd_raw_open()` (или в момент + close/open), даёт make одному владельцу, а break — другому: снимать + нечего, бит остаётся. +4. **Держащий вход эмулятора.** `set_input ... frames=0` из MCP-моста держит + клавишу нажатой, typematic шлёт make повторно (наблюдалось: погашенный + отладчиком бит возвращался). Это артефакт отладки, а не игры, но именно + он мог создать исходное состояние в этой сессии. + +**Варианты решения.** + +| Вариант | За | Против | +|---------|----|--------| +| **A. Таймаут в циклах ожидания** (ждать отпускания не дольше ~1 с, дальше идти) | Дёшево, локально; снимает КЛАСС отказа «вечный клин» независимо от причины; заставка от лишней секунды не страдает | Лечит симптом, а не причину: залипшая клавиша дальше продолжит «нажиматься» в gameplay | +| **B. Анти-залипание в `kbd_raw`** (гасить биты, которые давно не подтверждались typematic-повтором) | Чинит класс причин целиком, помогает и в gameplay | **Модификаторы typematic НЕ повторяют** (обжигались: `kbd_overrun_wipe_modifiers`) — значит Shift/Ctrl гасить нельзя, нужен список исключений; плюс хранение «свежести» на 512 кодов дорого, придётся огрублять | +| **C. Ужесточить страховку по overrun** (чистить held не только по биту RR1, но и по «подозрительным» ситуациям: длинный DI, возврат из загрузки) | Бьёт по самой вероятной причине (1); дёшево по памяти | Эвристика: можно снять РЕАЛЬНО зажатую клавишу (Кид перестанет бежать посреди разбега) | +| **D. Ничего, считать артефактом отладки** | Ноль работы | Если это не мост, а SIO, то на железе получаем намертво зависшую игру — худший класс отказа | + +**Если возвращаться — начинать с точной причины:** лог байтов SIO в +трамплине (кольцо на 32-64 байта + дамп по клавише) и повтор сценария +«Shift+L во время дисковой загрузки». Без этого выбор между B и C — гадание. + +**Смежное:** `kbd_raw_fifo_drain`, `kbd_overrun_wipe_modifiers` в memory — +там уже закрытые случаи залипания того же битмапа. + +--- + + +## CUTSCENE-LTR. Переходы story через экран TITLE должны проявляться слева направо + +**Наблюдение пользователя (2026-08-24).** В SDLPoP переходы `Marry Jaffar` → +экран с надписью *Prince of Persia* → credits выполнены тем же приёмом, что и +`In the absence…`: следующая композиция появляется вертикальными полосами +слева направо. Сейчас это последовательность статичных сцен без этого +перехода. + +**Очередь.** Не прерывать ради него текущую работу над палитрой и анимацией +Princess/Jaffar. Когда будет взят, выделить общий helper полосового перехода +и проверить все три story-маршрута, включая пропуск клавишей. + +--- + ## GRAB-KBD-TIMING. Зацеп в падении срабатывает НЕ ТАК СТАБИЛЬНО, как в оригинале — РАЗБОР ПОСЛЕ ВСЕХ УРОВНЕЙ @@ -920,3 +1062,90 @@ Makefile правила нет. CBL, и копать надо в системных буферах DSS. **Приоритет низкий:** один раз за сеанс, на играбельность не влияет. + +--- + + +## PAL-L1-AFTER-INTRO. Вход в игру на уровень 1 после интро — палитры нет (чёрный экран) + +**Наблюдение (пользователь, 2026-08-23).** После заставок (title → intro → +demo) нажатие клавиши в attract-demo начинает новую игру на уровне 1 — +экран ЧЁРНЫЙ при живом геймплее. При этом: + +- Shift+L с уровня 1 на уровень 2 (тот же маршрут через pre-cutscene) — + палитра появляется (закрыто быстрым фиксом `fade_in_pending` в + `roomtest.c`, маршрут CUTSCENE → LEVEL_LOAD → PLAYING); +- Restart Game через меню — уровень 1 с НОРМАЛЬНОЙ палитрой. + +**Контекст.** Маршрут demo_new_game (`roomtest.c` ~996): dispatch(NEW_GAME) +→ `pop_level_switch()` → dispatch(LEVEL_READY) → PLAYING. По коду палитру +там никто не трогает, а демо-ветка перед этим делает +snapshot+dim(4,0)+fade_in(4) — то есть к моменту новой игры палитра должна +быть яркой. Контрольный прогон в MAME (сборка с фиксом) демо и уровень 1 +за настройками показал ЯРКУЮ картинку — воспроизведение нестабильно. + +**Гипотезы для проверки (порядок дешевизны):** + +1. **Тайминг нажатия.** Клавиша, СОСКИПНУВШАЯ intro (intro_run абортится по + `kbd_raw_any_down`), остаётся в held-state и первым же кадром демо даёт + `demo_new_game` — но fade_in демо-ветки блокирующий и завершается ДО + входа в игровой цикл, так что в эту гипотезу код не верит. Проверить + трассой: снять обе экранные палитры (`gfx_pal_get` 0 и 1) на первом кадре + PLAYING. +2. **Снимок демо-ветки снят с чёрной палитры.** Если нажатие клавиши + приходится МЕЖДУ `intro_load_palette` (fload+sync+snapshot+dim(4,0)) и + `intro_restore_game_palette` — аборт оставляет экран чёрным, но restore + всё равно выполняется после... сверить фактический порядок вызовов + трассой. +3. **`font_ready`=0 в момент fade_in** — dim/fade молча выходят (guard в + `pop_ui.c`). Маловероятно (шрифт грузится в `pop_boot` → + `pop_menu_init`), но дёшево исключить. + +**Правильное решение, скорее всего, — этап B из +[`../docs/palette_plan.md`](../docs/palette_plan.md):** явный +«load → apply/fade» контракт на КАЖДОМ входе в PLAYING, а не точечные +fade_in в отдельных маршрутах. Точечно чинить только после стабильного +воспроизведения. + +--- + + +## PAL-DUNGEON-STALE. Переход 3→4 (подземелье→дворец): остаётся палитра подземелья + +**Наблюдение (пользователь, 2026-08-23).** Shift+L с уровня 3 на уровень 4: +pre-cutscene отыгрывает, уровень 4 начинается — но env/wall-цвета (слоты +0x50–0x6F) остаются ПОДЗЕМНЫМИ. На переходе 1→2 (оба подземелье) всё +правильно. + +**Корень ясен.** Быстрый фикс `fade_in_pending` (`roomtest.c`, маршрут +CUTSCENE → LEVEL_LOAD → PLAYING) восстанавливает яркость из СНИМКА, а снимок +делает `intro_restore_game_palette()` ДО `pop_level_switch()`: + +``` +pop_pre_cutscene_show(pre) → kid.pal + bg(СТАРЫЙ набор) + snapshot + dim(4,0) +pop_level_switch() → pop_bg_load(дворец): в палитры записаны + 32 записи ДВОРЦА (pal_tile.pal) +pop_ui_fade_in(4) → восстанавливает ОБЕ палитры ИЗ СНИМКА — + то есть ЗАЛИВАЕТ НАЗАД подземные 0x50..0x6F +``` + +fade_in по построению пишет все 256 записей из снимка — он затирает и только +что загруженный тайлсет. На 1→2 не видно, потому что наборы совпадают. + +**Как чинить (в порядке правильности):** + +1. **По плану (этап B `../docs/palette_plan.md`):** разделить «логическую + палитру» и «яркость» (pop_pal: load_* обновляет снимок ПОСЛЕ bg_load, + apply/fade только показывает). Тогда снимок на момент fade_in всегда + актуален. +2. Точечно: переставить обновление снимка ПОСЛЕ `pop_level_switch()` + (снять snapshot заново перед fade_in). Работает, но это ещё один пластырь + поверх модели, которая и так уже в трёх местах по-разному управляет + яркостью. + +**Проверять:** Shift+L 3→4 И 5→7 (дворец→подземелье, обратное направление), +плюс штатный проход уровня 3 через дверь (там же смена набора); после починки +сверить цвета кладки дворца с `pal_tile.pal` (таблица в palette_plan.md §1.3). + +**Решение пользователя (2026-08-23): пока не фиксить** — закрыть вместе с +этапом B плана палитр. diff --git a/applications/PoP/roomtest/CLAUDE.md b/applications/PoP/roomtest/CLAUDE.md index ceae1d2..fc00c5e 100644 --- a/applications/PoP/roomtest/CLAUDE.md +++ b/applications/PoP/roomtest/CLAUDE.md @@ -56,8 +56,7 @@ Kid — `kid_data.h` (генерится `pop_extract_kid_data.py`). старт `run_bridge.sh` → 6 с → `go` → 8 с → `keyseq d:{ENTER}` + `keyseq roomtest{ENTER}` → 5 с → программа работает. -Отладочные тумблеры в живой сессии (`roomtest.c`): **SPACE** — вкл/выкл -дабл-буфер (в однобуфере видно баги рисования без мерцания-через-кадр); +Отладочные тумблеры в живой сессии (`roomtest.c`): **1** — заморозить кадр, **2** — продолжить (разбор позы/окклюзии); **ESC** — выход. Читы (`pop_cheat.h`): **K** — убить стража, **I** — бессмертие, **Shift+L** — следующий уровень, diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index b4e5c03..81253cc 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -35,6 +35,11 @@ MEMORY ?= huge # small-раскладка + банки кода в W # ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том # же ALLOCS — иначе сравниваются не правки, а уровни оптимизации. ALLOCS ?= 3000 +# Build info не хранится вручную в C: ID и дата извлекаются Make из текущего +# commit. Переопределения удобны для воспроизводимой release-сборки. +BUILD_ID ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo dev) +BUILD_DATE ?= $(shell git log -1 --format=%cs 2>/dev/null || echo unknown) +BUILD_FLAGS := -DPOP_BUILD_ID=\"$(BUILD_ID)\" -DPOP_BUILD_DATE=\"$(BUILD_DATE)\" # Единственный список банковых исходников: из него строятся И ключи # sprinter-cc, И зависимости roomtest.exe. Раньше эти списки жили отдельно, # поэтому добавленные pop_shadow.c/pop_sfx_cold.c компилировались, но make не @@ -49,8 +54,20 @@ BANK7_SRCS := pop_room.c pop_redraw.c BANK8_SRCS := roomtest_cold.c pop_level_cold.c pop_kboot.c pop_guard_cold.c \ pop_shadow.c pop_sfx_cold.c pop_qsave.c pop_qsave_coldio.c \ pop_level_qsave.c +# Банк 9 — оболочка: menu/settings/config/font, автомат, global timer, +# title, attract-demo и таблица рекордов. Здесь остаются небольшие редкие +# верхнеуровневые точки входа, но не рендеры крупных заставок. +BANK9_SRCS := pop_menu.c pop_status.c pop_config.c pop_app.c pop_timer.c pop_ui.c pop_title.c pop_demo.c \ + pop_flow.c pop_hof.c +# Банк 10 — единый владелец экранных палитр/fade. Он намеренно отделён от UI: +# его будут вызывать и игровой lifecycle, и все cutscene-ветки. +BANK10_SRCS := pop_pal.c +# Банк 11 — тяжёлая кинематика заставок: интерпретатор сценария и renderer +# story/PV. Так анимации не вытесняют menu/config из банка оболочки. +BANK11_SRCS := pop_cutscene.c pop_intro.c BANKED_SRCS := $(BANK1_SRCS) $(BANK2_SRCS) $(BANK3_SRCS) $(BANK4_SRCS) \ - $(BANK5_SRCS) $(BANK6_SRCS) $(BANK7_SRCS) $(BANK8_SRCS) + $(BANK5_SRCS) $(BANK6_SRCS) $(BANK7_SRCS) $(BANK8_SRCS) \ + $(BANK9_SRCS) $(BANK10_SRCS) $(BANK11_SRCS) BANK_FLAGS := $(foreach f,$(BANK1_SRCS),--bank 1=$(f)) \ $(foreach f,$(BANK2_SRCS),--bank 2=$(f)) \ $(foreach f,$(BANK3_SRCS),--bank 3=$(f)) \ @@ -58,9 +75,12 @@ BANK_FLAGS := $(foreach f,$(BANK1_SRCS),--bank 1=$(f)) \ $(foreach f,$(BANK5_SRCS),--bank 5=$(f)) \ $(foreach f,$(BANK6_SRCS),--bank 6=$(f)) \ $(foreach f,$(BANK7_SRCS),--bank 7=$(f)) \ - $(foreach f,$(BANK8_SRCS),--bank 8=$(f)) + $(foreach f,$(BANK8_SRCS),--bank 8=$(f)) \ + $(foreach f,$(BANK9_SRCS),--bank 9=$(f)) \ + $(foreach f,$(BANK10_SRCS),--bank 10=$(f)) \ + $(foreach f,$(BANK11_SRCS),--bank 11=$(f)) EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) \ - $(BANK_FLAGS) $(PROF_FLAGS) + $(BANK_FLAGS) $(PROF_FLAGS) $(BUILD_FLAGS) # Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по # умолчанию, пока идёт работа с производительностью; make PROF=0 выключает. # Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте @@ -70,29 +90,29 @@ PROF_FLAGS := -DPROF_BORDER=$(PROF) # Стартовый уровень (отладка): make LEVEL=9 — начать сразу с девятого. # Дефолт держим на отлаживаемом сейчас уровне; для «настоящей» игры — LEVEL=1. # Константа объявлена в pop_tune.h (её видят обе половины главного цикла). -LEVEL ?= 11 +LEVEL ?= 1 PROF_FLAGS += -DFIRST_LEVEL=$(LEVEL) # Стартовая КОМНАТА и позиция Кида в ней (отладка): make ROOM=15 POS=2 — # начать прямо в целевой комнате оптимизации, минуя проход уровня. POS — # тайл 0..29, то есть row*10+col. Дефолт — сцена 11/15 (Кид в 0,2), по # которой сняты замеры в docs/perf_l11_room15.md. make ROOM= (пусто) # возвращает штатный старт из данных уровня. -ROOM ?= 15 +ROOM ?= POS ?= 2 ifneq ($(strip $(ROOM)),) PROF_FLAGS += -DDBG_START_ROOM=$(ROOM) -DDBG_START_POS=$(POS) endif -EXTRA_SRCS := pop_vflip.c pop_pace.c pop_sfx.c pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c pop_qsave_io.c +EXTRA_SRCS := pop_vflip.c pop_pace.c pop_sfx.c pop_state.c pop_draw.c pop_tile.c \ + pop_kid.c pop_level.c pop_geom.c pop_guard.c pop_qsave_io.c BG_DIR := $(CURDIR)/../poc/res/bg KID_DIR := $(CURDIR)/../poc/res/kid LVL_DIR := $(CURDIR)/../SDLPoP/data/LEVELS -# ВСЕ уровни игры (res2001..res2015, по 2305 Б = 34 КБ на всех): переход -# уровень→уровень и чит Shift+L требуют, чтобы файл следующего лежал на -# диске. res2000 — демо-уровень, в скоуп не входит. -LVL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 +# res2000 — attract-demo; res2001..res2014 — штатная игра. res2015/copy +# protection намеренно не пакуется и не существует в маршруте загрузчика. +LVL_NUMS := 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 LVL_DATA := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin) -# ДВА тайлсета: pop_* — подземелье (уровни 1-3, 7-9, 12, 13, 15), +# ДВА тайлсета: pop_* — подземелье (уровни 1-3, 7-9, 12, 13), # pal_* — дворец (4-6, 10, 11, 14). Раскладка id у них общая, различаются # файлы и палитра тайлсета (*tile.pal, 32 записи). chtab_1 (pop_pot.atl: # пламя, меч-предмет, зелья) от набора не зависит — один на оба. @@ -148,15 +168,50 @@ SHADOW_DATA := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # ЗВУКОВЫЕ ЭФФЕКТЫ — оригинальная оцифровка, приведённая к частоте CBL # (10 937,5 Гц, 8 бит моно). 8 страниц по 16 КБ; см. ../docs/sound_plan.md. SND_DIR := $(CURDIR)/../poc/res/sound -SND_DATA := $(foreach n,0 1 2 3 4 5 6 7,$(SND_DIR)/s$(n).bin) +# Число файлов задаёт упаковщик (POP_SND_PAGES в pop_sound_tbl.h): с +# 2026-08-25 звук не режется по границе страницы, поэтому страниц стало 10. +# Список ОБЯЗАН совпадать: недостающий файл — это молча пропавшие эффекты. +SND_DATA := $(foreach n,0 1 2 3 4 5 6 7 8 9,$(SND_DIR)/s$(n).bin) + +# UI: два исходных шрифта SDLPoP в одном EMM-атласе. Файл короче страницы: +# хвост использует menu для временного snapshot палитры (без расхода W2). +FONT_DIR := $(CURDIR)/../poc/res/font +FONT_ATL := $(FONT_DIR)/font.atl +FONT_DATA := $(FONT_ATL) +# TITLE: четыре готовые композиции `show_title()` до story/intro. Каждая +# режется на пять полос высотой <=49 px, так что runtime держит ровно одну +# EMM-страницу во время рисования, а не отдельный framebuffer. +TITLE_DIR := $(CURDIR)/../poc/res/title +TITLE_SCENES := 0 1 2 3 +TITLE_PARTS := 0 1 2 3 4 +TITLE_DATA := $(TITLE_DIR)/title.pal \ + $(foreach s,$(TITLE_SCENES),$(foreach p,$(TITLE_PARTS),$(TITLE_DIR)/t$(s)_$(p).atl)) +# PV/INTRO: story и fallback-стадии режутся на полосы. Для настоящего PV +# intro есть ещё чистый фон `b0_*` и шесть <=16K actor-атласов: они остаются +# в EMM на время сцены, а не перечитывают полный 320x200 кадр с HDD. +PV_DIR := $(CURDIR)/../poc/res/pv +PV_STORY := 0 1 2 3 4 +PV_SCENES := 0 1 2 3 4 5 6 7 +PV_PARTS := 0 1 2 3 4 +PV_ANIM := $(foreach p,$(PV_PARTS),$(PV_DIR)/b0_$(p).atl) \ + $(PV_DIR)/a0.atl $(PV_DIR)/a1.atl $(PV_DIR)/a2.atl \ + $(PV_DIR)/j0.atl $(PV_DIR)/j1.atl \ + $(PV_DIR)/j2.atl $(PV_DIR)/h0.atl $(PV_DIR)/t0.atl +PV_DATA := $(PV_DIR)/story.pal $(PV_DIR)/pv.pal \ + $(foreach s,$(PV_STORY),$(foreach p,$(PV_PARTS),$(PV_DIR)/s$(s)_$(p).atl)) \ + $(foreach s,$(PV_SCENES),$(foreach p,$(PV_PARTS),$(PV_DIR)/p$(s)_$(p).atl)) \ + $(PV_ANIM) EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \ - $(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(LVL_DATA) + $(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA) # Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS), # чтобы корень диска не зарастал десятками .atl. # BG\ — фон (env/wall/fore/pot) # KID\ — персонаж (kid0..27, палитра, меч, таблицы анимации) # GUARD\ — стражи (появятся здесь же) +# FONT\ — текстовый атлас pause/settings UI +# TITLE\ — нарезанные композиции оригинальной заставки +# PV\ — story и Princess/Jaffar intro, нарезанные полосами # LEVELS\ — уровни HDD_PACK_ARGS := $(EXAMPLE).exe \ $(foreach f,$(BG_DATA),BG:$(f)) \ @@ -166,6 +221,9 @@ HDD_PACK_ARGS := $(EXAMPLE).exe \ $(foreach f,$(VIZIER_DATA),VIZIER:$(f)) \ $(foreach f,$(SHADOW_DATA),SHADOW:$(f)) \ $(foreach f,$(SND_DATA),SND:$(f)) \ + $(foreach f,$(FONT_DATA),FONT:$(f)) \ + $(foreach f,$(TITLE_DATA),TITLE:$(f)) \ + $(foreach f,$(PV_DATA),PV:$(f)) \ $(foreach f,$(LVL_DATA),LEVELS:$(f)) include $(PROJ_ROOT)/app.mk @@ -185,6 +243,9 @@ SHADOW_STAMP := $(RESOURCE_STAMP_DIR)/shadow SOUND_STAMP := $(RESOURCE_STAMP_DIR)/sound KID_STAMP := $(RESOURCE_STAMP_DIR)/kid KID_BIN_STAMP := $(RESOURCE_STAMP_DIR)/kid-bin +FONT_STAMP := $(RESOURCE_STAMP_DIR)/font +TITLE_STAMP := $(RESOURCE_STAMP_DIR)/title +PV_STAMP := $(RESOURCE_STAMP_DIR)/pv $(RESOURCE_STAMP_DIR): mkdir -p $@ @@ -237,14 +298,62 @@ $(KID_BIN_STAMP): $(TC)/pop_extract_kid_data.py | $(RESOURCE_STAMP_DIR) kid_data.h $(KID_BIN): $(KID_BIN_STAMP) @test -f $@ || { $(MAKE) -B $(KID_BIN_STAMP); test -f $@; } +$(FONT_STAMP): $(PROJ_ROOT)/toolchain/pop_extract_font.py \ + $(CURDIR)/../SDLPoP/src/menu.c $(CURDIR)/../SDLPoP/src/seg009.c | $(RESOURCE_STAMP_DIR) + cd $(PROJ_ROOT) && python3 toolchain/pop_extract_font.py + touch $@ +$(FONT_ATL) pop_font.h: $(FONT_STAMP) + @test -f $@ || { $(MAKE) -B $(FONT_STAMP); test -f $@; } + +$(TITLE_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_title.py \ + $(CURDIR)/../SDLPoP/data/TITLE/res51.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res52.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res53.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res54.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR) + cd $(PROJ_ROOT) && python3 toolchain/pop_pack_title.py + touch $@ +$(TITLE_DATA): $(TITLE_STAMP) + @test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; } + +$(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \ + $(CURDIR)/../SDLPoP/data/TITLE/res41.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res42.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res43.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res44.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res45.png \ + $(CURDIR)/../SDLPoP/data/TITLE/res51.png \ + $(CURDIR)/../SDLPoP/data/PV/res801.png \ + $(CURDIR)/../SDLPoP/data/PV/res805.png \ + $(CURDIR)/../SDLPoP/data/PV/res816.png \ + $(CURDIR)/../SDLPoP/data/PV/res851.png \ + $(CURDIR)/../SDLPoP/data/PV/res870.png \ + $(CURDIR)/../SDLPoP/data/PV/res901.png \ + $(CURDIR)/../SDLPoP/data/PV/res920.png \ + $(CURDIR)/../SDLPoP/data/PV/res951.png \ + $(CURDIR)/../SDLPoP/data/PV/res953.png \ + $(CURDIR)/../SDLPoP/data/PV/res959.png \ + $(CURDIR)/../SDLPoP/data/PV/res981.png \ + $(foreach n,802 803 804 806 807 808 809 810 811 812 813 814 815 817,$(CURDIR)/../SDLPoP/data/PV/res$(n).png) \ + $(foreach n,852 853 854 855 856 857 858 859 860 861 862 863 864,$(CURDIR)/../SDLPoP/data/PV/res$(n).png) \ + $(CURDIR)/../SDLPoP/data/PV/res952.png \ + $(foreach n,151 152 153 154 155 156 157 158 159,$(CURDIR)/../SDLPoP/data/PRINCE/res$(n).png) | $(RESOURCE_STAMP_DIR) + cd $(PROJ_ROOT) && python3 toolchain/pop_pack_intro.py + touch $@ +$(PV_DATA): $(PV_STAMP) + @test -f $@ || { $(MAKE) -B $(PV_STAMP); test -f $@; } + # Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу # принудительно пересобирает также libc, libbgi и само приложение. resources-bg: $(BG_DATA) resources-kid: $(KID_DATA) $(KID_BIN) resources-actors: $(GUARD_DATA) $(SKEL_DATA) $(VIZIER_DATA) $(SHADOW_DATA) resources-sound: $(SND_DATA) +resources-font: $(FONT_DATA) +resources-title: $(TITLE_DATA) +resources-pv: $(PV_DATA) resources-levels: $(LVL_DATA) -resources: resources-bg resources-kid resources-actors resources-sound resources-levels +resources: resources-bg resources-kid resources-actors resources-sound resources-font resources-title resources-pv resources-levels resources-rebuild: $(MAKE) -B resources @@ -263,10 +372,10 @@ hdd: resources # широкий: цена лишней полной сборки ниже риска получить stale-бинарник. LOCAL_HEADERS := $(wildcard *.h) $(EXAMPLE).exe: Makefile $(PROJ_ROOT)/app.mk $(BANKED_SRCS) $(LOCAL_HEADERS) \ - $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) + $(BG_DATA) $(KID_DATA) $(KID_BIN) $(FONT_DATA) $(TITLE_DATA) $(PV_DATA) $(LVL_DATA) clean-resources: rm -rf $(RESOURCE_STAMP_DIR) .PHONY: resources resources-rebuild resources-bg resources-kid resources-actors relink \ - resources-sound resources-levels clean-resources + resources-sound resources-font resources-title resources-pv resources-levels clean-resources diff --git a/applications/PoP/roomtest/_pop_config.h b/applications/PoP/roomtest/_pop_config.h new file mode 100644 index 0000000..881f554 --- /dev/null +++ b/applications/PoP/roomtest/_pop_config.h @@ -0,0 +1,39 @@ +/* + * _pop_config.h — внутренний бинарный контракт POP.CFG. + * + * Владелец формата ровно один — pop_config.c (банк 9). Внешний игровой + * код видит только pop_settings.h и публичные pop_cfg_load/save. Формат + * допускает добавление полей в конец payload той же версии: декодер знает + * первые девять байтов, но контрольную сумму считает по ВСЕМУ payload. + */ +#ifndef _POP_CONFIG_H +#define _POP_CONFIG_H + +#include + +#define POP_CFG_VERSION 1 +#define POP_CFG_HEADER_SIZE 7 +/* Ранний MS0-файл имел семь полей. Его продолжаем принимать: отсутствующие + * поля берут исторический дефолт (лимит времени ON, отладочная полоса ON), + * а новая запись содержит все девять. */ +#define POP_CFG_LEGACY_PAYLOAD_SIZE 7 +#define POP_CFG_TIMELIMIT_PAYLOAD_SIZE 8 +#define POP_CFG_PAYLOAD_SIZE 9 +#define POP_CFG_TAIL_SIZE 2 +/* CFG остаётся маленьким. Верхняя граница не даёт ручному мусору занять + * стек W2, но оставляет запас для будущих полей версии 1. */ +#define POP_CFG_MAX_PAYLOAD 64 +#define POP_CFG_SIZE (POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + \ + POP_CFG_TAIL_SIZE) +#define POP_CFG_MIN_SIZE (POP_CFG_HEADER_SIZE + POP_CFG_LEGACY_PAYLOAD_SIZE + \ + POP_CFG_TAIL_SIZE) +#define POP_CFG_MAX_SIZE (POP_CFG_HEADER_SIZE + POP_CFG_MAX_PAYLOAD + \ + POP_CFG_TAIL_SIZE) + +/* Чистое ядро формата: доступно host-тесту без DSS/EMM. encode пишет + * текущий v1 размер, decode принимает прежние семи-/восьмибайтные и более + * длинный v1 payload, игнорируя неизвестный хвост. */ +void pop_cfg_encode(uint8_t *buf); +uint8_t pop_cfg_decode(const uint8_t *buf, uint16_t size); + +#endif diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c index 9ce9891..2c5b4da 100644 --- a/applications/PoP/roomtest/guards.c +++ b/applications/PoP/roomtest/guards.c @@ -930,10 +930,13 @@ static void autocontrol_shadow(void) static void autocontrol_opponent(void) { move_0_nothing(); - /* Таймеры боя убывают раз в кадр — здесь, как в оригинале. */ - if (justblocked) --justblocked; - if (kid_sword_strike) --kid_sword_strike; - if (guard_refrac) --guard_refrac; + /* В оригинале эти таймеры убывают только в ветке НЕ-Кида. Demo Kid + * использует тот же AI, но skill 10 и таймеры стража не расходует. */ + if (Char.charid != CHARID_0_KID) { + if (justblocked) --justblocked; + if (kid_sword_strike) --kid_sword_strike; + if (guard_refrac) --guard_refrac; + } /* autocontrol_skeleton (seg002:685): у скелета меч вынут ВСЕГДА — он не * бывает «неактивным», поэтому ветка inactive ему недоступна. */ if (Char.charid == CHARID_4_SKELETON) Char.sword = SWORD_2_DRAWN; @@ -946,6 +949,24 @@ static void autocontrol_opponent(void) else autocontrol_guard_active(); } +/* do_demo() (seg006:0D49): пока меч не вынут, управление даёт demo_moves; + * в бою Кид становится AI-персонажем skill 10, а страж затем получает + * skill 11. Команды AI уже actor-relative, поэтому pop_control вызывается + * напрямую — без user_control(), как и исправленная таблица demo_moves. */ +uint8_t pop_demo_kid_ai(void) __banked +{ + if (Kid.sword != SWORD_2_DRAWN) return 0; + + pop_loadkid_and_opp(); + pop_load_fram_det_col(); + guard_skill = 10; + autocontrol_opponent(); + guard_skill = 11; + pop_control(); + pop_savekid_state(); + return 1; +} + /* ---- Урон мечом (seg002:0CD4..0E1F) -------------------------------- */ #define ACTION_99_HURT 99 #define ACTION_1_RUN 1 diff --git a/applications/PoP/roomtest/kid_data.h b/applications/PoP/roomtest/kid_data.h index f7e2901..1bd0c1c 100644 --- a/applications/PoP/roomtest/kid_data.h +++ b/applications/PoP/roomtest/kid_data.h @@ -22,6 +22,11 @@ typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe; * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */ #define KID_NGFRAMES 41 #define KID_BIN_GFRAMES_OFF 3515 +/* Таблица кадров КАТСЦЕН (frame_tbl_cuts, seg006:417): + * Princess/Vizier, индекс — номер кадра как есть. Набор + * спрайтов выбирает sword>>6 (1 = PV1 res800, 2 = PV2 res900). */ +#define KID_NCFRAMES 86 +#define KID_BIN_CFRAMES_OFF 3720 #define KID_NSEQ 115 #define KID_SEQ_OFF_INIT { \ diff --git a/applications/PoP/roomtest/new bugs b/applications/PoP/roomtest/new bugs index 092e7f4..b111bc9 100644 --- a/applications/PoP/roomtest/new bugs +++ b/applications/PoP/roomtest/new bugs @@ -1,10 +1,6 @@ -Уровень 4, комната 22 - страж идет навстречу Киду и попадает сам в челюсти - должен быть инстинк самосохранения - -Перед решеткой Кид делает лишний мелкий шаг. - Уровень 5, комната 2 - с разбега чуть раньше - иногда попадаем в стену (некорректное отображение падения) Уровень 7, комната 9 - падает плита -1,1 - звук потери HP у Кида (причем раз даже когда Кид просто бъет по плите стоя на 0,0) -Уровень 10, комната 1 - когда Кид стоя на кнопке 1,8 роняет плиту 0,8 на кнопку 1,8 - кнопка ломается (превращается в щебень) -но двери которые она должна открыть остаются закрытыми +/// Уровень 10, комната 1 - когда Кид стоя на кнопке 1,8 роняет плиту 0,8 на кнопку 1,8 - кнопка ломается (превращается в щебень) +/// но двери которые она должна открыть остаются закрытыми diff --git a/applications/PoP/roomtest/pop_app.c b/applications/PoP/roomtest/pop_app.c new file mode 100644 index 0000000..42a23eb --- /dev/null +++ b/applications/PoP/roomtest/pop_app.c @@ -0,0 +1,133 @@ +/* + * pop_app.c — таблица переходов оболочки, БАНК 9 (FG1). + * + * Модуль не рисует и не выделяет память. Это чистая политика переходов, + * поэтому её можно проверять host-тестом без DSS/MAME. Жизненный цикл + * ресурсов остаётся у enter/leave соответствующих состояний. + */ +#include "pop_app.h" + +pop_app_state_t pop_app_state; + +void pop_app_reset(void) __banked +{ + pop_app_state = POP_APP_BOOT; +} + +uint8_t pop_app_dispatch(pop_app_event_t event) __banked +{ + pop_app_state_t next = pop_app_state; + uint8_t accepted = 0; + + switch (pop_app_state) { + case POP_APP_BOOT: + if (event == POP_APP_EV_BOOT_READY) { + next = POP_APP_BUILD_INFO; accepted = 1; + } + break; + + case POP_APP_BUILD_INFO: + if (event == POP_APP_EV_BUILD_DONE) { + next = POP_APP_TITLE; accepted = 1; + } + break; + + case POP_APP_TITLE: + if (event == POP_APP_EV_INTRO_STARTED) { + next = POP_APP_INTRO; accepted = 1; + } else if (event == POP_APP_EV_DEMO_STARTED) { + next = POP_APP_DEMO; accepted = 1; + } else if (event == POP_APP_EV_NEW_GAME) { + next = POP_APP_LEVEL_LOAD; accepted = 1; + } + break; + + case POP_APP_INTRO: + if (event == POP_APP_EV_CUTSCENE_FINISHED) { + next = POP_APP_DEMO; accepted = 1; + } else if (event == POP_APP_EV_NEW_GAME) { + next = POP_APP_LEVEL_LOAD; accepted = 1; + } + break; + + case POP_APP_DEMO: + if (event == POP_APP_EV_DEMO_FINISHED) { + next = POP_APP_TITLE; accepted = 1; + } else if (event == POP_APP_EV_NEW_GAME) { + next = POP_APP_LEVEL_LOAD; accepted = 1; + } + break; + + case POP_APP_LEVEL_LOAD: + if (event == POP_APP_EV_LEVEL_READY) { + next = POP_APP_PLAYING; accepted = 1; + } + break; + + case POP_APP_PLAYING: + if (event == POP_APP_EV_PAUSE) { + next = POP_APP_PAUSE_MENU; accepted = 1; + } else if (event == POP_APP_EV_CUTSCENE_STARTED) { + next = POP_APP_CUTSCENE; accepted = 1; + } else if (event == POP_APP_EV_LEVEL_FINISHED) { + next = POP_APP_LEVEL_LOAD; accepted = 1; + } else if (event == POP_APP_EV_TIME_EXPIRED) { + next = POP_APP_TIME_EXPIRED; accepted = 1; + } else if (event == POP_APP_EV_ENDING_STARTED) { + next = POP_APP_ENDING; accepted = 1; + } + break; + + case POP_APP_PAUSE_MENU: + if (event == POP_APP_EV_RESUME) { + next = POP_APP_PLAYING; accepted = 1; + } else if (event == POP_APP_EV_RESTART_INTRO) { + /* Restart Game начинает заново именно title/intro. Сброс новой + * игры произойдёт позже общим LEVEL_LOAD после skip/attract. */ + next = POP_APP_TITLE; accepted = 1; + } + break; + + case POP_APP_CUTSCENE: + if (event == POP_APP_EV_CUTSCENE_FINISHED) { + next = POP_APP_LEVEL_LOAD; accepted = 1; + } + break; + + case POP_APP_TIME_EXPIRED: + if (event == POP_APP_EV_CUTSCENE_FINISHED) { + next = POP_APP_TITLE; accepted = 1; + } + break; + + case POP_APP_ENDING: + if (event == POP_APP_EV_ENDING_FINISHED) { + /* ВРЕМЕННО в обход Hall of Fame: экран HOF написан (pop_hof.c), + * но после финала показывает чёрное вместо таблицы, и упереться + * в него сразу после победы хуже, чем не показать вовсе. + * Решение пользователя 2026-08-24: пока уходим прямо в начало, + * HOF чинится отдельно (BUGS_OPEN.md#hof-after-ending). + * Из title таблица по-прежнему доступна (pop_hof_show_title). */ + next = POP_APP_TITLE; accepted = 1; + } + break; + + case POP_APP_HALL_OF_FAME: + if (event == POP_APP_EV_HOF_FINISHED) { + next = POP_APP_TITLE; accepted = 1; + } + break; + + case POP_APP_QUIT: + default: + break; + } + + if (event == POP_APP_EV_QUIT && pop_app_state != POP_APP_QUIT) { + next = POP_APP_QUIT; + accepted = 1; + } + if (!accepted) return 0; + pop_app_state = next; + return 1; +} diff --git a/applications/PoP/roomtest/pop_app.h b/applications/PoP/roomtest/pop_app.h new file mode 100644 index 0000000..87c3dbb --- /dev/null +++ b/applications/PoP/roomtest/pop_app.h @@ -0,0 +1,63 @@ +/* + * pop_app.h — автомат верхнего уровня Prince of Persia (FG1). + * + * Игровой цикл — лишь состояние PLAYING, а не «вся программа». Каждый + * режим возвращает результат через pop_app_dispatch(), поэтому будущие + * TITLE/demo/cutscene не будут вызывать друг друга рекурсивно и копить + * стек или EMM-ресурсы. + */ +#ifndef POP_APP_H +#define POP_APP_H + +#include + +typedef enum { + POP_APP_BOOT = 0, + POP_APP_BUILD_INFO, + POP_APP_TITLE, + POP_APP_INTRO, + POP_APP_DEMO, + POP_APP_LEVEL_LOAD, + POP_APP_PLAYING, + POP_APP_PAUSE_MENU, + POP_APP_CUTSCENE, + POP_APP_TIME_EXPIRED, + POP_APP_ENDING, + POP_APP_HALL_OF_FAME, + POP_APP_QUIT +} pop_app_state_t; + +/* События не содержат деталей конкретного режима: режим завершился, а + * переход выбирает центральная таблица. Это намеренно делает недопустимый + * переход явным no-op вместо случайного прыжка в код другого экрана. */ +typedef enum { + POP_APP_EV_BOOT_READY = 0, + POP_APP_EV_BUILD_DONE, + POP_APP_EV_INTRO_STARTED, + POP_APP_EV_DEMO_STARTED, + POP_APP_EV_CUTSCENE_STARTED, + POP_APP_EV_NEW_GAME, + POP_APP_EV_PAUSE, + POP_APP_EV_RESUME, + POP_APP_EV_LEVEL_READY, + POP_APP_EV_LEVEL_FINISHED, + POP_APP_EV_DEMO_FINISHED, + POP_APP_EV_CUTSCENE_FINISHED, + POP_APP_EV_TIME_EXPIRED, + POP_APP_EV_ENDING_STARTED, + POP_APP_EV_ENDING_FINISHED, + POP_APP_EV_HOF_FINISHED, + POP_APP_EV_RESTART_INTRO, + POP_APP_EV_QUIT +} pop_app_event_t; + +extern pop_app_state_t pop_app_state; + +/* Начать новый маршрут с BOOT. */ +void pop_app_reset(void) __banked; + +/* Выполнить допустимый переход. Возвращает 1 при успехе, 0 при ошибке + * протокола; в ошибочном случае состояние не меняется. */ +uint8_t pop_app_dispatch(pop_app_event_t event) __banked; + +#endif /* POP_APP_H */ diff --git a/applications/PoP/roomtest/pop_cdraw.c b/applications/PoP/roomtest/pop_cdraw.c index fcdbcb4..d4f3575 100644 --- a/applications/PoP/roomtest/pop_cdraw.c +++ b/applications/PoP/roomtest/pop_cdraw.c @@ -23,6 +23,8 @@ #include "_pop_kdraw.h" #include "pop_vflip.h" /* зеркальные страницы атласов (зелье инверсии) */ /* меч + блит по id — сосед по банку 4 */ /* атласы/кадр Кида, pop_sword_draw, окна Char */ #include "pop_cdraw.h" +#include "pop_status.h" +#include "pop_ui.h" /* POP_COL_OUTSIDE — фон вне игрового поля */ #include "pop_shadow.h" /* запечённый набор Тени */ #include "pop_pace.h" /* pop_beam_sample — счёт кадров по лучу */ #include "pop_state.h" /* ВРЕМЕННО: зонды pop_dbg_b* */ @@ -34,6 +36,25 @@ #include "kid_data.h" #include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */ +/* Последний curr_guard_color обычного стража (0 — цвет ещё не выбирали). + * Нужен pop_guard_pal_apply, чтобы восстановить слоты после kid.pal. */ +static uint8_t guard_pal_cur; + +/* 16 слотов POP_GUARD_PAL_BASE в ОБЕ палитры дабл-буфера. + * Таблицы — rodata ЭТОГО модуля, то есть страница банка (0xC000+), а + * gfx_pal_load отдаёт указатель в BIOS ($A4 через rst #0x08). BIOS читает + * только #4000-#BFFF (см. корневой CLAUDE.md), поэтому копируем запись в + * локальный буфер — стек гарантированно в W2. Без копии BIOS забирал + * мусор с чужой страницы и страж становился невидимым. */ +static void pal_load16(const uint8_t *src) +{ + uint8_t buf[64]; + uint8_t i; + for (i = 0; i < 64; i++) buf[i] = src[i]; + gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf); + gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf); +} + /* Цвет стража (set_chtab_palette, seg003:257). Оригинал держит ОДИН набор * спрайтов и подменяет 16 цветов палитры chtab_5 по level.guards_color — * так на уровне 2 стражи комнат 4/7/11/15/24 разного цвета (1/3/1/1/6). @@ -43,19 +64,27 @@ * Грузим В ОБЕ палитры: у страниц дабл-буфера они раздельные. */ void pop_guard_set_palette(uint8_t color) __banked { - /* Таблица — rodata ЭТОГО модуля, то есть страница банка (0xC000+), а - * gfx_pal_load отдаёт указатель в BIOS ($A4 через rst #0x08). BIOS - * читает только #4000-#BFFF (см. корневой CLAUDE.md), поэтому копируем - * запись в локальный буфер — стек гарантированно в W2. Без копии BIOS - * забирал мусор с чужой страницы и страж становился невидимым. */ - uint8_t buf[64]; - const uint8_t *p; - uint8_t i; if (color == 0 || color > POP_GUARD_NPAL) return; - p = pop_guard_pal[color - 1]; - for (i = 0; i < 64; i++) buf[i] = p[i]; - gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf); - gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf); + guard_pal_cur = color; /* curr_guard_color, для re-apply */ + pal_load16(pop_guard_pal[color - 1]); +} + +/* Пере-применить палитру ТЕКУЩЕГО набора соперника — пара к + * pop_bg_pal_apply/pop_shadow_pal_apply. Слоты 0x90..0x9F динамические: + * общий kid.pal содержит все 256 записей и затирает их палитрой обычного + * стража (цвет 2, тот что запёк упаковщик). Поэтому КАЖДЫЙ file-load + * логической палитры обязан доплатить этот диапазон, иначе скелет (и + * Джафар) остаются в цвете стража: у скелета все пиксели индекса 3, а это + * слот 0x93 = розовый цвет стража — ровно то, что было видно на уровне 3. + * Звать из pop_pal_* ДО snapshot. */ +void pop_guard_pal_apply(void) __banked +{ + int8_t type = pop_level_guard_type(pop_current_level); + if (type == 2 || type == 3) { /* свой цвет — из файла набора */ + pal_load16((type == 2) ? pop_skel_pal : pop_vizier_pal); + } else if (type == 0 && guard_pal_cur != 0) { + pal_load16(pop_guard_pal[guard_pal_cur - 1]); + } } /* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */ @@ -105,13 +134,7 @@ int pop_guard_load(void) __banked /* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется * из res10.bin по guards_color — это делает pop_guard_set_palette при * входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */ - if (type == 2 || type == 3) { - const uint8_t *src = (type == 2) ? pop_skel_pal : pop_vizier_pal; - uint8_t buf[64], k; - for (k = 0; k < 64; k++) buf[k] = src[k]; /* BIOS: буфер в W2 */ - gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf); - gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf); - } + pop_guard_pal_apply(); return 0; } @@ -925,7 +948,7 @@ void pop_mirror_draw(int clip_top) __banked * * Спрайты Кида (216/217) лежат в его же атласах, поэтому блитим через * pop_kid_img_blit; спрайт стража — image 0 его chtab (наша страница 0). */ -#define HP_Y 194 +#define HP_Y POP_HP_Y /* полосу делит UI: см. pop_cdraw.h */ #define HP_STEP 7 #define HP_KID_FULL 216 #define HP_KID_EMPTY 217 @@ -962,10 +985,21 @@ void pop_hp_draw(void) __banked if (hp_todo == 0) return; hp_todo--; - /* Стереть прошлую полосу: фон под ней — чёрный борт. */ + /* Стереть прошлую полосу. Фон под ней — НЕ цвет 0, а POP_COL_OUTSIDE + * (в палитре тоже чёрный): вспышки подменяют нулевой цвет, и полоса HP + * не должна мигать вместе с игровым полем (pop_ui.h). */ gfx_set_bank(GFX_BANK_NORMAL); - setfillstyle(SOLID_FILL, BLACK); - bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6); + setfillstyle(SOLID_FILL, POP_COL_OUTSIDE); + /* Полосу мы делим со статус-строкой (pop_status.h): пока в ней висит + * текст, его зону не трогаем — иначе он мигал бы при каждом изменении + * жизней. Деления Кида левее POP_STATUS_L, стража — правее POP_STATUS_R, + * так что чистить края по отдельности достаточно. */ + if (pop_status_ticks) { + bar(0, HP_Y + POP_YOFF, POP_STATUS_L - 1, HP_Y + POP_YOFF + 6); + bar(POP_STATUS_R + 1, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6); + } else { + bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6); + } /* Кид: сначала пустые деления, поверх — полные (порядок оригинала). */ n = (uint8_t)(hitp_max > HP_MAXDRAW ? HP_MAXDRAW : hitp_max); diff --git a/applications/PoP/roomtest/pop_cdraw.h b/applications/PoP/roomtest/pop_cdraw.h index 3c9552d..a79c491 100644 --- a/applications/PoP/roomtest/pop_cdraw.h +++ b/applications/PoP/roomtest/pop_cdraw.h @@ -37,6 +37,12 @@ #include +/* Нижняя статус-полоса: строка делений HP. Живёт здесь, а не в pop_cdraw.c, + * потому что её делит UI — «GAME PAUSED» в оригинале пишется в ту же полосу + * (SDLPoP rect_bottom_text = {193,70,202,250}), между делениями Кида слева и + * стража справа. */ +#define POP_HP_Y 194 + #define POP_CH_KID 0 #define POP_CH_OPP 1 #define POP_CH_N 2 diff --git a/applications/PoP/roomtest/pop_cfg.h b/applications/PoP/roomtest/pop_cfg.h new file mode 100644 index 0000000..78eefe9 --- /dev/null +++ b/applications/PoP/roomtest/pop_cfg.h @@ -0,0 +1,19 @@ +/* + * pop_cfg.h — файл настроек POP.CFG (план docs/menu_settings_plan.md §6). + * Единственный владелец формата — модули pop_cfg_*; gameplay читает + * только pop_settings (pop_settings.h). + */ +#ifndef POP_CFG_H +#define POP_CFG_H + +#include + +/* Читает POP.CFG в pop_settings; файла нет / битый — остаются дефолты. + * 1 — прочитано из файла, 0 — дефолты (штатно), -1 — нет памяти. */ +int8_t pop_cfg_load(void) __banked; + +/* Записывает текущие pop_settings в POP.CFG. Звать после Apply/выхода + * из Settings. 1 — записано, 0 — ошибка записи, -1 — нет памяти. */ +int8_t pop_cfg_save(void) __banked; + +#endif diff --git a/applications/PoP/roomtest/pop_cheat.h b/applications/PoP/roomtest/pop_cheat.h index d69821b..c9fd28b 100644 --- a/applications/PoP/roomtest/pop_cheat.h +++ b/applications/PoP/roomtest/pop_cheat.h @@ -8,13 +8,15 @@ * * Реализовано: K — убить стража (seg000:786), I/S/Shift+L (наши отладочные), * [ / ] — подгонка Кида по X (seg000:1828), U — переворот экрана - * (seg000:793). Остальные читы оригинала - * (время, перо, воскрешение, переход по комнатам) пока не портированы. + * (seg000:793), F7/F8 — минута −/+ (аналог оригинальных keypad −/+). + * Остальные читы оригинала (перо, воскрешение, переход по комнатам) пока + * не портированы. */ #ifndef POP_CHEAT_H #define POP_CHEAT_H #include +#include "pop_level.h" extern uint8_t pop_cheats; /* 0 = выключены, иначе включены */ @@ -32,8 +34,12 @@ extern uint8_t pop_cheats; /* 0 = выключены, иначе включ * seg000:0793: в оригинале это тоже ЧИТ-клавиша). * Без него зелье инверсии проверяется только * честным проходом уровня 9 до комнаты 7. */ +/* F7/F8 свободны в игровой раскладке. Numpad +/- уже принадлежат ROOMNAV, + * поэтому буквально оригинальные клавиши здесь были бы опасным конфликтом. */ +#define KBD_CHEAT_TIME_SUB 0x83 /* F7 (PS/2 set 2) — отнять минуту */ +#define KBD_CHEAT_TIME_ADD 0x0A /* F8 (PS/2 set 2) — добавить минуту */ -/* Последний уровень игры (tbl_* в pop_level рассчитаны на 0..15). */ -#define POP_LAST_LEVEL 15 +/* Старое имя оставлено для обработчика Shift+L; граница одна — pop_level.h. */ +#define POP_LAST_LEVEL POP_LEVEL_LAST #endif diff --git a/applications/PoP/roomtest/pop_config.c b/applications/PoP/roomtest/pop_config.c new file mode 100644 index 0000000..4fe4b25 --- /dev/null +++ b/applications/PoP/roomtest/pop_config.c @@ -0,0 +1,192 @@ +/* + * pop_config.c — ВСЯ модель пользовательских настроек и POP.CFG (банк 9). + * + * Здесь вместе живут state, дефолты, бинарный codec и DSS I/O: это один + * модуль приложения, не библиотека. Данные остаются в общем W2 (для + * --bank-data нет ключа), код уезжает в банк 9. Игра читает только + * pop_settings либо свои runtime-флаги, которые синхронизирует + * pop_settings_apply(). + * + * POP.CFG v1: + * 0..3 "PCFG", 4 version, 5..6 payload size LE, payload, XOR, ~XOR. + * Неверный файл не меняет уже установленные дефолты. Расширенный payload + * v1 допустим: известны первые девять байтов, неизвестный хвост пропускается. + */ +#include +#include + +#ifndef POP_CONFIG_HOST_TEST +#include +#include +#include +#include +#include +#endif + +#include "pop_settings.h" +#include "pop_cfg.h" +#include "pop_pace.h" +#include "pop_cheat.h" +#include "pop_sfx.h" +#include "_pop_config.h" + +#define CFG_BASE 0x100 + +pop_settings_t pop_settings; + +static uint8_t cfg_checksum(const uint8_t *data, uint16_t size) +{ + uint8_t sum = 0; + while (size--) sum ^= *data++; + return sum; +} + +void pop_settings_defaults(void) __banked +{ + pop_settings.sound_enabled = 1; + pop_settings.speed_mode = POP_SPEED_NORMAL; + pop_settings.gameplay_profile = POP_PROFILE_VANILLA; + pop_settings.cheats_enabled = 1; /* пока roomtest — dev-сборка */ + pop_settings.show_build_info = 0; + pop_settings.enhancement_flags = 0; + pop_settings.time_limit_enabled = 1; + pop_settings.show_debug_bar = 1; /* roomtest — dev-сборка */ +} + +/* Применить модель к настоящим владельцам runtime-состояния. Sound нельзя + * применять до pop_sfx_init(): тот поднимает набор и только затем знает, + * можно ли честно включить CBL. */ +void pop_settings_apply(uint8_t sound_ready) __banked +{ + pop_speed_mode = pop_settings.speed_mode; + pop_cheats = pop_settings.cheats_enabled; + if (sound_ready && pop_snd_want != pop_settings.sound_enabled) { + pop_sfx_toggle(); + /* Если звуковой набор не поднялся, toggle возвращает want в 0. + * Файл должен отражать фактически применимое состояние. */ + pop_settings.sound_enabled = pop_snd_want; + } +} + +void pop_cfg_encode(uint8_t *buf) +{ + uint8_t sum; + memcpy(buf, "PCFG", 4); + buf[4] = POP_CFG_VERSION; + buf[5] = POP_CFG_PAYLOAD_SIZE; + buf[6] = 0; + buf[7] = pop_settings.sound_enabled; + buf[8] = pop_settings.speed_mode; + buf[9] = pop_settings.gameplay_profile; + buf[10] = pop_settings.cheats_enabled; + buf[11] = pop_settings.show_build_info; + buf[12] = (uint8_t)pop_settings.enhancement_flags; + buf[13] = (uint8_t)(pop_settings.enhancement_flags >> 8); + buf[14] = pop_settings.time_limit_enabled; + buf[15] = pop_settings.show_debug_bar; + sum = cfg_checksum(buf + POP_CFG_HEADER_SIZE, POP_CFG_PAYLOAD_SIZE); + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE] = sum; + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + 1] = (uint8_t)~sum; +} + +uint8_t pop_cfg_decode(const uint8_t *buf, uint16_t size) +{ + uint16_t payload_size, flags; + uint8_t sum; + + if (size < POP_CFG_MIN_SIZE || + memcmp(buf, "PCFG", 4) != 0 || buf[4] != POP_CFG_VERSION) + return 0; + payload_size = (uint16_t)(buf[5] | ((uint16_t)buf[6] << 8)); + if (payload_size < POP_CFG_LEGACY_PAYLOAD_SIZE || + payload_size > POP_CFG_MAX_PAYLOAD || + size != (uint16_t)(POP_CFG_HEADER_SIZE + payload_size + + POP_CFG_TAIL_SIZE)) + return 0; + sum = cfg_checksum(buf + POP_CFG_HEADER_SIZE, payload_size); + if (buf[POP_CFG_HEADER_SIZE + payload_size] != sum || + buf[POP_CFG_HEADER_SIZE + payload_size + 1] != (uint8_t)~sum) + return 0; + + /* Сначала проверить ВСЕ известные поля, только затем менять state. */ + if (buf[7] > 1 || buf[8] >= POP_SPEED_MODES || + buf[9] > POP_PROFILE_ENHANCED || buf[10] > 1 || buf[11] > 1) + return 0; + if (payload_size >= POP_CFG_TIMELIMIT_PAYLOAD_SIZE && buf[14] > 1) return 0; + if (payload_size >= POP_CFG_PAYLOAD_SIZE && buf[15] > 1) return 0; + flags = (uint16_t)(buf[12] | ((uint16_t)buf[13] << 8)); + pop_settings.sound_enabled = buf[7]; + pop_settings.speed_mode = buf[8]; + /* ENHANCED пока не имеет аудита: безопасно понижаем до VANILLA. */ + pop_settings.gameplay_profile = POP_PROFILE_VANILLA; + pop_settings.cheats_enabled = buf[10]; + pop_settings.show_build_info = buf[11]; + pop_settings.enhancement_flags = flags; + /* Старый POP.CFG создавался до настройки таймера (и до тумблера + * отладочной полосы): отсутствующее поле означает исторический дефолт — + * ограничение 60 минут и включённую полосу, а не случайное значение в + * памяти. */ + pop_settings.time_limit_enabled = + (payload_size >= POP_CFG_TIMELIMIT_PAYLOAD_SIZE) ? buf[14] : 1; + pop_settings.show_debug_bar = + (payload_size >= POP_CFG_PAYLOAD_SIZE) ? buf[15] : 1; + return 1; +} + +#ifndef POP_CONFIG_HOST_TEST + +/* В отличие от bank_load_file здесь нужен точный размер файла: только так + * отличаем валидный будущий payload от короткого/дописанного мусора. Буфер + * на стеке лежит в W2, то есть разрешён ESTEX; файл мал, EMM ему не нужен. */ +static uint8_t cfg_read_file(uint8_t *buf, uint16_t *size) +{ + int fd, got; + long n; + + fd = open("POP.CFG", O_RDONLY); + if (fd < 0) return 0; + n = lseek(fd, 0, SEEK_END); + if (n < POP_CFG_MIN_SIZE || n > POP_CFG_MAX_SIZE || + lseek(fd, 0, SEEK_SET) < 0) { + close(fd); + return 0; + } + got = read(fd, buf, (uint16_t)n); + if (close(fd) != 0 || got != (int)n) return 0; + *size = (uint16_t)n; + return 1; +} + +int8_t pop_cfg_load(void) __banked +{ + uint8_t buf[POP_CFG_MAX_SIZE]; + uint16_t size; + + if (!cfg_read_file(buf, &size) || !pop_cfg_decode(buf, size)) return 0; + if (buf[9] != POP_PROFILE_VANILLA) + printf("POP.CFG: ENHANCED profile -> VANILLA\n"); + return 1; +} + +int8_t pop_cfg_save(void) __banked +{ + uint8_t blk, page; + uint8_t buf[POP_CFG_SIZE]; + int rc; + + blk = mem_alloc_pages(1); + if (!blk) return -1; + page = mem_get_page(blk, 0); + gfx_w0_page_prepare(page); + pop_cfg_encode(buf); + gfx_w0_map(page); + memcpy((uint8_t *)CFG_BASE, buf, sizeof(buf)); + gfx_w0_unmap(); + /* Не unlink: bank_save_file сам делает O_TRUNC и не создаёт лишнего + * окна, когда POP.CFG вообще отсутствует после ошибки unlink. */ + rc = bank_save_file(page, CFG_BASE, "POP.CFG", sizeof(buf)); + mem_free_block(blk); + return (rc == sizeof(buf)) ? 1 : 0; +} + +#endif diff --git a/applications/PoP/roomtest/pop_ctrl.c b/applications/PoP/roomtest/pop_ctrl.c index 27591fb..73ef3da 100644 --- a/applications/PoP/roomtest/pop_ctrl.c +++ b/applications/PoP/roomtest/pop_ctrl.c @@ -704,6 +704,27 @@ static void save_ctrl_1(void) ctrl1_up = control_up; ctrl1_down = control_down; ctrl1_shift2 = control_shift2; } +/* Общий хвост control_kid: raw-ввод и attract-demo различаются только тем, + * кто до него заполнил control_*. Окно Char и ctrl1_* обязаны быть теми же, + * иначе запись demo_moves перестаёт совпадать с логикой обычного игрока. */ +static void ctrl_kid_dispatch(void) +{ + pop_loadkid(); + pop_load_fram_det_col(); + user_control(); + pop_savekid_state(); + save_ctrl_1(); +} + +static void ctrl_kid_death(void) +{ + /* control_kid (seg006:0CD1): HP кончились — персонаж мёртв. */ + if (Kid.alive < 0 && hitp_curr == 0) { + Kid.alive = 0; + pop_kid_dead = 1; + } +} + /* ---- Публичный API ------------------------------------------------ */ int pop_ctrl_init(void) __banked { @@ -733,10 +754,7 @@ void pop_ctrl_tick(void) __banked * возвращался в стойку с нулём HP, и страж убивал его снова по кругу * (BUG-DEATH-1). pop_kid_dead — наш сигнал главному циклу на респавн * (в оригинале это счётчик Char.alive в play_kid, seg006:0BEE). */ - if (Kid.alive < 0 && hitp_curr == 0) { - Kid.alive = 0; - pop_kid_dead = 1; - } + ctrl_kid_death(); rest_ctrl_1(); read_input(); read_user_control(); @@ -750,9 +768,56 @@ void pop_ctrl_tick(void) __banked * его перезаписывает страж; без перезагрузки диспетчер считает * геометрию Кида (dx_weight -> determine_col -> distance_to_edge_weight * -> get_edge_distance) по кадру СТРАЖА. Замерено: BUG-CTRL-FRAME-1. */ + ctrl_kid_dispatch(); /* -> pop_control(): может сменить Char.curr_seq */ +} + +void pop_ctrl_auto_move(int8_t move) __banked +{ + ctrl_kid_death(); + /* control_kid() оригинала в demo-ветке делает do_demo(); control(); + * БЕЗ rest_ctrl_1/read_user_control/user_control/save_ctrl_1. Команды + * auto_move уже относительны к направлению Char. Если пропустить их + * через user_control(), то при Kid.direction=RIGHT backward зеркалится + * во forward: во второй комнате Кид вместо разворота бежит в пропасть. */ + switch (move) { + case 0: + control_shift = control_y = control_x = control_shift2 = 0; + control_down = control_up = control_backward = control_forward = 0; + break; + case 1: + control_x = POP_CONTROL_HELD_FORWARD; + control_forward = POP_CONTROL_HELD; + break; + case 2: + control_backward = POP_CONTROL_HELD; + control_x = POP_CONTROL_HELD_BACKWARD; + break; + case 3: + control_y = POP_CONTROL_HELD_UP; + control_up = POP_CONTROL_HELD; + break; + case 4: + control_down = POP_CONTROL_HELD; + control_y = POP_CONTROL_HELD_DOWN; + break; + case 5: + control_y = POP_CONTROL_HELD_UP; + control_up = POP_CONTROL_HELD; + control_x = POP_CONTROL_HELD_FORWARD; + control_forward = POP_CONTROL_HELD; + break; + case 6: + control_shift = POP_CONTROL_HELD; + control_shift2 = POP_CONTROL_HELD; + break; + case 7: + control_shift = POP_CONTROL_RELEASED; + break; + default: + break; + } pop_loadkid(); pop_load_fram_det_col(); - user_control(); /* -> pop_control(): может сменить Char.curr_seq */ + pop_control(); pop_savekid_state(); - save_ctrl_1(); } diff --git a/applications/PoP/roomtest/pop_ctrl.h b/applications/PoP/roomtest/pop_ctrl.h index 0b5f550..52d1fa1 100644 --- a/applications/PoP/roomtest/pop_ctrl.h +++ b/applications/PoP/roomtest/pop_ctrl.h @@ -35,6 +35,12 @@ void pop_ctrl_close(void) __banked; * kid_tick() (play_seq продолжит с новой последовательности). */ void pop_ctrl_tick(void) __banked; +/* Один кадр синтетического управления attract-demo. move — код SDLPoP: + * 0 nothing, 1 forward, 2 backward, 3 up, 4 down, 5 up+forward, + * 6 shift, 7 отпустить shift, -1 ничего не менять. Raw-клавиатуру не + * читает: это важно, чтобы ввод мог сперва прервать demo. */ +void pop_ctrl_auto_move(int8_t move) __banked; + /* control_shift зажат ПРЯМО СЕЙЧАС? — для check_grab в физике (pop_map): * зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */ int8_t pop_ctrl_shift_held(void) __banked; diff --git a/applications/PoP/roomtest/pop_cutscene.c b/applications/PoP/roomtest/pop_cutscene.c new file mode 100644 index 0000000..7c61cca --- /dev/null +++ b/applications/PoP/roomtest/pop_cutscene.c @@ -0,0 +1,55 @@ +/* + * pop_cutscene.c — общий data-driven шагатель кат-сцен, банк 9 (FG8). + * + * Один вызов = один логический кадр. WAIT съедает текущий шаг сразу, поэтому + * WAIT(1) откладывает следующую видимую команду ровно на один тик. Команды + * без ожидания выдаются по одной: runtime успевает переключить палитру, + * собрать hidden page или сделать fade до следующего шага. + */ +#include +#include "pop_cutscene.h" + +void pop_cutscene_begin(pop_cutscene_t *cs, const pop_cs_cmd_t *script) __banked +{ + cs->script = script; + cs->pc = 0; + cs->waiting = 0; + cs->ended = 0; +} + +void pop_cutscene_abort(pop_cutscene_t *cs) __banked +{ + cs->waiting = 0; + cs->ended = 1; +} + +pop_cs_event_t pop_cutscene_tick(pop_cutscene_t *cs, uint16_t *arg) __banked +{ + const pop_cs_cmd_t *cmd; + + *arg = 0; + if (cs->ended) return POP_CS_EVENT_END; + if (cs->waiting) { + --cs->waiting; + return POP_CS_EVENT_NONE; + } + + cmd = &cs->script[cs->pc++]; + *arg = cmd->arg; + switch (cmd->op) { + case POP_CS_SHOW: return POP_CS_EVENT_SHOW; + case POP_CS_PALETTE: return POP_CS_EVENT_PALETTE; + case POP_CS_FADE_IN: return POP_CS_EVENT_FADE_IN; + case POP_CS_FADE_OUT: return POP_CS_EVENT_FADE_OUT; + case POP_CS_FLASH: return POP_CS_EVENT_FLASH; + case POP_CS_WAIT: + /* Текущий tick — первый из N: это исключает скрытый +1 кадр. */ + cs->waiting = cmd->arg; + if (cs->waiting) --cs->waiting; + return POP_CS_EVENT_NONE; + case POP_CS_END: + default: + cs->ended = 1; + return POP_CS_EVENT_END; + } +} diff --git a/applications/PoP/roomtest/pop_cutscene.h b/applications/PoP/roomtest/pop_cutscene.h new file mode 100644 index 0000000..3a8fcbc --- /dev/null +++ b/applications/PoP/roomtest/pop_cutscene.h @@ -0,0 +1,49 @@ +/* + * pop_cutscene.h — маленький интерпретатор сценариев экранных сцен (FG8). + * + * Данные сценария статичны, состояние — только pc и счётчик ожидания. Это + * пригодится и PV intro, и сценам перед уровнями FG9: графика/звук остаются + * у вызывающего режима, а не тащатся в общий механизм. + */ +#ifndef POP_CUTSCENE_H +#define POP_CUTSCENE_H + +#include + +typedef enum { + POP_CS_SHOW = 1, /* arg: номер готового кадра */ + POP_CS_WAIT, /* arg: число логических кадров */ + POP_CS_PALETTE, /* arg: номер палитрового набора */ + POP_CS_FADE_IN, + POP_CS_FADE_OUT, + POP_CS_FLASH, /* arg: длительность белой вспышки */ + POP_CS_END +} pop_cs_opcode_t; + +typedef struct { + uint8_t op; + uint16_t arg; +} pop_cs_cmd_t; + +typedef struct { + const pop_cs_cmd_t *script; + uint8_t pc; + uint16_t waiting; + uint8_t ended; +} pop_cutscene_t; + +typedef enum { + POP_CS_EVENT_NONE = 0, + POP_CS_EVENT_SHOW, + POP_CS_EVENT_PALETTE, + POP_CS_EVENT_FADE_IN, + POP_CS_EVENT_FADE_OUT, + POP_CS_EVENT_FLASH, + POP_CS_EVENT_END +} pop_cs_event_t; + +void pop_cutscene_begin(pop_cutscene_t *cs, const pop_cs_cmd_t *script) __banked; +void pop_cutscene_abort(pop_cutscene_t *cs) __banked; +pop_cs_event_t pop_cutscene_tick(pop_cutscene_t *cs, uint16_t *arg) __banked; + +#endif /* POP_CUTSCENE_H */ diff --git a/applications/PoP/roomtest/pop_demo.c b/applications/PoP/roomtest/pop_demo.c new file mode 100644 index 0000000..4aaa18c --- /dev/null +++ b/applications/PoP/roomtest/pop_demo.c @@ -0,0 +1,73 @@ +/* + * pop_demo.c — воспроизведение attract-demo SDLPoP, банк 9 (FG7). + * + * Это не отдельная физика и не сценарные телепорты. Таблица custom->demo_moves + * подаёт ровно те же control_* в pop_ctrl_auto_move(), через которые идут + * игрок и автоконтроль персонажей. Поэтому демо проверяет настоящий маршрут + * level 0, а не его нарисованную имитацию. + */ +#include +#include +#include "pop_ctrl.h" +#include "pop_guard.h" +#include "pop_map.h" /* hitp_curr — смерть demo имеет приоритет над AI */ +#include "pop_demo.h" + +typedef struct { + uint8_t time; + int8_t move; +} demo_move_t; + +/* data.h SDLPoP: демо пробегает уровень 0 до комнаты 24. Последний -1 + * означает «больше не менять уже поданный control_*», а не ввод. */ +static const demo_move_t demo_moves[] = { + {0x00, 0}, {0x01, 1}, {0x0D, 0}, {0x1E, 1}, {0x25, 5}, + {0x2F, 0}, {0x30, 1}, {0x41, 0}, {0x49, 2}, {0x4B, 0}, + {0x63, 2}, {0x64, 0}, {0x73, 5}, {0x80, 6}, {0x88, 3}, + {0x9D, 7}, {0x9E, 0}, {0x9F, 1}, {0xAB, 4}, {0xB1, 0}, + {0xB2, 1}, {0xBC, 0}, {0xC1, 1}, {0xCD, 0}, {0xE9, -1}, + /* do_auto_moves читает moves[demo_index] ещё после сдвига индекса. + * Это недостижимая метка из data.h, не команда: она держит индекс в + * границах, пока demo_time доходит до 0xFE. */ + {0xFF, -2} +}; + +static uint8_t demo_time; +static uint8_t demo_index; + +void pop_demo_begin(void) __banked +{ + demo_time = 0; + demo_index = 0; +} + +uint8_t pop_demo_tick(void) __banked +{ + uint8_t index; + int8_t move; + + /* Урон мечом сводится ПОСЛЕ этого вызова в предыдущем игровом кадре. + * На следующем тике HP уже ноль, но pop_kid_dead ещё не поднят: обычный + * pop_ctrl_tick делает это в ctrl_kid_death(), а demo обходит его своим + * AI. Без этого гарда AI снова управлял мёртвым Кидом, возвращал его в + * стойку и страж убивал его по кругу. Attract-demo при смерти кончается; + * текущий главный цикл ещё дорисует один последний кадр и уйдёт в title. */ + if (hitp_curr == 0) return 1; + + /* do_demo(): в бою запись времени/кнопок стоит, Кидом управляет тот же + * combat AI, что стражами, но с отдельным skill 10. */ + if (pop_demo_kid_ai()) return 0; + if (demo_time >= 0xFE) return 1; + ++demo_time; + index = demo_index; + if (demo_moves[index].time <= demo_time) ++demo_index; + else index = (uint8_t)(demo_index - 1); + move = demo_moves[index].move; + pop_ctrl_auto_move(move); + return (uint8_t)(demo_time >= 0xFE); +} + +uint8_t pop_demo_user_input(void) __banked +{ + return kbd_raw_any_down(); +} diff --git a/applications/PoP/roomtest/pop_demo.h b/applications/PoP/roomtest/pop_demo.h new file mode 100644 index 0000000..e4c0002 --- /dev/null +++ b/applications/PoP/roomtest/pop_demo.h @@ -0,0 +1,17 @@ +/* pop_demo.h — attract-mode уровня 0, банк 9 (FG7). */ +#ifndef POP_DEMO_H +#define POP_DEMO_H + +#include + +/* Начать неизменяемую запись demo_moves SDLPoP с кадра ноль. */ +void pop_demo_begin(void) __banked; + +/* Один логический кадр записи: подать синтетический control_*. + * Возвращает 1, когда запись исчерпана. */ +uint8_t pop_demo_tick(void) __banked; + +/* «Любой ввод» без покадрового перебора 512 scan-кодов. */ +uint8_t pop_demo_user_input(void) __banked; + +#endif diff --git a/applications/PoP/roomtest/pop_flow.c b/applications/PoP/roomtest/pop_flow.c new file mode 100644 index 0000000..b96fa98 --- /dev/null +++ b/applications/PoP/roomtest/pop_flow.c @@ -0,0 +1,48 @@ +/* pop_flow.c — таблица level-flow SDLPoP, банк 9 (FG9). */ +#include +#include "pop_level.h" +#include "pop_flow.h" + +/* Индекс равен номеру следующего уровня. pre_cutscene повторяет + * tbl_cutscenes[] SDLPoP, но тип значения — компактный экранный сценарий, + * а не указатель на игровую функцию. Уровень 14 намеренно скрывает номер. */ +static const pop_level_flow_t level_flow[POP_LEVEL_LAST + 1] = { + { 0, POP_PRE_NONE, 0, 0 }, { 1, POP_PRE_NONE, 1, 0 }, + { 2, POP_PRE_2_6, 1, 0 }, { 3, POP_PRE_NONE, 1, 0 }, + { 4, POP_PRE_4, 1, 0 }, { 5, POP_PRE_NONE, 1, 0 }, + { 6, POP_PRE_2_6, 1, 0 }, { 7, POP_PRE_NONE, 1, 0 }, + { 8, POP_PRE_8, 1, 0 }, { 9, POP_PRE_9, 1, 0 }, + { 10, POP_PRE_NONE, 1, 0 }, { 11, POP_PRE_NONE, 1, 0 }, + { 12, POP_PRE_12, 1, 0 }, { 13, POP_PRE_NONE, 1, 0 }, + { 14, POP_PRE_NONE, 0, 1 } +}; + +const pop_level_flow_t *pop_flow_level(uint8_t level) __banked +{ + if (level > POP_LEVEL_LAST) return 0; + return &level_flow[level]; +} + +pop_pre_cutscene_t pop_flow_pre_cutscene(uint8_t level, + uint8_t remaining_minutes) __banked +{ + pop_pre_cutscene_t scene; + const pop_level_flow_t *flow = pop_flow_level(level); + + if (!flow) return POP_PRE_NONE; + scene = (pop_pre_cutscene_t)flow->pre_cutscene; + /* hourglass_frame(): frame 6 iff `time_bound[0] (6) > rem_min`. */ + if (scene == POP_PRE_12 && remaining_minutes < 6) + return POP_PRE_12_SHORT; + return scene; +} + +uint8_t pop_flow_is_ending(uint8_t level, uint8_t room) __banked +{ + const pop_level_flow_t *flow = pop_flow_level(level); + + /* WIN_LEVEL/WIN_ROOM в SDLPoP: level 14, room 5. Номер комнаты держим + * здесь, а не в горячем игровом цикле, чтобы таблица оставалась + * единственным владельцем специальных переходов. */ + return (uint8_t)(flow && flow->ending_rule && room == 5); +} diff --git a/applications/PoP/roomtest/pop_flow.h b/applications/PoP/roomtest/pop_flow.h new file mode 100644 index 0000000..47022cc --- /dev/null +++ b/applications/PoP/roomtest/pop_flow.h @@ -0,0 +1,40 @@ +/* + * pop_flow.h — таблица маршрута уровней и pre-level cutscene (FG9). + * + * Здесь нет механики rooms/guards: номер назначения превращается в правило + * оболочки один раз на границе уровня. Поэтому добавить особый маршрут не + * означает разбрасывать условия по горячему игровому кадру. + */ +#ifndef POP_FLOW_H +#define POP_FLOW_H + +#include + +typedef enum { + POP_PRE_NONE = 0, + POP_PRE_2_6, + POP_PRE_4, + POP_PRE_8, + POP_PRE_9, + POP_PRE_12, + POP_PRE_12_SHORT +} pop_pre_cutscene_t; + +typedef struct { + uint8_t level; + uint8_t pre_cutscene; + uint8_t show_level_number; + uint8_t ending_rule; +} pop_level_flow_t; + +/* Нулевой указатель означает некорректный номер назначения. Сцена 12 + * выбирается по тем же границам SDLPoP: при <=5 оставшихся минутах короткая. */ +const pop_level_flow_t *pop_flow_level(uint8_t level) __banked; +pop_pre_cutscene_t pop_flow_pre_cutscene(uint8_t level, + uint8_t remaining_minutes) __banked; + +/* Победная комната не принадлежит механике movement: это правило маршрута + * оболочки. Сейчас оно есть только у скрытого уровня 14. */ +uint8_t pop_flow_is_ending(uint8_t level, uint8_t room) __banked; + +#endif /* POP_FLOW_H */ diff --git a/applications/PoP/roomtest/pop_font.h b/applications/PoP/roomtest/pop_font.h new file mode 100644 index 0000000..994b1d2 --- /dev/null +++ b/applications/PoP/roomtest/pop_font.h @@ -0,0 +1,23 @@ +/* + * pop_font.h — контракт FONT\FONT.ATL, СГЕНЕРИРОВАН. + * Источник: SDLPoP hc_small_font_data[] и hc_font_data[]. + * Генератор: toolchain/pop_extract_font.py. Руками не править. + */ +#ifndef POP_FONT_H +#define POP_FONT_H + +#include + +#define POP_FONT_COLOR 0x3F +#define POP_FONT_FIRST 32 +#define POP_FONT_LAST 126 +#define POP_FONT_SMALL_BASE 0 +#define POP_FONT_SMALL_ASCENT 5 +#define POP_FONT_SMALL_SPACE 1 +#define POP_FONT_BIG_BASE 95 +#define POP_FONT_BIG_ASCENT 7 +#define POP_FONT_BIG_SPACE 1 +#define POP_FONT_SNAPSHOT_OFF 0x3C00 +#define POP_FONT_ATLAS_SIZE 7667 + +#endif diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index dd86519..fdd0e4b 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -72,6 +72,10 @@ * Отличают Джафара набор спрайтов (VIZIER.DAT), skill 9 и три события. */ #define CHARID_4_SKELETON 4 #define CHARID_5_PRINCESS 5 +/* Джафар КАТСЦЕН (не игровой Джафар уровня 13!): в сценах intro он ходит + * по той же таблице кадров, что и Princess (frame_tbl_cuts), только свой + * набор спрайтов. */ +#define CHARID_6_VIZIER 6 /* МЫШЬ (спецсобытие уровня 8) — тоже персонаж слота Guard: своей таблицы * кадров у неё нет, кадры 186..188 лежат в таблице КИДА (load_frame, * seg006:0293 — общий case с charid_0_kid), а спрайты (images 130..132) — @@ -221,6 +225,11 @@ extern int8_t guard_refrac, justblocked, kid_sword_strike; * holding_sword — клинок в руке (для индикатора HP стража). */ extern int8_t offguard, holding_sword; +/* do_demo() SDLPoP: когда у Кида вынут меч, scripted demo_moves временно + * заменяются боевым AI skill 10, после чего skill 11 остаётся стражу. + * Возврат 1 — AI применён; 0 — меч ещё не вынут, продолжать таблицу. */ +uint8_t pop_demo_kid_ai(void) __banked; + /* Спецсобытие уровня 12 — СЛИЯНИЕ С ТЕНЬЮ (united_with_shadow, seg000:288). * Значение читается как три разных состояния, и это НЕ флаг: * 0 — тень ещё отдельный персонаж (или уровень не 12); @@ -352,6 +361,13 @@ void pop_hp_invalidate(void) __banked; * отрисовки комнаты, как это делает redraw_screen. */ void pop_guard_set_palette(uint8_t color) __banked; +/* Пере-применить палитру ТЕКУЩЕГО набора соперника (скелет/Джафар — своя из + * файла набора, обычный страж — последний curr_guard_color). Диапазон + * 0x90..0x9F динамический: kid.pal затирает его палитрой стража, поэтому + * каждый file-load логической палитры доплачивает его заново — так же, как + * pop_bg_pal_apply/pop_shadow_pal_apply. Звать ДО snapshot. */ +void pop_guard_pal_apply(void) __banked; + /* Отрисовка соперника — общая на всех Char: pop_char_draw(POP_CH_OPP) и * компания, см. pop_cdraw.h. Метрики его последнего кадра (в т.ч. ширина * спрайта для char_width_half) — pop_cd[POP_CH_OPP]. */ diff --git a/applications/PoP/roomtest/pop_guard_cold.c b/applications/PoP/roomtest/pop_guard_cold.c index e141971..9b74cbb 100644 --- a/applications/PoP/roomtest/pop_guard_cold.c +++ b/applications/PoP/roomtest/pop_guard_cold.c @@ -96,7 +96,7 @@ void pop_guard_enter(uint8_t room) int8_t dir; pop_guard_reset(); /* load_lev_spr (seg000:1112): tbl_guard_type == −1 — стражей на уровне - * НЕТ вообще (14/15), их графика даже не грузится. Без этой проверки + * НЕТ вообще (14), их графика даже не грузится. Без этой проверки * на таком уровне полезли бы стражи из данных комнат. */ if (pop_level_guard_type(pop_current_level) < 0) return; if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return; diff --git a/applications/PoP/roomtest/pop_hof.c b/applications/PoP/roomtest/pop_hof.c new file mode 100644 index 0000000..97d92fa --- /dev/null +++ b/applications/PoP/roomtest/pop_hof.c @@ -0,0 +1,370 @@ +/* + * pop_hof.c — persistent Hall of Fame, банк 9 (FG12). + * + * POP.HOF — небольшой самодостаточный формат: magic, version, fixed records + * и XOR+инверсия. Поэтому обрыв/мусор на HDD не может поломать старт игры: + * decode просто начинает с пустой таблицы. Ввод — raw-клавиатура, как весь + * PoP; он не открывает DSS-console и не конфликтует с активным raw hook. + */ +#include +#include +#include +#include +#include +#include +#include +#include "pop_bg.h" +#include "pop_settings.h" +#include "pop_sfx.h" +#include "pop_timer.h" +#include "pop_pal.h" +#include "pop_ui.h" +#include "pop_hof.h" + +#define HOF_COUNT 5 +#define HOF_NAME_SIZE 10 /* девять букв + '\0' */ +#define HOF_REC_SIZE 14 +#define HOF_HEADER_SIZE 6 +#define HOF_SIZE (HOF_HEADER_SIZE + HOF_COUNT * HOF_REC_SIZE + 2) +#define HOF_BASE 0x100 +#define HOF_VERSION 1 +#define HOF_FADE 4 +#define HOF_NO_ROW 0xFF + +typedef struct { + char name[HOF_NAME_SIZE]; + uint8_t minutes; + uint16_t ticks; + uint8_t limited; +} hof_entry_t; + +static hof_entry_t hof_table[HOF_COUNT]; +static uint8_t hof_buf[HOF_SIZE]; + +static uint8_t hof_xor(const uint8_t *p, uint8_t n) +{ + uint8_t sum = 0; + while (n--) sum ^= *p++; + return sum; +} + +static void hof_clear_entry(hof_entry_t *entry) +{ + uint8_t i; + for (i = 0; i < HOF_NAME_SIZE; i++) entry->name[i] = 0; + entry->minutes = 0; + entry->ticks = 0; + entry->limited = 0; +} + +static void hof_clear(void) +{ + uint8_t i; + for (i = 0; i < HOF_COUNT; i++) hof_clear_entry(&hof_table[i]); +} + +static uint8_t hof_decode(const uint8_t *src, uint8_t size) +{ + uint8_t i, j, off, sum; + + if (size != HOF_SIZE || src[0] != 'P' || src[1] != 'H' || + src[2] != 'O' || src[3] != 'F' || src[4] != HOF_VERSION || + src[5] != HOF_COUNT) + return 0; + sum = hof_xor(src, (uint8_t)(HOF_SIZE - 2)); + if (src[HOF_SIZE - 2] != sum || src[HOF_SIZE - 1] != (uint8_t)~sum) + return 0; + + off = HOF_HEADER_SIZE; + for (i = 0; i < HOF_COUNT; i++) { + hof_entry_t *entry = &hof_table[i]; + for (j = 0; j < HOF_NAME_SIZE - 1; j++) { + uint8_t ch = src[off++]; + /* Имя намеренно ASCII: это также отбрасывает мусор из чужого + * формата, не позволяя строке уйти за свою границу. */ + if (ch && (ch < ' ' || ch > 'Z')) return 0; + entry->name[j] = (char)ch; + } + entry->name[HOF_NAME_SIZE - 1] = 0; + entry->minutes = src[off++]; + entry->ticks = (uint16_t)(src[off] | ((uint16_t)src[off + 1] << 8)); + off += 2; + entry->limited = src[off++]; + if (entry->limited > 1 || + (entry->limited && (entry->ticks == 0 || + entry->ticks > POP_TIMER_START_TICKS))) + return 0; + } + return 1; +} + +static void hof_encode(uint8_t *dst) +{ + uint8_t i, j, off, sum; + + dst[0] = 'P'; dst[1] = 'H'; dst[2] = 'O'; dst[3] = 'F'; + dst[4] = HOF_VERSION; + dst[5] = HOF_COUNT; + off = HOF_HEADER_SIZE; + for (i = 0; i < HOF_COUNT; i++) { + const hof_entry_t *entry = &hof_table[i]; + for (j = 0; j < HOF_NAME_SIZE - 1; j++) dst[off++] = entry->name[j]; + dst[off++] = entry->minutes; + dst[off++] = (uint8_t)entry->ticks; + dst[off++] = (uint8_t)(entry->ticks >> 8); + dst[off++] = entry->limited; + } + sum = hof_xor(dst, (uint8_t)(HOF_SIZE - 2)); + dst[HOF_SIZE - 2] = sum; + dst[HOF_SIZE - 1] = (uint8_t)~sum; +} + +static void hof_load(void) +{ + uint8_t blk, page, i; + int got; + + hof_clear(); + blk = mem_alloc_pages(1); + if (!blk) return; + page = mem_get_page(blk, 0); + gfx_w0_page_prepare(page); + got = bank_load_file(page, HOF_BASE, "POP.HOF", HOF_SIZE); + if (got == HOF_SIZE) { + gfx_w0_map(page); + for (i = 0; i < HOF_SIZE; i++) + hof_buf[i] = ((const uint8_t *)HOF_BASE)[i]; + gfx_w0_unmap(); + if (!hof_decode(hof_buf, HOF_SIZE)) hof_clear(); + } + mem_free_block(blk); +} + +static void hof_save(void) +{ + uint8_t blk, page, i; + + blk = mem_alloc_pages(1); + if (!blk) return; + page = mem_get_page(blk, 0); + gfx_w0_page_prepare(page); + hof_encode(hof_buf); + gfx_w0_map(page); + for (i = 0; i < HOF_SIZE; i++) ((uint8_t *)HOF_BASE)[i] = hof_buf[i]; + gfx_w0_unmap(); + /* Неудача записи не меняет только что показанную таблицу: следующее + * открытие разумно начнёт с прежнего файла или с пустого списка. */ + bank_save_file(page, HOF_BASE, "POP.HOF", HOF_SIZE); + mem_free_block(blk); +} + +static uint8_t hof_better(const hof_entry_t *entry) +{ + uint8_t limited = pop_settings.time_limit_enabled; + + if (!entry->name[0]) return 1; + /* Unlimited сохраняется и выводится, но не может вытеснить ограниченный + * таймером результат: у него нет сравнимого времени. */ + if (limited != entry->limited) return limited; + if (pop_timer_minutes != entry->minutes) + return (uint8_t)(pop_timer_minutes > entry->minutes); + return (uint8_t)(pop_timer_ticks > entry->ticks); +} + +static uint8_t hof_insert_current(void) +{ + uint8_t i, j; + + for (i = 0; i < HOF_COUNT; i++) { + if (!hof_better(&hof_table[i])) continue; + for (j = HOF_COUNT - 1; j > i; j--) hof_table[j] = hof_table[j - 1]; + hof_clear_entry(&hof_table[i]); + hof_table[i].minutes = pop_timer_minutes; + hof_table[i].ticks = pop_timer_ticks; + hof_table[i].limited = pop_settings.time_limit_enabled; + return i; + } + return HOF_NO_ROW; +} + +static uint8_t hof_has_entries(void) +{ + uint8_t i; + for (i = 0; i < HOF_COUNT; i++) + if (hof_table[i].name[0]) return 1; + return 0; +} + +static void hof_dec(uint8_t value, char *out) +{ + if (value >= 100) { + *out++ = (char)('0' + value / 100); + value %= 100; + *out++ = (char)('0' + value / 10); + } else if (value >= 10) { + *out++ = (char)('0' + value / 10); + } + *out++ = (char)('0' + value % 10); + *out = 0; +} + +static void hof_result_text(const hof_entry_t *entry, char *out) +{ + if (!entry->limited) { + strcpy(out, "UNLIMITED"); + return; + } + hof_dec(entry->minutes, out); + while (*out) out++; + *out++ = ' '; + *out++ = 'M'; + *out++ = 'I'; + *out++ = 'N'; + *out = 0; +} + +static void hof_draw(uint8_t edit_row) +{ + uint8_t page, i; + + page = pop_screen_begin(); + pop_screen_fill(BLACK); + gfx_set_bank(GFX_BANK_SPRITE); + pop_text_map(); + pop_text_center_mapped(POP_TEXT_BIG, 160, 58, "HALL OF FAME"); + pop_text_draw_mapped(POP_TEXT_SMALL, 64, 84, "NAME"); + pop_text_right_mapped(POP_TEXT_SMALL, 255, 84, "TIME"); + for (i = 0; i < HOF_COUNT; i++) { + char rank[3], value[12]; + const hof_entry_t *entry = &hof_table[i]; + int y = 106 + (int)i * 20; + + rank[0] = (char)('1' + i); rank[1] = '.'; rank[2] = 0; + pop_text_draw_mapped(POP_TEXT_SMALL, 46, y, rank); + if (entry->name[0]) + pop_text_draw_mapped(POP_TEXT_SMALL, 72, y, entry->name); + else if (i == edit_row) + pop_text_draw_mapped(POP_TEXT_SMALL, 72, y, "_"); + else + pop_text_draw_mapped(POP_TEXT_SMALL, 72, y, "---"); + if (entry->name[0]) { + hof_result_text(entry, value); + pop_text_right_mapped(POP_TEXT_SMALL, 255, y, value); + } + } + if (edit_row != HOF_NO_ROW) + pop_text_center_mapped(POP_TEXT_SMALL, 160, 228, "TYPE NAME - ENTER"); + else + pop_text_center_mapped(POP_TEXT_SMALL, 160, 228, "PRESS ANY KEY"); + pop_text_unmap(); + pop_screen_present(page); +} + +static void hof_wait_release(void) +{ + while (kbd_raw_any_down()) { + kbd_raw_sync(); + gfx_wait_vsync(); + } +} + +/* Set 2 make-коды A..Z, затем space/backspace/enter/esc. Храним edge для + * каждой клавиши, поэтому зажатая буква не заполняет имя автоповтором. */ +static const uint8_t hof_key_code[30] = { + 0x1C, 0x32, 0x21, 0x23, 0x24, 0x2B, 0x34, 0x33, 0x43, + 0x3B, 0x42, 0x4B, 0x3A, 0x31, 0x44, 0x4D, 0x15, 0x2D, + 0x1B, 0x2C, 0x3C, 0x2A, 0x1D, 0x22, 0x35, 0x1A, + KBD_SPACE, 0x66, KBD_ENTER, KBD_ESC +}; + +static uint8_t hof_name_tick(hof_entry_t *entry, uint8_t *length, + uint8_t *previous) +{ + uint8_t i; + + for (i = 0; i < 30; i++) { + uint8_t down = kbd_raw_down(hof_key_code[i]); + if (down && !previous[i]) { + if (i < 26 && *length < HOF_NAME_SIZE - 1) { + entry->name[(*length)++] = (char)('A' + i); + entry->name[*length] = 0; + } else if (i == 26 && *length && *length < HOF_NAME_SIZE - 1) { + entry->name[(*length)++] = ' '; + entry->name[*length] = 0; + } else if (i == 27 && *length) { + entry->name[--(*length)] = 0; + } else if (i == 28 || i == 29) { + return 1; + } + } + previous[i] = down; + } + return 0; +} + +static void hof_edit(uint8_t row) +{ + uint8_t length = 0, previous[30], i; + hof_entry_t *entry = &hof_table[row]; + + for (i = 0; i < sizeof(previous); i++) previous[i] = 0; + hof_wait_release(); + for (;;) { + kbd_raw_sync(); + if (hof_name_tick(entry, &length, previous)) { + if (!length) strcpy(entry->name, "PLAYER"); + hof_save(); + hof_wait_release(); + return; + } + hof_draw(row); + } +} + +void pop_hof_show(void) __banked +{ + uint8_t row; + + pop_sfx_pause(); + hof_load(); + row = hof_insert_current(); + + pop_pal_story_load(); + pop_pal_apply(4); + hof_draw(row); + pop_pal_fade_in(HOF_FADE); + + if (row != HOF_NO_ROW) { + hof_edit(row); + } else { + hof_wait_release(); + while (!kbd_raw_any_down()) { + kbd_raw_sync(); + gfx_wait_vsync(); + } + hof_wait_release(); + } + pop_pal_fade_out(HOF_FADE); +} + +void pop_hof_show_title(void) __banked +{ + /* Один keypress должен пропускать всю цепочку title/intro, а не внезапно + * останавливаться на уже известной таблице. */ + if (kbd_raw_any_down()) return; + + hof_load(); + if (!hof_has_entries()) return; + pop_sfx_pause(); + pop_pal_story_load(); + pop_pal_apply(4); + hof_draw(HOF_NO_ROW); + pop_pal_fade_in(HOF_FADE); + hof_wait_release(); + while (!kbd_raw_any_down()) { + kbd_raw_sync(); + gfx_wait_vsync(); + } + hof_wait_release(); + pop_pal_fade_out(HOF_FADE); +} diff --git a/applications/PoP/roomtest/pop_hof.h b/applications/PoP/roomtest/pop_hof.h new file mode 100644 index 0000000..faee361 --- /dev/null +++ b/applications/PoP/roomtest/pop_hof.h @@ -0,0 +1,15 @@ +/* pop_hof.h — таблица результатов после финала, банк 9 (FG12). */ +#ifndef POP_HOF_H +#define POP_HOF_H + +/* Показать Hall of Fame, при подходящем результате запросить имя и сохранить + * POP.HOF. Ошибка или повреждение файла означают пустую таблицу; gameplay и + * последующий title от этого не зависят. */ +void pop_hof_show(void) __banked; + +/* Показать уже существующую непустую таблицу между credits и demo. Новая + * запись здесь не создаётся; удержанная клавиша title/intro пропускает экран + * целиком. */ +void pop_hof_show_title(void) __banked; + +#endif /* POP_HOF_H */ diff --git a/applications/PoP/roomtest/pop_intro.c b/applications/PoP/roomtest/pop_intro.c new file mode 100644 index 0000000..b56947e --- /dev/null +++ b/applications/PoP/roomtest/pop_intro.c @@ -0,0 +1,1383 @@ +/* + * pop_intro.c — вторая половина show_title(): story + Princess/Jaffar (FG8). + * + * Готовые сцены создаёт pop_pack_intro.py из TITLE/PV SDLPoP. Каждая сцена + * хранится пятью полосами <=49 строк, поэтому EMM удерживает лишь текущую + * полосу. pop_cutscene_t остаётся независимым от графики: этот модуль только + * исполняет его события и позднее будет образцом для pre-level сцен FG9. + */ +#include +#include +#include +#include +#include +#include "pop_bg.h" +#include "pop_shadow.h" +#include "pop_sfx.h" +#include "pop_cutscene.h" +#include "pop_intro.h" +#include "pop_pal.h" +#include "pop_timer.h" +#include "pop_ui.h" +#include "pop_kid.h" /* Char/окна, play_seq — движок персонажей катсцен */ +#include "pop_guard.h" /* Guard, CHARID_*, pop_gframe */ + +#define INTRO_PARTS 5 +#define INTRO_FADE 4 +#define PV_ANIM_TICKS 1959 + +/* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422). Музыка сюжета + * (50/52/53) в наборе пустая — её у нас нет; эти два ЕСТЬ и в оригинале + * звучат именно здесь: створка ворот закрывается, затем открывается дверь + * покоев, из которой входит Джафар. */ +#define PV_SND_GATE_CLOSING 4 +#define PV_SND_DOOR_OPENING 51 + +/* Шкала ниже повторяет порядок pv_scene() SDLPoP. Обычный кадр cutscene + * длится 8 тиков, после начала заклинания — 7. Длинные речевые паузы здесь + * заданы явно: в DOS они заканчиваются по окончании CBL-сэмпла. */ +enum { + PV_WAIT_END = 360, + PV_GATE_END = 408, + PV_DOOR_END = 432, + PV_TURN_START = PV_DOOR_END, + PV_WALK1_START = 472, + PV_DIALOG1_START = 520, + PV_WALK2_START = 696, + PV_DIALOG2_START = 936, + PV_RAISE_START = 1216, + PV_STEPBACK_START = 1223, + PV_MAGIC_START = 1342, + PV_EXIT_START = 1490, + PV_GLASS_DONE = 1700, + PV_SLUMP_START = 1763 +}; + +enum { + INTRO_STORY_ABSENCE = 0, + INTRO_STORY_MARRY, + INTRO_STORY_CREDITS, + INTRO_STORY_HAIL, + INTRO_STORY_FRAME, + INTRO_PV_WAITING, + INTRO_PV_JAFFAR, + INTRO_PV_MAGIC, + INTRO_PV_ALONE, + INTRO_PV_STAND, + INTRO_PV_LYING, + INTRO_PV_MOUSE, + INTRO_PV_SHORT +}; + +enum { + INTRO_PAL_STORY = 0, + INTRO_PAL_PV = 1 +}; + +/* 0..4 — TITLE/res41 + тексты 42/43/45/44 и чистая рамка; 5..12 — + * PV/res951 + персонажи. + * 8.3-имена намеренно короткие: DSS не получает длинные path-компоненты. */ +static const char * const intro_part[13][INTRO_PARTS] = { + { "PV\\s0_0.atl", "PV\\s0_1.atl", "PV\\s0_2.atl", "PV\\s0_3.atl", "PV\\s0_4.atl" }, + { "PV\\s1_0.atl", "PV\\s1_1.atl", "PV\\s1_2.atl", "PV\\s1_3.atl", "PV\\s1_4.atl" }, + { "PV\\s2_0.atl", "PV\\s2_1.atl", "PV\\s2_2.atl", "PV\\s2_3.atl", "PV\\s2_4.atl" }, + { "PV\\s3_0.atl", "PV\\s3_1.atl", "PV\\s3_2.atl", "PV\\s3_3.atl", "PV\\s3_4.atl" }, + { "PV\\s4_0.atl", "PV\\s4_1.atl", "PV\\s4_2.atl", "PV\\s4_3.atl", "PV\\s4_4.atl" }, + { "PV\\p0_0.atl", "PV\\p0_1.atl", "PV\\p0_2.atl", "PV\\p0_3.atl", "PV\\p0_4.atl" }, + { "PV\\p1_0.atl", "PV\\p1_1.atl", "PV\\p1_2.atl", "PV\\p1_3.atl", "PV\\p1_4.atl" }, + { "PV\\p2_0.atl", "PV\\p2_1.atl", "PV\\p2_2.atl", "PV\\p2_3.atl", "PV\\p2_4.atl" }, + { "PV\\p3_0.atl", "PV\\p3_1.atl", "PV\\p3_2.atl", "PV\\p3_3.atl", "PV\\p3_4.atl" }, + { "PV\\p4_0.atl", "PV\\p4_1.atl", "PV\\p4_2.atl", "PV\\p4_3.atl", "PV\\p4_4.atl" }, + { "PV\\p5_0.atl", "PV\\p5_1.atl", "PV\\p5_2.atl", "PV\\p5_3.atl", "PV\\p5_4.atl" }, + { "PV\\p6_0.atl", "PV\\p6_1.atl", "PV\\p6_2.atl", "PV\\p6_3.atl", "PV\\p6_4.atl" }, + { "PV\\p7_0.atl", "PV\\p7_1.atl", "PV\\p7_2.atl", "PV\\p7_3.atl", "PV\\p7_4.atl" } +}; + +/* Чистая Princess-room без персонажей. Shadow-копии обеих страниц содержат + * именно этот фон: каждый анимационный кадр получает его accelerator-копией, + * а полупрозрачные actor-спрайты не оставляют следа. */ +static const char * const pv_base_part[INTRO_PARTS] = { + "PV\\b0_0.atl", "PV\\b0_1.atl", "PV\\b0_2.atl", "PV\\b0_3.atl", "PV\\b0_4.atl" +}; + +/* Две текстовые половины show_title(). Между ними PV теперь идёт отдельным + * покадровым renderer-ом, а не четырьмя полными статичными композициями. */ +static const pop_cs_cmd_t intro_before_pv[] = { + /* absence собирается прямо поверх последнего title-кадра полосовым + * transition_ltr(), как в SDLPoP; оба экрана используют title.pal. */ + { POP_CS_WAIT, 0x258 }, /* story 1: In the absence */ + { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; + +/* Файловый fallback сохраняет возможность пройти title на неполном HDD. + * Нормальная ветка его не вызывает: p0..p3 оставлены также для диагностики + * точек сюжета и не являются анимационным ресурсом. */ +static const pop_cs_cmd_t intro_pv_fallback[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, + { POP_CS_SHOW, INTRO_PV_WAITING }, + { POP_CS_FADE_IN, 0 }, + { POP_CS_WAIT, 0x1A }, /* princess, gate and door */ + { POP_CS_SHOW, INTRO_PV_JAFFAR }, + { POP_CS_WAIT, 0x6D }, /* Jaffar comes/walks */ + { POP_CS_SHOW, INTRO_PV_MAGIC }, + { POP_CS_FLASH, 5 }, /* hourglass appears */ + { POP_CS_WAIT, 0x2F }, + { POP_CS_SHOW, INTRO_PV_ALONE }, + { POP_CS_WAIT, 0x3D }, /* Jaffar leaves, princess looks */ + { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; + +static const pop_cs_cmd_t intro_after_pv[] = { + { POP_CS_PALETTE, INTRO_PAL_STORY }, + { POP_CS_SHOW, INTRO_STORY_MARRY }, + { POP_CS_FADE_IN, 0 }, + { POP_CS_WAIT, 0x78 }, + { POP_CS_SHOW, INTRO_STORY_CREDITS }, + { POP_CS_WAIT, 0x168 }, + { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; + +/* Статические fallback-сценарии хранят уже физические кадры Sprinter. + * Один логический кадр cutscene SDLPoP длится 6/60 с; на 50 Гц это ровно + * 5/50 с. Поэтому суммы proc_cutscene_frame() здесь умножены на пять. */ +static const pop_cs_cmd_t pre_2_6[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_STAND }, + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 130 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +static const pop_cs_cmd_t pre_4[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_LYING }, + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 130 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +static const pop_cs_cmd_t pre_8[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_MOUSE }, + /* 20 + 20 + 20 логических кадров = 6,0 с. */ + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 300 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +static const pop_cs_cmd_t pre_9[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_MOUSE }, + /* 5 + 9 + 58 логических кадров = 7,2 с. */ + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 360 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +static const pop_cs_cmd_t pre_12_short[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_SHORT }, + /* 2 + 24 логических кадра = те же 2,6 с, что и длинная ветка. */ + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 130 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +/* time_expired() SDLPoP: hourglass_state=7, fade in, 2+100 кадров, fade + * out. P7 — готовая стадия комнаты с часами; звук намеренно не стартует, + * так как CBL на lifecycle screen погашен до дисковых чтений. */ +static const pop_cs_cmd_t time_expired_script[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_SHORT }, + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 102 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +/* end_sequence_anim() — объятие/мышь — здесь сведён к отдельной PV-стадии; + * затем end_sequence() SDLPoP показывает HAIL ровно 900 тиков. */ +/* Хвост финала после живой сцены встречи: только исходный story-экран. */ +static const pop_cs_cmd_t ending_hail_script[] = { + { POP_CS_PALETTE, INTRO_PAL_STORY }, { POP_CS_SHOW, INTRO_STORY_HAIL }, + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 900 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; +static const pop_cs_cmd_t ending_script[] = { + { POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_MOUSE }, + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 54 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_PALETTE, INTRO_PAL_STORY }, { POP_CS_SHOW, INTRO_STORY_HAIL }, + { POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 900 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; + +static uint8_t intro_scene; +static uint8_t intro_pending_pal; +static uint8_t intro_pal_pending; + +/* Таблицы intro_part/pv_base_part расположены в этом же банке 11. Общий + * pop_screen_blit_atlas_strips находится в банке 9, и передавать ему + * указатель на W3-данные нельзя: после bcall тот же адрес уже видит другой + * физический банк. Держим короткий полосовой loader рядом с его таблицами. + * atlas_load сам временно занимает W3 только ПОСЛЕ open(path). */ +static int intro_blit_atlas_strips(const char * const *parts, + uint8_t count, int x, int y) +{ + uint8_t part; + + gfx_set_bank(GFX_BANK_NORMAL); + for (part = 0; part < count; part++) { + atlas_t at; + const uint8_t *img; + + if (atlas_load(&at, parts[part]) != 0) return -1; + gfx_w0_map(at.page); + img = (const uint8_t *)atlas_image(&at, 0); + gfx_blit(x, y, img); + y += img[2]; + gfx_w0_unmap(); + atlas_free(&at); + } + return 0; +} + +/* ---- «Пропустить сцену» — по НОВОМУ нажатию, а не по удержанию -------- * + * + * Клавиша, которой закончили уровень (движение Кида в дверь, чит Shift+L, + * skip предыдущего экрана), в момент старта сцены ЕЩЁ ЗАЖАТА. Считать это + * за skip нельзя: сцена схлопывается в один кадр, и её никто не видит. + * Ждать отпускания безусловно тоже нельзя — потерянный break-код вешает + * игру намертво (BUGS_OPEN.md#kbd-stuck-wait). + * + * Поэтому детектор ФРОНТА: пока хоть что-то зажато со старта, skip не + * взводится; как только всё отпущено, детектор «взводится» и следующее + * нажатие сцену прерывает. Залипший бит в худшем случае делает сцену + * непропускаемой — она доиграет сама, а игра не встанет. */ +typedef struct { uint8_t armed; } intro_skip_t; + +static void intro_skip_begin(intro_skip_t *sk) +{ + kbd_raw_sync(); + sk->armed = (uint8_t)!kbd_raw_any_down(); +} + +static uint8_t intro_skip_requested(intro_skip_t *sk) +{ + kbd_raw_sync(); + if (!kbd_raw_any_down()) { sk->armed = 1; return 0; } + return sk->armed; +} + +static int intro_draw_page(uint8_t scene, uint8_t page) +{ + if (scene >= sizeof(intro_part) / sizeof(intro_part[0])) return -1; + gfx_set_draw_page(page); + gfx_set_bank(GFX_BANK_NORMAL); + pop_screen_fill(BLACK); + if (intro_blit_atlas_strips(intro_part[scene], INTRO_PARTS, + 0, POP_YOFF) != 0) + return -1; + return 0; +} + +static int intro_draw(uint8_t scene) +{ + uint8_t page = pop_screen_begin(); + + if (intro_draw_page(scene, page) != 0) return -1; + pop_screen_present(page); + return 0; +} + +/* У gfx_pal_fload/sync палитра меняется по 64 цвета за вызов BIOS. Если в + * этот момент хоть одна страница содержит следующую сцену, один VSync может + * показать её уже яркой, ещё до штатного fade-in. Поэтому перед сменой + * источника палитры уничтожаем растр на ОБЕИХ страницах, а не только гасим + * их палитры. */ +static void intro_black_pages(void) +{ + gfx_set_draw_page(0); + gfx_set_bank(GFX_BANK_NORMAL); + pop_screen_fill(BLACK); + gfx_set_draw_page(1); + pop_screen_fill(BLACK); + pop_screen_present(1); +} + +/* Для смены полного источника палитры сначала гасим старую сцену и + * оставляем на видимой странице настоящий чёрный растр. */ +static void intro_prepare_palette(uint8_t pal) +{ + intro_pending_pal = pal; + intro_pal_pending = 1; + pop_pal_apply(4); + intro_black_pages(); +} + +static void intro_commit_palette(void) +{ + if (intro_pending_pal == INTRO_PAL_PV) pop_pal_pv_load(); + else pop_pal_story_load(); + pop_pal_apply(4); + intro_pal_pending = 0; +} + +static int intro_draw_new_palette(uint8_t scene) +{ + uint8_t page; + + /* Палитра загружается, пока обе страницы содержат только чёрный растр. + * После повторного затемнения можно собрать новую сцену в скрытую + * страницу и показать её исключительно чёрной перед fade-in. */ + intro_commit_palette(); + page = pop_screen_begin(); + if (intro_draw_page(scene, page) != 0) return -1; + pop_screen_present(page); + return 0; +} + +/* В отличие от intro_draw_page() здесь base собирается в shadow ОБЕИХ + * страниц. После show page 1 palette 0 не видна и в неё безопасно загрузить + * PV\pv.pal; первый animated frame соберётся в page 0. */ +static int intro_pv_draw_base(void) +{ + gfx_rect_t full; + + gfx_set_draw_page(0); + gfx_set_bank(GFX_BANK_NORMAL); + pop_screen_fill(BLACK); + if (intro_blit_atlas_strips(pv_base_part, INTRO_PARTS, + 0, POP_YOFF) != 0) + return -1; + gfx_set_draw_page(1); + full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; + gfx_copy_page(&full, GFX_COPY_DIRECT); + pop_screen_present(1); + return 0; +} + +static void pv_blit_floor(const atlas_t *at, uint8_t frame, int x, int floor) +{ + const uint8_t *img; + + gfx_w0_map(at->page); + img = (const uint8_t *)atlas_image(at, frame); + /* add_*table() оригинала задаёт нижнюю строку спрайта включительно. */ + gfx_blit(x, POP_YOFF + floor - img[2] + 1, img); + gfx_w0_unmap(); +} + +static void pv_blit_actor(const atlas_t *at, uint8_t frame, int anchor_x, + int floor, uint8_t flip) +{ + const uint8_t *img; + int x = anchor_x; + + gfx_w0_map(at->page); + img = (const uint8_t *)atlas_image(at, frame); + /* draw_mid SDLPoP зеркалит правое направление относительно hot-point: + * экранный x сначала масштабирован, затем из него вычитается width. */ + if (flip) x -= img[0]; + gfx_blit_cols(x, POP_YOFF + floor - img[2] + 1, img, flip); + gfx_w0_unmap(); +} + +static void pv_draw_stars(const uint8_t *phase) +{ + static const uint8_t star_x[] = { 20, 16, 23, 17, 24, 18 }; + static const uint8_t star_y[] = { 97, 104, 110, 116, 120, 128 }; + static const uint8_t star_color[] = { 8, 7, 15, 15, 7 }; + uint8_t i; + + for (i = 0; i < sizeof(star_x); i++) { + /* draw_rect SDLPoP получает полуинтервал [x,x+1)×[y,y+1). + * BGI bar() включает правую/нижнюю границу и давал звезду 2x2. */ + putpixel(star_x[i], POP_YOFF + star_y[i], star_color[phase[i]]); + } +} + +/* У Princess/Jaffar номер frame — это индекс frame_tbl_cuts, а картинка + * лежит в отдельном chtab. Эти таблицы — компактная нужная часть таблицы + * SDLPoP: не приходится тащить в банк весь interpreter SEQTBL. */ +typedef struct { + int16_t x; /* Char.x оригинала: полу-пиксели */ + uint8_t frame; + uint8_t left; + uint8_t visible; + uint8_t step; +} pv_actor_t; + +/* frame_tbl_cuts[1..18].image; 10 — служебная пустая запись. */ +static const uint8_t pv_princess_image[19] = { + 0, 15, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 9, 10, 11, 12, 13, 14, 16 +}; +static const int8_t pv_princess_dx[19] = { + 0, 0, 0, 0, 0, -1, 2, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +static const uint8_t pv_princess_parity[19] = { + 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0 +}; + +/* frame_tbl_cuts[48..85].dx. image равен frame - 48. */ +static const int8_t pv_jaffar_dx[38] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 3, 3, 2, 3, 5, 5, 1, 2, 2, 1, 1, 2, 3, 3, 0, 2, 2, 1 +}; +static const uint8_t pv_jaffar_parity[38] = { + 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0 +}; +static const uint8_t pv_walk_frame[6] = { 48, 49, 50, 51, 52, 53 }; +static const int8_t pv_walk_dx[6] = { 1, 2, 6, 1, -1, 1 }; +static const uint8_t pv_raise_frame[] = { + 85, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 83, 84, 76 +}; + +static int pv_actor_x(const pv_actor_t *actor, int frame_dx, uint8_t parity) +{ + int x = actor->x; + int logical; + + if (actor->left) x -= frame_dx; + else x += frame_dx; + logical = x * 2 - 116; + /* load_frame_to_obj: нечётная фаза зависит от flags кадра и direction. + * chtab Princess/Jaffar затем проходит calc_screen_x_coord(320/280). */ + if (actor->left == parity) ++logical; + return logical * 8 / 7; +} + +static void pv_walk_step(pv_actor_t *jaffar) +{ + uint8_t step = (uint8_t)(jaffar->step % 6); + int dx = pv_walk_dx[step]; + + if (jaffar->left) jaffar->x -= dx; + else jaffar->x += dx; + jaffar->frame = pv_walk_frame[step]; + ++jaffar->step; +} + +static void pv_princess_step(pv_actor_t *princess, uint16_t tick) +{ + uint16_t pos; + + /* Palert: 2..9, flip, dx(8), 11. Она продолжается и во время входа + * Jaffar, как последовательность Palert оригинала. */ + if (tick >= PV_TURN_START && tick < PV_TURN_START + 72 && + ((tick - PV_TURN_START) & 7) == 0) { + pos = (uint16_t)((tick - PV_TURN_START) >> 3); + if (pos < 8) princess->frame = (uint8_t)(2 + pos); + else { + princess->left = 0; + princess->x += 8; + princess->frame = 11; + } + } + + /* Pstepback: flip, dx(11), 12; затем 13..16 и вечный кадр 17. */ + if (tick >= PV_STEPBACK_START && tick < PV_STEPBACK_START + 42 && + ((tick - PV_STEPBACK_START) % 7) == 0) { + pos = (uint16_t)((tick - PV_STEPBACK_START) / 7); + if (pos == 0) { + princess->left = 1; + princess->x -= 11; + princess->frame = 12; + } else if (pos == 1) { + princess->x -= 1; + princess->frame = 13; + } else if (pos == 2) { + princess->x -= 1; + princess->frame = 14; + } else if (pos == 3) { + princess->x -= 3; + princess->frame = 15; + } else if (pos == 4) { + princess->x -= 1; + princess->frame = 16; + } else { + princess->frame = 17; + } + } + + /* Pslump: один переходный frame 1, затем frame 18 до fade-out. */ + if (tick == PV_SLUMP_START) princess->frame = 1; + else if (tick == PV_SLUMP_START + 7) princess->frame = 18; +} + +static void pv_jaffar_exit_step(pv_actor_t *jaffar) +{ + uint8_t pos = jaffar->step++; + + if (pos < 6) { + jaffar->frame = (uint8_t)(77 + pos); + } else if (pos < 12) { + if (pos == 6) --jaffar->x; + jaffar->frame = 54; + } else if (pos < 22) { + uint8_t frame = (uint8_t)(57 + pos - 12); + + if (frame == 62) jaffar->x -= 2; + else if (frame == 63) ++jaffar->x; + else if (frame == 64) jaffar->x += 3; + else if (frame == 66) ++jaffar->x; + jaffar->frame = frame; + } else if (pos == 22) { + /* Vexit заканчивается flip, dx(16), dx(3), затем Vwalk2. */ + jaffar->left = 0; + jaffar->x += 19; + jaffar->frame = 49; + } else { + /* Vexit прыгает на метку Vwalk2: кадр 49 уже выведен в ветке + * flip, следующий шаг обязан начинаться с dx(6), frame 50. */ + uint8_t walk = (uint8_t)(pos - 23 + 2); + int dx = pv_walk_dx[walk % 6]; + + jaffar->x += dx; + jaffar->frame = pv_walk_frame[walk % 6]; + } +} + +static void pv_jaffar_step(pv_actor_t *jaffar, uint16_t tick) +{ + uint16_t pos; + + if (tick == PV_WALK1_START || tick == PV_WALK2_START) jaffar->step = 0; + if (((tick >= PV_WALK1_START && tick < PV_DIALOG1_START) || + (tick >= PV_WALK2_START && tick < PV_DIALOG2_START)) && + ((tick - ((tick < PV_DIALOG1_START) ? PV_WALK1_START : + PV_WALK2_START)) & 7) == 0) { + jaffar->visible = 1; + pv_walk_step(jaffar); + } + + /* Vstop: dx(1), 55, 56, Vstand. */ + if (tick == PV_DIALOG1_START || tick == PV_DIALOG2_START) { + --jaffar->x; + jaffar->frame = 55; + } else if (tick == PV_DIALOG1_START + 8 || tick == PV_DIALOG2_START + 8) { + jaffar->frame = 56; + } else if ((tick >= PV_DIALOG1_START + 16 && tick < PV_WALK2_START) || + (tick >= PV_DIALOG2_START + 16 && tick < PV_RAISE_START)) { + jaffar->frame = 54; + } + + if (tick >= PV_RAISE_START && tick < PV_EXIT_START && + ((tick - PV_RAISE_START) % 7) == 0) { + pos = (uint16_t)((tick - PV_RAISE_START) / 7); + if (pos < sizeof(pv_raise_frame)) jaffar->frame = pv_raise_frame[pos]; + else jaffar->frame = 76; + } + + if (tick == PV_EXIT_START) jaffar->step = 0; + if (tick >= PV_EXIT_START && ((tick - PV_EXIT_START) % 7) == 0) { + pv_jaffar_exit_step(jaffar); + if (!jaffar->left && jaffar->x > 205) jaffar->visible = 0; + } +} + +static void pv_blit_jaffar(const atlas_t *j0, const atlas_t *j1, + const atlas_t *j2, const pv_actor_t *jaffar) +{ + uint8_t image; + int x; + + if (!jaffar->visible || jaffar->frame < 48 || jaffar->frame > 85) return; + image = (uint8_t)(jaffar->frame - 48); + x = pv_actor_x(jaffar, pv_jaffar_dx[image], pv_jaffar_parity[image]); + if (image < 13) + pv_blit_actor(j0, image, x, 166, (uint8_t)!jaffar->left); + else if (image < 24) + pv_blit_actor(j1, (uint8_t)(image - 13), x, 166, + (uint8_t)!jaffar->left); + else + pv_blit_actor(j2, (uint8_t)(image - 24), x, 166, + (uint8_t)!jaffar->left); +} + +static void pv_blit_princess(const atlas_t *princess, const pv_actor_t *actor) +{ + uint8_t frame = actor->frame; + + if (frame == 0 || frame > 18) return; + pv_blit_actor(princess, pv_princess_image[frame], + pv_actor_x(actor, pv_princess_dx[frame], + pv_princess_parity[frame]), + 166, (uint8_t)!actor->left); +} + +/* Внутренняя шкала сценария пока остаётся в исходных тиках 60 Гц: так + * сохраняются точки начала реплик и последовательностей. Внешне каждый + * такой логический кадр показывается четыре физических кадра Sprinter. */ +static uint8_t pv_seq_period(uint16_t tick) +{ + if (tick < PV_WAIT_END) return 6; + if (tick < PV_RAISE_START) return 8; + return 7; +} + +static void intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0, + const atlas_t *j1, const atlas_t *j2, + const atlas_t *hourglass, const atlas_t *torch, + const pv_actor_t *p, const pv_actor_t *j, + const uint8_t *star_phase, + uint8_t torch_left, uint8_t torch_right, + uint8_t sand_frame, uint8_t flash, + uint16_t tick) +{ + gfx_rect_t full; + uint8_t back = (uint8_t)(gfx_get_visible_page() ^ 1); + + gfx_set_draw_page(back); + full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; + gfx_copy_page(&full, GFX_COPY_DIRECT); + gfx_set_bank(GFX_BANK_SPRITE); + /* y=116 в princess_room_torch — нижняя строка, не верх спрайта. */ + pv_blit_floor(torch, torch_left, 93, 116); + pv_blit_floor(torch, torch_right, 211, 116); + pv_draw_stars(star_phase); + pv_blit_princess(princess, p); + pv_blit_jaffar(j0, j1, j2, j); + if (tick >= PV_MAGIC_START) { + /* В SDLPoP hourglass_sandflow=0 означает первый кадр потока, + * а -1 — отсутствие песка. Корпус появляется ещё при молнии. */ + pv_blit_floor(hourglass, + (uint8_t)(tick < PV_GLASS_DONE ? 1 : 2), 152, 168); + } + if (tick >= PV_EXIT_START) { + /* hourglass_sandflow=0 оригинал ставит лишь после окончания + * conjuring — когда Jaffar опускает руки и начинает уходить. */ + pv_blit_floor(hourglass, (uint8_t)(8 + sand_frame), 160, 164); + } + /* draw_princess_room_bg() SDLPoP помещает image 2 chtab PV в + * foretable с x=30*8, ybottom=167. В нашем h0 это кадр 0 (res952): + * его надо повторить ПОСЛЕ актёров, иначе Jaffar проходит перед + * правой передней колонной. */ + pv_blit_floor(hourglass, 0, 240, 167); + gfx_wait_vsync(); + gfx_set_visible_page(back); + if (flash) { + uint8_t pulse; + + /* Молния намеренно живёт на физической, а не cutscene-шкале: + * пять быстрых white/black импульсов, актёры на это время стоят. */ + for (pulse = 0; pulse < 5; pulse++) { + pop_pal_flash_white(1); + gfx_wait_vsync(); + pop_pal_flash_white(0); + gfx_wait_vsync(); + } + } else { + uint8_t hold; + + /* Номинальные 50/4 = 12,5 FPS. Если сама сборка кадра пересекла + * лишний фронт, фактическая частота естественно станет ниже; точный + * anchor-based pacing оставлен отдельным TODO. */ + for (hold = 1; hold < 4; hold++) gfx_wait_vsync(); + } +} + +/* Сцена pv_scene() SDLPoP: ожидание Princess с факелами/звёздами, поворот, + * вход и речь Jaffar, заклинание+молния, часы с песком, уход. Тайминг + * воспроизводится на кадровой шкале; пропуск проверяется каждый кадр. */ +static int intro_pv_animated(void) +{ + atlas_t princess, jaffar0, jaffar1, jaffar2, hourglass, torch; + pv_actor_t princess_actor; + pv_actor_t jaffar_actor; + uint16_t tick; + uint16_t frame_end; + uint16_t render_tick; + uint8_t anim_step = 0; + uint8_t torch_left = 1; + uint8_t torch_right = 6; + uint8_t star_phase[6] = { 1, 1, 1, 1, 1, 1 }; + uint8_t sand_frame = 0; + uint8_t flash_done = 0; + uint8_t flash; + intro_skip_t skip; + int result = -1; + + intro_prepare_palette(INTRO_PAL_PV); + /* commit обязан быть до сборки base: gfx_pal_sync обновляет и видимую + * страницу, поэтому там в этот момент должен оставаться только чёрный + * растр из intro_prepare_palette(). */ + intro_commit_palette(); + if (intro_pv_draw_base() != 0) return -1; + + /* b0_* собирается полосами: на время очередной полосы ей тоже нужна + * временная EMM-страница. Поэтому фон готовится до постоянных + * actor-атласов: порядок сохраняет независимость загрузчика полос от + * числа одновременно живущих анимационных ресурсов. */ + if (atlas_load(&princess, "PV\\a0.atl") != 0) return -1; + if (atlas_load(&jaffar0, "PV\\j0.atl") != 0) { + goto free_princess; + } + if (atlas_load(&jaffar1, "PV\\j1.atl") != 0) { + goto free_jaffar0; + } + if (atlas_load(&jaffar2, "PV\\j2.atl") != 0) { + goto free_jaffar1; + } + if (atlas_load(&hourglass, "PV\\h0.atl") != 0) { + goto free_jaffar2; + } + if (atlas_load(&torch, "PV\\t0.atl") != 0) { + goto free_hourglass; + } + + pop_pal_fade_in(INTRO_FADE); + /* Все атласы сцены уже в EMM: дальше файлового ввода-вывода нет, и + * насос CBL можно открывать (иначе ESTEX не даёт долить блок — см. + * контракт pop_sfx_start). Полноэкранная accel-копия кадра насосу не + * мешает: leaf режет DI бандами по 16 строк. */ + (void)pop_sfx_start(); + result = 0; + princess_actor.x = 120; + princess_actor.frame = 11; /* Pstand, facing left */ + princess_actor.left = 1; + princess_actor.visible = 1; + princess_actor.step = 0; + jaffar_actor.x = 198; + jaffar_actor.frame = 54; /* Vstand, behind the closed door */ + jaffar_actor.left = 1; + jaffar_actor.visible = 0; + jaffar_actor.step = 0; + tick = 0; + intro_skip_begin(&skip); + while (tick < PV_ANIM_TICKS) { + if (intro_skip_requested(&skip)) { + result = 1; + break; + } + + /* Один проход = один логический кадр cutscene. Внутри проходим все + * его исходные 6/8/7 тиков, снаружи draw_frame держит результат + * четыре физических кадра — единые номинальные 12,5 FPS. */ + flash = 0; + frame_end = (uint16_t)(tick + pv_seq_period(tick)); + if (frame_end > PV_ANIM_TICKS) frame_end = PV_ANIM_TICKS; + do { + pv_princess_step(&princess_actor, tick); + pv_jaffar_step(&jaffar_actor, tick); + if (tick == PV_MAGIC_START && !flash_done) { + flash = 1; + flash_done = 1; + } + /* Границы шкалы уже стоят там, где оригинал ЖДЁТ окончания + * очередного сэмпла, — заявляем эффект ровно на них. */ + if (tick == PV_WAIT_END) pop_sfx_play(PV_SND_GATE_CLOSING); + else if (tick == PV_GATE_END) pop_sfx_play(PV_SND_DOOR_OPENING); + ++tick; + } while (tick < frame_end); + + { + uint8_t star = (uint8_t)(anim_step % 6); + + torch_left = (uint8_t)((torch_left + 1) % 9); + torch_right = (uint8_t)((torch_right + 1) % 9); + star_phase[star] = (uint8_t)((star_phase[star] + 1) % 5); + if (tick > PV_EXIT_START) + sand_frame = (uint8_t)((sand_frame + 1) % 3); + ++anim_step; + } + render_tick = (uint16_t)(tick - 1); + intro_pv_draw_frame(&princess, &jaffar0, &jaffar1, &jaffar2, + &hourglass, &torch, &princess_actor, + &jaffar_actor, star_phase, torch_left, + torch_right, sand_frame, flash, render_tick); + pop_sfx_tick(); /* как в оригинале — в конце отрисовки кадра */ + } + pop_sfx_pause(); /* дальше снова только загрузка файлов */ + if (!result) pop_pal_fade_out(INTRO_FADE); + +free_torch: + atlas_free(&torch); +free_hourglass: + atlas_free(&hourglass); +free_jaffar2: + atlas_free(&jaffar2); +free_jaffar1: + atlas_free(&jaffar1); +free_jaffar0: + atlas_free(&jaffar0); +free_princess: + atlas_free(&princess); + return result; +} + +/* hourglass_frame() SDLPoP: четыре границы оставшегося времени выбирают + * один из кадров 6..2. В h0.atl номер кадра совпадает с hourglass_state. */ +static uint8_t pre_hourglass_state(void) +{ + static const uint8_t bound[] = { 6, 17, 33, 65 }; + uint8_t i; + + for (i = 0; i < sizeof(bound); i++) + if (bound[i] > pop_timer_minutes) break; + return (uint8_t)(6 - i); +} + +/* cutscene_2_6()/cutscene_4(): 26 логических кадров по 100 мс. У SDLPoP + * это 6/60 секунды, у Sprinter — 5/50, то есть 130 физических кадров. + * Статична только поза Princess; draw_princess_room_bg оригинала + * каждый логический кадр заново обновляет факелы, звёзды, корпус часов и + * одну из трёх фаз сыплющегося песка. */ +static int pre_princess_animated(pop_pre_cutscene_t scene) +{ + atlas_t hourglass, torch; + gfx_rect_t full; + uint8_t picture = (uint8_t)(scene == POP_PRE_4 ? + INTRO_PV_LYING : INTRO_PV_STAND); + uint8_t state = pre_hourglass_state(); + uint8_t torch_left = 1, torch_right = 6, sand = 0, frame; + uint8_t star_phase[6] = { 1, 1, 1, 1, 1, 1 }; + intro_skip_t skip; + int result = -1; + + intro_prepare_palette(INTRO_PAL_PV); + intro_commit_palette(); + /* Обе shadow-страницы содержат чистую статическую сцену. Динамика ниже + * рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */ + if (intro_draw_page(picture, 0) != 0 || + intro_draw_page(picture, 1) != 0) return -1; + if (atlas_load(&hourglass, "PV\\h0.atl") != 0) return -1; + if (atlas_load(&torch, "PV\\t0.atl") != 0) goto free_hourglass_pre; + + pop_screen_present(1); + pop_pal_fade_in(INTRO_FADE); + result = 0; + full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; + intro_skip_begin(&skip); + for (frame = 0; frame < 26; frame++) { + uint8_t back, hold, star; + + if (intro_skip_requested(&skip)) { result = 1; break; } + back = (uint8_t)(gfx_get_visible_page() ^ 1); + gfx_set_draw_page(back); + gfx_copy_page(&full, GFX_COPY_DIRECT); + gfx_set_bank(GFX_BANK_SPRITE); + pv_blit_floor(&torch, torch_left, 93, 116); + pv_blit_floor(&torch, torch_right, 211, 116); + pv_draw_stars(star_phase); + pv_blit_floor(&hourglass, state, 152, 168); + pv_blit_floor(&hourglass, (uint8_t)(8 + sand), 160, 164); + pv_blit_floor(&hourglass, 0, 240, 167); /* передняя колонна */ + gfx_wait_vsync(); + gfx_set_visible_page(back); + /* Первый тик уже потрачен на commit: ещё четыре = 5/50 с. */ + for (hold = 1; hold < 5; hold++) gfx_wait_vsync(); + + torch_left = (uint8_t)((torch_left + 1) % 9); + torch_right = (uint8_t)((torch_right + 1) % 9); + sand = (uint8_t)((sand + 1) % 3); + star = (uint8_t)(frame % 6); + star_phase[star] = (uint8_t)((star_phase[star] + 1) % 5); + } + if (!result) pop_pal_fade_out(INTRO_FADE); + + atlas_free(&torch); +free_hourglass_pre: + atlas_free(&hourglass); + return result; +} + +/* SDLPoP transition_ltr(): подготовленная story-страница копируется на + * видимый title узкими вертикальными полосами слева направо. У оригинала + * 2px/120 FPS; здесь 4px/60 FPS даёт ту же длительность (~1.3 с), не + * удваивая число полных копий 320x256 в ограниченном bank 9. */ +static int intro_story_transition(void) +{ + gfx_rect_t stripe; + uint8_t visible = gfx_get_visible_page(); + uint8_t target = (uint8_t)(visible ^ 1); + int x; + + if (intro_draw_page(INTRO_STORY_ABSENCE, target) != 0) return -1; + gfx_set_draw_page(visible); /* source gfx_copy_page = target */ + stripe.y = 0; + stripe.h = GFX_HEIGHT; + for (x = 0; x < GFX_WIDTH; x += 4) { + if (kbd_raw_any_down()) { + stripe.x = 0; stripe.w = GFX_WIDTH; + gfx_copy_page(&stripe, GFX_COPY_DIRECT); + break; + } + stripe.x = x; + stripe.w = (uint16_t)((GFX_WIDTH - x < 4) ? GFX_WIDTH - x : 4); + gfx_copy_page(&stripe, GFX_COPY_DIRECT); + gfx_wait_vsync(); + } + return 0; +} + +static void intro_flash(uint16_t ticks) +{ + uint8_t page; + + page = pop_screen_begin(); + pop_screen_fill(WHITE); + pop_screen_present(page); + while (ticks--) gfx_wait_vsync(); + /* Вспышка ничего не меняет в сценарии: вернуть ту же собранную стадию. */ + intro_draw(intro_scene); +} + +static void intro_restore_game_palette(void) +{ + /* Обе страницы — именно чёрный растр до gfx_pal_fload/sync. Иначе + * синхронизация palette 1 способна на один кадр высветить credits перед + * переходом в demo. */ + intro_black_pages(); + pop_pal_game_load(); + pop_pal_apply(4); +} + +/* ================= ПЕРСОНАЖИ КАТСЦЕН ================================= * + * + * Порт cutscene_8 / cutscene_9 (seg001:034D/03B7) и end_sequence_anim + * (seg001:041C). Все три идут через load_intro(1, …): фон — комната + * принцессы (та же b0_*, что у intro), а поверх играют ДВА персонажа. + * + * Движок общий с игрой, и это главное: последовательности крутит наш + * play_seq (pop_kid_play/pop_guard_play), кадры принцессы берутся из + * таблицы КАТСЦЕН (kid_data.bin, KID_BIN_CFRAMES_OFF), мышь и Кид — из + * таблицы Кида, как в оригинале (frame_table_kid общая для kid и mouse). + * Отличается только ОТРИСОВКА: спрайты принцессы лежат в PV-атласах, а + * Кид/мышь — в игровых kid-атласах, поэтому в pv.pal под них отведена + * своя палитровая строка 0x70. + * + * Слоты — как в оригинале: Kid-окно занимает мышь (или Кид в финале), + * Guard-окно — принцесса. + */ + +/* Набор PV2 (res901..930) не влезает в одну EMM: image 0..16 в a1, + * 17..29 в a2. Номер кадра при этом остаётся исходным image-id. */ +#define CUT_PV2_SPLIT 17 +/* Один логический кадр катсцены = 5 vsync, как у остальных PV-сцен + * (cutscene_frame_time SDLPoP; см. pre_princess_animated). */ +#define CUT_FRAME_VSYNC 5 + +static atlas_t cut_pv1, cut_pv2a, cut_pv2b, cut_hg, cut_torch; +static uint8_t cut_atlases; /* сколько успели загрузить — столько и вернём */ +/* Детектор фронта живёт на ВСЮ сцену: она склеена из нескольких cut_run, и + * взводить его заново на каждом отрезке значило бы ловить одно удержание + * несколько раз. */ +static intro_skip_t cut_skip; +/* Фазы «живого» фона: пламя двух факелов, песок и мерцание звёзд. Счётчики + * общие на всю сцену — она склеена из нескольких cut_run. */ +static uint8_t cut_torch_l, cut_torch_r, cut_sand, cut_star_phase[6]; +static uint8_t cut_hg_state; + +static void cut_free(void) +{ + if (cut_atlases > 4) atlas_free(&cut_torch); + if (cut_atlases > 3) atlas_free(&cut_hg); + if (cut_atlases > 2) atlas_free(&cut_pv2b); + if (cut_atlases > 1) atlas_free(&cut_pv2a); + if (cut_atlases > 0) atlas_free(&cut_pv1); + cut_atlases = 0; +} + +static int cut_load(void) +{ + cut_atlases = 0; + if (atlas_load(&cut_pv1, "PV\\a0.atl") != 0) return -1; + cut_atlases = 1; + if (atlas_load(&cut_pv2a, "PV\\a1.atl") != 0) { cut_free(); return -1; } + cut_atlases = 2; + if (atlas_load(&cut_pv2b, "PV\\a2.atl") != 0) { cut_free(); return -1; } + cut_atlases = 3; + /* Комната принцессы живёт и в катсценах: пламя факелов, звёзды в окне, + * песок в часах — и ПЕРЕДНЯЯ КОЛОННА, которая по оригиналу закрывает + * персонажей (draw_princess_room_bg + foretable). */ + if (atlas_load(&cut_hg, "PV\\h0.atl") != 0) { cut_free(); return -1; } + cut_atlases = 4; + if (atlas_load(&cut_torch, "PV\\t0.atl") != 0) { cut_free(); return -1; } + cut_atlases = 5; + return 0; +} + +/* Экранная X кадра: та же математика, что у актёров intro — logical + * координата (x*2 − 116) с нечётной фазой по flags кадра, затем + * calc_screen_x_coord (×8/7). */ +static int cut_screen_x(const pop_char_t *ch, const kframe *fr) +{ + int x = ch->x; + int logical; + uint8_t left = (uint8_t)(ch->direction < 0); + + if (left) x -= fr->dx; + else x += fr->dx; + logical = x * 2 - 116; + if (left == (uint8_t)((fr->flags & 0x80) != 0)) ++logical; + return logical * 8 / 7; +} + +/* Принцесса: набор выбирает поле sword кадра (>>6: 1 = PV1 res800, + * 2 = PV2 res900) — ровно как chtab_base + (cur_frame.sword >> 6) + * в seg008:1759. */ +static void cut_blit_princess(const pop_char_t *ch, const kframe *fr) +{ + const atlas_t *at; + uint8_t idx; + + if (fr->image == 255 || cut_atlases < 3) return; + if ((uint8_t)(fr->sword >> 6) >= 2) { + if (fr->image < CUT_PV2_SPLIT) { at = &cut_pv2a; idx = fr->image; } + else { at = &cut_pv2b; idx = (uint8_t)(fr->image - CUT_PV2_SPLIT); } + } else { + at = &cut_pv1; idx = fr->image; + } + pv_blit_actor(at, idx, cut_screen_x(ch, fr), + (int)ch->y + fr->dy, (uint8_t)(ch->direction >= 0)); +} + +/* Кид и мышь — спрайты chtab_2 из игровых атласов (kidp[]). Атласы Кида + * хранятся column-major (ради бесплатного флипа), поэтому блит колоночный. */ +static void cut_blit_kid(const pop_char_t *ch, const kframe *fr) +{ + const uint8_t *img; + uint8_t pg, idx, flip; + int x; + + if (fr->image == 255) return; + pg = (uint8_t)(fr->image >> 3); + idx = (uint8_t)(fr->image & 7); + if (pg >= kid_npages) return; + flip = (uint8_t)(ch->direction >= 0); + x = cut_screen_x(ch, fr); + gfx_w0_map(kidp[pg].page); + img = (const uint8_t *)atlas_image(&kidp[pg], idx); + if (flip) x -= img[0]; + gfx_blit_cols(x, POP_YOFF + (int)ch->y + fr->dy - img[2] + 1, img, flip); + gfx_w0_unmap(); +} + +/* Сменить последовательность персонажа в его окне (seqtbl_offset_kid_char / + * seqtbl_offset_shad_char оригинала). */ +static void cut_kid_seq(uint8_t seq) +{ + pop_loadkid(); pop_char_set_seq(seq); pop_savekid(); +} + +static void cut_shad_seq(uint8_t seq) +{ + pop_loadshad(); pop_char_set_seq(seq); pop_saveshad(); +} + +/* Поставить персонажа так, как это делают init_* оригинала: позиция, + * направление и стартовая последовательность, затем первый play_seq. */ +static void cut_kid_init(uint8_t charid, uint8_t x, uint8_t y, uint8_t seq) +{ + Kid.charid = charid; + Kid.x = x; + Kid.y = y; + Kid.direction = -1; /* dir_FF_left */ + Kid.alive = -1; + Kid.frame = 0; + Kid.fall_x = Kid.fall_y = 0; + Kid.repeat = 0; + cut_kid_seq(seq); + pop_kid_play(); +} + +static void cut_shad_init(uint8_t x, uint8_t y, uint8_t seq) +{ + Guard.charid = CHARID_5_PRINCESS; + Guard.x = x; + Guard.y = y; + Guard.direction = -1; + Guard.alive = -1; + Guard.frame = 0; + Guard.fall_x = Guard.fall_y = 0; + Guard.repeat = 0; + cut_shad_seq(seq); + pop_guard_play(); +} + +/* proc_cutscene_frame(n): прокрутить n логических кадров, каждый — шаг + * последовательности обоих персонажей плюс отрисовка. Возврат 1 — игрок + * прервал сцену (любое НОВОЕ нажатие). */ +static uint8_t cut_run(uint16_t frames, uint8_t kid_visible) +{ + gfx_rect_t full; + uint16_t i; + uint8_t hold; + + full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; + for (i = 0; i < frames; i++) { + uint8_t back; + kframe kid_fr, pr_fr; + + if (intro_skip_requested(&cut_skip)) return 1; + + /* Кадр забираем СРАЗУ после play каждого слота. load_frame кладёт + * его в кэш ПО charid активного персонажа: у Кида это kid_frame, у + * всех прочих — общий pop_gframe. В Kid-окне здесь сидит МЫШЬ + * (charid 24), то есть её кадр уходит в pop_gframe и затирается + * следом кадром принцессы, а kid_frame держит позу игрового Кида. + * Читать кэши в конце кадра значило рисовать мышь спрайтом Кида и + * терять принцессу — ровно это и было видно в сценах 8/9. */ + pop_kid_play(); /* мышь либо Кид */ + kid_fr = (Kid.charid == CHARID_0_KID) ? kid_frame : pop_gframe; + pop_guard_play(); /* принцесса */ + pr_fr = pop_gframe; + + back = (uint8_t)(gfx_get_visible_page() ^ 1); + gfx_set_draw_page(back); + /* Чистый фон обеих страниц лежит в shadow-копии; персонажи рисуются + * банком спрайтов и потому в неё не оседают. */ + gfx_copy_page(&full, GFX_COPY_DIRECT); + gfx_set_bank(GFX_BANK_SPRITE); + pv_blit_floor(&cut_torch, cut_torch_l, 93, 116); + pv_blit_floor(&cut_torch, cut_torch_r, 211, 116); + pv_draw_stars(cut_star_phase); + pv_blit_floor(&cut_hg, cut_hg_state, 152, 168); + pv_blit_floor(&cut_hg, (uint8_t)(8 + cut_sand), 160, 164); + cut_blit_princess(&Guard, &pr_fr); + if (kid_visible) cut_blit_kid(&Kid, &kid_fr); + /* ПОСЛЕ персонажей: передняя колонна — передний слой комнаты, она + * обязана закрывать и принцессу, и мышь (иначе мышь «просвечивает» + * сквозь неё — поймано пользователем 2026-08-25). */ + pv_blit_floor(&cut_hg, 0, 240, 167); + gfx_set_bank(GFX_BANK_NORMAL); + gfx_wait_vsync(); + gfx_set_visible_page(back); + for (hold = 1; hold < CUT_FRAME_VSYNC; hold++) gfx_wait_vsync(); + + cut_torch_l = (uint8_t)((cut_torch_l + 1) % 9); + cut_torch_r = (uint8_t)((cut_torch_r + 1) % 9); + cut_sand = (uint8_t)((cut_sand + 1) % 3); + { + uint8_t star = (uint8_t)(i % 6); + cut_star_phase[star] = (uint8_t)((cut_star_phase[star] + 1) % 5); + } + } + return 0; +} + +/* Общая обвязка всех трёх сцен: чистый фон комнаты принцессы в обе + * страницы, PV-палитра, актёрские атласы. Возврат -1 — ресурсов нет + * (неполный HDD), и caller откатывается на статический сценарий. */ +static int cut_begin(void) +{ + uint8_t i; + + intro_skip_begin(&cut_skip); + cut_torch_l = 1; cut_torch_r = 6; cut_sand = 0; + for (i = 0; i < 6; i++) cut_star_phase[i] = 1; + cut_hg_state = pre_hourglass_state(); + intro_prepare_palette(INTRO_PAL_PV); + intro_commit_palette(); + if (intro_pv_draw_base() != 0) return -1; + if (cut_load() != 0) return -1; + return 0; +} + +static void cut_end(uint8_t aborted) +{ + if (!aborted) pop_pal_fade_out(INTRO_FADE); + cut_free(); +} + +/* cutscene_8 (seg001:034D): мышь сидит рядом с принцессой, встаёт и + * убегает; принцесса поднимается ей вслед. */ +static int cut_scene_8(void) +{ + uint8_t aborted; + + if (cut_begin() != 0) return -1; + /* init_mouse_cu8 (seg001:0212) = init_mouse_go + x=144 + seq_106. */ + cut_kid_init(CHARID_24_MOUSE, 144, 167, 106); + /* princess_crouching (seg001:024D): сидит и гладит мышь. */ + cut_shad_init(131, 169, 110); + pop_pal_fade_in(INTRO_FADE); + aborted = cut_run(20, 1); + if (!aborted) { + cut_kid_seq(107); /* мышь встаёт и уходит */ + aborted = cut_run(20, 1); + } + if (!aborted) { + cut_shad_seq(111); /* принцесса встаёт */ + aborted = cut_run(20, 1); + } + cut_end(aborted); + return 0; +} + +/* cutscene_9 (seg001:03B7): принцесса стоит, мышь прибегает, принцесса + * приседает к ней. */ +static int cut_scene_9(void) +{ + uint8_t aborted; + + if (cut_begin() != 0) return -1; + /* princess_stand (seg001:026A) — поза PV1, лицом вправо. */ + cut_shad_init(144, 169, 94); + Guard.direction = 0; + pop_pal_fade_in(INTRO_FADE); + /* init_mouse_go (seg001:022A): мышь вбегает справа. */ + cut_kid_init(CHARID_24_MOUSE, 199, 167, 105); + aborted = cut_run(5, 1); + if (!aborted) { + cut_shad_seq(112); /* принцесса приседает */ + aborted = cut_run(9, 1); + } + if (!aborted) { + cut_kid_seq(114); /* мышь останавливается */ + aborted = cut_run(58, 1); + } + cut_end(aborted); + return 0; +} + +/* end_sequence_anim (seg001:041C): Кид вбегает к принцессе, она + * поворачивается и обнимает его; следом появляется мышь. */ +static int cut_ending(void) +{ + uint8_t aborted; + + if (cut_begin() != 0) return -1; + /* init_ending_princess (seg001:0251) + init_ending_kid (seg001:0287). */ + cut_shad_init(136, 164, 109); + cut_kid_init(CHARID_0_KID, 198, 164, 1); + pop_pal_fade_in(INTRO_FADE); + pop_sfx_play(26); /* «arrived to princess» */ + aborted = cut_run(8, 1); + if (!aborted) { + cut_shad_seq(108); /* поворот и объятие */ + aborted = cut_run(5, 1); + } + if (!aborted) { + cut_kid_seq(13); /* Кид останавливается */ + aborted = cut_run(2, 1); + } + /* Kid.frame = 0 (seg001:0348): дальше Кида не видно — он в объятиях, + * и рисуется только принцесса. */ + if (!aborted) aborted = cut_run(39, 0); + if (!aborted) { + /* init_mouse_1 (seg001:02D5): мышь приходит к паре. */ + cut_kid_init(CHARID_24_MOUSE, 197, 164, 105); + aborted = cut_run(9, 1); + } + if (!aborted) { + cut_kid_seq(101); /* мышь встаёт на задние лапы */ + aborted = cut_run(41, 1); + } + cut_end(aborted); + return 0; +} + +static uint8_t intro_run(const pop_cs_cmd_t *script) +{ + pop_cutscene_t cs; + intro_skip_t skip; + uint16_t arg; + uint8_t aborted = 0; + + pop_cutscene_begin(&cs, script); + intro_skip_begin(&skip); + for (;;) { + pop_cs_event_t ev; + + if (intro_skip_requested(&skip)) { + pop_cutscene_abort(&cs); + aborted = 1; + break; + } + ev = pop_cutscene_tick(&cs, &arg); + if (ev == POP_CS_EVENT_END) break; + if (ev == POP_CS_EVENT_PALETTE) intro_prepare_palette((uint8_t)arg); + else if (ev == POP_CS_EVENT_SHOW) { + intro_scene = (uint8_t)arg; + if ((intro_pal_pending ? intro_draw_new_palette(intro_scene) : + intro_draw(intro_scene)) != 0) { + pop_cutscene_abort(&cs); + aborted = 1; + break; + } + } else if (ev == POP_CS_EVENT_FADE_IN) pop_pal_fade_in(INTRO_FADE); + else if (ev == POP_CS_EVENT_FADE_OUT) pop_pal_fade_out(INTRO_FADE); + else if (ev == POP_CS_EVENT_FLASH) intro_flash(arg); + gfx_wait_vsync(); + } + + return aborted; +} + +uint8_t pop_intro_show(void) __banked +{ + int pv_result; + + /* Title уже заглушил CBL; оставляем это явным lifecycle-правилом, если + * intro позднее будет вызвано отдельно (например, из debug-маршрута). */ + pop_sfx_pause(); + if (intro_story_transition() != 0) { + /* Ошибка файла не должна оставить старый title без выхода: менее + * эффектный, но корректный fallback остаётся обычным fade-in. */ + intro_prepare_palette(INTRO_PAL_STORY); + if (intro_draw_new_palette(INTRO_STORY_ABSENCE) == 0) + pop_pal_fade_in(INTRO_FADE); + } + if (intro_run(intro_before_pv)) { + intro_restore_game_palette(); + return 1; + } + pv_result = intro_pv_animated(); + if (pv_result > 0) { + intro_restore_game_palette(); + return 1; + } + if (pv_result < 0 && intro_run(intro_pv_fallback)) { + intro_restore_game_palette(); + return 1; + } + if (intro_run(intro_after_pv)) { + intro_restore_game_palette(); + return 1; + } + /* Abort возможен в любой WAIT и не оставляет загруженных атласов: каждая + * полоса освобождена внутри intro_draw. */ + intro_restore_game_palette(); + return 0; +} + +void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked +{ + const pop_cs_cmd_t *script = 0; + + pop_sfx_pause(); + if (scene == POP_PRE_2_6 || scene == POP_PRE_4 || scene == POP_PRE_12) { + if (pre_princess_animated(scene) >= 0) { + intro_restore_game_palette(); + return; + } + /* Неполный HDD: статический fallback сохраняет корректные 130 тиков. */ + } + /* Сцены 8/9 — живая анимация мыши и принцессы (cutscene_8/cutscene_9). */ + if (scene == POP_PRE_8 || scene == POP_PRE_9) { + int done = (scene == POP_PRE_8) ? cut_scene_8() : cut_scene_9(); + if (done == 0) { + intro_restore_game_palette(); + return; + } + } + if (scene == POP_PRE_2_6 || scene == POP_PRE_12) script = pre_2_6; + else if (scene == POP_PRE_4) script = pre_4; + else if (scene == POP_PRE_8) script = pre_8; + else if (scene == POP_PRE_9) script = pre_9; + else if (scene == POP_PRE_12_SHORT) script = pre_12_short; + if (!script) return; + + intro_run(script); + intro_restore_game_palette(); +} + +void pop_time_expired_show(void) __banked +{ + pop_sfx_pause(); + intro_run(time_expired_script); + intro_restore_game_palette(); +} + +void pop_ending_show(void) __banked +{ + pop_sfx_pause(); + /* Экран гасим ПЕРВЫМ действием — как load_intro оригинала, который + * начинается с draw_rect(&screen_rect, color_0_black). Иначе загрузка + * ресурсов сцены идёт поверх ЖИВОЙ комнаты уровня 14, и она «мелькает» + * перед встречей. */ + pop_pal_apply(4); + /* Встреча с принцессой — живая сцена (end_sequence_anim). Дальше по + * скрипту идёт только текст HAIL, поэтому статический сценарий ниже + * начинается с него; при нехватке ресурсов отыгрывается целиком. */ + if (cut_ending() == 0) intro_run(ending_hail_script); + else intro_run(ending_script); + intro_restore_game_palette(); +} diff --git a/applications/PoP/roomtest/pop_intro.h b/applications/PoP/roomtest/pop_intro.h new file mode 100644 index 0000000..e25eb4f --- /dev/null +++ b/applications/PoP/roomtest/pop_intro.h @@ -0,0 +1,23 @@ +/* pop_intro.h — story/PV intro после оригинальной заставки (FG8). */ +#ifndef POP_INTRO_H +#define POP_INTRO_H + +#include "pop_flow.h" + +/* Показать absence -> Princess/Jaffar -> marry -> credits. Возврат 1 — + * пользователь прервал цепочку и надо сразу начать FIRST_LEVEL; 0 — intro + * завершилось штатно и далее запускается attract-demo. */ +uint8_t pop_intro_show(void) __banked; + +/* Показать одну pre-level сцену 2/4/6/8/9/12. NONE — no-op. После любого + * skip возвращается затемнённая игровая палитра, далее caller грузит уровень. */ +void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked; + +/* Отдельная сцена истёкшего времени (PV hourglass), после которой автомат + * возвращается в TITLE. Любой raw-ввод сокращает ожидание. */ +void pop_time_expired_show(void) __banked; + +/* Финал level 14: Princess room, затем исходный story-screen HAIL. */ +void pop_ending_show(void) __banked; + +#endif /* POP_INTRO_H */ diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index 73ba773..fb487fb 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -17,6 +17,7 @@ #include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */ #include "pop_map.h" /* pop_clip_char_top (clip_char) */ #include "pop_geom.h" +#include "pop_level.h" /* POP_LEVEL_LAST — level 15 исключён */ #include #include #include @@ -86,6 +87,17 @@ static const uint8_t *kd_gframe_ptr(uint8_t i) off += (uint16_t)(KD_DATA_OFF + KID_BIN_GFRAMES_OFF); return (const uint8_t *)off; } + +/* ...и для таблицы кадров КАТСЦЕН (frame_tbl_cuts): по ней ходят Princess и + * Vizier в сценах 8/9, финале и intro. Индекс — номер кадра как есть; какой + * набор спрайтов рисовать, говорит поле sword (>>6: 1 = PV1, 2 = PV2). */ +static const uint8_t *kd_cframe_ptr(uint8_t i) +{ + uint16_t off = (uint16_t)i; + off += (uint16_t)(off << 2); + off += (uint16_t)(KD_DATA_OFF + KID_BIN_CFRAMES_OFF); + return (const uint8_t *)off; +} #define KD_SEQ(cs) (*((const uint8_t *)(KD_DATA_OFF + KID_BIN_SEQTBL_OFF) + \ (uint16_t)((cs) - SEQTBL_BASE))) /* Байт seqtbl по адресу оригинала (база SEQTBL_BASE); ТОЛЬКО под замапленной @@ -198,7 +210,10 @@ static void load_frame(void) const uint8_t *f; uint8_t use_guard_tbl = pop_frame_tbl_is_guard(Char.charid, Char.frame); f = 0; - if (use_guard_tbl) { + if (Char.charid == CHARID_5_PRINCESS || Char.charid == CHARID_6_VIZIER) { + /* seg006:538 — у персонажей КАТСЦЕН своя таблица. */ + if (Char.frame < KID_NCFRAMES) f = kd_cframe_ptr(Char.frame); + } else if (use_guard_tbl) { int16_t idx = (int16_t)Char.frame; if (idx >= 102 && idx < 107) idx += 70; idx -= 149; @@ -344,11 +359,11 @@ void play_seq(void) } break; case 0xF1: /* END_LEVEL: seq_70 доиграл — уровень пройден. - * В оригинале это `++next_level`, а главный цикл - * (play_level_2, seg003:0386) выходит, когда next_level - * разошёлся с current_level. У нас так же: счётчик, а - * решение — снаружи (roomtest.c). */ - pop_next_level++; + * До 13-го это тот же `++next_level`, что в оригинале. + * Level 15/copy protection вырезан: 14-й позже передаст + * управление ENDING (FG11), но уже сейчас не может + * породить запрос res2015.bin. */ + if (pop_next_level < POP_LEVEL_LAST) pop_next_level++; break; case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета * (порт play_seq -> proc_get_object, seg006) */ diff --git a/applications/PoP/roomtest/pop_level.h b/applications/PoP/roomtest/pop_level.h index d2f1bf2..5c359c9 100644 --- a/applications/PoP/roomtest/pop_level.h +++ b/applications/PoP/roomtest/pop_level.h @@ -30,9 +30,17 @@ void pop_level_free(void) __banked; /* ---- Номер уровня и потабличные различия (levels_plan.md §1) -------- */ +/* Единый допустимый диапазон ресурсов. 0 зарезервирован под demo (FG7), + * 1..14 — настоящая игра; level 15/copy protection намеренно отсутствует. + * Эти границы используют загрузчик, QuickLoad и отладочная навигация, чтобы + * ни один путь не мог запросить несуществующий res2015.bin. */ +#define POP_LEVEL_DEMO 0 +#define POP_LEVEL_FIRST 1 +#define POP_LEVEL_LAST 14 + /* Порт current_level (seg000 load_lev_spr: current_level = next_level = - * level). Уровень 1..15; 0 — демо-уровень, в скоуп не входит. Читают - * потабличные функции ниже, стражи (HP/тип) и стартовая логика. */ + * level). Читают потабличные функции ниже, стражи (HP/тип) и стартовая + * логика. */ extern uint8_t pop_current_level; void pop_level_qsave(pop_qs_io_t *io); @@ -52,7 +60,7 @@ uint8_t pop_level_entry_pose(uint8_t n) __banked; uint8_t pop_level_guard_hp(uint8_t n) __banked; /* tbl_guard_type (data.h:844): 0 обычный, 1 толстый, 2 скелет, 3 визирь, - * 4 тень, −1 — стражей на уровне НЕТ (уровни 14/15). */ + * 4 тень, −1 — стражей на уровне НЕТ (уровень 14). */ int8_t pop_level_guard_type(uint8_t n) __banked; /* tbl_level_type (data.h:840): 0 = dungeon, 1 = palace. Пока читается diff --git a/applications/PoP/roomtest/pop_level_cold.c b/applications/PoP/roomtest/pop_level_cold.c index 30f145e..5ba3b67 100644 --- a/applications/PoP/roomtest/pop_level_cold.c +++ b/applications/PoP/roomtest/pop_level_cold.c @@ -28,27 +28,27 @@ void pop_level_free(void) __banked /* ---- Потабличные различия уровней ------------------------------------ */ -/* Таблицы из SDLPoP data.h:840..848, индекс = номер уровня (0 = демо). - * Держим целиком по 16 байт: короче и дешевле любых «оптимизаций», а - * расхождение с оригиналом сразу видно глазами. */ -static const uint8_t tbl_entry_pose[16] = - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 }; -static const uint8_t tbl_guard_hp[16] = - { 4, 3, 3, 3, 3, 4, 5, 4, 4, 5, 5, 5, 4, 6, 0, 0 }; -static const int8_t tbl_guard_type[16] = - { 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 4, 3, -1, -1 }; +/* Таблицы из SDLPoP data.h:840..848, индекс = номер уровня (0 = demo). + * Хвоста level 15 здесь намеренно нет: настоящий маршрут заканчивается 14. + * Так диапазон данных и диапазон загрузчика не могут разъехаться. */ +static const uint8_t tbl_entry_pose[POP_LEVEL_LAST + 1] = + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0 }; +static const uint8_t tbl_guard_hp[POP_LEVEL_LAST + 1] = + { 4, 3, 3, 3, 3, 4, 5, 4, 4, 5, 5, 5, 4, 6, 0 }; +static const int8_t tbl_guard_type[POP_LEVEL_LAST + 1] = + { 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 4, 3, -1 }; /* tbl_level_type (SDLPoP data.h:840): 0 — подземелье, 1 — дворец. По нему * расходятся не только атласы, но и ветки отрисовки (seg008), кладка стены * (wall_pattern) и модификатор пустой клетки от упавшей плиты * (remove_loose, seg007:0EB8). */ -static const uint8_t tbl_level_type[16] = - { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0 }; +static const uint8_t tbl_level_type[POP_LEVEL_LAST + 1] = + { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1 }; -uint8_t pop_level_entry_pose(uint8_t n) __banked { return n < 16 ? tbl_entry_pose[n] : 0; } -uint8_t pop_level_guard_hp(uint8_t n) __banked { return n < 16 ? tbl_guard_hp[n] : 3; } -int8_t pop_level_guard_type(uint8_t n) __banked { return n < 16 ? tbl_guard_type[n] : -1; } -uint8_t pop_level_tileset(uint8_t n) __banked { return n < 16 ? tbl_level_type[n] : 0; } -uint8_t pop_level_type(void) __banked { return tbl_level_type[pop_current_level & 15]; } +uint8_t pop_level_entry_pose(uint8_t n) __banked { return n <= POP_LEVEL_LAST ? tbl_entry_pose[n] : 0; } +uint8_t pop_level_guard_hp(uint8_t n) __banked { return n <= POP_LEVEL_LAST ? tbl_guard_hp[n] : 3; } +int8_t pop_level_guard_type(uint8_t n) __banked { return n <= POP_LEVEL_LAST ? tbl_guard_type[n] : -1; } +uint8_t pop_level_tileset(uint8_t n) __banked { return n <= POP_LEVEL_LAST ? tbl_level_type[n] : 0; } +uint8_t pop_level_type(void) __banked { return pop_level_tileset(pop_current_level); } static int level_read_file(const char *path) { @@ -83,7 +83,7 @@ int pop_level_load_num(uint8_t n) __banked static char alt[] = "a:\\res2000.bin"; /* цифры — [8],[9] */ uint8_t saved_blk = pop_lvl_blk, saved_page = pop_lvl_page, saved_ok = pop_lvl_ok; - if (n > 15) return -1; + if (n > POP_LEVEL_LAST) return -1; pri[12] = alt[8] = (char)('0' + n / 10); pri[13] = alt[9] = (char)('0' + n % 10); diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index a6a61ab..3a0f312 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -595,6 +595,15 @@ static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol) { uint8_t type = wall_type(tiletype); int coll_left, cx; + /* ОТКРЫТЫЕ ворота стеной НЕ являются (seg004:0A18: `if (tiletype == + * tiles_4_gate && !can_bump_into_gate()) return -1`). Без этой ветки + * get_edge_distance считал расстояние до створки как до стены и отдавал + * маленькое d с типом EDGE_WALL: осторожный шаг играл короткую + * последовательность (safe_step берёт seq по самому d), и к краю + * приходилось шагать ДВАЖДЫ. Возврат −1 уводит разбор на тайл впереди, + * где открытые ворота опознаются как обычный пол (d = 11, полный шаг). + * Поймано пользователем 2026-08-24 (подход слева направо). */ + if (tiletype == TILE_GATE && gate_passable(tcol, Char.curr_row)) return -1; if (type == 0) return 127; coll_left = pop_x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX; cx = char_front_coll(); @@ -1367,14 +1376,11 @@ void pop_proc_get_object(void) __banked * лечебных): красный кадр даёт общий механизм «Кид ранен» * (flash_if_hurt, seg003:0AFC) — у нас pop_kid_hurt. Раньше мы * ставили вспышку руками, то есть красили экран дважды. - * Спецсобытие уровня зелий: там синие склянки забирают ПОЛОВИНУ - * запаса, а не одну единицу. */ + * Level 15/copy protection отсутствует, поэтому синие склянки + * всегда забирают ровно одну единицу здоровья. */ pop_sfx_stop(); /* seg006:1893 — stop_sounds */ pop_sfx_play(13); - if (pop_current_level == POP_POTIONS_LEVEL) - hitp_delta = (int8_t)(-((hitp_max + 1) >> 1)); - else - hitp_delta = -1; + hitp_delta = -1; break; default: /* 6 «открыть все ворота» — TODO * вместе с его уровнем (13) */ @@ -3159,6 +3165,43 @@ static void chomped(uint8_t tilepos, int8_t col) if (Char.charid == CHARID_0_KID) pop_kid_dead = 1; } +/* check_gate_push (seg004:0833): ОПУСКАЮЩАЯСЯ решётка выталкивает персонажа + * из своего тайла. Без неё Кид, зашедший в тайл ворот при поднятой решётке, + * так и остаётся в нём после опускания — «в суперпозиции»: створка рисуется + * то перед ним, то за ним (слой выбирается по направлению взгляда), и уйти + * он может в обе стороны. Поймано пользователем 2026-08-24; то же и для + * попытки проползти под опускающейся решёткой в присяде. + * + * Условия оригинала — только позы, где персонаж стоит на месте и его можно + * сдвинуть, не ломая анимацию: РАЗВОРОТ (action 7), СТОЙКА (кадр 15) и + * кадры приседа 108..110. Решётка ищется в своём тайле И в тайле СЛЕВА: + * створка нарисована у ПРАВОЙ грани тайла, поэтому левый сосед давит так + * же. Перекрытие обязано быть обеими гранями и в этом кадре, и в прошлом + * (у оригинала `curr_row_coll_flags & prev_coll_flags == 0xFF`) — иначе + * толчок срабатывал бы на пролёте мимо. Сам толчок — 5 пикселей: ВЛЕВО, + * если решётка в тайле персонажа, ВПРАВО — если в левом соседе. */ +static void check_gate_push(void) +{ + int8_t orig_col = Char.curr_col, col, row = Char.curr_row; + uint8_t frame = Char.frame; + + if (Char.action != ACT_TURN && frame != 15 && + !(frame >= 108 && frame < 111)) return; + col = orig_col; + if (get_tile(col, row) != TILE_GATE) { + col--; + if (get_tile(col, row) != TILE_GATE) return; + } + /* Слот флагов годен только внутри ОКОН обоих кадров: вне их лежат + * значения прошлых кадров (та же оговорка, что в check_chomped_flags). */ + if (col < coll_lo || col > coll_hi || + col < coll_prev_lo || col > coll_prev_hi) return; + if ((coll_curr[COLL_IDX(col)] & coll_prev[COLL_IDX(col)]) != 3) return; + if (gate_passable(col, row)) return; /* can_bump_into_gate */ + pop_sfx_play(8); /* bumped_sound (seg004:05F1) */ + Char.x = (uint8_t)((int)Char.x + (orig_col <= col ? -5 : 5)); +} + /* check_chomped_kid (seg004:076B): по колонкам ряда — там, где персонаж * перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF, * у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (modif & 0x7F == 2), @@ -3221,6 +3264,7 @@ static void kid_phys(void) pop_dbg_p6(); /* ЗАМЕР: check_collisions целиком */ pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */ check_bumped(); /* удержать у стены до check_action (порядок PoP) */ + check_gate_push(); /* seg004: опустившаяся решётка выталкивает из тайла */ check_action(); pop_dbg_p7(); /* ЗАМЕР */ pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */ diff --git a/applications/PoP/roomtest/pop_menu.c b/applications/PoP/roomtest/pop_menu.c new file mode 100644 index 0000000..590e339 --- /dev/null +++ b/applications/PoP/roomtest/pop_menu.c @@ -0,0 +1,757 @@ +/* + * pop_menu.c — pause menu, шрифты и временный UI-слой, БАНК 9. + * + * Отрисовка принципиально идёт GFX_BANK_SPRITE: прозрачный цвет 0xFF + * пропускается, а пауза не становится частью фоновой ОЗУ-копии игры. + * Каждый кадр UI собирается на скрытой + * видеостранице, флипается целиком и затем стирается копией чистого фона. + * Поэтому при переходе между пунктами нет видимой построчной перерисовки. + * + * Шрифты — исходные hc_small_font_data/hc_font_data из SDLPoP, заранее + * переведённые toolchain/pop_extract_font.py в FONT\\FONT.ATL. Атлас в W0 + * нужен ещё и как временное хранилище снимка палитры: затемнение не тратит + * 1 КБ дефицитной W2 и всегда восстанавливает точные цвета уровня. + */ +#include +#include +#include +#include +#include +#include "pop_menu.h" +#include "pop_pal.h" +#include "pop_ui.h" +#include "pop_font.h" /* POP_FONT_*_ASCENT — раскладка считается по шрифту */ +#include "pop_bg.h" /* POP_YOFF/POP_PLAYFIELD_H — границы игровой области */ +#include "pop_cdraw.h" /* POP_HP_Y — нижнюю статус-полосу делим с HP */ +#include "pop_pace.h" +#include "pop_settings.h" +#include "pop_cfg.h" +#include "pop_qsave.h" +#include "pop_status.h" +#include "pop_sfx.h" + +/* --- ОБЩАЯ ВЕРТИКАЛЬНАЯ РАСКЛАДКА ------------------------------------- * + * + * Экран у нас 320x256, но ИГРА занимает только 200 строк: 192 поля тайлов + * плюс 8 строк полосы HP, начиная с POP_YOFF. Всё, что выше и ниже, — + * борта, и на реальном ТВ/мониторе они могут быть срезаны. Поэтому каждый + * экран UI центрируется в этой самой полосе, а не в 256 строках кадра, и + * обязан в неё помещаться целиком. + * + * Блок считается «сверху вниз»: UI_CENTER_TOP отдаёт строку, с которой + * начинается силуэт содержимого (не базовая линия!). Первая базовая линия + * = верх + ascent шрифта, дальше — шаги. */ +#define UI_AREA_TOP POP_YOFF +#define UI_AREA_H (POP_PLAYFIELD_H + 8) /* поле + полоса HP = 200 */ +#define UI_CENTER_TOP(h) (UI_AREA_TOP + (UI_AREA_H - (h)) / 2) +/* Отдельный вариант для экранов, у которых нижняя статус-полоса занята + * своим содержимым: центрируем только по полю тайлов. */ +#define UI_CENTER_FIELD(h) (UI_AREA_TOP + (POP_PLAYFIELD_H - (h)) / 2) + +/* Рамка выделения строки. Заглавные малого шрифта занимают РОВНО пять + * строк [base-ascent .. base-1], поэтому симметричный отступ считается от + * них, а не от базовой линии: SEL_PAD строк воздуха сверху и снизу плюс + * сама линия рамки. Раньше низ рамки брался «на глаз» (base+5), и она + * визуально сидела ниже текста. */ +#define SEL_PAD 3 +#define SEL_TOP_DY (POP_FONT_SMALL_ASCENT + SEL_PAD + 1) /* 9 */ +#define SEL_BOT_DY (SEL_PAD) /* 3 */ + +#define MENU_N_ITEMS 7 +#define MENU_STEP 14 +#define MENU_LIST_H (SEL_TOP_DY + (MENU_N_ITEMS - 1) * MENU_STEP + \ + SEL_BOT_DY + 1) +/* Центрируем СПИСОК по полю тайлов: GAME PAUSED — не пункт меню, а строка + * состояния программы, и в блок центрирования не входит. */ +#define MENU_FIRST_BASE (UI_CENTER_FIELD(MENU_LIST_H) + SEL_TOP_DY) +/* Оригинал пишет её в нижнюю статус-полосу, между делениями HP Кида слева и + * стража справа (SDLPoP rect_bottom_text = {193,70,202,250}, valign_bottom): + * базовая линия на строку ниже полосы, силуэт крупного шрифта ложится ровно + * на неё. */ +#define MENU_PAUSED_BASE (POP_YOFF + POP_HP_Y + 8) +#define MENU_SEL_L 115 +#define MENU_SEL_R 205 +#define MENU_BORDER 0x37 /* светло-серый VGA16, не затемняем */ + +#define UI_LEFT 45 +#define UI_RIGHT 274 +#define UI_VALUE_RIGHT 257 + +#define SETTINGS_N_ITEMS 9 +/* Три секции по одному заголовку: T -> GENERAL, i2 -> GAMEPLAY, + * i6 -> CONTROLS. Между заголовком и строками, как и между строками, — + * фиксированные шаги, поэтому вся высота выражается через них. */ +#define SET_STEP 14 +#define SET_HEAD_GAP 17 +#define SET_BLOCK_H (POP_FONT_BIG_ASCENT + 3 * SET_HEAD_GAP + \ + 9 * SET_STEP + SEL_BOT_DY + 1) +#define SET_TITLE_BASE (UI_CENTER_TOP(SET_BLOCK_H) + POP_FONT_BIG_ASCENT) +#define SET_HEAD1 (SET_TITLE_BASE + SET_HEAD_GAP) +#define SET_HEAD2 (SET_HEAD1 + 3 * SET_STEP + SET_HEAD_GAP) +#define SET_HEAD3 (SET_HEAD2 + 4 * SET_STEP + SET_HEAD_GAP) + +/* CONTROLS: число строк зависит от того, включены ли читы, поэтому верх + * блока считается в рантайме. */ +#define CTRL_TITLE_GAP 20 +#define CTRL_N_BASE 6 /* общие клавиши */ +#define CTRL_N_CHEAT 5 /* дополнительные строки читов */ +#define CTRL_COL_KEY 45 +#define CTRL_COL_DESC 145 + +/* ABOUT = ровно тот же текст, что на стартовом Sprinter screen, минус + * строка про клавиши. ABOUT_H — и высота блока, и смещение ПОСЛЕДНЕЙ + * базовой линии от его верха (совпадают, т.к. последняя строка малая). */ +#define ABOUT_GAP1 30 +#define ABOUT_GAP2 15 +#define ABOUT_H (POP_FONT_BIG_ASCENT + 2 * ABOUT_GAP1 + ABOUT_GAP2) +#define BUILD_KEYS_GAP 48 + +/* Подтверждение: рамка карточки, вопрос крупным, две кнопки. */ +#define CONF_BOX_H 66 +#define CONF_TOP UI_CENTER_TOP(CONF_BOX_H) +#define CONF_Q_BASE (CONF_TOP + 20) +#define CONF_BTN_BASE (CONF_TOP + 50) + +/* Уведомление: крупное сообщение + строка про клавиши. */ +#define NOTICE_GAP 44 +#define NOTICE_H (POP_FONT_BIG_ASCENT + NOTICE_GAP) +#define NOTICE_BASE (UI_CENTER_TOP(NOTICE_H) + POP_FONT_BIG_ASCENT) + +#define UI_KEYS_TEXT "ESC, ENTER OR SPACE: CONTINUE" + +/* SDLPoP menu.c: движение = loose_shake_2, подтверждение/Back = + * loose_shake_3, изменение значения = sword_vs_sword. Открытие pause menu + * намеренно без звука. */ +#define MENU_SND_CHANGE 10 +#define MENU_SND_MOVE 21 +#define MENU_SND_SELECT 22 +#define MENU_SND_NONE 0xFF + +/* Значения приходят из Makefile: ID — короткий git commit, DATE — дата + * этого коммита. Fallback оставляет исходник самостоятельным для host/IDE. */ +#ifndef POP_BUILD_ID +#define POP_BUILD_ID "DEV" +#endif +#ifndef POP_BUILD_DATE +#define POP_BUILD_DATE "UNKNOWN" +#endif + +/* Порядок = план §3. SETTINGS — вложенный экран в этом же bank 9. */ +static const uint8_t menu_cmds[MENU_N_ITEMS] = { + POP_MENU_RESUME, + POP_MENU_QUICKSAVE, + POP_MENU_QUICKLOAD, + POP_MENU_RESTART_LEVEL, + POP_MENU_SETTINGS, + POP_MENU_RESTART_GAME, + POP_MENU_QUIT +}; + +static const char * const menu_text[MENU_N_ITEMS] = { + "RESUME", + "QUICKSAVE (F6)", + "QUICKLOAD (F9)", + "RESTART LEVEL", + "SETTINGS", + "RESTART GAME", + "QUIT GAME" +}; + +/* Через bar, а не rectangle: у игры уже есть быстрый rectfill, тогда как + * rectangle тянет тяжёлый универсальный line-движок в и без того тесный W1. */ +static void menu_box(int left, int top, int right, int bottom, uint8_t color) +{ + setfillstyle(SOLID_FILL, color); + bar(left, top, right, top); + bar(left, bottom, right, bottom); + bar(left, top, left, bottom); + bar(right, top, right, bottom); +} + +/* Рамка выделения СТРОКИ: её вертикаль задаёт только базовая линия текста, + * поэтому ни один экран не считает отступы сам. */ +static void menu_sel_box(int left, int right, int baseline) +{ + menu_box(left, baseline - SEL_TOP_DY, right, baseline + SEL_BOT_DY, + MENU_BORDER); +} + +/* Весь UI собирается на скрытой странице. Источник gfx_copy_page — shadow + * видимой страницы, где оверлей отсутствует: все строки ниже идут через + * GFX_BANK_SPRITE. Один ui_end() = один готовый, tear-free кадр. */ +static uint8_t ui_begin(void) +{ + gfx_rect_t full; + uint8_t back = (uint8_t)(gfx_get_visible_page() ^ 1); + + /* Полностраничная accel-копия держит DI дольше половины CBL-буфера. + * Закрываем вывод, иначе железо повторяет старые 128 байт (скрежет). + * ui_end откроет его уже с заново заполненной тишиной. */ + pop_sfx_pause(); + gfx_set_draw_page(back); + full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; + gfx_copy_page(&full, GFX_COPY_DIRECT); + gfx_set_bank(GFX_BANK_SPRITE); + return back; +} + +static void ui_end(uint8_t back) +{ + pop_wait_edge(); /* готовый кадр показать только на фронте */ + gfx_set_visible_page(back); + (void)pop_sfx_start(); +} + +static void menu_sound(uint8_t id) +{ + if (id == MENU_SND_NONE) return; + pop_sfx_play(id); + pop_sfx_tick(); /* play_menu_sound: запуск немедленно */ +} + +static void ui_wait_release(void) +{ + while (kbd_raw_down(KBD_ENTER) || kbd_raw_down(KBD_ESC) || + kbd_raw_down(KBD_SPACE) || + kbd_raw_down(KBD_UP) || kbd_raw_down(KBD_DOWN) || + kbd_raw_down(KBD_LEFT) || kbd_raw_down(KBD_RIGHT)) + pop_wait_edge(); +} + +/* Действие = Enter ИЛИ Space: в оригинале выбор подтверждается обеими, и + * держать их врозь незачем — обе читаются одним фронтом. */ +static uint8_t ui_action_down(void) +{ + return (uint8_t)(kbd_raw_down(KBD_ENTER) || kbd_raw_down(KBD_SPACE)); +} + +/* Информационный экран уже нарисован — ждём Esc/Enter/Space по фронту. */ +static void ui_wait_ack(void) +{ + uint8_t esc_prev = 0, act_prev = 0; + + for (;;) { + uint8_t esc = kbd_raw_down(KBD_ESC); + uint8_t act = ui_action_down(); + if ((esc && !esc_prev) || (act && !act_prev)) { + ui_wait_release(); + return; + } + esc_prev = esc; act_prev = act; + pop_wait_edge(); + } +} + +static void menu_draw(uint8_t sel) +{ + uint8_t back = ui_begin(); + uint8_t i; + + /* Как в SDLPoP: только затемнённая замороженная сцена, компактный + * список и тонкий серый контур выбора, без непрозрачной карточки. */ + menu_sel_box(MENU_SEL_L, MENU_SEL_R, + MENU_FIRST_BASE + (int)sel * MENU_STEP); + + pop_text_map(); + for (i = 0; i < MENU_N_ITEMS; i++) + pop_text_center_mapped(POP_TEXT_SMALL, GFX_WIDTH / 2, + MENU_FIRST_BASE + (int)i * MENU_STEP, menu_text[i]); + pop_text_center_mapped(POP_TEXT_BIG, GFX_WIDTH / 2, MENU_PAUSED_BASE, + "GAME PAUSED"); + pop_text_unmap(); + ui_end(back); +} + +/* Явные (uint8_t) — не «на всякий случай»: SDCC 4.5 на константном + * ВЫРАЖЕНИИ больше 127 в инициализаторе uint8_t даёт warning 158, хотя + * значение помещается (на голом литерале 218 — молчит). */ +static const uint8_t settings_y[SETTINGS_N_ITEMS] = { + (uint8_t)(SET_HEAD1 + 1 * SET_STEP), (uint8_t)(SET_HEAD1 + 2 * SET_STEP), + (uint8_t)(SET_HEAD1 + 3 * SET_STEP), + (uint8_t)(SET_HEAD2 + 1 * SET_STEP), (uint8_t)(SET_HEAD2 + 2 * SET_STEP), + (uint8_t)(SET_HEAD2 + 3 * SET_STEP), (uint8_t)(SET_HEAD2 + 4 * SET_STEP), + (uint8_t)(SET_HEAD3 + 1 * SET_STEP), (uint8_t)(SET_HEAD3 + 2 * SET_STEP) +}; + +static const char * const speed_text[] = { "NORMAL", "FAST", "FASTEST" }; + +static void settings_line(uint8_t item, const char *name, const char *value, + uint8_t selected) +{ + int y = settings_y[item]; + if (selected) menu_sel_box(UI_LEFT, UI_RIGHT, y); + pop_text_draw_mapped(POP_TEXT_SMALL, 60, y, name); + if (value) pop_text_right_mapped(POP_TEXT_SMALL, UI_VALUE_RIGHT, y, value); +} + +static void settings_draw(uint8_t sel) +{ + uint8_t back = ui_begin(); + + pop_text_map(); + pop_text_center_mapped(POP_TEXT_BIG, GFX_WIDTH / 2, SET_TITLE_BASE, + "SETTINGS"); + pop_text_draw_mapped(POP_TEXT_SMALL, 60, SET_HEAD1, "GENERAL"); + settings_line(0, "SOUND", pop_settings.sound_enabled ? "ON" : "OFF", sel == 0); + settings_line(1, "DEBUG BAR", + pop_settings.show_debug_bar ? "ON" : "OFF", sel == 1); + settings_line(2, "RESTORE DEFAULTS...", 0, sel == 2); + pop_text_draw_mapped(POP_TEXT_SMALL, 60, SET_HEAD2, "GAMEPLAY"); + settings_line(3, "TIME LIMIT", + pop_settings.time_limit_enabled ? "60 MIN" : "UNLIMITED", + sel == 3); + settings_line(4, "SPEED", speed_text[pop_settings.speed_mode], sel == 4); + settings_line(5, "GAMEPLAY PROFILE", "VANILLA", sel == 5); + settings_line(6, "CHEATS", pop_settings.cheats_enabled ? "ON" : "OFF", sel == 6); + pop_text_draw_mapped(POP_TEXT_SMALL, 60, SET_HEAD3, "CONTROLS"); + settings_line(7, "SHOW KEY BINDINGS", 0, sel == 7); + settings_line(8, "ABOUT...", 0, sel == 8); + pop_text_unmap(); + ui_end(back); +} + +/* Две колонки «клавиша — что делает». Первые CTRL_N_BASE строк общие, за + * ними идут читовые: их показываем, только когда читы включены. */ +static const char * const ctrl_key[CTRL_N_BASE + CTRL_N_CHEAT] = { + "ARROWS", "SHIFT", "ESC", "F6 / F9", "CTRL+S", "P", + "K", "I", "SHIFT+L", "U", "F7 / F8" +}; + +static const char * const ctrl_desc[CTRL_N_BASE + CTRL_N_CHEAT] = { + "MOVE / JUMP", "ACTION", "PAUSE MENU", "SAVE / LOAD", "SOUND", "SPEED", + "KILL GUARD", "IMMORTAL", "NEXT LEVEL", "FLIP SCREEN", "TIME - / +" +}; + +static void controls_draw(void) +{ + uint8_t back = ui_begin(); + /* С выключенными читами вместо пяти строк идёт одна пояснительная — + * список короче, и блок обязан центрироваться по ФАКТИЧЕСКОЙ высоте. */ + uint8_t cheats = pop_settings.cheats_enabled; + uint8_t rows = (uint8_t)(cheats ? CTRL_N_BASE + CTRL_N_CHEAT + : CTRL_N_BASE + 1); + uint8_t i; + int top = UI_CENTER_TOP(POP_FONT_BIG_ASCENT + CTRL_TITLE_GAP + + (int)(rows - 1) * SET_STEP); + int y = top + POP_FONT_BIG_ASCENT; + + pop_text_map(); + pop_text_center_mapped(POP_TEXT_BIG, GFX_WIDTH / 2, y, "CONTROLS"); + y += CTRL_TITLE_GAP; + for (i = 0; i < rows; i++) { + const char *key = ctrl_key[i], *desc = ctrl_desc[i]; + if (!cheats && i == CTRL_N_BASE) { key = "CHEATS"; desc = "DISABLED"; } + pop_text_draw_mapped(POP_TEXT_SMALL, CTRL_COL_KEY, y, key); + pop_text_draw_mapped(POP_TEXT_SMALL, CTRL_COL_DESC, y, desc); + y += SET_STEP; + } + pop_text_unmap(); + ui_end(back); +} + +/* Текст экрана About — он же текст стартового Sprinter screen. Общий код + * ровно затем, чтобы эти два экрана не разъехались: пункт меню обязан + * показывать то же самое, только без строки про клавиши. */ +static void about_text(int top) +{ + int y = top + POP_FONT_BIG_ASCENT; + + pop_text_center_mapped(POP_TEXT_BIG, GFX_WIDTH / 2, y, "PRINCE OF PERSIA"); + y += ABOUT_GAP1; + pop_text_center_mapped(POP_TEXT_SMALL, GFX_WIDTH / 2, y, + "SPRINTER SP2000 BUILD"); + y += ABOUT_GAP1; + pop_text_center_mapped(POP_TEXT_SMALL, GFX_WIDTH / 2, y, + "BUILD " POP_BUILD_ID); + y += ABOUT_GAP2; + pop_text_center_mapped(POP_TEXT_SMALL, GFX_WIDTH / 2, y, POP_BUILD_DATE); +} + +static void about_draw(void) +{ + uint8_t back = ui_begin(); + + pop_text_map(); + about_text(UI_CENTER_TOP(ABOUT_H)); + pop_text_unmap(); + ui_end(back); +} + +static void confirm_draw(const char *question, uint8_t sel) +{ + uint8_t back = ui_begin(); + + menu_box(82, CONF_TOP, 238, CONF_TOP + CONF_BOX_H - 1, MENU_BORDER); + pop_text_map(); + pop_text_center_mapped(POP_TEXT_BIG, GFX_WIDTH / 2, CONF_Q_BASE, question); + if (sel == 0) menu_sel_box(104, 151, CONF_BTN_BASE); + else menu_sel_box(169, 216, CONF_BTN_BASE); + pop_text_center_mapped(POP_TEXT_SMALL, 128, CONF_BTN_BASE, "YES"); + pop_text_center_mapped(POP_TEXT_SMALL, 192, CONF_BTN_BASE, "NO"); + pop_text_unmap(); + ui_end(back); +} + +static uint8_t ui_confirm(const char *question) +{ + uint8_t sel = 1; /* разрушительные действия по умолчанию NO */ + uint8_t up_prev = 0, down_prev = 0, left_prev = 0, right_prev = 0; + uint8_t esc_prev = 0, ent_prev = 0; + + ui_wait_release(); + confirm_draw(question, sel); + menu_sound(MENU_SND_SELECT); + for (;;) { + uint8_t up = kbd_raw_down(KBD_UP); + uint8_t down = kbd_raw_down(KBD_DOWN); + uint8_t left = kbd_raw_down(KBD_LEFT); + uint8_t right = kbd_raw_down(KBD_RIGHT); + uint8_t esc = kbd_raw_down(KBD_ESC); + uint8_t ent = ui_action_down(); + uint8_t redraw = 0; + + if ((up && !up_prev) || (down && !down_prev) || + (left && !left_prev) || (right && !right_prev)) { + sel ^= 1; + redraw = 1; + } + if (esc && !esc_prev) { + ui_wait_release(); + return 0; + } + if (ent && !ent_prev) { + uint8_t yes = (uint8_t)(sel == 0); + ui_wait_release(); + return yes; + } + up_prev = up; down_prev = down; left_prev = left; right_prev = right; + esc_prev = esc; ent_prev = ent; + if (redraw) { + confirm_draw(question, sel); + menu_sound(MENU_SND_MOVE); + } + pop_wait_edge(); + } +} + +static void notice_draw(const char *message) +{ + uint8_t back = ui_begin(); + pop_text_map(); + pop_text_center_mapped(POP_TEXT_BIG, GFX_WIDTH / 2, NOTICE_BASE, message); + pop_text_center_mapped(POP_TEXT_SMALL, GFX_WIDTH / 2, + NOTICE_BASE + NOTICE_GAP, UI_KEYS_TEXT); + pop_text_unmap(); + ui_end(back); +} + +static void ui_notice(const char *message) +{ + ui_wait_release(); + notice_draw(message); + menu_sound(MENU_SND_SELECT); + ui_wait_ack(); +} + +static void controls_process(void) +{ + ui_wait_release(); + controls_draw(); + menu_sound(MENU_SND_SELECT); + ui_wait_ack(); +} + +static void about_process(void) +{ + ui_wait_release(); + about_draw(); + menu_sound(MENU_SND_SELECT); + ui_wait_ack(); +} + +static uint8_t settings_finish(uint8_t changed) +{ + int8_t rc; + if (!changed) return 0; + pop_settings_apply(1); + /* ESTEX может уйти в диск на много CBL-периодов. */ + pop_sfx_pause(); + rc = pop_cfg_save(); + (void)pop_sfx_start(); + ui_notice(rc > 0 ? "SETTINGS SAVED" : "SAVE ERROR"); + return 1; +} + +static uint8_t settings_process(void) +{ + uint8_t sel = 0, changed = 0; + uint8_t up_prev = 0, down_prev = 0, left_prev = 0, right_prev = 0; + uint8_t esc_prev = 0, ent_prev = 0; + + ui_wait_release(); + settings_draw(sel); + menu_sound(MENU_SND_SELECT); /* вход во вложенное меню */ + for (;;) { + uint8_t up = kbd_raw_down(KBD_UP); + uint8_t down = kbd_raw_down(KBD_DOWN); + uint8_t left = kbd_raw_down(KBD_LEFT); + uint8_t right = kbd_raw_down(KBD_RIGHT); + uint8_t esc = kbd_raw_down(KBD_ESC); + uint8_t ent = ui_action_down(); + uint8_t redraw = 0; + uint8_t change = 0; + uint8_t sound = MENU_SND_NONE; + int8_t delta = 0; + + if (up && !up_prev) { + sel = (uint8_t)(sel ? sel - 1 : SETTINGS_N_ITEMS - 1); + redraw = 1; sound = MENU_SND_MOVE; + } + if (down && !down_prev) { + sel = (uint8_t)(sel == SETTINGS_N_ITEMS - 1 ? 0 : sel + 1); + redraw = 1; sound = MENU_SND_MOVE; + } + if (esc && !esc_prev) { + uint8_t saved = settings_finish(changed); + ui_wait_release(); + return saved; + } + if ((left && !left_prev) || (right && !right_prev)) + delta = (right && !right_prev) ? 1 : -1; + /* Left/right принадлежат только числовой настройке скорости; + * остальные строки меняются Enter либо Space, чтобы стрелкой не + * открыть случайно Controls и не сбросить параметры. */ + if ((ent && !ent_prev) || (sel == 4 && delta)) { + if (sel == 0) { + pop_settings.sound_enabled ^= 1; change = 1; + } else if (sel == 1) { + pop_settings.show_debug_bar ^= 1; change = 1; + } else if (sel == 2) { + if (ui_confirm("RESTORE DEFAULTS?")) { + pop_settings_defaults(); change = 1; + } + redraw = 1; sound = MENU_SND_SELECT; + } else if (sel == 3) { + pop_settings.time_limit_enabled ^= 1; change = 1; + } else if (sel == 4) { + uint8_t speed = pop_settings.speed_mode; + if (delta < 0) speed = (uint8_t)(speed ? speed - 1 : POP_SPEED_FASTEST); + else speed = (uint8_t)(speed == POP_SPEED_FASTEST ? POP_SPEED_NORMAL : speed + 1); + pop_settings.speed_mode = speed; change = 1; + } else if (sel == 5) { + ui_notice("VANILLA ONLY"); redraw = 1; sound = MENU_SND_SELECT; + } else if (sel == 6) { + pop_settings.cheats_enabled ^= 1; change = 1; + } else if (sel == 7) { + controls_process(); redraw = 1; sound = MENU_SND_SELECT; + } else if (sel == 8) { + about_process(); redraw = 1; sound = MENU_SND_SELECT; + } + } + if (change) { + /* Настройки работают сразу, а запись откладывается до выхода. */ + pop_settings_apply(1); + changed = 1; + redraw = 1; + sound = MENU_SND_CHANGE; + } + up_prev = up; down_prev = down; left_prev = left; right_prev = right; + esc_prev = esc; ent_prev = ent; + if (redraw) { + settings_draw(sel); + menu_sound(sound); + } + pop_wait_edge(); + } +} + +static int8_t menu_quickload(void) +{ + uint8_t slots; + /* probe делает open/close на D:, поэтому насос в это время выключен. */ + pop_sfx_pause(); + slots = pop_qsave_probe(); + (void)pop_sfx_start(); + if (slots & POP_QS_SLOT_SAVE) return POP_MENU_QUICKLOAD; + if ((slots & POP_QS_SLOT_BACKUP) && ui_confirm("LOAD BACKUP?")) + return POP_MENU_QUICKLOAD_BACKUP; + if (!(slots & POP_QS_SLOT_BACKUP)) ui_notice("NO QUICKLOAD"); + return POP_MENU_NONE; +} + +/* Вернуть чистую сцену на ОБЕ страницы и показать ту, которая была до + * меню. Это важно и для отладочного single-buffer: главный цикл там + * ожидает страницу 0, а не ту, в которую меню сделало последний флип. */ +static void menu_erase(uint8_t entry_page) +{ + gfx_rect_t full; + uint8_t visible = gfx_get_visible_page(); + uint8_t hidden = (uint8_t)(visible ^ 1); + + full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; + + /* Из shadow видимой страницы берём кадр без текстов. */ + gfx_set_draw_page(hidden); + gfx_copy_page(&full, GFX_COPY_DIRECT); + pop_wait_edge(); + gfx_set_visible_page(hidden); + + /* Теперь чистая hidden — источник для второй страницы. */ + gfx_set_draw_page(visible); + gfx_copy_page(&full, GFX_COPY_DIRECT); + if (entry_page != hidden) { + pop_wait_edge(); + gfx_set_visible_page(entry_page); + } +} + +static void build_info_draw(void) +{ + uint8_t back = pop_screen_begin(); + int top = UI_CENTER_TOP(ABOUT_H + BUILD_KEYS_GAP); + + pop_screen_fill(BLACK); + gfx_set_bank(GFX_BANK_SPRITE); + pop_text_map(); + about_text(top); + pop_text_center_mapped(POP_TEXT_SMALL, GFX_WIDTH / 2, + top + ABOUT_H + BUILD_KEYS_GAP, UI_KEYS_TEXT); + pop_text_unmap(); + pop_screen_present(back); +} + +/* В roomtest ещё нет автомата TITLE (это FG1/FG6), поэтому build screen + * показывается перед первым игровым кадром и после выхода возвращает ровно + * ту же сцену. Настройка уже прочитана в pop_boot до initgraph. */ +void pop_build_info_show(void) __banked +{ + uint8_t entry_page, saved_page, saved_bank, frames; + struct fillsettingstype saved_fill; + + if (!pop_ui_ready() || !pop_settings.show_build_info) return; + entry_page = gfx_get_visible_page(); + saved_page = gfx_get_draw_page(); + saved_bank = gfx_get_bank(); + getfillsettings(&saved_fill); + /* Первый реальный пользователь FG5 fade: старая игровая палитра + * запоминается в pop_ui, затем full-screen появляется из чёрного. */ + pop_pal_fade_out(4); + build_info_draw(); + pop_pal_fade_in(4); + for (frames = 0; frames < 150; frames++) { /* около трёх секунд */ + if (kbd_raw_down(KBD_ESC) || kbd_raw_down(KBD_ENTER) || + kbd_raw_down(KBD_SPACE)) + break; + pop_wait_edge(); + } + ui_wait_release(); + pop_pal_fade_out(4); + menu_erase(entry_page); + pop_pal_fade_in(4); + setfillstyle(saved_fill.pattern, saved_fill.color); + gfx_set_bank(saved_bank); + gfx_set_draw_page(saved_page); +} + +int pop_menu_init(void) __banked +{ + return pop_ui_init(); +} + +void pop_menu_free(void) __banked +{ + pop_ui_free(); +} + +int8_t pop_menu_process(void) __banked +{ + uint8_t sel = 0; + uint8_t up_prev = 0, down_prev = 0, esc_prev = 0, ent_prev = 0; + uint8_t entry_page, saved_page, saved_bank; + uint8_t exit_sound = MENU_SND_NONE; + struct fillsettingstype saved_fill; + int8_t result = POP_MENU_NONE; + + if (!pop_menu_request) return POP_MENU_NONE; + pop_menu_request = 0; + if (!pop_ui_ready()) return POP_MENU_RESUME; + + /* Открыли по НАЖАТИЮ Esc: дождаться отпускания, иначе то же нажатие + * закроет меню раньше первого кадра. pop_wait_edge опрашивает FIFO. */ + while (kbd_raw_down(KBD_ESC)) pop_wait_edge(); + + entry_page = gfx_get_visible_page(); + saved_page = gfx_get_draw_page(); + saved_bank = gfx_get_bank(); + getfillsettings(&saved_fill); + pop_sfx_pause(); /* snapshot/dim + первый full redraw */ + pop_pal_snapshot(); + pop_pal_menu_dim(2); /* 50%, диапазон UI остаётся ярким */ + menu_draw(sel); + + for (;;) { + uint8_t up = kbd_raw_down(KBD_UP); + uint8_t down = kbd_raw_down(KBD_DOWN); + uint8_t esc = kbd_raw_down(KBD_ESC); + uint8_t ent = ui_action_down(); + uint8_t redraw = 0; + uint8_t sound = MENU_SND_NONE; + + if (up && !up_prev) { + sel = (uint8_t)(sel ? sel - 1 : MENU_N_ITEMS - 1); + redraw = 1; sound = MENU_SND_MOVE; + } + if (down && !down_prev) { + sel = (uint8_t)(sel == MENU_N_ITEMS - 1 ? 0 : sel + 1); + redraw = 1; sound = MENU_SND_MOVE; + } + if (esc && !esc_prev) { + result = POP_MENU_RESUME; + break; + } + if (ent && !ent_prev && menu_cmds[sel] != POP_MENU_NONE) { + uint8_t cmd = menu_cmds[sel]; + if (cmd == POP_MENU_SETTINGS) { + (void)settings_process(); + redraw = 1; sound = MENU_SND_SELECT; /* Back -> pause menu */ + } else if (cmd == POP_MENU_QUICKLOAD) { + result = menu_quickload(); + if (result != POP_MENU_NONE) { exit_sound = MENU_SND_SELECT; break; } + redraw = 1; sound = MENU_SND_SELECT; + } else if (cmd == POP_MENU_QUIT) { + if (ui_confirm("QUIT GAME?")) { + result = POP_MENU_QUIT; + exit_sound = MENU_SND_SELECT; + break; + } + redraw = 1; sound = MENU_SND_SELECT; + } else { + result = (int8_t)cmd; + exit_sound = MENU_SND_SELECT; + break; + } + } + up_prev = up; down_prev = down; esc_prev = esc; ent_prev = ent; + + if (redraw) { + menu_draw(sel); + menu_sound(sound); + } + pop_wait_edge(); + pop_pace_end(pop_pace_n(0)); /* игра стоит — обычный темп */ + } + + pop_sfx_pause(); /* две full-page копии menu_erase */ + menu_erase(entry_page); + /* menu_erase вернул обе страницы из тени, снятой ДО меню: всё, что + * статус-полосы нарисовали за это время (или сообщение, дотикавшее в + * паузе), надо выложить заново. */ + pop_status_invalidate(); + pop_pal_apply(0); + setfillstyle(saved_fill.pattern, saved_fill.color); + gfx_set_bank(saved_bank); + gfx_set_draw_page(saved_page); + (void)pop_sfx_start(); + menu_sound(exit_sound); /* Esc main menu остаётся бесшумным */ + return result; +} diff --git a/applications/PoP/roomtest/pop_menu.h b/applications/PoP/roomtest/pop_menu.h new file mode 100644 index 0000000..ef12301 --- /dev/null +++ b/applications/PoP/roomtest/pop_menu.h @@ -0,0 +1,45 @@ +/* + * pop_menu.h — контракт pause menu (план docs/menu_settings_plan.md §10.3, + * этап MS0). + * + * UI НЕ вызывает gameplay-модули напрямую: меню формирует КОМАНДУ, а + * исполнение остаётся в главном цикле (roomtest.c). Вся работа с меню — + * код, шрифты и таблицы строк — живёт в БАНКЕ 9. Флаг запроса принадлежит + * главному циклу (W2): это единственная резидентная граница UI. + */ +#ifndef POP_MENU_H +#define POP_MENU_H + +#include + +typedef enum { + POP_MENU_NONE = 0, /* меню не открывалось / пункт-заглушка */ + POP_MENU_RESUME, /* закрыть меню, продолжить игру */ + POP_MENU_QUICKSAVE, /* то же, что F6 */ + POP_MENU_QUICKLOAD, /* то же, что F9 */ + POP_MENU_RESTART_LEVEL, /* штатный reset текущего уровня (без CFG) */ + POP_MENU_RESTART_GAME, /* новая игра с уровня 1, настройки и SAV */ + POP_MENU_QUIT, /* выход из программы (после подтверждения) */ + POP_MENU_QUICKLOAD_BACKUP, /* загрузить подтверждённый POP.BAK */ + POP_MENU_SETTINGS /* внутренний экран Settings (не возвращается) */ +} pop_menu_cmd_t; + +/* Флаг запроса в roomtest.c (W2); банк читает напрямую. */ +extern uint8_t pop_menu_request; + +/* Загрузить/освободить EMM-атлас исходных шрифтов SDLPoP. Вызывается из + * холодного boot/shutdown, не из кадрового пути. */ +int pop_menu_init(void) __banked; +void pop_menu_free(void) __banked; + +/* Граница кадра. Запроса нет — мгновенный POP_MENU_NONE. Меню МОДАЛЬНО: + * пока открыто, не возвращается (игра стоит — логика, таймер и звуковой + * насос главного цикла просто не выполняются); по выбору пользователя + * отдаёт pop_menu_cmd_t. */ +int8_t pop_menu_process(void) __banked; + +/* Экран Sprinter build, если он включён в POP.CFG. Вызывается после + * открытия raw-клавиатуры и до первого игрового кадра. */ +void pop_build_info_show(void) __banked; + +#endif diff --git a/applications/PoP/roomtest/pop_pal.c b/applications/PoP/roomtest/pop_pal.c new file mode 100644 index 0000000..4831697 --- /dev/null +++ b/applications/PoP/roomtest/pop_pal.c @@ -0,0 +1,114 @@ +/* + * pop_pal.c — владелец порядка «load logical palette -> apply brightness». + * + * Снимок физически хранит pop_ui (в хвосте FONT.ATL), потому что только тот + * модуль владеет страницей шрифта. Здесь остаётся политика: kid.pal всегда + * дополняют живые слоты тайлсета и Тени, а file-load никогда не считается + * показом яркого кадра сам по себе. + */ +#include +#include +#include +#include "pop_bg.h" +#include "pop_shadow.h" +#include "pop_guard.h" +#include "pop_pal.h" +#include "pop_ui.h" /* POP_COL_OUTSIDE */ + +static void pal_file_load(const char *path, const char *fallback) +{ + if (gfx_pal_fload(0, path) < 0) gfx_pal_fload(0, fallback); + gfx_pal_sync(); + pop_ui_palette_snapshot(); +} + +void pop_pal_black(void) __banked +{ + pop_ui_palette_black(); +} + +void pop_pal_snapshot(void) __banked +{ + pop_ui_palette_snapshot(); +} + +void pop_pal_apply(uint8_t fade) __banked +{ + pop_ui_palette_dim(fade, 0); +} + +void pop_pal_fade_out(uint8_t steps) __banked +{ + pop_ui_fade_out(steps); +} + +void pop_pal_fade_in(uint8_t steps) __banked +{ + pop_ui_fade_in(steps); +} + +/* Слот «вне игрового поля» (POP_COL_OUTSIDE) — всегда ЧЁРНЫЙ. + * + * Это ещё один ДИНАМИЧЕСКИЙ слот поверх файловой палитры: kid.pal содержит + * все 256 записей и кладёт в единицу свой цвет, а нам она нужна чёрной — + * иначе борта и полоса HP станут цветными. Ставим после каждого + * file-load, рядом с тайлсетом/Тенью/соперником (см. pop_pal_game_load). */ +void pop_pal_outside_apply(void) __banked +{ + gfx_pal_set(0, POP_COL_OUTSIDE, 0, 0, 0); + gfx_pal_set(1, POP_COL_OUTSIDE, 0, 0, 0); +} + +void pop_pal_flash_white(uint8_t on) __banked +{ + uint8_t color = on ? 255 : 0; + + gfx_pal_set(0, 0, color, color, color); + gfx_pal_set(1, 0, color, color, color); +} + +void pop_pal_menu_dim(uint8_t fade) __banked +{ + pop_ui_palette_dim(fade, 1); +} + +void pop_pal_game_load(void) __banked +{ + pal_file_load("KID\\kid.pal", "a:\\kid.pal"); + /* kid.pal содержит все 256 записей и намеренно затирает эти три + * динамических диапазона (тайлсет, Тень, набор соперника). + * Восстановить их надо ДО snapshot. */ + pop_bg_pal_apply(); + pop_shadow_pal_apply(); + pop_guard_pal_apply(); + pop_pal_outside_apply(); + pop_ui_palette_snapshot(); +} + +void pop_pal_level_load(uint8_t full) __banked +{ + if (full) { + pop_pal_game_load(); + return; + } + /* При обычной границе уровня меняются только env/wall и набор соперника. */ + pop_bg_pal_apply(); + pop_guard_pal_apply(); + pop_pal_outside_apply(); + pop_ui_palette_snapshot(); +} + +void pop_pal_title_load(void) __banked +{ + pal_file_load("TITLE\\title.pal", "a:\\title.pal"); +} + +void pop_pal_story_load(void) __banked +{ + pal_file_load("PV\\story.pal", "a:\\story.pal"); +} + +void pop_pal_pv_load(void) __banked +{ + pal_file_load("PV\\pv.pal", "a:\\pv.pal"); +} diff --git a/applications/PoP/roomtest/pop_pal.h b/applications/PoP/roomtest/pop_pal.h new file mode 100644 index 0000000..eb2a001 --- /dev/null +++ b/applications/PoP/roomtest/pop_pal.h @@ -0,0 +1,43 @@ +/* + * pop_pal.h — единый lifecycle палитры PoP, банк 10. + * + * Атласы запечены под фиксированные диапазоны: env/wall 0x50..0x6F, + * Kid 0x70..0x8F, страж 0x90..0x9F, Тень 0xA0..0xAF. Поэтому palette + * уровня нельзя «восстановить fade-ом» из старого снимка: сначала надо + * загрузить логический источник, затем только выбрать яркость apply/fade. + */ +#ifndef POP_PAL_H +#define POP_PAL_H + +#include + +/* Все 256 записей обеих графических страниц = чёрный; снимок не меняет. */ +void pop_pal_black(void) __banked; + +/* Логические источники палитры. Они обновляют снимок, но сами не выбирают + * яркость: caller обязан явно вызвать apply()/fade_in(). */ +void pop_pal_game_load(void) __banked; +void pop_pal_level_load(uint8_t full) __banked; +void pop_pal_title_load(void) __banked; +void pop_pal_story_load(void) __banked; +void pop_pal_pv_load(void) __banked; + +/* Показ текущего снимка: 0 — исходная яркость, 4 — чёрный. */ +void pop_pal_snapshot(void) __banked; +void pop_pal_apply(uint8_t fade) __banked; +void pop_pal_fade_out(uint8_t steps) __banked; +void pop_pal_fade_in(uint8_t steps) __banked; + +/* Слот POP_COL_OUTSIDE (всё вне игрового поля) — чёрным. Динамический + * слот: file-load палитры кладёт в него своё, поэтому доплачивается после + * каждого источника, как тайлсет/Тень/соперник. */ +void pop_pal_outside_apply(void) __banked; + +/* do_flash/remove_flash SDLPoP: меняется только нулевая запись обеих + * page-палитр, сама картинка и снимок логической палитры не затрагиваются. */ +void pop_pal_flash_white(uint8_t on) __banked; + +/* Меню — единственный пользователь keep_ui (0x37..0x3F не темнеют). */ +void pop_pal_menu_dim(uint8_t fade) __banked; + +#endif /* POP_PAL_H */ diff --git a/applications/PoP/roomtest/pop_qsave.c b/applications/PoP/roomtest/pop_qsave.c index 41f6f0f..637c269 100644 --- a/applications/PoP/roomtest/pop_qsave.c +++ b/applications/PoP/roomtest/pop_qsave.c @@ -9,6 +9,7 @@ #include #include #include "pop_qsave.h" +#include "pop_status.h" #include "pop_kid.h" #include "pop_guard.h" #include "pop_map.h" @@ -19,11 +20,13 @@ #include "pop_tile.h" #include "pop_sfx.h" #include "pop_pace.h" +#include "pop_timer.h" #include "roomtest_cold.h" -#define QS_VERSION 3 +#define QS_VERSION 4 #define QS_SAVE 1 #define QS_LOAD 2 +#define QS_LOAD_BACKUP 3 #define QS_LIMIT 4096 #define QS_BASE 0x100 @@ -54,6 +57,9 @@ static void qs_payload(pop_qs_io_t *io) } pop_speed_mode=speed; pop_immortal=immortal; pop_snd_want=sound; } + /* Глобальный timer сохраняется до состояния уровня: QuickLoad на том же + * уровне вернёт ровно тот логический кадр, без «бесплатной» минуты. */ + pop_timer_qsave(io); qs_char(io, &Kid); qs_char(io, &Guard); pop_qs_u8(io, &cur_room); pop_qs_u8(io, &kid_room); @@ -108,7 +114,8 @@ static uint8_t qs_header_read(pop_qs_io_t *io, uint8_t *level) uint8_t h[7]; if (!pop_qs_raw_get(io,h,sizeof(h))) return 0; if (h[0]!='P' || h[1]!='O' || h[2]!='P' || h[3]!='Q' || - h[4] != QS_VERSION || h[5] < 1 || h[5] > 15 || h[6] != 0) return 0; + h[4] != QS_VERSION || h[5] < POP_LEVEL_FIRST || + h[5] > POP_LEVEL_LAST || h[6] != 0) return 0; *level = h[5]; return 1; } @@ -184,6 +191,33 @@ static int8_t qs_load_one(const char *name, uint8_t page) return 1; } +/* Проверка слотов для меню. Это именно probe: не вызывает pop_level_switch, + * не применяет payload и не трогает звук. Отдельная EMM-страница делает + * проверку независимой от живой страницы уровня. */ +uint8_t pop_qsave_probe(void) __banked +{ + uint8_t blk, page, level, slots = 0; + uint16_t payload_size; + int size; + + blk = mem_alloc_pages(1); + if (!blk) return 0; + page = mem_get_page(blk, 0); + gfx_w0_page_prepare(page); + + size = bank_load_file(page, QS_BASE, "POP.SAV", QS_LIMIT); + if (size > 0 && qs_validate_page(page, (uint16_t)size, &level, + &payload_size)) + slots |= POP_QS_SLOT_SAVE; + size = bank_load_file(page, QS_BASE, "POP.BAK", QS_LIMIT); + if (size > 0 && qs_validate_page(page, (uint16_t)size, &level, + &payload_size)) + slots |= POP_QS_SLOT_BACKUP; + + mem_free_block(blk); + return slots; +} + int8_t pop_qsave_process(void) __banked { uint8_t request = pop_qsave_request; @@ -197,11 +231,21 @@ int8_t pop_qsave_process(void) __banked gfx_w0_page_prepare(page); pop_sfx_pause(); if (request == QS_SAVE) rc = qs_save(page); + else if (request == QS_LOAD_BACKUP) rc = qs_load_one("POP.BAK", page); else { rc = qs_load_one("POP.SAV",page); if (rc < 0) rc = qs_load_one("POP.BAK",page); } pop_sfx_start(); mem_free_block(blk); + /* Сообщение заявляем ЗДЕСЬ, а не у вызывающего: только тут известно, + * что именно делали (F6/F9 и оба пункта меню приходят одним запросом). + * Порт seg000:497/514 — те же четыре строки, что и в оригинале. */ + if (request == QS_SAVE) + pop_status_msg = (uint8_t)(rc > 0 ? POP_MSG_QUICKSAVE + : POP_MSG_NO_QUICKSAVE); + else + pop_status_msg = (uint8_t)(rc > 0 ? POP_MSG_QUICKLOAD + : POP_MSG_NO_QUICKLOAD); return rc; } diff --git a/applications/PoP/roomtest/pop_qsave.h b/applications/PoP/roomtest/pop_qsave.h index 37ba1f1..564c296 100644 --- a/applications/PoP/roomtest/pop_qsave.h +++ b/applications/PoP/roomtest/pop_qsave.h @@ -36,5 +36,13 @@ uint16_t pop_qs_checksum_page(uint8_t page, uint16_t pos, uint16_t size); int8_t pop_qsave_process(void) __banked; void pop_qsave_request_save(void); void pop_qsave_request_load(void); +void pop_qsave_request_load_backup(void); + +/* Валидность слотов без изменения состояния игры. Нужна menu для вопроса + * LOAD BACKUP?: основной SAV уже пригоден, backup спрашиваем только если + * основной отсутствует или повреждён. */ +#define POP_QS_SLOT_SAVE 0x01 +#define POP_QS_SLOT_BACKUP 0x02 +uint8_t pop_qsave_probe(void) __banked; #endif diff --git a/applications/PoP/roomtest/pop_qsave_io.c b/applications/PoP/roomtest/pop_qsave_io.c index 86b7310..cc4fc28 100644 --- a/applications/PoP/roomtest/pop_qsave_io.c +++ b/applications/PoP/roomtest/pop_qsave_io.c @@ -7,6 +7,7 @@ uint8_t pop_qsave_request; void pop_qsave_request_save(void) { pop_qsave_request=1; } void pop_qsave_request_load(void) { pop_qsave_request=2; } +void pop_qsave_request_load_backup(void) { pop_qsave_request=3; } void pop_qs_bytes(pop_qs_io_t *io, void *data, uint16_t size) { diff --git a/applications/PoP/roomtest/pop_settings.h b/applications/PoP/roomtest/pop_settings.h new file mode 100644 index 0000000..56748e8 --- /dev/null +++ b/applications/PoP/roomtest/pop_settings.h @@ -0,0 +1,39 @@ +/* + * pop_settings.h — рантайм-настройки игры (план docs/menu_settings_plan.md + * §5, этап MS0). Единственный контракт между UI/файлом настроек и игрой: + * gameplay-код читает значения, но не знает про UI-структуры и POP.CFG. + * + * Экран Settings и загрузчик POP.CFG уже используют эти поля; начальные + * значения ставит pop_settings_defaults() из pop_boot() (crt0 зануляет + * _DATA, поэтому ненулевые дефолты литералами в данных запрещены). + */ +#ifndef POP_SETTINGS_H +#define POP_SETTINGS_H + +#include + +typedef enum { + POP_PROFILE_VANILLA = 0, /* текущее поведение roomtest (план §1) */ + POP_PROFILE_ENHANCED = 1 /* будущий набор fixes (план §12) */ +} pop_gameplay_profile_t; + +typedef struct { + uint8_t sound_enabled; /* 0/1 — зеркало звука (Ctrl+S / pop_sfx) */ + uint8_t speed_mode; /* POP_SPEED_* (pop_pace.h), 0..2 */ + uint8_t gameplay_profile; /* pop_gameplay_profile_t */ + uint8_t cheats_enabled; /* 0/1 — активны ли чит-клавиши */ + uint8_t show_build_info; /* 0/1 — build info на title (этап MS8) */ + uint16_t enhancement_flags; /* маска будущего ENHANCED (план §12) */ + uint8_t time_limit_enabled;/* 0/1 — 60-минутный лимит / unlimited */ + uint8_t show_debug_bar; /* 0/1 — отладочная полоса в верхнем борте */ +} pop_settings_t; + +extern pop_settings_t pop_settings; +void pop_settings_defaults(void) __banked; + +/* Применить модель к runtime-владельцам (темп, читы, звук). До окончания + * pop_sfx_init() sound_ready=0: звук применится отдельным вызовом после + * инициализации набора. */ +void pop_settings_apply(uint8_t sound_ready) __banked; + +#endif diff --git a/applications/PoP/roomtest/pop_sfx.c b/applications/PoP/roomtest/pop_sfx.c index e0ebbb7..22d2bb3 100644 --- a/applications/PoP/roomtest/pop_sfx.c +++ b/applications/PoP/roomtest/pop_sfx.c @@ -79,7 +79,13 @@ int pop_sfx_fill(uint16_t n) sfx_ptr += n; if (sfx_ptr >= 0x4000) { /* граница страницы — ровно по блоку */ sfx_ptr = 0; - sfx_pg++; + /* Переход на следующую страницу нужен ровно одному эффекту — тому, + * что сам ДЛИННЕЕ страницы (упаковщик с 2026-08-25 кладёт остальные + * целиком в одну). Гард обязателен: без него ошибка в длине увела + * бы насос за pop_snd_page[] и он читал бы чужую память — на слух + * это «играет не тот звук», причём каждый раз другой. */ + if ((uint8_t)(sfx_pg + 1) < POP_SND_PAGES) sfx_pg++; + else sfx_left = n; /* обрываем */ } sfx_left -= n; return 1; diff --git a/applications/PoP/roomtest/pop_sound_tbl.h b/applications/PoP/roomtest/pop_sound_tbl.h index 77acc4b..c6d9fb7 100644 --- a/applications/PoP/roomtest/pop_sound_tbl.h +++ b/applications/PoP/roomtest/pop_sound_tbl.h @@ -6,7 +6,11 @@ * * Начало каждого звука кратно 128 — размеру блока запроса CBL, а * страница (16384) кратна ему же. Поэтому НИ ОДИН блок не пересекает - * границу страницы, и проигрывателю не нужна логика стыка. + * границу страницы. Больше того: с 2026-08-25 каждый звук лежит + * ЦЕЛИКОМ в одной странице — не влезает в остаток, значит начинаем + * новую (страницы EMM у нас с запасом). Единственное исключение — + * эффект, который сам ДЛИННЕЕ страницы: для него насос переключает + * страницу на ходу, и эта ветка в pop_sfx_fill остаётся ради него. * * Смещение хранится РАЗОБРАННЫМ на страницу и адрес в окне: на Z80 * 32-битная арифметика дорога, а так всё влезает в 8/16 бит. @@ -17,7 +21,7 @@ #include -#define POP_SND_PAGES 8 +#define POP_SND_PAGES 10 #define POP_SND_COUNT 57 #define POP_SND_BLOCK 128 /* Первый блок страницы 0 — тишина для насоса (см. упаковщик). */ @@ -30,28 +34,28 @@ typedef struct { uint8_t page; uint16_t off; uint16_t len; } pop_snd_ent_t; static const pop_snd_ent_t pop_snd_tbl[POP_SND_COUNT] = { { 0, 0x0080, 1664 }, /* 0 */ { 0, 0x0700, 12032 }, /* 1 */ - { 0, 0x3600, 6272 }, /* 2 */ - { 1, 0x0E80, 1280 }, /* 3 */ - { 1, 0x1380, 1152 }, /* 4 */ - { 1, 0x1800, 1408 }, /* 5 */ - { 1, 0x1D80, 5504 }, /* 6 */ - { 1, 0x3300, 896 }, /* 7 */ - { 1, 0x3680, 1664 }, /* 8 */ - { 1, 0x3D00, 2816 }, /* 9 */ - { 2, 0x0800, 4992 }, /* 10 */ - { 2, 0x1B80, 1280 }, /* 11 */ - { 2, 0x2080, 1280 }, /* 12 */ - { 2, 0x2580, 3840 }, /* 13 */ - { 2, 0x3480, 7552 }, /* 14 */ - { 3, 0x1200, 17664 }, /* 15 */ - { 4, 0x1700, 4736 }, /* 16 */ - { 4, 0x2980, 4224 }, /* 17 */ - { 4, 0x3A00, 6912 }, /* 18 */ - { 5, 0x1500, 2816 }, /* 19 */ - { 5, 0x2000, 1408 }, /* 20 */ - { 5, 0x2580, 1152 }, /* 21 */ - { 5, 0x2A00, 1280 }, /* 22 */ - { 5, 0x2F00, 1408 }, /* 23 */ + { 1, 0x0000, 6272 }, /* 2 */ + { 1, 0x1880, 1280 }, /* 3 */ + { 1, 0x1D80, 1152 }, /* 4 */ + { 1, 0x2200, 1408 }, /* 5 */ + { 1, 0x2780, 5504 }, /* 6 */ + { 2, 0x0000, 896 }, /* 7 */ + { 2, 0x0380, 1664 }, /* 8 */ + { 2, 0x0A00, 2816 }, /* 9 */ + { 2, 0x1500, 4992 }, /* 10 */ + { 2, 0x2880, 1280 }, /* 11 */ + { 2, 0x2D80, 1280 }, /* 12 */ + { 3, 0x0000, 3840 }, /* 13 */ + { 3, 0x0F00, 7552 }, /* 14 */ + { 4, 0x0000, 17664 }, /* 15 */ + { 5, 0x0500, 4736 }, /* 16 */ + { 5, 0x1780, 4224 }, /* 17 */ + { 6, 0x0000, 6912 }, /* 18 */ + { 6, 0x1B00, 2816 }, /* 19 */ + { 6, 0x2600, 1408 }, /* 20 */ + { 6, 0x2B80, 1152 }, /* 21 */ + { 6, 0x3000, 1280 }, /* 22 */ + { 6, 0x3500, 1408 }, /* 23 */ { 0, 0x0000, 0 }, /* 24 */ { 0, 0x0000, 0 }, /* 25 */ { 0, 0x0000, 0 }, /* 26 */ @@ -72,14 +76,14 @@ static const pop_snd_ent_t pop_snd_tbl[POP_SND_COUNT] = { { 0, 0x0000, 0 }, /* 41 */ { 0, 0x0000, 0 }, /* 42 */ { 0, 0x0000, 0 }, /* 43 */ - { 5, 0x3480, 2688 }, /* 44 */ - { 5, 0x3F00, 4224 }, /* 45 */ - { 6, 0x0F80, 4608 }, /* 46 */ - { 6, 0x2180, 5120 }, /* 47 */ - { 6, 0x3580, 5120 }, /* 48 */ - { 7, 0x0980, 4480 }, /* 49 */ + { 7, 0x0000, 2688 }, /* 44 */ + { 7, 0x0A80, 4224 }, /* 45 */ + { 7, 0x1B00, 4608 }, /* 46 */ + { 8, 0x0000, 5120 }, /* 47 */ + { 8, 0x1400, 5120 }, /* 48 */ + { 8, 0x2800, 4480 }, /* 49 */ { 0, 0x0000, 0 }, /* 50 */ - { 7, 0x1B00, 4864 }, /* 51 */ + { 9, 0x0000, 4864 }, /* 51 */ { 0, 0x0000, 0 }, /* 52 */ { 0, 0x0000, 0 }, /* 53 */ { 0, 0x0000, 0 }, /* 54 */ @@ -101,6 +105,8 @@ static const char *const pop_snd_files[POP_SND_PAGES] = { "SND\\s5.bin", "SND\\s6.bin", "SND\\s7.bin", + "SND\\s8.bin", + "SND\\s9.bin", }; #endif diff --git a/applications/PoP/roomtest/pop_status.c b/applications/PoP/roomtest/pop_status.c new file mode 100644 index 0000000..c4cf2f8 --- /dev/null +++ b/applications/PoP/roomtest/pop_status.c @@ -0,0 +1,303 @@ +/* + * pop_status.c — текст в двух служебных полосах экрана, БАНК 9. + * + * Нижняя полоса (та же, где деления HP) — статус-строка оригинала; + * верхняя — наша отладочная. Обе живут ВНЕ игрового поля, поэтому фон у + * них POP_COL_OUTSIDE, а не цвет 0: вспышки (урон, зелья, молния) сделаны + * подменой нулевого цвета палитры, и полосы не должны мигать вместе с + * игрой (pop_ui.h). + * + * ЦЕНА ВЫВОДА — главное ограничение. Блит одного глифа стоит ~4,6 тыс. + * тактов почти независимо от размера (см. memory blit_cost_model): цена + * вызова, а не пикселей. Полная отладочная строка — это полсотни глифов, + * то есть ПОЛКАДРА. Отсюда три правила, на которых держится модуль: + * + * 1. Всё change-driven. Пока показанное не изменилось, не рисуем вовсе + * — за кадр остаётся только сравнение нескольких байт. + * 2. Отладочная строка перерисовывается ПО ПОЛЯМ. Смена комнаты — это + * две цифры (~9 тыс. тактов, 2% кадра), а не вся строка; подписи + * («Level», «, Room», …) не меняются никогда и рисуются лишь при + * полной инвалидации, которая случается на старте уровня, где кадр и + * так тяжёлый. + * 3. Пробелы не блитятся (pop_ui.c): их глиф целиком прозрачен, и на + * строке из полусотни символов это девять сэкономленных блитов. + * + * ГРЯЗЬ СЧИТАЕТСЯ ПО СТРАНИЦАМ, а не счётчиком «осталось два кадра». У + * каждой страницы дабл-буфера своя видео-ОЗУ, и если поля меняются в + * разных кадрах вперемешку, общий счётчик их путает: маска на страницу + * всегда описывает, что именно этой странице ещё должны. + * + * Рисуем прозрачным банком GFX_BANK_TRANSPARENT (не SPRITE): 0xFF шрифта + * не пишется, но ОЗУ-копия обновляется — и текст переживает полностраничные + * копии (вход в комнату, выход из меню), которые берут картинку из тени. + * + * Данных в W2 — 11 байт, и семь из них просто переехали сюда из прежнего + * ярлыка комнаты в roomtest_cold.c. Кода в резиденте не появляется вовсе: + * заявка сообщения — присваивание одного байта, всё остальное здесь. + */ +#include +#include +#include +#include "pop_status.h" +#include "pop_ui.h" +#include "pop_font.h" +#include "pop_bg.h" /* POP_YOFF */ +#include "pop_cdraw.h" /* POP_HP_Y — полосу делим с делениями жизней */ +#include "pop_settings.h" +#include "pop_state.h" /* pop_immortal */ +#include "pop_level.h" /* pop_current_level */ +#include "pop_pace.h" /* pop_speed_mode */ +#include "pop_sfx.h" /* pop_snd_want */ + +/* ---- нижняя полоса: статус-строка -------------------------------------- */ + +/* Как у оригинала: 24 тика (text_time_total = 24) — около двух секунд. */ +#define MSG_TICKS 24 +/* Базовая линия та же, что у GAME PAUSED (pop_menu.c): строка ниже делений. */ +#define MSG_BASE (POP_YOFF + POP_HP_Y + 8) +#define MSG_TOP (MSG_BASE - POP_FONT_BIG_ASCENT - 2) +#define MSG_BOTTOM (MSG_BASE) + +static const char * const status_text[POP_MSG_N] = { + "", + "QUICKSAVE", + "NO QUICKSAVE", + "QUICKLOAD", + "NO QUICKLOAD", + "SOUND ON", + "SOUND OFF" +}; + +uint8_t pop_status_msg; /* заявка (пишут откуда угодно) */ +uint8_t pop_status_ticks; /* сколько кадров ещё висит; читает HP */ +static uint8_t st_shown; /* что реально нарисовано */ +static uint8_t st_dirty; /* бит на страницу дабл-буфера */ + +/* ---- верхняя полоса: отладочная строка --------------------------------- * + * + * Level ##, Room ##, Speed: ..., Sound: ..., Immortal # + * + * Раскладка фиксированная: у каждого поля свой левый край и своя зона + * стирания, поэтому изменение одного значения не трогает соседей. Ширины + * посчитаны по самому длинному значению (FASTEST, OFF, две цифры). */ +#define DBG_BASE 6 /* базовая линия малого шрифта */ +#define DBG_TOP 0 +/* На строку НИЖЕ базовой линии: у малого шрифта 'p' высотой 7 (и ',' — 6) + * свисает под неё, и при стирании ровно до базовой линии от хвоста «p» в + * «Speed:» оставалась одинокая точка. */ +#define DBG_BOTTOM 7 +#define DBG_LEFT 8 +#define DBG_RIGHT 254 + +/* Значения ПРИЖАТЫ ВПРАВО к границе своей зоны, а подпись следующего поля + * начинается сразу за ней. Так запятая всегда стоит вплотную к значению — + * при выравнивании влево она отрывалась бы на всю ширину зоны («Level 1 , + * Room»). Дырка слева от значения при этом крошечная: варианты близки по + * ширине (NORMAL 30 против FASTEST 32, ON 9 против OFF 14). */ +#define X_LBL_LEVEL 10 /* "Level" 10..30 */ +#define R_VAL_LEVEL 44 /* зона 31..43 */ +#define X_LBL_ROOM 44 /* ", Room" 44..70 */ +#define R_VAL_ROOM 84 /* зона 71..83 */ +#define X_LBL_SPEED 84 /* ", Speed:" 84..116 */ +#define R_VAL_SPEED 152 /* зона 117..151 */ +#define X_LBL_SOUND 152 /* ", Sound:" 152..184 */ +#define R_VAL_SOUND 202 /* зона 185..201 */ +#define X_LBL_IMM 202 /* ", Immortal" 202..243 */ +#define R_VAL_IMM 253 /* зона 244..252: цифре нужен зазор в пробел */ + +/* Ширина зоны значения — под самый длинный вариант плюс воздух. */ +#define W_VAL_NUM 13 /* две цифры */ +#define W_VAL_SPEED 35 /* FASTEST */ +#define W_VAL_SOUND 17 /* OFF */ +#define W_VAL_IMM 9 /* одна цифра */ + +enum { /* биты маски «что перерисовать» */ + DBG_F_LEVEL = 0x01, + DBG_F_ROOM = 0x02, + DBG_F_SPEED = 0x04, + DBG_F_SOUND = 0x08, + DBG_F_IMM = 0x10, + DBG_F_ALL = 0x1F /* + подписи: см. dbg_labels */ +}; + +static const char * const speed_text[] = { "NORMAL", "FAST", "FASTEST" }; + +static uint8_t dbg_lvl, dbg_room, dbg_spd, dbg_snd, dbg_imm; +static uint8_t dbg_dirty[2]; /* маска по странице; DBG_F_ALL => и подписи */ +static uint8_t dbg_on; /* показанное состояние тумблера Settings */ + +/* ---- общее ------------------------------------------------------------- */ + +static void wipe(int left, int top, int right, int bottom) +{ + gfx_set_bank(GFX_BANK_NORMAL); + setfillstyle(SOLID_FILL, POP_COL_OUTSIDE); + bar(left, top, right, bottom); +} + +/* Число 0..99 БЕЗ ведущего нуля и без ведущего пробела: значение + * выравнивается вправо, а пробел в шрифте имеет ненулевую ширину и сдвинул + * бы цифру влево от границы зоны. Деления нет намеренно: у SDCC z80 один + * «/» стоит ~5,4 тыс. тактов (memory sdcc_z80_division_hoisting), здесь же + * максимум девять вычитаний. */ +static void dec2(uint8_t v, char *buf) +{ + uint8_t t = 0; + while (v >= 10) { v = (uint8_t)(v - 10); t++; } + if (t) { *buf++ = (char)('0' + t); } + buf[0] = (char)('0' + v); + buf[1] = 0; +} + +/* ---- нижняя полоса ----------------------------------------------------- */ + +static void status_draw(void) +{ + wipe(POP_STATUS_L, MSG_TOP, POP_STATUS_R, MSG_BOTTOM); + if (!st_shown) return; + gfx_set_bank(GFX_BANK_TRANSPARENT); + pop_text_map(); + pop_text_center_mapped(POP_TEXT_BIG, (POP_STATUS_L + POP_STATUS_R) / 2, + MSG_BASE, status_text[st_shown]); + pop_text_unmap(); +} + +/* ---- верхняя полоса ---------------------------------------------------- */ + +/* right — граница зоны (первый столбец СЛЕДУЮЩЕЙ подписи), w — её ширина. */ +static void dbg_value(int right, uint8_t w, const char *s) +{ + wipe(right - w, DBG_TOP, right - 1, DBG_BOTTOM); + gfx_set_bank(GFX_BANK_TRANSPARENT); + pop_text_map(); + pop_text_right_mapped(POP_TEXT_SMALL, right - 1, DBG_BASE, s); + pop_text_unmap(); +} + +/* Подписи неизменны — рисуем их только при полной перерисовке полосы. */ +static void dbg_labels(void) +{ + wipe(DBG_LEFT, DBG_TOP, DBG_RIGHT, DBG_BOTTOM); + gfx_set_bank(GFX_BANK_TRANSPARENT); + pop_text_map(); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_LEVEL, DBG_BASE, "Level"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_ROOM, DBG_BASE, ", Room"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_SPEED, DBG_BASE, ", Speed:"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_SOUND, DBG_BASE, ", Sound:"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_IMM, DBG_BASE, ", Immortal"); + pop_text_unmap(); +} + +static void dbg_draw(uint8_t mask) +{ + char buf[3]; + + /* Полная перерисовка стирает всю полосу разом — значит зоны значений + * стирать по второму разу не надо, но нарисовать их всё равно нужно. */ + if (mask == DBG_F_ALL) dbg_labels(); + if (mask & DBG_F_LEVEL) { + dec2(dbg_lvl, buf); + dbg_value(R_VAL_LEVEL, W_VAL_NUM, buf); + } + if (mask & DBG_F_ROOM) { + dec2(dbg_room, buf); + dbg_value(R_VAL_ROOM, W_VAL_NUM, buf); + } + if (mask & DBG_F_SPEED) + dbg_value(R_VAL_SPEED, W_VAL_SPEED, + speed_text[dbg_spd < 3 ? dbg_spd : 0]); + if (mask & DBG_F_SOUND) + dbg_value(R_VAL_SOUND, W_VAL_SOUND, dbg_snd ? "ON" : "OFF"); + if (mask & DBG_F_IMM) { + buf[0] = (char)('0' + (dbg_imm < 10 ? dbg_imm : 9)); + buf[1] = 0; + dbg_value(R_VAL_IMM, W_VAL_IMM, buf); + } +} + +/* Снимок ДО сравнения — обход sdcc_z80_cmp_store_a_bug: `if (n != s) s = n;` + * SDCC z80 собирает неверно (SUB затирает A, в снимок уезжает разность). */ +static void dbg_scan(uint8_t room) +{ + uint8_t prev_lvl = dbg_lvl, prev_room = dbg_room, prev_spd = dbg_spd; + uint8_t prev_snd = dbg_snd, prev_imm = dbg_imm; + uint8_t mask = 0; + + dbg_lvl = pop_current_level; + dbg_room = room; + dbg_spd = pop_speed_mode; + dbg_snd = pop_snd_want; + dbg_imm = pop_immortal; + if (dbg_lvl != prev_lvl) mask |= DBG_F_LEVEL; + if (dbg_room != prev_room) mask |= DBG_F_ROOM; + if (dbg_spd != prev_spd) mask |= DBG_F_SPEED; + if (dbg_snd != prev_snd) mask |= DBG_F_SOUND; + if (dbg_imm != prev_imm) mask |= DBG_F_IMM; + if (mask) { + dbg_dirty[0] |= mask; + dbg_dirty[1] |= mask; + } +} + +/* ---- кадровая точка ---------------------------------------------------- */ + +void pop_status_invalidate(void) __banked +{ + st_dirty = 3; + dbg_dirty[0] = DBG_F_ALL; + dbg_dirty[1] = DBG_F_ALL; +} + +void pop_status_frame(uint8_t room) __banked +{ + uint8_t page, bit, saved; + uint8_t want = pop_settings.show_debug_bar; + + if (!pop_ui_ready()) return; + page = (uint8_t)(gfx_get_draw_page() & 1); + bit = (uint8_t)(1 << page); + saved = gfx_get_bank(); + + /* --- нижняя полоса: заявка, таймер, перерисовка --- */ + if (pop_status_msg != st_shown) { + st_shown = pop_status_msg; + pop_status_ticks = (uint8_t)(st_shown ? MSG_TICKS : 0); + st_dirty = 3; + } else if (pop_status_ticks) { + pop_status_ticks--; + if (!pop_status_ticks) { + /* Оригинал по истечении зовёт erase_bottom_text(1) — снимаем и + * заявку, иначе то же сообщение никогда не покажется снова. */ + pop_status_msg = 0; + st_shown = 0; + st_dirty = 3; + } + } + if (st_dirty & bit) { + status_draw(); + st_dirty = (uint8_t)(st_dirty & ~bit); + } + + /* --- верхняя полоса --- */ + if (want != dbg_on) { + dbg_on = want; + /* Выключили — полосу надо стереть на ОБЕИХ страницах, иначе её + * остаток висел бы до следующей смены комнаты. */ + dbg_dirty[0] = DBG_F_ALL; + dbg_dirty[1] = DBG_F_ALL; + } + if (!want) { + if (dbg_dirty[page]) { + wipe(DBG_LEFT, DBG_TOP, DBG_RIGHT, DBG_BOTTOM); + dbg_dirty[page] = 0; + } + } else { + dbg_scan(room); + if (dbg_dirty[page]) { + dbg_draw(dbg_dirty[page]); + dbg_dirty[page] = 0; + } + } + + gfx_set_bank(saved); +} diff --git a/applications/PoP/roomtest/pop_status.h b/applications/PoP/roomtest/pop_status.h new file mode 100644 index 0000000..cf1eca2 --- /dev/null +++ b/applications/PoP/roomtest/pop_status.h @@ -0,0 +1,56 @@ +/* + * pop_status.h — ТЕКСТ в двух служебных полосах экрана (банк 9). + * + * Нижняя (полоса HP) — статус-строка оригинала: порт display_text_bottom + * (seg008:2644) и пары text_time_total/text_time_remaining. Полный + * разбор всех сообщений SDLPoP — ../docs/status_line_text.md. + * Верхняя — наша отладочная строка (была палочками, теперь текст). + * + * ЗАЯВКА — ОДИН БАЙТ. `pop_status_msg` лежит в W2 и пишется откуда + * угодно простым присваиванием: ни трамплина, ни функции. Всё остальное + * (таймер, перерисовка на обеих страницах, шрифт) делает pop_status_frame() + * из банка 9 — один банковый вызов на кадр, рядом с прежним ярлыком + * комнаты. Так в резиденте W1/W2 не появляется ни кода, ни таблиц строк. + */ +#ifndef POP_STATUS_H +#define POP_STATUS_H + +#include + +/* Сообщения нижней полосы. Порядок = таблица строк в pop_status.c. */ +enum { + POP_MSG_NONE = 0, + POP_MSG_QUICKSAVE, /* F6 успешно */ + POP_MSG_NO_QUICKSAVE, /* F6 не удалось */ + POP_MSG_QUICKLOAD, /* F9 успешно */ + POP_MSG_NO_QUICKLOAD, /* F9 нечего грузить / битый слот */ + POP_MSG_SOUND_ON, /* Ctrl+S */ + POP_MSG_SOUND_OFF, + POP_MSG_N +}; + +/* Заявка/текущее сообщение. Пишется откуда угодно: 0 — снять строку, + * POP_MSG_* — показать. Повторная заявка того же номера продлевает показ + * только если строка уже погасла (как у оригинала: он тоже сравнивает). */ +extern uint8_t pop_status_msg; + +/* Сколько логических кадров сообщение ещё висит; 0 — не висит. Читает + * pop_hp_draw (банк 4), чтобы не затирать чужой текст своим фоном. */ +extern uint8_t pop_status_ticks; + +/* Горизонтальные границы ТЕКСТА в полосе HP. Ровно промежуток между + * делениями Кида (слева) и стража (справа) — как rect_bottom_text + * {193,70,202,250} у оригинала. */ +#define POP_STATUS_L 70 +#define POP_STATUS_R 250 + +/* Раз в логический кадр, ПОСЛЕ pop_hp_draw: тик таймера, перерисовка + * нижней строки и обновление верхней отладочной. Всё change-driven — + * пока ничего не менялось, не рисует вовсе. */ +void pop_status_frame(uint8_t room) __banked; + +/* Заставить перерисовать ОБЕ страницы: звать после того, как полосы + * затёрли чужим содержимым (вход в комнату, выход из меню). */ +void pop_status_invalidate(void) __banked; + +#endif /* POP_STATUS_H */ diff --git a/applications/PoP/roomtest/pop_timer.c b/applications/PoP/roomtest/pop_timer.c new file mode 100644 index 0000000..60bd8c4 --- /dev/null +++ b/applications/PoP/roomtest/pop_timer.c @@ -0,0 +1,63 @@ +/* + * pop_timer.c — единый владелец глобального таймера (FG4, банк 9). + * + * SDLPoP хранит rem_min/rem_tick и уменьшает rem_tick на каждом логическом + * игровом кадре. Здесь та же схема: после нуля tick возвращается к 719, + * поэтому 720 тиков образуют минуту. Настройки, экранные сообщения и + * переход TIME_EXPIRED остаются снаружи: это делает ядро таймера чистым и + * не привязывает его к ещё не реализованным title/cutscene режимам. + */ +#include +#include "pop_timer.h" + +uint8_t pop_timer_minutes; +uint16_t pop_timer_ticks; + +void pop_timer_new_game(void) __banked +{ + pop_timer_minutes = POP_TIMER_START_MINUTES; + pop_timer_ticks = POP_TIMER_START_TICKS; +} + +uint8_t pop_timer_tick(uint8_t enabled, uint8_t may_run) __banked +{ + if (!enabled || !may_run || pop_timer_minutes == 0) return 0; + + --pop_timer_ticks; + if (pop_timer_ticks != 0) return 0; + + pop_timer_ticks = POP_TIMER_START_TICKS; + --pop_timer_minutes; + return (uint8_t)(pop_timer_minutes == 0); +} + +void pop_timer_cheat_subtract(void) __banked +{ + /* Это буквально правило SDLK_KP_MINUS: последнюю минуту читом не + * отнимаем, иначе не получится отличить чит от настоящего expiry. */ + if (pop_timer_minutes > 1) --pop_timer_minutes; +} + +void pop_timer_cheat_add(void) __banked +{ + /* SDLPoP хранит signed short, у нас достаточно byte: 255 минут — + * практический максимум и насыщение не даёт тихо свернуться в нуль. */ + if (pop_timer_minutes != 0xFF) ++pop_timer_minutes; +} + +void pop_timer_qsave(pop_qs_io_t *io) __banked +{ + uint8_t minutes = pop_timer_minutes; + uint16_t ticks = pop_timer_ticks; + + pop_qs_u8(io, &minutes); + pop_qs_u16(io, &ticks); + if (io->reading && io->apply) { + if (ticks == 0 || ticks > POP_TIMER_START_TICKS) { + io->failed = 1; + return; + } + pop_timer_minutes = minutes; + pop_timer_ticks = ticks; + } +} diff --git a/applications/PoP/roomtest/pop_timer.h b/applications/PoP/roomtest/pop_timer.h new file mode 100644 index 0000000..7a90fd0 --- /dev/null +++ b/applications/PoP/roomtest/pop_timer.h @@ -0,0 +1,44 @@ +/* + * pop_timer.h — глобальный игровой таймер (FG4, банк 9). + * + * Время измеряется логическими кадрами SDLPoP: 720 тиков = одна минута. + * Модуль намеренно не знает ни о клавиатуре, ни о графике, ни о состоянии + * Settings: вызывающий передаёт, разрешён ли лимит и можно ли времени идти. + * Благодаря этому тот же код проверяется host-тестом без DSS/MAME. + */ +#ifndef POP_TIMER_H +#define POP_TIMER_H + +#include +#include "pop_qsave.h" + +#define POP_TIMER_START_MINUTES 60 +#define POP_TIMER_TICKS_PER_MINUTE 720 +#define POP_TIMER_START_TICKS (POP_TIMER_TICKS_PER_MINUTE - 1) + +/* Оставшееся время. ticks всегда 1..719 вне короткого участка timer_tick. + * Нуль minutes означает естественное истечение; выключенный в Settings + * лимит состояние не меняет, а просто останавливает отсчёт. */ +extern uint8_t pop_timer_minutes; +extern uint16_t pop_timer_ticks; + +/* Начать новую игру: ровно исходные 60:00 SDLPoP (60 минут, tick 719). */ +void pop_timer_new_game(void) __banked; + +/* Один логический игровой кадр. enabled — переключатель Settings, + * may_run — правила текущей сцены/уровня. Возвращает 1 ОДИН раз, когда + * отсчёт дошёл до нуля; на паузе, HDD/QuickSave и при выключенном лимите + * вызывать можно — состояние останется неизменным. */ +uint8_t pop_timer_tick(uint8_t enabled, uint8_t may_run) __banked; + +/* Читы, повторяющие +/- SDLPoP: минус не даёт искусственно поставить 0, + * плюс добавляет минуту. Они меняют счётчик и при выключенном лимите — + * после включения пользователь продолжит именно это подготовленное время. */ +void pop_timer_cheat_subtract(void) __banked; +void pop_timer_cheat_add(void) __banked; + +/* Состояние входит в payload QuickSave. Неверный tick помечает поток + * ошибочным, не подменяя живой таймер частично. */ +void pop_timer_qsave(pop_qs_io_t *io) __banked; + +#endif /* POP_TIMER_H */ diff --git a/applications/PoP/roomtest/pop_title.c b/applications/PoP/roomtest/pop_title.c new file mode 100644 index 0000000..83c8c2c --- /dev/null +++ b/applications/PoP/roomtest/pop_title.c @@ -0,0 +1,130 @@ +/* + * pop_title.c — оригинальная графическая часть show_title(), банк 9 (FG6). + * + * Готовые композиции создаёт toolchain/pop_pack_title.py непосредственно из + * TITLE/res51..55.png SDLPoP. Каждый 320x200 экран режется на пять лент: + * загружаем одну в EMM, блитим непрозрачно в normal bank, освобождаем. Это + * сознательно не держит title-ресурсы рядом с игровыми атласами. + */ +#include +#include +#include +#include +#include +#include "pop_bg.h" /* POP_YOFF: title занимает 200px по центру 256px */ +#include "pop_sfx.h" +#include "pop_settings.h" +#include "pop_pal.h" +#include "pop_title.h" +#include "pop_ui.h" + +#define TITLE_W 320 +#define TITLE_H 200 +#define TITLE_Y POP_YOFF +#define TITLE_PARTS 5 +#define TITLE_FADE 4 + +enum { + TITLE_MAIN = 0, + TITLE_PRESENTS, + TITLE_GAME, + TITLE_LOGO +}; + +/* Имена короче 8.3; строка = композиция, столбец = полоса 49/49/49/49/4. */ +static const char * const title_part[4][TITLE_PARTS] = { + { "TITLE\\t0_0.atl", "TITLE\\t0_1.atl", "TITLE\\t0_2.atl", "TITLE\\t0_3.atl", "TITLE\\t0_4.atl" }, + { "TITLE\\t1_0.atl", "TITLE\\t1_1.atl", "TITLE\\t1_2.atl", "TITLE\\t1_3.atl", "TITLE\\t1_4.atl" }, + { "TITLE\\t2_0.atl", "TITLE\\t2_1.atl", "TITLE\\t2_2.atl", "TITLE\\t2_3.atl", "TITLE\\t2_4.atl" }, + { "TITLE\\t3_0.atl", "TITLE\\t3_1.atl", "TITLE\\t3_2.atl", "TITLE\\t3_3.atl", "TITLE\\t3_4.atl" } +}; + +/* Последовательность и интервалы буквально из show_title(): + * main --0x82--> presents --0xCD--> main --0x41--> game + * --0x10E--> main --0xEB--> logo+Mechner. + * SDLPoP BASE_FPS = 60, поэтому один счёт = один Sprinter vsync. */ +static const uint8_t title_scene[] = { + TITLE_MAIN, TITLE_PRESENTS, TITLE_MAIN, TITLE_GAME, TITLE_MAIN, TITLE_LOGO +}; +static const uint16_t title_wait_ticks[] = { + 0x82, 0xCD, 0x41, 0x10E, 0xEB, 0xEB +}; + +static uint8_t title_shown; + +static uint8_t title_skip_pressed(void) +{ + /* Как и в attract-demo, любой удерживаемый raw-код означает «не ждать». + * Это заодно не дублирует список клавиш у title, intro и cutscene. */ + return kbd_raw_any_down(); +} + +/* Нарисовать одну из готовых 320x200 композиций в скрытую страницу. + * Сопоставление W0 охватывает ТОЛЬКО atlas_image/blit: BIOS/vsync сюда + * не попадает, значит не конфликтует с EMM и CBL. Возвращает номер + * подготовленной страницы; show=0 оставляет её скрытой для palette commit. */ +static int title_draw(uint8_t scene, uint8_t show) +{ + uint8_t page = pop_screen_begin(); + + pop_screen_fill(BLACK); + if (pop_screen_blit_atlas_strips(title_part[scene], TITLE_PARTS, + 0, TITLE_Y) != 0) + return -1; + if (show) pop_screen_present(page); + return page; +} + +static uint8_t title_wait(uint16_t ticks) +{ + while (ticks--) { + if (title_skip_pressed()) return 1; + gfx_wait_vsync(); + } + return 0; +} + +/* Новый источник не становится видимым сам: cold boot уже чёрный, а + * последующий fade_in выбирает яркость только после сборки t0. */ +static void title_load_palette_dimmed(void) +{ + pop_pal_title_load(); + pop_pal_apply(4); +} + +uint8_t pop_title_show(void) __banked +{ + uint8_t step; + uint8_t skipped = 0; + int prepared; + uint8_t cold_start = (uint8_t)(!title_shown && + !pop_settings.show_build_info); + + /* До музыки title дойдём в FG8; игровые SFX не должны звучать поверх + * немой заставки и не должны обслуживаться во время дисковых чтений. */ + pop_sfx_pause(); + if (!cold_start) pop_pal_fade_out(TITLE_FADE); + + /* Видимая VRAM могла сохраниться от предыдущей программы. Обнулить её + * при чёрной палитре, затем собрать t0 СКРЫТО: загрузка title.pal тогда + * освещает только индекс 0, а не старый экран или недорисованный title. */ + gfx_set_draw_page(gfx_get_visible_page()); + gfx_set_bank(GFX_BANK_NORMAL); + pop_screen_fill(BLACK); + prepared = title_draw(TITLE_MAIN, 0); + if (prepared < 0) return 0; + title_load_palette_dimmed(); + pop_screen_present((uint8_t)prepared); + title_shown = 1; + + for (step = 0; step < sizeof(title_scene); step++) { + if (step && title_draw(title_scene[step], 1) < 0) + break; + if (step == 0) pop_pal_fade_in(TITLE_FADE); + if (title_wait(title_wait_ticks[step])) { + skipped = 1; + break; + } + } + return skipped; +} diff --git a/applications/PoP/roomtest/pop_title.h b/applications/PoP/roomtest/pop_title.h new file mode 100644 index 0000000..a66d004 --- /dev/null +++ b/applications/PoP/roomtest/pop_title.h @@ -0,0 +1,17 @@ +/* + * pop_title.h — первая часть оригинальной title sequence, банк 9 (FG6). + * + * Экран живёт отдельно от игрового кадра: он загружает одну ленту TITLE + * за раз, рисует готовую страницу и не оставляет EMM-страниц после выхода. + */ +#ifndef POP_TITLE_H +#define POP_TITLE_H + +#include + +/* Показать TITLE_MAIN/PRESENTS/GAME/POP+MECHNER в порядке show_title(). + * Возврат 1 — пользователь прервал ожидание и надо начать FIRST_LEVEL + * напрямую; 0 — последовательность завершилась штатно, продолжать intro. */ +uint8_t pop_title_show(void) __banked; + +#endif /* POP_TITLE_H */ diff --git a/applications/PoP/roomtest/pop_tune.h b/applications/PoP/roomtest/pop_tune.h index 4c8747e..3b92b65 100644 --- a/applications/PoP/roomtest/pop_tune.h +++ b/applications/PoP/roomtest/pop_tune.h @@ -109,11 +109,6 @@ #define POP_MOUSE_TURN_X 166 /* x < этого — развернуться и уйти */ #define POP_MOUSE_LEAVE_X 200 /* x >= этого в стойке — исчезнуть */ -/* ---- Уровень зелий (SDLPoP: «potions level», seg006:1894) ------------ * - * На нём синие склянки «вреда» забирают ПОЛОВИНУ запаса HP, а не единицу. - * В оригинале это условие `current_level == 15` прямо в proc_get_object. */ -#define POP_POTIONS_LEVEL 15 - /* С какого уровня начинается игра (-DFIRST_LEVEL=N при сборке). Живёт * здесь, а не в roomtest.c: старт уровня уехал в холодную половину цикла * (roomtest_cold.c, банк 8), и константа нужна обеим. */ diff --git a/applications/PoP/roomtest/pop_ui.c b/applications/PoP/roomtest/pop_ui.c new file mode 100644 index 0000000..758ee6d --- /dev/null +++ b/applications/PoP/roomtest/pop_ui.c @@ -0,0 +1,299 @@ +/* + * pop_ui.c — текст, палитровый fade и full-screen примитивы (FG5, банк 9). + * + * Шрифты извлечены из SDLPoP в FONT\FONT.ATL. Внутри map/unmap разрешены + * лишь обращения к EMM/W0: всё, что ждёт кадр или трогает BIOS-палитру, + * выполняется строго после unmap. Это общий контракт для menu/title/PV. + */ +#include +#include +#include +#include +#include "pop_bg.h" /* POP_YOFF/POP_PLAYFIELD_H — граница игрового поля */ +#include "pop_font.h" +#include "pop_ui.h" +#include "pop_status.h" + +typedef struct { + uint8_t base; + uint8_t ascent; + uint8_t space; +} pop_font_desc_t; + +static const pop_font_desc_t font_desc[] = { + { POP_FONT_SMALL_BASE, POP_FONT_SMALL_ASCENT, POP_FONT_SMALL_SPACE }, + { POP_FONT_BIG_BASE, POP_FONT_BIG_ASCENT, POP_FONT_BIG_SPACE } +}; + +static atlas_t font_at; +static uint8_t font_ready; + +static uint8_t text_index(const pop_font_desc_t *font, uint8_t ch) +{ + if (ch < POP_FONT_FIRST || ch > POP_FONT_LAST) ch = '?'; + return (uint8_t)(font->base + ch - POP_FONT_FIRST); +} + +int pop_ui_init(void) __banked +{ + if (font_ready) return 0; + if (atlas_load(&font_at, "FONT\\font.atl") != 0) return -1; + font_ready = 1; + return 0; +} + +void pop_ui_free(void) __banked +{ + if (!font_ready) return; + atlas_free(&font_at); + font_ready = 0; +} + +uint8_t pop_ui_ready(void) +{ + return font_ready; +} + +void pop_text_map(void) +{ + gfx_w0_map(font_at.page); +} + +void pop_text_unmap(void) +{ + gfx_w0_unmap(); +} + +uint8_t pop_text_width_mapped(pop_text_font_t font, const char *s) +{ + const pop_font_desc_t *desc = &font_desc[font]; + uint8_t width = 0; + + while (*s) { + const uint8_t *img = (const uint8_t *)atlas_image(&font_at, + text_index(desc, (uint8_t)*s++)); + width = (uint8_t)(width + img[0] + desc->space); + } + return width ? (uint8_t)(width - desc->space) : 0; +} + +void pop_text_draw_mapped(pop_text_font_t font, int x, int baseline, + const char *s) +{ + const pop_font_desc_t *desc = &font_desc[font]; + + while (*s) { + const uint8_t *img = (const uint8_t *)atlas_image(&font_at, + text_index(desc, (uint8_t)*s++)); + /* Пробел не рисует НИ ОДНОГО пикселя: его глиф целиком 0xFF, а + * текст всегда идёт прозрачным банком. Блит при этом стоит те же + * ~4,6 тыс. тактов, что и буква, поэтому в строке из полусотни + * символов пропуск пробелов — самая дешёвая экономия, какая тут + * есть (важно для статус-строк, они рисуются в игровом кадре). + * У крупного пробела вдобавок высота 0: для accel это 256 строк, + * и нулевой блит нельзя отдавать ядру даже noclip-вариантом. */ + if (img[2] && s[-1] != ' ') gfx_blit_noclip(x, baseline - desc->ascent, img); + x += img[0] + desc->space; + } +} + +void pop_text_center_mapped(pop_text_font_t font, int center, int baseline, + const char *s) +{ + pop_text_draw_mapped(font, center - pop_text_width_mapped(font, s) / 2, + baseline, s); +} + +void pop_text_right_mapped(pop_text_font_t font, int right, int baseline, + const char *s) +{ + pop_text_draw_mapped(font, right - pop_text_width_mapped(font, s), + baseline, s); +} + +/* W0 свободно: snapshot кладём в зарезервированный хвост страницы шрифта. + * 4 × 256 Б = ровно 1024 Б, [0x3C00,0x4000), и не пересекаются с атласом. */ +static void snapshot_chunk(uint8_t chunk, uint8_t *buf, uint8_t store) +{ + uint16_t i; + uint8_t *slot; + + gfx_w0_map(font_at.page); + slot = (uint8_t *)(POP_FONT_SNAPSHOT_OFF + (uint16_t)chunk * 256u); + for (i = 0; i < 256; i++) { + if (store) slot[i] = buf[i]; + else buf[i] = slot[i]; + } + gfx_w0_unmap(); +} + +void pop_ui_palette_snapshot(void) __banked +{ + uint8_t chunk, buf[256]; + + if (!font_ready) return; + for (chunk = 0; chunk < 4; chunk++) { + gfx_pal_get(0, (uint8_t)(chunk << 6), 64, buf); + snapshot_chunk(chunk, buf, 1); + } +} + +/* Из одного исходного снимка приготовить обе экранные палитры. Умножения + * нет: шкала 1/4, 1/2, 3/4 на 8-битном значении не должна тащить делитель + * в постоянно вызываемый (во время fade) путь. */ +void pop_ui_palette_dim(uint8_t dim_step, uint8_t keep_ui) __banked +{ + uint8_t chunk, i, start, buf[256]; + + if (!font_ready) return; + if (dim_step > 4) dim_step = 4; + for (chunk = 0; chunk < 4; chunk++) { + start = (uint8_t)(chunk << 6); + snapshot_chunk(chunk, buf, 0); + if (dim_step) { + for (i = 0; i < 64; i++) { + uint8_t idx = (uint8_t)(start + i); + uint16_t p = (uint16_t)i * 4u; + if (keep_ui && idx >= 0x37 && idx <= 0x3F) continue; + if (dim_step == 1) { + buf[p] = (uint8_t)((buf[p] >> 1) + (buf[p] >> 2)); + buf[p + 1] = (uint8_t)((buf[p + 1] >> 1) + + (buf[p + 1] >> 2)); + buf[p + 2] = (uint8_t)((buf[p + 2] >> 1) + + (buf[p + 2] >> 2)); + } else if (dim_step == 2) { + buf[p] >>= 1; buf[p + 1] >>= 1; buf[p + 2] >>= 1; + } else if (dim_step == 3) { + buf[p] >>= 2; buf[p + 1] >>= 2; buf[p + 2] >>= 2; + } else { + buf[p] = buf[p + 1] = buf[p + 2] = 0; + } + } + } + gfx_pal_load(0, start, 64, buf); + gfx_pal_load(1, start, 64, buf); + } +} + +void pop_ui_palette_restore(void) __banked +{ + pop_ui_palette_dim(0, 0); +} + +void pop_ui_palette_black(void) __banked +{ + uint8_t chunk; + uint16_t i; + uint8_t black[256]; + + /* BIOS принимает максимум 64 записи за вызов; одним буфером покрываем + * все четыре четверти палитры, не занимая постоянную память. */ + for (i = 0; i < sizeof(black); i++) black[i] = 0; + for (chunk = 0; chunk < 4; chunk++) { + uint8_t start = (uint8_t)(chunk << 6); + gfx_pal_load(0, start, 64, black); + gfx_pal_load(1, start, 64, black); + } +} + +void pop_ui_fade_out(uint8_t steps) __banked +{ + uint8_t i; + if (!steps) return; + pop_ui_palette_snapshot(); + for (i = 1; i <= steps; i++) { + uint8_t step = (uint8_t)((uint16_t)i * 4u / steps); + pop_ui_palette_dim(step, 0); + gfx_wait_vsync(); + } +} + +void pop_ui_fade_in(uint8_t steps) __banked +{ + uint8_t i; + if (!steps) { pop_ui_palette_restore(); return; } + for (i = steps; i > 0; i--) { + uint8_t step = (uint8_t)((uint16_t)(i - 1) * 4u / steps); + pop_ui_palette_dim(step, 0); + gfx_wait_vsync(); + } +} + +uint8_t pop_screen_begin(void) __banked +{ + uint8_t page = (uint8_t)(gfx_get_visible_page() ^ 1); + gfx_set_draw_page(page); + gfx_set_bank(GFX_BANK_NORMAL); + return page; +} + +void pop_screen_fill(uint8_t color) __banked +{ + setfillstyle(SOLID_FILL, color); + bar(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); +} + +/* Экран под ИГРУ: вне поля — цвет POP_COL_OUTSIDE, внутри — цвет 0. + * + * Зачем два разных чёрных. Вспышки (молния Джафара, урон, зелья) сделаны + * подменой ЦВЕТА 0 всей палитры — как в оригинале. Пока борта и полоса HP + * тоже были нулём, мигал весь экран целиком, вместе с рамкой и жизнями. + * Красим всё, что вне поля 320x192, отдельным индексом, а в палитре держим + * его чёрным (pop_pal_outside_apply): картинка та же, но вспышка достаёт + * только игровую область. Идея пользователя, 2026-08-25. + * + * Индексы 1..15 у нас свободны: атласы кладут либо 0xFF (прозрачность), + * либо PAL_BASE+v (0x40 и выше), шрифт — 0x3F. */ +void pop_screen_fill_game(void) __banked +{ + setfillstyle(SOLID_FILL, POP_COL_OUTSIDE); + bar(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + setfillstyle(SOLID_FILL, BLACK); + bar(0, POP_YOFF, GFX_WIDTH - 1, POP_CLIP_BOTTOM - 1); + /* Заливка съела и обе служебные полосы — они ВНЕ игрового поля. + * Инвалидируем здесь, а не у вызывающих: точек заливки несколько + * (старт игры, смена уровня), и пропустить одну из них — ровно тот + * баг, из-за которого строка исчезала при переходе между комнатами. */ + pop_status_invalidate(); +} + +/* Только ИГРОВОЕ ПОЛЕ, без бортов. Вход в комнату перерисовывает лишь + * его: борта от комнаты к комнате не меняются, а всё, что на них живёт — + * полоса HP, статус-строка, отладочная строка — обязано вход пережить. + * Заодно это на четверть меньше заливки (192 строки вместо 256). */ +void pop_screen_fill_field(void) __banked +{ + setfillstyle(SOLID_FILL, BLACK); + bar(0, POP_YOFF, GFX_WIDTH - 1, POP_CLIP_BOTTOM - 1); +} + +void pop_screen_present(uint8_t page) __banked +{ + gfx_wait_vsync(); + gfx_set_visible_page(page); +} + +/* Полноэкранные изображения не помещаются одним getimage в EMM-страницу. + * Общий загрузчик полос держит лишь одну страницу и не трогает page flip: + * caller уже собрал скрытую страницу и сам решает, когда её показать. */ +int pop_screen_blit_atlas_strips(const char * const *parts, + uint8_t count, int x, int y) __banked +{ + uint8_t part; + + gfx_set_bank(GFX_BANK_NORMAL); + for (part = 0; part < count; part++) { + atlas_t at; + const uint8_t *img; + + if (atlas_load(&at, parts[part]) != 0) return -1; + gfx_w0_map(at.page); + img = (const uint8_t *)atlas_image(&at, 0); + /* gfx_blit_noclip ограничен шириной uint8 и обрезает 320px. */ + gfx_blit(x, y, img); + y += img[2]; + gfx_w0_unmap(); + atlas_free(&at); + } + return 0; +} diff --git a/applications/PoP/roomtest/pop_ui.h b/applications/PoP/roomtest/pop_ui.h new file mode 100644 index 0000000..aeafa57 --- /dev/null +++ b/applications/PoP/roomtest/pop_ui.h @@ -0,0 +1,78 @@ +/* + * pop_ui.h — общий экранный слой PoP (FG5, банк 9). + * + * Здесь только примитивы: исходный SDLPoP-шрифт, палитровый fade и сборка + * полной страницы. Menu, title и cutscene держат собственные автоматы и + * строки, но не дублируют формат FONT.ATL или опасную работу с W0. + */ +#ifndef POP_UI_H +#define POP_UI_H + +#include + +typedef enum { + POP_TEXT_SMALL = 0, + POP_TEXT_BIG = 1 +} pop_text_font_t; + +/* Жизненный цикл единственного FONT\FONT.ATL. Загрузка — в холодном boot, + * освобождение — после закрытия графики; повторные вызовы безопасны. */ +int pop_ui_init(void) __banked; +void pop_ui_free(void) __banked; +uint8_t pop_ui_ready(void); + +/* Группа text_*_mapped требует активного pop_text_map() и не вызывает + * BIOS/ESTEX. Для прозрачных букв установить GFX_BANK_SPRITE до рисования. */ +void pop_text_map(void); +void pop_text_unmap(void); +uint8_t pop_text_width_mapped(pop_text_font_t font, const char *s); +void pop_text_draw_mapped(pop_text_font_t font, int x, int baseline, + const char *s); +void pop_text_center_mapped(pop_text_font_t font, int center, int baseline, + const char *s); +void pop_text_right_mapped(pop_text_font_t font, int right, int baseline, + const char *s); + +/* Снимок обеих (идентичных по контракту) палитр хранится в свободном хвосте + * FONT.ATL. dim_step: 0 — исходная, 1 — ~75%, 2 — 50%, 3 — 25%, 4 — чёрная. + * keep_ui сохраняет диапазон 0x37..0x3F ярким для текста/рамки меню. */ +void pop_ui_palette_snapshot(void) __banked; +void pop_ui_palette_dim(uint8_t dim_step, uint8_t keep_ui) __banked; +void pop_ui_palette_restore(void) __banked; + +/* Немедленно погасить все 256 цветов обеих страниц. В отличие от dim(), + * не требует загруженного FONT.ATL: это ранний экран запуска, когда в VRAM + * ещё может лежать картинка предыдущей программы. */ +void pop_ui_palette_black(void) __banked; + +/* Пошаговый fade к снимку: fade_out сначала делает снимок, fade_in требует + * его сохранить. Каждый шаг показывается на следующем фронте. */ +void pop_ui_fade_out(uint8_t steps) __banked; +void pop_ui_fade_in(uint8_t steps) __banked; + +/* Полная экранная страница: begin выбирает скрытую страницу, fill рисует + * непрозрачный фон и present делает ГОТОВУЮ страницу видимой на фронте. + * Текст на второй странице надо нарисовать заново — GFX_BANK_SPRITE не + * меняет shadow-копию, и это сознательный контракт временного UI. */ +uint8_t pop_screen_begin(void) __banked; +void pop_screen_fill(uint8_t color) __banked; +/* Индекс палитры для всего, что ВНЕ игрового поля 320x192 (борта, полоса + * HP). В палитре он чёрный, но НЕ нулевой — поэтому вспышки, которые + * подменяют цвет 0, не задевают рамку и жизни. */ +#define POP_COL_OUTSIDE 1 +/* Подготовить экран под игру: вне поля POP_COL_OUTSIDE, внутри — цвет 0. + * Звать на СТАРТЕ и смене уровня; заодно сбрасывает служебные полосы. */ +void pop_screen_fill_game(void) __banked; +/* Только игровое поле (борта и всё, что на них, не трогает) — это путь + * входа в комнату. */ +void pop_screen_fill_field(void) __banked; +void pop_screen_present(uint8_t page) __banked; + +/* Нарисовать в уже выбранную draw-страницу набор вертикальных полос SPA1. + * Полоса загружается, блитится и тут же освобождается; функция не вызывает + * BIOS/vsync, поэтому её можно безопасно применять в title и cutscene. + * Возвращает -1 при ошибке чтения очередной полосы. */ +int pop_screen_blit_atlas_strips(const char * const *parts, + uint8_t count, int x, int y) __banked; + +#endif /* POP_UI_H */ diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 522fa87..3ba2359 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -27,6 +27,18 @@ #include "pop_pace.h" /* фиксированный логический кадр по лучу */ #include "roomtest_cold.h" /* холодная половина цикла — в банке */ #include "pop_qsave.h" +#include "pop_status.h" +#include "pop_menu.h" /* pause menu — банк 9, команды наружу */ +#include "pop_settings.h" /* единая модель скорости/звука/читов */ +#include "pop_app.h" /* автомат приложения: игровой цикл = PLAYING */ +#include "pop_timer.h" /* global 60-minute timer — bank 9 */ +#include "pop_title.h" /* исходная графическая title sequence — bank 9 */ +#include "pop_intro.h" /* story + Princess/Jaffar после title — bank 11 */ +#include "pop_flow.h" /* таблица pre-level cutscene — bank 9 */ +#include "pop_demo.h" /* attract mode level 0 — bank 9 */ +#include "pop_pal.h" /* lifecycle палитры title/intro/level */ +#include "pop_hof.h" /* persistent Hall of Fame — bank 9 */ +#include "pop_ui.h" /* чёрные обе страницы перед сменой палитры */ __sfr __at (0xFE) io_border; @@ -70,13 +82,18 @@ __sfr __at (0xFE) io_border; * 6 = pop_trob (ловушки/анимация тайлов), 7 = pop_room (ХОЛОДНАЯ половина * фона: полная отрисовка комнаты, точечные перерисовки, падающие плиты), * 8 = roomtest_cold (ХОЛОДНАЯ половина главного цикла: старт уровня и - * отладочная навигация — см. roomtest_cold.h). + * отладочная навигация — см. roomtest_cold.h), 9 = pop_menu (ВСЯ работа + * с меню: автомат, рендерер, Settings и build screen; наружу только + * команды gameplay). * Отрисовка персонажей и холодная половина фона вынесены из банка 2 * отдельно: тот дважды подходил к 16 КБ вплотную, а расти ему ещё есть * куда — тайлы поздних уровней, чомперы, зеркало. */ /* Режим читов (pop_cheat.h): на время разработки включаем в main. */ uint8_t pop_cheats; +/* Единственная резидентная часть UI: банк 9 вызывается только по запросу, + * поэтому обычный кадр не платит трамплин. */ +uint8_t pop_menu_request; /* ПОТЕРЯ НАЖАТИЙ ПРИ ЗАЖАТОМ Shift — лечится ПЛОТНЫМ опросом клавиатуры. * @@ -111,6 +128,9 @@ static void kbd_idle(void) #define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */ #define KBD_QUICKSAVE 0x0B /* F6, PS/2 set 2 */ #define KBD_QUICKLOAD 0x01 /* F9, PS/2 set 2 */ +#define KBD_MENU 0x76 /* Esc — открыть pause menu (edge) */ +#define KBD_QUIT 0x09 /* F10 — отладочный немедленный выход (был на Esc, + * пока меню не готово рисовать — см. план MS4) */ #define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */ #define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */ @@ -219,10 +239,10 @@ static void flash_bg(uint8_t r, uint8_t g, uint8_t b) int main(void) { - /* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1 - * по умолчанию (без мерцания); SPACE переключает в однобуфер для - * отладки багов рисования (edge по sp_prev, чтоб не дёргать). */ - uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0; + /* Дабл-буфер: front — видимая страница, back — куда рисуем. Отладочный + * тумблер «однобуфер по SPACE» убран (решение пользователя 2026-08-25): + * дабл-буфер теперь безусловный, а SPACE свободен под игру. */ + uint8_t front = 0, back, frozen = 0; uint8_t pace_prev = 0; /* edge клавиши смены режима скорости */ uint8_t skip = 0; /* маска «слот перерисовывать не надо» */ uint16_t dead_frames = 0; @@ -231,9 +251,11 @@ int main(void) #endif uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0; uint8_t xdec_prev = 0, xinc_prev = 0, upsd_prev = 0; + uint8_t failed = 0; /* Загрузка ресурсов, палитра, первый экран — В БАНКЕ (roomtest_cold.c): * отрабатывает ровно один раз за игру, а кода там на полкилобайта. */ + pop_app_reset(); if (pop_boot() != 0) return 1; if (pop_ctrl_init() != 0) { /* raw-канал клавиатуры (забирает ввод у DSS) */ @@ -242,36 +264,139 @@ int main(void) return 1; } gfx_set_idle_hook(kbd_idle); /* вычерпывать FIFO, пока ждём кадр */ + if (!pop_app_dispatch(POP_APP_EV_BOOT_READY)) { + pop_shutdown(); + return 1; + } + /* MS8: CFG уже применён в pop_boot до первого показа. Экран build + * показывается только по явной пользовательской настройке. */ + pop_build_info_show(); + if (!pop_app_dispatch(POP_APP_EV_BUILD_DONE)) { + pop_shutdown(); + return 1; + } /* Темп кадра по лучу. Если бит 5 порта 0xFE не шевелится (иное * железо), pop_pace_* молча откатываются на прежние gfx_wait_vsync. */ pop_pace_arm(); { uint8_t qs_save_prev = 0, qs_load_prev = 0; - while (!kbd_raw_down(KBD_ESC)) { - /* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один - * переключ). Выключение возвращает показ на страницу 0. */ + uint8_t quit = 0, esc_prev = 0, menu_restart = 0; + uint8_t demo_new_game, demo_finished; + uint8_t reveal_pending = 0; /* уровень готовится за чёрной палитрой */ + for (;;) { + uint8_t is_demo; + /* Title -> intro -> demo — три самостоятельных состояния. Сцены не + * вызывают gameplay рекурсивно: после каждого return следующий маршрут + * выбирается только pop_app_dispatch(). */ + if (pop_app_state == POP_APP_TITLE) { + pop_app_event_t ev = pop_title_show() ? POP_APP_EV_NEW_GAME : + POP_APP_EV_INTRO_STARTED; + if (!pop_app_dispatch(ev)) { + failed = 1; + break; + } + } + if (pop_app_state == POP_APP_INTRO) { + uint8_t skipped = pop_intro_show(); + pop_app_event_t ev; + + if (!skipped) { + /* show_title() SDLPoP помещает сохранённую таблицу после credits + * и до attract-demo. При skip таблица тоже пропускается. */ + pop_hof_show_title(); + } + ev = skipped ? POP_APP_EV_NEW_GAME : POP_APP_EV_CUTSCENE_FINISHED; + if (!pop_app_dispatch(ev)) { + failed = 1; + break; + } + } + if (pop_app_state == POP_APP_ENDING) { + pop_ending_show(); + if (!pop_app_dispatch(POP_APP_EV_ENDING_FINISHED)) { + failed = 1; + break; + } + continue; + } + if (pop_app_state == POP_APP_HALL_OF_FAME) { + pop_hof_show(); + if (!pop_app_dispatch(POP_APP_EV_HOF_FINISHED)) { + failed = 1; + break; + } + continue; + } + + /* Enter/Esc на title/story/PV/credits и клавиша в attract-demo сходятся + * в ОДИН маршрут LEVEL_LOAD. Начальные уровень/комната берутся из + * FIRST_LEVEL и данных уровня — то есть из параметров текущей сборки. */ + if (pop_app_state == POP_APP_LEVEL_LOAD) { + if (pop_new_game_load() != 0 || + !pop_app_dispatch(POP_APP_EV_LEVEL_READY)) { + failed = 1; + break; + } + } + + /* После intro всегда запускается настоящий level 0. pop_level_switch() + * даёт тот же lifecycle, что и обычной границе уровней: новый tileset, + * актёры, палитра, обе страницы room background. Это важно, поскольку + * девелоперская сборка может до title держать дворец LEVEL=11. */ + if (pop_app_state == POP_APP_DEMO) { + pop_next_level = 0; + pop_seamless = 0; + pop_checkpoint = 0; + if (pop_level_switch() != 0) { + failed = 1; + break; + } + /* custom->demo_hitp = 4. level_switch переносит HP предыдущего + * уровня по обычному правилу, поэтому demo-вариант задаём после + * него и просим полосу HP перепечатать новое число. */ + hitp_beg_lev = 4; + pop_kid_hp_reset(); + pop_hp_invalidate(); + pop_demo_begin(); + /* show_title() SDLPoP начинает attract-demo сразу, без fade-in. + * Credits уже ушли в чёрный, intro_restore_game_palette оставил + * обе страницы чёрным растром, а level_switch успел собрать на них + * level 0. Поэтому яркую game palette можно включить одним commit: + * старый credits-кадр физически уже негде высветить. */ + pop_pal_apply(0); + (void)pop_sfx_start(); + } + is_demo = (uint8_t)(pop_app_state == POP_APP_DEMO); + if (!is_demo && pop_app_state != POP_APP_PLAYING) break; + demo_new_game = 0; + demo_finished = 0; + + while (!quit && (pop_app_state == POP_APP_PLAYING || + pop_app_state == POP_APP_DEMO)) { PROF(2); /* ввод + heal */ - uint8_t sp = kbd_raw_down(KBD_SPACE); - if (sp && !sp_prev) { - dbuf ^= 1; - if (!dbuf) { - gfx_set_visible_page(0); - front = 0; + /* Demo читает raw-карту первым: любая клавиша начинает новую игру, + * не попадая в Pause/QuickSave/чит. pop_ctrl_auto_move() ниже raw + * вообще не трогает, поэтому карта не будет сброшена между проверкой + * и решением оболочки. */ + if (is_demo) { + kbd_raw_sync(); + if (pop_demo_user_input()) { + demo_new_game = 1; + break; } } - sp_prev = sp; - /* P — переключить режим скорости по кругу (FASTEST -> FAST -> * NORMAL). Edge, чтоб одно нажатие = один шаг. Дефолт FASTEST * (crt0 зануляет _DATA). Что означают режимы — pop_pace.h. */ - { + if (!is_demo) { uint8_t pk = kbd_raw_down(KBD_SPEED_MODE); if (pk && !pace_prev) { uint8_t m = (uint8_t)(pop_speed_mode + 1); pop_speed_mode = (uint8_t)(m >= POP_SPEED_MODES ? 0 : m); + pop_settings.speed_mode = pop_speed_mode; } pace_prev = pk; } @@ -279,14 +404,60 @@ int main(void) /* Отладочный стоп-кадр: '1' (PS/2 0x16) — заморозить, '2' (0x1E) — * продолжить. В заморозке пропускаем весь tick/draw/flip: картинка * стоит, можно спокойно разбирать позу Kid и окклюзию. */ - if (kbd_raw_down(0x16)) frozen = 1; - if (kbd_raw_down(0x1E)) frozen = 0; + if (!is_demo) { + if (kbd_raw_down(0x16)) frozen = 1; + if (kbd_raw_down(0x1E)) frozen = 0; + } /* Читы — В БАНКЕ (roomtest_cold.c): они дёргаются раз в кадр, а места * в резиденте W1/W2 нет (см. TASKS_OPEN, MEM). */ - pop_cheat_tick(); + if (!is_demo) pop_cheat_tick(); + /* Esc — открыть меню (edge). Меню МОДАЛЬНО: пока пользователь не + * выбрал пункт, pop_menu_process не возвращается и игра стоит. + * Команды исполняет ГЛАВНЫЙ ЦИКЛ — UI не зовёт gameplay internals: + * QuickSave/QuickLoad идут тем же механизмом, что F6/F9 (запрос + * подхватит блок ниже в этой же границе кадра), рестарты исполняются + * после вычисления back-страницы (как респавн), QUIT рвёт цикл. */ + if (!is_demo) { + uint8_t e = kbd_raw_down(KBD_MENU); + uint8_t menu_opened; + int8_t mrc = POP_MENU_NONE; + if (e && !esc_prev) pop_menu_request = 1; + esc_prev = e; + menu_opened = pop_menu_request; + if (menu_opened) { + if (!pop_app_dispatch(POP_APP_EV_PAUSE)) { + failed = 1; quit = 1; break; + } + mrc = pop_menu_process(); + } + if (mrc == POP_MENU_QUICKSAVE) pop_qsave_request_save(); + else if (mrc == POP_MENU_QUICKLOAD) pop_qsave_request_load(); + else if (mrc == POP_MENU_QUICKLOAD_BACKUP) + pop_qsave_request_load_backup(); + else if (mrc == POP_MENU_RESTART_LEVEL) menu_restart = 1; + else if (mrc == POP_MENU_RESTART_GAME) { + if (!pop_app_dispatch(POP_APP_EV_RESTART_INTRO)) failed = 1; + break; + } + else if (mrc == POP_MENU_QUIT) { + if (!pop_app_dispatch(POP_APP_EV_QUIT)) failed = 1; + quit = 1; + break; + } + if (menu_opened && !pop_app_dispatch(POP_APP_EV_RESUME)) { + failed = 1; quit = 1; break; + } + } + /* F10 — отладочный немедленный выход (раньше был на Esc, но Esc + * теперь открывает меню; уйдёт вместе с отладкой). */ + if (!is_demo && kbd_raw_down(KBD_QUIT)) { + if (!pop_app_dispatch(POP_APP_EV_QUIT)) failed = 1; + quit = 1; + break; + } /* F6/F9 только ставят запрос; файловая операция выполняется здесь, * на границе кадров — вне окон Char и до heal/tick/draw. */ - { + if (!is_demo) { uint8_t s = kbd_raw_down(KBD_QUICKSAVE); uint8_t l = kbd_raw_down(KBD_QUICKLOAD); int8_t qrc; @@ -310,7 +481,7 @@ int main(void) * '-' (0x7B / 0x4E) — назад. Edge по nav_prev: одно нажатие = одна * комната. Сама навигация — в банке (roomtest_cold.c): она нужна * только по клавише, а место в резиденте дефицитно. */ - { + if (!is_demo) { uint8_t nav = (uint8_t)((kbd_raw_down(0x79) || kbd_raw_down(0x55)) | ((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1)); if (nav && !nav_prev && pop_dbg_roomnav((uint8_t)(nav & 1 ? 1 : 2))) @@ -319,14 +490,22 @@ int main(void) } #endif - /* Рисуем в СКРЫТУЮ страницу (при dbuf); в однобуфере — в 0. + /* Рисуем в СКРЫТУЮ страницу дабл-буфера. * Видимую перечитываем У ЖЕЛЕЗА, а не ведём свой счёт: её меняют и * вход в комнату (ENTER-ROOM-FAST), и переворот экрана * (pop_flip_screen), причём оба — из банка, где front недоступен. */ front = gfx_get_visible_page(); - back = dbuf ? (uint8_t)(front ^ 1) : 0; + back = (uint8_t)(front ^ 1); gfx_set_draw_page(back); + /* Рестарты из меню — ЗДЕСЬ, с известной back-страницей (тот же + * контракт, что у респавна ниже). */ + if (menu_restart == 1) { /* Restart Level — штатный reset уровня */ + pop_start_level(); + dead_frames = 0; + menu_restart = 0; + } + /* Переворот только что переключился (зелье инверсии, ур. 9) — * привести уже нарисованное в новый вид ОДИН раз, до кадра * (порт need_redraw_because_flipped, seg000:939). Дальше слои @@ -342,7 +521,7 @@ int main(void) * не стоит ничего (pop_vflip_ready). */ if (pop_upside && !pop_vflip_ready()) pop_vflip_load_all(); pop_flip_screen(); - back = dbuf ? (uint8_t)(gfx_get_visible_page() ^ 1) : 0; + back = (uint8_t)(gfx_get_visible_page() ^ 1); gfx_set_draw_page(back); } pop_dbg_m13(); /* ЗАМЕР: ввод и читы разобраны */ @@ -356,7 +535,8 @@ int main(void) pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */ pop_dbg_m14(); /* ЗАМЕР: спецсобытия + луч видимости */ pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */ - pop_ctrl_tick(); /* ввод -> control(): смена seq */ + if (is_demo) demo_finished = pop_demo_tick(); + else pop_ctrl_tick(); /* ввод -> control(): смена seq */ pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */ /* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы — * тот на ней уже нарисован правильно: ни стирать, ни рисовать заново @@ -386,7 +566,25 @@ int main(void) * попадания с обеих сторон, потом применить, потом свести HP. */ pop_check_sword_hurting(); pop_check_sword_hurt(); + /* check_sword_vs_sword (seg000:1353): кадр 167 — клинки уже + * столкнулись. Раньше сам parry работал, но эта отдельная строка + * оригинального play_frame была потеряна, поэтому звон sound 10 + * не номинировался ни в обычном бою, ни в attract-demo. */ + if (Kid.frame == FRAME_167_BLOCKED || Guard.frame == FRAME_167_BLOCKED) + pop_sfx_play(10); pop_do_delta_hp(); + /* Порт show_time(): счётчик уменьшается только в живом игровом + * кадре. Пауза, frozen, HDD и QuickSave проходят через continue + * выше, поэтому не создают бесплатных/лишних тиков. На уровне + * Джафара время идёт до открытия двери, но истечение уже не может + * перевести игру в TIME_EXPIRED — буквально правило SDLPoP. */ + if (!is_demo && pop_timer_tick(pop_settings.time_limit_enabled, + (uint8_t)(Kid.alive < 0 && + (pop_current_level < 13 || + (pop_current_level == 13 && pop_leveldoor_open == 0))))) { + if (!pop_app_dispatch(POP_APP_EV_TIME_EXPIRED)) failed = 1; + break; + } pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */ pop_guard_fallout(); /* страж провалился ниже комнаты — убрать (seg002:241) */ if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */ @@ -536,13 +734,82 @@ int main(void) pop_next_level = (uint8_t)(pop_current_level + 1); pop_seamless = 1; } - if (pop_next_level != pop_current_level) { + /* WIN_LEVEL/WIN_ROOM SDLPoP: скрытый 14-й уровень заканчивается + * входом в room 5. Не подменяем это `next_level=15`: уровня 15 в + * сборке принципиально нет, а ending — самостоятельное состояние. */ + if (!is_demo && pop_flow_is_ending(pop_current_level, kid_room)) { + if (!pop_app_dispatch(POP_APP_EV_ENDING_STARTED)) { + failed = 1; + quit = 1; + } + break; + } + /* В level 0 нет обычной победы: достигли последней комнаты, умерли + * или доиграли запись — показать текущий кадр и затем вернуться в + * title. В частности, не вызываем level_switch(0->1). */ + if (is_demo && (kid_room == 24 || pop_kid_dead)) demo_finished = 1; + if (!is_demo && pop_next_level != pop_current_level) { + pop_pre_cutscene_t pre = pop_flow_pre_cutscene(pop_next_level, + pop_timer_minutes); /* Смена уровня целиком — в банке (pop_level_switch): грузится - * файл, спрайты соперника и тайлсет. Раз на уровень. */ - if (pop_level_switch() != 0) return 1; + * файл, спрайты соперника и тайлсет. До 2/4/6/8/9/12 таблица + * flow запускает отдельное состояние CUTSCENE, как play_level() + * SDLPoP: следующий уровень не трогаем до завершения/skip. */ + if (pre != POP_PRE_NONE) { + if (!pop_app_dispatch(POP_APP_EV_CUTSCENE_STARTED)) { + failed = 1; + quit = 1; + break; + } + pop_pre_cutscene_show(pre); + if (!pop_app_dispatch(POP_APP_EV_CUTSCENE_FINISHED)) { + failed = 1; + quit = 1; + break; + } + /* Катсцена оставляет экран чёрным. Новый уровень будет + * открыт одним применением его готовой палитры. */ + reveal_pending = 1; + } else { + /* CBL глушим ДО первой же операции с палитрой. Палитра идёт + * через BIOS ($A4) с запрещёнными прерываниями: насос не + * успевает долить блок, железо крутит хвост своего буфера — + * на слух скрежет. Раньше pop_sfx_pause() звался только + * внутри pop_level_switch, то есть УЖЕ ПОСЛЕ гашения, и + * переход без катсцены (в т.ч. телепорт Shift+L) скрежетал + * почти всегда — поймано пользователем 2026-08-25. Ветка с + * катсценой свободна от этого: pop_pre_cutscene_show глушит + * звук первым действием. */ + pop_sfx_pause(); + /* Без fade: старый кадр гасим сразу и обязательно даём + * показать один чёрный растр ДО диска/перерисовки. */ + pop_pal_apply(4); + pop_wait_edge(); + reveal_pending = 1; + if (!pop_app_dispatch(POP_APP_EV_LEVEL_FINISHED)) { + failed = 1; + quit = 1; + break; + } + } + if (pop_level_switch() != 0 || + !pop_app_dispatch(POP_APP_EV_LEVEL_READY)) { + failed = 1; + quit = 1; + break; + } + if (reveal_pending) { + /* Обе страницы и snapshot уже принадлежат новому уровню: + * включить его корректную палитру одним переходом. */ + pop_pal_apply(0); + reveal_pending = 0; + } + /* level_switch оставляет CBL закрытым: palette/fade идут через + * BIOS и при открытом буфере давали скрежет на первом кадре. */ + (void)pop_sfx_start(); gfx_set_draw_page(back); } - if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */ + if (!is_demo && pop_kid_dead) { /* demo после смерти завершается, без respawn */ dead_frames++; if (dead_frames > RESPAWN_SETTLE && (kbd_raw_down(KBD_UP) || dead_frames > RESPAWN_DELAY)) { @@ -721,12 +988,12 @@ int main(void) * важнейшая. Раньше по кадру нельзя — потеряются поздние заявки. */ pop_sfx_tick(); - /* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО - * при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера), - * а не каждый кадр (обход sdcc_z80_cmp_store_a_bug: сначала сохранить). */ -#ifdef ROOMNAV - pop_dbg_room_label(cur_room); /* номер комнаты палочками — в банке */ -#endif + /* Обе служебные полосы — нижняя статус-строка (сообщения оригинала) + * и верхняя отладочная. Всё change-driven и целиком в банке 9, + * поэтому звать можно каждый кадр: пока показанное не изменилось, + * работы там нет. Место — ПОСЛЕ pop_hp_draw: тот делит нижнюю + * полосу с текстом. */ + pop_status_frame(cur_room); pop_dbg_m4(); /* ЗАМЕР: конец спрайтов */ pop_beam_sample(); /* ТЕМП КАДРА: выборка луча (pop_pace.h) */ @@ -738,13 +1005,47 @@ int main(void) /* Условие боя — буквально как у оригинала (SDLPoP seg003.c:363): * не «идёт бой» и не «рядом страж», а «у Кида ВЫНУТ МЕЧ». */ pop_pace_end(pop_pace_n((uint8_t)(Kid.sword == SWORD_2_DRAWN))); - if (dbuf) { - gfx_set_visible_page(back); - front = back; + gfx_set_visible_page(back); + front = back; + if (is_demo && demo_finished) break; + } + + if (quit) break; + /* Restart Game завершает только игровой цикл. Следующая итерация + * верхнего автомата начинает title/intro с самого первого экрана. */ + if (pop_app_state == POP_APP_TITLE) continue; + if (pop_app_state == POP_APP_TIME_EXPIRED) { + pop_time_expired_show(); + if (!pop_app_dispatch(POP_APP_EV_CUTSCENE_FINISHED)) { + failed = 1; + break; } + continue; /* TITLE -> intro -> новый demo */ + } + /* ENDING/HOF тоже завершили внутренний игровой цикл, но это не ошибка + * протокола и не demo-result: следующая итерация верхнего автомата + * вызовет соответствующий экран. */ + if (pop_app_state == POP_APP_ENDING || + pop_app_state == POP_APP_HALL_OF_FAME) + continue; + if (pop_app_state != POP_APP_DEMO) break; + if (demo_new_game) { + /* Чистую инициализацию выполняет общий LEVEL_LOAD в начале следующей + * итерации — тот же маршрут теперь используют skip title/intro. */ + if (!pop_app_dispatch(POP_APP_EV_NEW_GAME)) { + failed = 1; + break; + } + continue; + } else if (!pop_app_dispatch(POP_APP_EV_DEMO_FINISHED)) { + failed = 1; + break; + } } } + PROF(0); /* выйти с ЧЁРНЫМ бордюром (иначе цвет */ + /* последней фазы остаётся в DOS) */ pop_shutdown(); /* снять хук, закрыть ввод/графику, отдать ресурсы */ - return 0; + return failed ? 1 : 0; } diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index 2abfec1..e940a52 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -28,6 +28,13 @@ #include "pop_vflip.h" /* сброс зеркальных страниц на смене уровня */ #include "pop_cheat.h" /* pop_cheats — режим разработки включает pop_boot */ #include "pop_pace.h" /* pop_speed_mode — режим скорости в отладочной метке */ +#include "pop_settings.h" /* pop_settings_defaults — стартовый профиль VANILLA */ +#include "pop_cfg.h" /* pop_cfg_load — POP.CFG поверх дефолтов */ +#include "pop_status.h" /* статус-строка и отладочная полоса — банк 9 */ +#include "pop_menu.h" /* pop_menu_init/free — атлас UI в bank 9 */ +#include "pop_ui.h" /* ранняя чёрная палитра + snapshot стартового экрана */ +#include "pop_pal.h" /* единый lifecycle game/title/level палитр */ +#include "pop_timer.h" /* global 60-minute timer — core in bank 9 */ #include /* puts/printf — сообщения об ошибках загрузки */ #include @@ -41,6 +48,11 @@ #define TILE_EMPTY 0 #define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */ +/* Во время смены уровня enter_room должен только СОБРАТЬ новую страницу. + * Физический flip делает pop_level_switch после окончательной палитры. */ +static uint8_t level_prepare; +static uint8_t level_prepared_page; + /* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ — * это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью * (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72). @@ -211,9 +223,21 @@ void pop_start_level(void) __banked } } Kid.sword = 0; /* set_start_pos: sword_0_sheathed */ + /* Остальные одноразовые флаги того же set_start_pos (seg003:028A). Они + * НЕ косметика: droppedout переживал границу уровня и на следующем + * уровне заставлял стража идти «за упавшим Кидом» вниз + * (guard_follows_kid_down) — на 4-м уровне в комнате 22 страж по этому + * флагу шёл прямо в челюсти, хотя Кида он даже не видел. Взводится он + * на ПРОШЛОМ уровне: Кид сорвался с уступа в боевой позе (кадры + * 150..179, pop_map.c start_fall) — обычная концовка боя со скелетом на + * 3-м. Поймано пользователем 2026-08-24 (переход 3 -> 4 ногами, через + * дверь уровня; читом Shift+L старт чистый, и баг не воспроизводился). */ + pop_droppedout = 0; + offguard = 0; + knock = 0; /* play_level (seg003:106): have_sword = level >= have_sword_from_level - * (=2) — начиная со второго уровня меч у Кида уже есть. */ - pop_have_sword = (uint8_t)(pop_current_level >= 2); + * (=2); исключение — demo level 0, где меч уже выдан. */ + pop_have_sword = (uint8_t)(pop_current_level == 0 || pop_current_level >= 2); /* seamless (seg003:158): пришли БЕЗ двери уровня, прямо на бегу — HP не * трогаем, оно переезжает как есть. Флаг одноразовый: у оригинала его * гасит заставка «LEVEL N», которую этот же переход и пропускает. */ @@ -320,7 +344,8 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked * Смысл прежний: одно нажатие = одно срабатывание. */ static uint8_t cheat_prev, immo_prev, sound_prev, nlvl_prev; -static uint8_t upsd_prev, xdec_prev, xinc_prev; +static uint8_t upsd_prev, xdec_prev, xinc_prev, cfg_prev; +static uint8_t time_sub_prev, time_add_prev; void pop_cheat_tick(void) __banked { @@ -335,10 +360,12 @@ void pop_cheat_tick(void) __banked * пользовательскую раскладку оригинала незачем; * Shift+L — следующий уровень (seg000:698, ветка next_level = * current_level + 1). Единственный вменяемый способ - * тестировать уровни 2..15. NB: L без Shift — отладочный + * тестировать уровни 2..14. NB: L без Shift — отладочный * «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с * Shift шаг тоже пройдёт, но уровень тут же сменится, так * что конфликта на практике нет; + * F7 / F8 — отнять / добавить минуту. Это точный аналог + * keypad −/+ SDLPoP, но +/- заняты обходом комнат; * [ / ] — подвинуть Кида на ПИКСЕЛЬ влево/вправо (seg000:1828, * `--Char.x` / `++Char.x`). Нужен потому, что мост MAME * теряет нажатия при быстрой отправке, и подогнать Кида в @@ -356,12 +383,23 @@ void pop_cheat_tick(void) __banked uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE); uint8_t xd = kbd_raw_down(KBD_CHEAT_XDEC); uint8_t xi = kbd_raw_down(KBD_CHEAT_XINC); + uint8_t ts = kbd_raw_down(KBD_CHEAT_TIME_SUB); + uint8_t ta = kbd_raw_down(KBD_CHEAT_TIME_ADD); + /* C — записать POP.CFG сейчас (отладка MS2, до экрана Settings: + * там сохранять будет Apply/выход). */ + uint8_t cf = kbd_raw_down(0x21); /* C (PS/2 set 2) */ if (pop_cheats && k && !cheat_prev) pop_guard_kill(); /* Бессмертие по кругу: 0 выкл -> 1 «только бой» -> 2 «бой и * мелкий урон» -> 0. Что означают уровни — pop_state.h. */ if (pop_cheats && i && !immo_prev) pop_immortal = (uint8_t)(pop_immortal >= 2 ? 0 : pop_immortal + 1); - if (s && !sound_prev) pop_sfx_toggle(); /* не под pop_cheats */ + if (s && !sound_prev) { + pop_sfx_toggle(); /* не под pop_cheats */ + pop_settings.sound_enabled = pop_snd_want; + /* Порт seg000:657: оригинал отвечает строкой в статус-полосе. */ + pop_status_msg = (uint8_t)(pop_snd_want ? POP_MSG_SOUND_ON + : POP_MSG_SOUND_OFF); + } if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL) pop_next_level = (uint8_t)(pop_current_level + 1); /* U — переворот экрана (seg000:0793 — чит и в оригинале). @@ -372,6 +410,9 @@ void pop_cheat_tick(void) __banked } if (pop_cheats && xd && !xdec_prev) Kid.x = (uint8_t)(Kid.x - 1); if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1); + if (pop_cheats && ts && !time_sub_prev) pop_timer_cheat_subtract(); + if (pop_cheats && ta && !time_add_prev) pop_timer_cheat_add(); + if (pop_cheats && cf && !cfg_prev) (void)pop_cfg_save(); cheat_prev = k; immo_prev = i; sound_prev = s; @@ -379,104 +420,15 @@ void pop_cheat_tick(void) __banked upsd_prev = up; xdec_prev = xd; xinc_prev = xi; + time_sub_prev = ts; + time_add_prev = ta; + cfg_prev = cf; } -/* ---- ОТЛАДКА: состояние прогона в верхнем борте ---------------------- * - * - * Четыре блока ПАЛОЧКАМИ, каждый своим цветом (outtextxy не годится: текст - * тянет системный знакогенератор, _gfx_font_buf — 2 КБ статики в W2, и - * сажает кучу). Двузначные числа — двумя группами: слева десятки, справа - * единицы, || |||| = 24. - * - * жёлтый x 10.. 85 уровень (1..15) - * белый x 100..175 комната (1..24) - * голубой x 190..205 скорость (1 палочка NORMAL, 2 FAST, 3 FASTEST) - * красный x 220..230 бессмертие (1 палочка = только бой, 2 = и мелкий урон) - * пурпурный x 245 звук ВЫКЛЮЧЕН (Ctrl+S) либо не поднялся вовсе - - * Звук показан «от противного» — палочка есть, когда звука НЕТ. Иначе - * индикатор ничего не сообщает: включённый звук слышно и так, а вот - * молчание неотличимо от «в этот момент нечему звучать». - * - * ЦВЕТА — индексы ИГРОВОЙ палитры, и выбраны они не по вкусу, а по двум - * ограничениям. - * - * Первое: записи палитры лежат в ФОРМАТЕ BGR (BIOS $A4), а не RGB — в - * kid.pal 0x3E это `55 ff ff`, то есть жёлтый, а не голубой. Читать файл - * «как привычно» нельзя, цвета выйдут переставленными (на этом я и - * попался в первом заходе). - * - * Второе, важнее: часть палитры ПЕРЕЗАПИСЫВАЕТСЯ на ходу — 0x40..0x4F - * зелья, 0x50..0x5F env, 0x60..0x6F стена (эти два меняются при смене - * ТАЙЛСЕТА, подземелье/дворец), 0x90..0x9F страж. Прежняя метка комнаты - * рисовалась цветом 0x57, то есть из env-диапазона: белой она была только - * в подземелье, а во дворце брала случайный цвет тайлсета. Здесь берём - * только 0x3A..0x3F — их не трогает никто: - * 0x3E жёлтый (255,255,85), 0x3F белый, 0x3B голубой (85,255,255), - * 0x3C красный (226,0,48) — тот же, которым игра даёт вспышку урона. - * - * Перерисовка change-driven: пока ничего из показанного не изменилось, в - * борт не пишем вовсе. Счётчик 2 — обе страницы дабл-буфера. */ - -static uint8_t room_label_sig = 0xFF; /* последняя показанная комната */ -static uint8_t lvl_label_sig = 0xFF; /* ...уровень */ -static uint8_t spd_label_sig = 0xFF; /* ...режим скорости */ -static uint8_t imm_label_sig = 0xFF; /* ...бессмертие */ -static uint8_t snd_label_sig = 0xFF; /* ...звук (pop_snd_want) */ -static uint8_t room_label_redraw; /* остаток кадров (2 = обе страницы) */ - -/* Группа палочек: n штук от x вправо, шаг 5, ширина 2. */ -static void label_bars(int x, uint8_t n) -{ - uint8_t i; - for (i = 0; i < n; i++) bar(x + i * 5, 0, x + 1 + i * 5, 5); -} - -void pop_dbg_room_label(uint8_t room) __banked -{ - /* СНАЧАЛА сохранить, потом сравнивать — обход sdcc_z80_cmp_store_a_bug - * (`if (n != g) g = n;` пишет разность вместо значения). */ - uint8_t prev_room = room_label_sig, prev_lvl = lvl_label_sig; - uint8_t prev_spd = spd_label_sig, prev_imm = imm_label_sig; - uint8_t prev_snd = snd_label_sig; - uint8_t lvl = pop_current_level, spd = pop_speed_mode, imm = pop_immortal; - uint8_t snd = pop_snd_want; - room_label_sig = room; - lvl_label_sig = lvl; - spd_label_sig = spd; - imm_label_sig = imm; - snd_label_sig = snd; - if (room != prev_room || lvl != prev_lvl || spd != prev_spd || - imm != prev_imm || snd != prev_snd) - room_label_redraw = 2; - if (!room_label_redraw) return; - - setfillstyle(SOLID_FILL, BLACK); - bar(10, 0, 250, 5); - - setfillstyle(SOLID_FILL, 0x3E); /* уровень — жёлтый */ - label_bars(10, (uint8_t)(lvl / 10)); - label_bars(40, (uint8_t)(lvl % 10)); - - setfillstyle(SOLID_FILL, 0x3F); /* комната — белый */ - label_bars(100, (uint8_t)(room / 10)); - label_bars(130, (uint8_t)(room % 10)); - - /* Скорость: номер режима + 1, то есть NORMAL 1, FAST 2, FASTEST 3 — - * чем больше палочек, тем быстрее (см. нумерацию в pop_pace.h). */ - setfillstyle(SOLID_FILL, 0x3B); /* скорость — голубой */ - label_bars(190, (uint8_t)(spd + 1)); - - if (imm) { /* бессмертие — красный */ - setfillstyle(SOLID_FILL, 0x3C); - label_bars(220, imm); /* палочек = уровень (1 или 2) */ - } - if (!snd) { /* звука НЕТ — пурпурный */ - setfillstyle(SOLID_FILL, 0x3D); - label_bars(245, 1); - } - room_label_redraw--; -} +/* Отладочная строка в верхнем борте переехала в pop_status.c (банк 9): + * она теперь ТЕКСТОВАЯ, а шрифт живёт там же. Здесь не осталось ничего — + * см. заголовок pop_status.h про то, почему заявка сообщения стоит один + * байт, а весь рендер сидит в банке. */ /* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ * страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет @@ -568,7 +520,7 @@ void enter_room_side(uint8_t room, uint8_t side) __banked } seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */ /* ENTER-ROOM-FAST: комната рисуется ОДИН раз — в скрытую страницу, — и - * тут же показывается флипом; вторая страница дабл-буфера получает её + * обычно тут же показывается флипом; вторая страница дабл-буфера получает её * КОПИЕЙ акселератора (gfx_copy_page, ~384 burst-строки против почти * полумиллиона тактов на вторую отрисовку). Побочный эффект важнее * тактов: игрок больше не видит, КАК рисуются тайлы — только готовый @@ -585,13 +537,20 @@ void enter_room_side(uint8_t room, uint8_t side) __banked gfx_rect_t full; pg = (uint8_t)(gfx_get_visible_page() ^ 1); /* скрытая */ gfx_set_draw_page(pg); - setfillstyle(SOLID_FILL, BLACK); - bar(0, 0, 319, 255); + pop_screen_fill_field(); pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg); - gfx_set_visible_page(pg); /* показать готовое */ - gfx_set_draw_page((uint8_t)(pg ^ 1)); - full.x = 0; full.y = 0; full.w = 320; full.h = 256; - gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */ + if (level_prepare) { + /* Межуровневый staging: пока pop_pal_level_load последовательно + * пишет KID.PAL и env/wall, видима нулевая чёрная страница. + * Показывать эту комнату разрешено только после финальной + * палитры — иначе level 4 успевает мелькнуть цветами level 3. */ + level_prepared_page = pg; + } else { + gfx_set_visible_page(pg); /* показать готовое */ + gfx_set_draw_page((uint8_t)(pg ^ 1)); + full.x = 0; full.y = 0; full.w = 320; full.h = 256; + gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */ + } } cur_room = room; kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */ @@ -647,7 +606,7 @@ void pop_qsave_restore_room(uint8_t room) __banked seam_redraw = seam_rows = 0; pg = (uint8_t)(gfx_get_visible_page() ^ 1); gfx_set_draw_page(pg); - setfillstyle(SOLID_FILL, BLACK); bar(0, 0, 319, 255); + pop_screen_fill_field(); pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg); gfx_set_visible_page(pg); gfx_set_draw_page((uint8_t)(pg ^ 1)); @@ -898,16 +857,44 @@ void pop_flip_screen(void) __banked int pop_boot(void) __banked { - /* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph - * (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан, - * поэтому тип берём напрямую от номера. */ + /* Настройки: дефолты, затем POP.CFG, если он есть и корректен. */ + pop_settings_defaults(); + pop_cfg_load(); + pop_timer_new_game(); /* default 60:00, Settings may freeze it */ + + /* Экран Sprinter хранит VRAM и палитры между программами. Перейти в + * графику надо ДО долгих чтений, но первой же операцией погасить ОБЕ + * палитры: тогда ни старый экран, ни частично собранный первый уровень + * не могут мелькнуть перед первым title fade-in. */ + /* PIC-палитры графики живут и в text mode. Предзапись чёрного сокращает + * окно между аппаратным SETVMOD и первой строкой нашего кода, в котором + * иначе успевает мелькнуть VRAM предыдущей программы. */ + pop_pal_black(); + initgraph_black(); + /* initgraph_black уже обнулил VRAM до SETVMOD. Повторная заливка после + * него фиксирует и shadow-копии, откуда затем работают heal/page-copy. */ + gfx_set_bank(GFX_BANK_NORMAL); + setfillstyle(SOLID_FILL, BLACK); + gfx_set_draw_page(0); + bar(0, 0, 319, 255); + gfx_set_draw_page(1); + bar(0, 0, 319, 255); + gfx_set_visible_page(0); + pop_pal_black(); + + /* Набор фона — по типу СТАРТОВОГО уровня; atlas_load безопасен и после + * initgraph: при первом вызове он сохраняет страницу DSS W0. Уровень к + * этому моменту ещё не прочитан, поэтому тип берём напрямую от номера. */ pop_current_level = FIRST_LEVEL; if (pop_bg_load(pop_level_type()) != 0) { puts("pop_bg_load failed"); return -1; } + /* pop_bg_load установил только свои слоты палитры. До конца загрузки + * всё равно держим экран чёрным. */ + pop_pal_black(); pop_cd_init(); /* метка «фон трогали» — пустые диапазоны */ - pop_cheats = 1; /* режим разработки: читы включены */ + /* pop_cheats/speed применит pop_settings_apply после pop_sfx_init. */ pop_guard_reset(); if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */ printf("kid_data.bin load failed\n"); @@ -917,6 +904,7 @@ int pop_boot(void) __banked pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */ pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */ pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */ + pop_settings_apply(1); /* CFG -> speed/cheats/CBL */ if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */ puts("kid atlas load failed"); return -1; @@ -929,38 +917,34 @@ int pop_boot(void) __banked pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */ pop_checkpoint = 0; /* init_game (seg003:37) */ - initgraph(); - gfx_set_visible_page(0); - if (gfx_pal_fload(0, "KID\\kid.pal") < 0) /* единая игровая палитра */ - gfx_pal_fload(0, "a:\\kid.pal"); - gfx_pal_sync(); /* палитра 0 -> плоскость 1 */ - /* kid.pal — ЕДИНАЯ игровая палитра, собранная из VDUNGEON, и она только - * что затёрла слоты тайлсета. Возвращаем цвета текущего набора: на - * дворцовых уровнях без этого рисуется дворцовая геометрия подземельными - * красками (сине-серое вместо песочного). */ - pop_bg_pal_apply(); - /* ...и палитру Тени: kid.pal затёр и её 16 слотов (pop_shadow.c). */ - pop_shadow_pal_apply(); + /* FONT.ATL нужен palette snapshot/dim; загрузка идёт ещё на чёрной + * палитре и поэтому не открывает игроку старое содержимое VRAM. */ + if (pop_menu_init() != 0) { + puts("menu font atlas load failed"); + return -1; + } + /* kid.pal + динамические slots tileset/Shadow — один логический + * источник. pop_pal_game_load делает снимок ПОСЛЕ всех доплат. */ + pop_pal_game_load(); + /* Сохраняем ИМЕННО игровую палитру: после стартового title/intro она + * понадобится как источник обычного fade-in. Для build-info экран + * остаётся штатный яркий игровой фон. */ + if (!pop_settings.show_build_info) { + pop_pal_apply(4); + } - /* Фон комнаты рисуется в ОБЕ графические страницы: у каждой своя - * ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ - * страницы, в которую рисуем. Дальше каждый тик восстанавливаем - * ТОЛЬКО прямоугольник Кида (pop_char_heal), а не весь экран. */ - /* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить - * trob/room_modif (ленивая инициализация из уровня при первом входе). */ - /* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см. - * pop_start_level. Управление: ←/→ бег, ↓ присед, ↑+→ прыжок с места, - * Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */ - pop_start_level(); - /* Зеркальный набор персонажей — ТУТ ЖЕ, если стартуем сразу на уровне с - * зельем инверсии (make LEVEL=9). Иначе первый переворот утащил бы - * чтение 34 файлов (~1.5 с) прямо в кадр: пользователь видел «затык» в - * синей фазе профиля. Тот же вызов есть в смене уровня; функция - * идемпотентна и звать её можно откуда угодно (pop_vflip.h). */ - if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all(); - gfx_set_draw_page(0); - pop_char_draw(POP_CH_KID); - pop_char_fore(POP_CH_KID); + /* Стартовая комната до title НЕ НУЖНА: LEVEL_LOAD всё равно заново + * выполняет pop_level_switch. Если нарисовать её сейчас в обе страницы, + * она становится сохранённой VRAM и может мелькнуть перед первым title + * при следующем запуске приложения. Единственное исключение — явно + * включённый build-info: его фон по контракту является игровой сценой. */ + if (pop_settings.show_build_info) { + pop_start_level(); + if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all(); + gfx_set_draw_page(0); + pop_char_draw(POP_CH_KID); + pop_char_fore(POP_CH_KID); + } /* Звук включаем ПОСЛЕДНИМ действием инициализации — когда все атласы, * уровень и палитры уже прочитаны. Раньше нельзя: ESTEX уходит в диск * надолго, насос не успевает долить блок, и на слух это скрежет @@ -975,6 +959,16 @@ int pop_boot(void) __banked * следующий. Возврат -1 — фатально (не удалось сменить набор фона). */ int pop_level_switch(void) __banked { + uint8_t old_page; + gfx_rect_t full; + + /* Граница FG0: повреждённый snapshot или старый внешний вызов не должен + * даже пытаться открыть res2015.bin. Финальный маршрут 14 -> ENDING + * добавит автомат приложения (FG11); пока просто оставляем уровень. */ + if (pop_next_level > POP_LEVEL_LAST) { + pop_next_level = pop_current_level; + return 0; + } /* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81): * цикл уровня кончается, когда next_level разошёлся с @@ -990,6 +984,20 @@ int pop_level_switch(void) __banked if (pop_level_load_num(pop_next_level) != 0) { pop_next_level = pop_current_level; /* файла нет — остаёмся */ } else { + /* С этого места видим только индекс 0. Это одновременно + * скрывает дисковую подготовку и даёт безопасный экран, + * пока loaders физически меняют отдельные диапазоны palette. */ + pop_pal_black(); + old_page = gfx_get_visible_page(); + gfx_set_bank(GFX_BANK_NORMAL); + /* Обнуляем ОБЕ страницы, а не только software-visible: + * аппаратный SCREEN_SWITCH и сохранённое BGI-состояние не + * должны оставлять старые sprites на соседней странице. */ + gfx_set_draw_page(0); + pop_screen_fill_game(); + gfx_set_draw_page(1); + pop_screen_fill_game(); + pop_wait_edge(); /* Набор спрайтов соперника у каждого уровня СВОЙ * (load_lev_spr, seg000:1092): уровень 3 — скелет, у него и * кадров меньше, чем у стража. Без перезагрузки соперник @@ -997,6 +1005,9 @@ int pop_level_switch(void) __banked * пределы, и персонаж просто не виден. */ pop_guard_free(); pop_guard_load(); + /* guard loader применяет свои цветовые слоты физически; + * пока остальной уровень читается, экран обязан быть чёрным. */ + pop_pal_black(); /* Зеркальные копии атласов сделаны из СТАРЫХ страниц — * после перезагрузки спрайтов они мусор (pop_vflip.h). */ pop_vflip_reset(); @@ -1008,18 +1019,82 @@ int pop_level_switch(void) __banked puts("bg set load failed"); return -1; } + /* Смена tileset тоже пишет env/wall slots. Не открывать их + * до готовности обеих страниц нового уровня. */ + pop_pal_black(); /* Всё состояние прошлого уровня сносит сам pop_start_level: * pop_trob_reset обнуляет room_seen (модификаторы комнат * перечитаются из НОВОЙ страницы), тайлы/стражи приезжают * из неё же, loose чистит enter_room. */ + level_prepare = 1; pop_start_level(); + level_prepare = 0; + /* pop_bg_load мог заменить только env/wall, а до этого + * экран жил после fade на чёрной палитре заставки. Полный + * game source обязателен даже для dungeon->dungeon: иначе + * fade_in восстановит старый (либо уже чёрный) snapshot. + * Яркость caller выберет сам: gameplay может показать кадр + * сразу, lifecycle title/demo — сначала apply(4)+fade_in. */ /* Уровень с зельем инверсии — заранее поднять зеркальный * набор персонажей (pop_vflip.h): 34 страницы читаются ~1.5 с, - * и делать это в момент глотка нельзя. Звать можно откуда - * угодно — функция идемпотентна. */ + * и делать это после открытия палитры либо в момент глотка + * нельзя. Звать можно откуда угодно — функция идемпотентна. */ if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all(); - pop_sfx_start(); /* ...и обратно, когда всё прочитано */ + /* Последнее действие подготовки: собрать полный logical + * source новой палитры. Пока он собирается физическими + * записями, видимая old_page заполнена нулём. */ + pop_pal_level_load(1); + /* Атомарный commit на границе растра: впервые показать + * готовую комнату уже с окончательной палитрой уровня. + * Вторую страницу синхронизировать аппаратной копией. */ + pop_wait_edge(); + gfx_set_visible_page(level_prepared_page); + gfx_set_draw_page(old_page); + full.x = 0; full.y = 0; full.w = 320; full.h = 256; + gfx_copy_page(&full, GFX_COPY_DIRECT); } + /* CBL намеренно НЕ открываем здесь. После загрузки caller ещё + * может делать BIOS fade/palette либо восстанавливать QuickSave; + * открытый буфер за это время успевает повторить старую половину + * и даёт скрежет. Caller запускает звук последним действием. */ + return 0; +} + +/* Общий холодный маршрут title/intro/credits/demo -> настоящая игра. + * Держим его в банке 8: это разовый lifecycle-код, а не часть кадрового + * пути, и перенос возвращает резиденту W1 место под стек/heap. */ +int pop_new_game_load(void) __banked +{ + /* Клавиша, которой пропустили заставку/demo, не должна стать первым + * движением Кида или тут же открыть pause menu на уровне. */ + while (kbd_raw_any_down()) { + kbd_raw_sync(); + gfx_wait_vsync(); + } + + pop_kid_hp_newgame(); + hitp_max = POP_START_HITP; + pop_timer_new_game(); + pop_checkpoint = 0; + pop_next_level = FIRST_LEVEL; + + /* Title мог завершиться на яркой title-палитре. Сначала погасить её, + * затем восстановить полный game snapshot; level_switch собирает обе + * страницы уже невидимо, и первый игровой кадр проявляется. */ + pop_pal_apply(4); + gfx_set_draw_page(0); + gfx_set_bank(GFX_BANK_NORMAL); + pop_screen_fill_game(); + gfx_set_draw_page(1); + pop_screen_fill_game(); + gfx_set_visible_page(1); + pop_pal_game_load(); + pop_pal_apply(4); + if (pop_level_switch() != 0) return -1; + /* Первый уровень появляется сразу, без fade, но только после того как + * обе страницы и его собственная палитра полностью готовы. */ + pop_pal_apply(0); + (void)pop_sfx_start(); return 0; } @@ -1039,6 +1114,7 @@ void pop_shutdown(void) __banked pop_bg_free(); pop_kid_free(); pop_shadow_free(); + pop_menu_free(); pop_guard_free(); pop_kid_data_free(); pop_level_free(); diff --git a/applications/PoP/roomtest/roomtest_cold.h b/applications/PoP/roomtest/roomtest_cold.h index ed1ebfb..870e13e 100644 --- a/applications/PoP/roomtest/roomtest_cold.h +++ b/applications/PoP/roomtest/roomtest_cold.h @@ -64,12 +64,7 @@ void pop_start_level(void) __banked; * вернуть себе draw-страницу и обнулить счётчик кадров смерти). */ uint8_t pop_dbg_roomnav(uint8_t dir) __banked; -/* ОТЛАДОЧНЫЙ лейбл номера комнаты палочками в верхнем борте. Рисует ТОЛЬКО - * при смене комнаты (и по разу на каждую страницу дабл-буфера), поэтому - * звать можно каждый кадр. */ -void pop_dbg_room_label(uint8_t room) __banked; - -/* Читы по фронту нажатия (K/I/S/Shift+L/U/[/]). В банке: раз в кадр, +/* Читы по фронту нажатия (K/I/S/Shift+L/U/[/]/F7/F8). В банке: раз в кадр, * а резидент W1/W2 переполнен — см. шапку функции. */ void pop_cheat_tick(void) __banked; @@ -93,9 +88,16 @@ void pop_flip_screen(void) __banked; int pop_boot(void) __banked; /* Перейти на pop_next_level: файл уровня, спрайты соперника, тайлсет, - * старт. Если файла нет — молча остаться на текущем. -1 — фатально. */ + * старт. На время I/O закрывает CBL и оставляет его закрытым: caller обязан + * вызвать pop_sfx_start ПОСЛЕ palette/fade/восстановления snapshot. Если + * файла нет — молча остаться на текущем. -1 — фатально. */ int pop_level_switch(void) __banked; +/* Чистый старт настоящей игры после Enter/Esc на title/intro/credits либо + * клавиши в attract-demo. Ждёт отпускания клавиши, сбрасывает состояние + * новой игры, загружает FIRST_LEVEL и проявляет его первый кадр. */ +int pop_new_game_load(void) __banked; + /* Снять idle-хук, закрыть ввод и графику, отдать все ресурсы. */ void pop_shutdown(void) __banked; diff --git a/applications/PoP/roomtest/tests-host/Makefile b/applications/PoP/roomtest/tests-host/Makefile index 9d2d17f..ccaa697 100644 --- a/applications/PoP/roomtest/tests-host/Makefile +++ b/applications/PoP/roomtest/tests-host/Makefile @@ -6,8 +6,11 @@ ENGINE_DIR := $(abspath $(CURDIR)/..) # t_phys гоняет НАСТОЯЩУЮ физику, а она ходит по таблицам анимации # абсолютными адресами от KD_DATA_OFF (0x100) — там, куда на устройстве # мапится страница W0. Значит блоб kid_data.bin лежит по 0x100, и код -# набора обязан начинаться выше его конца (0x100 + 3720 = 0x0F88). -CODE_LOC := 0x1000 +# набора обязан начинаться выше его конца. С добавлением таблицы кадров +# КАТСЦЕН блоб вырос до 4150 Б (0x100 + 4150 = 0x1136), и прежние 0x1000 +# уже резали его хвост — набор падал как «main() не позвал ни одной +# проверки». Держим запас: следующая таблица снова сдвинет границу. +CODE_LOC := 0x1400 # Данные — выше 0x8000: набор t_phys линкует уже полдвижка (геометрия, Kid, # карта/физика, управление, страж) и его код перевалил за 0x8000. Раньше # это проявлялось как «тест зациклился»: хвост кода ложился поверх _DATA. @@ -42,6 +45,9 @@ OBJS_phys := build/eng_pop_geom.rel build/eng_pop_kid.rel \ OBJS_grab := $(OBJS_phys) # t_wall — удар о стену в воздухе (BUG-JUMPWALL-1), состав тот же. OBJS_wall := $(OBJS_phys) +# t_gate — открытая решётка не должна считаться стеной у осторожного шага +# (нужны pop_map + физика, то есть тот же состав). +OBJS_gate := $(OBJS_phys) # t_char — физика над Char для РАЗНЫХ персонажей (Кид / страж / тень). OBJS_char := $(OBJS_phys) # t_mouse — спецсобытие уровня 8 (мышь). ЕДИНСТВЕННЫЙ набор, куда линкуется @@ -55,9 +61,39 @@ OBJS_mouse := $(OBJS_phys) build/eng_guards.rel OBJS_shadow := $(OBJS_phys) build/eng_guards.rel # t_jaffar — спецсобытия уровня 13 (встреча, победа, выход, падающая гряда). OBJS_jaffar := $(OBJS_phys) build/eng_guards.rel +# t_cfg — весь codec POP.CFG из единственного прикладного модуля. +OBJS_cfg := build/eng_pop_config.rel build/bank_stub.rel +# pop_app — чистая таблица переходов, без DSS/графики. +OBJS_app := build/eng_pop_app.rel build/bank_stub.rel +# pop_demo — запись управления; t_demo подменяет выход control и raw input. +OBJS_demo := build/eng_pop_demo.rel build/bank_stub.rel +# pop_timer — чистое ядро логического времени; поток QuickSave заглушен +# memory-stream'ом в t_timer.c. +OBJS_timer := build/eng_pop_timer.rel build/bank_stub.rel +# pop_cutscene — общий интерпретатор статических сценариев. Он не знает +# графики, поэтому проверяется отдельно от PV/title-ресурсов. +OBJS_cutscene := build/eng_pop_cutscene.rel build/bank_stub.rel +# pop_flow — чистая таблица маршрута перед загрузкой следующего уровня. +OBJS_flow := build/eng_pop_flow.rel build/bank_stub.rel include $(TESTKIT)/host-tests.mk +# У host-теста нет DSS/EMM: ему нужен тот же codec/state, но не target-I/O. +build/eng_pop_config.rel: $(ENGINE_DIR)/pop_config.c | $(BUILD) + @$(SDCC) $(CFLAGS) -DPOP_CONFIG_HOST_TEST -c -o $@ $< + +build/eng_pop_timer.rel: $(ENGINE_DIR)/pop_timer.c | $(BUILD) + @$(SDCC) $(CFLAGS) -c -o $@ $< + +build/eng_pop_demo.rel: $(ENGINE_DIR)/pop_demo.c | $(BUILD) + @$(SDCC) $(CFLAGS) -c -o $@ $< + +build/eng_pop_cutscene.rel: $(ENGINE_DIR)/pop_cutscene.c | $(BUILD) + @$(SDCC) $(CFLAGS) -c -o $@ $< + +build/eng_pop_flow.rel: $(ENGINE_DIR)/pop_flow.c | $(BUILD) + @$(SDCC) $(CFLAGS) -c -o $@ $< + # --- t_phys: блоб таблиц анимации ------------------------------------ KID_BIN := $(abspath $(CURDIR)/../../poc/res/kid/kid_data.bin) diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index eea8121..0cced7d 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -131,6 +131,13 @@ uint8_t kbd_raw_down(uint16_t code) return 0; } +uint8_t kbd_raw_any_down(void) +{ + uint8_t i; + for (i = 0; i < TK_KEYS_MAX; i++) if (tk_keys[i]) return 1; + return 0; +} + void tk_keys_clear(void) { uint8_t i; diff --git a/applications/PoP/roomtest/tests-host/t_app.c b/applications/PoP/roomtest/tests-host/t_app.c new file mode 100644 index 0000000..ff65e5c --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_app.c @@ -0,0 +1,135 @@ +/* pop_app — протокол верхнего автомата: допустимые и запрещённые переходы. */ +#include "tcheck.h" +#include "pop_app.h" + +TC_TEST(boot_to_title) +{ + pop_app_reset(); + TC_EQ(pop_app_state, POP_APP_BOOT); + TC_TRUE(pop_app_dispatch(POP_APP_EV_BOOT_READY)); + TC_EQ(pop_app_state, POP_APP_BUILD_INFO); + TC_TRUE(pop_app_dispatch(POP_APP_EV_BUILD_DONE)); + TC_EQ(pop_app_state, POP_APP_TITLE); +} + +TC_TEST(level_load_roundtrip) +{ + pop_app_reset(); + pop_app_dispatch(POP_APP_EV_BOOT_READY); + pop_app_dispatch(POP_APP_EV_BUILD_DONE); + TC_TRUE(pop_app_dispatch(POP_APP_EV_NEW_GAME)); + TC_EQ(pop_app_state, POP_APP_LEVEL_LOAD); + TC_TRUE(pop_app_dispatch(POP_APP_EV_LEVEL_READY)); + TC_EQ(pop_app_state, POP_APP_PLAYING); + TC_TRUE(pop_app_dispatch(POP_APP_EV_PAUSE)); + TC_EQ(pop_app_state, POP_APP_PAUSE_MENU); + TC_TRUE(pop_app_dispatch(POP_APP_EV_RESUME)); + TC_EQ(pop_app_state, POP_APP_PLAYING); + TC_TRUE(pop_app_dispatch(POP_APP_EV_LEVEL_FINISHED)); + TC_EQ(pop_app_state, POP_APP_LEVEL_LOAD); + TC_TRUE(pop_app_dispatch(POP_APP_EV_LEVEL_READY)); + TC_EQ(pop_app_state, POP_APP_PLAYING); +} + +TC_TEST(rejects_wrong_event) +{ + pop_app_reset(); + TC_FALSE(pop_app_dispatch(POP_APP_EV_LEVEL_READY)); + TC_EQ(pop_app_state, POP_APP_BOOT); + TC_TRUE(pop_app_dispatch(POP_APP_EV_QUIT)); + TC_EQ(pop_app_state, POP_APP_QUIT); + TC_FALSE(pop_app_dispatch(POP_APP_EV_BOOT_READY)); +} + +TC_TEST(future_routes_are_declared) +{ + pop_app_reset(); + pop_app_state = POP_APP_TITLE; + TC_TRUE(pop_app_dispatch(POP_APP_EV_NEW_GAME)); + TC_EQ(pop_app_state, POP_APP_LEVEL_LOAD); + TC_TRUE(pop_app_dispatch(POP_APP_EV_LEVEL_READY)); + TC_EQ(pop_app_state, POP_APP_PLAYING); + TC_TRUE(pop_app_dispatch(POP_APP_EV_TIME_EXPIRED)); + TC_EQ(pop_app_state, POP_APP_TIME_EXPIRED); + TC_TRUE(pop_app_dispatch(POP_APP_EV_CUTSCENE_FINISHED)); + TC_EQ(pop_app_state, POP_APP_TITLE); +} + +TC_TEST(prelevel_cutscene_route) +{ + pop_app_state = POP_APP_PLAYING; + TC_TRUE(pop_app_dispatch(POP_APP_EV_CUTSCENE_STARTED)); + TC_EQ(pop_app_state, POP_APP_CUTSCENE); + TC_TRUE(pop_app_dispatch(POP_APP_EV_CUTSCENE_FINISHED)); + TC_EQ(pop_app_state, POP_APP_LEVEL_LOAD); +} + +TC_TEST(demo_routes) +{ + pop_app_reset(); + pop_app_dispatch(POP_APP_EV_BOOT_READY); + pop_app_dispatch(POP_APP_EV_BUILD_DONE); + TC_TRUE(pop_app_dispatch(POP_APP_EV_INTRO_STARTED)); + TC_EQ(pop_app_state, POP_APP_INTRO); + TC_TRUE(pop_app_dispatch(POP_APP_EV_CUTSCENE_FINISHED)); + TC_EQ(pop_app_state, POP_APP_DEMO); + TC_TRUE(pop_app_dispatch(POP_APP_EV_DEMO_FINISHED)); + TC_EQ(pop_app_state, POP_APP_TITLE); + TC_TRUE(pop_app_dispatch(POP_APP_EV_DEMO_STARTED)); + TC_TRUE(pop_app_dispatch(POP_APP_EV_NEW_GAME)); + TC_EQ(pop_app_state, POP_APP_LEVEL_LOAD); +} + +TC_TEST(intro_skip_starts_new_game) +{ + pop_app_reset(); + pop_app_dispatch(POP_APP_EV_BOOT_READY); + pop_app_dispatch(POP_APP_EV_BUILD_DONE); + pop_app_dispatch(POP_APP_EV_INTRO_STARTED); + TC_EQ(pop_app_state, POP_APP_INTRO); + TC_TRUE(pop_app_dispatch(POP_APP_EV_NEW_GAME)); + TC_EQ(pop_app_state, POP_APP_LEVEL_LOAD); +} + +TC_TEST(restart_game_returns_to_intro) +{ + pop_app_state = POP_APP_PLAYING; + TC_TRUE(pop_app_dispatch(POP_APP_EV_PAUSE)); + TC_EQ(pop_app_state, POP_APP_PAUSE_MENU); + TC_TRUE(pop_app_dispatch(POP_APP_EV_RESTART_INTRO)); + TC_EQ(pop_app_state, POP_APP_TITLE); +} + +/* Финал ВРЕМЕННО ведёт мимо Hall of Fame прямо в title: экран HOF после + * победы показывает чёрное, и застревать в нём хуже, чем не показывать + * (решение пользователя 2026-08-24, BUGS_OPEN.md#hof-after-ending). Сам + * маршрут HOF -> TITLE остаётся живым: в него ходит title-экран. */ +TC_TEST(ending_to_title_route) +{ + pop_app_state = POP_APP_PLAYING; + TC_TRUE(pop_app_dispatch(POP_APP_EV_ENDING_STARTED)); + TC_EQ(pop_app_state, POP_APP_ENDING); + TC_TRUE(pop_app_dispatch(POP_APP_EV_ENDING_FINISHED)); + TC_EQ(pop_app_state, POP_APP_TITLE); +} + +TC_TEST(hof_route_still_exits_to_title) +{ + pop_app_state = POP_APP_HALL_OF_FAME; + TC_TRUE(pop_app_dispatch(POP_APP_EV_HOF_FINISHED)); + TC_EQ(pop_app_state, POP_APP_TITLE); +} + +void main(void) +{ + TC_RUN(boot_to_title); + TC_RUN(level_load_roundtrip); + TC_RUN(rejects_wrong_event); + TC_RUN(future_routes_are_declared); + TC_RUN(prelevel_cutscene_route); + TC_RUN(demo_routes); + TC_RUN(intro_skip_starts_new_game); + TC_RUN(restart_game_returns_to_intro); + TC_RUN(ending_to_title_route); + TC_RUN(hof_route_still_exits_to_title); +} diff --git a/applications/PoP/roomtest/tests-host/t_cfg.c b/applications/PoP/roomtest/tests-host/t_cfg.c new file mode 100644 index 0000000..066b6aa --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_cfg.c @@ -0,0 +1,197 @@ +/* + * t_cfg — полный codec POP.CFG: заголовок, размер, checksum и payload. + * + * DSS/EMM под host не живут, но pure codec лежит в том же pop_config.c, что + * и target-I/O. Поэтому проверяем весь формат, а не только payload. + */ +#include +#include + +#include "tcheck.h" +#include "pop_settings.h" +#include "pop_pace.h" /* POP_SPEED_MODES */ +#include "_pop_config.h" + +/* Runtime-владельцы нужны только для линковки pop_settings_apply(); codec + * их не вызывает. */ +uint8_t pop_speed_mode, pop_cheats, pop_snd_want; +void pop_sfx_toggle(void) __banked { pop_snd_want = (uint8_t)!pop_snd_want; } + +static void set_all(uint8_t snd, uint8_t spd, uint8_t prof, uint8_t cheats, + uint8_t info, uint16_t flags, uint8_t time_limit, + uint8_t debug_bar) +{ + pop_settings.sound_enabled = snd; + pop_settings.speed_mode = spd; + pop_settings.gameplay_profile = prof; + pop_settings.cheats_enabled = cheats; + pop_settings.show_build_info = info; + pop_settings.enhancement_flags = flags; + pop_settings.time_limit_enabled = time_limit; + pop_settings.show_debug_bar = debug_bar; +} + +/* ser→parse должен возвращать ровно те же значения (кроме профиля: + * ENHANCED осознанно садится в VANILLA). */ +TC_TEST(cfg_roundtrip) +{ + uint8_t buf[POP_CFG_SIZE]; + set_all(1, POP_SPEED_FAST, POP_PROFILE_VANILLA, 1, 1, 0xBEEF, 0, 0); + pop_cfg_encode(buf); + memset(&pop_settings, 0, sizeof(pop_settings)); /* стереть */ + TC_TRUE(pop_cfg_decode(buf, sizeof(buf))); + TC_EQ(pop_settings.sound_enabled, 1); + TC_EQ(pop_settings.speed_mode, POP_SPEED_FAST); + TC_EQ(pop_settings.cheats_enabled, 1); + TC_EQ(pop_settings.show_build_info, 1); + TC_EQ(pop_settings.enhancement_flags, 0xBEEF); + TC_EQ(pop_settings.time_limit_enabled, 0); + TC_EQ(pop_settings.show_debug_bar, 0); +} + +/* ENHANCED из файла применяется как VANILLA (режим ещё не реализован). */ +TC_TEST(cfg_enhanced_downgrades_to_vanilla) +{ + uint8_t buf[POP_CFG_SIZE]; + set_all(1, POP_SPEED_NORMAL, POP_PROFILE_ENHANCED, 0, 0, 0x0003, 1, 1); + pop_cfg_encode(buf); + memset(&pop_settings, 0, sizeof(pop_settings)); + TC_TRUE(pop_cfg_decode(buf, sizeof(buf))); + TC_EQ(pop_settings.gameplay_profile, POP_PROFILE_VANILLA); + /* Остальные поля при этом применяются. */ + TC_EQ(pop_settings.enhancement_flags, 0x0003); +} + +/* Мусор в ЛЮБОМ поле => отказ без частичного применения. */ +TC_TEST(cfg_garbage_rejected_whole) +{ + uint8_t buf[POP_CFG_SIZE]; + uint8_t i; + + for (i = 0; i < POP_CFG_PAYLOAD_SIZE; i++) { + set_all(1, POP_SPEED_FASTEST, POP_PROFILE_VANILLA, 1, 0, 0x0001, 1, 1); + pop_cfg_encode(buf); + switch (i) { + case 0: buf[7 + i] = 2; break; /* sound > 1 */ + case 1: buf[7 + i] = (uint8_t)POP_SPEED_MODES; break; + case 2: buf[7 + i] = 2; break; /* profile > ENHANCED */ + case 3: buf[7 + i] = 2; break; /* cheats > 1 */ + case 4: buf[7 + i] = 2; break; /* buildinfo > 1 */ + case 7: buf[7 + i] = 2; break; /* timer > 1 */ + case 8: buf[7 + i] = 2; break; /* debug bar > 1 */ + default: buf[7 + i] = 0xFF; break; /* флаги — любые */ + } + /* Изменённый payload обязан иметь корректную checksum: иначе мы + * тестируем checksum, а не диапазон поля. */ + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE] = 0; + { + uint8_t j; + for (j = 0; j < POP_CFG_PAYLOAD_SIZE; j++) + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE] ^= buf[7 + j]; + } + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + 1] = + (uint8_t)~buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE]; + if (i == 5 || i == 6) { /* flags не валидируются */ + memset(&pop_settings, 0, sizeof(pop_settings)); + TC_TRUE(pop_cfg_decode(buf, sizeof(buf))); + continue; + } + memset(&pop_settings, 0, sizeof(pop_settings)); + TC_FALSE(pop_cfg_decode(buf, sizeof(buf))); + /* Настройки не тронуты — нули после memset остались нулями. */ + TC_EQ(pop_settings.sound_enabled, 0); + TC_EQ(pop_settings.speed_mode, 0); + } +} + +TC_TEST(cfg_header_checksum_and_future_tail) +{ + uint8_t buf[POP_CFG_MAX_SIZE]; + uint8_t i, sum; + set_all(1, POP_SPEED_FAST, POP_PROFILE_VANILLA, 0, 1, 0xA55A, 1, 1); + pop_cfg_encode(buf); + + buf[0] = 'X'; + TC_FALSE(pop_cfg_decode(buf, POP_CFG_SIZE)); + pop_cfg_encode(buf); + buf[4] = 2; + TC_FALSE(pop_cfg_decode(buf, POP_CFG_SIZE)); + pop_cfg_encode(buf); + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE] ^= 1; + TC_FALSE(pop_cfg_decode(buf, POP_CFG_SIZE)); + pop_cfg_encode(buf); + TC_FALSE(pop_cfg_decode(buf, (uint16_t)(POP_CFG_SIZE - 1))); + + /* Будущий v1: два незнакомых байта перед checksum должны пропуститься. */ + pop_cfg_encode(buf); + buf[5] = POP_CFG_PAYLOAD_SIZE + 2; + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE] = 0x12; + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + 1] = 0x34; + sum = 0; + for (i = 0; i < POP_CFG_PAYLOAD_SIZE + 2; i++) sum ^= buf[7 + i]; + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + 2] = sum; + buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + 3] = (uint8_t)~sum; + memset(&pop_settings, 0, sizeof(pop_settings)); + TC_TRUE(pop_cfg_decode(buf, (uint16_t)(POP_CFG_SIZE + 2))); + TC_EQ(pop_settings.enhancement_flags, 0xA55A); +} + +/* Созданный до FG4 файл (семь payload-байтов) остаётся валиден и обязан + * включить лимит: это исторический дефолт 60 минут, не значение в _DATA. */ +TC_TEST(cfg_legacy_enables_time_limit) +{ + uint8_t buf[POP_CFG_SIZE]; + uint8_t sum = 0, i; + + set_all(1, POP_SPEED_NORMAL, POP_PROFILE_VANILLA, 1, 0, 0, 0, 0); + pop_cfg_encode(buf); + buf[5] = POP_CFG_LEGACY_PAYLOAD_SIZE; + for (i = 0; i < POP_CFG_LEGACY_PAYLOAD_SIZE; i++) sum ^= buf[7 + i]; + buf[POP_CFG_HEADER_SIZE + POP_CFG_LEGACY_PAYLOAD_SIZE] = sum; + buf[POP_CFG_HEADER_SIZE + POP_CFG_LEGACY_PAYLOAD_SIZE + 1] = (uint8_t)~sum; + pop_settings.time_limit_enabled = 0; + TC_TRUE(pop_cfg_decode(buf, POP_CFG_MIN_SIZE)); + TC_EQ(pop_settings.time_limit_enabled, 1); +} + +/* Файл, записанный ДО тумблера отладочной полосы (восемь payload-байтов), + * обязан остаться валидным и включить полосу: это её исторический дефолт. */ +TC_TEST(cfg_legacy_enables_debug_bar) +{ + uint8_t buf[POP_CFG_SIZE]; + uint8_t sum = 0, i; + + set_all(1, POP_SPEED_NORMAL, POP_PROFILE_VANILLA, 1, 0, 0, 1, 0); + pop_cfg_encode(buf); + buf[5] = POP_CFG_TIMELIMIT_PAYLOAD_SIZE; + for (i = 0; i < POP_CFG_TIMELIMIT_PAYLOAD_SIZE; i++) sum ^= buf[7 + i]; + buf[POP_CFG_HEADER_SIZE + POP_CFG_TIMELIMIT_PAYLOAD_SIZE] = sum; + buf[POP_CFG_HEADER_SIZE + POP_CFG_TIMELIMIT_PAYLOAD_SIZE + 1] = (uint8_t)~sum; + pop_settings.show_debug_bar = 0; + TC_TRUE(pop_cfg_decode(buf, (uint16_t)(POP_CFG_HEADER_SIZE + + POP_CFG_TIMELIMIT_PAYLOAD_SIZE + + POP_CFG_TAIL_SIZE))); + TC_EQ(pop_settings.show_debug_bar, 1); + TC_EQ(pop_settings.time_limit_enabled, 1); +} + +TC_TEST(settings_apply_syncs_runtime) +{ + set_all(0, POP_SPEED_FASTEST, POP_PROFILE_VANILLA, 0, 0, 0, 1, 1); + pop_snd_want = 1; + pop_settings_apply(1); + TC_EQ(pop_speed_mode, POP_SPEED_FASTEST); + TC_EQ(pop_cheats, 0); + TC_EQ(pop_snd_want, 0); +} + +void main(void) +{ + TC_RUN(cfg_roundtrip); + TC_RUN(cfg_enhanced_downgrades_to_vanilla); + TC_RUN(cfg_garbage_rejected_whole); + TC_RUN(cfg_header_checksum_and_future_tail); + TC_RUN(cfg_legacy_enables_time_limit); + TC_RUN(cfg_legacy_enables_debug_bar); + TC_RUN(settings_apply_syncs_runtime); +} diff --git a/applications/PoP/roomtest/tests-host/t_cutscene.c b/applications/PoP/roomtest/tests-host/t_cutscene.c new file mode 100644 index 0000000..e36ede6 --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_cutscene.c @@ -0,0 +1,46 @@ +/* pop_cutscene — порядок событий, точная семантика WAIT и прерывание. */ +#include +#include "tcheck.h" +#include "pop_cutscene.h" + +static const pop_cs_cmd_t script[] = { + { POP_CS_PALETTE, 1 }, { POP_CS_SHOW, 7 }, { POP_CS_WAIT, 2 }, + { POP_CS_FLASH, 3 }, { POP_CS_WAIT, 1 }, { POP_CS_FADE_OUT, 0 }, + { POP_CS_END, 0 } +}; + +TC_TEST(script_events_and_waits) +{ + pop_cutscene_t cs; + uint16_t arg; + + pop_cutscene_begin(&cs, script); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_PALETTE); TC_EQ(arg, 1); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_SHOW); TC_EQ(arg, 7); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_NONE); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_NONE); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_FLASH); TC_EQ(arg, 3); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_NONE); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_FADE_OUT); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_END); +} + +TC_TEST(skip_aborts_at_any_wait) +{ + pop_cutscene_t cs; + uint16_t arg; + + pop_cutscene_begin(&cs, script); + pop_cutscene_tick(&cs, &arg); + pop_cutscene_tick(&cs, &arg); + pop_cutscene_tick(&cs, &arg); /* WAIT(2), ещё есть один кадр */ + pop_cutscene_abort(&cs); + TC_EQ(pop_cutscene_tick(&cs, &arg), POP_CS_EVENT_END); + TC_EQ(cs.ended, 1); +} + +void main(void) +{ + TC_RUN(script_events_and_waits); + TC_RUN(skip_aborts_at_any_wait); +} diff --git a/applications/PoP/roomtest/tests-host/t_demo.c b/applications/PoP/roomtest/tests-host/t_demo.c new file mode 100644 index 0000000..85d28c3 --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_demo.c @@ -0,0 +1,89 @@ +/* pop_demo — запись level 0: смена команд и прерывание любым вводом. */ +#include +#include "tcheck.h" +#include "pop_demo.h" + +static int8_t moves[255]; +static uint8_t moves_n; +static uint8_t any_key; +static uint8_t fight_ai; +uint8_t hitp_curr; + +uint8_t pop_demo_kid_ai(void) __banked +{ + return fight_ai; +} + +void pop_ctrl_auto_move(int8_t move) __banked +{ + moves[moves_n++] = move; +} + +uint8_t kbd_raw_any_down(void) +{ + return any_key; +} + +TC_TEST(replays_sdlpop_move_table) +{ + uint8_t done = 0; + + moves_n = 0; + fight_ai = 0; + hitp_curr = 4; + pop_demo_begin(); + TC_FALSE(pop_demo_tick()); + TC_EQ(moves_n, 1); + TC_EQ(moves[0], 0); /* t=1, запись {0, nothing} */ + while (moves_n < 14) { + TC_FALSE(pop_demo_tick()); + } + TC_EQ(moves[1], 1); /* t=2, запись {1, forward} */ + TC_EQ(moves[11], 1); /* t=12 всё ещё forward */ + TC_EQ(moves[12], 0); /* t=13, запись {0x0D, nothing} */ + + while (!done) done = pop_demo_tick(); + TC_EQ(moves_n, 254); /* SDLPoP останавливает demo_time на FE */ + TC_EQ(moves[253], -1); /* хвост после записи {0xE9, -1} */ +} + +TC_TEST(fight_ai_pauses_scripted_moves) +{ + moves_n = 0; + fight_ai = 1; + hitp_curr = 4; + pop_demo_begin(); + TC_FALSE(pop_demo_tick()); + TC_EQ(moves_n, 0); /* во время боя demo_time не идёт */ + + fight_ai = 0; + TC_FALSE(pop_demo_tick()); + TC_EQ(moves_n, 1); + TC_EQ(moves[0], 0); /* первый scripted тик не потерян */ +} + +TC_TEST(death_stops_demo_before_combat_ai) +{ + moves_n = 0; + fight_ai = 1; + hitp_curr = 0; + pop_demo_begin(); + TC_TRUE(pop_demo_tick()); + TC_EQ(moves_n, 0); +} + +TC_TEST(any_raw_key_interrupts_demo) +{ + any_key = 0; + TC_FALSE(pop_demo_user_input()); + any_key = 1; + TC_TRUE(pop_demo_user_input()); +} + +void main(void) +{ + TC_RUN(replays_sdlpop_move_table); + TC_RUN(fight_ai_pauses_scripted_moves); + TC_RUN(death_stops_demo_before_combat_ai); + TC_RUN(any_raw_key_interrupts_demo); +} diff --git a/applications/PoP/roomtest/tests-host/t_flow.c b/applications/PoP/roomtest/tests-host/t_flow.c new file mode 100644 index 0000000..36a9aad --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_flow.c @@ -0,0 +1,52 @@ +/* pop_flow — только канонические pre-level маршруты и ветка scene 12. */ +#include +#include "tcheck.h" +#include "pop_flow.h" + +TC_TEST(only_original_levels_have_pre_scenes) +{ + uint8_t level; + + for (level = 0; level <= 14; level++) { + pop_pre_cutscene_t scene = pop_flow_pre_cutscene(level, 60); + if (level == 2 || level == 6) TC_EQ(scene, POP_PRE_2_6); + else if (level == 4) TC_EQ(scene, POP_PRE_4); + else if (level == 8) TC_EQ(scene, POP_PRE_8); + else if (level == 9) TC_EQ(scene, POP_PRE_9); + else if (level == 12) TC_EQ(scene, POP_PRE_12); + else TC_EQ(scene, POP_PRE_NONE); + } + TC_EQ(pop_flow_pre_cutscene(15, 60), POP_PRE_NONE); +} + +TC_TEST(scene_12_short_time_boundary) +{ + TC_EQ(pop_flow_pre_cutscene(12, 6), POP_PRE_12); + TC_EQ(pop_flow_pre_cutscene(12, 5), POP_PRE_12_SHORT); + TC_EQ(pop_flow_pre_cutscene(12, 0), POP_PRE_12_SHORT); +} + +TC_TEST(level_14_is_ending_without_number) +{ + const pop_level_flow_t *flow = pop_flow_level(14); + TC_TRUE(flow != 0); + TC_FALSE(flow->show_level_number); + TC_TRUE(flow->ending_rule); + TC_TRUE(pop_flow_level(15) == 0); +} + +TC_TEST(ending_room_is_exact) +{ + TC_TRUE(pop_flow_is_ending(14, 5)); + TC_FALSE(pop_flow_is_ending(14, 4)); + TC_FALSE(pop_flow_is_ending(13, 5)); + TC_FALSE(pop_flow_is_ending(15, 5)); +} + +void main(void) +{ + TC_RUN(only_original_levels_have_pre_scenes); + TC_RUN(scene_12_short_time_boundary); + TC_RUN(level_14_is_ending_without_number); + TC_RUN(ending_room_is_exact); +} diff --git a/applications/PoP/roomtest/tests-host/t_gate.c b/applications/PoP/roomtest/tests-host/t_gate.c new file mode 100644 index 0000000..0340fdf --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_gate.c @@ -0,0 +1,202 @@ +/* + * t_gate.c — ОТКРЫТЫЕ ворота не должны быть «стеной» для осторожного шага. + * + * Наблюдение (пользователь, 2026-08-24): перед ОТКРЫТОЙ решёткой Кид, + * идущий слева направо, делает лишний мелкий шаг — чтобы подойти к краю, + * Shift+вперёд надо нажимать дважды. + * + * Механика: длину осторожного шага выбирает сам safe_step (seg005:0604) по + * числу, которое вернул get_edge_distance — seq 29..42 это «шаг ровно на + * distance пикселей». А get_edge_distance для тайла-стены зовёт + * dist_from_wall_forward, и та у ОРИГИНАЛА первой строкой отвечает −1, если + * тайл — ворота, сквозь которые можно пройти (seg004:0A18: + * `if (tiletype == tiles_4_gate && !can_bump_into_gate()) return -1`). + * Минус единица уводит разбор дальше, где открытые ворота опознаются как + * обычный ПОЛ: distance = 11, полный шаг. Без этой ветки расстояние + * считается до створки, тип края — EDGE_WALL, и шаг выходит коротким. + * + * Проверяем не поведение через кадры, а сам ответ get_edge_distance: он и + * есть источник длины шага, и утверждение «открытая решётка == обычный + * пол» переживёт любую перенастройку последовательностей. + */ +#include "tcheck.h" +#include "scene.h" +#include "stubs.h" +#include "pop_kid.h" +#include "pop_map.h" +#include "pop_cdraw.h" /* pop_cd[].fph — высоту кадра ставит отрисовка */ + +#define F 1 /* floor */ +#define G 4 /* gate */ +#define W 20 /* wall */ + +/* Ряд 0: пол до колонки 4, решётка в колонке 5, пол дальше — «коридор с + * решёткой» уровня 1 (комната 5) в чистом виде. */ +static const uint8_t room_gate[30] = { + F, F, F, F, F, G, F, F, F, F, + W, W, W, W, W, W, W, W, W, W, + W, W, W, W, W, W, W, W, W, W, +}; + +/* Тот же ряд, но вместо решётки обычный пол — эталон «свободного» края. */ +static const uint8_t room_floor[30] = { + F, F, F, F, F, F, F, F, F, F, + W, W, W, W, W, W, W, W, W, W, + W, W, W, W, W, W, W, W, W, W, +}; + +/* Кид на колонке 4 лицом вправо, решётка (или пол) — следующим тайлом. + * modif — «открытость» ворот: 188 = подняты (проход открыт), 0 = опущены. + * Возврат — distance, тип края остаётся в pop_edge_type(). */ +static int edge_at_gate_x(const uint8_t *fg, uint8_t modif, uint8_t x) +{ + int d; + sc_room(fg, 1); + tk_modif[5] = modif; + if (x) sc_kid_at_x(4, 0, x, 0 /* лицом вправо */); + else sc_kid_at(4, 0, 0); + /* «Открыты ли ворота» решается сравнением подъёма створки с ВЫСОТОЙ + * КАДРА персонажа (can_bump_into_gate, seg004:0373). В продукте её + * пишет отрисовка (pop_cdraw), которой в наборе нет, — значит задаём + * сами. Порог важен только тем, что 6 (опущенная створка) под + * высотой кадра, а 53 (поднятая) над ней. */ + pop_cd[POP_CH_KID].fpw = 12; + pop_cd[POP_CH_KID].fph = 41; /* габарит стойки, снят в MAME */ + pop_loadkid(); + d = pop_edge_distance(); + pop_savekid(); + return d; +} + +static int edge_at_gate(const uint8_t *fg, uint8_t modif) +{ + return edge_at_gate_x(fg, modif, 0); +} + +/* ОТКРЫТАЯ решётка — не препятствие: и расстояние, и тип края обязаны + * совпасть с обычным полом, иначе осторожный шаг окажется коротким. */ +TC_TEST(open_gate_is_not_a_wall) +{ + int d_floor, d_gate; + uint8_t t_floor, t_gate; + + d_floor = edge_at_gate(room_floor, 0); + t_floor = pop_edge_type(); + d_gate = edge_at_gate(room_gate, 188); + t_gate = pop_edge_type(); + + TC_EQ(d_gate, d_floor); + TC_EQ(t_gate, t_floor); +} + +/* ЗАКРЫТАЯ решётка обязана остаться стеной — иначе фикс выше открыл бы + * проход сквозь опущенные ворота. */ +/* ЗАКРЫТАЯ решётка обязана остаться стеной: фикс выше не должен открывать + * проход сквозь опущенные ворота. X берём у самой створки — в центре тайла + * она ещё «далеко» и честно классифицируется как пол. */ +TC_TEST(closed_gate_is_a_wall) +{ + int d = edge_at_gate_x(room_gate, 0, 136); + TC_EQ(pop_edge_type(), EDGE_WALL); + TC_EQ(d < 11, 1); +} + +/* ...а ОТКРЫТАЯ на том же X ведёт себя как чистый пол — это и есть длина + * осторожного шага: 11, а не обрезок до створки. */ +TC_TEST(open_gate_steps_like_floor) +{ + int d_gate, d_floor; + uint8_t t_gate, t_floor; + + d_gate = edge_at_gate_x(room_gate, 188, 136); + t_gate = pop_edge_type(); + d_floor = edge_at_gate_x(room_floor, 0, 136); + t_floor = pop_edge_type(); + + TC_EQ(d_gate, d_floor); + TC_EQ(t_gate, t_floor); +} + +/* ---- check_gate_push (seg004:0833) --------------------------------- * + * Кид стоит в тайле решётки, решётка опускается — оригинал ВЫТАЛКИВАЕТ его + * из тайла (иначе Кид «в суперпозиции»: створка рисуется то перед ним, то + * за ним, и уйти он может в обе стороны). Прогоняем кадр физики и смотрим, + * сдвинулся ли X. */ +static int8_t push_col; /* curr_col, с которым сработал толчок */ + +static int push_from_dir(uint8_t x, uint8_t modif, int8_t dir) +{ + sc_room(room_gate, 1); + tk_modif[5] = modif; + /* Габарит ПОСЛЕДНЕГО НАРИСОВАННОГО кадра — его в продукте пишет + * отрисовка (pop_cdraw), которой в наборе нет. Значения сняты с живой + * сцены в MAME для стойки Кида: ширина 12, высота 41. */ + pop_cd[POP_CH_KID].fpw = 12; + pop_cd[POP_CH_KID].fph = 41; + sc_kid_at_x(5, 0, x, dir); + sc_run(0, 1); /* кадр «стоим» — флаги перекрытия за прошлый */ + push_col = Kid.curr_col; + sc_run(0, 1); /* ...и за этот: check_gate_push смотрит оба */ + return (int)Kid.x - (int)x; +} + +static int push_from(uint8_t x, uint8_t modif) +{ + return push_from_dir(x, modif, 0); +} + +/* СТОРОНА толчка — формула оригинала `Char.x += 5 - (orig_col <= tile_col) * 10`: + * пока тайл ПЕРСОНАЖА и есть решётка, его выталкивает ВЛЕВО (−5); как только + * персонаж отнесён к следующей колонке (решётка стала левым соседом) — ВПРАВО + * (+5). Граница между сторонами, стало быть, ровно там, где меняется + * curr_col, и проверяется здесь на обоих направлениях взгляда. + * + * Решётка — колонка 5, значит push_col == 5 обязан давать −5, push_col == 6 + * (+5), а вне зоны касания створки толчка нет вовсе. */ +static uint8_t check_side(int8_t dir, uint8_t *nleft, uint8_t *nright) +{ + uint8_t x, bad = 0; + for (x = 130; x <= 162; x++) { + int dx = push_from_dir(x, 0, dir); + if (dx == 0) continue; + /* Величина — кратная 5: пока персонаж остаётся в зоне створки, + * толчок повторяется каждый кадр (так же и в оригинале), поэтому + * проверяем СТОРОНУ и шаг, а не одно конкретное число. */ + if (push_col == 5) { if (dx >= 0 || dx % 5) bad++; else (*nleft)++; } + else if (push_col == 6) { if (dx <= 0 || dx % 5) bad++; else (*nright)++; } + else bad++; /* толчок вне тайлов решётки — ошибка */ + } + return bad; +} + +TC_TEST(closing_gate_pushes_kid) +{ + uint8_t nleft = 0, nright = 0, bad; + + bad = check_side(0, &nleft, &nright); /* лицом вправо */ + bad += check_side(-1, &nleft, &nright); /* лицом влево */ + + TC_EQ(bad, 0); + TC_EQ(nleft > 0, 1); /* обе стороны реально есть */ + TC_EQ(nright > 0, 1); +} + +/* ОТКРЫТУЮ решётку выталкивание не трогает вовсе (can_bump_into_gate). */ +TC_TEST(open_gate_does_not_push) +{ + uint8_t x, moved = 0; + for (x = 130; x <= 162; x++) + if (push_from_dir(x, 188, 0) != 0) moved++; + TC_EQ(moved, 0); +} + +int main(void) +{ + sc_init(); + TC_RUN(open_gate_is_not_a_wall); + TC_RUN(closed_gate_is_a_wall); + TC_RUN(open_gate_steps_like_floor); + TC_RUN(closing_gate_pushes_kid); + TC_RUN(open_gate_does_not_push); + return 0; +} diff --git a/applications/PoP/roomtest/tests-host/t_phys.c b/applications/PoP/roomtest/tests-host/t_phys.c index cdfe94f..143c51c 100644 --- a/applications/PoP/roomtest/tests-host/t_phys.c +++ b/applications/PoP/roomtest/tests-host/t_phys.c @@ -21,6 +21,7 @@ #include "stubs.h" #include "pop_kid.h" #include "pop_map.h" +#include "pop_ctrl.h" #include "pop_guard.h" /* pop_do_delta_hp — урон сводится дельтой */ /* Коды тайлов (pop_map): 0 пусто, 1 пол, 3 колонна, 11 loose, 20 стена. */ @@ -166,6 +167,19 @@ TC_TEST(phys_turn_around) SC_CHECK(golden_turn, "turn"); } +TC_TEST(demo_backward_is_not_screen_mirrored) +{ + /* demo_moves уже задаёт forward/backward относительно Кида. На плите + * 0,6 он смотрит вправо, и move=2 обязан начать seq_5 turn, а не + * превратиться через user_control() в seq_1 run вправо к пропасти. */ + sc_room(room_flat, 1); + sc_kid_at(4, 1, 0); + pop_ctrl_auto_move(0); + pop_ctrl_auto_move(2); + /* SEQTBL_BASE+0x01CB — начало seq_5 в исходном kid_data.bin. */ + TC_EQ((uint16_t)(Kid.curr_seq - SEQTBL_BASE), 0x01CB); +} + TC_TEST(phys_crouch_and_stand_up) { sc_room(room_flat, 1); @@ -354,6 +368,7 @@ void main(void) TC_RUN(phys_run_right); TC_RUN(phys_step_with_shift); TC_RUN(phys_turn_around); + TC_RUN(demo_backward_is_not_screen_mirrored); TC_RUN(phys_crouch_and_stand_up); TC_RUN(phys_jump_up_nothing_above); TC_RUN(phys_run_off_ledge); diff --git a/applications/PoP/roomtest/tests-host/t_timer.c b/applications/PoP/roomtest/tests-host/t_timer.c new file mode 100644 index 0000000..b28ad27 --- /dev/null +++ b/applications/PoP/roomtest/tests-host/t_timer.c @@ -0,0 +1,116 @@ +/* pop_timer — логические тики, pause/unlimited, читы и QuickSave state. */ +#include +#include "tcheck.h" +#include "pop_timer.h" + +/* Минимальный memory-stream вместо EMM. pop_timer_qsave использует ровно + * u8/u16, поэтому этот stub одновременно проверяет его endian-контракт. */ +static uint8_t stream[3]; +static uint8_t stream_pos; + +void pop_qs_u8(pop_qs_io_t *io, uint8_t *value) +{ + if (io->reading) { + if (io->apply) *value = stream[stream_pos]; + } else { + stream[stream_pos] = *value; + } + ++stream_pos; +} + +void pop_qs_u16(pop_qs_io_t *io, uint16_t *value) +{ + uint8_t lo, hi; + if (io->reading) { + lo = stream[stream_pos++]; hi = stream[stream_pos++]; + if (io->apply) *value = (uint16_t)(lo | ((uint16_t)hi << 8)); + } else { + stream[stream_pos++] = (uint8_t)*value; + stream[stream_pos++] = (uint8_t)(*value >> 8); + } +} + +TC_TEST(timer_starts_and_only_runs_when_allowed) +{ + pop_timer_new_game(); + TC_EQ(pop_timer_minutes, POP_TIMER_START_MINUTES); + TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); + TC_FALSE(pop_timer_tick(0, 1)); /* Settings: unlimited */ + TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); + TC_FALSE(pop_timer_tick(1, 0)); /* pause/HDD/cutscene */ + TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); + + pop_timer_minutes = 2; + pop_timer_ticks = 2; + TC_FALSE(pop_timer_tick(1, 1)); + TC_EQ(pop_timer_minutes, 2); + TC_EQ(pop_timer_ticks, 1); + TC_FALSE(pop_timer_tick(1, 1)); + TC_EQ(pop_timer_minutes, 1); + TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); +} + +TC_TEST(timer_expiry_is_one_shot) +{ + pop_timer_minutes = 1; + pop_timer_ticks = 1; + TC_TRUE(pop_timer_tick(1, 1)); + TC_EQ(pop_timer_minutes, 0); + TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); + TC_FALSE(pop_timer_tick(1, 1)); + TC_FALSE(pop_timer_tick(0, 1)); +} + +TC_TEST(timer_cheats_match_sdlpop_rules) +{ + pop_timer_minutes = 5; + pop_timer_cheat_subtract(); + TC_EQ(pop_timer_minutes, 4); + pop_timer_minutes = 1; + pop_timer_cheat_subtract(); + TC_EQ(pop_timer_minutes, 1); /* minus не создаёт expiry */ + pop_timer_minutes = 0; + pop_timer_cheat_add(); + TC_EQ(pop_timer_minutes, 1); + pop_timer_minutes = 0xFF; + pop_timer_cheat_add(); + TC_EQ(pop_timer_minutes, 0xFF); /* не сворачиваемся в 0 */ +} + +TC_TEST(timer_qsave_restores_exact_state_and_rejects_bad_tick) +{ + pop_qs_io_t io; + + pop_timer_minutes = 42; + pop_timer_ticks = 103; + stream_pos = 0; + io.reading = io.apply = io.failed = 0; + pop_timer_qsave(&io); + TC_EQ(stream_pos, 3); + + pop_timer_minutes = 7; + pop_timer_ticks = 400; + stream_pos = 0; + io.reading = io.apply = 1; io.failed = 0; + pop_timer_qsave(&io); + TC_FALSE(io.failed); + TC_EQ(pop_timer_minutes, 42); + TC_EQ(pop_timer_ticks, 103); + + stream[0] = 9; stream[1] = 0; stream[2] = 0; /* tick 0 — impossible */ + pop_timer_minutes = 33; + pop_timer_ticks = 444; + stream_pos = 0; io.failed = 0; + pop_timer_qsave(&io); + TC_TRUE(io.failed); + TC_EQ(pop_timer_minutes, 33); + TC_EQ(pop_timer_ticks, 444); +} + +void main(void) +{ + TC_RUN(timer_starts_and_only_runs_when_allowed); + TC_RUN(timer_expiry_is_one_shot); + TC_RUN(timer_cheats_match_sdlpop_rules); + TC_RUN(timer_qsave_restores_exact_state_and_rejects_bad_tick); +} diff --git a/applications/PoP/toolchain/pop_extract_kid_data.py b/applications/PoP/toolchain/pop_extract_kid_data.py index c96f660..2175b1e 100644 --- a/applications/PoP/toolchain/pop_extract_kid_data.py +++ b/applications/PoP/toolchain/pop_extract_kid_data.py @@ -79,6 +79,11 @@ def main(): frames = parse_frame_table(seg006) gframes = parse_frame_table(seg006, "frame_tbl_guard") + # Кадры КАТСЦЕН (frame_tbl_cuts, seg006:417) — Princess и Vizier. Нужны + # сценам 8/9 и финалу: там персонажи ходят теми же последовательностями + # seqtbl, что и в игре, но кадры адресуют свои chtab (sword>>6: 1 = набор + # PV1 ресурса 800, 2 = PV2 ресурса 900). + cframes = parse_frame_table(seg006, "frame_tbl_cuts") seqtbl = parse_byte_array(seqc, "original_seqtbl") offs = parse_word_array(seqc, "original_seqtbl_offsets") swtbl = parse_sword_tbl(seg006) @@ -109,7 +114,13 @@ def main(): f.write("/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс\n" " * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */\n") f.write("#define KID_NGFRAMES %d\n" % len(gframes)) - f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl))) + f.write("#define KID_BIN_GFRAMES_OFF %d\n" % (len(frames) * 5 + len(seqtbl))) + f.write("/* Таблица кадров КАТСЦЕН (frame_tbl_cuts, seg006:417):\n" + " * Princess/Vizier, индекс — номер кадра как есть. Набор\n" + " * спрайтов выбирает sword>>6 (1 = PV1 res800, 2 = PV2 res900). */\n") + f.write("#define KID_NCFRAMES %d\n" % len(cframes)) + f.write("#define KID_BIN_CFRAMES_OFF %d\n\n" + % (len(frames) * 5 + len(seqtbl) + len(gframes) * 5)) # seq offsets # kid_seq_off — МАКРО-инициализатором, не готовым массивом: заголовок @@ -146,7 +157,10 @@ def main(): b.write(bytes(seqtbl)) for (img, dx, dy, fl, sw) in gframes: b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF))) - print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5} Б (кадры Kid + seqtbl + кадры стража) -> {binpath}") + for (img, dx, dy, fl, sw) in cframes: + b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF))) + print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5 + len(cframes)*5} Б " + f"(кадры Kid + seqtbl + кадры стража + кадры катсцен) -> {binpath}") print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, " f"{len(offs)} seq-оффсетов -> {OUT}") diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py index a5319bb..75d8d58 100644 --- a/applications/PoP/toolchain/pop_pack_bg.py +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -142,7 +142,11 @@ VGA16_PAL_BASE = 0x30 VGA16 = [(0,0,0),(0,0,42),(0,42,0),(0,42,42),(42,0,0),(42,0,42),(48,38,20), (42,42,42),(21,21,21),(21,21,63),(21,63,21),(21,63,63),(56,0,12), (63,21,63),(63,63,21),(63,63,63)] # 6-битные каналы, как в .pal -POT_MASK_COLOR = VGA16_PAL_BASE + 0 # чёрный (blitters_40h_mono без цвета) +# ЧЁРНЫЙ ФОН АНИМАЦИЙ — ИМЕННО ЦВЕТ 0, а не «чёрный из палитры набора». +# Вспышки (молния, урон, зелья) подменяют НУЛЕВУЮ запись палитры, и всё, +# что закрашено 0x30/0x40, остаётся чёрным островом посреди вспышки — +# пламя факелов и склянки «не мигали» (поймано пользователем 2026-08-25). +POT_MASK_COLOR = 0 # маска mono-блита = фон вспышки POT_BUBBLE_COLOR = VGA16_PAL_BASE + 12 # ярко-красный (color 12: «лечение») # Цвет пузырька зависит от ТИПА зелья (draw_tile_anim, seg008:652): типы 3/4 # (перо / переворот) — зелёный, 5/6 (вред / открыть) — синий, остальные — @@ -322,7 +326,10 @@ def load_potion(id, mono=None, opaque=False): if mono is not None: out = bytes((0xFF if v == 0 else mono) for v in src) elif opaque: - out = bytes((POT_PAL_BASE + v) for v in src) + # Пиксель 0 — ЦВЕТ 0 (общий фон игрового поля), остальные — палитра + # chtab_1. Ноль здесь принципиален: непрозрачный бокс пламени иначе + # закрашивал бы фон «своим» чёрным (0x40) и не участвовал во вспышке. + out = bytes((0 if v == 0 else POT_PAL_BASE + v) for v in src) else: out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src) return im.width, im.height, out diff --git a/applications/PoP/toolchain/pop_pack_sound.py b/applications/PoP/toolchain/pop_pack_sound.py index 935f2c8..a7d022f 100644 --- a/applications/PoP/toolchain/pop_pack_sound.py +++ b/applications/PoP/toolchain/pop_pack_sound.py @@ -130,7 +130,16 @@ def main(): # Длину округляем ВВЕРХ до блока: хвост последнего блока — это # выравнивающая тишина следующего звука, и проигрыватель за счёт # этого не знает слова «неполный блок». - table[sid] = (len(blob), (len(sounds[sid]) + BLOCK - 1) // BLOCK * BLOCK) + need = (len(sounds[sid]) + BLOCK - 1) // BLOCK * BLOCK + # ЗВУК ЦЕЛИКОМ В ОДНОЙ СТРАНИЦЕ (решение пользователя 2026-08-25). + # Раньше длинные эффекты перетекали в следующую страницу, и насос + # переключал её прямо посреди воспроизведения — лишняя логика в ISR + # и целый класс отказов «играет не тот звук». Теперь страница — + # как атлас: не влезает в остаток — начинаем новую, хвост добиваем + # тишиной. Платим страницами EMM, их у нас с запасом. + if len(blob) % PAGE + need > PAGE: + blob += bytes([0x80]) * ((-len(blob)) % PAGE) + table[sid] = (len(blob), need) blob += sounds[sid] blob += bytes([0x80]) * ((-len(blob)) % PAGE) @@ -149,7 +158,7 @@ def main(): " *", " * Начало каждого звука кратно %d — размеру блока запроса CBL, а" % BLOCK, " * страница (%d) кратна ему же. Поэтому НИ ОДИН блок не пересекает" % PAGE, - " * границу страницы, и проигрывателю не нужна логика стыка.", + " * границу страницы. Больше того: с 2026-08-25 каждый звук лежит\n * ЦЕЛИКОМ в одной странице — не влезает в остаток, значит начинаем\n * новую (страницы EMM у нас с запасом). Единственное исключение —\n * эффект, который сам ДЛИННЕЕ страницы: для него насос переключает\n * страницу на ходу, и эта ветка в pop_sfx_fill остаётся ради него.", " *", " * Смещение хранится РАЗОБРАННЫМ на страницу и адрес в окне: на Z80", " * 32-битная арифметика дорога, а так всё влезает в 8/16 бит.", diff --git a/docs/libc-reference.md b/docs/libc-reference.md index 33660fe..428ed4a 100644 --- a/docs/libc-reference.md +++ b/docs/libc-reference.md @@ -229,7 +229,10 @@ font[row*256+char]) — грузится лениво при первом исп driver-либой на линковке: `sprinter-cc --gfx 256` (→ 320×256×256; 16 — позже). API mode-agnostic: код не меняется при смене режима. Setup: `initgraph()` (без аргументов — режим фиксирован либой; грузит -EGA-палитру 0..15, цвет=WHITE, фон=BLACK, CP=(0,0)), +EGA-палитру 0..15, цвет=WHITE, фон=BLACK, CP=(0,0)); +`initgraph_black()` задаёт то же состояние, но перед SETVMOD очищает обе +VRAM-страницы и сохраняет заранее погашенную аппаратную палитру для +собственного первого fade-in; `closegraph()`, `graphresult()`, `cleardevice()`. Границы/цвет: `getmaxx/getmaxy` (319/255), `getmaxcolor` (255), `setcolor/getcolor`, `setbkcolor/getbkcolor`. Константы BLACK..WHITE. @@ -315,6 +318,7 @@ Set 2: `0xF0` — префикс отпускания, `0xE0` — префикс | `int kbd_raw_open(void)` | включить: с этого момента ВЕСЬ поток клавиатурных байт достаётся нам, DSS его не видит. 0 / -1+errno (EBUSY повтор) | | `void kbd_raw_close(void)` | выключить, вернуть клавиатуру DSS; идемпотентно; висит на atexit | | `uint8_t kbd_raw_down(uint16_t code)` | зажата ли code ПРЯМО СЕЙЧАС (0/1); code вне 0..511 — 0 | +| `uint8_t kbd_raw_any_down(void)` | зажата ли хотя бы одна клавиша raw-канала (0/1); для экранов «любой клавишей» | | `void kbd_raw_sync(void)` | звать РАЗ В КАДР до опроса: recovery после Rx-overrun SIO — сбрасывает held-состояние всех клавиш КРОМЕ модификаторов (те не перечитываются typematic'ом; docs/kbd-games.md) | | `uint8_t kbd_raw_poll(void)` | вычерпать FIFO ОПРОСОМ, не дожидаясь прерывания: 0 — было пусто (~40 тактов), 1 — что-то декодировано. Звать МЕЖДУ фазами кадра, после тяжёлых блитов — не раз в кадр (см. ниже, «почему одного прерывания мало») | | `KBD_EXT` | ИЛИ-флаг кода: клавиша была расширенной (0xE0-префикс на проводе) | diff --git a/libbgi/_bgi.h b/libbgi/_bgi.h index 81d0c26..7f65060 100644 --- a/libbgi/_bgi.h +++ b/libbgi/_bgi.h @@ -33,6 +33,7 @@ * Байт _gfx_bank (0x50..0x5F): какой видеобанк мапится при записи — * 0x50 normal, 0x54 temp, 0x58 transparent, 0x5C оба. */ extern uint8_t _bgi_prevmode; /* видеорежим до initgraph */ +extern uint8_t _bgi_init_black; /* initgraph_black: не открывать EGA-палитру */ /* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или * 0xC140 (page 1); каждый примитив использует его вместо константы. */ diff --git a/libbgi/bgi256/_bgi_mode_set.c b/libbgi/bgi256/_bgi_mode_set.c index 597e938..6490562 100644 --- a/libbgi/bgi256/_bgi_mode_set.c +++ b/libbgi/bgi256/_bgi_mode_set.c @@ -36,7 +36,8 @@ uint8_t _bgi_mode_set(void) _gfx_bank = 0x50; gfx_set_visible_page(0); gfx_set_draw_page(0); - gfx_pal_load(0, 0, 16, _bgi_ega_pal); + if (!_bgi_init_black) + gfx_pal_load(0, 0, 16, _bgi_ega_pal); _bgi_maxx = GFX_WIDTH - 1; _bgi_maxy = GFX_HEIGHT - 1; _bgi_maxcolor = 255; diff --git a/libbgi/common/_bgi_state.c b/libbgi/common/_bgi_state.c index 4d25fd1..44666d2 100644 --- a/libbgi/common/_bgi_state.c +++ b/libbgi/common/_bgi_state.c @@ -16,4 +16,5 @@ int _bgi_maxx; int _bgi_maxy; int _bgi_maxcolor; uint8_t _bgi_prevmode; +uint8_t _bgi_init_black; int _bgi_result; diff --git a/libbgi/common/atlas_load.c b/libbgi/common/atlas_load.c index 90fdc25..f88a7ab 100644 --- a/libbgi/common/atlas_load.c +++ b/libbgi/common/atlas_load.c @@ -26,7 +26,17 @@ int atlas_load(atlas_t *a, const char *path) a->page = mem_get_page(blk, 0); n = bank_load_file(a->page, 0, path, 16384); - if (n >= 5) bank_read(a->page, 0, h, sizeof(h)); + /* Не затирать errno от open/read: вызывающему важно отличить отсутствие + * файла или короткое чтение от файла, который действительно не .atl. + * Раньше оба случая проваливались в EINVAL ниже, что маскировало + * фактическую причину сбоя загрузки экранных полос. */ + if (n < (int)sizeof(h)) { + mem_free_block(blk); + a->page = 0; + if (n >= 0) errno = EREAD; + return -1; + } + bank_read(a->page, 0, h, sizeof(h)); ok = (n >= (int)_ATL_DATA_OFF && h[0] == 'S' && h[1] == 'P' && h[2] == 'A' && h[3] == '1'); count = 0; diff --git a/libbgi/common/initgraph.c b/libbgi/common/initgraph.c index c884129..3982f52 100644 --- a/libbgi/common/initgraph.c +++ b/libbgi/common/initgraph.c @@ -10,6 +10,9 @@ void initgraph(void) { _bgi_prevmode = _bgi_mode_set(); + /* Флаг одноразовый: обычный следующий initgraph обязан снова получить + * стандартную EGA-палитру, даже если этот вход был «чёрным». */ + _bgi_init_black = 0; _bgi_fg = WHITE; _bgi_bg = BLACK; _bgi_cx = 0; diff --git a/libbgi/common/initgraph_black.c b/libbgi/common/initgraph_black.c new file mode 100644 index 0000000..684466a --- /dev/null +++ b/libbgi/common/initgraph_black.c @@ -0,0 +1,27 @@ +/* + * initgraph_black — войти в графику, не открывая стандартную палитру. + * + * Нужен программам с собственным fade-in: палитру можно заранее погасить + * ещё в текстовом режиме, а SETVMOD тогда не осветит сохранённую VRAM + * промежуточной EGA-палитрой. ABI совпадает с initgraph: аргументов нет. + */ + +#include "../_bgi.h" + +void initgraph_black(void) +{ + /* Очистить обе графические VRAM-страницы ЕЩЁ В ТЕКСТОВОМ РЕЖИМЕ. + * SETVMOD переключает video descriptors раньше, чем вызывающий успеет + * выполнить следующую C-инструкцию; одна только чёрная palette не + * спасает, если прошивка при переключении кратко выбирает старую. + * Окна 0x50/0xC000 доступны независимо от текущего режима экрана. */ + gfx_set_bank(GFX_BANK_NORMAL); + setfillstyle(SOLID_FILL, BLACK); + gfx_set_draw_page(0); + bar(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + gfx_set_draw_page(1); + bar(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); + + _bgi_init_black = 1; + initgraph(); +} diff --git a/libbgi/include/graphics.h b/libbgi/include/graphics.h index b2661de..5aa2a3d 100644 --- a/libbgi/include/graphics.h +++ b/libbgi/include/graphics.h @@ -63,6 +63,10 @@ extern uint8_t _bgi_fill_pattern, _bgi_fill_color; * позиция = (0,0). Предыдущий видеорежим запоминается для closegraph. */ void initgraph(void); +/* То же состояние BGI, но без загрузки EGA-палитры; перед SETVMOD очищает + * обе графические VRAM-страницы для чистого собственного fade-in. */ +void initgraph_black(void); + /* Вернуться в текстовый режим, действовавший до initgraph(). */ void closegraph(void); diff --git a/libc/include/kbd_raw.h b/libc/include/kbd_raw.h index faa8d09..0f33d38 100644 --- a/libc/include/kbd_raw.h +++ b/libc/include/kbd_raw.h @@ -46,6 +46,11 @@ void kbd_raw_close(void); * на проводе) прибавить KBD_EXT к базовому коду. */ uint8_t kbd_raw_down(uint16_t code); +/* Есть ли хотя бы одна зажатая клавиша (0/1). Полезно для экранов с + * семантикой «продолжить любой клавишей»; один вызов читает 64-байтную + * bitmap, а не перебирает все 512 scan-кодов через kbd_raw_down(). */ +uint8_t kbd_raw_any_down(void); + /* Восстановление после Rx-overrun SIO: звать РАЗ В КАДР (до чтения * kbd_raw_down). Аппаратный FIFO SIO 3 байта; при длинных DI-окнах пачка * скан-кодов (напр. быстрый тап стрелки: make+break = 5 байт) переполняет diff --git a/libc/kbd/kbd_raw_any_down.c b/libc/kbd/kbd_raw_any_down.c new file mode 100644 index 0000000..f10e134 --- /dev/null +++ b/libc/kbd/kbd_raw_any_down.c @@ -0,0 +1,17 @@ +/* + * kbd_raw_any_down — есть ли хотя бы одна зажатая клавиша raw-канала. + * + * Нужен полноэкранным режимам, где «любая клавиша» должна прервать + * заставку или attract-demo. Сканирует 64 байта битовой карты, а не 512 + * отдельных kbd_raw_down(): это постоянная и малая цена одного кадра. + */ +#include +#include "_kbdraw.h" + +uint8_t kbd_raw_any_down(void) +{ + uint8_t i; + for (i = 0; i < sizeof(_kbdraw_down); i++) + if (_kbdraw_down[i]) return 1; + return 0; +} diff --git a/toolchain/pop_extract_font.py b/toolchain/pop_extract_font.py new file mode 100644 index 0000000..e645ea2 --- /dev/null +++ b/toolchain/pop_extract_font.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""pop_extract_font.py — SDLPoP-шрифты в один Sprinter .atl-ресурс. + +Источник истины — оригинальные встроенные шрифты SDLPoP: + * hc_small_font_data[] из menu.c — пункты меню; + * hc_font_data[] из seg009.c — крупные сообщения/заголовки. + +В исходнике глиф — IMAGE_DATA(height, width, flags) и 1-битные строки. +Здесь его силуэт переводится в getimage (0xFF = прозрачность) и пакуется в +FONT\\FONT.ATL. Это именно шрифтовой атлас, а не заранее нарисованные +строки: номер ленты = код ASCII, поэтому runtime сохраняет произвольный +текст и пропорциональную ширину оригинала. + +Хвост страницы [0x3C00..0x3FFF] намеренно оставлен свободным: pop_menu.c +кладёт туда снимок игровой палитры на время паузы. Тогда 1 КБ не занимает +дефицитную W2. +""" +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SRC_SMALL = ROOT / "applications" / "PoP" / "SDLPoP" / "src" / "menu.c" +SRC_BIG = ROOT / "applications" / "PoP" / "SDLPoP" / "src" / "seg009.c" +OUT_DIR = ROOT / "applications" / "PoP" / "poc" / "res" / "font" +OUT_ATL = OUT_DIR / "font.atl" +OUT_H = ROOT / "applications" / "PoP" / "roomtest" / "pop_font.h" + +FIRST, LAST = 32, 126 +FONT_COLOR = 0x3F +ATL_MAGIC = b"SPA1" +ATL_DIR_OFF = 0x68 +SNAPSHOT_OFF = 0x3C00 +PAGE_SIZE = 16384 + + +def extract_array(path: Path, name: str) -> list[int]: + text = path.read_text() + match = re.search(rf"byte {name}\[\] = \{{(.*?)\n\}};", text, re.S) + assert match, f"{name} not found in {path}" + body = re.sub(r"//[^\n]*", "", match.group(1)) + body = re.sub(r"/\*.*?\*/", "", body, flags=re.S) + + def word(mm: re.Match) -> str: + value = int(mm.group(1), 0) + return f"{value & 0xFF}, {(value >> 8) & 0xFF}" + + def bin4(mm: re.Match) -> str: + b7, b6, b5, b4 = (int(v, 0) if v != "_" else 0 for v in mm.groups()) + return str((b4 << 4) | (b5 << 5) | (b6 << 6) | (b7 << 7)) + + def bin8(mm: re.Match) -> str: + bits = [int(v, 0) if v != "_" else 0 for v in mm.groups()] + return str(sum(bit << (7 - i) for i, bit in enumerate(bits))) + + def image_data(mm: re.Match) -> str: + h, w, flags = (int(v, 0) for v in mm.groups()) + return (f"{h & 0xFF}, {(h >> 8) & 0xFF}, " + f"{w & 0xFF}, {(w >> 8) & 0xFF}, " + f"{flags & 0xFF}, {(flags >> 8) & 0xFF}") + + body = re.sub(r"\bWORD\s*\(\s*(\w+)\s*\)", word, body) + body = re.sub(r"\bIMAGE_DATA\s*\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\)", + image_data, body) + body = re.sub(r"\bBINARY_4\s*\(\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*" + r"([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*\)", bin4, body) + body = re.sub(r"\bBINARY_8\s*\(\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*" + r"([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*" + r"([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*\)", bin8, body) + body = re.sub(r"\b_\b", "0", body) + # Hex обязан идти первым: иначе regexp съедает в `0x20` только `0`. + return [int(v, 0) for v in re.findall(r"0[xX][0-9a-fA-F]+|\d+", body)] + + +def parse_glyphs(vals: list[int]) -> tuple[dict, list[dict]]: + first, last = vals[0], vals[1] + font = { + "first": first, + "last": last, + "ascent": vals[2] | (vals[3] << 8), + "below": vals[4] | (vals[5] << 8), + "space_lines": vals[6] | (vals[7] << 8), + "space_chars": vals[8] | (vals[9] << 8), + } + pos = 10 + (last - first + 1) * 2 # offsets у SDLPoP вычисляются в runtime + glyphs = [] + while pos < len(vals): + h = vals[pos] | (vals[pos + 1] << 8) + w = vals[pos + 2] | (vals[pos + 3] << 8) + pos += 6 # height, width, flags + stride = (w + 7) // 8 + rows = [] + for _ in range(h): + rows.append(vals[pos:pos + stride]) + pos += stride + glyphs.append({"h": h, "w": w, "rows": rows}) + assert len(glyphs) == last - first + 1, f"glyph count {len(glyphs)}" + return font, glyphs + + +def glyph_pixels(glyph: dict) -> bytes: + """1 бит/пиксель SDLPoP -> 8bpp getimage: 1 = FONT_COLOR.""" + out = bytearray() + for row in glyph["rows"]: + for x in range(glyph["w"]): + byte = row[x // 8] + on = byte & (1 << (7 - (x & 7))) + out.append(FONT_COLOR if on else 0xFF) + return bytes(out) + + +def pack_atlas(fonts: list[tuple[dict, list[dict]]]) -> int: + entries = [] + for font, glyphs in fonts: + assert font["first"] <= FIRST <= LAST <= font["last"] + entries.extend(glyphs[FIRST - font["first"]:LAST - font["first"] + 1]) + assert len(entries) <= 255 + + data_off = ATL_DIR_OFF + len(entries) * 8 + out = bytearray(data_off) + out[0:4] = ATL_MAGIC + out[4] = len(entries) + for index, glyph in enumerate(entries): + offset = len(out) + pixels = glyph_pixels(glyph) + out += glyph["w"].to_bytes(2, "little") + out += glyph["h"].to_bytes(2, "little") + out += pixels + directory = ATL_DIR_OFF + index * 8 + out[directory:directory + 2] = offset.to_bytes(2, "little") + out[directory + 2] = glyph["w"] + out[directory + 3] = glyph["h"] + out[directory + 4] = 1 + out[directory + 5] = 1 + + if len(out) > SNAPSHOT_OFF: + raise SystemExit(f"font atlas: {len(out)} Б, нет места для palette snapshot @ {SNAPSHOT_OFF:#x}") + OUT_DIR.mkdir(parents=True, exist_ok=True) + OUT_ATL.write_bytes(out) + return len(out) + + +def emit_header(small: dict, big: dict, atlas_size: int) -> None: + small_n = LAST - FIRST + 1 + text = f"""/* + * pop_font.h — контракт FONT\\FONT.ATL, СГЕНЕРИРОВАН. + * Источник: SDLPoP hc_small_font_data[] и hc_font_data[]. + * Генератор: toolchain/pop_extract_font.py. Руками не править. + */ +#ifndef POP_FONT_H +#define POP_FONT_H + +#include + +#define POP_FONT_COLOR 0x{FONT_COLOR:02X} +#define POP_FONT_FIRST {FIRST} +#define POP_FONT_LAST {LAST} +#define POP_FONT_SMALL_BASE 0 +#define POP_FONT_SMALL_ASCENT {small['ascent']} +#define POP_FONT_SMALL_SPACE {small['space_chars']} +#define POP_FONT_BIG_BASE {small_n} +#define POP_FONT_BIG_ASCENT {big['ascent']} +#define POP_FONT_BIG_SPACE {big['space_chars']} +#define POP_FONT_SNAPSHOT_OFF 0x{SNAPSHOT_OFF:04X} +#define POP_FONT_ATLAS_SIZE {atlas_size} + +#endif +""" + OUT_H.write_text(text) + + +def main() -> None: + parsed = [] + for path, name in ((SRC_SMALL, "hc_small_font_data"), (SRC_BIG, "hc_font_data")): + font, glyphs = parse_glyphs(extract_array(path, name)) + print(f"{name}: chars={font['first']}..{font['last']} ascent={font['ascent']} " + f"space={font['space_chars']}") + parsed.append((font, glyphs)) + atlas_size = pack_atlas(parsed) + emit_header(parsed[0][0], parsed[1][0], atlas_size) + print(f"{OUT_ATL}: 2 x {LAST - FIRST + 1} glyphs, {atlas_size} Б; " + f"snapshot from 0x{SNAPSHOT_OFF:04X}") + + +if __name__ == "__main__": + main() diff --git a/toolchain/pop_pack_intro.py b/toolchain/pop_pack_intro.py new file mode 100644 index 0000000..4f4f09d --- /dev/null +++ b/toolchain/pop_pack_intro.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +"""Упаковка story/PV intro SDLPoP в полосы SPA1 для Sprinter. + +Исходники остаются единственным источником истины: TITLE/res41..45, res51 и +PV/res800/850/950/980. Runtime получает только готовые 320x200 композиции +и 256-цветные палитры. Это намеренно переносит смешивание палитров Princess, +Jaffar, спальни и кровати из ограниченного Z80 в build-time. +""" +from pathlib import Path + +from PIL import Image + +ROOT = Path(__file__).resolve().parent.parent +TITLE = ROOT / "applications" / "PoP" / "SDLPoP" / "data" / "TITLE" +PV = ROOT / "applications" / "PoP" / "SDLPoP" / "data" / "PV" +PRINCE = ROOT / "applications" / "PoP" / "SDLPoP" / "data" / "PRINCE" +KID = ROOT / "applications" / "PoP" / "SDLPoP" / "data" / "KID" +OUT = ROOT / "applications" / "PoP" / "poc" / "res" / "pv" + +ATL_MAGIC = b"SPA1" +ATL_DIR_OFF = 0x68 +ATL_MIN_SIZE = 0x100 +PAGE_SIZE = 16384 +STRIP_H = 49 + + +def transpose_cols(width: int, height: int, rowmajor: bytes) -> bytes: + """Row-major -> column-major для бесплатного horizontal flip в BGI.""" + out = bytearray(width * height) + for column in range(width): + base = column * height + for row in range(height): + out[base + row] = rowmajor[row * width + column] + return bytes(out) + + +def atlas_blob(pixels: bytes, width: int, height: int) -> bytes: + """Один непрозрачный getimage в минимальном SPA1-атласе.""" + data_off = ATL_DIR_OFF + 8 + out = bytearray(data_off) + out[:4] = ATL_MAGIC + out[4] = 1 + out[ATL_DIR_OFF:ATL_DIR_OFF + 2] = data_off.to_bytes(2, "little") + out[ATL_DIR_OFF + 2] = min(width, 255) + out[ATL_DIR_OFF + 3] = height + out[ATL_DIR_OFF + 4] = out[ATL_DIR_OFF + 5] = 1 + out += width.to_bytes(2, "little") + height.to_bytes(2, "little") + out += pixels + if len(out) < ATL_MIN_SIZE: + out += bytes(ATL_MIN_SIZE - len(out)) + if len(out) > PAGE_SIZE: + raise SystemExit(f"intro atlas {len(out)} B > {PAGE_SIZE} B") + return bytes(out) + + +def sprite_atlas(path: Path, entries: dict[int, tuple[int, int, bytes]]) -> None: + """Атлас небольших прозрачных кадров, целиком живущий в одной EMM. + + В отличие от atlas_blob() для полноэкранной полосы, здесь нужен каталог + нескольких картинок: runtime держит страницу открытой всю PV-сцену и + переключает только idx. Пустые индексы не требуются — все наборы ниже + нумеруются подряд с нуля. + """ + count = len(entries) + if not count or count > 255: + raise SystemExit(f"{path}: bad sprite atlas count {count}") + data_off = ATL_DIR_OFF + count * 8 + directory = bytearray(count * 8) + blobs = bytearray() + for idx in range(count): + width, height, pixels = entries[idx] + offset = data_off + len(blobs) + blobs += width.to_bytes(2, "little") + height.to_bytes(2, "little") + blobs += pixels + d = idx * 8 + directory[d:d + 2] = offset.to_bytes(2, "little") + directory[d + 2] = width + directory[d + 3] = height + directory[d + 4] = directory[d + 5] = 1 + out = bytearray(data_off) + out[:4] = ATL_MAGIC + out[4] = count + out[ATL_DIR_OFF:ATL_DIR_OFF + len(directory)] = directory + out += blobs + if len(out) < ATL_MIN_SIZE: + out += bytes(ATL_MIN_SIZE - len(out)) + if len(out) > PAGE_SIZE: + raise SystemExit(f"{path}: {len(out)} B > one EMM page") + path.write_bytes(out) + + +def sprite_frames(directory: Path, first: int, last: int, + color_base: int, column_major: bool = False + ) -> dict[int, tuple[int, int, bytes]]: + """PNG-кадры -> getimage. Ноль PNG становится прозрачным 0xFF BGI.""" + result = {} + for index, number in enumerate(range(first, last + 1)): + image = png(directory, number) + pixels = bytes(0xFF if pixel == 0 else color_base + pixel + for pixel in image.tobytes()) + if column_major: + pixels = transpose_cols(image.width, image.height, pixels) + result[index] = image.width, image.height, pixels + return result + + +def png(directory: Path, number: int) -> Image.Image: + image = Image.open(directory / f"res{number}.png") + if image.mode != "P": + raise SystemExit(f"res{number}.png: expected indexed PNG, got {image.mode}") + return image.copy() + + +def pal_file(path: Path) -> list[tuple[int, int, int]]: + """16 цветов из .pal ресурса (4 байта заголовка, дальше RGB по 6 бит).""" + raw = path.read_bytes() + return [(raw[4 + i * 3] << 2, raw[5 + i * 3] << 2, raw[6 + i * 3] << 2) + for i in range(16)] + + +def palette(image: Image.Image) -> list[tuple[int, int, int]]: + raw = image.getpalette() + return [tuple(raw[index * 3:index * 3 + 3]) for index in range(16)] + + +def write_palette(path: Path, entries: list[tuple[int, int, int]]) -> None: + """B,G,R,0 — ровно формат gfx_pal_fload; незадействованное чёрное.""" + out = bytearray() + entries = (entries + [(0, 0, 0)] * 256)[:256] + for red, green, blue in entries: + out += bytes((blue, green, red, 0)) + path.write_bytes(out) + + +def write_scene(prefix: str, number: int, canvas: Image.Image) -> None: + if canvas.size != (320, 200): + raise SystemExit(f"{prefix}{number}: expected 320x200, got {canvas.size}") + for part, top in enumerate(range(0, 200, STRIP_H)): + band = canvas.crop((0, top, 320, min(top + STRIP_H, 200))) + (OUT / f"{prefix}{number}_{part}.atl").write_bytes( + atlas_blob(band.tobytes(), band.width, band.height)) + + +def story_scenes() -> list[Image.Image]: + """show_title/end_sequence в единой с title палитре. + + res41 кодирует тёмный фон индексом 14, а его палитра делает этот индекс + чёрным; show_title() SDLPoP подменяет его #100060. Тексты res42..45 + имеют индекс 1, но этот индекс у res41 тоже тёмный. Ремап на индексы + title/res51 (9 = тёмно-синий, 15 = белый) позволяет проявлять историю + прямо поверх последнего title-экрана без смены палитры. + """ + frame_raw = bytearray(png(TITLE, 41).tobytes()) + for offset, pixel in enumerate(frame_raw): + if pixel == 14: + frame_raw[offset] = 9 + frame = Image.frombytes("P", (320, 200), bytes(frame_raw)) + result = [] + for text_number, ypos in ((42, 25), (43, 25), (45, 26), (44, 25)): + canvas = frame.copy() + raw = bytearray(canvas.tobytes()) + text = png(TITLE, text_number) + for sy in range(text.height): + for sx in range(text.width): + if text.getpixel((sx, sy)): + raw[(ypos + sy) * 320 + 24 + sx] = 15 + canvas.frombytes(bytes(raw)) + result.append(canvas) + result.append(frame) + return result + + +def pv_palette_canvas() -> tuple[Image.Image, list[tuple[int, int, int]]]: + """Спальня + bed. Номера 0..15=room, 16..31=Princess, + 32..47=Jaffar, 48..63=bed. Благодаря этому у смешанных PNG не теряются + исходные цвета при одном gfx_pal_fload() на весь PV.""" + room = png(PV, 951) + bed = png(PV, 981) + raw = bytearray(room.tobytes()) + bed_raw = bed.tobytes() + for sy in range(bed.height): + for sx in range(bed.width): + pixel = bed_raw[sy * bed.width + sx] + if pixel: + raw[(142 + sy) * 320 + sx] = 48 + pixel + canvas = Image.frombytes("P", room.size, bytes(raw)) + # 80..95 — пламя факелов. Это отдельный chtab PRINCE в оригинале; без + # отдельной строки цвета динамические кадры становились бы цветами стены. + # 112..127 (0x70) — палитра КИДА: на PV-экране финала и сцен 8/9 он и + # мышь рисуются СВОИМИ атласами (chtab_2), а их индексы запечены под + # игровые слоты 0x70. Без этой строки они выходили бы чёрными. + return (canvas, palette(room) + palette(png(PV, 801)) + + palette(png(PV, 851)) + palette(bed) + palette(png(PV, 901)) + + palette(png(PRINCE, 151)) + [(0, 0, 0)] * 16 + + pal_file(KID / "res400.pal")) + + +def sprite_paste(dst: Image.Image, sprite: Image.Image, x: int, floor: int, + color_base: int) -> None: + """Положить спрайт ногами на floor, remap его палитру в отдельную строку.""" + raw = bytearray(dst.tobytes()) + src = sprite.tobytes() + y = floor - sprite.height + for sy in range(sprite.height): + dy = y + sy + if dy < 0 or dy >= 200: + continue + for sx in range(sprite.width): + dx = x + sx + pixel = src[sy * sprite.width + sx] + if pixel and 0 <= dx < 320: + raw[dy * 320 + dx] = color_base + pixel + dst.frombytes(bytes(raw)) + + +def pv_scenes() -> tuple[list[Image.Image], list[tuple[int, int, int]]]: + base, colors = pv_palette_canvas() + + # Позиции Char из init_princess()/init_vizier(): x=120/198, y=166. + # В оригинале точный offset задаёт frame table; статические композиции + # выравнивают персонажей по полу y=166, не хранят таблицы PV-анимации. + waiting = base.copy() + sprite_paste(waiting, png(PV, 801), 110, 166, 16) + + jaffar = waiting.copy() + sprite_paste(jaffar, png(PV, 851), 188, 166, 32) + + magic = base.copy() + sprite_paste(magic, png(PV, 805), 110, 166, 16) + sprite_paste(magic, png(PV, 870), 188, 166, 32) + # res953 — первый полный кадр песочных часов; он использует палитру room. + sprite_paste(magic, png(PV, 953), 18, 166, 0) + + alone = base.copy() + sprite_paste(alone, png(PV, 816), 110, 166, 16) + sprite_paste(alone, png(PV, 959), 18, 166, 0) + + # FG9: статические ключевые позы тех же scene 2/4/6/8/9/12. PV2 + # использует второй набор Princess (res900), поэтому он лежит в строке + # палитры 64..79. Мышь в оригинале меняет только короткую позу рядом с + # Princess; здесь соответствующая стадия выделена отдельным кадром. + stand = base.copy() + sprite_paste(stand, png(PV, 801), 134, 166, 16) + + lying = base.copy() + sprite_paste(lying, png(PV, 901), 78, 162, 64) + # Часы для cutscene_4 выбираются runtime по оставшимся минутам и + # анимируются вместе с песком; в статической основе их быть не должно. + + mouse = base.copy() + sprite_paste(mouse, png(PV, 920), 120, 169, 64) + sprite_paste(mouse, png(PV, 959), 18, 166, 0) + + short_time = base.copy() + sprite_paste(short_time, png(PV, 816), 146, 166, 16) + sprite_paste(short_time, png(PV, 953), 18, 166, 0) + + return [waiting, jaffar, magic, alone, stand, lying, mouse, short_time], colors + + +def pv_animation_assets() -> None: + """Ресурсы покадрового intro. + + `b0_*` — единственный чистый фон. Он копируется accelerator-ом между + обеими экранными страницами; спрайты рисуются банком без shadow, так что + следующий кадр автоматически начинает с фона. Каждый actor-атлас <=16K, + поэтому нет чтения 320x200 с HDD в анимационном цикле. + """ + base, _ = pv_palette_canvas() + write_scene("b", 0, base) + # frame_tbl_cuts из SDLPoP адресует Princess как image 0..16 ресурса + # 800 (то есть PNG res801..817). Для Jaffar PV1 адресуется ресурс 850: + # image 0..37 = res851..888. Все 38 кадров не помещаются в одну EMM, + # поэтому сохраняем их тремя последовательными атласами. Runtime знает + # границы, но номер кадра остаётся исходным image-id 0..37. + sprite_atlas(OUT / "a0.atl", sprite_frames(PV, 801, 817, 16, True)) + sprite_atlas(OUT / "j0.atl", sprite_frames(PV, 851, 863, 32, True)) + sprite_atlas(OUT / "j1.atl", sprite_frames(PV, 864, 874, 32, True)) + sprite_atlas(OUT / "j2.atl", sprite_frames(PV, 875, 888, 32, True)) + # ВТОРОЙ набор Princess (PV2, res901..930 = image 0..29 ресурса 900). + # Он нужен сценам 8/9 (сидит и гладит мышь, встаёт, приседает) и финалу + # (поворачивается и обнимает Кида) — поз PV1 для этого не хватает. + # 26.7 КБ не влезают в одну EMM, поэтому два атласа; номер кадра при + # этом остаётся исходным image-id 0..29, границу знает runtime. + # Палитровая строка 64..79 — та же, что у PV2 в статических сценах. + sprite_atlas(OUT / "a1.atl", sprite_frames(PV, 901, 917, 64, True)) + sprite_atlas(OUT / "a2.atl", sprite_frames(PV, 918, 930, 64, True)) + # res953/res954 — состояния часов, res960..962 — три фазы песка. + sprite_atlas(OUT / "h0.atl", sprite_frames(PV, 952, 962, 0)) + sprite_atlas(OUT / "t0.atl", sprite_frames(PRINCE, 151, 159, 80)) + + +def main() -> None: + OUT.mkdir(parents=True, exist_ok=True) + + stories = story_scenes() + write_palette(OUT / "story.pal", palette(png(TITLE, 51))) + for number, scene in enumerate(stories): + write_scene("s", number, scene) + + scenes, colors = pv_scenes() + write_palette(OUT / "pv.pal", colors) + for number, scene in enumerate(scenes): + write_scene("p", number, scene) + + pv_animation_assets() + + print(f"{OUT}: 5 story + 8 PV compositions + base x 5 strips, " + f"6 animated actor atlases, one strip/page <= {STRIP_H} rows") + + +if __name__ == "__main__": + main() diff --git a/toolchain/pop_pack_title.py b/toolchain/pop_pack_title.py new file mode 100644 index 0000000..e7d4fdb --- /dev/null +++ b/toolchain/pop_pack_title.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""pop_pack_title.py — титульные композиции SDLPoP для Sprinter. + +Исходник — PNG-ресурсы ``data/TITLE/res51..55`` из локального SDLPoP. +Runtime не должен держать 320x200 картинку в W2 и не разбирает PNG: каждая +готовая композиция разрезана на пять getimage-лент высотой не более 49 +строк. Лента вместе с заголовком атласа занимает одну EMM-страницу; экран +рисуется лента за лентой, и страница сразу освобождается. + +Композиции в точности повторяют ``show_title()`` до story/intro: + t0 — основной рисунок; + t1 — рисунок + PRESENTS; + t2 — рисунок + название игры; + t3 — рисунок + логотип PRINCE OF PERSIA и Jordan Mechner. +""" +from pathlib import Path + +from PIL import Image + +ROOT = Path(__file__).resolve().parent.parent +SRC = ROOT / "applications" / "PoP" / "SDLPoP" / "data" / "TITLE" +OUT = ROOT / "applications" / "PoP" / "poc" / "res" / "title" + +ATL_MAGIC = b"SPA1" +ATL_DIR_OFF = 0x68 +ATL_MIN_SIZE = 0x100 +PAGE_SIZE = 16384 +STRIP_H = 49 + + +def atlas_blob(pixels: bytes, width: int, height: int) -> bytes: + """Один непрозрачный getimage в минимальном SPA1-атласе.""" + data_off = ATL_DIR_OFF + 8 + out = bytearray(data_off) + out[:4] = ATL_MAGIC + out[4] = 1 + out[ATL_DIR_OFF:ATL_DIR_OFF + 2] = data_off.to_bytes(2, "little") + # Каталог хранит preview-габарит в uint8, но atlas_image читает только + # offset; реальный getimage-width — u16 в блобе. Так уже пакуются + # широкие 320-пиксельные полосы фона. + out[ATL_DIR_OFF + 2] = min(width, 255) + out[ATL_DIR_OFF + 3] = height + out[ATL_DIR_OFF + 4] = out[ATL_DIR_OFF + 5] = 1 + out += width.to_bytes(2, "little") + height.to_bytes(2, "little") + out += pixels + if len(out) < ATL_MIN_SIZE: + out += bytes(ATL_MIN_SIZE - len(out)) + if len(out) > PAGE_SIZE: + raise SystemExit(f"title atlas {len(out)} B > {PAGE_SIZE} B") + return bytes(out) + + +def image(n: int) -> Image.Image: + im = Image.open(SRC / f"res{n}.png") + if im.mode != "P": + raise SystemExit(f"res{n}.png: expected indexed PNG, got {im.mode}") + return im.copy() + + +def assert_palette(base: Image.Image, overlay: Image.Image, n: int) -> None: + if base.getpalette()[:48] != overlay.getpalette()[:48]: + raise SystemExit(f"res{n}.png uses a palette different from res51.png") + + +def paste_opaque(dst: Image.Image, src: Image.Image, x: int, y: int, n: int) -> None: + assert_palette(dst, src, n) + dst.paste(src, (x, y)) + + +def paste_transparent(dst: Image.Image, src: Image.Image, x: int, y: int, n: int) -> None: + assert_palette(dst, src, n) + # TITLE_POP — единственный из пяти элементов, который SDLPoP рисует + # blitters_10h_transp. В PNG тот же факт записан colour-key index 0. + # Image.point() у P-изображения возвращает снова P, а paste() принимает + # маску только L/1/RGBA. Строим L явно, не меняя индексы самой картинки. + mask = Image.frombytes("L", src.size, + bytes(0 if p == 0 else 255 for p in src.tobytes())) + dst.paste(src, (x, y), mask) + + +def write_palette(base: Image.Image) -> None: + rgb = base.getpalette()[:48] + pal = bytearray() + for i in range(16): + r, g, b = rgb[i * 3:i * 3 + 3] + pal += bytes((b, g, r, 0)) # родной формат gfx_pal_fload + (OUT / "title.pal").write_bytes(pal) + + +def write_scene(scene: int, canvas: Image.Image) -> int: + count = 0 + for part, top in enumerate(range(0, canvas.height, STRIP_H)): + band = canvas.crop((0, top, canvas.width, + min(top + STRIP_H, canvas.height))) + path = OUT / f"t{scene}_{part}.atl" + path.write_bytes(atlas_blob(band.tobytes(), band.width, band.height)) + count += 1 + return count + + +def main() -> None: + OUT.mkdir(parents=True, exist_ok=True) + main_image = image(51) + if main_image.size != (320, 200): + raise SystemExit(f"res51.png: expected 320x200, got {main_image.size}") + + presents = main_image.copy() + paste_opaque(presents, image(52), 96, 106, 52) + + game = main_image.copy() + paste_opaque(game, image(53), 96, 122, 53) + + logo = main_image.copy() + paste_transparent(logo, image(54), 24, 107, 54) + paste_opaque(logo, image(55), 48, 184, 55) + + write_palette(main_image) + scenes = (main_image, presents, game, logo) + strips = [write_scene(i, scene) for i, scene in enumerate(scenes)] + assert strips == [5, 5, 5, 5] + print(f"{OUT}: {len(scenes)} title compositions x {strips[0]} strips, " + f"one strip <= {STRIP_H} rows / one EMM page") + + +if __name__ == "__main__": + main()