/*
 * _bgi_heal_rows_raw — специализация accel-копии для heal (src == dst ==
 * адрес экрана): h строк по w байт «прочитать ОЗУ-копию → записать в
 * VRAM+ОЗУ» на одном месте.  Выделен из _bgi_copy_rows_raw по профилю
 * examples/balls (2026-07-12): у heal ОБА страйда нулевые по
 * определению (строку выбирает Port_Y, CPU-адрес не меняется), поэтому
 * адресной арифметики в цикле нет вообще — строка 77Т + burst'ы против
 * 204Т у универсального leaf'а до оптимизации.
 *
 * ВХОД (__sdcccall(1)): scr→HL (base + x), y0→DE (E — стартовый
 * Port_Y); стек: w 4(ix), h 5(ix) — 1..256, 0 = 256 (конвенция
 * акселератора).  Callee-pops 2 байта.
 *
 * Raw: клиппинга нет, W3 замаплен вызывающим; банк выбирает
 * вызывающий (gfx_heal форсит 0x50 — иначе heal бессмыслен).
 * Полосы > 256 байт режет вызывающий.
 *
 * Квирк CPU-байта write-триггера: фикс (preread + EX AF,AF') СНЯТ
 * (2026-07-13).  Для heal он был избыточен ВСЕГДА: банк 0x50 без
 * прозрачности — даже если CPU-байт утёк бы в dst[0], burst тут же
 * перезаписывает его тем же значением из ОЗУ-копии.  Плюс точная
 * dev-MAME показала, что ПЛМ вообще подавляет CPU-байт (см.
 * _bgi_blit_rows_raw).  Закомментированные строки ниже — след старого
 * фикса на случай отличий реального железа.
 *
 * Клоббер: AF/BC/DE/HL; IX сохраняется.
 */

#include "../_bgi.h"

void _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h) __naked
{
    __asm
        push  ix
        ld    ix, #0
        add   ix, sp
        ;; SMC: размер блока <- w (immediate — требование FSM)
        ld    a, 4 (ix)
        ld    (hr_len_imm), a
        ld    c, e                 ; C = y (взять ДО затирания DE)
        ld    b, 5 (ix)            ; B = счётчик строк (0 => 256)
        ld    d, h
        ld    e, l                 ; DE = HL = scr (src == dst)
        ;; ОДИН DI на весь спрайт (arm + все строки): убирает per-row
        ;; EI-окно, где CBL-ISR мог пере-армировать акселератор своим
        ;; размером блока (латентная реентерабельность), и тесней loop.
        ;; Санкция пользователя 2026-07-12: 16×16 = 256 байт под одним DI
        ;; (как 1×256 fill) на аудио не влияет.  ВНИМАНИЕ: h ограничен
        ;; малыми спрайтами — для больших блитов (putimage 256 строк)
        ;; вызывающий обязан резать на чанки ≤16 строк (иначе долгий DI).
        di
        ld    d, d                 ; 0x52 — режим размера блока
        ld    a, #0
    hr_len_imm = . - 1
        ld    b, b                 ; 0x40 — стоп (B=счётчик не тронут)
    hr_row:
        ld    a, c
        out   (#0x89), a           ; Port_Y = y
        inc   c
        ;; ld    a, (hl)              ; предчитать ОЗУ[0] (accel выключен)
        ;; ex    af, af'
        ld    l, l                 ; 0x6D — армировать копию строки
        ld    a, (hl)              ; burst: ОЗУ-копия -> память акселератора
        ;; ex    af, af'              ; A = ОЗУ[0] — CPU-байт триггера корректен
        ld    (de), a              ; burst: память акселератора -> VRAM+ОЗУ
        ld    b, b                 ; 0x40 — стоп
        djnz  hr_row               ; адреса не меняются — только Port_Y
        ei                         ; закрыть DI-окно спрайта
        pop   ix
        pop   hl                   ; ret-адрес; callee-pop 2 байта (w,h)
        inc   sp
        inc   sp
        jp    (hl)
    __endasm;
}
