libbgi: спрайтовый движок v2 + accel-блит/heal leaf'ы + noclip-путь

Спрайтовая графика поверх accel block-copy (docs/sprite-api-design.md):

- Ядро блиттинга: leaf'ы _bgi_blit_rows_raw (dst фикс, только src-страйд) /
  _bgi_copy_rows_raw (getimage) / _bgi_heal_rows_raw (src==dst). DI один на
  спрайт (санкция: малый спрайт под одним DI аудио не рвёт); src[0]-фикс
  снят (точная MAME подавляет CPU-байт триггера записи — на железе
  перепроверить; для heal был избыточен и снят безусловно).
- Общие bracket-free ядра _gfx_blit_full/_gfx_heal_full (полная ширина:
  клип по экрану + split >256 для putimage) + лин _gfx_blit_sprite/
  _gfx_heal_sprite (кадр ≤64, без split, 8-бит w/h) + noclip-варианты
  (клип-кода нет → полный codegen-win).  Имя *_full (не *_clip) — «clip»
  двусмысленно (sprite-ядра тоже клипуют; различитель — ширина/split).
- Движок retained-модели <sprite.h>: sprite_init/update/flip + inline
  move/frame/show/hide/touch; drawn[2] per-page внутри структуры; кадр —
  двухпроходно heal ВСЕ -> блит ВСЕ под одной W3-скобкой/банком на проход.
- Флаг gfx_sprite_clip(on/off): приложение, само следящее за границами,
  отключает клип (~+19% на анимации; диспетч пока через if — funcptr далее).
- putsprite/movesprite/gfx_blit/putimage(COPY)/getimage переведены на ядро.

Тесты: examples/balls (движок, дабл-буфер, boundary-тест клипа),
tests/sprites (RAM PASS, клип 4 края, атлас), tests/blitw (trig-leak),
tests/spriteclip (hardware-probe: железо НЕ режет за краем -> клип нужен),
tests/blitperf, tests/gfxbanks. size-baseline обновлён (53 программы).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 21:47:37 +03:00
parent 78161561e7
commit 72ce66275e
42 changed files with 2835 additions and 32 deletions
+97
View File
@@ -0,0 +1,97 @@
/*
* spriteclip — ЭКСПЕРИМЕНТ (гипотеза пользователя 2026-07-12): режет ли
* ЖЕЛЕЗО спрайт, выведенный за ПРАВЫЙ/НИЖНИЙ край экрана, БЕЗ
* программного клипа? Или запись за x>=320 / y>=256 портит соседнюю
* память (следующую строку / страницу 1 / верх экрана)?
*
* Метод: блит 16×16 НЕПРОЗРАЧНОГО белого спрайта мимо клипа — прямым
* вызовом leaf'а _bgi_copy_rows_raw (в обход _gfx_blit_clip). Фон —
* синий; зелёные линии-маркеры на x=319 и y=255 (граница экрана);
* левый край (0..7) и верх (0..7) залиты красным-маркером, чтобы
* поймать «заворот» переполнения именно туда.
*
* Читать скриншот:
* - ПРАВЫЙ (спрайт в x=312): если видно только 8 левых колонок белого
* (x=312..319), а правых 8 нет И красная левая полоса цела →
* железо режет по правому краю. Если правые 8 колонок «завернулись»
* белым в x=0..7 (поверх красного) или появился мусор — НЕ режет,
* клип обязателен.
* - НИЖНИЙ (спрайт в y=250): если видно 6 строк белого (y=250..255),
* а строк за 256 нет И красная верхняя полоса цела → железо режет по
* нижнему. Если белое всплыло сверху (y=0..) — Port_Y завернулся.
* - УГОЛ (x=312,y=250): комбинация.
*
* НЕ для CI-регресса — разовый hardware-probe (и в MAME, и на железе).
*/
#include <graphics.h>
#include <gfx.h>
#include <conio.h>
#include <stdint.h>
/* Внутренние символы libbgi (глобальны в bgi256.lib) — тянем напрямую
* ради no-clip блита. Обычному коду так делать НЕ надо (есть gfx_blit). */
extern void _bgi_begin(void);
extern void _bgi_end(void);
extern void _bgi_copy_rows_raw(const uint8_t *src, uint8_t *dst,
uint8_t w, uint8_t h,
int dstride, int sstride, uint8_t y0);
extern uint16_t _gfx_addr_base;
#define BW 16
static uint8_t sprite[4 + BW * BW];
static void build_sprite(void)
{
uint8_t *p = sprite;
int i;
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
for (i = 0; i < BW * BW; i++)
*p++ = WHITE; /* непрозрачный: каждый байт пишется */
}
/* Блит БЕЗ клипа: адрес = base + x, Port_Y стартует с y, строки шагают
* Port_Y'ем (dstride 0). x/y могут выходить за экран — в этом и суть. */
static void blit_noclip(int x, int y)
{
uint8_t *dst = (uint8_t *)(_gfx_addr_base + x);
gfx_set_bank(GFX_BANK_NORMAL);
_bgi_begin();
_bgi_copy_rows_raw(sprite + 4, dst, BW, BW, 0, BW, (uint8_t)y);
_bgi_end();
}
int main(void)
{
build_sprite();
initgraph();
/* Фон синий. */
setfillstyle(SOLID_FILL, BLUE);
bar(0, 0, 319, 255);
/* Красные маркер-полосы: левые 8 колонок и верхние 8 строк —
* сюда «завернётся» переполнение вправо/вниз, если железо не режет. */
setfillstyle(SOLID_FILL, RED);
bar(0, 0, 7, 255);
bar(0, 0, 319, 7);
/* Зелёные маркеры границы экрана. */
setcolor(GREEN);
line(319, 0, 319, 255);
line(0, 255, 319, 255);
/* Sanity: клипнутый putsprite тем же спрайтом в безопасном месте. */
putsprite(140, 120, sprite);
/* Эксперимент — три no-clip блита за края: */
blit_noclip(312, 100); /* правый: x+16=328, 8 колонок за 320 */
blit_noclip(100, 250); /* нижний: y+16=266, 10 строк за 256 */
blit_noclip(312, 250); /* угол: и вправо, и вниз */
getch();
closegraph();
return 0;
}