# TODO / Roadmap

Открытые задачи в порядке убывания приоритета. По мере появления реальных программ — приоритеты будут смещаться.

## Этап 5 — malloc / free + banking-aware page allocator ✅ ГОТОВО

- [x] SDCC's `malloc`/`free` + наш `runtime/heap.s` (полностью заменяет library heap.rel, 14000-байтный heap в окне 2)
- [x] `libc/mem/mem_alloc.c` — page allocator: `mem_alloc_pages`/`mem_free_block`/`mem_get_page`/`mem_info` через ESTEX `$3C/$3D/$3E` + BIOS `$C4`
- [x] `libc/mem/bank_io.c` — HOME-резидентные `bank_read`/`bank_write`/`bank_load_byte`/`bank_store_byte` со свопом W3 внутри
- [x] `examples/malloc_test/` — проверка SDCC's malloc (~210 64-байтных allocations через всю heap)
- [x] `examples/mem_test/` — проверка page allocator: 3 страницы, разные паттерны через bank_write, верификация через bank_read

## Этап 6 — argv parsing + sprinter-cc wrapper ✅ ГОТОВО

- [x] crt0 парсит ESTEX command-line из IX-prefix (inline asm в `runtime/crt0.s`)
- [x] Strip leading CP/M-style space (DSS quirk)
- [x] Передача `argc`/`argv` в main() через HL/DE (SDCC __sdcccall(1) ABI)
- [x] argv[0] = basename .EXE через ESTEX APPINFO ($47 subfn 2)
- [x] `runtime/crt0_minimal.s` — opt-out для очень маленьких программ
- [x] `runtime/crt0_banked.s` — теперь тоже парсит argv (parse_argv + get_progname скопированы из crt0.s; будет factored в argv.s когда возьмёмся за libsprinter.lib)
- [x] Bash-обёртка `bin/sprinter-cc`: `sprinter-cc -o foo.exe foo.c` одной строкой
- [x] Поддержка опций: `--memory`, `--memory-manual`, `--stack-size`, `--crt0=`, `--bank N=FILE.c`, `--debug`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`

## Этап 8 — графика (320×256×256 + 640×256×16 + accel + bitmap font) ✅ ГОТОВО

- [x] **8a** Graphics core: `gfx_init`/`gfx_done`/`gfx_clear`/`gfx_putpixel`/`gfx_pal_load`/`gfx_pal_set` (libc/gfx/gfx_core.c). Палитра через BIOS PIC_SET_PAL ($A4). Verified 320×256×256.
- [x] **8b** Линии/прямоугольники/fill через accelerator (libc/gfx/gfx_lines.c): `gfx_hline`/`gfx_vline` через accel Fill (LD C,C / LD E,E + SMC block-size), `gfx_rect`/`gfx_fill_rect` с heuristic выбором ориентации (h/v bursts count), `gfx_line` с Bresenham для диагоналей. `gfx_clear` тоже переписан на column-major accel (~4× быстрее).
- [x] **8c** 640×256×16 mode (libc/gfx/gfx_16.c): `gfx_*16` API, HIGH nibble = LEFT pixel (документация misleading), per-row RMW для vline (один байт = 2 горизонтальных пикселя).
- [x] **8d** Bitmap font + gfx_text (libc/gfx/gfx_text.c): шрифт через BIOS WIN_GET_ZG ($B8), interleaved layout `font[row*256+char]`, `gfx_text`/`gfx_putchar` для 320 mode, `gfx_text16`/`gfx_putchar16` для 640 mode с pair-table lookup.

См. memory/sprinter_graphics.md, sprinter_accelerator.md, sprinter_graphics_16.md, sprinter_font_format.md.

Открытые мелочи (не блокируют):
- [ ] Шрифт-quad для 640: per-cell палитра (mode 0x82 разрешает 1 из 4 палитр per 16×8 cell) — через прямой доступ к area-описания экрана 0x0300..0x039F

## Auto-banking (см. `memory/banking_roadmap.md` для деталей)

Phase 1 — file-level bin-packing — реализовывать когда проект перерастёт ~30 KB кода.

- [ ] `toolchain/auto_bank.py`:
    - Парсит размеры из `.rel`-файлов (или из .map после dry-run link'а)
    - First-fit-decreasing bin-packing
    - Уважает `#pragma codeseg BANKn` как manual override
    - Перелинковывает с новыми `-Wl-b_BANKn=...` параметрами
    - Печатает план распределения

