PoP: факелы и зелья (chtab_1) + быстрый блит фона gfx_blit_noclip
Графика chtab_1 (flame/sword/potion) — новый атлас pop_pot.atl: - склянка зелья берётся из chtab_1 (seg008 draw_tile_fore: id 12 малая / 13 большая при типах 2..4), а НЕ chtab_6 id 12 — res212 в VDUNGEON нет, каскад падал на VPALACE и рисовал кусок дворцовой декорации («мусорный объект» в комнате 5); - пакуем chtab_1 целиком (id 1..23): пламя, склянки, кадры пузырька; - MONO-блит (method_3_blit_mono, seg009:3040) красит спрайт цветом из ОБЩЕЙ 16-цветной палитры экрана — она добавлена в 0x30..0x3F, палитра chtab_1 в 0x40..0x4F; пузырёк пакуется силуэтом цвета 12 (красный), его маска — цвета 0; - в env-атлас добавлено основание факела (env 146, seg008:489 — рисует правый сосед); в статический render_room оно не попадало. Анимация (seg007 animate_torch/animate_potion + seg000:0B12 anim_tile_modif): при входе в комнату факелам и зельям задаётся случайная стартовая фаза и заводится trob; факел — get_torch_frame, пламя в ячейке ПРАВОГО соседа (xh = draw_xh+1, y = draw_main_y−40); зелье — bubble_next_frame по младшим 3 битам модификатора (старшие 5 = тип, при загрузке уровня modif <<= 3, seg009). Пламя и пузырёк не запекаются в фон: heal своей области + кадр поверх. Скорость пламени /2 — наш логический кадр короче игрового тика оригинала. Скорость отрисовки (профиль в MAME, кадр = 430 080 тактов): - замер показал, что цена блита почти НЕ зависит от размера — 13 288 тактов на спрайт 32×3 против 4 617 у линейного спрайтового ядра без клипа; платим за проход gfx_blit → gfx_blit_part → _gfx_blit_full; - в libbgi добавлен gfx_blit_noclip() — блит без клипа в ТЕКУЩЕМ банке (putsprite не годится: навязывает GFX_BANK_SPRITE, фону нужен TRANSPARENT ради ОЗУ-копии); pop_bg.blit_b уходит на него, когда спрайт целиком на экране и не нужен g_clip_top → ~2.9× на блит; - W3-скобку ставит САМА libbgi: из модуля с --w3 её вызывать нельзя — после _bgi_begin окно W3 занято видеобанком и код вызывающего исчезает (проявлялось белым экраном); - редрой шва больше не перерисовывает полосу потолка (bar начинается с POP_YOFF+3) — это удваивало стоимость блока при анимации решётки; - docs/size_optimization_plan.md §7–§8: замеры, сделанное и запас (батчинг W3-скобки, решётка одним спрайтом, лишние блиты). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -289,3 +289,47 @@ Kid, обычно 2–4 за кадр) рисовал ещё и `bottom_id` —
|
||||
`redraw_frames_above`; сузить до колонок, реально задетых спрайтом.
|
||||
6. `wall_pattern` внутри fore/overlay — тяжёлая (PRNG + до 4 блитов); проверить,
|
||||
не зовём ли её там, где оригинал ограничивается `wall_fram_main`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Скорость отрисовки: замеры и запас (2026-07-27)
|
||||
|
||||
Профилирование в MAME (маркеры в порт 0xFE + `wpiset … totalcycles`, приём из
|
||||
memory `mame_mcp_bridge`). Кадр Sprinter = **430 080 тактов**.
|
||||
|
||||
**Стоимость блита почти НЕ зависит от размера** — платим за проход по цепочке
|
||||
`gfx_blit → gfx_blit_part → _gfx_blit_full` (16-битная арифметика, клип,
|
||||
пересчёт src, нарезка полос >256), а не за пиксели:
|
||||
|
||||
| путь (спрайт 32×3) | тактов |
|
||||
|---|---|
|
||||
| `gfx_blit` (общее ядро, с клипом) | 13 288 |
|
||||
| линейное спрайтовое ядро без клипа (`putsprite` при `gfx_sprite_clip(0)`) | 4 617 |
|
||||
|
||||
Отсюда `draw_tile(0,0)` тайла шва (9 блитов) стоил **183 690 тактов = 43 %
|
||||
кадра**; сам `bar` — только 13 308.
|
||||
|
||||
**СДЕЛАНО (шаг 1):** в libbgi добавлен `gfx_blit_noclip()`
|
||||
(`common/gfx_blit_noclip.c`, прототип в `include/gfx.h`) — блит без клипа в
|
||||
ТЕКУЩЕМ банке через линейное ядро; `pop_bg.blit_b` уходит на него, когда
|
||||
спрайт целиком на экране и не нужен `g_clip_top`. Выигрыш ~2.9× на каждом
|
||||
фоновом блите (подтверждено в MAME).
|
||||
**ВАЖНО:** W3-скобку (`_bgi_begin/_bgi_end`) ставит САМА libbgi — вызывать её
|
||||
из модуля, собранного с `--w3`, нельзя: после `_bgi_begin` окно W3 занято
|
||||
видеобанком и код вызывающего исчезает из адресного пространства (проверено:
|
||||
белый экран).
|
||||
|
||||
**ЗАПАС (шаг 2), когда перестанет хватать бюджета кадра:**
|
||||
|
||||
1. **Батчинг W3-скобки** — одна `_bgi_begin/_bgi_end` на весь `draw_tile`
|
||||
вместо скобки на блит; нужен публичный batch-API в libbgi (как у
|
||||
спрайтового движка). Осторожно: между begin/end стоит `DI` — длинная
|
||||
серия задержит кадровое прерывание.
|
||||
2. **Решётка ворот одним спрайтом** — `draw_gate_back` рисует бары по одному
|
||||
(`env 52`, до 7 блитов). Сгенерировать в атласе «столб решётки» (повтор
|
||||
бара на высоту тайла) и выводить одним `gfx_blit_part` с обрезкой по фазе
|
||||
`gate_bot_y & 7`: 7 блитов → 1.
|
||||
3. **Не перерисовывать статичные части шва** — грань ворот (env 47, 26×62),
|
||||
пол (41) и кромка (43) при анимации решётки не меняются; если стирать
|
||||
только полосу баров, уйдут ещё 3 блита из 9.
|
||||
4. См. также §7 (лишние блиты, которых нет в оригинале).
|
||||
|
||||
Binary file not shown.
@@ -27,6 +27,8 @@ static const char *const pop_env_atl[POP_ENV_PAGES] = {
|
||||
};
|
||||
#define POP_WALL_ATL "pop_wall.atl"
|
||||
#define POP_FORE_ATL "pop_fore.atl"
|
||||
#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья */
|
||||
#define POP_PAL_POT 0x40
|
||||
#define POP_BG_PAL "pop_bg.pal"
|
||||
|
||||
#endif
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,7 @@ EXAMPLE := roomtest
|
||||
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
|
||||
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
|
||||
MEMORY ?= small
|
||||
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c --w3 pop_map.c pop_bg.c
|
||||
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c pop_map.c --w3 pop_bg.c
|
||||
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c
|
||||
|
||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||
@@ -15,7 +15,8 @@ LVL_DIR := $(CURDIR)/../SDLPoP/data/LEVELS
|
||||
LVL_DATA := $(LVL_DIR)/res2001.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
|
||||
$(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl \
|
||||
$(BG_DIR)/pop_pot.atl
|
||||
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
|
||||
# не рисуются (kid_draw пропускает незагруженную страницу). Палитра —
|
||||
# ЕДИНАЯ игровая (env+wall+kid).
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
static atlas_t env[5];
|
||||
static atlas_t wall_at;
|
||||
static atlas_t fore_at;
|
||||
static atlas_t pot_at; /* chtab_1: зелья (склянка + пузырёк) */
|
||||
|
||||
static const char *const ENV_ATL[5] = {
|
||||
"pop_env0.atl", "pop_env1.atl", "pop_env2.atl",
|
||||
@@ -34,6 +35,7 @@ int pop_bg_load(void)
|
||||
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;
|
||||
if (atlas_load(&pot_at, "pop_pot.atl") != 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -44,6 +46,7 @@ void pop_bg_free(void)
|
||||
atlas_free(&env[i]);
|
||||
atlas_free(&wall_at);
|
||||
atlas_free(&fore_at);
|
||||
atlas_free(&pot_at);
|
||||
}
|
||||
|
||||
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра с screen_y <
|
||||
@@ -72,8 +75,16 @@ static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
int sy = g_clip_top - top; /* пропустить верхние строки */
|
||||
if (sy < (int)h)
|
||||
gfx_blit_part(x, g_clip_top, img, 0, sy, (int)w, (int)h - sy);
|
||||
} else if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
||||
x + (int)w <= 320 && top + (int)h <= 256) {
|
||||
/* Спрайт целиком на экране — линейное ядро без клипа: ~2.9×
|
||||
* быстрее (замер: 4617 против 13288 тактов на спрайт 32×3;
|
||||
* общее ядро gfx_blit платит за клип/16-бит/split, а не за
|
||||
* пиксели). W3-скобку ставит сама libbgi: из кода, собранного
|
||||
* с --w3, её вызывать нельзя (окно W3 уходит под видеобанк). */
|
||||
gfx_blit_noclip(x, top, img);
|
||||
} else {
|
||||
gfx_blit(x, top, img);
|
||||
gfx_blit(x, top, img); /* край экрана — с клипом */
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
@@ -115,6 +126,9 @@ void pop_fore_heal(void)
|
||||
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); }
|
||||
static void pot_b(uint8_t id, int x, int yb) { blit_b(&pot_at, id, x, yb); }
|
||||
|
||||
|
||||
|
||||
/* ---- Таблица тайлов (piece, seg008.c:27) --------------------------- */
|
||||
typedef struct {
|
||||
@@ -180,6 +194,40 @@ static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
||||
#define RES_WALL_MARK_TR 16
|
||||
#define RES_WALL_MARK_BR 17
|
||||
|
||||
/* ---- Зелье (tiles_10_potion, seg008 draw_tile_anim/draw_tile_fore) ----
|
||||
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
||||
* склянка (id 13) и pot_size=1, остальные — маленькая (id 12). Спрайты — из
|
||||
* chtab_1 (атлас pop_pot), поэтому НЕ из env: раньше мы рисовали chtab_6 id 12
|
||||
* (res212), которого в VDUNGEON нет — оттуда и «мусорный объект».
|
||||
* Позиции оригинала: склянка xh = fore_x + draw_xh, xl = 6, ybottom = fore_y +
|
||||
* draw_main_y; пузырёк/маска xh = draw_xh + 3, xl = 1,
|
||||
* ybottom = draw_main_y − (pot_size << 2) − 14. */
|
||||
#define POT_MASK_ID 23
|
||||
static const uint8_t POTION_FRAM_BUBB[8] = {0, 16, 17, 18, 19, 20, 21, 22};
|
||||
|
||||
static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3); }
|
||||
|
||||
static void potion_flask(int row, int col, uint8_t modif) /* draw_tile_fore */
|
||||
{
|
||||
uint8_t type = potion_type(modif);
|
||||
uint8_t id = 12;
|
||||
if (type == 0) return;
|
||||
if (type >= 2 && type < 5) id = 13; /* большая склянка */
|
||||
pot_b(id, (tile_table[10].fore_x + COL_XH[col]) * 8 + 6,
|
||||
tile_table[10].fore_y + 63 * row + 62);
|
||||
}
|
||||
|
||||
static void potion_bubble(int row, int col, uint8_t modif, uint8_t fore)
|
||||
{
|
||||
uint8_t type = potion_type(modif);
|
||||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||||
int x = (COL_XH[col] + 3) * 8 + 1;
|
||||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
||||
if (type == 0) return;
|
||||
if (fore) pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
||||
else pot_b(POT_MASK_ID, x, yb); /* маска (под ним) */
|
||||
}
|
||||
|
||||
/* ---- Текущая комната (globals для tile_code/mod) ------------------- */
|
||||
static const uint8_t *g_fg, *g_bg, *g_lfg, *g_lbg;
|
||||
static const uint8_t *g_below; /* row0-коды комнаты СНИЗУ (для topright под нижним рядом) */
|
||||
@@ -435,6 +483,9 @@ static void draw_tile(int row, int col)
|
||||
} else {
|
||||
if (lt->right_id)
|
||||
env_b(lt->right_id, x, lt->right_y + dmy);
|
||||
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
|
||||
if (lcode == 19 || lcode == 30)
|
||||
env_b(146, x, dby - 28);
|
||||
/* stripe — только палас, dungeon пропускает */
|
||||
}
|
||||
}
|
||||
@@ -452,6 +503,9 @@ static void draw_tile(int row, int col)
|
||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||
}
|
||||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
|
||||
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
|
||||
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
|
||||
}
|
||||
|
||||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||||
@@ -497,9 +551,13 @@ static void draw_tile(int row, int col)
|
||||
wall_b(WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
|
||||
wall_pattern(row, col, 1);
|
||||
}
|
||||
if (t->fore_id)
|
||||
if (code == 10) { /* зелье: chtab_1, не env fore_id.
|
||||
* Пузырёк — динамика (pop_potion_draw). */
|
||||
potion_flask(row, col, tile_mod(row, col));
|
||||
} else if (t->fore_id) {
|
||||
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Loose floors — динамическая перерисовка (seg008 draw_loose) ---- *
|
||||
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
|
||||
@@ -565,15 +623,13 @@ void pop_room_redraw_seam_left(void)
|
||||
{
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, POP_YOFF, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
|
||||
/* Полосу кладки У ПОТОЛКА (ряд −1) НЕ трогаем: её низ на room-space y=2,
|
||||
* а бары ворот начинаются с y=3 (gate_top_y = dby−62). Раньше bar шёл от
|
||||
* POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать ещё и
|
||||
* draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации решётки. */
|
||||
bar(0, POP_YOFF + 3, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
|
||||
/* Bar стёр и полосу кладки У ПОТОЛКА (ряд −1 = ряд 2 комнаты сверху,
|
||||
* draw_tile_aboveroom): без её перерисовки потолок над решёткой пропадал
|
||||
* после первой же анимации ворот. Клип — как в pop_room_draw. */
|
||||
g_clip_top = POP_YOFF;
|
||||
draw_tile(-1, 0);
|
||||
g_clip_top = 0;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
@@ -611,6 +667,41 @@ void pop_ceil_bake_empty(int col)
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
/* ---- Анимация факела и зелья (динамика, каждый кадр) ---------------- *
|
||||
* Оба кадра рисуются ПОВЕРХ статического фона и стираются heal'ом своей
|
||||
* области (фон запечён БЕЗ них). Факел: пламя chtab_1 id modifier+1 в
|
||||
* ячейке ПРАВОГО соседа (seg008:560: xh = draw_xh + 1, ybottom =
|
||||
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
||||
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
||||
* (pot_size<<2) − 14 (seg008:668). */
|
||||
void pop_torch_draw(int row, int col, uint8_t modif)
|
||||
{
|
||||
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1
|
||||
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
||||
* 146), которое тоже лежит в ячейке соседа. */
|
||||
int x = (col + 1 < 10 ? COL_XH[col + 1] : COL_XH[col] + 4) * 8 + 8;
|
||||
int yb = 63 * row + 62 - 40;
|
||||
if (modif >= 9) return;
|
||||
heal_off(x - 2, yb - 19, 22, 22);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pot_b((uint8_t)(modif + 1), x, yb);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
void pop_potion_draw(int row, int col, uint8_t modif)
|
||||
{
|
||||
uint8_t type = potion_type(modif);
|
||||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||||
int x = (COL_XH[col] + 3) * 8 + 1;
|
||||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
||||
if (type == 0) return;
|
||||
heal_off(x - 1, yb - 10, 8, 12);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
|
||||
pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
|
||||
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
||||
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
||||
@@ -917,9 +1008,12 @@ static void fore_only_tile(int row, int col)
|
||||
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
|
||||
env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
|
||||
}
|
||||
if (t->fore_id)
|
||||
if (code == 10) { /* зелье: склянка (пузырёк — динамика) */
|
||||
potion_flask(row, col, tile_mod(row, col));
|
||||
} else if (t->fore_id) {
|
||||
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
||||
}
|
||||
}
|
||||
|
||||
/* Полоса кладки У ПОТОЛКА поверх Kid — порт redraw_needed_above (seg008:0221).
|
||||
* Там draw_tile_bottom(1) и draw_loose(1): аргумент 1 = «добавить ещё и в
|
||||
|
||||
@@ -85,6 +85,12 @@ extern uint8_t pop_loose_landed;
|
||||
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
|
||||
void pop_spike_redraw(int row, int col);
|
||||
|
||||
/* Анимация факела/зелья (динамика каждый кадр; фон запечён без них):
|
||||
* modif — живой room_modif тайла. Для факела col — колонка САМОГО факела
|
||||
* (пламя рисуется в ячейке правого соседа). */
|
||||
void pop_torch_draw(int row, int col, uint8_t modif);
|
||||
void pop_potion_draw(int row, int col, uint8_t modif);
|
||||
|
||||
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
|
||||
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
|
||||
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#define TILE_CLOSER 0x06
|
||||
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
|
||||
#define TILE_OPENER 0x0F
|
||||
#define TILE_POTION 0x0A
|
||||
#define TILE_TORCH 0x13
|
||||
#define TILE_TORCH_DEBRIS 0x1E
|
||||
|
||||
/* ---- Персистентное per-room modifier-состояние -------------------- */
|
||||
static uint8_t room_modif[POP_NROOMS][POP_ROOMTILES];
|
||||
@@ -38,6 +41,17 @@ static uint8_t button_rest[POP_ROOMTILES];
|
||||
* расхождение было именно в безусловном редрое). */
|
||||
static uint8_t trob_drawn[POP_ROOMTILES];
|
||||
|
||||
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
|
||||
* выбора следующего кадра пламени. */
|
||||
static unsigned long trob_seed;
|
||||
|
||||
static uint16_t trob_prandom(uint16_t maxv)
|
||||
{
|
||||
trob_seed = trob_seed * 214013UL + 2531011UL;
|
||||
return (uint16_t)((uint16_t)(trob_seed >> 16) % (uint16_t)(maxv + 1));
|
||||
}
|
||||
|
||||
|
||||
void pop_trob_reset(void)
|
||||
{
|
||||
uint8_t i;
|
||||
@@ -50,7 +64,15 @@ uint8_t *pop_trob_modif(uint8_t room)
|
||||
{
|
||||
if (room < 1 || room > POP_NROOMS) return 0;
|
||||
if (!room_seen[room - 1]) {
|
||||
uint8_t i;
|
||||
pop_level_room_bg(room, room_modif[room - 1]);
|
||||
/* seg009 load_level: у ЗЕЛЬЯ модификатор сдвигается влево на 3 —
|
||||
* старшие биты = тип зелья, младшие 3 = фаза пузырька. */
|
||||
pop_level_access_begin();
|
||||
for (i = 0; i < POP_ROOMTILES; i++)
|
||||
if ((pop_level_tile_raw(room, i) & 0x1F) == TILE_POTION)
|
||||
room_modif[room - 1][i] = (uint8_t)(room_modif[room - 1][i] << 3);
|
||||
pop_level_access_end();
|
||||
room_seen[room - 1] = 1;
|
||||
}
|
||||
return room_modif[room - 1];
|
||||
@@ -81,11 +103,51 @@ void pop_trob_room_changed(void)
|
||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
||||
}
|
||||
|
||||
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
|
||||
* тайлов: факелам — случайная стартовая фаза пламени (start_anim_torch,
|
||||
* seg007:0847: modif = prandom(8)), зельям — случайная фаза пузырька
|
||||
* (start_anim_potion: младшие 3 бита = prandom(6)+1, старшие = тип). */
|
||||
void pop_trob_anim_room(uint8_t room)
|
||||
{
|
||||
uint8_t *mod = pop_trob_modif(room);
|
||||
uint8_t i, code;
|
||||
if (!mod) return;
|
||||
pop_level_access_begin();
|
||||
for (i = 0; i < POP_ROOMTILES; i++) {
|
||||
code = (uint8_t)(pop_level_tile_raw(room, i) & 0x1F);
|
||||
if (code == TILE_TORCH || code == TILE_TORCH_DEBRIS) {
|
||||
mod[i] = (uint8_t)trob_prandom(8);
|
||||
pop_add_trob(room, i, 1);
|
||||
} else if (code == TILE_POTION) {
|
||||
mod[i] = (uint8_t)((mod[i] & 0xF8) | (trob_prandom(6) + 1));
|
||||
pop_add_trob(room, i, 1);
|
||||
}
|
||||
}
|
||||
pop_level_access_end();
|
||||
}
|
||||
|
||||
/* animate_spike (seg007:04D3): автомат по modifier. *m — modifier тайла,
|
||||
* *type — тип trob (>=0 активен, <0 = удалить). Выдвиг: ++modif (1..4),
|
||||
* на 5 → 0x8F (полностью вышли, безвредны), на 9 → 0 + конец. Убирание
|
||||
* (флаг 0x80): --modif; когда 0x7F-часть обнулилась → modif=6 (начать
|
||||
* визуальный уход 6..8). 0xFF = disabled. */
|
||||
/* get_torch_frame (seg007:06CD): случайный кадр 0..8, иначе следующий. */
|
||||
static uint8_t get_torch_frame(uint8_t curr)
|
||||
{
|
||||
uint8_t next = (uint8_t)trob_prandom(255);
|
||||
if (next != curr && next < 9) return next;
|
||||
next = (uint8_t)(curr + 1);
|
||||
if (next >= 9) next = 0;
|
||||
return next;
|
||||
}
|
||||
|
||||
/* bubble_next_frame (seg007:0759): фазы пузырька 1..7 по кругу. */
|
||||
static uint8_t bubble_next_frame(uint8_t curr)
|
||||
{
|
||||
uint8_t next = (uint8_t)(curr + 1);
|
||||
return (next >= 8) ? 1 : next;
|
||||
}
|
||||
|
||||
static void animate_spike(uint8_t *m, int8_t *type)
|
||||
{
|
||||
uint8_t mod = *m;
|
||||
@@ -230,9 +292,12 @@ uint8_t pop_trob_room_active(uint8_t room)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t torch_tick; /* делитель скорости анимации пламени */
|
||||
|
||||
void pop_process_trobs(uint8_t cur_room)
|
||||
{
|
||||
uint8_t i, w = 0;
|
||||
torch_tick++;
|
||||
|
||||
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
|
||||
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
|
||||
@@ -286,12 +351,35 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
case TILE_GATE:
|
||||
animate_door(&mod[tp], &type);
|
||||
break;
|
||||
case TILE_TORCH:
|
||||
case TILE_TORCH_DEBRIS:
|
||||
/* animate_torch (seg007:03C1): кадр пламени; анимация вечная —
|
||||
* type остаётся 1, пока комната отрисована. Смена кадра — через
|
||||
* ЛОГИЧЕСКИЙ кадр (torch_tick): у нас кадр короче игрового тика
|
||||
* оригинала, и пламя «кипело» вдвое быстрее нужного. */
|
||||
if (room == cur_room && (torch_tick & 1) == 0)
|
||||
mod[tp] = get_torch_frame(mod[tp]);
|
||||
break;
|
||||
case TILE_POTION:
|
||||
/* animate_potion (seg007:03E9): крутим младшие 3 бита (фаза
|
||||
* пузырька), старшие 5 — тип зелья. */
|
||||
if (room == cur_room)
|
||||
mod[tp] = (uint8_t)((mod[tp] & 0xF8) | bubble_next_frame(mod[tp] & 7));
|
||||
break;
|
||||
default:
|
||||
type = -1;
|
||||
break;
|
||||
}
|
||||
trobs[i].type = type;
|
||||
|
||||
if (room == cur_room &&
|
||||
(code == TILE_TORCH || code == TILE_TORCH_DEBRIS || code == TILE_POTION)) {
|
||||
/* Пламя/пузырёк анимируются КАЖДЫЙ кадр и в фон не запечены —
|
||||
* рисуем поверх (сами делают heal своей области). */
|
||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||
if (code == TILE_POTION) pop_potion_draw(trow, tcol, mod[tp]);
|
||||
else pop_torch_draw(trow, tcol, mod[tp]);
|
||||
}
|
||||
if (room == cur_room &&
|
||||
(code == TILE_SPIKE || code == TILE_OPENER || code == TILE_CLOSER)) {
|
||||
/* Редрой ТОЛЬКО при изменении видимого состояния (redraw_11h из
|
||||
|
||||
@@ -58,4 +58,8 @@ uint8_t pop_trob_room_active(uint8_t room);
|
||||
* enter_room. */
|
||||
void pop_trob_room_changed(void);
|
||||
|
||||
/* anim_tile_modif (seg000:0B12): завести анимации тайлов комнаты (факелы,
|
||||
* зелья) со случайной стартовой фазой. Звать в enter_room. */
|
||||
void pop_trob_anim_room(uint8_t room);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -134,6 +134,7 @@ static void enter_room(uint8_t room)
|
||||
pop_map_set_room(room);
|
||||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||||
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||||
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||||
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
||||
* а не по свежему backtable. Фаза P0. */
|
||||
@@ -280,7 +281,7 @@ int main(void)
|
||||
io_border = 6; /* голубой */
|
||||
pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */
|
||||
/* слой фона — ДО Kid) */
|
||||
io_border = 2; /* синий */
|
||||
io_border = 4; /* синий */
|
||||
{ /* ворота соседа слева (room8) видны в col0 — */
|
||||
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
|
||||
if (lft) {
|
||||
@@ -297,7 +298,7 @@ int main(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
io_border = 4; /* зеленый */
|
||||
io_border = 2; /* зеленый */
|
||||
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
||||
dead_frames++;
|
||||
if (dead_frames > RESPAWN_SETTLE &&
|
||||
|
||||
@@ -71,6 +71,32 @@ SPIKE_ANIM_ENV_IDS = {128, 129, 130, 131, 132, # left
|
||||
# открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно.
|
||||
GATE_ANIM_ENV_IDS = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 67}
|
||||
|
||||
# ---- chtab_1 (flame/sword/potion), база ресурса 150, папка PRINCE ----
|
||||
# Зелье (seg008 draw_tile_fore/draw_tile_anim): склянка id 12 (малая) / 13
|
||||
# (большая, типы 2..4), кадры пузырька 16..22 (potion_fram_bubb) и его маска
|
||||
# 23. Пузырёк и маска рисуются MONO-блитом (одним цветом), поэтому пакуем их
|
||||
# силуэтами уже в нужном цвете: маска — чёрный env[0], пузырёк — env[12]
|
||||
# (красный, тип «лечение»). Цвета других типов (9 синий / 10 зелёный) —
|
||||
# TODO, когда появятся такие зелья.
|
||||
POT_DIR_NAME = "PRINCE"
|
||||
POT_FLAME_IDS = tuple(range(1, 10)) # пламя факела: modifier+1 (seg008:560)
|
||||
POT_FLASK_IDS = (12, 13)
|
||||
POT_BUBBLE_IDS = (16, 17, 18, 19, 20, 21, 22)
|
||||
POT_MASK_ID = 23
|
||||
POT_PAL_BASE = 0x40 # 16 цветов chtab_1 -> слоты 0x40..0x4F
|
||||
# MONO-блит (method_3_blit_mono, seg009:3040) красит спрайт цветом из ОБЩЕЙ
|
||||
# 16-цветной палитры экрана, а не из палитры chtab. Кладём её в 0x30..0x3F.
|
||||
VGA16_PAL_BASE = 0x30
|
||||
VGA16 = [(0,0,0),(0,0,42),(0,42,0),(0,42,42),(42,0,0),(42,0,42),(42,21,0),
|
||||
(42,42,42),(21,21,21),(21,21,63),(21,63,21),(21,63,63),(63,21,21),
|
||||
(63,21,63),(63,63,21),(63,63,63)] # 6-битные каналы, как в .pal
|
||||
POT_MASK_COLOR = VGA16_PAL_BASE + 0 # чёрный (blitters_40h_mono без цвета)
|
||||
POT_BUBBLE_COLOR = VGA16_PAL_BASE + 12 # ярко-красный (color 12: «лечение»)
|
||||
|
||||
# Основание факела (seg008:489 draw_tile_right: при левом соседе-факеле
|
||||
# рисуется chtab_6 id 146). В статический render_room не попадает.
|
||||
TORCH_ENV_IDS = {146}
|
||||
|
||||
ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31
|
||||
ENV_MASK = (1 << ENV_SHIFT) - 1
|
||||
|
||||
@@ -139,6 +165,25 @@ def load_indexed(chtab, id):
|
||||
return im.width, im.height, out
|
||||
|
||||
|
||||
def load_potion(id, mono=None):
|
||||
"""chtab_1: res(150+id).png из data/PRINCE. mono=None — обычный спрайт
|
||||
(палитра chtab_1 -> слоты POT_PAL_BASE+i); mono=цвет — силуэт этим цветом
|
||||
(порт blitters_40h_mono)."""
|
||||
path = os.path.join(os.path.dirname(R.VDUNGEON_DIR), POT_DIR_NAME,
|
||||
f"res{150 + id}.png")
|
||||
if not os.path.exists(path):
|
||||
die(f"нет файла {path}")
|
||||
im = Image.open(path)
|
||||
if im.mode != "P":
|
||||
die(f"res{150+id}: mode {im.mode}, ожидался P")
|
||||
src = im.tobytes()
|
||||
if mono is None:
|
||||
out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src)
|
||||
else:
|
||||
out = bytes((0xFF if v == 0 else mono) 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
|
||||
@@ -206,6 +251,8 @@ def build_palette(out_path):
|
||||
|
||||
env = read_pal("res200.pal", R.VDUNGEON_DIR)
|
||||
wall = read_pal("res360.pal", R.VDUNGEON_DIR)
|
||||
pot = read_pal("res150.pal", os.path.join(os.path.dirname(R.VDUNGEON_DIR),
|
||||
POT_DIR_NAME))
|
||||
pal = bytearray(256 * 4)
|
||||
for i, (r, g, b) in enumerate(env):
|
||||
o = (0x50 + i) * 4
|
||||
@@ -213,6 +260,12 @@ def build_palette(out_path):
|
||||
for i, (r, g, b) in enumerate(wall):
|
||||
o = (0x60 + i) * 4
|
||||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||||
for i, (r, g, b) in enumerate(pot):
|
||||
o = (POT_PAL_BASE + i) * 4
|
||||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||||
for i, (r, g, b) in enumerate(VGA16):
|
||||
o = (VGA16_PAL_BASE + i) * 4
|
||||
pal[o:o + 4] = bytes((scale6to8(b), scale6to8(g), scale6to8(r), 0))
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(pal)
|
||||
|
||||
@@ -249,6 +302,8 @@ def write_header(out_path, env_pages, wall_name, fore_name):
|
||||
"};",
|
||||
'#define POP_WALL_ATL "%s"' % wall_name,
|
||||
'#define POP_FORE_ATL "%s"' % fore_name,
|
||||
'#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья */',
|
||||
"#define POP_PAL_POT 0x%02X" % POT_PAL_BASE,
|
||||
'#define POP_BG_PAL "pop_bg.pal"',
|
||||
"",
|
||||
"#endif",
|
||||
@@ -265,7 +320,8 @@ def main():
|
||||
| CLIMB_OVERLAY_ENV_IDS # + пол-оверлей подъёма
|
||||
| LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose
|
||||
| SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик
|
||||
| GATE_ANIM_ENV_IDS) # + кадры открытия ворот
|
||||
| GATE_ANIM_ENV_IDS # + кадры открытия ворот
|
||||
| TORCH_ENV_IDS) # + основание факела
|
||||
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]
|
||||
@@ -302,6 +358,22 @@ def main():
|
||||
total_bytes += sz
|
||||
print(f" pop_fore.atl: {len(env_fore)} спрайтов, каталог {cnt}, {sz} Б")
|
||||
|
||||
# POTION (chtab_1) -> idx=id: склянки + кадры пузырька + маска
|
||||
# chtab_1 целиком (res151..res173 = id 1..23): пламя факелов, меч-предмет,
|
||||
# склянки зелий. Пузырьки и их маска рисуются MONO-блитом — пакуем
|
||||
# силуэтами в нужном цвете (см. POT_*_COLOR).
|
||||
pot_entries = {}
|
||||
for id in range(1, 24):
|
||||
if id in POT_BUBBLE_IDS:
|
||||
pot_entries[id] = load_potion(id, POT_BUBBLE_COLOR)
|
||||
elif id == POT_MASK_ID:
|
||||
pot_entries[id] = load_potion(id, POT_MASK_COLOR)
|
||||
else:
|
||||
pot_entries[id] = load_potion(id)
|
||||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries)
|
||||
total_bytes += sz
|
||||
print(f" pop_pot.atl (chtab_1: факелы+зелья): {len(pot_entries)} спрайтов, каталог {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")
|
||||
|
||||
@@ -69,14 +69,21 @@ def _read16(path):
|
||||
|
||||
def build_palette(out_path):
|
||||
"""ЕДИНАЯ игровая палитра (gfx_pal_fload пишет с индекса 0, перезатирая
|
||||
всё — bg и Kid обязаны жить в одном файле): env@0x50, wall@0x60, kid@0x70.
|
||||
всё — bg и Kid обязаны жить в одном файле): chtab_1@0x40 (пламя/зелья),
|
||||
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
|
||||
(B.POT_PAL_BASE, os.path.join(os.path.dirname(B.R.VDUNGEON_DIR),
|
||||
B.POT_DIR_NAME, "res150.pal")), # chtab_1 0x40
|
||||
]
|
||||
# 16 базовых цветов экрана (mono-блит красит ими: пламя, пузырьки зелья)
|
||||
for i, (r, g, bl) in enumerate(B.VGA16):
|
||||
o = (B.VGA16_PAL_BASE + i) * 4
|
||||
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
|
||||
for base, path in groups:
|
||||
for i, (r, g, bl) in enumerate(_read16(path)):
|
||||
o = (base + i) * 4
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* gfx_blit_noclip — блит ЦЕЛОЙ картинки getimage-формата БЕЗ клипа, в
|
||||
* ТЕКУЩЕМ банке (gfx_set_bank). Быстрый вариант gfx_blit для тайловых
|
||||
* движков: вызывающий гарантирует, что картинка целиком на экране.
|
||||
*
|
||||
* Зачем отдельная функция: gfx_blit идёт через общее ядро _gfx_blit_full
|
||||
* (16-битная арифметика, клип, пересчёт src, нарезка полос >256) — замер
|
||||
* в MAME (PoP roomtest, 2026-07-27): 13 288 тактов на блит спрайта 32×3,
|
||||
* т.е. цена почти не зависит от размера, платим за проход по цепочке.
|
||||
* Линейное спрайтовое ядро на том же спрайте — 4 617 тактов (в 2.9 раза
|
||||
* быстрее): 8-битные w/h, без split, без клип-кода.
|
||||
*
|
||||
* От putsprite отличается тем, что НЕ навязывает GFX_BANK_SPRITE: фон
|
||||
* рисуется в GFX_BANK_TRANSPARENT (0xFF не пишется, но ОЗУ-копия
|
||||
* обновляется — иначе gfx_heal сотрёт нарисованное).
|
||||
*
|
||||
* W3-скобка ставится ЗДЕСЬ (код libbgi живёт в W1): вызывать её из
|
||||
* приложения, собранного с --w3, нельзя — после _bgi_begin окно W3
|
||||
* занято видеобанком и код вызывающего исчезает из адресного
|
||||
* пространства (проверено: белый экран).
|
||||
*
|
||||
* Ограничения: w,h ≤ 255; картинка целиком в пределах экрана. Не
|
||||
* подходит — зовите gfx_blit/gfx_blit_part.
|
||||
*/
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_blit_noclip(int x, int y, const void *img)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
|
||||
_bgi_begin();
|
||||
_gfx_blit_sprite_noclip(x, y, p + 4, (uint16_t)(p[0] | (p[1] << 8)),
|
||||
p[0], p[2]);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -87,6 +87,13 @@ inline uint8_t gfx_get_sprite_clip(void) { return _gfx_sprite_clip; }
|
||||
/* Блит целой картинки левым-верхним углом в (x,y). */
|
||||
void gfx_blit(int x, int y, const void *img);
|
||||
|
||||
/* Быстрый блит БЕЗ клипа в ТЕКУЩЕМ банке: картинка обязана быть целиком на
|
||||
* экране, w/h ≤ 255. Идёт линейным спрайтовым ядром — ~2.9× быстрее
|
||||
* gfx_blit (замер PoP roomtest 2026-07-27: 4617 против 13288 тактов на
|
||||
* спрайт 32×3; общее ядро тратит время на клип/16-бит/split, а не на
|
||||
* пиксели). Для тайловых движков; где нужен клип — gfx_blit/gfx_blit_part. */
|
||||
void gfx_blit_noclip(int x, int y, const void *img);
|
||||
|
||||
/* Блит под-прямоугольника (sx,sy,w,h) картинки (атлас кадров:
|
||||
* кадр N ленты — sx = N*FRAME_W). Под-прямоугольник обязан лежать
|
||||
* внутри img; по экрану — клиппится. */
|
||||
|
||||
Reference in New Issue
Block a user