Commit Graph

182 Commits

Author SHA1 Message Date
snark13 21f978d441 roomtest: L1 — данные уровня из сырого res2001.bin вместо хардкода
Отказ от room1_data.h: уровень читается из сырого blueprnt DAT 1.0
(applications/PoP/SDLPoP/data/LEVELS/res2001.bin, формат — Table 6 POP-DAT).

- pop_level.c/.h: уровень грузится ОДИН раз в отдельную EMM-страницу (данные
  с offset 0x100, ISR-стаб в первых байтах — страница безопасна для W0-маппинга,
  как атласы).  pop_room_load(room) извлекает комнату в массивы приложения (W2):
  fg[30] (тайл-код = байт & 0x1F, верхние биты-модификаторы пока отброшены,
  как делал render_room.py), bg[30] (raw) + срезы соседей для кромок — правый
  столбец left-комнаты (leftcol), верхний ряд down-комнаты (belowrow).
  pop_room_link() — связи для будущих переходов (L2/L3).
- Рабочая копия ТЕКУЩЕЙ комнаты — в обычной памяти (W2, мутабельная: loose→
  empty); страница уровня маппится в W0 только на время извлечения.
- Проверено в MAME: комната 1 рисуется идентично.  Данные сверены байт-в-байт
  (fg&0x1F, bg raw, leftcol из room5 совпали); belowrow теперь из реального
  room2 (links.down=2), а не из прежнего частично-выдуманного хардкода.
- Память (small): DATA до 0xA260, стек от 0xBFFF — запас ~7.5КБ; EMM-страница
  уровня вне W1/W2.

NB: res2001.bin берётся из клона SDLPoP (data/ в .gitignore) — как и генерация
атласов через pop_pack_bg.py; в наш репозиторий сырой уровень не тащим.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:52:31 +03:00
snark13 8b30dc20c8 roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME:

- Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57)
  теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный
  хвост-тень на полу 2,7 ПОСЛЕ падения.
- Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты
  перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край
  прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1).
- Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq ->
  флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80).
  KNOCK_DOWN в land-seq (приземление) и runcyc (footstep).
- Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0)
  перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) —
  иначе на одной странице застревает дрожащий кадр правой грани (живёт в
  тайлах col и col+1) -> мерцание через флип.

Инфраструктура/документация:
- app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME).
- docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация
  форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT.
- README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP —
  источник истины», порядок слоёв, режим отладки freeze 1/2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:27:47 +03:00
snark13 5ef084eca4 docs: loose_floors_plan — конкретика SDLPoP + подход интеграции
Дополнен точными данными из SDLPoP (сверено): кадровые таблицы
loose_fram_left/right/bottom, get_loose_frame, y_loose_land, delay=11;
триггеры с call-sites (check_press->make_loose_fall при стоянии на 11,
frame79-сверху; do_knock на жёстком приземлении; animate каждый кадр);
tile_is_floor(11)=1.  Плюс подход интеграции в наш движок: общая
мутабельная копия комнаты pop_map<->pop_bg + per-page запекание пустоты
после падения; тонкое место — перерисовка динамического тайла в дабл-буфере.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.4-pop-roomtest
2026-07-18 21:43:43 +03:00
snark13 36a5a5e194 roomtest: фикс провала сквозь пол при беге с кромки (порт start_fall/in_wall)
Тап-бег с кромки [1,3] вправо -> Kid проваливался в стену col2/3 ->
респавн, вместо посадки на [2,4].  Сверено с SDLPoP seg006:

- start_fall: frame 9 -> seq_7_fall (был ошибочно seq_19 как у 13);
  frame 13 -> seq_19 (лишний dx(1)).  + хвост seg006:1099: тайл ПЕРЕД
  персонажем — стена -> Char.x = char_dx_forward(-1);
- in_wall переписан аутентично (seg006:1292): выталкивает ВПЕРЁД из стены
  (delta+4) на соседний тайл, а не назад вглубь (наш старый через
  dist_from_wall_forward давал отрицательный сдвиг -> Kid уходил в стену
  col2 -> проваливался).  distance_to_edge_weight + 6-delta/+4.

Проверено в MAME (пользователь): бег с кромки [1,3] -> посадка на [2,4].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 21:23:09 +03:00
snark13 237f780e0e roomtest: fore-окклюзия пола/стены над Kid (порт redraw_at_char/char2)
Kid — высокий спрайт: голова/руки торчат в ряд выше опорного, floor/wall
там должны перекрывать его.  Сверено с SDLPoP (seg003 redraw_at_char2 +
seg008 draw_tile_fore/draw_other_overlay/draw_floor_overlay):

- fore_tile (draw_tile_fore): стена рисует и WALL_FRAM_BOTTOM (нижняя
  грань-решётка), не только MAIN — руки при прыжке в потолок [2,7]->[1,7]
  уходят за стену;
