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

Замеры в 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
@@ -47,6 +47,31 @@ def transpose_cols(w, h, rowmajor):
return bytes(out)
def vflip_cols(w, h, colmajor):
"""COLUMN-MAJOR (out[col*h + row]) -> тот же формат, но кадр ЗЕРКАЛЕН ПО
ВЕРТИКАЛИ: внутри каждой колонки байты идут снизу вверх.
Зачем отдельный набор. Спрайты персонажей лежат колонками ради
БЕСПЛАТНОГО горизонтального флипа (accel копирует колонки, направление
задаётся знаком страйда). Вертикальное зеркало у такой раскладки — это
разворот байтов ВНУТРИ колонки, а accel копирует блок только вперёд:
в рантайме это стоит ~0.6 с на страницу (замер MAME 2026-08-12), тогда
как ГОТОВАЯ страница читается с диска за 0.045 с. Поэтому зелье
инверсии (уровень 9) обслуживается вторым набором файлов *_v.atl.
Раскладка каталога у него та же — движку достаточно подменить номер
EMM-страницы (см. roomtest/pop_vflip.h)."""
out = bytearray(len(colmajor))
for col in range(w):
b = col * h
out[b:b + h] = colmajor[b:b + h][::-1]
return bytes(out)
def vflip_page(page):
"""{idx: (w,h,pix)} -> то же с зеркальными по вертикали пикселями."""
return {i: (w, h, vflip_cols(w, h, pix)) for i, (w, h, pix) in page.items()}
def load_kid(aidx):
"""Атлас-индекс aidx (== frame.image) -> res(401+aidx).png -> (w, h, 8bpp
COLUMN-MAJOR). 0->0xFF, i->0x70+i. Пиксели транспонированы в колонки.
@@ -149,6 +174,10 @@ def main():
total += sz
names.append(nm)
print(f" {nm}: {len(pages[pg])} спрайтов, каталог {cnt}, {sz} Б")
# Зеркальный по вертикали набор (зелье инверсии, уровень 9).
nmv = f"kid{pg}_v.atl"
_, szv = B.pack_atlas(os.path.join(OUT_DIR, nmv), vflip_page(pages[pg]))
total += szv
else:
names.append(None)
print(f" (kid стр {pg}: пусто)")
@@ -160,6 +189,8 @@ def main():
sw[i] = (w, h, pix)
cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, "sword.atl"), sw)
total += sz
_, szv = B.pack_atlas(os.path.join(OUT_DIR, "sword_v.atl"), vflip_page(sw))
total += szv
print(f" sword.atl (chtab_0, id {SWORD_IDS[0]}..{SWORD_IDS[-1]}): "
f"{len(sw)} спрайтов, каталог {cnt}, {sz} Б")