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