- диапазон рядов форсит включение ряда над опорным (y_to_row верха спрайта
  мог схлопнуться из-за +60-сдвига);
- other_overlay_tile (draw_other_overlay): на КРОМКЕ пола (сосед слева пуст)
  перерисовать весь тайл поверх Kid — но ТОЛЬКО в позах захвата (78-79),
  виса (action 2/6), полёта (3/4), старта падения (bumped 102-106) и начала
  подъёма (135/136, до SEQ_UP на 141 Kid ещё в ряду ПОД полом).  Гейтинг
  как в redraw_at_char2 — при стоянии/беге ложной окклюзии нет;
- отладочный стоп-кадр: '1' заморозить / '2' продолжить (для разбора поз).

Проверено в MAME (пользователь): прыжок-в-потолок, короткий прыжок,
спрыгивание, подъём на [0,3] — окклюзия корректна.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 20:41:55 +03:00
snark13 023b45eb85 roomtest: двойная буферизация (два экрана + флип), тумблер SPACE
Убирает мерцание/тиринг при перерисовке слоёв (Kid/fore/пол-оверлей):
рендер всего кадра в скрытую страницу, tear-free флип на vblank.
Инфра libbgi (gfx_set_draw_page/visible_page/wait_vsync) уже была.

- фон комнаты рисуется в ОБЕ графические страницы (у каждой своя
  ОЗУ-копия — источник heal); палитра 0->1 уже синкалась gfx_pal_sync;
- kid_heal/kid_draw: прямоугольник Kid запоминается ПО СТРАНИЦЕ
  (kid_l*[2]) — при чередовании страниц heal стирает пиксели своей
  страницы (прошлый Kid там был 2 логических кадра назад);
- цикл: draw в back, 3x wait_vsync (пейсинг), gfx_set_visible_page(back);
- SPACE (edge) — тумблер: off = однобуфер (draw==visible==0) для отладки.

Мерцание при подъёме подтверждено устранённым в MAME (пользователь).
План: applications/PoP/docs/double_buffer_plan.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:07:03 +03:00
snark13 fce3e58830 applications/PoP/docs: план порта loose floors (проваливающиеся полы)
Разбор SDLPoP (seg007 loose/trob/mob, seg008 draw_loose): хранение
состояния (tile 11 + curr_room_modif: 0 покой / 0x80.. тряска / 1..11
отсчёт падения), триггеры тряски (do_knock на приземлении → shake ряда)
и падения (make_loose_fall при стойке/зацепе на loose), падающий кусок
(mob → debris снизу, empty сверху), отрисовка по статусу (loose_fram_*
через get_loose_frame).  Порядок реализации L1..L5 + что нужно в нашем
движке (динамический тайловый слой: room_modif[], trob-очередь, mob).

ПЛАН — не реализация.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 12:31:23 +03:00
snark13 3c0baacbf6 libc/kbd: recovery по Rx-overrun SIO (залипание клавиш) + kbd_raw_sync
Симптом (интермиттентный): при отпускании shift+стрелка иногда стрелка
залипает.  Диагностика: на чистом одновременном release break-коды
обрабатываются верно (проверено MCP) → drain-логика ISR корректна.
Остаточное залипание = переполнение 3-байтного аппаратного FIFO SIO при
пачке скан-кодов (F0 12 E0 F0 74 = 5 байт) во время длинных DI-окон →
потерян break → залипание.

Фикс: трамплин после drain читает RR1 SIO (бит5 = Rx Overrun), при
overrun делает Error Reset (WR0=0x30) и взводит _kbdraw_overrun.
Новый kbd_raw_sync() (звать раз в кадр) по флагу сбрасывает всё
held-состояние _kbdraw_down (какой break потерян — неизвестно; реально
зажатые перечитаются).  pop_ctrl_tick зовёт kbd_raw_sync().  Буфер W2-
трамплина 288→320 (трамплин 244 Б).

ВНИМАНИЕ: путь overrun НЕ проверен детерминированно (баг интермиттентный,
зависит от тайминга DI) — ТРЕБУЕТ ПРОВЕРКИ на железе/в длинной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 12:26:42 +03:00
snark13 cbae48dbc4 applications/PoP/roomtest: пол-оверлей при подъёме (draw_floor_overlay)
Проблема из динамики: при подъёме [1,2]→[0,3] нижняя часть Kid, которая
физически за полом назначения, просвечивала.

Порт SDLPoP draw_floor_overlay (seg008:1457): на кадрах подъёма 137..144,
если тайл-назначения floor-подобный (floor/pillar/stuck/torch) И тайл СЛЕВА
пуст (кромка), передний край пола (floor_left_overlay[frame-137] =
{32,151,151,150,150,151,32,32}) + низ пола рисуются ПОВЕРХ нижней части Kid.

