libbgi: chunked-заливки на мульти-триггере акселератора + сплит rectfill

Семантика FSM акселератора вскрыта по драйверу MAME (sprinter.cpp) и
подтверждена экспериментами в MAME (tests/accfill): армирование живёт до
следующего accel-опкода (мульти-триггер работает); под армированием
триггерит ЛЮБОЙ non-M1 доступ к памяти (fetch операнда djnz/out!);
вертикальный Fill двигает Port_Y и не возвращает; размер блока переживает
LD B,B.  Детали: memory/accel_multitrigger_fill.

- _bgi_clear_raw: 20 DI-скобок по 16 колонок вместо полного брекета на
  каждую из 320 колонок; армирование размера один раз на скобку.
- _gfx_rectfill256 разделён: диспетчер (проверка ориентации ~160-245Т,
  break-even |w-h| >= ~4) + leaf'ы _gfx_recthfill256/_gfx_rectvfill256
  для прямого вызова, когда форма известна заранее.
- Leaf'ы: чанки <=16 линий одной скобкой (~54-56Т/линию против ~250Т у
  per-line варианта); счётчик чанков precompute'ится в байт-регистр
  (dec e/jr nz ~46Т/чанк вместо 16-бит арифметики в IX-слотах
  ~173Т/чанк); хвост — отдельная скобка со своим армированием (CBL-ISR
  в окне EI армирует акселератор своим размером — не выносить).
- getpixel/putpixel/_bgi_read: уборка мёртвого закомментированного кода.
- tests/accfill: регресс chunked-заливок (B0 clear, B1 vert 1+3 чанка,
  B2 horz чанк+хвост, полноширинный bar 320x8 — путь w>=256).

Прежние реализации сохранены под #if 0 для отката/сравнения.
Проверено в MAME (все PASS); семантика эмуляции — перепроверить на
реальном Sprinter.  size-baseline: accfill добавлен, gfx_demo +54 Б
(обвязка диспетчера), остальные без роста.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 17:41:49 +03:00
parent e553e5e6c9
commit 2c414da465
11 changed files with 617 additions and 49 deletions
+95
View File
@@ -0,0 +1,95 @@
/*
* accfill — регресс chunked-заливок libbgi на мульти-триггере акселератора
* (B0: cleardevice/_bgi_clear_raw; B1: bar → _gfx_rectvfill256, пути
* «1 чанк», «2 чанка + хвост»; B2: bar → _gfx_recthfill256, «чанк + хвост»;
* плюс полноширинный bar 320×8 — precompute-путь w>=256).
*
* Семантика FSM (из драйвера MAME src/mame/sinclair/sprinter.cpp;
* согласуется с примерами docs/part2/accelerator_doc.txt; сырые
* asm-эксперименты E1-E3, подтвердившие её, удалены — см. git history
* 2026-07-10 и memory/accel_multitrigger_fill):
* - армирование (LD C,C / LD E,E / ...) живёт до следующего accel-опкода —
* мульти-триггер поддерживается;
* - под армированием триггерит ЛЮБОЙ non-M1 доступ к памяти: в т.ч. fetch
* immediate/displacement (djnz/jr/ld a,#n) и стек → между триггерами
* допустимы ТОЛЬКО регистровые M1-опкоды, либо выключать (LD B,B);
* - вертикальный Fill двигает Port_Y (+len) и НЕ возвращает; адрес
* константен. Горизонтальный — двигает адрес, Port_Y на месте;
* - OUT — I/O, акселератор не триггерит (но fetch операнда порта — да,
* поэтому OUT делать при выключенном акселераторе);
* - размер блока хранится во внутреннем регистре и переживает LD B,B —
* армировать размер достаточно один раз на скобку.
*
* Верификация: getpixel по всем линиям прямоугольника + нетронутый фон
* в 1px вокруг. Результат (PASS/FAIL) печатается на экране.
*/
#include <graphics.h>
/* Прямоугольник (x0,y0,w,h) залит col целиком, а 1px вокруг — фон (0)?
* Сканируем все колонки в трёх строках и все строки в трёх колонках —
* ловит пропуск отдельного триггера (дырку в колонке/строке). */
static uint8_t rect_ok(int x0, int y0, int w, int h, color_t col)
{
int i;
for (i = 0; i < w; i++) {
if (getpixel(x0 + i, y0) != col) return 0;
if (getpixel(x0 + i, y0 + h / 2) != col) return 0;
if (getpixel(x0 + i, y0 + h - 1) != col) return 0;
}
for (i = 0; i < h; i++) {
if (getpixel(x0, y0 + i) != col) return 0;
if (getpixel(x0 + w / 2, y0 + i) != col) return 0;
if (getpixel(x0 + w - 1, y0 + i) != col) return 0;
}
/* вокруг — нетронутый фон */
if (getpixel(x0 - 1, y0) != 0) return 0;
if (getpixel(x0 + w, y0) != 0) return 0;
if (getpixel(x0, y0 - 1) != 0) return 0;
if (getpixel(x0, y0 + h) != 0) return 0;
return 1;
}
int main(void)
{
uint8_t b0;
initgraph();
cleardevice();
/* B0: cleardevice (chunked _bgi_clear_raw) — углы и центр чистые */
b0 = getpixel(0, 0) == 0 && getpixel(319, 0) == 0 &&
getpixel(0, 255) == 0 && getpixel(319, 255) == 0 &&
getpixel(160, 128) == 0;
setcolor(WHITE);
outtextxy(8, 8, "ACCEL multi-trigger fill test v3");
/* B1: bar → вертикальная ветка rectfill: 1 чанк (16 кол.) и 3 чанка (40) */
setfillstyle(SOLID_FILL, LIGHTBLUE);
bar(16, 60, 31, 123); /* 16×64 */
setfillstyle(SOLID_FILL, YELLOW);
bar(230, 60, 269, 119); /* 40×60 */
/* B2: bar → горизонтальная ветка rectfill: 2 чанка (20 строк) */
setfillstyle(SOLID_FILL, LIGHTMAGENTA);
bar(200, 18, 299, 37); /* 100×20 */
bar(0, 8, 319, 15); /* 320×8 */
outtextxy(8, 132, b0
? "B0 cleardevice chunked: PASS"
: "B0 cleardevice chunked: FAIL");
outtextxy(8, 144, (rect_ok(16, 60, 16, 64, LIGHTBLUE) &&
rect_ok(230, 60, 40, 60, YELLOW))
? "B1 bar vert 1+3 chunks: PASS"
: "B1 bar vert 1+3 chunks: FAIL");
outtextxy(8, 156, rect_ok(200, 18, 100, 20, LIGHTMAGENTA)
? "B2 bar horz 2 chunks: PASS"
: "B2 bar horz 2 chunks: FAIL");
outtextxy(8, 208, "done");
for (;;) { }
}