_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>
25 KiB
TODO / Roadmap
Открытые задачи в порядке убывания приоритета; закрытые этапы — в «Истории» внизу. Текущий срез libc-работ: docs/libc-roadmap.md.
Ближайшее
Порядок реализации связки: сначала цепочка irq, потом FPS-делитель поверх неё (делитель — просто один слот цепи; так снимается EBUSY на сосуществование со своим тиком приложения).
- (1) irq: цепочка кадровых обработчиков — СДЕЛАНО 2026-07-15.
Массив
isr_t _irq_chain[4]+_irq_chain_nв W2-data (заменили_irq_user); трамплинtr_frameпроходит слоты (один тяжёлый сейв на всю цепь, пустой слот пропускается); APIirq_chain_add(0/-1+ENOMEM),irq_chain_remove(h);irq_install→ обёртка chain_add (EBUSY исчез),irq_remove()рвёт всю цепь; refcount таблицы на первом/последнем слоте; мутации под IRQ_DISABLE. All-modes W1-remap: трамплин копируется в W2 (_irq_tramp_w2buf, copy-safe — только jr/djnz + литерал jp 0x0038), вокруг вызова восстанавливает базовую W1-страницу (_irq_app_w1_page= IN 0xA2 при install). ПРОВЕРЕНО в MAME (tests/irqtest, 2 хендлера): tiny/big/huge — chain 97=97 → remove h2 → 48/0; huge = код в W1, remap работает (цепочка тикает синхронно). Дизайн: docs/im2_isr_design.md. FOLLOW-UP: (а) CTC-трамплин пока требует код в W2 → в small/hugeirq_ctc_install= EINVAL (нужна такая же W2-копия _irq_ctc_tramp); (б) small для мелких программ (BSS в W1) → irq_install EINVAL (safe; снять резервом area _IM2 в верхней W2). - (2) FPS-делитель (frame pacing) — gfx_set_fps_div(n): логический кадр = ровно n кадровых интервалов (50/25/16.7 fps), при переполнении слота — выравнивание на ближайший фронт; счётчик кадров через irq_chain_add(_gfx_frame_isr) (только tiny/big в первой версии). ПОЛНЫЙ ПЛАН: docs/sprite-api-design.md §9е (обсуждён 2026-07-14; ограничение EBUSY снято цепочкой).
- П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 помнит размер)
Новые возможности:
пакетное чтение/запись массива байт через акселератор— сделано 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.
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— СДЕЛАН 2026-07-12/13 (sprite.h: retained-модель + batch-пасс, см. пункт batch-пасса ниже) и расширен: W0-атласы (.atl, atlas_load/atlas_sprite_init, ISR-стаб — §9в), авто-анимация кадровая + tween (sprite_anim/ sprite_moveto/sprite_update тикер — §9г), gfx_pal_sync/fload/ fsave. Замерен бюджет: ~26К тактов на спрайт 16×16, лимит 14 спрайтов на стабильные 48 fps (§9д); демо examples/space, examples/rpgwalk (+rpgprof — профайлер кадра).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)
Оптимизации (не сейчас):
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/13: движок v2 (sprite.h: sprite_init/update/flip, retained per-page dirty), два прохода heal→blit под одной W3-скобкой/банком, спец-leaf'ы_bgi_blit_rows_raw(dstride 0, SMC sstride) и_bgi_heal_rows_raw, лин-ядра ≤64×64, ОДИН DI на спрайт (санкция 2026-07-12), noclip-ядра + funcptr-диспетч_gfx_blit_fn/_gfx_heal_fn(см. sprite-api-design §9б). Замер dev-MAME 16 шаров uncapped: clip 50 → noclip 60-61 fps (+20-22%); с vsync-капом кадр < 20 мс. Исходный уровень был 24 fps. (Диагноз разрыва с asm-референсом — per-call C-обвязка, не W3-скобка — и путь оптимизации описаны в sprite-api-design §9-9б.) На железе перепроверить снятие src[0]-фикса (§9а).- клип 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) — проверить нужность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