Phase 2-5: incremental rebalance, declarative `banks.toml`, function-level, call-graph-aware. Только если/когда понадобится.

## Bank-local static data (mutable data в том же банке что и код) — ✅ ГОТОВО

- [x] Пример `examples/bank_local_data/` — функция в BANK1 со своим writable BSS array + const table + malloc-тест
- [x] `mkexe -p 0` для нулевого padding банков (BSS-storage обнуляется при загрузке)
- [x] Канонический рецепт: `--codeseg BANK1 --constseg BANK1 --dataseg BANK1` для bank1.c + `-Wl-b_BANK1=0x1C000` для линковки. **`--dataseg BANK1` РАБОТАЕТ** — раньше казалось обратное из-за trampoline bug который маскировал результат.
- [x] **Критичный фикс trampoline'a в runtime/bank.s** — старый `pop af; out (n), a` клобберил A → все banked-функции возвращающие uint8_t тихо возвращали мусор. Новый `pop bc; out (c), b` сохраняет A.
- [x] **malloc из banked-функции работает прозрачно** — heap живёт в W2 (HOME), W2 никогда не свапается trampoline'ом, pointer валиден из любого контекста. См. memory/bank_local_data_pattern.md.
- [x] Документация в memory: `memory/bank_local_data_pattern.md` (полный рецепт + malloc + nuances), `memory/sdcc_banking.md` (trampoline fix)
- [ ] Опционально — расширить `check_banks.py` чтобы показывать разбивку size = code + const + bss per bank (cosmetic)

Зачем: для модулей с большим private state (level loader, audio engine, scene data). Экономит W2 heap для динамики, а статика остаётся в бэке.

## Подсказки из solid-c (нативный Sprinter C — `third_party/solid-c/`)

После анализа solid-c'овской libc (см. `memory/solid_c_findings.md`) выявлены готовые паттерны для следующих недостающих функций. Приоритет от **высокого** к низкому:

### High-priority gaps (легко портировать, большая польза)
- [x] **`errno` + `strerror`/`perror`** — табличка 32 ошибок (libc/io/errno.c)
- [x] **Расширенный `open()`** для O_CREAT/O_TRUNC/O_APPEND/O_EXCL state machine
- [x] **`atexit`** — 8-callback LIFO + `exit()` + `_exit()` (libc/io/atexit.c)
- [x] **`setjmp`/`longjmp`** — 6-байт jmp_buf={sp,ix,pc} (libc/io/setjmp.c)
- [x] **`sleep(seconds)`** — 50Hz halt-loop (libc/io/sleep.c)
- [x] **ESTEX ENV API** ($46, getenv/putenv) — libc/io/env.c. Учли doc-bug: реально A=0 это NOT FOUND

### Medium-priority (нужно для shell-like утилит)
- [ ] **Mouse driver** — `rst $30h`, 17 функций. **Сначала тест что работает в MAME**.
- [x] **`ffirst`/`fnext` + ffblk_t struct** для directory listing — реализовано, demo: ls.exe
- [x] **`getdatetime`/`setdatetime`** через ESTEX $21/$22 — libc/io/time.c, demo: time_dir_test
- [x] **`chdir`/`getcwd`/`mkdir`/`rmdir`** — wrappers для ESTEX $1B-$1E — libc/io/fsdir.c
- [x] **conio: `kbhit`/`getch`/`getche`/`cputs`/`clrscr`/`gotoxy`** — реализовано
- [x] **conio extras**: `wherex`/`wherey` ($53), `wrchar`/`rdchar` ($58/$57), `textmode_get/set` ($50/$51), `clrscr_attr` ($56) + COLOR macros

### Low-priority — ✅ FILE* stack ГОТОВО

- [x] **Минимальный unbuffered FILE\*** — `libc/stdio/file.c` + `libc/include/stdio.h`. fopen/fclose/fputs/fgets/fread/fwrite/fseek/ftell/rewind/feof/ferror/clearerr/fflush + stdin/stdout/stderr как pseudo-streams. См. `memory/file_star_design.md` и `examples/filetest`.
- [ ] fprintf / fscanf — нужна printf-через-callback machinery. Пока пользователь может `sprintf(buf, ...) + fputs(buf, fp)`.
- [ ] Опциональный buffered mode (setvbuf, line/block buffering) — если когда-то понадобится.

