/*
 * sprite_update — отрисовать кадр сцены на ТЕКУЩЕЙ draw-странице p
 * (gfx_get_draw_page).  Ядро batch-пасса движка v2 (см.
 * docs/sprite-api-design.md §9.1): вся работа кадра — здесь, двухпроходно
 * «heal ВСЕ → блит ВСЕ», под ОДНОЙ W3-скобкой и ОДНИМ банком на проход.
 *
 * Дисциплина двух проходов ОБЯЗАТЕЛЬНА: слияние heal+блит per-sprite не
 * работает на перекрытиях (heal соседа выкусывает только что
 * нарисованный спрайт — проверено на examples/balls 2026-07-11).
 *
 * Что перерисовывается: спрайт, у которого страница p ГРЯЗНАЯ (dbit —
 * взведён sprite_move/frame/show/hide/touch) ИЛИ флаг SPR_ALWAYS.
 * Неизменившиеся с прошлого визита этой страницы (и без SPR_ALWAYS) не
 * трогаются — суть retained-модели.
 *
 * Проход 1 (банк 0x50): heal старой позиции drawn[p] у тех, кто нарисован
 * (drawn[p].on) и требует обновления → снять drawn[p].on.
 * Проход 2 (банк 0x5C): блит видимых, требующих обновления, в (x,y) кадром
 * (sx,sy) → записать drawn[p] = (x,y), on = 1; снять dbit (страница p
 * актуальна для этого спрайта).  z-order = порядок массива.
 *
 * DI живёт внутри leaf'ов (по строке) — скобка _bgi_begin/_bgi_end
 * прерывания НЕ трогает (только мапит W3), поэтому длина прохода не
 * влияет на аудио-ISR.  w,h кадра ≤ 255 (см. sprite.h).
 */
#include <sprinter.h>
#include "../_bgi.h"
#include <sprite.h>

void sprite_update(sprite_t *arr, uint8_t count)
{
    uint8_t p     = gfx_get_draw_page();               /* 0 / 1        */
    uint8_t dbit  = p ? _SPR_DIRTY1 : _SPR_DIRTY0;
    uint8_t saved = gfx_get_bank();
    uint8_t i;
    const uint8_t *rec;          /* курсор сорт-таблицы (NULL = без) */
    sprite_t *s;

    /* Проход 0: тик авто-анимаций (§9г) — ДО heal/блита (двигает x/y и
     * ось кадра, взводя dirty).  Указатель ставят sprite_anim/
     * sprite_moveto; без них — один if на кадр, код тикера не линкуется. */
    if (_spr_tick_fn)
        (*_spr_tick_fn)(arr, count);

    /* Проход 1: heal ВСЕХ старых позиций, требующих обновления. */
    gfx_set_bank(GFX_BANK_NORMAL);
    _bgi_begin();
    for (i = 0, s = arr; i < count; i++, s++) {
        if (s->drawn[p].on && (s->flags & (dbit | SPR_ALWAYS))) {
            (*_gfx_heal_fn)(s->drawn[p].x, s->drawn[p].y, s->w, s->h);
            s->drawn[p].on = 0;
        }
    }
    _bgi_end();

    /* Y-сортировка блит-прохода (gfx_sprite_ysort): asm-сортировка
     * строит таблицу записей {key,ptr} по ВОЗРАСТАНИЮ y (нижние
     * рисуются последними = поверх) и возвращает её адрес; rec — курсор
     * по полю ptr записей (+1, шаг 3), NULL = сортировки нет.
     * Подключена указателем (funcptr-DCE).  Сортируем ПОСЛЕ тика (y уже
     * сдвинуты).  count больше таблицы — фолбэк на порядок массива. */
    rec = 0;
    if (_spr_ysort_fn && count <= _GFX_YSORT_MAX)
        rec = (*_spr_ysort_fn)(arr, count) + 2;   /* поле ptr записи */

    /* Проход 2: блит ВСЕХ видимых, требующих обновления.  Спрайты из
     * W0-атласов (s->page): подключить страницу перед блитом — один OUT
     * на СМЕНУ страницы (спрайты одного атласа подряд = один OUT; при
     * Y-сортировке смен может быть больше);  порядок «_gfx_w0_cur,
     * затем OUT» закрывает гонку с ISR-стабом (он восстановит уже
     * новую).  heal-проходу страница и порядок не нужны. */
    gfx_set_bank(GFX_BANK_SPRITE);
    _bgi_begin();
    {
        uint8_t mapped = 0;         /* 0 = W0 не тронута (там ядро DSS) */

        for (i = 0; i < count; i++) {
            if (rec) {
                s = *(sprite_t * const *)rec;   /* Y-порядок */
                rec += 4;
            } else
                s = arr + i;                    /* z = индекс массива */
            if (s->flags & (dbit | SPR_ALWAYS)) {
                if (s->flags & SPR_VISIBLE) {
                    if (s->page != mapped && s->page) {
                        _gfx_w0_cur = s->page;
                        _io_page_w0 = s->page;
                        mapped = s->page;
                    }
                    /* src/stride — готовый кэш (sprite_frame/тикер),
                     * ядро не считает адрес кадра вообще. */
                    (*_gfx_blit_fn)(s->x, s->y, s->src, s->stride, s->w, s->h);
                    s->drawn[p].x  = s->x;
                    s->drawn[p].y  = s->y;
                    s->drawn[p].on = 1;
                }
                s->flags &= ~dbit;  /* страница p обновлена для спрайта */
            }
        }
        if (mapped)
            _io_page_w0 = _gfx_w0_dss;   /* вернуть ядро DSS */
    }
    _bgi_end();

    gfx_set_bank(saved);
}
