From 0941ef1d900ec5f8da11df0fc7999d31f83f735a Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 10 Aug 2026 12:04:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A8=D0=B0=D0=B3=20D:=20=D0=BF=D0=B0=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D0=BD=D1=8B=D0=B5=20=D0=B2=D0=B5=D1=82=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D0=BA=D0=B8=20+?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=82=D0=B5=D1=80=D1=8F=D0=BD=D0=BD=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=B2=D0=B5=D1=80=D1=85=20=D0=B2=D0=BE=D1=80=D0=BE=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Порт семи мест seg008, расходящихся по tbl_level_type, плюс общая дыра порта, которая на дворце стала видна. Паласные ветки (все — «в подземелье этого нет»): - doortop_fram_top / doortop_fram_bot (seg008:413, 506): декоративная панель над воротами. У шва она и есть тот «ковёр», которого не хватало. - stripe_id соседа слева (seg008:486): орнаментная лента под окнами. Она непрерывная, потому что stripe_id = 145 у пола, кнопок, зелья, loose, чомпера и меча; без неё лента шла кусками (только blueline). - полоска на стене id 84 (seg008:510), при (modifier & 0x80) == 0. - blueline_fram3: условие `num == !!level_type` — в подземелье пропускается num==0, в паласе num==1 (seg008:501). - левая половина кнопки-opener без пола (id 148) — только подземелье (seg008:628). - склянка зелья: id += 2 во дворце (seg008:747). - remove_loose возвращает ТИП УРОВНЯ, и он ложится модификатором пустой клетки от упавшей плиты (seg007:846/1083). Потерянный вывод (НЕ паласное расхождение, просто заметили здесь): draw_tile_anim_topright (seg008:0568) не был портирован вовсе — верх ворот, который рисует тайл НАД ними: маска 68 (mono, чёрным) + door_fram_top [(modifier>>2) % 8] = 60..67. Ids 60..68 в атлас не паковались. Симптом — чёрный клин над воротами; нашёлся сравнением с эталоном SDLPoP (--screenshot) и трассой add_backtable. Флаг тайлсета pop_palace вынесен в резидент (pop_tile.c): по нему расходятся ветки в банке 7 (полная отрисовка), банке 2 (fore-проход) и банке 3 (модификатор пустой клетки) — читается напрямую, без трамплина. Известное расхождение: модификатора ряда СНИЗУ у нас нет (pop_t_below — только fg), поэтому паласная панель над воротами в комнате снизу не рисуется. Помечено в коде. tests-host: 5/5 (добавлен include-путь до pop_bg_atlas.h и стаб pop_palace). Проверено в MAME: уровень 4 совпадает с эталоном SDLPoP в рядах 0-1 попиксельно (кроме фазы пламени); уровень 1 не изменился. Co-Authored-By: Claude Opus 5 --- applications/PoP/poc/res/bg/pal_env3.atl | Bin 1875 -> 2643 bytes applications/PoP/poc/res/bg/pal_env4.atl | Bin 4280 -> 5052 bytes applications/PoP/poc/res/bg/pop_env3.atl | Bin 1885 -> 2653 bytes applications/PoP/poc/res/bg/pop_env4.atl | Bin 4296 -> 5068 bytes applications/PoP/roomtest/pop_map.c | 4 + applications/PoP/roomtest/pop_room.c | 76 +++++++++++++++--- applications/PoP/roomtest/pop_tile.c | 8 ++ applications/PoP/roomtest/pop_tile.h | 5 ++ applications/PoP/roomtest/tests-host/Makefile | 3 +- applications/PoP/roomtest/tests-host/stubs.c | 5 ++ applications/PoP/toolchain/pop_pack_bg.py | 11 ++- 11 files changed, 101 insertions(+), 11 deletions(-) diff --git a/applications/PoP/poc/res/bg/pal_env3.atl b/applications/PoP/poc/res/bg/pal_env3.atl index 11e3b6240f9d02094dfdd6faf19d352ae071d94d..6a86a5a2b9aaef0a7ad174ef9735167c4e2fc3b7 100644 GIT binary patch literal 2643 zcmd^AJ8r`;43+KplORDlWKiagojUXYy+NI%h&}fRJx&kOqx1k>I&|yMG366YiILqD zK@cE74a5|mNb%`Wa@G&`w^!!;&!<_=lEjz~Dg}F&+9_OwhQxdXm8WOGDLb;ZHyn907^zeq_9nNnbuOl%p>m0!S3Q;_}{>^-tn+Iv|7p`M5 zmiXiFxtYWoMbh*X2~xUJvAe%*Cvu>4*bQ+svZe~md{5d2XE&xuehu7nNFDqvDEU_g+IyTfsuC7ggsR=`3Q=eEr-@0jCmheBLO-uzt<4_3Yj0gE|OjQ<}I z-uf0XCRxH!X91&K)Wf`Ej$k5ngq7CpAm+YHIwI)ED0dfc9syN1fb{Ajx?2Gahk3^w bu~Lj-tsb_=5n0D`3|ZidBA&O~8Ta`ImMqeU delta 129 zcmcaCa+!}cIKa`6XQE*ELAA% diff --git a/applications/PoP/poc/res/bg/pal_env4.atl b/applications/PoP/poc/res/bg/pal_env4.atl index c4e24fb17fd39622e1f37038cbeb15040a31df76..7c1ec1da96e017083693fedf9e7e104693997d48 100644 GIT binary patch delta 601 zcmdm?xJP|LM$i)m5l%)%28KC|5ITejLh~>~=p)P$VEF|s3Va}SDQpV7Aew_iQUI)v z6G9tsD=>rjQ@AJYl4JV8J@J;w#9spSk>UUU2L}B|0a(Q%{{N2zVh8}K0P!OuA_Ad8 z|6yW4MF=slu82s89WWI@U0|~UgG44i)dadi47-h!c^DNuV483n3w096F;HWHPJ=oS z+6XI7Ad(943k?ju1r_M-~N(n}Y2_6^3a*7lnu`Fz_)4{>R!i^;J zZb3rsn}$SdS}3=tAMd_x7zs{;H;0xZkz{aNf$qCHbl*iHL+Yb1R_MqV_Z1{KgQJg2 zw5g*|FJ3EffYeFC<9<_A>aX1VNTXDnV@*^|)*{s)-8-wGj8zrpm98}se($C!M+#`S znBs(ML+gMioIx;5efJzuAOJd!W^CW^EK}*@-(hpIOKmn%x^WBVT&8+arYL zxsQdjx3(BFsthI_71I-2u>L+!PqO44gB8^nYq!I^Q;wuGoeh?wXm2q&6BLtf!jZG+ zpB#eOw}>&x5{@@025$NFQN^Z&!-(97zOQ%aOF~zId8z-n8zSnm2$j&5GLB f!+L8?<&a|MIdU$}V$D6zQL}`j=#5G|RaN~1{x8~6 delta 129 zcmcaBa+i-aIKa`6XQE*EL~=p)P$VEF|s3Va}SDQpV7Aew_iQUI)v zQwmHwa4Rr__)EAa?vi8T;F);KWa2M@`iO}C|ARySqkwQ6Vv!**WguPu|A$9JMnu5H zK)OI;KniXiNDQbZ5@H9EuK$r>F%f14PKL>hOu`d?3j@j#-O;t)}EahNEoI6@Rz99a}BZVI*=RT!oLT@)g&z`(~aS&&0=vOg31<^zlz F`~bhYPUQdq delta 86 zcmX@3enN3V#>4{>R!#k16AS0WTPBmunEN&_VA0_R0KCo*&Hw-a diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index b74a13e..5d59b98 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -2068,6 +2068,10 @@ void pop_loose_tick(void) __banked * — кусок улетает из комнаты вниз, на месте остаётся пусто * (стена-вниз от тайла снизу + чёрный фон). */ g_fg[pos] = TILE_EMPTY; + /* remove_loose возвращает ТИП УРОВНЯ, и вызывающий кладёт + * его модификатором пустой клетки (seg007:846/1083): по нему + * draw_tile_right выбирает blueline_fram1 у дыры. */ + pop_trob_modif(g_room)[pos] = pop_palace; pop_loose_modif[pos] = 0; pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */ pop_loose_mob_spawn(row, col); /* отрыв падающего куска */ diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index e46812f..cc8e408 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -91,6 +91,7 @@ int pop_bg_load(uint8_t set) __banked if (bg_set == 0xFF && /* chtab_1 от набора не зависит */ atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1; bg_set = set; + pop_palace = (uint8_t)(set == 1); /* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от * прежнего набора и картинка хотя бы не разъезжается. */ return bg_load_tile_pal(pop_tile_pal[set]); @@ -156,10 +157,34 @@ static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3 +/* Дворец? — по ЗАГРУЖЕННОМУ набору (pop_palace ставит pop_bg_load), а не по + * номеру уровня: так ветки отрисовки и картинки заведомо согласованы даже в + * момент смены. */ +#define PALACE (pop_palace) + +/* Декоративная панель над воротами — ТОЛЬКО в паласе (в подземелье над + * воротами не рисуется ничего). Индекс — модификатор тайла doortop. + * seg008:407 doortop_fram_top / seg008:453 doortop_fram_bot. */ +static const uint8_t DOORTOP_FRAM_TOP[4] = {0, 81, 83, 0}; +static const uint8_t DOORTOP_FRAM_BOT[4] = {78, 80, 82, 0}; + +/* Верх ворот: его рисует тайл НАД воротами (draw_tile_anim_topright, + * seg008:0568). Кадр — по открытости соседа снизу-слева; 68 — маска, + * mono-блит чёрным «продавливает» фон под створкой. + * Без этого над воротами оставался чёрный клин — виден у шва во дворце, + * где сверху идёт декоративная панель doortop. */ +static const uint8_t DOOR_FRAM_TOP[8] = {60, 61, 62, 63, 64, 65, 66, 67}; +#define GATE_TOP_MASK 68 + /* topright тайла снизу-слева (seg008.c:410). */ -static void draw_topright(uint8_t tt, int x, int dby) +static void draw_topright(uint8_t tt, uint8_t tmod, int x, int dby) { - if (tt == 7 || tt == 12) return; + if (tt == 7 || tt == 12) { /* верх ворот */ + if (!PALACE) return; /* подземелье: ничего (seg008:413) */ + if (DOORTOP_FRAM_TOP[tmod & 3]) + pop_env_b(DOORTOP_FRAM_TOP[tmod & 3], x, dby); + return; + } if (tt == 20) { pop_wall_b(2, x, dby); return; } if (pop_tile_table[tt].topright_id) pop_env_b(pop_tile_table[tt].topright_id, x, dby); @@ -236,7 +261,7 @@ static void draw_tile(int row, int col) int dmy = dby - 3; const piece *t = &pop_tile_table[code]; const piece *lt = &pop_tile_table[lcode]; - uint8_t rbl_code; + uint8_t rbl_code, rbl_mod; uint8_t wmod; /* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0 @@ -250,11 +275,28 @@ static void draw_tile(int row, int col) * load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */ if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20; else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 1; - } else rbl_code = pop_tile_code(row + 1, col - 1); + /* Модификатора ряда снизу у нас нет (pop_t_below — только fg), а он + * нужен паласной панели над воротами. Ставим 0 = «панели нет»: + * случай узкий (ворота в комнате СНИЗУ, по диагонали от пустого + * тайла нижнего ряда), но расхождение с оригиналом честное. */ + rbl_mod = 0; + } else { + rbl_code = pop_tile_code(row + 1, col - 1); + rbl_mod = pop_tile_mod(row + 1, col - 1); + } + + /* --- draw_tile_anim_topright (seg008:0568): верх ВОРОТ снизу-слева. + * Условие дословно: свой тайл пуст / верх большой колонны / верх двери, + * а снизу-слева — ворота. --- */ + if ((code == 0 || code == 9 || code == 12) && rbl_code == 4) { + uint8_t om = (rbl_mod > 188) ? 188 : rbl_mod; + pop_env_b(GATE_TOP_MASK, x, dby); /* маска (mono, чёрным) */ + pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], x, dby); + } /* --- floorright (seg008.c:392) — только can_see_bottomleft --- */ if (code == 0 || code == 9 || code == 12 || code == 26) { - draw_topright(rbl_code, x, dby); + draw_topright(rbl_code, rbl_mod, x, dby); /* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op * (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет * floor_right: lt->floor_right. */ @@ -263,6 +305,10 @@ static void draw_tile(int row, int col) /* --- draw_tile_right (seg008.c:456) --- */ if (code != 20) { if (lcode == 20) { + /* Полоска на стене (seg008:510) — только палас и только если у + * стены не взведён старший бит модификатора. */ + if (PALACE && (lmod & 0x80) == 0) + pop_env_b(84, x + 3 * 8, dmy - 27); pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */ } else if (lcode == 0) { if (lmod <= 3 && BLUELINE_FRAM1[lmod]) @@ -271,15 +317,26 @@ static void draw_tile(int row, int col) uint8_t num; pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */ num = (lmod <= 3) ? lmod : 0; - if (num != 0) /* dungeon: level_type=0 */ + /* seg008:501 — `if (num == !!level_type) return;`: в подземелье + * пропускается num==0, в паласе num==1. */ + if (num != (PALACE ? 1 : 0)) pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */ + } else if (lcode == 7 || lcode == 12) { + /* Низ декоративной панели над воротами (seg008:506) — только + * палас; в подземелье у doortop right_id нулевой и ветка пустая. */ + if (PALACE && DOORTOP_FRAM_BOT[lmod & 3]) + pop_env_b(DOORTOP_FRAM_BOT[lmod & 3], x, lt->right_y + dmy); } else { if (lt->right_id) pop_env_b(lt->right_id, x, lt->right_y + dmy); + /* Орнаментная лента соседа (seg008:486) — только палас. Она и + * даёт непрерывную полосу под окнами: stripe_id = 145 у пола, + * кнопок, зелья, loose, чомпера и меча. */ + if (PALACE && lt->stripe_id) + pop_env_b(lt->stripe_id, x, dmy - 27); /* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */ if (lcode == 19 || lcode == 30) pop_env_b(146, x, dby - 28); - /* stripe — только палас, dungeon пропускает */ } } @@ -325,8 +382,9 @@ static void draw_tile(int row, int col) uint8_t base_id = t->base_id; if (code == 11) /* loose: левая грань (анимир.) */ base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])]; - else if (code == 0x0F && lcode == 0) /* opener, слева пусто */ - base_id = 148; /* левая половина без пола */ + else if (code == 0x0F && lcode == 0 && !PALACE) /* opener, слева пусто */ + base_id = 148; /* левая половина без пола — только */ + /* подземелье (seg008:628) */ if (base_id) pop_env_b(base_id, x, t->base_y + dmy); } diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index f4103e2..6bc73aa 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -19,6 +19,13 @@ /* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */ atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at; +/* Загруженный тайлсет: 0 подземелье, 1 дворец (порт tbl_level_type в той + * части, что касается ОТРИСОВКИ). Ставит pop_bg_load; резидент — потому + * что по нему расходятся ветки и в банке 7 (полная отрисовка тайла), и в + * банке 2 (fore-проход), и в банке 3 (модификатор пустой клетки от упавшей + * плиты, remove_loose seg007:0EB8). Читать напрямую, без трамплина. */ +uint8_t pop_palace; + const uint8_t *pop_t_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg; const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod; uint8_t pop_t_room; @@ -337,6 +344,7 @@ void pop_potion_flask(int row, int col, uint8_t modif) uint8_t id = 12; if (type == 0) return; if (type >= 2 && type < 5) id = 13; /* большая склянка */ + if (pop_palace) id += 2; /* seg008:747: во дворце склянки другие */ pop_pot_b(id, (POT_FORE_X + POP_COL_XH[col]) * 8 + 6, POT_FORE_Y + 63 * row + 62); } diff --git a/applications/PoP/roomtest/pop_tile.h b/applications/PoP/roomtest/pop_tile.h index 9af1d67..9fb0397 100644 --- a/applications/PoP/roomtest/pop_tile.h +++ b/applications/PoP/roomtest/pop_tile.h @@ -39,6 +39,11 @@ #include "pop_bg_atlas.h" extern atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at; +/* Загруженный тайлсет: 0 подземелье, 1 дворец. Ставит pop_bg_load. По нему + * расходится с десяток веток отрисовки (seg008) и модификатор пустой клетки + * от упавшей плиты (seg007:0EB8). */ +extern uint8_t pop_palace; + /* ---- Текущая отрисованная комната ----------------------------------- * * fg — foretable-коды, bg — модификаторы; lfg/lbg — правый столбец ЛЕВОГО * соседа (кромка); below — ряд 0 комнаты снизу (11 байт, см. pop_bg.h); diff --git a/applications/PoP/roomtest/tests-host/Makefile b/applications/PoP/roomtest/tests-host/Makefile index 40da43b..11c4728 100644 --- a/applications/PoP/roomtest/tests-host/Makefile +++ b/applications/PoP/roomtest/tests-host/Makefile @@ -18,7 +18,8 @@ DATA_LOC := 0xA000 # sprinter: под тестом эти функции заглушены (stubs.c), но объявления # берутся настоящие — иначе разъедутся сигнатуры. PROJ_ROOT := $(abspath $(TESTKIT)/..) -EXTRA_INC := -I$(PROJ_ROOT)/libc/include -I$(PROJ_ROOT)/libbgi/include -I$(CURDIR) +EXTRA_INC := -I$(PROJ_ROOT)/libc/include -I$(PROJ_ROOT)/libbgi/include -I$(CURDIR) \ + -I$(ENGINE_DIR)/../poc/res/bg # pop_bg_atlas.h (генерит pop_pack_bg.py) OBJS_geom := build/eng_pop_geom.rel diff --git a/applications/PoP/roomtest/tests-host/stubs.c b/applications/PoP/roomtest/tests-host/stubs.c index 7305a9c..aaca387 100644 --- a/applications/PoP/roomtest/tests-host/stubs.c +++ b/applications/PoP/roomtest/tests-host/stubs.c @@ -313,3 +313,8 @@ const uint8_t POP_TILE_MOD[256] = { 12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1, }; + +/* Тайлсет (0 подземелье, 1 дворец) — в приложении его ставит pop_bg_load; + * тесты гоняют физику подземелья, поэтому 0. Не заглушка-функция, а та же + * переменная, что и в резиденте: карта читает её напрямую. */ +uint8_t pop_palace; diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py index a37b138..1b203fc 100644 --- a/applications/PoP/toolchain/pop_pack_bg.py +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -88,6 +88,12 @@ CHOMPER_BLOOD_COLOR = None # ставится ниже, когда изве # (низкий modif → используются только нижние срезы 50-53); срезы более # открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно. GATE_ANIM_ENV_IDS = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 67} +# ВЕРХ ворот, который рисует тайл НАД ними (draw_tile_anim_topright, +# seg008:0568): маска 68 (mono, чёрным — «продавить» фон под створкой) и +# кадр door_fram_top[(modifier>>2) % 8] = 60..67 по открытости. Без них +# над воротами остаётся чёрный треугольник (видно у шва во дворце). +GATE_TOP_ENV_IDS = {60, 61, 62, 63, 64, 65, 66, 67} +GATE_TOP_MASK_ID = 68 # НАЖАТАЯ кнопка-closer (get_tile_to_draw seg008:2FE: при таймере связи >1 # tiles_6_closer рисуется как tiles_5_stuck). tile_table[5] = база 41 (уже @@ -459,6 +465,8 @@ def main(): | LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose | SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик | GATE_ANIM_ENV_IDS # + кадры открытия ворот + | GATE_TOP_ENV_IDS # + верх ворот (тайл сверху) + | {GATE_TOP_MASK_ID} # и его маска (mono) | TORCH_ENV_IDS # + основание факела | STUCK_ENV_IDS # + нажатая кнопка-closer | CHOMPER_BOT_IDS # + чомпер: низ @@ -479,7 +487,8 @@ def main(): for id in env_bg: w, h, pix = load_indexed(R.CHTAB_ENV, id, id in NO_TRANSP_ENV_IDS, CHOMPER_BLOOD_COLOR if id in CHOMPER_BLOOD_IDS - else None) + else (POT_MASK_COLOR if id == GATE_TOP_MASK_ID + else None)) pages.setdefault(id >> ENV_SHIFT, {})[id & ENV_MASK] = (w, h, pix) max_pg = max(pages) if pages else -1 env_page_names = []