- pop_bg: climb_overlay_tile + доп-проход в pop_fore_over_kid (новый параметр
  frame) на кадрах 137..144.
- pop_pack_bg.py: CLIMB_OVERLAY_ENV_IDS={32,150,151} явно добавлены в env-фон
  (не попадают в used render_room — рантайм-анимация); env4 count 16→24.
- roomtest: передача Kid.frame в pop_fore_over_kid.

Проверено в MAME (кадр 137): при подтягивании видна только голова/плечи Kid
над кромкой [0,3], нижняя часть скрыта за полом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 11:37:56 +03:00
snark13 419d5e4eff applications/PoP/roomtest: фикс ухода Kid в стену при прыжке с кромки
Баг (из динамики): стоя на кромке [1,3]/[1,4] лицом вправо + Up, Kid
телепортировался/застревал в стене col8.

КОРЕНЬ (трасса MCP): jumpup(seq_14) на кромке → приземление над ямой →
падение; на кадрах падения action=3 (midair) check_bumped был НЕ заглушён
(guard покрывал только freefall/hang/climb-кадры).  Kid дрейфовал к стене,
get_tile вне рядов давал WALL, dist_from_wall_forward/x_bump[] — мусор с
большим отрицательным сдвигом → Kid.x=char_dx_forward((int8_t)d) → underflow
uint8 X (0→255) → долёт до col8 и застревание в стене.

Фикс check_bumped: заглушить и в action MIDAIR (кадры падения), и при
curr_row вне [0..2] (падение мимо пола — тайлы вне комнаты = WALL, bump-мусор;
fell_out ловит do_fall).  Проверено в MAME: на кромке Kid больше НЕ уходит
в стену — чисто падает (в тесте сброс на старт).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 11:20:48 +03:00
snark13 5935724897 applications/PoP/roomtest: стены в fore-проходе поверх Kid
Проблема из динамики: за стенами Kid не прятался (стена [2,9] должна
перекрывать).  В PoP основная грань стены (wall_fram_main) добавляется в
FORETABLE (seg008:712) — перекрывает персонажа.  У нас fore-проход
рисовал только fore_id, а у стены (0x14) fore_id=0.

fore_tile: для стены (code==20) рисуем WALL_FRAM_MAIN + wall_pattern(,,1)
поверх Kid (как draw_tile_fore), а не только fore_id.  Проверено в MAME:
Kid прячется за правой стеной col9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:47:10 +03:00
snark13 78f2aaec1a applications/PoP/roomtest: fore-слой поверх Kid (передние тайлы)
Порт SDLPoP seg003 redraw_at_char + seg008 set_char_collision: после
kid_draw передний слой (fore_id = foretable-кусок) тайлов ФУТПРИНТА
спрайта Kid перерисовывается ПОВЕРХ него — то, что по изометрии перед
персонажем (передние грани колонн/ворот/большой колонны/щебня).  Стены и
факелы имеют fore_id=0 → остаются сзади (в статическом фоне).

- pop_bg: pop_fore_over_kid(obj_x,obj_y,w,h,dir) — считает футпринт
  (char_x_left/right, col_from_x, y_to_row_mod4) рядов top..bottom ×
  колонок left..right (≤2×2=4 тайла) и рисует fore_id каждого в
  GFX_BANK_SPRITE (видео-ОЗУ; kid_heal восстановит из теневого фона,
  fore в нём запечён pop_room_draw).
- pop_kid: kid_fp_obj_x/y/width/height — метрики последнего кадра
  (obj_x ЛОГИЧЕСКАЯ, до ×8/7) для футпринта.
- roomtest: вызов pop_fore_over_kid после kid_draw.

Проверено в MAME: Kid, идя влево мимо колонны под навесом (row1 col3),
корректно уходит ЗА её переднюю грань (скрывается) и выходит с другой
стороны; задние колонны/факелы остаются сзади.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:34:32 +03:00
snark13 ea57a03543 applications/PoP/roomtest: K4 — зацеп/вис/подтягивание/спуск
Порт SDLPoP seg004/005/006 «hang state»:

- pop_map: check_grab (зацеп за уступ в падении по Shift → seq_15 → вис),
  can_grab/can_grab_front_above + tile-запросы над/за персонажем;
  pop_jump_up_seq (check_jump_up: ↑ в стойке = чистый прыжок seq_28/14
  ЛИБО прыжок-с-зацепом seq_8/24/16 за уступ выше → запрыгнуть на этаж);
  pop_hang_* (climb_up seq_10/73, hang_fall seq_23/11, hang-у-стены);
  pop_down_action (спуск seq_68 у края лицом от края / отступ / присед).
- pop_ctrl: control_hanging/can_climb_up/hang_fall, control_jumpup,
  jump_up через pop_jump_up_seq, down_pressed через pop_down_action;
  pop_ctrl_shift_held() для check_grab.

