Уровень 4: второй тайлсет (дворец) — ассеты и переключение (шаги A и B)

Порт tbl_envir_ki[tbl_level_type[level]] (seg000:1108): оригинал под один и
тот же набор id грузит РАЗНЫЙ .DAT — VDUNGEON или VPALACE.

Упаковщик (toolchain/pop_pack_bg.py):
- аргумент набора: `pop_pack_bg.py dungeon|palace`.  Каскад каталогов —
  сначала свой набор, потом чужой фолбэком (в распакованном data/ res230/
  231/348 есть только в VDUNGEON, два десятка — только в VPALACE).
- ОБА набора пакуются по одному объединению id, поэтому раскладка
  id -> (страница, idx) общая и заголовок один: коду достаточно подменить
  имена файлов.
- PALACE_ENV_IDS: 78/80/82 (doortop_fram_bot), 81/83 (doortop_fram_top),
  84 (полоска стены), 145 (stripe_id) — их рисует только палас, render_room
  про них не знает.
- ENV_SHIFT 5 -> 4: с паласными кусками страница 2 переваливала за 16 КБ
  (16 996).  Цена — 10 страниц EMM на набор вместо 5, при 215 свободных.
- *tile.pal: 32 записи (env 0x50..0x5F + wall 0x60..0x6F) на набор.  Полная
  kid.pal не трогается — Кид, страж, меч и зелья в других слотах.

Движок:
- pop_level_type() (tbl_level_type, SDLPoP data.h:840): дворцовые уровни
  4, 5, 6, 10, 11, 14.  Живёт в pop_level.c, потому что по типу расходятся
  не только атласы, но и ветки отрисовки seg008, кладка стены и модификатор
  пустой клетки от упавшей плиты (remove_loose, seg007:0EB8).
- pop_bg_load(set): no-op при том же наборе, при смене выгружает старый
  (иначе текут 12 EMM-страниц) и правит 32 записи палитры в ОБЕ страницы
  дабл-буфера.  Зовётся на старте и на границе уровня, не в кадре.
- Путь к атласу склеивается на месте (bg_path): двадцать строк-имён в банке
  — лишние полкилобайта.

Проверено в MAME: уровень 1 (подземелье) рисуется как прежде; уровень 4
(-DFIRST_LEVEL=4) — дворцовые арки, окна, пол, решётчатая дверь уровня,
Кид не перекрашен.  Стены пока чёрные: паласный wall_pattern — шаг C.

