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

339 lines
25 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.
## Ближайшее
Порядок реализации связки: **сначала цепочка irq, потом FPS-делитель
поверх неё** (делитель — просто один слот цепи; так снимается EBUSY
на сосуществование со своим тиком приложения).
- [x] **(1) irq: цепочка кадровых обработчиков** — СДЕЛАНО 2026-07-15.
Массив `isr_t _irq_chain[4]` + `_irq_chain_n` в W2-data (заменили
`_irq_user`); трамплин `tr_frame` проходит слоты (один тяжёлый сейв
на всю цепь, пустой слот пропускается); API `irq_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/huge
`irq_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 помнит размер)
Новые возможности:
- [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 — пример-курсор.
- [x] ~~**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)
Оптимизации (не сейчас):
- [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Т/строку — делать по замеру).
- [x] ~~**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) — проверить нужность
- [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