/*
 * _bgi_scroll_rows_raw — БЫСТРАЯ построчная копия video->video со сдвигом
 * ТОЛЬКО по X для gfx_scroll_h (mode 0x81).  Специализация _bgi_copy_rows_raw
 * для скролла: обе стороны — видео, ОБА страйда НУЛЕВЫЕ (CPU-адрес строки
 * фиксирован, строку выбирает Port_Y), поэтому адресной арифметики в цикле
 * НЕТ ВООБЩЕ — ни страйдовых add (как у copy_rows), ни даже inc адресов
 * (как у heal).  От _bgi_heal_rows_raw отличается тем, что src != dst
 * (HL — колонка-старт src-страницы, DE — dst-страницы со сдвигом dx):
 * горизонтальный сдвиг целиком в разнице адресов, задаётся вызывающим.
 *
 * Вертикального сдвига НЕТ (read и write одной строки на одном Port_Y) —
 * для сдвига по Y есть _bgi_scroll_cols_raw (колонки, вертикальный режим).
 * Зато rows на ~5-7% быстрее cols: на строку один OUT Port_Y (54Т/строку),
 * а cols нужен OUT на чтение И на запись колонки.
 *
 * ПОРЯДОК на строку: {OUT Port_Y · arm(LD L,L) · read(LD A,(HL)) ·
 * write(LD (DE),A) · STOP(LD B,B)}.  Между read- и write-триггером —
 * НИЧЕГО: на Sprinter accel перехватывает любое чтение программной памяти,
 * а fetch операнда любой инструкции там (immediate у OUT/ADD) спурьезно
 * перезапустил бы read-burst и набил буфер кодом.  Port_Y ставится ДО
 * arm'а (accel разоружён STOP'ом прошлой строки — операнд OUT безопасен).
 * (memory/accel_operand_fetch_retrigger.)
 *
 * DI/EI — БАНДАМИ по 16 строк ВНУТРИ (как _gfx_recthfill256): накрывать DI
 * весь сдвиг нельзя (порвёт CBL-звук).  Армирование размера блока — в
 * КАЖДОЙ скобке (в окне EI CBL-ISR армирует accel своим размером).  Число
 * полных 16-банд и хвост считаем ОДИН раз на входе (precompute) — в петле
 * банд остаётся дешёвый dec (без per-band `sub 16` через IX-слот).
 * Регистры упёрты (HL src, DE dst, B djnz, C бегущий Port_Y) — быстрее уже
 * некуда: out(c) без стопов потребовал бы Port_Y-регистра, которого нет.
 *
 * ВХОД (__sdcccall(1)): src->HL, dst->DE; стек: w 4(ix) — байт в строке
 * (размер accel-блока, 1..256, 0=256), h 5(ix) — строк 1..256 (0 = 256,
 * КОНВЕНЦИЯ accel — как у w), y0 6(ix) — стартовый Port_Y (инкремент на
 * строку).  Callee-pop 3 байта.
 *
 * Raw: клиппинга нет (вызывающий), W3 замаплен (_bgi_begin/end снаружи),
 * банк 0x50 (запись бьёт VRAM+тень — скролл И heal цели).  Перекрытия
 * src/dst нет by design — стороны в РАЗНЫХ видеостраницах.  Строку >256
 * байт режет вызывающий.  Клоббер: AF/BC; HL/DE/IX сохраняются
 * (адреса-константы не трогаются — LD L,L/LD D,D это accel-опкоды-no-op'ы).
 */

#include "../_bgi.h"

void _bgi_scroll_rows_raw(uint16_t src, uint16_t dst,
                          uint8_t w, uint8_t h, uint8_t y0) __naked {
    (void)src; (void)dst; (void)w; (void)h; (void)y0;
    __asm
        push  ix
        ld    ix, #0
        add   ix, sp
        ;; HL = src, DE = dst (адреса-константы; строку задаёт Port_Y)
        ld    a, 4 (ix)
        ld    (sc_row_len), a           ; SMC: размер блока <- w (0 = 256)
        ld    (sc_row_len2), a          ; ...и в хвостовую скобку
        ld    c, 6 (ix)                 ; C = бегущий Port_Y (y0)

        ;; --- precompute: полных 16-банд (ix+5) + хвост (SMC); h=0 => 256 ---
        ld    a, 5 (ix)
        and   #0x0F
        ld    (sc_row_tail), a          ; хвост = h & 15 (0 и при h=0, и при h=256)
        ld    a, 5 (ix)
        or    a, a
        jr    NZ, sc_row_shr
        ld    a, #16                    ; h=0 => 256 => ровно 16 полных банд
        jr    sc_row_setfull
    sc_row_shr:
        rrca
        rrca
        rrca
        rrca
        and   #0x0F                     ; full = h >> 4
    sc_row_setfull:
        ld    5 (ix), a                 ; ix+5 = число полных 16-строчных банд

    sc_row_band:
        ld    a, 5 (ix)
        or    a, a
        jr    Z, sc_row_tailband        ; полные банды кончились
        dec   5 (ix)                    ; full -= 1
        ld    b, #16                    ; 16 строк в банде
        di
        ld    d, d                      ; 0x52 — режим размера блока (no-op для D)
        ld    a, #0
    sc_row_len = . - 1
        ld    b, b                      ; выключить — размер сохранён (B=16 цел)
    sc_row_next:
        ld    a, c
        out   (#0x89), a                ; Port_Y = y (accel ВЫКЛЮЧЕН — операнд безопасен)
        inc   c                         ; y++
        ld    l, l                      ; 0x6D — арм гориз. копии (no-op для L)
        ld    a, (hl)                   ; read-burst: src-строка -> буфер
        ld    (de), a                   ; write-burst: буфер -> dst-строка (вплотную!)
        ld    b, b                      ; 0x40 — стоп
        djnz  sc_row_next
        ei                              ; окно прерываний между бандами
        jr    sc_row_band
        ;; --- хвост h&15 строк: отдельная скобка со СВОИМ армированием ---
    sc_row_tailband:
        ld    a, #0
    sc_row_tail = . - 1
        or    a, a
        jr    Z, sc_row_done            ; хвоста нет
        ld    b, a                      ; B = остаток строк (1..15)
        di
        ld    d, d
        ld    a, #0
    sc_row_len2 = . - 1
        ld    b, b
    sc_row_trow:
        ld    a, c
        out   (#0x89), a
        inc   c
        ld    l, l
        ld    a, (hl)
        ld    (de), a
        ld    b, b
        djnz  sc_row_trow
        ei
    sc_row_done:
        pop   ix
        ;; callee-pop 3 байта (w, h, y0)
        pop   hl                        ; ret-адрес
        pop   af
        inc   sp
        jp    (hl)
    __endasm;
}
