/*
 * _spr_ysort — ПЕРСИСТЕНТНАЯ таблица порядка отрисовки спрайтов для
 * блит-прохода sprite_update (gfx_sprite_ysort): 4-байтные записи
 * {key16, ptr16}, отсортированные вставками по возрастанию ключа.
 * Возвращает адрес таблицы.
 *
 * КЛЮЧ = {layer, y}: старший байт — слой спрайта (слой 1 всегда поверх
 * слоя 0 — ходячие/летающие/курсор), младший — clamp(y, 0, 255)
 * (идея пользователя: экран 256 строк — вся видимая зона сортируется
 * точно, за краями порядок неважен, перекрытие невидимо).  Компоненты
 * отключаемы независимо (YSORT_Y/YSORT_LAYER): маски _spr_ysort_kmask
 * затирают выключенный байт ключа — без ветвлений в горячем цикле.
 *
 * ПЕРСИСТЕНТНОСТЬ (идея пользователя): таблица живёт МЕЖДУ кадрами.
 * Каждый вызов — «resort»: для каждой записи ключ пересчитывается по
 * её спрайту и запись вставляется в отсортированный префикс.  Спрайты
 * двигаются на пиксели за кадр → порядок прошлого кадра почти актуален
 * → вставки вырождаются в одно сравнение с соседом (почти линейно).
 * Смена сцены (arr/count != прошлых, см. _spr_ysort_last/_lastn) —
 * rebuild: заполнить ptr-поля из массива и прогнать тот же resort
 * (первый кадр — полная сортировка, разово дороже).  МАССИВ ПРИЛОЖЕНИЯ
 * НЕ ТРОГАЕТСЯ — сортируются только внутренние записи-указатели.
 *
 * Стабильность: равные ключи сохраняют порядок ПРОШЛОГО КАДРА (спрайты
 * на одной высоте не мерцают взаимным порядком, пока реально не
 * разойдутся).
 *
 * C-эквивалент:
 *   if (arr != last || count != lastn) {       // новая сцена
 *       for (i = 0; i < count; i++) tab[i].ptr = &arr[i];
 *       last = arr; lastn = count;
 *   }
 *   for (i = 0; i < count; i++) {              // resort
 *       p   = tab[i].ptr;
 *       key = (p->layer << 8) | clamp8(p->y);
 *       for (pos = i; pos && tab[pos-1].key > key; pos--)
 *           tab[pos] = tab[pos-1];
 *       tab[pos] = {key, p};
 *   }
 *   return tab;
 *
 * Офсеты: y = +8/9, layer = +56 в sprite_t, sizeof(sprite_t) = 57 —
 * сверять пробником при правке структуры (рецепт в шапке tick_move).
 * Вход __sdcccall(1): arr → HL, count → DE (используется E; count
 * ограничен _GFX_YSORT_MAX вызывающим); ВОЗВРАТ — адрес таблицы (DE).
 * IX callee-saved.
 */
#include "../_bgi.h"

/* Данные модуля (без вызова gfx_sprite_ysort не линкуются вместе с
 * кодом).  _spr_ysort_last обнуляет сеттер при включении — форсирует
 * rebuild.  НЕ инициализировать (crt0 зануляет). */
uint8_t _gfx_ysort_tab[4u * _GFX_YSORT_MAX];
void   *_spr_ysort_last;                  /* arr прошлого кадра   */
uint8_t _spr_ysort_lastn;                 /* count прошлого кадра */
uint8_t _spr_ysort_kmask[2];              /* маски ключа (сеттер): *
                                           * [0] & y, [1] & layer  */