Ключевой фикс check_bumped (seg004 guard'ы): не бампить при action
hang_climb/hang_straight, на кадрах подъёма/спуска 135..148 И на кадрах
виса 87..99.  Без последнего спуск (seq_68) на кадре frame_91 (action
ещё midair, act(hang_climb) идёт следующим опкодом) ловил отскок у стены
и рвал цепочку hang→hang_fall→seq_11, приземляя не туда.

Проверено в MAME (HDD-тест, покадровая трасса через мост): прыжок-с-
зацепом [row2 col4]→вис→подтягивание→[row1 col3]; спуск [row1 col3]→
[row2 col4] с корректной позицией у стены (x=122, совпадает с SDLPoP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:07:20 +03:00
snark13 cd8d566d82 applications/PoP: порт Prince of Persia — PoC (roomtest) + пайплайн
Порт PoP на Sprinter.  Текущий PoC — applications/PoP/roomtest/:
комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре
и коллизией с картой.

- roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип,
  seqtbl-анимация), pop_ctrl (порт control() PoP на held-state
  kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены,
  падение/приземление, отскок seq_47, вертикальный прыжок K4.1).
  MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge).
- toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract —
  распаковка res-графики MSDOS в атласы + композиция комнат.
- toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD),
  png_strip.py / room_compose.py (ассет-пайплайн).
- docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT).
- bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип).

.gitignore: build-артефакты applications/*/*/*; исключены внешние
референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и
оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:51:08 +03:00
snark13 484b18d10c libc+libbgi: raw-клавиатура (held-state) + column-major блит спрайтов
libc/kbd: kbd_raw_open/close/down — сырой PS/2-канал клавиатуры с
held-state (битовая карта _kbdraw_down[512], EXT-клавиши +256).  Пока
raw открыт, кадровый IRQ-трамплин перехватывает байт SIO у DSS и
декодирует make/break (0xF0/0xE0-префиксы) сам.  FIFO вычерпывается В
ЦИКЛЕ (приёмный буфер SIO 3 байта; пачка break-кодов при одновременном
отпускании иначе теряется → залипание клавиши).  Буфер W2-трамплина
поднят 224→288 Б под выросший обработчик.

libc/conio: kbd_mod_state() — live-состояние модификаторов (ESTEX
CTRLKEY $33h), Shift/Ctrl/Alt/Lock прямо сейчас, KBD_MOD_* маска.

libbgi: gfx_blit_cols(x,y,img,flip) + _bgi_blit_cols_raw — блит
column-major спрайта вертикальным accel-проходом, бесплатный
горизонтальный флип (sstride<0), клип по экрану.  Для персонажей.

libbgi/atlas_load: восстанавливать W3 ДО записи a->count (atlas_t в
--bank памяти резолвится через W3; count оставался мусором).

tests/kbdraw — тест raw-клавиатуры; size-baseline +kbdraw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:48:32 +03:00
snark13 e14b6745f7 examples/rpgwalk: переключатель FPS-делителя (1/2/3) — проверка пейсинга
Клавиши 1/2/3 зовут gfx_set_fps_div(n) на лету (дефолт n=1 не меняет
поведение).  Проверено в MAME: при n=2 FPS-метр стоит РОВНО на 024
(48.83/2) все 8 секунд без плавания — логический кадр = ровно 2 vsync'а,
скорость персонажей (пиксель/сек) постоянна.  +808 Б (демо тянет код
делителя).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:42:28 +03:00
snark13 1b4fbeaa6b libbgi: FPS-делитель gfx_set_fps_div(n) поверх цепочки кадровых IRQ
Логический кадр = ровно 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>
2026-07-15 10:36:37 +03:00
snark13 2c6f4e33c3 libc/irq: цепочка кадровых обработчиков + all-modes W1-remap
_irq_user → _irq_chain[4]+_irq_chain_n (W2-BSS); трамплин tr_frame
проходит слоты (один тяжёлый сейв на всю цепь, пустой слот пропуск).
API irq_chain_add (0/-1+ENOMEM) / irq_chain_remove(h); irq_install →
обёртка chain_add (EBUSY исчез), irq_remove() рвёт всю цепь; refcount
таблицы на первом/последнем слоте, мутации под IRQ_DISABLE. Лимит
4 кадровых + 1 CTC.

All-modes: трамплин copy-safe (только jr/djnz + литерал jp 0x0038),
_irq_table_ref копирует его в _irq_tramp_w2buf (W2) когда оригинал в W1
(small/huge), вектор → на копию; вокруг вызова хендлеров восстанавливает
базовую W1-страницу (_irq_app_w1_page = IN 0xA2). Хендлер может лежать
где угодно в плоском 0x4000-0xBFFF.

