Files
Sprinter-SDCC/applications/PoP/roomtest/pop_vflip.c
T
snark13 755190674c L9-INVERT II.4: персонажи рисуются перевёрнутыми (зеркальные страницы атласов)
Спрайты персонажей хранятся column-major (ради бесплатного H-флипа), а
вертикальное зеркало у такой раскладки — разворот байтов ВНУТРИ колонки,
чего accel не умеет.  Поэтому pop_vflip.c готовит зеркальные КОПИИ страниц
атласов: копия побайтово повторяет раскладку .atl (заголовок, каталог,
смещения лент), развёрнуты только пиксели — значит вызывающему достаточно
подменить номер страницы, которую он мапит в W0.

* Кэш ленивый (страница зеркалится при первом обращении в перевёрнутом
  виде) и живёт до смены уровня — зелье переключает состояние туда-обратно,
  перезеркаливать по 16 КБ на каждый переворот незачем.  EMM хватает: 34
  страницы из ~215 свободных.
* pop_cdraw/pop_kdraw: страница + позиция (top = 191 - obj_y для персонажа,
  192 - top - h для клинка).  «Пропустить skip строк сверху» у зеркальной
  ленты = «срезать снизу» у оригинала, поэтому клипы считаются как обычно.
* Модуль резидентный: он маппит W3 (приёмник копии) — банку так нельзя.
* pop_vflip_reset на смене уровня: копии сделаны из старых страниц.

Проверено в MAME: чит U переворачивает всё разом, Кид и факелы вверх ногами,
полоса HP на месте, бег без следов на фоне (heal бьёт в те же координаты).

ОСТАЁТСЯ (L9-CLIPCHAR): clip_char в перевороте должен резать снизу, а не
сверху — пока при pop_upside не режем вовсе.  Тени/брызг это не касается.
2026-08-12 22:25:18 +03:00

112 lines
5.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_vflip.c — ЗЕРКАЛЬНЫЕ ПО ВЕРТИКАЛИ копии страниц атласов персонажей
* (зелье инверсии, уровень 9). См. pop_vflip.h.
*
* Зачем отдельный набор страниц. Спрайты персонажей хранятся COLUMN-MAJOR
* (out[col*h + row], transpose_cols в pop_pack_kid.py) — ради БЕСПЛАТНОГО
* горизонтального флипа: accel копирует колонки, и порядок колонок задаётся
* знаком страйда. А вертикальное зеркало у такой раскладки — это разворот
* байтов ВНУТРИ колонки, чего accel не умеет: блок он копирует только
* вперёд. Значит перевёрнутые кадры надо готовить заранее.
*
* Устройство. Копия — ПОСТРАНИЧНАЯ и побайтово совпадает с оригиналом по
* раскладке: тот же заголовок .atl, тот же каталог, ленты на тех же
* смещениях; развёрнуты только сами пиксели. Поэтому вызывающему не нужно
* менять НИЧЕГО, кроме номера страницы, которую он мапит в W0 — все
* atlas_image-смещения и размеры кадров остаются прежними.
*
* Кэш ЛЕНИВЫЙ: страница зеркалится при первом обращении к ней в
* перевёрнутом виде (~16 КБ копирования = заметно, но однократно на кадр
* анимации), дальше отдаётся из таблицы. Держится до конца уровня;
* pop_vflip_reset освобождает всё (смена уровня / зелье выпито обратно —
* см. pop_vflip.h про то, почему НЕ освобождаем при каждом перевороте).
*
* РЕЗИДЕНТ, а не банк: функция маппит окно W3 (приёмник копии), а банковый
* код в нём же и исполняется — правило из _pop_level.h.
*/
#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; берём с запасом. */
#define VF_MAX 40
static uint8_t vf_src[VF_MAX], vf_dst[VF_MAX], vf_blk[VF_MAX], vf_n;
/* Развернуть ленту на месте В СТРАНИЦЕ-ПРИЁМНИКЕ (она замаплена в W3).
* off — смещение ленты от начала страницы; формат ленты: w, _, h, _, пиксели
* колонками по h байт. */
static void flip_strip(uint16_t off)
{
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;
}
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);
}
void pop_vflip_reset(void)
{
uint8_t i;
for (i = 0; i < vf_n; i++) mem_free_block(vf_blk[i]);
vf_n = 0;
}