72ce66275e
Спрайтовая графика поверх 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>
190 lines
6.8 KiB
C
190 lines
6.8 KiB
C
/*
|
||
* sprites — тест Фазы B спрайтового API (docs/sprite-api-design.md §3):
|
||
* putsprite / movesprite / gfx_heal / gfx_blit_part поверх accel-ядра.
|
||
*
|
||
* Сцена: полосатый фон (вертикальные полосы 8px RED/LIGHTGRAY, банк
|
||
* 0x50 → фон и в VRAM, и в ОЗУ-копии). Спрайт — шар 24×24 (YELLOW с
|
||
* RED-ободом), углы = 0xFF (GFX_TRANSPARENT).
|
||
*
|
||
* Стадии (паузы по RTC, скриншот после каждой):
|
||
* S1 фон + разметка.
|
||
* S2 putsprite:
|
||
* - в центре зоны (C) — прозрачные углы: полосы сквозь них;
|
||
* - 4 клип-позиции: слева x=-12, справа x=308, сверху y=-10,
|
||
* снизу y=244 — рисуется только видимая часть, соседняя
|
||
* память не портится;
|
||
* - heal-проба: putsprite в H=(60,130) и тут же gfx_heal того же
|
||
* rect — шара БЫТЬ НЕ ДОЛЖНО (первая проверка heal src==dst
|
||
* через акселератор — риск §10.4 дизайна);
|
||
* - атлас: gfx_blit_part кадра 1 (MAGENTA-шар) из ленты 2×24×24
|
||
* в (270,130) банком GFX_BANK_SPRITE.
|
||
* Проверка A (по ОЗУ, getpixel = ОЗУ-копия): полосы под C целы.
|
||
* S3 movesprite: шар из (16,60) 12 шагов по (+16,+8) до (208,156) —
|
||
* след обязан быть чистым (heal старой позиции на каждом шаге).
|
||
* Проверка B: ОЗУ вдоль пути — полосы.
|
||
*
|
||
* VRAM программно не читается (write-only) — корректность экрана
|
||
* подтверждают скриншоты; программные проверки ловят порчу ОЗУ-копии.
|
||
*
|
||
* Запуск:
|
||
* python3 toolchain/mame_interactive.py tests/sprites/sprites.exe \
|
||
* --snap 13,17,21 --timeout 23
|
||
*/
|
||
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <time.h>
|
||
|
||
#define BW 24 /* сторона спрайта */
|
||
#define BG_Y0 40
|
||
#define BG_Y1 199
|
||
|
||
#define CX 150 /* центральный putsprite */
|
||
#define CY 100
|
||
|
||
static uint8_t ball[4 + BW * BW]; /* YELLOW шар */
|
||
static uint8_t strip[4 + 2 * BW * BW]; /* лента: кадр0 YELLOW, кадр1 MAGENTA */
|
||
|
||
static uint8_t stripe_color(int x)
|
||
{
|
||
return ((x >> 3) & 1) ? LIGHTGRAY : RED;
|
||
}
|
||
|
||
/* Шар: r<=9 — fill, r 9..11 — ring, дальше прозрачно (0xFF). */
|
||
static uint8_t ball_pixel(int x, int y, uint8_t fill, uint8_t ring)
|
||
{
|
||
int dx = x - BW / 2, dy = y - BW / 2;
|
||
int d2 = dx * dx + dy * dy;
|
||
|
||
if (((x == 0 || x == BW - 1) && ((y >=0 && y <= 3) || (y >=BW - 4 && y <= BW - 1))) ||
|
||
((y == 0 || y == BW - 1) && ((x >=0 && x <= 3) || (x >=BW - 4 && x <= BW - 1)))) return GREEN;
|
||
if (x==0) return GFX_TRANSPARENT;
|
||
if (d2 <= 81) return fill;
|
||
if (d2 <= 121) return ring;
|
||
return GFX_TRANSPARENT;
|
||
}
|
||
|
||
static void build_images(void)
|
||
{
|
||
uint8_t *p;
|
||
int x, y, f;
|
||
|
||
p = ball;
|
||
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||
for (y = 0; y < BW; y++)
|
||
for (x = 0; x < BW; x++)
|
||
*p++ = ball_pixel(x, y, YELLOW, RED);
|
||
|
||
/* Лента 48×24: кадры лежат рядом по x (кадр N: sx = N*BW). */
|
||
p = strip;
|
||
*p++ = 2 * BW; *p++ = 0; *p++ = BW; *p++ = 0;
|
||
for (y = 0; y < BW; y++)
|
||
for (f = 0; f < 2; f++)
|
||
for (x = 0; x < BW; x++)
|
||
*p++ = ball_pixel(x, y, f ? LIGHTMAGENTA : YELLOW,
|
||
f ? BLUE : RED);
|
||
}
|
||
|
||
/* ОЗУ-копия (getpixel) в трёх точках зоны 24×24 — нетронутые полосы? */
|
||
static uint8_t ram_is_stripes(int zx, int zy)
|
||
{
|
||
static const uint8_t off[3][2] = { {12, 12}, {8, 16}, {16, 6} };
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < 3; i++) {
|
||
int x = zx + off[i][0];
|
||
if (getpixel(x, zy + off[i][1]) != stripe_color(x))
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static void pause_sec(uint8_t n)
|
||
{
|
||
datetime_t dt;
|
||
uint8_t last;
|
||
|
||
getdatetime(&dt);
|
||
last = dt.second;
|
||
while (n) {
|
||
getdatetime(&dt);
|
||
if (dt.second != last) {
|
||
last = dt.second;
|
||
n--;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void say(int y, const char *s)
|
||
{
|
||
outtextxy(4, y, s);
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
uint8_t chkA, chkB;
|
||
int x, i;
|
||
|
||
build_images();
|
||
initgraph();
|
||
cleardevice();
|
||
gfx_sprite_clip(1);
|
||
setcolor(WHITE);
|
||
|
||
/* ---- S1: фон -------------------------------------------------- */
|
||
say(4, "SPRITES: putsprite/movesprite/heal");
|
||
for (x = 0; x < 320; x += 8) {
|
||
setfillstyle(SOLID_FILL, stripe_color(x));
|
||
bar(x, BG_Y0, x + 7, BG_Y1);
|
||
}
|
||
say(208, "S1 bg");
|
||
(void)getchar();
|
||
|
||
/* ---- S2: putsprite + клипы + heal-проба + атлас ---------------- */
|
||
putsprite(CX, CY, ball); /* центр: прозрачные углы */
|
||
(void)getchar();
|
||
putsprite(-12, 52, ball); /* клип слева */
|
||
(void)getchar();
|
||
putsprite(308, 82, ball); /* клип справа */
|
||
(void)getchar();
|
||
putsprite(297, 162, ball); /* клип справа */
|
||
(void)getchar();
|
||
putsprite(60, -10, ball); /* клип сверху */
|
||
(void)getchar();
|
||
putsprite(290, 244, ball); /* клип снизу */
|
||
(void)getchar();
|
||
putsprite(60, 130, ball); /* heal-проба: нарисовать... */
|
||
(void)getchar();
|
||
gfx_heal(60, 130, BW, BW); /* ...и стереть (шара НЕТ) */
|
||
(void)getchar();
|
||
gfx_set_bank(GFX_BANK_SPRITE); /* атлас: кадр 1 (MAGENTA) */
|
||
(void)getchar();
|
||
gfx_blit_part(270, 130, strip, BW, 0, BW, BW);
|
||
(void)getchar();
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
(void)getchar();
|
||
|
||
chkA = ram_is_stripes(CX, CY);
|
||
say(218, "S2 putsprite+clip, heal@60,130");
|
||
say(228, chkA ? "A RAM under sprite: PASS"
|
||
: "A RAM under sprite: FAIL");
|
||
pause_sec(4);
|
||
|
||
/* ---- S3: movesprite — след обязан быть чистым ------------------ */
|
||
x = 16;
|
||
putsprite(x, 60, ball);
|
||
(void)getchar();
|
||
for (i = 0; i < 12; i++) {
|
||
movesprite(x, 60 + (x - 16) / 2, x + 16, 60 + (x - 16 + 16) / 2,
|
||
ball);
|
||
x += 16;
|
||
(void)getchar();
|
||
}
|
||
chkB = ram_is_stripes(16, 60) && ram_is_stripes(112, 108);
|
||
say(238, chkB ? "B RAM along path: PASS"
|
||
: "B RAM along path: FAIL");
|
||
say(248, "S3 moved 16,60 -> 208,156 done");
|
||
|
||
(void)getchar();
|
||
}
|