### POSIX time API — ✅ ГОТОВО
- [x] `libc/io/posix_time.c` — time/localtime/gmtime/mktime/asctime/ctime поверх getdatetime. SDCC's time.rel избегаем (нельзя override _RtcRead). См. `examples/ptime`.

### sys/stat — ✅ ГОТОВО
- [x] `libc/io/stat.c` — POSIX stat/fstat. Гибрид open+fstat для файлов, ffirst+iter для папок (включая "."/".."). См. `examples/stattest` и `memory/estex_ffirst_dotdot.md`.

### assert — ✅ ГОТОВО (используем SDCC's __assert через fallback include path)

## libc/stdlib — ✅ не нужно делать (см. memory/sdcc_stdlib_works.md)

Проверено через `examples/stdlib_test/`: SDCC z80.lib содержит работающие реализации:
- `atoi/atol/atof, strtol/strtoul, rand/srand, qsort/bsearch, abs/labs, div/ldiv`
- Полный `<string.h>` (memchr/cmp/set/cpy, strcat/cmp/cpy/len/chr/spn/etc.)
- `<ctype.h>` (toupper/tolower)
- `<math.h>` (sinf/cosf/sqrtf/etc.)

Линкер автоматически тянет из z80.lib когда нужно. **НЕ переписывать**.

Наши Sprinter-specific обязательные модули остаются: atexit, env, errno, setjmp, putchar/puts/getchar, conio, fsdir, time, mouse, open/read/lseek/close.

## Build-system: libsprinter.lib + sprinter-cc — ✅ ГОТОВО

