diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..bbdc742
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,68 @@
+# Sprinter C-Compiler — правила проекта
+
+Target-слой SDCC 4.5 (z80) для компьютера Sprinter Sp2000: crt0,
+линковка, libc, mkexe. Общение и комментарии — на русском.
+
+## Сборка и проверка
+
+```
+make # tools + lib + все тесты (43) + examples
+make -C lib # только libc → lib/sprinter.lib
+make floppy # упаковать все .exe в mame/v306/IMG/mc.img
+make size-check # размерный регресс: _CODE vs docs/size_baseline.tsv
+make size-baseline # принять текущие размеры эталоном
+```
+
+Одиночный тест: `cd tests/<имя> && make run` (пакует ТОЛЬКО этот exe
++ EXTRA_DATA на дискету и запускает MAME). Тесты в MAME гоняет
+пользователь — готовь дискету и проси прогнать.
+
+После правок libc: пересборка от чистого листа (`make -C lib clean`)
+не обязательна — stale .rel чистятся автоматически; `make size-check`
+обязателен (рост _CODE без причины — регрессия).
+
+## Правила libc
+
+- **1 публичная функция = 1 .c-модуль** (линкер тянет .rel целиком —
+ гранулярность файлов = гранулярность DCE). Никакой группировки
+ «используются вместе». Internal-хелперы — тоже по одному на модуль
+ (`_`-префикс); общие статики — в отдельные data-модули
+ (`_xxx_state.c`); internal-заголовки (`_file.h`, `_gfx.h`, …) —
+ рядом с исходниками, НЕ в libc/include.
+- Имя файла = имя функции. lib/Makefile собирает wildcard'ом —
+ ничего регистрировать не надо.
+- Комментарии — на русском; шапка модуля объясняет что/зачем + ABI.
+- File-scope переменные НЕ инициализировать `= 0` (crt0 зануляет
+ _DATA; см. memory/sdcc_static_storage_gotcha).
+- asm-связки между модулями: `call/jp _global` — ок; `jr/djnz` через
+ границу и fall-through — НЕЛЬЗЯ (docs/libc-split-asm-cases.md).
+- Заголовки: сначала пробовать include_next-паттерн; полная замена
+ SDCC-заголовка обязана дублировать его контракт
+ (docs/libc-headers.md).
+- Справочник API — docs/libc-reference.md (обновлять при добавлении
+ функций).
+
+## ABI и платформа (кратко; детали в memory/)
+
+- SDCC `__sdcccall(1)`: arg1 → HL (8-бит → A), arg2 → DE, остальные
+ на стеке (callee-pops в __naked); **возврат int/ptr в DE**, uint8 в A.
+ IX callee-saved (в __naked с IX — push/pop обязательны).
+- ESTEX (rst #0x10): CF=1 — ошибка, код в A → `call __errno_set`;
+ все регистры клобберятся (IX сохранять); стек обязан быть в W2.
+- BIOS (rst #0x08): строки/буферы в #4000-#BFFF.
+- Квирки: ESTEX WRITE возвращает DE=0 на успехе (судить по CF/A);
+ лимит 8 файловых манипуляторов, 9-й OPEN ВЕШАЕТ DSS (_fd_guard);
+ ENV $46: A=0 = NOT FOUND.
+- Перед обвинением компилятора/железа — подтвердить артефактом
+ (сгенерированный .asm в lib/build/, дамп, репро) — см.
+ memory/defer_unexplained_quirks.
+
+## Структура
+
+- `libc//*.c` — модули libc; `libc/include/` — публичные заголовки
+- `runtime/` — crt0-семейство, heap, bank (bank.s собирается per-build)
+- `bin/sprinter-cc` — обёртка компилятора; `toolchain/mkexe` — упаковщик
+- `tests/` — по одному API/фиче; `examples/` — реальные приложения
+- `docs/` — дизайн-доки; `docs/TODO.md` — roadmap
+- `third_party/solid-c/` — нативный Sprinter C (референс, CP866;
+ их ABI несовместим — только как образец)
diff --git a/docs/TODO.md b/docs/TODO.md
index 8a8a36c..0db398b 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -1,436 +1,153 @@
# TODO / Roadmap
-Открытые задачи в порядке убывания приоритета. По мере появления реальных программ — приоритеты будут смещаться.
+Открытые задачи в порядке убывания приоритета; закрытые этапы — в
+«Истории» внизу. Текущий срез libc-работ: docs/libc-roadmap.md.
-## Этап 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`
-- Полный `` (memchr/cmp/set/cpy, strcat/cmp/cpy/len/chr/spn/etc.)
-- `` (toupper/tolower)
-- `` (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 ``** (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-сравнения, чтобы тесты примеров проходили без человека
-
-## Идеи на потом
-
-- Поддержка `` (есть в SDCC stdlib — нужно протестировать что наш crt0 совместим)
-- `` через 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 × количество строк/столбцов экономии. Применимо ко всем композитным примитивам.
+## Ближайшее
+
+- [ ] **П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)
+
+Расширить `gfx_*` до уровня Turbo-C ``: initgraph/
+closegraph/cleardevice-алиасы, current point (moveto/lineto/linerel),
+setcolor/setbkcolor, getpixel, circle/arc/ellipse/pieslice, drawpoly/
+fillpoly, floodfill, bar/bar3d, outtext[xy]/settextstyle/textwidth,
+imagesize/getimage/putimage (COPY/XOR/AND/OR/NOT_PUT), setviewport/
+клиппинг, setactivepage/setvisualpage (2 страницы есть), setlinestyle.
+Acceptance: типичная BGI-программа переносится без существенных
+правок. Референс: Turbo C 2.x BGIDEMO.
+
+### IM2 Interrupt Handlers
+
+User-ISR через IM 2 — timer ticks, музыка (AY/COVOX), real-time
+игры, async input. Решение: отдельный memory mode `--memory im2`.
+Полный research/design: docs/im2_isr_design.md (vector 0xFF общий,
+disambiguation по портам 0x19/0xFE; таблица/ISR/стек в W2; chain к
+DSS-хендлеру обязателен).
+
+### Прочее 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 помнит размер)
+
+Новые возможности:
+- [ ] `gfx_blit` / `gfx_blit_transparent` — block copy (LD L,L / LD A,A),
+ прозрачность через bank 0x58 («FF is transparent»)
+- [ ] `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)
+
+Оптимизации (не сейчас):
+- [ ] gfx_line через accel для пологих диагоналей (runs ≥ 4-5 px)
+- [ ] композитные примитивы с одним W3-swap на операцию
+
+## Прочий backlog
+
+- [ ] factoring parse_argv из crt0/crt0_banked в общий argv.s
+- [ ] `restore SP on EXIT` (паттерн z88dk +pps) — проверить нужность
+- [ ] CI: MAME с -aviwrite для screenshot-сравнения без человека
+- [ ] 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 для Брезенхэма); если нет — вычистить из доков.
+- [ ] fdmax: лимит манипуляторов и зависание 9-го OPEN — MAME vs железо.
+
+## 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 совместимость закрыта**: (даты/диски/absread),
+ errno-алиасы, — 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
diff --git a/docs/libc-reference.md b/docs/libc-reference.md
new file mode 100644
index 0000000..a44332c
--- /dev/null
+++ b/docs/libc-reference.md
@@ -0,0 +1,194 @@
+# libc — справочник API (2026-07-06)
+
+Сводка по заголовкам: сигнатура + одна строка + особенности ABI.
+Детали дизайна: docs/libc-headers.md (контракт затенения SDCC),
+docs/file-buffering-design.md (FILE*), docs/solid_c_compatibility.md.
+
+Общие соглашения:
+- ошибки: возврат -1/NULL/EOF + `errno` (код DSS as-is, см. errno.h);
+- SDCC `__sdcccall(1)`: 1-й аргумент HL (8-битный — A), 2-й — DE,
+ остальные на стеке; **int/указатель возвращается в DE**;
+- строки для BIOS-вызовов (rst 8) должны лежать в #4000–#BFFF;
+- стек при любых ESTEX/BIOS-вызовах — в W2 (обеспечено crt0).
+
+## — полная замена SDCC (контракт: printf-семейство из z80.lib)
+
+Из SDCC z80.lib: `printf sprintf vprintf vsprintf`.
+
+Консоль (ESTEX, без атрибутов — быстрый путь; цветной вывод — conio):
+| Сигнатура | Описание |
+|---|---|
+| `int putchar(int c)` | символ через PUTCHAR $5B; '\n'→CR LF |
+| `int getchar(void)` | блокирующий WAITKEY $30, ASCII |
+| `char puts(const char *s)` | строка + '\n' (посимвольно через putchar) |
+| `char *gets(char *buf)` | строка с консоли, без контроля длины |
+| `void dec8/dec16/dec32(v)` | десятичная печать без ведущих нулей |
+| `void hex8/hex16/hex32(v)` | hex-печать фиксированной ширины |
+
+FILE* (буферизованный, вариант B+ — единый ленивый буфер BUFSIZ=512
+на чтение/запись с автопереключением; таблица `OPEN_MAX=8` слотов;
+exit() сбрасывает всё через atexit; **ошибки записи отложенные —
+проверять fclose**):
+| Сигнатура | Описание |
+|---|---|
+| `FILE *fopen(path, mode)` | "r/w/a" + '+', 'b/t' игнорируются |
+| `FILE *fdopen(fd, mode)` | завернуть готовый fd (закрывать fclose!) |
+| `FILE *freopen(path, mode, fp)` | переоткрыть тот же FILE* |
+| `int fclose(FILE*)` / `void fclosall(void)` | сброс+закрытие / все потоки |
+| `int fflush(FILE*)` | сброс записи / откат readahead; NULL = все |
+| `int fgetc/fputc(...)` | горячий путь на asm; getc/putc — макро-алиасы |
+| `char *fgets(buf, n, fp)` | до '\n' (сохраняется); блочный LDI-сканер |
+| `int fputs(s, fp)` | без '\n'; через fwrite |
+| `size_t fread/fwrite(p, sz, n, fp)` | блоки ≥ 512 идут мимо буфера |
+| `int ungetc(c, fp)` | 1 байт putback (и на stdin) |
+| `int fseek(fp, off, whence)` / `long ftell(fp)` | ftell без побочных эффектов |
+| `void rewind(fp)` | fseek(0) + сброс EOF/ERROR |
+| `int fgetpos/fsetpos(fp, &pos)` | fpos_t = long |
+| `int feof/ferror(fp)`, `void clearerr(fp)` | флаги потока |
+| `int fprintf/vfprintf(fp, fmt, ...)` | vsprintf в статический буфер 256 |
+| `int scanf/fscanf/sscanf(...)` | %d %u %x %o %c %s, `l`, ширина, %*, %% |
+| `int rename(old, new)` | ESTEX RENAME $10 |
+
+`stdin/stdout/stderr` — консольные псевдопотоки (fd 0/-1/-2), не
+буферизуются; freopen на них меняет только FILE*-операции (printf
+идёт в ESTEX напрямую).
+
+## — include_next + добавки
+
+Из SDCC: malloc/free/calloc/realloc (heap в W2), atoi/atol/strtol/
+strtoul, rand/srand, qsort/bsearch, abs/labs, div/ldiv, exit-типы.
+Наше: `int16_t min(a,b)`, `int16_t max(a,b)` (функции, как в Solid-C).
+
+## — include_next + добавки
+
+Из SDCC: mem*/str* полностью. Наше: `char *strlwr/strupr(char *s)` —
+in-place регистр, латиница + кириллица CP866.
+
+## — полная замена (struct tm в SDCC-ABI, __TIME_UNSIGNED=1)
+
+| Сигнатура | Описание |
+|---|---|
+| `void getdatetime(datetime_t*)` | RTC как есть (ESTEX SYSTIME $21) |
+| `int setdatetime(const datetime_t*)` | установка RTC ($22) |
+| `time_t time(time_t*)` | Unix-эпоха из RTC |
+| `mktime/gmtime/localtime/asctime/ctime` | POSIX поверх RTC (без TZ) |
+
+`datetime_t`: day/month/year(полный)/hour/minute/second/dow (1=Вс).
+
+## , — fd-уровень (манипуляторы DSS)
+
+| Сигнатура | Описание |
+|---|---|
+| `int open(path, flags)` | O_RDONLY/WRONLY/RDWR + O_CREAT/TRUNC/EXCL/APPEND (ESTEX $11/$0A/$0B) |
+| `int creat(path, mode)` | open(W|CREAT|TRUNC); mode игнорируется |
+| `int read/write(fd, buf, n)` | ESTEX $13/$14. **Квирк WRITE: DE-возврат ненадёжен, успех = CF=0&A=0** (см. memory/estex_write_de_quirk) |
+| `int close(fd)` | ESTEX $12 |
+| `long lseek(fd, off, whence)` | 32-битная позиция (MOVE_FP $15) |
+| `int unlink(path)` | удалить (DELETE $0E) |
+| `int isatty(fd)` | fd <= 0 (файловые манипуляторы DSS с 1) |
+| `int mkdir/rmdir/chdir(path)` | ESTEX $1B/$1C/$1D |
+| `char *getcwd(buf, size)` | буфер 256 байт, size игнорируется |
+| `void sleep(seconds)` | 50 halt/с по кадровому IRQ |
+
+**Лимит: 8 одновременных манипуляторов**; 9-й OPEN вешает DSS —
+libc отказывает сама (EMFILE, предохранитель _fd_guard).
+
+##
+
+`errno` (int), коды = коды DSS (EOK..EUNKERR) + POSIX-имена
+(ENOENT/EBADF/EMFILE/…) + алиасы Solid-C (EZERO/EINVFNC/ENOFILE/…).
+`const char *strerror(int)`, `void perror(const char*)`.
+
+## — DOS-слой Solid-C
+
+| Сигнатура | Описание |
+|---|---|
+| `void getdate/gettime(&d)` | struct date/time (Turbo-C; ti_hund=0) |
+| `int setdate/settime(&d)` | RMW полного datetime |
+| `uint8_t getdisk(void)` | текущий диск, 0=A (ESTEX $02) |
+| `int setdisk(uint8_t)` | смена диска; возврат = число дисков ($01) |
+| `int absread/abswrite(disk, sect, cnt, buf)` | секторы ЛОГИЧЕСКОГО диска (BIOS $55/$56); буфер в #4000–#BFFF; abswrite минует ФС! |
+
+##
+
+`int ffirst(pattern, ffblk_t*, attrib)` / `int fnext(ffblk_t*)` —
+поиск по шаблону (ESTEX $19/$1A). Квирк: "."/".." находятся только
+итерацией "*.*" (memory/estex_ffirst_dotdot). FA_*-атрибуты.
+
+##
+
+`int stat(path, struct stat*)` / `int fstat(fd, ...)` — st_mode
+(S_ISREG/S_ISDIR), st_size, st_mtime (Unix-эпоха).
+
+## — текстовый экран с атрибутами (Turbo-C стиль)
+
+Клавиатура: `kbhit getch getche getkey` (+KEY_* коды позиций),
+`char *cgets(buf)`.
+Вывод с атрибутом: `putch cputs cprintf` (~10× медленнее stdio-пути;
+'\n' НЕ транслируется — писать "\r\n").
+Атрибуты: `textcolor textbackground textattr`, `set/get_text_attr`,
+COLOR_*-enum, `COLOR(fg,bg)`, COLOR_BLINK; `set/get_putch_raw_mode`.
+Экран: `clrscr clrscr_attr gotoxy home() wherex wherey wherexy scroll
+wrchar rdchar`; режимы `gettextmode/settextmode` (0x02=40×32,
+0x03=80×32).
+Порты/IRQ: `inp outp enable() disable()`.
+Текстовая палитра: `text_pal_load/set_color/get/get_color/reset`
+(план 0..3 → страница BIOS 4..7).
+
+## — быстрый BIOS-вывод (rst 8, place-based)
+
+`bios_set_place/get_place`, `bios_write[attr][_until|_stop]`,
+`bios_fillchar/fillattr/fillcharattr`, `bios_clearwin[_ch]`,
+`bios_scrollwin`. Строка s — в #4000–#BFFF; place продвигается.
+
+## — графика (0x81: 320×256×256; 0x82: 640×256×16)
+
+Setup: `gfx_init(mode,page)→prev`, `gfx_done(prev)`.
+Страницы/банк: `gfx_set/get_visible_page`, `gfx_set/get_draw_page`
+(double buffering), `gfx_set/get_bank` (0x50..0x5F, 0x58 = FF-
+прозрачность), `gfx_wait_vsync()`.
+Примитивы (суффикс _256 / _16): `clear putpixel hline vline line
+rect fill_rect` — через акселератор (hline/vline burst до 256 байт;
+в 0x82 vline через RMW). Координаты int, клиппинг по краям.
+Текст: `gfx_putchar256/16, gfx_text256/16` (8×8, fg/bg; в 0x82 x —
+чётный), `gfx_load_default_font`, `gfx_set_font(ptr)` (interleaved
+font[row*256+char]).
+Палитра: `gfx_pal_load/set/get/get_color/reset` (страницы 0..3).
+
+## — низкий уровень (BIOS $A4/$A6)
+
+`pal_load pal_get pal_set_color pal_get_color` (страница 0..7,
+записи B,G,R,0), `pal_reset(type)` / `pal_reset_at(type,page,graph)`;
+PAL_GRAPH/PAL_SINCLAIR/PAL_CGA.
+
+## — драйвер RST 30h
+
+`mouse_init show hide refresh read(mouse_state_t*) goto bounds_x/y
+text_cursor load_cursor/get_cursor(mouse_cursor_t*) set_sensitivity
+get_sensitivity_x/y video_mode_changed`. Sensitivity = делитель
+(меньше = быстрее). Solid-C алиасы ms_* включены.
+
+## — платформа
+
+Константы портов (PORT_PAGE_W0..W3, PORT_RGADR, PORT_RGMOD), номера
+всех ESTEX-функций (ESTEX_*), BIOS EMM ($C0..$C7); `__sfr`-доступ и
+inline `sprinter_page_w0..w3(page)`; ENV: `getenv putenv sysenv`.
+
+## — EMM-страницы и банковый I/O
+
+`mem_alloc_pages(n)→blk_id, mem_free_block, mem_get_page(blk,idx),
+mem_info(&total,&free)` (реализации _bios/_estex; макро-выбор
+MEM_MANAGE_MODE_*). HOME-резидентный доступ к чужим страницам:
+`bank_load_byte/store_byte/read/write` (своп W3 внутри; *_w1 —
+вариант через окно W1).
+
+##
+
+`atexit` (LIFO, 8 слотов), `exit` (хендлеры+сброс FILE), `_exit`.
+
+## /
+
+Типы (BYTE/BOOL/WORD/uint/FD/f_point), TRUE/FALSE/OK/ERROR,
+`setmem movmem` (порядок аргументов!), `strerr seek tell ltell
+remove _ffirst _setargv abort()`, isascii. `` —
+зонтичный: один include для портирования Solid-C программ.
diff --git a/docs/libc-roadmap.md b/docs/libc-roadmap.md
index 5e23b2a..ae11c98 100644
--- a/docs/libc-roadmap.md
+++ b/docs/libc-roadmap.md
@@ -91,16 +91,14 @@ fgetpos/fsetpos (хвост П2). bdos/brk/ioctl — отказ решение
подтвердить на эмуляторе
- [ ] Потом на железе (mdview2 и так ждёт проверки на железе — совместить)
-## П7. Документация
+## П7. Документация — ЗАКРЫТ 2026-07-06
-- [ ] docs/libc-reference.md — сводный справочник API (по заголовкам:
- сигнатура + 1 строка описания + особенности ABI); сейчас знание
- размазано по memory/ и комментариям
-- [ ] TODO.md: этапы 5/6/8 закрыты — перенести в history, добавить «этап 9 —
- libc-оптимизация» (этот план)
-- [ ] В CLAUDE.md/README зафиксировать правила libc: 1 ф-я = 1 модуль,
- internal `_`-модули, русские комментарии, без `= 0`, asm-правила
- (docs/libc-split-asm-cases.md)
+- [x] **docs/libc-reference.md** — справочник API по всем заголовкам
+- [x] docs/TODO.md переписан: открытое наверху, закрытые этапы (5-10)
+ в «Истории»; протухшие пункты (FILE rewrite «для v2») сняты
+- [x] **CLAUDE.md** создан: сборка/проверка, правила libc (1 ф-я =
+ 1 модуль, `_`-модули, русские комментарии, без `= 0`,
+ asm-правила), ABI-шпаргалка, квирки, структура
## П8. Смежное (не libc, из TODO.md — чтобы не потерялось)