Проверено в MAME (tests/irqtest, 2 хендлера): tiny/big/huge — chain
h1=h2 → remove h2 → h1 жив/h2=0; huge = код в W1, remap работает.
Follow-up: CTC в small/huge = EINVAL (нужна W2-копия _irq_ctc_tramp);
small для мелких программ (BSS в W1) = irq_install EINVAL, safe.

Доки: im2_isr_design (цепочка), sprite-api §9е (делитель поверх цепи),
fast_ram §8. rpgprof: gfx_sprite_ysort в профиль (+230 Б baseline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:23:39 +03:00
snark13 6b6986d9b6 docs: W1-перемапы DSS при EI подтверждены артефактом; дизайн цепочки irq
wpiset на порт W1 (0xA2) + iff1 в dev-MAME: DSS перемапливает W1 при
ВКЛЮЧЁННЫХ прерываниях во время системных вызовов (файловые, загрузка
exe, видео; страницы 0xFE/FF/F3/0x50, PC ядра 0x15xx-0x2Exx) —
ограничение irq_install «только tiny/big» обосновано, handler в
W1-коде небезопасен принципиально.  §9е дополнен фактом; в TODO —
дизайн цепочки irq-обработчиков (массив слотов в W2, chain_add/remove,
irq_install как обёртка; реализация по потребности).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 17:34:21 +03:00
snark13 b9ddce8d34 libbgi: спрайты — кэш адреса кадра, DDA+asm тик, Y-сортировка со слоями
Оптимизации 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>
2026-07-14 17:19:24 +03:00
snark13 0eec977630 libbgi: sy*img_w умножением вместо O(sy)-цикла — rpgwalk 24 → стабильные 48 fps
Профилирование rpgwalk в dev-MAME (watchpoint на OUT-маркеры +
totalcycles) показало: blit 8 спрайтов ел 17 мс из 20.5 — 85% в цикле
`while (sy--) src += img_w;` blit-обёрток (писался под «обычно sy==0»,
а вертикальные ленты атласов дают sy до 176 → до 64К тактов на кадр).

Фикс: src += sy*img_w через __mulint (O(1); __mul16 адаптивен — при
sy < 256 крутит 8 итераций, ~500Т) + if (sy): горизонтальные ленты и
одиночные спрайты не платят и за умножение.  Blit: 45К → 11.8К
тактов/спрайт.

Замерен бюджет кадра (docs/sprite-api-design.md §9д): кадр 48.83 Гц =
430080 тактов @21МГц; спрайт 16×16 ≈ 26К (тик 7.3К + heal 6.4К +
blit 11.8К) → лимит стабильных 48 fps = 14 спрайтов (15 — 94% кадров,
16 — на грани).  FPS-плашка bar+outtextxy стоит ~210К (полкадра!) —
HUD рисовать putimage-заготовкой.

examples/rpgwalk/rpgprof.c — профилировочная копия демо: визуальный
профайлер (цвет бордера по секциям кадра) + маркеры для тактового
профайла через wpiset дебаггера (рецепт в шапке и §9д).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:54:37 +03:00
snark13 02f7afe765 examples/rpgwalk: FPS-метр
Плашка слева-сверху (значение раз в секунду, рисуется на обеих
страницах — fps_draw=2, как в examples/space).  ~39 fps на 8 ходящих
персонажах (кадр на грани 20 мс — частично двухvsync'овые кадры).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 17:38:04 +03:00
snark13 b010d24792 examples/rpgwalk: 8 RPG-персонажей ходят по травяному полю
Демо на реальном арте (third_party/16x16-RPG-characters, bard):
conv_sprites.py режет PNG 192×128 (8 персонажей = блоки 3×4 кадров:
ряды вниз/влево/вправо/вверх × кадры маятника 0/1/2) в 8 вертикальных
лент по 12 кадров и строит bard.pal (слоты 0-15 EGA + цвета PNG с 16,
прозрачность → 0xFF).  8×12 кадров не лезут в одну EMM-страницу —
ДВА атласа по 4 персонажа (движок сам переключает страницы W0).

Палитра из файла: gfx_pal_fload + gfx_pal_sync.  Смена направления =
sprite_anim(dir*3, dir*3+2, PINGPONG).  Правила хождения (двухфазная
машина на sprite_moveto): до края → разворот 180° → случайная точка
(не меньше четверти экрана) → поворот ±90° → снова до края.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 17:27:00 +03:00
snark13 6dea6955c2 libbgi: gfx_pal_sync + палитра ↔ файл (gfx_pal_fsave/fload); все PASS
- 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>
2026-07-13 16:51:49 +03:00
snark13 8792594c5a examples/space: демо полного спрайтового стека (атлас W0 + авто-анимации)
Всё сразу: .atl с диска (mkatlas.py из генерённых лент) → atlas_load в
EMM-страницу (W0, ISR-стаб) → движок sprite_t.page → авто-анимации:
5 астероидов (ANIM_LOOP вращение + sprite_moveto к случайным целям
разных скоростей; по прибытии — взрыв ANIM_ONCE в точке + новая цель,
по SPR_ANIM_DONE взрыв прячется), маяк ANIM_PINGPONG; дабл-буфер,
FPS-метр, ESC.  ~48 fps (vsync-кап).

Грабли по дороге (оба — прикладные, не библиотека):
- фон рисовался rand()'ом с разными последовательностями на страницах
  → мерцание звёзд; фикс — фиксированный seed на draw_space;
- НЕ была скопирована палитра страницы 0 → 1 (у каждой страницы своя,
  см. gfx.h) — страница 1 показывалась чёрной, флип мигал
  «сцена/чёрный»; fps-плашка теперь рисуется на ОБЕИХ страницах
  (fps_draw=2 кадра при смене секунды).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:40:48 +03:00
snark13 eb9ca0179d libbgi: авто-анимация спрайтов — кадровая + tween (tests/spranim все PASS)
Реализация §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>
2026-07-13 16:14:31 +03:00
snark13 0e935343d9 libbgi: W0-атласы спрайтов — загрузчик, движок, упаковщик (все проверки PASS)
Реализация §3.1/§9в: атлас = один .atl-файл = одна EMM-страница,
подключаемая в W0 на время блита.

- atlas_load: read() файла целиком в страницу через W3 + патч ISR-стаба
  (0x38: JP _gfx_w0_isr; 0x66: RETN); atlas_free/atlas_image/
  atlas_sprite_init; gfx_w0_map/unmap для ручных вызовов.
- _gfx_w0_isr (стаб из tests/w0page): свап на ядро DSS → честный 0x38 →
  restore спрайт-страницы; покрывает IM1 и IM2-чейн.
- sprite_t.page (0 = обычная память); sprite_update в блит-проходе
  мапит страницу по смене (один OUT на атлас), эпилог возвращает DSS
  (+52 Б на движок — цена фичи).
- toolchain/mkatlas.py: PNG (indexed) / raw → .atl; заголовок 0x100,
  каталог 0x68 (19 лент), файл-офсет == офсет страницы == W0-адрес.
- Сплит _gfx_sprite_fns → _gfx_blit_fn.c + _gfx_heal_fn.c (1 указатель
  = 1 модуль): putsprite-only программа не тянет heal-ядро (spriteclip
  ловил +292 Б; теперь −68 Б от эталона).

tests/atlas (MAME dev, PASS): загрузка (count/страница), каталог и
данные по W0-адресам (заголовок ленты через gfx_w0_map), 3 спрайта из
двух лент через движок — пиксели проверены read_vram побайтно
(0x10/0x12/0x21, прозрачный угол = фон).

docs: §9г — предложение авто-анимации (кадровая ±step без умножений,
tween-Брезенхэм порциями, тикер через funcptr) — ОБСУЖДАЕТСЯ.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:52:00 +03:00
snark13 c4a512200f tests/w0page + docs §9в: атласы в страницах W0 — probe-тест (все PASS)
Схема: атлас в EMM-странице, на время sprite_update страница
подключается в W0.  Все прерывания исполняют 0x38 текущей страницы W0
(IM1 напрямую, IM2-трамплин чейнит jp 0x0038) — защита: стаб в первых
0x100 байтах страницы (0x38: JP на W2-хелпер: свап на страницу DSS →
честный 0x38 → restore спрайт-страницы; 0x66: RETN).

Проверено в dev-MAME (tests/w0page, все PASS):
  P1 ESTEX READ в W3-замапленную страницу;
  P3 IM1: ~5 c busy-цикла с EI при странице в W0 — жив, сентинел цел;
  P4 IM2: 100 кадровых прерываний посчитаны при подключенной странице
     (тракт трамплин→чейн→стаб→DSS→restore);
  P5 accel-блит src=0x0100 (W0) — пиксели верны по ОЗУ-копии.

Формат .atl в §3.1 дополнен: атлас ≤ 16К−0x100 (одна страница, один
файл), вариант II — 0x100-заголовок, файл-офсет == офсет страницы ==
W0-адрес, загрузка = read() целиком + патч стаба.  На железе
перепроверить.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 12:30:24 +03:00
snark13 88a00bb4ec docs: атласы — рекомендация по компоновке + предложение файл-формата .atl
Решение: in-memory формат остаётся (getimage + sx/sy, ленты/сетки).
Рекомендация: кадры одного спрайта — одного размера с полной сеткой;
для спрайтов разных размеров — контейнерный формат .atl (каталог +
независимые getimage-ленты, паддинг невозможен по построению).
Формат — предложение, реализация по потребности первого приложения.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:44:49 +03:00
snark13 ee1ca00c6f libbgi: funcptr-диспетч clip/noclip спрайтовых ядер + снятие src[0]-фикса
Диспетчеризация 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>
2026-07-13 11:37:54 +03:00
snark13 72ce66275e 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>
2026-07-12 21:51:52 +03:00
snark13 78161561e7 sprinter-cc: --max-allocs 100000 по умолчанию для пользовательского кода
Тот же приём, что во fast-библиотеках (786836e): агрессивная
регистровая аллокация SDCC.  --max-allocs N переопределяет (меньшее
значение = быстрее компиляция).

Замер (47 программ): mdview -235, banklocl -149, fbench -110,
filetest -90, ls -81, solidt -69 и т.д.; 4 микро-роста (+1..+12 —
другие развязки аллокатора, шум).  Полная сборка ~2 мин -> ~4:15.
MAME: filetest/bgitest/seek (с big.txt, скриншот пользователя) зелёные.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:35:44 +03:00
snark13 786836e2e7 libc+libbgi: fast-версии собираются с --max-allocs-per-node 100000
Идея из mdview2 (memory/mdview2_size_budget): большее время компиляции
покупает более агрессивную регистровую аллокацию SDCC.  Применено к
fast-вариантам обеих библиотек (sprinter.lib, bgi256.lib); safe-версии
остаются на дефолте — быстрая пересборка для отладки.

Замер (47 программ, роста нет): solidt -507, filetest -506, fbench
-461, bgi_img -339, bgitest -316, gfx_dbuf -316, fdmax -278, errno
-179, gfx_demo -173, accfill -143, ptime -128, stattest -117, cbl* -53,
остальные до -28.  Время сборки: libc fast 17с -> 68с, libbgi 43с
(обе) — терпимо.  MAME: filetest + bgitest зелёные.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:24:50 +03:00
snark13 9f8aa6fc28 libc: две версии библиотеки — sprinter.lib (fast) / sprinter_safe.lib
Симметрично libbgi (bgi256/bgi256_safe): fast = -DLIBC_NOCHECK,
дефолт sprinter-cc; safe линкуется по --safe (флаг уже существовал).

Под LIBC_NOCHECK вырезаны ТОЛЬКО параметр-валидации:
- fgetc/fputc: NULL-check в горячей asm-обёртке (~11Т на каждый байт);
- fgets/fputs/fread/fwrite: NULL ptr/fp и EBADF на неверное направление
  потока; ftell/fseek/ungetc/fclose: NULL fp; cputs: NULL s.
НЕ тронуты: критичный _fd_guard (9-й OPEN вешает DSS — в обеих
версиях), функциональная маршрутизация (консоль/направление/hold),
cold-path валидации (fopen/cbl_open/irq/settextmode — экономии ноль).

libc/Makefile — dual-build по образцу libbgi (build/fast + build/safe,
общий стейл-контроль).  Корневой Makefile: в TESTS добавлены bgitest,
bgi_img, accfill, cblstream — раньше не собирались корневым make и
выпадали из size-check при чистой пересборке.

Дельты fast vs старая (safe-семантика): filetest -481, fbench -384,
solidt -180, errno -71, остальные -3..-9; роста нет.  Проверено в MAME:
filetest fast и safe (--safe, sprinter_safe.lib) — прогоны идентичны.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:11:01 +03:00
snark13 c9ac0999fd libc: тонкие аксессоры → inline в заголовках (по размерному критерию)
Inline (чистый C99 `inline` без static, паттерн из libbgi/5de2f06):
textattr, get_text_attr, get/set_putch_raw_mode (conio.h; g_text_attr
и pc_raw_mode объявлены публично), isatty (unistd.h — сворачивается в
константу при константном fd).  Модули удалены (5 шт).

«Толстые» кандидаты НЕ инлайнены — критерий проверен замером на 47
программах: feof/ferror/clearerr (тело ~12-15 байт с NULL-проверкой,
filetest +56 Б при инлайне) и textcolor/textbackground/set_text_attr
(RMW-маски, conio2 +13 Б) остаются модулями — при 2+ сайтах вызова
инлайн крупнее call+общее тело.  Правило: инлайнить только тела
<= ~6 байт на сайте или сворачиваемые константами.

Дельты: solidt -94, hello -24, mouse -7, bios_text -7; conio2 +11
(textattr×5 — паритет, принято за скорость).  z80.lib SDCC не содержит
feof/isatty — маскировки удалённых модулей нет, провал инлайна = ошибка
линковки.  Проверено в MAME: conio2 (атрибутная матрица), filetest
(полный прогон).  В size_baseline также вошли gfx_dbuf +608/gfx_demo
+25 — это первый чистый релинк run-рендера текста (ba09c0b), не inline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 21:53:37 +03:00
snark13 5de2f06cfb libbgi: тривиальные аксессоры → inline в заголовках (17 модулей удалено)
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>
2026-07-10 21:21:26 +03:00
snark13 ba09c0bd05 libbgi: _bgi_draw_scaled — рендер текста run'ами вместо поштучных плотов
Строка глифа сканируется на прогоны единичных битов; прогон 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>
2026-07-10 19:36:53 +03:00
snark13 38bfabb2ca libbgi: __preserves_regs(d,e) на raw-примитивы (контракт, без эффекта сейчас)
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>
2026-07-10 19:35:32 +03:00
snark13 40e896a73d libbgi: фикс утечки W3-скобки в bar() + удалить дубликат _bgi_read
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>
2026-07-10 19:35:32 +03:00
snark13 5f0c46f0ea docs: идея span-примитивов для узких прямоугольников (TODO + fill-budget)
При узкой стороне <= 8 линий chunked-rectfill проигрывает циклу
_bgi_hspan_raw/_bgi_vspan_raw (подготовка ~340Т впустую, break-even
n~9): либо fast-path в диспетчере, либо рецепт для пользователя —
решать по профилю.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:02:16 +03:00
snark13 580837d2ee docs: бюджет тактов/размеров chunked-заливок + идеи дальнейших упрощений
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>
2026-07-10 17:58:17 +03:00
snark13 2c414da465 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>
2026-07-10 17:41:49 +03:00
snark13 e553e5e6c9 docs: обновить size_baseline после bgi256 register-ABI рефактора
Эталон отставал от коммита 0296079.  Дельты объяснены: bgitest/bgi_img
уменьшились (убран скретч _gfx_acc256), gfx_demo вырос (демо rectfill в
исходнике), gfx_dbuf вырос (vsync-wait тянет CBL через общий порт
0x004E — см. _cbl_port_ref/unref).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:17:04 +03:00
snark13 f0f0ab9276 docs: TODO — пакетное чтение/запись массива байт через акселератор
Задел для будущих leaf'ов, читающих/пишущих строку или столбец пикселей
одним burst'ом (как _bgi_hspan_raw/_bgi_vspan_raw), чтобы ускорить блит
getimage/putimage (сейчас per-pixel _bgi_read_raw повторяет Port_Y +
addr + bounds-check).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:17:04 +03:00
snark13 9b1ce71130 libbgi: упростить safe-контроль span'ов + отсекать len==0
_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>
2026-07-10 15:16:53 +03:00
snark13 029607971f libbgi: bgi256 fill-примитивы на register-ABI + typedef color_t
- удалён глобальный скретч акселератора (_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>
2026-07-10 14:32:21 +03:00
snark13 52636a5d6e libbgi: выделить графику BGI в отдельную библиотеку + тест спрайтов
Графика вынесена из 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>
2026-07-09 14:18:33 +03:00
snark13 3bf50f7ff7 docs: единый справочник по автотестам в MAME; убрать метод AUTORUN.BAT
- docs/mame-autotest.md — исчерпывающий документ: запуск, ввод команд,
  скриншоты, завершение сессий, анализ, раскладка клавиатуры, все квирки.
  Одного этого документа достаточно, чтобы работать с MAME в режиме
  автотестирования.
- mame_interactive.py теперь единственный инструмент: авто-запускает exe
  вводом пути (a:\<exe>+Enter), --step опционален (доп. ввод в программу),
  умные дефолты снимков/таймаута.
- удалён mame_auto_test.py (старый метод через AUTORUN.BAT chainload) и
  все упоминания AUTORUN.BAT в доках; интерактивный ввод его заменил.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:41:45 +03:00
snark13 657c6d2955 libc: BGI Фаза 2d-3 — settextstyle (масштаб и направление текста)
settextstyle/gettextsettings/textwidth/textheight: DEFAULT_FONT 8×8,
целочисленный масштаб 1..10, HORIZ/VERT (поворот 90° CCW), прозрачный
фон. Свой scaled-рендер (_bgi_draw_scaled) читает глиф через leaf
_bgi_font_rows (interleaved системный шрифт) и рисует блоки size×size
raw в одной W3-скобке. outtext/outtextxy переведены на него. Проверено
в MAME (tests/bgitest): размеры 1..4 + вертикальный текст.

Доки обновлены (Ф2d-1/2/3 готовы; осталось viewport/клиппинг).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:18:48 +03:00
snark13 b26560c409 libc: BGI Фаза 2d-2 — setlinestyle (стили и толщина линий)
setlinestyle/getlinesettings: SOLID/DOTTED/CENTER/DASHED/USERBIT +
NORM/THICK. _bgi_styled_line — Брезенхэм с 16-битной маской (пропуск
пикселя по биту) и дублированием ±1 перпендикулярно оси для THICK;
SOLID+NORM идёт быстрым путём (accel _bgi_lineseg). line/lineto/linerel/
rectangle/drawpoly переведены на него. Проверено в MAME (tests/bgitest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:14:03 +03:00