- [x] `lib/Makefile` — собирает каждый libc/*.c в `.rel`, архивирует через sdar в `lib/sprinter.lib`
- [x] Включает runtime/bank.s и runtime/heap.s (auto-pulled при __banked/malloc)
- [x] `bin/sprinter-cc` — bash-wrapper: `sprinter-cc -o foo.exe foo.c` одной строкой
- [x] Поддержка опций `--crt0=default|minimal|banked`, `--bank N=FILE.c`, `-I`, `-L`/`-E`/`-S`, `-Wl`, `--mkexe`
- [x] `examples/hello_sccc/` — демо: `hello.c` собирается за один shell-вызов, размер совпадает с ручным Makefile (925 байт)
- [x] Split `putchar.c` → `putchar.c` + `puts.c` для per-function granularity (puts override SDCC's z80.lib version)
- [x] Включён в `make all` (зависимость `lib` перед `examples`)

Возможные улучшения (опционально):
- [ ] Мигрировать остальные examples на sprinter-cc вместо ручных Makefile (косметика)
- [ ] Дальнейшая декомпозиция libc/*.c per-function (но текущая granularity уже даёт нужный размер — линкер пакетует .rel целиком, и для большинства файлов это одна функция)

## Этап 9 — memory modes для sprinter-cc

DSS выделяет страницы памяти по размеру приложения: < 16 KB → одна страница, в остальные окна подключается «страница #FF» (read=0xFF, write игнорится). Из-за этого CODE-в-W1 + DATA-в-W2 для маленькой программы молча ломается. См. [memory/sprinter_memory_modes.md](../../.claude/projects/-Volumes-SAM8-Projects-DIY-Z80-Sprinter-C-Compiler/memory/sprinter_memory_modes.md).

- [x] **`tiny`**: всё (CODE+DATA+стек) в W2. Default. Verified hello/argv/conio/malloc/file/etc.
- [x] **`--memory MODE` флаг в sprinter-cc**: parser + per-mode дефолты CODE_LOC/DATA_LOC, override через явные `--code-loc`/`--data-loc`. tiny работает; small/big/huge компилируются с warning'ом (runtime не готов). Реализовано 2026-05-30.
- [x] **`--memory-manual SPEC`**: парсит `CODE=W1|W2,DATA=W1|W2|SAME,BANKED=W1|W3`. Реализовано 2026-05-30.
- [x] **`small` runtime**: `runtime/crt0_small.s` использует ESTEX `$3D GETMEM` + `$3A SETWIN2` чтобы выделить и замапить W2-страницу ДО gsinit. **НЕ** BIOS `$C4` — стек на этом этапе в W1 (boot_stack в HOME), а BIOS требует стек в W2. После маппинга SP переключается на 0xBFFE, дальше стандартный flow. Реализовано 2026-05-30, verified hello.exe.
- [x] **`small` auto-detect для >16 KB программ**: `crt0_small.s` читает порт `0xC2` (текущая страница в W2 — не `0xA2`! это W1). Если 0xFF — выделяет page; иначе DSS уже сделала это (программа сама вылезла в W2). Один crt0 покрывает 0..30 KB. mkexe также разрешает HOME span W1+W2 (0x4000..0xBFFF). Verified hello: small (5 KB файл, SETWIN2 path) + 32 KB файл (auto-skip). Реализовано 2026-05-30.
- [x] **`big` runtime** (tiny + banked code в W1): параметризовали `crt0_banked.s` + `bank.s` через `.ifdef BANK_W1` — другой banking port (0xA2 vs 0xE2), другой load-addr (0x4000 vs 0xC000). sprinter-cc prepend'ит `BANK_W1 = 1` при `--memory big`, передаёт `mkexe -B 0x4000`. Пример `examples/banked_big/`. Реализовано 2026-05-30.
- [x] **`huge` runtime** (small + banked code в W3): merge W2-detect логики из `crt0_small.s` в `crt0_banked.s`. Существующий пример `examples/banked/` теперь использует MEMORY=huge. Реализовано 2026-05-30.
- [x] **`--debug` флаг**: prepend `DEBUG_RT = 1` в crt0 + `-DDEBUG_RT` в sdcc. Открывает symbol `_w2_self_allocated` (uint8_t) — runtime diagnostic кто аллоцировал W2. Реализовано 2026-05-30.

### Дизайн-решения по libc и crt0

**Одна `sprinter.lib`** работает для всех memory mode — `.rel`-члены relocatable, SDLD делает dead-code elimination per-member (без графики не подтягивает `gfx_core.rel` и т.д.). Verified hello vs malloc_test через map-файлы.

**`gfx.lib` отдельно — НЕ нужен**: dead-code elimination уже работает.

**`libc_banked` (libc в bank вместо HOME)** — идея на потом, когда HOME (16 KB) забит user-кодом + libc в `huge` mode. Реализуется через `--codeseg BANK0` при компиляции libc; trade-off: trampoline ~30 циклов на каждый libc-вызов. Триггер: реальная программа упрётся в HOME budget.

**HW-зависимые модули — `sprinter_home.lib` отдельно.** Часть libc физически не может быть забанкована в W3, потому что она РАБОТАЕТ с W3:
- `gfx_*` — пишет в видеопамять `0xC000+` после swap W3 на video page
- `bank_io` (mem_alloc_pages/bank_read/bank_write) — swap'ит W3 через `OUT (0xE2)`
- Будущие ISR — прерывание может прийти когда W3 на чём угодно

В huge mode эти модули ДОЛЖНЫ остаться в HOME (W1). Когда будем делать `libc_banked`, **одновременно** выделяем `sprinter_home.lib` (HOME-only) из `sprinter.lib` (bankable). Финальная схема:
```
sprinter_home.lib    HOME-only:   gfx, bank_io, ISR shims
sprinter.lib         bankable:    printf, malloc, string, conio, stdio, env, ...
sprinter_banked.lib  тот же sprinter.lib но --codeseg BANK0 (для huge)
```
Триггер: реализация `--memory huge` runtime.

**crt0 — по одному на mode:**
- `crt0.s` — текущий, для **tiny/big**: SP=0xBFFE, парсит argv (W2-ресурс уже выделен DSS).
- `crt0_minimal.s` — текущий, для tiny без argv.
- `crt0_small.s` — **новый, step 3**: для **small/huge**, аллоцирует W2 через `mem_alloc_pages` ДО gsinit, маппит в порт `0xA2`, потом стандартный flow.
- `crt0_banked.s` — текущий, для **big**: trampoline-таблица для W3 банков, CODE в W2.
- `crt0_banked_small.s` — **новый**: huge = small (W2-alloc) + banked (W3 trampolines).

sprinter-cc подбирает crt0 по `--memory` mode (сейчас `--crt0=` это override).
- [x] **Настраиваемый размер стека**: флаг `sprinter-cc --stack-size BYTES`. Wrapper генерирует `heap_top.s` с `___sdcc_heap_end = stack_top + 1 - stack_size`, отдельный .rel линкуется per-program. Default ≈1278 байт (heap_top=0xBB00) из `runtime/heap_top.s`. Реализовано 2026-05-30.

Интерфейс: `sprinter-cc --memory [tiny|small|big|huge|manual] [--memory-manual SPEC] [--stack-size N] foo.c`. `--memory-manual` имеет смысл только с `--memory manual`.

## Known issues / quirks

- **ESTEX $46 ENV API**: ✅ работает. Док-ция в `DiskSyscalls.txt v1.6` ошибочно описывает return-status — A=0 это NOT FOUND, не FOUND. Зафиксировано в `memory/sprinter_platform.md`.

## ОБЯЗАТЕЛЬНЫЕ ЗАДАЧИ ДЛЯ V2 (после релиза v1)

### Turbo-C-style graphics API (BGI-like) — **MUST для v2**

Расширить наш `gfx_*` API до уровня **Turbo-C `<graphics.h>`** (BGI) для MS-DOS.
Программисты привыкшие к Turbo-C должны переносить графический код 1-в-1.

**Что должно быть** (на основе Borland BGI):

Setup/teardown:
- `initgraph()` / `closegraph()` — у нас сейчас `gfx_init`/`gfx_done`, добавить alias
- `getmaxx()` / `getmaxy()` — макрос на GFX_WIDTH-1 / GFX_HEIGHT-1
- `cleardevice()` — alias to gfx_clear
- `getgraphmode()` / `setgraphmode()` — у нас get_videomode/set_videomode

Color/palette:
- `setcolor(c)`, `getcolor()` — current draw color
- `setbkcolor(c)`, `getbkcolor()` — background color
- `setpalette(idx, c)` — палитра entry
- `getpalette(&info)` — read all palette

Primitives (мы уже имеем эквиваленты — добавить BGI-имена как aliases):
- `putpixel(x, y, c)` — есть как gfx_putpixel
- `getpixel(x, y)` — нужно реализовать (RMW обратное — IN)
- `moveto(x, y)`, `lineto(x, y)`, `linerel(dx, dy)` — current point + line drawing
- `line(x1, y1, x2, y2)` — есть как gfx_line
- `rectangle(x1, y1, x2, y2)` — есть как gfx_rect (но другой API: x1,y1,x2,y2 vs x,y,w,h!)
- `bar(x1, y1, x2, y2)` — есть как gfx_fill_rect
- `bar3d(x1, y1, x2, y2, depth, topflag)` — новое: rect + 3d edges
- `circle(x, y, r)`, `arc(...)`, `ellipse(...)`, `pieslice(...)` — новые primitives
- `fillpoly()`, `drawpoly()` — полигоны
- `floodfill(x, y, border_color)` — заливка

Text on graphics screen:
- `outtext(s)` / `outtextxy(x, y, s)` — есть как gfx_text (alias)
- `settextstyle(font, dir, size)` — multiple bitmap fonts
- `gettextsettings(&info)`
- `textwidth(s)` / `textheight(s)` — measure

Image manipulation:
- `imagesize(x1, y1, x2, y2)` — bytes needed for getimage
- `getimage(x1, y1, x2, y2, buf)` — save rect to buffer
- `putimage(x, y, buf, op)` — paste back with COPY_PUT/XOR_PUT/AND_PUT/OR_PUT/NOT_PUT

Clipping/viewport:
- `setviewport(x1, y1, x2, y2, clip)` — drawing clip rect
- `getviewsettings(&info)`
- `clearviewport()`
- `setactivepage(p)` / `setvisualpage(p)` — двойная буферизация (Sprinter имеет 2 screen)

Line style:
- `setlinestyle(style, pattern, thickness)` — SOLID_LINE / DOTTED_LINE / etc.
- `getlinesettings(&info)`

**Acceptance:** перенос типичной Turbo-C BGI программы (рисующей с использованием
moveto/lineto/circle/bar/setcolor) должен работать без существенных правок.

**Notes:**
- BGI fonts (TRIPLEX/SANS_SERIF/GOTHIC) — у нас один BIOS font, остальные нужно
  добавить (как bitmap data в lib)
- imagesize/getimage/putimage — самые востребованные для game/animation
- Active/visual page (двойная буферизация) — Sprinter поддерживает 2 graphics pages,
  нужен API switching

См. также `examples/` Turbo C 2.x BGIDEMO как reference что нужно.

### IM2 Interrupt Handlers — **MUST для v2**

User-задаваемые ISR через Z80 IM 2 mode.  Нужны для:
- Timer ticks (50 Hz frame counter, плавная анимация)
- Music playback (AY, COVOX)
- Real-time games (input + game logic + render в interrupt-driven)
- Async keyboard / mouse handling

**Status:** ОТЛОЖЕНО до v2.  Полный research + design в `docs/im2_isr_design.md`.

**Решение по архитектуре:** реализовать как отдельный memory mode `--memory im2`
(вместо того чтобы лезть во все существующие crt0).  Detail'и в design-doc.

**Резюме research'а** (полный текст в `docs/im2_isr_design.md`):
- Vector 0xFF — frame + keyboard + CBL.  Disambiguation по портам 0x19 / 0xFE
- Mouse hardware-IRQ не приходит (на текущей плате)
- Vector table / ISR / stack ОБЯЗАНЫ быть в W2 (0x8000..0xBFFF)
- DSS имеет свой IM 2 handler — нужно chain'иться (иначе клавиатура / SYSTIME ломаются)

### Прочие крупные пункты для v2

- [ ] **FILE API rewrite — buffered streams** — текущая реализация в
  `libc/stdio/file.c` это provisional unbuffered shim (каждый fputc/fgetc
  = один read/write syscall).  Нужна полноценная buffered семантика
  как в Solid-C:

  ```c
  typedef struct {
      uint   flags;     // +0..1   file status flags
      int    level;     // +2..3   empty/fill level of buffer
      char  *curp;      // +4..5   current active pointer
      int    fd;        // +6..7   underlying low-level fd
      char  *buffer;    // +8..9   data transfer buffer
      char   hold;      // +10     ungetc byte if no buffer
      short  token;     // +11..12 reserved
      char   dummy;     // +13     reserved
  } FILE;
  ```

  stdin/stdout/stderr — fd-маркеры `0 / -1 / -2`.  Отрицательные для
  stdout/stderr выбраны намеренно: ESTEX OPEN может вернуть positive
  small fd (1, 2, …) для обычного файла → если бы stdout=1, реальный
  fd=1 сталкивался бы с идентификатором.  fd=0 для stdin безопасно
  (ESTEX 0 не возвращает).  Сами fd не передаются в syscall'ы —
  диспетчеризация по флагам `_F_CONIN/_F_CONOUT`.

  Принтер-потоки (stdaux/stdprn) НЕ реализуем — Sprinter принтерной
  API не имеет.

  Альтернатива — взять реализацию из third_party/solid-c (sources в
  `SRC/CLIB/`); там есть готовый buffered FILE + fopen/fread/fwrite/
  fseek/setvbuf и т.д.  Адаптировать к нашим open/read/write/lseek.

  При rewrite заодно решить deferred issues stdio-review:
  - `fwrite` short-write должен ставить `_F_ERROR`
  - `fgets(buf, 1, fp)` — стандарт говорит "empty string", мы вернули NULL
  - `mode_to_flags` — break-out на '+' (cosmetic)

- [ ] **Audio API** — AY-3-8910 + COVOX через прерывания (требует IM2)
- [ ] **ISA-8 slot support** — ZX-Bus карты (sound, network, etc.) — требует IM2 + чтения portов

## Прочие задачи (v1 backlog, не блокирующие)

- [x] **#9: text I/O split (Turbo-C style)** — stdio (puts/printf/putchar) теперь fast no-attr через PCHARS/PUTCHAR. conio (cputs/cprintf/putch) применяет attr через textcolor/textbackground/textattr. KEEP_EXIST_ATTR → conio fallback на fast path. Verified в hello.exe. См. `memory/text_output_api_split.md`. Реализовано 2026-05-31.
- [x] **Mouse API полный** (резидентный driver, RST 30h) — все 14 функций обёрнуты (init/show/hide/refresh/read/goto/bounds/text_cursor/load_cursor/get_cursor/get/set_sensitivity/video_mode_changed). См. `memory/mouse_api.md`. Verified в MAME 2026-05-31. Sensitivity = divider (меньше = быстрее).
- [ ] Interrupt handlers — IM 2 vector table в HOME для user ISR'ов
- [ ] Поддержка `restore SP on EXIT` (паттерн из z88dk +pps) — проверить нужно ли
- [ ] CI: автоматически запускать MAME с `-aviwrite` для screenshot-сравнения, чтобы тесты примеров проходили без человека

## Идеи на потом

- Поддержка `<setjmp.h>` (есть в SDCC stdlib — нужно протестировать что наш crt0 совместим)
- `<time.h>` через ESTEX SYSTIME (`$21`) и CMOS BIOS-функции
- ZX Spectrum-совместимый режим как отдельный target (для портирования спектрумовских программ)
- Поддержка ZX-Bus карт (sound, network, etc.) — нужны драйверы
- Profile-guided optimization tools (hot/cold detection) для крупных программ

## Linker duplicate-symbol warnings (благоприятные, отфильтрованы)

Когда мы сознательно overrides'им SDCC z80.lib функции собственной версией в `sprinter.lib`, `sdldz80` пишет `?ASlink-Warning-Definition of public symbol '...' found more than once`. Линкер берёт первое найденное определение (наше), поэтому поведение корректное — warning только noise.

Текущие overrides:
- `_puts` — наша версия через PCHARS+\r\n vs SDCC posix puts
- `___sdcc_heap` — наш heap в W2 vs SDCC's стандартный
- `_asctime`, `_localtime` (и возможно другие из time) — наш `posix_time.c` через ESTEX SYSTIME vs SDCC's `time.rel` который зависит от `_RtcRead`

**Текущее решение:** `bin/sprinter-cc` отфильтровывает warning-блок (warning + 2 follow-up `Library:` строки) из вывода `sdc

c`. Через `-v` (verbose) всё показывается. Реализовано через awk-pipe.

**Возможные улучшения:**
- Перейти на explicit `--nostdlib` + ручной список нужных модулей из z80.lib (string, math, stdlib без override'нутых) — убрать ИСТОЧНИК warning'ов, не маскировать
- Или: переименовать наши `_puts` → `_puts_sprinter` + alias через linker flag (не уверен что SDCC поддерживает)
- Или: оставить как сейчас (рабочее и benign) — приоритет низкий

## TODO: проверить на реальном железе

- [ ] **Port_Y banking trick** (`docs/part2/SprinterGraphics programming.txt`):
      доку утверждает что после `OUT (0x89), Y` адреса 0xC000+0x400*N в окне W3
      маппятся на строки Y..Y+15 (одно программирование → 16 строк).
      Empirical 2026-06-01 в MAME 0.283 этот trick **не работает** — пиксели
      по адресам выше 0xC000+row_width уходят в невидимую область.  Канонический
      `docs/samples/plasma2.asm` тоже не использует banking, переустанавливает
      Port_Y per row.
      План:
      1. Получить доступ к реальному Sprinter
      2. Запустить тест dual-write (`_gfx_putpixel_raw` + второй write в `0xD000+x`)
      3. Если на железе видны двойные линии → бага MAME, открыть issue с
         минимальным репро
      4. Если на железе тоже одна линия → документ неверный, удалить упоминание
         из доки и просто оставить текущую реализацию (Port_Y per pixel)
      5. Если banking работает на железе → внедрить кэширование Port_Y в
         `_gfx_putpixel_raw` (sentinel out-of-range, см. memory/gfx_port_y_banking.md)

      Связанный выигрыш для Bresenham (60-pixel диагональ) — около 8× меньше
      OUT (0x89) операций, для `gfx_fill_rect 320x256` — 16× меньше.  Не блокирует
      release v1.

## GFX: расширения по `docs/part2/accelerator_doc.txt`

После прочтения детального accelerator doc выявлены незакрытые направления.
Сейчас в коде используется только горизонтальный/вертикальный Fill mode.

### Quick wins для текущих primitives

- [ ] **Заменить SMC на `LD A, (var)` для block-size**.  Документ явно
      разрешает `LD A, (HL)`, `LD A, (BC)`, `LD A, (DE)` (но не `LD A, r`).
      Это уберёт SMC complexity в `gfx_lines.c:hfill_chunk/vfill_chunk` и
      `gfx_16.c:g16_hfill_chunk`.  Запрещено только register-to-register.
- [ ] **Кэширование block-size**.  Документ показывает что accel запоминает
      block size между bursts (см. `Horizontal_Line_Fill`: устанавливают
      size + `LD B,B` отключение, потом включают Fill mode и используют
      сохранённый size).  Для `gfx_fill_rect` с 100 одинаковыми
      строками — установить size 1 раз, а не 100.

### Bank-prefix modes (port 0xE2 bits)

Документ показывает три варианта банка видеостраницы помимо стандартного 0x50:

| Bank byte | Effect |
|---|---|
| 0x50 | Normal write — пишется в shadow + видимый |
| 0x54 | "no copy in main shadow RAM" |
| 0x58 | **"FF is transparent"** — байт 0xFF при write оставляет background |
| 0x5C | both |

Bank 0x58 объясняет почему mouse cursor рисуется с 0xFF-прозрачностью.
Это путь к **sprite-blending через accel block copy**:

- [ ] **`gfx_set_bank_transparent(on)`** или флаг в `gfx_set_bank` для
      выбора 0x50/0x58 при отрисовке sprite'ов
- [ ] Использовать в новом `gfx_blit()` чтобы по факту получать
      transparent sprites через accel-копию

### Block copy mode (sprite blit'ы)

`LD L,L` (horizontal) и `LD A,A` (vertical) — режим копирования блока через
256-байтную accel memory.  Это базис для blit'ов.

- [ ] **`gfx_blit(src_data, x, y, w, h)`** — копирование sprite'а
      (произвольный размер, через accel)
- [ ] **`gfx_blit_transparent(src, x, y, w, h)`** — с использованием bank 0x58

См. `Draw_Restangle_Data` в accelerator_doc.txt как референс.

### AND / OR / XOR operations через accel

Документ показывает что accel поддерживает логические операции с блоками
данных.  Применения:
- XOR — инверсия области (выделение selection в UI)
- OR / AND — masking, alpha-style blending
- См. пример в accelerator_doc.txt: "256 bytes block coding via XOR"

- [ ] **`gfx_xor_rect`** / **`gfx_or_rect`** / **`gfx_and_rect`** —
      примитивы логических операций над прямоугольником
- [ ] **`gfx_invert_rect(x, y, w, h)`** — alias на xor с 0xFF

### Bitmap fonts разных размеров

Сейчас `gfx_text` / `gfx_putchar` хардкоженно работают с 8×8 шрифтом
(BIOS WIN_GET_ZG возвращает 256×8 байт).  Для будущих UI / титульников
нужны:
- [ ] **`gfx_set_font_size(w, h)`** — переключить ширину/высоту glyph'а
- [ ] **`gfx_set_font_data(ptr, w, h, advance)`** — заменить указатель
      на пользовательский шрифт + размеры
- [ ] Поддержка **proportional** (advance != w) шрифтов — добавить
      array advance[256] на ширину каждого glyph'а
- [ ] **Big-font режимы**: 8×16, 16×16, 16×8 (для титульников)
- [ ] Возможно отдельный API `gfx_text_ex(x, y, str, font_id)` где
      font_id выбирает один из загруженных шрифтов
- [ ] **Anti-alias 2-bit шрифты** (бит фон / бит граница / 2-бит alpha?)
      — far future, для smooth UI

## Финальный этап оптимизаций (не сейчас)

- **`gfx_line` через accel для пологих диагоналей** — Bresenham для линии с |dy| << |dx| (или наоборот) выдаёт длинные runs одинакового Y (или X): пиксель, пиксель, пиксель, шаг Y, пиксель, пиксель... Каждый такой run — это готовый аргумент для `gfx_hline` (или `vline`).  
  План исследования: посчитать длину runs как функцию от наклона; решить минимальный run length, при котором выгоднее accel hline чем N×putpixel (overhead accel ~20µs, putpixel ~5µs — accel выгоднее при run ≥ 4-5 px); для крутых диагоналей (dx ≈ dy) оставить Bresenham, для пологих — run-length-based fill.
  Сейчас `gfx_line` orthogonal cases уже через accel — оптимизировать только косые.

- **`gfx_fill_rect` с одним W3-swap на всю операцию** — сейчас каждый внутренний `gfx_hline`/`gfx_vline` делает свой DI/save-W3/restore-W3/EI. Можно сделать internal `_fill_rect_inner` который держит W3 замапленным и DI весь цикл; ~20µs × количество строк/столбцов экономии. Применимо ко всем композитным примитивам.
