Зеркальные кадры персонажей — готовыми файлами, а не разворотом в рантайме
Замеры в MAME (такты Z80, кадр = 430 000) решили вопрос однозначно: загрузка 28 атласов Кида с HDD — 27 160 092 такта (1.26 с), 970 003 на страницу; разворот ОДНОЙ страницы на месте — 12 645 582 такта (0.59 с), т.е. 771 такт/байт. Даже идеальный вариант (копия accel'ом ~0.25 кадра + asm-реверс ~2.2 кадра) дал бы ~1.05 млн на страницу — то есть в лучшем случае сравнялся бы с чтением готового файла. На 34 страницы: 1.5 с загрузки против 20 с разворота. * Упаковщики пишут второй набор: vflip_cols/vflip_page в pop_pack_kid.py -> kid0_v.atl…kid27_v.atl, sword_v.atl; pop_pack_guard.py -> g0_v.atl…g4_v.atl ТОЛЬКО для обычного стража (на уровне 9 tbl_guard_type = 0; тень рисуется атласами Кида, скелет/визирь с переворотом не встречаются). * pop_vflip.c схлопнулся до таблицы «страница -> зеркальная пара»; весь побайтовый разворот и аллокация EMM удалены. * pop_vflip_load_all (банк 8) грузит набор ОДНИМ вызовом и откуда угодно — задел под загрузку ресурсов во время интро. Зовётся при старте и при смене уровня, если это POP_UPSIDE_LEVEL; чит U на других уровнях поднимает набор лениво (страховка в главном цикле). * Стартовый уровень стал параметром сборки: make LEVEL=N (-DFIRST_LEVEL). Проверено в MAME на уровне 9: переворот мгновенный, pop_flip_screen = 897 263 такта (2.1 кадра) — это сама копия экрана, загрузки в кадре больше нет. tests-host 6/6.
This commit is contained in:
@@ -42,6 +42,11 @@ EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --ban
|
||||
# MAME = время фазы в растровых строках.
|
||||
PROF ?= 1
|
||||
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
||||
# Стартовый уровень (отладка): make LEVEL=9 — начать сразу с девятого.
|
||||
# Дефолт держим на отлаживаемом сейчас уровне; для «настоящей» игры — LEVEL=1.
|
||||
# Константа объявлена в pop_tune.h (её видят обе половины главного цикла).
|
||||
LEVEL ?= 9
|
||||
PROF_FLAGS += -DFIRST_LEVEL=$(LEVEL)
|
||||
EXTRA_SRCS := pop_vflip.c pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c
|
||||
|
||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||
@@ -67,16 +72,26 @@ BG_DATA := $(foreach s,$(BG_SETS),\
|
||||
# не рисуются (отрисовка пропускает незагруженную страницу). Палитра —
|
||||
# ЕДИНАЯ игровая (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 $(KID_DIR)/sword.atl
|
||||
# Зеркальный ПО ВЕРТИКАЛИ набор (зелье инверсии, уровень 9): те же спрайты с
|
||||
# развёрнутыми колонками. Готовит упаковщик, а не рантайм — разворот
|
||||
# страницы стоит 0.59 с против 0.045 с на её чтение с диска (замер MAME
|
||||
# 2026-08-12, протокол в pop_vflip.c).
|
||||
KID_VPAGES := $(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)_v.atl)
|
||||
KID_DATA := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/kid.pal \
|
||||
$(KID_DIR)/sword.atl $(KID_DIR)/sword_v.atl
|
||||
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
|
||||
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
|
||||
KID_BIN := $(KID_DIR)/kid_data.bin
|
||||
GUARD_DIR := $(CURDIR)/../poc/res/guard
|
||||
GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
|
||||
$(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl
|
||||
$(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl \
|
||||
$(GUARD_DIR)/g0_v.atl $(GUARD_DIR)/g1_v.atl $(GUARD_DIR)/g2_v.atl \
|
||||
$(GUARD_DIR)/g3_v.atl $(GUARD_DIR)/g4_v.atl
|
||||
# Скелет — ОТДЕЛЬНЫЙ набор спрайтов (SKEL.DAT в оригинале): его грузит
|
||||
# уровень 3 (tbl_guard_type == 2), кадров меньше (28 против 34).
|
||||
SKEL_DIR := $(CURDIR)/../poc/res/skel
|
||||
# Зеркального набора у скелета НЕТ: зелье инверсии — уровень 9, там обычный
|
||||
# страж (tbl_guard_type[9] = 0). См. pop_pack_guard.py.
|
||||
SKEL_DATA := $(SKEL_DIR)/g0.atl $(SKEL_DIR)/g1.atl $(SKEL_DIR)/g2.atl \
|
||||
$(SKEL_DIR)/g3.atl
|
||||
|
||||
|
||||
@@ -105,6 +105,31 @@ int pop_guard_load(void) __banked
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Зеркальный набор соперника — пара к pop_guard_load: те же страницы, но
|
||||
* файлы *_v.atl (см. pop_vflip.h). Здесь, а не в загрузчике набора,
|
||||
* потому что gp[] и выбор GUARD/SKEL живут именно тут. */
|
||||
int pop_guard_vflip_load(void) __banked
|
||||
{
|
||||
static const char *const guard_v[GUARD_PAGES] = {
|
||||
"GUARD\\g0_v.atl", "GUARD\\g1_v.atl", "GUARD\\g2_v.atl",
|
||||
"GUARD\\g3_v.atl", "GUARD\\g4_v.atl"
|
||||
};
|
||||
static const char *const skel_v[GUARD_PAGES] = {
|
||||
"SKEL\\g0_v.atl", "SKEL\\g1_v.atl", "SKEL\\g2_v.atl",
|
||||
"SKEL\\g3_v.atl", 0
|
||||
};
|
||||
const char *const *names;
|
||||
int8_t type = pop_level_guard_type(pop_current_level);
|
||||
uint8_t i;
|
||||
if (type < 0 || !g_ok) return 0; /* соперников на уровне нет */
|
||||
names = (type == 2) ? skel_v : guard_v;
|
||||
for (i = 0; i < GUARD_PAGES; i++) {
|
||||
if (names[i] == 0) break;
|
||||
if (pop_vflip_add(&gp[i], names[i]) != 0) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pop_guard_free(void) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
@@ -236,6 +236,11 @@ uint8_t pop_guard_present(void);
|
||||
/* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid.
|
||||
* 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */
|
||||
int pop_guard_load(void) __banked;
|
||||
|
||||
/* Зеркальный ПО ВЕРТИКАЛИ набор соперника (файлы *_v.atl) — для зелья
|
||||
* инверсии; зовётся из pop_vflip_load_all (pop_vflip.h). Живёт рядом с
|
||||
* pop_guard_load: там же gp[] и выбор GUARD/SKEL. */
|
||||
int pop_guard_vflip_load(void) __banked;
|
||||
void pop_guard_free(void) __banked;
|
||||
|
||||
/* Вход в комнату: поднять стража из данных уровня (порт enter_guard,
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "pop_kid.h"
|
||||
#include "_pop_kid.h"
|
||||
#include "kid_data.h"
|
||||
#include "pop_vflip.h"
|
||||
#include "pop_guard.h" /* pop_guard_vflip_load — зеркала соперника */
|
||||
|
||||
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
|
||||
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
|
||||
@@ -56,3 +58,29 @@ void pop_kid_data_free(void) __banked
|
||||
{
|
||||
if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; }
|
||||
}
|
||||
|
||||
/* ---- Зеркальный набор (зелье инверсии, уровень 9) -------------------- *
|
||||
* Грузится ОДНИМ вызовом и откуда угодно: со старта игры, из показа интро
|
||||
* или при входе на уровень с зельем — см. pop_vflip.h. Внутри обычный
|
||||
* atlas_load (он резидентный и сам мапит W3, банку это разрешено делать
|
||||
* только через него). Идемпотентна. */
|
||||
int pop_vflip_load_all(void) __banked
|
||||
{
|
||||
static const char *const kidv[KID_MAXPAGES] = {
|
||||
"KID\\kid0_v.atl","KID\\kid1_v.atl","KID\\kid2_v.atl","KID\\kid3_v.atl",
|
||||
"KID\\kid4_v.atl","KID\\kid5_v.atl","KID\\kid6_v.atl","KID\\kid7_v.atl",
|
||||
"KID\\kid8_v.atl","KID\\kid9_v.atl","KID\\kid10_v.atl","KID\\kid11_v.atl",
|
||||
"KID\\kid12_v.atl","KID\\kid13_v.atl","KID\\kid14_v.atl","KID\\kid15_v.atl",
|
||||
"KID\\kid16_v.atl","KID\\kid17_v.atl","KID\\kid18_v.atl","KID\\kid19_v.atl",
|
||||
"KID\\kid20_v.atl","KID\\kid21_v.atl","KID\\kid22_v.atl","KID\\kid23_v.atl",
|
||||
"KID\\kid24_v.atl","KID\\kid25_v.atl","KID\\kid26_v.atl","KID\\kid27_v.atl"
|
||||
};
|
||||
uint8_t i;
|
||||
int res = 0;
|
||||
if (pop_vflip_ready()) return 0;
|
||||
for (i = 0; i < kid_npages; i++)
|
||||
if (pop_vflip_add(&kidp[i], kidv[i]) != 0) res = -1;
|
||||
if (pop_sword_vflip_load() != 0) res = -1;
|
||||
if (pop_guard_vflip_load() != 0) res = -1;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,13 @@ int pop_sword_load(void) __banked
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Зеркальная страница меча (зелье инверсии) — пара к pop_sword_load. */
|
||||
int pop_sword_vflip_load(void) __banked
|
||||
{
|
||||
if (!sword_ok) return 0;
|
||||
return pop_vflip_add(&swordp, "KID\\sword_v.atl");
|
||||
}
|
||||
|
||||
/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в
|
||||
* кадрах подъёма/ножен, ИЛИ держит меч, ИЛИ это ЖИВОЙ страж (у стража меч
|
||||
* в руке всегда, даже пока sword ещё sheathed). Страницу W0 оставляем
|
||||
|
||||
@@ -48,6 +48,8 @@ void pop_kid_data_free(void) __banked;
|
||||
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
|
||||
* кадры рисуются без клинка). */
|
||||
int pop_sword_load(void) __banked;
|
||||
/* Зеркальная (вертикально) страница меча — для зелья инверсии. */
|
||||
int pop_sword_vflip_load(void) __banked;
|
||||
void pop_kid_free(void) __banked;
|
||||
|
||||
/* Сама отрисовка клинка (pop_sword_draw) и блит спрайта chtab_2 по id
|
||||
|
||||
@@ -121,4 +121,9 @@
|
||||
#define FIRST_LEVEL 1
|
||||
#endif
|
||||
|
||||
/* Уровень с зельем инверсии (переворот экрана). По нему делается
|
||||
* ПРЕДзагрузка зеркального набора спрайтов персонажей — чтобы пауза на
|
||||
* чтение 34 страниц пришлась на загрузку уровня, а не на глоток зелья. */
|
||||
#define POP_UPSIDE_LEVEL 9
|
||||
|
||||
#endif /* POP_TUNE_H */
|
||||
|
||||
@@ -1,111 +1,55 @@
|
||||
/*
|
||||
* pop_vflip.c — ЗЕРКАЛЬНЫЕ ПО ВЕРТИКАЛИ копии страниц атласов персонажей
|
||||
* pop_vflip.c — таблица «страница атласа -> её ВЕРТИКАЛЬНО ЗЕРКАЛЬНАЯ пара»
|
||||
* (зелье инверсии, уровень 9). См. pop_vflip.h.
|
||||
*
|
||||
* Зачем отдельный набор страниц. Спрайты персонажей хранятся COLUMN-MAJOR
|
||||
* (out[col*h + row], transpose_cols в pop_pack_kid.py) — ради БЕСПЛАТНОГО
|
||||
* горизонтального флипа: accel копирует колонки, и порядок колонок задаётся
|
||||
* знаком страйда. А вертикальное зеркало у такой раскладки — это разворот
|
||||
* байтов ВНУТРИ колонки, чего accel не умеет: блок он копирует только
|
||||
* вперёд. Значит перевёрнутые кадры надо готовить заранее.
|
||||
* Сами зеркальные ленты делает упаковщик (`vflip_cols` в pop_pack_kid.py) и
|
||||
* кладёт рядом с обычными: kid0_v.atl, sword_v.atl, g0_v.atl… Здесь остаётся
|
||||
* только связать пары: зеркальный файл грузится обычным atlas_load, а его
|
||||
* раскладка байт-в-байт совпадает с оригиналом — значит движку достаточно
|
||||
* подменить НОМЕР СТРАНИЦЫ, которую он мапит в W0.
|
||||
*
|
||||
* Устройство. Копия — ПОСТРАНИЧНАЯ и побайтово совпадает с оригиналом по
|
||||
* раскладке: тот же заголовок .atl, тот же каталог, ленты на тех же
|
||||
* смещениях; развёрнуты только сами пиксели. Поэтому вызывающему не нужно
|
||||
* менять НИЧЕГО, кроме номера страницы, которую он мапит в W0 — все
|
||||
* atlas_image-смещения и размеры кадров остаются прежними.
|
||||
* Почему не считаем в рантайме (замеры MAME 2026-08-12, такты Z80):
|
||||
* загрузка одной 16-КБ страницы с HDD — 970 003 такта (0.045 с);
|
||||
* разворот той же страницы на месте — 12 645 582 такта (0.59 с).
|
||||
* Даже идеальная конвертация (accel-копия + asm-реверс) даёт ~1.05 млн —
|
||||
* то есть в лучшем случае сравнивается с чтением готового файла, а стоит
|
||||
* asm-модуля. На все 34 страницы: 1.5 с загрузки против 20 с разворота.
|
||||
*
|
||||
* Кэш ЛЕНИВЫЙ: страница зеркалится при первом обращении к ней в
|
||||
* перевёрнутом виде (~16 КБ копирования = заметно, но однократно на кадр
|
||||
* анимации), дальше отдаётся из таблицы. Держится до конца уровня;
|
||||
* pop_vflip_reset освобождает всё (смена уровня / зелье выпито обратно —
|
||||
* см. pop_vflip.h про то, почему НЕ освобождаем при каждом перевороте).
|
||||
*
|
||||
* РЕЗИДЕНТ, а не банк: функция маппит окно W3 (приёмник копии), а банковый
|
||||
* код в нём же и исполняется — правило из _pop_level.h.
|
||||
* РЕЗИДЕНТ: pop_vflip_page зовёт отрисовка персонажей (банк 4) на каждый
|
||||
* спрайт — трамплин тут ни к чему, а кода всего ничего.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_vflip.h"
|
||||
|
||||
/* Раскладка .atl (sprite.h §Атласы): 0x100-байтовый заголовок, каталог с
|
||||
* 0x68 — по 8 байт на ленту: [0..1] смещение ленты, дальше размеры. */
|
||||
#define ATL_DIR_OFF 0x68u
|
||||
#define ATL_COUNT_OFF 0x04u /* число лент в заголовке */
|
||||
|
||||
/* Таблица кэша: страница-источник -> страница-копия. Персонажных атласов
|
||||
* у нас 28 (Кид) + 5 (страж) + 1 (меч) = 34; берём с запасом. */
|
||||
/* Персонажных страниц: 28 (Кид) + 5 (страж) + 1 (меч) = 34; с запасом. */
|
||||
#define VF_MAX 40
|
||||
static uint8_t vf_src[VF_MAX], vf_dst[VF_MAX], vf_blk[VF_MAX], vf_n;
|
||||
static uint8_t vf_src[VF_MAX]; /* страница оригинала */
|
||||
static atlas_t vf_atl[VF_MAX]; /* зеркальный атлас (его page) */
|
||||
static uint8_t vf_n;
|
||||
|
||||
/* Развернуть ленту на месте В СТРАНИЦЕ-ПРИЁМНИКЕ (она замаплена в W3).
|
||||
* off — смещение ленты от начала страницы; формат ленты: w, _, h, _, пиксели
|
||||
* колонками по h байт. */
|
||||
static void flip_strip(uint16_t off)
|
||||
int pop_vflip_add(const atlas_t *orig, const char *path)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)(0xC000u + off);
|
||||
uint8_t w = p[0], h = p[2];
|
||||
uint8_t *col = p + 4;
|
||||
uint8_t c;
|
||||
|
||||
if (!w || !h) return;
|
||||
for (c = 0; c < w; c++) {
|
||||
uint8_t *a = col, *b = col + h - 1;
|
||||
while (a < b) { uint8_t t = *a; *a++ = *b; *b-- = t; }
|
||||
col += h;
|
||||
}
|
||||
}
|
||||
|
||||
/* Сделать зеркальную копию страницы src. Возврат номера копии или 0. */
|
||||
static uint8_t make_flipped(uint8_t src)
|
||||
{
|
||||
uint8_t blk, dst, saved, count, i;
|
||||
uint16_t k;
|
||||
const uint8_t *s;
|
||||
uint8_t *d;
|
||||
|
||||
blk = mem_alloc_pages(1);
|
||||
if (!blk) return 0;
|
||||
dst = mem_get_page(blk, 0);
|
||||
|
||||
/* Побайтовая копия страницы: источник в W0 (0x0000-0x3FFF), приёмник в
|
||||
* W3 (0xC000). Оба окна свободны для резидентного кода W1/W2. */
|
||||
saved = _io_page_w3;
|
||||
gfx_w0_map(src);
|
||||
sprinter_page_w3(dst);
|
||||
s = (const uint8_t *)0x0000;
|
||||
d = (uint8_t *)0xC000;
|
||||
for (k = 0; k < 16384u; k++) d[k] = s[k];
|
||||
gfx_w0_unmap();
|
||||
|
||||
/* Развернуть пиксели каждой ленты — уже в приёмнике (он в W3). */
|
||||
count = ((const uint8_t *)0xC000)[ATL_COUNT_OFF];
|
||||
for (i = 0; i < count; i++) {
|
||||
const uint8_t *e = (const uint8_t *)(0xC000u + ATL_DIR_OFF) + (uint16_t)i * 8;
|
||||
uint16_t off = (uint16_t)(e[0] | ((uint16_t)e[1] << 8));
|
||||
if (off >= 0x100u && off < 0x4000u) flip_strip(off);
|
||||
}
|
||||
sprinter_page_w3(saved);
|
||||
|
||||
if (vf_n < VF_MAX) {
|
||||
vf_src[vf_n] = src; vf_dst[vf_n] = dst; vf_blk[vf_n] = blk; vf_n++;
|
||||
}
|
||||
return dst;
|
||||
if (!orig || vf_n >= VF_MAX) return -1;
|
||||
if (atlas_load(&vf_atl[vf_n], path) != 0) return -1;
|
||||
vf_src[vf_n] = orig->page;
|
||||
vf_n++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t pop_vflip_page(uint8_t src)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < vf_n; i++)
|
||||
if (vf_src[i] == src) return vf_dst[i];
|
||||
return make_flipped(src);
|
||||
if (vf_src[i] == src) return vf_atl[i].page;
|
||||
return 0; /* пары нет — рисуем обычным атласом */
|
||||
}
|
||||
|
||||
uint8_t pop_vflip_ready(void) { return vf_n != 0; }
|
||||
|
||||
void pop_vflip_reset(void)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < vf_n; i++) mem_free_block(vf_blk[i]);
|
||||
for (i = 0; i < vf_n; i++) atlas_free(&vf_atl[i]);
|
||||
vf_n = 0;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,48 @@
|
||||
/*
|
||||
* pop_vflip.h — зеркальные по вертикали копии страниц атласов персонажей
|
||||
* (зелье инверсии, уровень 9). Реализация и «почему постранично» —
|
||||
* в pop_vflip.c.
|
||||
* pop_vflip.h — ВЕРТИКАЛЬНО ЗЕРКАЛЬНЫЕ наборы спрайтов персонажей
|
||||
* (зелье инверсии, уровень 9). Реализация и замеры — в pop_vflip.c.
|
||||
*
|
||||
* Кому нужно. Фон и все row-major спрайты переворачиваются даром (порядок
|
||||
* строк = знак страйда, gfx_blit_*_vflip), а персонажи хранятся колонками —
|
||||
* им нужен заранее развёрнутый набор пикселей. Отсюда правило вызывающего:
|
||||
* строк = знак страйда, gfx_blit_*_vflip), а персонажи лежат КОЛОНКАМИ —
|
||||
* у них даром получается горизонтальное зеркало, а вертикальное требует
|
||||
* развернуть байты внутри колонки. Такой набор готовит упаковщик
|
||||
* (kid0_v.atl, sword_v.atl, g0_v.atl…), а здесь он только грузится и
|
||||
* связывается с оригиналом. Правило вызывающего:
|
||||
*
|
||||
* страница = pop_upside ? pop_vflip_page(a->page) : a->page;
|
||||
* if (!страница) страница = a->page; // набор не загружен
|
||||
*
|
||||
* и больше НИЧЕГО не меняется: смещения лент, размеры кадров, флип по
|
||||
* горизонтали (знак страйда) остаются как были.
|
||||
* Ни смещения лент, ни размеры кадров, ни горизонтальный флип при этом не
|
||||
* меняются: зеркальный файл имеет ТУ ЖЕ раскладку, отличаются только пиксели.
|
||||
*
|
||||
* Почему кэш не сбрасывается на каждом перевороте: зелье инверсии в
|
||||
* оригинале переключает состояние туда-обратно (второе зелье, смерть Кида —
|
||||
* seg000:1224), и держать 34 страницы EMM дешевле, чем перезеркаливать по
|
||||
* 16 КБ при каждом переключении. Бюджет EMM это позволяет: свободно ~215
|
||||
* страниц из 256 (memory sprinter_emm_budget). Освобождаем на смене
|
||||
* уровня — вместе с самими атласами.
|
||||
* КОГДА ГРУЗИТЬ — на усмотрение приложения: pop_vflip_load_all можно звать
|
||||
* где угодно (старт игры, показ интро, вход на уровень с зельем). До
|
||||
* загрузки движок просто рисует неперевёрнутыми кадрами — это заметно, но
|
||||
* не ломает ни кадр, ни физику.
|
||||
*/
|
||||
#ifndef POP_VFLIP_H
|
||||
#define POP_VFLIP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sprite.h>
|
||||
|
||||
/* Номер страницы с ВЕРТИКАЛЬНО ЗЕРКАЛЬНОЙ копией атласа src. Первый вызов
|
||||
* для страницы делает копию (~16 КБ побайтно — ощутимо, но однократно),
|
||||
* дальше отдаёт из кэша. 0 — не хватило EMM-страниц (вызывающий должен
|
||||
* нарисовать обычным атласом: лучше неперевёрнутый кадр, чем пустота). */
|
||||
/* Загрузить зеркальный файл path и связать его с атласом orig.
|
||||
* 0 — OK, -1 — файла нет / не хватило EMM / таблица полна. */
|
||||
int pop_vflip_add(const atlas_t *orig, const char *path);
|
||||
|
||||
/* Страница-зеркало для страницы src; 0 — пары нет (набор не загружен). */
|
||||
uint8_t pop_vflip_page(uint8_t src);
|
||||
|
||||
/* Освободить все копии (смена уровня). Атласы к этому моменту тоже
|
||||
* перезагружаются, и старые копии стали бы мусором. */
|
||||
/* Загружен ли хоть один зеркальный атлас (чтобы не грузить дважды). */
|
||||
uint8_t pop_vflip_ready(void);
|
||||
|
||||
/* Освободить весь набор (смена набора спрайтов соперника / уровня). */
|
||||
void pop_vflip_reset(void);
|
||||
|
||||
/* Загрузить ВЕСЬ зеркальный набор: страницы Кида, меч и текущий набор
|
||||
* соперника. Идемпотентна — повторный вызов ничего не делает. 0 — OK,
|
||||
* -1 — что-то не загрузилось (движок останется на обычных кадрах).
|
||||
* Живёт в банке 8 (pop_kboot.c): зовётся раз за игру/уровень. */
|
||||
int pop_vflip_load_all(void) __banked;
|
||||
|
||||
#endif /* POP_VFLIP_H */
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "pop_cdraw.h" /* общая отрисовка персонажей (слоты Кид/соперник) */
|
||||
#include "pop_cheat.h"
|
||||
#include "pop_tune.h"
|
||||
#include "pop_vflip.h" /* зеркальный набор персонажей (зелье инверсии) */
|
||||
#include "roomtest_cold.h" /* холодная половина цикла — в банке */
|
||||
|
||||
__sfr __at (0xFE) io_border;
|
||||
@@ -340,6 +341,11 @@ int main(void)
|
||||
* рисуют зеркально сами. */
|
||||
if (pop_upside_dirty) {
|
||||
pop_upside_dirty = 0;
|
||||
/* Страховка для ЧИТА U на «неподготовленном» уровне и для
|
||||
* случая, когда зелье выпито раньше предзагрузки: набор
|
||||
* поднимается здесь же. На уровне 9 он уже загружен, и вызов
|
||||
* не стоит ничего (pop_vflip_ready). */
|
||||
if (pop_upside && !pop_vflip_ready()) pop_vflip_load_all();
|
||||
pop_flip_screen();
|
||||
back = dbuf ? (uint8_t)(gfx_get_visible_page() ^ 1) : 0;
|
||||
gfx_set_draw_page(back);
|
||||
|
||||
@@ -591,6 +591,12 @@ int pop_boot(void) __banked
|
||||
* pop_start_level. Управление: ←/→ бег, ↓ присед, ↑+→ прыжок с места,
|
||||
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
|
||||
pop_start_level();
|
||||
/* Зеркальный набор персонажей — ТУТ ЖЕ, если стартуем сразу на уровне с
|
||||
* зельем инверсии (make LEVEL=9). Иначе первый переворот утащил бы
|
||||
* чтение 34 файлов (~1.5 с) прямо в кадр: пользователь видел «затык» в
|
||||
* синей фазе профиля. Тот же вызов есть в смене уровня; функция
|
||||
* идемпотентна и звать её можно откуда угодно (pop_vflip.h). */
|
||||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||||
gfx_set_draw_page(0);
|
||||
pop_char_draw(POP_CH_KID);
|
||||
pop_char_fore(POP_CH_KID);
|
||||
@@ -640,6 +646,11 @@ int pop_level_switch(void) __banked
|
||||
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
|
||||
* из неё же, loose чистит enter_room. */
|
||||
pop_start_level();
|
||||
/* Уровень с зельем инверсии — заранее поднять зеркальный
|
||||
* набор персонажей (pop_vflip.h): 34 страницы читаются ~1.5 с,
|
||||
* и делать это в момент глотка нельзя. Звать можно откуда
|
||||
* угодно — функция идемпотентна. */
|
||||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -344,3 +344,15 @@ uint8_t pop_palace;
|
||||
* pop_map зовёт pop_mirror_draw из pop_check_mirror, поэтому символ нужен. */
|
||||
void pop_mirror_draw(int clip_top) __banked { (void)clip_top; }
|
||||
void pop_mirror_heal(void) __banked { }
|
||||
|
||||
/* ---- Зеркальный набор персонажей (зелье инверсии) -------------------- *
|
||||
* В host-тестах графики нет: pop_kboot линкуется ради pop_kid_data_load, а
|
||||
* его хвост (загрузка *_v.atl) заглушен — проверять там нечего, атласов в
|
||||
* тесте не существует вовсе. */
|
||||
uint8_t pop_vflip_ready(void) { return 1; } /* «уже загружено» — грузить не станет */
|
||||
int pop_vflip_add(const void *orig, const char *path)
|
||||
{ (void)orig; (void)path; return 0; }
|
||||
uint8_t pop_vflip_page(uint8_t src) { return src; }
|
||||
void pop_vflip_reset(void) { }
|
||||
int pop_sword_vflip_load(void) __banked { return 0; }
|
||||
int pop_guard_vflip_load(void) __banked { return 0; }
|
||||
|
||||
Reference in New Issue
Block a user