Зеркальные кадры персонажей — готовыми файлами, а не разворотом в рантайме

Замеры в 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:
2026-08-12 23:17:22 +03:00
parent c4dd2c7a87
commit 5b6664ffce
48 changed files with 219 additions and 107 deletions
+30 -19
View File
@@ -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 */