Шаг D: паласные ветки отрисовки + потерянный верх ворот

Порт семи мест 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 12:04:49 +03:00
parent 89400ff1d4
commit 0941ef1d90
11 changed files with 101 additions and 11 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -2068,6 +2068,10 @@ void pop_loose_tick(void) __banked
* — кусок улетает из комнаты вниз, на месте остаётся пусто * — кусок улетает из комнаты вниз, на месте остаётся пусто
* (стена-вниз от тайла снизу + чёрный фон). */ * (стена-вниз от тайла снизу + чёрный фон). */
g_fg[pos] = TILE_EMPTY; 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_loose_modif[pos] = 0;
pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */ pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
pop_loose_mob_spawn(row, col); /* отрыв падающего куска */ pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
+67 -9
View File
@@ -91,6 +91,7 @@ int pop_bg_load(uint8_t set) __banked
if (bg_set == 0xFF && /* chtab_1 от набора не зависит */ if (bg_set == 0xFF && /* chtab_1 от набора не зависит */
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1; atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
bg_set = set; bg_set = set;
pop_palace = (uint8_t)(set == 1);
/* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от /* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от
* прежнего набора и картинка хотя бы не разъезжается. */ * прежнего набора и картинка хотя бы не разъезжается. */
return bg_load_tile_pal(pop_tile_pal[set]); 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). */ /* 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 (tt == 20) { pop_wall_b(2, x, dby); return; }
if (pop_tile_table[tt].topright_id) if (pop_tile_table[tt].topright_id)
pop_env_b(pop_tile_table[tt].topright_id, x, dby); 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; int dmy = dby - 3;
const piece *t = &pop_tile_table[code]; const piece *t = &pop_tile_table[code];
const piece *lt = &pop_tile_table[lcode]; const piece *lt = &pop_tile_table[lcode];
uint8_t rbl_code; uint8_t rbl_code, rbl_mod;
uint8_t wmod; uint8_t wmod;
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0 /* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
@@ -250,11 +275,28 @@ static void draw_tile(int row, int col)
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */ * load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20; 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_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 --- */ /* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
if (code == 0 || code == 9 || code == 12 || code == 26) { 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 /* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет * (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
* floor_right: lt->floor_right. */ * floor_right: lt->floor_right. */
@@ -263,6 +305,10 @@ static void draw_tile(int row, int col)
/* --- draw_tile_right (seg008.c:456) --- */ /* --- draw_tile_right (seg008.c:456) --- */
if (code != 20) { if (code != 20) {
if (lcode == 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); /* грань стены */ pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */
} else if (lcode == 0) { } else if (lcode == 0) {
if (lmod <= 3 && BLUELINE_FRAM1[lmod]) if (lmod <= 3 && BLUELINE_FRAM1[lmod])
@@ -271,15 +317,26 @@ static void draw_tile(int row, int col)
uint8_t num; uint8_t num;
pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */ pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */
num = (lmod <= 3) ? lmod : 0; 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); /* силуэт кладки */ 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 { } else {
if (lt->right_id) if (lt->right_id)
pop_env_b(lt->right_id, x, lt->right_y + dmy); 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): рисует ПРАВЫЙ сосед. */ /* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
if (lcode == 19 || lcode == 30) if (lcode == 19 || lcode == 30)
pop_env_b(146, x, dby - 28); 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; uint8_t base_id = t->base_id;
if (code == 11) /* loose: левая грань (анимир.) */ if (code == 11) /* loose: левая грань (анимир.) */
base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])]; base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
else if (code == 0x0F && lcode == 0) /* opener, слева пусто */ else if (code == 0x0F && lcode == 0 && !PALACE) /* opener, слева пусто */
base_id = 148; /* левая половина без пола */ base_id = 148; /* левая половина без пола — только */
/* подземелье (seg008:628) */
if (base_id) if (base_id)
pop_env_b(base_id, x, t->base_y + dmy); pop_env_b(base_id, x, t->base_y + dmy);
} }
+8
View File
@@ -19,6 +19,13 @@
/* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */ /* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */
atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at; 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_fg, *pop_t_bg, *pop_t_lfg, *pop_t_lbg;
const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod; const uint8_t *pop_t_below, *pop_t_above, *pop_t_abovemod;
uint8_t pop_t_room; uint8_t pop_t_room;
@@ -337,6 +344,7 @@ void pop_potion_flask(int row, int col, uint8_t modif)
uint8_t id = 12; uint8_t id = 12;
if (type == 0) return; if (type == 0) return;
if (type >= 2 && type < 5) id = 13; /* большая склянка */ 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, pop_pot_b(id, (POT_FORE_X + POP_COL_XH[col]) * 8 + 6,
POT_FORE_Y + 63 * row + 62); POT_FORE_Y + 63 * row + 62);
} }
+5
View File
@@ -39,6 +39,11 @@
#include "pop_bg_atlas.h" #include "pop_bg_atlas.h"
extern atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at; 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 — правый столбец ЛЕВОГО * fg — foretable-коды, bg — модификаторы; lfg/lbg — правый столбец ЛЕВОГО
* соседа (кромка); below — ряд 0 комнаты снизу (11 байт, см. pop_bg.h); * соседа (кромка); below — ряд 0 комнаты снизу (11 байт, см. pop_bg.h);
@@ -18,7 +18,8 @@ DATA_LOC := 0xA000
# sprinter: под тестом эти функции заглушены (stubs.c), но объявления # sprinter: под тестом эти функции заглушены (stubs.c), но объявления
# берутся настоящие — иначе разъедутся сигнатуры. # берутся настоящие — иначе разъедутся сигнатуры.
PROJ_ROOT := $(abspath $(TESTKIT)/..) 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 OBJS_geom := build/eng_pop_geom.rel
@@ -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, 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, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13, 0, 1,
}; };
/* Тайлсет (0 подземелье, 1 дворец) — в приложении его ставит pop_bg_load;
* тесты гоняют физику подземелья, поэтому 0. Не заглушка-функция, а та же
* переменная, что и в резиденте: карта читает её напрямую. */
uint8_t pop_palace;
+10 -1
View File
@@ -88,6 +88,12 @@ CHOMPER_BLOOD_COLOR = None # ставится ниже, когда изве
# (низкий modif → используются только нижние срезы 50-53); срезы более # (низкий modif → используются только нижние срезы 50-53); срезы более
# открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно. # открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно.
GATE_ANIM_ENV_IDS = {50, 51, 52, 53, 54, 55, 56, 57, 58, 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 # НАЖАТАЯ кнопка-closer (get_tile_to_draw seg008:2FE: при таймере связи >1
# tiles_6_closer рисуется как tiles_5_stuck). tile_table[5] = база 41 (уже # tiles_6_closer рисуется как tiles_5_stuck). tile_table[5] = база 41 (уже
@@ -459,6 +465,8 @@ def main():
| LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose | LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose
| SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик | SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик
| GATE_ANIM_ENV_IDS # + кадры открытия ворот | GATE_ANIM_ENV_IDS # + кадры открытия ворот
| GATE_TOP_ENV_IDS # + верх ворот (тайл сверху)
| {GATE_TOP_MASK_ID} # и его маска (mono)
| TORCH_ENV_IDS # + основание факела | TORCH_ENV_IDS # + основание факела
| STUCK_ENV_IDS # + нажатая кнопка-closer | STUCK_ENV_IDS # + нажатая кнопка-closer
| CHOMPER_BOT_IDS # + чомпер: низ | CHOMPER_BOT_IDS # + чомпер: низ
@@ -479,7 +487,8 @@ def main():
for id in env_bg: for id in env_bg:
w, h, pix = load_indexed(R.CHTAB_ENV, id, id in NO_TRANSP_ENV_IDS, w, h, pix = load_indexed(R.CHTAB_ENV, id, id in NO_TRANSP_ENV_IDS,
CHOMPER_BLOOD_COLOR if id in CHOMPER_BLOOD_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) pages.setdefault(id >> ENV_SHIFT, {})[id & ENV_MASK] = (w, h, pix)
max_pg = max(pages) if pages else -1 max_pg = max(pages) if pages else -1
env_page_names = [] env_page_names = []