Ревью на 32-бит сделан ПО ASM, а не по коду (искали и безымянные
временные): во всём приложении был ровно ОДИН 32-битный вызов —
__mullong в pop_prandom. Замер в MAME: 8 430 тактов на вызов, два
вызова за кадр. Прочие библиотечные вызовы 16-битные (__divsint 16,
__modsint 12, __moduchar 8, __divuchar 5).
1. pop_prandom. Состояние 32-бит -> две 16-битные половины. Два
генератора, выбор через POP_PRANDOM_EXACT:
- 0 (по умолчанию) — xorshift16 + шаг Вейля, без единого умножения;
- 1 — LCG оригинала бит-в-бит, посчитанный половинами (для сверки
картинки с эталоном).
8-битный RND Apple II (5*x+23 mod 256) НЕ взят: у LCG по модулю 256
вырождены младшие биты (бит 0 просто чередуется), а раскладка кладки
берёт как раз prandom(1) и prandom(4) — вместо шума вышла бы
правильная шахматка. Шаг Вейля ещё и убирает ноль как неподвижную
точку xorshift (сид кладки вполне может быть нулём).
Бит-в-бит эквивалентность half-word версии проверена на хосте:
70 000 сидов x 8 шагов + 7 крайних сидов x 2000 шагов.
Остаток 0..maxv: делитель степень двойки — маска вместо __moduint.
2. libbgi: gfx_blit_cols_part_noclip — column-major блит без клипа
(пара к gfx_blit_cols_part, как gfx_blit_part_noclip к
gfx_blit_part). Клипающий вариант платит ~5 622 такта подготовки на
КАЖДЫЙ вызов независимо от того, вылезает край (замер: подготовка
5 622 против 13 596 на сам accel-проход). Kid, страж и клинок
выбирают путь по pop_onscreen_cols. size-check: роста нет.
Бюджет (175 кадров, комната 3, медиана):
было (после клинка) 416 154 0.968 кадра
стало 397 986 0.926 кадра
Разница между генераторами, замер на одинаковой сборке:
xorshift16 + Вейль 397 986 prandom->torch_draw 7 927
бит-в-бит LCG 403 632 prandom->torch_draw 10 933
то есть точность обходится в 5 646 тактов за кадр (1.3 %).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Жалоба: стойка неподвижных Кида и стража занимала больше полутора
кадровых периодов. Гипотеза «виноват __banked» ЗАМЕРОМ НЕ
ПОДТВЕРДИЛАСЬ: весь банковый вызов (трамплин + смена страницы + тело
pop_guard_tick + возврат) стоит 654 такта при бюджете кадра 430 000.
Как мерил (выборка PC бесполезна — мост MAME отвечает из фреймового
колбэка, все сэмплы падают в обработчик прерывания): breakpoint'ы MAME с
действием {printf totalcycles; g} на входах фаз главного цикла, разности
соседних меток = стоимость фазы. Плюс профилирование полосами бордюра
(make PROF=1, макрос PROF() в roomtest.c) для быстрого взгляда.
Замер комнаты 3 (Kid + страж), такты, кадр = 430 000:
fore поверх стража 431 964
fore поверх Kid 402 816 -> 78 % всей работы кадра
остальное 241 956
ИТОГО 1 076 736 = 2.5 кадра
Две причины, обе устранены:
1. Fore-слой рисовал ЦЕЛЫЕ тайлы, хотя существует ровно для того, чтобы
вернуть куски поверх спрайта — за его прямоугольником в видеопамяти и
так правильный фон. Введено ОКНО клипа (pop_fore_set_clip): спрайт
сообщает свой итоговый габарит (у Kid — с клинком, брызгами и
обрезкой clip_char), fore-проход режет по нему. Отсев трёхступенчатый:
тайл целиком (tile_in_fclip, до обращения к атласу), кусок по грубому
габариту (до gfx_w0_map — w/h лежат в EMM-странице), и точный клип в
blit_b. Для последнего добавлен libbgi-примитив
gfx_blit_part_noclip — пара к gfx_blit_noclip, но под-прямоугольник.
2. Оставшиеся 341 К после клипа оказались НЕ пикселями: 18 вызовов
pop_prandom за проход, ~10 700 тактов каждый (32-битный LCG:
__mullong ~8 000 + __moduint). Раскладка кладки тайла — чистая
функция (комната, ряд, колонка), то есть константа комнаты, а
wall_pattern пересчитывал её каждый кадр. Теперь кэшируются готовые
РЕШЕНИЯ (что рисовать и с каким смещением), 3 байта на тайл, сброс в
pop_room_draw. Порядок вызовов prandom воспроизведён один в один,
включая то, что значение метки берётся только при сработавшем условии.
Итог того же замера: fore поверх стража 37 464, поверх Kid 46 464,
кадр целиком 334 716 = 0.78 периода (было 2.5). Ускорение 3.2x, сами
fore-проходы — 10x.
Проверка отсутствия регрессии: попиксельная разность скриншотов комнат
1/2/3 до и после — отличаются ТОЛЬКО языки пламени факелов (анимация),
кладка и метки совпадают байт в байт.
Побочно: sprinter-cc научился пробрасывать -DNAME в sdcc.
Память: куча W2 1245 -> 996 Б (кэш кладки 120 Б), резидент W3 12 819 ->
14 512 (свободно 1872 Б — становится тесно), банк 1 236/16384.
ВНИМАНИЕ: make size-check показывает рост 7 программ, но эталон
docs/size_baseline.tsv отстал (последний раз принят в 484b18d, libbgi
менялась в 95c22be/c127a4b/64ce633) — к этой правке рост отношения не
имеет: новый модуль библиотеки в чужие программы не линкуется.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- docs/new/ — сводные справочники (архитектура, BIOS, DSS, память,
графика, акселератор, IRQ, порты, ввод, звук, известные баги);
- docs/Original/ — первоисточники, из которых они собраны (BIOS, Estex
DSS, мануалы, описание акселератора), + Форум.doc/.docx в reference;
- libbgi/common/gfx_scroll_h.c — обход бага скролла при ширине >256
(правка автора: шаг банды 255 и продвижение указателей на cw; старый
вариант с 256 оставлен закомментированным с TODO);
- удалён applications/PoP/roomtest/hang_variants.png — рабочая раскладка
из разбора позы виса, в репозитории ей не место.
Большие архивы (docs/extra ~568 МБ, docs/sources с вложенными git-репо
~68 МБ) в коммит НЕ включены — см. обсуждение.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Спуск Кида с кнопки (room8, кромка (0,6)) рисовался неверно: Кид просвечивал
в щель между кнопкой и ближним столбом, а ближняя рука была срезана до одного
оторванного пикселя. Две независимые причины.
1. Не был портирован clip_char() (seg006:1749) — оригинал перед add_objtable
обрезает спрайт персонажа сверху по y_clip[curr_row+1], когда тайл над
головой стена или пол. Порт: pop_clip_char_top() (pop_map.c, метрики по
set_char_collision) + новый примитив gfx_blit_cols_part() в libbgi (блит
column-major с пропуском skip верхних строк; обрезка сверху бесплатна —
колонка непрерывна в ОЗУ, сдвигается только старт). gfx_blit_cols стал
тонкой обёрткой над ним. kid_heal чистит уже ОБРЕЗАННЫЙ прямоугольник,
иначе стирается кромка пола над срезом.
2. climb_overlay_tile (порт draw_floor_overlay, seg008:1E3A) выбирал ветку по
СЫРОМУ коду тайла, а get_tile_to_draw (seg008:240) подменяет нажатую кнопку
на floor/stuck. Тайл-кнопка не проходил тест floor → уходил в
draw_other_overlay, который кладёт поверх Кида ВЕСЬ тайл вместо узкой
кромки floor_left_overlay[frame-137]. Фикс — tile_code_drawn(): одна
подстановка на все слои (fore_only_tile/overlay_mid_tile перестали её
дублировать, W3 −293 Б).
Проверено покадрово в MAME (шаг gv + снимок): кадры спуска 148..138 чистые,
после приземления на кнопке мусора нет.
Заодно: gfx_heal_noclip() (пара к gfx_blit_noclip; heal 11658 -> 8982 тактов)
и rest_pending в pop_trob — холостой проход по 30 тайлам только когда есть
отложенные редрои пик/кнопок (pop_process_trobs 89-111К -> 70-92К тактов).
START_ROOM временно = 8 (отладка спуска с кнопки), вернуть на 6/старт уровня.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Графика chtab_1 (flame/sword/potion) — новый атлас pop_pot.atl:
- склянка зелья берётся из chtab_1 (seg008 draw_tile_fore: id 12 малая /
13 большая при типах 2..4), а НЕ chtab_6 id 12 — res212 в VDUNGEON нет,
каскад падал на VPALACE и рисовал кусок дворцовой декорации («мусорный
объект» в комнате 5);
- пакуем chtab_1 целиком (id 1..23): пламя, склянки, кадры пузырька;
- MONO-блит (method_3_blit_mono, seg009:3040) красит спрайт цветом из
ОБЩЕЙ 16-цветной палитры экрана — она добавлена в 0x30..0x3F, палитра
chtab_1 в 0x40..0x4F; пузырёк пакуется силуэтом цвета 12 (красный),
его маска — цвета 0;
- в env-атлас добавлено основание факела (env 146, seg008:489 — рисует
правый сосед); в статический render_room оно не попадало.
Анимация (seg007 animate_torch/animate_potion + seg000:0B12
anim_tile_modif): при входе в комнату факелам и зельям задаётся случайная
стартовая фаза и заводится trob; факел — get_torch_frame, пламя в ячейке
ПРАВОГО соседа (xh = draw_xh+1, y = draw_main_y−40); зелье —
bubble_next_frame по младшим 3 битам модификатора (старшие 5 = тип, при
загрузке уровня modif <<= 3, seg009). Пламя и пузырёк не запекаются в
фон: heal своей области + кадр поверх. Скорость пламени /2 — наш
логический кадр короче игрового тика оригинала.
Скорость отрисовки (профиль в MAME, кадр = 430 080 тактов):
- замер показал, что цена блита почти НЕ зависит от размера — 13 288
тактов на спрайт 32×3 против 4 617 у линейного спрайтового ядра без
клипа; платим за проход gfx_blit → gfx_blit_part → _gfx_blit_full;
- в libbgi добавлен gfx_blit_noclip() — блит без клипа в ТЕКУЩЕМ банке
(putsprite не годится: навязывает GFX_BANK_SPRITE, фону нужен
TRANSPARENT ради ОЗУ-копии); pop_bg.blit_b уходит на него, когда
спрайт целиком на экране и не нужен g_clip_top → ~2.9× на блит;
- W3-скобку ставит САМА libbgi: из модуля с --w3 её вызывать нельзя —
после _bgi_begin окно W3 занято видеобанком и код вызывающего исчезает
(проявлялось белым экраном);
- редрой шва больше не перерисовывает полосу потолка (bar начинается с
POP_YOFF+3) — это удваивало стоимость блока при анимации решётки;
- docs/size_optimization_plan.md §7–§8: замеры, сделанное и запас
(батчинг W3-скобки, решётка одним спрайтом, лишние блиты).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Скролл региона video->video (неактивная страница -> активная, банк 0x50:
копия = скролл + heal цели):
- gfx_scroll_h / _bgi_scroll_rows_raw — горизонтальный, построчно без
страйдов (~54Т/строку), DI/EI бандами по 16 строк, h=0=>256;
- gfx_scroll_v / _bgi_scroll_cols_raw — верт. И/ИЛИ гориз. за один проход
без буфера (колонка = accel-burst LD A,A, STOP между read/write делает
промежуточный OUT Port_Y безопасным), банды по 16 колонок;
- _gfx_addr_shadow_base (адрес неактивной страницы) + gfx_rect_t.
Пример examples/scroll.
check_banks.py + sprinter-cc: отчёт раскладки памяти для ЛЮБОЙ модели
(W1/W2 код/данные, остаток кучи/стека, W3 при --w3, банки при --bank),
не только при --bank. Док docs/memory-management.md §10.
--w3 (резидентный код окна W3) + сопутствующее: crt0_banked W3_RESIDENT,
mkexe -W, tests/w3probe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Логический кадр = ровно n кадровых интервалов (1=50/2=25/3=~16.7 fps);
при переполнении слота — выравнивание на ближайший фронт (без дрейфа
фазы, в отличие от наивного «жди n фронтов»).
Механика: фоновый счётчик _gfx_frame_tick инкрементит _gfx_frame_isr,
поставленный в СВОЙ слот цепи (irq_chain_add); gfx_set_fps_div(1) снимает
только этот слот (irq_chain_remove), не трогая хендлер приложения.
gfx_wait_vsync: ветка n>=2 (счётчик + halt) перед лучевым поллингом;
поллинг вынесен в static gfx_wait_vsync_beam (функция с хвостовым __asm
не должна иметь переходов через asm — SDCC не эмитит эпилог-метку;
ранний return делителя в чистом-C gfx_wait_vsync).
Файлы: common/_gfx_fps_state.c (данные), _gfx_frame_isr.c (ISR),
gfx_set_fps_div.c (сеттер, единственная ссылка на irq-механику → DCE).
Работает tiny/big/huge (цепочка all-modes); small для мелких программ
= EINVAL.
Проверено MAME (tests/fpsdiv): n=1/2/3 → 20/40/60 кадров на 20 wait'ов
(drift=0); n=2 с рендер-заглушкой ~1 кадр → период держится 2
(поглощение перерасхода, наивный путь дал бы ~60); huge идентично;
small = EINVAL graceful.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Оптимизации A+B (профиль rpgwalk-15: активная часть кадра 410К → 307К
тактов из 430080; лимит спрайтов 16×16 на стабильные 48.8 fps: 14 → ~21):
- (B) sprite_t.src/stride — готовый адрес кадра: считают только
sprite_frame (теперь функция, одно умножение на СМЕНУ кадра) и тикер
(±an_step БАЙТ инкрементально); блит-ядра принимают src+stride,
img/sx/sy из сигнатуры ушли. Блит 177К → 146К на кадр.
- (A) тик 100К → 37.7К: tween переформулирован Брезенхэм → беззнаковый
DDA (mv_rem/mv_acc, «приехали» = rem==0 — без знаковых 16-бит
сравнений), tick_move и tick_anim — ручной asm (SDCC спиллит такие
функции в IX-фрейм ~100 обращений; C-реструктуризации не помогали —
проверено кодогеном). Биты an_flags переименованы по категориям
(_SPR_STRIP_HORZ, _SPR_PP_BACK).
Y-сортировка (gfx_sprite_ysort, идеи пользователя — 8-бит ключ,
персистентность):
- painter's algorithm по ключу {layer:8, clamp_y:8}; поле
sprite_t.layer (в КОНЦЕ структуры — asm-офсеты не сдвигает): слои
сцены в одном массиве/одном sprite_update;
- ПЕРСИСТЕНТНАЯ asm-таблица {key16, ptr16}: resort порядка прошлого
кадра (почти линейно), rebuild при смене arr/count; массив
приложения не трогается; ~28К/15 спрайтов (с нуля было 44К);
- компоненты YSORT_Y/YSORT_LAYER отключаемы независимо масками ключа
(без ветвлений в сортировщике); ВНИМАНИЕ: mode=1 значит Y-only,
полный порядок = YSORT_Y|YSORT_LAYER;
- funcptr-DCE: выключено = код и таблица не линкуются (rpgwalk −190 Б);
- ПРАВИЛО в sprite.h: два sprite_update на страницу запрещены (heal
второй группы стирает спрайты первой — ОЗУ-копия чистая).
Попутные фиксы:
- libbgi/Makefile: .rel зависят от заголовков (HDRS) — stale .rel со
старой раскладкой sprite_t молча ломал рантайм;
- rpgprof: --memory small (перерос tiny: BSS вылезал за W2 → мгновенный
«Unexpected application termination»; mkexe это пока не ловит);
- tests/spranim: проверки переведены на кэш src, добавлены T6 (reframe
после тикера) и T7 (Y-сортировка: порядок, слои, персистентный
resort, LAYER-only) — 7/7 PASS в MAME.
Доки: §9д — новый бюджет (19.5К/спрайт), §9е — ПЛАН FPS-делителя
(frame pacing, gfx_set_fps_div); TODO — дизайн цепочки irq-обработчиков.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- gfx_pal_sync(): палитра страницы 1 := палитре 0 (все 256 записей,
чанками по 64 через общий буфер _gfx_pal_buf в W2) — обязательный
шаг дабл-буфера (грабли examples/space: без него флип на страницу 1
чёрный). examples/space переведён на хелпер.
- gfx_pal_fsave(pal, path): 256 записей × 4 Б (B,G,R,0 — родной формат
BIOS $A4) = 1024 Б.
- gfx_pal_fload(pal, path): принимает и усечённый файл (64 Б = палитра
16 цветов) — грузит сколько есть, остальные слоты не трогает;
возвращает число записей.
tests/palfile (MAME dev, все PASS): fsave; fload восстанавливает
испорченные слоты (n=256); усечённый файл на 2 записи чинит только их
(n=2, слот 2 не тронут); sync чинит испорченный слот палитры 1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Реализация §9г по требованиям пользователя:
- sprite_anim(first,last,speed,mode): ANIM_LOOP / ANIM_PINGPONG /
ANIM_ONCE (one-shot замирает на last), | ANIM_HORIZ для
горизонтальных лент. Смена кадра в тике = ±an_step к оси ленты —
без умножений (осевое смещение first считается в setup циклом).
- sprite_moveto(tx,ty,max_step,interval): Брезенхэм порциями
≤max_step вдоль большей оси (меньшая — err-аккумулятором,
нелинейные шаги Y сами собой), деления нет.
- Одновременность кадровой и tween — независимые поля/биты.
- Статус: sprite_anim_status() — битовое поле SPR_ANIM_ON/DONE +
SPR_MOVE_ON/DONE; sprite_anim_frame() — текущий индекс кадра;
sprite_moving().
- Стопы: sprite_anim_stop(frame | -1 = текущий);
sprite_move_stop(0 = замереть / 1 = прыжок в цель + DONE).
- Тикер _sprite_tick — проход 0 sprite_update через funcptr
_spr_tick_fn (DCE: без sprite_anim/moveto код не линкуется,
цена — один if на кадр; +40 Б программе с движком, sprite_t +18 Б).
tests/spranim (MAME dev, все PASS): LOOP/PINGPONG (разворот на границе
без удвоения краёв)/ONCE+DONE; пример (0,0)→(100,50), шаг 5,
интервал 4 → ровно 80 кадров, Y идёт 3/2/3/2; стопы обоих видов.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Диспетчеризация clip/noclip через указатели _gfx_blit_fn/_gfx_heal_fn
(common/_gfx_sprite_fns.c, дефолт clip): gfx_sprite_clip() — теперь
модуль, переключает указатели один раз; sprite_update/putsprite/
movesprite зовут через указатель — ветка if(clip) из горячего цикла
убрана (съедала половину выигрыша noclip). Программа без вызова
gfx_sprite_clip() noclip-ядра не линкует.
Замер dev-MAME (16 шаров, uncapped): clip 50 → noclip 60-61 fps
(+20-22%). Регресс tests/sprites (A/B PASS), size-check OK
(balls −237 Б, sprites −3177 Б — отвязались лишние ядра).
Фикс CPU-байта write-триггера (preread + EX AF,AF') снят: точная
dev-MAME эмулирует ПЛМ, подавляющую CPU-байт при активном burst'е —
подтверждено по байтам VRAM (tests/blitw col0 = GREEN через
read_vram MCP-моста). Для heal фикс был избыточен всегда (банк 0x50
перезаписывает dst[0]). Строки фикса оставлены закомментированными
в трёх leaf'ах на случай отличий реального железа; шапки и §9а/§9б
дизайна обновлены. НА ЖЕЛЕЗЕ ПЕРЕПРОВЕРИТЬ.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Спрайтовая графика поверх 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>
setcolor/getcolor/setbkcolor/getbkcolor/getmaxx/getmaxy/getmaxcolor/
getx/gety/moveto/moverel/setfillstyle/graphresult (graphics.h) и
gfx_get_bank/gfx_set_bank/gfx_get_draw_page/gfx_get_visible_page
(gfx.h) определены inline в публичных заголовках; state-переменные
объявлены там же (хранилище прежнее — _bgi_state.c/_gfx_state.c).
Именно `inline` БЕЗ static: проверено артефактами (.asm) — SDCC 4.5
инлайнит вызов при всех наших флагах (--opt-code-size/--opt-code-speed/
--max-allocs) и не эмитит standalone-тело; `static inline` эмитил бы
мёртвую копию каждого аксессора в КАЖДЫЙ включивший модуль. Отказ
инлайнить = громкая ошибка линковки (все 47 программ слинковались).
Экономия ~30-40Т на вызов, минус 17 .rel; по _CODE размер-нейтрально
(сайт вызова ≈ телу). size_baseline: bgitest +267/accfill +528 — это
НЕ inline, а run-рендер текста из ba09c0b (draw_scaled потянул
vspan_raw+vfill256 и сам вырос) — цена за ~2-6× скорость текста.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Строка глифа сканируется на прогоны единичных битов; прогон n битов =
прямоугольник n*size×size → hspan/vspan-примитивы (одиночный пиксель —
plot_raw). Координаты инкрементальные (+= size за бит) — умножений на
пиксель нет. Прогон клипится до span'а — клип теперь работает и в
fast-сборке (span-raw диапазон не клипят). ~2× на size=1, ~6× на
size=4. Проверено в MAME (bgitest: масштабы 1..4 + VERT — пиксель в
пиксель).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plot/read/hspan/vspan_raw читают D/E (y в DE), но не пишут — аннотация
задокументирована в _bgi.h. Честное измерение (полная пересборка с/без,
diff всех .asm в common/ и bgi256/): кодогенерация SDCC 4.5 НЕ меняется —
вызывающие держат локали в IX-фрейме и перезагружают DE перед каждым
вызовом, спасений DE вокруг вызовов не было. Оставлено как контракт на
будущее (изменение вызывающих/компилятора); при правке asm сверять
клоббер-лист.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bar(): rectfill-ветки выходили ранним return без _bgi_end() — W3
оставался замаплен на видеобанк после каждого bar() со сплошной
заливкой.
_bgi_read дублировал getpixel (та же композиция begin+read_raw+end);
единственный потребитель floodfill переведён на getpixel, модуль
удалён. getpixel.c: убраны мёртвые статики _gp_* (остались от
до-register версии).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/accel-fill-budget.md: полный разбор v3 (подготовка ~340Т на
прямоугольник, 46Т на чанк 16 линий, 53Т/51Т на линию, ~107 байт/leaf)
против per-line di/ei вариантов (djnz 72Т/линию ~63 байта; 16-бит IX
206Т/линию); break-even n≈9 линий, тотализатор, решение остаться на v3.
Краткие выжимки — в шапки _gfx_rect*fill256.
Идеи на будущее (там же): ограничить контракт стороной <=256 (широкие
прямоугольники пользователь выводит в два приёма); реализовать оба
варианта (поблочный/построчный) с переключением опцией сборки в духе
GFX_NOCHECK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Семантика 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>
_bgi_vspan_raw: safe-версия (без GFX_NOCHECK) больше НЕ клиппит диапазон
y+len и не обрабатывает y<0 — проверяется только валидность x/y (как в
_bgi_hspan_raw). Сознательный компромисс: safe ловит грубый выход за
экран по координате, но не частичный отрезок; y+len<=256 — обязанность
вызывающего.
Оба span'а: в safe добавлена проверка len==0 → return (иначе B=0 по
конвенции акселератора рисует «256»). В fast (GFX_NOCHECK) проверка
вырезается — поведение прежнее (256 точек), задокументировано в шапках.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- удалён глобальный скретч акселератора (_gfx_acc256.c); fill-сегменты
(_gfx_hfill256/_gfx_vfill256) принимают аргументы в регистрах HL/C/B/E,
вызываются только из asm raw-примитивов
- новый _gfx_rectfill256: заливка прямоугольника через Horizontal_Size
- raw-примитивы (plot/read/hspan/vspan/clear) переписаны под новый ABI
- graphics.h: typedef color_t (uint8_t) для всех public color-функций
- sprinter-cc: флаг --safe (линковка *_safe.lib при наличии)
- CLAUDE.md/mame_interactive: авто-прогон тестов в MAME
- gfx_demo: демонстрация rectfill
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Графика вынесена из libc/ в новую библиотеку libbgi/:
- common/ — mode-agnostic математика и состояние (один исходник,
.rel попадает в оба driver-архива);
- bgi256/ + bgi16/ — mode-specific leaf'ы (raw-плот/чтение/спаны);
- include/ — graphics.h + gfx.h; _bgi.h — внутренний заголовок.
Собираются lib/bgi256.lib (и bgi16.lib в Фазе 2); выбор режима
линковкой через sprinter-cc --gfx 256|16. libc/ теперь без графики.
tests/bgi_img — тест спрайтов getimage/putimage/imagesize (5 операций
COPY/XOR/OR/AND/NOT + XOR-round-trip + self-check imagesize).
Проверен автотестом в MAME.
Примечание: make size-check пока красный (gfx_dbuf/gfx_demo выросли
после реорга) — закрыть по завершении миграции libbgi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>