From cd8d566d82a08b600438d69b260c1412f35a86ef Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Fri, 17 Jul 2026 17:51:08 +0300 Subject: [PATCH] =?UTF-8?q?applications/PoP:=20=D0=BF=D0=BE=D1=80=D1=82=20?= =?UTF-8?q?Prince=20of=20Persia=20=E2=80=94=20PoC=20(roomtest)=20+=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=B9=D0=BF=D0=BB=D0=B0=D0=B9=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Порт PoP на Sprinter. Текущий PoC — applications/PoP/roomtest/: комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре и коллизией с картой. - roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип, seqtbl-анимация), pop_ctrl (порт control() PoP на held-state kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены, падение/приземление, отскок seq_47, вертикальный прыжок K4.1). MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge). - toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract — распаковка res-графики MSDOS в атласы + композиция комнат. - toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD), png_strip.py / room_compose.py (ассет-пайплайн). - docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT). - bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип). .gitignore: build-артефакты applications/*/*/*; исключены внешние референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов). Co-Authored-By: Claude Opus 4.8 --- .gitignore | 24 + applications/PoP/bgtest/Makefile | 27 + applications/PoP/bgtest/bgtest.c | 112 +++ applications/PoP/coltest/Makefile | 6 + applications/PoP/coltest/coltest.c | 64 ++ .../PoP/docs/APPLEII_RESOURCE_FORMAT.md | 283 ++++++ applications/PoP/docs/KID_PLAN.md | 199 +++++ .../PoP/docs/MSDOS_RESOURCE_FORMAT.md | 291 ++++++ .../PoP/docs/POP-DAT-FormatSpecifications.pdf | Bin 0 -> 222037 bytes applications/PoP/docs/PORT_PLAN.md | 509 +++++++++++ applications/PoP/docs/README.md | 81 ++ applications/PoP/poc/Makefile | 53 ++ applications/PoP/poc/level.h | 153 ++++ applications/PoP/poc/poc.c | 264 ++++++ applications/PoP/poc/res/bg/pop_bg.pal | Bin 0 -> 1024 bytes applications/PoP/poc/res/bg/pop_bg_atlas.h | 32 + applications/PoP/poc/res/bg/pop_env0.atl | Bin 0 -> 10578 bytes applications/PoP/poc/res/bg/pop_env1.atl | Bin 0 -> 11725 bytes applications/PoP/poc/res/bg/pop_env2.atl | Bin 0 -> 8718 bytes applications/PoP/poc/res/bg/pop_env3.atl | Bin 0 -> 5032 bytes applications/PoP/poc/res/bg/pop_env4.atl | Bin 0 -> 1230 bytes applications/PoP/poc/res/bg/pop_fore.atl | Bin 0 -> 7763 bytes applications/PoP/poc/res/bg/pop_wall.atl | Bin 0 -> 11543 bytes applications/PoP/poc/res/kid/kid.pal | Bin 0 -> 1024 bytes applications/PoP/poc/res/kid/kid0.atl | Bin 0 -> 7263 bytes applications/PoP/poc/res/kid/kid1.atl | Bin 0 -> 7891 bytes applications/PoP/poc/res/kid/kid10.atl | Bin 0 -> 5263 bytes applications/PoP/poc/res/kid/kid11.atl | Bin 0 -> 5915 bytes applications/PoP/poc/res/kid/kid12.atl | Bin 0 -> 6199 bytes applications/PoP/poc/res/kid/kid13.atl | Bin 0 -> 9521 bytes applications/PoP/poc/res/kid/kid14.atl | Bin 0 -> 5823 bytes applications/PoP/poc/res/kid/kid15.atl | Bin 0 -> 5387 bytes applications/PoP/poc/res/kid/kid16.atl | Bin 0 -> 3000 bytes applications/PoP/poc/res/kid/kid17.atl | Bin 0 -> 7389 bytes applications/PoP/poc/res/kid/kid18.atl | Bin 0 -> 5529 bytes applications/PoP/poc/res/kid/kid19.atl | Bin 0 -> 5872 bytes applications/PoP/poc/res/kid/kid2.atl | Bin 0 -> 8145 bytes applications/PoP/poc/res/kid/kid20.atl | Bin 0 -> 6326 bytes applications/PoP/poc/res/kid/kid21.atl | Bin 0 -> 7409 bytes applications/PoP/poc/res/kid/kid22.atl | Bin 0 -> 10223 bytes applications/PoP/poc/res/kid/kid23.atl | Bin 0 -> 9764 bytes applications/PoP/poc/res/kid/kid24.atl | Bin 0 -> 5932 bytes applications/PoP/poc/res/kid/kid25.atl | Bin 0 -> 6246 bytes applications/PoP/poc/res/kid/kid26.atl | Bin 0 -> 5390 bytes applications/PoP/poc/res/kid/kid27.atl | Bin 0 -> 932 bytes applications/PoP/poc/res/kid/kid3.atl | Bin 0 -> 7043 bytes applications/PoP/poc/res/kid/kid4.atl | Bin 0 -> 9758 bytes applications/PoP/poc/res/kid/kid5.atl | Bin 0 -> 7809 bytes applications/PoP/poc/res/kid/kid6.atl | Bin 0 -> 5426 bytes applications/PoP/poc/res/kid/kid7.atl | Bin 0 -> 4085 bytes applications/PoP/poc/res/kid/kid8.atl | Bin 0 -> 9085 bytes applications/PoP/poc/res/kid/kid9.atl | Bin 0 -> 7885 bytes applications/PoP/poc/res/kid/kid_atlas.h | 39 + applications/PoP/poc/res/room1.dat | Bin 0 -> 63 bytes applications/PoP/poc/res/tiles/0-0.png | Bin 0 -> 87 bytes applications/PoP/poc/res/tiles/1-0.png | Bin 0 -> 155 bytes applications/PoP/poc/res/tiles/10-0.png | Bin 0 -> 631 bytes applications/PoP/poc/res/tiles/11-0.png | Bin 0 -> 624 bytes applications/PoP/poc/res/tiles/12-0.png | Bin 0 -> 825 bytes applications/PoP/poc/res/tiles/13-0.png | Bin 0 -> 130 bytes applications/PoP/poc/res/tiles/14-0.png | Bin 0 -> 236 bytes applications/PoP/poc/res/tiles/15-0.png | Bin 0 -> 832 bytes applications/PoP/poc/res/tiles/16-0.png | Bin 0 -> 911 bytes applications/PoP/poc/res/tiles/17-0.png | Bin 0 -> 236 bytes applications/PoP/poc/res/tiles/18-0.png | Bin 0 -> 236 bytes applications/PoP/poc/res/tiles/19-0.png | Bin 0 -> 208 bytes applications/PoP/poc/res/tiles/1A-0.png | Bin 0 -> 206 bytes applications/PoP/poc/res/tiles/1B-0.png | Bin 0 -> 208 bytes applications/PoP/poc/res/tiles/1C-0.png | Bin 0 -> 199 bytes applications/PoP/poc/res/tiles/1D-0.png | Bin 0 -> 190 bytes applications/PoP/poc/res/tiles/1E-0.png | Bin 0 -> 698 bytes applications/PoP/poc/res/tiles/1F-0.png | Bin 0 -> 478 bytes applications/PoP/poc/res/tiles/2-0.png | Bin 0 -> 889 bytes applications/PoP/poc/res/tiles/3-0.png | Bin 0 -> 236 bytes applications/PoP/poc/res/tiles/4-0.png | Bin 0 -> 764 bytes applications/PoP/poc/res/tiles/5-0.png | Bin 0 -> 821 bytes applications/PoP/poc/res/tiles/6-0.png | Bin 0 -> 831 bytes applications/PoP/poc/res/tiles/7-0.png | Bin 0 -> 825 bytes applications/PoP/poc/res/tiles/8-0.png | Bin 0 -> 236 bytes applications/PoP/poc/res/tiles/9-0.png | Bin 0 -> 236 bytes applications/PoP/poc/res/tiles/A-0.png | Bin 0 -> 715 bytes applications/PoP/poc/res/tiles/B-0.png | Bin 0 -> 130 bytes applications/PoP/poc/res/tiles/C-0.png | Bin 0 -> 867 bytes applications/PoP/poc/res/tiles/D-0.png | Bin 0 -> 650 bytes applications/PoP/poc/res/tiles/E-0.png | Bin 0 -> 131 bytes applications/PoP/poc/res/tiles/F-0.png | Bin 0 -> 759 bytes applications/PoP/poc/res/tiles/tile00.pal | Bin 0 -> 64 bytes applications/PoP/poc/res/tiles/tile01.atl | Bin 0 -> 2308 bytes applications/PoP/poc/res/tiles/tile01.pal | Bin 0 -> 76 bytes applications/PoP/poc/res/tiles/tile01.raw | 1 + .../PoP/poc/res/tiles/tile01_merged.pal | Bin 0 -> 92 bytes applications/PoP/poc/res/tiles/tile02.pal | Bin 0 -> 460 bytes applications/PoP/poc/res/tiles/tile03.atl | Bin 0 -> 2308 bytes applications/PoP/poc/res/tiles/tile03.raw | 1 + .../PoP/poc/res/tiles/tile03_merged.pal | Bin 0 -> 100 bytes applications/PoP/poc/res/tiles/tile04.pal | Bin 0 -> 424 bytes applications/PoP/poc/res/tiles/tile05.pal | Bin 0 -> 444 bytes applications/PoP/poc/res/tiles/tile06.pal | Bin 0 -> 412 bytes applications/PoP/poc/res/tiles/tile07.pal | Bin 0 -> 460 bytes applications/PoP/poc/res/tiles/tile08.pal | Bin 0 -> 72 bytes applications/PoP/poc/res/tiles/tile09.pal | Bin 0 -> 72 bytes applications/PoP/poc/res/tiles/tile0a.pal | Bin 0 -> 364 bytes applications/PoP/poc/res/tiles/tile0b.atl | Bin 0 -> 2308 bytes applications/PoP/poc/res/tiles/tile0b.raw | 1 + .../PoP/poc/res/tiles/tile0b_merged.pal | Bin 0 -> 112 bytes applications/PoP/poc/res/tiles/tile0c.pal | Bin 0 -> 472 bytes applications/PoP/poc/res/tiles/tile0d.pal | Bin 0 -> 320 bytes applications/PoP/poc/res/tiles/tile0e.atl | Bin 0 -> 2308 bytes applications/PoP/poc/res/tiles/tile0e.raw | 1 + .../PoP/poc/res/tiles/tile0e_merged.pal | Bin 0 -> 108 bytes applications/PoP/poc/res/tiles/tile0f.pal | Bin 0 -> 432 bytes applications/PoP/poc/res/tiles/tile10.pal | Bin 0 -> 300 bytes applications/PoP/poc/res/tiles/tile11.pal | Bin 0 -> 292 bytes applications/PoP/poc/res/tiles/tile12.pal | Bin 0 -> 416 bytes applications/PoP/poc/res/tiles/tile13.atl | Bin 0 -> 2308 bytes applications/PoP/poc/res/tiles/tile13.raw | 1 + .../PoP/poc/res/tiles/tile13_merged.pal | Bin 0 -> 104 bytes applications/PoP/poc/res/tiles/tile14.atl | Bin 0 -> 2308 bytes applications/PoP/poc/res/tiles/tile14.pal | Bin 0 -> 72 bytes applications/PoP/poc/res/tiles/tile14.raw | 1 + .../PoP/poc/res/tiles/tile14_merged.pal | Bin 0 -> 100 bytes applications/PoP/poc/res/tiles/tile15.pal | Bin 0 -> 448 bytes applications/PoP/poc/res/tiles/tile16.pal | Bin 0 -> 532 bytes applications/PoP/poc/res/tiles/tile17.pal | Bin 0 -> 72 bytes applications/PoP/poc/res/tiles/tile18.pal | Bin 0 -> 72 bytes applications/PoP/poc/res/tiles/tile19.pal | Bin 0 -> 384 bytes applications/PoP/poc/res/tiles/tile1a.pal | Bin 0 -> 376 bytes applications/PoP/poc/res/tiles/tile1b.pal | Bin 0 -> 352 bytes applications/PoP/poc/res/tiles/tile1c.pal | Bin 0 -> 376 bytes applications/PoP/poc/res/tiles/tile1d.pal | Bin 0 -> 372 bytes applications/PoP/poc/res/tiles/tile1e.pal | Bin 0 -> 300 bytes applications/PoP/poc/res/tiles/tile1f.pal | Bin 0 -> 192 bytes applications/PoP/poc/room.c | 158 ++++ applications/PoP/poc/room.h | 68 ++ .../PoP/poc/tools/build_room_palette.py | 75 ++ .../PoP/poc/tools/build_tile_atlases.py | 70 ++ applications/PoP/poc/tools/extract_room.py | 61 ++ .../PoP/poc/tools/gen_kid_placeholder.py | 67 ++ .../PoP/poc/tools/gen_tile_placeholders.py | 153 ++++ applications/PoP/poc/tools/kid.atl | Bin 0 -> 3332 bytes applications/PoP/poc/tools/kid.pal | Bin 0 -> 80 bytes applications/PoP/poc/tools/kid.raw | 1 + applications/PoP/poc/tools/room.pal | Bin 0 -> 112 bytes applications/PoP/roomtest/Makefile | 34 + applications/PoP/roomtest/kid_data.h | 474 ++++++++++ applications/PoP/roomtest/pop_bg.c | 394 ++++++++ applications/PoP/roomtest/pop_bg.h | 32 + applications/PoP/roomtest/pop_ctrl.c | 368 ++++++++ applications/PoP/roomtest/pop_ctrl.h | 31 + applications/PoP/roomtest/pop_kid.c | 220 +++++ applications/PoP/roomtest/pop_kid.h | 63 ++ applications/PoP/roomtest/pop_map.c | 382 ++++++++ applications/PoP/roomtest/pop_map.h | 48 + applications/PoP/roomtest/room1_data.h | 10 + applications/PoP/roomtest/roomtest.c | 79 ++ applications/PoP/toolchain/1.1 | Bin 0 -> 36981 bytes applications/PoP/toolchain/1.1-2.png | Bin 0 -> 11117 bytes .../PoP/toolchain/pop_extract_kid_data.py | 105 +++ applications/PoP/toolchain/pop_pack_bg.py | 288 ++++++ applications/PoP/toolchain/pop_pack_kid.py | 136 +++ applications/PoP/toolchain/render_room.py | 842 ++++++++++++++++++ applications/PoP/toolchain/room1_debug.png | Bin 0 -> 9291 bytes applications/PoP/toolchain/room1_render.png | Bin 0 -> 6281 bytes .../PoP/toolchain/tiles_room1/r0c0_00.png | Bin 0 -> 543 bytes .../PoP/toolchain/tiles_room1/r0c1_00.png | Bin 0 -> 89 bytes .../PoP/toolchain/tiles_room1/r0c2_00.png | Bin 0 -> 130 bytes .../PoP/toolchain/tiles_room1/r0c3_01.png | Bin 0 -> 216 bytes .../PoP/toolchain/tiles_room1/r0c4_01.png | Bin 0 -> 239 bytes .../PoP/toolchain/tiles_room1/r0c5_01.png | Bin 0 -> 301 bytes .../PoP/toolchain/tiles_room1/r0c6_01.png | Bin 0 -> 239 bytes .../PoP/toolchain/tiles_room1/r0c7_01.png | Bin 0 -> 475 bytes .../PoP/toolchain/tiles_room1/r0c8_14.png | Bin 0 -> 595 bytes .../PoP/toolchain/tiles_room1/r0c9_14.png | Bin 0 -> 595 bytes .../PoP/toolchain/tiles_room1/r1c0_13.png | Bin 0 -> 698 bytes .../PoP/toolchain/tiles_room1/r1c1_13.png | Bin 0 -> 208 bytes .../PoP/toolchain/tiles_room1/r1c2_01.png | Bin 0 -> 262 bytes .../PoP/toolchain/tiles_room1/r1c3_03.png | Bin 0 -> 800 bytes .../PoP/toolchain/tiles_room1/r1c4_00.png | Bin 0 -> 628 bytes .../PoP/toolchain/tiles_room1/r1c5_14.png | Bin 0 -> 622 bytes .../PoP/toolchain/tiles_room1/r1c6_14.png | Bin 0 -> 595 bytes .../PoP/toolchain/tiles_room1/r1c7_14.png | Bin 0 -> 651 bytes .../PoP/toolchain/tiles_room1/r1c8_14.png | Bin 0 -> 597 bytes .../PoP/toolchain/tiles_room1/r1c9_14.png | Bin 0 -> 563 bytes .../PoP/toolchain/tiles_room1/r2c0_14.png | Bin 0 -> 588 bytes .../PoP/toolchain/tiles_room1/r2c1_14.png | Bin 0 -> 607 bytes .../PoP/toolchain/tiles_room1/r2c2_14.png | Bin 0 -> 646 bytes .../PoP/toolchain/tiles_room1/r2c3_14.png | Bin 0 -> 712 bytes .../PoP/toolchain/tiles_room1/r2c4_0E.png | Bin 0 -> 844 bytes .../PoP/toolchain/tiles_room1/r2c5_03.png | Bin 0 -> 767 bytes .../PoP/toolchain/tiles_room1/r2c6_0B.png | Bin 0 -> 458 bytes .../PoP/toolchain/tiles_room1/r2c7_01.png | Bin 0 -> 313 bytes .../PoP/toolchain/tiles_room1/r2c8_01.png | Bin 0 -> 521 bytes .../PoP/toolchain/tiles_room1/r2c9_14.png | Bin 0 -> 541 bytes toolchain/make_hdd.sh | 52 ++ toolchain/png_strip.py | 141 +++ toolchain/room_compose.py | 135 +++ 196 files changed, 6630 insertions(+) create mode 100644 applications/PoP/bgtest/Makefile create mode 100644 applications/PoP/bgtest/bgtest.c create mode 100644 applications/PoP/coltest/Makefile create mode 100644 applications/PoP/coltest/coltest.c create mode 100644 applications/PoP/docs/APPLEII_RESOURCE_FORMAT.md create mode 100644 applications/PoP/docs/KID_PLAN.md create mode 100644 applications/PoP/docs/MSDOS_RESOURCE_FORMAT.md create mode 100644 applications/PoP/docs/POP-DAT-FormatSpecifications.pdf create mode 100644 applications/PoP/docs/PORT_PLAN.md create mode 100644 applications/PoP/docs/README.md create mode 100644 applications/PoP/poc/Makefile create mode 100644 applications/PoP/poc/level.h create mode 100644 applications/PoP/poc/poc.c create mode 100644 applications/PoP/poc/res/bg/pop_bg.pal create mode 100644 applications/PoP/poc/res/bg/pop_bg_atlas.h create mode 100644 applications/PoP/poc/res/bg/pop_env0.atl create mode 100644 applications/PoP/poc/res/bg/pop_env1.atl create mode 100644 applications/PoP/poc/res/bg/pop_env2.atl create mode 100644 applications/PoP/poc/res/bg/pop_env3.atl create mode 100644 applications/PoP/poc/res/bg/pop_env4.atl create mode 100644 applications/PoP/poc/res/bg/pop_fore.atl create mode 100644 applications/PoP/poc/res/bg/pop_wall.atl create mode 100644 applications/PoP/poc/res/kid/kid.pal create mode 100644 applications/PoP/poc/res/kid/kid0.atl create mode 100644 applications/PoP/poc/res/kid/kid1.atl create mode 100644 applications/PoP/poc/res/kid/kid10.atl create mode 100644 applications/PoP/poc/res/kid/kid11.atl create mode 100644 applications/PoP/poc/res/kid/kid12.atl create mode 100644 applications/PoP/poc/res/kid/kid13.atl create mode 100644 applications/PoP/poc/res/kid/kid14.atl create mode 100644 applications/PoP/poc/res/kid/kid15.atl create mode 100644 applications/PoP/poc/res/kid/kid16.atl create mode 100644 applications/PoP/poc/res/kid/kid17.atl create mode 100644 applications/PoP/poc/res/kid/kid18.atl create mode 100644 applications/PoP/poc/res/kid/kid19.atl create mode 100644 applications/PoP/poc/res/kid/kid2.atl create mode 100644 applications/PoP/poc/res/kid/kid20.atl create mode 100644 applications/PoP/poc/res/kid/kid21.atl create mode 100644 applications/PoP/poc/res/kid/kid22.atl create mode 100644 applications/PoP/poc/res/kid/kid23.atl create mode 100644 applications/PoP/poc/res/kid/kid24.atl create mode 100644 applications/PoP/poc/res/kid/kid25.atl create mode 100644 applications/PoP/poc/res/kid/kid26.atl create mode 100644 applications/PoP/poc/res/kid/kid27.atl create mode 100644 applications/PoP/poc/res/kid/kid3.atl create mode 100644 applications/PoP/poc/res/kid/kid4.atl create mode 100644 applications/PoP/poc/res/kid/kid5.atl create mode 100644 applications/PoP/poc/res/kid/kid6.atl create mode 100644 applications/PoP/poc/res/kid/kid7.atl create mode 100644 applications/PoP/poc/res/kid/kid8.atl create mode 100644 applications/PoP/poc/res/kid/kid9.atl create mode 100644 applications/PoP/poc/res/kid/kid_atlas.h create mode 100644 applications/PoP/poc/res/room1.dat create mode 100644 applications/PoP/poc/res/tiles/0-0.png create mode 100644 applications/PoP/poc/res/tiles/1-0.png create mode 100644 applications/PoP/poc/res/tiles/10-0.png create mode 100644 applications/PoP/poc/res/tiles/11-0.png create mode 100644 applications/PoP/poc/res/tiles/12-0.png create mode 100644 applications/PoP/poc/res/tiles/13-0.png create mode 100644 applications/PoP/poc/res/tiles/14-0.png create mode 100644 applications/PoP/poc/res/tiles/15-0.png create mode 100644 applications/PoP/poc/res/tiles/16-0.png create mode 100644 applications/PoP/poc/res/tiles/17-0.png create mode 100644 applications/PoP/poc/res/tiles/18-0.png create mode 100644 applications/PoP/poc/res/tiles/19-0.png create mode 100644 applications/PoP/poc/res/tiles/1A-0.png create mode 100644 applications/PoP/poc/res/tiles/1B-0.png create mode 100644 applications/PoP/poc/res/tiles/1C-0.png create mode 100644 applications/PoP/poc/res/tiles/1D-0.png create mode 100644 applications/PoP/poc/res/tiles/1E-0.png create mode 100644 applications/PoP/poc/res/tiles/1F-0.png create mode 100644 applications/PoP/poc/res/tiles/2-0.png create mode 100644 applications/PoP/poc/res/tiles/3-0.png create mode 100644 applications/PoP/poc/res/tiles/4-0.png create mode 100644 applications/PoP/poc/res/tiles/5-0.png create mode 100644 applications/PoP/poc/res/tiles/6-0.png create mode 100644 applications/PoP/poc/res/tiles/7-0.png create mode 100644 applications/PoP/poc/res/tiles/8-0.png create mode 100644 applications/PoP/poc/res/tiles/9-0.png create mode 100644 applications/PoP/poc/res/tiles/A-0.png create mode 100644 applications/PoP/poc/res/tiles/B-0.png create mode 100644 applications/PoP/poc/res/tiles/C-0.png create mode 100644 applications/PoP/poc/res/tiles/D-0.png create mode 100644 applications/PoP/poc/res/tiles/E-0.png create mode 100644 applications/PoP/poc/res/tiles/F-0.png create mode 100644 applications/PoP/poc/res/tiles/tile00.pal create mode 100644 applications/PoP/poc/res/tiles/tile01.atl create mode 100644 applications/PoP/poc/res/tiles/tile01.pal create mode 100644 applications/PoP/poc/res/tiles/tile01.raw create mode 100644 applications/PoP/poc/res/tiles/tile01_merged.pal create mode 100644 applications/PoP/poc/res/tiles/tile02.pal create mode 100644 applications/PoP/poc/res/tiles/tile03.atl create mode 100644 applications/PoP/poc/res/tiles/tile03.raw create mode 100644 applications/PoP/poc/res/tiles/tile03_merged.pal create mode 100644 applications/PoP/poc/res/tiles/tile04.pal create mode 100644 applications/PoP/poc/res/tiles/tile05.pal create mode 100644 applications/PoP/poc/res/tiles/tile06.pal create mode 100644 applications/PoP/poc/res/tiles/tile07.pal create mode 100644 applications/PoP/poc/res/tiles/tile08.pal create mode 100644 applications/PoP/poc/res/tiles/tile09.pal create mode 100644 applications/PoP/poc/res/tiles/tile0a.pal create mode 100644 applications/PoP/poc/res/tiles/tile0b.atl create mode 100644 applications/PoP/poc/res/tiles/tile0b.raw create mode 100644 applications/PoP/poc/res/tiles/tile0b_merged.pal create mode 100644 applications/PoP/poc/res/tiles/tile0c.pal create mode 100644 applications/PoP/poc/res/tiles/tile0d.pal create mode 100644 applications/PoP/poc/res/tiles/tile0e.atl create mode 100644 applications/PoP/poc/res/tiles/tile0e.raw create mode 100644 applications/PoP/poc/res/tiles/tile0e_merged.pal create mode 100644 applications/PoP/poc/res/tiles/tile0f.pal create mode 100644 applications/PoP/poc/res/tiles/tile10.pal create mode 100644 applications/PoP/poc/res/tiles/tile11.pal create mode 100644 applications/PoP/poc/res/tiles/tile12.pal create mode 100644 applications/PoP/poc/res/tiles/tile13.atl create mode 100644 applications/PoP/poc/res/tiles/tile13.raw create mode 100644 applications/PoP/poc/res/tiles/tile13_merged.pal create mode 100644 applications/PoP/poc/res/tiles/tile14.atl create mode 100644 applications/PoP/poc/res/tiles/tile14.pal create mode 100644 applications/PoP/poc/res/tiles/tile14.raw create mode 100644 applications/PoP/poc/res/tiles/tile14_merged.pal create mode 100644 applications/PoP/poc/res/tiles/tile15.pal create mode 100644 applications/PoP/poc/res/tiles/tile16.pal create mode 100644 applications/PoP/poc/res/tiles/tile17.pal create mode 100644 applications/PoP/poc/res/tiles/tile18.pal create mode 100644 applications/PoP/poc/res/tiles/tile19.pal create mode 100644 applications/PoP/poc/res/tiles/tile1a.pal create mode 100644 applications/PoP/poc/res/tiles/tile1b.pal create mode 100644 applications/PoP/poc/res/tiles/tile1c.pal create mode 100644 applications/PoP/poc/res/tiles/tile1d.pal create mode 100644 applications/PoP/poc/res/tiles/tile1e.pal create mode 100644 applications/PoP/poc/res/tiles/tile1f.pal create mode 100644 applications/PoP/poc/room.c create mode 100644 applications/PoP/poc/room.h create mode 100644 applications/PoP/poc/tools/build_room_palette.py create mode 100644 applications/PoP/poc/tools/build_tile_atlases.py create mode 100644 applications/PoP/poc/tools/extract_room.py create mode 100644 applications/PoP/poc/tools/gen_kid_placeholder.py create mode 100644 applications/PoP/poc/tools/gen_tile_placeholders.py create mode 100644 applications/PoP/poc/tools/kid.atl create mode 100644 applications/PoP/poc/tools/kid.pal create mode 100644 applications/PoP/poc/tools/kid.raw create mode 100644 applications/PoP/poc/tools/room.pal create mode 100644 applications/PoP/roomtest/Makefile create mode 100644 applications/PoP/roomtest/kid_data.h create mode 100644 applications/PoP/roomtest/pop_bg.c create mode 100644 applications/PoP/roomtest/pop_bg.h create mode 100644 applications/PoP/roomtest/pop_ctrl.c create mode 100644 applications/PoP/roomtest/pop_ctrl.h create mode 100644 applications/PoP/roomtest/pop_kid.c create mode 100644 applications/PoP/roomtest/pop_kid.h create mode 100644 applications/PoP/roomtest/pop_map.c create mode 100644 applications/PoP/roomtest/pop_map.h create mode 100644 applications/PoP/roomtest/room1_data.h create mode 100644 applications/PoP/roomtest/roomtest.c create mode 100644 applications/PoP/toolchain/1.1 create mode 100644 applications/PoP/toolchain/1.1-2.png create mode 100644 applications/PoP/toolchain/pop_extract_kid_data.py create mode 100644 applications/PoP/toolchain/pop_pack_bg.py create mode 100644 applications/PoP/toolchain/pop_pack_kid.py create mode 100755 applications/PoP/toolchain/render_room.py create mode 100644 applications/PoP/toolchain/room1_debug.png create mode 100644 applications/PoP/toolchain/room1_render.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c0_00.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c1_00.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c2_00.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c3_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c4_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c5_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c6_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c7_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c8_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r0c9_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c0_13.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c1_13.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c2_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c3_03.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c4_00.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c5_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c6_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c7_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c8_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r1c9_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c0_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c1_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c2_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c3_14.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c4_0E.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c5_03.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c6_0B.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c7_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c8_01.png create mode 100644 applications/PoP/toolchain/tiles_room1/r2c9_14.png create mode 100755 toolchain/make_hdd.sh create mode 100644 toolchain/png_strip.py create mode 100644 toolchain/room_compose.py diff --git a/.gitignore b/.gitignore index 651e592..74a1045 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,30 @@ tests/*/*.cdb tests/*/*.mem tests/*/*.rst +# applications/// — реальные приложения (та же схема, что +# examples/ и tests/, но на уровень глубже: applications/PoP/roomtest/...) +applications/*/*/*.exe +applications/*/*/*.asm +applications/*/*/*.lst +applications/*/*/*.lk +applications/*/*/*.ihx +applications/*/*/*.noi +applications/*/*/*.sym +applications/*/*/*.map +applications/*/*/*.rel +applications/*/*/*.cdb +applications/*/*/*.mem +applications/*/*/*.rst + +# PoP-порт: внешние референс-репозитории (собственные git-клоны — НЕ +# часть этого репозитория) + оригинальные game-данные (копирайт, только +# для реверса форматов на этой машине). +applications/PoP/SDLPoP/ +applications/PoP/mininim/ +applications/PoP/PR/ +applications/PoP/Prince-of-Persia-Apple-II/ +applications/PoP/MSDOS/ + # libc + libbgi archives (built by libc/Makefile + libbgi/Makefile) lib/*.lib diff --git a/applications/PoP/bgtest/Makefile b/applications/PoP/bgtest/Makefile new file mode 100644 index 0000000..cd2bee0 --- /dev/null +++ b/applications/PoP/bgtest/Makefile @@ -0,0 +1,27 @@ +# bgtest — мини-тест атласов статического фона PoP (Шаг 2, до порта room.c). +# Проверяет загрузку .atl + палитру + прямую адресацию + прозрачность. +# +# --memory huge (как poc): atlas_load/gfx_blit трогают W3; huge кладёт +# CODE в W1, DATA/BSS в W2 — без банков. --gfx 256 подлинкует bgi256. +# +# Данные фона генерит toolchain/pop_pack_bg.py в ../poc/res/bg/. + +PROJ_ROOT := $(abspath $(CURDIR)/../../..) +EXAMPLE := bgtest +MEMORY ?= huge +EXTRA_FLAGS ?= --gfx 256 + +BG_DIR := $(CURDIR)/../poc/res/bg +BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.atl \ + $(BG_DIR)/pop_env3.atl $(BG_DIR)/pop_env4.atl \ + $(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl $(BG_DIR)/pop_bg.pal + +EXTRA_DATA := $(BG_DATA) +include $(PROJ_ROOT)/app.mk + +# Ассеты фона: пересобрать пакером, если исходники поменялись. +$(BG_DATA): $(PROJ_ROOT)/applications/PoP/toolchain/pop_pack_bg.py \ + $(PROJ_ROOT)/applications/PoP/toolchain/render_room.py + cd $(PROJ_ROOT)/applications/PoP/toolchain && python3 pop_pack_bg.py + +$(EXAMPLE).exe: $(BG_DATA) diff --git a/applications/PoP/bgtest/bgtest.c b/applications/PoP/bgtest/bgtest.c new file mode 100644 index 0000000..6d76751 --- /dev/null +++ b/applications/PoP/bgtest/bgtest.c @@ -0,0 +1,112 @@ +/* + * bgtest.c — мини-тест атласов статического фона PoP (Шаг 2 порта, ДО + * порта room.c). Проверяет на MAME: загрузку 7 .atl, палитру pop_bg.pal, + * ПРЯМУЮ адресацию (id -> страница/idx без remap-таблиц) и прозрачность + * (индекс 0xFF). Рисует сетку репрезентативных спрайтов на цветной + * заливке — прозрачные области должны показать фон. + * + * Раскладка из toolchain/pop_pack_bg.py (см. pop_bg_atlas.h): + * ENV фон id N -> env[N>>5], idx N&31 ; WALL/FORE idx = id. + */ +#include +#include +#include +#include +#include + +/* Имена файлов — плоская ФС диска (make_disk кладёт по basename). */ +static const char *const ENV_ATL[5] = { + "pop_env0.atl", "pop_env1.atl", "pop_env2.atl", + "pop_env3.atl", "pop_env4.atl" +}; + +static atlas_t env[5]; +static atlas_t wall_a; +static atlas_t fore_a; + +/* Блит одного спрайта ленты idx атласа a в (x,y): страница атласа в W0, + * gfx_blit читает w/h из getimage-заголовка ленты (в W0). */ +static void put(atlas_t *a, unsigned char idx, int x, int y) +{ + const void *img = atlas_image(a, idx); + gfx_w0_map(a->page); + gfx_blit(x, y, img); + gfx_w0_unmap(); +} + +static void put_env(unsigned char id, int x, int y) +{ + put(&env[id >> 5], (unsigned char)(id & 31), x, y); +} + +int main(void) +{ + int i; + + /* Загрузка всех 7 атласов (env0..4 + wall + fore). */ + for (i = 0; i < 5; i++) { + if (atlas_load(&env[i], ENV_ATL[i]) != 0) { + printf("atlas_load %s failed\n", ENV_ATL[i]); + return 1; + } + } + if (atlas_load(&wall_a, "pop_wall.atl") != 0) { puts("wall atl fail"); return 1; } + if (atlas_load(&fore_a, "pop_fore.atl") != 0) { puts("fore atl fail"); return 1; } + + initgraph(); + gfx_set_draw_page(0); + gfx_set_visible_page(0); + if (gfx_pal_fload(0, "pop_bg.pal") < 0) + gfx_pal_fload(0, "a:\\pop_bg.pal"); + /* Свой яркий фон-индекс (вне 0x50..0x6F, занятых графикой) — чтобы + * прозрачные (0xFF) области спрайтов были ЯВНО видны. */ +#define BG_IDX 0x20 + gfx_pal_set(0, BG_IDX, 120, 0, 90); /* r,g,b — тёмно-пурпурный */ + gfx_pal_sync(); + + setfillstyle(SOLID_FILL, BG_IDX); + bar(0, 0, 319, 255); + setcolor(WHITE); + outtextxy(2, 2, "PoP bg atlas test"); + + /* Прозрачный блит: банк не пишет 0xFF (фон проступает). */ + gfx_set_bank(GFX_BANK_TRANSPARENT); + + /* --- Стены (chtab_7): нижние грани 7/9/5/3, основные 8/10/6/4 --- */ + put(&wall_a, 3, 4, 20); put(&wall_a, 5, 40, 20); + put(&wall_a, 7, 76, 20); put(&wall_a, 9, 112, 20); + put(&wall_a, 4, 4, 90); put(&wall_a, 6, 40, 90); + put(&wall_a, 8, 76, 90); put(&wall_a, 10, 112, 90); + /* декали-марки стен 14..17 */ + put(&wall_a, 14, 150, 20); put(&wall_a, 15, 168, 20); + put(&wall_a, 16, 186, 20); put(&wall_a, 17, 204, 20); + + /* --- Столб: база 92, боковая грань 93, фронт 95 (fore) --- */ + put_env(92, 4, 160); + put_env(93, 40, 160); + put(&fore_a, 95, 76, 160); + + /* --- Пол: база 41, правый треуг. 42, низ 43; силуэт 44/45 --- */ + put_env(41, 150, 120); + put_env(42, 190, 120); + put_env(44, 230, 120); + put_env(45, 260, 120); + + /* --- Решётка-окно 126, дебрис 97/98, слайсы ворот 52/53 --- */ + put_env(126, 150, 160); + put_env(97, 190, 160); + put_env(52, 230, 160); + put_env(53, 250, 160); + + gfx_set_bank(GFX_BANK_NORMAL); + /* Держим картинку на экране до нажатия (не полагаемся на блокирующий + * getch — в автотесте клавиш нет, крутимся до таймаута MAME). */ + while (!kbhit()) + gfx_wait_vsync(); + + closegraph(); + for (i = 0; i < 5; i++) atlas_free(&env[i]); + atlas_free(&wall_a); + atlas_free(&fore_a); + return 0; +} diff --git a/applications/PoP/coltest/Makefile b/applications/PoP/coltest/Makefile new file mode 100644 index 0000000..4ee59f6 --- /dev/null +++ b/applications/PoP/coltest/Makefile @@ -0,0 +1,6 @@ +# coltest — прототип вертикального accel-copy (gfx_blit_cols) + флип. +PROJ_ROOT := $(abspath $(CURDIR)/../../..) +EXAMPLE := coltest +MEMORY ?= huge +EXTRA_FLAGS ?= --gfx 256 +include $(PROJ_ROOT)/app.mk diff --git a/applications/PoP/coltest/coltest.c b/applications/PoP/coltest/coltest.c new file mode 100644 index 0000000..87a6ce6 --- /dev/null +++ b/applications/PoP/coltest/coltest.c @@ -0,0 +1,64 @@ +/* + * coltest.c — прототип K2a: проверка вертикального accel-COPY + * (gfx_blit_cols) + прозрачности 0xFF + горизонтального флипа. + * + * Спрайт 16x24 column-major, АСИММЕТРИЧНЫЙ: + * левые 8 колонок: верх (row<12) = ПРОЗРАЧНО (0xFF), низ = БЕЛЫЙ (1) + * правые 8 колонок: КРАСНЫЙ (2) + * На синем фоне (3). Ожидаем на MAME: + * normal @ (50,100): слева бело-снизу+прозрачно-сверху, справа красный; + * flip @(120,100): ЗЕРКАЛО — слева красный, справа бело+прозрачно-сверху; + * в прозрачных местах виден СИНИЙ фон. + */ +#include +#include +#include + +#define W 16 +#define H 24 +static unsigned char spr[4 + W * H]; + +int main(void) +{ + int col, row; + + spr[0] = W; spr[1] = 0; + spr[2] = H; spr[3] = 0; + for (col = 0; col < W; col++) + for (row = 0; row < H; row++) { + /* ФИНАЛ: АСИММЕТРИЧНЫЙ — лево верх прозрачно / низ белый, + * право красное. normal: лево бело+прозрач-верх, право красн; + * flip: ЗЕРКАЛО (лево красн, право бело+прозрач-верх). */ + unsigned char v; + if (col < 8) + v = (row < 12) ? 0xFF : 1; + else + v = 2; + spr[4 + col * H + row] = v; + } + + initgraph(); + gfx_set_draw_page(0); + gfx_set_visible_page(0); + gfx_pal_set(0, 1, 255, 255, 255); /* белый */ + gfx_pal_set(0, 2, 224, 32, 32); /* красный */ + gfx_pal_set(0, 3, 32, 48, 200); /* синий фон */ + gfx_pal_set(0, 255, 0, 224, 0); /* ДИАГ: 0xFF как ЗЕЛЁНЫЙ цвет (bank NORMAL) */ + gfx_pal_sync(); + + setfillstyle(SOLID_FILL, 3); + bar(0, 0, 319, 255); + setcolor(1); + outtextxy(40, 80, "normal"); + outtextxy(110, 80, "flip"); + + gfx_set_bank(GFX_BANK_TRANSPARENT); /* ДИАГ: 0x58 подавление 0xFF (с тенью) */ + gfx_blit_cols(50, 100, spr, 0); /* обычный */ + gfx_blit_cols(120, 100, spr, 1); /* зеркало */ + gfx_set_bank(GFX_BANK_NORMAL); + + while (!kbhit()) + gfx_wait_vsync(); + closegraph(); + return 0; +} diff --git a/applications/PoP/docs/APPLEII_RESOURCE_FORMAT.md b/applications/PoP/docs/APPLEII_RESOURCE_FORMAT.md new file mode 100644 index 0000000..a08fed1 --- /dev/null +++ b/applications/PoP/docs/APPLEII_RESOURCE_FORMAT.md @@ -0,0 +1,283 @@ +# Формат ресурсов Prince of Persia (Apple II, оригинальные исходники 1989) + +Источник — официально опубликованные Джорданом Мехнером исходники +(`Prince-of-Persia-Apple-II/`, 6502-ассемблер). В отличие от DOS-версии, здесь +формат восстановлен **напрямую по коду**, а не по догадкам о байтах — +уверенность высокая везде, где указана ссылка на конкретный файл/строки. + +--- + +## 1. Формат уровня (`01 POP Source/Levels/LEVEL0`…`LEVEL14`, 2304 байта) + +Файлы уровня — это побайтовый дамп структуры `blueprnt`, которая грузится по +фиксированному адресу `$b700` (`EQ.S:28`) и объявлена как `dum blueprnt` в +`EQ.S:258-266`. Никакого отдельного заголовка файла нет — это чистый образ +структуры в памяти: + +| Поле | Размер | Смещение в файле | Описание | +|---|---|---|---| +| `BLUETYPE` | 720 Б | 0–719 | 24 экрана × 30 тайлов: тип объекта/тайла | +| `BLUESPEC` | 720 Б | 720–1439 | 24 экрана × 30 тайлов: доп. байт состояния объекта | +| `LINKLOC` | 256 Б | 1440–1695 | Таблица связей нажимных плит/дверей, часть 1 | +| `LINKMAP` | 256 Б | 1696–1951 | Таблица связей, часть 2 | +| `MAP` | 96 Б | 1952–2047 | 24 экрана × 4 байта: граф соседних экранов | +| `INFO` | 256 Б | 2048–2303 | Метаданные уровня: старт Кида, стражников и т.д. | + +Сумма: 720+720+256+256+96+256 = **2304** — точно совпадает с размером файла, +что подтверждает: это чистый дамп структуры, без обёртки. + +### 1.1 Сетка тайлов (`BLUETYPE` / `BLUESPEC`) + +Каждый экран — ровно **30 тайлов** (10 столбцов × 3 ряда): подтверждено +таблицами `BlockTable`/`BlockEdge` (`TABLES.S:74-154`) и логикой перехода +между экранами в `CTRLSUBS.S:218-234` (при переходе через край экрана +`tempblockx` меняется на ±10, `tempblocky` — на ±3). + +Функция `CALCBLUE` (`GRAFIX.S:1757-1784`) вычисляет для экрана 1–24: +`BlueType = blueprnt + (screen-1)*30`, `BlueSpec = BlueType + 24*30`, +используя таблицу `Mult30` (`TABLES.S:131-140`). + +Байт `BLUETYPE` упакован битовыми полями (`EQ.S:484-486`): + +``` +бит 7-6: secmask (%11000000) — назначение не установлено по доступному коду + (возможно, служебное поле редактора) +бит 5: reqmask (%00100000) — флаг "необходимая опорная плитка" + (проверяется в BREAKLOOSE, MOVER.S:395-397) +бит 4-0: idmask (%00011111) — тип тайла/объекта, 0-29 +``` + +Перечень 30 типов объектов (`MOVEDATA.S:8-37`): + +``` +0 space 8 pillarbottom 16 exit 24 window2 +1 floor 9 pillartop 17 exit2 25 archbot +2 spikes 10 flask 18 slicer 26 archtop1 +3 posts 11 loose 19 torch 27 archtop2 +4 gate 12 panelwof 20 block 28 archtop3 +5 dpressplate 13 mirror 21 bones 29 archtop4 +6 pressplate 14 rubble 22 sword +7 panelwif 15 upressplate 23 window +``` + +Проверено вручную на дампе начала `LEVEL1` (`00 00 00 21 01 21 21 21 34 34 +33 33 21 23 00 34 14 14 14 34 14 34 34 2e 23 0b 01 21 34`) — например, +`0x33 → id=0x13=19 (torch)`+reqmask, `0x34 → id=20 (block)`+reqmask — +декодирование по таблице сходится чисто. + +`BLUESPEC` — доп. байт, чья семантика зависит от типа тайла (единой схемы +нет, разбирается объект-специфичным кодом): + +- **gate** (дверь, `FRAMEADV.S:2222-2234`): на диске — маленький enum (1 = + начинает открытой сверху, 2 = снизу, …), который через `initsettings` + (`FRAMEADV.S:22-23`, диапазон `gminval=0`..`gmaxval=188`, из + `MOVEDATA.S:56-57`) при инициализации уровня превращается в живой счётчик + "высоты двери" 0–188. +- **loose** (шаткая плитка, `FRAMEADV.S:2224-2237`): при инициализации всегда + принудительно обнуляется, независимо от значения на диске. +- **flask** (зелье, `FRAMEADV.S:2226,2239-2246`): значение×32 выбирает + цвет/тип зелья. +- **spikes** (шипы, `MOVER.S:365-382`, константы `spikeExt=5, spikeRet=9` в + `MOVEDATA.S:45-46`): 0 = безопасно/убраны, 1–8 — кадр анимации + выдвижения/втягивания, `$FF` = навсегда заклинило (тело наколото). +- **pressplate/upressplate** (нажимные плиты, `MOVER.S:425-464`, + `FRAMEADV.S:2059-2098`): значение — это **индекс в цепочке связей** + `LINKLOC`/`LINKMAP` (см. ниже); младшие 5 бит `LINKMAP` по этому индексу + одновременно служат счётчиком таймера плиты (0–31), определяющим + состояние "поднято/опущено". + +### 1.2 `LINKLOC` / `LINKMAP` — граф триггеров (нажимные плиты → двери и т.п.) + +Два параллельных массива по 256 байт кодируют цепочки "нажатие плиты X → +сработать объект на экране S, блок B". Восстановлено из `MOVER.S:506-537` +(цикл `trigger`) и `MOVER.S:1549-1581` (`gettimer/chgtimer/getloc/ +getlastflag/getscrn`): + +``` +LINKLOC[i]: бит 7 = флаг "последнее звено цепочки" + биты 6-5 = младшие 2 бита номера целевого экрана + биты 4-0 = номер целевого блока (0-29); $FF = "никуда не привязано" + +LINKMAP[i]: биты 7-5 = старшие 3 бита номера целевого экрана + (вместе с LINKLOC биты 6-5 → полный номер экрана 0-31) + биты 4-0 = таймер обратного отсчёта плиты (0-31, значим только + по индексу самой плиты) +``` + +`BLUESPEC` плиты хранит индекс `i` её *первого* звена; `getlastflag` идёт +вперёд (`inc linkindex`), пока не встретит бит 7 в `LINKLOC`. Сверено на +`LEVEL1`: байт по смещению 1440 (`0x89 = 10001001` → флаг конца цепочки, +целевой блок 9) и параллельно байт по смещению 1696 (`0x60 = 01100000` → +старшие биты номера экрана) — согласуется с этой раскладкой. Заполнены +реально используемые уровнем звенья, остальное — "мусорные" повторяющиеся +байты-заполнители. + +### 1.3 `MAP` — граф соседних экранов + +24 записи × 4 байта = 96 байт: для каждого экрана (1–24) +`MAP[(scrn-1)*4 + 0..3] = левый, правый, верхний, нижний соседние экраны`, +читается через `GETLEFT/GETRIGHT/GETUP/GETDOWN` (`CTRLSUBS.S:244-274`, +индексация `MAP-4..MAP-1,x` при `x = scrn*4`). Экран `0` зарезервирован как +"нет экрана" (проверка `beq ]rts` в этих же процедурах). + +### 1.4 `INFO` — метаданные уровня (256 байт, база = смещение файла 2048) + +Объявлено как `dum INFO` в `EQ.S:272-288`: + +| Смещение (от начала INFO) | Поле | Размер | +|---|---|---| +| 0 | "число экранов + 1" (используется в `SETINITIALS`, `SUBS.S:1441-1445`) | 1 | +| 1–63 | резерв/не используется | 63 | +| 64 | `KidStartScrn` | 1 | +| 65 | `KidStartBlock` | 1 | +| 66 | `KidStartFace` (направление; при загрузке инвертируется XOR `$ff`, `SUBS.S:1516-1518`) | 1 | +| 67 | заполнитель | 1 | +| 68 | `SwStartScrn` (стартовый экран меча) | 1 | +| 69 | `SwStartBlock` | 1 | +| 70 | заполнитель | 1 | +| 71–94 | `GdStartBlock[1..24]` — стартовый блок стражника на экране; **≥30 = "стражника нет"** (`AUTO.S:1832-1834`, `SUBS.S:1677-1679`) | 24 | +| 95–118 | `GdStartFace[1..24]` (86 = "стражника нет", см. `ShadFace cmp #86` по всему `AUTO.S`) | 24 | +| 119–142 | `GdStartX[1..24]` — пересчитывается заново из блока при старте уровня, значение на диске почти не используется (`SUBS.S:1674-1690`) | 24 | +| 143–166 | `GdStartSeqL[1..24]` | 24 | +| 167–190 | `GdStartProg[1..24]` — "программа"/поведение ИИ стражника | 24 | +| 191–214 | `GdStartSeqH[1..24]` — обнуляется при старте (`SUBS.S:1685-1686`) | 24 | +| 215–255 | резерв/не используется | 41 | + +Проверено на `LEVEL1`: байт по смещению файла 0x800 = `0x18`=24 (число +активных экранов = 23+1); по смещению 0x840 — `01 00 ff 00 00 00 ff 1e 1e +11 1e 1e ...` → `KidStartScrn=1, KidStartBlock=0, KidStartFace=$FF, +SwStartScrn=0, SwStartBlock=0`, далее 24 байта `GdStartBlock`, в основном +`0x1e`(30, "нет стражника"), с реальной расстановкой только на экране 3 +(`0x11`=17) и экране 23 (`0x06`) — согласуется с уровнем, где всего два +стражника. + +### 1.5 Как уровень попадает с диска (важно: имя файла — не игровой механизм) + +В рантайме нет чтения "по имени файла LEVELn" — это чисто утилита для +экспорта в этом репозитории. Реально `LOADLEVELX` (`MISC.S:795-809`) +использует фиксированные таблицы по номеру уровня `bluepTRKlst`/ +`bluepREGlst` (`MISC.S:776-787`), дающие физическую **дорожку (1-33)** и +**регион (0/1)**, затем `rdbluep` (`MASTER.S:598-616`) вызывает +низкоуровневое чтение `rw18` (`RdGrpErr`) 9 физических групп по 256 байт +(`$b7-$bf`) — 9×256=2304 байта — прямо в буфер blueprint; регион 0/1 выбирает +половину 18-секторной дорожки (два уровня делят одну дорожку). Файлы +`LEVELn` в этом репозитории — реконструкция этого сырого блока для удобства +работы с инструментами. + +--- + +## 2. Формат изображений/спрайтов (`IMG.CHTAB1-7`, `IMG.BGTAB1/2.DUN/.PAL`) + +Каждый такой файл грузится целиком по **фиксированному адресу**, заданному +константами `chtableN`/`bgtableN` (`GAMEEQ.S:9-18`): + +``` +chtable1=$6000 chtable2=$8400 chtable3=$0800 chtable4=$9600 +chtable5=$a800 chtable6=$6000 chtable7=$9f00 +bgtable1=$6000 bgtable2=$8400 +``` +— то есть смещения внутри файла один-в-один совпадают с адресами в памяти +после загрузки. + +### 2.1 Раскладка контейнера + +Восстановлено из заголовка-комментария "Image table format" в `HIRES.S:181-186`, +процедуры разрешения указателя `setimage` (`HIRES.S:263-277`) и +`GETWIDTH`/`PREPREP` (`HIRES.S:283-339`): + +``` +Смещение 0 : 1 байт — число изображений в таблице (максимум 127, + в образцах встречается 0x7f) +Смещение 1..254 : 127 × 2-байтных little-endian указателей + (указатель на изображение N — по смещению 1+(N-1)*2, + N=1..127) — АБСОЛЮТНЫЕ адреса в адресном пространстве + фиксированной загрузки этой таблицы, указывающие на + запись данных этого изображения +Смещение 255 : заполнитель (таблица указателей занимает ровно 256 байт) +Смещение 256 (база+0x100) и далее: + последовательно идущие записи данных изображений: + байт 0: ширина (в байтах на строку) + байт 1: высота (число строк) + байты 2..(2+ширина*высота-1): сырые байты пикселей, + слева направо, сверху вниз, БЕЗ сжатия +``` + +Проверено вручную на `IMG.BGTAB1.DUN`: с точной арифметикой индексов из +`setimage` (`Y = image*2 - 1`, `HIRES.S:264-267`) первые ~30 записей дают +строго возрастающую последовательность указателей `0x6101, 0x6133, 0x6159, +0x618b, 0x61c9, 0x61fb, 0x6221, 0x6313, 0x63c9, ...` — указатель +изображения #1 приходится ровно на `bgtable1 ($6000) + 0x100`, то есть точно +на конец 256-байтной таблицы указателей. Это независимо подтверждает и +размер таблицы, и семантику указателей. + +**Важно: сжатия в этом формате нет.** RLE/дельта-упаковка (`SngExpand`/ +`DblExpand`/`DeltaExpPop`/`DeltaExpWipe` в `01 POP Source/Source/UNPACK.S`) +применяется только к полноэкранным изображениям (титры/пролог/катсцены), но +не к CHTAB/BGTAB — спрайты и фоновые тайлы хранятся как чистые упакованные +байты hi-res/double-hi-res экрана Apple II, без какого-либо RLE или дельты. + +### 2.2 Параметры отрисовки (не часть файла ресурса) + +При выводе спрайта (`LAY`/`FASTLAY`/`PEEL` и т.д., `HIRES.S:658-1740`) +используются zero-page параметры `PAGE/XCO/YCO/OFFSET/IMAGE/OPACITY/TABLE/ +BANK` (описаны в `HIRES.S:155-178`): `OFFSET` (0–6) — горизontальный сдвиг на +под-байтовый пиксель, `OPACITY` выбирает режим совмещения (AND/OR/STA/XOR/ +маска-OR) плюс отдельный бит горизонтального зеркалирования (бит 7). Это +чисто рантайм-параметры отрисовки, не хранящиеся в файле ресурса. Точный +механизм барабанного сдвига для `OFFSET` (таблицы `HRTABLES.S`/`YLO`/`YHI`) +не прослежен до конца — при необходимости требует отдельного анализа. + +### 2.3 Инструмент DRAZ (авторская утилита создания спрайтов) + +В `04 Support/DRAZ` нет исходников самой утилиты DRAZ — только файлы данных +(`PAC.*` — позы персонажей, и уже скомпилированные `IMG.*`), поэтому +внутренний пайплайн DRAZ (как позы превращаются в CHTAB) напрямую не виден. +Формат контейнера выше выведен полностью из кода движка-потребителя, что +является надёжным, но косвенным источником. + +Отдельно: в игровой логике списков объектов (`ADDBACK`, `GRAFIX.S:191-214`) +встречается **рантайм-упаковка ссылки на фоновую картинку** в один байт: бит +7 выбирает `bgtable1` или `bgtable2`, биты 0-6 — номер картинки в таблице +(0-63). Это соглашение для внутриигровых списков объектов (`bgIMG` и т.п.), а +не свойство самих файлов CHTAB/BGTAB на диске. + +--- + +## 3. "Главного индекса ресурсов" не существует + +В отличие от DOS-версии (см. `docs/MSDOS_RESOURCE_FORMAT.md`), в рантайм-коде +Apple II **нет обобщённого справочника "имя ресурса → расположение на +диске"**. Расположение каждого ресурса зашито напрямую как таблицы +дорожка/группа-секторов прямо в коде загрузчика: + +- Уровни: `bluepTRKlst`/`bluepREGlst` (`MISC.S:776-787`), используются + `LOADLEVELX`/`LOADLEVEL` (`MISC.S:795-809`, `MASTER.S:467-481`). +- Альтернативные наборы фонов/персонажей: `bg1trk`/`bg2trk`/`ch4trk`/`ch4off` + (`MASTER.S:522-528`). +- Массовая загрузка при старте (chtable1-7, bgtable1-2, seqtable и т.д.): + прямые вызовы `rw18`/`RdGrp`/`RdSeq` с литеральными hex-списками + групп-секторов в `MASTER.S:1250-1360` и `BOOT.S:100-118`. + +Весь дисковый ввод-вывод идёт через нестандартный низкоуровневый драйвер +`rw18` (`rw18 = $d000`, `EQ.S:11-12`; папка `02 POP Disk Routines/RW1835`), +реализующий нестандартный формат **18 секторов/дорожку** (вместо 16 у +стандартного DOS 3.3) — этим объясняется, почему регионы уровня (9×256Б) +идут парами на одной физической дорожке. Символические имена +`chtableN`/`bgtableN` в `GAMEEQ.S` — ближайший аналог "индекса ресурсов", но +они связывают ресурс с **фиксированным адресом в ОЗУ**, а не с положением на +диске; связь с диском — отдельная, вручную сопровождаемая таблица, +сопоставленная с ресурсом лишь порядком вызовов загрузчика. + +--- + +## 4. Что ещё не восстановлено (открытые вопросы) + +- Точное назначение бит `secmask` (%11000000) в `BLUETYPE` — не встречено + использование в доступном игровом коде (возможно, поле только для + редактора уровней, не читается движком). +- Механизм барабанного сдвига `OFFSET` для суб-байтового позиционирования + спрайта по X (`HRTABLES.S`) — не прослежен в деталях. +- Внутренний формат авторских файлов `PAC.*` инструмента DRAZ (как позы + скелетной анимации превращаются в растровые кадры CHTAB) — исходники DRAZ + отсутствуют в репозитории, можно только косвенно восстановить по + результату (уже скомпилированным `IMG.*`). diff --git a/applications/PoP/docs/KID_PLAN.md b/applications/PoP/docs/KID_PLAN.md new file mode 100644 index 0000000..f0756f6 --- /dev/null +++ b/applications/PoP/docs/KID_PLAN.md @@ -0,0 +1,199 @@ +# Prince of Persia — Kid (персонаж): анализ и план + +Статус: план (2026-07-16). Опирается на разбор `SDLPoP/src/seg006.c` +(ядро физики/управления Kid), `seqtbl.c` (таблицы последовательностей), +`types.h` (char_type, seq_*, SEQ_*, actions_*), `SDLPoP/data/KID` (спрайты). +Фон уже готов и проверен на MAME (`applications/PoP/roomtest`, см. +`memory/pop_background_strategy`) — Kid развиваем в том же `roomtest` как +новый PoC (решение пользователя: старый `poc/` не трогаем). + +**Копирайт:** спрайты Kid (`SDLPoP/data/KID`) — Broderbund/Ubisoft. +Использование настоящей графики Kid — сознательное решение пользователя +(в отличие от плейсхолдера в старом `poc/`, см. PORT_PLAN §5.1). + +--- + +## 1. Как устроен персонаж в оригинале (что портируем) + +### 1.1 Состояние — `char_type` (14 полей, types.h) + +``` +frame текущий номер кадра (индекс во frame_table_kid) +x, y позиция (byte; x — с учётом direction) +direction -1 влево / 0 вправо +curr_col, логическая клетка (тайл), где персонаж +curr_row +action КАТЕГОРИЯ действия (actions_*, см. 1.2) +fall_x, скорость падения (fall_y<22 = 1 ряд, <33 = 2 ряда) +fall_y +room комната +repeat счётчик для удержания-ввода (напр. повторный прыжок) +sword есть ли меч (бой — вне Фазы 1) +alive жив/мёртв +curr_seq УКАЗАТЕЛЬ в seqtbl (байткод текущей последовательности) +``` + +Состояние крошечное — легко живёт в W2. + +### 1.2 Категории действия — `actions_*` (9 шт) + +`0 stand`, `1 run_jump`, `2 hang_climb`, `3 in_midair`, `4 in_freefall`, +`5 bumped`, `6 hang_straight`, `7 turn`, `99 hurt`. `action` определяет, +как `check_action()`/`play_kid()` реагируют на ввод и физику каждый тик. + +### 1.3 Движок анимации/движения — ГЛАВНОЕ + +**Движение НЕ физика, а байткод + per-frame смещения** (подтверждает +PORT_PLAN §6). Три уровня: + +1. **`seqtbl`** — байткод-программа на действие. Опкоды (types.h): + `SEQ_DX`(0xFB) сдвиг x на amount×direction, `SEQ_DY`(0xFA) сдвиг y, + `SEQ_FLIP`(0xFE) разворот, `SEQ_JMP`(0xFF)/`SEQ_JMP_IF_FEATHER`(0xF7), + `SEQ_UP`/`SEQ_DOWN`(0xFD/0xFC) смена ряда, `SEQ_ACTION`(0xF9) задать + `Char.action`, `SEQ_SET_FALL`(0xF8), `SEQ_KNOCK_UP/DOWN`, `SEQ_SOUND`, + `SEQ_DIE`/`SEQ_END_LEVEL`/`SEQ_GET_ITEM`. **Байт < 0xF0 = НОМЕР КАДРА** + → ставит `Char.frame` и play_seq возвращается (один кадр за тик). +2. **`play_seq()`** (seg006.c:570) — интерпретатор: крутит опкоды из + `seqtbl + Char.curr_seq`, пока не встретит кадр. ~15 case — портируется + 1-в-1. **Квирк:** seqtbl использует АБСОЛЮТНЫЕ DOS-адреса в JMP; + `SEQTBL_0 = seqtbl - SEQTBL_BASE(0x196E)` — при порте пересчитать + базу (JMP-адреса в наших данных). +3. **`frame_table_kid[]`** (seg006.c:127, ~180 кадров) — на КАЖДЫЙ кадр: + `{image, sword_flags, dx, dy, flags}`. `image` — индекс спрайта Kid; + `dx/dy` — смещение позиции ЭТОГО кадра; `flags`: 0x1F weight_x, 0x20 + thin, 0x40 needs_floor, 0x80 even/odd-pixel (влияет на x-рендер). + +**Тик персонажа:** `play_kid()` (диспетчер по action+вводу) → `play_seq()` +(двигает curr_seq, ставит кадр, применяет seq-dx/dy) → `frame_table[frame]` +даёт image+собственные dx/dy → позиция и спрайт. У нас это ложится на +`sprite_frame`+`sprite_move` (НЕ `sprite_anim`/`sprite_moveto` — см. +PORT_PLAN §6: авторские таблицы, не автопрогрессия). + +### 1.4 Управление — `control_kid()`/`read_user_control()` (seg006.c) + +Читает ввод (у нас — held-state `kbd_raw`, уже готово, §2 PORT_PLAN) и по +`Char.action` выбирает последовательность (`seqtbl_offset_char(seq_id)`). +Логика «что можно из какого состояния» — ядро ощущения PoP. + +### 1.5 Взаимодействие с картой — collision (seg006.c) + +`check_on_floor()`/`start_fall()` — пол под ногами / падение в яму; +`in_wall()` — упор в стену (сдвиг наружу); `check_grab()`/ +`can_grab_front_above()` — зацеп за уступ; `fell_out()` — вывалиться из +комнаты; `check_spiked()`/loose — ловушки; `fall_accel()`/`fall_speed()` — +ускорение падения. Всё читает ТИП тайла (`get_tile`) — у нас это уже +разобранные `fg[]/bg[]` (level.h/room1_data.h). + +--- + +## 2. Спрайты Kid (219 шт, 16 цветов, 177 КБ) + +- 219 PNG (`data/KID`), 16-цветные (палитра `res400.pal`, 16×RGB как env/ + wall), макс кадр **53×35** — влезает в лимит движка 64×64. 177 КБ в + 8bpp. +- `frame_table_kid` отображает кадр→`image` (индекс спрайта). Число + РАЗЛИЧНЫХ image — уточнить (≤219); паковать те, что реально используются + платформинг-последовательностями Фазы 1 (не все 219 — бой/катсцены + отдельно). +- **Палитра:** Kid 16 цветов → слоты Sprinter `0x70-0x7F` (env 0x50, wall + 0x60 уже заняты; Kid не пересекается). Пиксель i: 0→0xFF, i→0x70+i. + Тот же пайплайн, что `pop_pack_bg.py`. +- **Атлас:** прямая адресация по номеру image (как фон): `kid[img>>5]`, + idx `img&31`; ~7 EMM-страниц (или SHIFT=4). Свой пакер `pop_pack_kid.py` + (переиспользовать код `pop_pack_bg.py`). + +### 2.1 РЕШЕНИЕ ДО СТАРТА: per-frame offset vs padding + +Кадры Kid — РАЗНОГО размера, а `sprite_t` рисует от угла фикс. w/h. Два +пути (см. PORT_PLAN §6.1, `memory/png_strip_padding_tradeoff`): +- **Padding** (bottom-center) — просто, но 219×53×35 ≈ 406 КБ (раздув ×2.3). +- **Per-frame offset** — хранить XCO/YCO кадра (у оригинала он и есть, + `APPLEII_RESOURCE_FORMAT §2.2`), рисовать `blit(x+xco, y+yco)`; паддинг не + нужен, память по факту (177 КБ). Требует лёгкого расширения хранения + (offset рядом с кадром) ИЛИ ручного смещения в коде рендера Kid. +**Рекомендация:** per-frame offset — оригинал так и делает (frame_table dx/dy ++ image XCO/YCO), даёт точное позиционирование И экономию. Хранить xco/yco +в нашей копии frame_table (добавить 2 байта/кадр — ~360 Б). Не тянуть +расширение `sprite.h` — рисовать Kid прямым `gfx_blit(x+xco, y+yco, img)` +(как фон), НЕ через retained `sprite_t`, раз позиция и кадр всё равно +задаются вручную каждый тик. + +--- + +## 3. Данные для порта (объём) + +- `frame_table_kid` → C-массив ~180×(5+2 offset) ≈ 1.3 КБ (const, ROM). +- `seqtbl` (нужные последовательности) → C-массив байт. Весь seqtbl ~1-2 КБ; + для Фазы 1 можно взять только платформинг-последовательности (вырезать + бой/гардов 55-92) — оценить после разметки. JMP-адреса пересчитать под + свою базу. +- Спрайты — атласы (EMM, не W2). + +--- + +## 4. Фазы работы (по твоему списку, порядок по зависимостям) + +**Фаза K0 — конвейер спрайтов + отрисовка одного кадра** +- `pop_pack_kid.py`: 219 (или подмножество) → `kid*.atl` + `kid.pal` + (слоты 0x70), таблица кадр→image + xco/yco. +- Отрисовать Kid ОДНИМ кадром (stand) в roomtest поверх фона на верном + тайле — проверить палитру/позицию/прозрачность на MAME. +- Артефакт-цель: Kid стоит на уступе комнаты 1 как в `1.1-2.png`. + +**Фаза K1 — движок анимации (play_seq + frame_table)** +- Портировать `play_seq()` (интерпретатор) + `frame_table_kid` + минимальный + `seqtbl` (stand/run/turn). +- Прогнать несколько последовательностей вручную (stand→run→stop) — + проверить, что кадры и смещения совпадают с оригиналом (сверять с + SDLPoP/скриншотами, тайминг 50 Гц). + +**Фаза K2 — управление на месте + ходьба (твои а, б)** +- `control_kid` подмножество: stand (2), run (1/84/13), turn (5/6), + standing_jump (3), crouch (50/49), safe_step (29-44 — аккуратный шаг). +- Held-state через `kbd_raw` (готово). + +**Фаза K3 — коллизия с картой (твой п.3)** +- `check_on_floor`/`start_fall` — падение в ямы (тип тайла под ногами из + `fg[]`); `in_wall`/стоп у стены; `fell_out` (край экрана — пока без + перехода комнат). +- Падения/приземления (seq 7/17/19/20) + `fall_accel/fall_speed`. + +**Фаза K4 — прыжки и повисание (твои а-прыжок, в)** +- run_jump (4), jump_up (28/14), grab (8/16/24), climb_up (10)/down (68), + hang (25/6), release (11/23). Это самый «PoP-овый» кусок — сверять + дистанции/тайминг с оригиналом (не на глаз). + +**Фаза K5 — прочее (твой г)** +- drink (78), level_door (70), crouch_hop (79), spiked/loose/chomped + (ловушки, если тайлы есть в комнате), death (71). + +Бой (меч, seq 55-92, стражники — seg005) — ВНЕ этого плана (отдельная фаза +полного приложения, PORT_PLAN §7 Фаза 3). + +--- + +## 5. Риски/решения ДО кода (правило defer_unexplained_quirks) + +1. **Per-frame offset** (§2.1) — решить до K0 (влияет на формат данных). + Рекомендация: xco/yco в frame_table, прямой blit. +2. **seqtbl rebasing** — JMP-адреса абсолютные (SEQTBL_BASE 0x196E); при + порте пересчитать в оффсеты своего массива. Проверить на 1-2 seq. +3. **Тайминг** — оригинал (DOS) фиксированный тик; наш 50 Гц. Если + логическая частота кадров иная — пересчёт dx/dy (PORT_PLAN §8.4). + Сверять дистанцию бега/прыжка с эталоном. +4. **Число реально нужных кадров/последовательностей** для Фазы 1 — + разметить (вырезать бой/катсцены/гардов), чтобы не тянуть все 219 + спрайта и весь seqtbl. +5. **Копирайт графики Kid** — подтверждено решение пользователя (§вводная). + +--- + +## 6. Что переиспользуем (готово) + +- Фон комнаты (`pop_bg.c`) — Kid рисуется ПОВЕРХ (сейчас — прямым blit; + heal против фона — когда/если понадобится через RAM-копию, фон её уже + заполняет, `GFX_BANK_TRANSPARENT`). +- `kbd_raw` held-state (§2 PORT_PLAN) — готов и проверен. +- Пакер спрайтов/палитра (`pop_pack_bg.py`) — шаблон для `pop_pack_kid.py`. +- Разобранная карта комнаты (`fg[]/bg[]`, level.h) — для коллизий. +- `gfx_blit`/`gfx_w0_map` из W0-атласа — проверенный путь (bgtest/roomtest). diff --git a/applications/PoP/docs/MSDOS_RESOURCE_FORMAT.md b/applications/PoP/docs/MSDOS_RESOURCE_FORMAT.md new file mode 100644 index 0000000..5034e56 --- /dev/null +++ b/applications/PoP/docs/MSDOS_RESOURCE_FORMAT.md @@ -0,0 +1,291 @@ +# Формат ресурсов Prince of Persia (MS-DOS, каталог `MSDOS/`) + +Документ описывает бинарный формат `*.DAT`-файлов ресурсов DOS-версии PoP. +Исходников для этой версии нет, поэтому всё, что ниже — результат +структурного (эмпирического) анализа реальных файлов из `MSDOS/`, а не чтения +кода. Уровень уверенности указан для каждого раздела. Все находки проверены +скриптами (Python), которые разбирают файл и валидируют согласованность +(например: смещение+размер последней записи таблицы точно совпадает с +началом самой таблицы — то есть данные и каталог стыкуются без дыр). + +Для справки при последующей реализации (порт на ZX Sprinter) стоит держать в +уме два внешних проекта: + +- **SDLPoP** (github.com/NagyD/SDLPoP, GPLv3) — open-source реализация + DOS-версии на основе дизассемблирования оригинального `PRINCE.EXE`. Содержит + рабочий код чтения `.DAT`-файлов и полный кодек изображений/уровней. Точные + структуры (`dat_table_type` и т.п.), процитированные ниже, получены через + автоматический пересказ содержимого файла третьей стороной, а не через + прямое чтение исходника — поэтому такие детали помечены как "требует сверки + при реализации", в отличие от эмпирически подтверждённых байтовых оффсетов. +- **Princed Resources / PR** (github.com/NagyD/PR, princed.org, GPLv2) — это + профильный инструмент именно для распаковки/запаковки `.DAT`-ресурсов PoP + (версии DAT 1 и 2), сделанный тем же автором. В его документации + (`doc/Dataformats.md`) официально описаны экспортные форматы ресурсов — + это подтверждает и уточняет часть находок ниже (см. §3–4), и является более + надёжным источником, чем самостоятельная догадка по байтам. + +**Важная находка:** репозиторий SDLPoP в папке `data/` содержит не только +код движка, но и **реальные ресурсы игры** — как сырые `.DAT`-контейнеры, так +и уже распакованные поштучно файлы (PNG-кадры спрайтов, `.pal`-палитры, +`.bin`-дампы уровней), см. §7. Это готовый источник ассетов и одновременно +независимая проверка формата, описанного в этом документе. + +--- + +## 1. Общий контейнер `.DAT` (уверенность: высокая, подтверждено на 28 файлах) + +Каждый `*.DAT`-файл (кроме служебных `config.dat`/`setup.dat`, см. §5) — это +простой архив-контейнер: блок данных + оглавление (каталог ресурсов) в конце +файла. + +### 1.1 Заголовок файла (6 байт, смещение 0x00) + +| Смещение | Размер | Поле | Значение | +|----------|--------|--------------|----------| +| 0x00 | 4 | `tableOffset`| LE u32. Абсолютное смещение в файле, с которого начинается таблица оглавления. Совпадает с "концом данных". | +| 0x04 | 2 | `tableSize` | LE u16. Размер таблицы оглавления в байтах. | + +Инвариант, подтверждённый на всех 28 `.dat`-файлах в каталоге: + +``` +tableOffset + tableSize == размер файла (без исключений) +``` + +Данные ресурсов идут сразу после заголовка, начиная с байта 0x06, и +заканчиваются на `tableOffset`. + +### 1.2 Таблица оглавления (по смещению `tableOffset`, длиной `tableSize`) + +Таблица — плоский массив записей по 8 байт. Количество записей: +`tableSize / 8` (округление вниз; в файле почти всегда остаётся 2 "лишних" +байта в хвосте таблицы — назначение не установлено, вероятно, служебное поле +инструмента-упаковщика или паддинг; на итоговый разбор не влияет). + +Запись (8 байт): + +| Смещение в записи | Размер | Поле | Описание | +|---|---|---|---| +| 0 | 2 | `size` | LE u16 — размер данных ресурса в байтах | +| 2 | 2 | `id` | LE u16 — идентификатор ресурса | +| 4 | 2 | `offset` | LE u16 — **абсолютное** смещение данных ресурса в файле (не относительное!) | +| 6 | 2 | `reserved` | во всех проверенных записях (сотни штук) всегда `0x0000` | + +Проверено на `levels.dat`: 16 записей, `id`=2000..2015, и `offset[i] + size[i] +== offset[i+1]` для всех соседних записей, а последняя запись заканчивается +ровно на `tableOffset` — то есть данные абсолютно плотно упакованы, без +пробелов, для этого файла. В других файлах (например `guard.dat`) между +записями изредка есть небольшие зазоры в несколько байт (вероятно, выравнивание +или "мёртвые" байты от инструмента-компоновщика) — не является нарушением +формата. + +### 1.3 Диапазоны `id` по типам файлов (собрано эмпирически) + +Похоже, что числовые ID образуют условные "пространства имён" по типу +контента — вероятно, глобальные константы в оригинальном коде: + +| Файл(ы) | Диапазон `id` | Кол-во записей | Предполагаемое содержимое | +|---|---|---|---| +| `levels.dat` | 2000–2015 | 16 | id=2000 — служебный блок (16 байт, см. §3); id=2001..2015 — 15 уровней | +| `guard.dat`, `fat.dat`, `skel.dat`, `shadow.dat` | 750–784 (варьируется) | ~30–35 | id=751(750) — служебный блок; остальные — кадры анимации спрайта | +| `vizier.dat` | аналогично guard | — | кадры анимации визиря | +| `kid.dat` | ~400+ | 220 | кадры анимации игрока (намного больше — герой умеет гораздо больше действий) | +| `guard1.dat`, `guard2.dat` | 750 (1 запись) | 1 | вероятно, дополнительные/альтернативные кадры/варианты | +| `title.dat` | 40–55 | 12 | картинки титульного экрана/логотипов | +| `cpalace.dat`,`epalace.dat`,`vpalace.dat`,`cdungeon.dat`,`edungeon.dat`,`vdungeon.dat` | 200–1343 | 205–238 | фоновые тайлы дворца/подземелья, отдельно для CGA(`c*`)/EGA(`e*`)/VGA(`v*`) | +| `pv.dat` | 800–981 | 103 | доп. графика (возможно, "Prince/Vizier" катсцены) | +| `digisnd1/2/3.dat` | 10000+ | 20–44 | оцифрованный звук (Covox/Disney Sound Source) | +| `midisnd1/2.dat` | 10024+ / аналог | 16 | General MIDI музыка | +| `mt32snd1/2.dat` | 10000+ | 24/7 | музыка для Roland MT-32 | +| `ibm_snd1/2.dat` | 10000+ | 44 | музыка/эффекты через PC-спикер | +| `prince.dat` | — (1 крупный ресурс) | — | MIDI-тема (вероятно, финальная тема "Принц"/титры — см. текстовые события "The Princess awaits") | + +Во всех файлах первая (наименьшая по `id`) запись — маленький "служебный" +ресурс (6–44 байта), стоящий перед основным контентом. Скорее всего это +локальная мини-таблица/палитра/список ссылок для данного набора ресурсов — +по аналогии с тем, что у уровней id=2000 отдельно от самих уровней (см. §3). + +--- + +## 2. Формат уровня (`levels.dat`, id=2001..2015) — уверенность: высокая + +Каждая запись уровня имеет размер **2305 байт** и по данным полностью +совпадает по объёму с уровнями из Apple II версии (`01 POP Source/Levels/LEVELn` +— ровно **2304 байта** каждый, см. `docs/APPLEII_RESOURCE_FORMAT.md`). + +Вывод: формат карты уровня в DOS-версии, судя по всему, **унаследован +практически без изменений от оригинального Apple II формата** (Джордан +Мехнер писал игру на 6502 и данные уровней переносились как есть), с добавлением +одного лишнего байта в DOS-упаковке (2304+1=2305 — вероятно, контрольный байт/ +маркер конца, добавленный DOS-упаковщиком ресурсов, а не часть игровых данных). + +**Практическое следствие:** байтовая структура самого уровня (тайлы 3×10 на +экран, 24 экрана, таблицы стражников, дверей и т.д.) должна документироваться +один раз — по исходникам Apple II (см. соответствующий раздел), и напрямую +применяться к DOS `levels.dat`, отбросив 1 лишний байт в конце каждой записи. +Байтовые значения тайлов в дампе (в основном 0x00–0x39) визуально согласуются +с диапазоном небольших целых кодов тайлов, что для формата карты и ожидается. + +Первая запись, id=2000, размер 16 байт — не уровень, а отдельный маленький +блок (возможно: количество уровней, начальный уровень, версия формата, +стартовые координаты игрока/охраны по умолчанию). Точное назначение не +установлено — требует сопоставления с диз­ассемблированным кодом загрузчика +уровней (в SDLPoP это, по всем признакам, отдельная процедура чтения +`level` ресурса). + +**Сверка с независимой распаковкой SDLPoP (`data/LEVELS/`):** там лежат файлы +`res2000.bin`…`res2015.bin` (16 штук — количество совпадает). Байты +`res2001.bin` содержательно совпадают с тайловыми данными нашей записи +id=2001 (та же последовательность значений тайлов) — это подтверждает, что +нумерация id верна. Но есть нестыковка по размеру: у SDLPoP `res2000.bin` — +**2305 байт** (как и все остальные), тогда как в нашем локальном +`levels.dat` запись id=2000 — всего **16 байт**. Скорее всего, это разные +релизы/сборки игры (см. §7 — размеры некоторых `.dat` у SDLPoP и у нас уже +отличались), и в версии SDLPoP маленький служебный блок либо отсутствует, +либо пронумерован иначе. Это не меняет сам формат контейнера, но означает, +что **точную семантику 16-байтного блока id=2000 в нашей копии игры пока +нельзя проверить через данные SDLPoP** — открытый вопрос. + +--- + +### 2.1 Кросс-подтверждение по исходникам Apple II + +Фоновый анализ исходников Apple II (см. `docs/APPLEII_RESOURCE_FORMAT.md`) +подтверждает и объясняет структуру уровня напрямую по коду. Уровень на Apple +II — дамп структуры `blueprnt` (`EQ.S`): `BLUETYPE`(720Б, 24 экрана×30 тайлов) ++ `BLUESPEC`(720Б) + `LINKLOC`(256Б) + `LINKMAP`(256Б) + `MAP`(96Б, граф +соседних экранов) + `INFO`(256Б, метаданные/старт Кида/стражников) = ровно +2304 байта. Учитывая, что DOS-запись уровня — это ровно 2304+1 байт с +байтовыми значениями тайлов, укладывающимися в диапазон 0–29 (id тайла) плюс +служебные биты (аналогично `idmask=%00011111`, `reqmask=%00100000` из +`EQ.S:484-486`), можно с высокой уверенностью считать, что **DOS-версия +использует ту же самую раскладку `blueprnt`**, лишь с добавлением одного +байта (вероятно, контрольной суммы) в конце DOS-упаковки. Это снимает +необходимость отдельно реверсить формат уровня для DOS — таблица тайлов, +enum id (0=space...29=archtop4), формат `LINKLOC`/`LINKMAP` и `INFO` из +Apple II документа применимы напрямую. + +## 3. Графика (спрайты и фоновые тайлы) — уверенность: средняя/низкая + +Файлы `kid.dat`, `guard.dat`, `fat.dat`, `shadow.dat`, `skel.dat`, +`vizier.dat`, `title.dat`, `c/e/v-palace.dat`, `c/e/v-dungeon.dat`, `pv.dat` +хранят по контейнерному формату (§1) множество мелких чанков (десятки—сотни +байт каждый). + +Что подтверждено: +- Наборы `shadow.dat`/`kid.dat` и `fat.dat`/`vizier.dat` содержат **побайтово + идентичные фрагменты** данных в начале файла — это ожидаемо: "Тень" (Shadow) + визуально копирует анимацию Кида, а "Толстый страж" (Fat guard, пасхалка) + переиспользует модель Визиря. Подтверждает, что персонажи одного "типа + тела" используют общий набор геометрии/анимации. +- Отдельные чанки *не* имеют очевидного унифицированного заголовка + (высота/ширина/палитра) фиксированного размера — попытка интерпретировать + первые байты чанка как `{height:u16, width:u16, flags:u16}` не подтвердилась + на реальных данных (получаются нереалистичные размеры для маленьких чанков). + Вероятно, как и в Apple II версии (см. `FRAMEDEF.S`/`SEQTABLE.S`), геометрия + кадра (ширина, высота, точка привязки) хранится **отдельно от самих + пиксельных данных** — в таблицах внутри `PRINCE.EXE`, а не в `.DAT`-чанке. + Сам чанк, вероятно, содержит только упакованные пиксельные данные + (RLE/дельта-упаковка, по аналогии с `UNPACK.S` в Apple II исходниках). +- Точный алгоритм упаковки пикселей **не восстановлен** в рамках этого + анализа по сырым байтам — байт-в-байт разбор распаковщика без + дизассемблирования `PRINCE.EXE` надёжно не сделать. **Но для практических + целей это не требуется**: см. §7 — в SDLPoP уже есть тот же самый набор + изображений в готовом, распакованном виде (PNG), которым можно пользоваться + напрямую как источником ассетов, не реализуя свой декодер `.DAT`-пикселей. + Писать собственный декодер имеет смысл только если понадобится читать + оригинальные `.DAT` "на лету" (например, для точной сверки контента именно + нашей копии игры) — тогда ориентир — исходник SDLPoP (`src/seg009.c`). + +--- + +## 4. Звук — уверенность: высокая (по структуре), низкая (по деталям кодека) + +Обнаружено 4 параллельных набора звуковых ресурсов под разные звуковые +устройства DOS-эпохи — типично для игр начала 1990-х с "звуковым меню": + +| Файл | Устройство | Формат чанка | +|---|---|---| +| `midisnd1.dat`, `midisnd2.dat` | General MIDI / MPU-401 | каждый чанк = 2-байтовый LE-префикс длины + встроенный Standard MIDI File (`MThd`...`MTrk`...) | +| `mt32snd1.dat`, `mt32snd2.dat` | Roland MT-32/CM-32L | тот же формат: префикс длины + `MThd`/`MTrk`, с MT-32-специфичными SysEx (видны строки `MT-32.mff`, текстовые мета-события вроде `"The Princess awaits"`) | +| `prince.dat` | (аналогично MIDI) | отдельный крупный музыкальный ресурс, тот же MIDI-контейнер — вероятно, финальная тема | +| `digisnd1/2/3.dat` | Covox / Disney Sound Source / Sound Blaster (оцифрованный звук) | чанк начинается с нескольких служебных байт, среди которых слово `0x2AF8` = 11000 — похоже на частоту дискретизации 11 кГц; далее — сырые 8-битные PCM-сэмплы (значения кластеризуются вокруг ~0x7A–0x90, типично для беззнакового 8-бит аудио, смещённого к середине шкалы) | +| `ibm_snd1.dat`, `ibm_snd2.dat` | PC Speaker | чанк — последовательность троек байт похожих на (длительность, делитель_частоты) — простой формат "бипера", отличный от MIDI | + +Подтверждено разбором первых чанков в каждом файле (см. байтовые дампы, +проверялись скриптом). Точная семантика полей внутри `digisnd`/`ibm_snd` +(разрядность, порядок байт служебного заголовка) не выведена до конца — при +реализации порта достаточно распознавания по типу файла и (для MIDI-семейства) +можно напрямую воспроизводить встроенный Standard MIDI File, пропустив +2-байтовый префикс длины. + +--- + +## 5. Готовые распакованные ассеты в SDLPoP (`data/`) — практический источник для порта + +Репозиторий github.com/NagyD/SDLPoP содержит папку `data/`, где, помимо +самих `.DAT`-контейнеров, каждый ресурс **продублирован в виде отдельно +распакованного файла**, названного по его `id` из таблицы оглавления (§1.2). +Проверено через GitHub API (`api.github.com/repos/NagyD/SDLPoP/contents/...`): + +| Подпапка/файл в `data/` | Содержимое | Соответствие нашему разбору | +|---|---|---| +| `GUARD.DAT`, `GUARD1.DAT`, `GUARD2.DAT` | сырые `.DAT` | размер **побайтово совпадает** с нашими локальными `guard.dat`/`guard1.dat`/`guard2.dat` (6950 / 117 / 117 байт) | +| `DIGISND1.DAT`, `MIDISND2.DAT` и др. | сырые `.DAT` | размер **не совпадает** с нашими локальными файлами (48545 vs 50101, 18408 vs 18958) — другой релиз/сборка игры | +| `GUARD/res751.png` … `res784.png` | готовые PNG, по одному на кадр анимации, имя = `res.png` | id-диапазон (751-784) точно совпадает с нашим разбором `guard.dat` | +| `VPALACE/res200.pal`, `res201.png`, `res202.png`, … | палитра (JASC `.pal`) + PNG-кадры фонов дворца, **VGA-вариант (256 цветов)** | id-диапазон (200+) совпадает с `vpalace.dat` | +| `LEVELS/res2000.bin` … `res2015.bin` | сырые дампы уровней по 2304-2305 байт | id совпадает с `levels.dat`; содержимое `res2001.bin` **сверено побайтово** с нашим id=2001 — тайловые данные совпадают | +| `KID/`, `PRINCE/`, `SHADOW/`, `SKEL/`, `VIZIER/`, `FAT/`, `TITLE/`, `VDUNGEON/`, `PV/`, `IBM_SND1/`, `IBM_SND2/`, `font/`, `music/` | аналогичные наборы для остальных ресурсов | не проверялись по отдельности, но структура (папка на каждый `.dat`, файлы `res.ext`) наблюдается одинаково | + +**Вывод:** это данные из немного **другого релиза DOS-версии**, чем те, что +лежат у нас в `MSDOS/` (см. расхождение в размере `digisnd`/`midisnd`), но +формат контейнера и нумерация `id` — те же самые. Практически это значит: + +1. Для получения играбельных PNG-спрайтов и VGA-фонов **не нужно + реализовывать декодер сжатия пикселей** — можно взять готовые файлы + `data/<ИМЯ>/res.png` напрямую как исходный материал для конвертации + под видеорежим ZX Sprinter (в т.ч. `VPALACE`/`VDUNGEON` — уже + 256-цветный VGA-арт, что прямо отвечает на вопрос про полноцветность). +2. Если в проекте важно использовать именно ту версию контента, что в наших + `MSDOS/*.dat` (а не версию из SDLPoP) — распаковку своих файлов всё же + придётся делать (кодек пикселей по-прежнему не восстановлен для сырых + `.DAT`, см. §3), либо принять решение работать с версией SDLPoP как + мастер-источником ассетов вместо своей. + +--- + +## 6. Служебные не-ресурсные файлы + +- `config.dat`, `setup.dat` — 28 байт, не являются ресурсными контейнерами + (не проходят проверку §1.1 — "размер" получается больше самого файла). + Скорее всего простые бинарные структуры настроек (звук/видеорежим, + выбранный на этапе `SETUP.EXE`/`INSTALL.EXE`), не связаны с игровым + контентом. +- `desktopd.cfg`, `setup.cfg` — текстовые/бинарные конфиги DOS-инсталлятора, + вне скоупа игровых ресурсов. +- `PRINCE.EXE` / `PRINCE.REM` — почти идентичны (отличие в единичных байтах + в районе смещения ~0x4ED0), похоже на кряк/патч одного байта проверки — + не относится к формату ресурсов. +- `old-games.nfo` — ASCII-арт NFO релиз-группы (old-games.ru), не игровые + данные. + +--- + +## 7. Итоговая таблица уверенности + +| Раздел | Уверенность | Как подтверждено | +|---|---|---| +| Контейнер `.DAT` (заголовок + таблица) | Высокая | Проверено скриптом на всех 28 файлах, инвариант offset+size выполняется без исключений; независимо подтверждено именованием `res.*` в SDLPoP `data/` | +| ID-пространства ресурсов | Средняя-высокая | Наблюдение по диапазонам + сверка с `res` именами файлов SDLPoP и побайтовым содержимым `res2001.bin` | +| Формат уровня = формату Apple II | Высокая (по размеру и содержимому), служебный блок id=2000 — открытый вопрос | Совпадение размера (2304 vs 2305), тайловые байты сходятся с `res2001.bin` из SDLPoP | +| Формат изображений/спрайтов (сырой `.DAT`) | Низкая-средняя | Контейнер подтверждён, кодек пикселей — нет; но практически закрыто наличием готовых PNG в SDLPoP `data/` (§5) | +| Формат звука (тип контейнера) | Высокая для MIDI-семейств, средняя для digisnd/ibm_snd | Явные MIDI-сигнатуры `MThd`/`MTrk` видны в байтах | + +**Рекомендация для дальнейшей работы:** для получения арт-ассетов (спрайты, +фоны, палитры) — использовать готовые распакованные файлы из +`github.com/NagyD/SDLPoP/tree/master/data` (§5), это быстрее и надёжнее +самостоятельной реализации декодера. Декодер сырого `.DAT`-формата +изображений и точную семантику служебных полей `digisnd`/`ibm_snd` +(§3, §4) стоит восстанавливать только если понадобится читать именно нашу +локальную копию `MSDOS/*.dat` "как есть" — тогда ориентир прежний: исходник +SDLPoP (`src/seg009.c`, `src/data.c`/`data.h`). diff --git a/applications/PoP/docs/POP-DAT-FormatSpecifications.pdf b/applications/PoP/docs/POP-DAT-FormatSpecifications.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0d5c21968bec960e887a93174ee264584eb6c3b5 GIT binary patch literal 222037 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/docs/PORT_PLAN.md b/applications/PoP/docs/PORT_PLAN.md new file mode 100644 index 0000000..6249770 --- /dev/null +++ b/applications/PoP/docs/PORT_PLAN.md @@ -0,0 +1,509 @@ +# Prince of Persia на ZX Sprinter — план порта + +Статус: план (2026-07-15). §2 (A: kbd_mod_state / B: kbd_raw) — +РЕАЛИЗОВАНО и частично проверено в MAME (tests/kbdraw, 2026-07-15, +подробности в §2.2); PoC (§5) и остальные фазы — не начаты. Опирается на +`APPLEII_RESOURCE_FORMAT.md` / `MSDOS_RESOURCE_FORMAT.md` / `README.md` в +этой папке, на текущий sprinter-cc/libc/libbgi (см. §1) и на локальные копии +`applications/PoP/SDLPoP` (github.com/NagyD/SDLPoP, GPLv3) и +`applications/PoP/PR` (github.com/NagyD/PR, GPLv2) — используются только как +справочник по структурам/константам оригинального движка и как источник +готовых распакованных ассетов (`SDLPoP/data/`), не как код для копирования. + +--- + +## 1. Что уже есть в sprinter-cc и библиотеках (используем как есть) + +Собрано из `docs/TODO.md`, `docs/libc-reference.md`, `docs/sprite-api-design.md`, +`libbgi/include/{gfx.h,sprite.h,graphics.h}`, `examples/rpgwalk`. + +- **Графика 320×256×256** (`GFX_MODE_320x256x256`, режим 0x81) — разрешение и + глубина цвета совпадают почти впрямую с VGA-ассетами оригинала + (`SDLPoP/data/VPALACE`, `VDUNGEON` — уже 256-цветные PNG). Не нужно ужимать + в EGA/CGA палитру. +- **BGI-слой** (`graphics.h`) — примитивы, палитра, текст, `getimage/putimage` + — Фазы 1-2d готовы и проверены в MAME. +- **Спрайтовый движок v2** (`sprite.h`, ветка `sprite-engine-v2`) — ровно то, + что нужно персонажам PoP: + - retained-модель (`sprite_update`/`sprite_flip`, double-buffer, dirty-биты, + heal+blit за один проход); + - кадровая анимация по ленте (`sprite_anim`, LOOP/PINGPONG/ONCE, + горизонтальная/вертикальная лента) и tween-перемещение + (`sprite_moveto`, DDA без knowledge-heavy арифметики); + - Y-сортировка слоями (`gfx_sprite_ysort`, `layer`) — то, что нужно для + «Кид перед/за стражником» без ручной пересортировки; + - атласы в EMM-страницах (`atlas_t`/`atlas_load`) — на восьмерых + персонажей в `rpgwalk` уже работает: прямой прецедент для Кида/стражника; + - ограничение кадра ≤ 64×64 — с запасом (см. §3: кадры Кида в оригинале + ~12-30 × 39-42 px). +- **Frame pacing** (`gfx_set_fps_div`) + цепочка кадровых IRQ — стабильный + логический тик независимо от рендер-нагрузки экрана (проверено MAME). +- **EMM-бюджет**: ~3.3 МБ свободно на старте (`memory/sprinter_emm_budget`) — + с большим запасом на все спрайт-атласы и предрендеренные фоны комнат (см. + §4) даже без выгрузки неиспользуемых уровней. +- **Файловый ввод-вывод** (FILE* v2, `fopen/fread/...`) — для загрузки + уровней/атласов/палитр с дискеты, по образцу `rpgwalk` (`atlas_load`, + `gfx_pal_fload`). +- **Клавиатура (событийная)** — `kbhit/getch/getkey` (ASCII + `KEY_*` скан-код + для стрелок), см. §2 — это НЕ то, что нужно для управления Кидом один в + один (см. ниже). +- **Звук** — `cbl.h` (потоковый CBL/COVOX, callback-модель, verified MAME) — + подходит для оцифрованных эффектов (`digisnd*.dat` — PC-звук + ~11 кГц 8-бит, см. `MSDOS_RESOURCE_FORMAT.md` §4). + +Вывод: **движок отрисовки и анимации почти не требует нового кода** — +самый близкий по духу пример (`rpgwalk`: атласы, анимация, tween, дабл-буфер, +FPS-делитель) переносится на PoP почти без изменений архитектуры. + +--- + +## 2. Единственный принципиальный пробел: удержание клавиш + +**Спайк проведён (2026-07-15), вопрос закрыт артефактами — не догадкой.** + +`getch`/`getkey` — это события ESTEX WAITKEY/SCANKEY (по нажатию), без чёткой +информации о СОСТОЯНИИ (что зажато прямо сейчас, несколько клавиш +одновременно). Prince of Persia на управлении требует именно состояния: +держать направление (бег) + одновременно нажать вверх (прыжок вперёд), держать +Shift (модификатор) + направление и т.д. + +### 2.1 Находки + +1. **`docs/converted/ProgrammerManual.txt` документирует функцию, которую мы + раньше пропустили: `CTRLKEY` (ESTEX $33h)** — «Получить состояние + клавиатуры». Дословно: «данные берутся не из буфера клавиатуры (как в + остальных функциях), а непосредственно из результатов ПОСЛЕДНЕГО + сканирования» — то есть это НАСТОЯЩЕЕ live-state, не событие. Но + покрывает только модификаторы: Left/Right Shift, Ctrl, Alt, + Rus/Lat, Num/Scroll/Caps Lock, Insert (не обычные клавиши вроде стрелок). + Готовое решение для «держать Shift = бежать» — тривиальная обёртка, + без архитектурных рисков. +2. Для ОБЫЧНЫХ клавиш (стрелки, буквы) такого live-state нет нигде в ESTEX — + `WAITKEY`/`SCANKEY`/`TESTKEY` ($30/$31/$37h) — все три отдают ОДИНАКОВЫЙ + формат «очередное нажатие», без release. `TESTKEY` не удаляет событие из + буфера (полезно для «подсмотреть, не потребляя»), но это тоже разовое + нажатие, не состояние. +3. Автоповтор клавиатуры (typematic) не годится как замена held-state: + `MAME_MCP_GUIDE.md` фиксирует задержку до первого повтора ~1 секунда + (типично для PS/2) — на порядок медленнее кадра (20 мс), не подходит для + платформера. +4. **Решающий артефакт — `libc/irq/_irq_tramp.c` (сам трамплин прерывания, + не гипотеза):** вектор 0xFF общий для кадра/клавиатуры/CBL. Ветка + клавиатуры (бит 0 порта 0x19 = SIO-A RR0 «байт принят») делает буквально + `jp 0x0038` (прямиком в DSS) **до какого-либо чтения порта данных 0x18 И + до нашей кадровой цепочки (`_irq_chain`)** — наш `irq_chain_add` + вообще не видит клавиатурные прерывания, они физически не доходят до + цепочки (см. `tr_notkbd`/`tr_frame` разбор в файле). Значит текущая + инфраструктура (тот же механизм, что несёт FPS-делитель) НЕ дает + зацепки для клавиатуры без правки самого трамплина. +5. Регистр данных SIO (порт 0x18) — аппаратный приёмный буфer, чтение + деструктивно (дёргает байт из очереди); кто прочитал первым, тот и + владеет байтом. Значит «подглядеть, не мешая DSS» технически + невозможно — необходимо либо совсем не трогать этот путь (статус-кво), + либо взять его СЕБЕ полностью на время геймплея. + +### 2.2 Рекомендация (конкретная, не три равнозначных варианта) + +**A. Тривиально, почти без риска — обернуть `CTRLKEY` ($33h)** отдельной +функцией (например `kbd_mod_state()` в ``) — даёт настоящий +held-state для Shift/Ctrl/Alt. Можно делать хоть сейчас, не архитектурное +решение. + +**B. Для обычных клавиш (стрелки и т.д.) — по прецеденту CBL.** В +`_irq_tramp.c` уже есть пример «приватного» пути на том же векторе 0xFF, +который сознательно НЕ чейнится к DSS (CBL: бит 7 порта 0xFE, свой +хук `_irq_cbl_hook`, полный сейв, свой `reti`). Предлагаемый новый +компонент `` — симметричный: ветка по биту 0 порта 0x19 читает +порт 0x18 САМА (декодирует PS/2 make/break, `0xF0`-префикс — протокол +уже задокументирован в `docs/samples/sprinterKeybLib.asm`), ведёт битовую +карту «клавиша N зажата», и НЕ прыгает в DSS, пока путь активен — +жизненный цикл `kbd_raw_open()`/`kbd_raw_close()` один в один как у +`cbl_open`/`cbl_close`. + + **Важное следствие (сообщить пользователю явно, не прятать):** пока + `kbd_raw_open()` активен, DSS вообще не получает клавиатурных байт — + `kbhit/getch/getkey/CTRLKEY` заведомо не будут работать, ESC для выхода + в DSS-смысле тоже (нужно проверять raw-битовую карту самим). Это + нормально для активной фазы геймплея (у самой игры и так свой цикл + ввода), но означает: экраны/паузы, которым нужен ESTEX-ввод (например, + диалог сохранения через `fopen`, если тот когда-либо потребует ввода + с консоли), должны на это время `kbd_raw_close()`. + +**Не рекомендую вариант «таймаут-эвристика поверх SCANKEY»** — after +находки о typematic-задержке ~1с он не даёт нужной задержки для игры; +рекомендация A+B закрывает потребность без компромиссов. + +**Статус: A+B РЕАЛИЗОВАНЫ (2026-07-15, по согласованию с пользователем).** + +- A: `kbd_mod_state()` — `libc/conio/kbd_mod_state.c` + `` + (`KBD_MOD_*`). +- B: `` (`libc/kbd/`) + правка `libc/irq/_irq_tramp.c` + (новая ветка на бите 0 порта 0x19: raw активен → сама читает порт + 0x18, декодирует make/break, НЕ чейнится к DSS; raw выключен — + поведение как раньше, без изменений). Трамплин вырос со 150 до + 220 байт — `_IRQ_TRAMP_BUF_SIZE` поднят с 224 до 288 (было 4 байта + запаса, стало ≥60). `make -C libc` (fast+safe) — чисто. +- **Верификация в MAME** (`tests/kbdraw`, полный цикл open→держать→ + отпустить→ESC-выход→close): `KBD_LEFT` (0x16B, расширенный код + E0 6B) — down на нажатие, up на отпускание, ТОЧНО совпало с + константой из ``; `KBD_ESC` (0x76, обычный код) — + корректно закрыл raw-канал и вернул DSS (`IM` вернулся в 1). + Побочно найдено и задокументировано в `docs/libc-reference.md` + (``): MAME-мостовой `press_key` дёргает ОБЕ клавиатуры + (PC+ZX) одновременно и через ZX-путь давал паразitный незатухающий + бит — не относится к реальному сценарию (пользователь подтвердил: + матрица на Sprinter давно не используется), но означает, что + будущие MAME-тесты этой функции надо гонять через `:kbd:ms_naturl:*` + напрямую, не через удобный `press_key`. UP/DOWN/RIGHT/SPACE/SHIFT + константы — НЕ перепроверены поштучно (тот же общеизвестный + стандарт PS/2 Set 2, что и подтверждённые LEFT/ESC — проверить перед + использованием в PoC, если управление будет ощущаться неверно). +- На реальном железе — не проверено (только MAME). + +--- + +## 3. Формат данных — что напрямую переносим из docs/*RESOURCE_FORMAT.md + +- **Уровень** (`BLUETYPE`/`BLUESPEC`/`LINKLOC`/`LINKMAP`/`MAP`/`INFO`, + 2304 байта, 24 экрана × 30 тайлов) — читаем один раз при загрузке уровня + в свою C-структуру (прямой memcpy дампа файла, поля читаем по офсетам + из `APPLEII_RESOURCE_FORMAT.md` §1). DOS `levels.dat` даёт то же самое + +1 байт в конце записи — отбросить. +- **Графика фона/спрайтов** — кодек сжатия DOS `.DAT` не восстановлен и + восстанавливать не будем: используем уже распакованные PNG из + `SDLPoP/data/{KID,GUARD,VPALACE,VDUNGEON,...}` (см. + `MSDOS_RESOURCE_FORMAT.md` §5, §7 — тот же контейнерный формат/нумерация, + просто другой релиз сборки данных). Измерено локально: кадры Кида — + ~12×39 .. 30×42 px (P-режим, 4-бит палитра), фоновые тайлы подземелья — + 32 px по ширине (10 колонок × 32 = 320 — сходится с шириной экрана), высота + тайла 20/60/62 px (неоднородные ряды пола/потолка/арок) — укладывается в + лимит спрайтового движка (кадр ≤ 64×64) без всяких изменений движка. +- **Звук** — `digisnd*.dat` (PC-звук 8-бит ~11 кГц) — конвертация в сырой + PCM и проигрывание через `cbl_open`/`cbl_push_*`; `ibm_snd*.dat` (PC-спикер + тройки «частота×2Б + длительность») — тривиальный бипер, не требует CBL. + MIDI-семейство (`midisnd`, `mt32snd`, `prince.dat`) — вне скоупа (нет + синтеза MIDI на платформе; не блокирует геймплей). + +--- + +## 4. Стратегия фона — ПЕРЕСМОТРЕНО 2026-07-15: тайловый рендерер В РАНТАЙМЕ + +**Было** (первая версия плана): офлайн-склейка каждой комнаты в готовую +растровую картинку 320×~193, `gfx_blit` целиком при входе — обоснование +было «ноль нового кода в libbgi». Пересчёт по факту наличия структурных +данных комнаты (§3.4 формата, `level.h`) показал: 16 уровней × 24 комнаты × +~60-80 КБ/картинка — это **30+ МБ**, при том что одна и та же картинка +тайла (пол/стена/колонна) переиспользуется в десятках комнат — офлайн- +склейка печёт её заново в каждую копию. + +**Стало**: тайлы — переиспользуемый набор картинок ОДИН на визуальный +стиль (не на комнату), структурные данные комнаты — компактные (60 байт: +30×foretable+30×backtable, все 16 уровней ≈ 37 КБ, см. `level.h`). +`room_draw()` (applications/PoP/poc/room.c) проходит 30 тайлов комнаты и +зовёт `gfx_blit` для каждого, читая картинку из таблицы по типу тайла +(`tile_images[TILE_TYPE]`). Итог: десятки-сотни КБ переиспользуемых +тайл-картинок на весь визуальный стиль + ~37 КБ структуры уровней — +вместо 30+ МБ. + +**Почему это НЕ бьёт по бюджету кадра**: `room_draw()` зовётся ОДИН РАЗ +при входе в комнату (смена комнаты — не every-frame событие), не в +игровом цикле — это не `sprite_update`, тактовый бюджет кадра не +затронут. + +Анимированные тайлы (факел, шипы, дверь-плита) по-прежнему рисуются как +отдельные `sprite_t` поверх фона — движок это уже умеет (Y-order/layers, +dirty-биты, heal против фона через ОЗУ-копию); `room_draw()` кладёт в +ОЗУ-копию именно статичную геометрию (пол/стены/колонны Фазы 1 — §5.2), +поверх неё heal спрайтов работает как обычно. + +`toolchain/room_compose.py` (генерик-компоновщик тайлов в одну картинку, +§6.1) остаётся полезным ИНСТРУМЕНТОМ конвертации отдельных тайл-картинок +(PNG → getimage raw), просто теперь его выход — 32 маленьких файла +`tileNN.raw` (по одному на тип тайла), а не один большой файл на комнату; +сама раскладка/повторное использование по комнатам — в C-коде +(`room_draw`), не в офлайн-склейке. + +--- + +## 5. Proof-of-Concept — цель: доказать, что порт вообще ощущается как PoP + +**Объём**: одна комната (например Level 1, экран старта Кида), без +переходов между экранами, без стражников (стретч-цель, не обязательна). + +**Что показываем**: +1. Кид на экране, с закреплённым офлайн-конвертированным набором кадров + (подмножество: idle, walk L/R, jump-начало/дуга/приземление, стоп-на-краю, + возможно повисание на краю) — атлас в W0-странице, по образцу `rpgwalk`. +2. Управление: держать влево/вправо — идёт; отпустил — тормозит/стоит; + нажатие вверх во время бега — прыжок вперёд (дуга по авторским таблицам + смещений, не по gravity-физике «с нуля» — см. §6). Здесь же проверяется + решение по §2 (реальный held-state). +3. Столкновения: пол/край экрана/провал — по факту чтения тайла из + `BLUETYPE` под ногами (без LINKLOC-триггеров пока). +4. Стабильный кадр 50 Гц через уже готовый `gfx_wait_vsync`/дабл-буфер + (без FPS-делителя — Кид анимируется каждый видеокадр, как в оригинале). + +**Критерий успеха**: субъективно «прыжок ощущается как в PoP» (дистанция и +тайминг прыжка сверены с оригинальными таблицами, не подобраны на глаз — +см. §6), управление отзывчивое (не событийное с задержкой), сцена не мерцает +на стыке спрайт/фон. + +**Не входит в PoC**: стражники/бой, звук, HUD/таймер, переходы между +комнатами, ловушки/триггеры, титры/меню, сохранения. + +**Расположение**: `applications/PoP/poc/` (свой sprinter-cc проект + Python +конвертер ассетов, по структуре `examples/rpgwalk`). + +### 5.1 Статус (2026-07-15) — первая итерация: управление + коллизия края + +Сделано и проверено в MAME (`applications/PoP/poc/`, `make run`): +держать LEFT/RIGHT (`kbd_raw_down`, raw-канал из §2) — идёт непрерывно, +отпустил — стоит на месте (не событийно, реальный held-state); +столкновение с краями экрана (клип по `MINX`/`MAXX`); анимация +ходьбы/разворота лицом по направлению (`sprite_anim` пинг-понг); +дабл-буфер + `gfx_wait_vsync` — без видимого мерцания. Сборка — +`--memory huge` без `--bank` (§10, подтверждено рабочим). + +**Важное отступление от плана (осознанно, не молча):** персонаж — +ВРЕМЕННАЯ заглушка (лицензированный спрайт-пак +`third_party/16x16-RPG-characters` через `tools/gen_kid_placeholder.py`, +тот же источник, что уже использует `examples/rpgwalk`), а НЕ +конвертированная графика оригинальной Prince of Persia. Причина: +исходный набор кадров Кида (`SDLPoP/data/KID`) — копирайт +Broderbund/Ubisoft; автоматический конвейер, который систематически +извлекает и переупаковывает его в новый формат, — это на практике +внутрипроектное решение, которое стоит принимать пользователю явно +для каждого шага, а не проводить асинхронно агентом без лишнего +подтверждения. Сама графика — не то, что проверяет PoC (§5 явно: +цель — ощущение управления/коллизий, не визуальная точность). Замена +на настоящую графику Кида — отдельный шаг, на усмотрение пользователя. + +**Ещё не сделано** (следующие итерации §5): авторские таблицы +смещений кадров (§6 — движение при ходьбе линейное, px/кадр), +реальный уровень/фон по `BLUETYPE`/`LEVEL1` (сейчас — плейсхолдер: +плоский пол на весь экран, без ямы/выступа), `kbd_mod_state`/ +Shift-бег не подключены к игровому циклу (обёртка готова с Фазы A). + +**Прыжок/присед добавлены и ПРОВЕРЕНЫ (2026-07-15)**: состояние +`jumping`/`jump_t`/`crouching`, своя приблизительная дуга прыжка +(`jump_height[]`, 40 кадров) — не авторская таблица, см. §6.1. +HUD-текст статуса (нет отдельной позы). + +Живое тестирование пользователем нашло реальный баг: держа UP чуть +дольше 0.8 с (длительность дуги), получали ДВА прыжка подряд — код +проверял `kbd_raw_down(KBD_UP)` как уровень (держится, пока клавиша +физически зажата), а не как фронт нажатия, поэтому в момент +приземления «UP всё ещё зажат» тут же триггерил новый прыжок. +Исправлено edge-detect'ом (`up_prev` — предыдущее состояние UP, +триггер только на переход 0→1). Проверено брейкпоинтом в отладчике +MAME на адресе входа в код прыжка: за одно длинное удержание UP +брейкпоинт срабатывает РОВНО ОДИН РАЗ — фикс подтверждён на уровне +кода, не только «на глаз». + +Побочный урок (см. `docs/libc-reference.md` ``): моя +более ранняя попытка проверить UP/DOWN/RIGHT по скриншотам после +`press_key` ошибочно решила, что скрипт их не нажимает вообще — +на самом деле нажимает исправно, просто скриншот ловил случайный +момент дуги. Брейкпоинт/watchpoint на конкретный адрес кода — +надёжнее скриншота для таких проверок. + +--- + +## 6. Модель движения: авторские таблицы кадров, не физика с нуля + +Оригинальный движок PoP не считает прыжок как непрерывную физику +(gravity/velocity каждый тик) — движение персонажа задано таблицами кадров +анимации, где у части кадров зашито фиксированное смещение (dx, dy) для +ЭТОГО конкретного кадра последовательности (структура видна и в +исходниках Apple II — `SEQTABLE.S`/`MOVER.S`, и в SDLPoP `seg003.c`/`seq*` +таблицах). Практическое следствие для нашего движка: +- **Не использовать** `sprite_anim`/`sprite_moveto` для основного + персонажа как есть (они лианейно тянут по таймеру/тянут к линейной + цели) — вместо этого приложение само на каждый логический тик: + переключает кадр (`sprite_frame`, атлас как лента поз, не «прогрессия + первый..последний» автоматом) и одновременно применяет dx,dy ЭТОГО + кадра к позиции (`sprite_move`). +- Готовая автоматика движка (`sprite_anim`/`sprite_moveto`/tween, + Y-сортировка) остаётся полезной для декоративных/фоновых элементов + (факелы, патрулирующий стражник вне боя — почти один в один паттерн + `rpgwalk`). +- Источник таблиц смещений: переснять из `Prince-of-Persia-Apple-II/01 POP + Source/Source/{MOVER.S,SEQTABLE.S,FRAMEADV.S}` и/или + `SDLPoP/src/seq*.c` — задача Фазы 1 полной реализации (§7), не PoC + (для PoC можно взять урезанный набор смещений вручную по количеству + пикселей на кадр, посчитанному по видео/скриншотам оригинала, и уточнить + позже). + +### 6.1 Инструмент конвертации кадров разного размера (`toolchain/png_strip.py`) + +Кадры персонажа в оригинале — РАЗНОГО размера каждый (bbox зависит от +позы; `sprite_t` нашего движка (`libbgi/include/sprite.h`) хранит ОДИН +фиксированный w/h на весь спрайт и рисует от угла, без per-frame +смещения — в отличие от оригинала, где на каждый кадр было своё XCO/YCO +(`APPLEII_RESOURCE_FORMAT.md` §2.2). `toolchain/png_strip.py` (генерик, +не завязан на PoP — принимает произвольный список PNG) закрывает это +ПАДДИНГОМ: канвас = макс. w/h среди кадров ленты, якорь по умолчанию +bottom-center («ноги на месте»), остальное — прозрачность. + +**Компромисс, не полноценное решение**: один сильно выбивающийся по +размеру кадр в ленте раздувает канвас (и память) ВСЕХ кадров этой же +ленты. Смягчается группировкой по похожим размерам в отдельные атласы +(не одна лента на все позы актора — так уже сделано для ходьбы отдельно +от прыжка). + +**Полноценное решение (кандидат в будущее расширение библиотеки, НЕ +делать без предложения и подтверждения пользователя)**: per-frame +смещение в `sprite_t` (аналог XCO/YCO оригинала) — тогда паддинг +не нужен вообще, экономия памяти по полной. Делать только если память +станет РЕАЛЬНОЙ проблемой (не гипотетической) — тогда предложить как +отдельную правку `sprite.h`/движка. Подробности компромисса — +memory/png_strip_padding_tradeoff. + +--- + +## 7. Полноценное приложение — фазы (после PoC) + +Порядок — по риску и зависимостям, не по геймплейной важности. + +**Фаза 0 — инфраструктура порта** (расширяет PoC, не переписывает): +- Хелд-стейт клавиатуры — финальное решение и реализация по §2 (после + подтверждения пользователем). +- Полный конвертер уровней (все 15 файлов `levels.dat`/`LEVELn`) → бинарный + формат приложения (можно 1-в-1 raw dump, читать по офсетам в рантайме — + не обязательно разворачивать в C-struct с указателями). +- Полный конвертер фона (24 экрана × N уровней) в растры + конвертер + спрайт-лент Кид/стражник/скелет/тень/Джаффар в атласы `.atl` (расширение + `conv_sprites.py`/формата `.atl`, если частот кадров/атласов на актора не + хватит текущего лимита — см. риск в §8). + +**Фаза 1 — Кид, полный набор действий**: стоять/идти/бежать/тормозить/ +разворот/прыжок (на месте, вперёд, «прыжок с разбега»)/повисание на +краю/подтягивание/спуск по свисанию/приседание/питьё зелья/смерть от +провала. Переходы между экранами (`MAP`-граф, `INFO.KidStartScrn`). + +**Фаза 2 — мир и ловушки**: нажимные плиты/двери через граф +`LINKLOC`/`LINKMAP` (см. `APPLEII_RESOURCE_FORMAT.md` §1.2), шипы +(выдвижение/втягивание/заклинивание), шаткие плиты (loose, обрушение), +зелья (эффект по `BLUESPEC×32`), стартовые позиции по `INFO`. + +**Фаза 3 — бой**: подбор/выхватывание меча, состояние стойки, парирование/ +удар, коллизия клинков — по логике `AUTO.S`/`seg003-006.c` (референс, не +копия). Стражник: базовое AI-поведение по `GdStartProg` (несколько +шаблонов программ), Y-сортировка слоями уже есть в движке для «кто +спереди/сзади». + +**Фаза 4 — разнообразие противников**: скелет, тень (копия анимации Кида — +подтверждено побайтовым совпадением данных, см. `MSDOS_RESOURCE_FORMAT.md` +§3), толстый стражник/визирь (общая база анимации с визирем). + +**Фаза 5 — звук**: CBL-эффекты (шаги, удары, двери, падение) из +`digisnd*.dat`→PCM; PC-спикер тройки (`ibm_snd*.dat`) как опциональный +дешёвый бипер без CBL, если формат подтвердится простым парсингом. + +**Фаза 6 — оболочка**: титры, меню/выбор уровня, HUD (таймер/жизни), +сохранение прогресса (FILE*), финальные катсцены — по минимуму, +геймплейно не критично. + +**Фаза 7 — стабилизация**: полный прогон всех 14 уровней в MAME +(`mame_interactive.py`), затем на реальном железе; профилирование бюджета +кадра по методике `sprite_engine_perf`/`sprite-api-design.md` §9д на самых +насыщенных экранах (несколько стражников + ловушки одновременно — +проверить лимит ~21 спрайт/кадр и Y-sort лимит 32); при необходимости — +банкинг (`--memory big/huge`) для кода/уровня, если размер вылезет за +tiny/small. + +--- + +## 8. Риски, требующие спайка/артефакта до架构 решений + +(по правилу `defer_unexplained_quirks` — не гадать, проверять) + +1. **Held-state клавиатуры** (§2) — блокирует даже PoC, если решать + «правильно»; иначе PoC на компромиссном варианте 2 (таймаут-эвристика). +2. **Бюджет спрайтов на насыщенный экран** — сцена с 2+ стражниками + + несколько анимированных ловушек может приблизиться к лимиту + ~21 спрайт/кадр (`sprite_engine_perf`) — нужна прикидка по реальным + уровням (сколько объектов одновременно активно в худшем экране). +3. **Ёмкость одного атласа/страницы EMM на актора** — у Кида ~220 кадров + (все действия) против 4×12 у `rpgwalk` — потребуется либо несколько + атласов на актора с переключением по фазе действия (стоять/идти отдельно + от боя), либо расширение формата `.atl`/загрузчика на мульти-страничные + атласы — оценить фактический байтовый вес конвертированных кадров Кида + прежде чем проектировать. +4. **Тайминг оригинала** — сверить логическую частоту кадров анимации + оригинала (Apple II ~60 Гц NTSC / DOS — фиксированный таймер) с 50 Гц + Sprinter; если оригинал считался на другой частоте — потребуется + коэффициент пересчёта смещений кадров (§6), иначе прыжки/бег будут + визуально быстрее/медленнее эталона. + +--- + +## 10. Режим памяти сборки + +Пользователь предложил `huge` (горячий код в W1, данные в W2, редко +вызываемая логика — банками в W3) как целевой режим. Согласен, с уточнением +по срокам принятия решения. + +**`huge` — правильная цель для ПОЛНОГО приложения**, но не то, с чего надо +стартовать: + +- Layout `huge` (см. `memory_modes_implemented`): CODE_LOC=0x4100 (W1), + DATA_LOC=0x8000 (W2), банки — W3 (порт 0xE2), `crt0_banked` + + автодетект W2 (как `small`). Состояние приложения (структуры Кида, + уровня, массив `sprite_t`) остаётся в обычном W2-heap ДАЖЕ если код, + который его трогает, забанкован — `malloc` из банка возвращает + W2-указатель (`bank_local_data_pattern`), так что данные не привязаны к + конкретному банку. +- Оверхед `__banked`-вызова (trampoline: +3 байта на стеке между ret и + аргументами, виртуальный 24-битный адрес, см. `sdcc_banking`) — фиксированная + небольшая цена ЗА ВЫЗОВ, не за такт. Это не страшно для функций, которые + вызываются РЕДКО за кадр (AI одного стражника, диалог, переход между + комнатами) — страшно было бы забанковать что-то, что дёргается ВНУТРИ + горячего цикла отрисовки (там уже и так основной бюджет уходит на + `sprite_update`/блиты — см. `sprite_engine_perf`, ~19.5К тактов/спрайт). + Правило простое: **не банковать код на пути "раз в кадр на объект", + банковать код на пути "раз в кадр на комнату/раз в переход/раз в + редкое событие"**: логика ИИ стражника целиком, диалоги/катсцены, меню/ + титры/выбор уровня, парсинг уровня при входе в комнату, сериализация + сохранений — хорошие кандидаты в банки; тик Кида, чтение столкновений, + вызов `sprite_update`/`gfx_wait_vsync`, обработка ввода — должны остаться + небанкованными (W1/W2). +- Гранулярность банкования — целый файл (`--bank N=FILE.c`), это уже + системный паттерн проекта (тот же принцип, что и «1 файл = 1 юнит DCE» в + libc) — значит выгодно с САМОГО начала Фазы 1 (не задним числом) резать + исходники приложения по границе «горячее/холодное» файл-в-файл: например + `kid_tick.c`/`collision.c`/`room.c`/`input.c` — неизменно вне банков; + `guard_ai_*.c`/`dialogue.c`/`menu.c`/`levelload.c`/`combat.c` — кандидаты + под `--bank`. Тогда переход на `huge` позже — это правка Makefile/ + sprinter-cc-вызова (`--memory huge --bank N=file.c ...`), а не рефакторинг + логики. + +**Уточнение (проверено в `bin/sprinter-cc`, строки ~342-350): можно сразу +собирать PoC на `--memory huge` без единого `--bank`.** Скрипт сам +подставляет стаб `const unsigned char n_banks = 0;`, когда `--bank` не +передан ни один раз — `crt0_banked` линкуется и корректно пропускает цикл +загрузки банков при старте. Layout при этом byte-в-byte совпадает с тем, +что делает `crt0_small` для режима `small` (CODE 0x4100/W1, DATA 0x8000/W2, +автодетект W2) — разница только в том, что попутно линкуется сам +`bank.s` (таблица `_bank_pages` + trampoline-инфраструктура), это +незначительный довесок к размеру, не к рантайм-цене. Значит **PoC можно +сразу собирать вызовом `sprinter-cc --memory huge` без `--bank`-флагов** — +и когда в полном приложении появятся первые «холодные» файлы, переход на +банкование — это просто добавление `--bank N=file.c`, без смены +`--memory`/адресов/crt0. Сборочная конфигурация не потребует миграции +между PoC и полным приложением. + +Единственное, что стоит сделать уже в Фазе 1 полного приложения (не в +PoC) — планировать структуру исходников с расчётом на будущий файл-в-файл +сплит под банки (см. выше), раз гранулярность банкования — целый файл. + +--- + +## 9. Что нужно от пользователя, прежде чем двигаться дальше + +- Подтверждение направления по §2 (какой из трёх вариантов held-state + клавиатуры пробовать первым, или сначала спайк-эксперимент в MAME). +- Подтверждение объёма PoC (§5) — устраивает ли «одна комната без + стражников», или сразу закладывать хотя бы одного патрулирующего + стражника (это не архитектурно сложнее — Y-order и tween уже есть, + просто больше конвертации ассетов). diff --git a/applications/PoP/docs/README.md b/applications/PoP/docs/README.md new file mode 100644 index 0000000..c645b33 --- /dev/null +++ b/applications/PoP/docs/README.md @@ -0,0 +1,81 @@ +# Форматы ресурсов Prince of Persia — сводка + +Цель этих документов — подготовить почву для будущего порта Prince of Persia +на ZX Sprinter, разобрав, как устроены ресурсы игры в двух доступных нам +версиях: + +- [`APPLEII_RESOURCE_FORMAT.md`](./APPLEII_RESOURCE_FORMAT.md) — формат + уровней и графики по официально опубликованным исходникам 1989 года + (6502-ассемблер). Уверенность высокая везде — восстановлено прямым чтением + кода движка, а не догадками. +- [`MSDOS_RESOURCE_FORMAT.md`](./MSDOS_RESOURCE_FORMAT.md) — формат `.DAT` + ресурсов DOS-версии (исходников нет). Восстановлено эмпирически (разбор + байтов + перепроверка скриптами) и сверено с документацией открытых + сторонних инструментов (SDLPoP, Princed Resources). + +## Главный вывод + +**Формат уровня практически идентичен в обеих версиях**: Apple II `LEVELn` +занимает ровно 2304 байта (структура `blueprnt` — тайлы, связи +плит/дверей, граф экранов, метаданные старта Кида/стражников), а запись +уровня в DOS `levels.dat` занимает 2305 байт с байтовыми значениями тайлов +того же диапазона. То есть Джордан Мехнер перенёс формат карты уровня в +DOS-порт практически без изменений (+1 байт, вероятно контрольная сумма от +DOS-упаковщика). Это значит: раскладку `BLUETYPE`/`BLUESPEC`/`LINKLOC`/ +`LINKMAP`/`MAP`/`INFO`, задокументированную по Apple II исходникам, можно +применять напрямую и к DOS `levels.dat`. + +Формат же **графики отличается принципиально**: на Apple II это простой +несжатый rowbyte-формат hi-res экрана с плоской таблицей указателей; в DOS — +контейнер с оглавлением ресурсов (id/size/offset), с отдельными вариантами +под CGA/EGA/VGA — точный кодек пикселей внутри сырого `.DAT`-чанка не +восстановлен ни для той, ни для другой версии до конца. **Но для DOS-графики +это не блокирует работу**: в репозитории github.com/NagyD/SDLPoP (папка +`data/`) уже лежат готовые распакованные PNG для каждого спрайта/фона +(включая VGA-256-цветный вариант `VPALACE`/`VDUNGEON` — то, что нужно под +320×256×256 Sprinter), см. §5 `MSDOS_RESOURCE_FORMAT.md`. Это другой +релиз/сборка данных, чем наш локальный `MSDOS/` (некоторые звуковые `.dat` +отличаются по размеру), но нумерация ресурсов и формат контейнера — те же, +что подтверждено побайтовой сверкой уровня `res2001.bin`. + +## Общий контейнерный формат DOS `.DAT` (кратко) + +``` +[0x00] u32 LE tableOffset — смещение начала таблицы оглавления +[0x04] u16 LE tableSize — размер таблицы оглавления +[0x06..tableOffset) — данные ресурсов (конкатенация чанков) +[tableOffset..tableOffset+tableSize) + — массив записей по 8 байт: + u16 size, u16 id, u16 offset(абсолютный), u16 reserved(=0) +``` + +Инвариант `tableOffset + tableSize == размер файла` подтверждён на всех 28 +`.dat`-файлах в `MSDOS/`, и независимо — именованием файлов `res.*` в +`data/` репозитория SDLPoP. + +## Готовые ассеты для порта (важно для практической работы) + +`github.com/NagyD/SDLPoP/tree/master/data` содержит не только код движка, но +и сами ресурсы игры — как сырые `.DAT`, так и распакованные поштучно файлы +(`res.png` для спрайтов/фонов, `res.pal` для палитр, `res.bin` +для уровней). Для арт-ассетов (в т.ч. нужного полноцветного VGA-варианта +дворца/подземелий) практичнее взять их оттуда напрямую, чем писать свой +декодер сжатия пикселей DOS `.DAT`. + +## Что дальше (не сделано в этом заходе) + +1. Точный кодек сжатия пикселей спрайтов в сыром DOS `.DAT` (нужен только + если понадобится читать именно нашу локальную копию `MSDOS/*.dat` + "как есть", а не ассеты из SDLPoP `data/`) — сверка с исходником SDLPoP, + `src/seg009.c`. +2. Семантика служебных полей `digisnd*.dat`/`ibm_snd*.dat` перед сырыми + сэмплами/нотами (частично прояснено документацией Princed Resources — + PC speaker: 1 байт заголовка + повторяющиеся тройки байт "2 байта частоты + + 1 байт длительности"; WAV: 8 бит, моно, unsigned, 11025 Гц). +3. Назначение бит `secmask` в `BLUETYPE` (Apple II) и служебного блока + `id=2000` в начале DOS `levels.dat` (16 байт в нашей копии, но 2305 байт + в версии SDLPoP — расхождение между релизами, не разобрано). +4. Оценка, какие видеорежимы/цветовые палитры ZX Sprinter реалистично + покрывают исходную графику (CGA/EGA/VGA варианты в DOS-ресурсах против + hi-res Apple II) — отдельная архитектурная задача порта, не формат + ресурсов как таковой. diff --git a/applications/PoP/poc/Makefile b/applications/PoP/poc/Makefile new file mode 100644 index 0000000..a2e46ad --- /dev/null +++ b/applications/PoP/poc/Makefile @@ -0,0 +1,53 @@ +# PoC порта Prince of Persia (applications/PoP/docs/PORT_PLAN.md §5). +# --memory huge, БЕЗ --bank. +# +# ПОЧЕМУ huge, а НЕ small (исправлено 2026-07-16): poc использует +# raw-клавиатуру (kbd_raw_open → IM2-таблица). Буферы IM2 (_irq_vec_buf, +# BSS) ОБЯЗАНЫ жить в W2 (0x8000-0xBFFF) — во время прерывания W1/W3 +# могут быть перемаплены DSS (см. libc/irq/_irq_table.c, memory/ +# fps_divider: «verified tiny/big/huge, small=EINVAL»). --memory small +# пулит W1+W2 в плоские ~32КБ и чейнит DATA за CODE — при небольшом CODE +# BSS уезжает в W1 (<0x8000), и _irq_table_ref отдаёт EINVAL → +# kbd_raw_open молча возвращал -1, poc печатал ошибку УЖЕ в графическом +# режиме (невидимо) и выходил в prompt. huge кладёт CODE в W1, а +# DATA/BSS/STACK/HEAP жёстко в W2 → IM2 работает. Цена: раздел 16КБ +# CODE / 16КБ DATA вместо общего 32КБ-пула small — сейчас влезает с +# запасом; при росте настоящего PoP CODE>16КБ понадобится банк под код. +# +# --bank НЕ нужен: gfx_blit_part()/atlas_load() сами временно трогают W3 +# (видеобанк / чтение атласа) — банк room.c в W3 давал вероятностный +# «снег»; банк в W1 несовместим с CODE=W1 (трамплин переключения W1 сам +# бы уехал). sprintf() заменён на ручное hex-форматирование пути в +# tile_atlas_load() (единственный потребитель printf, ~2.9КБ). + +PROJ_ROOT := $(abspath $(CURDIR)/../../..) +EXAMPLE := poc +MEMORY ?= huge +EXTRA_FLAGS ?= --gfx 256 +EXTRA_SRCS := room.c +TILE_ATLASES := res/tiles/tile01.atl res/tiles/tile14.atl res/tiles/tile03.atl \ + res/tiles/tile13.atl res/tiles/tile0e.atl res/tiles/tile0b.atl +EXTRA_DATA := tools/kid.atl tools/room.pal res/room1.dat $(TILE_ATLASES) +include $(PROJ_ROOT)/app.mk + +LEVEL1_BIN := $(PROJ_ROOT)/applications/PoP/SDLPoP/data/LEVELS/res2001.bin + +tools/kid.raw tools/kid.pal: tools/gen_kid_placeholder.py + cd tools && python3 gen_kid_placeholder.py + +tools/kid.atl: tools/kid.raw + python3 $(PROJ_ROOT)/toolchain/mkatlas.py $@ tools/kid.raw:16x16:1x12 + +res/room1.dat: tools/extract_room.py $(LEVEL1_BIN) + python3 tools/extract_room.py $(LEVEL1_BIN) 1 res/room1.dat + +res/tiles/1F-0.png: tools/gen_tile_placeholders.py + cd tools && python3 gen_tile_placeholders.py + +tools/room.pal $(TILE_ATLASES): tools/kid.pal res/tiles/1F-0.png tools/build_room_palette.py + cd tools && python3 build_room_palette.py + +# make_disk.py упаковывает EXTRA_DATA на диск ПОД БАЗОВЫМ ИМЕНЕМ +# (плоская ФС) — tileNN.atl/room1.dat оказываются в корне рядом с +# kid.atl/room.pal, room.c/poc.c грузят их без пути. +$(EXAMPLE).exe: room.c tools/kid.atl tools/room.pal res/room1.dat $(TILE_ATLASES) diff --git a/applications/PoP/poc/level.h b/applications/PoP/poc/level.h new file mode 100644 index 0000000..fe119df --- /dev/null +++ b/applications/PoP/poc/level.h @@ -0,0 +1,153 @@ +/* + * level.h — структуры уровня PoP под наш движок. + * + * Формат — POP-DAT-FormatSpecifications.pdf §3.4 (DAT 1.0): комната = + * 30 тайлов (10 колонок x 3 ряда), foretable даёт тип тайла, + * backtable — модификатор/состояние (семантика зависит от типа). + * Адресация тайла: tile = (room-1)*30 + tileOffset, tileOffset 0-9 = + * верхний ряд, 10-19 = средний, 20-29 = нижний, слева направо. + * + * Фаза 1 (applications/PoP/docs/PORT_PLAN.md §5, сузили объём): + * только геометрия (пол/стены/провалы) для коллизий — двери, факелы, + * ловушки, гарды НЕ используются (структуры под них здесь тоже + * упрощены/оставлены как заготовка на потом, см. §7 плана Фаза 2). + */ +#ifndef LEVEL_H +#define LEVEL_H + +#include + +/* 1 (не 24) — PoC грузит и рисует только комнату 1; расширить, когда + * появится настоящий level_load() на несколько комнат. */ +#define LEVEL_ROOMS 1 /* комнаты нумеруются 1..24 в файле, + * здесь индекс 0..23 = комната N+1 */ +#define ROOM_COLS 10 +#define ROOM_ROWS 3 +#define ROOM_TILES (ROOM_COLS * ROOM_ROWS) /* 30 */ + +/* --- Типы тайлов (Table 7 спецификации) --- */ +#define TILE_TYPE(byte) ((uint8_t)((byte) & 0x1F)) +#define TILE_MODIFIER(byte) ((uint8_t)(((byte) >> 5) & 1)) + +enum { + TILE_EMPTY = 0x00, + TILE_FLOOR = 0x01, + TILE_SPIKES = 0x02, + TILE_PILLAR = 0x03, + TILE_GATE = 0x04, + TILE_STUCK_BUTTON = 0x05, + TILE_DROP_BUTTON = 0x06, + TILE_TAPESTRY = 0x07, + TILE_PILLAR_BOTTOM = 0x08, + TILE_PILLAR_TOP = 0x09, + TILE_POTION = 0x0A, + TILE_LOOSE = 0x0B, + TILE_TAPESTRY_TOP = 0x0C, + TILE_MIRROR = 0x0D, + TILE_DEBRIS = 0x0E, + TILE_RAISE_BUTTON = 0x0F, + TILE_EXIT_LEFT = 0x10, + TILE_EXIT_RIGHT = 0x11, + TILE_CHOPPER = 0x12, + TILE_TORCH = 0x13, + TILE_WALL = 0x14, + TILE_SKELETON = 0x15, + TILE_SWORD = 0x16, + TILE_BALCONY_LEFT = 0x17, + TILE_BALCONY_RIGHT = 0x18, + TILE_LATTICE_PILLAR = 0x19, + TILE_LATTICE_SUPPORT= 0x1A, + TILE_LATTICE_SMALL = 0x1B, + TILE_LATTICE_LEFT = 0x1C, + TILE_LATTICE_RIGHT = 0x1D, + TILE_TORCH_DEBRIS = 0x1E, + TILE_NULL = 0x1F +}; + +/* Твёрдые тайлы — Фаза 1 (только геометрия); классификация наша, для + * коллизий движка, не часть исходного формата. Двери/шипы/дробилки и + * т.п. сознательно исключены из объёма Фазы 1 (см. §5 плана) — при + * встрече в реальных данных трактовать как проходимые до Фазы 2. + * + * tile_is_solid() — "есть опора сверху" (вертикальный смысл: можно + * стоять НА этом тайле) — Floor ТОЖЕ solid в этом смысле! Для + * горизонтальной коллизии (можно ли ВОЙТИ в эту клетку сбоку) нужен + * ОТДЕЛЬНЫЙ предикат — см. tile_blocks_side ниже. Баг 2026-07-16: + * col_blocked() в poc.c ошибочно звал tile_is_solid() для бокового + * упора — Floor блокировал сам себя, Кид не мог сдвинуться с места + * стоя на полу. */ +static inline uint8_t tile_is_solid(uint8_t byte) +{ + switch (TILE_TYPE(byte)) { + case TILE_FLOOR: + case TILE_PILLAR: + case TILE_PILLAR_BOTTOM: + case TILE_PILLAR_TOP: + case TILE_WALL: + case TILE_BALCONY_LEFT: + case TILE_BALCONY_RIGHT: + return 1; + default: + return 0; + } +} + +/* tile_blocks_side() — настоящая преграда СБОКУ (нельзя войти в + * клетку по горизонтали): Wall/Pillar-семейство. Floor/Balcony НЕ + * блокируют — по ним идёшь (тайл под ногами, не впереди). Lattice- + * колонны (0x19-0x1D) — узкие, Кид физически проходит мимо (см. + * gen_tile_placeholders.py draw_lattice_like) — тоже НЕ блокируют. */ +static inline uint8_t tile_blocks_side(uint8_t byte) +{ + switch (TILE_TYPE(byte)) { + case TILE_PILLAR: + case TILE_PILLAR_BOTTOM: + case TILE_PILLAR_TOP: + case TILE_WALL: + return 1; + default: + return 0; + } +} + +/* --- Тайл и комната --- */ +typedef struct { + uint8_t type; /* foretable byte (rrmccccc — см. TILE_TYPE/MODIFIER) */ + uint8_t state; /* backtable byte — модификатор/состояние */ +} tile_t; + +typedef struct { + tile_t tiles[ROOM_TILES]; /* индекс = tileOffset 0..29 */ + uint8_t link_left, link_right; /* links-блок: 0 = нет соседа */ + uint8_t link_up, link_down; + uint8_t guard_location; /* 0..29; 30 (и выше) = нет гарда */ + int8_t guard_direction; /* 0 = вправо, -1 = влево */ + uint8_t guard_skill; /* 0..9 */ + uint8_t guard_colour; /* индекс палитры, Table 11 */ + /* door I/II (событийные цепочки) — Фаза 2, не здесь */ +} room_t; + +typedef struct { + room_t rooms[LEVEL_ROOMS]; /* индекс 0 = комната 1 (файл 1-based) */ + uint8_t start_room; /* 1..24 */ + uint8_t start_location; /* 0..29 */ + int8_t start_direction; /* 0 = вправо, -1 = влево */ +} level_t; + +/* Тайл по (room 1-based, col 0-9, row 0-2). */ +static inline tile_t *level_tile(level_t *lv, uint8_t room, uint8_t col, uint8_t row) +{ + return &lv->rooms[room - 1].tiles[row * ROOM_COLS + col]; +} + +/* Загружает ОДНУ комнату + стартовую позицию уровня из файла в формате + * tools/extract_room.py (63 Б: foretable[30]+backtable[30] той комнаты + * + start_room+start_pos+start_dir, вырезанные из res20NN.bin — layout + * подтверждён декодом байт 2026-07-15/16: файл начинается СРАЗУ с + * foretable[720], потом backtable[720], без заголовка; start_position + * — смещение 2112, сверено со структурой level_type в SDLPoP/src/ + * types.h). Заполняет lv->start_room/start_location/start_direction. + * 0 — OK, -1 — файл не найден/короче 63 Б. */ +int level_load_room(level_t *lv, uint8_t room, const char *path); + +#endif diff --git a/applications/PoP/poc/poc.c b/applications/PoP/poc/poc.c new file mode 100644 index 0000000..5c03ad5 --- /dev/null +++ b/applications/PoP/poc/poc.c @@ -0,0 +1,264 @@ +/* + * poc.c — PoC порта Prince of Persia (applications/PoP/docs/PORT_PLAN.md + * §5): проверяем управление (raw-клавиатура, held-state) + коллизию + * по краям экрана + анимацию ходьбы + прыжок/присед поверх готового + * спрайтового движка (sprite.h). + * + * ВАЖНО: персонаж — ВРЕМЕННАЯ ЗАГЛУШКА (лицензированный спрайт-пак + * third_party/16x16-RPG-characters через tools/gen_kid_placeholder.py, + * тот же источник, что уже использует examples/rpgwalk), НЕ графика + * оригинальной Prince of Persia — см. §5 и §8.5 плана. У заглушки нет + * отдельных поз прыжка/приседа — механика (тайминг дуги, состояние, + * коллизия с полом) проверяется на том же спрайте без смены позы; + * визуально это упрощение, не финальный вид. + * + * Дуга прыжка (jump_height[]) — СВОЯ, приблизительная (не таблица + * смещений оригинала — см. §6 плана: авторские таблицы кадров решено + * не переносить, только код/структуры). + * + * Нет ещё (следующие итерации): реальный уровень/фон по BLUETYPE, + * рывок вбок при прыжке с разбега, зацепление за уступ. + */ +#include +#include +#include +#include +#include +#include +#include "level.h" +#include "room.h" + +/* Комната 1 уровня 1 — РЕАЛЬНАЯ геометрия (foretable/backtable), + * вырезана tools/extract_room.py из applications/PoP/SDLPoP/data/ + * LEVELS/res2001.bin (level_load_room(), см. level.h/room.c) — не + * плейсхолдер. Верхний ряд (row0) — floor-уступ на cols 3-7 (там + * реально стоит персонаж в оригинале), стены по cols 8-9; ряды 1-2 — + * ниже уступа (торч/колонны/пол — Фаза 1 просто их отрисовывает по + * тем же типам, без многоуровневой физики падения). */ +static level_t test_level; + +#define CHAR_ROW 0 /* ряд, где стоит персонаж (floor-уступ room1) */ +#define ROW_Y(r) ((r) * 64) /* room_row_h все по 64 */ +#define GROUND_Y ROW_Y(CHAR_ROW + 1) /* низ ряда CHAR_ROW = верх пола */ +#define KIDY (GROUND_Y - 16) /* y спрайта (16 px высотой) */ +#define SPEED 2 /* px/кадр — заглушка, не авторский темп */ + +/* Настоящая преграда (Wall/Pillar) слева/справа от кандидата x в + * CHAR_ROW — блокирует движение (грубая проверка по краям хитбокса + * 16px, без под-тайловой подгонки — для PoC достаточно, см. + * PORT_PLAN.md §6). tile_blocks_side(), НЕ tile_is_solid(): Floor — + * тайл, на котором Кид СТОИТ (тот же CHAR_ROW), tile_is_solid() его + * тоже считает "твёрдым" (можно стоять сверху) — если проверять им же + * боковую преграду, Кид не мог сдвинуться с собственного пола (баг, + * найден 2026-07-16). */ +static uint8_t col_blocked(int x) +{ + uint8_t c0, c1; + + if (x < 0 || x + 15 >= ROOM_COLS * ROOM_TILE_W) + return 1; + c0 = (uint8_t)(x / ROOM_TILE_W); + c1 = (uint8_t)((x + 15) / ROOM_TILE_W); + if (tile_blocks_side(level_tile(&test_level, 1, c0, CHAR_ROW)->type)) + return 1; + if (tile_blocks_side(level_tile(&test_level, 1, c1, CHAR_ROW)->type)) + return 1; + return 0; +} + +/* Ленты атласа: dir*3+frame, dir 0=вниз/1=влево/2=вправо/3=вверх, + * 3 кадра маятника на направление (см. tools/gen_kid_placeholder.py). */ +#define DIR_DOWN 0 +#define DIR_LEFT 1 +#define DIR_RIGHT 2 + +/* Дуга прыжка: своя, приблизительная (не авторская таблица, см. шапку + * файла) — высота над полом (px) по кадрам 0..19, УЖЕ ПОЛНЫЙ горб + * (подъём 2→22 к элементу 10, спуск обратно к 2 к элементу 19) — БЕЗ + * зеркалирования в коде, массив читается один раз целиком. БАГ, + * найденный пользователем 2026-07-15: раньше код ЕЩЁ РАЗ зеркалил + * этот уже полный горб на 40 кадров — получалось два полных прыжка + * подряд от одного триггера (не проблема клавиатуры/декодера, чистая + * рассинхронизация данных и комментария). 20 кадров @ 50 Гц ~= 0.4 с. */ +static const uint8_t jump_height[20] = { + 2, 4, 7, 10, 13, 16, 18, 20, 21, 22, + 22, 21, 20, 18, 16, 13, 10, 7, 4, 2 +}; +#define JUMP_FRAMES 20 + +static atlas_t at; +static sprite_t kid; +static uint8_t facing = DIR_DOWN; /* текущее направление анимации */ +static uint8_t jumping = 0; /* 0 = на земле */ +static uint8_t jump_t = 0; /* кадр дуги, 0..JUMP_FRAMES-1 */ +static uint8_t crouching = 0; +/* Прыжок — level-triggered НАМЕРЕННО (не edge-detect): если UP всё ещё + * зажат к моменту приземления — следующий прыжок стартует СРАЗУ (цепочка + * прыжков, пока держишь); отпустил раньше — второй прыжок не начнётся + * сам, только по следующему нажатию. Раньше здесь были up_prev/ + * jump_cooldown — попытка "починить" ровно ЭТО поведение, приняв его + * за баг; убрано 2026-07-15 после уточнения желаемого поведения. */ + +static void draw_room(void) +{ + setfillstyle(SOLID_FILL, BLACK); + bar(0, 0, 319, 255); + room_draw(&test_level, 1); + setcolor(LIGHTGRAY); + outtextxy(60, 4, "PoP PoC: hold LEFT/RIGHT to walk, ESC to quit"); + outtextxy(4, 14, "(placeholder tiles -- not original PoP art)"); +} + +/* HUD-плашка состояния (нет отдельной позы прыжка/приседа — статус + * текстом, рисуется банком 0x50, heal спрайтового движка её не + * трогает — как fps-плашка в examples/rpgwalk). */ +static void show_state(uint8_t jump, uint8_t crouch) +{ + setfillstyle(SOLID_FILL, BLACK); + bar(0, 24, 60, 32); + setcolor(YELLOW); + if (jump) + outtextxy(0, 24, "JUMP"); + else if (crouch) + outtextxy(0, 24, "CROUCH"); +} + +static void set_facing(uint8_t dir) +{ + if (facing == dir) + return; + facing = dir; + sprite_anim(&kid, (uint8_t)(dir * 3), (uint8_t)(dir * 3 + 2), + 6, ANIM_PINGPONG); +} + +int main(void) +{ + uint8_t page, hidden; + int x; + + if (level_load_room(&test_level, 1, "room1.dat") != 0 && + level_load_room(&test_level, 1, "a:\\room1.dat") != 0) { + puts("room1.dat not found"); + return 1; + } + /* Стартовая позиция. В данных room1 start_location = tileOffset 0 + * (row0/col0) — а там EMPTY (провал, без пола); в Фазе 1 нет физики + * падения, поэтому для PoC ставим Кида на floor-уступ (col 3, где он + * реально стоит в оригинале). Когда появится многоуровневая физика — + * брать col из start_location. start_direction: -1 = влево, 0 = + * вправо (level.h). */ + x = 3 * ROOM_TILE_W; + facing = (test_level.start_direction < 0) ? DIR_LEFT : DIR_RIGHT; + + if (atlas_load(&at, "kid.atl") != 0 && + atlas_load(&at, "a:\\kid.atl") != 0) { + puts("kid.atl not found"); + return 1; + } + if (tile_atlas_load("") == 0 && tile_atlas_load("a:\\") == 0) { + puts("tile atlases not found"); + atlas_free(&at); + return 1; + } + + initgraph(); + /* room.pal = EGA16 + Kid + Floor + Wall — ОДНА палитра на всё, + * собрана tools/build_room_palette.py (см. --seed-pal в + * toolchain/png_strip.py) — Kid и тайлы на экране одновременно, + * их "свои" цвета обязаны жить в одной таблице. */ + if (gfx_pal_fload(0, "room.pal") < 0) + gfx_pal_fload(0, "a:\\room.pal"); + gfx_pal_sync(); + gfx_sprite_clip(0); /* коллизия по краям гарантирует границы */ + + /* kid.atl — ОДНА лента (12 кадров вертикально: dir*3+кадр, см. шапку + * файла и examples/rpgwalk); индекс atlas_sprite_init — это НОМЕР + * ЛЕНТЫ (персонажа), а не кадра. Лента одна → всегда 0. Стартовый + * кадр направления выставляем sprite_frame (вертикальная лента, fh=16: + * кадр N на sy=N*16). Баг Соннета (найден 2026-07-16): здесь стоял + * facing*3 как индекс ЛЕНТЫ — при старте лицом влево (idx 3) читался + * мусор за каталогом атласа → мусорные w/h/src → блит спрайта заливал + * пол-экрана «снегом». */ + atlas_sprite_init(&kid, &at, 0); + sprite_frame(&kid, 0, (int)(facing * 3) * 16); + kid.x = x; + kid.y = KIDY; + sprite_show(&kid); + + for (page = 0; page < 2; page++) { /* фон + спрайт на обе страницы */ + gfx_set_draw_page(page); + draw_room(); + sprite_update(&kid, 1); + } + gfx_set_visible_page(0); + + /* kbd_raw требует BSS в W2 (IM2-таблица) — недоступно в --memory small + * (там BSS может уехать в W1 → EINVAL); poc собирается --memory huge + * (CODE в W1, DATA/BSS в W2). closegraph ДО puts — иначе сообщение + * ушло бы в графический режим (невидимо), а программа молча вышла бы + * в prompt (баг Соннета, найден 2026-07-16). */ + if (kbd_raw_open() != 0) { + closegraph(); + puts("kbd_raw_open failed (need memory mode with BSS in W2)"); + atlas_free(&at); + return 1; + } + + for (;;) { + uint8_t moving = 0; + int y = KIDY; + + if (jumping) { + /* дуга идёт сама; направлением можно скользить вбок, + * поза не меняется (нет отдельного кадра прыжка) */ + if (kbd_raw_down(KBD_LEFT)) { + if (!col_blocked(x - SPEED)) x -= SPEED; + } else if (kbd_raw_down(KBD_RIGHT)) { + if (!col_blocked(x + SPEED)) x += SPEED; + } + y = KIDY - jump_height[jump_t]; + jump_t++; + if (jump_t >= JUMP_FRAMES) { + jumping = 0; + y = KIDY; + } + } else if (crouching) { + if (!kbd_raw_down(KBD_DOWN)) + crouching = 0; + } else if (kbd_raw_down(KBD_UP)) { + jumping = 1; + jump_t = 0; + } else if (kbd_raw_down(KBD_DOWN)) { + crouching = 1; + } else if (kbd_raw_down(KBD_LEFT)) { + if (!col_blocked(x - SPEED)) x -= SPEED; + set_facing(DIR_LEFT); + moving = 1; + } else if (kbd_raw_down(KBD_RIGHT)) { + if (!col_blocked(x + SPEED)) x += SPEED; + set_facing(DIR_RIGHT); + moving = 1; + } + if (!moving && !jumping && (sprite_anim_status(&kid) & SPR_ANIM_ON)) + sprite_anim_stop(&kid, (int8_t)(facing * 3)); + + sprite_move(&kid, x, y); + + if (kbd_raw_down(KBD_ESC)) + break; + + hidden = gfx_get_visible_page() ^ 1; + gfx_set_draw_page(hidden); + show_state(jumping, crouching); + sprite_update(&kid, 1); + gfx_wait_vsync(); + gfx_set_visible_page(hidden); + } + + kbd_raw_close(); + closegraph(); + tile_atlas_free(); + atlas_free(&at); + return 0; +} diff --git a/applications/PoP/poc/res/bg/pop_bg.pal b/applications/PoP/poc/res/bg/pop_bg.pal new file mode 100644 index 0000000000000000000000000000000000000000..ed5e3286ff9e5d32548e1cf38b88a78269a8891b GIT binary patch literal 1024 zcmZQz7zLDufQL!^rVLt(57!_?MFhI7aEG5r7kpJCOiRScn_p$v_Mv0yc^ e9tI4xnV}3mr3?&nmqKi&R6nKeAJso{LjVAGqa|?w literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/bg/pop_bg_atlas.h b/applications/PoP/poc/res/bg/pop_bg_atlas.h new file mode 100644 index 0000000..9fb8247 --- /dev/null +++ b/applications/PoP/poc/res/bg/pop_bg_atlas.h @@ -0,0 +1,32 @@ +/* pop_bg_atlas.h — раскладка атласов статического фона PoP. + * Сгенерировано toolchain/pop_pack_bg.py — НЕ править вручную. + * + * Прямая адресация (ноль remap-таблиц в W2): + * ENV фон id N -> atlas env_bg[N>>5], idx N&31 + * WALL id N -> atlas wall, idx N + * FORE id N -> atlas fore, idx N + */ +#ifndef POP_BG_ATLAS_H +#define POP_BG_ATLAS_H + +#define POP_ENV_SHIFT 5 +#define POP_ENV_MASK 31 +#define POP_ENV_PAGES 5 + +/* Палитра: env-слоты, wall-слоты (сприйт-пиксель i -> база+i). */ +#define POP_PAL_ENV 0x50 +#define POP_PAL_WALL 0x60 + +/* Имена файлов атласов (грузятся atlas_load). */ +static const char *const pop_env_atl[POP_ENV_PAGES] = { + "pop_env0.atl", + "pop_env1.atl", + "pop_env2.atl", + "pop_env3.atl", + "pop_env4.atl", +}; +#define POP_WALL_ATL "pop_wall.atl" +#define POP_FORE_ATL "pop_fore.atl" +#define POP_BG_PAL "pop_bg.pal" + +#endif diff --git a/applications/PoP/poc/res/bg/pop_env0.atl b/applications/PoP/poc/res/bg/pop_env0.atl new file mode 100644 index 0000000000000000000000000000000000000000..91f08be77b1b7d61edaeb029737ff38c43f2457f GIT binary patch literal 10578 zcmeHNy^h>A5Z1wsn*af91A*NLfq@iGlfvCKd4uR@rQ6)?x=oQX50o;Gl1IqfDBm~3 zA%_wvCD`Nw1L0vsa=!UyhCdq8yB>b{{=2sM_sdUrO>>9O&v)%d)?dDCU$Fk=W&45k zA75yF|4M7quhqYP)!vi$zHMK#Hof1zewX!4{@nHN@7q_5>)UVMzG2PMHXobuggDAFrC;} z`N+o9k7J+nqnr)EBkVIG@sAim2=Z|fQ<;C_CO2Q1f5IAr_+|{=zs1x|>YKj2fAigU z>YKhi-+aS~DEvSE|7!iYxpWPR>p%6o>p%6Up8K@2{(w)aP+Wh3MWML<0xx2rwEqR} z7AQyvoBdC`6I>Ot{m1PM%UySk23F%&dbeCPg7y9nbnvOr<2_jKe_DrsaX^L4ztIC9 zc>ewyzJRX|^7lVPNAorU?;qg=_kj-w-oKfTe8DjxQkZ|xknj)r*K3%e%zxl7R=Tx+ zEQ~K(|LWazI6tGD!Dx!Z zbh8kxE^YI{pMyNtDqZ$tL?n2)#)6t^DwXDc}K;Vlb9DmKc08YMLgct zbvkp>nM6I4TON#cN7LJL(D^jqsLMW63}v6@+n+9KTr>9n;LXZ!y<88yc2=g2&jN*=c7dxXb3mFGR&#z zPc@*WXacOIHVDI<3G-Un6_BdJ&N+EcPLf=d^)!=bqRbs)tAj*tgKy^V2X|;5htU4t z0em|j=ipvpjx8oumuwGG?5UPpOUXLcGw#JkOz1gUS_%Kj9F2ak!Sx}J;|H{sTa=ioiD!!e*GcD4iB+9_o9N#~H zueEWTjmoqbeBJkcoa4%~9?c)Yx97Os**lIuMOtnhnBPmUf@zNRw^o;223<153hDl!-@e1T6Dy<}pt%X}zM1LLUhNfU({-xPNEVquR@ zgp=+h_7bX{a!k78W?b`w|<+9Z5UqZnz9?olOUX*_6#@xPXTqbU_X$POL*ZCIw zI?wy|&*z$bOu}WFtN47LfuBEpIv3*u@jPcZCpCV~=LLTrr@i!n?m6@SBkZ}d!9|}O z|H1sG@vpF8|8lYM7xByWdI{qz;`0Ij(0`iWvi$?x&3}=9EAH06X#WU59Dmy1W%~zs ze1Et7Wm(Z@dO*irHC zHQMHNqbU~O^5chJ{Iaw93`5HCJgV0p{Kjl&G|s?c;R$AEaJtmT7VM`pP<~q;@D=PZ z43M&9XP_RZKowIw@Z}6fJEFQ{WBdW1lpD8mJR@kM2NAEQ`@{s zzgvco@@jUwIY*>7--d&}Yad_*k3^o#r9#tVU!yO$HTO_h%Kk|vy>xY-XAt=cKm*U* zKg_9^PgV25LHa-{jm|zABIkw+tAAFNY z217+!4Qp+y4MYl%Xy1lII>3!QM(gYwCy&RZtkr+=n=l(!l30rltew|2`Lg^8jg2&) literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/bg/pop_env1.atl b/applications/PoP/poc/res/bg/pop_env1.atl new file mode 100644 index 0000000000000000000000000000000000000000..bfc986bf607e21e8fae1a517e460cfe0e9ef5bef GIT binary patch literal 11725 zcmeHM&2G~`5Z-;b*imOoCWH(352 zO{x9ud3B2I>lf2AEPuVM4zau%U(B$)JfDrR{4$ez{#;7=kFU>3PTT)_HF<{pe!dx< zVEOs&X=hqAu%``1q6(>&^R>uS4gyrxU-xbE6@>yK(St2i^G98V0$IUTZ7RJX0JxK3dYCa@#!ki5E49Z9cGsDX>O=a@A8Iqe- zW%kmZ{dDV;J?7>NGoJ^HIz<5w8PY)bFhtrE9x(>Z92du72oW^Iqe$Z-q>t;}z$sgddDOt)-6TFedO2m6?#0PA^Yu2tM zSHEVx(%AP<+fcj(Oidag-e9I;a&zDn=s0D%f&WfDRh^16k-`ZJ_AS#7)~l&gMcJkm z?laUP+&F4GC_G{e9xx6Lj2*CtJ2C-omHr^o~IYVQ?Y?o zwfTjZg)6c)GjzL3PHuay9@G!sl;khv&Gvewmb=UOJs;lEqk$JwJwhr6_-ik>ZqaRh z_a&c3r?jHRrpD;|p<7SmkG2++GcgY5J@m-9v}+0?=FbRf8LxfwUdwCvL)bB8yu*rb ziztN5EYHn_ge)mYtKBQg&U6?n{clOT7naMmT`n4asD&@_Df6SOk8t16?Xo}o%lkvq zUM!c3I`vVhFp#*E=#&`1IaB7rKE)uh(a}0>xUV|!^~skhU*UA}Buo4}=Oens z;lpsA`DV>1OCK}?a{{r8?mr_Y;u(B$X;&+V`D%@UZ{e1MT(2ZTq=T=9wd{#P4(@7z zy3qr5(-S4Mezc1%v?woWS0h9c;p4iuqsKyCf2%=u(0YRIYP=z;^^e3!6r(UUaZGG9 zWh}xNgkzUcupZl}NWa3i?TP$E9EUzahjoL~JMeV{ZNdYe(LvhE%yUEyGzWCt)EH0$ h%>j+8u@H^pc5&Cb#kB@G2P8fbN5bum9JKe+*T02H*<1hs literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/bg/pop_env2.atl b/applications/PoP/poc/res/bg/pop_env2.atl new file mode 100644 index 0000000000000000000000000000000000000000..c204ff5c52f75bbc9f116f8f48eb5dac9ca86e36 GIT binary patch literal 8718 zcmeHMNp2KD5Nt~b!~t1|8yqlC;Eq82fL>;}*n$H`F1(Bn@Do13FN%oB%vyRG8w(^` zbdBgGqjF!m-HadJzkb>M`}O&(>(21+uAjf0T|A(jzs@e+@ci}h^S8C#KhDqJ@&4Ph zrw?oT&u6y((+k`G{ne94#JMiI2VL4)$B!S6hiTk`(_g|bx;HZ$j+A^n9HtHBJHE$f zuaCVy{?qwzsODe4oX!VKPM;6MFf2D5u?{MpaddpDrBJ%_ui}RVJ~jM_8>C|zP5lg~ z_K~`WzVFjGPAv}o-rU-;hUf7I5fHTC51a-_Q{^8lbNBdsk$|y&#yRXETHwYm3&9@( ziu@2CF*X4icXs110vw;FEMENCLg7%y4VE3+$0Q;~h!Ak0I>Oieb}E_P&(4l7}=gImP_6DoIBGmr=LuOpa+HN@sbZ)1G)JXHNCSw6pgz&bim zi^NUn;hHCnf-tYnsEWf=Le$1T#WY5bc6<6+))ICqSJ^DjmoMofV3e?J1EKvPuWCK~IT5Eu&m-(Nj9_-gU6S1B-A#g5TqW*j2Zz zziV!Du&qiV!WIP&)?82p8t+|g+*&k*Ns;`4oN$AZD8)(S7QuE%nB9{hZcWmDtEgK7 z_u#PHtpx99sXXl-cFB5{&7)=-f{@!O2(bfsSDRP97a6&?MRJ)b)t`JgUSgpK`No=3 zkDo6eb5zGZeASwT(xsBq#C^;PAc zuUD~DeW&V=_RZoK)z3KJvi?d4SN$(A^2-1=@rT;7LFNJfgZ$C%yjJyRwA5!6zY4AJ zQh)`AnIXUBZ|s+UtM+g5n+i&a{#xZHt~HNeF){E{{EPYv^RJlyMg2AN>u=QGEdPI6 ze<+~S{?LHlOR)S9&r5hH2pkO^8(R6=pmyKH z2fht%^CNyIag-qO{7Gz8Kjx9^NBXWx1JgXd!3K^q_`^HaucMdclTW1pFY)3Pzh7BM z{Fu>c(kCD03BSf68RmdqmUAf02ay0>vF z&+=ju03$%$V2vx-1*OV?D4L55u&tF zgr&{c^P5jQyRhkh{&?uz$B(B~w_dHB`@QO}AfMMa*O0&HItTgv^0q@CFJCtIkejb} zkB}GNq+jfC=hkjm9)1}QKE5(KcjabG0uS#)iwg|Fd%vG@p?k(*?2nR@yOf{(J*i{N zEm3UDwFgic3aB;=_AeAQmDN7LA8RUyFJN7h%6q0XM@K~N=qjMdgK(6VzRZZ>ipVb~rVmE=%+NFo5Ko{BW>*{5mGq!CAA0@&zf zKBtfImhM*A^~LxMXXvhveNWe1rwbc!nyBG(iT8%Z~|yK((|m<^?<{(5(_ znHGU5W8?12bC8`3TnzFr;8HDbW%kr?j^i=a6pN2%b%wXE@;B2wOIlqnKTYerrGLy$ zfz@YN*Y`haoL2B2O>2km+~er}lz)nLtwe;j@vzz+6kpCzR5vD_5iWL}oY)=r8Bvyg4pKC7SbbUJHpr zmue+h!9!%v)MpK`KcJis(s+V#f20>#An$9*hZqH-6YQN-%(6gq^4aIR&eQBGj>haQ z-2-QIo}Yc5+Ii}GtCji)v$`_hE&OIcFKIIL0XdS1PJR;*vrL=Z&<1C!ue%d2zVue7 rL8`h)SvPuEM-U literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/bg/pop_env4.atl b/applications/PoP/poc/res/bg/pop_env4.atl new file mode 100644 index 0000000000000000000000000000000000000000..9b98786373b9f34b029e0e69b6ea6e36f83b7716 GIT binary patch literal 1230 zcmcJO%?*Pv424bU8E#co^}q@pp&L{o+?b$)bxqfC64ww1xb#UV&ink6AQ)fI#|8ec zYXi{Gv$x)xTYn^Pug_8bISrkj_dXA8*azL92Vaw`0OxE%z9tg0k4W93Gju}&3P`)_qUt7oc^WxUYq~zyNlbE zr{~YZ&HI}yZ$GZC@0a-e{oyL>H=kFZK5i})L|c!zR)24 z;3SJ9{-FLt86P3t0^W~f{}O!c`{nq%-E#aR`0nXx=jz*W<|K#opWqe-eR3rDXvfjo z-9#P)?@sJ);|HAq)X1e|!4`jN24`8p*?ybAs zt9bCS^Sh&H4$&zd+~kLB4=%iqA1x-RjkxBRp#d+T0cZRJU)B&3XHjeSd$A~>&cBEX zzJLN>MEN`jRC>xL*Jae&i|V$rUEg=6ZPx26#MT{bJeZlx~_ws4+4JHwD_c)sC$U4`BZJN5oFW|PAykM6GP)EgUAxM z-flxiJbV0R!a{_2vd?qwA3{G4r--7wGKJ~=BMlG9?#O4*Hz!t`k0P!I7m>EKngr0rRW$S)v3~% ziX|J@+2CtVynWD_rTQ~Z^me9yS4r}87J@*0EQ+8Q5l$-%npaBmc=O<7TT+q|in^;e z&d8&4Jx)C(S_jodq&azld(53EnVh#O&XN(lD!8voPsz%XOmR=gIz!wZ@;pMW@LDRH zggfEU!u?e@UEN!;FF?`o->bav`3CW~a*e9N>w_w2xLH8F!OStJ8_5`M`J>=ir?G~Y zSv5_pvyd^ZON|S4ZMQSN$m`@;r9?v;M46v*&+zr>+{XrdT~WiW7g}@{brLSuZrdr?oXQcpw)Gw7q&f_m^mpi$hm=D*^y!AfzU1d-nn<=bMs+qbmk{oWn2&fITY5Gz{CtETgo`e~drVvKl4 g4Q|r~uDEIto8jaVPqf!Ni3C_f>K)JGrB^ll1H_Lq^#A|> literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/bg/pop_wall.atl b/applications/PoP/poc/res/bg/pop_wall.atl new file mode 100644 index 0000000000000000000000000000000000000000..955659229a67a714e471fe969588940adf283266 GIT binary patch literal 11543 zcmeHNO^O{i5N>14jExygz~EWst>Tz?lV!jRWSvbQJMX%m-ezDnxk7figu(am6>^2_ zLKcBeeO0A@sq`j2j%Vx<%#&1KeWg-$N$T$DUw;1a`>&>tU;d^c9z2+)f2Q*b)MqE> zPf=f-?jE9kf4X~v`kQCc|NWWt&t6FX*B8?N>uc%1-AVtCmye%=_wL(Mu77&9xq$}98V1l z(v*-W#WTExP^SzpWNQh-TNgYqC?1}{wE}zzYCxjLj9`x#KBz$^re=R-@|oj8W-Fa@xkv$E7ld491t`!t7rpTqXf|%l$06~mMM<5j=RLUsD z3h=Hg5e>h@fOfqebo~WDuP2$>O%JET#^MHLH@%tI18hE>GVl9F=ro}f*mu&Af&m{6 z?k?k3w*Lg%^BeFHRx>`w^WjRfMEh-(fL|R*q1Fj_(>nWa=Lf9{E5ER42Bn{!f^aGLBPz)3Z=7hb8IE?aZkejS|Tbe z#CbRG_02@L8;l5kxh*pR0aDx$x+Bhc^9ZF)c2E|&m)LgBJKQ;%UN{?ulXfa}aZNA5 zTe&8(E?cHJKOHyXb- znMua>Gxe&|=S$#9JcLsl4(EKSRE#n>L@5i+J@hV|^VLRNgkak_MaJrdl#lRP;32v4 zxnYSU^J{8FJlugsBUePTyOdV3T8 z8RjKEh62VnU^BkNbMc&qmBt>6dD_oPhl9O`VvKFi^T=_-2i)yi@9nY6D_$nCTt!-C zg7+fnaZ#^-zYM$=9ZDk~-#Rp$o`SdekSf2cVA>bHksp-6_IrDWGu}Ms3AWGq!M!j| zu@#y`whgB{p~P0Jd5^cE0-0o87N0y3xtHM$QzP8WPqH`KLqA=9>OtR_b1E9mA9xjt<3W=4n9C;ApqSUeNTx%>k$(Y{2Y>JY literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid.pal b/applications/PoP/poc/res/kid/kid.pal new file mode 100644 index 0000000000000000000000000000000000000000..4a5d9abb191406344ba638146cf17da5d6a75c6f GIT binary patch literal 1024 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid10.atl b/applications/PoP/poc/res/kid/kid10.atl new file mode 100644 index 0000000000000000000000000000000000000000..fa9575268b2c95100894f72515cbac67c1a70790 GIT binary patch literal 5263 zcmdUyO>WyT5QUYZC;|it(7?Gtk!0EKy6p`LblYVY-MWaxm90bc8oi_X-u#e5Q?g~$ zS(FG&iu^SEnKwh_+t)9juDjPSf4ZCJtE;a2`9|vxZ*S%K7{rP4i_4m7n zC-T4FZys5*+;q>~w7v}e(7*$_QX0oGB^ldI+h4;l^mssLDZ`N40`9h3U_;;IUDULI zlF-x^e_>+>t|y0XR47RxGzyodJvo3qIU4a|c8%SkfMb#6IayQVNNy7B#r8omP_pAO z?J@5}5R{a%CboDa31yo0dna<8Q9HOiFw-zw)yM^WSJ{(?>^nShf$CH2-F&QXMBkCp zB8QnDNCw|w(2kFui6^N(cHeQ7!(O{Rh(UcUzDuSBp`tb5mG1(W=TzS*SA2&zWdn|h z@7M_+>#qH`EJ;0#{v{tzYOV3`?RjzMGrvxa{mdBVrY+v<4ch^!GT=Oq_k`n%WNr(| zxTClFp*rOZv4I)I#}@CEb1U0)G2@u=p3{Nivz%yVG6x5avB`<|633*rjCb_uA}z|X z0g?$)kT?bgN{VR-Hr&VLX5*a$4*Bb>?=G(e5o}J@+~HVe3=hm*maGNJKjvfXitkA- zq7MxVYpFx<(z; z=4zRn)6My>oGb#y$k?~g7II;%?gm+ROw;|Dn#o!?G*q607EIK6tlBv5MXp^gZg&?@ zZ${0S7EWr$l0U3w;T3aeQ3`e+Lkl8>YTO@`D_U4E$S~%;owdOIxDBaAGu;1y77h$b zbP#Jw0FP@1pni!g+fw5KHuKDM{2ySu@Xh5ljY&MeNm6eo)t(tmn>*^DC#RZDL8%an ziN{>6T9wNQ&Zvat4otwqWLR5=ELFHCBzNEX;o z5tC&z@a*HxZShiYYkC6cfJ5T-#!Y4{Luw)X^6uxOolzKxOEKUfL4&Z z;eDku52bYw=E!cC^@oSYf$V8EXNavIL0%NU0a3v#yALtF3#23|XNaTtG`UYH#~g`# z90UDP?V{x3#{^~=(eXIW6teHf5XF~joi#(PGm!-mF{VeZRn;(quqf22q8o=oRZ+x_ z(LSMqX6UoldBiXzD3~f=993Z!^)-lPD+LjQg$*{3LudV8?kCs~(3|07CGkG<@t=wy zJewQl6|*G04>L{d!Hj%CeWEsYjoJI4=uxek^gK?h5ExsI-DT$(-oWxsfN~$0L@YQa zX3OdXHSnOaba9eXlIo?B9v&7WL`bV)_tAm@q+@8JQCMR%x>kNumZ7MfP;Ujm0bQ{Z z!8=0wU{2!!sx1lv4Oe1j`Bjx;R*4HB4gJ;d>3UU5+MA4;lXXXlSkuLnswf%;pmOO8 zrEHF&Z?X;tRR$=)Rc*~VpfULtQiYaDqzG6)4{XmNDTP`Xu#DZ9w8TLVUMIov88?3y zU2bJRDB9Pq9P*u0LdoP zxem?B0PS}8+SR;;ye+gQh>(Eunl@g*%h_)@GG$J%=_y+JmgC^k?{WOUOpQCR$P z{esnt3lWwwFt`;O3&-ML5trSt(%8Is7?iHIBzL2D_!0{GDhJi!ImH4KN*PxJKZ_-> zYPmb{&2<+y9tvlt>KZC2$Kzh5Hk^z~y^^wi54AQ|bFJAI!?$i#`9+1JUFBhAsywhm zz4UA$2LG}P%(Gb>ML^wKbc^Q|qkVAXVb&Y0;Wk^d@FenjESF_N?#5{q%Vz zacX}o^|kEM2|m zS4~_=Dwf65&G5N%--j>@yd9X7mV2oM)N_gmDay;XU4%ggfnpgA_=Sm4Bx&mTdrod3 znR&xQGJ-#&84@fZCKfV0@G2mg5We6cuRPEE7j>(}e^68)TS$G*i_tdW9Tza}`dEq3 ON$i#3PsBbZwEqXAI&eJz literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid12.atl b/applications/PoP/poc/res/kid/kid12.atl new file mode 100644 index 0000000000000000000000000000000000000000..64e27f589d75e58e56b2848280457880c1c92234 GIT binary patch literal 6199 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 Y4_>fM6fmE~VpwUTqF9-gs3oNYh?(Z@Fe%n7_ z{QItd#Q5?3;Rz$ILwD27wQHE?=M_HuJpDMGP772pTqSlI6f}%>!zO*O-vPv_z{Eqa zVVb6q6pyNypre?k(O}#==AdCZOiFBHMzeqnPDt)-j@c+O52iR1c1)Htp3jQ8^9V$t z*`49&&}_sA#)t%M89pD4dJ>iX$#VeGzkjMcKt@ zOs5^YW2>T0sf^WPx60ipxP)D{XwF0Yc_k3?rR>i|tD1^XcX;$vhxrWl4r z6#}=$m^qei%;r?|jk=fuLJ01n^eOPkW-vU52JUe(fW|VquS4RD+>RWdJ+iR73*{WOFLljl9SO zeGET!n|I;ZzCqJcQn{lrIqsmL^q1+>{O~qyd}K!TZVHn$*Yztl~8nt~5?{tJ<<( z$nv_%Q2}lXuIMd4R zDXdXN$Iw5@)bkTl$)!;AkBT{p?9Pau0#eVynLz^en{XDhH;LO8&JcTH&|6;^JE3Kk zL#OII`;=yF30lm1YA^qWY!-<8x8joQ#=d*J)DM;58))yS0D1SgFakud2^_%gvB3y9 zb*kSe76YFnLRjrvF+uUgG8N$BHa=Vm29#tHu%xOW&C$Dw6eqAD7$X}BLA+)X64?+& zsnaR?hAyUxLs;54%Gk`dn@aB=$^X$^Mo^8><%^%iqKe)MnMBqx5DtU zGe*i*%_Df)B_h8zM^777c|FdVM~?n&EaFgt>fsrdr4(xpxk!_SQ~~nEim!MIPZ(ZE zJcDCerx`!VP#Gzwq>bfwa?Ed$YH|w>;-~T=$GAyO^AKWyT5QQ1kMw`S29OMp3wmm{`P@ts^Dvwt_lgY@+euKULod)tY}^wA1*^3j zv@2m!;3iToXu4(5IA+1{reG~Ug3<;H#KiJxDsz*<;h`H2!$pgViZuv#5f;O4DA=Id zsWNz)(16Bw*2Q!T2rSp%r{|Qfs*8MGVGv`3BXrtKF6s%160Q!zfMvSTa8$9V4N(kA zSZ-;I;+Uwx_!liz#1mQ2>VQq5^bzwmV6uesObOOfR4buf%=!!Z78nP!{pzP>Qf!4} ztet&GE58&vdf0VpD^D74x6D4{4>rK%`k#`+W6`0xg%;FsjHcS(vwE9!=YCOcf^X>2 zSlRl4YN(151dQB!_kb&;b@W(UJ@RmLosaU6x9O#_?aThNzwS`frvmvx#L!TDi?im( zB1c4}RF$t>)pJoQUMOD^=OWv|IOvQ(vunpmI~fP{N=n(q#59~`ApK~-J3kr?23j~j z8;wcdx`^Uzl&U8PEs;~Nf}L?y3}R>zt>t2_8St?1)s`39ySK$f1&gzSk?msV7GyaC z;#GBvT7)agvO%%*|g?C#1ZMC$Na#gWd+QKjQZ-UKIE0B?9vrL#e3#-dgQOYV# zlBE~+wb)><8pn`S! zsess6kq3ySW1D`Bpd-?2CRirrvMBWyDDqXMUtZ6|)VgE>`NA(OTorO;TMD2iOus~{ jn<8vsi=@b56P|tuNi)m>2I@G=t1Lv6nlj15@d&Yh=YsA< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid17.atl b/applications/PoP/poc/res/kid/kid17.atl new file mode 100644 index 0000000000000000000000000000000000000000..940aa54b86de6fc08f6e802a20bccc372875d97b GIT binary patch literal 7389 zcmchbO^(zs5QT$Szycv8Mgk2BR{Sj3a0G6E#06NfA4Dgs>6{0NBd|bREWB4gb~%Y1 zk7%$olXmi@T(;|#?R@(9;r+wm-mqi zbGiTP*;C=aUq61!`z%+(n_;q0engQaCT9~+yd^ZCdznW%GNd0Wx& z*xrwKl&NbuaM3gu&iB(g$ zqy z)E%I?V#02#r7q~e^DduMXLU z<=qGR7aYsEf*td{0L2t`DJfTI=SwPWQY=^a2F58dMGv)A%N1%VwMeHlA_7%rf#kHj zids5{_yMEbR}gcGdlU?mm153_$?n5)Me#6K5YYz#E$noMJ<9d)s_UfEjDGj92WlM% zY_wYGM8+MXMW)uAGcu+XC^M2Jya&ZKGiln3=d2ttVG*qx!5V6I3MR`!noY;*=tNS5 zy5jL(s?bE?-e#x}p-`KByLgt|65-%%ju~JvONDA=!P~B$#<&94=W?;+@`Gh-vAT9j z^G?M9DSoS;#jTx+P?U1Bfa*9Q=%Uj!#D`rvZiIMj0DeEe>`CKbB&_iUm4l`(caI^) zV26|&sn0DjCaMD)p}mO|p&C>y0^vg$)3+-%Z%;8CL4`6nzWgALv5)o1s>#q6I>T&j zauZ3cO3jhl2$AhcAF_O0i5wia2zpVsm zc{QHWAz)?FIbDrXMx-I+p9LYND*!72HflrY&5lWnCFPOyJB0~9qIkup1kGfce z=pLq=fWApvVNeF!U@DVXmLTOYFc$bkiUb!u7XJw1h+|(mf^!ywHhvO*SRxR|Qc@pE PEPd>dCSlDqv8I+k@_OgX literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid18.atl b/applications/PoP/poc/res/kid/kid18.atl new file mode 100644 index 0000000000000000000000000000000000000000..d964cdc6727e1943b2614768eceb4f1e46efea02 GIT binary patch literal 5529 zcmd6rO-|h~5XVDRRUo~ms&oNJY*^zWP^l7L5XbZLWIP|XAK$)y`Fz^_|MsKX-<_Ov-LE%ik9dB6ySvBp=es>W|D2vb z;QiC?@)pnE@2_s~{P6MO8c(!+ch~vX-;Pxi$8nmbaU2CW`rdnIbdU}M0fccA^w=MU zr(qa?s*GNh64v`8K(vTWfQ! znX+^e`nKeyPBs%SeJ}HuXd5GtoMw%D=iKVID8#c#G;2g0>kZ+y&rpZsbckuYG&8pi zvw0SUG;OL%lm_Sa?YV5mmIEJ6=n-$x2w6MHbZ6a+M>T6b}?|$#O8%+z|N?ai3@{VitWVfe!H8r4dVe z$ce<2sYv^K&50KUTW7C7t+>}wznMjxdB=wv;E;wpQ1ni@;Joy2>K^iNNX-<;`bJ~> zU17!bltRREViqNu=?=NZQSLLr!V6d_{eD3!maD_Gnei?|iYWv-8gv5VxQG{NcS*wp zF*UHZ@KlcedH3+5J!L-s6})dwJF=BxzSUw*k5xF_T{g0Of8oQ3!lJ4*M0u3t4udlv zhY%n%#L^lDphfR76YaAmTpX0-3}ZXg!P+yhdIHuc8P_wAOJ$bPv#HTb z;;oqnfQu89{oFR^SS#SUWxQ{hYsV1t6c44L#~Jz&ao|VN}whwV_T*c@5;vK_Gg}3e9Z8FP%uu z!Qk*^r;En*nAC`??q|#DyW@GMZ`j_0NzTp3RrPw z;YwblBI`?GpjEb`{DQ67;3;halYBPoO5Qe#Pl+mGslR~;Xf|Yg8aCgTr8}}!vD*>kE)X17*2zjk0ISS`7|Mekdn=8M^}=Ogo(fi G@Bac~-w7N5 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid19.atl b/applications/PoP/poc/res/kid/kid19.atl new file mode 100644 index 0000000000000000000000000000000000000000..cd127b9726231b407f894447bf25e941b71851cb GIT binary patch literal 5872 zcmcJT-L2d<5QSv~0gMC&0>m&bE*4(E9i#+zU?6Yl;u=~hFDv$t0#boXa0PBkIp@p_ z|3t0xknlkiznS6hA;sO_KYje|_4NOzFVpqit5?(X#}C)H%)kBk{)zeDuP@?1{rs%* z_U3XS|Ld3MEAy|vK7C-$be-N!`~JeTcS-PoN$0t9Wd-xH4Cu0aKGFs0*5Q|z+wEAx z4tncd_kCSPFs0Qit$G3&rN|MV61pJgs!gN9M~Y6=olZy^M%1IqNYS1QWqhKB3*3zo z*W+`V);}xP@GS$B5&iWP<0dXNPse|XS8Jed(t0C)z}6pn+~pPzc;B}>QUJ@>v>zc& za=cD&q1I87SX+Zor(d#Oy+qY|s}it-a*5i-;>H=McBI)_v97}vDSGVfV^YwBVBQQ^ zVrLoc#^4y<2mzBKDT6oQ2T{v%TN_u z#Z-+w(3{dhn61tGCO?XZRzRr386sp%kkJ;*+$EQ-lNJRC*YL>sHh>%9eSbeanck?I z!vX2%=Dz6sfb=a?_ahI)+>}8Y)YLr9#B0Kj2jY8gf+SsdDCP;5~DR+SZ-M z;M}H?oh!#VF~(jFFzdT0!|!*Ov*xEvzs1p`OF6RRka;g|_uxS}&@)9o!hVKc^u>4W z_H9wPNF63R%9${0CP#pFQl?Z6XQu+&(^J}FJ>~o~xshRZ6%qm@BaJe1V-0jvyUx|x z+nUYutQQFtDpFx}==!~x9;6W|z*jd^61g8qQH)$T3dp@4^y}VGHO+-8l!5`~n)X!k zyIX%5utFxA{0BM|@04imAG9dEjK1Zjs-j2pG|l0CU1D|&g#@u)0jvgVm@AUGAw9`? zKWGvxT&M&c&XDXHMqev%bBG?qfwh{JtA$fFDddn11_};?xi?R?QE898GJC7kL_yIa z{gfIgGg%~|+@ha}kei9f4An`=+R?Ij!J0BXU?`){_%dD2e$klEnZ9T})0o?aI&<4l zYi8r3*Oz=!OYI35G>lyHj^*Oai)WYIXg@%{Z_9Q0i~U3yHfn3oYBa)q-Rzs~GHq=s zicRhU5u-fHT5h}9KxC+`VG9d9BNP`ykHh`adL=7cC^mD6r!IOC7tnLv1x{>|a_qT? z^G4m4djPY>hMXpyjm7B0#t$FYy9u5_WkxszOL?{fG&yIg0akd%?|eU42UbImwgo&M z?F=)YZVoe_?hl!%)>ExN+$9VjY8D1h%WXU@ZswH6Zi$d8ug&2WcMZ59*fvhK;U$Or zI_Glt;^xts(bLVTi^@f|&6Gj=QioF+Na4UMWth2v>^*xlW@|%FtHFM4ees&lA4Z)V>-97M$XkXZEZh=yq9JY2E3l_=asB3?h9rVl6oFw|cE z<{dYiQ$Z^px988bRF0MMe*GN}xjbq|m-O?`2pYxCo^i6bbJcW4M+3hq4Q&c^vF}cU zyFzS~4p&94wgzpF$`Y?!*~cy}4GW0B8X{b|dn6R}TJ}Y_g-4d&@iaz1`qb6an^jf| zem~ACZald^idGv_vVQ5y6kTm9Q4bKpjTDW*F@}Sw%VCW}c4>xCSHU=*52b>r=^uZ5 B^~L}I literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid2.atl b/applications/PoP/poc/res/kid/kid2.atl new file mode 100644 index 0000000000000000000000000000000000000000..8e2f3a1a27eb04d7946c904a14f3a6e9e929093f GIT binary patch literal 8145 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 R!cK#BqUV73u&{Td{TGDd2VDRF literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid21.atl b/applications/PoP/poc/res/kid/kid21.atl new file mode 100644 index 0000000000000000000000000000000000000000..22cbda0785f07809f81e00e248e5f10927749c4c GIT binary patch literal 7409 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid24.atl b/applications/PoP/poc/res/kid/kid24.atl new file mode 100644 index 0000000000000000000000000000000000000000..57623df9b9fe6405166b1c9c435041d43349130e GIT binary patch literal 5932 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid26.atl b/applications/PoP/poc/res/kid/kid26.atl new file mode 100644 index 0000000000000000000000000000000000000000..39ee166f69f19a9361bdd9fab97d7266e2adfd1f GIT binary patch literal 5390 zcmchbO>Pu15QRHJLP#hfgOmtm5y6HHD~^D~0T7&IP`YO|(--1othhn*Uj4dlcLv0d z7j661=d#;XZBO#$(}(xZhJRna4cBikE{5Uy$(__cp5DD<|MT-p>3_Vudc*$hi`_lz zSG${A*56;g-mzx69^MX1mTAiNW^4Kt3eY3I_RBKDKcQ`E|1f=}eMZ^FK(P=4HP5ze z=|^wb3pNVov>~i)kWk|&-a3?FdtycvKC&s^K!G#S)|Uud%jvXWVJMGLr_{y=`H_yUa1n*CJ~L3;%l*TsJe~9jh79 zuPEJV64{2dp`R~Kyx?@GNa*bPaiRR3qcsyjc&Ns zi1)e~;Ko(W-F|!xkyRkiuz`}?aEttc&2!a*-6W!pq0}Zl51JnZn z9!SO|oF1WPF`1;^#A0$dxtLX)7T2-z0s2VPGDOa}N$=!<*eF1t>l|Sq;}-A z8^^=SW98F^$%*GzbisGL@(_)CxwI9kzKZXXggad<`ZV6`Uvx0coT7O(CP| z#8OwnMi!d;m0QzT0Zw$3P;H^bDEU;tok1`83&PTMHM;3orZ)OokUO}3Y3paIcz){o z;;CDRr~1s)41$fIltc39Z$^U(vo~=xNRmBtGSV43lM8*nUs7vZamqBIFIhievvYoS7`l&;y08Set~Un+!Ljup0n2IAJ@ zP_Y?kG;7Y8Ev}POm~u1R{$>WGt897jQo$q()G9wX^Eg&{K~Yr($z4E0XOu`!NF4NG zQdP49?&a?k6)_N@;b$+5qyiom&fmaOA*C5le%^|1T%h-MsYn%@_&O*S@MYzf#!VPXik6^d2X zVh;s96lo%)2%-uM`+S&?FjUy)HctfNi#^Pv0>vlX3NoS~W)tXCiwKzZbTZ*U1;Q~D pTj%kJe*UwWOvLI`kY~KPX`TLR^I$Lg>}Fqj+3o!4^AoAGMUnsj literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid27.atl b/applications/PoP/poc/res/kid/kid27.atl new file mode 100644 index 0000000000000000000000000000000000000000..03c1cffb3191c90e008fcc22f39f9c9d98eec373 GIT binary patch literal 932 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 K-V>1eI{qJUPs%y~ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid5.atl b/applications/PoP/poc/res/kid/kid5.atl new file mode 100644 index 0000000000000000000000000000000000000000..bcc0463618db1eaa0b8abfc8f53f0a55e80b70cf GIT binary patch literal 7809 zcmds5OO72y5NvM{LbiltjF3jMWqIN867OKg4Ujkh5;quYUia#~Ua{jS+=_z~5&5ag z`gNlL7SOV$sxl%!Rh60D&p-Y6!?&L=|Nis)^6~YvXUp>2XCLl3{_(~AJB~kp`Q|Oh zU%q<&p5ymlzmV~}Z(iMV{p;OJ85!Zn<@J*K=h};cachbtU@6^RHZE{qN z^1jips3}|&F(Y_Uxln3lq7iI7Z{|=n`M0)R#Xn0TMKr6*wF~b)9L1Ca7rJRyHS7d zEd6c4LDRn;V$&VsTxZw}L4r(l=BYzuA8&sS2{O*7+LN?ivtBC+5_@(rN60&Jt(1sp zQ`B+@kpo)SPwI<%svv2^f_0F0*}hjw3l^m&iX{CvxGJ$jm@7?O1;`r=L>(jzS*83N zwGg`i{-+f3)Ur{sP2IAsMs3G(H2=jdXjddzI9VG zj7A;7+gL0TLY3TL)-4cn3{}!}wTul4C}53NaOc%^zq~tbE3@J)p`NujGTmd)YNiK$ z!kO6aQ8c)bV}{(QnP73+mzJBiboPo9M#J%(u!|zaE8MI!jH=R=V(DD&8b)%(3&j{E zE?B|6u9qTFH^l}@8D*Yx21E;_s9;t)0mZ!=Qt(AaP#>j4HzH?En$Q^-BTdUg6Iv+B ziyV_s%9X+ae}Xh1biLfw8)YgICJ%|}u*SZP}i5|&S7 z@b^|(du|uWsO@AMnz<|+e?y9W{R0~}`Q0Di$m&3AXuAPtM!HvQ&ZgE@Jgc1mKWbZ! zLEkEp(h%cYg;*rW=f9;q@z8AYIp&fOLTLb*P>zBYvWWo>CK(!GeN)9qHZe93Ow0&$ z!u=MT?F_FQ|3+ycTZ3@UnCQCr)C2-`?qvK2OjM0>?B*|o(I`G&-k*McH7dTe)b{0I zEH4nIYq&q%A(sCm#VuiT8`yR-+04e@BRCb=Z8$cYif5jn zCW)S1zVmF3StnA>61@rBzdpsS<&f{~i;r*EMWplsQXsuEAW6fJBMWb1p!< z`a-lFTLvfdKy`goV66PM78ogtBUXqO%XPe3?w?xL+0*}g!p{LF=-D=TQ-02VYq%Lx zE2(T3*X0UR5Iu#hEaq~PE2`~T%0^(mVvd@NeP=K8zT(7zV|;boEKixv$~1o13cgOb z8|!+#ot%ogXKSKvfXy@qCR95b%jkkNJfnF^XI|bqB>9{(ndOW#0qN}ONRp@Mj%O#l zm!n!bO&fD{7NpV;frrNJd2-nYokNUM=ed38vf{~ZL|$$PHX$kf6pL&uvT)+^o-8jT zyOr!P-x5<0S7-0G#)z;)rsg!akb^bPSGr&BBIjUCsqC{fILpD9DusoapP=QSJE}Oc zsE8@xRkBi!=#n2a6VSLm;ibhLlO8^C5h+o3m$s`OpwcqYFWAKx<($i!_eS?c;xiA< N)+CN-PA}sD?N2^)#G?QJ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid6.atl b/applications/PoP/poc/res/kid/kid6.atl new file mode 100644 index 0000000000000000000000000000000000000000..1b2fd425038860deb5c21e455923ae72357cb8f0 GIT binary patch literal 5426 zcmc(iOK#jS42B&PNLw^Xk;ftzNU|(?gx;V)Pcp^W&Su6J>bk4mP36y*NZFnkNP~JY zmaR{eME;~TKfZta`k{OK>sNR8bavKtf8O0b;69ySKjQxJ;}yUE^67%_?=LU;e*XD} z`+r~GGmjPCbx&P+!>b=Q86P%c{Wfc>)UFga5tlYj(^LoT_QTJ?mQ*o7ks}OODWmql zhBw{bj=6pqWH^2+p z?ui(>%r%51b0a!HW~_j-u8ASDP|DG*73XU2a*MsZ+?C!Ew+R()NprW|!<*(qTJGY5 z>29&pUT%Y2>NafQzT|`!t0|CsVm6fuksfNyQ!Y`!#e%5Zshmlpythm+Y9Wt;W|gum zvcZ%90n&9LHDV5t93nJfjg~=34H#<~HWl-SFh$wc$dzE^B$GXJmJ!0{l%bw+3gw%E;7^9BxM~0i;y}ueVydo@<48RjvT$+Xr2>s^xm>brjic)Nn%EdPnaY1L zrx#RMuvwLGi3IUzX?Gi(Z+L1Y7C}`RP0>=c0tJ&2N%>c=Ib5s+J$%5iclxa2)?AG= zEaaRpVUe}$h%d-@np1doP{m=s2PM3Y(#cgFV~uE}%wGL~YuK|fo7c;aMLtc*hG|;* zmdXbl#m4G^uWB*&OKTF_I1Yp>1m8UYuq{IteTT>f)wIT1Fr1sm=j{5i=aR*ybO^wJ@;m8o?uORj_wZa%Pnyor+gv|R3F!xymxE+tkW!CF22=n8DznnG3LRb@iibS5BR!siTxN7({Toq)fVgvp&SWMlZ zLhMAVa}rm)0uJxQq)p-HaROS?@7uzEv*{R;lhvn)4GMEi*4S0s@Xm`AyAY?XI&Rid zH#cQO-7B=03wSTsQl~bNPcF7%*)aaCK@lONx`>7{OI&GoFF;TlUFq+d+cQh7#}!I; zDLaOx{eEr>3Sy(|QZS|~>A|mE#EIi~`<|}iMMkD5&(A{U=s49@q2PBlfLnVSSf*we zhTi|fe|{^|Vj8o^4*x3<^xrk@KTb;UZ`TK1i1r@ zgwM0fPE0nCs}>$y#%MHlD73PM2n7a8ZY&7dFhoTQGiEitf*sGUc0~|+-IXR+hAe9g xwA>;$@kx(Ac@3JMuTnN3ziNlw6q4J&Sd#7SD38MEo>n{Wa+7)wsjyjLH)-0kTJ z3V1PgSG_9N=h>dGUp{}h>i+-u(QThDF1qgHn>(4mUv8iI{po7+!1?#v+eglq?>6r_ z|Gc@m=lpAPEi)tBc2C{($}tRQ9HikKWE{^x(l}1XwS52>>o5+7LnQzpRRRDi%)$k< zl#R*l<_OahqrM4nF!xe>(F-P-zmkdwGQbuop=8t4V@yo|LL!2IzHcG~u#P}{O&=Ph zC!#)fyFHS%=$RS(mqh!1x07PZ57mp&@Aepzq83^R?*0K2TYmpSCg~lIr8#cPWJ8Z; zT&;8pi->6HPEfjXBu)SdA_8Qci?7Y8Ig-*hjGRP^B0z*QX$Wb}J~J>LNkwSy_6Cta zauPu74#^Sjg&gujOTb~T6(to8x>F$$_)d;^%|R8-H49n`IEfxwkib)_x83urZ@g(b zwstBqPy#$E-}TA~GYm^m%`|C(S5Ta|tuqNTg*TTx1t6DW(4AZ|B6u;0^UTXg z|Jk807+#RF+4`%cR)r{=?qP+TLjf+fgP=ht#Ng!^aVO>D3bbb-{bj5zCB#NZ zcM~inKUD{9MISYf+DWX+56T3WGSI7;F<|h|vt%!s+F3~13G)h!0%5X>i=fsUikD2` z=Y+D-8U<0J=p|(Ble5+LBJ)2KIwJ}T{I+{s>6(FFKKVAMER@Zsw2)U1bpu!am3RiE zYq`{l-sxjqaQ>CzNuj+Tg=d{s2l+Qa=Cy literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid8.atl b/applications/PoP/poc/res/kid/kid8.atl new file mode 100644 index 0000000000000000000000000000000000000000..04de057d5c4781aea37071a826ff13fb7c040e6f GIT binary patch literal 9085 zcmcJUJC5Ww5QcjL0~FwqJzy7@3e|q!ga=Cr;?veLD-ag9x^S5^&S^w)h zz5o6F{Rh^6{^8+0@BjRG_rN=^`^%@x{@T~etiUsHUAL98UPd4wxxn29pix9`;JOk6 zx6N`RFv{a#dYTir%8)ti=H*;TJ?;C|aGwvIm8}%=^t}AOEDIW4j$zwDEkIrcw=52C zx-OLcB;<98@*=nq;w9j<0HGjDOd_91UavtQxKN8A(0yC2AGd-_Aavu}$@!)7R$efn zoXmZ9N@hyU-}eT`uB*Tf@!kO=BITCm3Sz@F2+dnl!m)AI#Qj!J&QX@uZ?|@P)q3lO z-T@s1X-NXiQT6WfILK(Dap%S6G*&+0IuoWYpg2zU`} z!3J?Uf-2l21?ShaE+LFyFRvnSWo|9hVY1EyCxv5cUrY(@p0brOUbeJrU&^_cw554+ zIajVpsAc2hFdlk(zrOwG=pBi3SPt)w7-@z(jk=|89d7>ixVh$uH1V&qw5LwEo!Dj6 zz)>mSOQ;i3CaYyu@bgVIwkSB9HRFt?0ozcC?Ori69bh9Kuf*7sW1Ccp+nD1r!je#% z8Zydp;5-3)jtk06Cd9B&htbP%s;tC{vx*8+lUSA#k`j+J+LwOQgar(stqmHpIU;4< z%p*gNOOvD;_4kENo*KG2DQ=sZYSJ^Y5Ja(=sU~*!%D(iyes7=U=Csm0mpr`BVRIDD zi$h)3IZJ)Lu)E90!69hI*4+bP2-B4ZfDVMv0Ml7f$PZI{_FfY<Bo4Z9P%~Szt?Q zAQsM2`V=XJguzea%`rl*R*oww!zC>_SUZ$NpV{OH?l**J5~X&8UM~vKOMkGNa#cz% z`=uPCqU^2iU{47(U+HRhkPLZDeFsT=>#H+g1?l@)NPM+6HJ^!(2TYwX9eM9}?r6v5 znTWSZOkNy&2E}%42C>>HV`J>}gy!Y!3@L1XkdwJ0duz~L6{o1fhEfTUhPZ>Zi5*@L zUi^9xLgY-AhQrAVtU|T#7J6S9I?<-dXA0U1GwX^PUbh!5Q;_Dap;76xPa*wSacUq6 z203+n+OwwGY?S1UKpwKD%i<6M3QDqsib@)7pa5lKkbJnL?^{upO3PHvnl&X~2y@ny zL$Od|kp1Wom<{E)C?B@)6KsxyB!(?d27w>nJp+=94&|0uy1#t*+H*w`HR8EyJZiv#37j?s>ch~ zt@^0uHZcnMpjc$NBYl^4$Rz8em#Kq+>WX={g%QXz2N{I455k#v+>&^gVW~Yi?3oYr zH8V2=!PzwMCKL`^B@=*9oMHG?Jv?$KK3v|vT4p(jZd50ZDbZIN641oM$B7^wJ=zba zRxEIE-P9Zl;ta8&E(;P@@4q1GO+zWKf|%Ei6paz_eHk)->XAwd7!%S(@TtIs^rVEG zm0}EAPS9DTtSlzFhNP^lOCQFmK@J37qa~T)+%UTNdC|1lXfnei3ZDw@Q!@l|3c=SK zPZ~i^f#@HEnnCSEA^lm3|4ksDvm_6Rv0_YGQ%`Ap%o)RU#Q4n_Eh4E~3d~gU<*bjS zx5okE>5RzfJJkreyF85ZR9yISD6HxJQn!1{D*v2#t-9-ahXp?ny8eg8T($ioEmH20 zl6411H$W42w;PE)eNqW-lBG+x`DSv)+$|eJ>aNwF_VYb?^!&~sbuVtEgR;M2LGgTJ zjF^EeUrL8bt^tHEKpb3jIRTgm3u}{?lEjMiAcK(HkhP^Wf=-d4n$vh$g^>M;>5f|5 tGF?xv0eChduMCh}VJPDD22f`o=ZWF@ipd@UI9c^CN*Dr+_5S1N`4?V`tjYiY literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/kid/kid9.atl b/applications/PoP/poc/res/kid/kid9.atl new file mode 100644 index 0000000000000000000000000000000000000000..ed13db5fbed814eebf18f5d975ac0f004b013498 GIT binary patch literal 7885 zcmcgxO^+Nk5S<*5K#I_c147yyHrXUn4mrR-;KUWdFB!BwtwB4}KZD@m`h7<*MrL&F{bd{NvZVFW>&zJwCj6vD@8!_4<+hU*9~uWq<$etM}}G z`R@K5`=7r5_>%o^KYX}n|M#mmckJ07cMrSui?-7R?en~zPD8>xFUxt10|kB_s292{ zbAS(rJfUi~2(JluY=cs~fLl0X(49Bq;BZJgYTI__bvhgzP!Gq%Hce9vM-bx%ZcxCV zB;K5Cqkdz3Ioh0k5N*gfPn9Re;05~G?9iFOWnF(-ai2s2aKz7Y;0g1V*@$I6BY^1r z!&JG{RZ!%#?tvfX5FT!&c0BF(xE|sqe)3iv>_M1F84!T)_u!2Pj+0O+P7}wmMGwZB z)Q`knW}w~Sv34=8NMmzXwCP`?%EZMB)I__5(b=!66^+4+iIve%>P=+ikRc&c3c~|N zG0B>fs#aoT@f6A)syvSl6Xn6Fg3&InVnliQgi0b~Ly(fo3{v`xQi4HAM^KlyDLz3S zL?vD<=HS{Iry}UVBC=8Gh!browyicaa|&f1mEG)ii4_64j6Zv%wiUm066d6;B`>Rk zpM$oMlnJ7s0+2$L-NqC}0?kpZAtuT)cLLX`mgyzxd_DA}V0w|0=r~po><+Iay<~3N zs;j(rsva*Pp^G=#E|Ny({0B^yRtDmn~OVy)QfuqR$NH0danM8?P#J4rF5@iEQK6w5+5 zIoA^EX^bI>B0x%Q%atpiU0T6O4XNeZ;51>&ayDhCk+hge73Ha0bP5G^=?NB3@LySM zj3Ec<2_F}VjB;z)Cr(roVU5K>5xi7*R@M|~?`Mv%`~#Encr>_$!#f+vm-X8?pGgr+ zx>_>-li9C!?{BF=mx@PrO_L;fy$lei;i+8K!^q+nxpXdh5?~C=EB!JkS2>+5FKY2L z%Xt!VXG5$>G3*1iB8@naG~@)5I2{ilL^I3uH06tifuyFRaY0`ljkcv?Ez0Ec+Ik%- zW`1aRW##74ewCk98{x-UoClUjPsQ#)qjjl)I_(a;QT5^QsV~KJqnhks=QTW-I;WDk!tb6mVx&8pPPOa)JxJu};LtY1l%;6macwoO?`G!4nBe3x;zQ&^04Gn8$id$3-n>Cwv+JP#Xp47*|HWtIIe_HR24X2VLL7)`P`{`6lVJ^BVMjWgZDRk z$x&kQCp&6eWLSf&6;d`uUrwRMAkN*ez{w>>8d7DT*^!eg3>Ban;qNVI6$6u0U|{jg zk~Ji$zRci&)app{TwZSb)Eeb*t$n!L$KA_Ls^c15o;Tu_>2_?QVFK`}J55h%9S8F_ z8U1l9xk}^2SD@G!PK7fRzq$nbTbbP`9{;>xySQp>_f9|zBXRLq9*M^?-g68;q3tnF z6Ad|5uwdE7n1%5g$IF~Jw9m#I;2$?ehtxpuq%o1XjP-XHa?mukb77p)N$d1P24q_i z;M9e;W9gB^=_LJE#-&u$WuvU41f13nyZcUjNcaEu6H8IEYczKQ4du3l(q)GTd^%9a z`*QKRC!*M{mod{FUw}3OT-XSe)NG-Wsa+%|-*TKw0g=$3s!fnO9+_Nb$SeqdiJFv- z$D;!iIb5@aGu0)rMlivB~=P^0N;A!Z92lTrg@bu!Qk0k_Ld zSQjy|9^{vjUWR3(OvF#Yd?oZ53~N5jm~O*Yt#^zLtC6xHU1p$;Pg)T(59s%J96&Q} z=4l`V5x6sGNkgO-_vs+|ud^-y(P0J&OS<3e?k<*<5?>N%o|b%iri(YYew-pqOHi}$ z;~5nA8m75_D8RYWl-x&ObDk~)EIVzQjhByG-m(R9OnTYIsk~0>`YBKA{u+mLAk2H?#bJ${}QzU1*Zpp literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/0-0.png b/applications/PoP/poc/res/tiles/0-0.png new file mode 100644 index 0000000000000000000000000000000000000000..e0ed9f1ef4f9ae0e45006c41ca6da78a7a4bfc67 GIT binary patch literal 87 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVDLGFU$B>FSZx1pu0(nge|JUbv dIDnWS@SaVFSZ?75(9xz}y>~K~5 zf@6@t(TkSFH@@Gr9G VkbF1cS`^SW22WQ%mvv4FO#tv_G^zjq literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/10-0.png b/applications/PoP/poc/res/tiles/10-0.png new file mode 100644 index 0000000000000000000000000000000000000000..9592e3daae9207a283e3f186c9bb24d5f37ae35a GIT binary patch literal 631 zcmV--0*L*IP)l+j z%NJjKSYfy@L@F>wQ$a%ePbF><{@IOIF7AQd90o(%_4x4nz`6H#{@imumvjF-{3HNb z2*jWsP7J>kvUp@eEf@sDmXyHHiAnTcyn;4)=ij!;Da20u5k2DJXSJ@IK=-M0uxzY> zBuQFZB6u4WvIFi1VXTrJNQ5H#3G517L(S2{c>_K^?ZX;(9gnMAd-18KS3d!h#XM)g zk5o!)H?6clNqy5#Km@GNfMsSglw^v>%6Jkc^NPh4sB~_}+h`Y$-#mPb)y^G@D`0Cp ziLd=}{u++;$6;&m6jeY{{{{3Z76}u&I!FK{01{v%01^-kBLR?rU>FI21O&rK03;w7 zMgkxK!7vg42?&Of0A_L5z73C#)oW!a()Q1bzK@JzxV=NGCqj3$`2H5>{Ig~`N{mMA zyB9(s%BFI_cTe{Bw(0b`JKt|z0XnWKZA0+4c7fG#Jo*#wTBn9 z$bUZrnY04QP=a4+rRV~z(13||@A3KB3+xF7@VuoJ$j(hQXJ=-3Df$+aJ2qkb)gWZQ z56joDMVYk+>gu!}S#mOYBS-h!Ll{1NIbTGKgsE zFfcMQG7uw9bq6?hO=QrFh`~^VOPuNs;1m|YstA`j)g3SpfN4N%9d!T_UA9WcOvY19D>qYfBgz%=RrhEWF$Fkl*W0K=#Q1{g4n zI)Gu+0Rs$}MjgN~>VN?TOe5X_EXbTiRt5~BI=XPaoShScc1$9MrVn@TFuXW_nn5EZ z63oA}U>1X3LK>1mUW(sV_=5${u>X|6%=joY%5;<-3N}gVe5pdkxdc2xe!A(Tk5JM*T+1Q_lf>8qGeSYE4x3V;fL3b0fFRDfq# zDgY|LGb|MV72p|`3V;gm3`+$-1$c&~0-yps!~XXIf~AX3RgckLZulH4kk}zCDUd^hV9I^QiU?UQv?`;h<$_tR^+_893vjQJfCD?X6l@#aj2#!Q= zof`9Vb6^FAw5GcS`1Y|G(_|H%f<5+SAhI_lE?~$G8RBvbwzaH?n+V9Qd-pT3wV5JDt%Fs_ZAHNE-HFT*lm z*u{s86^hKqv4H=+BWS)?NVbTh`%Q&$Zn)6=6YIJqlr*zAxY2dFSZ!b6sG8k|$J9Y`Q zEZni~R0!+*-p12!cE;+9O`l!J&LN=S(7?dR#Da^_=x;FZ9@GBJmr9;MLm50>{an^L HB{Ts5`^X^_ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/14-0.png b/applications/PoP/poc/res/tiles/14-0.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc421ce38db5cea8e15e9976e5a2b3ff4dd5c65 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsm-1)jv*Cu-cC20Lf3yW%mzc<$bSzl%SwwqQ>&l)v+unXV;8}D|V`)N)eO!BfWqn1;d;w?f z158O3m5nQsRnEG7zr63hzxzj?M)78&qol`;+0KaZqZU6uP literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/15-0.png b/applications/PoP/poc/res/tiles/15-0.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf07093e23ecde8ae1f757c4ca0d211a28d627e GIT binary patch literal 832 zcmV-G1Hb%U{1&i}*qoag-SeSVyC?gsZ^02sle zgr<+|+_$_GzkXf{7CejP8$dXFpq*0TWqk>h?-j^8+D>vq{Vixw6hiDEA!84(O_0Ur zS_eS!AP4J$lF{E+!{sK`!`Kjc1k$ifSbYn&r(D4L@GLWfMm-EWu^X%<;p~A(>^je2 zS^?E~Ke{`v!N%4R(eixQJNhi+A!Hc1xCJ96<2C|A($K6py=28L0tg8|$nC$6e*(rv zdY2cVz3D7If2bqz@YgOly9QbB3@jvB>+mAJCf>L-xYgKaq6JOx-izfSrhlIY#DMYm49~O@+iehKvW5 zO;G(*a@S7lG%yE_Hv&19hmUr?8RO3pxOCk^$ng(ybcvRj@B>;N87Gt9`osyu? z^qCbgP9tg0RS1MOFd7Z`Hq>EuV@o~kzmP%g<6Q3KPfdD$d zL1ARxAeGz<3)3{orjeVLCX$t|ZE^mZ&Svy7JwfODu>XC(&;NJ+=REws3;-KAl#u-? zKh)1j@N3l?5O6HQHGt5*F*`Sn_RcE28*W5dX&3FY*{rm0{8JAI5g{|}9M*XTtxg6W zHI~C)x)ETQ#lGHwI}nK^C@!c&T+Bgq=r1}gxC6k>jLV3PDqS{V@a+R=BKOj|Iw&17 z|G3qA1ny#~!-T160}St*U>IqJtBV&>l1{AcA+%b|@RX=fn0pKB!*`>lt#WMv#9}{$ zs&~*jTTXl)SX%&cJR}OI2fbdCh_d~9#|*TzokT`z6$*22B4$I0)7_E3X5icR z30N$1v>p2DGx4(`W7%aHi{1qaRJKX?l ze6YI=Q)fZy8;yw2JhZl-qMp?6eiN~8B*h=Y{rY|McHB7j917VM1O=wSIHsrOmCa_u z#N_!FY(P^V1aeFed)H0l#fP7Olp7X^X91!nUMjHa%IF&)A~a_M@u95r^%|1m=l zs7zjf)gde-o7UBV8DN;Duw*IylQ4~U!_{RSG?B&Fl~ax0fm$dPT58#u#=4;hNPx_L z6F!)9uvq7)n_~Ne^UDz7VjzpfY&T+EW)_ph1z^z7aui=?jI=#FI|WaP5<-C+eWpXh z4Uo&?X?9+|s)kaYNav~KNf0Lf3yW%mzc<$bSzl%SwwqQ>&l)v+unXV;8}D|V`)N)eO!BfWqn1;d;w?f z158O3m5nQsRnEG7zr63hzxzj?M)78&qol`;+0KaZqZU6uP literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/18-0.png b/applications/PoP/poc/res/tiles/18-0.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc421ce38db5cea8e15e9976e5a2b3ff4dd5c65 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsm-1)jv*Cu-cC20Lf3yW%mzc<$bSzl%SwwqQ>&l)v+unXV;8}D|V`)N)eO!BfWqn1;d;w?f z158O3m5nQsRnEG7zr63hzxzj?M)78&qol`;+0KaZqZU6uP literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/19-0.png b/applications/PoP/poc/res/tiles/19-0.png new file mode 100644 index 0000000000000000000000000000000000000000..c7dc66421b917e093cc1fabde0edbdca1d211858 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsp+0Bjv*Cu-d^3vd)R@;IdIm6 z2=mUFE`Es(d>rRKe>}v$?sa9&EXmTFQLk4Y|9ku$bidV$iR8w_`B6RsS*$0UBs%-q4HWg}yT!;KZqds+3i zeBZ=&9I-zr9koj(!EGDM0iY8ZJYD@<);T3K F0RZqyP?rDz literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/1A-0.png b/applications/PoP/poc/res/tiles/1A-0.png new file mode 100644 index 0000000000000000000000000000000000000000..7fdc838795d8867deb212aecc661325cb6b5cf97 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsi~eWjv*Cu-d^3v+u*?C9GINC zW_HV@gBmlRd~4!VV2#lGkj;5&`h*ir?5}st{c)i1&FQY!;sOV zK~lJ3Kl_#2@hfc-o``h>?p0uvJ=DW_;6C#z=@YsEi));jq*{P#SZ0KpPl#UN_Op$} zgu_^kfzx1hp2BvA-cP)o83JiO3_=NEcOCK?XZ;n9sXe8V;D0ej0_Z>nPgg&ebxsLQ E0F78nwg3PC literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/1B-0.png b/applications/PoP/poc/res/tiles/1B-0.png new file mode 100644 index 0000000000000000000000000000000000000000..adae927213217795790097854f8042c1a81f11ed GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsp+0Bjv*Cu-cH@fdBi}(S@h%a zPb!-E8>Y-WQhhhKz(pZIaPPHg6$u;oIOaTm{`<+j+W6y_&o!Qidj0K^7{M5#aArkgUZeCa zX>A3kmPFQoMuWv{yIAuw^4BzQ2)2njupa7>dLg!>)BYg8`6`tJ*QUasKqoSIy85}S Ib4q9e0EKE$Pyhe` literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/1C-0.png b/applications/PoP/poc/res/tiles/1C-0.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4b7796b6a1ab16d586fe02ce4cf76c4c5f5631 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsXk8^$B>FSZ>JvQWHuCFE=@XJ zr25?_u`*8eo%TW1BRU!nX13RT*FZE8i!0I?hrjJ27fw_JoR literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/1D-0.png b/applications/PoP/poc/res/tiles/1D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e1fdf39f2da7fa1d5d6ee942a5c429afad9657 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsa8)H$B>FSZ?7KYY;X{8z9_M5 z&DtZ2x8}unsao(nc~GCuE6?)$!i3Lz-kqyn>X^OO|NQ&CsjV{^1O?KVzI<<(B~x9$ z_F}_P-HZ)~d>S}7KEG$qD(Z>8kO);G5;i&cP7|N@8J3O)1K|Z5X30DjD~U){b6{zi k$FVdQ&MBb@0ES>l3;+NC literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/1E-0.png b/applications/PoP/poc/res/tiles/1E-0.png new file mode 100644 index 0000000000000000000000000000000000000000..1a18c880183c03c2d74bdd55ee725b8dd83614f7 GIT binary patch literal 698 zcmV;r0!96aP)@DS=wH5L6nvBHeCsQ3l-YOpn?*l7wF?kf+Xx?5ZJ2( zy+9$6eh7v&O(PazQkZ_|jXp$zFI#JIpUvCUVpH7Cht41F&i|Y}=RCi=d)S5Z=m5mQ zQIh)6?{DUke9hJ4VBjpq8$e3!{D_Pp5W0tsp)g+5ozuo0t&J$P6lwj+qV@2v+dlsQ zhdZvoYjdD;I6Ngt*6VT2djwg31ebOkLfzV}SxJ-;z*18laxE#7plB?H`u59OzrpQ; zqd>_WfSjv6z(dkp9)Yt!!3no-UI29JyFmOE^AzKn9RdIX00Mvs00;oiVgdjHfU}qY zfB@huCIBD+IEx7Y2msDv0ssPlv)F$RAWfPlLt(VOc}l+T%OG6KY~6)CFt<&+;6Zr91H2q=qx3LkN4&K6ObOHsH}^h5-`7F=^YYc?pM4y_?&LnHDc*<%_dfD!N%p`h4|yfuf|1Dh>;dBcsO(CN zf$7xKejpbAg?Ky>r`Iv$8I0s*rboWPq|Hf&&kkTyVKE+mctu`qJ%uZ0#FSaA=p24c z{?5R2Tt<807*qoM6N<$f~}8CHUIzs literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/1F-0.png b/applications/PoP/poc/res/tiles/1F-0.png new file mode 100644 index 0000000000000000000000000000000000000000..92f75ea4aa0a8aa25869f74fd0b0dbe128a4af3a GIT binary patch literal 478 zcmV<40U`d0P)6#f4DmmzlhGC04<%7a0XUx?M*x)3j)<_@4IV4|yS zqYl6Vm>32aFpWBZVblQw446h8z%c570R~K?4qzB{zyJfLQ3o)LI$(eS)2IU&MjbG~ zfN9hL45JPhV8Ar$0ESTq3@~6CR1VNz)WZ;mG%5}Prc3*YG6-26v3f8Zz{bqX@b~{e zhEp#dF$`D~XRL9bh3T52jB(e@L<6WH|s43P%_Sfq(!1)5Za2 z5^@YojEoE?&_V&(LeLc7?|*-3;{aS~ftQV);m(IQVD{#_*BBTX7#V2h0815F!TTTO zreT`dAle8uE{Q8Cu4zs>Iy3R^lxa>xX!C;JAMU(!-h21n-#zEP`EU;fKnWJbXMQAC z6Iq$uHK!0XSQgDT0DrAhF7Cx6aW58R%eZ{l4?(+aH5Y+t=^`q+`fxuk61R^FkX7II z*C$^GF#BZ*alUS#DGJs`2E^BB3LJNACyi09WB`Yq?Ah6bL$mY9Z|lM(fuH69q_Sls zS2Q9^7z|T=UCjb00i-^CiKGB`c-ojlQ{sO&9*cX?+&@Y-Us-22479XxHzr(j0H-{h zN&D~q326=h901FrIRJ0~EQ{sY^gc%%aIhvm zbNoLzGq?I&y1W9pQi(%*?2ua7v>squx=3F3wzVL~_5vq+L>E-304NGH&0wgc&D;3# zFj4B6m?8FQPZx4sQ7DifdlWi+9u!I?+DFF0qbQO>(eN1Rdxn0bfXVH}ipJHQ&lE0f zj0}h^S18f+VT9NIE5irryMMgvrfnbPvG=*$oFnPxKwcP_KXzPNZjy9<&sS{K3yLBuCXNoG}YH5ON zVF5_5X+c>>HyJN8GKlmSIN2e#s8Y3X0LtGLuhs4Ltq@bZCIDML@*n6c0Lf3yW%mzc<$bSzl%SwwqQ>&l)v+unXV;8}D|V`)N)eO!BfWqn1;d;w?f z158O3m5nQsRnEG7zr63hzxzj?M)78&qol`;+0KaZqZU6uP literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/4-0.png b/applications/PoP/poc/res/tiles/4-0.png new file mode 100644 index 0000000000000000000000000000000000000000..49356c3124b6a8d43047b0590d2c41de262765fb GIT binary patch literal 764 zcmV3_HEN2vLb(YewXS z5-f48)HEgi(PrAJ=!O<+n_vlw3G!?X`Sn76`D>)at_2|q`32gI z!}vTpiP(shvV_kPuvqQ5QmThW9SPaL0}^$wOqiImBJ)-=ttY3g$S!Qb<@D{4MIa$+ z6+M0so>uzL!zC8ycR)g~Jq$S@vZQc%5b1S9kqU_$+~9T{n&=g*sa`sy)H*Pztb%4)mMH zpvu)D;Zzmw*7nfxo0T2(mwxJ{XBeNbAgiDS>Wfd2oL39Oz?X#!^qWT!wA{xz;N_u2 zLFR4zXLY= zOarx~wg-*vgBTf`!jt@c$Wnzt*Y*J~IzHm-lx5B$FAbky8XkAnlhZc3bCSHv0s;Qs zFb7E3vW+%D&npb5Ekr*jBC%4lNt$)wiOi( uMhJoe7mh@^ETE9O=PaAoRDenPcfJALlI3F1`=~bn0000JDg)oye&9)*jw9BGyVrskXZp_KuG+iH}c7o>rFns6Cob&(9`M#N#j{;x> zi{dkvLFExv%q6>OK!at`YyxH#Lv+Txo5#%3@)GKp&qeQ*%lAU8D_9`0i6od87ve%{WguZTrrlrK(~xBqqX z1pvtj8_f9+5AlHN?Syp!%B!BBweuZ`dta!rZmpAbYv80b5V<9fPGefKk zux7PA$^U=%C!{$5Z~!ce<^aF}uq>Jb00+RbXbu1z0L!8|0B`^-i{=2p0kABZ0{{oW zvSFz{*&((Gm&l0ASls~PXrU&#CwMelDLY>mIUPZ~F!vhERt;faM9!$^Z$jL1AMyBz^=UJqm zeTZb{^s<}DUcc@gpK=GUhcpZ3>G=cM(Lfx?XhMEj8@iu=#FmY&NIx6^yA=Xdmqac5 zM#Ys~hz|3_4S5XqgnJQilXTwN})llWGX3i7MnBo+#IP+W+`$cHI3+*es{SU5S@qCggc zjB_odW5VN6v*2QO@ygq;~4>4q~N9fa07`vk6U6 zD9aDS{*4^8HSckC6d%Gh8-n{tQ$#6$)fNq-GtChSelS^ znN-?A5HEBStR~@%U|OJHDVS)uQ@pR+m`pQL_g(b;FZMj&_dR>wAJ6xE@5TFw0NBB! zIQla(e3qBR;}cszz_SS70G!jY+3cvPAAn)_JH#A8ZFM^2Q8MpU;K@@X{Qbn}XwSpx zQ@ha6sNeL(mjOKQ{DKqtJD8twP>$r}Q18A1)7UKD_I$;=o^b>ONN~Me?cEBP%~nK4 z_@nKi2FLQYqq3^cy8yw#Qeya}r)!f`7u9WV@p$xevfLQFqj_auw7A&|}`UX;|7)P^X*9G97cf>f&@~#NrE~%8m z)iM>TYx^01DzzP1Susf183avs94Z_K+kSpx)Z96MkPs=0DJ)Rn!^bIB#k!hwL`5z? z@K!2FRQ6Sdy&@t4@mY$Y zl1PY04~%}EfwuUCD~7DL`0VT=n(hvwY&#LstJs{RSs;;*g|C9FqV5002ov JPDHLkV1jh;f_4A^ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/7-0.png b/applications/PoP/poc/res/tiles/7-0.png new file mode 100644 index 0000000000000000000000000000000000000000..e090d766713e506b320e7e669356dcc96ca50703 GIT binary patch literal 825 zcmV-91IGM`P)M$WKu!7V=UC?eSGYE_V+2(L1IXz zS(zlMl_{x%p(Ke@Q!9vxH$7L>)(1`sX;>g-lb8NZX*VzFU-Ai{$~p z15g&r1AqshES3iV4?tNg4*(v3vREDfJOE`07lO8?QB;@rlUUbfKzO7NiIbHw>`j!e zx$(V{9&%^68O8FcS)#lWjhz{5Zo1rKcE!ZcYcVrz#=(?896J%>C~zn> z5Pdo$7zW5Ih=qqljN<$bOih}|-05tN9Kk!h@wj3D^)*9a7zUZ=ccG_i0w$A%)Df-( zz`cArI|}{*o=_&$IsnXOD}qA3kd-4x`E4EY3Ke9#o$5f=g(x&P3`5&Gj=5R0BhP)cV&66r$M2WoW??6UZY(~H zPCK6YVVZ}{YR4XhFY+&Ha3ozu%oj_z5`7Q`h;k)o90N!8#Y00sh<`-OhXO~hi} zVncCh0z_f~Y&JW3-Wb3N7~~ejqPz189+mgwW!*yv9 zJK8mR62E#eg2>%l5f(J=QpU$;8>-cko?%WJ@kpk_Sak9<oI|u{}F6Bp)wdgosoIF|& zo81o8nFvxhJjw^{ZTf#7Lua%WN0l316M$7!S*3mfHt7c;A4^Z500000NkvXXu0mjf Det>Jc literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/8-0.png b/applications/PoP/poc/res/tiles/8-0.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc421ce38db5cea8e15e9976e5a2b3ff4dd5c65 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsm-1)jv*Cu-cC20Lf3yW%mzc<$bSzl%SwwqQ>&l)v+unXV;8}D|V`)N)eO!BfWqn1;d;w?f z158O3m5nQsRnEG7zr63hzxzj?M)78&qol`;+0KaZqZU6uP literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/9-0.png b/applications/PoP/poc/res/tiles/9-0.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc421ce38db5cea8e15e9976e5a2b3ff4dd5c65 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVsm-1)jv*Cu-cC20Lf3yW%mzc<$bSzl%SwwqQ>&l)v+unXV;8}D|V`)N)eO!BfWqn1;d;w?f z158O3m5nQsRnEG7zr63hzxzj?M)78&qol`;+0KaZqZU6uP literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/A-0.png b/applications/PoP/poc/res/tiles/A-0.png new file mode 100644 index 0000000000000000000000000000000000000000..85e7a559fd3916beef1f9156bd7627b25666d481 GIT binary patch literal 715 zcmV;+0yO=JP)L-72XqcK5*~3=l*{8{yz7fd+)PFoyhd0*<>bT-4t}XgJJ9ptq|Zm8SK0`)ojZfoNm`(Qp)74wk~z z)i1q(`?(J#+gCzWya+*G2(f5fn%8iO-cB$4&d+#b@nAGC29Mp3wcA!e5`lOufnm=G zPTsGheYe#|hheS-$kax^V+d+pK{8NOu14Pr-<%fzNx=Eki{h=zlL3WNj(JOR@v$!` z%@EWyRQwV&x7FhR`w~hGk-Q=om<9o32p0esU<}~`-~x;xTmW2vF@y_%3owRo0dN7v z5H0{Nz!<^>zy%m1PwiaZ+=@c|Qa}LW6LHj>uSB62Y5 z7mz7rI6nD%cF6Sw4=z|ye{WxUf$OiRz_OCXboo={nF8w4d;|wZ5&9ISBI9FG)7gU$p>{(ec@zfh4~(v6~@R zrXWx*%t`hILCF3!)K+Tf9FmCGYAZC~YsC|E$tG7IKMGEV%qCEM)`*^meP}uMh_=-Y xM!J7>xf<=KIIVmXp_1fJya9z5%r%@Q`S}g~$K^002ovPDHLkV1jraN(=x1 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/B-0.png b/applications/PoP/poc/res/tiles/B-0.png new file mode 100644 index 0000000000000000000000000000000000000000..1c698b4400ba25899f294c219fa37338dc67bc1a GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVDNj!q$B>FSZ!b6sG8k|$J9g={ zC@z|JI)rt8Z{z7VJ7e|5rq3>9=MYeEXkcJuV!_2|^f#E-!nD7z_q{F9PzFy|KbLh* G2~7agvLK)U literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/C-0.png b/applications/PoP/poc/res/tiles/C-0.png new file mode 100644 index 0000000000000000000000000000000000000000..15824da12d9e38b7a8746482d4e44e7727a08ddf GIT binary patch literal 867 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2d_FfeO*x;TbZ%y~Q0(?>W^;`sm1 zQBl)2u(UWi=w?l1OPc7)6Mcn&)%2pqqzyAp2sDc%}elAj4~Yi4w{Vt+o}=d*A8{hIxi_h(yvNoLsaK+y2}#6|Y! zyg%K(H{J1oaZ5hq$3v5!v46cFe&x`iV@yfeoKEN7HucV4Tgg(Ty>Ef<GIp`c>>TdV3B%Y2_jHp=Td7liE6k>atH@4VV} zhHcHn`PUg|^1iW^{jj$%p{|AX)OnGa{s*ibTUTj|8c6kg+4DB?a!k%N+Xp*ub1Tn1 ztYI^O-`ia~!ZI?s`j_8l&Vm&|+b=%eUnMN$$k4=aUVvz_Bfa_DBQcXFA(waTU&U@z zUoNFRIdsBm(f>AuZ+Y(~HGU18{o-Ju`3tF};>R0iUAg;bL&Sq}p6xsjeSc|7xT!r9 zea<{z`Csv*xb_;ufXnO#Jco?0PPpay(QL7ZaPwmW-S~BONV=m4J?|tSz|9}Ax zlV8iD=urJ-YCNZfGtR7bOo-Bu5%H_oV_xpp@LRjScYTjqrtX!;wP}(%vN{WWzBO+? zCHlB_v4*Gak$uNKSM2-6S9nx&#tV7pv!$|1o33oR({}OXQdTDawuP~md)8flYtVBx zB3NQwj&!C*f1tzkrK}qjBwqfp{66_*r{=BBj~yRczLf}0>C~E4qxfhki&uZ9LVwr= z>&y37&pm(MAmPLxXBQQvU2iUK|K_YB+HAV<#rij_%8yP7_paY1XY!=R<+iZxAw9SA z=MM|I`Ux4Pk?!QK_khopR8T-$be$K#{vJ6PAZp1IFES9R?d>veCBzb(m|IbXhx zWBS^>jvY(frf*A5+t(R;k4r;1^n%Kr_Qz@I9&eU}IH?F;-2Fx)%Q3c0Rb^XHOKe9| zVbQF-8_Sxa43wHK^mtf3+nCHe@0fT)YUB@D-euRPvhG^Ed(GU{DmV6L#ufWJPSX3@ m|6I2vJ1)3UG&Z&M4^#Ja!IDnR9dm)1hr!d;&t;ucLK6VMc8H?@ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/D-0.png b/applications/PoP/poc/res/tiles/D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6fc2f1331ad600f2cf198a591a1794127f93d5 GIT binary patch literal 650 zcmV;50(Jd~P)vS;Y{|#e>_PMDU^&gra!!Qbj>U4?e(ySG@@F zg$FM^q!a`}E7n%AV8x^j^f;Hv&$aCb0cQwr0r|%yx$+Pi^hQ9EP|RBhZ7~qsw5oan9>oLm{7x1ZW^=f< zI|;?_Mbk#T`maFtdtr2&c$mCZvB`faK&cI@`0#w1pMYH`f+!w-0#gqrP#aSD2;^pF zaraa&^se>Q+XYtMLT~5Uxoudmpm!ypu5PKG!1m#z`0xINLz}t}tG(?wR`cVdTVHMf*8+_xU zaRgh34u6lAZdi`Gh=xdKtR!zXb@U&&iu|X~P-=XLAKMIn{rr+Pl%KwVKs<=an@?Cg z+5Z@Ubs@N6_f^2)#5?CygkO^%s?92+E!Nq zi4bV9pkw}OFpVa2?y!rNnLBC6KBRn5-+~%QM8d~RmgUMkpQ`|o32W<^+xDOJfdh2e z@15?07IT04-=VHqhG%^vET*N~!3S}wT%2gbc+W5z6XCxWF!q`l@41iEwll0W_O&~< kljvgcJiKNCTxS2u7wSx;pdb(PZ~y=R07*qoM6N<$f*Fn^LI3~& literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/E-0.png b/applications/PoP/poc/res/tiles/E-0.png new file mode 100644 index 0000000000000000000000000000000000000000..a468fe6cb8fb303c14d1c1a206a47f24114830aa GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^3P9|@!3HF&`%2dVDKAeK$B>FSZ!b6sG8k|$J9b&P zC@lI{pSr5){ILV6-|nmv?>>EY2Qv$YfPzB<10xeI#sPJSz-LUoEe-E~15IV{boFyt I=akR{0HL%bCIA2c literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/F-0.png b/applications/PoP/poc/res/tiles/F-0.png new file mode 100644 index 0000000000000000000000000000000000000000..5251ccbed9ad59f139df2ae5032af996a62a9eb4 GIT binary patch literal 759 zcmV5!w5qTNQecMJ zi%A=WbfJbJ4n?M=j^P6>l_o7~rkK|aPkFmF8o2Q+JU_S>&V78}@4PnW697nHh-&_p zH_*f+`K_@1AYcq(Eud01YN9isiPXvM?fc>ok(ar00-~1}Qg2saRZRh5Eud01Tn-1C zb4ulQ{Q1-H4brZhfXLSXYk{Mt`)mbRK$v9Z0tyHiL%0CA0AmOj02g2k;R4_Sj3HbA zT!1lz3xEqShHwFJ0mcw604}iF1yqYyOe8q0-w}KEB(~@yJ%0N9ZVXeVK7?f-SXMG+ zeET=s|4++j&7){9u0%#dl}8}I9Rc)OF3R{b1H*Wnaq4dYB^yHmV4MC#d)rxd8TL6V zLbFm3k^8HLbVq-Po-Oy+Ho*MwDFi{lo}xTxB16$teib&$EZhz!^d%>t+4ZLmwSd1a z$a4ggyzhL8t#MK4uWiKM!d&=;1i+A%FAFU2s;UlIEjMACwxH*FJw}_KA^cc6G*LQO zCqJS!{~Qh)uHauLyLy(cn7Exzd>NiVrtun7Yu8}J*pBhGZh796Xp{8lD7}dABWXyg zD8{|yAJtt$u)^M=hA@?nPiq0`1j6ozX)e z@j@Y8kZ)@Y7;bEX+v!3|bvZ|d>D&MCpBm@p(&7Gu1_gw4BmVyyK!eLvA*!s{FLi^li%^YI)3CBDI0%nXju zt!B(Fg1;^p>J96ugsAqS->7}oL7NFbnZEyep002ovPDHLkV1iVeR3!ia literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile00.pal b/applications/PoP/poc/res/tiles/tile00.pal new file mode 100644 index 0000000000000000000000000000000000000000..1ca55374597e1ecc7d068fae9833794a86c8d1eb GIT binary patch literal 64 ocmXYl!3_W~5P}?%A>Gn)Ngt?~B$bQ)(GyG$AQNT_OrLJc137LNaR2}S literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile01.atl b/applications/PoP/poc/res/tiles/tile01.atl new file mode 100644 index 0000000000000000000000000000000000000000..854bd57342ecb08e1012fe43319711421d60ebd4 GIT binary patch literal 2308 zcmWFza5Q9OpaWo3aA2g1MWp#ffx&@6ghC)X3I;R;#74nr2#kinXb6mkz-S1N5dr`< Co3Gjc literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile01.pal b/applications/PoP/poc/res/tiles/tile01.pal new file mode 100644 index 0000000000000000000000000000000000000000..d15dee2e4a7ac6cc687b3bfa691ff026ae304b2b GIT binary patch literal 76 zcmXYlF%bYD3Fd<+)?{Bp*pic#(hP6O$aaRp6q*F1&CWVjs;F HYJKPeZ#f$4 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile01.raw b/applications/PoP/poc/res/tiles/tile01.raw new file mode 100644 index 0000000..f406d72 --- /dev/null +++ b/applications/PoP/poc/res/tiles/tile01.raw @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/applications/PoP/poc/res/tiles/tile01_merged.pal b/applications/PoP/poc/res/tiles/tile01_merged.pal new file mode 100644 index 0000000000000000000000000000000000000000..350006da77d206341480e161af587a306ff0ea9b GIT binary patch literal 92 zcmZQzU|?7Q#2{h?l(7QD2?3Hofsl|8hW|hu@*hY8aY)F2Apbv*52S&ZgM)*C!QuzQ ZiU)rfL_|awDyFSs2uP`5&@i%L001^a9`gVI literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile02.pal b/applications/PoP/poc/res/tiles/tile02.pal new file mode 100644 index 0000000000000000000000000000000000000000..ac7c0f1e01f61e516e1c51748f1c35746d8c3d08 GIT binary patch literal 460 zcmXYt>r2yN6vsa>uUj*BbDQ~_OXsGU8Kileb@W^zZi$B8LhgDfw8ZYyW54I zW#sl6L)X94c43-=LL(!0eh?UX&+*zY`Xe@mZ!VJ>|3`1)3nyEyV%M6a->PU^$7J)8 zc<_tr`hJ}5Hu_SFw1%cI+w1U0UQ%qZ(i8uTF0YWT!MB|2e~rrCJL%*Sc}UCAvXnckMa7Kz2^2tUJMbWmb)$sTs`G(?Gv&SNNXVk>VX`DBZ5VvhLz z4fIAkR%Zh`T>(M$fsUT1xP9FOV;?zI)k)&vZ$hydqSwBmc>)+qHPO*3^Q_PmNXza{ Fa0qMWZQ}p{ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile03.atl b/applications/PoP/poc/res/tiles/tile03.atl new file mode 100644 index 0000000000000000000000000000000000000000..e91004b76eae611cad7ab54e33ca7f9677e1f7b3 GIT binary patch literal 2308 zcmeIw!3}^Q3aADyLh(+;3&rimg1Cf;%Pt{A zTjr}*$t94TKadc$$9HSlbVLw)JtG`iI@6Uw0@6b^8^{#y(QaGLD%{@ z{>k4cRtM&iDqbWdW3TVPJF-BQ-ASISf$-WH?(R=Cf7`)YQP1Gs9iE=gSj&Bc7LTyG z8ySk+)A{R|oT5sUJO|bNUvYhyB-iPsZ|f4fFNCG67L)3seen>z(S#c8rgc6-@8$)1 zC7Ysvpnc(h@X9I1*D5+gru;L9!BT)v`f-LvNYUy@Gn&aT*)SEm>D#`PKI+gZSu)cN zL(vCX=J#kA|4C(d25q_`Z?5>bbV5>!hBwvk@lX9B_~kFd(MQTVMls|TVkxbmZtMrH c&It;=ZKP&o%Itpf-vtP-o?=$35X=Ag4~+$9$p8QV literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile05.pal b/applications/PoP/poc/res/tiles/tile05.pal new file mode 100644 index 0000000000000000000000000000000000000000..33830483186523f5979764d9987f898966ec8a9c GIT binary patch literal 444 zcmXYp|0^7D9L8T<4tHG~ciiD7Uv`);cb(n1FL6%39b2~hVAq9HlA^URt)v+uA|gd? zHnX)$5wjLX+b<}7vrYSNZl9~Y>v^8{^VIVIge9UB{{MudMo5-{5Q2z=h)9(XA|ic6 zdZ`l8>pA;N!~7;1Lly>$9ZRsEs+V&(o=9|q zpN-?4SjA|qpzYHcq3?f4NYawy@zK2U8UKf4GVN|2t?!Xh?v(ov$gnwS5C5RNcL=3g zgCahGl)OTj{iNpAitIL_O4ZBm3EjuP@hyME+BrZ>tb)MaCE4ye0=wsEbId$``xW=- zEQu*P?l$>pT-+kRzLn~+McglDD17)-Zv3Kg;XT3qE3~-jtu(g}XK#_Vgu zH8L$XFG$p;5%X<-R_hpUL{vocR z`;;0CxE~B-b+u91(aE*dbx!H^gkHQv@w9W=;gG$*skt5}{PsOcyO+e)59}9%Slis3 z@m@eIuc6YE;u{^KqPC9C*Kbhk%enb;pU|@@db3NEDGFCUWHC3la6Wbw&&Wgizwco4 z2WUv&!qU=;SpCM};a|#acADf@MQ1iM@aupRCslO6TR>+r5&QI+WNsHzqm%BrdHFYqO0A~4H%5G83uApf XXMKL+o7>ofArjj;6t{=y;zy1F5Q}jQ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile07.pal b/applications/PoP/poc/res/tiles/tile07.pal new file mode 100644 index 0000000000000000000000000000000000000000..d3ba12e714ace35967d437e028c4afb293e870bb GIT binary patch literal 460 zcmXYs-AmJP6vjWg_U1Czez`f+<;z@ZzO1%tGIPGnsdJUumyxd2g%_d_B#2&Eghh~1 z8KMPJH%U&5ys@lQoR~pLh?V7kv%Z54e9m*8!?`#QKunTsvHwFXX@u-@A%q|y5ecah zArjIjq?am5R`g(-{9prFXHH>umZ3Ff;JrJFb0A9e?-xuZMRaa2(=hV@NBE+=bJDRg zPiSElTYWXh8-29An&$H71u`p7Qa^Q{6jds9Pw(Lx>PM4)nD*s4%%>|b+VeR(dV{j- zU07TdSlpF}wYNmRf28n20Be<-(Bdqfv70ER4gxQp;2exne5H-x+zegcR!GZKP-!*P zJ|3rL;x;ya4Gs|^yzvfGu^pYs$kiXKbbNSC;iYE0<0F*!ML49_q0(vT`4y-8XB@p^ z!jNU=h%JZS{VnWmEi}EHB)j??;rJ42LmCFF1^qFF?!66q|7=mz9wf5!iBz>3l}3&K z*#vokT6~Z0;D7#*?DHOaes6MZcO9K_ROZd2ptX_QIxn4H-^(ADuzFmG)i=0@2W7?+ Lcy0}mm8!R%|vUF-0t`97`#=QEtVBo0U6} z3%PN)aU~-E&3sor^}O%aY>IPa;<-#IDBFS&gx^iiw*g z+BOFX-L2w17^6U^V)}Lky{iIcv6{$h3X8vv@^%}ZWFL8Q85O=7+{?X`ILaBhieVpb z#(xr`X2MNpI*9bRj>cSsb-=;w(*|mzj@aie@`8Nk-qR?wO4J4&3XKx~=?w9o1H#W+ RRQA>qe%Zp(SBJH~o(ld{s%%=njFdL^n*r7>t1+Dc1z??(Q8x5iQC;N)uD+ rFf*#KsupHdUgc`xoP+V9+%MjHQa+j2L_Y+U7&&sLix*Se2x;d7=%poL literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile0c.pal b/applications/PoP/poc/res/tiles/tile0c.pal new file mode 100644 index 0000000000000000000000000000000000000000..4ea9a759b1b3c5be924e94fc8bf7e7448f09f967 GIT binary patch literal 472 zcmXYt$!}6&6vcn_F~8OjkTN`mQYZpe(-bUN?0}&Il-g3iBDI!6ZBjc)V`Ab$H!e*y zF)lPFy3qx?Zd|B|t~6?lb@so(kHnjtbM85ddvh;9Q=(1H|4p-Pgk%{AA!td6mQ)EL zTGF?qmnzY&y}xyGw=#6c?Pw=m1joEwdRu1U$rQeno1KFy^N%L*rQHNF8t0!E=`TmP z@^h6=r-j|`Rn#M9Dl*^uQztebrf_#0|FDProiQSXAj6G8>|rb3As3N+kc|&Z6z`wG zEBAFM9n9RzlDV3|*kxd4zl3M7o6+k-B-Z-zB%EkTCu?s6o1d16&W7-va#MLxWc)@N zN6bd)@g%A30Zvq+EIuo6`QrsthXtKcVfywcp`4#%B7!4o!)!CL_2nYfmkY$^LL}FZ zqc`i&nG|+^tYWd7>2#@B{fBA%su7p>1Ws$Lzbj)<^>n#acD_|<{I0S1G*5UUK)lq4 z$!cWt^D?zp=TP*APz(xZALIy6_AvJ_N6(1H_SY3T-$ZI_0Gs^B(V1R)vpzQ7FHwIj L7`c`tdo#^Hx4mfc literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile0d.pal b/applications/PoP/poc/res/tiles/tile0d.pal new file mode 100644 index 0000000000000000000000000000000000000000..a6144a103771ae71397b4114a1d69ca6adc6a91d GIT binary patch literal 320 zcmXYn+bhFy7>A#=nQe@u9UM1Xvn$zb<`4@Bs~zjtwq|C#pwwi^1(P#dl2UG6aWfZ` zxL3F#MRNLY=DYIN^ZC5b+Y3+L6bDI?C<~;rXn`b^g_nhk7F4GAo1sc2 zr=!=1#bdI09C3cM(Ec0~qZM9DJoI{A6eszpjXlg-bt+*Fii{JAw5+ z@=tNhZUgb-5yrDlCYN1g&L(j9EkqB!Y&<70yA0R{o7sL%qEss=e5a|?YuNipv3egx itJB~NS@Es7i5+=ax(OqbLGCup+Cz-a;TH0bG5!GBPg1u4 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile0e.atl b/applications/PoP/poc/res/tiles/tile0e.atl new file mode 100644 index 0000000000000000000000000000000000000000..114a61f4d20c290fcd1c9d23ed7f00263c24d044 GIT binary patch literal 2308 xcmWFza5Q9OpaWo3aA2g1MWp#ffx&@6Y7~ryz-S1JhQMeDjE2By2#mN8006)3(b@n2 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile0e.raw b/applications/PoP/poc/res/tiles/tile0e.raw new file mode 100644 index 0000000..10116ff --- /dev/null +++ b/applications/PoP/poc/res/tiles/tile0e.raw @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/applications/PoP/poc/res/tiles/tile0e_merged.pal b/applications/PoP/poc/res/tiles/tile0e_merged.pal new file mode 100644 index 0000000000000000000000000000000000000000..1dcf99d34c09c768824d344589fa8c78bc731443 GIT binary patch literal 108 zcmXYlAr62r5Cli|0o7 literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile0f.pal b/applications/PoP/poc/res/tiles/tile0f.pal new file mode 100644 index 0000000000000000000000000000000000000000..32fde773752d20cb9194d6e9b3fe755ca03884c4 GIT binary patch literal 432 zcmXYt+bd!H z9Nl%HjtVCuFP;>$hTf5UUX41K{^(}wvXzn-CUTrQGVKY>Z&fk1>ZaIZprXmb+(reP z7cFSxB8bu|`1Y%f_s4ZSuQuTkY0Pg{@%d*1MvIo&Pi3U%#`E>OiT=qVL{An9WeBbs zGftPDnKciwi7JxxksRK7u~_2>3VuZQ8#~?s8@A$PzMM9oRE07p`xKU@P+FTxbgYuS z>kf|od0E(bNu(y6?WbyeKv^)mn~tqLx==y@ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile11.pal b/applications/PoP/poc/res/tiles/tile11.pal new file mode 100644 index 0000000000000000000000000000000000000000..6c1b7051a86cc26d5f49ac0f8fed0f0667a56624 GIT binary patch literal 292 zcmXYl-7AA}7=|y_OnW!-`EJa|!P;=Z=G~0VXTH+VmS2mcwgXa{B9bD-kppKtk|>H7 zwX!71!45d_QI7E6ynZWBUH5%o4}dNyg#JJDf(V)NA%wt?2t&3+2t&FdUAClfzn>}I z^7F(d-PjZ}nonhCvm3jugqNpTN-ZXWnuD#iF79vQ>}~h3p4JF13YGD2>4a*^cafO%=dee?Bz5|$#y~kCs(IY=0pnzyAcK= PE(UuW_uVBV6o(%$otMTo&8bt9IUS>!Bc0CXblRLQh@$vB4ma9JOd0 z^dTxXh!#1bXd@*e2r3Htu*F)J|HfXS1JCbr;2eOeq$$<^q1r4$s$2*mXh=juxOAD?DeubgDaYvIS60*}k%ga#~xeO5M?Q)Eu{vcEmWrK}gX zqk{(vCx{N(81$H!PJ8&UE-)Fi>}?fjQ7M?a806iPG!~ru|edsv=}u_SZi z_+bupXizB?bjmxmy!k2r8`?EYjJeQhlo<6|e!VR+e5{Kz(QfYDi1P4Of|%b$T51d4*9n@L!@s-*+npVW>oV@qBSqpa3Y%k#V(=h)RW8`Y?(B~*&XRM#Y zSO{;CmA%J_h8oRD7>4^>5B>OX*Fb~rQ#|uaxpi>mH6KZ2RM^ElU~cusZnZ729~=#be9+N qCD6{dp)T5rt>`4j??ZlEW+VHP!`O2qPpcWX`1~dLRLi^e5*s&c`DW+< literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile16.pal b/applications/PoP/poc/res/tiles/tile16.pal new file mode 100644 index 0000000000000000000000000000000000000000..4f0b5369a47bd970256b40849d9d1b7b9dc76d40 GIT binary patch literal 532 zcmXYt?`zUg6vjV8XX@6Px#^rvXU@3|Y_n~qn~F}|G&ghYySbnKsMZ$MyC9;7UiE{Z zprR;ZP!#zph@cniC)Fx~AgV?GjUAy2&w1`SoclcY9Ds_T7ApTQm0F@ml!BrtR0T>^ zSfD6X$*YnJ3u@=sgrARuQe3~!fGqtffv&gUF8mET>7_?f>_(vFSYbRlM@@96Ko3RN37AuaXU2Ka- z-qXj!RFuRi%!ps)UQV3obnBGB|OID9ye1i zH#<}+9*j?tb#-$slHhhcg+^+ZBir&fty;}zndfO{8lz6fkHRV!gAwk;)0_~UyNNVLtq!e9&6kBTX3=^n3xDXRp}OvnwROxZ{kUMaasf4-Yc>V z={V9#*sk-j<({L#TtRtiF+RJOc$t~DdH5Q<^>k$qa#sgGRMaC-!Nls9pS890^KRR;j3HP6oB3JS`97dg##$E3TKjOBnrT`NrFN*ji drovo&)nO-}j6X2K*n+{lF}aw->UxlwMEl#3}M ziBhzN<`ByHznSmKTc787pSSno0f-2a68T>u$sh?82T77h2&9B?fg~kFPKYdAketuZ zFY(|LU6yV%at#?W1y#A#ShGslZrPzS$H79~BAWC}j=N4Vs*ObbcYK6?FlCzAZQH}9 zw~?A6!D3wa!@iI1~0MibWS+Q9D_RY&@n0 NJZ3MK(iL8FBY*MMUxxqy literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile1c.pal b/applications/PoP/poc/res/tiles/tile1c.pal new file mode 100644 index 0000000000000000000000000000000000000000..482365784567ea501e6537db77604b123df6774d GIT binary patch literal 376 zcmXYs+bhEX6vsczW#-Z{_d6{~44W~w+9>2!0VPYv~?lT0I2`C8BFN za6Qs%8eyaPl;N^z^5Ts2myFX@I7ncC3X|;G(yL_N%)aY^#+)`*>UKD6e?q5;Wz06m zN#`rA`Ms!v!o)It(hiezahib6o31cU{O5)d5_Ei4Ge)xa-D zJ(ogg$M z3`sbyG8@@*J@3&ak6HUQTrOly{`;aF4JNeLLIKfpiAQ1 z{mF&rTkd(GAl1%J*Bu&FBzkQ;XZ;^Usr7g}ZwU`oaq0QNmQl}Ct(W1-6*A6^y=uF^l(X-I1`}9K zO_&T;yj>yI!-sfWK{lpNu$rBmr(ZZ&(O3xVvlqXkvB5w-cFS;YRPH7S`;sIli(F*h z7#mn(s5?R_^U7kNfV<7dxM!7Y^opQ&4Wq$AVfmizxhk2NG7*28MEHo~%?DZ?Zd&Y} zRCYcw8*SX=f5cr}=f0@szq!nQ$iJCpdk0QiI~J3Jt!RbO{y4e$8mZ|Lr>Q6WJ+qvp Mo++wFM*3p>0W@|=(EtDd literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/res/tiles/tile1f.pal b/applications/PoP/poc/res/tiles/tile1f.pal new file mode 100644 index 0000000000000000000000000000000000000000..185a9edf955bfa49f1ba6530067ecfc2782c87ba GIT binary patch literal 192 zcmZQzU|?7Q#2{h?l(7QD2?3Hofsl|8hW|hu@*hY8aY)F2Apbv*52S$@1S%45Fcd_e zV$f9yU|?tEVPIkAV33nAWpFYpXOIy$W^lErVPI$DWyp;9K%87N|DTVnl(GaEy+=VAt4ZV85zz%2}J7PSoWk`@d>PLml#1XLI- PbW<24M06PJjf)rn!Fet} literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/room.c b/applications/PoP/poc/room.c new file mode 100644 index 0000000..798728a --- /dev/null +++ b/applications/PoP/poc/room.c @@ -0,0 +1,158 @@ +/* + * room.c — вся логика комнаты, ОБЫЧНЫЙ HOME-файл (без --bank). + * + * 2026-07-15/16: изначально была банковская (--bank 1), сначала в W3 + * (huge-дефолт), потом пробовали W1 (--memory big) — оба варианта + * упирались в проблемы (W3 конфликтует с gfx_blit_part/atlas_load, + * которые сами трогают W3; W1-банк несовместим с CODE=W1 — трамплин + * сам бы уехал из W1; --memory big тесно — CODE+DATA+BSS ~20КБ, не + * лезет в один 16КБ). После замены sprintf() на ручное hex-форматирование + * пути (экономия ~2.9КБ — sprintf/vsprintf тянули за собой заметный + * кусок форматирующего кода, единственный потребитель в HOME) всё + * снова влезает в W1 обычным HOME-кодом — банк ОКАЗАЛСЯ НЕ НУЖЕН. + * Так проще: gfx_blit_part()/atlas_load() (трогают W3 транзитивно) + * теперь просто обычные HOME-функции, без всяких W1/W3-танцев. + * + * gfx_blit_part пишет ПРЯМО в текущий банк (видео-ОЗУ + теневая копия + * при GFX_BANK_NORMAL) — перманентно, не через sprite_t. Вызывающий + * код обязан позвать room_draw() на КАЖДУЮ из двух страниц дабл-буфера + * при входе в комнату, иначе heal спрайтов увидит фон только на одной + * странице. + */ +#include +#include +#include "room.h" + +/* 32x64 на тайл: 3*64+8=200 — сходится с разрешением DOS 320x200 + * (8px внизу — под полоску жизней/строку текста, не часть игрового + * поля). Уточнить точнее, когда будут готовы настоящие ассеты. */ +uint8_t room_row_h[ROOM_ROWS] = {64, 64, 64}; +tile_atlas_t tile_atlas[32]; + +int tile_atlas_load(const char *dir) +{ + static const char HEXD[] = "0123456789abcdef"; + char path[64]; + int i, count = 0; + + for (i = 0; i < 32; i++) { + /* Вручную, не sprintf("%stile%02x.atl",...) — единственный + * потребитель printf-семейства в этом приложении, тянул за + * собой ~2.9КБ форматирующего кода; формат тут фиксированный + * (2 hex-цифры). */ + char *p = path; + const char *s = dir; + while (*s) *p++ = *s++; + s = "tile"; + while (*s) *p++ = *s++; + *p++ = HEXD[(i >> 4) & 0xF]; + *p++ = HEXD[i & 0xF]; + s = ".atl"; + while (*s) *p++ = *s++; + *p = '\0'; + if (atlas_load(&tile_atlas[i].at, path) == 0) { + tile_atlas[i].loaded = 1; + count++; + } else { + tile_atlas[i].loaded = 0; + } + } + return count; +} + +void tile_atlas_free(void) +{ + int i; + + for (i = 0; i < 32; i++) { + if (tile_atlas[i].loaded) { + atlas_free(&tile_atlas[i].at); + tile_atlas[i].loaded = 0; + } + } +} + +int level_load_room(level_t *lv, uint8_t room, const char *path) +{ + FILE *f = fopen(path, "rb"); + uint8_t buf[ROOM_TILES * 2 + 3]; /* fore[30]+back[30]+start_room/pos/dir */ + int i; + + if (!f) + return -1; + if (fread(buf, 1, sizeof(buf), f) != sizeof(buf)) { + fclose(f); + return -1; + } + fclose(f); + + for (i = 0; i < ROOM_TILES; i++) { + tile_t *t = level_tile(lv, room, (uint8_t)(i % ROOM_COLS), + (uint8_t)(i / ROOM_COLS)); + t->type = buf[i]; + t->state = buf[ROOM_TILES + i]; + } + lv->start_room = buf[ROOM_TILES * 2]; + lv->start_location = buf[ROOM_TILES * 2 + 1]; + lv->start_direction = (int8_t)buf[ROOM_TILES * 2 + 2]; + return 0; +} + +/* _bgi_blit_rows_raw держит ОДИН DI на весь блит (см. её шапку) — + * вызывающий обязан резать блиты выше 16 строк на чанки, иначе окно + * DI слишком длинное (риск сорвать кадровую цепочку IRQ/CBL). Наши + * тайлы 64px высотой — режем на чанки по CHUNK_H строк. */ +#define CHUNK_H 16 + +void room_draw(level_t *lv, uint8_t room) +{ + int row, col, y = 0; + + for (row = 0; row < ROOM_ROWS; row++) { + int x = 0; + uint8_t fh = room_row_h[row]; + + for (col = 0; col < ROOM_COLS; col++) { + tile_t *t = level_tile(lv, room, (uint8_t)col, (uint8_t)row); + uint8_t type = TILE_TYPE(t->type); + + if (tile_atlas[type].loaded) { + uint8_t count = tile_atlas[type].at.count; + uint8_t variant = 0; + uint8_t is_var; + + if (count != 0) { + switch (type) { + case TILE_SPIKES: + case TILE_GATE: + case TILE_TAPESTRY: + case TILE_TAPESTRY_TOP: + case TILE_CHOPPER: + case TILE_POTION: + is_var = 1; + break; + default: + is_var = 0; + } + variant = is_var + ? (uint8_t)(t->state % count) + : (uint8_t)((room * 31u + col * 7u + row * 13u) % count); + } + const void *img = atlas_image(&tile_atlas[type].at, 0); + uint8_t sub_y; + + gfx_w0_map(tile_atlas[type].at.page); + for (sub_y = 0; sub_y < fh; sub_y = (uint8_t)(sub_y + CHUNK_H)) { + uint8_t chunk = (uint8_t)(fh - sub_y); + if (chunk > CHUNK_H) + chunk = CHUNK_H; + gfx_blit_part(x, y + sub_y, img, variant * ROOM_TILE_W, + sub_y, ROOM_TILE_W, chunk); + } + gfx_w0_unmap(); + } + x += ROOM_TILE_W; + } + y += fh; + } +} diff --git a/applications/PoP/poc/room.h b/applications/PoP/poc/room.h new file mode 100644 index 0000000..ee26e91 --- /dev/null +++ b/applications/PoP/poc/room.h @@ -0,0 +1,68 @@ +/* + * room.h — отрисовка комнаты из тайлов В РАНТАЙМЕ, через спрайтовые + * атласы (см. пересмотр PORT_PLAN.md §4, 2026-07-15). + * + * Один атлас на ТИП тайла (0x00..0x1F) — лента из N вариантов + * (кадр fw x fh, fw=ROOM_TILE_W, fh=высота ряда, где тип реально + * стоит). Для типов, где backtable — настоящее состояние + * (gate/tapestry/chomp/spike) — вариант = state; для остальных — + * простой псевдослучайный выбор по позиции (свой приём, не алгоритм + * оригинала — просто чтобы плитка не повторялась один-в-один). + * + * Загрузка "всей лентой — копии одной картинки" на старте, до + * появления настоящих вариантов — нормальная стратегия: движок/API + * не меняются, когда картинки заменятся на настоящие. + * + * Фаза 1 (PORT_PLAN.md §5): анимированные типы (факел/дверь/шипы/ + * дробилка) НЕ рисуются вообще — просто не грузим для них атлас, + * room_draw пропускает клетки без атласа. Анимация через отдельные + * sprite_t поверх фона — Фаза 2, см. §7 плана. + * + * room_draw() зовётся ОДИН РАЗ при входе в комнату, не в игровом + * цикле — бюджет кадра не затронут (gfx_w0_map/unmap на тайл — тоже + * разовая цена, не per-frame). + */ +#ifndef ROOM_H +#define ROOM_H + +#include +#include /* atlas_t, atlas_load/free, atlas_image, gfx_w0_map/unmap */ +#include "level.h" + +#define ROOM_TILE_W 32 /* ширина колонки, px (10 колонок = 320) */ + +/* Высоты рядов, px — измерено по реальным тайлам подземелья + * (PORT_PLAN.md §3: ~20/60/62) — ПОДТВЕРДИТЬ при подготовке настоящих + * ассетов, значения приблизительные. */ +extern uint8_t room_row_h[ROOM_ROWS]; + +typedef struct { + atlas_t at; + uint8_t loaded; /* 0 = атласа нет — тип не рисуется */ +} tile_atlas_t; + +/* Индекс — TILE_TYPE (0..31). */ +extern tile_atlas_t tile_atlas[32]; + +/* Грузит "{dir}tileNN.atl" (hex TILE_TYPE, 2 знака, напр. "tile01.atl" + * — Floor) — какие есть, те и грузятся. dir — префикс ПОДКЛЕИВАЕТСЯ + * как есть (без добавления разделителя) — файлы на диске лежат ПЛОСКО + * (см. соглашение других examples: "name.ext" либо "a:\\name.ext"), так что + * dir обычно "" или "a:\\". Каждый файл — ОДНА лента (idx 0) из N + * кадров fw=ROOM_TILE_W x fh=room_row_h[row] (row, где этот тип + * реально стоит) — готовится toolchain/png_strip.py + + * toolchain/mkatlas.py. Возврат: число загруженных (0..32). + * + * room.c — обычный HOME-файл, БЕЗ --bank (см. шапку room.c за + * историей: банк в W3 конфликтовал с gfx_blit_part/atlas_load, + * банк в W1 невозможен при CODE=W1 — трамплин сам уехал бы из W1; + * после чистки sprintf() всё влезает в HOME без банка вообще). */ +int tile_atlas_load(const char *dir); +void tile_atlas_free(void); + +/* Нарисовать комнату (room 1..24) — только типы с загруженным + * атласом; остальные клетки пропускаются (в т.ч. все анимированные + * типы в Фазе 1 — атлас для них просто не грузится). */ +void room_draw(level_t *lv, uint8_t room); + +#endif diff --git a/applications/PoP/poc/tools/build_room_palette.py b/applications/PoP/poc/tools/build_room_palette.py new file mode 100644 index 0000000..6ffe9e7 --- /dev/null +++ b/applications/PoP/poc/tools/build_room_palette.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +""" +build_room_palette.py — сводит kid.pal (уже собран +gen_kid_placeholder.py) + Floor + Wall тайлы в ОДНУ merged-палитру +room.pal, грузимую ОДИН раз через gfx_pal_fload (см. poc.c) — экран +показывает Kid и тайлы ОДНОВРЕМЕННО, значит их "свои" цвета обязаны +жить в одной палитре (см. --seed-pal в toolchain/png_strip.py). + +На вход — только типы, которые Фаза 1 реально рисует (room_draw +рисует только загруженные атласы; Floor/Wall — единственные твёрдые +типы с нетривиальной картинкой заглушки, см. gen_tile_placeholders.py +draw_floor_like/draw_wall_like). Остальные 30 типов из Фазы 1 +исключены полностью (не грузятся, не рисуются, PORT_PLAN.md §5) — +добавить сюда, когда понадобятся. + +Перезаписывает res/tiles/tile01.raw/.atl (Floor) и tile14.raw/.atl +(Wall, TILE_WALL=0x14 — room.c ищет "tile%02x.atl" ОБЯЗАТЕЛЬНО в hex, +не decimal!) с индексами палитры, согласованными с kid.pal; пишет +kid+floor+wall.pal рядом с kid.pal (poc.c грузит её вместо отдельной +kid.pal). +""" +import subprocess +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +PROJ_ROOT = HERE.parents[3] +TILES_DIR = HERE.parent / "res" / "tiles" +PNG_STRIP = PROJ_ROOT / "toolchain" / "png_strip.py" +MKATLAS = PROJ_ROOT / "toolchain" / "mkatlas.py" + +ROOM_TILE_W = 32 +ROOM_ROW_H = 64 + +KID_PAL = HERE / "kid.pal" +OUT_PAL = HERE / "room.pal" + +TYPES = [0x01, 0x14, 0x03, 0x13, 0x0E, 0x0B] # Floor, Wall, Pillar, Torch, Debris, Loose +# (2026-07-16: временное ограничение до 2 типов снято — корень +# вероятностного "снега" найден: tile_atlas_load()/room_draw() трогали +# W3 транзитивно (через atlas_load/gfx_blit_part) ИЗ БАНКА, который сам +# живёт в W3 — конфликт. Обе функции переехали в room_draw.c (HOME), +# см. её шапку.) + + +def run(cmd): + print("+", " ".join(str(c) for c in cmd)) + subprocess.run([str(c) for c in cmd], check=True) + + +def main(): + if not KID_PAL.exists(): + sys.exit(f"build_room_palette: нет {KID_PAL} — сначала gen_kid_placeholder.py") + + seed = KID_PAL + for t in TYPES: + base = f"tile{t:02x}" # room.c ищет "tile%02x.atl" — hex, не decimal + png = TILES_DIR / f"{t:X}-0.png" + raw = TILES_DIR / f"{base}.raw" + pal = TILES_DIR / f"{base}_merged.pal" + atl = TILES_DIR / f"{base}.atl" + + run([sys.executable, PNG_STRIP, raw, pal, png, + "--canvas", f"{ROOM_TILE_W}x{ROOM_ROW_H}", + "--anchor", "top-left", "--seed-pal", seed]) + run([sys.executable, MKATLAS, atl, f"{raw}:{ROOM_TILE_W}x{ROOM_ROW_H}:1x1"]) + seed = pal + + import shutil + shutil.copyfile(seed, OUT_PAL) + print(f"build_room_palette: итоговая палитра -> {OUT_PAL}") + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/poc/tools/build_tile_atlases.py b/applications/PoP/poc/tools/build_tile_atlases.py new file mode 100644 index 0000000..cb12893 --- /dev/null +++ b/applications/PoP/poc/tools/build_tile_atlases.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +build_tile_atlases.py — собирает tileNN.atl для всех 32 типов тайлов +из applications/PoP/poc/res/tiles/{TYPE:X}-{N}.png (см. +gen_tile_placeholders.py). Для каждого типа находит ВСЕ файлы +{TYPE:X}-*.png (сколько бы вариантов ни было — 1 заглушка или много +настоящих), сортирует по номеру варианта и собирает через +png_strip.py + mkatlas.py. Типы без ни одного файла — пропускаются +(room_draw их и так не рисует). + +Запускать заново каждый раз, когда добавили/заменили картинки в +res/tiles/ — атласы просто пересоберутся. +""" +import re +import subprocess +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +PROJ_ROOT = HERE.parents[3] # .../C-Compiler +TILES_DIR = HERE.parent / "res" / "tiles" +OUT_DIR = HERE.parent / "res" / "tiles" +PNG_STRIP = PROJ_ROOT / "toolchain" / "png_strip.py" +MKATLAS = PROJ_ROOT / "toolchain" / "mkatlas.py" + +ROOM_TILE_W = 32 +ROOM_ROW_H = 64 # см. room.c room_row_h — держать в синхроне + +PATTERN = re.compile(r"^([0-9A-Fa-f]+)-(\d+)\.png$") + + +def run(cmd): + print("+", " ".join(str(c) for c in cmd)) + subprocess.run([str(c) for c in cmd], check=True) + + +def main(): + by_type = {} + for f in TILES_DIR.glob("*.png"): + m = PATTERN.match(f.name) + if not m: + continue + t = int(m.group(1), 16) + n = int(m.group(2)) + by_type.setdefault(t, {})[n] = f + + if not by_type: + sys.exit(f"build_tile_atlases: нет файлов TYPE-N.png в {TILES_DIR}") + + built = 0 + for t in sorted(by_type): + variants = by_type[t] + files = [variants[n] for n in sorted(variants)] + strip_raw = OUT_DIR / f"tile{t:02x}.raw" + strip_pal = OUT_DIR / f"tile{t:02x}.pal" + atl = OUT_DIR / f"tile{t:02x}.atl" + + run([sys.executable, PNG_STRIP, strip_raw, strip_pal, + *files, "--canvas", f"{ROOM_TILE_W}x{ROOM_ROW_H}", + "--anchor", "top-left"]) + run([sys.executable, MKATLAS, atl, + f"{strip_raw}:{ROOM_TILE_W}x{ROOM_ROW_H}:{len(files)}x1"]) + built += 1 + + print(f"build_tile_atlases: собрано {built} атласов " + f"(типы: {', '.join('%X' % t for t in sorted(by_type))})") + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/poc/tools/extract_room.py b/applications/PoP/poc/tools/extract_room.py new file mode 100644 index 0000000..0027782 --- /dev/null +++ b/applications/PoP/poc/tools/extract_room.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +extract_room.py — вырезает ОДНУ комнату (foretable+backtable, 30+30 +байт) + стартовую позицию уровня (start_room/start_pos/start_dir, 3 +байта) из level-ресурса формата "res20NN.bin" (SDLPoP data/LEVELS/). + +Layout подтверждён структурным разбором: файл начинается СРАЗУ с +foretable[720] (24 комнаты * 30 тайлов), затем backtable[720] — без +заголовка (applications/PoP/docs/POP-DAT-FormatSpecifications.pdf §3.4 ++ декод байт 2026-07-15). Тайл tileOffset 0-9 = верхний ряд, 10-19 = +средний, 20-29 = нижний (см. level.h level_tile()). + +Смещение start_room/start_pos/start_dir НЕ сразу после links (как +предполагалось изначально) — сверено со структурой level_type в +applications/PoP/SDLPoP/src/types.h (маленький структурный факт, +порядок полей, не объём кода): fg[720]+bg[720]+doorlinks1[256]+ +doorlinks2[256]+roomlinks[96]+used_rooms[1]+roomxs[24]+roomys[24]+ +fill_1[15] = 2112 -> start_room(u8), start_pos(u8), start_dir(s8) +(2026-07-16, подтверждено на res2001.bin: start_room=1, start_pos=0, +start_dir=-1 — согласуется с links[room1] и решулось эмпирически +через таблицу room-links, которая идеально легла на смещение 1952). + +Использование: + extract_room.py LEVEL.bin ROOM OUT.dat + +Выход: OUT.dat — 63 байта (fore[30]+back[30]+start_room+start_pos+ +start_dir), формат level_load_room() (room.c). ROOM — 1-based (как в +файле/level.h); start_* — общеуровневые (не зависят от ROOM). +""" +import sys + +FORETABLE_OFF = 0 +BACKTABLE_OFF = 720 +ROOM_TILES = 30 +START_OFF = 720 + 720 + 256 + 256 + 96 + 1 + 24 + 24 + 15 # 2112 + + +def main(): + if len(sys.argv) != 4: + sys.exit(__doc__.strip()) + src, room_s, out = sys.argv[1], sys.argv[2], sys.argv[3] + room = int(room_s) + + data = open(src, "rb").read() + if len(data) < START_OFF + 3: + sys.exit(f"extract_room: {src}: {len(data)} Б — меньше ожидаемых " + f"{START_OFF + 3} (foretable+backtable+...+start_position)") + + base = (room - 1) * ROOM_TILES + fore = data[FORETABLE_OFF + base:FORETABLE_OFF + base + ROOM_TILES] + back = data[BACKTABLE_OFF + base:BACKTABLE_OFF + base + ROOM_TILES] + start = data[START_OFF:START_OFF + 3] + open(out, "wb").write(fore + back + start) + print(f"extract_room: комната {room} из {src} -> {out} " + f"({len(fore)}+{len(back)}+{len(start)} Б); " + f"start_room={start[0]} start_pos={start[1]} " + f"start_dir={start[2] if start[2] < 128 else start[2] - 256}") + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/poc/tools/gen_kid_placeholder.py b/applications/PoP/poc/tools/gen_kid_placeholder.py new file mode 100644 index 0000000..2f6363e --- /dev/null +++ b/applications/PoP/poc/tools/gen_kid_placeholder.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +gen_kid_placeholder.py — временная ЗАГЛУШКА персонажа для PoC. + +НЕ используем графику оригинальной Prince of Persia (авторские права +Broderbund/Ubisoft) — PoC проверяет управление/коллизии/движок, не +визуальную точность (applications/PoP/docs/PORT_PLAN.md §5). Вместо +этого берём уже лицензированный спрайт-пак, который в проекте и так +используется (examples/rpgwalk/conv_sprites.py) — +third_party/16x16-RPG-characters, один персонаж (индекс 0) той же +раскладкой: 12 кадров (dir*3+frame, dir 0=вниз/1=влево/2=вправо/3=вверх, +3 кадра маятника на направление). + +Замену на настоящую графику Кида (если/когда появится легально чистый +источник) пользователь делает сам — вне этого скрипта. + +Выход: kid.raw (16×192, 12 кадров) + kid.pal (EGA 16 + цвета спрайта). +""" +from PIL import Image + +SRC = ("../../../../third_party/16x16-RPG-characters/sprites/" + "old-style/02-bard.png") +CHAR = 0 # какой из 8 персонажей листа берём + +EGA = [ # B, G, R, pad — как в libbgi/bgi256/_bgi_mode_set.c + (0, 0, 0), (168, 0, 0), (0, 168, 0), (168, 168, 0), + (0, 0, 168), (168, 0, 168), (0, 84, 168), (168, 168, 168), + (84, 84, 84), (255, 84, 84), (84, 255, 84), (255, 255, 84), + (84, 84, 255), (255, 84, 255), (84, 255, 255), (255, 255, 255), +] +BASE = 16 + +im = Image.open(SRC).convert("RGBA") +assert im.size == (192, 128), im.size + +colors = {} + + +def px(x, y): + r, g, b, a = im.getpixel((x, y)) + if a < 128: + return 0xFF + key = (r, g, b) + if key not in colors: + assert BASE + len(colors) < 0xFF + colors[key] = BASE + len(colors) + return colors[key] + + +bx, by = (CHAR % 4) * 48, (CHAR // 4) * 64 +data = bytearray() +for d in range(4): + for f in range(3): + for y in range(16): + for x in range(16): + data.append(px(bx + f * 16 + x, by + d * 16 + y)) +open("kid.raw", "wb").write(data) + +pal = bytearray() +for b, g, r in EGA: + pal += bytes((b, g, r, 0)) +for (r, g, b) in colors: + pal += bytes((b, g, r, 0)) +open("kid.pal", "wb").write(pal) + +print(f"gen_kid_placeholder: kid.raw 16x192 (12 кадров), " + f"kid.pal EGA+{len(colors)} цветов ({len(pal)} Б)") diff --git a/applications/PoP/poc/tools/gen_tile_placeholders.py b/applications/PoP/poc/tools/gen_tile_placeholders.py new file mode 100644 index 0000000..54ac170 --- /dev/null +++ b/applications/PoP/poc/tools/gen_tile_placeholders.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +""" +gen_tile_placeholders.py — ЗАГЛУШКИ для 32 типов тайлов (не графика +оригинала!) — простая процедурная геометрия (кирпичная кладка линиями, +плоская поверхность и т.п.), чтобы визуально читалось "это стена"/ +"это пол", пока не готовы настоящие ассеты (см. room.h/room.c: +tile_atlas[TYPE], вариант 0 — "index"-картинка на тип). + +Выход: applications/PoP/poc/res/tiles/{TYPE:X}-0.png — 32 файла, +32x64 (ROOM_TILE_W x room_row_h), RGBA. + +Для твёрдых типов (Фаза 1: Floor/Wall/Pillar/PillarBot/PillarTop/ +BalconyL/R) — функциональная заглушка (см. draw_wall_like/draw_floor +ниже); Lattice-типы (0x19-0x1D) — узкая проходимая колонна той же +кладки (draw_lattice_like, см. reference-скриншот оригинала); для +остальных (декоративные/интерактивные, вне объёма Фазы 1) — просто +цвет + подпись, этого достаточно для отладки конвейера. Empty (0x00) +— полностью прозрачно (ничего не рисуется). + +По мере готовности настоящих ассетов — рядом кладутся 0-1.png, +0-2.png и т.д. (следующие варианты той же ленты), см. room.h. +""" +import colorsys +from pathlib import Path +from PIL import Image, ImageDraw, ImageFont + +W, H = 32, 64 + +NAMES = [ + "Empty", "Floor", "Spikes", "Pillar", "Gate", "StuckBtn", "DropBtn", + "Tapestry", "PillarBot", "PillarTop", "Potion", "Loose", "TapestryTop", + "Mirror", "Debris", "RaiseBtn", "ExitL", "ExitR", "Chopper", "Torch", + "Wall", "Skeleton", "Sword", "BalconyL", "BalconyR", "LatticePillar", + "LatticeSupport", "LatticeSmall", "LatticeL", "LatticeR", "TorchDebris", + "Null", +] +assert len(NAMES) == 32 + +WALL_LIKE = {0x03, 0x08, 0x09, 0x14, 0x17, 0x18} # Pillar/PillarBot/Top/Wall/BalconyL/R +FLOOR_LIKE = {0x01} # Floor +# Lattice — узкие декоративные каменные колонны (см. reference-скриншот +# оригинала 2026-07-16): Кид физически проходит МИМО них (не полноценная +# стена на всю ширину тайла) — колонна той же STONE/MORTAR кладки, но +# ýже тайла, с прозрачными полями по бокам. (col_w, align): +# align: "center"/"left"/"right". +LATTICE_LIKE = { + 0x19: (14, "center"), # LatticePillar + 0x1A: (18, "center"), # LatticeSupport — потолще + 0x1B: (10, "center"), # LatticeSmall — потоньше + 0x1C: (14, "left"), # LatticeLeft + 0x1D: (14, "right"), # LatticeRight +} +# Плоский однотонный цвет БЕЗ текста (не через антиалиасинг шрифта) — +# держит "своих" цветов в палитре по 1 на тип, иначе объединённая +# палитра (--seed-pal) быстро упирается в лимит 0xFF (см. +# pop_tile_atlas_palette_merge в памяти). +FLAT_LIKE = {0x13: (200, 90, 30, 255), # Torch — тёплый оранжевый + 0x0E: (110, 90, 70, 255), # Debris — серо-бурый + 0x0B: (140, 120, 60, 255)} # Loose — тускло-жёлтый (плита) + +STONE = (150, 150, 158, 255) +MORTAR = (90, 90, 98, 255) +GROUND = (120, 100, 80, 255) +GROUND_TOP = (170, 150, 120, 255) + +out_dir = Path(__file__).resolve().parent.parent / "res" / "tiles" +out_dir.mkdir(parents=True, exist_ok=True) + +try: + font = ImageFont.load_default() +except Exception: + font = None + + +def draw_wall_like(im): + """Кирпичная кладка — просто ряды прямоугольников со смещением + в шахматном порядке и линиями раствора между ними (обобщённый + геометрический приём, не стиль конкретной игры).""" + draw = ImageDraw.Draw(im) + draw.rectangle([0, 0, W - 1, H - 1], fill=STONE) + brick_h = 12 + brick_w = 16 + row = 0 + y = 0 + while y < H: + offset = (brick_w // 2) if (row % 2) else 0 + x = -brick_w + offset + while x < W: + draw.rectangle([x, y, x + brick_w - 2, y + brick_h - 2], + outline=MORTAR, width=1) + x += brick_w + y += brick_h + row += 1 + + +def draw_lattice_like(im, col_w, align): + """Узкая колонна кирпичной кладки шириной col_w {out_dir}") diff --git a/applications/PoP/poc/tools/kid.atl b/applications/PoP/poc/tools/kid.atl new file mode 100644 index 0000000000000000000000000000000000000000..6eff625b7fc673fa937d8c4811041f70e3ee2170 GIT binary patch literal 3332 zcmd5;TTTNp3~c}T$Zk)<5x4;o7v^Tm%=nqzm8uG;8Uc;t@ndW!5{+y1c)_d3ra_oQbj?c&VbGEUf;xy)p40L#oeMDA-(i#-jzSnHgxy^ literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/tools/kid.pal b/applications/PoP/poc/tools/kid.pal new file mode 100644 index 0000000000000000000000000000000000000000..0eeb4322b6326591fc72f2a9c012b92987df728f GIT binary patch literal 80 zcmXYlK@Gq_48!~^fPxv2fGn)R*ke-XllE{D2VmsH{9}&9^hm-=)|O=T)orONxE|(y G>25yI2^)C; literal 0 HcmV?d00001 diff --git a/applications/PoP/poc/tools/kid.raw b/applications/PoP/poc/tools/kid.raw new file mode 100644 index 0000000..7c51c15 --- /dev/null +++ b/applications/PoP/poc/tools/kid.raw @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/applications/PoP/poc/tools/room.pal b/applications/PoP/poc/tools/room.pal new file mode 100644 index 0000000000000000000000000000000000000000..b965cf3702efa0517f7b7501ef5f603a028f11d6 GIT binary patch literal 112 zcmXYlp$&jA6hxow0M%$T3Q>(ld{s%%=njFdL^n*r7>t1+Dc1z??(Q8x5iQC;N)uD+ rFf*#KsupHdUgc`xoP+V9+%MjHQa+j2L_Y+U7&&sLix*Se2x;d7=%poL literal 0 HcmV?d00001 diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile new file mode 100644 index 0000000..ddaeea4 --- /dev/null +++ b/applications/PoP/roomtest/Makefile @@ -0,0 +1,34 @@ +# roomtest — проверка порта статического фона PoP (Шаг 3): реальная +# композиция слоёв (pop_bg.c) для комнаты 1. --memory huge --gfx 256. + +PROJ_ROOT := $(abspath $(CURDIR)/../../..) +EXAMPLE := roomtest +# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе +# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge). +MEMORY ?= small +EXTRA_FLAGS ?= --gfx 256 +EXTRA_SRCS := pop_bg.c pop_kid.c pop_ctrl.c pop_map.c + +BG_DIR := $(CURDIR)/../poc/res/bg +KID_DIR := $(CURDIR)/../poc/res/kid +BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.atl \ + $(BG_DIR)/pop_env3.atl $(BG_DIR)/pop_env4.atl \ + $(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl +# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23) +# не рисуются (kid_draw пропускает незагруженную страницу). Палитра — +# ЕДИНАЯ игровая (env+wall+kid). +KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl) +KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal + +EXTRA_DATA := $(BG_DATA) $(KID_DATA) +include $(PROJ_ROOT)/app.mk + +TC := $(PROJ_ROOT)/applications/PoP/toolchain +$(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py + cd $(TC) && python3 pop_pack_bg.py +$(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py + cd $(TC) && python3 pop_pack_kid.py +kid_data.h: $(TC)/pop_extract_kid_data.py + cd $(TC) && python3 pop_extract_kid_data.py + +$(EXAMPLE).exe: pop_bg.c pop_bg.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h kid_data.h room1_data.h $(BG_DATA) $(KID_DATA) diff --git a/applications/PoP/roomtest/kid_data.h b/applications/PoP/roomtest/kid_data.h new file mode 100644 index 0000000..8f01566 --- /dev/null +++ b/applications/PoP/roomtest/kid_data.h @@ -0,0 +1,474 @@ +/* kid_data.h — данные анимации Kid (frame_table + seqtbl + + * offsets), извлечено toolchain/pop_extract_kid_data.py из + * SDLPoP. НЕ править вручную. seqtbl адресуется базой + * SEQTBL_BASE: kid_seqtbl[addr - SEQTBL_BASE]. */ +#ifndef KID_DATA_H +#define KID_DATA_H +#include + +#define SEQTBL_BASE 0x196Eu + +/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid). */ +typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe; +#define KID_NFRAMES 241 +static const kframe kid_frames[KID_NFRAMES] = { + {255, 0, 0,0x00,0x00}, + { 0, 1, 0,0xC4,0x00}, + { 1, 1, 0,0x44,0x00}, + { 2, 3, 0,0x47,0x00}, + { 3, 4, 0,0x48,0x00}, + { 4, 0, 0,0xE6,0x00}, + { 5, 0, 0,0x49,0x00}, + { 6, 0, 0,0x4A,0x00}, + { 7, 0, 0,0xC5,0x00}, + { 8, 0, 0,0x44,0x00}, + { 9, 0, 0,0x47,0x00}, + { 10, 0, 0,0x4B,0x00}, + { 11, 0, 0,0x43,0x00}, + { 12, 0, 0,0xC3,0x00}, + { 13, 0, 0,0x47,0x00}, + { 14, 0, 0,0x43,0x09}, + { 15, 0, 0,0xC3,0x00}, + { 16, 0, 0,0x44,0x00}, + { 17, 0, 0,0x46,0x00}, + { 18, 0, 0,0x48,0x00}, + { 19, 0, 0,0x89,0x00}, + { 20, 0, 0,0x0B,0x00}, + { 21, 0, 0,0x8B,0x00}, + { 22, 0, 0,0x11,0x00}, + { 23, 0, 0,0x07,0x00}, + { 24, 0, 0,0x05,0x00}, + { 25, 0, 0,0xC1,0x00}, + { 26, 0, 0,0xC6,0x00}, + { 27, 0, 0,0x43,0x00}, + { 28, 0, 0,0x48,0x00}, + { 29, 0, 0,0x42,0x00}, + { 30, 0, 0,0x42,0x00}, + { 31, 0, 0,0xC2,0x00}, + { 32, 0, 0,0xC2,0x00}, + { 33, 0, 0,0x43,0x00}, + { 34, 0, 0,0x48,0x00}, + { 35, 0, 0,0xCE,0x00}, + { 36, 0, 0,0xC1,0x00}, + { 37, 0, 0,0x45,0x00}, + { 38, 0, 0,0x8E,0x00}, + { 39, 0, 0,0x0B,0x00}, + { 40, 0, 0,0x8B,0x00}, + { 41, 0, 0,0x8A,0x00}, + { 42, 0, 0,0x01,0x00}, + { 43, 0, 0,0xC4,0x00}, + { 44, 0, 0,0xC3,0x00}, + { 45, 0, 0,0xC3,0x00}, + { 46, 0, 0,0xA5,0x00}, + { 47, 0, 0,0xA4,0x00}, + { 48, 0, 0,0x66,0x00}, + { 49, 4, 0,0x67,0x00}, + { 50, 3, 0,0x66,0x00}, + { 51, 1, 0,0x44,0x00}, + { 64, 0, 0,0xC2,0x00}, + { 65, 0, 0,0x41,0x00}, + { 66, 0, 0,0x42,0x00}, + { 67, 0, 0,0x00,0x00}, + { 68, 0, 0,0x00,0x00}, + { 69, 0, 0,0x80,0x00}, + { 70, 0, 0,0x00,0x00}, + { 71, 0, 0,0x80,0x00}, + { 72, 0, 0,0x00,0x00}, + { 73, 0, 0,0x80,0x00}, + { 74, 0, 0,0x00,0x00}, + { 75, 0, 0,0x00,0x00}, + { 76, 0, 0,0x80,0x00}, + {255, 0, 0,0x00,0x00}, + { 80, -2, 0,0x41,0x00}, + { 81, -2, 0,0x41,0x00}, + { 82, -1, 0,0xC2,0x00}, + { 83, -2, 0,0x42,0x00}, + { 84, -2, 0,0x41,0x00}, + { 85, -2, 0,0x41,0x00}, + { 86, -2, 0,0x41,0x00}, + { 87, -1, 0,0x07,0x00}, + { 88, -1, 0,0x05,0x00}, + { 89, 2, 0,0x07,0x00}, + { 90, 2, 0,0x07,0x00}, + { 91, 2, -3,0x00,0x00}, + { 92, 2, -10,0x00,0x00}, + { 93, 2, -11,0x80,0x00}, + { 94, 3, -2,0x43,0x00}, + { 95, 3, 0,0xC3,0x00}, + { 96, 3, 0,0xC3,0x00}, + { 97, 3, 0,0x63,0x00}, + { 98, 4, 0,0xE3,0x00}, + { 28, 0, 0,0x00,0x00}, + { 99, 7, -14,0x80,0x00}, + {100, 7, -12,0x80,0x00}, + {101, 4, -12,0x00,0x00}, + {102, 3, -10,0x80,0x00}, + {103, 2, -10,0x80,0x00}, + {104, 1, -10,0x80,0x00}, + {105, 0, -11,0x00,0x00}, + {106, -1, -12,0x00,0x00}, + {107, -1, -14,0x00,0x00}, + {108, -1, -14,0x00,0x00}, + {109, -1, -15,0x80,0x00}, + {110, -1, -15,0x80,0x00}, + {111, 0, -15,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {112, 0, 0,0xC6,0x00}, + {113, 0, 0,0x46,0x00}, + {114, 0, 0,0xC5,0x00}, + {115, 0, 0,0x45,0x00}, + {116, 0, 0,0xC2,0x00}, + {117, 0, 0,0xC4,0x00}, + {118, 0, 0,0xC5,0x00}, + {119, 0, 0,0x46,0x00}, + {120, 0, 0,0x47,0x00}, + {121, 0, 0,0x47,0x00}, + {122, 0, 0,0x49,0x00}, + {123, 0, 0,0xC8,0x00}, + {124, 0, 0,0xC9,0x00}, + {125, 0, 0,0x49,0x00}, + {126, 0, 0,0x45,0x00}, + {127, 2, 0,0x45,0x00}, + {128, 2, 0,0xC5,0x00}, + {129, 0, 0,0xC3,0x00}, + {255, 0, 0,0x00,0x00}, + {133, 0, 0,0x43,0x00}, + {134, 0, 0,0xC4,0x00}, + {135, 0, 0,0xC5,0x00}, + {136, 0, 0,0x48,0x00}, + {137, 0, 0,0x6C,0x00}, + {138, 0, 0,0xEF,0x00}, + {139, 0, 0,0x63,0x00}, + {140, 0, 0,0xC3,0x00}, + {141, 0, 0,0x43,0x00}, + {142, 0, 0,0x43,0x00}, + {143, 0, 0,0x44,0x00}, + {144, 0, 0,0x44,0x00}, + {172, 0, 1,0xC1,0x00}, + {173, 0, 1,0xC7,0x00}, + {145, 0, -12,0x01,0x00}, + {146, 0, -21,0x00,0x00}, + {147, 1, -26,0x80,0x00}, + {148, 4, -32,0x80,0x00}, + {149, 6, -36,0x81,0x00}, + {150, 7, -41,0x82,0x00}, + {151, 2, 17,0x42,0x00}, + {152, 4, 9,0xC4,0x00}, + {153, 4, 5,0xC9,0x00}, + {154, 4, 4,0xC8,0x00}, + {155, 5, 0,0x69,0x00}, + {156, 5, 0,0xE9,0x00}, + {157, 5, 0,0xE8,0x00}, + {158, 5, 0,0x69,0x00}, + {159, 5, 0,0x69,0x00}, + {184, 0, 2,0x80,0x10}, + {174, 0, 2,0x80,0x1A}, + {175, 3, 2,0x00,0x12}, + {176, 7, 2,0xC4,0x16}, + {177, 10, 2,0x00,0x15}, + {178, 7, 2,0x80,0x17}, + {179, 4, 2,0x80,0x19}, + {180, 0, 2,0xCE,0x18}, + {181, 0, 2,0xCD,0x0F}, + {182, 3, 2,0x00,0x14}, + {183, 3, 2,0x00,0x1F}, + {184, 0, 2,0x80,0x10}, + {185, 0, 2,0x80,0x11}, + {186, 0, 2,0x00,0x20}, + {187, 0, 2,0x80,0x21}, + {188, 2, 2,0xC3,0x22}, + { 14, 0, 0,0x43,0x00}, + {189, 7, 2,0x80,0x13}, + {190, 1, 2,0x80,0x0E}, + {191, 0, 2,0x80,0x1B}, + {181, 0, 2,0xCD,0x0F}, + {181, 0, 2,0xCD,0x0F}, + {112, 0, 0,0xC6,0x2B}, + {113, 0, 0,0x46,0x2C}, + {114, 0, 0,0xC5,0x2D}, + {115, 0, 0,0x45,0x2E}, + {114, 0, 0,0xC5,0x00}, + { 78, 0, 3,0x8A,0x00}, + { 77, 4, 3,0x87,0x00}, + {211, 0, 1,0x44,0x00}, + {212, 0, 1,0x44,0x00}, + {213, 0, 1,0x44,0x00}, + {214, 0, 1,0x47,0x00}, + {215, 0, 7,0x4B,0x00}, + {255, 0, 0,0x00,0x00}, + { 79, 4, 7,0x49,0x00}, + {130, 0, 0,0x44,0x00}, + {131, 0, 0,0x44,0x00}, + {132, 0, 2,0x44,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {192, 0, 0,0x00,0x00}, + {193, 0, 1,0x00,0x00}, + {194, 0, 0,0x80,0x00}, + {195, 0, 0,0x00,0x00}, + {196, -1, 0,0x00,0x00}, + {197, -1, 0,0x00,0x00}, + {198, -1, 0,0x00,0x00}, + {199, -4, 0,0x00,0x00}, + {200, -4, 0,0x80,0x00}, + {201, -4, 0,0x00,0x00}, + {202, -4, 0,0x00,0x00}, + {203, -4, 0,0x00,0x00}, + {204, -4, 0,0x00,0x00}, + {205, -5, 0,0x00,0x00}, + {206, -5, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {207, 0, 1,0x46,0x00}, + {208, 0, 1,0xC6,0x00}, + {209, 0, 1,0xC8,0x00}, + {210, 0, 1,0x4A,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + {255, 0, 0,0x00,0x00}, + { 52, 0, 0,0x80,0x00}, + { 53, 0, 0,0x00,0x00}, + { 54, 0, 0,0x00,0x00}, + { 55, 0, 0,0x00,0x00}, + { 56, 0, 0,0x80,0x00}, + { 57, 0, 0,0x00,0x00}, + { 58, 0, 0,0x00,0x00}, + { 59, 0, 0,0x00,0x00}, + { 60, 0, 0,0x80,0x00}, + { 61, 0, 0,0x00,0x00}, + { 62, 0, 0,0x80,0x00}, + { 63, 0, 0,0x00,0x00}, + {160, 1, 1,0xC3,0x23}, + {161, 0, 1,0x49,0x24}, + {162, 0, 1,0xC3,0x25}, + {163, 0, 1,0x49,0x26}, + {164, 0, 1,0xC3,0x27}, + {165, 1, 1,0x49,0x28}, + {166, 1, 1,0x43,0x29}, + {167, 1, 1,0xC9,0x2A}, + {168, 4, 1,0xC6,0x00}, + {169, 3, 1,0xCA,0x00}, + {170, 1, 1,0x43,0x00}, + {171, 1, 1,0xC8,0x00}, +}; + +#define KID_SEQTBL_LEN 2310 +static const uint8_t kid_seqtbl[KID_SEQTBL_LEN] = { + 0xF9,0x01,0xFF,0x81,0x19,0xF9,0x01,0x01,0x02,0x03,0x04,0xFB, + 0x08,0x05,0xFB,0x03,0x06,0xFB,0x03,0x07,0xFB,0x05,0x08,0xFB, + 0x01,0xF2,0x01,0x09,0xFB,0x02,0x0A,0xFB,0x04,0x0B,0xFB,0x05, + 0x0C,0xFB,0x02,0xF2,0x01,0x0D,0xFB,0x03,0x0E,0xFB,0x04,0xFF, + 0x81,0x19,0xF9,0x00,0x0F,0xFF,0xA0,0x19,0xF9,0x01,0xA6,0xFF, + 0xA8,0x19,0xF9,0x05,0xFB,0x0A,0xB1,0xB1,0xFB,0xF9,0xFA,0xFE, + 0xB2,0xFB,0x05,0xFA,0x02,0xA6,0xFB,0xFF,0xFF,0xD2,0x19,0xFF, + 0xD2,0x19,0xF9,0x01,0xFB,0x02,0xCF,0xD0,0xFB,0x02,0xD1,0xFB, + 0x02,0xD2,0xFB,0x03,0xF9,0x01,0xF2,0x00,0x9E,0xAA,0xAB,0xFF, + 0xD8,0x19,0xF9,0x05,0xF8,0xFF,0x00,0xAC,0xFB,0xFF,0xFA,0x01, + 0xAD,0xFB,0xFF,0xAE,0xFB,0xFF,0xFA,0x02,0xFB,0xFE,0xFA,0x01, + 0xFB,0xFB,0xFA,0xFC,0xFF,0x4D,0x1A,0xF9,0x01,0xF8,0x01,0x00, + 0x9B,0xFB,0x02,0xA5,0xFB,0xFE,0xFF,0xD2,0x19,0xF9,0x01,0xF8, + 0xFF,0x00,0x9B,0x9C,0x9D,0x9E,0xFF,0x2E,0x1A,0xF9,0x01,0xF8, + 0x01,0x00,0xFB,0x02,0xA3,0xFB,0x04,0xA4,0xA5,0xFF,0xD2,0x19, + 0xF9,0x01,0xF8,0x01,0x00,0xFB,0x06,0xA4,0xA5,0xFF,0xD2,0x19, + 0xF9,0x01,0xF8,0xFF,0x00,0xFB,0xFD,0xA0,0xFB,0xFE,0x9D,0xFF, + 0xD2,0x19,0xF9,0x01,0xF8,0xFF,0x00,0xA8,0xF9,0x01,0x97,0xF9, + 0x01,0x98,0x99,0x9A,0xF9,0x05,0x9B,0xF9,0x01,0x9C,0x9D,0xFF, + 0xD2,0x19,0xF9,0x01,0xA7,0xFF,0x4A,0x1A,0xA2,0xFF,0x45,0x1A, + 0xA9,0x96,0xFF,0xD2,0x19,0x9F,0xA0,0xFF,0x5F,0x1A,0xF9,0x01, + 0xF4,0xFF,0xD2,0x19,0xF9,0x05,0xFB,0xF8,0xFF,0xD2,0x19,0xF9, + 0x05,0xA0,0x9D,0xFF,0xD2,0x19,0xF9,0x07,0xFB,0xF8,0xFF,0x39, + 0x1B,0xF9,0x05,0xFE,0xFB,0x05,0xFF,0x2E,0x1A,0xF9,0x05,0xFE, + 0xFB,0x12,0xFF,0xA6,0x19,0xF9,0x01,0x10,0x11,0xFB,0x02,0x12, + 0xFB,0x02,0x13,0xFB,0x02,0x14,0xFB,0x02,0x15,0xFB,0x02,0x16, + 0xFB,0x07,0x17,0xFB,0x09,0x18,0xFB,0x05,0xFA,0xFA,0x19,0xFB, + 0x01,0xFA,0x06,0x1A,0xFB,0x04,0xF4,0xF2,0x01,0x1B,0xFB,0xFD, + 0x1C,0xFB,0x05,0x1D,0xF2,0x01,0x1E,0x1F,0x20,0x21,0xFB,0x01, + 0xFF,0xA0,0x19,0xF9,0x01,0xF2,0x01,0x22,0xFB,0x05,0x23,0xFB, + 0x06,0x24,0xFB,0x03,0x25,0xFB,0x05,0xF2,0x01,0x26,0xFB,0x07, + 0x27,0xFB,0x0C,0xFA,0xFD,0x28,0xFB,0x08,0xFA,0xF7,0x29,0xFB, + 0x08,0xFA,0xFE,0x2A,0xFB,0x04,0xFA,0x0B,0x2B,0xFB,0x04,0xFA, + 0x03,0x2C,0xFB,0x05,0xF4,0xF2,0x01,0xFF,0x81,0x19,0xF9,0x01, + 0xFB,0x01,0x6B,0xFB,0x02,0xFB,0x02,0x6C,0xFB,0x02,0x6D,0xFB, + 0x02,0x6D,0xFB,0x02,0x6D,0xFF,0x16,0x1B,0x00,0xF9,0x01,0xFB, + 0x01,0x6E,0x6F,0xFB,0x02,0x70,0xFB,0x02,0x6C,0xFB,0x02,0x6D, + 0xFF,0x29,0x1B,0xF9,0x07,0xFE,0xFB,0x06,0x2D,0xFB,0x01,0x2E, + 0xFF,0xC4,0x19,0xF9,0x07,0xFE,0xFB,0x06,0x2D,0xFB,0x01,0x2E, + 0xFB,0x02,0x2F,0xFB,0xFF,0x30,0xFB,0x01,0x31,0xFB,0xFE,0x32, + 0x33,0x34,0xFF,0xA0,0x19,0xF9,0x01,0xFB,0xFF,0xFF,0x75,0x19, + 0xF9,0x01,0xFB,0x01,0x35,0xFB,0x01,0xF2,0x01,0x36,0xFB,0x08, + 0x37,0xF2,0x01,0x38,0xFB,0x07,0x39,0xFB,0x03,0x3A,0xFB,0x01, + 0x3B,0x3C,0xFB,0x02,0x3D,0xFB,0xFF,0x3E,0x3F,0x40,0xFB,0xFF, + 0x41,0xFB,0xF2,0xFE,0xFF,0x95,0x19,0xF9,0x03,0xFA,0xFF,0x66, + 0xFB,0xFE,0xFA,0x06,0x67,0xFB,0xFE,0xFA,0x09,0x68,0xFB,0xFF, + 0xFA,0x0C,0x69,0xFB,0xFD,0xF8,0x00,0x0F,0xFF,0x49,0x1D,0xF9, + 0x03,0xFA,0xFF,0xFB,0xFE,0x66,0xFB,0xFD,0xFA,0x06,0x67,0xFB, + 0xFD,0xFA,0x09,0x68,0xFB,0xFE,0xFA,0x0C,0x69,0xFB,0xFD,0xF8, + 0x00,0x0F,0xFF,0x49,0x1D,0xF9,0x03,0xFB,0x01,0xFA,0xFF,0x66, + 0xFB,0x02,0xFA,0x06,0x67,0xFB,0xFF,0xFA,0x09,0x68,0xFA,0x0C, + 0x69,0xFB,0xFE,0xF8,0x01,0x0F,0xFF,0x49,0x1D,0xF9,0x03,0xFB, + 0x01,0xFA,0x03,0xF7,0x06,0x1C,0x66,0xFB,0x02,0xFA,0x06,0x67, + 0xFB,0xFF,0xFA,0x09,0x68,0xFA,0x0C,0x69,0xFB,0xFE,0xF8,0x01, + 0x0F,0xFF,0x49,0x1D,0xFB,0xFF,0xFA,0xFD,0xFF,0xE4,0x1B,0xFB, + 0x01,0xFF,0xDB,0x1B,0x66,0xFB,0x02,0xFA,0x03,0x67,0xFB,0xFF, + 0xFA,0x04,0x68,0xFA,0x05,0x69,0xFB,0xFE,0xF8,0x01,0x06,0xFF, + 0x49,0x1D,0xF9,0x03,0xFB,0x01,0xFA,0x03,0x66,0xFB,0x02,0xFA, + 0x06,0x67,0xFB,0x01,0xFA,0x09,0x68,0xFB,0x02,0xFA,0x0C,0x69, + 0xF8,0x02,0x0F,0xFF,0x49,0x1D,0xF9,0x03,0xFB,0x01,0xFA,0x03, + 0x66,0xFB,0x03,0xFA,0x06,0x67,0xFB,0x02,0xFA,0x09,0x68,0xFB, + 0x03,0xFA,0x0C,0x69,0xF8,0x03,0x0F,0xFF,0x49,0x1D,0xF9,0x01, + 0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0xF9, + 0x02,0x4E,0x4F,0x50,0xFF,0xA1,0x1C,0xF9,0x01,0x43,0x44,0x45, + 0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0xF9,0x02,0xFB,0x01, + 0x4E,0xFB,0x02,0x4F,0xFB,0x01,0x50,0xFF,0xA1,0x1C,0xF9,0x01, + 0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0xFB,0xFF, + 0x4D,0xF9,0x02,0xFB,0xFE,0x4E,0xFB,0xFF,0x4F,0xFB,0xFF,0x50, + 0xFF,0xA1,0x1C,0xF9,0x02,0x5B,0x5A,0x59,0x58,0x57,0x57,0x57, + 0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63, + 0x61,0x60,0x5F,0x5E,0x5D,0x5C,0x5B,0x5A,0x59,0x58,0x57,0x58, + 0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x5F,0x5E,0x5D,0x5C, + 0xFF,0x25,0x1D,0xF9,0x06,0x5C,0x5D,0x5D,0x5C,0x5C,0x5B,0xFF, + 0xD8,0x1C,0x87,0x88,0x89,0x89,0x8A,0x8A,0x8A,0x8A,0x89,0x88, + 0x87,0xFB,0xF9,0xFF,0x25,0x1D,0xF9,0x01,0x94,0x91,0x90,0x8F, + 0x8E,0x8D,0xFB,0xFB,0xFA,0x3F,0xFC,0xF9,0x03,0x8C,0x8A,0x88, + 0x5B,0xF9,0x02,0xFF,0xA4,0x1C,0xF9,0x01,0x87,0x88,0x89,0x8A, + 0x8B,0x8C,0xFB,0x05,0xFA,0xC1,0xFD,0x8D,0x8E,0x8F,0x90,0x91, + 0x92,0x93,0x94,0xF9,0x05,0x95,0xF9,0x01,0x76,0x77,0xFB,0x01, + 0xFF,0xA0,0x19,0x51,0x52,0xF9,0x05,0x53,0xF9,0x01,0xF4,0xF2, + 0x00,0x54,0x55,0xFB,0x03,0xFF,0xA0,0x19,0xF9,0x03,0x51,0xFA, + 0x06,0x51,0xFA,0x09,0x51,0xFA,0x0C,0xFB,0x02,0xF8,0x00,0x0C, + 0xFF,0x49,0x1D,0xF9,0x04,0x6A,0xFF,0x4B,0x1D,0xF9,0x01,0x35, + 0xFB,0x02,0xF2,0x01,0x36,0xFB,0x07,0x37,0xF2,0x01,0x38,0xFB, + 0x02,0x31,0xFB,0xFE,0x32,0x33,0x34,0xFF,0xA0,0x19,0xF9,0x01, + 0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E, + 0xF9,0x00,0xF5,0x4F,0xFF,0x25,0x1D,0xF9,0x01,0x43,0x44,0x45, + 0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0xFA,0xFC, + 0x4F,0xFA,0xFE,0x4F,0x4F,0xFA,0x02,0x4F,0xFA,0x04,0xFF,0x25, + 0x1D,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0xFA, + 0xFF,0x4D,0xFA,0xFD,0x4E,0xFA,0xFC,0x4F,0xFA,0xF6,0x4F,0xFA, + 0xF7,0x4F,0xFA,0xF8,0x4F,0xFA,0xF9,0x4F,0xFA,0xFA,0x4F,0xFA, + 0xFB,0x4F,0xFA,0xFC,0x4F,0xFA,0xFD,0x4F,0xFA,0xFE,0x4F,0xFA, + 0xFE,0x4F,0xFA,0xFF,0x4F,0xFA,0xFF,0x4F,0xFA,0xFF,0x4F,0x4F, + 0x4F,0x4F,0xFA,0x01,0x4F,0xFA,0x01,0x4F,0xFA,0x02,0x4F,0xFA, + 0x02,0x4F,0xFA,0x03,0x4F,0xFA,0x04,0x4F,0xFA,0x05,0x4F,0xFA, + 0x06,0x4F,0xF8,0x00,0x06,0xFF,0x49,0x1D,0xF9,0x03,0x50,0xFF, + 0xA1,0x1C,0xF9,0x05,0xFB,0xFC,0x32,0x33,0x34,0xFF,0xA0,0x19, + 0xF9,0x05,0xFB,0x01,0xFA,0x03,0xF7,0x25,0x1E,0x66,0xFB,0x02, + 0xFA,0x06,0x67,0xFB,0xFF,0xFA,0x09,0x68,0xFA,0x0C,0x69,0xFB, + 0xFE,0xF8,0x00,0x0F,0xFF,0x49,0x1D,0x66,0xFB,0x02,0xFA,0x03, + 0x67,0xFB,0xFF,0xFA,0x04,0x68,0xFA,0x05,0x69,0xFB,0xFE,0xF8, + 0x00,0x06,0xFF,0x49,0x1D,0xF9,0x05,0xFB,0xFF,0xFA,0xFC,0x66, + 0xFB,0xFF,0xFA,0x03,0xFB,0xFD,0xFA,0x01,0xF4,0xFB,0x01,0xF2, + 0x01,0x6B,0xFB,0x02,0x6C,0xF2,0x01,0x6D,0xFF,0xB3,0x1F,0x79, + 0xFB,0x01,0x7A,0x7B,0xFB,0x02,0x7C,0xFB,0x04,0x7D,0xFB,0x03, + 0x7E,0xFB,0xFC,0x56,0xF2,0x01,0xF4,0xFB,0xFC,0x74,0xFB,0xFE, + 0x75,0x76,0x77,0xFF,0xA0,0x19,0xFB,0xFB,0xFF,0xA0,0x19,0xF9, + 0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB, + 0x04,0x7D,0xFB,0x03,0x7E,0xFB,0xFF,0xFB,0x03,0x7F,0x80,0x81, + 0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A, + 0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB,0x04,0x7D,0xFB,0x03,0x7E, + 0xFB,0xFF,0xFB,0x02,0x7F,0x80,0x81,0x82,0x83,0x84,0xFF,0xA0, + 0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03, + 0x7C,0xFB,0x04,0x7D,0xFB,0x03,0x7E,0xFB,0xFF,0xFB,0x01,0x7F, + 0x80,0x81,0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB, + 0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB,0x04,0x7D,0xFB, + 0x03,0x7E,0xFB,0xFF,0x7F,0x80,0x81,0x82,0x83,0x84,0xFF,0xA0, + 0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03, + 0x7C,0xFB,0x04,0x7D,0xFB,0x03,0x7E,0xFB,0xFE,0x80,0x81,0x82, + 0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFF,0xFC,0x1E,0xF9, + 0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB, + 0x04,0x7D,0xFB,0xFF,0x7F,0x80,0x81,0x82,0x83,0x84,0xFF,0xA0, + 0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03, + 0x7C,0xFB,0x02,0x81,0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01, + 0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x02,0x7C,0xFB,0x02, + 0x81,0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,0x01, + 0x7A,0xFB,0x01,0x7B,0xFB,0x02,0x7C,0xFB,0x01,0x81,0x82,0x83, + 0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01, + 0x7B,0xFB,0x02,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB, + 0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x01,0x83,0x84,0xFF,0xA0,0x19, + 0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x84,0xFF,0xA0,0x19, + 0xF9,0x01,0x79,0xFB,0x01,0x84,0xFF,0xA0,0x19,0xF9,0x01,0xFB, + 0x01,0x6B,0xFB,0x02,0x6C,0x6D,0xFF,0xAF,0x1F,0xF9,0x05,0xFB, + 0x01,0x6E,0x6F,0xFB,0x02,0x70,0x71,0xFB,0x01,0x72,0x73,0x74, + 0xFB,0xFC,0x75,0x76,0x77,0xFF,0xA0,0x19,0xF9,0x01,0xF3,0x01, + 0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,0xE6,0xE7,0xE8,0xFF,0xDA,0x1F, + 0xF9,0x01,0xFB,0xFB,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0, + 0x85,0x85,0x86,0x86,0x86,0x30,0xFB,0x01,0x31,0xFB,0xFE,0xF9, + 0x05,0x32,0xF9,0x01,0x33,0x34,0xFF,0xA0,0x19,0xF9,0x01,0xFB, + 0xFB,0xEA,0xEC,0xEE,0xF0,0x86,0xFB,0xFF,0xFF,0xA0,0x19,0xF9, + 0x01,0xFB,0x04,0xBF,0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xF2,0x03, + 0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCD,0xCD,0xF3,0x01, + 0xCD,0xCD,0xC9,0xC6,0xFB,0xFC,0xFF,0xA0,0x19,0xF9,0x05,0xF4, + 0xFB,0x01,0x6B,0xFB,0x02,0x6C,0xF9,0x01,0x6D,0xFF,0x36,0x20, + 0xF9,0x01,0xFA,0xFE,0xFB,0x01,0x6B,0xFB,0x02,0x6C,0x6D,0xFB, + 0x01,0x6E,0x6F,0xFB,0x02,0x70,0x71,0xFB,0x01,0xFA,0x01,0x72, + 0xFA,0x01,0x73,0xFB,0xFE,0xFF,0x78,0x19,0xF9,0x05,0xF4,0xFA, + 0xFE,0xFB,0x01,0xFB,0x02,0x6C,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D, + 0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D, + 0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0xFB, + 0x01,0x6E,0x6E,0x6E,0x6F,0xFB,0x02,0x70,0x71,0xFB,0x01,0xFA, + 0x01,0x72,0xFA,0x01,0x73,0x74,0xFB,0xFC,0x75,0x76,0x77,0xFF, + 0xA0,0x19,0xF9,0x05,0xF4,0xFA,0xFE,0xFB,0x03,0xB9,0xF6,0xB9, + 0xFF,0xA5,0x20,0xF9,0x05,0xFF,0xAE,0x20,0xF9,0x01,0xF6,0xB3, + 0xB4,0xB5,0xB6,0xFB,0x01,0xB7,0xFB,0xFC,0xB9,0xFF,0xBA,0x20, + 0xF9,0x01,0xF4,0xFB,0x04,0xB1,0xF6,0xB1,0xFF,0xC5,0x20,0xF9, + 0x01,0xB2,0xF6,0xB2,0xFF,0xCD,0x20,0xFF,0x5A,0x20,0xF8,0x00, + 0x00,0xF9,0x04,0xB9,0xFF,0xD9,0x20,0xF9,0x05,0xFB,0xFB,0xFA, + 0xFF,0xF2,0x01,0xD9,0xDA,0xDB,0xFB,0x01,0xDC,0xFB,0xFC,0xFA, + 0xFD,0xF2,0x01,0xDD,0xFB,0xFC,0xFA,0xFE,0xDE,0xFB,0xFE,0xFA, + 0xFD,0xDF,0xFB,0xFD,0xFA,0xF8,0xF2,0x04,0xF2,0x01,0xE0,0xFB, + 0xFF,0xFA,0xFF,0xE1,0xFB,0xFD,0xFA,0xFC,0xE2,0xFB,0xFF,0xFA, + 0xFB,0xF2,0x01,0xE3,0xFB,0xFE,0xFA,0xFF,0xE4,0x00,0xF2,0x01, + 0x00,0x00,0x00,0xF2,0x01,0x00,0x00,0x00,0xF2,0x01,0x00,0x00, + 0x00,0xF2,0x01,0xF1,0x00,0xFF,0x2E,0x21,0x36,0xFF,0x32,0x21, + 0x55,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x44, + 0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x53,0x54,0x4C,0xFF,0x4B, + 0x21,0xFB,0x01,0x30,0xFB,0x02,0x31,0xFB,0x06,0x32,0xFB,0x01, + 0x33,0xFB,0xFF,0x34,0xFB,0x01,0x35,0xFB,0x01,0xFF,0x51,0x21, + 0xFB,0x01,0x37,0x38,0xFF,0x32,0x21,0x4D,0x4E,0x4F,0x50,0x51, + 0x52,0xFB,0x01,0x36,0x36,0x36,0x36,0x36,0x36,0x39,0x3A,0x3B, + 0x3C,0x3D,0xFB,0x02,0x3E,0xFB,0xFF,0x3F,0xFB,0xFD,0x40,0x41, + 0xFB,0xFF,0x42,0xFE,0xFB,0x10,0xFB,0x03,0xFF,0x54,0x21,0x0B, + 0xFF,0x95,0x21,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFE, + 0xFB,0x08,0x0B,0xFF,0x95,0x21,0xFE,0xFB,0x0B,0x0C,0xFB,0x01, + 0x0D,0xFB,0x01,0x0E,0xFB,0x03,0x0F,0xFB,0x01,0x10,0x11,0xFF, + 0xB8,0x21,0x13,0xFF,0xBC,0x21,0x14,0xFF,0xC0,0x21,0x15,0xFB, + 0x01,0x16,0x17,0x18,0xFB,0x01,0x19,0xFB,0xFD,0x1A,0xFB,0xFE, + 0x1B,0xFB,0xFC,0x1C,0xFB,0xFD,0x1D,0xFB,0xFE,0x1E,0xFB,0xFD, + 0x1F,0xFB,0xFF,0x20,0x21,0xFF,0xE2,0x21,0x25,0xFF,0xE6,0x21, + 0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0xFE, + 0xFB,0x0C,0x0B,0xFF,0xF8,0x21,0x0B,0x0B,0xFE,0xFB,0x0D,0x2F, + 0x2E,0x2D,0x2C,0x2B,0x2A,0x29,0x28,0x27,0x26,0x25,0x24,0x24, + 0x24,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x23, + 0x23,0x24,0x24,0x24,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22, + 0x22,0x22,0x23,0x23,0x24,0x24,0x24,0x23,0x23,0x23,0x22,0x22, + 0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x23,0x23,0x23,0x24,0xFF, + 0x3C,0x22,0x01,0x12,0xFF,0x41,0x22,0xF9,0x01,0xBA,0xFB,0x05, + 0xBA,0xFB,0x03,0xBB,0xFB,0x04,0xFF,0x47,0x22,0xBA,0xFF,0x53, + 0x22,0xBC,0xFF,0x57,0x22,0xF9,0x00,0xBA,0xBA,0xBA,0xBC,0xBC, + 0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xFE,0xFB,0x08,0xFF,0x47,0x22, + 0xBA,0xBA,0xBC,0xFF,0x70,0x22, +}; + +#define KID_NSEQ 115 +static const uint16_t kid_seq_off[KID_NSEQ] = { + 0x0000,0x1973,0x19A0,0x1A93,0x1ACD,0x1B39,0x1B5A,0x1BDB, + 0x1C54,0x1CA1,0x1D04,0x1D25,0x1D49,0x1D4F,0x1D68,0x1DF6, + 0x1C84,0x202B,0x1C1C,0x1C01,0x205A,0x1C38,0x209C,0x1D36, + 0x1C69,0x1CD1,0x1B04,0x1B1A,0x1D7D,0x1F9E,0x1F92,0x1F82, + 0x1F72,0x1F5D,0x1F48,0x1F33,0x1F19,0x1F13,0x1EF7,0x1EDA, + 0x1EBB,0x1E9C,0x1E7D,0x1B53,0x1E59,0x1E06,0x1E3B,0x1DFC, + 0x1D9B,0x1FB3,0x1FA7,0x20BE,0x20D1,0x20D4,0x20C9,0x19C4, + 0x1A13,0x1A2E,0x1A3C,0x1A7C,0x1A83,0x1A63,0x1A5E,0x1A68, + 0x1A6E,0x1A75,0x1A5A,0x19F9,0x1CEC,0x1A54,0x20DD,0x20AE, + 0x1E78,0x1CDC,0x19DC,0x1A42,0x1A07,0x19A8,0x2009,0x1B1B, + 0x1A8B,0x1B85,0x1BA1,0x1BBF,0x196E,0x20A9,0x1A22,0x19A6, + 0x19AC,0x1B2D,0x19C1,0x1FCA,0x1FDA,0x1FFB,0x2195,0x2132, + 0x214F,0x2166,0x2199,0x21A8,0x216D,0x226E,0x2136,0x21BC, + 0x1BFA,0x2245,0x2253,0x225B,0x21C4,0x21C0,0x21E6,0x21EA, + 0x21FC,0x2240,0x2257, +}; + +#endif diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c new file mode 100644 index 0000000..91cd722 --- /dev/null +++ b/applications/PoP/roomtest/pop_bg.c @@ -0,0 +1,394 @@ +/* + * pop_bg.c — статический фон комнаты PoP композицией слоёв (порт + * applications/PoP/toolchain/render_room.py -> seg008.c draw_tile/ + * wall_pattern). Подробности алгоритма — в render_room.py и + * memory/pop_background_strategy. + * + * Только DUNGEON-стиль (Фаза 1). Анимация (пламя факелов, шипы, дверь- + * плита, сама решётка ворот) НЕ рисуется — отдельными sprite_t поверх + * (Фаза 2). Решётка ворот из ЛЕВОЙ комнаты рисуется статичным кадром. + */ +#include +#include +#include +#include +#include +#include "pop_bg.h" + +/* ---- Атласы (прямая адресация) ------------------------------------- */ +static atlas_t env[5]; +static atlas_t wall_at; +static atlas_t fore_at; + +static const char *const ENV_ATL[5] = { + "pop_env0.atl", "pop_env1.atl", "pop_env2.atl", + "pop_env3.atl", "pop_env4.atl" +}; + +int pop_bg_load(void) +{ + int i; + for (i = 0; i < 5; i++) + if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1; + if (atlas_load(&wall_at, "pop_wall.atl") != 0) return -1; + if (atlas_load(&fore_at, "pop_fore.atl") != 0) return -1; + return 0; +} + +void pop_bg_free(void) +{ + int i; + for (i = 0; i < 5; i++) + atlas_free(&env[i]); + atlas_free(&wall_at); + atlas_free(&fore_at); +} + +/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x, + * прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0). */ +static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) +{ + const uint8_t *img; + uint16_t w, h; + if (idx >= a->count) + return; + img = (const uint8_t *)atlas_image(a, idx); + gfx_w0_map(a->page); + w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); + h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); + if (w && h) + gfx_blit(x, ybottom - (int)h + 1, img); + gfx_w0_unmap(); +} + +static void env_b(uint8_t id, int x, int yb) { blit_b(&env[id >> 5], (uint8_t)(id & 31), x, yb); } +static void wall_b(uint8_t id, int x, int yb) { blit_b(&wall_at, id, x, yb); } +static void fore_b(uint8_t id, int x, int yb) { blit_b(&fore_at, id, x, yb); } + +/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */ +typedef struct { + uint8_t base_id, floor_left; int8_t base_y; + uint8_t right_id, floor_right; int8_t right_y; + uint8_t stripe_id, topright_id, bottom_id, fore_id, fore_x; int8_t fore_y; +} piece; + +static const piece tile_table[31] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* 00 empty */ + { 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 01 floor */ + {127, 1, 0, 133, 1, 2, 145, 0, 43, 0, 0, 0}, /* 02 spike */ + { 92, 1, 0, 93, 1, 2, 0, 94, 43, 95, 1, 0}, /* 03 pillar */ + { 46, 1, 0, 47, 1, 2, 0, 48, 43, 49, 3, 0}, /* 04 gate */ + { 41, 1, 1, 35, 1, 3, 145, 0, 36, 0, 0, 0}, /* 05 stuck floor */ + { 41, 1, 0, 42, 1, 2, 145, 0, 96, 0, 0, 0}, /* 06 close button */ + { 46, 1, 0, 0, 0, 2, 0, 0, 43, 49, 3, 0}, /* 07 door top w/floor */ + { 86, 1, 0, 87, 1, 2, 0, 0, 43, 88, 1, 0}, /* 08 big pillar bottom */ + { 0, 0, 0, 89, 0, 3, 0, 90, 0, 91, 1, 3}, /* 09 big pillar top */ + { 41, 1, 0, 42, 1, 2, 145, 0, 43, 12, 2, -3}, /* 0A potion */ + { 0, 1, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0}, /* 0B loose floor */ + { 0, 0, 0, 0, 0, 2, 0, 0, 85, 49, 3, 0}, /* 0C door top */ + { 75, 1, 0, 42, 1, 2, 0, 0, 43, 77, 0, 0}, /* 0D mirror */ + { 97, 1, 0, 98, 1, 2, 145, 0, 43,100, 0, 0}, /* 0E debris */ + {147, 1, 0, 42, 1, 1, 145, 0,149, 0, 0, 0}, /* 0F open button */ + { 41, 1, 0, 37, 0, 0, 0, 38, 43, 0, 0, 0}, /* 10 leveldoor left */ + { 0, 0, 0, 39, 1, 2, 0, 40, 43, 0, 0, 0}, /* 11 leveldoor right */ + { 0, 0, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 12 chomper */ + { 41, 1, 0, 42, 1, 2, 0, 0, 43, 0, 0, 0}, /* 13 torch */ + { 0, 0, 0, 1, 1, 2, 0, 2, 0, 0, 0, 0}, /* 14 wall */ + { 30, 1, 0, 31, 1, 2, 0, 0, 43, 0, 0, 0}, /* 15 skeleton */ + { 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0}, /* 16 sword */ + { 41, 1, 0, 10, 0, 0, 0, 11, 43, 0, 0, 0}, /* 17 balcony left */ + { 0, 0, 0, 12, 1, 2, 0, 13, 43, 0, 0, 0}, /* 18 balcony right */ + { 92, 1, 0, 42, 1, 2, 145, 0, 43, 95, 1, 0}, /* 19 lattice pillar */ + { 1, 0, 0, 0, 0, 0, 0, 0, 2, 9, 0,-53}, /* 1A lattice down */ + { 3, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1B lattice small */ + { 4, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1C lattice left */ + { 5, 0,-10, 0, 0, 0, 0, 0, 0, 9, 0,-53}, /* 1D lattice right */ + { 97, 1, 0, 98, 1, 2, 0, 0, 43,100, 0, 0}, /* 1E debris w/torch */ +}; + +/* ---- Константные таблицы (render_room.py) -------------------------- */ +static const uint8_t COL_XH[10] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36}; +static const uint8_t TBL_LINE[3] = {0, 10, 20}; +static const uint8_t WALL_FRAM_BOTTOM[4] = {7, 9, 5, 3}; +static const uint8_t WALL_FRAM_MAIN[4] = {8, 10, 6, 4}; +static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52}; +static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0}; +static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126}; +static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0}; +static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45}; +static const uint8_t LPOS[5] = {58, 41, 37, 20, 16}; +static const uint8_t RPOS[4] = {52, 42, 31, 21}; + +/* Wall (chtab_7) id: делители/блок/марки. */ +#define RES_WALL_RNDBLOCK 13 +#define RES_WALL_DIVIDER1 11 +#define RES_WALL_MARK_TL 14 +#define RES_WALL_MARK_BL 15 +#define RES_WALL_MARK_TR 16 +#define RES_WALL_MARK_BR 17 + +/* ---- Текущая комната (globals для tile_code/mod) ------------------- */ +static const uint8_t *g_fg, *g_bg, *g_lfg, *g_lbg; +static uint8_t g_room; + +static uint8_t tile_code(int row, int col) +{ + if (col < 0) { + if (g_lfg && row >= 0 && row < 3) return g_lfg[row]; + return 20; /* уровневая кромка = стена */ + } + if (row < 0) return 1; /* верхняя кромка = пол */ + if (row > 2 || col > 9) return 20; + return g_fg[row * 10 + col]; +} + +static uint8_t tile_mod(int row, int col) +{ + if (col < 0) { + if (g_lbg && row >= 0 && row < 3) return g_lbg[row]; + return 0; + } + if (row < 0 || row > 2 || col > 9) return 0; + return g_bg[row * 10 + col]; +} + +/* seg008.c:1255 (без FAKE_TILES): сосед — стена, если его тип == 20. + * На кромке col0 сосед по умолчанию = стена (как render_room). */ +static uint8_t wall_modifier(int row, int col) +{ + uint8_t lw = (col > 0) ? (tile_code(row, col - 1) == 20) : 1; + uint8_t rw = (col < 9) ? (tile_code(row, col + 1) == 20) : 1; + if (lw && rw) return 3; /* WWW */ + if (lw) return 2; /* WWS */ + if (rw) return 1; /* SWW */ + return 0; /* SWS */ +} + +/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */ +static unsigned long rnd_seed; + +static uint16_t prandom(uint16_t maxv) +{ + rnd_seed = rnd_seed * 214013UL + 2531011UL; + return (uint16_t)((uint16_t)(rnd_seed >> 16) % (uint16_t)(maxv + 1)); +} + +/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */ +static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col) +{ + uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BL : RES_WALL_MARK_TL; + int lv2 = 0; + int xh = COL_XH[col]; + int dby = 63 * row + 65; + if (dv > 3) lv2 = arg1 + 6; + else if (dv > 1) lv2 = arg2 + 6; + wall_b(image_id, (xh + ((dv == 2 || dv == 3) ? 1 : 0)) * 8 + lv2, dby - LPOS[dv]); +} + +static void draw_right_mark(uint16_t dv, int arg1, int row, int col) +{ + uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BR : RES_WALL_MARK_TR; + int a = (dv < 2) ? 24 : arg1 - 3; + int xh = COL_XH[col]; + int dby = 63 * row + 65; + wall_b(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a, dby - RPOS[dv]); +} + +/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */ +static void wall_pattern(int row, int col, int which_part) +{ + int xh = COL_XH[col]; + int dby = 63 * row + 65; + unsigned long saved = rnd_seed; + uint16_t mid_div, mid_off, bot_div, bot_off; + uint8_t bg; + + rnd_seed = (unsigned long)(g_room + TBL_LINE[row] + col); + prandom(1); /* сброс */ + mid_div = prandom(1); + mid_off = prandom(4); + bot_div = prandom(1); + bot_off = prandom(4); + bg = wall_modifier(row, col); + + if (bg == 3) { /* WWW */ + if (which_part) { + if (prandom(4) == 0) + wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42); + wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21); + } + wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby); + if (which_part) { + if (prandom(4) == 0) + draw_right_mark(prandom(3), mid_off, row, col); + if (prandom(4) == 0) + draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col); + } + } else if (bg == 0) { /* SWS */ + if (which_part) { + if (prandom(6) == 0) + draw_left_mark(prandom(1), mid_off - mid_div, bot_off - bot_div, row, col); + } + } else if (bg == 1) { /* SWW */ + if (which_part) { + if (prandom(4) == 0) + wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42); + wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21); + if (prandom(4) == 0) + draw_right_mark(prandom(3), mid_off, row, col); + if (prandom(4) == 0) + draw_left_mark(prandom(3), mid_off - mid_div, bot_off - bot_div, row, col); + } + } else { /* WWS (bg==2) */ + if (which_part) + wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21); + wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby); + if (which_part) { + if (prandom(4) == 0) + draw_right_mark((uint16_t)(prandom(1) + 2), mid_off, row, col); + if (prandom(4) == 0) + draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col); + } + } + rnd_seed = saved; +} + +/* topright тайла снизу-слева (seg008.c:410). */ +static void draw_topright(uint8_t tt, int x, int dby) +{ + if (tt == 7 || tt == 12) return; + if (tt == 20) { wall_b(2, x, dby); return; } + if (tile_table[tt].topright_id) + env_b(tile_table[tt].topright_id, x, dby); +} + +/* Решётка ворот (portcullis) из ЛЕВОЙ комнаты (seg008.c:1110). */ +static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy) +{ + int x = xh * 8; + int gate_top_y = dby - 62; + int openness = (((modl > 188) ? 188 : modl) >> 2) + 1; + int gate_bot_y = dmy - openness; + int yb, gate_frame; + + if (gate_bot_y + 12 < dmy) + env_b(50, x, gate_bot_y); + else + env_b(51, x, gate_bot_y - 2); + yb = gate_bot_y - 12; + if (yb < 192) { + while (yb >= 0 && yb > 7 && (yb - 7) > gate_top_y) { + env_b(52, x, yb); + yb -= 8; + } + } + gate_frame = yb - gate_top_y + 1; + if (gate_frame > 0 && gate_frame < 9) + env_b(DOOR_FRAM_SLICE[gate_frame], x, yb); +} + +/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */ +static void draw_tile(int row, int col) +{ + uint8_t code = tile_code(row, col); + uint8_t lcode = tile_code(row, col - 1); + uint8_t lmod = tile_mod(row, col - 1); + int xh = COL_XH[col]; + int x = xh * 8; + int dby = 63 * row + 65; + int dmy = dby - 3; + const piece *t = &tile_table[code]; + const piece *lt = &tile_table[lcode]; + uint8_t rbl_code; + uint8_t wmod; + + /* тайл снизу-слева (для topright) */ + if (row + 1 > 2) rbl_code = 1; /* ниже нижнего ряда = пол */ + else rbl_code = tile_code(row + 1, col - 1); + + /* --- floorright (seg008.c:392) — только can_see_bottomleft --- */ + if (code == 0 || code == 9 || code == 12 || code == 26) { + draw_topright(rbl_code, x, dby); + /* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op + * (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет + * floor_right: lt->floor_right. */ + } + + /* --- draw_tile_right (seg008.c:456) --- */ + if (code != 20) { + if (lcode == 20) { + wall_b(1, x, tile_table[20].right_y + dmy); /* грань стены */ + } else if (lcode == 0) { + if (lmod <= 3 && BLUELINE_FRAM1[lmod]) + env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy); + } else if (lcode == 1) { + uint8_t num; + env_b(42, x, tile_table[1].right_y + dmy); /* правый треуг. пола */ + num = (lmod <= 3) ? lmod : 0; + if (num != 0) /* dungeon: level_type=0 */ + env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */ + } else { + if (lt->right_id) + env_b(lt->right_id, x, lt->right_y + dmy); + /* stripe — только палас, dungeon пропускает */ + } + } + + /* --- draw_tile_anim_right (seg008.c:530) — статичные части --- */ + if (lcode == 4) { /* gate: решётка */ + draw_gate_back(lmod, xh, dby, dmy); + } else if (lcode == 11) { /* loose: правый край */ + env_b(42, x, dby - 1); + } else if (lcode == 2) { /* spike: статич. кадр */ + uint8_t sf = (lmod & 0x80) ? 5 : lmod; + if (sf < 10 && SPIKES_FRAM_RIGHT[sf]) + env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7); + } + + /* --- draw_tile_bottom (seg008.c:570) --- */ + if (code == 20) { + wmod = wall_modifier(row, col); + wall_b(WALL_FRAM_BOTTOM[wmod], x, dby); /* сплошная нижняя грань */ + wall_pattern(row, col, 0); + } else if (t->bottom_id) { + env_b(t->bottom_id, x, dby); + } + + /* --- draw_loose (seg008.c:600) — статич. кадр --- */ + if (code == 11) + env_b(43, x, dby); /* loose_fram_bottom[0] */ + + /* --- draw_tile_base (seg008.c:611) --- */ + { + uint8_t base_id = t->base_id; + if (code == 11) base_id = 41; /* loose_fram_left[0] */ + if (base_id) + env_b(base_id, x, t->base_y + dmy); + } + + /* --- draw_tile_fore (seg008.c:690) --- */ + if (code == 20) { + wmod = wall_modifier(row, col); + wall_b(WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */ + wall_pattern(row, col, 1); + } + if (t->fore_id) + fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy); +} + +void pop_room_draw(uint8_t room_num, + const uint8_t *fg, const uint8_t *bg, + const uint8_t *lcol_fg, const uint8_t *lcol_bg) +{ + int row, col; + g_room = room_num; + g_fg = fg; g_bg = bg; + g_lfg = lcol_fg; g_lbg = lcol_bg; + + /* прозрачный блит: 0xFF не пишется (чёрный фон/нижние слои проступают) */ + gfx_set_bank(GFX_BANK_TRANSPARENT); + for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */ + for (col = 0; col < 10; col++) + draw_tile(row, col); + gfx_set_bank(GFX_BANK_NORMAL); +} diff --git a/applications/PoP/roomtest/pop_bg.h b/applications/PoP/roomtest/pop_bg.h new file mode 100644 index 0000000..464f17a --- /dev/null +++ b/applications/PoP/roomtest/pop_bg.h @@ -0,0 +1,32 @@ +/* + * pop_bg.h — отрисовка СТАТИЧЕСКОГО фона комнаты PoP композицией слоёв + * (порт seg008.c draw_tile/wall_pattern, Шаг 3). Рисует прямо в текущую + * страницу (banked GFX_BANK_TRANSPARENT — в видео-ОЗУ + теневую копию, + * 0xFF прозрачен), ОДИН раз при входе в комнату. + * + * Данные: getimage-спрайты в атласах pop_env0..4/pop_wall/pop_fore + * (toolchain/pop_pack_bg.py), палитра pop_bg.pal. Прямая адресация + * id: env[id>>5] idx id&31, wall idx=id, fore idx=id — ноль remap-таблиц. + */ +#ifndef POP_BG_H +#define POP_BG_H + +#include + +/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load + * трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ + * после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */ +int pop_bg_load(void); + +/* Освободить EMM-страницы атласов. */ +void pop_bg_free(void); + +/* Нарисовать комнату: fg[30]/bg[30] — foretable-коды/backtable-модификаторы + * (по строкам, row*10+col). lcol_fg/lcol_bg — 3 тайла правого столбца + * ЛЕВОЙ соседней комнаты (для границы, напр. решётки ворот), или 0 — + * тогда левая кромка = стена. room_num — номер комнаты (зерно стен). */ +void pop_room_draw(uint8_t room_num, + const uint8_t *fg, const uint8_t *bg, + const uint8_t *lcol_fg, const uint8_t *lcol_bg); + +#endif diff --git a/applications/PoP/roomtest/pop_ctrl.c b/applications/PoP/roomtest/pop_ctrl.c new file mode 100644 index 0000000..5a6d2a1 --- /dev/null +++ b/applications/PoP/roomtest/pop_ctrl.c @@ -0,0 +1,368 @@ +/* + * pop_ctrl.c — управление Kid (Фаза K2b). Порт SDLPoP: + * вход — read_user_control/user_control/rest_ctrl_1/save_ctrl_1 + * (seg006.c) поверх held-state ; + * диспетчер — control() + control_standing/turning/startrun/running/ + * crouched (seg005.c), Kid-only ветки (без меча/стражи); + * действия — forward/back/up/down_pressed, crouch, standing_jump, + * run_jump, safe_step (seg005.c). + * + * Map-запросы (стена/край/захват) ЗАСТАБЛЕНЫ «открытый пол» (map_* ниже) — + * реальная коллизия в K3/K4. Поэтому: forward -> всегда бег, down -> присед, + * back -> разворот, up+forward -> прыжок с места; osторожный шаг и climb + * помечены TODO(K3/K4). + * + * Значения CONTROL_* (types.h): RELEASED=0, IGNORE=1, HELD=-1; + * X: LEFT/FORWARD=-1, RIGHT/BACKWARD=+1; Y: UP=-1, DOWN=+1. + */ +#include +#include +#include "pop_kid.h" +#include "pop_map.h" + +/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */ +#define CONTROL_RELEASED 0 +#define CONTROL_IGNORE 1 +#define CONTROL_HELD (-1) +#define CONTROL_HELD_LEFT (-1) +#define CONTROL_HELD_RIGHT 1 +#define CONTROL_HELD_FORWARD (-1) +#define CONTROL_HELD_BACKWARD 1 +#define CONTROL_HELD_UP (-1) +#define CONTROL_HELD_DOWN 1 + +#define DIR_RIGHT 0 /* dir_0_right */ + +#define SEQ_1_START_RUN 1 +#define SEQ_2_STAND 2 +#define SEQ_3_STANDING_JUMP 3 +#define SEQ_4_RUN_JUMP 4 +#define SEQ_5_TURN 5 +#define SEQ_6_RUN_TURN 6 +#define SEQ_13_STOP_RUN 13 +#define SEQ_26_CROUCH_WHILE_RUNNING 26 +#define SEQ_43_START_RUN_AFTER_TURN 43 +#define SEQ_49_STAND_UP_FROM_CROUCH 49 +#define SEQ_50_CROUCH 50 +#define SEQ_79_CROUCH_HOP 79 +#define SEQ_STEP_BASE 28 /* +distance(1..14) = seq 29..42 шаг-N */ +#define SEQ_39_UNSAFE_STEP 39 /* шаг за кромку (distance 11) */ +#define SEQ_44_STEP_ON_EDGE 44 +#define SEQ_14_JUMP_INTO_CEILING 14 /* прыжок вверх: сверху стена/пол */ +#define SEQ_28_JUMP_NOTHING_ABOVE 28 /* прыжок вверх: сверху пусто */ + +#define FRAME_7_RUN 7 +#define FRAME_11_RUN 11 +#define FRAME_15_STAND 15 +#define FRAME_48_TURN 48 +#define FRAME_50_TURN 50 +#define FRAME_67_JUMPUP1 67 +#define FRAME_70_JUMPHANG 70 +#define FRAME_87_HANG1 87 +#define FRAME_109_CROUCH 109 + +/* ---- Состояние управления (seg006 globals) ------------------------ * + * control_x/y/shift — «сырое» состояние осей ЭТОГО кадра (абсолютно: + * X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный + * автомат held/released/ignore (авто-повтор); ctrl1_* — их память между + * кадрами (rest/save). */ +static int8_t control_x, control_y, control_shift; +static int8_t control_forward, control_backward, control_up, control_down, control_shift2; +static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2; + +/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ * + * Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl + * не дублируются в этот TU). */ +#define seqtbl_offset_char(seq) kid_set_seq(seq) + +static int8_t release_arrows(void) +{ + control_backward = control_forward = control_up = control_down = CONTROL_RELEASED; + return CONTROL_IGNORE; /* seg006: return 1 */ +} + +/* Край/стена впереди — из pop_map (get_edge_distance): pop_edge_distance() + * возвращает дистанцию и ставит pop_edge_type() = EDGE_WALL/FLOOR/CLOSER. */ + +/* ---- Действия (seg005) -------------------------------------------- */ +static void crouch(void) +{ + seqtbl_offset_char(SEQ_50_CROUCH); + control_down = release_arrows(); +} + +static void standing_jump(void) +{ + control_up = control_forward = CONTROL_IGNORE; + seqtbl_offset_char(SEQ_3_STANDING_JUMP); +} + +static void run_jump(void) +{ + /* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это + * полировка K3; K2b просто запускает run-jump. */ + if (Kid.frame >= FRAME_7_RUN) { + control_up = release_arrows(); + seqtbl_offset_char(SEQ_4_RUN_JUMP); + } +} + +static void safe_step(void); /* fwd */ + +static void forward_pressed(void) +{ + int distance = pop_edge_distance(); + if (pop_edge_type() == EDGE_WALL && distance < 8) { + /* рядом стена — вместо бега осторожный шаг (в стену не разбежится) */ + if (control_forward == CONTROL_HELD) + safe_step(); + } else { + seqtbl_offset_char(SEQ_1_START_RUN); + } +} + +static void back_pressed(void) +{ + control_backward = release_arrows(); + /* Kid без меча -> просто разворот. */ + seqtbl_offset_char(SEQ_5_TURN); +} + +static void down_pressed(void) +{ + control_down = CONTROL_IGNORE; /* без авто-повтора */ + /* climb-down у края (seq_68) — K4 (зацеп/спуск); пока просто присед. */ + crouch(); +} + +/* Вертикальный прыжок вверх (seg005 jump_up, упрощённо): сверху сплошное + * (стена/пол) → seq_14 (в потолок), иначе seq_28 (пусто сверху). Зацеп за + * уступ сверху-впереди (can_grab) — K4.2, пока чистый вертикальный прыжок. */ +static void jump_up(void) +{ + control_up = release_arrows(); + if (pop_above_solid()) + seqtbl_offset_char(SEQ_14_JUMP_INTO_CEILING); + else + seqtbl_offset_char(SEQ_28_JUMP_NOTHING_ABOVE); +} + +static void up_pressed(void) +{ + /* (дверь уровня/телепорт — нет в тесте.) ↑+forward = прыжок с места; + * иначе вертикальный прыжок вверх. */ + if (control_x == CONTROL_HELD_FORWARD) + standing_jump(); + else + jump_up(); +} + +/* Осторожный шаг (Shift+forward): шаг РОВНО до кромки/стены. distance>0 — + * шаг длины distance (seq 29..42 = step1..step14); ==0 у края — шаг на + * кромку (seq_44) либо неосторожный шаг за край (seq_39). Порт seg005. */ +static void safe_step(void) +{ + int distance; + control_shift2 = CONTROL_IGNORE; /* без авто-повтора */ + control_forward = CONTROL_IGNORE; + distance = pop_edge_distance(); + if (distance) { + Kid.repeat = 1; + seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE)); + } else if (pop_edge_type() == EDGE_WALL) { + /* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад + * (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */ + Kid.repeat = 1; + seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE)); + } else if (Kid.repeat != 0) { + Kid.repeat = 0; + seqtbl_offset_char(SEQ_44_STEP_ON_EDGE); + } else { + seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */ + } +} + +/* ---- control_* обработчики состояний ------------------------------ */ +static void control_crouched(void) +{ + if (control_y != CONTROL_HELD_DOWN) { + seqtbl_offset_char(SEQ_49_STAND_UP_FROM_CROUCH); + } else if (control_forward == CONTROL_HELD) { + control_forward = CONTROL_IGNORE; + seqtbl_offset_char(SEQ_79_CROUCH_HOP); + } +} + +static void control_standing(void) +{ + /* Kid charid_0, без меча: остаётся нижняя часть control_standing. */ + if (control_shift == CONTROL_HELD) { + if (control_backward == CONTROL_HELD) back_pressed(); + else if (control_up == CONTROL_HELD) up_pressed(); + else if (control_down == CONTROL_HELD) down_pressed(); + else if (control_x == CONTROL_HELD_FORWARD && control_forward == CONTROL_HELD) + safe_step(); + } else if (control_forward == CONTROL_HELD) { + if (control_up == CONTROL_HELD) standing_jump(); /* keyboard mode */ + else forward_pressed(); + } else if (control_backward == CONTROL_HELD) { + back_pressed(); + } else if (control_up == CONTROL_HELD) { + if (control_forward == CONTROL_HELD) standing_jump(); + else up_pressed(); + } else if (control_down == CONTROL_HELD) { + down_pressed(); + } else if (control_x == CONTROL_HELD_FORWARD) { + forward_pressed(); + } +} + +static void control_turning(void) +{ + if (control_shift >= CONTROL_RELEASED && + control_x == CONTROL_HELD_FORWARD && control_y >= CONTROL_RELEASED) { + seqtbl_offset_char(SEQ_43_START_RUN_AFTER_TURN); + } +} + +static void control_startrun(void) +{ + if (control_y == CONTROL_HELD_UP && control_x == CONTROL_HELD_FORWARD) + standing_jump(); +} + +static void control_running(void) +{ + /* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок + * seq_47 при беге в стену (как оригинал), с корректной позицией. */ + if (control_x == CONTROL_RELEASED && + (Kid.frame == FRAME_7_RUN || Kid.frame == FRAME_11_RUN)) { + control_forward = release_arrows(); + seqtbl_offset_char(SEQ_13_STOP_RUN); + } else if (control_x == CONTROL_HELD_BACKWARD) { + control_backward = release_arrows(); + seqtbl_offset_char(SEQ_6_RUN_TURN); + } else if (control_y == CONTROL_HELD_UP && control_up == CONTROL_HELD) { + run_jump(); + } else if (control_down == CONTROL_HELD) { + control_down = CONTROL_IGNORE; + seqtbl_offset_char(SEQ_26_CROUCH_WHILE_RUNNING); + } +} + +/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */ +static void control(void) +{ + uint8_t f = Kid.frame; + if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53)) + control_standing(); + else if (f == FRAME_48_TURN) + control_turning(); + else if (f < 4) + control_startrun(); + else if (f >= FRAME_67_JUMPUP1 && f < FRAME_70_JUMPHANG) + ; /* control_jumpup — K4 */ + else if (f < FRAME_15_STAND) + control_running(); + else if (f >= FRAME_87_HANG1 && f < 100) + ; /* control_hanging — K4 */ + else if (f == FRAME_109_CROUCH) + control_crouched(); +} + +/* ---- Модель ввода (seg006) ---------------------------------------- */ +static void read_input(void) +{ + uint8_t l = kbd_raw_down(KBD_LEFT); + uint8_t r = kbd_raw_down(KBD_RIGHT); + uint8_t u = kbd_raw_down(KBD_UP); + uint8_t d = kbd_raw_down(KBD_DOWN); + control_x = r ? CONTROL_HELD_RIGHT : (l ? CONTROL_HELD_LEFT : CONTROL_RELEASED); + control_y = d ? CONTROL_HELD_DOWN : (u ? CONTROL_HELD_UP : CONTROL_RELEASED); + control_shift = (kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)) + ? CONTROL_HELD : CONTROL_RELEASED; +} + +/* read_user_control (seg006:1557): «свежее нажатие» (RELEASED->HELD) с + * подавлением авто-повтора (IGNORE держится, пока клавишу не отпустят). */ +static void read_user_control(void) +{ + if (control_forward >= CONTROL_RELEASED) { + if (control_x == CONTROL_HELD_FORWARD) { + if (control_forward == CONTROL_RELEASED) control_forward = CONTROL_HELD; + } else control_forward = CONTROL_RELEASED; + } + if (control_backward >= CONTROL_RELEASED) { + if (control_x == CONTROL_HELD_BACKWARD) { + if (control_backward == CONTROL_RELEASED) control_backward = CONTROL_HELD; + } else control_backward = CONTROL_RELEASED; + } + if (control_up >= CONTROL_RELEASED) { + if (control_y == CONTROL_HELD_UP) { + if (control_up == CONTROL_RELEASED) control_up = CONTROL_HELD; + } else control_up = CONTROL_RELEASED; + } + if (control_down >= CONTROL_RELEASED) { + if (control_y == CONTROL_HELD_DOWN) { + if (control_down == CONTROL_RELEASED) control_down = CONTROL_HELD; + } else control_down = CONTROL_RELEASED; + } + if (control_shift2 >= CONTROL_RELEASED) { + if (control_shift == CONTROL_HELD) { + if (control_shift2 == CONTROL_RELEASED) control_shift2 = CONTROL_HELD; + } else control_shift2 = CONTROL_RELEASED; + } +} + +static void flip_control_x(void) +{ + int8_t t; + control_x = (int8_t)-control_x; + t = control_forward; control_forward = control_backward; control_backward = t; +} + +/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */ +static void user_control(void) +{ + if (Kid.direction >= DIR_RIGHT) { + flip_control_x(); + control(); + flip_control_x(); + } else { + control(); + } +} + +static void rest_ctrl_1(void) +{ + control_forward = ctrl1_forward; control_backward = ctrl1_backward; + control_up = ctrl1_up; control_down = ctrl1_down; control_shift2 = ctrl1_shift2; +} +static void save_ctrl_1(void) +{ + ctrl1_forward = control_forward; ctrl1_backward = control_backward; + ctrl1_up = control_up; ctrl1_down = control_down; ctrl1_shift2 = control_shift2; +} + +/* ---- Публичный API ------------------------------------------------ */ +int pop_ctrl_init(void) +{ + control_forward = control_backward = control_up = control_down = CONTROL_RELEASED; + control_shift2 = CONTROL_RELEASED; + ctrl1_forward = ctrl1_backward = ctrl1_up = ctrl1_down = ctrl1_shift2 = CONTROL_RELEASED; + return kbd_raw_open(); +} + +void pop_ctrl_close(void) +{ + kbd_raw_close(); +} + +void pop_ctrl_tick(void) +{ + rest_ctrl_1(); + read_input(); + read_user_control(); + user_control(); /* -> control(): может сменить Kid.curr_seq */ + save_ctrl_1(); +} diff --git a/applications/PoP/roomtest/pop_ctrl.h b/applications/PoP/roomtest/pop_ctrl.h new file mode 100644 index 0000000..727cfc8 --- /dev/null +++ b/applications/PoP/roomtest/pop_ctrl.h @@ -0,0 +1,31 @@ +/* + * pop_ctrl.h — управление Kid (Фаза K2b): порт диспетчера control() + * (SDLPoP seg005.c) + модель ввода read_user_control/user_control + * (seg006.c) поверх held-state клавиатуры . + * + * Kid-only подмножество: без меча и стражи. Map-запросы (стена/край/ + * захват) ЗАСТАБЛЕНЫ «открытый пол» — реальная коллизия (стоп у стены, + * падение в ямы, повисание) приходит в K3/K4. Даёт отклик на клавиши: + * бег влево/вправо, разворот, стоп, присед, прыжок с места, прыжок в + * беге, присед-прыжок. + * + * Раскладка (kbd_raw, PS/2 Set 2): ←/→ = движение, ↓ = присед, ↑ = + * прыжок вверх (с →), Shift = осторожный шаг (K3). ESC — выход (проверяет + * приложение сам, канал raw забирает весь ввод у DSS). + */ +#ifndef POP_CTRL_H +#define POP_CTRL_H + +#include + +/* Открыть raw-канал клавиатуры и обнулить память авто-повтора. + * 0 — OK, -1 — ошибка (EBUSY и пр., см. kbd_raw_open). */ +int pop_ctrl_init(void); +void pop_ctrl_close(void); + +/* Один кадр ввода: прочитать клавиши -> обновить control_* -> прогнать + * диспетчер control() (может сменить Kid.curr_seq). Звать ПЕРЕД + * kid_tick() (play_seq продолжит с новой последовательности). */ +void pop_ctrl_tick(void); + +#endif diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c new file mode 100644 index 0000000..ec345a2 --- /dev/null +++ b/applications/PoP/roomtest/pop_kid.c @@ -0,0 +1,220 @@ +/* + * pop_kid.c — движок Kid (Фаза K1): порт seg006.c play_seq/load_frame + + * seqtbl-интерпретатор. Данные — kid_data.h. + */ +#include +#include +#include +#include "pop_kid.h" +#include "kid_data.h" + +kid_t Kid; + +/* Координатные таблицы (data.h). */ +static const uint8_t x_bump[20] = { + (uint8_t)-12, 2, 16, 30, 44, 58, 72, 86, 100, 114, + 128, 142, 156, 170, 184, 198, 212, 226, 240, 254 +}; +static const int16_t y_land[5] = {-8, 55, 118, 181, 244}; +#define FIRST_ONSCREEN_COLUMN 5 +#define TILE_SIZEX 14 + +/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */ +#define KID_MAXPAGES 28 +static atlas_t kidp[KID_MAXPAGES]; +static uint8_t kid_npages; + +int pop_kid_load(uint8_t npages) +{ + static const char *const names[KID_MAXPAGES] = { + "kid0.atl","kid1.atl","kid2.atl","kid3.atl","kid4.atl","kid5.atl", + "kid6.atl","kid7.atl","kid8.atl","kid9.atl","kid10.atl","kid11.atl", + "kid12.atl","kid13.atl","kid14.atl","kid15.atl","kid16.atl","kid17.atl", + "kid18.atl","kid19.atl","kid20.atl","kid21.atl","kid22.atl","kid23.atl", + "kid24.atl","kid25.atl","kid26.atl","kid27.atl" + }; + uint8_t i; + if (npages > KID_MAXPAGES) npages = KID_MAXPAGES; + for (i = 0; i < npages; i++) + if (atlas_load(&kidp[i], names[i]) != 0) + return -1; + kid_npages = npages; + return 0; +} + +void pop_kid_free(void) +{ + uint8_t i; + for (i = 0; i < kid_npages; i++) + atlas_free(&kidp[i]); + kid_npages = 0; +} + +/* ---- seqtbl-доступ (база SEQTBL_BASE) ------------------------------ */ +#define SEQ(cs) kid_seqtbl[(uint16_t)((cs) - SEQTBL_BASE)] + +static uint8_t char_dx_forward(int8_t dx) +{ + if (Kid.direction < 0) dx = (int8_t)-dx; + return (uint8_t)(Kid.x + dx); +} + +/* Аксессоры к текущему кадру (для pop_map — без дублирования kid_frames). */ +int8_t kid_cur_dx(void) { return kid_frames[Kid.frame].dx; } +uint8_t kid_cur_flags(void) { return kid_frames[Kid.frame].flags; } + +/* seqtbl_offset_char: сменить последовательность Kid (единая точка — + * pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */ +void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; } + +/* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0). */ +void play_seq(void) +{ + for (;;) { + uint8_t cmd = SEQ(Kid.curr_seq); + Kid.curr_seq++; + switch (cmd) { + case 0xFB: /* DX */ + Kid.x = char_dx_forward((int8_t)SEQ(Kid.curr_seq)); + Kid.curr_seq++; + break; + case 0xFA: /* DY */ + Kid.y = (uint8_t)(Kid.y + (int8_t)SEQ(Kid.curr_seq)); + Kid.curr_seq++; + break; + case 0xFE: /* FLIP */ + Kid.direction = (int8_t)~Kid.direction; + break; + case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */ + Kid.curr_seq += 2; + break; + case 0xFF: { /* JMP */ + uint16_t a = (uint16_t)(SEQ(Kid.curr_seq) | + ((uint16_t)SEQ((uint16_t)(Kid.curr_seq + 1)) << 8)); + Kid.curr_seq = a; + break; + } + case 0xFD: /* UP */ + Kid.curr_row--; + break; + case 0xFC: /* DOWN */ + Kid.curr_row++; + break; + case 0xF9: /* ACTION */ + Kid.action = SEQ(Kid.curr_seq); + Kid.curr_seq++; + break; + case 0xF8: /* SET_FALL */ + Kid.fall_x = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++; + Kid.fall_y = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++; + break; + case 0xF5: case 0xF4: /* KNOCK up/down */ + break; + case 0xF2: /* SOUND — потребить 1 байт */ + Kid.curr_seq++; + break; + case 0xF1: /* END_LEVEL */ + break; + case 0xF3: /* GET_ITEM — потребить 1 байт */ + Kid.curr_seq++; + break; + case 0xF6: /* DIE nop */ + break; + default: /* КАДР */ + Kid.frame = cmd; + return; + } + } +} + +void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir) +{ + Kid.curr_col = col; + Kid.curr_row = row; + Kid.direction = dir; + Kid.x = (uint8_t)(x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX); + Kid.y = (uint8_t)y_land[row + 1]; + Kid.fall_x = Kid.fall_y = 0; + Kid.repeat = 0; + Kid.curr_seq = kid_seq_off[seq_id]; + play_seq(); /* до первого кадра */ +} + +void kid_tick(void) +{ + play_seq(); +} + +/* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy + * (seg008.c:1728 load_frame_to_obj). НИЗ спрайта на obj_y, левый край + * на obj_x. Калибровка XOFF/YOFF — сверить с эталоном. + * + * Зеркалирование (K2a): спрайты Kid хранятся ЛИЦОМ ВЛЕВО (как в оригинале). + * seg008.c:864 — флип, когда персонаж смотрит ВПРАВО (direction==dir_0_right + * ==0), и тогда xpos -= image->w (draw_mid). Реализуем бесплатным + * горизонтальным флипом column-major спрайта (gfx_blit_cols flip=1): + * зеркало происходит ВНУТРИ [x,x+w), поэтому при флипе подаём x=obj_x-w, + * чтобы «hot-point» кадра остался на месте. + * + * Kid рисуется банком GFX_BANK_SPRITE (только видео-ОЗУ, НЕ теневая копия + * — 0xFF прозрачен), чтобы kid_heal() мог восстановить чистый фон из + * теневой копии (фон запёкся в неё при pop_room_draw). Прямоугольник + * последней отрисовки запоминается для heal. */ +#define KID_XOFF 0 +#define KID_YOFF 0 + +static int kid_lx, kid_ly; +static uint16_t kid_lw, kid_lh; +static uint8_t kid_lvalid; + +/* Восстановить фон из теневой копии на месте ПРЕДЫДУЩЕГО кадра Kid. */ +void kid_heal(void) +{ + if (kid_lvalid) + gfx_heal(kid_lx, kid_ly, kid_lw, kid_lh); + kid_lvalid = 0; +} + +void kid_draw(void) +{ + kframe fr = kid_frames[Kid.frame]; + int fwd, obj_x, obj_y, top; + uint8_t page, idx; + const uint8_t *img; + uint16_t w, h; + + if (fr.image == 255) + return; /* пустой кадр */ + fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx); + obj_x = (fwd << 1) - 116; + obj_y = (int)Kid.y + fr.dy; + if ((int8_t)(fr.flags ^ Kid.direction) >= 0) + obj_x++; /* even/odd pixel (в ЛОГИЧЕСКОЙ координате) */ + /* seg008 calc_screen_x_coord: логическая X (280 шир = 28px/тайл) -> + * экран (320 шир = 32px/тайл), ×320/280 = ×8/7. БЕЗ этого Kid уезжает + * влево от тайлов тем сильнее, чем правее (col9 ~36px). Коллизия — в + * логической системе (не масштабируется). */ + obj_x = obj_x * 8 / 7; + + page = (uint8_t)(fr.image >> 3); + idx = (uint8_t)(fr.image & 7); + if (page >= kid_npages) + return; /* страница не загружена */ + img = (const uint8_t *)atlas_image(&kidp[page], idx); + gfx_w0_map(kidp[page].page); + w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); + h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); + if (w && h) { + uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */ + int bx = obj_x + KID_XOFF; + if (flip) + bx -= (int)w; /* seg008: xpos -= image->w */ + top = obj_y - (int)h + 1 + KID_YOFF; + gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */ + gfx_blit_cols(bx, top, img, flip); + gfx_set_bank(GFX_BANK_NORMAL); + kid_lx = bx; kid_ly = top; + kid_lw = w; kid_lh = h; kid_lvalid = 1; + } + gfx_w0_unmap(); +} diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h new file mode 100644 index 0000000..3ae3e32 --- /dev/null +++ b/applications/PoP/roomtest/pop_kid.h @@ -0,0 +1,63 @@ +/* + * pop_kid.h — движок анимации/движения Kid (Фаза K1): интерпретатор + * seqtbl (play_seq) + frame_table. Порт SDLPoP seg006.c. Данные — + * kid_data.h (pop_extract_kid_data.py). + * + * K1: только проигрывание последовательностей кадров с авторскими + * смещениями (движение = SEQ_DX/DY + frame.dx/dy). Управление (K2) и + * коллизия карты (K3) — отдельно. + */ +#ifndef POP_KID_H +#define POP_KID_H + +#include + +/* Состояние персонажа (подмножество char_type, types.h). */ +typedef struct { + uint8_t frame; + uint8_t x, y; + int8_t direction; /* -1 влево, 0 вправо */ + int8_t curr_col, curr_row; + uint8_t action; + int8_t fall_x, fall_y; + uint8_t repeat; + uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */ +} kid_t; + +extern kid_t Kid; + +/* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph. + * 0 — OK, -1 — ошибка. */ +int pop_kid_load(uint8_t npages); +void pop_kid_free(void); + +/* Инициализировать Kid: последовательность seq_id, клетка col/row, + * направление dir (-1/0). Ставит x/y по x_bump/y_land. */ +void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir); + +/* Один логический тик: play_seq() — продвинуть последовательность до + * следующего кадра (применить SEQ_DX/DY/ACTION/...). */ +void kid_tick(void); + +/* play_seq напрямую (для pop_map start_fall — переход в fall-seq в тот же + * physics-шаг, как оригинал). */ +void play_seq(void); + +/* Поля текущего кадра kid_frames[Kid.frame] (pop_map, без дублирования + * таблицы во втором TU). */ +int8_t kid_cur_dx(void); +uint8_t kid_cur_flags(void); + +/* Сменить последовательность (Kid.curr_seq = kid_seq_off[seq_id]) — единая + * точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */ +void kid_set_seq(uint8_t seq_id); + +/* Нарисовать текущий кадр Kid (банк GFX_BANK_SPRITE — видео-ОЗУ, без + * теневой копии; запоминает прямоугольник для kid_heal). */ +void kid_draw(void); + +/* Восстановить фон (из теневой копии) на месте ПРЕДЫДУЩЕГО кадра Kid — + * вместо перерисовки всего экрана. Звать ПЕРЕД kid_tick/kid_draw. */ +void kid_heal(void); + +#endif diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c new file mode 100644 index 0000000..54bf482 --- /dev/null +++ b/applications/PoP/roomtest/pop_map.c @@ -0,0 +1,382 @@ +/* + * pop_map.c — коллизия Kid с картой (Фаза K3.2: падение). Порт SDLPoP + * seg005/seg006. Пер-кадровый physics-диспетч check_action() по Char.action: + * grounded (stand/run/step/crouch) -> check_on_floor(): кадр с флагом + * FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall(); + * стена -> in_wall(); + * freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed), + * достиг y_land[curr_row+1] -> land()/inc_curr_row. + * + * Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX); + * col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 = + * div_mod(xpos-7). Карта: fg[row*10+col] & 0x1F; col<0/>9,row<0/>2 = стена. + * + * MVP: без spike/HP/grab/gate — падение + мягкое/среднее приземление. + * Бег В стену (check_bumped) и осторожный шаг у края (get_edge_distance) — + * следующим шагом. + */ +#include +#include "pop_kid.h" +#include "pop_map.h" + +/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */ +#define TILE_EMPTY 0 +#define TILE_WALL 20 +#define ACT_STAND 0 +#define ACT_RUNJUMP 1 +#define ACT_HANGCLIMB 2 +#define ACT_MIDAIR 3 +#define ACT_FREEFALL 4 +#define ACT_BUMPED 5 +#define ACT_HANGSTRAIGHT 6 + +#define SEQ_7_FALL 7 /* stepfall (stand/run/step/crouch) */ +#define SEQ_17_SOFT_LAND 17 +#define SEQ_18_FALL_STANDJUMP 18 +#define SEQ_19_FALL 19 /* stepfall2 (run frames 9/13) */ +#define SEQ_20_MEDIUM_LAND 20 +#define SEQ_21_FALL_RUNJUMP 21 +#define SEQ_47_BUMP 47 /* удар в стену: dx(-4) отскок + 50-52 + stand */ + +#define FRAME_NEEDS_FLOOR 0x40 +#define FRAME_WEIGHT_X 0x1F +#define TILE_SIZEX 14 +#define TILE_RIGHTX 13 +#define SCREENSPACE_X 58 +#define FALL_ACCEL 3 +#define FALL_MAX 33 + +#define TILE_MIDX 7 +#define FIRST_ONSCREEN_COLUMN 5 +#define FRAME_THIN 0x20 + +static const int16_t y_land[5] = {-8, 55, 118, 181, 244}; +static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */ +static const int16_t x_bump[20] = { + -12, 2, 16, 30, 44, 58, 72, 86, 100, 114, + 128, 142, 156, 170, 184, 198, 212, 226, 240, 254 +}; +/* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */ +static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0}; /* wall_dist_from_left */ +static const int8_t wall_dr[6] = {0, 0, 10, 13, 0, 0}; /* wall_dist_from_right */ +static int8_t g_infrontx; /* колонка «перед персонажем» (set в get_tile_infrontof) */ + +/* ---- Карта комнаты ------------------------------------------------- */ +uint8_t pop_fell_out; /* 1 = Kid упал за низ комнаты (сброс приложением) */ +static const uint8_t *g_fg; /* fg[30], row*10+col */ +static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */ +static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */ + +void pop_map_set(const uint8_t *fg) { g_fg = fg; } + +static uint8_t get_tile(int8_t col, int8_t row) +{ + if (col < 0 || col > 9 || row < 0 || row > 2) + g_curr_tile = TILE_WALL; /* край уровня = стена */ + else + g_curr_tile = g_fg[row * 10 + col] & 0x1F; + return g_curr_tile; +} + +static uint8_t tile_is_floor(uint8_t t) +{ + switch (t) { + case 0: case 9: case 12: case 20: + case 26: case 27: case 28: case 29: + return 0; + default: + return 1; + } +} + +static uint8_t get_tile_at_char(void) +{ + return get_tile(Kid.curr_col, Kid.curr_row); +} + +static uint8_t get_tile_infrontof_char(void) +{ + g_infrontx = (int8_t)(dir_front[Kid.direction + 1] + Kid.curr_col); + return get_tile(g_infrontx, Kid.curr_row); +} + +static uint8_t wall_type(uint8_t t) +{ + switch (t) { + case 4: case 7: case 12: return 1; /* стена справа (gate/doortop) */ + case 13: return 2; /* стена слева (mirror) */ + case 18: return 3; /* chomper */ + case 20: return 4; /* стена с обеих сторон */ + default: return 0; + } +} + +/* ---- Координаты ---------------------------------------------------- */ +static int char_dx_forward(int8_t dx) +{ + int d = dx; + if (Kid.direction < 0) d = -d; + return (int)Kid.x + d; +} + +/* col = (xpos-58)/14 округл. вниз; g_obj_xl = позиция в тайле (0..13). */ +static int8_t get_tile_div_mod(int xpos) +{ + int x = xpos - SCREENSPACE_X; + int xh = x / TILE_SIZEX; + int xl = x % TILE_SIZEX; + if (xl < 0) { --xh; xl += TILE_SIZEX; } /* округление вниз */ + g_obj_xl = (uint8_t)xl; + return (int8_t)xh; +} + +static int8_t get_tile_div_mod_m7(int xpos) { return get_tile_div_mod(xpos - 7); } + +/* «весовая» точка кадра (dx с вычетом weight-битов флагов), в направлении. */ +static int dx_weight(void) +{ + int8_t offset = (int8_t)(kid_cur_dx() - (kid_cur_flags() & FRAME_WEIGHT_X)); + return char_dx_forward(offset); +} + +static void determine_col(void) +{ + Kid.curr_col = get_tile_div_mod_m7(dx_weight()); +} + +/* расстояние до края тайла (для in_wall). */ +static int distance_to_edge(int xpos) +{ + int d; + get_tile_div_mod_m7(xpos); /* -> g_obj_xl */ + d = g_obj_xl; + if (Kid.direction == 0) d = TILE_RIGHTX - d; /* dir_0_right */ + return d; +} +static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); } + +/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- * + * Передний край персонажа char_x_*_coll = char_dx_forward(cur_frame.dx) + * (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для + * THIN-кадров. dist_from_wall_forward — расстояние переднего края до грани + * стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */ +static int char_front_coll(void) +{ + int cx = char_dx_forward(kid_cur_dx()); + if (kid_cur_flags() & FRAME_THIN) + cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */ + return cx; +} + +static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol) +{ + uint8_t type = wall_type(tiletype); + int coll_left, cx; + if (type == 0) return 127; + coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX; + cx = char_front_coll(); + if (Kid.direction < 0) /* лицом влево */ + return cx - (coll_left + TILE_RIGHTX - wall_dr[type]); + else /* лицом вправо */ + return wall_dl[type] + coll_left - cx; +} + +/* Выровнять передний край РОВНО к грани стены, если он зашёл за неё (bumped: + * Char.x += dist; char_dx_forward отражает знак для обоих направлений). + * Стена ищется под персонажем, иначе перед ним. */ +static void in_wall(void) +{ + uint8_t t; int8_t tcol; int d; + t = get_tile_at_char(); tcol = Kid.curr_col; + if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; } + if (!wall_type(t)) return; + d = dist_from_wall_forward(t, tcol); + if (d < 0) { + Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */ + determine_col(); + get_tile_at_char(); + } +} + +/* ---- Падение ------------------------------------------------------- */ +static void inc_curr_row(void) { Kid.curr_row++; } + +static void land(void) +{ + uint8_t seq; + Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; + /* к краю пола — чуть назад (как оригинал) */ + if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) + Kid.x = (uint8_t)char_dx_forward(-3); + if (Kid.fall_y < 22) + seq = SEQ_17_SOFT_LAND; /* 1 этаж — мягко */ + else + seq = SEQ_20_MEDIUM_LAND; /* 2+ этажа — присесть (HP/смерть — позже) */ + Kid.fall_x = Kid.fall_y = 0; + kid_set_seq(seq); + play_seq(); + determine_col(); +} + +static void start_fall(void) +{ + uint8_t frame = Kid.frame, seq; + inc_curr_row(); + if (frame == 9 || frame == 13) seq = SEQ_19_FALL; /* падение из бега */ + else if (frame == 26) seq = SEQ_18_FALL_STANDJUMP; + else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP; + else seq = SEQ_7_FALL; /* stand/step/crouch */ + kid_set_seq(seq); + play_seq(); + determine_col(); + if (get_tile_at_char() == TILE_WALL) + in_wall(); +} + +static void do_fall(void) +{ + uint8_t nrow = (uint8_t)(Kid.curr_row + 1); + if (nrow > 4) nrow = 4; /* защита y_land[] от выхода */ + if ((uint16_t)y_land[nrow] > (uint16_t)Kid.y) { + /* ещё летит (check_grab — K4) */ + } else { + if (get_tile_at_char() == TILE_WALL) + in_wall(); + if (tile_is_floor(g_curr_tile)) + land(); + else if (Kid.curr_row < 2) + inc_curr_row(); /* следующий ряд ВНУТРИ комнаты */ + else + pop_fell_out = 1; /* упал за низ комнаты (нет пола, + комнаты снизу нет) — приложение + сбросит Kid (K-later: переход) */ + } +} + +static void fall_accel(void) +{ + if (Kid.action == ACT_FREEFALL) { + int fy = (uint8_t)Kid.fall_y + FALL_ACCEL; + if (fy > FALL_MAX) fy = FALL_MAX; + Kid.fall_y = (int8_t)fy; + } +} + +static void fall_speed(void) +{ + Kid.y = (uint8_t)(Kid.y + (uint8_t)Kid.fall_y); + if (Kid.action == ACT_FREEFALL) { + Kid.x = (uint8_t)char_dx_forward(Kid.fall_x); + determine_col(); + } +} + +/* ---- get_edge_distance (seg004:067C) — ТОЧНО ---------------------- * + * Расстояние переднего края до стены/края впереди + тип g_edge_type: WALL + * (стена в пределах тайла), FLOOR (пол — далеко, 11), CLOSER (край уступа + * над ямой — distance_to_edge_weight). Стена сперва под персонажем, потом + * перед ним; dist<=TILE_RIGHTX -> WALL, иначе FLOOR. */ +static uint8_t g_edge_type; + +static int edge_classify(int d) +{ + if (d <= TILE_RIGHTX) g_edge_type = EDGE_WALL; + else { g_edge_type = EDGE_FLOOR; d = 11; } + return d; +} + +int pop_edge_distance(void) +{ + uint8_t t; int d; + determine_col(); + t = get_tile_at_char(); + if (wall_type(t)) { + d = dist_from_wall_forward(t, Kid.curr_col); + if (d >= 0) return edge_classify(d); + } + t = get_tile_infrontof_char(); + if (wall_type(t)) { + d = dist_from_wall_forward(t, g_infrontx); + if (d >= 0) return edge_classify(d); + } + if (tile_is_floor(t)) { g_edge_type = EDGE_FLOOR; return 11; } + g_edge_type = EDGE_CLOSER; /* яма впереди — до кромки уступа */ + return distance_to_edge_weight(); +} + +uint8_t pop_edge_type(void) { return g_edge_type; } + +/* Тайл НАД персонажем (curr_row-1, curr_col) сплошной (стена/пол)? — выбор + * seq прыжка вверх: solid -> seq_14 (в потолок), иначе seq_28 (пусто сверху). + * K4.1 упрощение: без grab-логики (уступ сверху-впереди) — та в K4.2. */ +uint8_t pop_above_solid(void) +{ + uint8_t t = get_tile(Kid.curr_col, (int8_t)(Kid.curr_row - 1)); + return (t == TILE_WALL || tile_is_floor(t)); +} + +/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига — + * позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */ +int pop_wall_ahead(void) +{ + int d = pop_edge_distance(); + return (g_edge_type == EDGE_WALL && d <= 2); +} + +/* ---- Диспетчер ----------------------------------------------------- */ +static void check_on_floor(void) +{ + if (kid_cur_flags() & FRAME_NEEDS_FLOOR) { + if (get_tile_at_char() == TILE_WALL) + in_wall(); /* забежал в стену -> выровнять */ + if (!tile_is_floor(g_curr_tile)) + start_fall(); + } +} + +static void check_action(void) +{ + uint8_t action = Kid.action, frame = Kid.frame; + if (action == ACT_BUMPED || action == ACT_HANGSTRAIGHT) { + if (frame == 109 || (frame >= 110 && frame <= 119)) + check_on_floor(); + } else if (action == ACT_FREEFALL) { + do_fall(); + } else if (action == ACT_MIDAIR) { + /* frames 102..105: check_grab — K4 */ + } else if (action != ACT_HANGCLIMB) { + check_on_floor(); + } +} + +/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять + * к грани И проиграть отскок seq_47_bump (dx−4 назад + кадры 50-52 + stand), + * как в оригинале (bumped_floor). Это даёт «шаг назад со звуком» при беге/ + * шаге в стену вместо падения. Уже в отскоке (action bumped) — только + * выравниваем, без рестарта. В падении/висе не трогаем. */ +static void check_bumped(void) +{ + uint8_t t; int8_t tcol; int d; + if (Kid.action == ACT_FREEFALL || Kid.action == ACT_HANGCLIMB) return; + t = get_tile_at_char(); tcol = Kid.curr_col; + if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; } + if (!wall_type(t)) return; + d = dist_from_wall_forward(t, tcol); + if (d >= 0) return; /* ещё не зашёл за грань */ + Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */ + determine_col(); + if (Kid.action == ACT_BUMPED) return; /* уже в отскоке — не рестартить */ + Kid.fall_y = 0; + kid_set_seq(SEQ_47_BUMP); /* отскок (TODO: bumped_sound) */ + play_seq(); + determine_col(); +} + +void pop_phys_tick(void) +{ + fall_accel(); + fall_speed(); + determine_col(); + check_bumped(); /* удержать у стены до check_action (порядок PoP) */ + check_action(); +} diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h new file mode 100644 index 0000000..bf804b2 --- /dev/null +++ b/applications/PoP/roomtest/pop_map.h @@ -0,0 +1,48 @@ +/* + * pop_map.h — коллизия Kid с картой комнаты (Фаза K3): тайловые запросы + * + физика падения + приземление. Порт SDLPoP seg005/seg006 + * (check_on_floor/start_fall/do_fall/fall_accel/fall_speed/land/in_wall). + * + * K3.2 (падение): Kid, сойдя/сбежав с края пола (тайл под ним не floor), + * начинает падать (fall_y-ускорение, Char.y растёт), приземляется на пол + * ниже (soft/medium land) или продолжает вниз. Стена под персонажем — + * in_wall (выталкивание). Бег В стену (check_bumped) — отдельным шагом. + */ +#ifndef POP_MAP_H +#define POP_MAP_H + +#include + +/* Задать карту текущей комнаты: fg[30] = коды тайлов (row*10+col, 3×10). + * col<0/>9 или row<0/>2 трактуются как стена (край уровня). */ +void pop_map_set(const uint8_t *fg); + +/* Установлен в 1, когда Kid упал ниже комнаты (комнаты снизу нет в этом + * тесте). Приложение должно сбросить Kid (kid_init) и снять флаг. */ +extern uint8_t pop_fell_out; + +/* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed + + * determine_col + check_action (триггер падения / do_fall / приземление). + * Порядок как в оригинале (seg000 play_kid_frame). */ +void pop_phys_tick(void); + +/* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после + * pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */ +#define EDGE_CLOSER 0 /* край уступа над ямой (дистанция = до кромки) */ +#define EDGE_WALL 1 /* стена впереди/под персонажем */ +#define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */ + +/* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */ +int pop_edge_distance(void); +uint8_t pop_edge_type(void); + +/* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной + * позиции (передний край у стены, симметрично) и возвращает 1. Для + * control_running (чистый стоп у стены). */ +int pop_wall_ahead(void); + +/* Тайл над персонажем сплошной (стена/пол)? — выбор seq вертикального + * прыжка вверх (K4.1). */ +uint8_t pop_above_solid(void); + +#endif diff --git a/applications/PoP/roomtest/room1_data.h b/applications/PoP/roomtest/room1_data.h new file mode 100644 index 0000000..d0cc8de --- /dev/null +++ b/applications/PoP/roomtest/room1_data.h @@ -0,0 +1,10 @@ +/* room1_data.h — данные комнаты 1 ур.1 (res2001) + левый столбец + * соседа (room 5 col9) для границы-решётки. Сгенерировано + * gen из render_room; fg=foretable code, bg=backtable modifier. */ +#ifndef ROOM1_DATA_H +#define ROOM1_DATA_H +static const unsigned char room1_fg[30] = {0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x14,0x14,0x13,0x13,0x01,0x03,0x00,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x0E,0x03,0x0B,0x01,0x01,0x14}; +static const unsigned char room1_bg[30] = {0xFF,0xFF,0xFF,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00}; +static const unsigned char leftcol_fg[3] = {0x04,0x14,0x14}; +static const unsigned char leftcol_bg[3] = {0x01,0x00,0x00}; +#endif diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c new file mode 100644 index 0000000..b9ab979 --- /dev/null +++ b/applications/PoP/roomtest/roomtest.c @@ -0,0 +1,79 @@ +/* + * roomtest.c — проверка порта статического фона (Шаг 3): рисует комнату 1 + * уровня 1 реальной композицией слоёв (pop_bg.c) и держит на экране. + * Сверять на MAME с эталоном applications/PoP/toolchain/1.1-2.png. + */ +#include +#include +#include +#include +#include +#include +#include "pop_bg.h" +#include "pop_kid.h" +#include "pop_ctrl.h" +#include "pop_map.h" +#include "room1_data.h" + +/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */ +#define SEQ_STAND 2 + +int main(void) +{ + if (pop_bg_load() != 0) { /* атласы — ДО initgraph */ + puts("pop_bg_load failed"); + return 1; + } + if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */ + puts("kid atlas load failed"); + return 1; + } + + initgraph(); + gfx_set_draw_page(0); + gfx_set_visible_page(0); + if (gfx_pal_fload(0, "kid.pal") < 0) /* единая игровая палитра */ + gfx_pal_fload(0, "a:\\kid.pal"); + gfx_pal_sync(); + + /* Фон комнаты рисуется ОДИН РАЗ — в видео-ОЗУ И теневую копию + * (GFX_BANK_TRANSPARENT внутри pop_room_draw). Дальше каждый тик + * восстанавливаем ТОЛЬКО прямоугольник Кида из теневой копии (heal), + * а не весь экран. */ + setfillstyle(SOLID_FILL, BLACK); + bar(0, 0, 319, 255); + pop_room_draw(1, room1_fg, room1_bg, leftcol_fg, leftcol_bg); + + /* K2b: Kid стоит на уступе (row1), управляется с клавиатуры. + * ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход. */ + pop_map_set(room1_fg); /* карта для коллизии/падения (K3) */ + kid_init(SEQ_STAND, 1, 1, 0); + kid_draw(); + + if (pop_ctrl_init() != 0) { /* raw-канал клавиатуры (забирает ввод у DSS) */ + closegraph(); + puts("kbd_raw open failed"); + return 1; + } + + while (!kbd_raw_down(KBD_ESC)) { + gfx_wait_vsync(); + gfx_wait_vsync(); + gfx_wait_vsync(); + pop_ctrl_tick(); /* ввод -> control(): смена seq */ + kid_heal(); /* стереть прошлый кадр (фон из тени) */ + kid_tick(); /* play_seq: следующий кадр */ + pop_phys_tick(); /* K3: падение/приземление/стена */ + if (pop_fell_out) { /* упал за низ комнаты — сброс на старт */ + pop_fell_out = 0; + kid_init(SEQ_STAND, 1, 1, 0); + } + kid_draw(); /* нарисовать новый кадр */ + } + + pop_ctrl_close(); + closegraph(); + pop_bg_free(); + pop_kid_free(); + return 0; +} diff --git a/applications/PoP/toolchain/1.1 b/applications/PoP/toolchain/1.1 new file mode 100644 index 0000000000000000000000000000000000000000..b71abdc62e1b1e1713f570544b58b946c4e72892 GIT binary patch literal 36981 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 literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/1.1-2.png b/applications/PoP/toolchain/1.1-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9c8a8e9505604cd6480628b144a89daeb1412715 GIT binary patch literal 11117 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 kframe kid_frames[] {image,dx,dy,flags} + original_seqtbl[] (seqtbl.c) -> byte kid_seqtbl[] (2310 Б, база 0x196E) + original_seqtbl_offsets[] (seqtbl.c) -> word kid_seq_off[] (seq_id -> адрес) + +seqtbl адресуется базой SEQTBL_BASE=0x196E: элемент = kid_seqtbl[addr-0x196E]. +Автогенерация — чтобы не переписывать ~180 кадров + 2КБ байткода руками. +""" +import os +import re + +SRC = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/src" +OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "roomtest", "kid_data.h") +SEQTBL_BASE = 0x196E + + +def read(name): + return open(os.path.join(SRC, name)).read() + + +def parse_frame_table(txt): + """frame_table_kid[] -> список (image, dx, dy, flags). Записи вида + { IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как OR-выражения.""" + m = re.search(r'const frame_type frame_table_kid\[\]\s*=\s*\{(.*?)\n\};', txt, re.S) + body = m.group(1) + frames = [] + for row in re.findall(r'\{([^}]*)\}', body): + parts = [p.strip() for p in row.split(',')] + image = int(parts[0], 0) + sword = eval(parts[1], {}, {}) # напр. "0x00| 9" + dx = int(parts[2], 0) + dy = int(parts[3], 0) + flags = eval(parts[4], {}, {}) + frames.append((image & 0xFF, dx & 0xFF, dy & 0xFF, flags & 0xFF, sword & 0xFF)) + return frames + + +def parse_byte_array(txt, name): + m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S) + return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))] + + +def parse_word_array(txt, name): + m = re.search(r'const word %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S) + return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))] + + +def emit_c(vals, per_line=12, fmt="0x%02X"): + out = [] + for i in range(0, len(vals), per_line): + out.append(" " + ",".join(fmt % v for v in vals[i:i + per_line]) + ",") + return "\n".join(out) + + +def main(): + seg006 = read("seg006.c") + seqc = read("seqtbl.c") + + frames = parse_frame_table(seg006) + seqtbl = parse_byte_array(seqc, "original_seqtbl") + offs = parse_word_array(seqc, "original_seqtbl_offsets") + + with open(OUT, "w") as f: + f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n" + " * offsets), извлечено toolchain/pop_extract_kid_data.py из\n" + " * SDLPoP. НЕ править вручную. seqtbl адресуется базой\n" + " * SEQTBL_BASE: kid_seqtbl[addr - SEQTBL_BASE]. */\n") + f.write("#ifndef KID_DATA_H\n#define KID_DATA_H\n#include \n\n") + f.write("#define SEQTBL_BASE 0x%04Xu\n\n" % SEQTBL_BASE) + + # frame table + f.write("/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid). */\n") + f.write("typedef struct { uint8_t image; int8_t dx, dy; " + "uint8_t flags, sword; } kframe;\n") + f.write("#define KID_NFRAMES %d\n" % len(frames)) + f.write("static const kframe kid_frames[KID_NFRAMES] = {\n") + for (img, dx, dy, fl, sw) in frames: + dxs = dx - 256 if dx > 127 else dx + dys = dy - 256 if dy > 127 else dy + f.write(" {%3d,%4d,%4d,0x%02X,0x%02X},\n" % (img, dxs, dys, fl, sw)) + f.write("};\n\n") + + # seqtbl bytes + f.write("#define KID_SEQTBL_LEN %d\n" % len(seqtbl)) + f.write("static const uint8_t kid_seqtbl[KID_SEQTBL_LEN] = {\n") + f.write(emit_c(seqtbl) + "\n};\n\n") + + # seq offsets + f.write("#define KID_NSEQ %d\n" % len(offs)) + f.write("static const uint16_t kid_seq_off[KID_NSEQ] = {\n") + f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n") + f.write("#endif\n") + + print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, " + f"{len(offs)} seq-оффсетов -> {OUT}") + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py new file mode 100644 index 0000000..27aea11 --- /dev/null +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 +""" +pop_pack_bg.py — упаковщик спрайтов СТАТИЧЕСКОГО ФОНА PoP (Шаг 2 порта). + +Что делает: + 1. Определяет РЕАЛЬНО используемый набор спрайтов фона (прогоняет + render_room.py по всем комнатам всех уровней, собирает (chtab,id)). + 2. Грузит исходные PNG (VDUNGEON→VPALACE каскад, как в игре), переводит + 16-цветный индексированный пиксель в 8bpp getimage-блоб: + индекс 0 -> 0xFF (прозрачность getimage) + индекс i -> 0x50+i (env, chtab_6) / 0x60+i (wall, chtab_7) + 3. Пакует в атласы .atl с ПРЯМОЙ адресацией (idx = id), каталог любого + размера (блобы кладутся ПОСЛЕ каталога — загрузчик atlas_image читает + offset явно, лимит 19 из libbgi/mkatlas.py не действует). libbgi НЕ + трогаем. Дырки в каталоге (неиспользуемые id) = фейковые записи, + ссылающиеся на общий пустой 0×0-блоб (осознанная трата места в EMM + ради нулевых remap-таблиц в W2 — требование пользователя). + 4. Собирает палитру Sprinter (.pal, записи B,G,R,0) из res200.pal (env + -> слоты 0x50..0x5F) и res360.pal (wall -> 0x60..0x6F). VGA 6-бит + каналы масштабируются в 8-бит. + 5. Пишет C-заголовок с раскладкой (имена файлов, SHIFT, число страниц, + набор fore-id). + +Раскладка (прямая адресация, SHIFT=5): + ENV фон : 5 страниц, страница = id>>5, idx = id&31 + WALL : 1 страница, idx = id + FORE : 1 страница (спрайты «перед персонажем»: фронты столбов/ + ворот/дебриса), idx = id — отдельный атлас (жизненный цикл + другой: рисуются каждый кадр как sprite_t, не запекаются). +W2-цена: 7 хэндлов atlas_t (~21 Б), ноль таблиц id->позиция. + +Выход: в applications/PoP/poc/res/bg/ — *.atl + pop_bg.pal + pop_bg_atlas.h +""" +import os +import sys +from PIL import Image + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +import render_room as R # noqa: E402 (экстрактор + каскад загрузки) + +OUT_DIR = os.path.join(HERE, "..", "poc", "res", "bg") + +# Спрайты FORE-слоя (add_foretable в seg008 — «перед персонажем»): фронты +# столбов, ворот, дебриса. См. tile_table.fore_id + анализ pop_background_strategy. +FORE_ENV_IDS = {9, 12, 49, 88, 91, 95, 100} + +ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31 +ENV_MASK = (1 << ENV_SHIFT) - 1 + +# ---- .atl формат (== libbgi, но каталог не ограничен 19) -------------- +ATL_MAGIC = b"SPA1" +ATL_DIR_OFF = 0x68 # _ATL_DIR_OFF +ATL_MIN_SIZE = 0x100 # atlas_load требует n >= _ATL_DATA_OFF +PAGE_SIZE = 16384 + + +def die(msg): + sys.exit(f"pop_pack_bg: {msg}") + + +# ---- 1. Набор используемых спрайтов ---------------------------------- +def collect_used(): + """Прогон render_room по всем комнатам -> множество (chtab, id).""" + used = set() + orig = R.load_sprite + + def spy(id, chtab=R.CHTAB_ENV, reason=""): + img = orig(id, chtab, reason) + if img is not None and id > 0: + used.add((chtab, id)) + return img + + R.load_sprite = spy + for lvl in range(1, 15): + fn = os.path.join(R.LEVEL_DIR, f"res{2000 + lvl}.bin") + if not os.path.exists(fn): + continue + lv = R.Level(open(fn, "rb").read()) + for room in range(1, 25): + rm = R.Room(lv, room) + L = lv.links[room][0] + if L: + rm.left_room = R.Room(lv, L) + canvas = Image.new("RGBA", (320, 192), (0, 0, 0, 0)) + for dr in (2, 1, 0): + for c in range(10): + R.draw_tile(canvas, rm, dr, c) + R.load_sprite = orig + return used + + +# ---- 2. Загрузка спрайта в 8bpp-блоб --------------------------------- +def load_indexed(chtab, id): + """PNG (mode P, 16 цветов) -> (w, h, bytes 8bpp). Каскад VDUNGEON->VPALACE. + Пиксель 0 -> 0xFF (прозрач); i -> base+i (env 0x50 / wall 0x60).""" + base_res = 360 if chtab == R.CHTAB_WALL else 200 + pal_base = 0x60 if chtab == R.CHTAB_WALL else 0x50 + fname = f"res{base_res + id}.png" + path = None + for d in (R.VDUNGEON_DIR, R.VPALACE_DIR): + p = os.path.join(d, fname) + if os.path.exists(p): + path = p + break + if path is None: + die(f"нет файла {fname}") + im = Image.open(path) + if im.mode != "P": + die(f"{fname}: mode {im.mode}, ожидался P (16-цветный)") + src = im.tobytes() + out = bytes((0xFF if v == 0 else pal_base + v) for v in src) + return im.width, im.height, out + + +def blob(w, h, pix): + """getimage-блоб: u16 w, u16 h (LE) + пиксели.""" + return w.to_bytes(2, "little") + h.to_bytes(2, "little") + pix + + +# ---- 3. Пакер .atl (каталог любого размера) -------------------------- +def pack_atlas(path, entries): + """entries: dict idx -> (w, h, pix). Пустые idx (0..maxidx) заполняются + фейковой ссылкой на общий 0×0-блоб. Каталог count записей с 0x68, + блобы — сразу после каталога.""" + if not entries: + die(f"{path}: пустой атлас") + count = max(entries) + 1 + if count > 255: + die(f"{path}: count {count} > 255") + data_off = ATL_DIR_OFF + count * 8 + + blobs = bytearray() + empty_off = data_off + len(blobs) # общий 0×0-блоб для дырок + blobs += blob(0, 0, b"") + dir_bytes = bytearray(count * 8) + for idx in range(count): + e = entries.get(idx) + if e is None: + off = empty_off + fw = fh = 0 + else: + w, h, pix = e + off = data_off + len(blobs) + blobs += blob(w, h, pix) + fw = min(w, 255) + fh = min(h, 255) + d = idx * 8 + dir_bytes[d:d + 2] = off.to_bytes(2, "little") + dir_bytes[d + 2] = fw + dir_bytes[d + 3] = fh + dir_bytes[d + 4] = 1 # nx + dir_bytes[d + 5] = 1 # ny + # d+6,d+7 резерв + + out = bytearray(data_off) + out[0:4] = ATL_MAGIC + out[4] = count + out[ATL_DIR_OFF:ATL_DIR_OFF + len(dir_bytes)] = dir_bytes + out += blobs + if len(out) < ATL_MIN_SIZE: + out += bytes(ATL_MIN_SIZE - len(out)) # atlas_load: n >= 0x100 + if len(out) > PAGE_SIZE: + die(f"{path}: {len(out)} Б > {PAGE_SIZE} (одна EMM-страница)") + with open(path, "wb") as f: + f.write(out) + return count, len(out) + + +# ---- 4. Палитра ------------------------------------------------------ +def scale6to8(v): + return (v * 255) // 63 + + +def build_palette(out_path): + """256-записная палитра B,G,R,0: env@0x50..0x5F, wall@0x60..0x6F.""" + def read_pal(res, d): + b = open(os.path.join(d, res), "rb").read() + return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)] + + env = read_pal("res200.pal", R.VDUNGEON_DIR) + wall = read_pal("res360.pal", R.VDUNGEON_DIR) + pal = bytearray(256 * 4) + for i, (r, g, b) in enumerate(env): + o = (0x50 + i) * 4 + pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0)) + for i, (r, g, b) in enumerate(wall): + o = (0x60 + i) * 4 + pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0)) + with open(out_path, "wb") as f: + f.write(pal) + + +# ---- 5. C-заголовок -------------------------------------------------- +def write_header(out_path, env_pages, wall_name, fore_name): + lines = [ + "/* pop_bg_atlas.h — раскладка атласов статического фона PoP.", + " * Сгенерировано toolchain/pop_pack_bg.py — НЕ править вручную.", + " *", + " * Прямая адресация (ноль remap-таблиц в W2):", + " * ENV фон id N -> atlas env_bg[N>>%d], idx N&%d" % (ENV_SHIFT, ENV_MASK), + " * WALL id N -> atlas wall, idx N", + " * FORE id N -> atlas fore, idx N", + " */", + "#ifndef POP_BG_ATLAS_H", + "#define POP_BG_ATLAS_H", + "", + "#define POP_ENV_SHIFT %d" % ENV_SHIFT, + "#define POP_ENV_MASK %d" % ENV_MASK, + "#define POP_ENV_PAGES %d" % len(env_pages), + "", + "/* Палитра: env-слоты, wall-слоты (сприйт-пиксель i -> база+i). */", + "#define POP_PAL_ENV 0x50", + "#define POP_PAL_WALL 0x60", + "", + "/* Имена файлов атласов (грузятся atlas_load). */", + "static const char *const pop_env_atl[POP_ENV_PAGES] = {", + ] + for pg in range(len(env_pages)): + nm = env_pages[pg] + lines.append(' %s,' % ('"%s"' % nm if nm else "0 /* пусто */")) + lines += [ + "};", + '#define POP_WALL_ATL "%s"' % wall_name, + '#define POP_FORE_ATL "%s"' % fore_name, + '#define POP_BG_PAL "pop_bg.pal"', + "", + "#endif", + "", + ] + with open(out_path, "w") as f: + f.write("\n".join(lines)) + + +def main(): + os.makedirs(OUT_DIR, exist_ok=True) + used = collect_used() + env_ids = sorted(id for ch, id in used if ch != R.CHTAB_WALL) + wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL) + + env_bg = [i for i in env_ids if i not in FORE_ENV_IDS] + env_fore = [i for i in env_ids if i in FORE_ENV_IDS] + + # ENV фон -> страницы по id>>SHIFT + pages = {} + for id in env_bg: + w, h, pix = load_indexed(R.CHTAB_ENV, id) + pages.setdefault(id >> ENV_SHIFT, {})[id & ENV_MASK] = (w, h, pix) + max_pg = max(pages) if pages else -1 + env_page_names = [] + total_bytes = 0 + for pg in range(max_pg + 1): + if pg in pages: + nm = f"pop_env{pg}.atl" + cnt, sz = pack_atlas(os.path.join(OUT_DIR, nm), pages[pg]) + total_bytes += sz + env_page_names.append(nm) + print(f" {nm}: {len(pages[pg])} спрайтов, каталог {cnt}, {sz} Б") + else: + env_page_names.append(None) + print(f" (env стр {pg}: пусто)") + + # WALL -> idx=id + wall_entries = {id: load_indexed(R.CHTAB_WALL, id) for id in wall_ids} + cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_wall.atl"), wall_entries) + total_bytes += sz + print(f" pop_wall.atl: {len(wall_ids)} спрайтов, каталог {cnt}, {sz} Б") + + # FORE -> idx=id + fore_entries = {id: load_indexed(R.CHTAB_ENV, id) for id in env_fore} + cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_fore.atl"), fore_entries) + total_bytes += sz + print(f" pop_fore.atl: {len(env_fore)} спрайтов, каталог {cnt}, {sz} Б") + + build_palette(os.path.join(OUT_DIR, "pop_bg.pal")) + write_header(os.path.join(OUT_DIR, "pop_bg_atlas.h"), + env_page_names, "pop_wall.atl", "pop_fore.atl") + + n_pages = sum(1 for n in env_page_names if n) + 2 + print(f"ИТОГО: {len(env_bg)} env-фон + {len(wall_ids)} wall + " + f"{len(env_fore)} fore = {len(env_bg)+len(wall_ids)+len(env_fore)} спрайтов") + print(f" {n_pages} EMM-страниц, {total_bytes} Б на диске " + f"(+1КБ палитра), W2: {n_pages}×atlas_t = {n_pages*3} Б хэндлов") + print(f" -> {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/toolchain/pop_pack_kid.py b/applications/PoP/toolchain/pop_pack_kid.py new file mode 100644 index 0000000..4baf7ef --- /dev/null +++ b/applications/PoP/toolchain/pop_pack_kid.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +pop_pack_kid.py — упаковщик спрайтов персонажа Kid (Фаза K0 порта). + +Аналог pop_pack_bg.py, но для chtab_2_kid (база ресурса 400): image id N += res(400+N).png (data/KID), палитра res400.pal (16 цветов) -> слоты +Sprinter 0x70-0x7F (env 0x50 / wall 0x60 заняты фоном). Пиксель i: +0->0xFF (прозрач), i->0x70+i. + +Прямая адресация (ноль remap в W2): kid[img>>5], idx img&31. Дырки — +фейковые записи (как в фоне). Переиспользует pack_atlas/blob/scale6to8 +из pop_pack_bg. + +Выход: poc/res/kid/kid0..N.atl + kid.pal + kid_atlas.h +""" +import os +import sys +from PIL import Image + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +import pop_pack_bg as B # pack_atlas, blob, scale6to8, ENV_SHIFT + +KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/KID" +OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid") +PAL_BASE = 0x70 # слоты палитры Sprinter под Kid +SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ) +MASK = (1 << SHIFT) - 1 + + +def transpose_cols(w, h, rowmajor): + """row-major (pix[row*w+col]) -> column-major (out[col*h+row]). + Спрайты Kid хранятся КОЛОНКАМИ: gfx_blit_cols рисует вертикальным accel- + проходом (даёт бесплатный горизонтальный флип реверсом порядка колонок; + см. memory/accel_vertical_copy). Заголовок getimage (w,h) не меняется.""" + out = bytearray(w * h) + for col in range(w): + base = col * h + for row in range(h): + out[base + row] = rowmajor[row * w + col] + return bytes(out) + + +def load_kid(aidx): + """Атлас-индекс aidx (== frame.image) -> res(401+aidx).png -> (w, h, 8bpp + COLUMN-MAJOR). 0->0xFF, i->0x70+i. Пиксели транспонированы в колонки. + + МАППИНГ (seg008.c:1589): персонаж рисуется add_midtable(chtab, obj_id+1), + внутри get_image(chtab, id-1)=get_image(chtab, obj_id) -> chtab-индекс = + frame.image; первая картинка chtab_2_kid = res401 (res400 = палитра). + Значит frame.image=N -> res(401+N). Раньше грузили res(400+N) — off-by-one: + стойка frame_15 (image 14) рисовалась res414 (беговой наклон) вместо res415 + (прямая стойка).""" + path = os.path.join(KID_DIR, f"res{401 + aidx}.png") + if not os.path.exists(path): + return None + im = Image.open(path) + if im.mode != "P": + B.die(f"res{401+aidx}: mode {im.mode}, ожидался P") + src = im.tobytes() + rowmajor = bytes((0xFF if v == 0 else PAL_BASE + v) for v in src) + return im.width, im.height, transpose_cols(im.width, im.height, rowmajor) + + +def _read16(path): + b = open(path, "rb").read() + return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)] + + +def build_palette(out_path): + """ЕДИНАЯ игровая палитра (gfx_pal_fload пишет с индекса 0, перезатирая + всё — bg и Kid обязаны жить в одном файле): env@0x50, wall@0x60, kid@0x70. + """ + pal = bytearray(256 * 4) + groups = [ + (0x50, os.path.join(B.R.VDUNGEON_DIR, "res200.pal")), # env + (0x60, os.path.join(B.R.VDUNGEON_DIR, "res360.pal")), # wall + (PAL_BASE, os.path.join(KID_DIR, "res400.pal")), # kid 0x70 + ] + for base, path in groups: + for i, (r, g, bl) in enumerate(_read16(path)): + o = (base + i) * 4 + pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0)) + with open(out_path, "wb") as f: + f.write(pal) + + +def main(): + os.makedirs(OUT_DIR, exist_ok=True) + # атлас-индексы aidx (== frame.image), для которых есть res(401+aidx).png + ids = [] + for aidx in range(0, 255): + if os.path.exists(os.path.join(KID_DIR, f"res{401 + aidx}.png")): + ids.append(aidx) + if not ids: + B.die("нет спрайтов KID") + + pages = {} + for id in ids: + w, h, pix = load_kid(id) + pages.setdefault(id >> SHIFT, {})[id & MASK] = (w, h, pix) + + max_pg = max(pages) + names = [] + total = 0 + for pg in range(max_pg + 1): + if pg in pages: + nm = f"kid{pg}.atl" + cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, nm), pages[pg]) + total += sz + names.append(nm) + print(f" {nm}: {len(pages[pg])} спрайтов, каталог {cnt}, {sz} Б") + else: + names.append(None) + print(f" (kid стр {pg}: пусто)") + + build_palette(os.path.join(OUT_DIR, "kid.pal")) + + # C-заголовок + hdr = os.path.join(OUT_DIR, "kid_atlas.h") + with open(hdr, "w") as f: + f.write("/* kid_atlas.h — раскладка атласов Kid. Сгенерировано " + "pop_pack_kid.py. */\n#ifndef KID_ATLAS_H\n#define KID_ATLAS_H\n") + f.write(f"#define KID_SHIFT {SHIFT}\n#define KID_MASK {MASK}\n") + f.write(f"#define KID_PAGES {len(names)}\n#define KID_PAL 0x{PAL_BASE:02X}\n") + f.write("static const char *const kid_atl[KID_PAGES] = {\n") + for nm in names: + f.write(" %s,\n" % ('"%s"' % nm if nm else "0")) + f.write('};\n#define KID_PAL_FILE "kid.pal"\n#endif\n') + + print(f"ИТОГО: {len(ids)} спрайтов Kid, {len(names)} EMM-страниц, " + f"{total} Б диск (+1КБ палитра) -> {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/toolchain/render_room.py b/applications/PoP/toolchain/render_room.py new file mode 100755 index 0000000..93cd8bb --- /dev/null +++ b/applications/PoP/toolchain/render_room.py @@ -0,0 +1,842 @@ +#!/usr/bin/env python3 +""" +Parse SDLPoP level file (res2001.bin) and render the first room by +faithfully replaying the tile-drawing logic of seg008.c (draw_room/draw_tile). + +Pixel model (from seg008.c): + - screen is 320px wide, 10 columns. + - col_xh[col] = {0,4,8,12,16,20,24,28,32,36}; x_px = xh*8 -> 0,32,64,...,288 + (tile column is 32px; the 4px offset handles overlap of floor/wall edges) + - rows drawn 2,1,0 (bottom..top). For each row: + draw_bottom_y = 63*drawn_row + 65 + draw_main_y = draw_bottom_y - 3 + row2 -> bottom_y=191, row1 -> 128, row0 -> 65 + - a tile sprite of height h drawn at ybottom is placed with its BOTTOM at ybottom: + y = ybottom - h + 1 + +We replay draw_tile() which composites (in order): + floorright, anim_topright, right, anim_right, bottom, loose, base, anim, fore +Dependencies on NEIGHBOURS: + - tile_left = tile in the column to the left (same room, same row) + - row_below_left_[col] = tile in (room_below, col-1, row_below) -> for topright/floorright + - For a single room with no links we treat out-of-range neighbours as walls + (level edges), matching SDLPoP's custom->drawn_tile_*_level_edge defaults + (left = wall, top = floor). + +We ignore animated variants (spike frames, chomper, torch flames, gate +animation) for this first static render; they use curr_modifier/backtable. +""" + +import os +from PIL import Image + +LEVEL_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/LEVELS" +VPALACE_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/VPALACE" +VDUNGEON_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/VDUNGEON" + +# For dungeon levels (tbl_level_type == 0) the environment sprites come from +# VDUNGEON where present, otherwise fall back to the shared VPALACE set +# (PRINCE.DAT common images). For palace levels use VPALACE only. +IS_DUNGEON = True # room 1 (res2001) is the dungeon + +# tile_table[code] = (base_id, floor_left, base_y, right_id, floor_right, +# right_y, stripe_id, topright_id, bottom_id, fore_id, +# fore_x, fore_y) +TILE_TABLE = [ + ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), # 0x00 empty + ( 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0), # 0x01 floor + (127, 1, 0, 133, 1, 2, 145, 0, 43, 0, 0, 0), # 0x02 spike + ( 92, 1, 0, 93, 1, 2, 0, 94, 43, 95, 1, 0), # 0x03 pillar + ( 46, 1, 0, 47, 1, 2, 0, 48, 43, 49, 3, 0), # 0x04 gate + ( 41, 1, 1, 35, 1, 3, 145, 0, 36, 0, 0, 0), # 0x05 stuck floor + ( 41, 1, 0, 42, 1, 2, 145, 0, 96, 0, 0, 0), # 0x06 close button + ( 46, 1, 0, 0, 0, 2, 0, 0, 43, 49, 3, 0), # 0x07 door top with floor + ( 86, 1, 0, 87, 1, 2, 0, 0, 43, 88, 1, 0), # 0x08 big pillar bottom + ( 0, 0, 0, 89, 0, 3, 0, 90, 0, 91, 1, 3), # 0x09 big pillar top + ( 41, 1, 0, 42, 1, 2, 145, 0, 43, 12, 2, -3), # 0x0A potion + ( 0, 1, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0), # 0x0B loose floor + ( 0, 0, 0, 0, 0, 2, 0, 0, 85, 49, 3, 0), # 0x0C door top + ( 75, 1, 0, 42, 1, 2, 0, 0, 43, 77, 0, 0), # 0x0D mirror + ( 97, 1, 0, 98, 1, 2, 145, 0, 43, 100, 0, 0), # 0x0E debris + (147, 1, 0, 42, 1, 1, 145, 0, 149, 0, 0, 0), # 0x0F open button + ( 41, 1, 0, 37, 0, 0, 0, 38, 43, 0, 0, 0), # 0x10 leveldoor left + ( 0, 0, 0, 39, 1, 2, 0, 40, 43, 0, 0, 0), # 0x11 leveldoor right + ( 0, 0, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0), # 0x12 chomper + ( 41, 1, 0, 42, 1, 2, 0, 0, 43, 0, 0, 0), # 0x13 torch + ( 0, 0, 0, 1, 1, 2, 0, 2, 0, 0, 0, 0), # 0x14 wall + ( 30, 1, 0, 31, 1, 2, 0, 0, 43, 0, 0, 0), # 0x15 skeleton + ( 41, 1, 0, 42, 1, 2, 145, 0, 43, 0, 0, 0), # 0x16 sword + ( 41, 1, 0, 10, 0, 0, 0, 11, 43, 0, 0, 0), # 0x17 balcony left + ( 0, 0, 0, 12, 1, 2, 0, 13, 43, 0, 0, 0), # 0x18 balcony right + ( 92, 1, 0, 42, 1, 2, 145, 0, 43, 95, 1, 0), # 0x19 lattice pillar + ( 1, 0, 0, 0, 0, 0, 0, 0, 2, 9, 0, -53), # 0x1A lattice down + ( 3, 0, -10, 0, 0, 0, 0, 0, 0, 9, 0, -53), # 0x1B lattice small + ( 4, 0, -10, 0, 0, 0, 0, 0, 0, 9, 0, -53), # 0x1C lattice left + ( 5, 0, -10, 0, 0, 0, 0, 0, 0, 9, 0, -53), # 0x1D lattice right + ( 97, 1, 0, 98, 1, 2, 0, 0, 43, 100, 0, 0), # 0x1E debris with torch +] + +TILE_NAMES = { + 0x00: "empty", 0x01: "floor", 0x02: "spike", 0x03: "pillar", + 0x04: "gate", 0x05: "stuck_floor", 0x06: "close_button", 0x07: "door_top_floor", + 0x08: "big_pillar_bottom", 0x09: "big_pillar_top", 0x0A: "potion", + 0x0B: "loose_floor", 0x0C: "door_top", 0x0D: "mirror", 0x0E: "debris", + 0x0F: "open_button", 0x10: "leveldoor_left", 0x11: "leveldoor_right", + 0x12: "chomper", 0x13: "torch", 0x14: "wall", 0x15: "skeleton", + 0x16: "sword", 0x17: "balcony_left", 0x18: "balcony_right", + 0x19: "lattice_pillar", 0x1A: "lattice_down", 0x1B: "lattice_small", + 0x1C: "lattice_left", 0x1D: "lattice_right", 0x1E: "debris_torch", +} + +COL_XH = [0, 4, 8, 12, 16, 20, 24, 28, 32, 36] + +# chtab -> directory/subfolder + id offset +# id_chtab_6_environment -> VPALACE, res(200+id).png +# id_chtab_7_environmentwall -> VPALACE (wall images also here, res(200+id)) +# id_chtab_1_flameswordpotion -> flames etc (skipped in static render) +CHTAB_ENV = 6 +CHTAB_WALL = 7 +CHTAB_FSP = 1 # flames/sword/potion (animated, skipped) + + +# ---- POP1 level file structure (see POP-DAT-FormatSpecifications.txt, sec 3.4) ---- +# Block layout (for the standard 2305-byte level): +# 0 .. 720 pop1 foretable (24 rooms x 30 bytes) +# 720 .. 1440 pop1 backtable (24 rooms x 30 bytes) +# 1440 .. 1696 door I (256 bytes, one per event line) +# 1696 .. 1952 door II (256 bytes) +# 1952 .. 2048 links (24 rooms x 4 bytes: L,R,Up,Dn) +# 2048 .. 2112 unknown I +# 2112 .. 2115 start position (room, location, direction) +# 2115 .. 2116 unknown II +# 2116 .. 2119 unknown III +# 2119 .. 2143 guard location (24) +# 2143 .. 2167 guard direction (24) +# 2167 .. 2191 unknown IV (a) +# 2191 .. 2215 unknown IV (b) +# 2215 .. 2239 guard skill (24) +# 2239 .. 2263 unknown IV (c) +# 2263 .. 2287 guard colour (24) +# 2287 .. 2303 unknown IV (d) +# 2303 .. 2305 0F 09 + +# foretable byte format: rrmccccc (2 random bits, 1 modifier bit, 5 code bits) +FG_RAND_MASK = 0xC0 +FG_MODBIT_MASK = 0x20 +FG_CODE_MASK = 0x1F + +# backtable: a tile modifier that depends on the group of the foretable code. +# group ids map foretable code -> "group" used to interpret backtable value. +# (see Table 7/8). We only need the group for door/event interpretation. +GROUP_OF_CODE = { + 0x00: "free", 0x01: "free", 0x02: "spike", 0x03: "none", + 0x04: "gate", 0x05: "none", 0x06: "event", 0x07: "tapest", + 0x08: "none", 0x09: "none", 0x0A: "potion", 0x0B: "none", + 0x0C: "ttop", 0x0D: "none", 0x0E: "none", 0x0F: "event", + 0x10: "none", 0x11: "none", 0x12: "chomp", 0x13: "none", + 0x14: "wall", 0x15: "none", 0x16: "none", 0x17: "none", + 0x18: "none", 0x19: "none", 0x1A: "none", 0x1B: "none", + 0x1C: "none", 0x1D: "none", 0x1E: "none", 0x1F: "none", +} + +# backtable modifier meanings per group (subset, from Table 8): +GATE_MOD = {0: "closed", 1: "open", 2: "with_lattice", 3: "alt_design"} +TTOP_MOD = {0: "normal", 1: "black", 2: "empty", 3: "?3", 4: "?4", 5: "?5", 6: "?6", 7: "?7"} +SPIKE_MOD = {0: "normal", 1: "barely_out", 2: "half_out", 3: "fully_out"} +CHOMP_MOD = {0: "normal", 1: "barely_out", 2: "half_out", 3: "fully_out", 4: "out?"} +POTION_MOD = {0: "blue", 1: "no_blue", 2: "window", 3: "spot?"} +TAPEST_MOD = {0: "normal", 1: "alt_design", 2: "black", 3: "empty"} +EVENT_MOD_DESC = { # group "event": the backtable byte IS the event id + 0: "closer", 1: "opener", +} + + +class Level: + """Full parsed POP1 level file.""" + def __init__(self, data): + self.data = data + self.rooms_fg = {} # room -> list[30] of (fg_byte, code, modbit) + self.rooms_bg = {} # room -> list[30] of backtable byte + for room in range(1, 25): + fg = []; bg = [] + for off in range(30): + i = (room - 1) * 30 + off + fb = data[i]; bb = data[720 + i] + fg.append((fb, fb & FG_CODE_MASK, (fb & FG_MODBIT_MASK) >> 5)) + bg.append(bb) + self.rooms_fg[room] = fg + self.rooms_bg[room] = bg + # door I / II (event lines 0..255) + self.door_i = data[1440:1696] + self.door_ii = data[1696:1952] + # links: 24 rooms x (L,R,Up,Dn) + self.links = {} + for room in range(1, 25): + b = data[1952 + (room - 1) * 4: 1952 + (room - 1) * 4 + 4] + self.links[room] = tuple(b) # (left, right, up, down) + # start position + self.start = tuple(data[2112:2115]) + # guards + self.guard_loc = list(data[2119:2143]) + self.guard_dir = list(data[2143:2167]) + self.guard_skill = list(data[2215:2239]) + self.guard_colour = list(data[2263:2287]) + + def door_for_event(self, event_id): + """Decode door I/II for an event line -> (room, location, trigger_next).""" + bi = self.door_i[event_id] + bii = self.door_ii[event_id] + # Byte I: t1 s4 s5 l1 l2 l3 l4 l5 + t1 = (bi >> 7) & 1 + s4 = (bi >> 6) & 1 + s5 = (bi >> 5) & 1 + l1 = (bi >> 4) & 1 + l2 = (bi >> 3) & 1 + l3 = (bi >> 2) & 1 + l4 = (bi >> 1) & 1 + l5 = bi & 1 + # Byte II: s1 s2 s3 00000 + s1 = (bii >> 7) & 1 + s2 = (bii >> 6) & 1 + s3 = (bii >> 5) & 1 + screen = (s1 << 4) | (s2 << 3) | (s3 << 2) | (s4 << 1) | s5 + loc = (l1 << 4) | (l2 << 3) | (l3 << 2) | (l4 << 1) | l5 + return screen, loc, t1 + + def backtable_desc(self, code, bb): + grp = GROUP_OF_CODE.get(code, "none") + tbl = {"gate": GATE_MOD, "ttop": TTOP_MOD, "spike": SPIKE_MOD, + "chomp": CHOMP_MOD, "potion": POTION_MOD, "tapest": TAPEST_MOD} + if code == 0x06 or code == 0x0F: # event group + act = EVENT_MOD_DESC.get(bb & 1, "?") + return f"event(id={bb}, {act})" + if grp in tbl: + return tbl[grp].get(bb, f"?0x{bb:02X}") + return f"0x{bb:02X}" + + +class Room: + def __init__(self, level, room_num): + self.level = level + self.room_num = room_num + self.fg = level.rooms_fg[room_num] + self.bg = level.rooms_bg[room_num] + self.left_room = None # соседняя комната слева (link L); load_leftroom + + def tile(self, row, col): + """Return (code, fg_byte, modbit, backtable_byte) for an in-room tile. + Out-of-range neighbours follow seg008 defaults: + col<0 -> берётся из ЛЕВОЙ комнаты (col 9), как load_leftroom(); + если левой комнаты нет — уровневая кромка = стена (20). + row<0 -> top level edge = floor (code 1, mod 0) + """ + if col < 0: + if self.left_room is not None and 0 <= row < 3: + return self.left_room.tile(row, 9) + return 20, 0, 0, 0 + if row < 0: + return 1, 0, 0, 0 + if not (0 <= row < 3 and 0 <= col < 10): + return 20, 0, 0, 0 + i = row * 10 + col + fb, code, mbit = self.fg[i] + return code, fb, mbit, self.bg[i] + + +# Log of every sprite loaded during a render, for the per-tile report. +SPRITE_LOG = [] + + +def load_sprite(id, chtab=CHTAB_ENV, reason=""): + """Load resource PNG для картинки id из набора chtab. + + База ресурса зависит от chtab (seg000.c:1109/1122): + chtab_6 environment -> res(200+id) + chtab_7 environmentwall -> res(360+id) + chtab_1 flame/sword/pot -> res(150+id) (анимация, статикой не грузим) + + Dungeon (IS_DUNGEON) cascade: try VDUNGEON first (dungeon-specific + overrides), then fall back to VPALACE (shared base set). + """ + if id <= 0: + return None + base = {CHTAB_ENV: 200, CHTAB_WALL: 360, CHTAB_FSP: 150}.get(chtab, 200) + fname = f"res{base + id}.png" + if IS_DUNGEON: + vd = os.path.join(VDUNGEON_DIR, fname) + if os.path.exists(vd): + try: + img = Image.open(vd).convert("RGBA") + CURRENT_TILE_LOG.append((id, "VDUNGEON", reason)) + return img + except Exception: + pass + vp = os.path.join(VPALACE_DIR, fname) + try: + img = Image.open(vp).convert("RGBA") + CURRENT_TILE_LOG.append((id, "VPALACE", reason)) + return img + except FileNotFoundError: + CURRENT_TILE_LOG.append((id, "MISSING", reason)) + return None + + +def blit(canvas, img, x, y): + """Composite img onto canvas at (x,y) using its alpha as mask (draw_main style).""" + if img is None: + return + w, h = img.size + if x + w <= 0 or y + h <= 0 or x >= canvas.width or y >= canvas.height: + return + canvas.alpha_composite(img, (x, y)) + + +def blit_black(canvas, img, x, y): + """blitters_9_black (seg008.c:1054): рисует МАСКУ спрайта ЧЁРНЫМ — + непрозрачная область формы заливается чёрным (тень-силуэт), а не + пикселями картинки. Используется для floor-right в draw_tile_floorright + (тень уступа тайла снизу-слева).""" + if img is None: + return + w, h = img.size + if x + w <= 0 or y + h <= 0 or x >= canvas.width or y >= canvas.height: + return + alpha = img.split()[3] + solid = Image.new("RGBA", img.size, (0, 0, 0, 255)) + canvas.alpha_composite(Image.composite(solid, Image.new("RGBA", img.size, (0, 0, 0, 0)), alpha), (x, y)) + + +# ---- Dungeon wall_pattern() (seg008.c:1928) ---- +# PRNG: seg009.c:321 random_seed = random_seed*214013 + 2531011; return (random_seed>>16) % (max+1) +_random_seed = 0 + + +def prandom(maxval): + global _random_seed + _random_seed = (_random_seed * 214013 + 2531011) & 0xFFFFFFFF + return (_random_seed >> 16) % (maxval + 1) + + +# wall sprite ids (chtab_7 / RSET_WALL = 7) res(200+id).png +RES_WALL_FACE_MAIN, RES_WALL_FACE_TOP, RES_WALL_CENTRE_BASE, RES_WALL_CENTRE_MAIN = 1, 2, 3, 4 +RES_WALL_RIGHT_BASE, RES_WALL_RIGHT_MAIN, RES_WALL_SINGLE_BASE, RES_WALL_SINGLE_MAIN = 5, 6, 7, 8 +RES_WALL_LEFT_BASE, RES_WALL_LEFT_MAIN, RES_WALL_DIVIDER1, RES_WALL_DIVIDER2 = 9, 10, 11, 12 +RES_WALL_RNDBLOCK = 13 +RES_WALL_MARK_TL, RES_WALL_MARK_BL, RES_WALL_MARK_TR, RES_WALL_MARK_BR = 14, 15, 16, 17 + +WALL_MODIFIER_SWS, WALL_MODIFIER_SWW, WALL_MODIFIER_WWS, WALL_MODIFIER_WWW = 0, 1, 2, 3 + +# Сплошная грань стены (chtab_7), индекс = wall_modifier (seg008.c:567/687): +WALL_FRAM_BOTTOM = [7, 9, 5, 3] # нижняя часть (draw_tile_bottom) +WALL_FRAM_MAIN = [8, 10, 6, 4] # основная часть (draw_tile_fore) + +LPOS = [58, 41, 37, 20, 16] # draw_left_mark vertical offsets +RPOS = [52, 42, 31, 21] # draw_right_mark vertical offsets + +TBL_LINE = [0, 10, 20] # смещение строки в 30-тайловом массиве комнаты + + +def wall_modifier(room, drawn_row, col): + """Модификатор стены (SWS/SWW/WWS/WWW = 0..3) по соседям слева/справа. + seg008.c:1255-1305 (без USE_FAKE_TILES): сосед считается стеной, если + его тип == wall(20). На кромке комнаты без линка сосед по умолчанию + считается стеной (wall_to_left/right = 1).""" + left_code = room.tile(drawn_row, col - 1)[0] if col > 0 else 20 + right_code = room.tile(drawn_row, col + 1)[0] if col < 9 else 20 + wall_left = (left_code == 20) + wall_right = (right_code == 20) + if wall_left and wall_right: + return WALL_MODIFIER_WWW # 3 + if wall_left: + return WALL_MODIFIER_WWS # 2 + if wall_right: + return WALL_MODIFIER_SWW # 1 + return WALL_MODIFIER_SWS # 0 + + +def _wall_add(canvas, which_table, id, xh, xl, ybottom, reason): + """Emulate ptr_add_table(RSET_WALL, id, xh, xl, ybottom, blit, 0). + which_table 0 -> backtable (blit transparent), 1 -> foretable. + RSET_WALL id -> res(200+id).png, cascade VDUNGEON->VPALACE.""" + img = load_sprite(id, CHTAB_WALL, reason=reason) + if img is None: + return + y = ybottom - img.height + 1 + x = xh * 8 + xl + blit(canvas, img, x, y) + + +def draw_left_mark(canvas, decal_variant, arg2, arg1, drawn_row, col): + image_id = RES_WALL_MARK_TL + if decal_variant % 2: + image_id = RES_WALL_MARK_BL + lv2 = 0 + if decal_variant > 3: + lv2 = arg1 + 6 + elif decal_variant > 1: + lv2 = arg2 + 6 + xh = COL_XH[col] + draw_bottom_y = 63 * drawn_row + 65 + _wall_add(canvas, 1, image_id, xh + (1 if decal_variant in (2, 3) else 0), + lv2, draw_bottom_y - LPOS[decal_variant], + reason=f"wall left-mark var{decal_variant}") + + +def draw_right_mark(canvas, decal_variant, arg1, drawn_row, col): + image_id = RES_WALL_MARK_TR + if decal_variant % 2: + image_id = RES_WALL_MARK_BR + a = 24 if decal_variant < 2 else arg1 - 3 + xh = COL_XH[col] + draw_bottom_y = 63 * drawn_row + 65 + _wall_add(canvas, 1, image_id, xh + (1 if decal_variant > 1 else 0), + a, draw_bottom_y - RPOS[decal_variant], + reason=f"wall right-mark var{decal_variant}") + + +def wall_pattern(canvas, room, drawn_row, col, which_part, which_table): + """Replay seg008.c wall_pattern() for a dungeon wall tile. + which_part: 0 = bottom part (draw_tile_bottom), 1 = fore part (draw_tile_fore). + which_table: 0 = backtable, 1 = foretable.""" + global _random_seed + xh = COL_XH[col] + draw_bottom_y = 63 * drawn_row + 65 + draw_main_y = draw_bottom_y - 3 + + saved_seed = _random_seed + # seg008.c:1941 — зерно зависит от НОМЕРА КОМНАТЫ (не строки!): + # random_seed = drawn_room + tbl_line[drawn_row] + drawn_col + room_num = getattr(room, "room_num", 1) + _random_seed = room_num + TBL_LINE[drawn_row] + col + prandom(1) # discard + is_dungeon = IS_DUNGEON + + middle_divider = prandom(1) + middle_divider_offset = prandom(4) + bottom_divider = prandom(1) + bottom_divider_offset = prandom(4) + + # Модификатор стены (какие соседи — стены) — тот же, что для сплошной грани. + bg = wall_modifier(room, drawn_row, col) + + if bg == WALL_MODIFIER_WWW: + if which_part != 0: + if prandom(4) == 0: + _wall_add(canvas, which_table, RES_WALL_RNDBLOCK, xh, 0, + draw_bottom_y - 42, reason="wall rndblock") + _wall_add(canvas, which_table, RES_WALL_DIVIDER1 + middle_divider, xh + 1, + middle_divider_offset, draw_bottom_y - 21, reason="wall middle-divider") + _wall_add(canvas, which_table, RES_WALL_DIVIDER1 + bottom_divider, xh, + bottom_divider_offset, draw_bottom_y, reason="wall bottom-divider") + if which_part != 0 and is_dungeon: + if prandom(4) == 0: + draw_right_mark(canvas, prandom(3), middle_divider_offset, drawn_row, col) + if prandom(4) == 0: + draw_left_mark(canvas, prandom(4), + middle_divider_offset - middle_divider, + bottom_divider_offset - bottom_divider, drawn_row, col) + elif bg == WALL_MODIFIER_SWS: + if is_dungeon and which_part != 0: + if prandom(6) == 0: + draw_left_mark(canvas, prandom(1), + middle_divider_offset - middle_divider, + bottom_divider_offset - bottom_divider, drawn_row, col) + elif bg == WALL_MODIFIER_SWW: + if which_part != 0: + if prandom(4) == 0: + _wall_add(canvas, which_table, RES_WALL_RNDBLOCK, xh, 0, + draw_bottom_y - 42, reason="wall rndblock") + _wall_add(canvas, which_table, RES_WALL_DIVIDER1 + middle_divider, xh + 1, + middle_divider_offset, draw_bottom_y - 21, reason="wall middle-divider") + if is_dungeon: + if prandom(4) == 0: + draw_right_mark(canvas, prandom(3), middle_divider_offset, drawn_row, col) + if prandom(4) == 0: + draw_left_mark(canvas, prandom(3), + middle_divider_offset - middle_divider, + bottom_divider_offset - bottom_divider, drawn_row, col) + elif bg == WALL_MODIFIER_WWS: + if which_part != 0: + _wall_add(canvas, which_table, RES_WALL_DIVIDER1 + middle_divider, xh + 1, + middle_divider_offset, draw_bottom_y - 21, reason="wall middle-divider") + _wall_add(canvas, which_table, RES_WALL_DIVIDER1 + bottom_divider, xh, + bottom_divider_offset, draw_bottom_y, reason="wall bottom-divider") + if which_part != 0 and is_dungeon: + if prandom(4) == 0: + draw_right_mark(canvas, prandom(1) + 2, middle_divider_offset, drawn_row, col) + if prandom(4) == 0: + draw_left_mark(canvas, prandom(4), + middle_divider_offset - middle_divider, + bottom_divider_offset - bottom_divider, drawn_row, col) + + _random_seed = saved_seed + + +# Per-tile sprite log: draw_tile resets CURRENT_TILE_LOG, load_sprite appends to it. +CURRENT_TILE_LOG = [] +TILE_LOGS = {} # (row, col) -> list of (id, source, reason) + +# seg008.c:1108 — вертикальные слайсы решётки ворот (portcullis). +DOOR_FRAM_SLICE = [67, 59, 58, 57, 56, 55, 54, 53, 52] +# seg008.c:517 — правый край шипов по кадру get_spike_frame. +SPIKES_FRAM_RIGHT = [0, 134, 135, 136, 137, 138, 137, 135, 134, 0] +# seg008.c:450 — «blueline»-декали на ЗАДНЕЙ стене (силуэты кладки/окна), +# рисуются правым соседом по МОДИФИКАТОРУ соседа слева: +# tile_left == empty -> blueline_fram1[mod] (окно/решётка-фрагменты 124..126) +# на высоте blueline_fram_y[mod] + main_y; +# tile_left == floor -> blueline_fram3[mod] (кирпичные силуэты 44/45) +# на main_y - 20 (если mod != !!level_type). +BLUELINE_FRAM1 = [0, 124, 125, 126] +BLUELINE_FRAM_Y = [0, -20, -20, 0] +BLUELINE_FRAM3 = [44, 44, 45, 45] + + +def draw_gate_back(canvas, modifier_left, draw_xh, draw_bottom_y, draw_main_y): + """seg008.c:1110 draw_gate_back — портикулис (решётка) ворот. Рисуется + на тайле СПРАВА от ворот (draw_tile_anim_right, tile_left==gate). У нас + это col 0 room1, где сосед слева — ворота из room 5. modifier_left — + модификатор тайла-ворот (степень поднятия решётки).""" + x = draw_xh * 8 + gate_top_y = draw_bottom_y - 62 + gate_openness = (min(modifier_left, 188) >> 2) + 1 + gate_bottom_y = draw_main_y - gate_openness + if gate_bottom_y + 12 < draw_main_y: + img = load_sprite(50, reason="gate bottom+B") # gate поднята высоко + if img: + blit(canvas, img, x, gate_bottom_y - img.height + 1) + else: + # id 47 (правая грань ворот) уже рисует draw_tile_right — не дублируем. + img = load_sprite(51, reason="gate bottom") + if img: + blit(canvas, img, x, (gate_bottom_y - 2) - img.height + 1) + # Стек 8px-слайсов решётки снизу вверх. + ybottom = gate_bottom_y - 12 + if ybottom < 192: + while ybottom >= 0 and ybottom > 7 and (ybottom - 7) > gate_top_y: + img = load_sprite(52, reason="gate slice 8px") + if img: + blit(canvas, img, x, ybottom - img.height + 1) + ybottom -= 8 + gate_frame = ybottom - gate_top_y + 1 + if 0 < gate_frame < 9: + img = load_sprite(DOOR_FRAM_SLICE[gate_frame], reason="gate top slice") + if img: + blit(canvas, img, x, ybottom - img.height + 1) + + +def draw_tile(canvas, room, drawn_row, col, left_edge_code=20): + global CURRENT_TILE_LOG + CURRENT_TILE_LOG = [] + code, fg_byte, modbit, bg_mod = room.tile(drawn_row, col) + # tile_left: колонка слева. col-1 == -1 -> Room.tile берёт из левой + # комнаты (или уровневая кромка = стена), см. Room.tile / load_leftroom. + left_code, _, _, left_mod = room.tile(drawn_row, col - 1) + draw_xh = COL_XH[col] + x = draw_xh * 8 + draw_bottom_y = 63 * drawn_row + 65 + draw_main_y = draw_bottom_y - 3 + + # For topright/floorright we need the tile below-left. + # Room below = same room (no room links) -> row_below = drawn_row+1 if <3 else treat as empty/floor top edge. + row_below = drawn_row + 1 + if row_below > 2: + # below the bottom row of the level: floor top edge + rbl_code, rbl_mod = 1, 0 + else: + rbl_code, _, _, rbl_mod = room.tile(row_below, col - 1) + row_below_left_code = rbl_code + + t = TILE_TABLE[code] + lt = TILE_TABLE[left_code] + + # ---- draw_tile_floorright (seg008.c:392) ---- + # Рисуется ТОЛЬКО когда can_see_bottomleft(): текущий тайл — + # empty/bigpillar_top/doortop/lattice_down. Иначе topright соседа + # снизу-слева перекрыт и рисовать его нельзя (иначе «арки» повсюду — + # это была главная причина замусоренного рендера). + if code in (0, 9, 12, 26): # can_see_bottomleft + _draw_topright(canvas, row_below_left_code, rbl_mod, x, draw_bottom_y, + reason=f"topright(below-left tile {row_below_left_code:02X})") + if lt[4]: # tile_table[tile_left].floor_right + # blitters_9_black: ЧЁРНЫЙ силуэт (тень уступа), не светлый спрайт. + img = load_sprite(42, reason=f"floor-right shadow(left tile {left_code:02X})") + blit_black(canvas, img, x, draw_main_y + TILE_TABLE[1][5] - (img.height if img else 0) + 1) + + # ---- draw_tile_anim_topright ---- (gate top mask) - skipped: no anim + # ---- draw_tile_right (seg008.c:456) ---- + if code != 20: # у самой стены правый край не рисуется + level_type = 0 if IS_DUNGEON else 1 + if left_code == 20: + # сосед слева — стена: её правая грань в текущую колонку (chtab_7 id 1) + img = load_sprite(1, CHTAB_WALL, reason="wall right-face(left tile 14)") + ry = TILE_TABLE[20][5] # right_y стены = 2 + blit(canvas, img, x, ry + draw_main_y - (img.height if img else 0) + 1) + elif left_code == 0: + # сосед слева — empty: фрагмент окна/решётки по его модификатору + if left_mod <= 3: + bid = BLUELINE_FRAM1[left_mod] + if bid: + img = load_sprite(bid, reason=f"blueline-window(left empty mod{left_mod})") + blit(canvas, img, x, + BLUELINE_FRAM_Y[left_mod] + draw_main_y - (img.height if img else 0) + 1) + elif left_code == 1: + # сосед слева — floor: правый треугольник (42) + СИЛУЭТ КЛАДКИ (44/45) + # на задней стене, если модификатор пола != !!level_type. + img = load_sprite(42, reason="floor-right(part of left tile 01)") + ry = TILE_TABLE[1][5] # right_y пола = 2 + blit(canvas, img, x, ry + draw_main_y - (img.height if img else 0) + 1) + num = left_mod if left_mod <= 3 else 0 + if num != level_type: + bid = BLUELINE_FRAM3[num] + img = load_sprite(bid, reason=f"brick-silhouette(left floor mod{num})") + blit(canvas, img, x, draw_main_y - 20 - (img.height if img else 0) + 1) + else: + rid = lt[3] # tile_left.right_id + if rid: + ry = lt[5] + img = load_sprite(rid, reason=f"right(part of left tile {left_code:02X})") + blit(canvas, img, x, ry + draw_main_y - (img.height if img else 0) + 1) + # stripe: only drawn when tbl_level_type != 0 (palace). Dungeon: skip. + sid = lt[6] + if sid and not IS_DUNGEON: + img = load_sprite(sid, reason=f"stripe(left tile {left_code:02X})") + blit(canvas, img, x, draw_main_y - 27 - (img.height if img else 0) + 1) + + # ---- draw_tile_anim_right (seg008.c:530) ---- + # «Анимированные» части соседа слева, проецируемые в текущий тайл. + # Статикой (кадр покоя) рисуем те, что формируют ГЕОМЕТРИЮ фона: + # gate -> решётка (portcullis) на границе с левой комнатой; + # loose -> правый треугольный край шаткой плиты (иначе провал в полу); + # spike -> статичный кадр шипов по модификатору. + # torch-flame/chomper — чистая анимация, пропускаем (отдельные sprite_t). + if left_code == 4: # tiles_4_gate + draw_gate_back(canvas, left_mod, draw_xh, draw_bottom_y, draw_main_y) + elif left_code == 11: # tiles_11_loose — правый край плиты (loose_fram_right[0]=42) + img = load_sprite(42, reason="loose right (part of left tile 0B)") + if img: + blit(canvas, img, x, (draw_bottom_y - 1) - img.height + 1) + elif left_code == 2: # tiles_2_spike — статичный кадр (spikes_fram_right) + sf = 5 if (left_mod & 0x80) else left_mod + sid = SPIKES_FRAM_RIGHT[sf] if 0 <= sf < len(SPIKES_FRAM_RIGHT) else 0 + if sid: + img = load_sprite(sid, reason="spike right (part of left tile 02)") + if img: + blit(canvas, img, x, (draw_main_y - 7) - img.height + 1) + + # ---- draw_tile_bottom (seg008.c:570) ---- + if code == 20: + # Сплошная НИЖНЯЯ грань стены (chtab_7 wall_fram_bottom) + декали. + wmod = wall_modifier(room, drawn_row, col) + img = load_sprite(WALL_FRAM_BOTTOM[wmod], CHTAB_WALL, reason="wall bottom-face") + if img: + blit(canvas, img, x, draw_bottom_y - img.height + 1) + wall_pattern(canvas, room, drawn_row, col, 0, 0) + else: + bid = t[8] + img = load_sprite(bid, reason=f"bottom({TILE_NAMES.get(code,'?')})") + if img: + blit(canvas, img, x, draw_bottom_y - img.height + 1) + + # ---- draw_loose (seg008.c:600) — статический кадр loose floor ---- + # Нижняя часть шаткой плиты (loose_fram_bottom[0]=43) на draw_bottom_y. + if code == 11: # 0x0B loose floor + img = load_sprite(43, reason="loose bottom (draw_loose)") + if img: + blit(canvas, img, x, draw_bottom_y - img.height + 1) + + # ---- draw_tile_base (seg008.c:611) ---- + base_id = t[0] + base_y = t[2] + if code == 11: # loose floor: базовая (верхняя) часть = loose_fram_left[0] = 41 + base_id = 41 + img = load_sprite(base_id, reason=f"base({TILE_NAMES.get(code,'?')})") + if img: + blit(canvas, img, x, base_y + draw_main_y - img.height + 1) + + # ---- draw_tile_anim ---- (spike/potion/chomper animated) skipped + + # ---- draw_tile_fore (seg008.c:690) ---- + if code == 20: + # Основная (средняя) грань стены (chtab_7 wall_fram_main) + декали/марки. + wmod = wall_modifier(room, drawn_row, col) + img = load_sprite(WALL_FRAM_MAIN[wmod], CHTAB_WALL, reason="wall main-face") + if img: + blit(canvas, img, x, draw_main_y - img.height + 1) + wall_pattern(canvas, room, drawn_row, col, 1, 1) + fid = t[9] + if fid: + # fore_x — в единицах xh (×8 пикс!), не в пикселях: seg008.c:743 + # xh = fore_x + draw_xh, затем x = xh*8. fore_y — в пикселях. + fx = t[10] * 8 + x + fy = t[11] + draw_main_y + img = load_sprite(fid, reason=f"fore({TILE_NAMES.get(code,'?')})") + if img: + blit(canvas, img, fx, fy - img.height + 1) + + # Save per-tile log (keyed by logical room position: row=drawn_row, col) + TILE_LOGS[(drawn_row, col)] = list(CURRENT_TILE_LOG) + + +def _draw_topright(canvas, tiletype, modifier, x, draw_bottom_y, reason=""): + if tiletype in (7, 12): # doortop_with_floor / doortop -> palace uses frame + return + elif tiletype == 20: # wall + img = load_sprite(2, CHTAB_WALL, reason=reason) + else: + tid = TILE_TABLE[tiletype][7] + if tid == 0: + return + img = load_sprite(tid, reason=reason) + if img: + blit(canvas, img, x, draw_bottom_y - img.height + 1) + + +def render_room(room_num): + with open(os.path.join(LEVEL_DIR, f"res{2000 + room_num}.bin"), "rb") as f: + data = f.read() + level = Level(data) + room = Room(level, room_num) + # load_leftroom(): левая комната по линку L — её col 9 виден на границе + # (col 0) текущей комнаты (напр. решётка ворот из соседней комнаты). + left_link = level.links[room_num][0] + if left_link: + room.left_room = Room(level, left_link) + + canvas = Image.new("RGBA", (320, 192), (0, 0, 0, 0)) + # rows drawn 2,1,0 (bottom first) + for drawn_row in (2, 1, 0): + for col in range(10): + draw_tile(canvas, room, drawn_row, col) + + return canvas, room, level + + +def render_single_tile(code, modifier=0, left_code=20, below_left_code=1): + """Render one tile in isolation on a 40x64 canvas (matching SDLPoP tile box).""" + TILE_W, TILE_H = 40, 64 + canvas = Image.new("RGBA", (TILE_W, TILE_H), (0, 0, 0, 0)) + # Place tile as if it were at drawn_row=2 (bottom), col=0 + drawn_row = 2 + col = 0 + # Build a fake Room backed by single values + class FakeRoom: + def tile(self, r, c): + if c < 0: + return left_code, 0, 0, 0 + if r < 0: + return 1, 0, 0, 0 + if r == drawn_row and c == 0: + return code, 0, 0, modifier + return below_left_code, 0, 0, 0 + room = FakeRoom() + draw_tile(canvas, room, drawn_row, col, left_edge_code=left_code) + return canvas + + +def main(): + import sys + room_num = 1 + if len(sys.argv) > 1: + room_num = int(sys.argv[1]) + + canvas, room, level = render_room(room_num) + out = f"./room{room_num}_render.png" + # Прозрачный фон (задняя чёрная стена, которую мы не рисуем) -> ЧЁРНЫМ, + # а не белым как показывает вьюер прозрачность. Композитим по альфе. + flat = Image.new("RGB", canvas.size, (0, 0, 0)) + flat.paste(canvas, (0, 0), canvas) + flat.save(out) + print(f"Saved {out} ({canvas.width}x{canvas.height})") + + # debug grid with code labels (на чёрном фоне) + from PIL import ImageDraw + dbg = flat.copy() + d = ImageDraw.Draw(dbg) + for r in range(3): + by = 63 * r + 65 + for c in range(10): + x = COL_XH[c] * 8 + d.rectangle([x, by - 63, x + 31, by], outline=(255, 0, 0), width=1) + code, _, _, _ = room.tile(r, c) + d.text((x + 2, by - 60), f"{code:02X}", fill=(255, 255, 0)) + dbg.save(f"./room{room_num}_debug.png") + print(f"Saved room{room_num}_debug.png") + + # Per-tile individual renders (the 30 tiles of the room) + # For proper neighbour context, render each tile inside its real room, + # then crop its 32x64 (40x64) column box. + tiles_dir = f"./tiles_room{room_num}" + os.makedirs(tiles_dir, exist_ok=True) + for r in range(3): + by = 63 * r + 65 + for c in range(10): + x = COL_XH[c] * 8 + code, _, _, _ = room.tile(r, c) + # crop the column box (40 wide to include left overlap, 64 tall above bottom) + box = (x, by - 64, x + 40, by) + tile_img = canvas.crop(box) + tile_img.save(os.path.join(tiles_dir, f"r{r}c{c}_{code:02X}.png")) + print(f"Saved 30 tile crops to {tiles_dir}/") + # Per-tile report: which images each tile pulls and why. + print() + print("=" * 78) + print(f"PER-TILE SPRITE REPORT (room {room_num}, {'DUNGEON' if IS_DUNGEON else 'PALACE'})") + print("=" * 78) + for r in range(3): + for c in range(10): + code, fg_byte, mbit, bb = room.tile(r, c) + name = TILE_NAMES.get(code, f"?0x{code:02X}") + btd = level.backtable_desc(code, bb) + line = (f"\n[{r},{c}] code=0x{code:02X} ({name}) " + f"fg=0x{fg_byte:02X} mbit={mbit} backtable=0x{bb:02X} ({btd})") + # If this tile is an event activator (group 'event'), show the door it drives. + if code in (0x06, 0x0F): + scr, loc, trig = level.door_for_event(bb) + line += f"\n -> DOOR EVENT id={bb}: screen={scr} loc={loc} trigger_next={trig}" + print(line) + logs = TILE_LOGS.get((r, c), []) + if not logs: + print(" (no sprites drawn)") + for sid, source, reason in logs: + print(f" res{200+sid:03d}.png <- {source:8s} {reason}") + print() + print("=" * 78) + + # Level-level summary: start position, links, door events, guards. + print() + print("LEVEL STRUCTURE SUMMARY") + print("-" * 78) + print(f"start position: room={level.start[0]} loc={level.start[1]} dir={level.start[2]}") + links = level.links.get(room_num, (0, 0, 0, 0)) + print(f"room {room_num} links: L={links[0]} R={links[1]} Up={links[2]} Dn={links[3]}") + # guard in this room + gi = room_num - 1 + gloc = level.guard_loc[gi] + if gloc < 30: + gr, gc = gloc // 10, gloc % 10 + print(f"guard: loc=tile(r{gr},c{gc}) dir={level.guard_dir[gi]} " + f"skill={level.guard_skill[gi]} colour={level.guard_colour[gi]}") + else: + print("guard: none") + # door events driven by any tile in this room + print("door events referenced by this room's tiles:") + seen = set() + for off in range(30): + fb, code, mbit = room.fg[off] + bb = room.bg[off] + if code in (0x06, 0x0F): + if bb not in seen: + seen.add(bb) + scr, loc, trig = level.door_for_event(bb) + print(f" event id={bb} (tile off {off}): screen={scr} loc={loc} trigger_next={trig}") + if not seen: + print(" (none)") + + print() + print("Room", room_num, "foreground codes (3 rows x 10 cols):") + for r in range(3): + print(" ", " ".join(f"{room.tile(r,c)[0]:02X}" for c in range(10))) + + +if __name__ == "__main__": + main() diff --git a/applications/PoP/toolchain/room1_debug.png b/applications/PoP/toolchain/room1_debug.png new file mode 100644 index 0000000000000000000000000000000000000000..9c30c669259c0394053a7fe09c5433f4832d76e9 GIT binary patch literal 9291 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 RTL~EP44$rjF6*2UngH!f^eg}X literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r0c1_00.png b/applications/PoP/toolchain/tiles_room1/r0c1_00.png new file mode 100644 index 0000000000000000000000000000000000000000..6925c1b1995dc623cd305c2ac9f5a485190b7fdb GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^8bIv8!3HEVB2A-!l!B*=V@SoEw+9&+fxIS#|LgNS d96(G6$Y;}VU|^gk*R~DB_jL7hS?83{1OUi{5wQRO literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r0c2_00.png b/applications/PoP/toolchain/tiles_room1/r0c2_00.png new file mode 100644 index 0000000000000000000000000000000000000000..ab917a91e70d2796d47d2509c5d1119f95a59d3d GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^8bIv8!3HEVB2A-!l&7bQV@SoEx92wUHYf-%95{Yc zqFGT#wn6YlcyiK8-qxQJc5~`60TqHlg67t@>77e9R!mv5G34McA#Y)uUq8;=-S%Ir ZSa1CVu1kA*TJL}qdb;|#taD0e0ssFSZ?9|=Y*7$!2>h0{ zUQGV#IwmLrn;HaK$twZTA%M^w(D%sabyuCOPr zYpfDVOH({nvF0rE)(^b literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r0c4_01.png b/applications/PoP/toolchain/tiles_room1/r0c4_01.png new file mode 100644 index 0000000000000000000000000000000000000000..53030d3790a5072fe797d45d37d32f9e139f703b GIT binary patch literal 239 zcmVBS@Dx20ssI2 z007XU4A_Gqk`r8fTE%gssB{_{2x_h{$r8eB4 znT2awYPUP$zi!Y2`K&g$`|egO;Yy>NrQ pa=Jeq57NtYf5><_SD#70s9#$QE_aE>&{_Zh002ovPDHLkV1ndLVq^dS literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r0c5_01.png b/applications/PoP/toolchain/tiles_room1/r0c5_01.png new file mode 100644 index 0000000000000000000000000000000000000000..50f344a31fc452aa04647171d05ec4ab60ef884d GIT binary patch literal 301 zcmV+|0n+}7P)3I9PUmBxND!&Ctx zA|fL4N{92Kc80d;3OgupPS8U@eV1~2PF~mPNY~%a)oFnseU!K+L5l&^OH-rO9ds8^ zGxNjmH1n?$S& zV~plCbJm&zh7ina=B(|v#7zf`hW^Y3T=EMo{h52dSQ~yxA@lszdN3NRR5W1xn0wYL zNy!}uhPh`BSgTLBhy4zBS@Dx20ssI2 z007XU4A_Gqk`r8fTE%gssB{_{2x_h{$r8eB4 znT2awYPUP$zi!Y2`K&g$`|egO;Yy>NrQ pa=Jeq57NtYf5><_SD#70s9#$QE_aE>&{_Zh002ovPDHLkV1ndLVq^dS literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r0c7_01.png b/applications/PoP/toolchain/tiles_room1/r0c7_01.png new file mode 100644 index 0000000000000000000000000000000000000000..ebed99f8794ff98800756448ea8648c3282f5d3c GIT binary patch literal 475 zcmV<10VMv3P)Zq}6oy|c4#40c1EYrw9X#|1j6Fbx9-@cp(4hzD&?98X*uf(Mg9i`Ysfa+L6oYuS z@eB1QAww*aU(YW;&o}@HHx3Y)LOwOdnA>@t=YRS$h>S4?geZU?kCXk;1&U7G<4H{R zGl&eI5l2Fx4OP{21*)nBQ5@+C6rDJ-0*54lt|Wo3>|3BL&kax(c>R1kgm`+8ji4A9 zG|3!Pz9Z=E|&i2 z0$Y@-ZpVHWO%jnySkj^?G;GP=Fg_<2K3~IX&+?s{Y-5Lebj|`f=jbuEvFR1CE1N}7Bl6P*^L;~Xe$EQ?c|+f9mV+8OOF;$d zf$w2wiG_>+U+_Jz0=LRj)p=pxW^<)ZXkOnx;PmkO`}^%MGJi{%S~Lv}`v<#b$XEaJ RjZOdn002ovPDHLkV1jh-&RYNg literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r0c8_14.png b/applications/PoP/toolchain/tiles_room1/r0c8_14.png new file mode 100644 index 0000000000000000000000000000000000000000..dab7a5d70348243ba9c20436cf6f06413c4ad2e8 GIT binary patch literal 595 zcmV-Z0<8UsP)Huk$vB+G%^pnt7`-v&ck7{+f%T-%3!jihSZ|bqX#mHHY3ImI8us z5QKvu90cJY2nXRe3F-yk8mkDd8K8CG#rySGdQdyB&N1A2bX=fwZ7Sz^;r8+Nt3DH$CAlcP|`%hP+w_V zqN=Kv5KZZ+K5geFV?jw%-Tk$sD|9@mhrAWbvPaftBwb?jo+Kn`qH>Zf%kG;wb$W@U zmk_gpF$cL4X>)<@lNclA26OY9wo!sz68S_a|Lx-c?hVvjsFA>J12qT-K{yD)K@bjt za1expARGkYAXh^=GL>KxRFU}(>kzAmd`JF0SOz3bR6ZrqS(2cniG-#kI)84ZX$ba} zrXjkLGuD-+G2G;gpw44kY0?1;L*(I)Bx|7Vi-kkdBI2ML2;eQjExxkq&>o!fp^@u7 zjef-~&;uPK;v>2Wy$2<&a*BG2hjR)KrG{WbgofK~B?;K! zq^_TEj?=F)U|@{Cb3SoOxMvQ6z?cwmMy#E6@ooEIwx{V5bi81N)S5XyMgfvLs9n@f z0%#~)F+i=9xn2}dYx^nd160HOUtATS8b&7ju3kXdcgDfkd06Zg%bFdmehy}JF!f5Q z#C=y<11x`48f?fVfcdi>+DY5d)Omyvhdxa=l_hjIscUZ-(yW5St+N=}ftArTj5FQ(cT!K&)0k ztlke$Wv-do0i|b&tHwcjFYGy*4iX1_!HWdY4LT)*mIyhK8+1wrEur6C#0I34pp~Lo zXmZ05i26;`5EGj0aOA>KWsrT$pTrGEr32DaKx|=Efe)Bc&cm^FN;yx#d}F1Y2j!r$ zRt>6HU>r<=lm)Viad63CL^=nl^?nU&Zmob=t$@`7Dr7F<)KJoS7#a&o0w|sJ8S}Pk h2B^akXo-OINdLJ5AFZ2S=Bxk!002ovPDHLkV1mSd3Eltz literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r1c0_13.png b/applications/PoP/toolchain/tiles_room1/r1c0_13.png new file mode 100644 index 0000000000000000000000000000000000000000..097334b1d58826d6f722c3851e4c484dc6128ad7 GIT binary patch literal 698 zcmV;r0!96aP)`s?{#%`_k1+QnEih5E~f8$Ho0@P3GpM5kx$kUak_k*ZTV5C=M7xQ6qs9Fjt{`mEy?e2IL}$<3A9q=}ret zz*wcAfcf`3qX<|FCM6PJxDP`c*7v=BsY~U-5XNu*5Uz!+%YzOw%!f-63xOzRR!TMn z7u#b^0da(@+59m1n}#>EoKuMbkd|?cHcb>kFjg(iaPk;7=<2dfQ%Vr|oTP`$HccsE z1VpmanOHmpbZ=LHU4?EdO6OR2>Hbh{;?PFa?2$$-@@F zv^16RO_xxF!3g2X?x zmb#9k~)>8viBRk&2KV#d;c`s gm#5?yF~h2*ABcvX@1?GELjV8(07*qoM6N<$f;8ej{r~^~ literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r1c1_13.png b/applications/PoP/toolchain/tiles_room1/r1c1_13.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b0c0ce2720b79c5fc3e7ff987e9091b24c66b1 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^8bIv8!3HEVB2A-!)O1f5$B>FSZ?A0RWisSxO%%S% zA=lW%DDjYsZNV7<9@mo$JWplcMQM3g?wZi>Z+)MzcHJgPpgtH7wp#mjs$;sdGrxQAYU)@Tmd1G7Z zwe&gvjt$?Ru(>}i*%^1v?9}J4+p6|2Ozopr0EHD& AtN;K2 literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r1c2_01.png b/applications/PoP/toolchain/tiles_room1/r1c2_01.png new file mode 100644 index 0000000000000000000000000000000000000000..a6cf25620850b7a7104beb64616797876a156a76 GIT binary patch literal 262 zcmV+h0r~!kP)Py5XSN2!U%5!3rh+|pl|>S3x=>{07q~HO9yZW2e5F2+d(@+p)cVAzLWfa9f5-{ z+~o+B6xJV4&)oT13cKAOr6T|U0000GO4IJ&Kl3WVsRXAIsHM%IwV#dwV5yof`;I*b z06=+F+Z{rGkX3?HZ=spFYsS>N1E!SRHDhX*+v2|tXpZ^J2AuMSp83o@U%dlArIdO8 z&t}k^U79tZ4fdY5OFHKpNRGYd4p?eV>&<#4rJOHU>2}-fHQ^Wa3!?Zli&K!O?f?J) M07*qoM6N<$f+N*!QUCw| literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r1c3_03.png b/applications/PoP/toolchain/tiles_room1/r1c3_03.png new file mode 100644 index 0000000000000000000000000000000000000000..9e269c00c62ae6aa0fc2ce83d08aa0017776391f GIT binary patch literal 800 zcmV+*1K<3KP)~>nrsV-6f zBK3gw<}J(NROQjyZ+}PdJo6Jt5k&?IO_cz2a|wUdLMU(qO1;LIMveoh*HktU^7=ARK#g0Yah(9 za$se3aO~u4YM2Lg#NAx;U^3jw;jY&Ea`7O9jGcpi(V6C-IGF7k2iX#30D*E6L66`}04UljuyVN>gGj9+Qulz-q#F z&^D>twD>!~aUGPhc?M~p$8j*Z^)Y61Y4c<2y>7QYIFOTs{MF|qRc3W5O`elz<^U>^ zlf)ntV+tzP2V)SL;o#_b5G!=u2d5BxEaYc9=*~~BLf0wI%1Fpx{U~mx%1NlYBqzC| zhWn{OpohQXoCH1)VY^Xz*0mT03OAzGft+N_TAXZRs4O-j|BIXitGb+|=t^>FPGUUW zqpEpOqNuvccu=CjQk8=jkK)_A59g^)o7&-McmMDm!-!DHc905Z5_2bl6QOc+6Q`Cw z8Hr|G$e|RWa#XNz>7imvJjK6$sm1~3I#i5-Sxpjd8MOUI0ek#EQy$O|;4qk#l=2Y} zNa0J#l|*_VLtw6Yz*2Bl7>)vF#oB&zOOA{=^&bG^!0mo%dLOjgZ=4ZqhRNCo73)?2 zX2m)up}=)J9R;)U!MR{$KnOlJgech+rx`5HJY-GsHds0000D%45Qg2!upq#oAfUjYV4$F2pm1QIaIg=6bB2lfDi1p@;C0RsgA1_tGw z9L1HDp6w(~a*^g=N-t89=F@-9B^PJ*{PKDxNP2t!B>Tq)=RWk1Vi{|3@l#3 zZB`uIe&cg+Xh7zGD*>?`v?)Ip({RuR$J51h9pn{UYX{X8r%S?EqD>ssP#7tvUIz^) zHSn0ARG-WhHJGmSr?KcrH3rp+D=IMPHZ+F3S1}ft)+FnosY%tryt;c#)1&#>9pt{I3EM&D zYnrgHVxQ0*^eOfvp^808sA5kNs#pg@6&(y!bTCxW!QiF|`-xiknkH-qy-gFggEG!E z{nweM`WPg(n5=h~o%N&t{r;&Hc=nTbp2DRS46#KM>~6cc`U4>ZVT@V9LZ-n=K=q?w ztZ}&o!L%3v*98oMD*;m@g>|iN?SmIm27t9mE%XYQHU(>&O4*@*1|4*PX($_9<^b!` z0a_ijrBXxH;hKOkHZSk9>Z6H}k1%MPan=+}OGAbT9D|`?eTohNCd8>XU^;|jtV(Sk zkaVb2vOXQe9Amaxs3WbzK?Qa!IATnPT|P@O!PH{D$r|Iu5!5u91^W;Frx4cxm_T0u O0000(`5HJuZPe*o?%8D&p z*0)nX;wbV+&%5?&-|p(=_3Z?S@c!`$&yV-UdvJB2kP_9%{;SRI;lw#_21QXo3UI^z zSWFVKfpWc$Lgw&AJA}se$8vJNCDGhjoGqo2TbKRbL$Sz7Y;Osa<;`VmA}4@`Cg_oM zu0F6o8^Y#w>Ln;hkxjdZy!yi4(p$X*eUoy*po?<>g3N&+b0EkZ2r>tP%z@x95u_eZ zjUWU=2536-;iFiPDo3ZY1w)V|4(nchB%$0)=3203Y*ZKcM$OnIs}{dm-Tv+auIq0A zU{FOQNa^vCSI!tUosz2fFAZI~7N}WImouxc*o1|k zD;9$Pk08~uj?@iCr2nB-H`Vb_nHB@MB5QZ=JG=PDDL7+fDK|zNoC@kzC9iSt0gu)KcAu>RLV1R&uKzaJ)SgEbZvYfN! zdoTTvVmZFwXXSK%ym@_lKf+3Ue15^p^JDZJvHw=ECi{{2o9+G6(HPTwD2f8sz(nE; zJ1ONB%BspDrq&wj`llx@7GJo@s?3=^-H7apd)Ud~nV4H#RTjN$#l%?V%t2rd0&@_UgTNdF<{&Ub9W-x&a$EJpvJ==9XT`D=DBDb;?QnH{YPoZuYtwYq zuFbdOm@g5h_Pf6@m!#GjlscR}h>O3EnI>vbv!Lk`ySw3bd(7}ymNs1?<_COEkKs?G zNiaQBCjE-72tQNhg5r7tyA|Ax{i;3%75Q#xZYVy6D!3iHLU}E+y~fa7>{vJc;3KAp zb5L(Ec@0t?HI*cRb7b>c*zU!Se}Po~1yZ>^fokTOIlDj^+2Yt_P_~7Aj;3Rg4Ekfr zPBclNH8-}awxpUKDwEzcbq!Cd>7kNjn$~<1C4AE~^-a*6giP6mY3drI9HwZ@l;M~$ z$|Y{dlr;jWg;fO%&)9h>$Ik*ObxO5`*#(JRYiKU^9a08WXV;+K+5&MKBi@#q&zku;OM znRwzz)_dA$*88N*`^V=QGU4m{2X?y`*wr;0TBhw&bw>_4F${kI&_icv-!!jp=W!gx z&33zm92hP96*V%}h%l$X(Dx#0#7lx){FQ7pt%2C+#I+Y!Rh)?*Q;H)Hml`b>KSJR^ zC>#id1EFvr6b?*9#7~#hQlvnB>VY+NxdK^JmmNVu(Ges(P6YYNna0ABA8j+uA(k!p z(Ulrhktqf9i631vQD?Mv9j$@nLaK06`jYO*MO7`!d>4P^GA*$&^gVEX65pxh_cBdu z$;K%PCd4pdRl8?5nH`geaWX+F$n(-O()+Fw=<7$M*^weuLG7N23CzOk8jio0bw%P- z-0FWS-;Ojei5v#=g5D`nTK+R@joF-LbotJe_u4(1h3MQF-OG;(-MA+HIfBc4=Uc-a zG@?`@-59RlRDNfo4gm`oV@}jQopTlt=+i0XD{hIWs|&?bU?`pfixbEr=S(bV7a>^$ z*_7CQi;mC6v36y4s;A?nd!26_0H9NkBCkDJsQ_hp9N-fQG#wg`U)S%(NGEF*T zl=391)9|D16o`=WB=K002ovPDHLkV1l!>Art@r literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r1c8_14.png b/applications/PoP/toolchain/tiles_room1/r1c8_14.png new file mode 100644 index 0000000000000000000000000000000000000000..f668ced2dbd1fb96bbe71ab506f6076fd7fe98c0 GIT binary patch literal 597 zcmV-b0;>IqP)KfXsfYR!IB&fazrG7zcJshiO2cU%jNmB_!Ps+h8u9XJcWC_FQ zYcQF#GrG4Q#RzGJOR$m~jPy9or{BGL{&p!lG${*=Xv%Nm`!qn)5rK6g@T#sKVmJ60 zr39~P504n|Q6rtD1TQ=aeA-0dH5qi(yO_Es1J}CReDNoP%X$|xhg+Lsx$f(Wzgnnc zBqASha~~`@CgYZqN(^yEZ>o#c+hDBT2H6>u2iHvG^c5jn2E|y|kC=2M$zTY)vKVxM zPnQV1sy#RMflrsf;3jA)wGySZocyVUifzZm5DbxW ztwgmR5E~4}7FN0O1Cy^UbPdMVDY^Ix7P^#6IL|2KSg02eC>h{*=Lz zU?c>IP*bmtbmBV(~HbzebZ76pQgWX0@uacMJ@X jWv zcbGP6eVj8hX2G1uM>kvoz6n=%Em8JnS^jj!RE>xQw|k2+_g&nz`t;G0ewS639Z$K} z`KoM|)7cAOZlv7ll=2DAGM;kBWT~0+N#Ed1<0szg24zX1={(mjl{zvdKhwPGf9Bo8 z;@sFzb5?Myy0~iMX)zwbJuXXh9)z}W#9fw1m2KX+b8m~OX?f$uJFJ_;S18qTJ{40{ vmv?qH7#KWV{an^LB{Ts5%s%^H literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r2c0_14.png b/applications/PoP/toolchain/tiles_room1/r2c0_14.png new file mode 100644 index 0000000000000000000000000000000000000000..e2909b4069d0230a4bacffcff57c75b584f30fb5 GIT binary patch literal 588 zcmV-S0<-;zP)3<%C(|u|4@%eRvLVW-H!u#7x@IO^^ASg)_(cjchucyB6&5!MN3nl0S z`juW(s)mQ%E{mD2ZK3P_j45p>re6(<&wG`{U}HX8^J$y&>0?fF8m(nlE2fVi90cJY z2nRtp2*SZ!LUcU07Lf+&*n+Beat*3_C%b|g(G}F(P6cV=woar_KucbM@tyAP*1oyb4ha`GgS{`eoVj85mmG z8H^~P&%OrNAbJf-20=Io!a)!Yf^ZOS-5@b@38prsoQI)vjV3XuoclB9ZPjS7L?H5E ad-(??{YR$bfl@O70000P$fr#J#}nb8XIBQOU7b09DW z0&^fR2j(JDr%P&KDNvmnkX6Z7psY&X5hSpVAmMf*s20w2E!h0%o9Px}rNxh-)L@Ft zDOg?kF*Fl%MoZVx67*b16>d&nQXiGBs{1lO#4o7)rV95`>83~9G@#T;-&4sS;@74r zmb6P~y2Rm8&Ne)@IGMSbWsEcfv$WPllPRT zudB`ct=R8}`$3?c?_z4WP>JOl*OhPS-x1CcR!Yh;VvK&-Oj?6m77#e)rJP%wjk~Lh z#a&=5?gH5fR4eC950XxFHNT5nQD4sG0QY< z@kL6wOjB-iTyTO+-jwqZgs1-_m2~cgwL;U_WJT<{7hEuk;O!1iN7izpHFSuihuGvXED$k z;%79-dC4|K5rj-r*Q{xdf)o+5#n0s6Zp(uZti-bxPnFzIdkE<1<-Oa`Za_oM95Z$%sLOYxlK9)KuZa4VSlp0L`(uMkOv@*ikVuYE&nK}DDma9Y zrXDqww{y8=N=I`+r}*8~1tA!6AO24TV*=Q%5kwDLp!+VQbc?`4`snety9apn67o1H~tZr1Mt5Re)NF^7e8#Qd2#Pia!iq(y3nxp&eQ{IC%+zFzmFM*{=@xMgeH&Rgv^4|IE)2-4H g9NP_=8>&hB1(>Nt#wK?xHUIzs07*qoM6N<$f{w8s{r~^~ literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r2c3_14.png b/applications/PoP/toolchain/tiles_room1/r2c3_14.png new file mode 100644 index 0000000000000000000000000000000000000000..48ec563f6ddaeecb3bfdb2e29726209a49395944 GIT binary patch literal 712 zcmV;(0yq7MP)0m%YbL?xmk5;5Y55%q9h+YFhI|4H*k z^co7y2_HAf%ex(JUS8jR8X~=ae2%Y&VK{zWtybgr_?qqZl8Vm?#`*2!=WPEa{|&0C zl>ugoA)FBFhId_6FzEZJaZJH&H=?PjT_hxD}HaDuEUIK#QX zCQFP10p_K;;nqdE}%xubF2BxIzk%HXNq=zCCu2jI^?PY#wGD8u{1t&9$yPE6bpYn^D#eET<^81{2 zIjFW*H9+BT+U1}+)h%`AaA|aqI&-)*ad2r-Q`gbKr3&Cc*Uf~$@P^~v`4lf@4+0#H zWk+4V0B{1NTxO2fodGt!$2t8HZ#TkwzmuoBkg7N+J*6zvFwX*%5j8Y4Ms?N~0TdC3 uD>UZ$>B0NHbq}gggwlgi$^Vp#o$D9oP=A7N5G-2&00000 literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r2c4_0E.png b/applications/PoP/toolchain/tiles_room1/r2c4_0E.png new file mode 100644 index 0000000000000000000000000000000000000000..88b0c988d86597eae3d21e1e3a43eb8e7b93c3bc GIT binary patch literal 844 zcmV-S1GD^zP)hoH%b}er^{bw@C#$NBYvTRA4EN*V^ug*DlKA*#z>$=X-F?|MDxv4_DxTl}r z7hL=8-G?iz*KWIcoo)9i7{So@rHexaJdNSp$sL$>Ut=YJRW)R=?vv2I41-G-C|w+S z$W~gGI+QMs2GcBQ85~rS#)2)i#M4RaFm?LSXxghS*xItqENG=>7SwFg|A!B{!r)^q zh_LIbaN>wFD+$Awp4xHtV)c4S|zS*(WDVXeVc7FHTxJgPWM$slO$lyv&5 z1vyAq#|(^u1><+~P^TRj8ZQu4%Kx_us$~rXC^z$_fD!}>7mrH-H)q0H5++9c|n664#wF!VGJwwARXOQyOfVD`$1{Neu z&9uQf<+lQBCI7F2nG{WPsH$k1LsdmnNfs-*s%R>0@Fp3xGZF~T5Su>buMt>SuqNf7 zBlTb0J$$_8AhiyTLO*VHj=vtC-#2gaKVqvw-PFns3NAx%L5S!8Km_+zTLds*F+>a( zL&V?HF}1UR8o2$t-HYSH@i8;bAGnA2`$7BHZ$GloqaUKjCv$GG&HOPU2H@HxU-TL< zSm&qn&|55?qMVgD3x~18x!gZKSU?Uer(@zjIS9@#V_#DNreNtR<5nG~v_Fa9B;S3J z^3$CRrZO2kw;D+~OYC=VRX!P}m>|=e@}W`kft+%H+mU1BZYpPSlP3!s#*d__i_f8c zId|~VAR4f3KRvl|I3FdrcxpK<=+9+S0ZCfF9IM5r&tIy3vUe2<3r5;EgUk}vR{9Mv WDh`TtJ%{K30000-Bp7J4^uLyllqzmxpG)^7wqa`Fx#V#v(K!d^jutiUX>-E8k$I zuy8Pj0g3CgZho%3*H9dsh9;y2&l%!+ii1HwQ*!Bd2cxJCnsB1RugAe1i~*O_L5frx z$%C&!thgh^WaFUE&Cwi`jtIB836o>SsDQ!|M7H$^4q?Vo94yCLg94d`qOQRhJBO*L z4)#sXDu+BMJ?_TJgTZnuhP$$@kdxGd&?AuQ!G-(<)>YPioLi`KcXDFNfHMEj_M%i zrL)vZQqA<)m${=@9la;ujC&* ziiO2lU&)_+6qiHwBv>8NlT5MYekcfJ`+u`1ffrQRdR0zwFnA2n9rW})=dc|^fTC5@ zFqtnR|3yz?D!ZPkU<(&0)^Yjd0|evL`HQ-ODqobFCx~>D|8l;l1XZWYaL6F9Cf* zZ@PRXCn+>JJHK42H?=tY%>>TE`8PRbr4Ain+ThZDghh6uQ0v4AMLpG*T6_-p)tpe{ xJZEc(X2yH=`yTv!*_gArWBH6n^36ZYD{rj*%nOVo22WQ%mvv4FO#nO(%Y*;` literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r2c7_01.png b/applications/PoP/toolchain/tiles_room1/r2c7_01.png new file mode 100644 index 0000000000000000000000000000000000000000..0352215f84a11789f00ff58d7255800355b8feb2 GIT binary patch literal 313 zcmV-90mlA`P)F76dH@f>0W2sjD5jvW*JzYbz$Q*y zS@FLP*kLEkWJn03&31RiQ+_HSWeJfBs;YXNOOgrfc#PRJfp2-s{?Yy9%l%?eoa^H_ zjvtGG61n&E(7Hbn5fKp)5z)U%=d1c-f?EkeM z6lImXCMb=$r>&Cl`2?(D?x_>(t52hE{MCjI`@<30a^YMP{Gh%8DNtGS7p;s100000 LNkvXXu0mjf5`TwG literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r2c8_01.png b/applications/PoP/toolchain/tiles_room1/r2c8_01.png new file mode 100644 index 0000000000000000000000000000000000000000..992c339860203ca7b563107beaf177ee56905382 GIT binary patch literal 521 zcmV+k0`~ohP)Zq}6owxb2Vn4!fzd;T4jy`hj6Fbx9-?Cp&?EE+9Xj*?9eRWe89R8);K2h!b}Azr z83aKX8^5IfB!nO_{`KDD@g637eS5E=$x|M9&@cGj9yWziKZ;NK#lI^^mac@ z6L*LuS5ngy8ZwpNT=E>BI6sHkIm>r!?w()j+jlW$2q1?5atI)Yz%Av$KCbnjPZs4g z?jbLxJQ$k$7I<8tW{hq@C1tb9G)DB2U!|sLA!(%v{(IAu>t`V#h5#~z%j>rxlv2)P z#>U!fbc+R2N}b1yjZLqBm$I4#l|y`vL404(H9oHj^m#$wtQLdH(Mv%EDl6}~dWnRz z0G-HtUInfdr=rb;W}k=tstvC2@%0Vchwl7lISkEbDML+@ov{A^P7d`iqTiu+00000 LNkvXXu0mjf7%k$4 literal 0 HcmV?d00001 diff --git a/applications/PoP/toolchain/tiles_room1/r2c9_14.png b/applications/PoP/toolchain/tiles_room1/r2c9_14.png new file mode 100644 index 0000000000000000000000000000000000000000..0f3076878cfb83b76eb18cd61656270f145c65ff GIT binary patch literal 541 zcmV+&0^-!Y|j%Nu~*O0Q(KOw{p#deEwFA$Trm#1v(c&>$My5csQ z4V1b7sBy%+|7fDDnRYHJ$PoQyCdbPP%*&al51 zD2h8wg>D^?RsCllg7P~MHqB{JpcW-|?M=)+6s2Xh1_g!|<-^SsbAiAd0&@t=Auxx) z9KwWF`~G`LVx?KQMo@bZGmuP$s_RLERZTvJ zIg%Z7rvH76++3iyDITFuW8p6hHs@NICY_o#;=kgAr{Stj0Zmie3|f(0aixc+IfOJ# z`n+zmiUfvhS$%Oz;IP(%J$2uEL%G{wTfm+)_c z%EC06)hIK}R|yQ4m1&w|H7eUZJYHRlPbjL>N5*QUDXM9T!GX(4lXP4dN?NF0Y9aqR zh#uT#St-;+Z}h literal 0 HcmV?d00001 diff --git a/toolchain/make_hdd.sh b/toolchain/make_hdd.sh new file mode 100755 index 0000000..8578fc0 --- /dev/null +++ b/toolchain/make_hdd.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# make_hdd.sh OUT.chd FILE1 [FILE2 ...] +# +# Собирает 32 МБ RAW-образ жёсткого диска (MBR + один раздел FAT16) с +# указанными файлами в корне и конвертит его в CHD для MAME (монтируется +# как -hard2). Быстрая альтернатива floppy (mc.img): MAME эмулирует FDD +# с реальной черепашьей скоростью, HDD — в разы быстрее. +# +# Требует: mtools (mpartition/mformat/mcopy) + chdman (из MAME). +# Sprinter DSS штатно монтирует стандартный MBR+FAT16 (подтверждено). +# +# Использование: +# toolchain/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ... +set -e + +if [ $# -lt 2 ]; then + echo "usage: $0 OUT.chd FILE1 [FILE2 ...]" >&2 + exit 1 +fi + +OUT_CHD="$1"; shift + +# Геометрия: 128*16*32*512 = 32 МБ. BPS=512. +CYLS=128; HEADS=16; SECS=32 +SECTORS=$((CYLS * HEADS * SECS)) # 65536 + +# macOS mktemp требует X'ы в КОНЦЕ шаблона. +RAW=$(mktemp /tmp/sphdd.raw.XXXXXX) +MTC=$(mktemp /tmp/sphdd.mtc.XXXXXX) +trap 'rm -f "$RAW" "$MTC"' EXIT + +dd if=/dev/zero of="$RAW" bs=512 count=$SECTORS status=none + +# mtools-диск z: = раздел 1 нашего RAW-образа. +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 + +# MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16. +mpartition -I -c -t $CYLS -h $HEADS -s $SECS z: +mformat z: + +for f in "$@"; do + mcopy -o "$f" z: +done + +echo "--- содержимое образа ---" +mdir z: + +# RAW -> CHD (без сжатия = быстро; та же геометрия). +chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \ + -chs $CYLS,$HEADS,$SECS -ss 512 -f +echo "готово: $OUT_CHD" diff --git a/toolchain/png_strip.py b/toolchain/png_strip.py new file mode 100644 index 0000000..d7c860a --- /dev/null +++ b/toolchain/png_strip.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +""" +png_strip.py — генерик-конвертер произвольного набора PNG-кадров (РАЗНОГО +размера каждый) в одну raw-ленту + палитру для sprite-движка Sprinter +(формат getimage/atlas — см. libbgi/include/sprite.h, toolchain/mkatlas.py). + +Не завязан ни на какой конкретный проект/игру — просто берёт список +картинок, которые вы сами укажете, и паковает их в ленту с общим +канвасом (кадр движка обязан быть фиксированного размера). + +Использование: + png_strip.py OUT.raw OUT.pal img1.png img2.png ... [опции] + +Опции: + --canvas WxH размер канваса кадра (по умолчанию — максимум + ширины/высоты среди входных картинок) + --anchor MODE bottom-center (по умолчанию) | top-left | center + --base-index N с какого индекса палитры начинать свои цвета + (по умолчанию 16 — под 16 EGA-слотов initgraph) + --ega добавить в палитру 16 EGA-цветов BGI перед своими + (по умолчанию включено; --no-ega выключает) + --seed-pal FILE переиспользовать цвета уже существующей палитры + (EGA16 + N своих) — свои индексы этой ленты + продолжаются ПОСЛЕ них (не с --base-index), а + OUT.pal получится ПОЛНОЙ merged-палитрой + (ega+seed+свои), готовой как --seed-pal для + следующего вызова. Так несколько независимо + собранных лент (напр. разные типы тайлов) можно + свести в ОДНУ палитру для одновременного показа + на экране (см. applications/PoP/poc/tools/ + build_room_palette.py). + +Каждая картинка — источник ОДНОГО кадра ленты (не атлас-сетка; +для сетки — см. mkatlas.py raw:FWxFH:NXxNY после этого шага, если +нужно). Прозрачность — альфа < 128 → индекс 0xFF (сентинел движка). + +Дальше: toolchain/mkatlas.py OUT.atl OUT.raw:CW x CH:1xN +""" +import argparse +import sys +from PIL import Image + +EGA = [ + (0, 0, 0), (168, 0, 0), (0, 168, 0), (168, 168, 0), + (0, 0, 168), (168, 0, 168), (0, 84, 168), (168, 168, 168), + (84, 84, 84), (255, 84, 84), (84, 255, 84), (255, 255, 84), + (84, 84, 255), (255, 84, 255), (84, 255, 255), (255, 255, 255), +] + + +def die(msg): + sys.exit(f"png_strip: {msg}") + + +def main(): + ap = argparse.ArgumentParser(add_help=False) + ap.add_argument("out_raw") + ap.add_argument("out_pal") + ap.add_argument("images", nargs="+") + ap.add_argument("--canvas", default=None) + ap.add_argument("--anchor", default="bottom-center", + choices=["bottom-center", "top-left", "center"]) + ap.add_argument("--base-index", type=int, default=16) + ap.add_argument("--ega", dest="ega", action="store_true", default=True) + ap.add_argument("--no-ega", dest="ega", action="store_false") + ap.add_argument("--seed-pal", default=None) + args = ap.parse_args() + + imgs = [Image.open(p).convert("RGBA") for p in args.images] + + seed_records = [] + if args.seed_pal: + raw = open(args.seed_pal, "rb").read() + if len(raw) % 4: + die(f"--seed-pal {args.seed_pal}: длина не кратна 4") + n_ega = 16 if args.ega else 0 + for i in range(n_ega, len(raw) // 4): + b, g, r, _ = raw[i * 4:i * 4 + 4] + seed_records.append((r, g, b)) + args.base_index = 16 + len(seed_records) + + if args.canvas: + cw, ch = (int(v) for v in args.canvas.lower().split("x")) + else: + cw = max(im.width for im in imgs) + ch = max(im.height for im in imgs) + if cw > 64 or ch > 64: + die(f"канвас {cw}x{ch} превышает лимит движка 64x64") + for p, im in zip(args.images, imgs): + if im.width > cw or im.height > ch: + die(f"{p}: {im.width}x{im.height} не влезает в канвас {cw}x{ch}") + + colors = {} + for i, rgb in enumerate(seed_records): + colors[rgb] = 16 + i + + def color_index(r, g, b): + key = (r, g, b) + if key not in colors: + idx = args.base_index + (len(colors) - len(seed_records)) + if idx >= 0xFF: + die("палитра переполнена (>= 0xFF своих цветов)") + colors[key] = idx + return colors[key] + + data = bytearray() + for im in imgs: + if args.anchor == "top-left": + ox, oy = 0, 0 + elif args.anchor == "center": + ox, oy = (cw - im.width) // 2, (ch - im.height) // 2 + else: # bottom-center + ox, oy = (cw - im.width) // 2, ch - im.height + canvas = [0xFF] * (cw * ch) + for y in range(im.height): + for x in range(im.width): + r, g, b, a = im.getpixel((x, y)) + if a < 128: + continue + canvas[(oy + y) * cw + (ox + x)] = color_index(r, g, b) + data.extend(canvas) + open(args.out_raw, "wb").write(data) + + pal = bytearray() + if args.ega: + for b, g, r in EGA: + pal += bytes((b, g, r, 0)) + new_colors = [rgb for rgb in colors if rgb not in seed_records] + for (r, g, b) in (*seed_records, *new_colors): + pal += bytes((b, g, r, 0)) + open(args.out_pal, "wb").write(pal) + + print(f"png_strip: {len(imgs)} кадров, канвас {cw}x{ch}, " + f"{len(data)} Б -> {args.out_raw}") + print(f" палитра {len(seed_records)} seed + {len(new_colors)} новых " + f"своих цветов ({len(pal)} Б) -> {args.out_pal}") + print(f"дальше: mkatlas.py OUT.atl {args.out_raw}:{cw}x{ch}:1x{len(imgs)}") + + +if __name__ == "__main__": + main() diff --git a/toolchain/room_compose.py b/toolchain/room_compose.py new file mode 100644 index 0000000..e438178 --- /dev/null +++ b/toolchain/room_compose.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +""" +room_compose.py — генерик-компоновщик полноэкранного фона комнаты из +тайлов ПРОИЗВОЛЬНОГО размера (см. applications/PoP/docs/PORT_PLAN.md §4: +один полноэкранный фон на комнату готовится офлайн, поверх — спрайты +через sprite.h). Не завязан ни на какой конкретный проект/игру — просто +берёт раскладку тайлов, которую вы сами опишете, и картинки, которые +сами укажете. + +Раскладка — JSON: + { + "rows": [ + {"height": 20, "tiles": ["a.png", "b.png", ...]}, + {"height": 60, "tiles": [...]}, + ... + ] + } + +Тайлы в ряду кладутся слева направо; высота ряда — из "height" (тайл +меньшей высоты — сверху, остаток — прозрачность 0xFF; выше — ошибка). +Ширина итоговой картинки = сумма ширин тайлов первого ряда — остальные +ряды обязаны давать ТУ ЖЕ сумму (иначе ошибка, во избежание кривого +экрана по краю). + +Использование: + room_compose.py OUT.raw OUT.pal --layout layout.json [--base-index N] + +Выход: + OUT.raw — ГОТОВЫЙ формат getimage (uint16 w, uint16 h LE + пиксели + построчно) — грузится fread'ом целиком и передаётся + напрямую в gfx_blit(x, y, buf). + OUT.pal — палитра (EGA 16 + свои цвета), формат gfx_pal_fload. +""" +import argparse +import json +import struct +import sys +from pathlib import Path +from PIL import Image + +EGA = [ + (0, 0, 0), (168, 0, 0), (0, 168, 0), (168, 168, 0), + (0, 0, 168), (168, 0, 168), (0, 84, 168), (168, 168, 168), + (84, 84, 84), (255, 84, 84), (84, 255, 84), (255, 255, 84), + (84, 84, 255), (255, 84, 255), (84, 255, 255), (255, 255, 255), +] + + +def die(msg): + sys.exit(f"room_compose: {msg}") + + +def main(): + ap = argparse.ArgumentParser(add_help=False) + ap.add_argument("out_raw") + ap.add_argument("out_pal") + ap.add_argument("--layout", required=True) + ap.add_argument("--base-index", type=int, default=16) + ap.add_argument("--ega", dest="ega", action="store_true", default=True) + ap.add_argument("--no-ega", dest="ega", action="store_false") + args = ap.parse_args() + + spec = json.loads(Path(args.layout).read_text()) + rows_spec = spec["rows"] + if not rows_spec: + die("layout: пустой список rows") + + rows_imgs = [] + width = None + for ri, row in enumerate(rows_spec): + rh = int(row["height"]) + tiles = row["tiles"] + if not tiles: + die(f"row {ri}: пустой список tiles") + imgs = [Image.open(t).convert("RGBA") for t in tiles] + for t, im in zip(tiles, imgs): + if im.height > rh: + die(f"{t}: высота {im.height} > высоты ряда {rh}") + row_w = sum(im.width for im in imgs) + if width is None: + width = row_w + elif row_w != width: + die(f"row {ri}: ширина {row_w} != ширины первого ряда {width} " + f"(все ряды должны давать одинаковую суммарную ширину)") + rows_imgs.append((rh, imgs)) + + height = sum(rh for rh, _ in rows_imgs) + if width > 65535 or height > 65535: + die(f"{width}x{height}: превышает предел формата (u16)") + + colors = {} + + def color_index(r, g, b): + key = (r, g, b) + if key not in colors: + idx = args.base_index + len(colors) + if idx >= 0xFF: + die("палитра переполнена (>= 0xFF своих цветов)") + colors[key] = idx + return colors[key] + + canvas = [0xFF] * (width * height) + y0 = 0 + for rh, imgs in rows_imgs: + x0 = 0 + for im in imgs: + for y in range(im.height): + for x in range(im.width): + r, g, b, a = im.getpixel((x, y)) + if a < 128: + continue + canvas[(y0 + y) * width + (x0 + x)] = color_index(r, g, b) + x0 += im.width + y0 += rh + + with open(args.out_raw, "wb") as f: + f.write(struct.pack(" {args.out_raw} ({4 + width * height} Б, вкл. getimage-заголовок)") + print(f" палитра {len(colors)} своих цветов " + f"({len(pal)} Б) -> {args.out_pal}") + + +if __name__ == "__main__": + main()