/* * 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 #include #include #include #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; }