tests-host: 5/5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 11:28:45 +03:00
parent c8fe0bd37a
commit 97929b55d3
34 changed files with 250 additions and 65 deletions
+12 -5
View File
@@ -35,7 +35,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
ALLOCS ?= 3000
EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
@@ -52,9 +52,16 @@ LVL_DIR := $(CURDIR)/../SDLPoP/data/LEVELS
# диске. res2000 — демо-уровень, в скоуп не входит.
LVL_NUMS := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
LVL_DATA := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin)
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 \
# ДВА тайлсета: pop_* — подземелье (уровни 1-3, 7-9, 12, 13, 15),
# pal_* — дворец (4-6, 10, 11, 14). Раскладка id у них общая, различаются
# файлы и палитра тайлсета (*tile.pal, 32 записи). chtab_1 (pop_pot.atl:
# пламя, меч-предмет, зелья) от набора не зависит — один на оба.
BG_SETS := pop pal
BG_PAGES := 0 1 2 3 4 5 6 7 8 9
BG_DATA := $(foreach s,$(BG_SETS),\
$(foreach n,$(BG_PAGES),$(BG_DIR)/$(s)_env$(n).atl) \
$(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl \
$(BG_DIR)/$(s)_tile.pal) \
$(BG_DIR)/pop_pot.atl
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
@@ -93,7 +100,7 @@ hdd: $(EXAMPLE).exe
TC := $(PROJ_ROOT)/applications/PoP/toolchain
$(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py
cd $(TC) && python3 pop_pack_bg.py
cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace
$(GUARD_DATA): $(TC)/pop_pack_guard.py
cd $(TC) && python3 pop_pack_guard.py
+1 -1
View File
@@ -34,7 +34,7 @@
/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load
* трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ
* после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */
int pop_bg_load(void) __banked;
int pop_bg_load(uint8_t set) __banked; /* 0 подземелье, 1 дворец */
/* Освободить EMM-страницы атласов. */
void pop_bg_free(void) __banked;
+13
View File
@@ -450,6 +450,19 @@ uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
return (uint8_t)(t < 30);
}
/* tbl_level_type (SDLPoP data.h:840). Держим ЗДЕСЬ, а не в слое фона:
* тип уровня нужен не только атласам — по нему расходятся ветки отрисовки
* (seg008), кладка стены (wall_pattern) и модификатор пустой клетки от
* упавшей плиты (remove_loose, seg007:0EB8). */
static const uint8_t LEVEL_TYPE[16] = {
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0
};
uint8_t pop_level_type(void)
{
return LEVEL_TYPE[pop_current_level & 15];
}
uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); }
uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); }
int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); }
+7
View File
@@ -135,6 +135,13 @@ uint8_t pop_guard_state_x(uint8_t room);
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
uint8_t *color, uint8_t *skill);
/* tbl_level_type (SDLPoP data.h:840) — какой тайлсет у уровня:
* 0 подземелье, 1 дворец. Дворцовые: 4, 5, 6, 10, 11, 14. Индекс —
* НОМЕР уровня; элемент 0 — демо-уровень. Оригинал по нему выбирает и
* .DAT окружения (tbl_envir_ki, seg000:1108), и десяток веток отрисовки
* в seg008, и графику стража (GUARD1/GUARD2). */
uint8_t pop_level_type(void);
uint8_t pop_level_start_room(void);
uint8_t pop_level_start_pos(void);
int8_t pop_level_start_dir(void);
+71 -15
View File
@@ -8,6 +8,9 @@
*
* Только DUNGEON-стиль (Фаза 1).
*/
/* Имена файлов атласов обоих тайлсетов нужны только здесь; макрос обязан
* стоять ДО первого включения pop_bg_atlas.h (его тянет pop_tile.h). */
#define POP_BG_ATLAS_NAMES
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
@@ -22,34 +25,87 @@
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
#include <fcntl.h>
#include <unistd.h>
static const char *const ENV_ATL[5] = {
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон,
* KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска
* зарастает десятками .atl. */
"BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl",
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
};
/* ---- Тайлсет: подземелье / дворец ----------------------------------- *
* Порт tbl_envir_ki[tbl_level_type[level]] (seg000:1108): оригинал под один
* и тот же набор id грузит РАЗНЫЙ .DAT — VDUNGEON или VPALACE. У нас так
* же: раскладка id -> (страница, idx) общая (упаковщик печёт оба набора по
* одному объединению id), меняются только файлы атласов и 32 записи
* палитры — env 0x50..0x5F и wall 0x60..0x6F. Кид, страж, меч и зелья
* живут в других слотах, поэтому общая kid.pal не трогается. */
static uint8_t bg_set = 0xFF; /* какой набор сейчас загружен */
int pop_bg_load(void) __banked
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон, KID —
* персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска зарастает
* десятками .atl. Путь склеиваем на месте: держать по строке на каждый из
* двадцати атласов — лишние полкилобайта в банке. */
static char bg_path_buf[20];
static const char *bg_path(const char *name)
{
char *d = bg_path_buf;
*d++ = 'B'; *d++ = 'G'; *d++ = '\\';
while (*name) *d++ = *name++;
*d = 0;
return bg_path_buf;
}
/* 32 записи (env, затем wall) в ОБЕ палитры: у страниц дабл-буфера они
* раздельные. Буфер — на стеке: gfx_pal_load отдаёт указатель BIOS ($A4),
* а тот читает только #4000-#BFFF, то есть банковую rodata бы не увидел
* (те же грабли, что в pop_guard_set_palette). */
static int bg_load_tile_pal(const char *name)
{
uint8_t buf[64];
int fd = open(bg_path(name), O_RDONLY);
if (fd < 0) return -1;
if (read(fd, buf, 64) != 64) { close(fd); return -1; }
gfx_pal_load(0, POP_PAL_ENV, 16, buf);
gfx_pal_load(1, POP_PAL_ENV, 16, buf);
if (read(fd, buf, 64) != 64) { close(fd); return -1; }
gfx_pal_load(0, POP_PAL_WALL, 16, buf);
gfx_pal_load(1, POP_PAL_WALL, 16, buf);
close(fd);
return 0;
}
/* Загрузить набор set (0 подземелье, 1 дворец). Повторный вызов с тем же
* набором ничего не делает; со сменой — сначала выгружает старый, иначе
* EMM-страницы утекут (их по 12 на набор). */
int pop_bg_load(uint8_t set) __banked
{
int i;
for (i = 0; i < 5; i++)
if (atlas_load(&pop_env[i], ENV_ATL[i]) != 0) return -1;
if (atlas_load(&pop_wall_at, "BG\\pop_wall.atl") != 0) return -1;
if (atlas_load(&pop_fore_at, "BG\\pop_fore.atl") != 0) return -1;
if (atlas_load(&pop_pot_at, "BG\\pop_pot.atl") != 0) return -1;
return 0;
if (set >= POP_TILESETS) return -1;
if (set == bg_set) return 0;
if (bg_set != 0xFF) { /* смена набора — освободить старый */
for (i = 0; i < POP_ENV_PAGES; i++) atlas_free(&pop_env[i]);
atlas_free(&pop_wall_at);
atlas_free(&pop_fore_at);
}
for (i = 0; i < POP_ENV_PAGES; i++)
if (atlas_load(&pop_env[i], bg_path(pop_env_atl[set][i])) != 0) return -1;
if (atlas_load(&pop_wall_at, bg_path(pop_wall_atl[set])) != 0) return -1;
if (atlas_load(&pop_fore_at, bg_path(pop_fore_atl[set])) != 0) return -1;
if (bg_set == 0xFF && /* chtab_1 от набора не зависит */
atlas_load(&pop_pot_at, bg_path(POP_POT_ATL)) != 0) return -1;
bg_set = set;
/* Палитра — ПОСЛЕ атласов: если атлас не загрузился, цвета остаются от
* прежнего набора и картинка хотя бы не разъезжается. */
return bg_load_tile_pal(pop_tile_pal[set]);
}
void pop_bg_free(void) __banked
{
int i;
for (i = 0; i < 5; i++)
if (bg_set == 0xFF) return;
for (i = 0; i < POP_ENV_PAGES; i++)
atlas_free(&pop_env[i]);
atlas_free(&pop_wall_at);
atlas_free(&pop_fore_at);
atlas_free(&pop_pot_at);
bg_set = 0xFF;
}
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
+2 -2
View File
@@ -17,7 +17,7 @@
#include "_pop_draw.h" /* pop_heal_fast */
/* ---- Состояние (писучее, _DATA/W2 — видно из любого банка) ---------- */
atlas_t pop_env[5], 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;
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;
@@ -262,7 +262,7 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
gfx_w0_unmap();
}
void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> 5], (uint8_t)(id & 31), x, yb); }
void pop_env_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_env[id >> POP_ENV_SHIFT], (uint8_t)(id & POP_ENV_MASK), x, yb); }
void pop_wall_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_wall_at, id, x, yb); }
void pop_fore_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_fore_at, id, x, yb); }
void pop_pot_b(uint8_t id, int x, int yb) { pop_blit_b(&pop_pot_at, id, x, yb); }
+7 -3
View File
@@ -31,9 +31,13 @@
#include "pop_geom.h" /* pop_rnd_t — сид кладки комнаты */
/* ---- Атласы фона (прямая адресация id) ------------------------------ *
* env[id>>5] idx id&31 — chtab_6 environment; wall — chtab_7; fore —
* передний слой; pot — chtab_1 (зелья, меч-предмет). */
extern atlas_t pop_env[5], pop_wall_at, pop_fore_at, pop_pot_at;
* env[id>>POP_ENV_SHIFT] idx id&POP_ENV_MASK — chtab_6 environment;
* wall — chtab_7; fore — передний слой; pot — chtab_1 (зелья, меч).
* Раскладка и число страниц — из pop_bg_atlas.h (генерит упаковщик).
* Тайлсетов ДВА (подземелье/дворец), но раскладка id у них общая:
* подменяются только файлы и 32 записи палитры (см. pop_bg_set_tileset). */
#include "pop_bg_atlas.h"
extern atlas_t pop_env[POP_ENV_PAGES], pop_wall_at, pop_fore_at, pop_pot_at;
/* ---- Текущая отрисованная комната ----------------------------------- *
* fg — foretable-коды, bg — модификаторы; lfg/lbg — правый столбец ЛЕВОГО
+13 -1
View File
@@ -479,7 +479,11 @@ int main(void)
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0;
uint8_t xdec_prev = 0, xinc_prev = 0;
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
/* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph
* (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан,
* поэтому тип берём напрямую от номера. */
pop_current_level = FIRST_LEVEL;
if (pop_bg_load(pop_level_type()) != 0) {
puts("pop_bg_load failed");
return 1;
}
@@ -757,6 +761,14 @@ int main(void)
* пределы, и персонаж просто не виден. */
pop_guard_free();
pop_guard_load();
/* Тайлсет: подземелье <-> дворец (порт tbl_envir_ki,
* seg000:1108). Внутри — no-op, если тип не менялся;
* при смене выгружает старые атласы и правит 32 записи
* палитры. Только на границе уровня, не в кадре. */
if (pop_bg_load(pop_level_type()) != 0) {
puts("bg set load failed");
return 1;
}
/* Всё состояние прошлого уровня сносит сам pop_start_level:
* pop_trob_reset обнуляет room_seen (модификаторы комнат
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают