L9-INVERT: переворот экрана accel-копией как основной вариант + UI-SPRITES

Момент переключения зелья переворота делаем не перерисовкой комнаты, а двумя
построчными проходами акселератора (A->B с реверсом Y, B->A без): accel
читает ОЗУ-копию, поэтому копируется чистый фон и ложится и в видео, и в
копию приёмника — heal остаётся без изменений.  Бюджет 768 burst-строк
(0.2-0.3 логического кадра) против почти миллиона тактов у перерисовки.

Отдельным пунктом: деления HP переезжают из column-major атласов в
const-массивы резидента, что убирает pop_kid_img_blit (334 Б) и делает
страницы kid27/g0 однородными для кэша перевёрнутых кадров.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 15:58:16 +03:00
parent 6a392f5403
commit c9a4efe119
+60 -5
View File
@@ -62,11 +62,47 @@
после (seg000:939/946); полоса HP рисуется прямо на экран (y = 194) и НЕ
переворачивается.
**Приём оригинала нам не подходит:** мы рисуем инкрементально прямо в
видеостраницу (heal + спрайты), готового offscreen-кадра нет, и «перевернуть
буфер на вывод» означало бы полный кадровый блит 61 440 Б. Значит
переворачиваем НА ОТРИСОВКЕ: `y' = POP_YOFF + POP_PLAYFIELD_H - (y - POP_YOFF) - h`
плюс вертикальное зеркало самого спрайта.
**Приём оригинала — переворачивать буфер на выводе КАЖДЫЙ кадр — нам не
подходит:** страниц ровно две (`gfx_set_visible_page` → ESTEX $54 SELPAGE,
бит 0), рабочей третьей нет, а переворот в видимую страницу порвёт картинку
(проход не влезает в vblank). Значит переворачиваем ОДИН РАЗ в момент смены
флага, а дальше рисуем зеркально:
`y' = POP_YOFF + POP_PLAYFIELD_H - (y - POP_YOFF) - h` плюс вертикальное
зеркало самого спрайта.
#### Момент переключения — accel-копия экрана (ОСНОВНОЙ ВАРИАНТ)
Решение пользователя 2026-08-12. Уже нарисованное переворачиваем не
перерисовкой комнаты, а построчной копией акселератора:
1. **проход A → B с реверсом Y**: строка `y` читается, пишется в `191-y`
(смена Port_Y между burst-чтением и burst-записью). 320 байт в один burst
не лезут — две скобки на строку;
2. **проход B → A без реверса**: вторая страница дабл-буфера получает то же
изображение.
Почему это работает на нашем железе, а не только на бумаге: **accel читает
ОЗУ-КОПИЮ, а не VRAM** (memory `accel_block_ops`), то есть копируется ЧИСТЫЙ
фон без персонажей, а запись банком `GFX_BANK_NORMAL` идёт и в видео, и в
ОЗУ-копию приёмника. После двух проходов обе страницы имеют перевёрнутый фон
в обеих плоскостях — **heal не меняется ни на строку**, он и дальше
восстанавливает правильный фон по фактическим координатам отрисовки.
Бюджет: 192 строки × 2 burst'а × 2 прохода = 768 burst-строк. По нашим
замерам строка-burst стоит ~300-530Т (`gfx_heal_noclip` 22×22 = 11 658Т
530Т/строку; у заливки линия 51-53Т, но там нет данных) — итого
**230-410 К тактов, то есть 0.2-0.3 логического кадра** (логический = 3
растровых ≈ 1.29 млн). Перерисовка комнаты в обе страницы стоила бы под
миллион тактов И требовала бы vflip-версии тайлового блита — здесь она не
нужна вовсе.
Что дописать в libbgi: `_bgi_copy_rows_raw` умеет только ИНКРЕМЕНТ Port_Y на
строку (`y0` + шаг вперёд, страйды патчатся SMC) — нужен вариант с
декрементом одной стороны (параметр «шаг Y» либо отдельное ядро
`_bgi_flip_rows_raw`). Перед реализацией подтвердить ДАМПОМ, что обе
страницы одновременно адресуемы в W3: `gfx.h` говорит, что страница 1
начинается на 320 байт дальше (0xC140), но это комментарий, а не проверка
(memory `defer_unexplained_quirks`).
Зеркало по вертикали стоит по-разному в зависимости от раскладки спрайта:
@@ -96,6 +132,25 @@
fore-слоя, и клип (`clip_char`), и брызги; полоса HP и лейбл комнаты остаются
как есть (они вне поля 192).
<a id="ui-sprites"></a>
### UI-SPRITES. Деления HP — из атласов персонажей в константы резидента
`pop_kid_img_blit` (**334 Б в W1/W2**) существует ради двух картинок 6×5:
полосе HP нужен блит по image id из кидовских атласов, а те column-major, и
обычным `gfx_blit_noclip` их не нарисовать. Деления Кида (images 216/217,
`kid27.atl`) и стража (idx 0 в `g0.atl`) — по 30 байт каждое.
**Решение пользователя 2026-08-12: положить их const-массивами в РЕЗИДЕНТ**
(~68 Б данных), а не подселять в фоновый атлас: резидент всегда ниже
0xC000, значит `gfx_blit*` их видит (буфер обязан быть вне W3), EMM-страница
не тратится, упаковщики не трогаются. Чистая экономия ~266 Б.
Побочный плюс для [L9-INVERT](#l9-invert): страницы `kid27` и `g0` сейчас
смешанные — деления HP (борт, не переворачиваются) лежат рядом с брызгами
крови 28×26 (поле, переворачиваются). После переноса обе страницы
становятся чисто «полевыми», и постраничный кэш перевёрнутых кадров получается
однородным, без спрайтов-исключений.
<a id="mem-cold2"></a>
### MEM-COLD2. Второй шаг разгрузки резидента