uint8_t *_spr_ysort(void *arr, uint16_t count) __naked
{
    (void)arr; (void)count;
    __asm
        push  ix
        ld    a, e
        or    a, a
        jr    NZ, ys_have_sprites
        ;; count == 0: вернуть таблицу как есть (сцену не запоминаем)
        ld    de, #__gfx_ysort_tab
        pop   ix
        ret

ys_have_sprites:
        ;; ---- та же сцена, что кадр назад? (arr и count совпадают) ----
        ld    a, (__spr_ysort_lastn)
        cp    a, e
        jr    NZ, ys_rebuild
        ld    bc, (__spr_ysort_last)
        ld    a, c
        cp    a, l
        jr    NZ, ys_rebuild
        ld    a, b
        cp    a, h
        jr    Z, ys_resort_entry  ; сцена та же — только resort

ys_rebuild:
        ;; ---- новая сцена: запомнить и заполнить ptr-поля записей ----
        ld    (__spr_ysort_last), hl
        ld    a, e
        ld    (__spr_ysort_lastn), a
        ld    b, a                ; B = count
        ld    de, #__gfx_ysort_tab + 2  ; DE = &tab[0].ptr
ys_fill:
        ld    a, l
        ld    (de), a
        inc   de
        ld    a, h
        ld    (de), a
        inc   de
        inc   de
        inc   de                  ; DE = &tab[i+1].ptr
        ld    a, l                ; cur += sizeof(sprite_t) = 57
        add   a, #57
        ld    l, a
        jr    NC, ys_fill_nc
        inc   h
ys_fill_nc:
        djnz  ys_fill

ys_resort_entry:
        ;; ---- resort: обновить ключи и пересортировать вставками ----
        ld    a, (__spr_ysort_lastn)
        ld    b, a                ; B = счётчик записей
        ld    hl, #__gfx_ysort_tab ; HL = R (текущая запись)

ys_next_rec:
        push  bc                  ; [счётчик]
        push  hl                  ; [R]
        ;; BC = P (спрайт записи)
        inc   hl
        inc   hl
        ld    c, (hl)
        inc   hl
        ld    b, (hl)
        ;; ---- ключ: E = clamp8(P->y), D = P->layer ----
        ld    hl, #8
        add   hl, bc              ; HL = &P->y
        ld    e, (hl)
        inc   hl
        ld    a, (hl)             ; y hi
        or    a, a
        jr    Z, ys_key_y_done    ; y в 0..255 — точный ключ
        ld    e, #0
        bit   7, a
        jr    NZ, ys_key_y_done   ; y < 0  → 0
        ld    e, #255             ; y > 255 → 255
ys_key_y_done:
        ld    hl, #56
        add   hl, bc              ; HL = &P->layer
        ld    d, (hl)             ; key = layer:8 | y:8 (DE)
        ;; маски компонентов (YSORT_Y/YSORT_LAYER): выключенный — в 0
        ld    a, (__spr_ysort_kmask + 0)
        and   a, e
        ld    e, a
        ld    a, (__spr_ysort_kmask + 1)
        and   a, d
        ld    d, a
        ;; стек к скану: [счётчик][R][P], HL = pos = R
        pop   hl                  ; R
        push  hl
        push  bc                  ; P

ys_scan:
        ;; pos == tab? → место в голове
        ld    a, l
        cp    a, #<(__gfx_ysort_tab)
        jr    NZ, ys_cmp_prev
        ld    a, h
        cp    a, #>(__gfx_ysort_tab)
        jr    Z, ys_place
ys_cmp_prev:
        ;; key < prev.key (prev = pos-4)? — сдвиг prev вправо и шаг
        ;; влево; key >= prev.key (включая равные — стабильность) —
        ;; место найдено.
        push  hl
        ld    a, l
        sub   a, #4
        ld    l, a
        jr    NC, ys_prev_nc
        dec   h
ys_prev_nc:                       ; HL = prev
        ld    a, e
        sub   a, (hl)             ; key.lo - prev.lo
        inc   hl
        ld    a, d
        sbc   a, (hl)             ; CF = key < prev.key (16-бит)
        pop   hl                  ; HL = pos
        jr    NC, ys_place
        ;; сдвиг: копировать prev → pos (4 байта), pos = prev
        push  de                  ; key
        ld    d, h
        ld    e, l                ; DE = pos (dst)
        ld    a, l
        sub   a, #4
        ld    l, a
        jr    NC, ys_shift_nc
        dec   h
ys_shift_nc:                      ; HL = prev (src)
        push  hl                  ; prev — станет новым pos
        ld    bc, #4
        ldir                      ; prev → pos, 4 байта
        pop   hl                  ; pos = prev
        pop   de                  ; key
        jr    ys_scan

ys_place:
        ;; записать {key, P} в pos (HL)
        ld    (hl), e
        inc   hl
        ld    (hl), d
        inc   hl
        pop   bc                  ; P
        ld    (hl), c
        inc   hl
        ld    (hl), b
        ;; продвинуть R и счётчик
        pop   hl                  ; R
        ld    a, l
        add   a, #4
        ld    l, a
        jr    NC, ys_adv_nc
        inc   h
ys_adv_nc:
        pop   bc                  ; счётчик
        dec   b
        jp    NZ, ys_next_rec

        ld    de, #__gfx_ysort_tab ; вернуть адрес таблицы (ptr в DE)
        pop   ix
        ret
    __endasm;
}
