Files
Sprinter-SDCC/docs/TODO.md
T
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

313 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO / Roadmap
Открытые задачи в порядке убывания приоритета; закрытые этапы — в
«Истории» внизу. Текущий срез libc-работ: docs/libc-roadmap.md.
## Ближайшее
- [ ] **П6/железо**: MAME-смоук всех тестов после libc-сплита (conio,
ptime, stattest, mouse, gfx_demo/gfx_d16/gfx_text/gfx_mous —
трогался asm акселератора); затем прогон на реальном Sprinter
(mdview2 + FILE* v2 + fdmax — подтвердить лимит 8 манипуляторов
и зависание DSS на 9-м OPEN)
- [ ] Мигрировать оставшиеся examples на sprinter-cc вместо ручных
Makefile (косметика)
- [ ] check_banks.py: разбивка size = code + const + bss per bank
## Auto-banking (memory/banking_roadmap.md)
Phase 1 — file-level bin-packing (`toolchain/auto_bank.py`) — когда
проект перерастёт ~30 KB кода: парсинг размеров из .rel/.map,
first-fit-decreasing, уважение `#pragma codeseg BANKn`, перелинковка,
печать плана. Phase 2-5 (rebalance, banks.toml, function-level) —
по потребности.
## ОБЯЗАТЕЛЬНОЕ ДЛЯ V2
### Turbo-C-style graphics API (BGI-like) — Фаза 1 ГОТОВА (256, 2026-07-08)
Архитектура: `graphics.h` — mode-agnostic слой (libc/bgi/*.c в
sprinter.lib), режим задаёт driver-архив; выбор линковкой через
`sprinter-cc --gfx 256` (16 — позже, тем же leaf-split'ом; см.
memory/bgi_two_lib_design).
**Фаза 1 (256, реализовано и проверено в MAME — tests/bgitest):**
initgraph/closegraph/graphresult/cleardevice, setcolor/getcolor/
setbkcolor/getbkcolor, getmaxx/getmaxy/getmaxcolor, putpixel/getpixel,
moveto/moverel/getx/gety, line/lineto/linerel, rectangle, bar, circle,
outtext/outtextxy. initgraph грузит EGA-палитру 0..15. Пакетные
примитивы (circle) — одна W3-скобка на весь примитив (raw-плот), иначе
на порядок медленнее.
**Фаза 2a/2b/2c ГОТОВЫ (2026-07-08, проверено в MAME tests/bgitest):**
- 2a: arc, ellipse, drawpoly (Q7-тригонометрия, БЕЗ 32-бит).
- 2b: setfillstyle/getfillsettings + 10 паттернов Borland, bar (с
паттерном), bar3d, fillpoly (scanline min/max), fillellipse (isqrt).
- 2c: floodfill (scanline span; медленный — self-bracket чтение, но
корректный), pieslice, sector.
**Фаза 2d-1/2/3 ГОТОВЫ (2026-07-08, MAME tests/bgitest):**
- 2d-1: getimage/putimage/imagesize (COPY/XOR/OR/AND/NOT_PUT), raw-блит
в одной W3-скобке (_gfx_getpixel256_raw).
- 2d-2: setlinestyle/getlinesettings (SOLID/DOTTED/CENTER/DASHED/USERBIT
+ NORM/THICK) — line/lineto/linerel/rectangle/drawpoly.
- 2d-3: settextstyle/gettextsettings/textwidth/textheight — масштаб
1..10, HORIZ/VERT, прозрачный фон (свой scaled-рендер поверх 8×8).
**Фаза 2d (осталось):** setviewport/clearviewport + клиппинг (инвазивно —
трогает все примитивы), settextjustify, setaspectratio (пиксели 320×256
неквадратные — круги визуально эллиптичны), setfillpattern (USER_FILL),
setactivepage/setvisualpage (2 страницы в gfx уже есть). Оптимизации:
floodfill на raw-чтении; ellipse/arc в одной W3-сессии. Потом drv16 +
sprinter_gfx16.lib (--gfx 16).
Acceptance: типичная BGI-программа переносится без существенных
правок. Референс: Turbo C 2.x BGIDEMO.
### IM2 Interrupt Handlers — Phase 1 ГОТОВ (2026-07-06, libc/irq, tests/irqtest; Phase 2: CBL/ISA/цепочки)
User-ISR через IM 2 — timer ticks, музыка (AY/COVOX), real-time
игры, async input. Phase 1 реализован БЕЗ отдельного memory mode:
libc/irq (<irq.h>: irq_install/irq_remove), таблица в BSS с runtime-
выравниванием, jp-заглушка внутри таблицы, чейн к DSS всегда;
работает в tiny/big, в small/huge — EINVAL. Детали и отличия от
исходного плана: docs/im2_isr_design.md.
### Прочее v2
- [ ] **Audio API** — AY-3-8910 + COVOX (требует IM2)
- [ ] **ISA-8 slot support** — ZX-Bus карты (требует IM2)
## GFX: расширения по accelerator_doc.txt
Quick wins:
- [ ] block-size через `LD A,(nn)` вместо SMC (док разрешает LD A,(HL/BC/DE))
- [ ] кэширование block-size между burst'ами (accel помнит размер)
Новые возможности:
- [x] ~~пакетное чтение/запись массива байт через акселератор~~
сделано 2026-07-11 (Фаза A спрайтового дизайна,
docs/sprite-api-design.md): leaf `_bgi_copy_rows_raw` (accel
block-copy LD L,L, до 256 байт/burst, размер блока армируется
один раз) + ядро `gfx_blit_part` (клиппинг, полосы ≤256);
putimage(COPY_PUT) и getimage переведены (регресс tests/bgi_img
1:1 с per-pixel эталоном, подрежимы банков на accel-пути —
tests/gfxbanks).
- [ ] **вертикальный copy-leaf `_bgi_copy_cols_raw`** (режим LD A,A —
вертикальная копия, Port_Y двигается сам как у vfill). Анализ
2026-07-12: для СПРАЙТОВ требует column-major хранения (читать
линейный буфер вертикально нельзя — Port_Y не действует вне
видеоокна; референс docs/samples/balls пишет колонками и потому
выводит спрайт ТРАНСПОНИРОВАННЫМ — незаметно только на
симметричном шаре) → формат несовместим с getimage, не делать.
А вот **вертикальный HEAL формат-независим** (экран→экран) и
выгоден для узких высоких областей: L-полоска 2×16 при
горизонтальном движении спрайта = 2 burst'а вместо 16 (8×
меньше оверхеда); выбор ориентации по форме — как в
_gfx_rectfill256. Нюанс: между read и write колонки Port_Y
надо вернуть на y0 (стоп → OUT → ре-арм, ~20Т/колонку).
Делать вместе с L-strip оптимизацией heal.
- [ ] **паттерны через акселератор + FF-прозрачность** (идея 2026-07-11):
всё, что сейчас рисуется по-пиксельно из-за «дырок», можно гнать
burst'ами через банк 0x58 — дырки паттерна кодируются 0xFF и
отбрасываются железом на записи:
- стилизованные линии (_bgi_styled_line: DOTTED/DASHED/CENTER/
USERBIT сейчас per-pixel): построить 16-байтовый шаблон строки
из 16-бит маски (бит=цвет, 0=0xFF) и повторять accel-copy;
- fill-паттерны (_bgi_fill_span, LTSLASH_FILL и пр.): 8-байтовые
строки-шаблоны 8×8 паттерна тем же способом;
- ВНИМАНИЕ: на MAME 0.283 запись FF через 0x58 портит теневое
ОЗУ (частичный скип, см. sprite-api-design «Результаты Фазы 0»)
— включать после подтверждения полного подавления на железе,
либо через 0x5C + пере-heal.
- [x] ~~gfx_blit / putsprite / gfx_heal / movesprite~~ — Фаза B
сделана 2026-07-11 (docs/sprite-api-design.md §3): GFX_BANK_*
константы + gfx_blit/gfx_blit_part/gfx_heal в gfx.h,
putsprite/movesprite в graphics.h; проверено tests/sprites
(прозрачность, клиппинг 4 краёв, heal src==dst, чистый след
movesprite, атлас). Осталась Фаза C — пример-курсор.
- [ ] **managed-движок спрайтов v2** — sprite_t (drawn-координаты
per-page ВНУТРИ структуры) + retained-модель:
sprite_move/frame/show/hide меняют только state,
sprite_update(arr,n) делает «heal все → блит все» на текущей
draw-странице, sprite_flip — сахар с vsync+флипом; z-order =
индекс массива. Интегрирует спрайты со сменой страниц —
снимает с приложения учёт drawn[2][N] (источник багов,
examples/balls 2026-07-11). Полный эскиз:
docs/sprite-api-design.md §9.1.
- [ ] `gfx_xor_rect` / `gfx_or_rect` / `gfx_and_rect` / `gfx_invert_rect`
- [ ] шрифты ≠ 8×8: gfx_set_font_data(ptr,w,h,advance), proportional,
8×16/16×16, отдельный font_id API; font-quad для 640×256
(per-cell палитра через дескрипторы 0x0300..0x039F)
Оптимизации (не сейчас):
- [x] ~~stride-арифметика в _bgi_copy_rows_raw — вон из горячего
цикла~~ — сделано 2026-07-12 (профиль examples/balls vs
docs/samples/balls): универсальный leaf — SMC-патч страйдов в
8-битные add/adc-цепочки при входе (140Т → 73Т/строку, BC/push/
pop/ex ушли); heal — отдельный `_bgi_heal_rows_raw` без адресной
арифметики вообще (77Т/строку против 204Т; референсный уровень).
Шар 16×16: ~9.6 → ~6.6 кТ (heal 16×173 + блит 16×236). Регресс:
blitw/bgi_img/sprites/balls — 1:1. Остатки разрыва с asm-
референсом — C-обвязка вызовов (batch-пасс, см. ниже) и спец-
blit-leaf (dstride 0: 30Т/строку — делать по замеру).
- [ ] **batch-пасс для спрайтов** (движок v2, §9.1) — В РАБОТЕ 2026-07-12.
Разрыв ~2× с asm-референсом (docs/samples/balls) — это НЕ
W3-скобка (_bgi_begin = 5 инструкций, ~35Т), а per-call
C-обвязка, повторяемая 2×N раз: полный клип (528/362 Б),
парс заголовка img, save/restore банка, SDCC-фрейм 7-арг вызова.
Референс всё это платит ОДИН раз на проход: W3 замаплен на видео
на всю программу (в цикле не трогает), банк-подрежим ставит один
`out` на проход (0x5C рисовать всё → 0x50 лечить всё), DI держит
на весь проход. Движок v2 повторяет это (per-pass W3/банк/клип-
fast-path, адресация из кэша w,h в структуре), НО DI оставляем
гранулярным (по строке в leaf) — референс держит длинный DI
только потому, что у него нет аудио-ISR; под CBL/IM2 длинный DI
сорвёт звук (docs/accel-fill-budget.md).
- [ ] клип fast-path «прямоугольник целиком на экране» в gfx_blit_part
(4 сравнения вместо полного пути) — делается в _gfx_blit_full/
_gfx_heal_full вместе с batch-пассом.
- [ ] размер: общий clip-хелпер для gfx_blit_part (528 Б) и gfx_heal
(362 Б) — клиппинг сейчас продублирован; кандидат −300..400 Б.
- [ ] gfx_line через accel для пологих диагоналей (runs ≥ 4-5 px)
- [ ] композитные примитивы с одним W3-swap на операцию
- [ ] ~~**спрайт-анимация: heal только открывшейся L-полоски**~~
ОТВЕРГНУТО 2026-07-12 для ПРОЗРАЧНЫХ спрайтов (наш случай).
L-полоска (bbox старой позиции минус новой) корректна ТОЛЬКО для
непрозрачного full-box спрайта: тогда зону перекрытия целиком
перекрашивает новый блит. У прозрачного спрайта (0xFF через
0x5C) в перекрытии дырки нового кадра НЕ перекрывают старые
непрозрачные пиксели → на хвосте остаётся «полумесяц» старого
изображения ВНУТРИ bbox-перекрытия, куда L-полоска не достаёт.
Точный «новооткрытый» набор = old_opaque AND NOT new_opaque —
это heal-с-маской по форме, а не по bbox, на Z80 дороже самого
heal. Подтверждено чтением референса docs/samples/balls:
restore_bg лечит ПОЛНЫЙ 16×16 (ld b,16 + 16 байт/строку),
L-полоску не использует. Итог: heal остаётся full-box; экономия
только через batch-пасс (амортизация обвязки), не через L-полоску.
L-полоска годна лишь для непрозрачных тайлов фона — не спрайтов.
Дисциплина «heal ВСЕ → блит ВСЕ» по-прежнему обязательна (слияние
heal+блит per-sprite выкусывает соседа на перекрытии — проверено
examples/balls 2026-07-11).
- [ ] **span-примитивы для узких прямоугольников**: при узкой стороне
≤ 8 линий chunked-rectfill проигрывает простому циклу
_bgi_hspan_raw/_bgi_vspan_raw (подготовка+precompute ~340Т уходят
впустую; break-even n≈9 — docs/accel-fill-budget.md). Варианты:
fast-path в диспетчере _gfx_rectfill256 («узкая сторона ≤ 8 →
цикл span'ов») или просто задокументировать рецепт для
пользователя. Делать по результатам профиля — если узкие
прямоугольники реально встречаются в горячем коде.
## Прочий backlog
- [ ] factoring parse_argv из crt0/crt0_banked в общий argv.s
- [ ] `restore SP on EXIT` (паттерн z88dk +pps) — проверить нужность
- [x] ~~CI: MAME с -aviwrite для screenshot-сравнения без человека~~
`toolchain/mame_interactive.py`: авто-запуск .exe вводом с
эмуляцией клавиатуры + Lua-таймер для скриншотов/выхода; сравнение
визуальное (Claude читает скриншот), не автоматический diff.
Полный справочник: docs/mame-autotest.md.
- [ ] linker duplicate-symbol warnings: сейчас фильтруются в
sprinter-cc (наши overrides _puts/___sdcc_heap/_asctime/…);
радикально — --nostdlib с ручным списком модулей z80.lib
- [ ] ZX Spectrum-совместимый target; ZX-Bus драйверы; PGO-tools
## Проверить на реальном железе
- [ ] **Port_Y banking trick** (адреса 0xC000+0x400*N → строки
Y..Y+15): в MAME 0.283 НЕ работает (2026-06-01). На железе:
dual-write тест → если работает, кэшировать Port_Y в putpixel
(~8× меньше OUT для Брезенхэма); если нет — вычистить из доков.
- [ ] **Подрежимы видеостраниц #50..#5F** (tests/gfxbanks +
tests/sprites, дизайн docs/sprite-api-design.md): перепрогнать
на железе и сверить со скриншотами MAME. Три открытых вопроса: (1) 0x58 — полное
подавление FF-записи (док/master-MAME) или FF протекает в
теневое ОЗУ (MAME 0.283)? (2) FF в VRAM — цвет 255 (MAME) или
display-подстановка фона из ОЗУ (тогда дешёвое стирание
FF-заливкой, tests/fferase)? (3) accel-путь = CPU-пути (в ПЛМ
пути физически разные).
- [ ] fdmax: лимит манипуляторов и зависание 9-го OPEN — MAME vs железо.
- [ ] CBL: щелчок перед первым проигрыванием звука за сессию (tests/
cbltest, tests/cblwav) — воспроизводится ТОЛЬКО на первом запуске
программы за сессию MAME, не зависит от содержимого потока
(тон/тишина/речь одинаково). Похоже на разовый прогрев звуковой
подсистемы MAME при первой активации канала — на реальном железе
скорее всего отсутствует. Проверить и закрыть либо описать как
реальный аппаратный эффект.
- [ ] **CBL: помехи в звуке при движении мыши** (tests/cblstream,
2026-07-07) — при потоковом воспроизведении движение мыши даёт
слышимые артефакты. Гипотеза (НЕ подтверждена): мышь может
делить SIO-канал/детект-бит с клавиатурой (порт 0x19 бит 0,
см. _irq_tramp.c), и трамплин классифицирует байты мыши как
«клавиатура» → сразу chain на DSS (0x0038), пропуская проверку
CBL-хука для ЭТОГО прерывания — при движении мыши часть тиков,
которые должны были обслужить CBL-насос, уходят мимо, кольцо
недоливается. Нужно исследование (MAME-дамп/лог трамплина).
## Known quirks (зафиксированы, обходы в libc)
- ESTEX $46 ENV: A=0 это NOT FOUND (док врёт) — memory/sprinter_platform
- ESTEX WRITE $14: на успехе DE=0, не счётчик; успех = CF=0 & A=0 —
memory/estex_write_de_quirk
- DSS: 8 манипуляторов, 9-й OPEN вешает систему; _fd_guard в libc —
memory/dss_fd_limit
- SDCC z80 `if(n!=g)g=n;` пишет (n-g) — memory/sdcc_z80_cmp_store_a_bug
---
# История — закрытые этапы
## Этап 10 — libc: сплит + FILE v2 + Solid-C (2026-07-05/06) ✅
Полный план/итоги: docs/libc-roadmap.md. Кратко:
- вся libc разложена «1 функция = 1 модуль» (~250 модулей, wildcard-
сборка, DCE на уровне файлов): gfx_text 4.4 КБ, timedir/ls/stattest
−3 КБ и т.д.; правила asm-связок: docs/libc-split-asm-cases.md
- **FILE* v2 (B+)**: ленивый буфер 512 на чтение/запись с
автопереключением, таблица OPEN_MAX=8, flush-on-exit, ungetc,
fprintf/vfprintf, fdopen/freopen/fclosall/fgetpos/fsetpos; горячие
пути fgetc/fputc/fgets на asm (fgets 100 КБ: 144с unbuffered-оценка
→ ~1 с). Дизайн: docs/file-buffering-design.md
- **scanf/fscanf/sscanf** — своё C-ядро (в SDCC z80 нет)
- **Solid-C совместимость закрыта**: <dos.h> (даты/диски/absread),
errno-алиасы, <sprinter_solid.h> — docs/solid_c_compatibility.md
- гигиена: stale .rel чистка, все 43 теста в make all, размерный
регресс (make size-check), контракт заголовков docs/libc-headers.md
- справочник API: docs/libc-reference.md
## Этап 9 — memory modes (tiny/small/big/huge/manual) ✅ 2026-05-30
`--memory MODE` в sprinter-cc; crt0-семейство (crt0/minimal/small/
banked); small: ESTEX GETMEM+SETWIN2 до gsinit, auto-detect W2 по
порту 0xC2; big/huge: параметризация crt0_banked/bank.s через
BANK_W1; --debug, --stack-size. Детали: memory/memory_modes_
implemented, memory/sprinter_memory_modes.
Дизайн-решения: одна sprinter.lib на все режимы (DCE per-member);
gfx.lib отдельно не нужен; libc_banked + sprinter_home.lib — идея
на потом (триггер: HOME забит user-кодом).
## Этап 8 — графика ✅
320×256×256 + 640×256×16, акселератор (Fill h/v, SMC block-size),
Брезенхэм, bitmap font (WIN_GET_ZG, interleaved), gfx_text.
memory/sprinter_graphics*, sprinter_accelerator, sprinter_font_format.
## Bank-local data ✅
--codeseg/--constseg/--dataseg BANKn + mkexe -p 0; фикс трамплина
(pop bc/out (c),b — сохраняет A); malloc из банка прозрачен (heap в
W2). memory/bank_local_data_pattern.
## Этапы 5-7 и ранняя libc ✅
- malloc/free (SDCC + runtime/heap.s в W2), page allocator
(mem_alloc_pages, ESTEX $3C-$3E + BIOS $C4), bank_read/bank_write
- crt0 argv-парсинг (IX-prefix, CP/M-space quirk, APPINFO basename),
sprinter-cc wrapper со всеми опциями
- errno+strerror/perror, open state-machine, atexit, setjmp/longjmp,
sleep, ENV API ($46), ffirst/fnext, getdatetime/setdatetime,
chdir/getcwd/mkdir/rmdir, conio (полный), mouse (RST 30h, 14 ф-й),
POSIX time API, sys/stat, assert
- text I/O split (stdio fast / conio attr) — memory/text_output_api_split
- SDCC stdlib НЕ переписываем — memory/sdcc_stdlib_works